DIRECTORY Atom, GGBasicTypes, GGBoundBox, GGError, GGInterfaceTypes, GGModelTypes, GGOutline, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, GGSequence, GGShapes, GGTransform, GGTraj, GGUtility, GGVector, Imager, ImagerColor, ImagerExtras, ImagerTransformation, IO, RealFns, Rope, Rosary; GGTrajImpl: CEDAR PROGRAM IMPORTS Atom, GGBoundBox, GGError, GGOutline, GGParseIn, GGParseOut, GGSegment, GGSequence, GGShapes, GGTransform, GGUtility, GGVector, Imager, ImagerColor, ImagerExtras, 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; SequenceOfReal: TYPE = GGBasicTypes.SequenceOfReal; 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; 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; 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] = { diff: Vector; outline: Outline; IF traj.segCount = 0 THEN ERROR Problem[msg: "single closed segment not implemented"]; 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] = { 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]; }; CopyTrajFromRun: PUBLIC PROC [seq: Sequence] RETURNS [copy: Traj] = { 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] = { 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 ]]; piece.boundBox _ GetBoundBox[piece]; }; Concat: PUBLIC PROC [fixed: Traj, fixedEnd: TrajEnd, moving: Traj, movingEnd: TrajEnd] RETURNS [longer: Traj] = { 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; newTraj.loArrow _ run.traj.loArrow; newTraj.hiArrow _ run.traj.hiArrow; }; IncludesLoEnd: PROC [run: Sequence] RETURNS [BOOL] = { RETURN[run.segments[0]]; }; ResetEndSelectionBits: PROC [traj: Traj, lo, hi: BOOL _ TRUE] = { joint: Joint; IF lo THEN { joint _ FetchJoint[traj, 0]; joint.TselectedInFull.active _ FALSE; }; IF hi THEN { 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; 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]; IF IncludesLoEnd[run] THEN { ResetEndSelectionBits[traj: traj1, lo: TRUE, hi: FALSE]; newTraj _ Concat[traj, hi, traj1, lo]; -- order is important. traj1's lo joint is kept } ELSE { ResetEndSelectionBits[traj: traj1, lo: FALSE, hi: TRUE]; newTraj _ Concat[traj, lo, traj1, hi]; -- order is important. traj1's hi joint is kept }; }; ENDCASE => { traj1 _ CopyTrajFromRun[run1]; ResetEndSelectionBits[traj: traj1, lo: FALSE, hi: TRUE]; traj2 _ CopyTrajFromRun[run2]; ResetEndSelectionBits[traj: traj2, lo: TRUE, hi: FALSE]; newTraj _ Concat[traj, lo, traj1, hi]; newTraj _ Concat[newTraj, hi, traj2, lo]; }; }; SpliceInClosed: PROC [run: Sequence, traj: Traj] RETURNS [newTraj: Traj] = { wholeSeq, remainder, run1, run2: Sequence; runGen: SequenceGenerator; traj1: Traj; 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[traj, lo, traj1, hi]; -- order is important because traj1's joints are kept. CloseByDistorting[newTraj, lo]; }; ReverseTraj: PUBLIC PROC [traj: Traj] = { newSegments: Rosary.ROSARY _ NIL; newJoints: Rosary.ROSARY _ NIL; seg: Segment; joint: Joint; 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]; }; GetBoundBox: PUBLIC PROC [traj: Traj] RETURNS [bBox: BoundBox] = { 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] = { 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]]; copy.boundBox _ GetBoundBox[original]; }; DrawTraj: PUBLIC PROC [dc: Imager.Context, traj: Traj] = { 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]; }; 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; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; firstSeg: Segment _ FetchSegment[traj, 0]; strokeWidth: REAL _ firstSeg.strokeWidth; dashed: BOOL _ firstSeg.dashed; pattern: SequenceOfReal _ firstSeg.pattern; offset: REAL _ firstSeg.offset; length: REAL _ firstSeg.length; IF strokeWidth = 0.0 OR firstSeg.color=NIL THEN RETURN; Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetColor[dc, firstSeg.color]; Imager.SetStrokeJoint[dc, round]; Imager.SetStrokeEnd[dc, round]; IF dashed THEN ImagerExtras.MaskDashedStroke[dc, BuildPath, pattern.len, PatternProc, offset, length] ELSE Imager.MaskStroke[dc, BuildPath, traj.role = fence OR traj.role = hole]; }; -- end DrawSingleStrokeTraj 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; }; }; 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; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; firstSeg: Segment _ FetchSegment[selSeq.traj, 0]; strokeWidth: REAL _ firstSeg.strokeWidth; pattern: SequenceOfReal _ firstSeg.pattern; 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]; IF firstSeg.dashed THEN ImagerExtras.MaskDashedStroke[dc, BuildPath, pattern.len, PatternProc, firstSeg.offset, firstSeg.length] ELSE Imager.MaskStroke[dc, BuildPath, selSeq.traj.role = fence OR selSeq.traj.role = hole]; }; -- end DrawSingleStrokeTrajTransformSeq 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; 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; 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]; }; MaskStroke: PROC [dc: Imager.Context, seg: Segment] = { MaskPath: Imager.PathProc = { moveTo[seg.lo]; seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo]; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; pattern: SequenceOfReal _ seg.pattern; IF seg.dashed THEN ImagerExtras.MaskDashedStroke[dc, MaskPath, pattern.len, PatternProc, seg.offset, seg.length] ELSE Imager.MaskStroke[dc, MaskPath, FALSE]; }; MaskStrokeTransform: PROC [dc: Imager.Context, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi: BOOL, controlPoints: BitVector] = { MaskPath: Imager.PathProc = { 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]; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; pattern: SequenceOfReal _ seg.pattern; IF seg.dashed THEN ImagerExtras.MaskDashedStroke[dc, MaskPath, pattern.len, PatternProc, seg.offset, seg.length] ELSE Imager.MaskStroke[dc, MaskPath, FALSE]; }; EqualSequence: PROC [s1, s2: SequenceOfReal] RETURNS [BOOL] = { IF s1.len # s2.len THEN RETURN[FALSE]; FOR i: NAT IN [0..s1.len) DO IF s1[i] # s2[i] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; AllStrokeWidthsAndColorsEqual: PROC [traj: Traj] RETURNS [BOOL] = { firstSeg: Segment _ FetchSegment[traj, 0]; seg: Segment; width: REAL _ firstSeg.strokeWidth; color: Color _ firstSeg.color; dashed: BOOL _ firstSeg.dashed; pattern: SequenceOfReal _ firstSeg.pattern; offset: REAL _ firstSeg.offset; length: REAL _ firstSeg.length; 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]; IF seg.dashed # dashed THEN RETURN[FALSE]; IF NOT dashed THEN LOOP; IF NOT EqualSequence[seg.pattern, pattern] THEN RETURN[FALSE]; IF seg.offset # offset THEN RETURN[FALSE]; IF seg.length # length 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; }; AllDashesEqual: PROC [traj: Traj] RETURNS [allEqual: BOOL, dashed: BOOL _ FALSE, pattern: SequenceOfReal _ NIL, offset, length: REAL _ 0.0] = { firstSeg: Segment _ FetchSegment[traj, 0]; seg: Segment; dashed _ firstSeg.dashed; pattern _ firstSeg.pattern; offset _ firstSeg.offset; length _ firstSeg.length; FOR i: INT IN [1..HiSegment[traj]] DO seg _ FetchSegment[traj, i]; IF seg.dashed # dashed THEN RETURN[FALSE]; IF NOT dashed THEN LOOP; IF NOT EqualSequence[seg.pattern, pattern] THEN RETURN[FALSE]; IF seg.offset # offset THEN RETURN[FALSE]; IF seg.length # length THEN RETURN[FALSE]; 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; }; }; 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]; }; TranslateTraj: PUBLIC PROC [traj: Traj, vector: Vector] = { transform: ImagerTransformation.Transformation; transform _ ImagerTransformation.Translate[[vector.x, vector.y]]; TransformTraj[traj, transform]; }; TransformTraj: PUBLIC PROC [traj: Traj, transform: ImagerTransformation.Transformation] = { 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] = { 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] = { 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]]; }; }; 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, offset, length: REAL; strokeOK, colorOK, dashOK, dashed: BOOL; pattern: SequenceOfReal; 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.SP]; [dashOK, dashed, pattern, offset, length] _ AllDashesEqual[traj]; f.PutRope["d: "]; GGParseOut.WriteBOOL[f, dashOK]; IF dashOK THEN { f.PutChar[IO.SP]; GGParseOut.WriteBOOL[f, dashed]; f.PutChar[IO.SP]; IF dashed THEN { GGParseOut.WriteArrayOfReal[f, pattern]; f.PutF[" %g %g", [real[offset]], [real[length]]]; }; }; f.PutChar[IO.CR]; 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]; }; IF NOT dashOK THEN { GGParseOut.WriteBOOL[f, seg.dashed]; f.PutChar[IO.SP]; IF seg.dashed THEN { GGParseOut.WriteArrayOfReal[f, seg.pattern]; f.PutChar[IO.SP]; f.PutF[" %g %g ", [real[seg.offset]], [real[seg.length]]]; }; }; 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]; }; IF NOT dashOK THEN { GGParseOut.WriteBOOL[f, seg.dashed]; f.PutChar[IO.SP]; IF seg.dashed THEN { GGParseOut.WriteArrayOfReal[f, seg.pattern]; f.PutChar[IO.SP]; f.PutF[" %g %g ", [real[seg.offset]], [real[seg.length]]]; }; }; 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, arrowCode: NAT; role: FenceHoleOpen; roleName, className: Rope.ROPE; pFirst, p0, p1: Point; seg: Segment; class: SegmentClass; strokeWidth, offset, length: REAL; pattern: SequenceOfReal; strokeColor: Color; success, colorOK, strokeOK, dashOK, dashed, good: BOOL; GGParseIn.ReadBlankAndRope[f, "Traj"]; GGParseIn.ReadBlankAndRope[f, "("]; roleName _ GGParseIn.ReadBlankAndWord[f]; role _ RoleFromRope[roleName]; GGParseIn.ReadBlankAndRope[f, ")"]; GGParseIn.ReadBlankAndRope[f, "["]; hiJoint _ GGParseIn.ReadBlankAndNAT[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:"]; [colorOK, good] _ GGParseIn.ReadBOOL[f, version]; IF NOT good THEN ERROR; strokeColor _ GGParseIn.ReadColor[f, version]; } ELSE { GGParseIn.ReadBlankAndRope[f, ":"]; strokeOK _ FALSE; colorOK _ FALSE; }; IF version >= 8701.23 THEN { GGParseIn.ReadBlankAndRope[f, "d:"]; [dashOK, good] _ GGParseIn.ReadBOOL[f, version]; IF NOT good THEN ERROR; IF dashOK THEN { [dashed, good] _ GGParseIn.ReadBOOL[f, version]; IF NOT good THEN ERROR; IF dashed THEN { pattern _ GGParseIn.ReadArrayOfReal[f]; offset _ GGParseIn.ReadBlankAndReal[f]; length _ GGParseIn.ReadBlankAndReal[f]; }; }; } ELSE { dashOK _ TRUE; dashed _ FALSE; }; 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 { IF NOT colorOK THEN strokeColor _ GGParseIn.ReadColor[f, version]; } ELSE strokeColor _ Imager.black; IF NOT dashOK THEN { [dashed, good] _ GGParseIn.ReadBOOL[f, version]; IF NOT good THEN ERROR; IF dashed THEN { pattern _ GGParseIn.ReadArrayOfReal[f]; offset _ GGParseIn.ReadBlankAndReal[f]; length _ GGParseIn.ReadBlankAndReal[f]; }; }; 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; 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, ")"]; p1 _ GGParseIn.ReadPoint[f]; seg.hi _ p1; seg.class.endPointMoved[seg, FALSE, p1]; seg.strokeWidth _ strokeWidth; seg.color _ strokeColor; seg.dashed _ dashed; seg.pattern _ pattern; seg.offset _ offset; seg.length _ length; IF class.type=$Circle OR class.type=$Disc THEN { 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 { IF NOT colorOK THEN strokeColor _ GGParseIn.ReadColor[f, version]; } ELSE strokeColor _ Imager.black; IF NOT dashOK THEN { [dashed, good] _ GGParseIn.ReadBOOL[f, version]; IF NOT good THEN ERROR; IF dashed THEN { pattern _ GGParseIn.ReadArrayOfReal[f]; offset _ GGParseIn.ReadBlankAndReal[f]; length _ GGParseIn.ReadBlankAndReal[f]; }; }; class _ GGSegment.FetchSegmentClass[Atom.MakeAtom[className]]; seg _ class.fileIn[f, p0, pFirst, version]; IF version >= 8610.29 THEN { lor: LIST OF Rope.ROPE; 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; seg.dashed _ dashed; seg.pattern _ pattern; seg.offset _ offset; seg.length _ length; 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; }; 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] = { 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] = { 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] = { 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] ]; }; 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; }; }; 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]]; }; SetArrows: PUBLIC PROC [traj: Traj, loArrow, hiArrow: BOOL] = { traj.loArrow _ loArrow; traj.hiArrow _ hiArrow; }; 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] = { 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] = { 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 thisDist2 < bestDist2 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] = { 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 thisDist2 < bestDist2 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] = { 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 thisDist2 < bestDist2 THEN { bestDist2 _ thisDist2; bestSeg _ next.index; bestControlPoint _ thisControlPoint; bestPoint _ thisPoint; }; }; ENDLOOP; bestDist _ RealFns.SqRt[bestDist2]; }; END. ˜GGTrajImpl.mesa Copyright c 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. Trajectory-Only Routines Moves seg so that the specified end of seg coincides with the specified end of traj. In other words, seg is translated. 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. For now, just translate into place and assume size is ok. 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. Building Trajectories from Parts seq should be a run (a single consecutive set of segments and joints). 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. I must create a new outline and copy the extrapoints as well. Moves moving so that the specified end moving coincides with the specified end fixed. In other words, moving is translated. A hack to get arrows to work reasonably for now. firstJointNum, lastJointNum: NAT; joint: Joint; [Artwork node; type 'ArtworkInterpress on' to command tool] 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; firstJointNum, lastJointNum: INT; joint: Joint; [Artwork node; type 'ArtworkInterpress on' to command tool] 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 IF traj.role # open THEN SIGNAL Problem["not yet implemented"]; 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. Computes it from segment boxes. 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. I must copy the extrapoints as well. Drawing Let Q be a logical variable corresponding to . Let S correspond to . Let O correspond to . Then segment J is drawn thick when: qSo. Segment J is drawn thin otherwise (i.e. when Q + s + O). Imager.SetStrokeJoint[dc, traj.strokeJoint]; Draw the joints. Draw the control points. someNormal _ GGSequence.ContainsSegmentParts[selectedParts, i]; someHot _ GGSequence.ContainsSegmentParts[hotParts, i]; Drawing Utilities [moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] Transforming A convenience routine which does a TransformTraj, where transform is a simple translation. Individually translates each joint and control point of the trajectory. 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. 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. Textual Description Properties of the whole trajectory. Now fileout the segment data. Traj (fence) [3] arrows: 0: Now read the Segment data. fake it with a closed arc segment Accessing Parts Moves the given joint and tells all interested segments that it has moved. Use sparingly Returns -1 if it doesn't exist. Returns -1 if it doesn't exist. Utilities Hit Testing Time to look at the control points. Style Will there be an arrowhead on the lo end of traj? On the hi end? Utilities | i j k | | v1x v1y v1z |=(v1y*v2z - v1z*v2y) i + (v1z*v2x - v1x*v2z) j | v2x v2y v2z |(v1x*v2y - v1y*v2x) k 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. Finds the nearest joint of traj to testPoint (and its distance from testPoint). Finds the nearest control point of seq (within tolerance) to testPoint (and its distance from testPoint). ÊJ1˜J˜Icodešœ™Kšœ Ïmœ1™­¡’ÄÑÈÄZ|©Ä~¬KÄ`}2Zˆ¡’ÄÉœÄÿ½ˆÄ'ö1ÄtQ;z°¡’˜¢¯“¡¡¨¢·“¢°“ý™yzŽ˜ r j1Ĭôa ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj– k é¢¯“¡¡¨¢·“¢°“Èo™g¢Žo˜ r jÄH/—Ä•?W ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁwholeSeq– k é¢¯“¡¡¨¢·“¢°“ÈU™MþŽU˜¢¯“¡¡¨¢·“¢°“{U™M¢ŽU˜ r jÙÄW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Árun1– k é r jƒ? ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Árun2– k é¢¯“¡¡¨¢·“¢°“µ™ë³—˜¢¯“¡¡¨¢·“¢°“µ™y뎘 r jÆk ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj1– k é¢¯“¡¡¨¢·“¢°“Š°™±‰—˜¢¯“¡¡¨¢·“¢°“Š™y±Ž˜ r jj ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj2– k é k é k é k g•Artwork Interpress•Bounds80.0 mm xmin 0.0 mm ymin 179.5639 mm xmax 47.625 mm ymax –G50.44722 mm topLeading 50.44722 mm topIndent 1.411111 mm bottomLeading šŸ=™=Kšœ˜Kšœ'žœžœ˜8Kšœ˜Kšœ'žœžœ˜8Kšœ&˜&Kšœ)˜)K˜—Kš ˆ™ˆKšœ.™.Kšœ-™-Kšœ+™+Kšœžœ™%Kšœ*™*Kšœžœ™%K˜K˜—šŸœžœžœ˜LKšœ*˜*K˜K˜ Kšœžœ™!Kšœ ™ K˜Kšžœžœžœ¡˜FKšœ/˜/Kšœ1˜1Kšœ#˜#Kšœ6˜6Kšœ'˜'Kšœ'˜'Kš žœžœžœžœžœžœ˜'Kšœ˜Kšœ˜Kšœ'¡6˜]Kšœ˜M–¿ Interpress/Xerox/3.0  f j k j¡¥“ÄWB ¤ ¨  Ew¡£=ú ¢ ¨ r j % ¢ ¨¡¡¨ÄWB ¤ ¨ r j¡¥“ÄWB ¤ ¨¡¡¨Ä¡¨‰ÄSôA™­)—‰ÄsžA—AŽ)—AÄSôA—‰Ž¡¡¡¡™¢·“¢°“¢¯“¡¡¨‰ÄSôA™­)—˜¥¯“¡¡¨­)™‰ÄsžA—˜¥¯“¡¡¨‰ÄsžA™AŽ˜¥¯“¡¡¨AÄsžA™)—˜¢¯“¡¡¨)™AÄSôA—˜¢¯“¡¡¨AÄSôA™‰Ž˜¢¯“¡¡¨¢·“¢°“ÄYðÃ-™ÄQŸ¼1—AÄw®AÄß1Ê1¡“Äõ-—˜ r jÄûĦëW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Árun– k é¥¯“¡¡¨¢·“¢°“y)™Ä"˜ÄYÚ7ĠĢÓ^UÄsžA¡’ăùQħ{\ÄyÉQħ{\ ÄsžA¡’Äë«°Ä¢Ó^Äÿ·†ÄYÚ7é)¡’˜ r j"ƒ ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj– k é¢¯“¡¡¨¢·“¢°“ÄYðÃ%™ÄQŸ¼!—Äx!ÑÄgYRÄr%O!¡“Äõ%—˜ r jÄ«Æ ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Árun1– k é¢¯“¡¡¨¢·“¢°“é™ ÄQjA—UŽy—˜ r jÄ #Äq9W ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj1– k é r jÄ(  ¤ê$ ¢ ¥ ¨¡¡¨Ä¯“Ÿ ™¡ Ÿ ¡“¡¸ k é r jĉÈÄ‚ŽW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁConcat– k é¢¯“¡¡¨¢·“¢°“k ™ÄÐÒÄ-&ÄmbyÄ}²O¡“˜£¯“¡¡¨¢·“¢°“†5™Äèž ÄI¦—†5—ÄsŸÄ²¡ —˜ r jĉĚÄa ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Álo– k é r jÄ;٠ĈUW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Áhi– k é r jÄ'y ÄzW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Áhi– k é r jÄ;y ÄŠÚa ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Álo– k é r jÄ>!Ä‚7W ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁClose– k é k é k é k g– Interpress–:0.0 mm xmin 0.0 mm ymin 148.5194 mm xmax 75.84722 mm ymax –G78.66944 mm topLeading 78.66944 mm topIndent 1.411111 mm bottomLeading šŸ=™=Kš ˆ™ˆKšœ.™.šžœžœ™Kšœžœ=™HKšœžœžœžœ™CKšœ/žœ™DKšœ+™+Kšœžœ™%Kšœ*™*Kšœžœ™%K™—K˜K˜—K™K™ K˜šŸ œžœžœ˜)Kšœžœžœ˜!Kšœžœžœ˜Kšœ ˜ K˜ Kšžœžœžœ ™?šžœžœžœž˜#Kšœ˜Kšœ˜Kšœ?˜?Kšžœ˜—šžœžœžœž˜#Kš œžœžœžœžœ˜`Kšœ=˜=Kšžœ˜—Kšœ˜K˜K˜K˜—š Ÿœžœžœžœžœ˜Kšžœžœžœžœ˜*Kšžœžœžœžœ˜*—šž˜Kšžœžœžœ˜—Kšžœ˜K˜K˜—šŸœžœžœ žœ˜AKšœ*˜*Kšœ ˜ Kšœ˜šžœžœžœž˜%Kšœ˜Kšžœžœžœ˜-Kšžœ˜—K˜K˜—šŸœžœžœ žœ˜RKšœ*˜*Kšœ ˜ Kšœ˜šžœžœžœž˜%Kšœ˜Kš žœžœžœžœžœ˜-Kšžœ˜—Kšœ žœ˜K˜K˜—šŸœžœžœ žœ žœžœžœžœ ˜Kšœ*˜*Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšœ˜šžœžœžœž˜%Kšœ˜Kšžœžœžœžœ˜*Kšžœžœžœžœ˜Kš žœžœ%žœžœžœ˜>Kšžœžœžœžœ˜*Kšžœžœžœžœ˜*Kšžœ˜—Kšœ žœ˜˜K˜K˜——š Ÿœžœ žœžœžœ˜MKšžœ˜%K˜K˜—šŸœž œUžœžœ+˜­K˜Kšœ˜Kšžœžœžœ˜(Kšœ,˜,Kšœ"˜"šžœžœžœ¡˜3Kšœ!˜!Kšœ˜šžœdžœ žœž˜~šžœ(žœ˜0Kšœžœžœžœ˜^Kš œžœžœžœžœžœžœ˜Kšœ¡F˜bKšœ˜K˜—Kšžœ˜—K˜—K˜K˜—šŸœžœ*žœ,˜mKšœžœžœ˜K˜*Kšœ žœ ˜.šžœ žœžœ˜šžœžœ˜Kšœ' œ˜,Kšœ žœ˜K˜—šžœžœ˜"Kšœ' œ˜,Kšœ žœ˜K˜—K˜—šžœ žœžœ˜šžœžœ˜"Kšœ' œ˜/Kšœ žœ˜K˜—šžœžœ˜%Kšœ' œ˜/Kšœ žœ˜K˜—K˜—šžœžœ¡˜4Kšžœžœžœ ˜2Kšžœžœžœ ˜2K˜—Kš žœžœžœ žœ žœ ˜C˜K˜—K˜—K™K™ K˜šŸ œž œ"˜Lšœ:˜:L˜—L˜—Lšœ˜Lšœ   œ˜$L˜Lšœ#˜#Lšœ   œ ˜ Lšžœ˜—šžœžœžœ˜1Lšœ"˜"Lšœ*˜*Lšžœ žœ  œ˜3Lšžœ  œ  œ˜Dšžœžœ žœ˜Lšœ   œ˜$Lšœ žœžœ˜L˜—šžœžœžœ˜Lšœ/žœžœ˜6šžœ žœ˜Lšœ7žœžœ˜>Lšœ:˜:L˜—L˜—Lšœ˜Lšœ   œ˜$L˜L˜—Lšœ žœžœ˜K˜—K˜šŸœž œžœžœ žœžœžœžœ˜cLšœžœ˜L˜Lšœžœ˜L˜L˜ Lšœ˜Lšœžœ˜"L˜L˜šœ2žœ˜7Lš ™—Lšœ&˜&Lšœ#˜#Lšœ)˜)Lšœ˜Lšœ#˜#Lšœ#˜#Lšœ'˜'Lšœ#˜#šžœžœ˜Lšœ)˜)Lšœ)˜)L˜—Lšžœ˜šžœ˜šžœ˜Lšœžœ˜ Lšœ$˜$Lš  œ!˜,Lš œ˜Lšœ$˜$Lšœ œ)˜1Lšžœžœžœžœ˜Lš  œ#˜.L˜—šžœ˜Lšœ#˜#Lšœ žœ˜Lšœ žœ˜L˜——šžœ˜šžœ˜Lšœ$˜$Lšœ œ)˜0Lšžœžœžœžœ˜šžœ œžœ˜Lšœ0˜0Lšžœžœžœžœ˜šžœžœ˜Lšœ'˜'Lšœ'˜'Lšœ'˜'L˜—L˜—L˜—šžœ˜Lš œžœ˜Lšœ žœ˜L˜L˜——Lš ™Lšœ%˜%Lšœ˜šžœžœžœž˜!Lšœ#˜#Lš  œ!˜*Lšžœžœ žœ-˜Ašžœžœ˜Lšžœžœ žœ/˜BL˜—Lšžœ  œ˜ šžœžœžœ˜Lšœ0˜0Lšžœžœžœžœ˜šžœžœ˜Lšœ'˜'Lšœ'˜'Lšœ'˜'L˜—L˜—Lšœ>˜>Lš /˜/šžœžœ˜Lšœžœžœžœ˜Lšœ#¡A˜dš žœžœžœžœžœžœž˜>Lšœ žœ˜(Lšžœ˜—L˜—Lšœ#˜#Lšœ˜Lšœ ˜ Lšœžœ˜(Lšœ˜Lšœ˜L˜L˜L˜L˜šžœžœžœ˜0K™!K˜(K˜/K˜,KšœLžœ˜VKšœ˜Kšœ žœ˜K˜—Lšžœ)˜-Lšžœžœ žœžœ˜Lšœ˜—Lšžœ˜šžœžœžœ˜'Lšœ#˜#Lš  œ!˜*Lšžœžœ žœ-˜Ašžœžœ˜Lšžœžœ žœ/˜BL˜—Lšžœ  œ˜ šžœžœžœ˜Lšœ0˜0Lšžœžœžœžœ˜šžœžœ˜Lšœ'˜'Lšœ'˜'Lšœ'˜'L˜—L˜—Lšœ>˜>Lš +˜+šžœžœ˜Lšœžœžœžœ˜Lšœ#¡A˜dš žœžœžœžœžœžœž˜>Lšœ žœ˜(Lšžœ˜—L˜—Lšœ#˜#Lšœ˜Lšœ˜L˜L˜L˜L˜šžœžœ¡3˜MKšœ(˜(Lšžœžœ žœžœ˜Kšœ˜Kšœ˜—Kšžœ!˜%L˜—L˜L˜—šŸ œžœžœžœ˜JLšžœžœž˜Lšœ.˜.Lšœ,˜,Lšœ,˜,Lšžœžœ˜L˜L˜—šŸ œžœžœžœ˜HLšžœž˜Lšœ˜Lšœ˜Lšœ˜Lšžœžœ˜L˜L˜—K™K™K˜š Ÿ œžœžœžœžœ˜MKšœžœ%˜1Kšœ˜K˜—š Ÿ œžœžœžœžœ˜KKšœžœ#˜1K˜K˜—š Ÿ œžœžœžœžœ˜NKšœ ˜ Kšžœ žœžœžœ˜2Kšœžœ#˜1Kšœ˜Kšœ˜K˜—šŸ œžœžœžœ˜AKšœžœ+˜@Kšœ˜Kšœ˜K˜—šŸ œžœžœžœ˜DK™JKšœžœ#˜8Kšœ˜Kšœ˜šžœ žœ˜Kšœ&˜&Kšœ˜Kšœ˜—šžœžœ˜Kšœ%˜%Kšœ˜K˜—Kšœ˜K˜—K˜š Ÿ œžœžœžœžœ˜CKšœ!˜!K˜K˜—š Ÿœžœžœžœžœ˜Bšžœ ž˜Kšœ%˜%Kšœ/˜/Kšžœžœ˜—K˜K˜—K˜š Ÿœžœžœžœžœ˜Ršžœžœ˜Kšžœ žœžœžœ˜Kšžœ'˜+K˜—šžœ˜Kšœ3žœ˜FK˜—K˜K˜—š Ÿœžœžœžœžœ žœ˜Tšžœžœ˜Kšžœ žœž˜Kšžœ˜K˜—šžœ˜Kšœ#žœ˜5K˜—K˜K˜—š Ÿœžœžœžœžœ žœ˜Wšžœžœ˜Kšžœžœž˜&Kšžœ˜K˜—šžœ˜Kšœžœ˜)K˜—K˜K˜—š Ÿœžœžœžœžœ žœ˜Ršžœ ž˜šœ ˜ Kšžœžœžœ˜$Kšœ˜K˜—Kšœ'žœ˜9Kšžœžœ˜—K˜K˜—K˜š Ÿ œžœžœžœžœžœ˜Dšžœ ž˜Kšœžœ žœ˜3Kšœžœžœ˜Kšžœžœ˜—K˜K˜—™K™ —š Ÿ œžœžœžœ žœ˜MK™Kšœ˜šžœžœžœž˜#Kšœ ˜ Kšžœžœžœ˜$Kšžœ˜—Kšžœ˜ ˜K˜——š Ÿœžœžœ žœ žœ˜SK™K˜šžœžœžœž˜%Kšœ ˜ Kšžœžœžœ˜$Kšžœ˜—Kšžœ˜ K˜—˜K™ —šŸ œžœžœ˜8KšœžœK˜UK˜—K™K™ K™Kšœžœžœ˜=šœžœžœ˜*Kšœ žœ˜K˜Kšœ)˜)Kšœ˜K˜—Kšœžœžœ˜Ešœžœžœ˜.K˜Kšœ˜K˜—šŸœžœžœžœ,˜cKšœ˜K˜Kšœ*˜*Kšœ,˜,Kšœ0˜0Kšœžœžœ˜@Kšœ žœ#žœ˜BK˜K˜—šŸœžœžœžœ4˜oKšœ ˜ K˜Kšœ,˜,Kšœžœ+˜4Kšœžœ'žœ˜JK˜—K˜šŸ œžœžœ)žœ.˜tKšœžœ˜=šžœžœžœ˜Kšœ žœ&˜4šžœžœ˜Kšœžœ˜K˜—šžœ˜KšœE˜EKšœžœ˜Kšžœ˜K˜—K˜K™#—Kšœ6˜6K˜K˜—šŸ œžœžœ-žœ6˜„Kšœ"žœ˜AK˜ Kšœ)˜)Kš žœžœžœžœžœ˜.Kšžœžœžœ˜%K˜—K™K˜šŸ œžœžœ žœ˜?K™AKšœ˜Kšœ˜K˜K˜—J˜J™ J™Jš  œžœžœžœžœ˜&šŸ œžœžœ˜ALšœ˜Lšœ˜Lšœ˜Lšœ˜—šŸ œžœžœ˜MLšœ™Lšœ=™=Lšœ$™$Lšœ%˜%Lšœ%˜%Lšœ%˜%Lšœ˜L˜—šŸœžœžœ žœ˜ILšŸœ ˜"LšŸœ ˜"Lšœ,¡˜KLšžœ˜L˜L˜—šŸ œžœžœžœ ˜™>Lšœ/™/LšœÈ™ÈL˜5Lšœžœ˜L˜šžœžœžœ ž˜L˜$šžœ žœžœ/ž˜DL˜gL˜Lšžœ˜—Lšœp˜pL˜Lšžœ˜—Lšœ¡ œ ˜L˜—šŸœžœAžœžœ ˜{Lšœ˜Lšœžœ˜!L˜Lšœ'˜'LšžœžœB˜Wšžœžœžœ ž˜L˜(šžœ žœžœ/ž˜DL˜Kšœ¡"˜;Kšœ¡˜7Kšœ˜Kšžœ˜K˜—Kšœp˜pšžœžœ žœ˜Kšœ¢œ˜Kšœ˜Kšœ˜Kšœ˜K˜—šžœ˜Kšœ¢œ2˜;Kšœ˜Kšœ$˜$Kšœ žœ˜K˜—šžœWžœ žœž˜qšžœžœ˜Kšœp˜pšžœžœ žœ˜Kšœ¢œ˜Kšœ˜K˜—šžœ˜Kšœ¢œ2˜;Kšœ žœ˜K˜—š žœ žœ ¢œ ¢œžœ˜+Kšœ¢œ ¢œ˜Kšœ˜Kšœ$˜$Kšœ˜K˜—K˜—Kšžœ˜—Kšœ ¢œ˜#Kšœ˜K˜—K™Kšžœ˜K˜—…—Å€)I