GGTrajImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Bier on November 13, 1986 5:03:39 pm PST
Contents: Procedures to implement the Traj Slice Class. Trajectories consist of Segments. One motivation for creating this module now is the GGOutlineImpl is too big to compile.
DIRECTORY
Atom, GGBasicTypes, GGBoundBox, GGError, GGInterfaceTypes, GGModelTypes, GGOutline, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, GGSequence, GGShapes, GGTransform, GGTraj, GGUtility, GGVector, Imager, ImagerColor, ImagerTransformation, IO, RealFns, Rope, Rosary;
GGTrajImpl:
CEDAR
PROGRAM
IMPORTS Atom, GGBoundBox, GGError, GGOutline, GGParseIn, GGParseOut, GGSegment, GGSequence, GGShapes, GGTransform, GGUtility, GGVector, Imager, ImagerColor, ImagerTransformation, IO, RealFns, Rope, Rosary
EXPORTS GGTraj =
BEGIN
BitVector: TYPE = GGBasicTypes.BitVector;
BoundBox: TYPE = GGBasicTypes.BoundBox;
CameraData: TYPE = GGModelTypes.CameraData;
Color: TYPE = ImagerColor.Color;
FeatureData: TYPE = GGInterfaceTypes.FeatureData;
FenceHoleOpen: TYPE = GGModelTypes.FenceHoleOpen;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
Joint: TYPE = REF JointObj;
JointObj: TYPE = GGSegmentTypes.JointObj;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
Outline: TYPE = GGModelTypes.Outline;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
Scene: TYPE = GGModelTypes.Scene;
SegAndIndex: TYPE = GGSequence.SegAndIndex;
SegmentClass: TYPE = GGSegmentTypes.SegmentClass;
SelectionClass: TYPE = GGInterfaceTypes.SelectionClass;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceGenerator: TYPE = GGModelTypes.SequenceGenerator;
TriggerBag: TYPE = GGInterfaceTypes.TriggerBag;
Traj: TYPE = REF TrajObj;
TrajObj: TYPE = GGModelTypes.TrajObj;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
Vector: TYPE = GGBasicTypes.Vector;
Problem: ERROR [msg: Rope.ROPE] = GGError.Problem;
Trajectory-Only Routines
CreateTraj:
PUBLIC
PROC [point: Point]
RETURNS [traj: Traj] = {
firstJoint: Joint ← NEW[JointObj ← [point: point]];
bBox: BoundBox ← GGBoundBox.CreateBoundBox[point.x, point.y, point.x, point.y];
traj ← NEW[TrajObj ← [open, 0, NIL, Rosary.FromItem[firstJoint], NIL, NIL, bBox, FALSE]];
};
AddSegment:
PUBLIC
PROC [traj: Traj, trajEnd: TrajEnd, seg: Segment, segEnd: TrajEnd]
RETURNS [success:
BOOL] = {
diff: Vector;
loJoint, hiJoint: Joint;
outline: Outline;
Moves seg so that the specified end of seg coincides with the specified end of traj. In other words, seg is translated.
IF traj.role = fence
OR traj.role = hole
THEN {
success ← FALSE;
RETURN;
};
success ← TRUE;
IF traj.segCount = 0
THEN {
-- this is the first segment
traj.segCount ← 1;
traj.segments ← Rosary.FromItem[seg];
loJoint ← NEW[JointObj ← [point: seg.lo]];
hiJoint ← NEW[JointObj ← [point: seg.hi]];
traj.joints ← Rosary.FromList[LIST[loJoint, hiJoint]];
traj.boundBox ← GGBoundBox.CopyBoundBox[seg.class.boundBox[seg]];
}
ELSE {
IF trajEnd = segEnd THEN GGSegment.ReverseSegment[seg];
IF trajEnd = lo
THEN {
diff ← GGVector.Sub[FetchJointPos[traj, 0], seg.hi];
GGSegment.TranslateSegment[seg, diff];
loJoint ← NEW[JointObj ← [point: seg.lo]];
traj.joints ← Rosary.Concat[Rosary.FromItem[loJoint], traj.joints];
traj.segments ← Rosary.Concat[Rosary.FromItem[seg], traj.segments];
}
ELSE {
diff ← GGVector.Sub[FetchJointPos[traj, traj.segCount], seg.lo];
GGSegment.TranslateSegment[seg, diff];
hiJoint ← NEW[JointObj ← [point: seg.hi]];
traj.joints ← Rosary.Concat[traj.joints, Rosary.FromItem[hiJoint]];
traj.segments ← Rosary.Concat[traj.segments, Rosary.FromItem[seg]];
};
traj.segCount ← traj.segCount + 1;
GGBoundBox.EnlargeByBox[traj.boundBox, seg.class.boundBox[seg]];
outline ← GGOutline.OutlineOfTraj[traj];
IF outline#NIL THEN GGOutline.UpdateOutlineBoundBox[outline];
}
};
CloseWithSegment:
PUBLIC
PROC [traj: Traj, seg: Segment, segEnd: TrajEnd] = {
Like AddSegment, except that here we rotate, scale, and translate seg as needed so that both of its endpoints coincide with the endpoints of traj (which must not already be closed). The endpoint correspondence is made so that the hi end of traj touches the named (segEnd) end of seg.
diff: Vector;
outline: Outline;
IF traj.segCount = 0
THEN
ERROR Problem[msg: "single closed segment not implemented"];
For now, just translate into place and assume size is ok.
IF segEnd = hi THEN GGSegment.ReverseSegment[seg];
diff ← GGVector.Sub[LastJointPos[traj], seg.lo];
GGSegment.TranslateSegment[seg, diff];
traj.segments ← Rosary.Concat[traj.segments, Rosary.FromItem[seg]];
traj.segCount ← traj.segCount + 1;
traj.role ← fence;
GGBoundBox.EnlargeByBox[traj.boundBox, seg.class.boundBox[seg]];
outline ← GGOutline.OutlineOfTraj[traj];
IF outline#NIL THEN GGOutline.UpdateOutlineBoundBox[outline];
};
CloseByDistorting:
PUBLIC
PROC [traj: Traj, distortEnd: TrajEnd] = {
Move either the first joint or the last joint (depending on the distortEnd argument) to coincide with the other end. This will reduce the number of joints in the trajectory by one (making all sequences which refer to this trajectory obsolete. Usually, no distortion will actually occur. The client is expected to call this routine when the joints already coincide.
seg: Segment;
loJoint, hiJoint: Joint;
outline: Outline;
loJoint ← FetchJoint[traj, 0];
hiJoint ← FetchJoint[traj, HiJoint[traj]];
SELECT distortEnd FROM
lo => {
seg ← FetchSegment[traj, 0];
loJoint.point ← hiJoint.point;
seg.lo ← hiJoint.point;
seg.class.endPointMoved[seg, TRUE, hiJoint.point];
};
hi => {
seg ← FetchSegment[traj, HiSegment[traj]];
hiJoint.point ← loJoint.point;
seg.hi ← loJoint.point;
seg.class.endPointMoved[seg, FALSE, loJoint.point];
};
ENDCASE => ERROR;
traj.joints ← Rosary.Substr[traj.joints, 0, HiJoint[traj]];
traj.role ← fence;
GGBoundBox.EnlargeByBox[traj.boundBox, seg.class.boundBox[seg]];
outline ← GGOutline.OutlineOfTraj[traj];
IF outline#NIL THEN GGOutline.UpdateOutlineBoundBox[outline];
};
Building Trajectories from Parts
CopyTrajFromRun:
PUBLIC
PROC [seq: Sequence]
RETURNS [copy: Traj] = {
seq should be a run (a single consecutive set of segments and joints).
originalSegments: Rosary.ROSARY ← seq.traj.segments;
desiredSegments: Rosary.Segment;
loSegments, hiSegments, extractedSegments: Rosary.ROSARY;
originalJoints: Rosary.ROSARY ← seq.traj.joints;
desiredJoints: Rosary.Segment;
loJoints, hiJoints, extractedJoints: Rosary.ROSARY;
segGen: SegmentGenerator;
next: GGSequence.SegAndIndex;
s1, len1, s2, len2: INT;
jointCount: NAT;
newRole: GGModelTypes.FenceHoleOpen;
CopyEachSegment:
PROC[q:
PROC[item: Rosary.Item, repeat:
INT ← 1]] = {
CopySegmentAndBuild:
PROC [item: Rosary.Item]
RETURNS [quit:
BOOLEAN ←
FALSE] = {
copy, oldSeg: Segment;
oldSeg ← NARROW[item];
copy ← GGSegment.CopySegment[oldSeg];
q[copy, 1];
};
[] ← Rosary.Map[desiredSegments, CopySegmentAndBuild];
};
CopyEachJoint:
PROC[q:
PROC[item: Rosary.Item, repeat:
INT ← 1]] = {
CopyJointAndBuild:
PROC [item: Rosary.Item]
RETURNS [quit:
BOOLEAN ←
FALSE] = {
copy, oldJoint: Joint;
oldJoint ← NARROW[item];
copy ← CopyJoint[oldJoint];
q[copy, 1];
};
[] ← Rosary.Map[desiredJoints, CopyJointAndBuild];
};
segGen ← GGSequence.OrderedSegmentsInSequence[seq];
next ← GGSequence.NextSegmentAndIndex[segGen];
[s1, len1, s2, len2] ← GGUtility.BreakIntervalMODLen[next.index, seq.segCount, seq.traj.segCount];
IF s2 # -1
THEN {
desiredSegments ← [originalSegments, s1, len1];
loSegments ← Rosary.FromProcProc[CopyEachSegment];
desiredSegments ← [originalSegments, s2, len2];
hiSegments ← Rosary.FromProcProc[CopyEachSegment];
extractedSegments ← Rosary.Concat[hiSegments, loSegments];
}
ELSE {
desiredSegments ← [originalSegments, next.index, seq.segCount];
extractedSegments ← Rosary.FromProcProc[CopyEachSegment];
};
jointCount ← seq.segCount + 1; -- ignore the joint information in the sequence. We just want all of the segments to have a joint on each side. This distinction is particularly important when a single joint of a closed trajectory is being deleted.
[s1, len1, s2, len2] ← GGUtility.BreakIntervalMODLen[next.index, jointCount, HiJoint[seq.traj]+1];
IF s2 # -1
THEN {
desiredJoints ← [originalJoints, s1, len1];
loJoints ← Rosary.FromProcProc[CopyEachJoint];
desiredJoints ← [originalJoints, s2, len2];
hiJoints ← Rosary.FromProcProc[CopyEachJoint];
extractedJoints ← Rosary.Concat[hiJoints, loJoints];
}
ELSE {
desiredJoints ← [originalJoints, next.index, jointCount];
extractedJoints ← Rosary.FromProcProc[CopyEachJoint];
};
newRole ← IF GGSequence.IsComplete[seq] AND seq.traj.role # open THEN fence ELSE open;
copy ← NEW[TrajObj ← [newRole, seq.segCount, extractedSegments, extractedJoints, NIL, NIL, NIL, seq.traj.visibleJoints, seq.traj.strokeJoint ]];
copy.boundBox ← GetBoundBox[copy];
};
CopyTrajFromRange:
PUBLIC
PROC [original: Traj, start:
INT, len:
INT]
RETURNS [piece: Traj] = {
First we pick out the desired subsection. Then, Rosary will call CopyEachSegment, which in turn maps CopySegmentAndBuild to each of the desired elements. The copies are made and are passed to procedure q of Rosary, which assembles them into a new Rosary.
originalSegments: Rosary.ROSARY ← original.segments;
desiredSegments: Rosary.Segment ← [originalSegments, start, len];
extractedSegments: Rosary.ROSARY;
originalJoints: Rosary.ROSARY ← original.joints;
desiredJoints: Rosary.Segment ← [originalJoints, start, len];
extractedJoints: Rosary.ROSARY;
CopyEachSegment:
PROC[q:
PROC[item: Rosary.Item, repeat:
INT ← 1]] = {
CopySegmentAndBuild:
PROC [item: Rosary.Item]
RETURNS [quit:
BOOLEAN ←
FALSE] = {
copy, oldSeg: Segment;
oldSeg ← NARROW[item];
copy ← GGSegment.CopySegment[oldSeg];
q[copy, 1];
};
[] ← Rosary.Map[desiredSegments, CopySegmentAndBuild];
};
CopyEachJoint:
PROC[q:
PROC[item: Rosary.Item, repeat:
INT ← 1]] = {
CopyJointAndBuild:
PROC [item: Rosary.Item]
RETURNS [quit:
BOOLEAN ←
FALSE] = {
copy, oldJoint: Joint;
oldJoint ← NARROW[item];
copy ← CopyJoint[oldJoint];
q[copy, 1];
};
[] ← Rosary.Map[desiredJoints, CopyJointAndBuild];
};
extractedSegments ← Rosary.FromProcProc[CopyEachSegment];
extractedJoints ← Rosary.FromProcProc[CopyEachJoint];
piece ←
NEW[TrajObj ← [fence, len, extractedSegments, extractedJoints,
NIL,
NIL,
NIL, original.visibleJoints, original.strokeJoint ]];
I must create a new outline and copy the extrapoints as well.
piece.boundBox ← GetBoundBox[piece];
};
Concat:
PUBLIC
PROC [fixed: Traj, fixedEnd: TrajEnd, moving: Traj, movingEnd: TrajEnd]
RETURNS [longer: Traj] = {
Moves moving so that the specified end moving coincides with the specified end fixed. In other words, moving is translated.
diff: Vector;
fixed ← CopyTraj[fixed];
moving ← CopyTraj[moving];
IF fixedEnd = movingEnd THEN ReverseTraj[moving];
IF fixedEnd = hi
THEN {
diff ← GGVector.Sub[FetchJointPos[fixed, HiJoint[fixed]], FetchJointPos[moving, 0]];
TranslateTraj[moving, diff];
longer ←
NEW[TrajObj ← [
role: open,
segCount: fixed.segCount + moving.segCount,
segments: Rosary.Concat[fixed.segments, moving.segments],
joints: Rosary.Concat[fixed.joints, Rosary.Substr[moving.joints, 1]],
extraPoints: NIL,
parent: NIL,
boundBox: NIL,
visibleJoints: fixed.visibleJoints,
strokeJoint: fixed.strokeJoint,
selectedInPart: [FALSE, FALSE, FALSE]
]];
longer.boundBox ← GetBoundBox[longer];
}
ELSE {
-- fixedEnd = lo
diff ← GGVector.Sub[FetchJointPos[fixed, 0], FetchJointPos[moving, HiJoint[moving]]];
TranslateTraj[moving, diff];
longer ←
NEW[TrajObj ← [
role: open,
segCount: fixed.segCount + moving.segCount,
segments: Rosary.Concat[moving.segments, fixed.segments],
joints: Rosary.Concat[moving.joints, Rosary.Substr[fixed.joints, 1]],
extraPoints: NIL,
parent: NIL,
boundBox: NIL,
visibleJoints: fixed.visibleJoints,
strokeJoint: fixed.strokeJoint,
selectedInPart: [FALSE, FALSE, FALSE]
]];
longer.boundBox ← GetBoundBox[longer];
};
};
SpliceIn:
PUBLIC PROC [run: Sequence, traj: Traj]
RETURNS [newTraj: Traj] = {
oldOutline: Outline ← GGOutline.OutlineOfTraj[run.traj];
SELECT run.traj.role FROM
hole => {
newTraj ← SpliceInClosed[run, traj];
newTraj.role ← hole;
[] ← GGOutline.ReplaceHole[oldOutline, run.traj, newTraj];
};
open => {
newTraj ← SpliceInOpen[run, traj];
newTraj.role ← open;
[] ← GGOutline.CreateOutline[newTraj, oldOutline.lineEnds, oldOutline.fillColor];
};
fence => {
newTraj ← SpliceInClosed[run, traj];
newTraj.role ← fence;
[] ← GGOutline.ReplaceFence[oldOutline, newTraj];
};
ENDCASE =>
ERROR;
A hack to get arrows to work reasonably for now.
newTraj.loArrow ← run.traj.loArrow;
newTraj.hiArrow ← run.traj.hiArrow;
};
IncludesLoEnd:
PROC [run: Sequence]
RETURNS [
BOOL] = {
RETURN[run.segments[0]];
};
ResetEndSelectionBits:
PROC [traj: Traj] = {
joint: Joint;
joint ← FetchJoint[traj, 0];
joint.TselectedInFull.active ← FALSE;
joint ← FetchJoint[traj, HiJoint[traj]];
joint.TselectedInFull.active ← FALSE;
};
SpliceInOpen:
PROC [run: Sequence, traj: Traj]
RETURNS [newTraj: Traj] = {
runGen: SequenceGenerator;
wholeSeq, remainder, run1, run2, run3: Sequence;
traj1, traj2: Traj;
firstJointNum, lastJointNum: NAT;
joint: Joint;
wholeSeq ← GGSequence.CreateComplete[run.traj];
remainder ← GGSequence.Difference[wholeSeq, run];
GGSequence.FillInJoints[remainder];
[runGen,----] ← GGSequence.RunsInSequence[remainder];
run1 ← GGSequence.NextSequence[runGen];
run2 ← GGSequence.NextSequence[runGen];
run3 ← GGSequence.NextSequence[runGen];
IF run3 # NIL THEN ERROR;
SELECT TRUE FROM
run1 =
NIL => {
newTraj ← traj;
};
run2 =
NIL => {
traj1 ← CopyTrajFromRun[run1];
ResetEndSelectionBits[traj1];
IF IncludesLoEnd[run]
THEN {
newTraj ← Concat[traj1, lo, traj, hi];
}
ELSE {
newTraj ← Concat[traj1, hi, traj, lo];
};
};
ENDCASE => {
traj1 ← CopyTrajFromRun[run1];
ResetEndSelectionBits[traj1];
traj2 ← CopyTrajFromRun[run2];
ResetEndSelectionBits[traj2];
newTraj ← Concat[traj1, hi, traj, lo];
newTraj ← Concat[newTraj, hi, traj2, lo];
};
To make sure the algorithm terminates, make sure the end joints of the run are not active selected. They can be normal or hot selected.
firstJointNum ← GGSequence.FirstJointNum[run];
lastJointNum ← firstJointNum + HiJoint[traj];
joint ← FetchJoint[newTraj, firstJointNum];
joint.TselectedInFull.active ← FALSE;
joint ← FetchJoint[newTraj, lastJointNum];
joint.TselectedInFull.active ← FALSE;
};
SpliceInClosed:
PROC [run: Sequence, traj: Traj]
RETURNS [newTraj: Traj] = {
wholeSeq, remainder, run1, run2: Sequence;
runGen: SequenceGenerator;
traj1: Traj;
firstJointNum, lastJointNum: INT;
joint: Joint;
IF GGSequence.IsComplete[run] THEN RETURN[traj]; -- no need to splice.
wholeSeq ← GGSequence.CreateComplete[run.traj];
remainder ← GGSequence.Difference[wholeSeq, run];
GGSequence.FillInJoints[remainder];
[runGen, ----] ← GGSequence.RunsInSequence[remainder];
run1 ← GGSequence.NextSequence[runGen];
run2 ← GGSequence.NextSequence[runGen];
IF run1 = NIL OR run2 # NIL THEN ERROR;
traj1 ← CopyTrajFromRun[run1];
ResetEndSelectionBits[traj1];
newTraj ← Concat[traj1, hi, traj, lo];
CloseByDistorting[newTraj, lo];
To make sure the algorithm terminates, make sure the end joints of the run are not active selected. They can be normal or hot selected.
firstJointNum ← GGSequence.FirstJointNum[run];
IF firstJointNum >= 0
THEN {
wraps: BOOL ← firstJointNum>GGSequence.LastJointNum[run, firstJointNum];
firstJointNum ← IF wraps THEN 0 ELSE GGSequence.FirstJointNum[run];
lastJointNum ← (firstJointNum + HiJoint[traj]) MOD newTraj.segCount;
joint ← FetchJoint[newTraj, firstJointNum];
joint.TselectedInFull.active ← FALSE;
joint ← FetchJoint[newTraj, lastJointNum];
joint.TselectedInFull.active ← FALSE;
};
};
Orientation
ReverseTraj:
PUBLIC
PROC [traj: Traj] = {
newSegments: Rosary.ROSARY ← NIL;
newJoints: Rosary.ROSARY ← NIL;
seg: Segment;
joint: Joint;
IF traj.role # open THEN SIGNAL Problem["not yet implemented"];
FOR i:
NAT
IN [0..traj.segCount)
DO
seg ← FetchSegment[traj, i];
GGSegment.ReverseSegment[seg];
newSegments ← Rosary.Concat[Rosary.FromItem[seg], newSegments];
ENDLOOP;
FOR i:
NAT
IN [0..traj.segCount]
DO
joint ← IF traj.role=open THEN FetchJoint[traj, i] ELSE FetchJoint[traj, (i MOD traj.segCount)];
newJoints ← Rosary.Concat[Rosary.FromItem[joint], newJoints];
ENDLOOP;
traj.segments ← newSegments;
traj.joints ← newJoints;
};
IsClockwiseTraj:
PUBLIC
PROC [traj: Traj]
RETURNS [
BOOL] = {
RETURN [SignedArea[traj]>0];
};
IsClockwiseTrajTransformSeq:
PUBLIC
PROC [seq: Sequence, transform: ImagerTransformation.Transformation]
RETURNS [
BOOL] = {
RETURN [SignedAreaTransformSeq[seq, transform]>0];
};
Fundamental Routines
The lifetime of a trajectory goes something like this: It begins as a single point. The sole purpose of this point is to be an "endpoint" to which the first segment can be attached. Subsequent segments are also added to endpoints, so this unifies the creation process. In Gargoyle, trajectories never appear at the top level of a scene; they are always part of an outline. It is suggested, then that each CreateTraj call be followed immediately by a CreateOutline call.
GetBoundBox:
PUBLIC
PROC [traj: Traj]
RETURNS [bBox: BoundBox] = {
Computes it from segment boxes.
segGen: SegmentGenerator;
segGen ← GGSequence.SegmentsInTraj[traj];
bBox ← GGBoundBox.NullBoundBox[];
FOR seg: Segment ← GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen]
UNTIL seg =
NIL
DO
GGBoundBox.EnlargeByBox[bBox: bBox, by: seg.class.boundBox[seg]];
ENDLOOP;
};
CopyTraj:
PUBLIC
PROC [original: Traj]
RETURNS [copy: Traj] = {
Rosary will call CopyEachSegment, which in turn maps CopySegmentAndBuild to each of the desired elements. The copies are made and are passed to procedure q of Rosary, which assembles them into a new Rosary.
originalSegments: Rosary.ROSARY ← original.segments;
desiredSegments: Rosary.Segment ← [originalSegments, 0, original.segCount];
extractedSegments: Rosary.ROSARY;
originalJoints: Rosary.ROSARY ← original.joints;
desiredJoints: Rosary.Segment ← [originalJoints, 0, HiJoint[original] + 1];
extractedJoints: Rosary.ROSARY;
CopyEachSegment:
PROC[q:
PROC[item: Rosary.Item, repeat:
INT ← 1]] = {
CopySegmentAndBuild:
PROC [item: Rosary.Item]
RETURNS [quit:
BOOLEAN ←
FALSE] = {
copy, oldSeg: Segment;
oldSeg ← NARROW[item];
copy ← GGSegment.CopySegment[oldSeg];
q[copy, 1];
};
[] ← Rosary.Map[desiredSegments, CopySegmentAndBuild];
};
CopyEachJoint:
PROC[q:
PROC[item: Rosary.Item, repeat:
INT ← 1]] = {
CopyJointAndBuild:
PROC [item: Rosary.Item]
RETURNS [quit:
BOOLEAN ←
FALSE] = {
copy, oldJoint: Joint;
oldJoint ← NARROW[item];
copy ← CopyJoint[oldJoint];
q[copy, 1];
};
[] ← Rosary.Map[desiredJoints, CopyJointAndBuild];
};
extractedSegments ← Rosary.FromProcProc[CopyEachSegment];
extractedJoints ← Rosary.FromProcProc[CopyEachJoint];
copy ←
NEW[TrajObj ← [original.role, original.segCount, extractedSegments, extractedJoints,
NIL,
NIL,
NIL, original.visibleJoints, original.strokeJoint, original.loArrow, original.hiArrow]];
I must copy the extrapoints as well.
copy.boundBox ← GetBoundBox[original];
};
Drawing
MaskStroke:
PROC [dc: Imager.Context, seg: Segment] = {
MaskPath: Imager.PathProc = {
moveTo[seg.lo];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
};
Imager.MaskStroke[dc, MaskPath, FALSE];
MaskStrokeTransform:
PROC [dc: Imager.Context, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL, controlPoints: BitVector] = {
MaskPath: Imager.PathProc = {
[moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc]
loPoint: Point;
loPoint ← IF entire OR lo THEN ImagerTransformation.Transform[transform, seg.lo] ELSE seg.lo;
moveTo[loPoint];
seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo];
};
Imager.MaskStroke[dc, MaskPath, FALSE];
};
AllStrokeWidthsAndColorsEqual:
PROC [traj: Traj]
RETURNS [
BOOL] = {
firstSeg: Segment ← FetchSegment[traj, 0];
seg: Segment;
width: REAL ← firstSeg.strokeWidth;
color: Color ← firstSeg.color;
FOR i:
INT
IN [1..HiSegment[traj]]
DO
seg ← FetchSegment[traj, i];
IF seg.strokeWidth # width THEN RETURN[FALSE];
IF seg.color # color THEN RETURN[FALSE];
REPEAT
FINISHED => RETURN[TRUE];
ENDLOOP;
};
AllStrokeWidthsEqual:
PROC [traj: Traj]
RETURNS [width:
REAL] = {
firstSeg: Segment ← FetchSegment[traj, 0];
seg: Segment;
width ← firstSeg.strokeWidth;
FOR i:
INT
IN [1..HiSegment[traj]]
DO
seg ← FetchSegment[traj, i];
IF seg.strokeWidth # width THEN RETURN[-1.0];
ENDLOOP;
};
AllStrokeColorsEqual:
PROC [traj: Traj]
RETURNS [allEqual:
BOOL, color: Color] = {
firstSeg: Segment ← FetchSegment[traj, 0];
seg: Segment;
color ← firstSeg.color;
FOR i:
INT
IN [1..HiSegment[traj]]
DO
seg ← FetchSegment[traj, i];
IF seg.color # color THEN RETURN[FALSE, NIL];
ENDLOOP;
allEqual ← TRUE;
SegmentSelected:
PROC [segNum:
NAT, selectedSeq: Sequence]
RETURNS [
BOOL] = {
RETURN[selectedSeq.segments[segNum]];
};
DrawSequenceFeedback:
PUBLIC PROC [dc: Imager.Context, seq: Sequence, normalSeq: Sequence, camera: CameraData, quick:
BOOL ←
FALSE, selectClass: SelectionClass ← normal] = {
segGen: SegmentGenerator;
color: Imager.Color;
IF camera.quality = quality THEN RETURN;
segGen ← GGSequence.SegmentsInSequence[seq];
Imager.SetColor[dc, Imager.black];
IF
NOT quick
THEN {
-- draw full selection feedback
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeEnd[dc, round];
FOR next: SegAndIndex ← GGSequence.NextSegmentAndIndex[segGen], GGSequence.NextSegmentAndIndex[segGen]
UNTIL next.seg =
NIL
DO
IF SegmentSelected[next.index, normalSeq]
THEN {
Imager.SetStrokeWidth[dc, 2.0*(IF next.seg.strokeWidth#0 THEN next.seg.strokeWidth ELSE 1.0)];
color ← IF (next.seg.color=NIL OR ImagerColor.GrayFromColor[NARROW[next.seg.color]] < 0.2) THEN Imager.black ELSE next.seg.color;
Imager.SetColor[dc, color]; -- want to always see feedback, even if stroke is white or NIL colored
MaskStroke[dc, next.seg];
};
ENDLOOP;
};
};
DrawThemJointsPlain: PROC [dc: Imager.Context, seg: Segment] = {
GGShapes.DrawJoint[dc, seg.lo];
GGShapes.DrawJoint[dc, seg.hi];
};
DrawThemJoints:
PROC [dc: Imager.Context, traj: Traj, segNum:
NAT, normalSeq: Sequence, hotSeq: Sequence] = {
drewLo, drewHi: BOOL ← FALSE;
seg: Segment ← FetchSegment[traj, segNum];
nextJoint: NAT ← FollowingJoint[traj, segNum];
IF hotSeq #
NIL
THEN {
IF hotSeq.joints[segNum]
THEN {
GGShapes.DrawSelectedJoint[dc, seg.lo, hot];
drewLo ← TRUE;
};
IF hotSeq.joints[nextJoint]
THEN {
GGShapes.DrawSelectedJoint[dc, seg.hi, hot];
drewHi ← TRUE;
};
};
IF normalSeq #
NIL
THEN {
IF normalSeq.joints[segNum]
THEN {
GGShapes.DrawSelectedJoint[dc, seg.lo, normal];
drewLo ← TRUE;
};
IF normalSeq.joints[nextJoint]
THEN {
GGShapes.DrawSelectedJoint[dc, seg.hi, normal];
drewHi ← TRUE;
};
};
IF seg.lo#seg.hi
THEN {
-- joints are not coincident
IF NOT drewLo THEN GGShapes.DrawJoint[dc, seg.lo];
IF NOT drewHi THEN GGShapes.DrawJoint[dc, seg.hi];
}
ELSE IF NOT (drewLo OR drewHi) THEN GGShapes.DrawJoint[dc, seg.hi];
DrawSelectionFeedback:
PUBLIC
PROC [traj: Traj, selectedParts, hotParts: Sequence, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick:
BOOL] = {
DoDrawFeedback:
PROC = {
segGen: GGModelTypes.SegmentGenerator;
jointGen: GGModelTypes.JointGenerator;
seg: Segment;
someNormal, someHot, thisCPisHot, thisCPisSelected: BOOL;
point: Point;
Draw the joints.
jointGen ← GGSequence.JointsInTraj[traj];
FOR i:
INT ← GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen]
UNTIL i = -1
DO
thisCPisHot ← hotParts#NIL AND hotParts.joints[i];
thisCPisSelected ← selectedParts#NIL AND selectedParts.joints[i];
point ← FetchJointPos[traj, i];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, point, hot];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, point, normal];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawJoint[dc, point];
ENDLOOP;
Draw the control points.
someNormal ← GGSequence.ContainsSegmentParts[selectedParts, i];
someHot ← GGSequence.ContainsSegmentParts[hotParts, i];
someNormal ← selectedParts # NIL;
someHot ← hotParts # NIL;
segGen ← GGSequence.SegmentsInTraj[traj];
FOR next: GGSequence.SegAndIndex ← GGSequence.NextSegmentAndIndex[segGen], GGSequence.NextSegmentAndIndex[segGen]
UNTIL next.seg =
NIL
DO
i: NAT ← next.index;
seg ← next.seg;
IF someNormal
OR someHot
THEN {
FOR j:
INT
IN [0..seg.class.controlPointCount[seg])
DO
thisCPisHot ← hotParts#NIL AND hotParts.controlPoints[i][j];
thisCPisSelected ← selectedParts#NIL AND selectedParts.controlPoints[i][j];
point ← seg.class.controlPointGet[seg, j];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, point, hot];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, point, normal];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, point];
ENDLOOP;
};
ENDLOOP;
};
IF caretIsMoving OR dragInProgress THEN RETURN;
IF selectedParts = NIL AND hotParts = NIL THEN RETURN;
IF camera.quality # quality THEN Imager.DoSaveAll[dc, DoDrawFeedback];
};
DrawTraj:
PUBLIC
PROC [dc: Imager.Context, traj: Traj] = {
Let Q be a logical variable corresponding to <gargoyleData.camera.quality = quality>.
Let S correspond to <segment J is selected>.
Let O correspond to <segment J is on the overlay plane>.
Then segment J is drawn thick when: qSo.
Segment J is drawn thin otherwise (i.e. when Q + s + O).
DrawTrajAux:
PROC [dc: Imager.Context, traj: Traj] = {
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeEnd[dc, round];
FOR i:
INT
IN [0..HiSegment[traj]]
DO
seg ← FetchSegment[traj, i];
IF seg.strokeWidth#0
AND seg.color#
NIL
THEN {
Imager.SetStrokeWidth[dc, seg.strokeWidth];
Imager.SetColor[dc, seg.color];
MaskStroke[dc, seg];
};
ENDLOOP;
};
seg: Segment;
IF AllStrokeWidthsAndColorsEqual[traj] THEN DrawSingleStrokeTraj[dc, traj]
ELSE DrawTrajAux[dc, traj];
};
DrawTrajTransformSeq:
PUBLIC PROC [dc: Imager.Context, selSeq: Sequence, transform: ImagerTransformation.Transformation] = {
cpCount: NAT;
seg: Segment;
IF AllStrokeWidthsAndColorsEqual[selSeq.traj] THEN DrawSingleStrokeTrajTransformSeq[dc, selSeq, transform]
ELSE {
point: Point;
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeEnd[dc, round];
FOR i:
INT
IN [0..HiSegment[selSeq.traj]]
DO
seg ← FetchSegment[selSeq.traj, i];
Imager.SetStrokeWidth[dc, (IF seg.strokeWidth#0 THEN seg.strokeWidth ELSE 1)];
Imager.SetColor[dc, IF seg.color#NIL THEN seg.color ELSE Imager.black];
MaskStrokeTransform[dc, seg, transform, selSeq.segments[i], selSeq.joints[i], selSeq.joints[IF selSeq.traj.role=open THEN (i+1) ELSE (i+1) MOD selSeq.traj.segCount], selSeq.controlPoints[i]];
cpCount ← seg.class.controlPointCount[seg];
FOR j:
NAT
IN [0..cpCount)
DO
IF selSeq.controlPoints[i][j]
THEN {
point ← ImagerTransformation.Transform[transform, seg.class.controlPointGet[seg, j]];
GGShapes.DrawCP[dc, point];
}
ELSE {
point ← seg.class.controlPointGet[seg, j];
GGShapes.DrawCP[dc, point];
};
ENDLOOP;
ENDLOOP;
};
};
DrawSingleStrokeTraj:
PROC [dc: Imager.Context, traj: Traj] = {
BuildPath: Imager.PathProc = {
seg: Segment;
firstPoint: Point ← FetchJointPos[traj, 0];
moveTo[firstPoint];
FOR i:
INT
IN [0..HiSegment[traj]]
DO
seg ← FetchSegment[traj, i];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
firstSeg: Segment ← FetchSegment[traj, 0];
strokeWidth: REAL ← firstSeg.strokeWidth;
IF strokeWidth = 0.0 OR firstSeg.color=NIL THEN RETURN;
Imager.SetStrokeWidth[dc, strokeWidth];
Imager.SetColor[dc, firstSeg.color];
Imager.SetStrokeJoint[dc, traj.strokeJoint];
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeEnd[dc, round];
Imager.MaskStroke[dc, BuildPath, traj.role = fence OR traj.role = hole];
}; -- end DrawSingleStrokeTraj
DrawSingleStrokeTrajTransformSeq:
PROC [dc: Imager.Context, selSeq: Sequence, transform: ImagerTransformation.Transformation] = {
BuildPath: Imager.PathProc = {
seg: Segment;
entire, lo, hi: BOOL;
controlPoints: BitVector;
firstPoint: Point;
entire ← selSeq.segments[0];
lo ← selSeq.joints[0];
firstPoint ← FetchJointPos[selSeq.traj, 0];
IF entire OR lo THEN firstPoint ← ImagerTransformation.Transform[transform, firstPoint];
moveTo[firstPoint];
FOR i:
INT
IN [0..HiSegment[selSeq.traj]]
DO
seg ← FetchSegment[selSeq.traj, i];
entire ← selSeq.segments[i];
lo ← selSeq.joints[i];
hi ← selSeq.joints[IF selSeq.traj.role=open THEN (i+1) ELSE (i+1) MOD selSeq.traj.segCount];
controlPoints ← selSeq.controlPoints[i];
seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
firstSeg: Segment ← FetchSegment[selSeq.traj, 0];
strokeWidth: REAL ← firstSeg.strokeWidth;
IF strokeWidth = 0.0 OR firstSeg.color=NIL THEN RETURN;
Imager.SetStrokeWidth[dc, strokeWidth];
Imager.SetColor[dc, firstSeg.color];
Imager.SetStrokeJoint[dc, selSeq.traj.strokeJoint];
Imager.SetStrokeEnd[dc, round];
Imager.MaskStroke[dc, BuildPath, selSeq.traj.role = fence OR selSeq.traj.role = hole];
}; -- end DrawSingleStrokeTrajTransformSeq
Transforming
TranslateTraj:
PUBLIC PROC [traj: Traj, vector: Vector] = {
A convenience routine which does a TransformTraj, where transform is a simple translation.
transform: ImagerTransformation.Transformation;
transform ← ImagerTransformation.Translate[[vector.x, vector.y]];
TransformTraj[traj, transform];
};
TransformTraj:
PUBLIC
PROC [traj: Traj, transform: ImagerTransformation.Transformation] = {
Individually translates each joint and control point of the trajectory.
seg: Segment;
joint: Joint;
FOR i:
NAT
IN [0..HiSegment[traj]]
DO
seg ← FetchSegment[traj, i];
GGSegment.TransformSegment[seg, transform];
ENDLOOP;
FOR i:
NAT
IN [0..HiJoint[traj]]
DO
joint ← NARROW[Rosary.Fetch[traj.joints, i]];
joint.point ← GGTransform.Transform[transform, joint.point];
ENDLOOP;
traj.boundBox ← GetBoundBox[traj];
GGOutline.UpdateOutlineBoundBox[traj.parent];
};
TransformSequence:
PUBLIC
PROC [seq: Sequence, transform: ImagerTransformation.Transformation] = {
A tricky operation. All selected segments are transformed. Then, all segments which are not selected but which touch a selected joint are told that the joint has moved.
joint: Joint;
seg: Segment;
SELECT seq.traj.role
FROM
open => TransformSequenceOpen[seq, transform];
fence, hole => TransformSequenceClosed[seq, transform];
ENDCASE => ERROR;
FOR i:
NAT
IN [0..HiJoint[seq.traj]]
DO
IF seq.joints[i]
THEN {
joint ← NARROW[Rosary.Fetch[seq.traj.joints, i]];
joint.point ← GGTransform.Transform[transform, joint.point];
};
ENDLOOP;
FOR i:
NAT
IN [0..HiSegment[seq.traj]]
DO
cpCount: NAT ← seq.controlPoints[i].len;
FOR j:
NAT
IN [0..cpCount)
DO
IF seq.controlPoints[i][j]
AND
NOT seq.segments[i]
THEN {
seg ← NARROW[Rosary.Fetch[seq.traj.segments, i]];
seg.class.controlPointMoved[seg, transform, j];
};
ENDLOOP;
ENDLOOP;
seq.traj.boundBox ← GetBoundBox[seq.traj];
GGOutline.UpdateOutlineBoundBox[seq.traj.parent];
};
TransformSequenceOpen:
PUBLIC
PROC [seq: Sequence, transform: ImagerTransformation.Transformation] = {
seg: Segment;
FOR i:
NAT
IN [0..HiSegment[seq.traj]]
DO
seg ← FetchSegment[seq.traj, i];
IF seq.segments[i]
THEN {
GGSegment.TransformSegment[seg, transform];
}
ELSE {
IF seq.joints[i] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]];
IF seq.joints[i+1] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]];
};
ENDLOOP;
};
TransformSequenceClosed:
PUBLIC
PROC [seq: Sequence, transform: ImagerTransformation.Transformation] = {
A tricky operation. All selected segments are transformed. Then, all segments which are not selected but which touch a selected joint are told that the joint has moved.
seg: Segment;
hiSeg: NAT ← HiSegment[seq.traj];
FOR i:
NAT
IN [0..hiSeg-1]
DO
seg ← FetchSegment[seq.traj, i];
IF seq.segments[i]
THEN {
GGSegment.TransformSegment[seg, transform];
}
ELSE {
IF seq.joints[i] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]];
IF seq.joints[i+1] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]];
};
ENDLOOP;
seg ← FetchSegment[seq.traj, hiSeg];
IF seq.segments[hiSeg]
THEN {
GGSegment.TransformSegment[seg, transform];
}
ELSE {
IF seq.joints[hiSeg] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]];
IF seq.joints[0] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]];
};
Textual Description
GetArrowCode:
PROC [traj: Traj]
RETURNS [code:
NAT] = {
code ←
IF
NOT traj.hiArrow
THEN
IF NOT traj.loArrow THEN 0
ELSE 1
ELSE
IF NOT traj.loArrow THEN 2
ELSE 3;
Fileout:
PUBLIC
PROC [f:
IO.
STREAM, traj: Traj] = {
roleRope: Rope.ROPE ← RoleToRope[traj.role];
strokeWidth: REAL;
strokeOK, colorOK: BOOL;
color: Color;
point: Point;
className: Rope.ROPE;
seg: Segment;
arrowCode: NAT;
hiJoint: NAT ← HiJoint[traj];
arrowCode ← GetArrowCode[traj];
f.PutF["Traj (%g) [%g] arrows: %g ", [rope[roleRope]], [integer[hiJoint]], [integer[arrowCode]]];
strokeWidth ← AllStrokeWidthsEqual[traj];
strokeOK ← strokeWidth >= 0.0;
f.PutF["w: %g ", [real[strokeWidth]]];
[colorOK, color] ← AllStrokeColorsEqual[traj];
f.PutRope["c: "];
GGParseOut.WriteBOOL[f, colorOK];
f.PutChar[IO.SP];
GGParseOut.WriteColor[f, color];
f.PutChar[
IO.
CR];
Now fileout the segment data.
point ← FetchJointPos[traj, 0];
GGParseOut.WritePoint[f, point];
FOR index:
NAT
IN [1..hiJoint]
DO
seg ← FetchSegment[traj, index - 1];
className ← Atom.GetPName[seg.class.type];
IF strokeOK THEN f.PutF[" (%g ", [rope[className]]]
ELSE f.PutF[" (%g %g ", [rope[className]], [real[seg.strokeWidth]]];
IF NOT colorOK THEN GGParseOut.WriteColor[f, seg.color];
f.PutChar[IO.SP];
seg.class.fileOut[seg, f];
GGParseOut.WriteProps[f, seg.props];
f.PutRope[") "];
point ← FetchJointPos[traj, index];
GGParseOut.WritePoint[f, point];
ENDLOOP;
IF (traj.role = fence
OR traj.role = hole)
THEN {
seg ← FetchSegment[traj, hiJoint];
className ← Atom.GetPName[seg.class.type];
IF strokeOK THEN f.PutF[" (%g ", [rope[className]]]
ELSE f.PutF[" (%g %g ", [rope[className]], [real[seg.strokeWidth]]];
IF NOT colorOK THEN GGParseOut.WriteColor[f, seg.color];
f.PutChar[IO.SP];
seg.class.fileOut[seg, f];
GGParseOut.WriteProps[f, seg.props];
f.PutRope[")"];
};
f.PutChar[IO.CR];
};
Filein:
PUBLIC PROC [f:
IO.
STREAM, version:
REAL]
RETURNS [traj: Traj, hasCircle:
BOOL ←
FALSE] = {
hiJoint: NAT;
role: FenceHoleOpen;
roleName, className: Rope.ROPE;
pFirst, p0, p1: Point;
seg: Segment;
arrowCode: NAT;
class: SegmentClass;
success: BOOL;
strokeWidth: REAL;
strokeColor: Color;
colorOK, strokeOK:
BOOL;
Traj (fence) [3] arrows: 0:
GGParseIn.ReadBlankAndRope[f, "Traj"];
GGParseIn.ReadBlankAndRope[f, "("];
roleName ← GGParseIn.ReadBlankAndWord[f];
role ← RoleFromRope[roleName];
GGParseIn.ReadBlankAndRope[f, ")"];
GGParseIn.ReadBlankAndRope[f, "["];
GGParseIn.ReadBlank[f];
hiJoint ← GGParseIn.ReadNAT[f];
GGParseIn.ReadBlankAndRope[f, "]"];
IF version >= 8607.22
THEN {
GGParseIn.ReadBlankAndRope[f, "arrows:"];
arrowCode ← GGParseIn.ReadBlankAndNAT[f];
}
ELSE arrowCode ← 0;
IF version >= 8701.135
THEN {
good: BOOL;
GGParseIn.ReadBlankAndRope[f, "w:"];
strokeWidth ← GGParseIn.ReadBlankAndReal[f];
strokeOK ← strokeWidth>= 0.0;
GGParseIn.ReadBlankAndRope[f, "c:"];
GGParseIn.ReadBlank[f];
[colorOK, good] ← GGParseIn.ReadBOOL[f];
IF NOT good THEN ERROR;
GGParseIn.ReadBlank[f];
strokeColor ← GGParseIn.ReadColor[f];
}
ELSE {
GGParseIn.ReadBlankAndRope[f, ":"];
strokeOK ← FALSE;
colorOK ← FALSE;
};
Now read the Segment data.
GGParseIn.ReadBlank[f];
pFirst ← p0 ← GGParseIn.ReadPoint[f];
traj ← CreateTraj[p0];
FOR index:
NAT
IN [1..hiJoint]
DO
GGParseIn.ReadBlankAndRope[f, "("];
className ← GGParseIn.ReadBlankAndWord[f];
IF NOT strokeOK THEN strokeWidth ← GGParseIn.ReadBlankAndReal[f];
IF version >= 8607.30
THEN {
GGParseIn.ReadBlank[f];
IF NOT colorOK THEN strokeColor ← GGParseIn.ReadColor[f];
}
ELSE strokeColor ← Imager.black;
class ← GGSegment.FetchSegmentClass[Atom.MakeAtom[className]];
seg ← class.fileIn[f, p0, [0.0, 0.0], version];
IF version >= 8610.29
THEN {
lor: LIST OF Rope.ROPE;
GGParseIn.ReadBlank[f];
lor ← GGParseIn.ReadListOfRope[f]; -- ReadListOfRope cheats and terminates at ') as well as at IO.CR
FOR next:
LIST
OF Rope.
ROPE ← lor, next.rest
UNTIL next=
NIL
DO
seg.props ← CONS[next.first, seg.props];
ENDLOOP;
};
GGParseIn.ReadBlankAndRope[f, ")"];
GGParseIn.ReadBlank[f];
p1 ← GGParseIn.ReadPoint[f];
seg.hi ← p1;
seg.class.endPointMoved[seg, FALSE, p1];
seg.strokeWidth ← strokeWidth;
seg.color ← strokeColor;
IF class.type=$Circle
OR class.type=$Disc
THEN {
fake it with a closed arc segment
p: Point ← GGVector.Sub[seg.hi, seg.lo];
newJoint: Point ← [seg.lo.x-p.x, seg.lo.y-p.y];
newCp: Point ← [seg.lo.x+p.x, seg.lo.y+p.y];
success ← AddSegment[traj, hi, GGSegment.MakeArc[newJoint, newCp, newJoint, NIL], lo];
CloseByDistorting[traj, lo];
hasCircle ← TRUE;
}
ELSE success ← AddSegment[traj, hi, seg, lo];
IF NOT success THEN ERROR;
p0 ← p1;
ENDLOOP;
IF (role = fence
OR role = hole)
THEN {
GGParseIn.ReadBlankAndRope[f, "("];
className ← GGParseIn.ReadBlankAndWord[f];
IF NOT strokeOK THEN strokeWidth ← GGParseIn.ReadBlankAndReal[f];
IF version >= 8607.30
THEN {
GGParseIn.ReadBlank[f];
IF NOT colorOK THEN strokeColor ← GGParseIn.ReadColor[f];
}
ELSE strokeColor ← Imager.black;
class ← GGSegment.FetchSegmentClass[Atom.MakeAtom[className]];
seg ← class.fileIn[f, p0, pFirst, version];
IF version >= 8610.29
THEN {
lor: LIST OF Rope.ROPE;
GGParseIn.ReadBlank[f];
lor ← GGParseIn.ReadListOfRope[f]; -- ReadListOfRope cheats and terminates at ') as well as at IO.CR
FOR next:
LIST
OF Rope.
ROPE ← lor, next.rest
UNTIL next=
NIL
DO
seg.props ← CONS[next.first, seg.props];
ENDLOOP;
};
GGParseIn.ReadBlankAndRope[f, ")"];
seg.strokeWidth ← strokeWidth;
seg.color ← strokeColor;
IF traj.segCount=0
THEN {
-- special case of closed single segment trajectory
success ← AddSegment[traj, hi, seg, lo];
IF NOT success THEN ERROR;
CloseByDistorting[traj, lo];
}
ELSE CloseWithSegment[traj, seg, lo];
};
};
RoleFromRope:
PROC [roleName: Rope.
ROPE]
RETURNS [role: FenceHoleOpen] = {
SELECT TRUE FROM
Rope.Equal[roleName, "fence"] => role ← fence;
Rope.Equal[roleName, "hole"] => role ← hole;
Rope.Equal[roleName, "open"] => role ← open;
ENDCASE => ERROR;
};
RoleToRope:
PROC [role: FenceHoleOpen]
RETURNS [roleName: Rope.
ROPE] = {
SELECT role FROM
fence => roleName ← "fence";
hole => roleName ← "hole";
open => roleName ← "open";
ENDCASE => ERROR;
};
Accessing Parts
FetchSegment:
PUBLIC
PROC [traj: Traj, index:
NAT]
RETURNS [seg: Segment] = {
seg ← NARROW[Rosary.Fetch[traj.segments, index]];
};
FetchJoint:
PUBLIC
PROC [traj: Traj, index:
NAT]
RETURNS [joint: Joint] = {
joint ← NARROW[Rosary.Fetch[traj.joints, index]];
};
FetchJointPos:
PUBLIC
PROC [traj: Traj, index:
NAT]
RETURNS [point: Point] = {
joint: Joint;
IF index < 0 OR index > HiJoint[traj] THEN ERROR;
joint ← NARROW[Rosary.Fetch[traj.joints, index]];
point ← joint.point
};
LastJointPos:
PUBLIC
PROC [traj: Traj]
RETURNS [point: Point] = {
joint: Joint ← NARROW[Rosary.Fetch[traj.joints, traj.segCount]];
point ← joint.point
};
SetJointPos:
PUBLIC
PROC [traj: Traj, index:
NAT, newPos: Point] = {
Moves the given joint and tells all interested segments that it has moved.
joint: Joint ← NARROW[Rosary.Fetch[traj.joints, index]];
segLeft, segRight: Segment;
joint.point ← newPos;
IF index > 0
THEN {
segLeft ← FetchSegment[traj, index-1];
segLeft.hi ← newPos;
};
IF index < traj.segCount
THEN {
segRight ← FetchSegment[traj, index];
segRight.lo ← newPos;
};
};
HiSegment:
PUBLIC
PROC [traj: Traj]
RETURNS [highestIndex:
NAT] = {
highestIndex ← traj.segCount - 1;
};
HiJoint:
PUBLIC
PROC [traj: Traj]
RETURNS [highestIndex:
NAT] = {
SELECT traj.role
FROM
open => highestIndex ← traj.segCount;
fence, hole => highestIndex ← traj.segCount -1;
ENDCASE => ERROR;
};
PreviousSegment:
PUBLIC
PROC [traj: Traj, segNum:
NAT]
RETURNS [prev: Segment] = {
IF traj.role = open
THEN {
IF segNum = 0 THEN RETURN[NIL]
ELSE prev ← FetchSegment[traj, segNum - 1];
}
ELSE {
prev ← FetchSegment[traj, (segNum-1+traj.segCount) MOD traj.segCount];
};
};
PreviousSegmentNum:
PUBLIC
PROC [traj: Traj, segNum:
NAT]
RETURNS [prevNum:
NAT] = {
IF traj.role = open
THEN {
IF segNum = 0 THEN ERROR
ELSE prevNum ← segNum - 1;
}
ELSE {
prevNum ← (segNum-1+traj.segCount) MOD traj.segCount;
};
};
FollowingSegmentNum:
PUBLIC
PROC [traj: Traj, segNum:
NAT]
RETURNS [followNum:
NAT] = {
IF traj.role = open
THEN {
IF segNum = HiSegment[traj] THEN ERROR
ELSE followNum ← segNum + 1;
}
ELSE {
followNum ← (segNum+1) MOD traj.segCount;
};
};
FollowingJoint:
PUBLIC
PROC [traj: Traj, index:
NAT]
RETURNS [nextIndex:
NAT] = {
SELECT traj.role
FROM
open => {
IF index = traj.segCount THEN ERROR;
nextIndex ← index + 1;
};
fence, hole => nextIndex ← (index + 1) MOD traj.segCount;
ENDCASE => ERROR;
};
IsEndJoint:
PUBLIC
PROC [traj: Traj, index:
NAT]
RETURNS [
BOOL] = {
SELECT traj.role
FROM
open => RETURN[index = 0 OR index = traj.segCount];
fence, hole => RETURN[FALSE];
ENDCASE => ERROR;
};
IndexOfJoint:
PUBLIC
PROC [joint: Joint, traj: Traj]
RETURNS [index:
INT] = {
Returns -1 if it doesn't exist.
thisJoint: Joint;
FOR i:
NAT
IN [0..HiJoint[traj]]
DO
thisJoint ← FetchJoint[traj, i];
IF thisJoint = joint THEN RETURN[i];
ENDLOOP;
RETURN[-1];
IndexOfSegment:
PUBLIC
PROC [segment: Segment, traj: Traj]
RETURNS [index:
INT] = {
Returns -1 if it doesn't exist.
thisSeg: Segment;
FOR i:
NAT
IN [0..HiSegment[traj]]
DO
thisSeg ← FetchSegment[traj, i];
IF thisSeg = segment THEN RETURN[i];
ENDLOOP;
RETURN[-1];
};
CopyJoint:
PROC [joint: Joint]
RETURNS [copy: Joint] = {
copy ← NEW[JointObj ← [point: joint.point, TselectedInFull: joint.TselectedInFull] ];
};
Hit Testing
TrajPointGeneratorData: TYPE = REF TrajPointGeneratorDataObj;
TrajPointGeneratorDataObj:
TYPE =
RECORD [
jointsDone: BOOL,
jointGen: JointGenerator,
cpGen: GGModelTypes.ControlPointGenerator
];
TrajPointPairGeneratorData: TYPE = REF TrajPointPairGeneratorDataObj;
TrajPointPairGeneratorDataObj:
TYPE =
RECORD [
segGen: SegmentGenerator
];
PointsInDescriptor:
PUBLIC
PROC [seq: Sequence]
RETURNS [pointGen: GGModelTypes.PointGenerator] = {
pgd: TrajPointGeneratorData;
jointGen: JointGenerator;
cpGen: GGModelTypes.ControlPointGenerator;
jointGen ← GGSequence.JointsInSequence[seq];
cpGen ← GGSequence.ControlPointsInSequence[seq];
pgd ← NEW[TrajPointGeneratorDataObj ← [FALSE, jointGen, cpGen]];
pointGen ← NEW[GGModelTypes.PointGeneratorObj ← [NIL, 0, 0, pgd]];
};
PointPairsInDescriptor:
PUBLIC
PROC [seq: Sequence]
RETURNS [pointPairGen: GGModelTypes.PointPairGenerator] = {
pgd: TrajPointPairGeneratorData;
segGen: SegmentGenerator;
segGen ← GGSequence.SegmentsInSequence[seq];
pgd ← NEW[TrajPointPairGeneratorDataObj ← [segGen]];
pointPairGen ← NEW[GGModelTypes.PointPairGeneratorObj ← [NIL, 0, 0, pgd]];
};
NextPoint:
PUBLIC
PROC [pointGen: GGModelTypes.PointGenerator]
RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
pgd: TrajPointGeneratorData ← NARROW[pointGen.classSpecific];
IF
NOT pgd.jointsDone
THEN {
nextJoint: INT ← GGSequence.NextJoint[pgd.jointGen];
IF nextJoint = -1
THEN {
pgd.jointsDone ← TRUE;
}
ELSE {
pointAndDone.point ← FetchJointPos[pgd.jointGen.seq.traj, nextJoint];
pointAndDone.done ← FALSE;
RETURN;
};
};
Time to look at the control points.
pointAndDone ← GGSequence.NextControlPoint[pgd.cpGen];
};
NextPointPair:
PUBLIC
PROC [pointGen: GGModelTypes.PointPairGenerator]
RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = {
pgd: TrajPointPairGeneratorData ← NARROW[pointGen.classSpecific];
seg: Segment;
seg ← GGSequence.NextSegment[pgd.segGen];
IF seg = NIL THEN RETURN[[[0,0], [0,0], TRUE]]
ELSE RETURN[[seg.lo, seg.hi, FALSE]];
};
Style
SetArrows:
PUBLIC
PROC [traj: Traj, loArrow, hiArrow:
BOOL] = {
Will there be an arrowhead on the lo end of traj? On the hi end?
traj.loArrow ← loArrow;
traj.hiArrow ← hiArrow;
};
Utilities
Vector3D: TYPE = ARRAY [1..3] OF REAL;
PointToVector:
PROC [point: Point]
RETURNS [vector: Vector3D] = {
vector[1] ← point.x;
vector[2] ← point.y;
vector[3] ← 0;
};
CrossProduct:
PROC [v1: Vector3D, v2: Vector3D]
RETURNS [prodV: Vector3D] = {
| i j k |
| v1x v1y v1z |=(v1y*v2z - v1z*v2y) i + (v1z*v2x - v1x*v2z) j
| v2x v2y v2z |(v1x*v2y - v1y*v2x) k
prodV[1] ← v1[2]*v2[3] - v1[3]*v2[2];
prodV[2] ← v1[3]*v2[1] - v1[1]*v2[3];
prodV[3] ← v1[1]*v2[2] - v1[2]*v2[1];
};
GetPartialArea:
PROC [pt1: Point, pt2: Point]
RETURNS [partial:
REAL] = {
D1: Vector3D ← PointToVector[pt1];
D2: Vector3D ← PointToVector[pt2];
areaVector: Vector3D ← CrossProduct[D2, D1];-- will only have a z component
RETURN [areaVector[3]];
};
SignedArea:
PROC [traj: Traj]
RETURNS [area:
REAL ← 0.0] = {
Choose an arbitrary point P in the plane. Choose two consecutive joints (v1, v2) on the polygon described by the traj. Call the vector P to v1, D1. Call the distance P to v2, D2. The area of the triangle made by these three points is |D1|*|D2|*sin(angle between D1 and D2)/2.
This is just D2 x D1 where "x" is the cross product operator.
This will be positive if D2 is clockwise of D1.
If P is in the interior of poly for poly convex, and if we add up all of the areas we get by taking vertices pairwise in clockwise order, area will be positive. If we take them in counter-clockwise order, area will be negative. This turns out to be true whatever the position of P and even if poly is concave part of the time.
lastPoint, thisPoint: Point ← FetchJointPos[traj, 0];
hiSeg: NAT ← HiSegment[traj];
thisSeg: Segment;
FOR index:
NAT
IN [0..hiSeg]
DO
thisSeg ← FetchSegment[traj, index];
FOR cpIndex:
NAT
IN [0..thisSeg.class.controlPointCount[thisSeg])
DO
area ← area + GetPartialArea[lastPoint, (thisPoint ← thisSeg.class.controlPointGet[thisSeg, cpIndex])];
lastPoint ← thisPoint;
ENDLOOP;
area ← area + GetPartialArea[lastPoint, (thisPoint ← FetchJointPos[traj, IF index=hiSeg THEN 0 ELSE index+1])];
lastPoint ← thisPoint;
ENDLOOP;
}; -- end of SignedArea
SignedAreaTransformSeq:
PROC [seq: Sequence, transform: ImagerTransformation.Transformation]
RETURNS [area:
REAL ← 0.0] = {
lastPoint, thisPoint: Point;
hiSeg: NAT ← HiSegment[seq.traj];
thisSeg: Segment;
lastPoint ← FetchJointPos[seq.traj, 0];
IF seq.joints[0] THEN lastPoint ← ImagerTransformation.Transform[transform, lastPoint];
FOR index:
NAT
IN [0..hiSeg]
DO
thisSeg ← FetchSegment[seq.traj, index];
FOR cpIndex:
NAT
IN [0..thisSeg.class.controlPointCount[thisSeg])
DO
thisPoint ← thisSeg.class.controlPointGet[thisSeg, cpIndex];
IF seq.controlPoints[index][cpIndex] THEN thisPoint ← ImagerTransformation.Transform[transform, thisPoint];
area ← area + GetPartialArea[lastPoint, thisPoint];
lastPoint ← thisPoint;
ENDLOOP;
thisPoint ← FetchJointPos[seq.traj, IF index=hiSeg THEN 0 ELSE index+1];
IF seq.joints[IF index=hiSeg THEN 0 ELSE index+1] THEN thisPoint ← ImagerTransformation.Transform[transform, thisPoint];
area ← area + GetPartialArea[lastPoint, thisPoint];
lastPoint ← thisPoint;
ENDLOOP;
}; -- end of SignedAreaTransformSeq
useBBox: BOOL = TRUE;
maxDistance: REAL = 99999.0;
noJoint: NAT = 9999;
noCP: NAT = 8888;
noSeg: NAT = 7777;
NearestSegment:
PUBLIC PROC [testPoint: Point, seq: Sequence, tolerance:
REAL]
RETURNS [bestDist:
REAL, bestSeg:
NAT, bestPoint: Point, success:
BOOL] = {
thisDist2, bestDist2: REAL;
thisPoint: Point;
segGen: SegmentGenerator;
thisSuccess: BOOL;
next: GGSequence.SegAndIndex;
bigBox: GGBasicTypes.BoundBoxObj;
success ← FALSE;
IF useBBox
THEN {
bigBox ← [seq.boundBox.loX-tolerance, seq.boundBox.loY-tolerance, seq.boundBox.hiX+tolerance, seq.boundBox.hiY+tolerance, FALSE, FALSE];
IF NOT GGBoundBox.PointIsInBox[testPoint, bigBox] THEN RETURN; -- success=FALSE
};
segGen ← GGSequence.SegmentsInSequence[seq];
next ← GGSequence.NextSegmentAndIndex[segGen];
IF next.seg =
NIL
THEN {
-- The sequence is a single joint
bestDist ← maxDistance; -- magic number for debugging
bestSeg ← noSeg; -- magic number meaning no segment
bestPoint ← [-1.0, -1.0];
RETURN;
};
[bestPoint, thisSuccess] ← next.seg.class.closestPoint[next.seg, testPoint, tolerance];
IF
NOT thisSuccess
THEN {
bestDist2 ← maxDistance;
bestSeg ← noSeg;
bestPoint ← [-1.0, -1.0];
}
ELSE {
bestDist2 ← GGVector.DistanceSquared[bestPoint, testPoint];
bestSeg ← next.index;
success ← TRUE;
};
FOR next ← GGSequence.NextSegmentAndIndex[segGen], GGSequence.NextSegmentAndIndex[segGen]
UNTIL next.seg =
NIL
DO
[thisPoint, thisSuccess] ← next.seg.class.closestPoint[next.seg, testPoint, tolerance];
IF
NOT thisSuccess
THEN {
thisDist2 ← maxDistance;
thisPoint ← [-1.0, -1.0];
}
ELSE {
thisDist2 ← GGVector.DistanceSquared[thisPoint, testPoint];
success ← TRUE;
};
IF success
AND thisDist
2 < bestDist
2
THEN {
bestDist2 ← thisDist2;
bestSeg ← next.index;
bestPoint ← thisPoint;
};
ENDLOOP;
bestDist ← RealFns.SqRt[bestDist2];
};
NearestJoint:
PUBLIC PROC [testPoint: Point, seq: Sequence, tolerance:
REAL]
RETURNS [bestDist:
REAL, bestJoint:
NAT, bestPoint: Point, success:
BOOL] = {
Finds the nearest joint of traj to testPoint (and its distance from testPoint).
tolerance2: REAL ← tolerance*tolerance;
thisDist2, bestDist2: REAL;
thisPoint: Point;
jointGen: JointGenerator;
index: INT;
bigBox: GGBasicTypes.BoundBoxObj;
success ← FALSE;
IF useBBox
THEN {
bigBox ← [seq.boundBox.loX-tolerance, seq.boundBox.loY-tolerance, seq.boundBox.hiX+tolerance, seq.boundBox.hiY+tolerance, FALSE, FALSE];
IF NOT GGBoundBox.PointIsInBox[testPoint, bigBox] THEN RETURN; -- success=FALSE
};
jointGen ← GGSequence.JointsInSequence[seq];
index ← GGSequence.NextJoint[jointGen];
IF index = -1
THEN {
bestDist ← maxDistance;
bestJoint ← noJoint;
bestPoint ← [-1.0, -1.0];
RETURN;
};
bestPoint ← FetchJointPos[seq.traj, index];
bestDist2 ← GGVector.DistanceSquared[bestPoint, testPoint];
bestJoint ← index;
IF bestDist2 < tolerance2 THEN success ← TRUE;
FOR index ← GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen]
UNTIL index = -1
DO
thisPoint ← FetchJointPos[seq.traj, index];
thisDist2 ← GGVector.DistanceSquared[thisPoint, testPoint];
IF thisDist2 < tolerance2 THEN success ← TRUE;
IF success
AND thisDist
2 < bestDist
2
THEN {
bestDist2 ← thisDist2;
bestJoint ← index;
bestPoint ← thisPoint;
};
ENDLOOP;
bestDist ← RealFns.SqRt[bestDist2];
};
NearestControlPoint:
PUBLIC PROC [testPoint: Point, seq: Sequence, tolerance:
REAL]
RETURNS [bestDist:
REAL, bestSeg:
NAT, bestControlPoint:
NAT, bestPoint: Point, success:
BOOL] = {
Finds the nearest control point of seq (within tolerance) to testPoint (and its distance from testPoint).
SomeCP:
PROC [i:
NAT]
RETURNS [
BOOL] = {
cpCount: NAT ← seq.controlPoints[i].len;
FOR j:
NAT
IN [0..cpCount)
DO
IF seq.controlPoints[i][j] THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
tolerance2: REAL ← tolerance*tolerance;
thisDist2, bestDist2: REAL;
thisControlPoint: NAT;
thisPoint: Point;
segGen: SegmentGenerator;
thisSuccess: BOOL;
next: GGSequence.SegAndIndex;
bigBox: GGBasicTypes.BoundBoxObj;
success ← FALSE;
IF useBBox
THEN {
bigBox ← [seq.boundBox.loX-tolerance, seq.boundBox.loY-tolerance, seq.boundBox.hiX+tolerance, seq.boundBox.hiY+tolerance, FALSE, FALSE];
IF NOT GGBoundBox.PointIsInBox[testPoint, bigBox] THEN RETURN; -- success=FALSE
};
segGen ← GGSequence.SegmentsInSequence[seq];
next ← GGSequence.NextSegmentAndIndex[segGen];
WHILE next.seg#
NIL
AND
NOT SomeCP[next.index]
DO
next ← GGSequence.NextSegmentAndIndex[segGen];
ENDLOOP;
IF next.seg =
NIL
THEN {
-- The sequence has no control points
bestDist ← maxDistance; -- magic number meaning no segment
bestControlPoint ← noCP; -- magic number meaning no CP
bestPoint ← [-1.0, -1.0];
RETURN;
};
[bestPoint, thisControlPoint, thisSuccess] ← next.seg.class.closestControlPoint[next.seg, testPoint, tolerance];
IF
NOT thisSuccess
THEN {
bestDist2 ← maxDistance;
bestSeg ← noSeg;
bestControlPoint ← noCP;
bestPoint ← [-1.0, -1.0];
}
ELSE {
bestDist2 ← GGVector.DistanceSquared[bestPoint, testPoint];
bestSeg ← next.index;
bestControlPoint ← thisControlPoint;
success ← TRUE;
};
FOR next ← GGSequence.NextSegmentAndIndex[segGen], GGSequence.NextSegmentAndIndex[segGen]
UNTIL next.seg =
NIL
DO
IF SomeCP[next.index]
THEN {
[thisPoint, thisControlPoint, thisSuccess] ← next.seg.class.closestControlPoint[next.seg, testPoint, tolerance];
IF
NOT thisSuccess
THEN {
thisDist2 ← maxDistance;
thisPoint ← [-1.0, -1.0];
}
ELSE {
thisDist2 ← GGVector.DistanceSquared[thisPoint, testPoint];
success ← TRUE;
};
IF success
AND thisDist
2 < bestDist
2
THEN {
bestDist2 ← thisDist2;
bestSeg ← next.index;
bestControlPoint ← thisControlPoint;
bestPoint ← thisPoint;
};
};
ENDLOOP;
bestDist ← RealFns.SqRt[bestDist2];
};
END.