DIRECTORY CodeTimer, Feedback, FeedbackTypes, FunctionCache, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGHistoryTypes, GGModelTypes, GGMUserProfile, GGOutline, GGProps, GGSegment, GGSegmentTypes, GGSequence, GGShapes, GGSlice, GGTraj, GGTransform, GGUtility, Imager, ImagerPath, ImagerTransformation, RealFns, Rope, Rosary, Vectors2d; GGSliceImplD: CEDAR PROGRAM IMPORTS CodeTimer, Feedback, FunctionCache, GGBoundBox, GGCoreOps, GGMUserProfile, GGProps, GGSegment, GGSequence, GGShapes, GGSlice, GGTraj, GGTransform, GGUtility, Imager, ImagerTransformation, RealFns, Rosary, Vectors2d EXPORTS GGSlice = BEGIN BitVector: TYPE = GGBasicTypes.BitVector; BoundBox: TYPE = GGCoreTypes.BoundBox; BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj; Camera: TYPE = GGModelTypes.Camera; Circle: TYPE = GGBasicTypes.Circle; Color: TYPE = Imager.Color; ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator; DefaultData: TYPE = GGModelTypes.DefaultData; EditConstraints: TYPE = GGModelTypes.EditConstraints; MsgRouter: TYPE = FeedbackTypes.MsgRouter; HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent; HitType: TYPE = GGModelTypes.TrajPartType; Joint: TYPE = GGSegmentTypes.Joint; JointGenerator: TYPE = GGModelTypes.JointGenerator; JointObj: TYPE = GGSegmentTypes.JointObj; Line: TYPE = GGCoreTypes.Line; Object: TYPE = Imager.Object; OutlineData: TYPE = GGOutline.OutlineData; Point: TYPE = GGBasicTypes.Point; PointAndDone: TYPE = GGModelTypes.PointAndDone; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj; PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone; Scene: TYPE = GGModelTypes.Scene; SegAndIndex: TYPE = GGSequence.SegAndIndex; SegCPSequence: TYPE = GGTraj.SegCPSequence; SegCPSequenceObj: TYPE = GGTraj.SegCPSequenceObj; Segment: TYPE = GGSegmentTypes.Segment; SegmentClass: TYPE = GGSegmentTypes.SegmentClass; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj; SelectionClass: TYPE = GGSegmentTypes.SelectionClass; SelectMode: TYPE = GGModelTypes.SelectMode; Sequence: TYPE = GGModelTypes.Sequence; SequenceOfReal: TYPE = GGCoreTypes.SequenceOfReal; Slice: TYPE = GGModelTypes.Slice; SliceBoundBoxProc: TYPE = GGModelTypes.SliceBoundBoxProc; SliceClass: TYPE = GGModelTypes.SliceClass; SliceClassObj: TYPE = GGModelTypes.SliceClassObj; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceObj: TYPE = GGModelTypes.SliceObj; SliceParts: TYPE = GGModelTypes.SliceParts; StrokeEnd: TYPE = GGModelTypes.StrokeEnd; StrokeJoint: TYPE = GGModelTypes.StrokeJoint; Traj: TYPE = GGModelTypes.Traj; TrajData: TYPE = GGModelTypes.TrajData; TrajEnd: TYPE = GGModelTypes.TrajEnd; TrajHitData: TYPE = GGTraj.TrajHitData; TrajHitDataObj: TYPE = GGTraj.TrajHitDataObj; TrajParts: TYPE = GGModelTypes.TrajParts; TrajPartsObj: TYPE = GGModelTypes.TrajPartsObj; TrajPartType: TYPE = GGModelTypes.TrajPartType; Transformation: TYPE = GGModelTypes.Transformation; Vector: TYPE = GGBasicTypes.Vector; WalkProc: TYPE = GGModelTypes.WalkProc; MoveToProc: TYPE = ImagerPath.MoveToProc; LineToProc: TYPE = ImagerPath.LineToProc; CurveToProc: TYPE = ImagerPath.CurveToProc; ConicToProc: TYPE = ImagerPath.ConicToProc; ArcToProc: TYPE = ImagerPath.ArcToProc; CPSequence: TYPE = REF CPSequenceObj; CPSequenceObj: TYPE = RECORD [ cpsInSeg: SEQUENCE len: NAT OF SegCPSequence]; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem; KillBoundBox: PUBLIC PROC [slice: Slice] = { FOR nextSlice: Slice _ slice, nextSlice.parent UNTIL nextSlice=NIL DO nextSlice.boxValid _ FALSE; nextSlice.tightBoxValid _ FALSE; ENDLOOP; }; KillBoundBoxOnly: PUBLIC PROC [slice: Slice] = { FOR nextSlice: Slice _ slice, nextSlice.parent UNTIL nextSlice=NIL DO nextSlice.boxValid _ FALSE; ENDLOOP; }; KillTightBoxOnly: PUBLIC PROC [slice: Slice] = { FOR nextSlice: Slice _ slice, nextSlice.parent UNTIL nextSlice=NIL DO nextSlice.tightBoxValid _ FALSE; ENDLOOP; }; TrajShapeChangeNotify: PUBLIC PROC [slice: Slice] = { trajData: TrajData _ NARROW[slice.data]; KillBoundBox[slice]; trajData.polyline _ NIL; }; BuildTrajSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { class _ NEW[SliceClassObj _ [ type: $Traj , unlink: TrajUnlink, getBoundBox: TrajGetBoundBox, getTransformedBoundBox: GGSlice.GenericTransformedBoundBox, getTightBox: TrajGetTightBox, copy: TrajCopy, restore: TrajRestore, buildPath: TrajBuildPath, drawBorder: TrajDrawBorder, drawParts: TrajDrawParts, drawTransform: TrajDrawTransform, drawSelectionFeedback: TrajDrawSelectionFeedback, drawAttractorFeedback: TrajDrawAttractorFeedback, attractorFeedbackBoundBox: TrajAttractorFeedbackBoundBox, saveSelections: TrajSaveSelections, remakeSelections: TrajRemakeSelections, transform: TrajTransform, isEmptyParts: TrajIsEmptyParts, isCompleteParts: TrajIsCompleteParts, newParts: TrajNewParts, unionParts: TrajUnionParts, differenceParts: TrajDiffParts, movingParts: TrajMovingParts, augmentParts: TrajAugmentParts, alterParts: TrajAlterParts, setSelectedFields: TrajSetSelectedFields ]]; GGSlice.BuildMoreTrajSliceClass[class]; -- kludge to split class procs across modules }; TrajUnlink: PROC [slice: Slice] = { trajData: TrajData _ NARROW[slice.data]; GGSlice.UnlinkSlice[slice]; trajData.segments _ trajData.joints _ NIL; }; TrajGetBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { IF slice.boxValid AND parts=NIL THEN RETURN[slice.boundBox] -- fast case ELSE { EnlargeBySegmentBox: GGSequence.SegmentWalkProc = { GGBoundBox.EnlargeByBox[bBox: box, by: seg.class.boundBox[seg]]; }; trajData: TrajData _ NARROW[slice.data]; trajParts: TrajParts _ NARROW[parts]; box _ GGBoundBox.NullBoundBox[]; IF parts=NIL THEN [] _ GGSequence.WalkSegmentsInTraj[trajData, EnlargeBySegmentBox] ELSE [] _ GGSequence.WalkSegmentsInSequence[trajData, trajParts, EnlargeBySegmentBox]; IF parts=NIL THEN { -- set up cache for fast case next time around GGSlice.KillBoundBoxOnly[slice.parent]; -- invalidate ancestor caches slice.boundBox _ box; slice.boxValid _ TRUE; }; }; }; TrajGetTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { trajData: TrajData _ NARROW[slice.data]; IF slice.tightBoxValid AND parts=NIL THEN RETURN[slice.tightBox] -- fast case ELSE { EnlargeBySegmentTightBox: GGSequence.SegmentWalkProc = { GGBoundBox.EnlargeByBox[bBox: box, by: seg.class.tightBox[seg]]; }; trajData: TrajData _ NARROW[slice.data]; trajParts: TrajParts _ NARROW[parts]; box _ GGBoundBox.NullBoundBox[]; IF parts=NIL THEN [] _ GGSequence.WalkSegmentsInTraj[trajData, EnlargeBySegmentTightBox] ELSE [] _ GGSequence.WalkSegmentsInSequence[trajData, trajParts, EnlargeBySegmentTightBox]; IF parts=NIL THEN { -- set up cache for fast case next time around GGSlice.KillTightBoxOnly[slice.parent]; -- invalidate ancestor caches slice.tightBox _ box; slice.tightBoxValid _ TRUE; }; }; }; TrajCopy: PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [copy: LIST OF Slice] = { trajParts: TrajParts; runs: GGSequence.SequenceGenerator; IF parts=NIL THEN RETURN[LIST[CopyTraj[slice]]]; -- quickly copy the whole thing trajParts _ NARROW[parts]; CodeTimer.StartInt[$CopyTraj, $Gargoyle]; runs _ GGSequence.RunsInSequence[trajParts].seqGen; FOR nextSeq: TrajParts _ GGSequence.NextSequence[runs], GGSequence.NextSequence[runs] UNTIL nextSeq=NIL DO newSlice: Slice _ GGTraj.CopyTrajFromRun[slice, nextSeq]; NARROW[newSlice.data, TrajData].forward _ NARROW[slice.data, TrajData].forward; GGProps.CopyAll[fromSlice: slice, toSlice: newSlice]; copy _ CONS[newSlice, copy]; ENDLOOP; CodeTimer.StopInt[$CopyTraj, $Gargoyle]; }; CopyTraj: PROC [original: Traj] RETURNS [copy: Traj] = { trajData: TrajData _ NARROW[original.data]; originalSegments: Rosary.ROSARY _ trajData.segments; desiredSegments: Rosary.Segment _ [originalSegments, 0, trajData.segCount]; extractedSegments: Rosary.ROSARY; originalJoints: Rosary.ROSARY _ trajData.joints; desiredJoints: Rosary.Segment _ [originalJoints, 0, GGTraj.HiJoint[original] + 1]; extractedJoints: Rosary.ROSARY; CopyEachSegment: PROC[q: PROC[item: Rosary.Item, repeat: INT _ 1]] = { CopySegmentAndBuild: PROC [item: Rosary.Item] RETURNS [quit: BOOL _ 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: BOOL _ FALSE] = { copy, oldJoint: Joint; oldJoint _ NARROW[item]; copy _ NEW[JointObj _ [point: oldJoint.point, TselectedInFull: oldJoint.TselectedInFull] ]; q[copy, 1]; }; [] _ Rosary.Map[desiredJoints, CopyJointAndBuild]; }; CodeTimer.StartInt[$CopyTraj, $Gargoyle]; extractedSegments _ Rosary.FromProcProc[CopyEachSegment]; extractedJoints _ Rosary.FromProcProc[CopyEachJoint]; copy _ GGTraj.CreateTrajFromData[trajData.role, trajData.segCount, extractedSegments, extractedJoints, NIL, trajData.visibleJoints, trajData.strokeJoint, trajData.loArrow, trajData.hiArrow]; NARROW[copy.data, TrajData].forward _ NARROW[original.data, TrajData].forward; GGProps.CopyAll[fromSlice: original, toSlice: copy]; CodeTimer.StopInt[$CopyTraj, $Gargoyle]; }; TrajRestore: PROC [from: Slice, to: Slice] = { IF to=NIL OR from=NIL THEN ERROR; IF to.class#from.class THEN ERROR; IF to.class.type#$Traj THEN ERROR; BEGIN fromData: TrajData _ NARROW[from.data]; toData: TrajData _ NARROW[to.data]; toData^ _ fromData^; to.selectedInFull _ from.selectedInFull; -- RECORD of BOOL to.normalSelectedParts _ NIL; -- caller must reselect to.hotSelectedParts _ NIL; -- caller must reselect to.activeSelectedParts _ NIL; -- caller must reselect to.matchSelectedParts _ NIL; -- caller must reselect to.tightBox^ _ from.tightBox^; to.tightBoxValid _ from.tightBoxValid; to.boundBox^ _ from.boundBox^; to.boxValid _ from.boxValid; to.onOverlay _ from.onOverlay; to.extraPoints _ from.extraPoints; to.priority _ from.priority; to.historyTop _ from.historyTop; END; }; BuildConstrainedPathSeg: PROC [slice: Slice, transformParts: SliceParts, segNum: NAT, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi: BOOL, controlPoints: BitVector, editConstraints: EditConstraints, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc] = { IF entire THEN seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo] ELSE { dirVec: Vector; distance: REAL; partialTransform, ctrlLoTrans, ctrlHiTrans: Transformation; newJoint: Point; ctrlPoint: Point; ctrlPoints: BitVector _ NEW[GGBasicTypes.BitVectorObj[2]]; -- only need two for a Bezier segment trajTransformParts: TrajParts _ NARROW[transformParts]; ctrlPoints[0] _ IF controlPoints=NIL THEN FALSE ELSE controlPoints[0]; ctrlPoints[1] _ IF controlPoints=NIL THEN FALSE ELSE controlPoints[1]; ctrlLoTrans _ IF ctrlPoints[0] THEN transform ELSE idTransform; ctrlHiTrans _ IF ctrlPoints[1] THEN transform ELSE idTransform; IF lo AND NOT ctrlPoints[0] THEN { -- Low side of segment ctrlPoint _ seg.class.controlPointGet[seg, 0]; ctrlPoints[0] _ TRUE; newJoint _ ImagerTransformation.Transform[transform, seg.lo]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, newJoint]]; dirVec _ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint]; IF editConstraints = tangent THEN { distance _ Vectors2d.Distance[newJoint, ctrlPoint]; IF dirVec#[0,0] THEN dirVec _ Vectors2d.Normalize[dirVec]; -- use exception ctrlLoTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]]; } ELSE ctrlLoTrans _ ImagerTransformation.PostTranslate[partialTransform, dirVec]; }; IF hi AND NOT ctrlPoints[1] THEN { -- High side of Segment ctrlPoint _ seg.class.controlPointGet[seg, 1]; ctrlPoints[1] _ TRUE; newJoint _ ImagerTransformation.Transform[transform, seg.hi]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, newJoint]]; dirVec _ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint]; IF editConstraints = tangent THEN { distance _ Vectors2d.Distance[newJoint, ctrlPoint]; IF dirVec#[0,0] THEN dirVec _ Vectors2d.Normalize[dirVec]; -- use exception ctrlHiTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]]; } ELSE ctrlHiTrans _ ImagerTransformation.PostTranslate[partialTransform, dirVec]; }; IF NOT ctrlPoints[0] AND NOT lo THEN { prevSegNum: INT _ GGTraj.PreviousSegmentNum[slice, segNum]; IF prevSegNum#-1 AND GGTraj.FetchSegment[slice, prevSegNum].class.type = $Bezier THEN { IF trajTransformParts.controlPoints[prevSegNum][1] THEN { angle: REAL; transformedPoint: Point; otherCtrlPoint: Point _ seg.class.controlPointGet[GGTraj.FetchSegment[slice, prevSegNum], 1]; otherDirVector: Vector _ Vectors2d.VectorFromPoints[seg.lo, otherCtrlPoint]; ctrlPoint _ seg.class.controlPointGet[seg, 0]; dirVec _ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint]; distance _ Vectors2d.Magnitude[dirVec]; transformedPoint _ ImagerTransformation.Transform[transform, otherCtrlPoint]; angle _ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.lo, transformedPoint]]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.lo]]; ctrlPoints[0] _ TRUE; IF editConstraints = tangent THEN { IF transformedPoint # seg.lo AND otherDirVector # [0,0] THEN { --Don't flip based on other cps 0 length vec. ctrlLoTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; }; -- Else just use identity. } ELSE { -- length IF otherDirVector # [0,0] THEN { distance _ RealFns.SqRt[Vectors2d.DistanceSquared[seg.lo, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance; ctrlLoTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; }; -- Else just use identity. }; }; }; }; IF NOT ctrlPoints[1] AND NOT hi THEN { nextSegNum: INT _ GGTraj.FollowingSegmentNum[slice, segNum]; IF nextSegNum # -1 AND GGTraj.FetchSegment[slice, nextSegNum].class.type = $Bezier THEN { IF trajTransformParts.controlPoints[nextSegNum][0] THEN { angle: REAL; transformedPoint: Point; otherCtrlPoint: Point _ seg.class.controlPointGet[GGTraj.FetchSegment[slice, nextSegNum], 0]; otherDirVector: Vector _ Vectors2d.VectorFromPoints[seg.hi, otherCtrlPoint]; ctrlPoint _ seg.class.controlPointGet[seg, 1]; dirVec _ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint]; distance _ Vectors2d.Magnitude[dirVec]; transformedPoint _ ImagerTransformation.Transform[transform, otherCtrlPoint]; angle _ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.hi, transformedPoint]]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.hi]]; ctrlPoints[1] _ TRUE; IF editConstraints = tangent THEN { IF transformedPoint # seg.hi AND otherDirVector # [0,0] THEN { --Don't flip based on other cps 0 length vec. ctrlHiTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; }; -- Else just use identity. } ELSE { -- length IF otherDirVector # [0,0] THEN { distance _ RealFns.SqRt[Vectors2d.DistanceSquared[seg.hi, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance; ctrlHiTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; }; --Else just use identity. }; }; }; }; GGSegment.BZSpecialTransformPath[seg, transform, lo, hi, ctrlPoints, ctrlLoTrans, ctrlHiTrans, curveTo]; }; }; TrajBuildPath: PROC [slice: Slice, transformParts: SliceParts, transform: ImagerTransformation.Transformation, moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc, editConstraints: EditConstraints _ none] = { TransformParts: PROC = { hiSegment: NAT _ GGTraj.HiSegment[slice]; trajParts: TrajParts _ NARROW[transformParts]; moveTo[IF trajParts.segments[0] OR trajParts.joints[0] THEN ImagerTransformation.Transform[transform, GGTraj.FetchJointPos[slice, 0]] ELSE GGTraj.FetchJointPos[slice, 0] ]; FOR index: INT IN [0..hiSegment] DO seg: Segment _ GGTraj.FetchSegment[slice, index]; IF seg.class.type=$Bezier AND editConstraints#none THEN BuildConstrainedPathSeg[slice, transformParts, index, seg, transform, trajParts.segments[index], trajParts.joints[index], trajParts.joints[(index+1) MOD trajParts.segments.len], trajParts.controlPoints[index], editConstraints, lineTo, curveTo, conicTo, arcTo] ELSE seg.class.buildPathTransform[seg, transform, trajParts.segments[index], trajParts.joints[index], trajParts.joints[(index+1) MOD trajParts.segments.len], trajParts.controlPoints[index], lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; TransformEntire: PROC = { hiSegment: NAT _ GGTraj.HiSegment[slice]; moveTo[ImagerTransformation.Transform[transform, GGTraj.FetchJointPos[slice, 0]] ]; FOR index: INT IN [0..hiSegment] DO seg: Segment _ GGTraj.FetchSegment[slice, index]; IF seg.class.type=$Bezier AND editConstraints#none THEN BuildConstrainedPathSeg[slice, transformParts, index, seg, transform, TRUE, TRUE, TRUE, NIL, editConstraints, lineTo, curveTo, conicTo, arcTo] ELSE seg.class.buildPathTransform[seg, transform, TRUE, TRUE, TRUE, NIL, lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; TransformNone: PROC = { hiSegment: NAT _ GGTraj.HiSegment[slice]; moveTo[GGTraj.FetchJointPos[slice, 0]]; FOR index: INT IN [0..hiSegment] DO seg: Segment _ GGTraj.FetchSegment[slice, index]; seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; IF transform#NIL THEN IF transformParts=NIL THEN TransformEntire[] ELSE TransformParts[] ELSE TransformNone[]; }; TrajDrawBorder: PROC [slice: Slice, drawParts: SliceParts, transformParts: SliceParts, transform: Transformation, dc: Imager.Context, camera: Camera, quick: BOOL, editConstraints: EditConstraints _ none] = { DoTrajDrawBorder: PROC = { trajDrawParts: TrajParts _ NARROW[drawParts]; trajTransformParts: TrajParts _ NARROW[transformParts]; drawEntireTraj: BOOL; transformNone: BOOL; transformEntireTraj: BOOL; CodeTimer.StartInt[$TrajDrawBorder, $Gargoyle]; drawEntireTraj _ trajDrawParts=NIL OR GGSequence.IsComplete[trajDrawParts]; transformNone _ transform=NIL; transformEntireTraj _ NOT transformNone AND (trajTransformParts=NIL OR GGSequence.IsComplete[trajTransformParts]); IF drawEntireTraj AND (transformNone OR (transformEntireTraj AND ImagerTransformation.CloseToTranslation[transform, identity])) THEN CachedDrawBorder[dc, slice, transform, trajTransformParts] ELSE DrawBorderAux[slice, trajDrawParts, trajTransformParts, transform, dc, camera, editConstraints, drawEntireTraj, transformNone, transformEntireTraj]; CodeTimer.StopInt[$TrajDrawBorder, $Gargoyle]; }; Imager.DoSave[dc, DoTrajDrawBorder]; }; identity: Transformation _ ImagerTransformation.Scale[1.0]; TrajDrawParts: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: Camera, quick: BOOL] = { cpCount: NAT; seg: Segment; trajData: TrajData _ NARROW[slice.data]; trajParts: TrajParts _ NARROW[parts]; IF trajParts=NIL THEN DrawTraj[dc, slice] ELSE IF AllStrokePropsAndColorsEqual[slice] THEN DrawSingleStrokeTrajSeq[dc, slice, parts] ELSE { hiSegment: NAT _ GGTraj.HiSegment[slice]; Imager.SetStrokeJoint[dc, trajData.strokeJoint]; FOR i: INT IN [0..hiSegment] DO IF trajParts.segments[i] THEN { seg _ GGTraj.FetchSegment[slice, i]; IF seg.strokeWidth=0.0 OR seg.color=NIL THEN LOOP; Imager.SetStrokeWidth[dc, seg.strokeWidth]; Imager.SetStrokeEnd[dc, seg.strokeEnd]; Imager.SetColor[dc, seg.color]; SegMaskStroke[dc, seg]; cpCount _ seg.class.controlPointCount[seg]; FOR j: NAT IN [0..cpCount) DO IF trajParts.controlPoints[i][j] THEN GGShapes.DrawCP[dc, seg.class.controlPointGet[seg, j], camera.cpScale]; ENDLOOP; }; ENDLOOP; }; }; TrajDrawTransform: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: Camera, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints] = { TrajDrawBorder[slice, NIL, parts, transform, dc, camera, FALSE, editConstraints]; }; TrajDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: Camera, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { DoDrawFeedback: PROC = { segGen: GGModelTypes.SegmentGenerator; jointGen: GGModelTypes.JointGenerator; seg: Segment; someNormal, someHot, thisCPisHot, thisCPisSelected: BOOL; point: Point; sliceTrajData: TrajData _ NARROW[slice.data]; selectedTrajParts: TrajParts _ NARROW[selectedParts]; hotTrajParts: TrajParts _ NARROW[hotParts]; jointGen _ GGSequence.JointsInTraj[sliceTrajData]; FOR i: INT _ GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen] UNTIL i = -1 DO thisCPisHot _ hotTrajParts#NIL AND hotTrajParts.joints[i]; thisCPisSelected _ selectedTrajParts#NIL AND selectedTrajParts.joints[i]; point _ GGTraj.FetchJointPos[slice, i]; IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, point, hot, camera.cpScale]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, point, normal, camera.cpScale]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawJoint[dc, point, camera.cpScale]; ENDLOOP; someNormal _ selectedTrajParts#NIL; someHot _ hotTrajParts#NIL; segGen _ GGSequence.SegmentsInTraj[sliceTrajData]; 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 _ hotTrajParts#NIL AND hotTrajParts.controlPoints[i][j]; thisCPisSelected _ selectedTrajParts#NIL AND selectedTrajParts.controlPoints[i][j]; point _ seg.class.controlPointGet[seg, j]; IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, point, hot, camera.cpScale]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, point, normal, camera.cpScale]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, point, camera.cpScale]; ENDLOOP; }; ENDLOOP; }; trajData: TrajData _ NARROW[slice.data]; IF caretIsMoving OR dragInProgress THEN RETURN; IF selectedParts=NIL AND hotParts=NIL THEN RETURN; IF camera.quality # quality THEN Imager.DoSave[dc, DoDrawFeedback]; }; TrajDrawAttractorFeedback: PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress: BOOL, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints] = { success: BOOL _ FALSE; partType: TrajPartType; traj: Traj _ slice; seg: Segment; jointNum, segNum: NAT; [success, partType, ----, ----, jointNum , ----, ----, seg, segNum] _ GGTraj.UnpackSimpleDescriptor[slice, attractorParts]; IF NOT success THEN ERROR; -- for debugging SELECT partType FROM joint => { -- highlight the cps and joints of the two adjacent segments prev: INT _ GGTraj.PreviousSegmentNum[slice, jointNum]; previous: Segment _ GGTraj.PreviousSegment[slice, jointNum]; this: Segment _ IF jointNum <= GGTraj.HiSegment[slice] THEN GGTraj.FetchSegment[slice, jointNum] ELSE NIL; IF selectedParts=NIL THEN { IF previous#NIL THEN DrawCpsAndJoints[dc, previous, camera]; IF this#NIL THEN DrawCpsAndJoints[dc, this, camera]; } ELSE { IF this#NIL AND NOT (dragInProgress AND GGSequence.ContainsSegmentParts[NARROW[selectedParts], jointNum]) THEN DrawCpsAndJoints[dc, this, camera, GGSlice.DescriptorFromParts[traj, selectedParts], jointNum, editConstraints]; IF previous#NIL AND NOT (dragInProgress AND GGSequence.ContainsSegmentParts[NARROW[selectedParts], GGTraj.PreviousSegmentNum[traj, jointNum]]) THEN DrawCpsAndJoints[dc, previous, camera, GGSlice.DescriptorFromParts[traj, selectedParts], prev, editConstraints]; }; }; segment, controlPoint => DrawCpsAndJoints[dc, seg, camera]; ENDCASE; -- none }; TrajAttractorFeedbackBoundBox: PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress: BOOL, camera: Camera, editConstraints: EditConstraints] RETURNS [box: BoundBox] = { success: BOOL _ FALSE; partType: TrajPartType; traj: Traj _ slice; seg: Segment; jointNum, segNum: NAT; box _ GGBoundBox.NullBoundBox[]; [success, partType, ----, ----, jointNum , ----, ----, seg, segNum] _ GGTraj.UnpackSimpleDescriptor[slice, attractorParts]; IF NOT success THEN ERROR; -- for debugging SELECT partType FROM joint => { -- highlight the cps and joints of the two adjacent segments prev: INT _ GGTraj.PreviousSegmentNum[slice, jointNum]; previous: Segment _ GGTraj.PreviousSegment[slice, jointNum]; this: Segment _ IF jointNum <= GGTraj.HiSegment[slice] THEN GGTraj.FetchSegment[slice, jointNum] ELSE NIL; IF selectedParts=NIL THEN { IF previous#NIL THEN EnlargeByCpsAndJoints[box, previous, camera]; IF this#NIL THEN EnlargeByCpsAndJoints[box, this, camera]; } ELSE { IF this#NIL AND NOT (dragInProgress AND GGSequence.ContainsSegmentParts[NARROW[selectedParts], jointNum]) THEN EnlargeByCpsAndJoints[box, this, camera, GGSlice.DescriptorFromParts[traj, selectedParts], jointNum, editConstraints]; IF previous#NIL AND NOT (dragInProgress AND GGSequence.ContainsSegmentParts[NARROW[selectedParts], GGTraj.PreviousSegmentNum[traj, jointNum]]) THEN EnlargeByCpsAndJoints[box, previous, camera, GGSlice.DescriptorFromParts[traj, selectedParts], prev, editConstraints]; }; }; segment, controlPoint => EnlargeByCpsAndJoints[box, seg, camera]; ENDCASE; -- none GGBoundBox.EnlargeByOffset[box, GGModelTypes.halfJointSize*camera.cpScale +1]; }; Props: TYPE = REF PropsRec; PropsRec: TYPE = RECORD [ slice: Slice, hiSeg: NAT, cpCount: SEQUENCE len: NAT OF NAT ]; FindImagerObject: PROC [slice: Slice, trajData: TrajData] RETURNS [object: Imager.Object, displacement: Vector _ [0,0]] = { EqualPattern: PROC [p1, p2: SequenceOfReal] RETURNS [BOOL _ FALSE] = { IF p1.len#p2.len THEN RETURN[FALSE]; FOR i: NAT IN [0.. p1.len) DO IF p1[i]#p2[i] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; CompareProps: FunctionCache.CompareProc = { cachedProps: Props _ NARROW[argument]; cachedSlice: Slice _ cachedProps.slice; cachedHiSeg: NAT _ cachedProps.hiSeg; cachedTrajData: TrajData _ NARROW[cachedSlice.data]; cachedSeg, seg: Segment; epsilon: REAL = 0.001; IF cachedHiSeg # hiSeg THEN RETURN[FALSE]; IF cachedTrajData.strokeJoint # trajData.strokeJoint THEN RETURN[FALSE]; FOR i: NAT IN [0..cachedHiSeg] DO cachedSeg _ GGTraj.FetchSegment[cachedSlice, i]; seg _ GGTraj.FetchSegment[slice, i]; IF seg.class # cachedSeg.class THEN RETURN[FALSE]; IF NOT GGSegment.SameShape[seg, cachedSeg] THEN RETURN[FALSE]; displacement _ Vectors2d.Sub[seg.lo, cachedSeg.lo]; IF NOT ClosePoints[Vectors2d.Add[cachedSeg.hi, displacement], seg.hi] THEN RETURN[FALSE]; IF cachedProps.cpCount[i] # seg.class.controlPointCount[seg] THEN RETURN[FALSE]; FOR j: NAT IN [0..cachedProps.cpCount[i]) DO IF NOT ClosePoints[Vectors2d.Add[cachedSeg.class.controlPointGet[cachedSeg, j], displacement], seg.class.controlPointGet[seg, j]] THEN RETURN[FALSE]; ENDLOOP; IF ABS[cachedSeg.strokeWidth-seg.strokeWidth] > epsilon THEN RETURN[FALSE]; IF cachedSeg.strokeEnd#seg.strokeEnd THEN RETURN[FALSE]; IF NOT GGCoreOps.EquivalentColors[cachedSeg.color, seg.color] THEN RETURN[FALSE]; IF cachedSeg.dashed#seg.dashed THEN RETURN[FALSE]; IF cachedSeg.dashed AND ( cachedSeg.offset#seg.offset OR cachedSeg.length#seg.length OR NOT EqualPattern[cachedSeg.pattern, seg.pattern]) THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; value: FunctionCache.Range; ok: BOOL; hiSeg: NAT _ GGTraj.HiSegment[slice]; [value, ok] _ FunctionCache.Lookup[x: cache, compare: CompareProps, clientID: $GGTrajObject]; RETURN [IF ok THEN NARROW[value] ELSE NIL, displacement]; }; ClosePoints: PROC [p1, p2: Point] RETURNS [BOOL] = { epsilon: REAL = 0.01; -- control points within 0.01 pixels should be plenty close enough RETURN[ABS[p1.x-p2.x] < epsilon AND ABS[p1.y-p2.y] < epsilon]; }; CreateProps: PROC [slice: Slice, trajData: TrajData] RETURNS [props: Props] = { hiSeg: NAT _ GGTraj.HiSegment[slice]; seg: Segment; props _ NEW[PropsRec[hiSeg+1]]; props.slice _ TrajCopy[slice].first; props.hiSeg _ hiSeg; FOR i: NAT IN [0..hiSeg] DO seg _ GGTraj.FetchSegment[slice, i]; props.cpCount[i] _ seg.class.controlPointCount[seg]; ENDLOOP; }; CachedDrawBorder: PROC [dc: Imager.Context, slice: Slice, viewView: Transformation, trajTransformParts: TrajParts] = { OPEN ImagerTransformation; -- see Transform and InverseTransform object: Imager.Object; position: Imager.VEC; trajProps: Props; trajData: TrajData _ NARROW[slice.data]; IF GGMUserProfile.GetTurboOn[] THEN { [object, position] _ FindImagerObject[slice, trajData]; IF object = NIL THEN { -- put traj in cache clipR: Imager.Rectangle _ GGBoundBox.RectangleFromBoundBox[TrajGetBoundBox[slice, NIL]]; props: Props _ CreateProps[slice, trajData]; -- props of this Imager.Object object _ NEW[Imager.ObjectRep _ [draw: TrajDrawObject, clip: clipR, data: props]]; position _ [0,0]; FunctionCache.Insert[cache, props, object, 60, $GGTrajObject]; }; trajProps _ NARROW[object.data]; IF viewView # NIL THEN Imager.ConcatT[dc, viewView]; Imager.DrawObject[context: dc, object: object, interactive: TRUE, position: position]; } ELSE DrawBorderAux[slice, NIL, trajTransformParts, viewView, dc, NIL, none, TRUE, viewView = NIL, viewView # NIL]; }; TrajDrawObject: PROC [self: Imager.Object, context: Imager.Context] = { trajProps: Props _ NARROW[self.data]; DrawBorderAux[trajProps.slice, NIL, NIL, NIL, context, NIL, none, TRUE, TRUE, FALSE]; }; DrawBorderAux: PROC [slice: Slice, trajDrawParts: TrajParts, trajTransformParts: TrajParts, transform: Transformation, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints _ none, drawEntireTraj, transformNone, transformEntireTraj: BOOL] = { hiSegment: NAT _ GGTraj.HiSegment[slice]; IF drawEntireTraj AND AllStrokePropsAndColorsEqual[slice] THEN { TrajDrawBorderSingleStroke[slice, trajTransformParts, transform, dc, editConstraints]; IF transform#NIL THEN DrawRubberControlPoints[dc, trajTransformParts, slice, transform, camera]; RETURN; }; FOR i: INT IN [0..hiSegment] DO IF drawEntireTraj OR trajDrawParts.segments[i] THEN { -- draw this segment seg: Segment _ GGTraj.FetchSegment[slice, i]; IF seg.strokeWidth=0.0 OR seg.color=NIL THEN LOOP; Imager.SetStrokeJoint[dc, bevel]; -- some contexts require that this be set to something Imager.SetStrokeEnd[dc, seg.strokeEnd]; Imager.SetStrokeWidth[dc, seg.strokeWidth]; Imager.SetColor[dc, seg.color]; SELECT TRUE FROM transformNone => MaskStrokeTransform[ dc: dc, seg: seg, transform: transform, entire: FALSE, lo: FALSE, hi: FALSE, controlPoints: NIL, -- nothing transformed slice: slice, transformParts: trajTransformParts, segNum: i, camera: camera, editConstraints: editConstraints]; transformEntireTraj => MaskStrokeTransform[ dc: dc, seg: seg, transform: transform, entire: TRUE, lo: TRUE, hi: TRUE, controlPoints: NIL, -- everything transformed slice: slice, transformParts: trajTransformParts, segNum: i, camera: camera, editConstraints: editConstraints]; ENDCASE => MaskStrokeTransform[ dc: dc, seg: seg, transform: transform, entire: trajTransformParts.segments[i], lo: trajTransformParts.joints[i], hi: trajTransformParts.joints[GGTraj.FollowingJoint[slice, i]], controlPoints: trajTransformParts.controlPoints[i], slice: slice, transformParts: trajTransformParts, segNum: i, camera: camera, editConstraints: editConstraints]; }; ENDLOOP; }; TrajDrawBorderSingleStroke: PROC [slice: Slice, trajParts: TrajParts, transform: Transformation, dc: Imager.Context, editConstraints: EditConstraints] = { BuildPathNoTransform: Imager.PathProc = { seg: Segment; hiSegment: NAT _ GGTraj.HiSegment[slice]; firstPoint: Point _ GGTraj.FetchJointPos[slice, 0]; moveTo[firstPoint]; FOR i: INT IN [0..hiSegment] DO seg _ GGTraj.FetchSegment[slice, i]; seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; BuildPathTransform: Imager.PathProc = { seg: Segment; entire, lo, hi: BOOL _ FALSE; controlPoints: BitVector; firstPoint: Point; hiSegment: NAT _ GGTraj.HiSegment[slice]; entire _ trajParts=NIL OR trajParts.segments[0]; lo _ trajParts=NIL OR trajParts.joints[0]; firstPoint _ GGTraj.FetchJointPos[slice, 0]; IF entire OR lo THEN firstPoint _ ImagerTransformation.Transform[transform, firstPoint]; moveTo[firstPoint]; FOR i: INT IN [0..hiSegment] DO seg _ GGTraj.FetchSegment[slice, i]; entire _ trajParts=NIL OR trajParts.segments[i]; lo _ trajParts=NIL OR trajParts.joints[i]; hi _ trajParts=NIL OR trajParts.joints[GGTraj.FollowingJoint[slice, i]]; controlPoints _ trajParts.controlPoints[i]; IF seg.class.type = $Bezier AND editConstraints # none THEN { BuildConstrainedPathSeg[slice, trajParts, i, seg, transform, entire, lo, hi, controlPoints, editConstraints, lineTo, curveTo, conicTo, arcTo]; } ELSE { seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo]; }; ENDLOOP; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = {RETURN[pattern[i]];}; trajData: TrajData _ NARROW[slice.data]; firstSeg: Segment _ GGTraj.FetchSegment[slice, 0]; strokeWidth: REAL _ firstSeg.strokeWidth; pattern: SequenceOfReal _ firstSeg.pattern; IF strokeWidth # 0.0 AND firstSeg.color # NIL THEN { Imager.SetColor[dc, firstSeg.color]; Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetStrokeEnd[dc, firstSeg.strokeEnd]; Imager.SetStrokeJoint[dc, trajData.strokeJoint]; IF firstSeg.dashed THEN Imager.MaskDashedStroke[dc, IF transform=NIL THEN BuildPathNoTransform ELSE BuildPathTransform, pattern.len, PatternProc, firstSeg.offset, firstSeg.length] ELSE Imager.MaskStroke[dc, IF transform=NIL THEN BuildPathNoTransform ELSE BuildPathTransform, trajData.role=fence OR trajData.role=hole]; }; }; -- end TrajDrawBorderSingleStroke DrawRubberControlPoints: PROC [dc: Imager.Context, trajParts: TrajParts, slice: Slice, transform: ImagerTransformation.Transformation, camera: Camera] = { controlPoints: BitVector; seg: Segment; cpCount: NAT; cPointsInSeq: CPSequence; cPointsInSeg: SegCPSequence; hiSegment: NAT _ GGTraj.HiSegment[slice]; drawForTraj, drawForSeg: BOOL _ FALSE; IF trajParts = NIL THEN RETURN; FOR i: INT IN [0..hiSegment] DO seg _ GGTraj.FetchSegment[slice, i]; controlPoints _ trajParts.controlPoints[i]; cpCount _ seg.class.controlPointCount[seg]; IF NOT trajParts.segments[i] THEN { FOR j: NAT IN [0..cpCount) DO IF controlPoints[j] THEN { IF NOT drawForTraj THEN cPointsInSeq _ NEW[CPSequenceObj[GGTraj.HiSegment[slice] + 1]]; IF NOT drawForSeg THEN cPointsInSeg _ NEW[SegCPSequenceObj[cpCount]]; cPointsInSeg[j].cPoint _ ImagerTransformation.Transform[transform, seg.class.controlPointGet[seg, j]]; cPointsInSeg[j].show _ TRUE; drawForTraj _ drawForSeg _ TRUE; } ELSE IF drawForSeg THEN cPointsInSeg[j].show _ FALSE; ENDLOOP; IF drawForSeg THEN { cPointsInSeq[i] _ cPointsInSeg; drawForSeg _ FALSE; }; }; ENDLOOP; IF cPointsInSeq#NIL THEN { FOR i: NAT IN [0..cPointsInSeq.len) DO IF NOT trajParts.segments[i] AND NOT cPointsInSeq[i]=NIL THEN { FOR j: NAT IN [0..cPointsInSeq[i].len) DO cPoint: Point _ cPointsInSeq[i][j].cPoint; IF cPointsInSeq[i][j].show THEN GGShapes.DrawCP[dc, cPoint, camera.cpScale]; ENDLOOP; }; ENDLOOP; }; }; -- end of DrawRubberControlPoints <> DrawSingleStrokeTraj: PROC [dc: Imager.Context, slice: Slice] = { BuildPath: Imager.PathProc = { seg: Segment; hiSegment: NAT _ GGTraj.HiSegment[slice]; firstPoint: Point _ GGTraj.FetchJointPos[slice, 0]; moveTo[firstPoint]; FOR i: INT IN [0..hiSegment] DO seg _ GGTraj.FetchSegment[slice, i]; seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; trajData: TrajData _ NARROW[slice.data]; firstSeg: Segment _ GGTraj.FetchSegment[slice, 0]; strokeWidth: REAL _ firstSeg.strokeWidth; pattern: SequenceOfReal _ firstSeg.pattern; IF strokeWidth=0.0 OR firstSeg.color=NIL THEN RETURN; Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetStrokeEnd[dc, firstSeg.strokeEnd]; Imager.SetStrokeJoint[dc, trajData.strokeJoint]; Imager.SetColor[dc, firstSeg.color]; IF firstSeg.dashed THEN Imager.MaskDashedStroke[dc, BuildPath, pattern.len, PatternProc, firstSeg.offset, firstSeg.length] ELSE Imager.MaskStroke[dc, BuildPath, trajData.role = fence OR trajData.role = hole]; }; -- end DrawSingleStrokeTraj DrawTraj: PROC [dc: Imager.Context, slice: Slice] = { DoDrawTraj: PROC [dc: Imager.Context] = { seg: Segment; hiSegment: NAT _ GGTraj.HiSegment[slice]; Imager.SetStrokeJoint[dc, trajData.strokeJoint]; FOR i: INT IN [0..hiSegment] DO seg _ GGTraj.FetchSegment[slice, i]; IF seg.strokeWidth#0.0 AND seg.color#NIL THEN { Imager.SetStrokeWidth[dc, seg.strokeWidth]; Imager.SetStrokeEnd[dc, seg.strokeEnd]; Imager.SetColor[dc, seg.color]; MaskStroke[dc, seg]; }; ENDLOOP; }; trajData: TrajData _ NARROW[slice.data]; IF AllStrokePropsAndColorsEqual[slice] THEN DrawSingleStrokeTraj[dc, slice] ELSE DoDrawTraj[dc]; -- why no DoSave here ?? }; DrawSingleStrokeTrajSeq: PROC [dc: Imager.Context, slice: Slice, parts: SliceParts _ NIL] = { BuildPath: Imager.PathProc = { hiSegment: NAT _ GGTraj.HiSegment[slice]; FOR i: INT IN [0..hiSegment] DO IF trajParts=NIL OR trajParts.segments[i] THEN { seg: Segment _ GGTraj.FetchSegment[slice, i]; moveTo[seg.lo]; seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo]; }; ENDLOOP; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; trajData: TrajData _ NARROW[slice.data]; trajParts: TrajParts _ NARROW[parts]; firstSeg: Segment _ GGTraj.FetchSegment[slice, 0]; pattern: SequenceOfReal _ firstSeg.pattern; IF firstSeg.strokeWidth=0.0 OR firstSeg.color=NIL THEN RETURN; Imager.SetStrokeWidth[dc, firstSeg.strokeWidth]; Imager.SetStrokeEnd[dc, firstSeg.strokeEnd]; Imager.SetStrokeJoint[dc, trajData.strokeJoint]; Imager.SetColor[dc, firstSeg.color]; IF firstSeg.dashed THEN Imager.MaskDashedStroke[dc, BuildPath, pattern.len, PatternProc, firstSeg.offset, firstSeg.length] ELSE Imager.MaskStroke[dc, BuildPath, trajData.role = fence OR trajData.role = hole]; }; DrawCpsAndJoints: PROC [dc: Imager.Context, seg: Segment, camera: Camera, selSeq: Sequence _ NIL, segNum: NAT _ 999, editConstraints: EditConstraints _ none] = { point: Point; GGShapes.DrawJoint[dc, seg.lo, camera.cpScale]; GGShapes.DrawJoint[dc, seg.hi, camera.cpScale]; FOR j:INT IN [0..seg.class.controlPointCount[seg]) DO IF editConstraints = none OR NOT GGSequence.IsConstrained[selSeq, segNum, j, editConstraints] THEN { point _ seg.class.controlPointGet[seg, j]; GGShapes.DrawCP[dc, point, camera.cpScale]; }; ENDLOOP; }; EnlargeByCpsAndJoints: PROC [box: BoundBox, seg: Segment, camera: Camera, selSeq: Sequence _ NIL, segNum: NAT _ 999, editConstraints: EditConstraints _ none] = { point: Point; GGBoundBox.EnlargeByPoint[box, seg.lo]; GGBoundBox.EnlargeByPoint[box, seg.hi]; FOR j: INT IN [0..seg.class.controlPointCount[seg]) DO IF editConstraints = none OR NOT GGSequence.IsConstrained[selSeq, segNum, j, editConstraints] THEN { point _ seg.class.controlPointGet[seg, j]; GGBoundBox.EnlargeByPoint[box, point]; }; ENDLOOP; }; TrajSaveSelections: PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] = { trajParts: TrajParts _ NARROW[parts]; IF trajParts=NIL THEN GGTraj.ClearSelection[slice, selectClass] ELSE GGTraj.SaveSelectionInParts[slice, trajParts, selectClass]; }; TrajRemakeSelections: PROC [slice: Slice, selectClass: SelectionClass] RETURNS [parts: SliceParts] = { RETURN[GGTraj.RemakeSelection[slice, selectClass]]; }; DrawConstrained: PROC [dc: Imager.Context, slice: Slice, transformParts: SliceParts, segNum: NAT, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi: BOOL, controlPoints: BitVector, editConstraints: EditConstraints, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc] RETURNS [cPointsInSeg: SegCPSequence] = { IF entire THEN seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo] ELSE { selSeq: TrajParts _ NARROW[transformParts]; dirVec: Vector; newJoint: Point; distance: REAL; partialTransform, ctrlLoTrans, ctrlHiTrans: Transformation; ctrlPoint: Point; ctrlPoints: BitVector _ NEW[GGBasicTypes.BitVectorObj[2]]; ctrlPoints[0] _ controlPoints[0]; ctrlPoints[1] _ controlPoints[1]; ctrlLoTrans _ IF ctrlPoints[0] THEN transform ELSE idTransform; ctrlHiTrans _ IF ctrlPoints[1] THEN transform ELSE idTransform; IF lo AND NOT ctrlPoints[0] THEN { -- Low side of segment ctrlPoint _ seg.class.controlPointGet[seg, 0]; ctrlPoints[0] _ TRUE; newJoint _ ImagerTransformation.Transform[transform, seg.lo]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, newJoint]]; dirVec _ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint]; IF editConstraints = tangent THEN { distance _ Vectors2d.Distance[newJoint, ctrlPoint]; IF dirVec # [0,0] THEN dirVec _ Vectors2d.Normalize[dirVec]; -- use exception ctrlLoTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]]; } ELSE ctrlLoTrans _ ImagerTransformation.PostTranslate[partialTransform, dirVec]; }; IF hi AND NOT ctrlPoints[1] THEN { -- High side of Segment ctrlPoint _ seg.class.controlPointGet[seg, 1]; ctrlPoints[1] _ TRUE; newJoint _ ImagerTransformation.Transform[transform, seg.hi]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, newJoint]]; dirVec _ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint]; IF editConstraints = tangent THEN { distance _ Vectors2d.Distance[newJoint, ctrlPoint]; IF dirVec # [0,0] THEN dirVec _ Vectors2d.Normalize[dirVec]; -- use exception ctrlHiTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]]; } ELSE ctrlHiTrans _ ImagerTransformation.PostTranslate[partialTransform, dirVec]; }; IF NOT ctrlPoints[0] AND NOT lo THEN { prevSegNum: INT _ GGTraj.PreviousSegmentNum[slice, segNum]; IF prevSegNum # -1 AND GGTraj.FetchSegment[slice, prevSegNum].class.type = $Bezier THEN { IF selSeq.controlPoints[prevSegNum][1] THEN { angle: REAL; transformedPoint: Point; otherCtrlPoint: Point _ seg.class.controlPointGet[GGTraj.FetchSegment[slice, prevSegNum], 1]; otherDirVector: Vector _ Vectors2d.VectorFromPoints[seg.lo, otherCtrlPoint]; ctrlPoint _ seg.class.controlPointGet[seg, 0]; dirVec _ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint]; distance _ Vectors2d.Magnitude[dirVec]; transformedPoint _ ImagerTransformation.Transform[transform, otherCtrlPoint]; angle _ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.lo, transformedPoint]]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.lo]]; ctrlPoints[0] _ TRUE; IF editConstraints = tangent THEN { IF transformedPoint # seg.lo AND otherDirVector # [0,0] THEN { --Don't flip based on other cps 0 length vec. ctrlLoTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; }; -- Else just use identity. } ELSE { -- length IF otherDirVector # [0,0] THEN { distance _ RealFns.SqRt[Vectors2d.DistanceSquared[seg.lo, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance; ctrlLoTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; }; -- Else just use identity. }; }; }; }; IF NOT ctrlPoints[1] AND NOT hi THEN { nextSegNum: INT _ GGTraj.FollowingSegmentNum[slice, segNum]; IF nextSegNum # -1 AND GGTraj.FetchSegment[slice, nextSegNum].class.type = $Bezier THEN { IF selSeq.controlPoints[nextSegNum][0] THEN { angle: REAL; transformedPoint: Point; otherCtrlPoint: Point _ seg.class.controlPointGet[GGTraj.FetchSegment[slice, nextSegNum], 0]; otherDirVector: Vector _ Vectors2d.VectorFromPoints[seg.hi, otherCtrlPoint]; ctrlPoint _ seg.class.controlPointGet[seg, 1]; dirVec _ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint]; distance _ Vectors2d.Magnitude[dirVec]; transformedPoint _ ImagerTransformation.Transform[transform, otherCtrlPoint]; angle _ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.hi, transformedPoint]]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.hi]]; ctrlPoints[1] _ TRUE; IF editConstraints = tangent THEN { IF transformedPoint # seg.hi AND otherDirVector # [0,0] THEN { --Don't flip based on other cps 0 length vec. ctrlHiTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; }; -- Else just use identity. } ELSE { -- length IF otherDirVector # [0,0] THEN { distance _ RealFns.SqRt[Vectors2d.DistanceSquared[seg.hi, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance; ctrlHiTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; }; --Else just use identity. }; }; }; }; GGSegment.BZSpecialTransformPath[seg, transform, lo, hi, ctrlPoints, ctrlLoTrans, ctrlHiTrans, curveTo]; IF NOT (ctrlPoints[0] OR ctrlPoints[1]) THEN RETURN[NIL]; cPointsInSeg _ NEW[SegCPSequenceObj[2]]; -- For Beziers IF ctrlPoints[0] THEN { cPointsInSeg[0].cPoint _ ImagerTransformation.Transform[ctrlLoTrans, seg.class.controlPointGet[seg, 0]]; cPointsInSeg[0].show _ TRUE; } ELSE cPointsInSeg[0].show _ FALSE; IF ctrlPoints[1] THEN { cPointsInSeg[1].cPoint _ ImagerTransformation.Transform[ctrlHiTrans, seg.class.controlPointGet[seg, 1]]; cPointsInSeg[1].show _ TRUE; } ELSE cPointsInSeg[1].show _ FALSE; RETURN[cPointsInSeg]; }; }; SegMaskStroke: 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 Imager.MaskDashedStroke[dc, MaskPath, pattern.len, PatternProc, seg.offset, seg.length] ELSE Imager.MaskStroke[dc, MaskPath, FALSE]; }; 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 Imager.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, slice: Slice, transformParts: TrajParts, segNum: NAT, camera: Camera, editConstraints: EditConstraints] = { MaskPath: Imager.PathProc = { loPoint: Point; cpCount: NAT _ seg.class.controlPointCount[seg]; loPoint _ IF entire OR lo THEN ImagerTransformation.Transform[transform, seg.lo] ELSE seg.lo; moveTo[loPoint]; IF seg.class.type=$Bezier AND editConstraints#none THEN [] _ DrawConstrained[dc, slice, transformParts, segNum, seg, transform, entire, lo, hi, controlPoints, editConstraints, lineTo, curveTo, conicTo, arcTo] ELSE seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo]; IF transform#NIL AND NOT entire THEN { -- might be rubber-banding FOR j: NAT IN [0..cpCount) DO IF controlPoints=NIL OR controlPoints[j] THEN { -- it is rubber-banding cPoint: Point _ ImagerTransformation.Transform[transform, seg.class.controlPointGet[seg, j]]; GGShapes.DrawCP[dc, cPoint, camera.cpScale]; } ENDLOOP; }; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; pattern: SequenceOfReal _ seg.pattern; IF seg.dashed THEN Imager.MaskDashedStroke[dc, MaskPath, pattern.len, PatternProc, seg.offset, seg.length] ELSE Imager.MaskStroke[dc, MaskPath, FALSE]; }; AllStrokePropsAndColorsEqual: PROC [slice: Slice] RETURNS [BOOL _ FALSE] = { seg: Segment; firstSeg: Segment _ GGTraj.FetchSegment[slice, 0]; width: REAL _ firstSeg.strokeWidth; end: StrokeEnd _ firstSeg.strokeEnd; color: Color _ firstSeg.color; dashed: BOOL _ firstSeg.dashed; pattern: SequenceOfReal _ firstSeg.pattern; offset: REAL _ firstSeg.offset; length: REAL _ firstSeg.length; hiSegment: NAT _ GGTraj.HiSegment[slice]; FOR i: INT IN [1..hiSegment] DO seg _ GGTraj.FetchSegment[slice, i]; IF seg.dashed # dashed THEN RETURN[FALSE]; IF seg.strokeEnd # end THEN RETURN[FALSE]; IF seg.strokeWidth # width THEN RETURN[FALSE]; IF NOT GGCoreOps.EquivalentColors[color, seg.color] THEN RETURN[FALSE]; IF NOT dashed THEN LOOP; IF seg.offset # offset THEN RETURN[FALSE]; IF seg.length # length THEN RETURN[FALSE]; IF NOT GGUtility.EquivalentPatterns[seg.pattern, pattern] THEN RETURN[FALSE]; REPEAT FINISHED => RETURN[TRUE]; ENDLOOP; }; SegmentSelected: PROC [segNum: NAT, selectedSeq: TrajParts] RETURNS [BOOL _ FALSE] = { RETURN[selectedSeq.segments[segNum]]; }; TrajTransform: PROC [slice: Slice, parts: SliceParts _ NIL, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints, history: HistoryEvent] = { joint: Joint; seg: Segment; trajData: TrajData _ NARROW[slice.data]; trajParts: TrajParts _ NARROW[parts]; hiSegment: NAT _ GGTraj.HiSegment[slice]; hiJoint: NAT _ GGTraj.HiJoint[slice]; SELECT trajData.role FROM open => TransformSequenceOpen[slice, parts, transform, editConstraints]; fence, hole => TransformSequenceClosed[slice, parts, transform, editConstraints]; ENDCASE => ERROR; FOR i: NAT IN [0..hiJoint] DO IF trajParts=NIL OR trajParts.joints[i] THEN { joint _ NARROW[Rosary.Fetch[trajData.joints, i]]; joint.point _ GGTransform.Transform[transform, joint.point]; }; ENDLOOP; TrajShapeChangeNotify[slice]; IF trajParts = NIL THEN RETURN; FOR i: NAT IN [0..hiSegment] DO cpCount: NAT _ trajParts.controlPoints[i].len; seg _ NARROW[Rosary.Fetch[trajData.segments, i]]; FOR j: NAT IN [0..cpCount) DO IF trajParts.controlPoints[i][j] AND NOT trajParts.segments[i] THEN { seg.class.controlPointMoved[seg, transform, j]; }; ENDLOOP; ENDLOOP; }; TransformSequenceOpen: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints] = { trajParts: TrajParts _ NARROW[parts]; seg: Segment; hiSegment: NAT _ GGTraj.HiSegment[slice]; FOR i: NAT IN [0..hiSegment] DO seg _ GGTraj.FetchSegment[slice, i]; IF trajParts=NIL OR trajParts.segments[i] THEN { GGSegment.TransformSegment[seg, transform]; } ELSE { IF seg.class.type = $Bezier AND editConstraints # none THEN { TransformConstrained[slice, parts, i, seg, transform, editConstraints]; } ELSE { IF trajParts.joints[i] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]]; IF trajParts.joints[i+1] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]]; }; }; ENDLOOP; }; TransformSequenceClosed: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints] = { trajParts: TrajParts _ NARROW[parts]; seg: Segment; hiSegment: NAT _ GGTraj.HiSegment[slice]; FOR i: NAT IN [0..hiSegment) DO seg _ GGTraj.FetchSegment[slice, i]; IF trajParts=NIL OR trajParts.segments[i] THEN { GGSegment.TransformSegment[seg, transform]; } ELSE { IF seg.class.type = $Bezier AND editConstraints # none THEN { TransformConstrained[slice, parts, i, seg, transform, editConstraints]; -- Same as open since not End. } ELSE { IF trajParts.joints[i] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]]; IF trajParts.joints[i+1] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]]; }; }; ENDLOOP; seg _ GGTraj.FetchSegment[slice, hiSegment]; IF trajParts=NIL OR trajParts.segments[hiSegment] THEN { GGSegment.TransformSegment[seg, transform]; } ELSE { IF seg.class.type = $Bezier AND editConstraints # none THEN { TransformConstrained[slice, parts, hiSegment, seg, transform, editConstraints]; -- Same as open } ELSE { IF trajParts.joints[hiSegment] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]]; IF trajParts.joints[0] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]]; }; }; }; TransformConstrained: PROC [slice: Slice, parts: SliceParts, segNum: NAT, seg: Segment, transform:ImagerTransformation.Transformation, editConstraints: GGModelTypes.EditConstraints] = { nextJointNum: NAT; dirVec: Vector; distance: REAL; ctrlPoint: Point; ctrlTrans, partialTransform: ImagerTransformation.Transformation; selSeq: TrajParts _ NARROW[parts]; IF selSeq.joints[segNum] AND NOT selSeq.controlPoints[segNum][0] THEN { -- Low side of segment ctrlPoint _ seg.class.controlPointGet[seg, 0]; dirVec _ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint]; GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.lo]]; IF editConstraints = tangent THEN { -- ??Should this really behave differently? distance _ Vectors2d.Distance[seg.lo, ctrlPoint]; IF dirVec # [0,0] THEN dirVec _ Vectors2d.Normalize[dirVec]; -- use exception ctrlTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]]; } ELSE ctrlTrans _ ImagerTransformation.PostTranslate[partialTransform, dirVec]; seg.class.controlPointMoved[seg, ctrlTrans, 0]; } ELSE { IF selSeq.controlPoints[segNum][0] THEN { IF selSeq.joints[segNum] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]]; } ELSE { --CP not selected (and not joint selected) so see if constrained movement. prevSegNum: INT _ GGTraj.PreviousSegmentNum[slice, segNum]; IF prevSegNum # -1 AND GGTraj.FetchSegment[slice, prevSegNum].class.type = $Bezier THEN { IF selSeq.controlPoints[prevSegNum][1] THEN { angle: REAL; transformedPoint: Point; otherCtrlPoint: Point _ seg.class.controlPointGet[GGTraj.FetchSegment[slice, prevSegNum], 1]; otherDirVector: Vector _ Vectors2d.VectorFromPoints[seg.lo, otherCtrlPoint]; ctrlPoint _ seg.class.controlPointGet[seg, 0]; dirVec _ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint]; distance _ Vectors2d.Magnitude[dirVec]; transformedPoint _ ImagerTransformation.Transform[transform, otherCtrlPoint]; angle _ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.lo, transformedPoint]]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.lo]]; IF editConstraints = tangent THEN { IF transformedPoint # seg.lo AND otherDirVector # [0,0] THEN { -- Don't flip based on 0 length vec. ctrlTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; } ELSE ctrlTrans _ ImagerTransformation.Scale[1]; -- Identity. } ELSE { -- length IF otherDirVector # [0,0] THEN { distance _ RealFns.SqRt[Vectors2d.DistanceSquared[seg.lo, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance; ctrlTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; } ELSE ctrlTrans _ ImagerTransformation.Scale[1]; -- Identity. }; seg.class.controlPointMoved[seg, ctrlTrans, 0]; }; }; }; }; nextJointNum _ GGTraj.FollowingJoint[slice, segNum]; IF selSeq.joints[nextJointNum] AND NOT selSeq.controlPoints[segNum][1] THEN { -- High side of Segment ctrlPoint _ seg.class.controlPointGet[seg, 1]; dirVec _ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint]; GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.hi]]; IF editConstraints = tangent THEN { distance _ Vectors2d.Distance[seg.hi, ctrlPoint]; IF dirVec # [0,0] THEN dirVec _ Vectors2d.Normalize[dirVec]; -- use exception ctrlTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]]; } ELSE ctrlTrans _ ImagerTransformation.PostTranslate[partialTransform, dirVec]; seg.class.controlPointMoved[seg, ctrlTrans, 1]; -- For Beziers } ELSE { IF selSeq.controlPoints[segNum][1] THEN { IF selSeq.joints[nextJointNum] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]]; } ELSE { --CP not selected (and not joint selected) so see if constrained movement. nextSegNum: INT _ GGTraj.FollowingSegmentNum[slice, segNum]; IF nextSegNum # -1 AND GGTraj.FetchSegment[slice, nextSegNum].class.type = $Bezier THEN { IF selSeq.controlPoints[nextSegNum][0] THEN { angle: REAL; transformedPoint: Point; otherCtrlPoint: Point _ seg.class.controlPointGet[GGTraj.FetchSegment[slice, nextSegNum], 0]; otherDirVector: Vector _ Vectors2d.VectorFromPoints[seg.hi, otherCtrlPoint]; ctrlPoint _ seg.class.controlPointGet[seg, 1]; dirVec _ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint]; distance _ Vectors2d.Magnitude[dirVec]; transformedPoint _ ImagerTransformation.Transform[transform, otherCtrlPoint]; angle _ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.hi, transformedPoint]]; partialTransform _ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.hi]]; IF editConstraints = tangent THEN { IF transformedPoint # seg.hi AND otherDirVector # [0,0] THEN { -- Don't flip based on 0 length vec. ctrlTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; } ELSE ctrlTrans _ ImagerTransformation.Scale[1]; -- Identity } ELSE { -- length IF otherDirVector # [0,0] THEN { distance _ RealFns.SqRt[Vectors2d.DistanceSquared[seg.hi, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance; ctrlTrans _ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]]; } ELSE ctrlTrans _ ImagerTransformation.Scale[1]; -- Identity }; seg.class.controlPointMoved[seg, ctrlTrans, 1]; }; }; }; }; }; TrajIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL _ TRUE] = { trajParts: TrajParts _ NARROW[sliceD.parts]; RETURN[trajParts=NIL OR GGSequence.IsEmpty[trajParts]]; }; TrajIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL _ FALSE] = { trajParts: TrajParts _ NARROW[sliceD.parts]; RETURN[trajParts#NIL AND GGSequence.IsComplete[trajParts]]; }; TrajNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [sliceD: SliceDescriptor] = { trajParts: TrajParts; trajData: TrajData _ NARROW[slice.data]; trajHitData: TrajHitData _ NARROW[hitData]; SELECT mode FROM literal => { hitType: HitType; segNum, cpNum, jointNum: INT; [hitType, segNum, cpNum, jointNum, ----] _ GGTraj.UnpackHitData[hitData]; trajParts _ SELECT hitType FROM joint => GGSequence.CreateFromJoint[trajData, jointNum], controlPoint => GGSequence.CreateFromControlPoint[trajData, segNum, cpNum], segment => GGSequence.CreateSimpleFromSegment[trajData, segNum], ENDCASE => ERROR; }; none => { trajParts _ GGSequence.CreateEmpty[trajData]; }; slice => { trajParts _ GGSequence.CreateComplete[trajData]; }; joint => { SELECT trajHitData.hitType FROM joint => { -- hit a joint jointNum: NAT _ trajHitData.jointNum; trajParts _ GGSequence.CreateJointToJoint[trajData, jointNum, jointNum]; }; segment => { -- we are in the middle of a segment. Select nearest joint or cp success: BOOL _ FALSE; segNum, cpNum, jointNum: NAT; jointPoint, cpPoint, caretPt: Point; seg: Segment; segNum _ trajHitData.segNum; seg _ GGTraj.FetchSegment[slice, segNum]; caretPt _ trajHitData.hitPoint; jointNum _ NearestJointToHitSpot[caretPt, slice, -1, segNum, segment]; jointPoint _ GGTraj.FetchJointPos[slice, jointNum]; [cpPoint, ----, cpNum, success] _ seg.class.closestControlPoint[seg, caretPt, GGUtility.plusInfinity]; IF NOT success THEN trajParts _ GGSequence.CreateJointToJoint[trajData, jointNum, jointNum] ELSE { cpDist: REAL _ Vectors2d.DistanceSquared[cpPoint, caretPt]; jointDist: REAL _ Vectors2d.DistanceSquared[jointPoint, caretPt]; trajParts _ IF cpDist < jointDist THEN GGSequence.CreateFromControlPoint[trajData, segNum, cpNum] ELSE GGSequence.CreateJointToJoint[trajData, jointNum, jointNum]; }; }; controlPoint => { segNum, cpNum: NAT; segNum _ trajHitData.segNum; cpNum _ trajHitData.cpNum; trajParts _ GGSequence.CreateFromControlPoint[trajData, segNum, cpNum]; }; ENDCASE => ERROR; }; segment => { SELECT trajHitData.hitType FROM joint => { jointNum: NAT _ trajHitData.jointNum; this: INT _ IF jointNum>GGTraj.HiSegment[slice] THEN -1 ELSE jointNum; previous: INT _ GGTraj.PreviousSegmentNum[slice, jointNum]; trajParts _ GGSequence.CreateFromSegment[trajData, IF this#-1 THEN this ELSE previous]; }; segment, controlPoint => { trajParts _ GGSequence.CreateFromSegment[trajData, trajHitData.segNum]; }; ENDCASE => ERROR; }; traj, topLevel => { trajParts _ GGSequence.CreateComplete[trajData]; }; ENDCASE => ERROR; sliceD _ GGSlice.DescriptorFromParts[slice, trajParts]; }; NearestJointToHitSpot: PROC [caretPt: Point, traj: Traj, hitJointNum: INT, hitSegNum: INT, hitType: HitType] RETURNS [jointNum: NAT] = { nextNum: NAT; p1, p2: Point; d1, d2: REAL; SELECT hitType FROM joint => { jointNum _ hitJointNum; }; segment, controlPoint => { nextNum _ GGTraj.FollowingJoint[traj, hitSegNum]; p1 _ GGTraj.FetchJointPos[traj, hitSegNum]; p2 _ GGTraj.FetchJointPos[traj, nextNum]; d1 _ Vectors2d.DistanceSquared[p1, caretPt]; d2 _ Vectors2d.DistanceSquared[p2, caretPt]; IF d1 <= d2 THEN jointNum _ hitSegNum ELSE jointNum _ nextNum; }; ENDCASE => ERROR Problem[msg: "NearestJointToHitSpot NYI"]; }; -- end of NearestJointToHitSpot TrajUnionParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aPlusB: SliceDescriptor] = { aPlusB _ GGSequence.Union[partsA, partsB]; }; TrajDiffParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aMinusB: SliceDescriptor] = { aMinusB _ GGSequence.Difference[partsA, partsB]; }; TrajMovingParts: PROC [slice: Slice, selectedParts: SliceParts, editConstraints: EditConstraints, bezierDrag: GGModelTypes.BezierDragRecord] RETURNS [background, overlay, rubber, drag: SliceDescriptor] = { bkgdParts, overParts, rubberParts, dragParts: TrajParts; [bkgdParts, overParts, rubberParts, dragParts] _ GGSequence.TrajMovingParts[slice, selectedParts, editConstraints, bezierDrag]; background _ GGSlice.DescriptorFromParts[slice, bkgdParts]; overlay _ GGSlice.DescriptorFromParts[slice, overParts]; rubber _ GGSlice.DescriptorFromParts[slice, rubberParts]; drag _ GGSlice.DescriptorFromParts[slice, dragParts]; }; TrajAugmentParts: PROC [sliceD: SliceDescriptor, selectClass: SelectionClass] RETURNS [more: SliceDescriptor] = { trajParts: TrajParts _ NARROW[sliceD.parts]; IF selectClass = normal THEN GGSequence.FillInJoints[trajParts]; GGSequence.FillInControlPoints[trajParts]; more _ GGSlice.DescriptorFromParts[sliceD.slice, trajParts]; }; TrajAlterParts: PROC [sliceD: SliceDescriptor, action: ATOM] RETURNS [alteredD: SliceDescriptor] = { slice: Slice _ sliceD.slice; trajData: TrajData _ NARROW[slice.data]; trajParts: TrajParts _ NARROW[sliceD.parts]; isACP: BOOL _ FALSE; jointNum, cpNum, segNum: INT _ 999; SELECT action FROM $Forward, $Backward => { IF TrajIsCompleteParts[sliceD] THEN RETURN[NIL]; IF GGSequence.CountSegmentsInSequence[trajData, trajParts]=0 THEN [isACP, segNum, cpNum, jointNum] _ GGSequence.UnpackOnePointSequence[trajParts] ELSE segNum _ GGSequence.UnpackOneSegmentSequence[trajParts]; SELECT TRUE FROM jointNum#999 => { IF action = $Forward THEN jointNum _ GGTraj.FollowingJoint[slice, jointNum] ELSE jointNum _ PreviousJoint[slice, jointNum]; alteredD _ IF jointNum = -1 THEN NIL ELSE GGSlice.DescriptorFromParts[slice, GGSequence.CreateFromJoint[trajData, jointNum]]; }; isACP => { alteredD _ sliceD; -- Don't walk for now }; segNum#999 => { IF action = $Forward THEN segNum _ GGTraj.FollowingSegmentNum[slice, segNum] ELSE segNum _ GGTraj.PreviousSegmentNum[slice, segNum]; alteredD _ IF segNum = -1 THEN NIL ELSE GGSlice.DescriptorFromParts[slice, GGSequence.CreateFromSegment[trajData, segNum]]; }; ENDCASE => ERROR; }; $Grow => RETURN[TrajNewParts[sliceD.slice, NIL, slice]]; $ShrinkForward, $ShrinkBackward => RETURN[NIL]; ENDCASE => ERROR; }; PreviousJoint: PROC [slice: Slice, index: INT] RETURNS [nextIndex: INT] = { trajData: TrajData _ NARROW[slice.data]; SELECT trajData.role FROM open => nextIndex _ index - 1; fence, hole => nextIndex _ (index + trajData.segCount -1) MOD trajData.segCount; ENDCASE => ERROR; }; TrajSetSelectedFields: PROC [sliceD: SliceDescriptor, selected: BOOL, selectClass: SelectionClass] = { joint: Joint; jointGen: JointGenerator; segGen: SegmentGenerator; trajParts: TrajParts _ NARROW[sliceD.parts]; trajData: TrajData _ NARROW[sliceD.slice.data]; SELECT selectClass FROM normal => trajData.selectedInPart.normal _ selected; hot => trajData.selectedInPart.hot _ selected; active => trajData.selectedInPart.active _ selected; match => trajData.selectedInPart.match _ selected; ENDCASE; jointGen _ GGSequence.JointsInSequence[trajParts]; FOR jointNum: INT _ GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen] UNTIL jointNum = -1 DO joint _ GGTraj.FetchJoint[sliceD.slice, jointNum]; SELECT selectClass FROM normal => joint.TselectedInFull.normal _ selected; hot => joint.TselectedInFull.hot _ selected; active => joint.TselectedInFull.active _ selected; match => joint.TselectedInFull.match _ selected; ENDCASE; ENDLOOP; segGen _ GGSequence.SegmentsInSequence[trajData, trajParts]; FOR seg: Segment _ GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen] UNTIL seg = NIL DO SELECT selectClass FROM normal => seg.TselectedInFull.normal _ selected; hot => seg.TselectedInFull.hot _ selected; active => seg.TselectedInFull.active _ selected; match => seg.TselectedInFull.match _ selected; ENDCASE; ENDLOOP; }; idTransform: Transformation; cache: FunctionCache.Cache; Init: PROC = { idTransform _ ImagerTransformation.Create[1,0,0,0,1,0]; -- identity; cache _ FunctionCache.Create[maxEntries: 100]; }; Init[]; END. NGGSliceImplD.mesa Contents: Implements some of the Traj slice class (see GGSliceImplE for more). Copyright Σ 1987, 1988, 1989 by Xerox Corporation. All rights reserved. Pier, July 17, 1991 11:25 am PDT Bier, December 2, 1991 3:35 pm PST Doug Wyatt, December 18, 1989 4:07:21 pm PST Traj-Only Procs Fundamentals Drawing Transforming Parts Traj Class Procs Fundamentals GGModelTypes.SliceBoundBoxProc SegmentWalkProc: TYPE = PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOL _ FALSE]; GGModelTypes.SliceBoundBoxProc SegmentWalkProc: TYPE = PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOL _ FALSE]; GGModelTypes.SliceCopyProc 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. copy _ CopyJoint[oldJoint]; GGModelTypes.SliceRestoreProc from and to must be identically structured slices, probably because from was made as a copy of to. Restore the contents of from, getting all non-REF values from to. Good luck. ASSERT: trajDatas can be copied without following REF chains to.nullDescriptor is always valid to.fullDescriptor is unused Drawing This does some extra work which would not be needed if the entire draw sequence routine were rewritten to use intersegment constraints Later should make more general with routines in the different segment classes that allow one to call for particular constraint parameters suited to the type. Class Procs for Drawing IF seg.class.type=$Bezier AND editConstraints#none THEN BuildConstrainedPathSeg[seg, index, FALSE, FALSE, FALSE, NIL] ELSE seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo]; Draw the border of the trajectory. Because the CPFeedback plane is reserved for stationary control points, we draw moving control points here. Joints aren't drawn. GGModelTypes.SliceDrawPartsProc GGModelTypes.SliceDrawTransformProc This routine is not finished. Should also draw the fill color (since this routine is only called when a Traj is top level). GGModelTypes.SliceDrawSelectionFeedbackProc Draw the joints. Draw the control points. If the selected attractor sequence contains either previous or next and dragInProgress, don't draw feedback If the selected attractor sequence contains either previous or next and dragInProgress, don't draw feedback The Imager Object Cache Check segment types Check geometric properties Check style properties Auxiliary Procs for Drawing Move to first joint. Build the segment paths. Still needs to be updated to draw moving control points. Draw any of the moving control points of rubber-banding segments. Figure out which control points of each segment to draw. Draw the rubber-banding control points. Perhaps these CPs should be drawn transparent. 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). GGModelTypes.SliceSaveSelectionsProc GGModelTypes.SliceRemakeSelectionsProc This does some extra work which would not be needed if the entire draw sequence routine were rewritten to use intersegment constraits Later should make more general with routines in the different segment classes that allow one to call for particular constraint parameters suited to the type. [moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] Draw the segment, transforming those parts described by entire, lo, hi, and controlPoints. If the segment is being rubber-banded, draw its moving control points. Transforming GGModelTypes.SliceTransformProc Update CPs moved explicitly. 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.class.controlPointMoved[seg, transform, 0]; -- Don't move here!! Later should make more general with routines in the different segment classes that allow one to call for particular constraint parameters suited to the type. Similar code for the high side of the segment. seg.class.controlPointMoved[seg, transform, 1]; Parts GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceDifferencePartsProc GGModelTypes.SliceMovingPartsProc GGModelTypes.SliceAugmentPartsProc If selectClass = normal, then for each segment mentioned in parts, place it, its adjacent joints, and its control points in more. If selectClass # normal then just place each segment and its control points in more. This mutates the SliceDescriptor which may mean trouble. -- Bier, March 9, 1987 GGModelTypes.SliceAlterPartsProc partType: TrajPartType; [success, partType, trajData, ----, jointNum, ----, cpNum, ----, segNum] _ GGTraj.UnpackSimpleDescriptor[sliceD.slice, sliceD.parts]; Assert: selection is either one joint, one CP, or a segment and its end joints. Unfortunately, it is not a simple descriptor Joint Fields. Segment Fields. InitStats: PROC = { interval: CodeTimer.Interval; interval _ CodeTimer.CreateInterval[$CopyTraj]; CodeTimer.AddInt[interval, $Gargoyle]; interval _ CodeTimer.CreateInterval[$TrajDrawBorder]; CodeTimer.AddInt[interval, $Gargoyle]; }; InitStats[]; ΚC¬˜Icodešœ™šΟbœF™NK™HK™ K™"K™,—K™šΟk ˜ JšœΜ˜Μ—K˜šΟn œžœž˜JšžœΧ˜ήKšžœ ž˜K˜Kšœ žœ˜)Kšœ žœ˜&Kšœ žœ˜,Kšœžœ˜#Kšœžœ˜#Kšœžœ˜Kšœžœ&˜AKšœ žœ˜-Kšœžœ ˜5Kšœ žœ˜*Kšœžœ˜1Kšœ žœ˜*Kšœžœ˜#Kšœžœ˜3Kšœ žœ˜)Kšœžœ˜Kšœžœ˜Kšœ žœ˜*Kšœžœ˜!Kšœžœ˜/Kšœžœ˜3Kšœžœ"˜9Kšœžœ#˜;Kšœžœ&˜AKšœžœ!˜7Kšœžœ˜!Kšœ žœ˜+Kšœžœ˜+Kšœžœ˜1Kšœ žœ˜'Kšœžœ˜1Kšœžœ!˜7Kšœžœ$˜=Kšœžœ!˜5Kšœ žœ˜+Kšœ žœ˜'Kšœžœ˜2Kšœžœ˜!Kšœžœ"˜9Kšœ žœ˜+Kšœžœ˜1Kšœžœ ˜5Kšœ žœ˜'Kšœ žœ˜+Kšœ žœ˜)Kšœ žœ˜-Kšœžœ˜Kšœ žœ˜'Kšœ žœ˜%Kšœ žœ˜'Kšœžœ˜-Kšœ žœ˜)Kšœžœ˜/Kšœžœ˜/Kšœžœ˜3Kšœžœ˜#Kšœ žœ˜'K˜Kšœ žœ˜)Kšœ žœ˜)Kšœ žœ˜+Kšœ žœ˜+Kšœ žœ˜'K˜Kšœ žœžœ˜%šœžœžœ˜Kšœ žœžœžœ˜.—K˜—KšŸœžœžœ žœ˜;K•StartOfExpansion# -- [cluster: GGModelTypes.Cluster]˜šŸ œžœžœ˜,šžœ,žœ žœž˜EKšœžœ˜Kšœžœ˜ Kšžœ˜—K˜—šŸœžœžœ˜0šžœ,žœ žœž˜EKšœžœ˜Kšžœ˜—K˜—šŸœžœžœ˜0šžœ,žœ žœž˜EKšœžœ˜ Kšžœ˜—K˜K˜—šŸœžœžœ˜5Kšœžœ ˜(Kšœ˜Kšœžœ˜K˜K˜K˜—Ihead™šŸœžœžœžœ˜Cšœžœ˜Kšœ˜Kšœ˜Kš ™ Kšœ˜Kšœ Ÿœ˜;K˜K˜K˜Kš™Kšœ˜Kšœ˜Kšœ˜K˜!K˜1K˜1K˜9K˜#Kšœ'˜'Kš ™ Kšœ˜Kš™K˜K˜%K˜K˜K˜K˜K˜K˜Kšœ(˜(K˜—Kšœ(Οc-˜UKšœ˜—™K™—šœ ™ K™—–# -- [cluster: GGModelTypes.Cluster]šŸ œžœ˜#Kšœžœ ˜(K˜Kšœ&žœ˜*K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šŸœžœ#žœ˜SKšœ™Kš žœžœžœžœžœ  ˜Hšžœ˜šŸœ ˜3Kš œžœžœ'žœžœžœžœ™eKšœ@˜@K˜—Kšœžœ ˜(šœžœ˜%K™—Kšœ ˜ KšžœžœžœB˜SKšžœR˜VK˜šžœžœžœ .˜BKšœ( ˜EKšœ˜Kšœžœ˜K˜—Kšœ˜—Kšœ˜K˜—–# -- [cluster: GGModelTypes.Cluster]šŸœžœ#žœ˜SKšœ™Kšœžœ ˜(Kš žœžœžœžœžœ  ˜Mšžœ˜šŸœ ˜8Kš œžœžœ'žœžœžœžœ™eKšœ@˜@K˜—Kšœžœ ˜(Kšœžœ˜%K˜Kšœ ˜ KšžœžœžœG˜XKšžœW˜[—˜šžœžœžœ .˜BKšœ( ˜EKšœ˜Kšœžœ˜K˜—K˜—K˜K˜—š Ÿœžœ$žœžœžœžœ ˜XKšœ™Kšœ˜Jšœ#˜#Kš žœžœžœžœžœ ˜PKšœ žœ˜K˜)Jšœ3˜3šžœSžœ žœž˜jJšœ9˜9Jšžœ$žœ˜OKšœ5˜5Jšœžœ˜Kšžœ˜—K˜(Kšœ˜K˜—šŸœžœžœ˜8KšœΟ™ΟKšœžœ˜+Kšœžœ˜4KšœK˜KKšœžœ˜!Kšœžœ˜0KšœR˜RKšœžœ˜šŸœžœžœžœ ˜Fš Ÿœžœžœžœžœ˜NKšœ˜Kšœ žœ˜Kšœ%˜%Kšœ ˜ K˜—Kšœ6˜6K˜—šŸ œžœžœžœ ˜Dš Ÿœžœžœžœžœ˜LKšœ˜Kšœ žœ˜Kšœ™KšœžœQ˜[Kšœ ˜ K˜—Kšœ2˜2K˜—K˜)Kšœ9˜9Kšœ5˜5KšœgžœT˜ΎKšžœ žœ"˜NK˜4K˜(Kšœ˜K˜—šŸ œžœžœ˜.Kšœ™K™±Kš žœžœžœžœžœžœ˜!Kšžœžœžœ˜"Kšžœžœžœ˜"šž˜Kšœžœ ˜'Kšœžœ ˜#K˜Kš<™Kšœ3˜3Kš žœžœ@žœžœžœ˜YKšžœ;žœžœžœ˜Pšžœžœžœž˜,Kš žœžœ|žœžœžœ˜•Kšžœ˜—Kš™Kš žœžœ2žœžœžœ˜Kšžœ#žœžœžœ˜8Kš žœžœ8žœžœžœ˜QKšžœžœžœžœ˜2šžœžœ˜Kšœž˜Kšœž˜Kšžœ/žœžœžœ˜E——Kšžœ˜—Kšžœžœ˜ K˜K˜—K–j[x: FunctionCache.Cache, compare: FunctionCache.CompareProc, clientID: FunctionCache.ClientID _ NIL]˜Kšœžœ˜ Kšœžœ˜%Kšœ6 œ˜]Kš žœžœžœžœžœžœ˜9K˜K˜—šŸ œžœžœžœ˜4Kšœ žœ  B˜XKšžœžœžœžœ˜>K˜K˜—šŸ œžœ$žœ˜OKšœžœ˜%K˜ Kšœžœ˜Kšœ$˜$Kšœ˜šžœžœžœ ž˜Kšœ$˜$Kšœ4˜4Kšžœ˜—K˜K˜—K™K˜š‘œžœ(Οuœ4˜vKšžœ %˜@Kšœ˜Kšœžœ˜Kšœ˜Kšœžœ ˜(K˜šžœžœ˜%Kšœ7˜7šžœ žœžœ ˜,šœ˜Kšœ8žœ˜>—Kšœ- ˜KK˜Kšœ žœœ˜RKšœ˜Kšœ>˜>K˜—Kšœ žœ˜ Kš žœ’œžœžœ’œ˜4Kšœ<žœ˜VK˜—Kšžœžœ’œžœžœ’œžœ’œžœ˜rK˜K˜—šœžœ4˜HKšœžœ ˜%Kšœžœžœžœ žœžœžœžœ˜UK˜K˜—šŸ œžœεžœ˜Kšœ žœ˜)šžœžœ%žœœ<˜—šžœ ž˜KšžœK˜OKšžœ˜—K˜—šžœžœžœž˜šžœžœžœ ˜JKšœ-˜-Kš žœžœ žœžœžœ˜2Kšœ" 6˜XKšœ'˜'Kšœ+˜+Kšœ˜šžœžœž˜šœœ˜%Kšœ'˜'Kš œžœžœžœžœ ˜OKšœo˜o—šœœ˜+Kšœ'˜'Kš œžœžœžœžœ ˜OKšœo˜o—šžœœ˜Kšœ'˜'Kšœ·˜½Kšœo˜o—K˜——Kšžœ˜—K˜K˜—šŸœžœz˜ššŸœ˜)K˜ Kšœ žœ˜)Kšœ3˜3Kšœ˜šžœžœžœž˜Kšœ$˜$Kš:˜:Kšžœ˜—K˜—šŸœ˜'K˜ Kšœžœžœ˜Kšœ˜Kšœ˜Kšœ žœ˜)K˜Kš™Kšœžœžœ˜0Kšœžœžœ˜*Kšœ,˜,KšžœžœžœD˜XKšœ˜K˜Kš™šžœžœžœž˜Kšœ$˜$Kšœžœžœ˜0Kšœžœžœ˜*Kšœžœžœ3˜HKšœ+˜+šžœžœžœ˜=KšŽ˜ŽK™8K˜—šžœ˜Kšm˜mK˜—Kšžœ˜—K˜—Kš Ÿ œžœžœžœžœžœ˜BKšœžœ ˜(Kšœ2˜2Kšœ žœ˜)Kšœ+˜+K˜šžœžœžœžœ˜4Kšœ$˜$Kšœ'˜'Kšœ,˜,Kšœ0˜0šžœ˜šž˜šœ˜Kšžœ žœžœžœ˜CKšœ;˜;——šž˜šœ˜Kšžœ žœžœžœ˜CKšœžœ˜+———K˜—šœ !˜$K˜——šŸœžœ}˜šKšA™AKšœ˜K˜ Kšœ žœ˜ Kšœ˜Kšœ˜Kšœ žœ˜)Kšœžœžœ˜&K˜Kšžœ žœžœžœ˜Kš8™8šžœžœžœž˜Kšœ$˜$Kšœ+˜+Kšœ+˜+šžœžœžœ˜#šžœžœžœž˜šžœžœ˜Kšžœžœ žœžœ-˜WKšžœžœ žœžœ˜EKšœf˜fKšœžœ˜Kšœžœ˜ K˜—Kšžœžœ žœžœ˜5Kšžœ˜—šžœ žœ˜Kšœ˜Kšœ žœ˜K˜—K˜—Kšžœ˜—Kš'™'šžœžœžœ˜šžœžœžœž˜&š žœžœžœžœžœžœ˜?šžœžœžœž˜)Kšœ*˜*Kšžœžœ-˜LKšœ.™.Kšžœ˜—K˜—Kšžœ˜—K˜—Kšœ !˜$K˜K˜—šŸ'œžœ'˜TšŸœ˜)K˜ Kšœ žœ˜)Kšœ3˜3Kšœ˜šžœžœžœž˜Kšœ$˜$Kš:˜:Kšžœ˜—K˜—Kš Ÿ œžœžœžœžœžœ˜BKšœžœ ˜(Kšœ2˜2Kšœ žœ˜)Kšœ+˜+K˜šžœžœžœžœ˜4Kšœ$˜$Kšœ'˜'Kšœ,˜,Kšœ0˜0šžœ˜Kšžœn˜rKšžœCžœ˜`—K˜—Kšœ ,˜/KšŸ˜K˜—šŸœžœ'˜AšŸ œ˜K˜ Kšœ žœ˜)Kšœ3˜3Kšœ˜šžœžœžœž˜Kšœ$˜$Kšœ:˜:Kšžœ˜—K˜—š Ÿ œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœžœ ˜(Kšœ2˜2Kšœ žœ˜)Kšœ+˜+Kš žœžœžœžœžœ˜5Kšœ'˜'Kšœ,˜,Kšœ0˜0Kšœ$˜$Kšžœžœc˜zKšžœ8žœ˜UKšœ ˜K˜—šŸœžœ'˜5KšœO™OK™,K™8K™)K™8šŸ œžœ˜)K˜ Kšœ žœ˜)Kšœ0˜0šžœžœžœž˜Kšœ$˜$šžœžœ žœžœ˜/Kšœ+˜+Kšœ'˜'Kšœ˜Kšœ˜K˜—Kšžœ˜—K˜—Kšœžœ ˜(Kšžœ%žœ ˜KKšžœ ˜-K˜K˜—šŸœžœ8žœ˜]šŸ œ˜Kšœ žœ˜)šžœžœžœž˜šžœ žœžœžœ˜0Kšœ-˜-Kšœ˜Kšœ:˜:K˜—Kšžœ˜—K˜—š Ÿ œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœžœ ˜(Kšœžœ˜%Kšœ2˜2Kšœ+˜+Kš žœžœžœžœžœ˜>Kšœ0˜0Kšœ,˜,Kšœ0˜0Kšœ$˜$Kšžœžœc˜zKšžœ8žœ˜UKšœ˜K˜—šŸœžœGžœ žœ4˜‘Kšœ ˜ Kšœ/˜/Kšœ/˜/šžœžœžœ'ž˜5šžœžœžœ>žœ˜dKšœ*˜*Kšœ+˜+K˜—Kšžœ˜—K˜K˜—šŸœžœBžœ žœ4˜‘Kšœ ˜ Kšœ'˜'Kšœ'˜'šžœžœžœ'ž˜6šžœžœžœ>žœ˜dKšœ*˜*Kšœ&˜&K˜—Kšžœ˜—K˜K˜—šŸœžœC˜[Kšœ$™$Kšœžœ˜%šžœ ž˜Kšžœ*˜.Kšžœ<˜@—K˜K˜—š‘œžœ-žœ˜fKšœ&™&Kšžœ-˜3K˜—˜K˜—š ŸœžœHžœPžœžœ"˜νK˜Kšžœžœm˜{šžœ˜Kšœžœ˜+Kšœ˜Kšœ˜Kšœ žœ˜Kšœ;˜;Kšœ˜Kšœžœ˜:Kšœ!˜!Kšœ!˜!Kšœžœžœ žœ ˜?Kšœžœžœ žœ ˜?š žœžœžœžœ ˜9Kšœ.˜.Kšœžœ˜Kšœ=˜=Kšœc˜cKšœ7˜7šžœžœ˜#Kšœ3˜3Kšžœžœ' ˜MKšœe˜eKšœ˜—KšžœL˜PKšœ˜—K˜š žœžœžœžœ ˜:Kšœ.˜.Kšœžœ˜Kšœ=˜=Kšœc˜cKšœ7˜7šžœžœ˜#Kšœ3˜3Kšžœžœ' ˜MKšœe˜eKšœ˜—KšžœL˜PKšœ˜—K˜Kšœ…™…K˜š žœžœžœžœžœ˜&Kšœ žœ,˜;šžœžœ=žœ˜YK˜Kšœ™K˜šžœ%žœ˜-Kšœžœ˜ Kšœ˜Kšœ]˜]KšœL˜LKšœ.˜.Kšœ7˜7Kšœ'˜'KšœM˜MKšœt˜tKšœa˜aKšœžœ˜K˜šžœžœ˜#šžœžœžœ -˜mKšœˆ˜ˆKšœ ˜—Kšœ˜—šžœ  ˜šžœžœ˜ Kšœ…˜…Kšœˆ˜ˆKšœ ˜—Kšœ˜—Kšœ˜—Kšœ˜—Kšœ˜K˜—š žœžœžœžœžœ˜&Kšœ žœ-˜<šžœžœ=žœ˜Yšžœ%žœ˜-Kšœžœ˜ Kšœ˜Kšœ]˜]KšœL˜LKšœ.˜.Kšœ7˜7Kšœ'˜'KšœM˜MKšœt˜tKšœa˜aKšœžœ˜K˜šžœžœ˜#šžœžœžœ -˜mKšœˆ˜ˆKšœ ˜—Kšœ˜—šžœ  ˜šžœžœ˜ Kšœ…˜…Kšœˆ˜ˆKšœ ˜—Kšœ˜—Kšœ˜—Kšœ˜—Kšœ˜K˜—K˜Kšœh˜hK˜Kš žœžœžœžœžœžœ˜9Kšœžœ ˜7šžœžœ˜Kšœh˜hKšœžœ˜K˜—Kšžœžœ˜"šžœžœ˜Kšœh˜hKšœžœ˜K˜—Kšžœžœ˜"Kšžœ˜Kšœ˜—Kšœ˜K˜—šŸ œžœ'˜:–‘ -- [moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc]šŸœ˜Kšœ™Kšœ˜Kšœ:˜:K˜—š Ÿ œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœ&˜&Kšžœ žœX˜jKšžœ!žœ˜,K˜K˜—šŸ œžœ'˜7–‘ -- [moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc]šŸœ˜Kšœ˜Kšœ:˜:K˜—š Ÿ œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœ&˜&Kšžœ žœX˜jKšžœ!žœ˜,˜K˜——šŸœžœdžœMžœ7˜ˆK–‘ -- [moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc]šœ’™’šŸœ˜Kšœ˜Kšœ žœ$˜0Kš œ žœžœžœ3žœ˜]Kšœ˜Kšžœžœžœ™˜ΠKšžœn˜rš žœ žœžœžœžœ ˜Ašžœžœžœž˜š žœžœžœžœ ˜GKšœ]˜]Kšœ,˜,K˜—Kšžœ˜—K˜—K˜—š Ÿ œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœ&˜&Kšžœ žœX˜jKšžœ!žœ˜,K˜K™—šŸœžœžœž œ˜LKšœ ˜ Kšœ2˜2Kšœžœ˜#Kšœ$˜$Kšœ˜Kšœžœ˜Kšœ+˜+Kšœžœ˜Kšœžœ˜Kšœ žœ˜)šžœžœžœž˜Kšœ$˜$Kšžœžœžœžœ˜*Kšžœžœžœžœ˜*Kšžœžœžœžœ˜.Kš žœžœ.žœžœžœ˜GKšžœžœžœžœ˜Kšžœžœžœžœ˜*Kšžœžœžœžœ˜*Kš žœžœ4žœžœžœ˜M—šž˜Kšžœžœžœ˜—Kšžœ˜K˜K˜—š Ÿœžœ žœžœžœžœ˜VKšžœ˜%K˜—K™šœ ™ K˜—šŸ œžœ$žœn˜¨Kšœ™Kšœ ˜ Kšœ ˜ Kšœžœ ˜(Kšœžœ˜%Kšœ žœ˜)Kšœ žœ˜%šžœž˜KšœH˜HKšœQ˜QKšžœžœ˜—šžœžœžœž˜šžœ žœžœžœ˜.Kšœžœ#˜1Kšœ<˜