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.FromRuns[CopyEachSegment]; extractedJoints ¬ Rosary.FromRuns[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; 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]; }; 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. Θ GGSliceImplD.mesa Contents: Implements some of the Traj slice class (see GGSliceImplE for more). Copyright Σ 1987, 1988, 1989, 1991, 1992 by Xerox Corporation. All rights reserved. Pier, July 17, 1991 11:25 am PDT Bier, April 22, 1993 0:03 am PDT Doug Wyatt, April 14, 1992 2:34 pm PDT 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. CodeTimer.StartInt[$TrajDrawBorder, $Gargoyle]; CodeTimer.StopInt[$TrajDrawBorder, $Gargoyle]; 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[]; Κ?θ•NewlineDelimiter –(cedarcode) style™codešœ™KšΟbœF™NKšœ ΟeœI™TK™ K™ K™&K™—šΟk ˜ KšœΜ˜Μ—K˜šΟn œŸœŸ˜KšŸœΧ˜ή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šœ˜Kšœ#˜#Kš ŸœŸœŸœŸœŸœ‘˜PKšœ Ÿœ˜K˜)K˜3šŸœSŸœ ŸœŸ˜jK˜9KšŸœ$Ÿœ˜OKšœ5˜5KšœŸœ˜KšŸœ˜—K˜(Kšœ˜K˜—š œŸœŸœ˜8KšœΟ™ΟKšœŸœ˜+KšœŸœ˜4K˜KKšœŸœ˜!KšœŸœ˜0K˜RKšœŸœ˜š œŸœŸœŸœ ˜Fš  œŸœŸœŸœŸœ˜NKšœ˜Kšœ Ÿœ˜K˜%Kšœ ˜ K˜—K˜6K˜—š  œŸœŸœŸœ ˜Dš  œŸœŸœŸœŸœ˜LKšœ˜Kšœ Ÿœ˜Kšœ™KšœŸœQ˜[Kšœ ˜ K˜—K˜2K˜—K˜)K˜5K˜1KšœgŸœT˜ΎKšŸœ Ÿœ"˜NK˜4K˜(Kšœ˜K˜—š  œŸœŸœ˜.Kšœ™K™±Kš ŸœŸœŸœŸœŸœŸœ˜!KšŸœŸœŸœ˜"KšŸœŸœŸœ˜"šŸ˜KšœŸœ ˜'KšœŸœ ˜#K˜Kš<™K˜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˜4KšŸœ˜—K˜K˜—K™K˜š’œŸœ(Οuœ4˜vKšŸœ‘%˜@Kšœ˜KšœŸœ˜Kšœ˜KšœŸœ ˜(K˜šŸœŸœ˜%K˜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˜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˜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˜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˜3Kšœ˜šŸœŸœŸœŸ˜K˜$Kš:˜:KšŸœ˜—K˜—Kš   œŸœŸœŸœŸœŸœ˜BKšœŸœ ˜(K˜2Kšœ Ÿœ˜)K˜+K˜šŸœŸœŸœŸœ˜4Kšœ$˜$Kšœ'˜'Kšœ,˜,Kšœ0˜0šŸœ˜KšŸœn˜rKšŸœCŸœ˜`—K˜—Kšœ‘,˜/Kš ˜K˜—š œŸœ'˜Aš  œ˜K˜ Kšœ Ÿœ˜)K˜3Kšœ˜šŸœŸœŸœŸ˜K˜$Kšœ:˜:KšŸœ˜—K˜—š   œŸœŸœŸœŸœ˜-KšŸœ ˜K˜—KšœŸœ ˜(K˜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˜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˜cK˜7šŸœŸœ˜#K˜3KšŸœŸœ'‘˜MK˜eKšœ˜—KšŸœL˜PKšœ˜—K˜š ŸœŸœŸœŸœ‘˜:K˜.KšœŸœ˜K˜=K˜cK˜7šŸœŸœ˜#K˜3KšŸœŸœ'‘˜MK˜eKšœ˜—KšŸœL˜PKšœ˜—K˜Kšœ…™…K˜š ŸœŸœŸœŸœŸœ˜&Kšœ Ÿœ,˜;šŸœŸœ=Ÿœ˜YK˜Kšœ™K˜šŸœ%Ÿœ˜-KšœŸœ˜ Kšœ˜K˜]K˜LK˜.K˜7K˜'K˜MK˜tK˜aKšœŸœ˜K˜šŸœŸœ˜#šŸœŸœŸœ‘-˜mK˜ˆKšœ‘˜—Kšœ˜—šŸœ‘ ˜šŸœŸœ˜ K˜…K˜ˆKšœ‘˜—Kšœ˜—Kšœ˜—Kšœ˜—Kšœ˜K˜—š ŸœŸœŸœŸœŸœ˜&Kšœ Ÿœ-˜<šŸœŸœ=Ÿœ˜YšŸœ%Ÿœ˜-KšœŸœ˜ Kšœ˜K˜]K˜LK˜.K˜7K˜'K˜MK˜tK˜aKšœŸœ˜K˜šŸœŸœ˜#šŸœŸœŸœ‘-˜mK˜ˆKšœ‘˜—Kšœ˜—šŸœ‘ ˜šŸœŸœ˜ K˜…K˜ˆKšœ‘˜—Kšœ˜—Kšœ˜—Kšœ˜—Kšœ˜K˜—K˜Kšœh˜hK˜Kš ŸœŸœŸœŸœŸœŸœ˜9KšœŸœ‘˜7šŸœŸœ˜K˜hKšœŸœ˜K˜—KšŸœŸœ˜"šŸœŸœ˜K˜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˜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˜