DIRECTORY CardTab, CodeTimer, Draw2d, Feedback, GGAlign, GGAlignTypes, GGBasicTypes, GGCaret, GGCircleCache, GGCircles, GGCoreTypes, GGDragTypes, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGRefresh, GGRefreshTypes, GGScene, GGSegmentTypes, GGSelect, GGSequence, GGShapes, GGSlice, GGSliceOps, GGState, GGTraj, GGUtility, Imager, ImagerBackdoor, ImagerTransformation, Lines2d, Process, Real, RealFns, RefTab, Rope, Vectors2d; GGAlignImpl: CEDAR PROGRAM IMPORTS CardTab, CodeTimer, Draw2d, Feedback, GGCaret, GGCircleCache, GGCircles, GGParent, GGRefresh, GGScene, GGSelect, GGSequence, GGShapes, GGSlice, GGSliceOps, GGState, GGTraj, GGUtility, Imager, ImagerBackdoor, ImagerTransformation, 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; ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator; Edge: TYPE = GGBasicTypes.Edge; EditConstraints: TYPE = GGModelTypes.EditConstraints; FeatureData: TYPE = REF FeatureDataObj; FeatureDataObj: TYPE = GGModelTypes.FeatureDataObj; FeatureWalkProc: TYPE = GGAlign.FeatureWalkProc; FilterLists: TYPE = GGAlignTypes.FilterLists; FilterListsObj: TYPE = GGAlignTypes.FilterListsObj; 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]; }; 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; circleCache: GGCircleCache.Cache _ GGState.GetRadiusCircleCache[ggData]; IF (cachedCircle ¬ GGCircleCache.Lookup[circleCache, circle.radius])#NIL THEN GGCircleCache.DrawCachedCircle[dc, circle.origin, cachedCircle] -- cache hit ELSE { -- cache miss. Attempt a cache entry and retry GGCircleCache.Insert[circleCache, circle.radius]; IF (cachedCircle ¬ GGCircleCache.Lookup[circleCache, circle.radius])#NIL THEN GGCircleCache.DrawCachedCircle[dc, circle.origin, cachedCircle] ELSE GGShapes.DrawCircle[dc, circle]; }; }; zip: Draw2d.Zip; rect: Imager.Rectangle ¬ GGState.GetViewport[ggData]; -- GetViewport returns viewer coordinates viewerToClient: ImagerTransformation.Transformation _ GGState.GetBiScrollersTransforms[ggData].viewerToClient; rect ¬ ImagerTransformation.TransformRectangle[viewerToClient, rect]; -- need client coordinates. 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. $R GGAlignImpl.mesa Contents: Contains routines for building and maintaining sets of gravity active scene objects and alignment objects, called "bags". Copyright Ó 1988, 1992 by Xerox Corporation. All rights reserved. Pier, August 12, 1992 4:41 pm PDT Bier, July 29, 1992 4:39 pm PDT 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[]; ÊLW•NewlineDelimiter –(cedarcode) style™code™KšÏbœ{™ƒKšœ Ïeœ7™BK™!K™K™)K˜—šÏk ˜ J˜©K˜—šÏn œŸœŸ˜JšŸœ™˜ KšŸœŸ˜)K˜Kšœ ŸœŸœÏc˜SK˜Kšœ Ÿœ˜+Kšœ Ÿœ ˜1KšœŸœ$˜9KšœŸœ'˜?KšœŸœ"˜5KšœŸœ%˜;KšœŸœ ˜5KšœŸœŸœ˜-KšœŸœ&˜=KšœŸœ˜!KšœŸœ%˜;KšœŸœ˜%KšœŸœ˜#KšœŸœ&˜AKšœŸœ˜KšœŸœ ˜5Kšœ ŸœŸœ˜'KšœŸœ˜3KšœŸœ˜0Kšœ Ÿœ˜-KšœŸœ˜3Kšœ Ÿœ˜)KšœŸœ˜0Kšœ Ÿœ˜&KšœŸœ˜'KšœŸœ˜!KšœŸœ˜3KšœŸœ˜Kšœ Ÿœ˜*KšœŸœ˜!KšœŸœ˜/KšœŸœ!˜7KšœŸœ#˜;KšœŸœ˜1KšœŸœŸœ!˜Ý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•Artwork Interpress•Bounds:0.0 mm xmin 0.0 mm ymin 178.1528 mm xmax 88.20494 mm ymax –G91.02716 mm topLeading 91.02716 mm topIndent 1.411111 mm bottomLeading šœ=™=—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˜0K˜K˜—š œŸœŸœ8˜Vš  œ˜'Kš œŸœŸœŸœŸœŸœ™Nš ŸœŸœŸœŸœŸœŸœŸ˜JK˜KšŸœŸœŸœ˜KšŸœ˜—K˜—K˜3K˜K˜—š  œŸœŸœ ŸœŸœ˜\š œ ŸœŸœŸœŸœ˜HK˜CK˜—KšœŸœŸœ ˜Kšœ*˜*K˜K˜—š  œŸœ(ŸœŸœ˜aKšœŸœ˜ KšœŸœŸœ˜K˜6Kš ŸœŸœŸœŸœŸœ˜š ŸœŸœŸœŸœŸœŸœŸ˜JKšŸœŸœ2ŸœŸœ ˜SKšŸœ˜—K˜K˜—š œŸœB˜VK™OKšœŸœ˜ KšœŸœŸœ*˜;KšœŸœŸœ˜K˜4šŸœŸœŸœŸœ˜KšœŸœŸœŸœ˜(K˜/KšœŸœ˜K˜0K˜—KšŸœŸœ˜ K˜K˜—š  œŸœ6˜IK™=KšœŸœ˜ KšœŸœŸœ*˜;KšœŸœŸœ˜K˜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˜1KšŸœ¡ ˜—K˜—KšœŸœ˜K˜ KšœŸœŸœ˜K˜K˜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˜8K˜—šŸœ˜KšœŸœŸœŸœ˜(KšœŸœ˜K˜7K˜—K˜K˜—š  œŸœŸœ Ÿœ$Ÿœ˜sKšœp™pKšœ ˜ Kšœ˜KšœŸœ˜K˜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˜3K˜-K˜/K˜K˜Kšœ Ÿœ!˜1K˜"KšœŸœŸœ#˜=K˜šŸœŸœ˜Kšœ˜Kšœ˜Kšœ$˜$K˜—šŸœ˜Kšœ.˜.Kš™Kšœ˜Kšœ<˜K˜3K˜-K˜K˜Kšœ Ÿœ!˜1K˜"KšœŸœŸœ#˜=šŸœŸœ˜Kšœ˜K˜—šŸœ˜K˜/Kš™Kšœ˜Kšœ<˜K™>Kšœ œü™‹Kšœ œ•™¢Kšœ œM™ZK˜K˜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˜2K˜3K˜/K˜-Kšœ Ÿœ!˜1KšœŸœŸœ#˜=Kšœ Ÿœ˜+K˜šŸœŸœ¡'˜NKšœ˜KšœC˜CKšœ?˜?K˜+K˜—šŸœ˜Kšœ ŸœŸœ˜KšœŸœŸœŸœ˜,K˜:KšœŸœŸœ˜?Kšœ4˜4K˜Kš™šŸœŸœ˜KšœC˜CK˜-K˜XKšœ!Ÿœ˜&K˜—KšŸœ"Ÿœ˜,Kš™Kšœ?˜?K˜+Kš™šŸœ Ÿœ Ÿœ˜ Kšœ'Ÿœ˜-šŸœ Ÿœ¡˜"KšœŸœ˜%Kšœ˜Kšœ=˜=Kšœ#Ÿœ˜)K˜—šŸœ¡˜!KšœŸœŸœ ˜"KšœŸœ˜$Kšœ=˜=š ŸœŸœŸœ+ŸœŸœŸ˜OK˜ƒKšŸœ˜—šŸœŸœ˜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˜7Kšœ3Ÿœ ˜?K˜)Kš™K™˜KšŸœ ŸœŸœŸ˜)šŸœ˜Kšœ¡ ˜%Kšœ¡ ˜7K˜dKšœŸœ ˜'K˜:KšŸœŸœ³˜ÇKšŸœÜ˜àK–E[slice: GGModelTypes.Slice, childD: GGModelTypes.SliceDescriptor]˜=KšœR˜`Kšœ&œ)˜]K˜—K˜K˜—š  œŸœŸœ ŸœŸœ˜R™RKš œ ™Kš œ0™5—K˜3K˜/K˜-K˜K˜Kš™š ŸœŸœŸœŸœŸœŸ˜BK˜Kšœ:Ÿœ ˜FKšœœ˜'KšŸœ˜—K˜—K˜™:K™—š œŸœ,˜BK˜-K˜K˜—š œŸœ+˜@Kšœ˜š  œŸœŸœŸœŸœ˜SK˜7Kšœ ˜ K˜—Kšœ,Ïtœ¢¢˜GK˜K˜—š  œŸœŸœŸœEŸœ ŸœŸœŸœŸœŸœ˜ÖKšœ˜KšŸœŸœ ŸœŸœ˜šŸœŸ˜˜ š  œŸœŸœŸœŸœ˜PKšœ˜K˜KšŸœ$ŸœŸœ¡˜OK˜5KšŸœ&ŸœŸœ¡˜QK˜K˜8šŸœŸœŸœ˜KšœŸœ˜1K˜KKšœ˜šŸœŸœ$Ÿœ˜0K˜2K˜7Kšœ0˜0KšœŸœ˜-Kšœ Ÿœ˜K˜—K˜—šŸœ˜K˜AKšœ ˜ KšœŸœ˜/Kšœ Ÿœ˜K˜—K˜—Kšœ=¢¢˜GK˜—Kšœ Ÿœ˜KšŸœŸœ˜ —K˜K˜—š œŸœWŸœ#˜–KšœA˜AKšœ.Ÿœ ˜:K˜gK˜+KšŸœŸœŸœŸœ ˜"K˜7Kšœ˜K˜—š  œŸœŸœdŸœ,ŸœŸœ˜ËKšœ9˜9Kš Ÿœ ŸœŸœŸœ¡˜BK˜|K˜+Kš ŸœŸœŸœŸœŸœ˜'K˜4KšŸœŸœŸœ˜-Kšœ˜K˜—š   œŸœLŸœŸœŸœ˜…KšœT™TK™ŽKšœ ŸœŸœ˜š  œŸœŸœŸœŸœ˜RKšœ(˜(Kšœ˜K˜/KšŸœŸœŸœŸœ˜ Kšœ Ÿœ˜%Kšœ œ˜RšŸœ Ÿœ¡˜,Kšœ˜KšœŸœ˜šŸœŸœ&Ÿœ˜2K˜DKšœ,˜,K˜—KšŸœ ˜$K˜—K˜—Kšœ,¢œ¢œ¢˜FK˜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™GKšœœ˜1K˜—š  œ˜KšœJ™JKšœ Ÿœ˜Kšœ œ˜;K˜9šŸœ…Ÿœ Ÿ˜šKšœœ0˜DKšŸœ˜—K˜K˜—Kšœ˜K˜!K˜Kš™šŸœŸœŸœ˜Kšœ Ÿœ˜,šŸœŸœ˜ Kšœ)˜)Kšœœ ˜3K˜—K˜—Kš™Kšœ*˜*K˜—Kšœ.˜.K˜K™—™\K˜—š œŸœŸœ)Ÿœ˜kK˜$Kšœ ˜ K˜—K˜š œŸœŸœ3Ÿœ˜tKšœ˜Kšœ˜K˜CšŸœŸœŸœ˜K˜:Kšœ-˜-K˜—šŸœ˜K˜-K˜:Kšœ3˜3K˜—K˜—K˜š  œŸœEŸœŸœŸœŸœ˜ªšœ(˜(Kš œŸœŸœŸœŸœŸœ˜G˜Kšœ œ.˜V—K˜—š ŸœŸœŸœŸœŸ˜?KšœA˜A—š ŸœŸœŸœŸœŸœ˜EK˜MšŸœ…Ÿœ Ÿ˜šKšœ*œ?˜xKšŸœ˜—K˜—K˜K˜—š œŸœŸœBŸœŸœŸœŸœ˜­K™¿KšœŸœ˜šŸœŸœ˜Kšœ˜K˜—šŸœ˜šŸœŸœŸ˜šœ˜K˜UK˜—˜Kš™šŸœŸœ˜ K˜(K˜1K˜—K˜—KšŸœŸœ˜—K˜—K˜—K™š œŸœŸœ ŸœBŸœŸœŸœŸœŸœ˜¿Kšœ`™`K˜Dš œ˜"KšœG™GKšœ* œ.˜eK˜—š  œ˜KšœJ™JKšœ Ÿœ˜šŸœ Ÿ˜šœ˜Kšœ œ˜;K˜—šœ˜K˜9šŸœ…Ÿœ Ÿ˜šKšœ*œ?˜xKšŸœ˜—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šŸœ ŸœŸœ˜Kšœ&˜&K˜—K˜K˜—K™K™K™K™/š  œŸœ7˜GK˜šŸœŸ˜˜ KšœŸœ˜4K˜KšœŸœ˜ KšœŸœŸœ˜K˜4šŸœŸœŸœ˜KšœŸœŸœŸœ˜.K˜1K˜—šŸœ˜KšœŸœŸœŸœ˜(KšœŸœ˜K˜0K˜—K˜—˜ K˜ K˜—KšŸœŸœ˜—K˜K˜—™ K™—š  œŸœ+˜=Kšœ˜š œ ŸœŸœŸœŸœ˜@K˜"Kšœ ˜ K˜—Kšœ$¢œ ¢˜2K˜—K™K™ š œŸœ5˜JK˜ K˜K˜—š   œŸœ>ŸœŸœŸœ˜€K˜KšœŸœ˜šŸœŸœŸœŸœœ ŸœŸœŸ˜Lšœ œ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜—KšŸœ ŸœŸœŸœ˜AKšŸœ˜—šŸœŸœŸœŸœœ ŸœŸœŸ˜Kšœ œ˜Kšœ)˜)Kšœ ˜ Kšœ˜Kšœ˜—KšŸœ ŸœŸœŸœ˜AKšŸœ˜—K˜K˜—š  œŸœ Ÿœ?ŸœŸœŸœ˜Kšœ˜KšœŸœ˜šŸœŸœŸœŸœœ ŸœŸœŸ˜OK˜‰KšŸœ ŸœŸœŸœ˜=KšŸœ ŸœŸœŸœ˜=KšŸœ˜—šŸœŸœŸœŸœœ ŸœŸœŸ˜LK˜rKšŸœ ŸœŸœŸœ˜=KšŸœ ŸœŸœŸœ˜=KšŸœ˜—K˜K˜—š œŸœ?ŸœŸœŸœ˜‚Kšœ˜K˜(K˜+K˜1K˜1Kšœ œœ˜;šŸœŸœŸœŸœœ ŸœŸœŸ˜OK˜‡KšŸœ ŸœŸœŸœ˜=KšŸœ ŸœŸœŸœ˜=KšŸœ˜—šŸœŸœŸœŸœœ ŸœŸœŸ˜LKšœ Ÿœ˜K–+[pt: GGBasicTypes.Point, degrees: REAL]šœp¡ ˜KšœŸœ˜K˜KšœŸœDŸœ˜[Kšœ˜KšŸœ ŸœŸœŸœ˜=KšŸœ˜—K˜K˜—K™K™&š  œŸœŸœ ŸœŸœ˜Kš  œŸœŸœŸœŸœ˜HšŸœ0œŸœ˜=Kšœ Ÿœ˜KšœŸœ˜ K˜—K˜—Kšœ,¢œ¢¢˜?K˜—K˜šœ™K™—š  œŸœ+Ÿœ:˜~KšœŸœ ˜KšœŸœ˜ KšœŸœŸœ˜K˜0KšŸœŸœŸœŸœŸœŸœŸœŸœ˜0š ŸœŸœŸœŸœŸœŸœŸ˜JK˜Kšœ Ÿœ˜%KšŸœŸœŸœ˜'KšŸœ˜—KšŸœŸœŸœ˜K˜K˜—š œŸœŸœŸœŸœŸœŸœŸœŸœ˜~KšœŸœŸœŸœ˜šŸœŸœŸ˜šŸœŸ˜Kš œŸœŸœŸœŸœ Ÿœ ˜9KšŸœ Ÿœ#˜6—K˜KšŸœ˜ —Kšœ¡˜K˜—K™Kšœ™K˜CK˜AKšœ-¡/™\šœ*™*K˜—šœ ŸœŸœ˜K˜—š œŸœŸœ˜1K˜4K˜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˜HKšŸœCŸœŸœA¡ ˜ššŸœ¡.˜5K˜1KšŸœCŸœŸœ@˜K–5[dc: Imager.Context, circle: GGBasicTypes.Circle]šŸœ!˜%K˜—K˜—K˜Kšœ6¡)˜_K˜nKšœF¡¡˜aKšœ ¡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šŸœ˜—…—¼$,Í