DIRECTORY CardTab, CodeTimer, Draw2d, Feedback, GGAlign, GGBasicTypes, GGCaret, GGCircleCache, GGCircles, GGControlPanelTypes, GGCoreTypes, GGDragTypes, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGRefresh, GGRefreshTypes, GGScene, GGSegmentTypes, GGSelect, GGSequence, GGShapes, GGSlice, GGSliceOps, GGState, GGTraj, GGUtility, Imager, ImagerBackdoor, Lines2d, Process, Real, RealFns, RefTab, Rope, Vectors2d; GGAlignImpl: CEDAR PROGRAM IMPORTS CardTab, CodeTimer, Draw2d, Feedback, GGCaret, GGCircleCache, GGCircles, GGParent, GGRefresh, GGSliceOps, GGScene, GGSelect, GGSequence, GGShapes, GGSlice, GGState, GGTraj, GGUtility, Imager, ImagerBackdoor, Lines2d, Process, Real, RealFns, RefTab, Vectors2d EXPORTS GGAlign, GGInterfaceTypes = BEGIN DragDataObj: PUBLIC TYPE = GGDragTypes.DragDataObj; -- exported to GGInterfaceTypes AlignBag: TYPE = GGInterfaceTypes.AlignBag; AlignBagObj: TYPE = GGInterfaceTypes.AlignBagObj; AlignmentCircle: TYPE = GGInterfaceTypes.AlignmentCircle; AlignmentCircleObj: TYPE = GGInterfaceTypes.AlignmentCircleObj; AlignmentLine: TYPE = GGInterfaceTypes.AlignmentLine; AlignmentLineObj: TYPE = GGInterfaceTypes.AlignmentLineObj; AlignmentObject: TYPE = GGModelTypes.AlignmentObject; AlignmentPoint: TYPE = REF AlignmentPointObj; AlignmentPointObj: TYPE = GGInterfaceTypes.AlignmentPointObj; Angle: TYPE = GGBasicTypes.Angle; BezierDragRecord: TYPE = GGInterfaceTypes.BezierDragRecord; Caret: TYPE = GGInterfaceTypes.Caret; Circle: TYPE = GGBasicTypes.Circle; ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator; Edge: TYPE = GGBasicTypes.Edge; EditConstraints: TYPE = GGModelTypes.EditConstraints; FeatureData: TYPE = REF FeatureDataObj; FeatureDataObj: TYPE = GGModelTypes.FeatureDataObj; FeatureWalkProc: TYPE = GGAlign.FeatureWalkProc; Filters: TYPE = GGInterfaceTypes.Filters; FilterSliceProc: TYPE = GGAlign.FilterSliceProc; FilterType: TYPE = GGAlign.FilterType; GGData: TYPE = GGInterfaceTypes.GGData; Joint: TYPE = GGModelTypes.Joint; JointGenerator: TYPE = GGModelTypes.JointGenerator; Line: TYPE = GGCoreTypes.Line; OutlineData: TYPE = GGOutline.OutlineData; Point: TYPE = GGBasicTypes.Point; PointAndDone: TYPE = GGModelTypes.PointAndDone; PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; PointWalkProc: TYPE = GGModelTypes.PointWalkProc; RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; SelectionClass: TYPE = GGSegmentTypes.SelectionClass; Sequence: TYPE = GGModelTypes.Sequence; SequenceGenerator: TYPE = GGSequence.SequenceGenerator; Slice: TYPE = GGModelTypes.Slice; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator; SliceGenerator: TYPE = GGModelTypes.SliceGenerator; SliceParts: TYPE = GGModelTypes.SliceParts; Traj: TYPE = GGModelTypes.Traj; TrajEnd: TYPE = GGModelTypes.TrajEnd; TrajData: TYPE = GGModelTypes.TrajData; TriggerBag: TYPE = REF TriggerBagObj; TriggerBagObj: TYPE = GGInterfaceTypes.TriggerBagObj; Vector: TYPE = GGBasicTypes.Vector; Problem: SIGNAL [msg: Rope.ROPE] = Feedback.Problem; EntityNotFound: PUBLIC SIGNAL = CODE; UnexpectedType: PUBLIC ERROR = CODE; BrokenInvariant: PUBLIC ERROR = CODE; emptyTriggerBag: PUBLIC TriggerBag; CreateTriggerBag: PUBLIC PROC [] RETURNS [triggerBag: TriggerBag] = { triggerBag _ NEW[TriggerBagObj _ [ slices: RefTab.Create[mod: 211], anchor: NIL]]; }; FlushTriggerBag: PUBLIC PROC [triggerBag: TriggerBag] = { RefTab.Erase[triggerBag.slices]; triggerBag.anchor _ NIL; }; EmptyTriggerBag: PUBLIC PROC [triggerBag: TriggerBag] RETURNS [BOOL] = { RETURN[ EmptyTriggerSlices[triggerBag] AND triggerBag.anchor = NIL ]; }; EmptyTriggerSlices: PROC [triggerBag: TriggerBag] RETURNS [empty: BOOL _ FALSE] = { SliceFeatureFound: RefTab.EachPairAction = { IF val = NIL THEN ERROR; quit _ TRUE; }; empty _ NOT RefTab.Pairs[triggerBag.slices, SliceFeatureFound]; }; CopyTriggerBag: PUBLIC PROC [to, from: TriggerBag] = { FlushTriggerBag[to]; CopyTriggerSlices[to.slices, from.slices]; to.anchor _ from.anchor; }; CopyTriggerSlices: PROC [toTable, fromTable: RefTab.Ref] = { CopySliceFeatures: RefTab.EachPairAction = { inserted: BOOL _ RefTab.Insert[toTable, key, val]; IF NOT inserted THEN ERROR; }; RefTab.Erase[toTable]; [] _ RefTab.Pairs[fromTable, CopySliceFeatures]; }; WalkSliceTriggers: PUBLIC PROC [triggerBag: TriggerBag, walkProc: FeatureWalkProc] = { DoForTrigger: RefTab.EachPairAction = { FOR list: LIST OF FeatureData _ NARROW[val], list.rest UNTIL list = NIL DO quit _ walkProc[list.first]; IF quit THEN EXIT; ENDLOOP; }; [] _ RefTab.Pairs[triggerBag.slices, DoForTrigger]; }; ListSliceTriggers: PROC [triggerBag: TriggerBag] RETURNS [features: LIST OF FeatureData] = { DoMakeList: PROC [feature: FeatureData] RETURNS [done: BOOL _ FALSE] = { [features, ptr] _ GGUtility.AddFeatureData[feature, features, ptr]; }; ptr: LIST OF FeatureData; WalkSliceTriggers[triggerBag, DoMakeList]; }; FindFeature: PROC [triggerBag: TriggerBag, slice: Slice] RETURNS [feature: FeatureData _ NIL] = { val: REF; found: BOOL _ FALSE; [found, val] _ RefTab.Fetch[triggerBag.slices, slice]; IF NOT found THEN RETURN[NIL]; FOR list: LIST OF FeatureData _ NARROW[val], list.rest UNTIL list = NIL DO IF NARROW[list.first.shape, SliceDescriptor].slice = slice THEN RETURN[list.first]; ENDLOOP; }; ReplaceFeature: PROC [triggerBag: TriggerBag, oldFeature, newFeature: FeatureData] = { val: REF; key: REF _ NARROW[oldFeature.shape, SliceDescriptor].slice; found: BOOL _ FALSE; [found, val] _ RefTab.Fetch[triggerBag.slices, key]; IF found AND val # NIL THEN { list: LIST OF FeatureData _ NARROW[val]; list _ RemoveFeatureFromList[oldFeature, list]; list _ CONS[newFeature, list]; [] _ RefTab.Store[triggerBag.slices, key, list]; } ELSE ERROR; }; DeleteFeature: PROC [triggerBag: TriggerBag, oldFeature: FeatureData] = { val: REF; key: REF _ NARROW[oldFeature.shape, SliceDescriptor].slice; found: BOOL _ FALSE; [found, val] _ RefTab.Fetch[triggerBag.slices, key]; IF found AND val # NIL THEN { list: LIST OF FeatureData _ NARROW[val]; list _ RemoveFeatureFromList[oldFeature, list]; IF list = NIL THEN [] _ RefTab.Delete[triggerBag.slices, key] ELSE [] _ RefTab.Store[triggerBag.slices, key, list]; } ELSE ERROR; }; AppendFeature: PUBLIC PROC [feature: FeatureData, sliceTriggers: RefTab.Ref] = { val: REF; slice: Slice _ NARROW[feature.shape, SliceDescriptor].slice; found: BOOL _ FALSE; Process.CheckForAbort[]; [found, val] _ RefTab.Fetch[sliceTriggers, slice]; IF found AND val # NIL THEN { list: LIST OF FeatureData _ NARROW[val]; list _ CONS[feature, list]; [] _ RefTab.Store[sliceTriggers, slice, list]; } ELSE { list: LIST OF FeatureData _ LIST[feature]; [] _ RefTab.Insert[sliceTriggers, slice, list]; }; }; FeatureFromSlice: PUBLIC PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [feature: FeatureData] = { sliceD: SliceDescriptor _ IF parts=NIL THEN GGSliceOps.NewParts[slice, NIL, slice] ELSE GGSlice.DescriptorFromParts[slice, parts]; feature _ NEW[FeatureDataObj]; feature.type _ slice; feature.shape _ sliceD; }; FeatureFromAnchor: PUBLIC PROC [anchor: Caret] RETURNS [feature: FeatureData] = { feature _ NEW[FeatureDataObj]; feature.type _ anchor; -- that is, anchor => the member of the enumerated type feature.shape _ anchor; -- that is, anchor => the parameter }; FillStaticTriggerBag: PUBLIC PROC [anchor: Caret, scene: Scene, heuristics: BOOL, triggerBag: TriggerBag] = { AddAnchorTrigger[anchor, triggerBag]; AddAllHotSlices[scene, triggerBag]; }; FillDynamicTriggerBag: PUBLIC PROC [anchor: Caret, scene: Scene, heuristics: BOOL, triggerBag: TriggerBag, editConstraints: EditConstraints, bezierDrag: BezierDragRecord] = { AddAnchorTrigger[anchor, triggerBag]; AddAllHotSlices[scene, triggerBag]; [] _ AddHeuristics[scene, heuristics, $Drag, triggerBag, editConstraints, bezierDrag]; [] _ RemoveMoving[scene, triggerBag, editConstraints, bezierDrag]; }; FillStaticSceneBag: PUBLIC PROC [scene: Scene, sceneBag: TriggerBag] = { AddAllSlices[scene, sceneBag]; }; FillDynamicSceneBag: PUBLIC PROC [scene: Scene, sceneBag: TriggerBag, editConstraints: GGModelTypes.EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord] = { AddAllSlices[scene, sceneBag]; [] _ RemoveMoving[scene, sceneBag, editConstraints, bezierDrag]; }; emptyAlignBag: PUBLIC AlignBag; CreateAlignBag: PUBLIC PROC [] RETURNS [alignBag: AlignBag] = { alignBag _ NEW[AlignBagObj _ [ slopeLineTable: CardTab.Create[mod: 211], angleLines: NIL, radiiCircles: NIL, distanceLines: NIL, anchor: NIL ]]; }; CopyAlignBag: PROC [to, from: AlignBag] = { CopySlopeLineTable[toTable: to.slopeLineTable, fromTable: from.slopeLineTable]; to.angleLines _ GGUtility.CopyFeatureDataList[from.angleLines]; to.radiiCircles _ GGUtility.CopyFeatureDataList[from.radiiCircles]; to.distanceLines _ GGUtility.CopyFeatureDataList[from.distanceLines]; to.anchor _ from.anchor; }; EmptyAlignBag: PUBLIC PROC [alignBag: AlignBag] RETURNS [BOOL] = { RETURN[ EmptySlopeLines[alignBag] AND alignBag.angleLines = NIL AND alignBag.radiiCircles = NIL AND alignBag.distanceLines = NIL AND alignBag.anchor = NIL]; }; CopySlopeLineTable: PROC [toTable, fromTable: CardTab.Ref] = { CopyFeatures: CardTab.EachPairAction = { inserted: BOOL _ CardTab.Insert[toTable, key, val]; IF NOT inserted THEN ERROR; }; CardTab.Erase[toTable]; [] _ CardTab.Pairs[fromTable, CopyFeatures]; }; EmptySlopeLines: PROC [alignBag: AlignBag] RETURNS [empty: BOOL _ FALSE] = { FeatureFound: CardTab.EachPairAction = { IF val = NIL THEN ERROR; quit _ TRUE; }; empty _ NOT CardTab.Pairs[alignBag.slopeLineTable, FeatureFound]; }; FlushAlignBag: PUBLIC PROC [alignBag: AlignBag] = { FlushSlopeLines[alignBag]; alignBag.angleLines _ NIL; alignBag.radiiCircles _ NIL; alignBag.distanceLines _ NIL; alignBag.anchor _ NIL; }; TriggersPerSlopeLine: PROC [ggData: GGData] RETURNS [lines, min, max: CARD, avg: REAL] = { Count: CardTab.EachPairAction = { list: LIST OF FeatureData _ NARROW[val]; aLine: AlignmentLine; triggersPerSlope: CARD _ 0; FOR l: LIST OF FeatureData _ list, l.rest UNTIL l = NIL DO lines _ lines + 1; aLine _ NARROW[l.first.shape]; triggersPerSlope _ 0; FOR pointList: LIST OF Point _ aLine.triggerPoints, pointList.rest UNTIL pointList = NIL DO triggersPerSlope _ triggersPerSlope + 1; ENDLOOP; min _ MIN[min, triggersPerSlope]; max _ MAX[max, triggersPerSlope]; totalTriggers _ totalTriggers + triggersPerSlope; ENDLOOP; -- next slope }; totalTriggers: REAL _ 0.0; lines _ 0; min _ LAST[CARD]; max _ 0; [] _ CardTab.Pairs[ggData.hitTest.alignBag.slopeLineTable, Count]; avg _ totalTriggers/lines; }; FlushSlopeLines: PROC [alignBag: AlignBag] = { CardTab.Erase[alignBag.slopeLineTable]; }; FillStaticAlignBag: PUBLIC PROC [triggerBag: TriggerBag, sceneBag: TriggerBag, ggData: GGData, hideAlignments: BOOL, alignBag: AlignBag] = { BuiltInFilters[triggerBag, ggData, hideAlignments, alignBag]; }; FillDynamicAlignBag: PUBLIC PROC [triggerBag: TriggerBag, sceneBag: TriggerBag, ggData: GGData, hideAlignments: BOOL, action: ATOM, alignBag: AlignBag] = { BuiltInFilters[triggerBag, ggData, hideAlignments, alignBag]; }; AddAlignment: PROC [featureData: FeatureData, alignBag: AlignBag] = { Process.CheckForAbort[]; SELECT featureData.type FROM distanceLine => alignBag.distanceLines _ CONS[featureData, alignBag.distanceLines]; slopeLine => AddSlopeLine[featureData, alignBag]; angleLine => alignBag.angleLines _ CONS[featureData, alignBag.angleLines]; radiiCircle => alignBag.radiiCircles _ CONS[featureData, alignBag.radiiCircles]; anchor => alignBag.anchor _ featureData; ENDCASE => SIGNAL Problem[msg: "Unimplemented feature type"]; }; AddSlopeLine: PROC [featureData: FeatureData, alignBag: AlignBag] = { line: Line _ NARROW[featureData.shape, AlignmentLine].line; distance: REAL _ line.d; key: CARD _ KeyFromDistance[distance]; val: REF; found: BOOL _ FALSE; [found, val] _ CardTab.Fetch[alignBag.slopeLineTable, key]; IF NOT found THEN { list: LIST OF FeatureData _ LIST[featureData]; [] _ CardTab.Insert[alignBag.slopeLineTable, key, list]; } ELSE { list: LIST OF FeatureData _ NARROW[val]; list _ CONS[featureData, list]; [] _ CardTab.Store[alignBag.slopeLineTable, key, list]; }; }; JointAddSlopeLine: PUBLIC PROC [degrees: REAL, point: Point, alignBag: AlignBag] RETURNS [feature: FeatureData] = { line: Line; coincident: FeatureData; cosine, sine, distance: REAL; [coincident, cosine, sine, distance] _ LineThru[point, degrees, alignBag.slopeLineTable]; IF coincident # NIL THEN { alignmentLine: AlignmentLine _ NARROW[coincident.shape]; alignmentLine.triggerPoints _ CONS[point, alignmentLine.triggerPoints]; feature _ NIL; } ELSE { alignmentLine: AlignmentLine; line _ Lines2d.LineFromCoefficients[sine, cosine, distance]; alignmentLine _ NEW[AlignmentLineObj _ [line: line, degrees: degrees, triggerPoints: CONS[point, NIL]]]; feature _ NEW[FeatureDataObj]; feature.type _ slopeLine; feature.shape _ alignmentLine; AddAlignment[feature, alignBag]; }; }; WalkSlopeLines: PUBLIC PROC [alignBag: AlignBag, walkProc: FeatureWalkProc] = { WalkAction: CardTab.EachPairAction = { FOR list: LIST OF FeatureData _ NARROW[val], list.rest UNTIL list = NIL DO quit _ walkProc[list.first]; IF quit THEN EXIT; ENDLOOP; }; [] _ CardTab.Pairs[alignBag.slopeLineTable, WalkAction]; }; ListOfSlopeLines: PROC [alignBag: AlignBag] RETURNS [slopeLines: LIST OF FeatureData _ NIL] = { ConsSlopeLine: CardTab.EachPairAction = { FOR list: LIST OF FeatureData _ NARROW[val], list.rest UNTIL list = NIL DO slopeLines _ CONS[list.first, slopeLines]; ENDLOOP; }; [] _ CardTab.Pairs[alignBag.slopeLineTable, ConsSlopeLine]; }; epsilon: REAL = 1.0e-3; epsilonInverse: REAL = 1.0e+3; KeysFromDistance: PROC [distance: REAL] RETURNS [keys: ARRAY[0..2] OF CARD] = { int: INT; distance _ distance * epsilonInverse; int _ Real.Floor[distance]; keys[0] _ LOOPHOLE[int]; keys[1] _ LOOPHOLE[int+1]; keys[2] _ LOOPHOLE[int-1]; }; KeyFromDistance: PROC [distance: REAL] RETURNS [key: CARD] = { int: INT; distance _ distance * epsilonInverse; -- giving 1001.2 in our example int _ Real.Floor[distance]; key _ LOOPHOLE[int]; }; LineThru: PROC [point: Point, degrees: REAL, table: CardTab.Ref] RETURNS [coincident: FeatureData, cosine, sine, distance: REAL] = { line: Line; keys: ARRAY[0..2] OF CARD; val: REF; found: BOOL _ FALSE; [distance, cosine, sine] _ DistanceFromOrigin[point, degrees]; keys _ KeysFromDistance[distance]; FOR i: NAT IN [0..2] DO [found, val] _ CardTab.Fetch[table, keys[i]]; IF NOT found THEN LOOP; FOR l: LIST OF FeatureData _ NARROW[val], l.rest UNTIL l = NIL DO line _ NARROW[l.first.shape, AlignmentLine].line; IF NARROW[l.first.shape, AlignmentLine].degrees = degrees AND Lines2d.LineDistance[point, line] < epsilon THEN {coincident _ l.first; RETURN} ENDLOOP; ENDLOOP; coincident _ NIL; }; JointAddCircle: PUBLIC PROC [radius: REAL, point: Point, alignBag: AlignBag] RETURNS [feature: FeatureData] = { circle: Circle; coincident: FeatureData; coincident _ SameCircle[point, radius, alignBag.radiiCircles]; IF coincident # NIL THEN { alignmentCircle: AlignmentCircle _ NARROW[coincident.shape]; alignmentCircle.triggerPoints _ CONS[point, alignmentCircle.triggerPoints]; feature _ NIL; } ELSE { alignmentCircle: AlignmentCircle; circle _ GGCircles.CircleFromPointAndRadius[point, radius]; alignmentCircle _ NEW[AlignmentCircleObj _ [circle: circle, triggerPoints: CONS[point, NIL]]]; feature _ NEW[FeatureDataObj]; feature.type _ radiiCircle; feature.shape _ alignmentCircle; AddAlignment[feature, alignBag]; }; }; SameCircle: PROC [point: Point, radius: REAL, list: LIST OF FeatureData] RETURNS [coincident: FeatureData] = { circle: Circle; FOR l: LIST OF FeatureData _ list, l.rest UNTIL l = NIL DO circle _ NARROW[l.first.shape, AlignmentCircle].circle; IF RealFns.AlmostEqual[circle.origin.x, point.x, -10] AND RealFns.AlmostEqual[circle.origin.y, point.y, -10] AND RealFns.AlmostEqual[circle.radius, radius, -10] THEN RETURN[l.first]; ENDLOOP; RETURN[NIL]; }; DistanceFromOrigin: PROC [pt1: Point, degrees: REAL] RETURNS [distance, c, s: REAL] = { c _ RealFns.CosDeg[degrees]; s _ RealFns.SinDeg[degrees]; distance _ pt1.y*c - pt1.x*s; }; SegmentAddTwoAngleLines: PUBLIC PROC [degrees: REAL, segNum: NAT, lo, hi: Point, alignBag: AlignBag] RETURNS [line1, line2: FeatureData] = { loLine: Line _ Lines2d.LineFromPointAndAngle[pt: lo, degrees: Vectors2d.AngleFromVector[[x: hi.x-lo.x, y: hi.y-lo.y]]+degrees]; hiLine: Line _ Lines2d.LineFromPointAndAngle[pt: hi, degrees: Vectors2d.AngleFromVector[[x: lo.x-hi.x, y: lo.y-hi.y]]+degrees]; line1 _ NEW[FeatureDataObj]; line1.type _ angleLine; line1.shape _ NEW[AlignmentLineObj _ [line: loLine, degrees: degrees, triggerPoints: NIL]]; AddAlignment[line1, alignBag]; line2 _ NEW[FeatureDataObj]; line2.type _ angleLine; line2.shape _ NEW[AlignmentLineObj _ [line: hiLine, degrees: degrees, triggerPoints: NIL]]; AddAlignment[line2, alignBag]; }; SegmentAddDistanceLines: PUBLIC PROC [distance: REAL, segNum: NAT, lo, hi: Point, alignBag: AlignBag] RETURNS [line1, line2: FeatureData] = { line, leftLine, rightLine: Line; line _ Lines2d.LineFromPoints[lo, hi]; leftLine _ Lines2d.LineLeftOfLine[line, distance]; line1 _ NEW[FeatureDataObj]; line1.type _ distanceLine; line1.shape _ leftLine; AddAlignment[line1, alignBag]; IF ABS[distance] > 0.0 THEN { rightLine _ Lines2d.LineRightOfLine[line, distance]; line2 _ NEW[FeatureDataObj]; line2.type _ distanceLine; line2.shape _ rightLine; AddAlignment[line2, alignBag]; } ELSE line2 _ NIL; }; SetStaticBags: PUBLIC PROC [ggData: GGData] = { triggerBag: TriggerBag _ ggData.hitTest.triggerBag; alignBag: AlignBag _ ggData.hitTest.alignBag; sceneBag: TriggerBag _ ggData.hitTest.sceneBag; scene: Scene _ ggData.scene; anchor: Caret _ ggData.anchor; heuristics: BOOL _ GGState.GetHeuristics[ggData]; filters: Filters _ ggData.hitTest; hideAlignments: BOOL _ NOT GGState.GetShowAlignments[ggData]; IF hideAlignments THEN { FlushAlignBag[alignBag]; FlushTriggerBag[sceneBag]; FillStaticSceneBag[scene, sceneBag]; } ELSE { CodeTimer.StartInt[$SetStaticBags, $Gargoyle]; FlushTriggerBag[triggerBag]; FillStaticTriggerBag[anchor, scene, heuristics, triggerBag]; FlushTriggerBag[sceneBag]; FillStaticSceneBag[scene, sceneBag]; FlushAlignBag[alignBag]; FillStaticAlignBag[triggerBag, sceneBag, ggData, hideAlignments, alignBag]; CodeTimer.StopInt[$SetStaticBags, $Gargoyle]; }; }; SetStaticTriggerAndAlignBags: PUBLIC PROC [ggData: GGData] = { triggerBag: TriggerBag _ ggData.hitTest.triggerBag; alignBag: AlignBag _ ggData.hitTest.alignBag; scene: Scene _ ggData.scene; anchor: Caret _ ggData.anchor; heuristics: BOOL _ GGState.GetHeuristics[ggData]; filters: Filters _ ggData.hitTest; hideAlignments: BOOL _ NOT GGState.GetShowAlignments[ggData]; IF hideAlignments THEN { FlushAlignBag[alignBag]; } ELSE { sceneBag: TriggerBag _ ggData.hitTest.sceneBag; FlushTriggerBag[triggerBag]; FillStaticTriggerBag[anchor, scene, heuristics, triggerBag]; FlushAlignBag[alignBag]; FillStaticAlignBag[triggerBag, sceneBag, ggData, hideAlignments, alignBag]; }; }; SetDynamicBags: PUBLIC PROC [ggData: GGData, action: ATOM] = { triggerBag: TriggerBag _ ggData.hitTest.triggerBag; alignBag: AlignBag _ ggData.hitTest.alignBag; sceneBag: TriggerBag _ ggData.hitTest.sceneBag; scene: Scene _ ggData.scene; anchor: Caret _ ggData.anchor; heuristics: BOOL _ GGState.GetHeuristics[ggData]; filters: Filters _ ggData.hitTest; hideAlignments: BOOL _ NOT GGState.GetShowAlignments[ggData]; IF hideAlignments THEN { FlushAlignBag[alignBag]; FlushTriggerBag[sceneBag]; FlushTriggerBag[triggerBag]; -- added March 30, 1987. KAP FillDynamicSceneBag[scene, sceneBag, ggData.drag.editConstraints, ggData.drag.bezierDrag]; } ELSE { CodeTimer.StartInt[$SetDynamicBags, $Gargoyle]; FlushTriggerBag[triggerBag]; FillDynamicTriggerBag[anchor, scene, heuristics, triggerBag, ggData.drag.editConstraints, ggData.drag.bezierDrag]; FlushTriggerBag[sceneBag]; FillDynamicSceneBag[scene, sceneBag, ggData.drag.editConstraints, ggData.drag.bezierDrag]; FlushAlignBag[alignBag]; FillDynamicAlignBag[triggerBag, sceneBag, ggData, hideAlignments, action, alignBag]; CodeTimer.StopInt[$SetDynamicBags, $Gargoyle]; }; }; StaticToDynamicBags: PUBLIC PROC [ggData: GGData, saveForeground: BOOL _ TRUE] = { scene: Scene _ ggData.scene; bd: BezierDragRecord _ ggData.drag.bezierDrag; ec: EditConstraints _ ggData.drag.editConstraints; triggerBag: TriggerBag _ ggData.hitTest.triggerBag; sceneBag: TriggerBag _ ggData.hitTest.sceneBag; alignBag: AlignBag _ ggData.hitTest.alignBag; heuristics: BOOL _ GGState.GetHeuristics[ggData]; hideAlignments: BOOL _ NOT GGState.GetShowAlignments[ggData]; hotMoving: BOOL _ SomeSelectedIsHot[scene]; IF ggData.camera.hideAlignments THEN { -- No alignments. Just build sceneBag. FlushAlignBag[alignBag]; CopyTriggerBag[to: ggData.hitTest.oldTriggerBag, from: triggerBag]; CopyTriggerBag[to: ggData.hitTest.oldSceneBag, from: sceneBag]; [] _ RemoveMoving[scene, sceneBag, ec, bd]; } ELSE { autoAdded: BOOL _ FALSE; autoTriggers: LIST OF SliceDescriptor _ NIL; filterLists: FilterLists _ GGState.GetFilterLists[ggData]; someAlignmentsActive: BOOL _ NOT EmptyFilterLists[filterLists]; CodeTimer.StartInt[$StaticToDynamicBags, $Gargoyle]; IF someAlignmentsActive THEN { CopyTriggerBag[to: ggData.hitTest.oldTriggerBag, from: triggerBag]; [] _ RemoveMoving[scene, triggerBag, ec, bd]; [autoAdded, autoTriggers] _ AddHeuristics[scene, heuristics, $Drag, triggerBag, ec, bd]; ggData.hitTest.oldTriggerBagOK _ TRUE; } ELSE ggData.hitTest.oldTriggerBagOK _ FALSE; CopyTriggerBag[to: ggData.hitTest.oldSceneBag, from: sceneBag]; [] _ RemoveMoving[scene, sceneBag, ec, bd]; IF autoAdded OR hotMoving THEN { ggData.hitTest.alignBagIsOldAlignBag _ FALSE; IF hotMoving THEN { -- all is lost ggData.hitTest.oldAlignBagOK _ FALSE; FlushAlignBag[alignBag]; BuiltInFilters[triggerBag, ggData, hideAlignments, alignBag]; GGRefresh.UpdateForeground[ggData, TRUE]; } ELSE { -- no hot parts are moving alignObjects: LIST OF FeatureData; ggData.hitTest.oldAlignBagOK _ TRUE; CopyAlignBag[to: ggData.hitTest.oldAlignBag, from: alignBag]; FOR list: LIST OF SliceDescriptor _ autoTriggers, list.rest UNTIL list = NIL DO alignObjects _ GGUtility.FeatureDataNconc[IncrementalFilterSlice[list.first, filterLists, hideAlignments, alignBag], alignObjects]; ENDLOOP; IF saveForeground THEN { GGRefresh.SaveForeground[ggData]; SaveLineTable[ggData]; }; GGRefresh.NoteNewForeground[ggData, alignObjects]; }; } ELSE { -- leave the Align Bag alone. Don't bother saving foreground plane. ggData.hitTest.alignBagIsOldAlignBag _ TRUE; ggData.hitTest.oldAlignBagOK _ FALSE; }; CodeTimer.StopInt[$StaticToDynamicBags, $Gargoyle]; }; }; DynamicToStaticBags: PUBLIC PROC [ggData: GGData, restoreForeground: BOOL _ TRUE] = { alignBag: AlignBag _ ggData.hitTest.alignBag; filters: Filters _ ggData.hitTest; hideAlignments: BOOL _ NOT GGState.GetShowAlignments[ggData]; CodeTimer.StartInt[$DynamicToStaticBags, $Gargoyle]; IF ggData.hitTest.oldTriggerBagOK THEN SwapOldAndNewTriggerBags[ggData]; SwapOldAndNewSceneBags[ggData]; IF ggData.hitTest.alignBagIsOldAlignBag THEN {} -- already restored ELSE { IF ggData.hitTest.oldAlignBagOK THEN { -- no hot were moving SwapOldAndNewAlignBags[ggData]; IF restoreForeground THEN { GGRefresh.RestoreForeground[ggData]; RestoreLineTable[ggData]; }; } ELSE { -- hot were moving. Just remake the alignBag FlushAlignBag[alignBag]; BuiltInFilters[ggData.hitTest.triggerBag, ggData, hideAlignments, alignBag]; GGRefresh.UpdateForeground[ggData, TRUE]; }; }; CodeTimer.StopInt[$DynamicToStaticBags, $Gargoyle]; }; SwapOldAndNewAlignBags: PROC [ggData: GGData] = { tempBag: AlignBag; tempBag _ ggData.hitTest.alignBag; ggData.hitTest.alignBag _ ggData.hitTest.oldAlignBag; ggData.hitTest.oldAlignBag _ tempBag; }; SwapOldAndNewTriggerBags: PROC [ggData: GGData] = { tempBag: TriggerBag; tempBag _ ggData.hitTest.triggerBag; ggData.hitTest.triggerBag _ ggData.hitTest.oldTriggerBag; ggData.hitTest.oldTriggerBag _ tempBag; }; SwapOldAndNewSceneBags: PROC [ggData: GGData] = { tempBag: TriggerBag; tempBag _ ggData.hitTest.sceneBag; ggData.hitTest.sceneBag _ ggData.hitTest.oldSceneBag; ggData.hitTest.oldSceneBag _ tempBag; }; UpdateBagsForAdd: PUBLIC PROC [oldAncestor: Slice, newAncestorD: SliceDescriptor, trajEnd: TrajEnd, ggData: GGData] RETURNS [repaintForeground: BOOL _ FALSE] = { triggerBag: TriggerBag _ ggData.hitTest.triggerBag; sceneBag: TriggerBag _ ggData.hitTest.sceneBag; alignBag: AlignBag _ ggData.hitTest.alignBag; scene: Scene _ ggData.scene; newHot, oldHot, newWhole, oldWhole, hotMinusOldHot: SliceDescriptor; hideAlignments: BOOL _ NOT GGState.GetShowAlignments[ggData]; alignObjects: LIST OF FeatureData; oldHot _ RemoveEntireHotSlice[oldAncestor, triggerBag]; newHot _ GGSelect.FindSelectedSlice[newAncestorD.slice, hot]; IF newHot # NIL THEN [] _ AddSliceFeature[newHot, triggerBag]; oldWhole _ RemoveEntireHotSlice[oldAncestor, sceneBag]; newWhole _ GGSliceOps.NewParts[newAncestorD.slice, NIL, slice]; [] _ AddSliceFeature[newWhole, sceneBag]; IF newHot = NIL THEN hotMinusOldHot _ NIL ELSE { maskD: SliceDescriptor; -- for a traj outD: SliceDescriptor; -- for the corresponding outline traj: Traj _ GGParent.FirstIncludedChild[newAncestorD.slice, newAncestorD.parts, leaf, $Traj].slice; trajData: TrajData _ NARROW[traj.data]; filterLists: FilterLists _ GGState.GetFilterLists[ggData]; IF trajEnd = lo THEN maskD _ GGSequence.Union[GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromSegment[trajData, 0]], GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromJoint[trajData, 0]]] ELSE maskD _ GGSequence.Union[GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromSegment[trajData, GGTraj.HiSegment[traj]]], GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromJoint[trajData, GGTraj.HiJoint[traj]]]]; outD _ GGParent.TopLevelDescriptorFromChildDescriptor[maskD]; hotMinusOldHot _ GGSliceOps.DifferenceParts[newHot, GGSliceOps.DifferenceParts[newWhole, outD]]; alignObjects _ IncrementalFilterSlice[hotMinusOldHot, filterLists, hideAlignments, alignBag]; }; }; UpdateBagsForNewSlices: PUBLIC PROC [newSlices: LIST OF Slice, ggData: GGData] = { triggerBag: TriggerBag _ ggData.hitTest.triggerBag; sceneBag: TriggerBag _ ggData.hitTest.sceneBag; alignBag: AlignBag _ ggData.hitTest.alignBag; scene: Scene _ ggData.scene; FOR list: LIST OF Slice _ newSlices, list.rest UNTIL list = NIL DO firstSlice: Slice _ list.first; wholeD: SliceDescriptor _ GGSliceOps.NewParts[firstSlice, NIL, slice]; [] _ AddSliceFeature[wholeD, sceneBag]; ENDLOOP; }; AddAnchorTrigger: PROC [anchor: Caret, triggerBag: TriggerBag] = { [] _ CreateAnchorTrigger[anchor, triggerBag]; }; AddAllHotSlices: PROC [scene: Scene, triggerBag: TriggerBag] = { feature: FeatureData; AddHotToTriggerBag: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { feature _ FeatureFromSlice[sliceD.slice, sliceD.parts]; AddFeature[feature, triggerBag]; }; [] _ GGScene.WalkSelectedSlices[scene, first, AddHotToTriggerBag, hot]; }; AddHeuristics: PROC [scene: Scene, heuristics: BOOL, atom: ATOM, triggerBag: TriggerBag, ec: EditConstraints, bd: BezierDragRecord] RETURNS [autoAdded: BOOL _ FALSE, autoTriggers: LIST OF SliceDescriptor _ NIL] = { feature: FeatureData; IF NOT heuristics THEN RETURN; SELECT atom FROM $Drag => { AddToTriggerBag: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { stationaryD: SliceDescriptor; oldFeature: FeatureData; IF GGSliceOps.IsCompleteParts[sliceD] THEN RETURN; -- the whole thing is moving stationaryD _ StationaryParts[sliceD, scene, ec, bd]; IF GGSliceOps.IsEmptyParts[stationaryD] THEN RETURN; -- the whole thing is moving oldFeature _ FindFeature[triggerBag, stationaryD.slice]; IF oldFeature # NIL THEN { hotD: SliceDescriptor _ NARROW[oldFeature.shape]; newlyHotD: SliceDescriptor _ GGSliceOps.DifferenceParts[stationaryD, hotD]; totalD: SliceDescriptor; IF NOT GGSliceOps.IsEmptyParts[newlyHotD] THEN { totalD _ GGSliceOps.UnionParts[stationaryD, hotD]; feature _ FeatureFromSlice[totalD.slice, totalD.parts]; ReplaceFeature[triggerBag, oldFeature, feature]; autoTriggers _ CONS[newlyHotD, autoTriggers]; autoAdded _ TRUE; }; } ELSE { feature _ FeatureFromSlice[stationaryD.slice, stationaryD.parts]; AddFeature[feature, triggerBag]; autoTriggers _ CONS[stationaryD, autoTriggers]; autoAdded _ TRUE; }; }; [] _ GGScene.WalkSelectedSlices[scene, first, AddToTriggerBag, normal]; }; $CaretPos => NULL; ENDCASE => ERROR UnexpectedType; }; StationaryParts: PROC [selectedD: SliceDescriptor, scene: Scene, ec: EditConstraints, bd: BezierDragRecord] RETURNS [stationaryD: SliceDescriptor] = { background, overlay, rubber, drag, move, totalD: SliceDescriptor; totalD _ GGSliceOps.NewParts[selectedD.slice, NIL, slice]; [background, overlay, rubber, drag] _ GGSliceOps.MovingParts[selectedD.slice, selectedD.parts, ec, bd]; move _ GGSliceOps.UnionParts[rubber, drag]; IF move = NIL THEN RETURN[totalD]; stationaryD _ GGSliceOps.DifferenceParts[totalD, move]; }; StationaryTriggerParts: PUBLIC PROC [hotD, selSliceD: SliceDescriptor, editConstraints: EditConstraints, bezierDrag: BezierDragRecord] RETURNS [stationary: SliceDescriptor, someRemoved: BOOL _ FALSE] = { background, overlay, rubber, drag, move: SliceDescriptor; IF selSliceD = NIL THEN RETURN[hotD]; -- clearly nothing is moving [background, overlay, rubber, drag] _ GGSliceOps.MovingParts[selSliceD.slice, selSliceD.parts, editConstraints, bezierDrag]; move _ GGSliceOps.UnionParts[rubber, drag]; IF move = NIL THEN RETURN[hotD, FALSE]; stationary _ GGSliceOps.DifferenceParts[hotD, move]; IF stationary # hotD THEN someRemoved _ TRUE; }; RemoveMoving: PROC [scene: Scene, bag: TriggerBag, ec: EditConstraints, bd: BezierDragRecord] RETURNS [someRemoved: BOOL _ FALSE] = { thisRemoved: BOOL _ FALSE; DoRemoveMoving: PROC [selectedD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { hotSliceD, stationaryD: SliceDescriptor; oldFeature: FeatureData; oldFeature _ FindFeature[bag, selectedD.slice]; IF oldFeature = NIL THEN RETURN; hotSliceD _ NARROW[oldFeature.shape]; [stationaryD, thisRemoved] _ StationaryTriggerParts[hotSliceD, selectedD, ec, bd]; IF thisRemoved THEN { -- nothing was removed newFeature: FeatureData; someRemoved _ TRUE; IF NOT GGSliceOps.IsEmptyParts[stationaryD] THEN { newFeature _ FeatureFromSlice[stationaryD.slice, stationaryD.parts]; ReplaceFeature[bag, oldFeature, newFeature]; } ELSE DeleteFeature[bag, oldFeature]; }; }; [] _ GGScene.WalkSelectedSlices[scene, first, DoRemoveMoving, normal]; }; FilterLists: TYPE = REF FilterListsObj; FilterListsObj: TYPE = GGState.FilterListsObj; EmptyFilterLists: PROC [filterLists: FilterLists] RETURNS [BOOL] = { RETURN[filterLists.onSlopes=NIL AND filterLists.onRadii = NIL AND filterLists.onAngles=NIL AND filterLists.onDistances = NIL]; }; FilterListsFromFilter: PROC [value: REAL, filterType: FilterType] RETURNS [filterLists: FilterLists] = { filterLists _ NEW[FilterListsObj _ [NIL, NIL, NIL, NIL]]; SELECT filterType FROM slope => filterLists.onSlopes _ LIST[value]; radius => filterLists.onRadii _ LIST[value]; angle => filterLists.onAngles _ LIST[value]; distance => filterLists.onDistances _ LIST[value]; ENDCASE => ERROR; }; BuiltInFilters: PUBLIC PROC [triggerBag: TriggerBag, ggData: GGData, hideAlignments: BOOL, alignBag: AlignBag] = { filterLists: FilterLists; anchor: Caret; anchorFeature: FeatureData; CodeTimer.StartInt[$BuiltInFilters, $Gargoyle]; anchorFeature _ triggerBag.anchor; IF hideAlignments THEN { FlushAlignBag[alignBag]; IF anchorFeature#NIL AND GGCaret.Exists[(anchor _ NARROW[anchorFeature.shape, Caret])] THEN AddAnchorObject[anchorFeature, alignBag]; RETURN; }; filterLists _ GGState.GetFilterLists[ggData]; -- filterLists are faster to loop over than filters IF EmptyFilterLists[filterLists] THEN { IF anchorFeature#NIL AND GGCaret.Exists[(anchor _ NARROW[anchorFeature.shape, Caret])] THEN AddAnchorObject[anchorFeature, alignBag]; } ELSE { DoPointFireRule: PointWalkProc = { [] _ PointFireRule[point, filterLists, alignBag]; }; DoForSlice: FeatureWalkProc = { sliceD _ NARROW[feature.shape]; GGSliceOps.WalkPointsInDescriptor[sliceD, DoPointFireRule]; pointPairGen _ GGSliceOps.PointPairsInDescriptor[sliceD]; FOR next: PointPairAndDone _ GGSliceOps.NextPointPair[sliceD.slice, pointPairGen], GGSliceOps.NextPointPair[sliceD.slice, pointPairGen] UNTIL next.done DO [] _ SegmentFireRule[9999, next.lo, next.hi, filterLists, alignBag]; ENDLOOP; }; sliceD: SliceDescriptor; pointPairGen: PointPairGenerator; IF anchorFeature#NIL THEN { anchor _ NARROW[anchorFeature.shape, Caret]; IF GGCaret.Exists[anchor] THEN { AddAnchorObject[anchorFeature, alignBag]; [] _ AnchorFireRule[anchor, filterLists, alignBag]; }; }; WalkSliceTriggers[triggerBag, DoForSlice]; }; CodeTimer.StopInt[$BuiltInFilters, $Gargoyle]; }; CreateAnchorTrigger: PUBLIC PROC [anchor: Caret, triggerBag: TriggerBag] RETURNS [feature: FeatureData] = { feature _ FeatureFromAnchor[anchor]; AddFeature[feature, triggerBag]; }; AddSliceFeature: PUBLIC PROC [sliceD: SliceDescriptor, triggerBag: TriggerBag] RETURNS [newFeature: FeatureData] = { oldD, unionD: SliceDescriptor; oldFeature: FeatureData; [oldD, oldFeature] _ FindOldSlice[sliceD.slice, triggerBag.slices]; IF oldD = NIL THEN { newFeature _ FeatureFromSlice[sliceD.slice, sliceD.parts]; AppendFeature[newFeature, triggerBag.slices]; } ELSE { unionD _ GGSliceOps.UnionParts[oldD, sliceD]; newFeature _ FeatureFromSlice[sliceD.slice, unionD.parts]; ReplaceFeature[triggerBag, oldFeature, newFeature]; }; }; IncrementalFilterSlice: PROC [sliceD: SliceDescriptor, filterLists: FilterLists, hideAlignments: BOOL, alignBag: AlignBag] RETURNS [alignObjects: LIST OF FeatureData] = { SplineInPointFeatures: PointWalkProc = { PointWalkProc: TYPE = PROC [point: Point] RETURNS [done: BOOL _ FALSE]; alignObjects _ GGUtility.FeatureDataNconc[PointFireRule[point, filterLists, alignBag], alignObjects]; }; IF filterLists.onSlopes # NIL OR filterLists.onRadii # NIL THEN GGSliceOps.WalkPointsInDescriptor[sliceD, SplineInPointFeatures]; IF filterLists.onAngles # NIL OR filterLists.onDistances # NIL THEN { pointPairGen: PointPairGenerator _ GGSliceOps.PointPairsInDescriptor[sliceD]; FOR next: PointPairAndDone _ GGSliceOps.NextPointPair[sliceD.slice, pointPairGen], GGSliceOps.NextPointPair[sliceD.slice, pointPairGen] UNTIL next.done DO alignObjects _ GGUtility.FeatureDataNconc[SegmentFireRule[9999, next.lo, next.hi, filterLists, alignBag], alignObjects]; ENDLOOP; }; }; IncrementalNewTrigger: PUBLIC PROC [trigger: FeatureData, filterLists: FilterLists, hideAlignments: BOOL, alignBag: AlignBag] RETURNS [alignObjects: LIST OF FeatureData] = { alignObjects _ NIL; IF hideAlignments THEN { FlushAlignBag[alignBag]; } ELSE { WITH trigger.shape SELECT FROM sliceD: SliceDescriptor => { alignObjects _ IncrementalFilterSlice[sliceD, filterLists, hideAlignments, alignBag]; }; anchor: Caret => { IF GGCaret.Exists[anchor] THEN { point: Point _ GGCaret.GetPoint[anchor]; [] _ PointFireRule[point, filterLists, alignBag]; }; }; ENDCASE => ERROR; }; }; IncrementalNewFilter: PUBLIC PROC [value: REAL, filterType: FilterType, triggerBag: TriggerBag, hideAlignments: BOOL, alignBag: AlignBag] RETURNS [alignObjects: LIST OF FeatureData _ NIL] = { filterLists: FilterLists _ FilterListsFromFilter[value, filterType]; DoPointFireRule: PointWalkProc = { alignObjects _ GGUtility.FeatureDataNconc[PointFireRule[point, filterLists, alignBag], alignObjects]; }; DoForSlice: FeatureWalkProc = { sliceD _ NARROW[feature.shape]; SELECT filterType FROM slope, radius => { GGSliceOps.WalkPointsInDescriptor[sliceD, DoPointFireRule]; }; angle, distance => { pointPairGen _ GGSliceOps.PointPairsInDescriptor[sliceD]; FOR next: PointPairAndDone _ GGSliceOps.NextPointPair[sliceD.slice, pointPairGen], GGSliceOps.NextPointPair[sliceD.slice, pointPairGen] UNTIL next.done DO alignObjects _ GGUtility.FeatureDataNconc[SegmentFireRule[9999, next.lo, next.hi, filterLists, alignBag], alignObjects]; ENDLOOP; }; ENDCASE => ERROR; }; sliceD: SliceDescriptor; pointPairGen: PointPairGenerator; anchor: Caret; anchorFeature: FeatureData; IF hideAlignments THEN RETURN; anchorFeature _ triggerBag.anchor; IF anchorFeature # NIL THEN { anchor _ NARROW[anchorFeature.shape, Caret]; IF GGCaret.Exists[anchor] THEN { AddAnchorObject[anchorFeature, alignBag]; alignObjects _ GGUtility.FeatureDataNconc[AnchorFireRule[anchor, filterLists, alignBag], alignObjects]; }; }; WalkSliceTriggers[triggerBag, DoForSlice]; }; RemoveAnchorTrigger: PUBLIC PROC [triggerBag: TriggerBag] = { triggerBag.anchor _ NIL; }; RemoveEntireHotSlice: PUBLIC PROC [slice: Slice, triggerBag: TriggerBag] RETURNS [oldSliceD: SliceDescriptor]= { oldFeature: FeatureData; [oldSliceD, oldFeature] _ FindOldSlice[slice, triggerBag.slices]; IF oldSliceD#NIL THEN { DeleteFeature[triggerBag, oldFeature]; }; }; AddFeature: PROC [featureData: FeatureData, triggerBag: TriggerBag] = { Process.CheckForAbort[]; SELECT featureData.type FROM slice => { sliceD: SliceDescriptor _ NARROW[featureData.shape]; key: RefTab.Key _ sliceD.slice; val: REF; found: BOOL _ FALSE; [found, val] _ RefTab.Fetch[triggerBag.slices, key]; IF NOT found THEN { list: LIST OF FeatureData _ LIST[featureData]; [] _ RefTab.Insert[triggerBag.slices, key, list]; } ELSE { list: LIST OF FeatureData _ NARROW[val]; list _ CONS[featureData, list]; [] _ RefTab.Store[triggerBag.slices, key, list]; }; }; anchor => { triggerBag.anchor _ featureData; }; ENDCASE => ERROR; }; AddAllSlices: PROC [scene: Scene, triggerBag: TriggerBag] = { feature: FeatureData; DoAddSlice: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { feature _ FeatureFromSlice[slice]; AddFeature[feature, triggerBag]; }; [] _ GGScene.WalkSlices[scene, first, DoAddSlice]; }; AddAnchorObject: PROC [anchorFeature: FeatureData, alignBag: AlignBag] = { alignBag.anchor _ anchorFeature; }; PointFireRule: PROC [point: Point, filterLists: FilterLists, alignBag: AlignBag] RETURNS [alignObjects: LIST OF FeatureData] = { feature: FeatureData; alignObjects _ NIL; FOR list: LIST OF REAL _ filterLists.onSlopes, list.rest UNTIL list = NIL DO feature _ JointAddSlopeLine[ degrees: list.first, point: point, alignBag: alignBag ]; IF feature # NIL THEN alignObjects _ CONS[feature, alignObjects]; ENDLOOP; FOR list: LIST OF REAL _ filterLists.onRadii, list.rest UNTIL list = NIL DO feature _ JointAddCircle[ radius: list.first*filterLists.scaleUnit, point: point, alignBag: alignBag ]; IF feature # NIL THEN alignObjects _ CONS[feature, alignObjects]; ENDLOOP; }; SegmentFireRule: PROC [segNum: NAT, lo, hi: Point, filterLists: FilterLists, alignBag: AlignBag] RETURNS [alignObjects: LIST OF FeatureData] = { line1, line2: FeatureData; alignObjects _ NIL; FOR list: LIST OF REAL _ filterLists.onDistances, list.rest UNTIL list = NIL DO [line1, line2] _ SegmentAddDistanceLines[distance: list.first*filterLists.scaleUnit, segNum: segNum, lo: lo, hi: hi, alignBag: alignBag]; IF line1 # NIL THEN alignObjects _ CONS[line1, alignObjects]; IF line2 # NIL THEN alignObjects _ CONS[line2, alignObjects]; ENDLOOP; FOR list: LIST OF REAL _ filterLists.onAngles, list.rest UNTIL list = NIL DO [line1, line2] _ SegmentAddTwoAngleLines[degrees: list.first, segNum: segNum, lo: lo, hi: hi, alignBag: alignBag]; IF line1 # NIL THEN alignObjects _ CONS[line1, alignObjects]; IF line2 # NIL THEN alignObjects _ CONS[line2, alignObjects]; ENDLOOP; }; AnchorFireRule: PROC [anchor: Caret, filterLists: FilterLists, alignBag: AlignBag] RETURNS [alignObjects: LIST OF FeatureData] = { line1, line2: FeatureData; point: Point _ GGCaret.GetPoint[anchor]; normal: Vector _ GGCaret.GetNormal[anchor]; lo: Point _ [point.x+normal.y, point.y-normal.x]; hi: Point _ [point.x-normal.y, point.y+normal.x]; alignObjects _ PointFireRule[point, filterLists, alignBag]; FOR list: LIST OF REAL _ filterLists.onDistances, list.rest UNTIL list = NIL DO [line1, line2] _ SegmentAddDistanceLines[distance: list.first*filterLists.scaleUnit, segNum: 9999, lo: lo, hi: hi, alignBag: alignBag]; IF line1 # NIL THEN alignObjects _ CONS[line1, alignObjects]; IF line2 # NIL THEN alignObjects _ CONS[line2, alignObjects]; ENDLOOP; FOR list: LIST OF REAL _ filterLists.onAngles, list.rest UNTIL list = NIL DO degrees: REAL _ list.first; loLine: Line _ Lines2d.LineFromPointAndAngle[pt: point, degrees: Vectors2d.AngleFromVector[normal]+90+degrees]; -- angle normal to anchor normal line1 _ NEW[FeatureDataObj]; line1.type _ angleLine; line1.shape _ NEW[AlignmentLineObj _ [line: loLine, degrees: degrees, triggerPoints: NIL]]; AddAlignment[line1, alignBag]; IF line1 # NIL THEN alignObjects _ CONS[line1, alignObjects]; ENDLOOP; }; SomeSelectedIsHot: PROC [scene: Scene] RETURNS [hotFound: BOOL _ FALSE] = { FindHot: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF GGSelect.IsSelectedInPart[sliceD.slice, scene, hot] THEN { hotFound _ TRUE; done _ TRUE; }; }; [] _ GGScene.WalkSelectedSlices[scene, first, FindHot, normal]; }; FindOldSlice: PROC [slice: Slice, sliceTriggers: RefTab.Ref] RETURNS [oldSliceD: SliceDescriptor, oldFeature: FeatureData] = { key: REF _ slice; val: REF; found: BOOL _ FALSE; [found, val] _ RefTab.Fetch[sliceTriggers, key]; IF NOT found OR val = NIL THEN RETURN[NIL, NIL]; FOR list: LIST OF FeatureData _ NARROW[val], list.rest UNTIL list = NIL DO oldFeature _ list.first; oldSliceD _ NARROW[oldFeature.shape]; IF oldSliceD.slice = slice THEN RETURN; ENDLOOP; RETURN [NIL, NIL]; }; RemoveFeatureFromList: PUBLIC PROC [ref: FeatureData, list: LIST OF FeatureData] RETURNS [val: LIST OF FeatureData _ NIL] = { z: LIST OF FeatureData _ NIL; UNTIL list = NIL DO IF list.first#ref THEN {IF val = NIL THEN {val _ CONS[list.first, NIL]; z _ val} ELSE {z.rest _ CONS[list.first, z.rest]; z _ z.rest}}; list _ list.rest; ENDLOOP; }; -- of RemoveFeatureFromList alignmentColor: Imager.Color _ ImagerBackdoor.MakeStipple[145065B]; checkerColor: Imager.Color _ ImagerBackdoor.MakeStipple[122645B]; useCache: BOOL _ TRUE; CreateLineTable: PUBLIC PROC [ggData: GGData] = { ggData.refresh.lineCache _ CardTab.Create[mod: 211]; ggData.refresh.savedLineTable _ CardTab.Create[mod: 211]; }; LineIsPresent: PROC [ggData: GGData, line: Line] RETURNS [present: BOOL _ FALSE] = { key: CARD _ CardFromLine[line]; found: BOOL _ FALSE; val: REF; list: LIST OF Line; [found, val] _ CardTab.Fetch[ggData.refresh.lineCache, key]; IF NOT found THEN RETURN[FALSE]; list _ NARROW[val]; FOR l: LIST OF Line _ list, l.rest UNTIL l = NIL DO IF l.first.d = line.d AND l.first.theta = line.theta THEN RETURN[TRUE]; ENDLOOP; present _ FALSE; }; TableStatistics: PROC [table: CardTab.Ref] RETURNS [count, min, max: CARD, avg: REAL] = { Count: CardTab.EachPairAction = { list: LIST OF Line _ NARROW[val]; itemsInBucket: CARD _ 0; count _ count + 1; FOR l: LIST OF Line _ list, l.rest UNTIL l = NIL DO itemsInBucket _ itemsInBucket + 1; ENDLOOP; min _ MIN[min, itemsInBucket]; max _ MAX[max, itemsInBucket]; total _ total + itemsInBucket; }; total: REAL _ 0.0; count _ 0; min _ LAST[CARD]; max _ 0; [] _ CardTab.Pairs[table, Count]; avg _ total/count; }; CopyLineList: PUBLIC PROC [l: LIST OF Line] RETURNS [copyList: LIST OF Line] = { z: LIST OF Line _ NIL; IF l = NIL THEN RETURN[NIL]; copyList _ CONS[l.first, NIL]; z _ copyList; UNTIL (l _ l.rest) = NIL DO z.rest _ CONS[l.first, NIL]; z _ z.rest; ENDLOOP; }; SaveLineTable: PROC [ggData: GGData] = { CopyLines: CardTab.EachPairAction = { inserted: BOOL _ CardTab.Insert[ggData.refresh.savedLineTable, key, val]; IF NOT inserted THEN ERROR; }; CardTab.Erase[ggData.refresh.savedLineTable]; [] _ CardTab.Pairs[ggData.refresh.lineCache, CopyLines]; }; RestoreLineTable: PROC [ggData: GGData] = { temp: CardTab.Ref _ ggData.refresh.savedLineTable; ggData.refresh.savedLineTable _ ggData.refresh.lineCache; ggData.refresh.lineCache _ temp; }; FlushLineTable: PUBLIC PROC [ggData: GGData] = { CardTab.Erase[ggData.refresh.lineCache]; }; CardFromLine: PROC [line: Line] RETURNS [key: CARD] = { key _ LOOPHOLE[line.d]; }; AddLineToTable: PROC [ggData: GGData, line: Line] = { key: CARD _ CardFromLine[line]; found, newValue: BOOL _ FALSE; val: REF; list: LIST OF Line; [found, val] _ CardTab.Fetch[ggData.refresh.lineCache, key]; IF NOT found THEN { -- empty bucket list _ LIST[line]; newValue _ CardTab.Store[ggData.refresh.lineCache, key, list]; IF NOT newValue THEN ERROR; } ELSE { -- collision, add to bucket list _ NARROW[val]; list _ CONS[line, list]; newValue _ CardTab.Store[ggData.refresh.lineCache, key, list]; IF newValue THEN ERROR; }; }; DrawFeatureList: PUBLIC PROC [dc: Imager.Context, alignObjects: LIST OF FeatureData, ggData: GGData] = { DrawLineAux: PROC [line: Line] = { -- uses FunctionCache to avoid duplication IF LineIsPresent[ggData, line] THEN RETURN ELSE AddLineToTable[ggData, line]; IF ABS[line.theta] < 1.0 OR ABS[line.theta - 90.0] < 1.0 THEN { -- horizontal or vertical Imager.SetColor[dc, checkerColor]; GGShapes.DrawLine[dc, line, rect, 0.0, zip]; -- 0 width lines go fast Imager.SetColor[dc, alignmentColor]; } ELSE GGShapes.DrawLine[dc, line, rect, 0.0, zip]; -- 0 width lines go fast }; DrawCircleAux: PROC [circle: Circle] = { -- uses GGCircleCache cachedCircle: GGCircleCache.CachedCircle; IF (cachedCircle _ GGCircleCache.Lookup[ggData.controls.radiusCircleCache, circle.radius])#NIL THEN GGCircleCache.DrawCachedCircle[dc, circle.origin, cachedCircle] -- cache hit ELSE { -- cache miss. Attempt a cache entry and retry GGCircleCache.Insert[ggData.controls.radiusCircleCache, circle.radius]; IF (cachedCircle _ GGCircleCache.Lookup[ggData.controls.radiusCircleCache, circle.radius])#NIL THEN GGCircleCache.DrawCachedCircle[dc, circle.origin, cachedCircle] ELSE GGShapes.DrawCircle[dc, circle]; }; }; rect: Imager.Rectangle _ GGState.GetViewport[ggData]; zip: Draw2d.Zip; Imager.SetStrokeWidth[dc, 0.0]; -- to convince Draw2d.GetZip that everything is ok zip _ Draw2d.GetZip[dc]; Imager.SetStrokeWidth[dc, 1.0]; Imager.SetColor[dc, alignmentColor]; FOR list: LIST OF FeatureData _ alignObjects, list.rest UNTIL list = NIL DO WITH list.first.shape SELECT FROM slopeLine: AlignmentLine => DrawLineAux[slopeLine.line]; angleLine: AlignmentLine => DrawLineAux[angleLine.line]; circle: AlignmentCircle => IF useCache THEN DrawCircleAux[circle.circle] ELSE GGShapes.DrawCircle[dc, circle.circle]; distanceLine: Line => GGShapes.DrawLine[dc, distanceLine, rect]; ENDCASE => ERROR; ENDLOOP; Draw2d.ReleaseZip[zip]; }; HasVisibleObjects: PUBLIC PROC [alignBag: AlignBag] RETURNS [BOOL] = { RETURN[NOT ( EmptySlopeLines[alignBag] AND alignBag.angleLines = NIL AND alignBag.radiiCircles = NIL AND alignBag.distanceLines = NIL)]; }; DrawAlignBagRegardless: PUBLIC PROC [dc: Imager.Context, alignBag: AlignBag, ggData: GGData] = { slopeLines: LIST OF FeatureData; IF alignBag = NIL THEN RETURN; slopeLines _ ListOfSlopeLines[alignBag]; DrawFeatureList[dc, slopeLines, ggData]; DrawFeatureList[dc, alignBag.angleLines, ggData]; DrawFeatureList[dc, alignBag.radiiCircles, ggData]; DrawFeatureList[dc, alignBag.distanceLines, ggData]; }; MakeFiltersGarbage: PUBLIC PROC [filters: Filters] = { UnlinkBag: PROC [bag: TriggerBag] = { DoUnLink: FeatureWalkProc = { WITH feature.shape SELECT FROM sd: SliceDescriptor => IF sd.slice#NIL THEN { GGSlice.UnlinkSlice[sd.slice]; sd.slice _ NIL; }; ENDCASE; }; IF bag=NIL THEN RETURN; WalkSliceTriggers[bag, DoUnLink]; }; UnlinkBag[filters.triggerBag]; UnlinkBag[filters.oldTriggerBag]; UnlinkBag[filters.sceneBag]; UnlinkBag[filters.oldSceneBag]; }; Init: PROC [] = { emptyTriggerBag _ CreateTriggerBag[]; emptyAlignBag _ CreateAlignBag[]; }; Init[]; END. $NGGAlignImpl.mesa Contents: Contains routines for building and maintaining sets of gravity active scene objects and alignment objects, called "bags". Copyright Ó 1988 by Xerox Corporation. All rights reserved. Pier, August 12, 1988 4:48:20 pm PDT Bier, March 5, 1992 2:40 pm PST Doug Wyatt, June 24, 1988 10:44:55 am PDT The TriggerBag, the SceneBag and the AlignBag The set of alignment objects is computed in three steps (see Figure 1). First, we determine which joints, control points, and segments in the scene are triggers. We put these in triggerBag. Next we determine which scene objects are gravity active. We put these in sceneBag. Finally, we determine the set of alignment objects from the triggerBag using the current filters. This set is represented by the selected slopes, angles, radii, and distances in the user interface. The bags are recomputed at these times: triggerBag: Updated whenever an Add or Drag operation is performed, or whenever anything becomes hot or cold. sceneBag: Updated whenever an Add or Drag operation is performed. alignBag. Updated whenever an Add or Drag operation is performed, whenever anything becomes hot or cold, or whenever the set of current filters changes. The bags are represented as follows: triggerBag and sceneBag: several LIST OF FeatureData. The FeatureData will point to a SliceDescriptor or the anchor. alignBag: several LIST OF FeatureData. The FeatureData will point to an AlignmentLine, an AlignmentCircle, an AlignmentPoint, or a Caret (e.g. the anchor). [Artwork node; type 'ArtworkInterpress on' to command tool] TriggerBag EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; Just copy the pointer to the list in each bucket. This works because we use non-destructive list operations on the copied bag. EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; Do non-destructive list operations to replace the old feature with the new one. Do non-destructive list operations to delete the old feature. Do non-destructive list operations to add the new feature to the hash table. list _ DeleteSliceFromList[slice, list]; -- may be needed later SceneBag AlignBag EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; Count the number of triggers per slope line. EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; Slopes, Circles, Angles, Distance Lines, and Midpoints The angle (degrees) and direction vector are the global information. lineList is used to avoid redundant lines. EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; For epsilon = 1.0e-3, we wish to truncate distance down to the nearest multiple of epsilon. For example, 1.0012 => 1.001. Note: distance can be negative. radius is global. [point, jointNum, traj] describes the joint. Each circle remembers all of the joints at its center. Exactly mimics Lines2d.FillLineFromPointAndAngle No global information is needed. [segNum, traj] describes the segment. Filling all Three Bags at Once We have two trigger bags and one object bag to get into shape. The Trigger Bag should already contain the anchor, and all hot objects. For now, however, we will put these into the bag here. Next, we remove all objects which are going to be moving during the upcoming operation. For selection operations, we remove all triggers. The Scene Bag will be empty. We fill it with all of the scene objects. Then, we remove those objects which are going to be moving during the upcoming operation. The Align Bag is made by filtering the Trigger Bag through the currently selected filters. Fill TriggerBag Fill Scene Bag Fill Align Bag Fill TriggerBag Fill Align Bag We have two trigger bags and one object bag to get into shape. The Trigger Bag should already contain the anchor, and all hot objects. For now, however, we will put these into the bag here. Next, we remove all objects which are going to be moving during the upcoming operation. For selection operations, we remove all triggers. The Scene Bag will be empty. We fill it with all of the scene objects. Then, we remove those objects which are going to be moving during the upcoming operation. The Align Bag is made by filtering the Trigger Bag through the currently selected filters. Fill TriggerBag Fill Scene Bag Fill Align Bag We are about to drag things around. Here is what that means for each of the bags: Trigger Bag: AddHeuristics may add some things. RemoveMoving may remove some things. Scene Bag: RemoveMoving will remove some things. Align Bag: Moving parts will no longer trigger alignment lines. Foreground plane: If hot objects are moving, remake it from scratch. Otherwise, augment it with any objects generated by the automatic rule. repaintForeground is TRUE if alignBag is altered. Incrementally Update TriggerBag Incrementally Update SceneBag Incrementally Update AlignBag Use the old TriggerBag and SceneBag. Restore AlignBag Looks like repaintForeground isn't used currently. This routine takes that bags from one static state to another static state, where the difference is that a line segment has been added. The bags should be updated as follows: Trigger: Remove old outline descriptor. Add new hotness descriptor for newAncestor. Align: Remove mentions of old outline. Use filters to add new mentions. Scene: Remove old outline descriptor. Add a complete descriptor for newAncestor. Fixing the TriggerBag. We can ignore Heuristics because there is no motion. We don't need to remove moving parts because there aren't any. Fixing the SceneBag. Fixing the AlignBag. We assume that the bags were set up static before this. Hence, there are two simple possibilities. Either the newly added segment is hot, or it isn't. Assuming that the new slices cannot be hot, the bags should be updated as follows: Trigger: No change. Scene: Add a complete descriptor for each new slice. Fixing the SceneBag The Filter Routines shown in the figure as boxes and ovals RemoveMovingSlice should return those parts which should be included in the new bag. For each moving slice, see if it is in the bag. If so, replace it by its moving parts. "bag" may be either the Scene Bag or the Trigger Bag. Add Alignment Objects to the alignBag, using the triggers in the triggerBag. The anchor as the only trigger. PointWalkProc: TYPE = PROC [point: Point] RETURNS [done: BOOL _ FALSE]; FeatureWalkProc: PROC [feature: FeatureData] RETURNS [done: BOOL _ FALSE]; The anchor as a trigger. The slices as triggers. Incremental Addition versions of the Filter Routines shown in the figure as boxes and ovals. A single new trigger has been added to the triggerBag. Add to the alignBag, all alignment objects generated by that trigger. Returns a list of the new alignment objects that were generated. The anchor as a trigger. A new alignment value has been activated. Create new alignObjects and add them to the alignBag. PointWalkProc: TYPE = PROC [point: Point] RETURNS [done: BOOL _ FALSE]; FeatureWalkProc: PROC [feature: FeatureData] RETURNS [done: BOOL _ FALSE]; The anchor as a trigger. The slices as triggers. Incremental Deletion versions of the Filter Routines shown in the figure as boxes and ovals. Other Routines In support of building triggerBag and sceneBag. In support of building sceneBag. In support of building alignBag. In support of building all three bags. FeatureList Utilities Drawing Alignment Objects alignmentColor: Imager.Color _ Imager.black; -- doing this buys less than 10% in performance checkerColor: Imager.Color _ Imager.black; LineCompare: PROC [argument: FunctionCache.Domain] RETURNS [good: BOOL] = { thisLine: Line _ NARROW[argument]; RETURN[thisLine.d = line.d AND thisLine.theta = line.theta]; }; [----, present] _ FunctionCache.Lookup[ggData.refresh.lineCache, LineCompare]; Use these as the arguments to TableStatistics depending on the type of information desired: ggData.refresh.lineCache ggData.hitTest.alignBag.slopeLineTable EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL _ FALSE]; FunctionCache.Flush[ggData.refresh.lineCache]; FunctionCache.Insert[ggData.refresh.lineCache, line, NIL, 2]; Draws all objects in the object bag regardless of how they have been marked. Ruthlessly destroy the various filter bags so the Cedar gargbage collector can sweep up. Wizards only. Initialization InitStats: PROC [] = { interval: CodeTimer.Interval; interval _ CodeTimer.CreateInterval[$StaticToDynamicBags]; CodeTimer.AddInt[interval, $Gargoyle]; interval _ CodeTimer.CreateInterval[$DynamicToStaticBags]; CodeTimer.AddInt[interval, $Gargoyle]; interval _ CodeTimer.CreateInterval[$SetDynamicBags, NIL]; CodeTimer.AddInt[interval, $Gargoyle]; interval _ CodeTimer.CreateInterval[$SetStaticBags, NIL]; CodeTimer.AddInt[interval, $Gargoyle]; interval _ CodeTimer.CreateInterval[$BuiltInFilters, NIL]; CodeTimer.AddInt[interval, $Gargoyle]; }; InitStats[]; ÊO-˜Icode™šÏbœ{™ƒK™ÝoÄL3OÄ&±EŒ¡’Ä[!ÄccpÄL=„Ä[kÄZÀ•ÄPña¡’Ä#í8ÄR‰eÄÃ*Ä£KÇÄ*–;Ä1;¡’ÄμÄGXSÄYøqÄÀQÖÄIU]Äe½m¡’Ä@¸SÄOËRÄ. AÄOŽOÄQ qÄ&Ÿ%¡’Ĉù¿Ä€ãjÄ@¡XÄ ÆùĈµÄ¹i¨¡’¡¸ r jª ¤Ä)Œ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁTrigger– k é r jª ¤Ä?~ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁBag– k é¢¯“¢°“¢·“¡¡¨p™e#p.¡“±Ž¼#±¡“pŽ¡¸ r j¡¡¨¢¯“Ä;÷™Ä;èÄYèÄÙ J¡”ÄwèÄw÷ÄÙ J¡”ÄwÄYÄÙ J¡”Ä;Ä;÷ÄÙ J¡”¡¸ k é¢¯“¢°“¢·“¡¡¨ÄB™ÄY—˜¢¯“¢°“¢·“¡¡¨ÄëB™.˜¢¯“¢°“¢·“¡¡¨Äá™ÄY—˜¢¯“¢°“¢·“¡¡¨Äe7ÄtyC™Äam;ĸ——ĹoăJMÄ?é¾ÄÌyÄc™;Ä÷Þ-¡’ĉ~QÄ9a#ÄZà7Ä1x¿ÄqŸEÄ¡’Ä61!Ä¥êmÄÛ•„ĶÆ{Äi>ÄV¯;¡’Ä`½8ÄLô5ÄJ_*Ä!yÄj¨;Ä­év¡’Ä,_Ä.SÄÔ®qÄe5BÄ®S]Äcøã¡’ÄšÚSÄAì)Äu6AÄƨyÄÔÅvÄ|7J¡’ÄXc¿Ä[±6Äž_WÄo·Äe7ÄtyC¡’¡¸ r jª ¤ÄUÄ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁTrigger– k é r jª ¤ÄkÄ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁBag– k é¢¯“¢°“¢·“¡¡¨Mó™BþM ¡“uŽ€þuó¡“MŽ¡¸ r jª ¤Pĵ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁFilters– k é¢¯“¢°“¢·“¡¡¨Ä Ç™ÄõÒÄ Ý¡“Ä£ŽÄ¹Òģǡ“Ä Ž¡¸ r jª ¤ÄÏ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“Á Not Moving– k é¢¯“¢°“¢·“¡¡¨ÄYè™ÄWÝ—˜¢¯“¢°“¢·“¡¡¨ÄWǙĻW«˜¢¯“¢°“¢·“¡¡¨œÄ¹i¨™ÄtßAÄ_D—Äe™8Ĩ ĆlíÄ‚Ý|ćÇJĈ#ƒ¡’ÄŠPKÄFSFÄÊ ÄL3OÄ>Å#Œ¡’ÄT/ÄccpÄÊ¿pÄ[kļ“ÄPña¡’Ä®M]ÄR‰eÄxÍ?Ä%¿.Ä‚óCÄ1;¡’ĉ±EÄGXSĘ*KÄÀQÖÄw@;Äe½m¡’Ä’èIÄOËRÄF3$ÄOŽOÄà<sÄ&Ÿ%¡’Ä&ÿÄ€ãjÄN¯(Ä ÆùœÄ¹i¨¡’¡¸ r jª ¤ÄA #Ä4@; ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁBag– k é r jª ¤wÄá ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁAlign– k é r j¢¯“¢°“¡¡¨ØBØX¡¹¢¯“¢°“¡¡¨ØX9X¡¹¢¯“¢°“¡¡¨9X9B¡¹¢¯“¢°“¡¡¨9BØB¡¹ k é r jª ¤ÝÄS ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“Á Scene Objects– k é¢¯“¢°“¢·“¡¡¨âÇ™×ÒâÝ¡“.Ž9Ò.Ç¡“⎡¸ r jª ¤çÏ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“Á Not Moving– k é¢¯“¢°“¢·“¡¡¨!Ĺi¨™ÄU¤AÄ_D—ÄJ±8Ĩ Ä™íÄ‚Ý|Äm´QĈ#ƒ¡’ÄfGKÄFSFÄ ÄL3OÄ-ô#Œ¡’Ä=/ÄccpÄ”ïpÄ[kÄÈ“ÄPña¡’Äž]ÄR‰eÄZˆ?Ä%¿.ÄbÂCÄ1;¡’ÄhŠEÄGXSÄt!KÄÀQÖÄZç;Äe½m¡’ÄoÕIÄOËRÄ4ç$ÄOŽOĨûsÄ&Ÿ%¡’ÄcÄ€ãjÄ;w(Ä Æù!Ĺi¨¡’¡¸ r jª ¤ÄV¤?~ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁBag– k é¢¯“¢°“¢·“¡¡¨ÄÑB™Ý—˜¢¯“¢°“¢·“¡¡¨Ç™Ä$!˜ r jª ¤ûÄÙ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁScene– k é r j¢¯“¢°“¡¡¨ää*¡¹¢¯“¢°“¡¡¨ä**¡¹¢¯“¢°“¡¡¨*¡¹¢¯“¢°“¡¡¨ä¡¹ k é r jª ¤éÄ÷ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁAnchor– k é¢¯“¢°“¢·“¡¡¨™ÄY—˜¢¯“¢°“¢·“¡¡¨ÃÄtyC™Ä~ÆAĸ——Än!8ăJMÄчíÄÌyÄ öQÄ÷Þ-¡’Ä•½KÄ9a#Äw Ä1x¿ÄD#Ä¡’Ä[=/Ä¥êmÄÛÏpĶÆ{Ä%!“ÄV¯;¡’ļx]ÄLô5Ä‚f?Ä!yÄ(CÄ­év¡’Ä…+>Ä.SÄ£—KÄe5BÄ€=;Äcøã¡’ÄžIÄAì)ÄK¯$ÄƨyÄñÁsÄ|7J¡’Ä* Ä[±6ÄTÇ(Äo·ÃÄtyC¡’¡¸ r jª ¤Ä~‚?Ä ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁBag– k é r jª ¤/ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁScene– k é¢¯“¢°“¢·“¡¡¨˜ó™þ˜ ¡“ÈŽÓþÈ󡓘Ž¡¸ r jª ¤”û ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“Á Midpoints– k é r j¬ ¤ÄÃÔ ¢ ¥ ¨ÅxeroxÅ tiogafontsÅMath12£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁU– k é r j¡¡¨¢¯“įٙįÊÄÍÊÄÙ J¡”ÄëÊÄëÙÄÙ J¡”ÄëèÄÍèÄÙ J¡”įèįÙÄÙ J¡”¡¸ k é¢¯“¢°“¢·“¡¡¨ÄO-Äs=O™ ˜¢¯“¢°“¢·“¡¡¨ÄdÜ1Ä^ÕA™ ˜¢¯“¢°“¢·“¡¡¨aó™ÄÍè—˜¢¯“¢°“¢·“¡¡¨ÄÍÊ™Ä# ˜¢¯“¢°“¢·“¡¡¨°ó™ÄÍè—˜ r jª ¤Ä#ÄÅ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁGravity– k é r jª ¤Ä'Ä© ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁActive– k é¢¯“¢°“¢·“¡¡¨áw™k²Žw˜ k é k gšœ=™=—K™Kšœ ™ Kšœžœ ˜#šŸœžœžœžœ˜Ešœ žœ˜"Kšœ ˜ Kšœžœ˜—K˜K˜—šŸœžœžœ˜9Kšœ ˜ Kšœžœ˜K˜K˜—šŸœž œžœžœ˜Hšžœ˜Kšœž˜"Kšœž˜K˜—K˜K˜—š Ÿœžœžœ žœžœ˜SšŸœ˜,Kš œžœžœžœžœžœ™NKšžœžœžœžœ˜Kšœžœ˜ K˜—Kšœžœ4˜?K˜K˜—šŸœžœžœ˜6Kšœ˜Kšœ*˜*Kšœ˜K˜—K˜šŸœžœ%˜<šŸœ˜,Kš œžœžœžœžœžœ™NK™Kšœ žœ$˜2Kšžœžœ žœžœ˜K˜—Kšœ˜Kšœ0˜0K˜K˜—šŸœžœžœ8˜VšŸ œ˜'Kš œžœžœžœžœžœ™Nš žœžœžœžœžœžœž˜JKšœ˜Kšžœžœžœ˜Kšžœ˜—K˜—Kšœ3˜3K˜K˜—š Ÿœžœžœ žœžœ˜\š œ žœžœžœžœ˜HJšœC˜CJ˜—Kšœžœžœ ˜Kšœ*˜*K˜K˜—šŸ œžœ(žœžœ˜aKšœžœ˜ Kšœžœžœ˜Kšœ6˜6Kš žœžœžœžœžœ˜š žœžœžœžœžœžœž˜JKšžœžœ2žœžœ ˜SKšžœ˜—K˜K˜—šŸœžœB˜VK™OKšœžœ˜ Kšœžœžœ*˜;Kšœžœžœ˜Kšœ4˜4šžœžœžœžœ˜Kšœžœžœžœ˜(Kšœ/˜/Kšœžœ˜Kšœ0˜0K˜—Kšžœžœ˜ K˜K˜—šŸ œžœ6˜IK™=Kšœžœ˜ Kšœžœžœ*˜;Kšœžœžœ˜Kšœ4˜4šžœžœžœžœ˜Kšœžœžœžœ˜(Kšœ/˜/Kšžœžœžœ+˜=Kšžœ1˜5K˜—Kšžœžœ˜ K˜K˜—šŸ œžœžœ6˜PK™LKšœžœ˜ Kšœžœ'˜šŸ œ˜(Kš œžœžœžœžœžœ™NKšœ žœ%˜3Kšžœžœ žœžœ˜K˜—Kšœ˜Kšœ,˜,K˜K˜—š Ÿœžœžœ žœžœ˜LšŸ œ˜(Kš œžœžœžœžœžœ™NKšžœžœžœžœ˜Kšœžœ˜ K˜—Kšœžœ6˜AK˜K˜—šŸ œžœžœ˜3Kšœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœ˜K˜—š Ÿœžœžœžœžœ˜ZK™,šŸœ˜!Kš œžœžœžœžœžœ™NKšœžœžœžœ˜(K˜Kšœžœ˜š žœžœžœžœžœž˜:Kšœ˜Kšœžœ˜Kšœ˜š žœ žœžœ-žœ žœž˜[Kšœ(˜(Kšžœ˜—Kšœžœ˜!Kšœžœ˜!Kšœ1˜1Kšžœ  ˜—K˜—Kšœžœ˜Kšœ ˜ Kšœžœžœ˜Kšœ˜KšœB˜BKšœ˜K˜K˜—šŸœžœ˜.Kšœ'˜'K˜—K˜šŸœžœžœPžœ˜ŒKšœ=˜=K˜K˜—š ŸœžœžœPžœ žœ˜›Kšœ=˜=K˜—K˜K™6šŸ œžœ3˜EK˜šžœž˜Kšœ)žœ&˜SKšœ1˜1Kšœ#žœ#˜JKšœ'žœ%˜PKšœ(˜(Kšžœžœ,˜=—K˜K˜—šŸ œžœ3˜EKšœ žœ(˜;Kšœ žœ ˜Kšœžœ˜&Kšœžœ˜ Kšœžœžœ˜Kšœ;˜;šžœžœžœ˜Kšœžœžœžœ˜.Kšœ8˜8K˜—šžœ˜Kšœžœžœžœ˜(Kšœžœ˜Kšœ7˜7K˜—K˜K˜—š Ÿœžœžœ žœ$žœ˜sKšœp™pKšœ ˜ Kšœ˜Kšœžœ˜KšœY˜Yšžœžœžœ˜Kšœžœ˜8Kšœžœ%˜GKšœ žœ˜K˜—šžœ˜K˜Kšœ<˜Kšœ[™[K™K™Kšœžœ˜ Kšœ& ˜EKšœ˜Kšœžœ˜K˜K˜—š Ÿœžœžœžœ3žœ˜„K˜ Kšœžœžœžœ˜Kšœžœ˜ Kšœžœžœ˜Kšœ>˜>Kšœ"˜"šžœžœžœž˜Kšœ-˜-Kšžœžœžœžœ˜š žœžœžœžœžœžœž˜AKšœžœ$˜1šž˜Kšžœ1ž˜:Kšœ+˜+—Kšžœžœ˜#Kšžœ˜—Kšžœ˜—Kšœ žœ˜K˜K˜—K˜š Ÿœžœžœ žœ$žœ˜oKšœ?™?K™6Kšœ˜Kšœ˜Kšœ>˜>šžœžœžœ˜Kšœ#žœ˜Kšœ œü™‹Kšœ œ•™¢Kšœ œM™ZK˜Kšœ3˜3Kšœ-˜-Kšœ/˜/Kšœ˜Kšœ˜Kšœ žœ!˜1Kšœ"˜"Kšœžœžœ#˜=K˜šžœžœ˜Kšœ˜Kšœ˜Kšœ$˜$K˜—šžœ˜Kšœ.˜.Kš™Kšœ˜Kšœ<˜Kšœ3˜3Kšœ-˜-Kšœ˜Kšœ˜Kšœ žœ!˜1Kšœ"˜"Kšœžœžœ#˜=šžœžœ˜Kšœ˜K˜—šžœ˜Kšœ/˜/Kš™Kšœ˜Kšœ<˜K™>Kšœ œü™‹Kšœ œ•™¢Kšœ œM™ZK˜Kšœ3˜3Kšœ-˜-Kšœ/˜/Kšœ˜Kšœ˜Kšœ žœ!˜1Kšœ"˜"Kšœžœžœ#˜=K˜šžœžœ˜Kšœ˜Kšœ˜Kšœ ˜9KšœZ˜ZK˜—šžœ˜Kšœ/˜/Kš™Kšœ˜Kšœr˜rKš™Kšœ˜KšœZ˜ZKš™Kšœ˜KšœT˜TKšœ.˜.K˜—K˜K˜—K˜š Ÿœžœžœ"žœžœ˜RK™RK™VK™1Kšœ@™@K™ŽKšœžœ™1Kšœ˜Kšœ.˜.Kšœ2˜2Kšœ3˜3Kšœ/˜/Kšœ-˜-Kšœ žœ!˜1Kšœžœžœ#˜=Kšœ žœ˜+K˜šžœžœ '˜NKšœ˜KšœC˜CKšœ?˜?Kšœ+˜+K˜—šžœ˜Kšœ žœžœ˜Kšœžœžœžœ˜,Kšœ:˜:Kšœžœžœ˜?Kšœ4˜4K˜Kš™šžœžœ˜KšœC˜CKšœ-˜-KšœX˜XKšœ!žœ˜&K˜—Kšžœ"žœ˜,Kš™Kšœ?˜?Kšœ+˜+Kš™šžœ žœ žœ˜ Kšœ'žœ˜-šžœ žœ ˜"Kšœžœ˜%Kšœ˜Kšœ=˜=Kšœ#žœ˜)K˜—šžœ ˜!Kšœžœžœ ˜"Kšœžœ˜$Kšœ=˜=š žœžœžœ+žœžœž˜OJšœƒ˜ƒJšžœ˜—šžœžœ˜Kšœ!˜!Kšœ˜K˜—Kšœ2˜2K˜—K˜—šžœ D˜KKšœ'žœ˜,Kšœžœ˜%K˜—K˜Kšœ3˜3K˜—K˜K˜K˜—š Ÿœžœžœ%žœžœ˜UKšœ-˜-Kšœ"˜"Kšœžœžœ#˜=K˜Kšœ4˜4Kš$™$Kšžœ žœ"˜HKšœ˜Kš™Kšžœ&žœ ˜Cšžœ˜šžœžœ ˜Kšžœ žœžœ*˜>Kš™Kšœ7˜7Kšœ3žœ ˜?Kšœ)˜)Kš™K™˜Kšžœ žœžœž˜)šžœ˜Kšœ  ˜%Kšœ  ˜7Kšœd˜dKšœžœ ˜'Kšœ:˜:Kšžœžœ³˜ÇKšžœÜ˜àK–E[slice: GGModelTypes.Slice, childD: GGModelTypes.SliceDescriptor]šœ=˜=KšœR˜`Kšœ&œ)˜]K˜—K˜K˜—š Ÿœžœžœ žœžœ˜R™RKšŸœ ™KšŸœ0™5—Kšœ3˜3Kšœ/˜/Kšœ-˜-Kšœ˜K˜Kš™š žœžœžœžœžœž˜BJ˜Jšœ:žœ ˜FKšœœ˜'Jšžœ˜—K˜—K˜™:K™—šŸœžœ,˜BKšœ-˜-K˜K˜—šŸœžœ+˜@Kšœ˜š Ÿœžœžœžœžœ˜SKšœ7˜7Kšœ ˜ K˜—Kšœ,Ïtœ¡¡˜GK˜K˜—šŸ œžœžœžœEžœ žœžœžœžœžœ˜ÖKšœ˜Kšžœžœ žœžœ˜šžœž˜˜ š Ÿœžœžœžœžœ˜PKšœ˜K˜Kšžœ$žœžœ ˜OKšœ5˜5Kšžœ&žœžœ ˜QK˜Kšœ8˜8šžœžœžœ˜Kšœžœ˜1KšœK˜KKšœ˜šžœžœ$žœ˜0Kšœ2˜2Kšœ7˜7Kšœ0˜0Kšœžœ˜-Kšœ žœ˜K˜—K˜—šžœ˜KšœA˜AKšœ ˜ Kšœžœ˜/Kšœ žœ˜K˜—K˜—Kšœ=¡¡˜GK˜—Kšœ žœ˜Kšžœžœ˜ —K˜K˜—šŸœžœWžœ#˜–KšœA˜AKšœ.žœ ˜:Kšœg˜gKšœ+˜+Kšžœžœžœžœ ˜"Kšœ7˜7Kšœ˜K˜—š Ÿœžœžœdžœ,žœžœ˜ËKšœ9˜9Kš žœ žœžœžœ ˜BKšœ|˜|Kšœ+˜+Kš žœžœžœžœžœ˜'Kšœ4˜4Kšžœžœžœ˜-Kšœ˜K˜—š Ÿ œžœLžœžœžœ˜…KšœT™TK™ŽKšœ žœžœ˜š Ÿœžœžœžœžœ˜RKšœ(˜(Kšœ˜Kšœ/˜/Kšžœžœžœžœ˜ Kšœ žœ˜%KšœŸœ˜Ršžœ žœ ˜,Kšœ˜Kšœžœ˜šžœžœ&žœ˜2KšœD˜DKšœ,˜,K˜—Kšžœ ˜$K˜—K˜—Kšœ,¡œ¡œ¡˜FK˜K˜—Kšœ žœžœ˜'šœžœ˜.K˜—šŸœžœžœžœ˜DKšžœžœžœžœžœžœžœžœ˜~K˜K˜—šŸœžœ žœžœ˜hKš œžœžœžœžœžœ˜9šžœ ž˜Kšœ žœ˜,Kšœ žœ˜,Kšœ žœ˜,Kšœ&žœ˜2Kšžœžœ˜—K˜K˜—šŸœžœžœ:žœ˜rK™LK˜K˜K˜K˜Kšœ/˜/Kšœ"˜"K˜šžœžœ˜Kšœ˜Kš žœžœžœžœžœ*˜…Kšžœ˜Kšœ˜K˜—Kšœ. 3˜aK˜šžœžœ˜'Kš™Kš žœžœžœžœžœ*˜…K˜—šžœ˜šŸœ˜"KšœG™GJšœ˜1J˜—šŸ œ˜KšœJ™JJšœ žœ˜Jšœ œ˜;Jšœ9˜9šžœ…žœ ž˜šJšœ0˜DJšžœ˜—K˜K˜—Kšœ˜K˜!K˜Kš™šžœžœžœ˜Kšœ žœ˜,šžœžœ˜ Kšœ)˜)Kšœ ˜3K˜—K˜—Kš™Kšœ*˜*J˜—Kšœ.˜.K˜K™—™\K˜—šŸœžœžœ)žœ˜kKšœ$˜$Kšœ ˜ K˜—K˜šŸœžœžœ3žœ˜tKšœ˜Kšœ˜KšœC˜Cšžœžœžœ˜Kšœ:˜:Kšœ-˜-K˜—šžœ˜Kšœ-˜-Kšœ:˜:Kšœ3˜3K˜—K˜—K˜š ŸœžœEžœžœžœžœ˜ªšœ(˜(Kš œžœžœžœžœžœ˜Gšœ˜Jšœ œ.˜V—J˜—š žœžœžœžœž˜?JšœA˜A—š žœžœžœžœžœ˜EKšœM˜Mšžœ…žœ ž˜šJšœ*œ?˜xJšžœ˜—J˜—J˜J˜—šŸœžœžœBžœžœžœžœ˜­K™¿Kšœžœ˜šžœžœ˜Kšœ˜K˜—šžœ˜šžœžœž˜šœ˜JšœU˜UJ˜—˜Kš™šžœžœ˜ Kšœ(˜(Kšœ1˜1K˜—K˜—Jšžœžœ˜—J˜—K˜—K™šŸœžœžœ žœBžœžœžœžœžœ˜¿Kšœ`™`KšœD˜DšŸœ˜"KšœG™GJšœ* œ.˜eJ˜—šŸ œ˜KšœJ™JJšœ žœ˜šžœ ž˜šœ˜Jšœ œ˜;K˜—šœ˜Jšœ9˜9šžœ…žœ ž˜šJšœ*œ?˜xJšžœ˜—K˜—Kšžœžœ˜—K˜—Kšœ˜K˜!K˜K˜Kšžœžœžœ˜Kš™Kšœ"˜"šžœžœžœ˜Kšœ žœ˜,šžœžœ˜ Kšœ)˜)Kšœ*œ/˜gK˜—K˜—Kš™Kšœ*˜*K˜K˜—™\K˜—šŸœžœžœ˜=Kšœžœ˜K˜K˜—šŸœžœžœ(žœ ˜pK˜KšœA˜Ašžœ žœžœ˜Kšœ&˜&K˜—K˜K˜—K™K™K™K™/šŸ œžœ7˜GK˜šžœž˜˜ Kšœžœ˜4Kšœ˜Kšœžœ˜ Kšœžœžœ˜Kšœ4˜4šžœžœžœ˜Kšœžœžœžœ˜.Kšœ1˜1K˜—šžœ˜Kšœžœžœžœ˜(Kšœžœ˜Kšœ0˜0K˜—K˜—˜ Kšœ ˜ K˜—Kšžœžœ˜—K˜K˜—™ K™—šŸ œžœ+˜=Kšœ˜š œ žœžœžœžœ˜@Kšœ"˜"Kšœ ˜ J˜—Kšœ$¡œ ¡˜2K˜—K™K™ šŸœžœ5˜JKšœ ˜ K˜K˜—š Ÿ œžœ>žœžœžœ˜€K˜Kšœžœ˜šžœžœžœžœœ žœžœž˜Lšœ œ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜—Kšžœ žœžœžœ˜AJšžœ˜—šžœžœžœžœœ žœžœž˜Kšœ œ˜Kšœ)˜)Kšœ ˜ Kšœ˜Kšœ˜—Kšžœ žœžœžœ˜AJšžœ˜—K˜K˜—š Ÿœžœ žœ?žœžœžœ˜Kšœ˜Kšœžœ˜šžœžœžœžœœ žœžœž˜OKšœ‰˜‰Kšžœ žœžœžœ˜=Kšžœ žœžœžœ˜=Jšžœ˜—šžœžœžœžœœ žœžœž˜LKšœr˜rKšžœ žœžœžœ˜=Kšžœ žœžœžœ˜=Jšžœ˜—K˜K˜—š œžœ?žœžœžœ˜‚Kšœ˜Kšœ(˜(Kšœ+˜+Kšœ1˜1Kšœ1˜1Kšœ œ˜;šžœžœžœžœœ žœžœž˜OKšœ‡˜‡Kšžœ žœžœžœ˜=Kšžœ žœžœžœ˜=Jšžœ˜—šžœžœžœžœœ žœžœž˜LKšœ žœ˜K–+[pt: GGBasicTypes.Point, degrees: REAL]šœp  ˜Kšœžœ˜Kšœ˜KšœžœDžœ˜[Kšœ˜Kšžœ žœžœžœ˜=Jšžœ˜—K˜K˜—K™K™&š Ÿœžœžœ žœžœ˜Kš Ÿœžœžœžœžœ˜Hšžœ0œžœ˜=Jšœ žœ˜Jšœžœ˜ J˜—K˜—Jšœ,¡œ¡¡˜?K˜—K˜šœ™K™—šŸ œžœ+žœ:˜~Kšœžœ ˜Kšœžœ˜ Kšœžœžœ˜Kšœ0˜0Kšžœžœžœžœžœžœžœžœ˜0š žœžœžœžœžœžœž˜JJšœ˜Jšœ žœ˜%Jšžœžœžœ˜'Jšžœ˜—Kšžœžœžœ˜K˜K˜—šŸœžœžœžœžœžœžœžœžœ˜~Kšœžœžœžœ˜šžœžœž˜šžœž˜Kš œžœžœžœžœ žœ ˜9Kšžœ žœ#˜6—K˜Kšžœ˜ —Kšœ ˜K˜—K™Kšœ™KšœC˜CKšœA˜AKšœ- /™\šœ*™*K˜—šœ žœžœ˜K˜—šŸœžœžœ˜1Kšœ4˜4Kšœ9˜9K˜K˜—š Ÿ œžœžœ žœžœ˜TšŸ œžœ"žœžœ™KKšœžœ ™"Kšžœžœ™˜>Kšžœžœ žœžœ˜K˜—šžœ ˜"Kšœžœ˜Kšœžœ ˜Kšœ>˜>Kšžœ žœžœ˜K˜—K˜K˜—š Ÿœžœžœ$žœžœ!˜hšŸ œžœ *˜MKšžœžœž˜*Kšžœ˜"š žœžœžœžœžœ ˜YKšœ"˜"Kšœ- ˜EKšœ$˜$K˜—Kšžœ. ˜JK˜—šŸ œžœ ˜>Kšœ)˜)KšžœYžœžœA  ˜°šžœ .˜5KšœG˜GKšžœYžœžœ@˜£K–5[dc: Imager.Context, circle: GGBasicTypes.Circle]šžœ!˜%K˜—K˜—Kšœ5˜5Kšœ˜Kšœ  2˜RKšœ˜K˜Kšœ$˜$š žœžœžœ'žœžœž˜Kšžœžœž˜!Kšœ8˜8Kšœ8˜8Kšœžœ žœžœ(˜uKšœ@˜@Kšžœžœ˜—Kšžœ˜—Kšœ˜K˜K˜—š Ÿœžœžœžœžœ˜Fšžœžœ˜ Kšœž˜Kšœžœž˜Kšœžœž˜Kšœžœ˜—K˜K˜—šŸœžœžœ=˜`Kšœ žœžœ ˜ KšœL™LKšžœ žœžœžœ˜Kšœ(˜(Kšœ(˜(Kšœ1˜1Kšœ3˜3Kšœ4˜4Kšœ˜K˜—šŸœžœžœ˜6K™XK™ šŸ œžœ˜%šŸœ˜šžœžœž˜šœžœ žœžœ˜-Kšœ˜Kšœ žœ˜K˜—Kšžœ˜—K˜—Kšžœžœžœžœ˜Kšœ!˜!K˜—K˜Kšœ˜Kšœ!˜!Kšœ˜Kšœ˜K˜—K˜K™šŸ œžœ™Kšœ™Kšœ:™:Kšœ&™&Kšœ:™:Kšœ&™&Kšœ5žœ™:Kšœ&™&Kšœ4žœ™9Kšœ&™&Kšœ5žœ™:Kšœ&™&K™K™—šŸœžœ˜Kšœ%˜%Kšœ!˜!Kšœ˜—K˜K™ K˜K™Kšžœ˜—…—»P.Ë