DIRECTORY CodeTimer, Cubic2, CubicPaths, GGAlign, GGBasicTypes, GGCaret, GGCircles, GGCoreTypes, GGInterfaceTypes, GGModelTypes, GGMultiGravity, GGParent, GGScene, GGSegmentTypes, GGSliceOps, GGState, GGUtility, Lines2d, Real, RealFns, Rope, Vectors2d; GGMultiGravityImpl: CEDAR PROGRAM IMPORTS CodeTimer, CubicPaths, GGAlign, GGCaret, GGCircles, GGParent, GGScene, GGSliceOps, GGState, Lines2d, RealFns, Vectors2d EXPORTS GGMultiGravity = BEGIN AlignBag: TYPE = REF AlignBagObj; AlignBagObj: TYPE = GGInterfaceTypes.AlignBagObj; AlignmentCircle: TYPE = GGInterfaceTypes.AlignmentCircle; AlignmentLine: TYPE = GGInterfaceTypes.AlignmentLine; AlignmentPoint: TYPE = REF AlignmentPointObj; AlignmentPointObj: TYPE = GGInterfaceTypes.AlignmentPointObj; Arc: TYPE = GGBasicTypes.Arc; Bezier: TYPE = Cubic2.Bezier; BezierRef: TYPE = REF Bezier; Caret: TYPE = GGInterfaceTypes.Caret; Circle: TYPE = GGBasicTypes.Circle; Edge: TYPE = GGBasicTypes.Edge; FeatureCycler: TYPE = REF FeatureCyclerObj; FeatureCyclerObj: TYPE = GGInterfaceTypes.FeatureCyclerObj; FeatureData: TYPE = REF FeatureDataObj; FeatureDataObj: TYPE = GGModelTypes.FeatureDataObj; GGData: TYPE = GGInterfaceTypes.GGData; GoodPoint: TYPE = REF GoodPointObj; GoodPointObj: TYPE = GGInterfaceTypes.GoodPointObj; GravityType: TYPE = GGInterfaceTypes.GravityType; JointGenerator: TYPE = GGModelTypes.JointGenerator; Line: TYPE = GGCoreTypes.Line; NearVertexEdgeAndFaces: TYPE = REF NearVertexEdgeAndFacesObj; NearVertexEdgeAndFacesObj: TYPE = GGInterfaceTypes.NearVertexEdgeAndFacesObj; Point: TYPE = GGBasicTypes.Point; PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; Sequence: TYPE = GGModelTypes.Sequence; Slice: TYPE = GGModelTypes.Slice; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj; TriggerBag: TYPE = REF TriggerBagObj; TriggerBagObj: TYPE = GGInterfaceTypes.TriggerBagObj; Vector: TYPE = GGBasicTypes.Vector; BestPoints: TYPE = REF BestPointsObj; BestPointsObj: TYPE = RECORD [ size: NAT, max, min: REAL, maxIndex: NAT, bestTossed: REAL, -- the distance of the closest object that has been thrown away dTol: REAL, -- min + s innerR: REAL, -- find all curves within this radius even if they are not neighbors of the nearest s: REAL, -- the size of neighborhoods. BestPoints should contain all objects that have been seen such that min <= dist(o, q) <= min+s, unless BestPoints overflows. overflow: BOOL, points: SEQUENCE len: NAT OF GoodPoint]; BestFaces: TYPE = REF BestFacesObj; BestFacesObj: TYPE = RECORD [ size: NAT, maxPriority, minPriority: INT, minIndex: NAT, bestPriorityTossed: INT, priorityTol: INT, -- the farthest overlap level that we are interested in overflow: BOOL, faces: SEQUENCE len: NAT OF GoodPoint]; MultiGravityPool: TYPE = REF MultiGravityPoolObj; MultiGravityPoolObj: TYPE = RECORD [ bestpoints: BestPoints, bestcurves: BestPoints, bestfaces: BestFaces ]; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE; Map: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { ENABLE UNWIND => { ggData.multiGravityPool ¬ NewMultiGravityPool[]; }; gravityType: GravityType ¬ GGState.GetGravityType[ggData]; CodeTimer.StartInt[$MultiMap, $Gargoyle]; IF GGState.GetGravity[ggData] THEN { SELECT gravityType FROM pointsPreferred => [resultPoint, normal, feature, hitData] ¬ PointsPreferred[testPoint, t, alignBag, sceneBag, ggData, intersections]; linesPreferred, facesPreferred => [resultPoint, normal, feature, hitData] ¬ LinesPreferred[testPoint, t, alignBag, sceneBag, ggData]; ENDCASE => ERROR; } ELSE { resultPoint ¬ testPoint; feature ¬ NIL; normal ¬ [0,-1]; }; CodeTimer.StopInt[$MultiMap, $Gargoyle]; }; PointsPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { count: NAT; nearVEF: NearVertexEdgeAndFaces; [nearVEF, count] ¬ MultiPointsPreferred[testPoint, t, alignBag, sceneBag, ggData, intersections]; IF count = 0 THEN RETURN [testPoint, [0,-1], NIL, NIL]; IF count = 1 THEN RETURN PrepareWinner[nearVEF, 0] ELSE { neighborCount: NAT ¬ 1; s: REAL = 0.072; -- 1/1000 inches nearestDist: REAL ¬ -1; nearestDist ¬ nearVEF[0].dist; FOR i: NAT IN [1..count) DO IF nearVEF[i].dist - nearestDist < s THEN neighborCount ¬ neighborCount + 1; ENDLOOP; IF neighborCount = 1 THEN RETURN PrepareWinner[nearVEF, 0]; FOR i: NAT IN [0..neighborCount) DO IF nearVEF[i].featureData.type = slice THEN { RETURN PrepareWinner[nearVEF, i]; }; REPEAT FINISHED => RETURN PrepareWinner[nearVEF, 0]; ENDLOOP; }; }; LinesPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { nearVEF: NearVertexEdgeAndFaces; count: NAT; [nearVEF, count] ¬ MultiLinesPreferred[testPoint, t, alignBag, sceneBag, ggData]; IF count = 0 THEN RETURN [testPoint, [0,-1], NIL, NIL]; IF count = 1 THEN RETURN PrepareWinner[nearVEF, 0] ELSE { <> RETURN PrepareWinner[nearVEF, 0]; }; }; FacesPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { nearVEF: NearVertexEdgeAndFaces; count: NAT; [nearVEF, count] ¬ MultiFacesPreferred[testPoint, t, alignBag, sceneBag, ggData]; IF count = 0 THEN RETURN [testPoint, [0,-1], NIL, NIL]; RETURN PrepareWinner[nearVEF, 0] }; PrepareWinner: PROC [nearVEF: NearVertexEdgeAndFaces, index: NAT] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { goodPoint: GoodPoint ¬ nearVEF[index]; resultPoint ¬ goodPoint.point; normal ¬ goodPoint.normal; feature ¬ goodPoint.featureData; hitData ¬ goodPoint.hitData; }; EmptyCycler: PUBLIC PROC [testPoint: Point] RETURNS [featureCycler: FeatureCycler] = { featureCycler ¬ NEW[FeatureCyclerObj ¬ [ nearVEF: NIL, count: 0, index: -1, testPoint: testPoint ]]; }; MapCycler: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [featureCycler: FeatureCycler] = { ENABLE UNWIND => { ggData.multiGravityPool ¬ NewMultiGravityPool[]; }; gravityType: GravityType ¬ GGState.GetGravityType[ggData]; IF GGState.GetGravity[ggData] THEN { SELECT gravityType FROM pointsPreferred => featureCycler ¬ PointsPreferredCycler[testPoint, t, alignBag, sceneBag, ggData, intersections]; linesPreferred, facesPreferred => featureCycler ¬ LinesPreferredCycler[testPoint, t, alignBag, sceneBag, ggData]; ENDCASE => ERROR; } ELSE { featureCycler ¬ EmptyCycler[testPoint]; }; }; CopyVEF: PROC [vef: NearVertexEdgeAndFaces, count: NAT] RETURNS [copy: NearVertexEdgeAndFaces] = { oldGoodPoint, newGoodPoint: GoodPoint; copy ¬ NEW[NearVertexEdgeAndFacesObj[count]]; FOR i: NAT IN [0..count) DO oldGoodPoint ¬ vef[i]; newGoodPoint ¬ NEW[GoodPointObj ¬ [ dist: oldGoodPoint.dist, point: oldGoodPoint.point, normal: oldGoodPoint.normal, featureData: oldGoodPoint.featureData, hitData: oldGoodPoint.hitData, dimension: oldGoodPoint.dimension]]; copy[i] ¬ newGoodPoint; ENDLOOP; }; PointsPreferredCycler: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE, maxDimension: [0..1] ¬ 1] RETURNS [featureCycler: FeatureCycler] = { count: NAT; nearVEF: NearVertexEdgeAndFaces; BEGIN [nearVEF, count] ¬ MultiPointsPreferred[testPoint, t, alignBag, sceneBag, ggData, intersections]; IF count = 0 THEN RETURN[EmptyCycler[testPoint]]; IF count = 1 THEN GOTO MakeSimpleCycler ELSE { neighborCount: NAT ¬ 1; s: REAL = 0.072; -- 1/1000 inches nearestDist: REAL ¬ -1; featureCycler ¬ NEW[FeatureCyclerObj ¬ [ nearVEF: NIL, -- will be filled in below count: count, index: -1, -- will be filled in below testPoint: testPoint ]]; nearestDist ¬ nearVEF[0].dist; FOR i: NAT IN [1..count) DO IF nearVEF[i].dist - nearestDist < s THEN neighborCount ¬ neighborCount + 1; ENDLOOP; IF neighborCount = 1 THEN GOTO MakeSimpleCycler; featureCycler.nearVEF ¬ CopyVEF[nearVEF, count]; FOR i: NAT IN [0..neighborCount) DO IF nearVEF[i].featureData.type = slice THEN { featureCycler.index ¬ i; RETURN; }; REPEAT FINISHED => featureCycler.index ¬ 0; ENDLOOP; }; EXITS MakeSimpleCycler => { featureCycler ¬ NEW[FeatureCyclerObj ¬ [ nearVEF: CopyVEF[nearVEF, count], count: count, index: 0, testPoint: testPoint ]]; }; END; }; LinesPreferredCycler: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [featureCycler: FeatureCycler] = { nearVEF: NearVertexEdgeAndFaces; count: NAT; BEGIN [nearVEF, count] ¬ MultiLinesPreferred[testPoint, t, alignBag, sceneBag, ggData]; IF count = 0 THEN RETURN[EmptyCycler[testPoint]]; IF count = 1 THEN GOTO MakeSimpleCycler ELSE { nearestDist: REAL ¬ -1; bestSceneObject: INT ¬ -1; neighborCount: NAT ¬ 1; s: REAL = 0.072; -- 1/1000 inches nearestDist ¬ nearVEF[0].dist; FOR i: NAT IN [1..count) DO IF nearVEF[i].dist - nearestDist < s THEN neighborCount ¬ neighborCount + 1; ENDLOOP; IF neighborCount = 1 THEN GOTO MakeSimpleCycler; bestSceneObject ¬ -1; featureCycler ¬ NEW[FeatureCyclerObj ¬ [ nearVEF: CopyVEF[nearVEF, count], count: count, index: 0, testPoint: testPoint ]]; }; EXITS MakeSimpleCycler => { featureCycler ¬ NEW[FeatureCyclerObj ¬ [ nearVEF: CopyVEF[nearVEF, count], count: count, index: 0, testPoint: testPoint ]]; }; END; }; FacesPreferredCycler: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [featureCycler: FeatureCycler] = { nearVEF: NearVertexEdgeAndFaces; count: NAT; BEGIN [nearVEF, count] ¬ MultiFacesPreferred[testPoint, t, alignBag, sceneBag, ggData]; IF count = 0 THEN RETURN[EmptyCycler[testPoint]]; IF count = 1 THEN GOTO MakeSimpleCycler ELSE { GOTO MakeSimpleCycler; <> }; EXITS MakeSimpleCycler => { featureCycler ¬ NEW[FeatureCyclerObj ¬ [ nearVEF: CopyVEF[nearVEF, count], count: count, index: 0, testPoint: testPoint ]]; }; END; }; FirstFeature: PUBLIC PROC [featureCycler: FeatureCycler] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { RETURN GetFeature[featureCycler, 0]; }; NextFeature: PUBLIC PROC [featureCycler: FeatureCycler] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { RETURN GetFeature[featureCycler, 1]; }; PreviousFeature: PUBLIC PROC [featureCycler: FeatureCycler] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { RETURN GetFeature[featureCycler, -1]; }; GetFeature: PUBLIC PROC [featureCycler: FeatureCycler, move: [-1..1]] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = { IF featureCycler = NIL THEN { -- return a backdrop position resultPoint ¬ [0.0, 0.0]; -- bogus normal ¬ [0,-1]; feature ¬ NIL; hitData ¬ NIL; } ELSE IF featureCycler.count = 0 THEN { -- return a backdrop position resultPoint ¬ featureCycler.testPoint; normal ¬ [0,-1]; feature ¬ NIL; hitData ¬ NIL; } ELSE { featureCycler.index ¬ (featureCycler.index + move + featureCycler.count) MOD featureCycler.count; [resultPoint, normal, feature, hitData] ¬ PrepareWinner[featureCycler.nearVEF, featureCycler.index]; }; }; MultiMap: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [nearVEF: NearVertexEdgeAndFaces, count: NAT] = { ENABLE UNWIND => ggData.multiGravityPool ¬ NewMultiGravityPool[]; -- in case an ABORT happened while pool was in use CodeTimer.StartInt[$MultiMap, $Gargoyle]; IF GGState.GetGravity[ggData] THEN { SELECT GGState.GetGravityType[ggData] FROM linesPreferred => [nearVEF, count] ¬ MultiLinesPreferred[testPoint, t, alignBag, sceneBag, ggData]; pointsPreferred => [nearVEF, count] ¬ MultiPointsPreferred[testPoint, t, alignBag, sceneBag, ggData, intersections]; ENDCASE => ERROR; } ELSE { nearVEF ¬ NIL; count ¬ 0; }; CodeTimer.StopInt[$MultiMap, $Gargoyle]; }; MultiFacesPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [nearVEF: NearVertexEdgeAndFaces, count: NAT] = { bestCurves, bestPoints: BestPoints; bestFaces: BestFaces; faceCount, curveCount, pointCount: NAT; IF GGAlign.EmptyAlignBag[alignBag] AND GGAlign.EmptyTriggerBag[sceneBag] THEN RETURN[NIL, 0]; [bestFaces, faceCount] ¬ FacesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, t]; SortByOverlap[bestFaces, faceCount]; [bestCurves, curveCount] ¬ CurvesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, t, 0]; [bestPoints, pointCount] ¬ VertsInNeighborhoodPlus[bestCurves, curveCount, alignBag, sceneBag, testPoint, t, ggData, FALSE]; SortPoints[bestPoints, pointCount]; count ¬ MIN[pointCount + curveCount, MaxFeatures]; nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[count]]; MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearVEF, count]; [nearVEF, count] ¬ MergeByOverlapAndDistance[bestFaces, faceCount, nearVEF, count]; }; MultiLinesPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [nearVEF: NearVertexEdgeAndFaces, count: NAT] = { bestCurves: BestPoints; bestPoints: BestPoints; pointCount, curveCount: NAT; IF GGAlign.EmptyAlignBag[alignBag] AND GGAlign.EmptyTriggerBag[sceneBag] THEN RETURN[NIL, 0]; [bestCurves, curveCount] ¬ CurvesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, t, 0]; [bestPoints, pointCount] ¬ VertsInNeighborhoodPlus[bestCurves, curveCount, alignBag, sceneBag, testPoint, t, ggData, FALSE]; SortPoints[bestPoints, pointCount]; count ¬ MIN[pointCount + curveCount, MaxFeatures]; nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[count]]; MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearVEF, count]; }; MultiPointsPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [nearVEF: NearVertexEdgeAndFaces, count: NAT] = { bestCurves: BestPoints; bestPoints: BestPoints; pointCount, curveCount: NAT; innerR: REAL ¬ t/2.0; IF GGAlign.EmptyAlignBag[alignBag] AND GGAlign.EmptyTriggerBag[sceneBag] THEN RETURN[NIL, 0]; [bestCurves, curveCount] ¬ CurvesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, t, innerR]; [bestPoints, pointCount] ¬ VertsInNeighborhoodPlus[bestCurves, curveCount, alignBag, sceneBag, testPoint, t, ggData, intersections]; SortPoints[bestPoints, pointCount]; IF pointCount > 0 AND bestPoints[0].dist < innerR THEN { count ¬ pointCount; nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[count]]; NearPointsFromPoints[bestPoints, pointCount, nearVEF]; } ELSE { count ¬ MIN[pointCount + curveCount, MaxFeatures]; nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[count]]; MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearVEF, count]; }; }; VertsInNeighborhoodPlus: PROC [bestCurves: BestPoints, curveCount: NAT, alignBag: AlignBag, sceneBag: TriggerBag, q: Point, t: REAL, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [h: BestPoints, pointCount: NAT] = { thisPoint: GoodPoint; ProcessPoint: PROC [thisPoint: GoodPoint, featureData: FeatureData] = { -- used for the anchor dSquared: REAL; dTolSquared: REAL ¬ dTol*dTol; dSquared ¬ Vectors2d.DistanceSquared[thisPoint.point, q]; thisPoint.hitData ¬ NIL; IF dSquared < dTolSquared THEN { thisPoint.dist ¬ RealFns.SqRt[dSquared]; thisPoint.normal ¬ GGCaret.GetNormal[NARROW[featureData.shape]]; -- the anchor IF Vectors2d.MagnitudeSquared[thisPoint.normal] = 0 THEN thisPoint.normal ¬ [0, -1]; thisPoint.featureData ¬ featureData; thisPoint.priority ¬ -1; dTol ¬ AddNeighbor[thisPoint, h, FALSE]; }; }; ProcessSlice: PROC [sliceD: SliceDescriptor, thisPoint: GoodPoint, featureData: FeatureData] = { [thisPoint.point, thisPoint.dist, thisPoint.normal, thisPoint.hitData, success] ¬ sliceD.slice.class.closestPoint[sliceD, q, dTol]; IF success THEN { IF thisPoint.dist < dTol THEN { thisPoint.featureData ¬ featureData; IF Vectors2d.MagnitudeSquared[thisPoint.normal] = 0 THEN { thisPoint.normal ¬ [0, -1]; }; thisPoint.priority ¬ GGScene.GetPriority[ggData.scene, sliceD.slice]; dTol ¬ AddNeighbor[thisPoint, h, FALSE]; }; }; }; DoForSliceTrigger: GGAlign.FeatureWalkProc = { sliceD ¬ NARROW[feature.shape, SliceDescriptor]; ProcessSlice[sliceD, thisPoint, feature]; }; sliceD: SliceDescriptor; featureData: FeatureData; success: BOOL ¬ FALSE; dTol: REAL ¬ t; midpoints: BOOL ¬ GGState.GetMidpoints[ggData]; thisPoint ¬ NEW[GoodPointObj]; h ¬ BestPointsFromPool[ggData, t]; IF intersections THEN { dTol ¬ FindIntersections[bestCurves, curveCount, thisPoint, q, dTol, h, ggData.scene]; IF midpoints THEN dTol ¬ FindMidpoints[bestCurves, curveCount, thisPoint, q, dTol, h, ggData.scene]; }; GGAlign.WalkSliceTriggers[sceneBag, DoForSliceTrigger]; featureData ¬ alignBag.anchor; IF featureData # NIL THEN { anchor: Caret ¬ NARROW[featureData.shape]; IF NOT GGCaret.Exists[anchor] THEN ERROR; thisPoint.point ¬ GGCaret.GetPoint[anchor]; ProcessPoint[thisPoint, featureData]; }; pointCount ¬ h.size; IF h.overflow THEN { CodeTimer.StartInt[$PointOverflow, $Gargoyle]; CodeTimer.StopInt[$PointOverflow, $Gargoyle]; }; }; -- end of VertsInNeighborhoodPlus FindIntersections: PROC [bestCurves: BestPoints, curveCount: NAT, thisPoint: GoodPoint, q: Point, tolerance: REAL, h: BestPoints, scene: Scene] RETURNS [dTol: REAL] = { curveI, curveJ: GoodPoint; theseIPoints: LIST OF Point; thisTangency, tangentList: LIST OF BOOL; success: BOOL ¬ FALSE; dTol ¬ tolerance; FOR i: NAT IN [0..curveCount) DO curveI ¬ bestCurves[i]; IF curveI.dist > dTol THEN EXIT; -- this curve is too far away (and the rest are farther) FOR j: NAT IN [0..i] DO curveJ ¬ bestCurves[j]; IF curveJ.dist > dTol THEN LOOP; -- this curve is too far away to be worth considering [theseIPoints, thisTangency] ¬ CurveMeetsCurve[curveI, curveJ]; tangentList ¬ thisTangency; FOR list: LIST OF Point ¬ theseIPoints, list.rest UNTIL list = NIL DO thisPoint.point ¬ list.first; thisPoint.dist ¬ Vectors2d.Distance[thisPoint.point, q]; success ¬ thisPoint.dist <= dTol; IF success THEN { featureData: FeatureData ¬ NEW[FeatureDataObj]; alignmentPoint: AlignmentPoint ¬ NEW[AlignmentPointObj ¬ [ point: thisPoint.point, tangent: tangentList.first, curve1: curveI.featureData, curve2: curveJ.featureData]]; featureData.type ¬ intersectionPoint; featureData.shape ¬ alignmentPoint; thisPoint.featureData ¬ featureData; thisPoint.priority ¬ -1; IF curveI.featureData.type = slice THEN { sliceD: SliceDescriptor ¬ NARROW[curveI.featureData.shape]; slice: Slice ¬ sliceD.slice; thisPoint.priority ¬ GGScene.GetPriority[scene, slice]; thisPoint.hitData ¬ curveI.hitData; thisPoint.normal ¬ GGSliceOps.ClosestSegment[sliceD, q, dTol].bestNormal; -- find normal at nearest vertex. Works for straight edges } ELSE IF curveJ.featureData.type = slice THEN { sliceD: SliceDescriptor ¬ NARROW[curveJ.featureData.shape]; slice: Slice ¬ sliceD.slice; thisPoint.priority ¬ MIN[thisPoint.priority, GGScene.GetPriority[scene, slice]]; thisPoint.hitData ¬ curveJ.hitData; thisPoint.normal ¬ GGSliceOps.ClosestSegment[sliceD, q, dTol].bestNormal; -- find normal at nearest vertex. Works for straight edges } ELSE thisPoint.hitData ¬ NIL; IF Vectors2d.MagnitudeSquared[thisPoint.normal] = 0 THEN thisPoint.normal ¬ [0, -1]; dTol ¬ AddNeighbor[thisPoint, h, FALSE]; }; tangentList ¬ tangentList.rest; ENDLOOP; ENDLOOP; ENDLOOP; }; FindMidpoints: PROC [bestCurves: BestPoints, curveCount: NAT, thisPoint: GoodPoint, q: Point, tolerance: REAL, h: BestPoints, scene: Scene] RETURNS [dTol: REAL] = { curve: GoodPoint; midpoint: Point; success: BOOL ¬ FALSE; dTol ¬ tolerance; FOR i: NAT IN [0..curveCount) DO curve ¬ bestCurves[i]; IF curve.featureData.type#slice THEN LOOP; [midpoint, success] ¬ ComputeMidpoint[curve]; IF NOT success THEN LOOP; thisPoint.point ¬ midpoint; thisPoint.dist ¬ Vectors2d.Distance[thisPoint.point, q]; success ¬ thisPoint.dist <= dTol; IF success THEN { sliceD: SliceDescriptor ¬ NARROW[curve.featureData.shape]; slice: Slice ¬ sliceD.slice; featureData: FeatureData ¬ NEW[FeatureDataObj]; alignmentPoint: AlignmentPoint ¬ NEW[AlignmentPointObj ¬ [ point: thisPoint.point, tangent: FALSE, curve1: curve.featureData, curve2: NIL]]; featureData.type ¬ midpoint; featureData.shape ¬ alignmentPoint; thisPoint.featureData ¬ featureData; thisPoint.hitData ¬ curve.hitData; thisPoint.normal ¬ GGSliceOps.ClosestSegment[sliceD, q, dTol].bestNormal; -- find normal at nearest vertex. Works for edges IF Vectors2d.MagnitudeSquared[thisPoint.normal] = 0 THEN thisPoint.normal ¬ [0, -1]; thisPoint.priority ¬ GGScene.GetPriority[scene, slice]; dTol ¬ AddNeighbor[thisPoint, h, FALSE]; }; ENDLOOP; }; CurvesInNeighborhoodPlus: PROC [alignBag: AlignBag, sceneBag: TriggerBag, q: Point, ggData: GGData, t: REAL, innerR: REAL] RETURNS [h: BestPoints, curveCount: NAT] = { ProcessLine: PROC [line: Line, thisCurve: GoodPoint, featureData: FeatureData] = { thisCurve.dist ¬ Lines2d.LineDistance[q, line]; IF thisCurve.dist < dTol THEN { thisCurve.featureData ¬ featureData; thisCurve.point ¬ Lines2d.DropPerpendicular[q, line]; thisCurve.normal ¬ Vectors2d.Sub[q, thisCurve.point]; IF Vectors2d.MagnitudeSquared[thisCurve.normal] = 0 THEN { thisCurve.normal ¬ [0, -1];}; thisCurve.hitData ¬ NIL; thisCurve.priority ¬ -1; dTol ¬ AddNeighbor[thisCurve, h, TRUE]; } }; ProcessCircle: PROC [circle: Circle, thisCurve: GoodPoint, featureData: FeatureData] = { QProjectedOntoCircle: PUBLIC PROC [] RETURNS [projectedPt: Point] = { epsilon: REAL = 1.0e-5; IF distQtoCenter < epsilon THEN projectedPt ¬ [circle.origin.x + circle.radius, circle.origin.y] ELSE projectedPt ¬ Vectors2d.Add[circle.origin, Vectors2d.Scale[centerToQ, circle.radius/distQtoCenter]]; }; centerToQ: Vector ¬ Vectors2d.Sub[q, circle.origin]; distQtoCenter: REAL ¬ Vectors2d.Magnitude[centerToQ]; thisCurve.dist ¬ ABS[distQtoCenter-circle.radius]; IF thisCurve.dist < dTol THEN { thisCurve.featureData ¬ featureData; thisCurve.point ¬ QProjectedOntoCircle[]; IF thisCurve.dist = 0.0 THEN thisCurve.normal ¬ centerToQ ELSE thisCurve.normal ¬ Vectors2d.Sub[q, thisCurve.point]; thisCurve.hitData ¬ NIL; thisCurve.priority ¬ -1; dTol ¬ AddNeighbor[thisCurve, h, TRUE]; }; }; ProcessSlice: PROC [sliceD: SliceDescriptor, thisCurve: GoodPoint, featureData: FeatureData] = { success: BOOL ¬ FALSE; [thisCurve.point, thisCurve.dist, thisCurve.normal, thisCurve.hitData, success] ¬ sliceD.slice.class.closestSegment[sliceD, q, dTol]; IF success THEN { IF thisCurve.dist < dTol THEN { thisCurve.featureData ¬ featureData; IF Vectors2d.MagnitudeSquared[thisCurve.normal] = 0 THEN { thisCurve.normal ¬ [0, -1]; }; thisCurve.priority ¬ GGScene.GetPriority[ggData.scene, sliceD.slice]; dTol ¬ AddNeighbor[thisCurve, h, TRUE]; }; }; }; ProcessSlopeLine: GGAlign.FeatureWalkProc = { line ¬ NARROW[feature.shape, AlignmentLine].line; ProcessLine[line, thisCurve, feature]; }; DoForSceneSlice: GGAlign.FeatureWalkProc = { sliceD ¬ NARROW[feature.shape, SliceDescriptor]; ProcessSlice[sliceD, thisCurve, feature]; }; line: Line; circle: Circle; sliceD: SliceDescriptor; featureData: FeatureData; added: BOOL ¬ FALSE; thisCurve: GoodPoint ¬ NEW[GoodPointObj]; dTol: REAL ¬ t; h ¬ BestCurvesFromPool[ggData, t, innerR]; GGAlign.WalkSlopeLines[alignBag, ProcessSlopeLine]; FOR angleLines: LIST OF FeatureData ¬ alignBag.angleLines, angleLines.rest UNTIL angleLines = NIL DO featureData ¬ angleLines.first; line ¬ NARROW[featureData.shape, AlignmentLine].line; ProcessLine[line, thisCurve, featureData]; ENDLOOP; FOR dLines: LIST OF FeatureData ¬ alignBag.distanceLines, dLines.rest UNTIL dLines = NIL DO featureData ¬ dLines.first; line ¬ NARROW[featureData.shape]; ProcessLine[line, thisCurve, featureData]; ENDLOOP; FOR circles: LIST OF FeatureData ¬ alignBag.radiiCircles, circles.rest UNTIL circles = NIL DO featureData ¬ circles.first; circle ¬ NARROW[featureData.shape, AlignmentCircle].circle; ProcessCircle[circle, thisCurve, featureData]; ENDLOOP; GGAlign.WalkSliceTriggers[sceneBag, DoForSceneSlice]; curveCount ¬ h.size; IF h.overflow THEN { CodeTimer.StartInt[$CurveOverflow, $Gargoyle]; CodeTimer.StopInt[$CurveOverflow, $Gargoyle]; }; SortCurves[h, curveCount]; }; -- end CurvesInNeighborhoodPlus FacesInNeighborhoodPlus: PROC [alignBag: AlignBag, sceneBag: TriggerBag, q: Point, ggData: GGData, t: REAL] RETURNS [h: BestFaces, faceCount: NAT] = { sliceD: SliceDescriptor; added: BOOL ¬ FALSE; thisFace: GoodPoint ¬ NEW[GoodPointObj]; priorityTol: INT ¬ -1; ProcessSlice: PROC [sliceD: SliceDescriptor, thisFace: GoodPoint, featureData: FeatureData] = { hitData: REF ANY; moreHitDatas: LIST OF REF ANY; success: BOOL ¬ TRUE; IF GGScene.GetPriority[ggData.scene, sliceD.slice] >= priorityTol THEN { [hitData, moreHitDatas] ¬ GGSliceOps.FilledPathsUnderPoint[sliceD.slice, q, t]; IF hitData # NIL THEN { thisFace.point ¬ q; thisFace.priority ¬ GGScene.GetPriority[ggData.scene, sliceD.slice]; thisFace.normal ¬ [0, -1]; thisFace.hitData ¬ hitData; thisFace.featureData ¬ featureData; priorityTol ¬ AddFace[thisFace, h]; }; FOR list: LIST OF REF ANY ¬ moreHitDatas, list.rest UNTIL list = NIL DO thisFace.point ¬ q; thisFace.priority ¬ GGScene.GetPriority[ggData.scene, sliceD.slice]; thisFace.normal ¬ [0, -1]; thisFace.hitData ¬ list.first; thisFace.featureData ¬ featureData; priorityTol ¬ AddFace[thisFace, h]; ENDLOOP; }; }; DoForSceneSlice: GGAlign.FeatureWalkProc = { sliceD ¬ NARROW[feature.shape, SliceDescriptor]; ProcessSlice[sliceD, thisFace, feature]; }; h ¬ BestFacesFromPool[ggData]; GGAlign.WalkSliceTriggers[sceneBag, DoForSceneSlice]; faceCount ¬ h.size; IF h.overflow THEN { CodeTimer.StartInt[$FaceOverflow, $Gargoyle]; CodeTimer.StopInt[$FaceOverflow, $Gargoyle]; }; SortFaces[h, faceCount]; }; MaxFeatures: NAT ¬ 20; BestFacesFromPool: PROC [ggData: GGData] RETURNS [h: BestFaces] = { h ¬ NARROW[ggData.multiGravityPool, MultiGravityPool].bestfaces; h.size ¬ 0; h.maxPriority ¬ -1; h.minIndex ¬ 9999; h.minPriority ¬ LAST[INT]; h.bestPriorityTossed ¬ -1; h.overflow ¬ FALSE; FOR i: NAT IN [0..MaxFeatures) DO h[i].dist ¬ Real.LargestNumber; h[i].featureData ¬ NIL; ENDLOOP; }; BestCurvesFromPool: PROC [ggData: GGData, t: REAL, innerR: REAL] RETURNS [h: BestPoints] = { h ¬ NARROW[ggData.multiGravityPool, MultiGravityPool].bestcurves; h.size ¬ 0; h.max ¬ 0.0; h.maxIndex ¬ 9999; h.min ¬ Real.LargestNumber; h.dTol ¬ t; h.innerR ¬ innerR; h.s ¬ 0.072; -- 1/1000 inches h.bestTossed ¬ Real.LargestNumber; h.overflow ¬ FALSE; FOR i: NAT IN [0..MaxFeatures) DO h[i].dist ¬ Real.LargestNumber; h[i].featureData ¬ NIL; ENDLOOP; }; BestPointsFromPool: PROC [ggData: GGData, t: REAL] RETURNS [h: BestPoints] = { h ¬ NARROW[ggData.multiGravityPool, MultiGravityPool].bestpoints; h.size ¬ 0; h.max ¬ 0.0; h.maxIndex ¬ 9999; h.min ¬ Real.LargestNumber; h.dTol ¬ t; h.s ¬ 0.072; -- 1/1000 inches h.bestTossed ¬ Real.LargestNumber; h.overflow ¬ FALSE; FOR i: NAT IN [0..MaxFeatures) DO h[i].dist ¬ Real.LargestNumber; h[i].featureData ¬ NIL; ENDLOOP; }; AddNeighbor: PROC [thisPoint: GoodPoint, h: BestPoints, curve: BOOL ¬ FALSE] RETURNS [dTol: REAL] = { d: REAL ¬ thisPoint.dist; n: NAT = MaxFeatures; BEGIN SELECT TRUE FROM d > h.dTol => GOTO Toss; -- the caller is not taking our hints and is passing us trash h.size < n => GOTO Add; h.size = n => GOTO ReplaceOrOverflow; ENDCASE => SIGNAL Problem[msg: "Impossible case."]; EXITS Add => { h[h.size]­ ¬ thisPoint­; IF d > h.max THEN {h.max ¬ d; h.maxIndex ¬ h.size}; h.min ¬ MIN[h.min, d]; h.size ¬ h.size + 1; IF curve THEN dTol ¬ h.dTol ¬ MAX[h.min+h.s, h.innerR] ELSE dTol ¬ h.dTol ¬ h.min+h.s; }; ReplaceOrOverflow => { IF d < h.max THEN { -- do the replace h[h.maxIndex]­ ¬ thisPoint­; h.bestTossed ¬ MIN[h.bestTossed, h.max]; h.min ¬ MIN[h.min, d]; IF curve THEN dTol ¬ h.dTol ¬ MAX[h.min+h.s, h.innerR] ELSE dTol ¬ h.dTol ¬ h.min+h.s; h.overflow ¬ h.bestTossed <= dTol; BEGIN maxIndex: NAT ¬ 0; maxDist: REAL ¬ 0.0; FOR i: NAT IN [0..MaxFeatures) DO IF h[i].dist > maxDist THEN {maxIndex ¬ i; maxDist ¬ h[i].dist}; ENDLOOP; h.max ¬ maxDist; h.maxIndex ¬ maxIndex; END; } ELSE { -- toss the new item dTol ¬ h.dTol; h.bestTossed ¬ MIN[h.bestTossed, d]; h.overflow ¬ TRUE; }; }; Toss => { dTol ¬ h.dTol; h.bestTossed ¬ MIN[h.bestTossed, d]; h.overflow ¬ h.bestTossed <= dTol; }; END; }; AddFace: PROC [thisFace: GoodPoint, h: BestFaces] RETURNS [priorityTol: INT] = { d: INT ¬ thisFace.priority; n: NAT = MaxFeatures; BEGIN SELECT TRUE FROM d < h.priorityTol => GOTO Toss; -- the caller is passing us trash h.size < n => GOTO Add; h.size = n => GOTO ReplaceOrOverflow; ENDCASE => SIGNAL Problem[msg: "Impossible case."]; EXITS Add => { h[h.size]­ ¬ thisFace­; IF d < h.minPriority THEN {h.minPriority ¬ d; h.minIndex ¬ h.size}; h.maxPriority ¬ MAX[h.maxPriority, d]; h.size ¬ h.size + 1; priorityTol ¬ h.priorityTol; }; ReplaceOrOverflow => { IF d > h.minPriority THEN { -- do the replace h[h.minIndex]­ ¬ thisFace­; h.bestPriorityTossed ¬ MAX[h.bestPriorityTossed, h.minPriority]; h.maxPriority ¬ MAX[h.maxPriority, d]; h.overflow ¬ TRUE; BEGIN minIndex: NAT ¬ 0; minPriority: INT ¬ LAST[INT]; FOR i: NAT IN [0..MaxFeatures) DO IF h[i].priority < minPriority THEN {minIndex ¬ i; minPriority ¬ h[i].priority}; ENDLOOP; h.minPriority ¬ minPriority; h.minIndex ¬ minIndex; END; priorityTol ¬ h.priorityTol ¬ h.minPriority; } ELSE { -- toss the new item priorityTol ¬ h.priorityTol; h.bestPriorityTossed ¬ MAX[h.bestPriorityTossed, d]; h.overflow ¬ TRUE; }; }; Toss => { priorityTol ¬ h.priorityTol; h.bestPriorityTossed ¬ MAX[h.bestPriorityTossed, d]; h.overflow ¬ TRUE; }; END; }; NearPointsFromPoints: PROC [bestPoints: BestPoints, pointCount: NAT, nearVEF: NearVertexEdgeAndFaces] = { FOR i: NAT IN [0..pointCount) DO nearVEF[i] ¬ bestPoints[i]; ENDLOOP; }; MergePointsAndCurves: PROC [bestPoints: BestPoints, pointCount: NAT, bestCurves: BestPoints, curveCount: NAT, nearVEF: NearVertexEdgeAndFaces, count: NAT] = { pointIndex, curveIndex: NAT; pointDist, curveDist: REAL; pointIndex ¬ 0; curveIndex ¬ 0; FOR i: INTEGER IN [0..count) DO IF pointIndex >= pointCount THEN GOTO NoMorePoints; IF curveIndex >= curveCount THEN GOTO NoMoreCurves; pointDist ¬ bestPoints[i].dist; curveDist ¬ bestCurves[i].dist; IF pointDist <= curveDist THEN { nearVEF[i] ¬ bestPoints[pointIndex]; pointIndex ¬ pointIndex + 1; } ELSE { nearVEF[i] ¬ bestCurves[curveIndex]; curveIndex ¬ curveIndex + 1; }; REPEAT NoMorePoints => { -- finish up with Curves data FOR k: NAT ¬ i, k+1 UNTIL k >= count DO nearVEF[k] ¬ bestCurves[curveIndex]; curveIndex ¬ curveIndex + 1; ENDLOOP}; NoMoreCurves => { -- finish up with points data FOR k: NAT ¬ i, k+1 UNTIL k >= count DO nearVEF[k] ¬ bestPoints[pointIndex]; pointIndex ¬ pointIndex + 1; ENDLOOP}; ENDLOOP; }; MergeByOverlapAndDistance: PROC [faces: BestFaces, faceCount: NAT, curves: NearVertexEdgeAndFaces, curveCount: NAT] RETURNS [nearVEF: NearVertexEdgeAndFaces, total: NAT] = { facePtr, curvePtr: NAT; total ¬ MIN[curveCount+faceCount, MaxFeatures]; nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[total]]; facePtr ¬ curvePtr ¬ 0; FOR i: NAT IN[0..total) DO IF curvePtr >= curveCount THEN GOTO CurvePtrWentOver; IF facePtr >= faceCount THEN GOTO FacePtrWentOver; BEGIN SELECT faces[facePtr].priority - curves[curvePtr].priority FROM = 0 => { -- they're part of the same slice slice: Slice ¬ NARROW[faces[facePtr].featureData.shape, SliceDescriptor].slice; IF GGParent.IsParent[slice] THEN { facePath, curvePath: Slice; faceHitData, curveHitData: REF ANY; [facePath, faceHitData] ¬ GGParent.PathOfHitData[slice, faces[facePtr].hitData]; [curvePath, curveHitData] ¬ GGParent.PathOfHitData[slice, curves[curvePtr].hitData]; IF facePath = curvePath THEN GOTO RemoveDuplicate; SELECT GGParent.ComparePriorities[facePath, curvePath] FROM less => GOTO CurveIsCloser; greater => GOTO FaceIsCloser; equal => GOTO RemoveDuplicate; ENDCASE => ERROR; } ELSE GOTO RemoveDuplicate; }; > 0 => GOTO FaceIsCloser; < 0 => GOTO CurveIsCloser; ENDCASE => ERROR; EXITS RemoveDuplicate => { nearVEF[i] ¬ curves[curvePtr]; curvePtr ¬ curvePtr + 1; facePtr ¬ facePtr + 1; total ¬ total - 1; -- a duplicate has been removed }; FaceIsCloser => { nearVEF[i] ¬ faces[facePtr]; facePtr ¬ facePtr + 1; }; CurveIsCloser => { nearVEF[i] ¬ curves[curvePtr]; curvePtr ¬ curvePtr + 1; }; END; REPEAT CurvePtrWentOver => { -- finish up with faces FOR k: NAT ¬ i, k+1 UNTIL k >= total DO nearVEF[k] ¬ faces[facePtr]; facePtr ¬ facePtr + 1; ENDLOOP}; FacePtrWentOver => { -- finish up with curves FOR k: NAT ¬ i, k+1 UNTIL k >= total DO nearVEF[k] ¬ curves[curvePtr]; curvePtr ¬ curvePtr + 1; ENDLOOP}; ENDLOOP; }; -- end of MergeByOverlapAndDistance SortPoints: PROC [bestPoints: BestPoints, pointCount: NAT] = { temp: GoodPoint; FOR i: INTEGER IN [0..pointCount-2] DO FOR j: INTEGER IN [1..pointCount-i) DO IF bestPoints[j-1].dist > bestPoints[j].dist THEN { temp ¬ bestPoints[j]; bestPoints[j] ¬ bestPoints[j-1]; bestPoints[j-1] ¬ temp; }; ENDLOOP; ENDLOOP; }; SortCurves: PROC [bestCurves: BestPoints, curveCount: NAT] = { temp: GoodPoint; FOR i: INTEGER IN [0..curveCount-2] DO FOR j: INTEGER IN [1..curveCount-i) DO IF bestCurves[j-1].dist > bestCurves[j].dist THEN { temp ¬ bestCurves[j]; bestCurves[j] ¬ bestCurves[j-1]; bestCurves[j-1] ¬ temp; }; ENDLOOP; ENDLOOP; }; SortFaces: PROC [bestFaces: BestFaces, faceCount: NAT] = { temp: GoodPoint; FOR i: INTEGER IN [0..faceCount-2] DO FOR j: INTEGER IN [1..faceCount-i) DO IF bestFaces[j-1].priority < bestFaces[j].priority THEN { temp ¬ bestFaces[j]; bestFaces[j] ¬ bestFaces[j-1]; bestFaces[j-1] ¬ temp; }; ENDLOOP; ENDLOOP; }; SortByOverlap: PROC [faces: BestFaces, count: NAT] = { temp: GoodPoint; FOR i: INTEGER IN [0..count-2] DO FOR j: INTEGER IN [1..count-i) DO IF faces[j-1].priority < faces[j].priority THEN { temp ¬ faces[j]; faces[j] ¬ faces[j-1]; faces[j-1] ¬ temp; }; ENDLOOP; ENDLOOP; }; ComputeMidpoint: PROC [curve: GoodPoint] RETURNS [midpoint: Point, success: BOOL ¬ TRUE] = { class: NAT; simpleCurve: REF ANY; [class, simpleCurve] ¬ ClassifyCurve[curve]; SELECT class FROM 3 => { -- edge edge: Edge ¬ NARROW[simpleCurve]; midpoint ¬ Vectors2d.Scale[Vectors2d.Add[edge.start, edge.end], 0.5]; success ¬ TRUE; }; 4 => { -- arc arc: Arc ¬ NARROW[simpleCurve]; midpoint ¬ Vectors2d.Scale[Vectors2d.Add[arc.p0, arc.p2], 0.5]; success ¬ TRUE; }; ENDCASE => RETURN[[0,0], FALSE]; }; ClassifyCurve: PROC [curve: GoodPoint] RETURNS [class: NAT, simpleCurve: REF ANY] = { feature: FeatureData ¬ curve.featureData; SELECT feature.type FROM slice => { sliceD: SliceDescriptor ¬ NARROW[feature.shape]; hitData: REF ANY ¬ curve.hitData; simpleCurve ¬ GGSliceOps.HitDataAsSimpleCurve[sliceD.slice, hitData]; IF simpleCurve = NIL THEN { simpleCurve ¬ sliceD; class ¬ 6; RETURN; }; }; radiiCircle => { class ¬ 2; simpleCurve ¬ NARROW[feature.shape, AlignmentCircle].circle; RETURN; }; slopeLine, angleLine => { class ¬ 1; simpleCurve ¬ NARROW[feature.shape, AlignmentLine].line; RETURN; }; distanceLine => { class ¬ 1; simpleCurve ¬ NARROW[feature.shape, Line]; RETURN; }; ENDCASE => {class ¬ 0; simpleCurve ¬ NIL; RETURN}; WITH simpleCurve SELECT FROM circle: Circle => class ¬ 2; edge: Edge => class ¬ 3; arc: Arc => class ¬ 4; cubic: BezierRef => class ¬ 5; ENDCASE => ERROR; }; CurveMeetsCurve: PROC [c1, c2: GoodPoint] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL] = { typeOfCurve1, typeOfCurve2: NAT; simpleCurve1, simpleCurve2: REF ANY; [typeOfCurve1, simpleCurve1] ¬ ClassifyCurve[c1]; [typeOfCurve2, simpleCurve2] ¬ ClassifyCurve[c2]; IF typeOfCurve1 >= typeOfCurve2 THEN [iPoints, tangency] ¬ ComputeIntersection[typeOfCurve1][typeOfCurve2][simpleCurve1, simpleCurve2] ELSE [iPoints, tangency] ¬ ComputeIntersection[typeOfCurve2][typeOfCurve1][simpleCurve2, simpleCurve1] }; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; ComputeIntersection: ARRAY [0..6] OF ARRAY [0..6] OF IntersectionProc = [ [NoOpI, NIL, NIL, NIL, NIL, NIL, NIL], -- 0) NoOp [NoOpI, LinLinI, NIL, NIL, NIL, NIL, NIL], -- 1) Line [NoOpI, CirLinI, CirCirI, NIL, NIL, NIL, NIL], -- 2) Circle [NoOpI, EdgLinI, EdgCirI, EdgEdgI, NIL, NIL, NIL], -- 3) Edge [NoOpI, ArcLinI, ArcCirI, ArcEdgI, ArcArcI, NIL, NIL], -- 4) Arc [NoOpI, CubLinI, NoOpI, CubEdgI, NoOpI, NoOpI, NIL], -- 5) Cubic [NoOpI, SlcLinI, SlcCirI, NoOpI, NoOpI, NoOpI, NoOpI] -- 6) Slice ]; NoOpI: IntersectionProc = { iPoints ¬ NIL; tangency ¬ NIL; }; LinLinI: IntersectionProc = { l1: Line ¬ NARROW[c1]; l2: Line ¬ NARROW[c2]; point: Point; parallel: BOOL ¬ FALSE; [point, parallel] ¬ Lines2d.LineMeetsLine[l1, l2]; IF NOT parallel THEN {iPoints ¬ LIST[point]; tangency ¬ LIST[FALSE]} ELSE {iPoints ¬ NIL; tangency ¬ NIL}; }; CirLinI: IntersectionProc = { circle: Circle ¬ NARROW[c1]; line: Line ¬ NARROW[c2]; points: ARRAY [1..2] OF Point; hitCount: [0..2]; tangent: BOOL ¬ FALSE; [points, hitCount, tangent] ¬ GGCircles.CircleMeetsLine[circle, line]; FOR i: NAT IN [1..hitCount] DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent, tangency]; ENDLOOP; }; CirCirI: IntersectionProc = { circle1: Circle ¬ NARROW[c1]; circle2: Circle ¬ NARROW[c2]; points: ARRAY [1..2] OF Point; hitCount: [0..2]; tangent: BOOL ¬ FALSE; [points, hitCount, tangent] ¬ GGCircles.CircleMeetsCircle[circle1, circle2]; FOR i: NAT IN [1..hitCount] DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent, tangency]; ENDLOOP; }; EdgLinI: IntersectionProc = { edge: Edge ¬ NARROW[c1]; line: Line ¬ NARROW[c2]; point: Point; noHit: BOOL ¬ FALSE; [point, noHit] ¬ Lines2d.LineMeetsEdge[line, edge]; IF NOT noHit THEN {iPoints ¬ LIST[point]; tangency ¬ LIST[FALSE]} ELSE {iPoints ¬ NIL; tangency ¬ NIL}; }; EdgCirI: IntersectionProc = { edge: Edge ¬ NARROW[c1]; circle: Circle ¬ NARROW[c2]; points: ARRAY [1..2] OF Point; hitCount: [0..2]; tangent: BOOL ¬ FALSE; [points, hitCount, tangent] ¬ GGCircles.CircleMeetsEdge[circle, edge]; FOR i: NAT IN [1..hitCount] DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent, tangency]; ENDLOOP; }; EdgEdgI: IntersectionProc = { e1: Edge ¬ NARROW[c1]; e2: Edge ¬ NARROW[c2]; point: Point; noHit: BOOL ¬ FALSE; [point, noHit] ¬ Lines2d.EdgeMeetsEdge[e1, e2]; IF NOT noHit THEN {iPoints ¬ LIST[point]; tangency ¬ LIST[FALSE]} ELSE {iPoints ¬ NIL; tangency ¬ NIL}; }; ArcLinI: IntersectionProc = { arc: Arc ¬ NARROW[c1]; line: Line ¬ NARROW[c2]; points: ARRAY [1..2] OF Point; hitCount: [0..2]; tangent: BOOL ¬ FALSE; [points, hitCount, tangent] ¬ GGCircles.ArcMeetsLine[arc, line]; FOR i: NAT IN [1..hitCount] DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent, tangency]; ENDLOOP; }; ArcCirI: IntersectionProc = { arc: Arc ¬ NARROW[c1]; circle: Circle ¬ NARROW[c2]; points: ARRAY [1..2] OF Point; hitCount: [0..2]; tangent: BOOL ¬ FALSE; [points, hitCount, tangent] ¬ GGCircles.CircleMeetsArc[circle, arc]; FOR i: NAT IN [1..hitCount] DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent, tangency]; ENDLOOP; }; ArcEdgI: IntersectionProc = { arc: Arc ¬ NARROW[c1]; edge: Edge ¬ NARROW[c2]; points: ARRAY [1..2] OF Point; hitCount: [0..2]; tangent: BOOL ¬ FALSE; [points, hitCount, tangent] ¬ GGCircles.ArcMeetsEdge[arc, edge]; FOR i: NAT IN [1..hitCount] DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent, tangency]; ENDLOOP; }; ArcArcI: IntersectionProc = { arc1: Arc ¬ NARROW[c1]; arc2: Arc ¬ NARROW[c2]; points: ARRAY [1..2] OF Point; hitCount: [0..2]; tangent: BOOL ¬ FALSE; [points, hitCount, tangent] ¬ GGCircles.ArcMeetsArc[arc1, arc2]; FOR i: NAT IN [1..hitCount] DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent, tangency]; ENDLOOP; }; CubLinI: IntersectionProc = { bezier: BezierRef ¬ NARROW[c1]; line: Line ¬ NARROW[c2]; points: ARRAY [0..2] OF Point; hitCount: [0..3]; tangent: ARRAY [0..2] OF BOOL ¬ ALL[FALSE]; [points, hitCount, tangent] ¬ CubicPaths.CubicMeetsLine[bezier­, -line.s, line.c, -line.d]; FOR i: NAT IN [0..hitCount) DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent[i], tangency]; ENDLOOP; }; CubicMeetsEdge: PROC [bezier: BezierRef, edge: Edge] RETURNS [points: ARRAY [0..2] OF Point, hitCount: [0..3] ¬ 0, tangent: ARRAY [0..2] OF BOOL ¬ ALL[FALSE]] = { linePoints: ARRAY [0..2] OF Point; lineHitCount: [0..3]; lineTangents: ARRAY [0..2] OF BOOL ¬ ALL[FALSE]; [linePoints, lineHitCount, lineTangents] ¬ CubicPaths.CubicMeetsLine[bezier­, -edge.line.s, edge.line.c, -edge.line.d]; FOR i: NAT IN [0..lineHitCount) DO IF Lines2d.OnEdge[linePoints[i], edge] THEN { points[hitCount] ¬ linePoints[i]; tangent[hitCount] ¬ lineTangents[i]; hitCount ¬ hitCount + 1; }; ENDLOOP; }; CubEdgI: IntersectionProc = { IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; bezier: BezierRef ¬ NARROW[c1]; edge: Edge ¬ NARROW[c2]; points: ARRAY [0..2] OF Point; hitCount: [0..3]; tangent: ARRAY [0..2] OF BOOL ¬ ALL[FALSE]; [points, hitCount, tangent] ¬ CubicMeetsEdge[bezier, edge]; FOR i: NAT IN [0..hitCount) DO iPoints ¬ CONS[points[i], iPoints]; tangency ¬ CONS[tangent[i], tangency]; ENDLOOP; }; SlcLinI: IntersectionProc = { sliceD: SliceDescriptor ¬ NARROW[c1]; line: Line ¬ NARROW[c2]; [iPoints, ----] ¬ GGSliceOps.LineIntersection[sliceD, line]; FOR list: LIST OF Point ¬ iPoints, list.rest UNTIL list = NIL DO tangency ¬ CONS[FALSE, tangency]; ENDLOOP; }; SlcCirI: IntersectionProc = { sliceD: SliceDescriptor ¬ NARROW[c1]; circle: Circle ¬ NARROW[c2]; [iPoints, ----] ¬ GGSliceOps.CircleIntersection[sliceD, circle]; FOR list: LIST OF Point ¬ iPoints, list.rest UNTIL list = NIL DO tangency ¬ CONS[FALSE, tangency]; ENDLOOP; }; NewMultiGravityPool: PUBLIC PROC [] RETURNS [REF]= { -- reuseable storage for BestPointAndCurve proc to avoid NEWs pool: MultiGravityPool ¬ NEW[MultiGravityPoolObj]; pool.bestpoints ¬ NEW[BestPointsObj[MaxFeatures]]; pool.bestcurves ¬ NEW[BestPointsObj[MaxFeatures]]; pool.bestfaces ¬ NEW[BestFacesObj[MaxFeatures]]; FOR i: NAT IN [0..MaxFeatures) DO pool.bestpoints[i] ¬ NEW[GoodPointObj]; pool.bestcurves[i] ¬ NEW[GoodPointObj]; pool.bestfaces[i] ¬ NEW[GoodPointObj]; ENDLOOP; RETURN[pool]; }; END. %FGGMultiGravityImpl.mesa Copyright Σ 1986, 1987, 1989, 1992 by Xerox Corporation. All rights reserved. Contents: Performs hit testing similar to uniGravity. Instead of returning a single nearest feature, we return the N (or fewer) nearest features which are within a given tolerance distance from the test point. The algorithm used is described in [Cyan]Documentation>MultiGravity.tioga. Pier, February 18, 1992 5:16 pm PST Bier, April 11, 1990 2:39:57 pm PDT Eisenman, August 11, 1987 6:14:13 pm PDT Doug Wyatt, April 10, 1992 12:09 pm PDT Arbitration [Artwork node; type 'ArtworkInterpress on' to command tool] We arbitrate between those points which are within an epsilon-width ring of the nearest point. Dispatches to StrictDistance, PointsPreferred, or does nothing depending on the currently selected gravity type. If intersections is TRUE, compute the intersections of the objects that are in the bags. In case an ABORT happened while pool was in use Calls MultiPointsPreferred and picks a single "best" feature. If there are no good features within radius t of testPoint, then resultPoint = testPoint and feature = hitData = NIL. If intersections is TRUE then compute all pair-wise intersections of gravity-active curves and count them as distinguished points. Otherwise, let's do arbitration. Prefer scene objects to alignment lines. Later, we will prefer objects that say the testpoint is "inside" them. Calls MultiLinesPreferred and picks a single "best" feature. If there are no good features within radius t of testPoint, then resultPoint = testPoint and feature = hitData = NIL. This code will be used when we wish to do more sophisticated arbitration: We have more than one "equally close" features. Now we choose on the following basis: 1) Prefer scene objects to alignment lines. 2) Prefer points to lines. Later, we will prefer objects that say the testpoint is "inside" them to those that don't. Calls MultiFacesPreferred and picks a single "best" feature. If there is a filled region that contains the cursor point, it choose the nearest such filled region. Otherwise, it behaves just like LinesPreferred gravity. Cycling Gravity In case an ABORT happened while pool was in use Otherwise, let's do arbitration. Find the nearest slice if one is close enough. Otherwise, start with the nearest alignment object. Start cycling at the first slice. Find the nearest line and any that are almost as near, if any lines exist with radius t. Choose one of these lines. If the winner is an edge, use its curve normal. If no object is found, use the cursor position. Find how many nearest neighbors there are. This may come in handy some day for extra arbitration Find how many nearest neighbors there are. Multi-Gravity Routines facesPreferred => [nearVEF, count] _ MultiFacesPreferred[testPoint, t, alignBag, sceneBag, ggData]; Returns up to MaxFeatures closest features, their closest points, and their distances from the testpoint. Features outside of criticalR, will not be included. The results will be located in nearVEF[0] .. nearVEF[count-1]. Returns up to MaxFeatures closest features, their closest points, and their distances from the testpoint. Features outside of criticalR, will not be included. The results will be located in nearVEF[0] .. nearVEF[count-1]. Caution: bestCurves and bestPoints are allocated from a pool. Copy them before returning them to the user (e.g. in a cycler). Returns up to MaxFeatures closest features, their closest points, and their distances from the testpoint. Features outside of criticalR, will not be included. The results will be located in nearVEF[0] .. nearVEF[count-1]. If any points are within the inner radius innerR, then only points (e.g. vertices, control points, and intersection points) will be mentioned. Otherwise, nearVEF may consist of a mixture of points and curves. Caution: bestCurves and bestPoints are allocated from a pool. Copy them before returning them to the user (e.g. in a cycler) For each gravity active point, find its distance from the testpoint. Package this information up into the thisPoint record. Then call AddNeighbor, which will add this point to the list of best points, if appropriate. When VertsInNeighborhoodPlus returns, h will contain a set of up to MaxFeatures points all of which are within a distance t of q. If h.overflow is FALSE, h contains the nearest point in the scene (distance h.min from q) and all other points o such that dist(o, q) <= h.min + h.s, and dist(o, q) <= t. If h.overflow is TRUE, there were more than MaxFeatures such points. In this case, h includes the nearest n such points n = MaxFeatures. thisPoint.normal _ [0,-1]; -- eventually this should just agree with the anchor orientation FeatureWalkProc: TYPE = PROC [feature: FeatureData] RETURNS [done: BOOL _ FALSE]; largeTolerance: REAL = 9999.00; largeTolerance: REAL = 9999.00; thisPoint.normal _ [0, -1]; -- Should figure out midpoint's normal for slice. -- Only edge and arc work now anyway. Returns the curves in the near band in order of increasing distance from q. For each gravity active object, find the distance of the gravity active object from the testpoint. Package this information up into the thisCurve record. Then call AddNeighbor, which will add this curve to the list of best curves, if appropriate. When CurvesInNeighborhood returns, h should contain some number of curves such that they are all within t of q. If h.overflow is FALSE, then h contains the closest curve (at distance h.min) and all other curves o such that dist(o, q) < MAX[h.min + h.s, innerR] and dist(o, q) < t. If h.overflow is TRUE then it wasn't possible to include all such curves. h contains the closest n such curves (where n = MaxFeatures). FeatureWalkProc: TYPE = PROC [feature: FeatureData] RETURNS [done: BOOL _ FALSE]; FeatureWalkProc: TYPE = PROC [feature: FeatureData] RETURNS [done: BOOL _ FALSE]; Align Bag Scene Bag. FeatureWalkProc: TYPE = PROC [feature: FeatureData] RETURNS [done: BOOL _ FALSE]; Scene Bag. Maintaining the Nearest-Neighbor Structure AddNeighbor maintains these invariants: There is valid data in h[0] up to h[size-1]. Call these the Elements of h. All other components of h have dist = infinity. h.min is the minimum value of dist(q, x) for x element of h. If overflow is FALSE, then h contains all objects o, seen so far, such that h.min <= dist(o, q) <= MAX[h.min+h.s, innerR], if curve is TRUE, h.min <= dist(o, q) <= h.min+h.s, if curve is FALSE h may contain other objects as well. If overflow is TRUE, all objects in h satisfy h.min <= dist(o, q) <= MAX[h.min+h.s, innerR], if curve is TRUE, h.min <= dist(o, q) <= h.min+h.s, if curve is FALSE but there are one or more objects that satisfy this property that are not in h. Those objects that are not included are at least as far from q as the farthest object in h. dTol is a hint to the caller that the caller need not pass in objects that are more than dTol units away from q, since AddNeighbor is just going to throw them away. When AddNeighbor returns, h.dTol = t if h.size = 0, h.dTol = MAX[h.min+h.s, innerR], if h.size > 0 and curve is TRUE, h.dTol = h.min + h.s, if h.size > 0 and curve is FALSE. Replace the worst element with the new one if the new is better. Now, update h.max and h.maxIndex the hard way. Replace the worst element with the new one if the new is better. Now, update h.minPriority and h.minIndex the hard way. Merge the bestPoints and the bestCurves. There will be count elements in the result. Merge the two sorted lists together classifying the segments by the AND of the Classifs for each segment We have different paths within the same trajectory or cluster. Choose whichever is closer in the overlap order of the cluster Sort the points in order of increasing distance. Since n is likely to be small, bubble sort is sensible. Sort the curves in order of increasing distance. Since n is likely to be small, bubble sort is sensible. Sort the curves in order of descreasing priority. Since n is likely to be small, bubble sort is sensible. Sort the vertices, edges, and faces in order of decreasing priority. Since n is likely to be small, bubble sort is sensible. Computing Intersections 0) NoOp 1) Line 2) Circle 3) Edge 4) Arc 5) Cubic 6) Slice IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; [points, hitCount] _ GGCircles.ArcMeetsArc[arc1, arc2]; IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL]; Utilities Κ;΅•NewlineDelimiter –(cedarcode) style˜codešœ™Kšœ ΟeœC™NKšΟnœ‘™©Kšœ#™#Kšœ#™#Kšœ(™(K™'K™—šΟk ˜ K˜ς—K˜KšžœŸœŸ˜!KšŸœx˜KšŸœŸ˜˜Kšœ ŸœŸœ ˜!Kšœ Ÿœ ˜1KšœŸœ$˜9KšœŸœ"˜5KšœŸœŸœ˜-KšœŸœ&˜=KšœŸœ˜KšœŸœ˜Kšœ ŸœŸœ˜KšœŸœ˜%KšœŸœ˜#KšœŸœ˜KšœŸœŸœ˜+KšœŸœ%˜;Kšœ ŸœŸœ˜'KšœŸœ˜3KšœŸœ˜'Kšœ ŸœŸœ˜#KšœŸœ!˜3Kšœ Ÿœ ˜1KšœŸœ˜3KšœŸœ˜KšœŸœŸœ˜=KšœŸœ.˜MKšœŸœ˜!KšœŸœ!˜7KšœŸœ#˜;KšœŸœ˜!Kšœ Ÿœ˜'KšœŸœ!˜7Kšœ Ÿœ˜'KšœŸœ˜!KšœŸœ ˜5KšœŸœ#˜;Kšœ ŸœŸœ˜%KšœŸœ"˜5KšœŸœ˜#K˜Kšœ ŸœŸœ˜%šœŸœŸœ˜KšœŸœ˜ Kšœ Ÿœ˜Kšœ Ÿœ˜Kšœ ŸœΟc?˜QKšœŸœ  ˜KšœŸœ S˜aKšœŸœ ›˜€Kšœ Ÿœ˜KšœŸœŸœŸœ ˜(K˜—Kšœ ŸœŸœ˜#šœŸœŸœ˜KšœŸœ˜ KšœŸœ˜Kšœ Ÿœ˜KšœŸœ˜Kšœ Ÿœ 7˜IKšœ Ÿœ˜KšœŸœŸœŸœ ˜'—K˜KšœŸœŸœ˜1šœŸœŸœ˜$Kšœ˜Kšœ˜K˜Kšœ˜—K˜—Kš žœŸœŸœ ŸœŸœ˜/K˜™ I artworkFigure–G91.72222 mm topLeading 91.72222 mm topIndent 1.411111 mm bottomLeading •Bounds60.0 mm xmin 0.0 mm ymin 93.83888 mm xmax 88.9 mm ymax •Artwork Interpress• Interpressš Interpress/Xerox/3.0  f j k j‘₯“ΔWB € ¨  ͺœ‘£Ι ’ ¨ r j  Ί ’ ¨‘‘¨ΔWB € ¨ r j‘₯“ΔWB € ¨‘‘¨ r jΔWΑΑ €ΔDBCΔŒ5r ’ ₯ ¨‘‘¨ΔŒZ―“Ÿ ™‘ Ÿ ‘“‘Έ k ι r jΔ Ω€ €Δ˜jeΔ[‹B ’ ₯ ¨ r j‘‘¨Ÿ ™‘ Ÿ ‘“‘‘ ‘™ k ι‘‘¨ΔI\―“Ÿ ™‘ Ÿ ‘“‘Έ k ι r jΔ Ω€ €Δ.΅0ΔYΥ? ’ ₯ ¨ r j‘‘¨Ÿ ™‘ Ÿ ‘“‘‘ ‘™ k ι‘‘¨ΔI\―“Ÿ ™‘ Ÿ ‘“‘Έ k ι r jΔ Ω€ €ΔΕ Δ1 ’ ₯ ¨ r j‘‘¨Ÿ ™‘ Ÿ ‘“‘‘ ‘™ k ι‘‘¨ΔI\―“Ÿ ™‘ Ÿ ‘“‘Έ k ι r jΔ Ω€ €ΔΗaSΔAh/ ’ ₯ ¨ r j‘‘¨Ÿ ™‘ Ÿ ‘“‘‘ ‘™ k ι‘‘¨ΔI\―“Ÿ ™‘ Ÿ ‘“‘Έ k ι r jΔ› €ΔDBCΔŒ5r ’ ₯ ¨‘‘¨Δ›Ž―“Ÿ ™‘ Ÿ ‘“‘Έ k ι r jΔ@Όρ €ΔDBCΔŒ5r ’ ₯ ¨‘‘¨Δρ ^―“Ÿ ™‘ Ÿ ‘“‘Έ k ι£―“   £‘ˆ‘‘ΕXeroxΕResearchΕ RGBLinear£‘‘¦ η • ” η­“’·“’°“ΔUήUΥ™ΔDBCΔŒ5r—§Υ—˜ r jΔΔ'εΉ ’ ¨ΕXeroxΕ TiogaFontsΕ Helvetica10£‘ “‘•‘ —‘‘¨  ŠΑTolerance Radius– k ι r j‘‘¨’·“‘―“‘‘¨ k ι r jΔV9^Δϊ^Ή ’ ¨ΕXeroxΕ TiogaFontsΕ Helvetica10£‘ “‘•‘ —‘‘¨  ŠΑNearest– k ι r j‘‘¨’·“‘―“‘‘¨ k ι r jΔ_?cΔμ ’ ¨ΕXeroxΕ TiogaFontsΕ Helvetica10£‘ “‘•‘ —‘‘¨  ŠΑCaret– k ι r j‘‘¨’·“‘―“‘‘¨ k ι r jΔ€Δm ’ ¨ΕXeroxΕ TiogaFontsΕ Helvetica10£‘ “‘•‘ —‘‘¨  ŠΑNearest + Epsilon– k ι r j‘‘¨’·“‘―“‘‘¨ k ι k ι k ι k gšœ=™=IartworkCaption™^K˜—šžœŸœŸœŸœKŸœŸœŸœEŸœŸœ˜άKšœ†Ÿœ@™ΚšŸœŸœ˜K˜0Kš /™/K˜—K˜:Kšœ)˜)šŸœŸœ˜$šŸœ Ÿ˜šœ˜K˜s—šœ!˜!K˜c—KšŸœŸœ˜—K˜—šŸœ˜K˜Kšœ Ÿœ˜K˜K˜—Kšœ(˜(K˜K™—šžœŸœŸœŸœKŸœŸœŸœEŸœŸœ˜θKšœ°Ÿœ…™ΈKšœŸœ˜ Kšœ ˜ K˜aKš Ÿœ ŸœŸœŸœŸœ˜7KšŸœ ŸœŸœ˜2šŸœ˜KšΟb ™ KšœŸœ˜KšœŸœ  ˜!Kšœ Ÿœ˜K˜šŸœŸœŸœ Ÿ˜KšŸœ#Ÿœ#˜LKšŸœ˜—KšŸœŸœŸœ˜;Kš‘(™(Kš‘F™FšŸœŸœŸœŸ˜#šŸœ%Ÿœ˜-KšŸœ˜!K˜—šŸ˜KšŸœŸœ˜-—KšŸœ˜—K˜—K˜—šžœŸœŸœŸœ<ŸœEŸœŸœ˜ΚKšœ³™³Kšœ ˜ KšœŸœ˜ Kšœ‘œ+˜QKš Ÿœ ŸœŸœŸœŸœ˜7KšŸœ ŸœŸœ˜2šŸœ˜Kš‘I™IKšœŸœ˜KšœŸœ˜KšœŸœ˜KšœŸœ  ˜!K˜šŸœŸœŸœ Ÿ˜KšŸœ#Ÿœ#˜LKšŸœ˜—šŸœŸœŸœ˜;Kš‘V™VKš‘+™+Kš‘™Kš‘Z™Z—K˜KšŸœ˜!K˜—K˜K˜—šžœŸœŸœŸœ<ŸœEŸœŸœ˜ΚKšœά™άKšœ ˜ KšœŸœ˜ K˜Kšœ‘œ+˜QKš Ÿœ ŸœŸœŸœŸœ˜7KšŸœ˜ K˜—K˜š ž œŸœ*ŸœŸœEŸœŸœ˜šK˜&K˜K˜K˜ K˜K˜K˜—K™K™šž œŸ œŸœ#˜VšœŸœ˜(Kšœ Ÿœ˜ Kšœ ˜ Kšœ ˜ Kšœ˜K˜—K˜K˜—šž œŸœŸœŸœKŸœŸœŸœ#˜΄šŸœŸœ˜K˜0Kš /™/K˜—K˜:šŸœŸœ˜$šŸœ Ÿ˜šœ˜K˜_—šœ!˜!K˜O—KšŸœŸœ˜—K˜—šŸœ˜K˜'K˜—K˜K˜—šžœŸœ&ŸœŸœ#˜bKšœ&˜&KšœŸœ#˜-šŸœŸœŸœ Ÿ˜K˜šœŸœ˜#Kšœ˜Kšœ˜Kšœ˜Kšœ&˜&Kšœ˜Kšœ$˜$K˜—KšŸœ˜—K˜K˜—šžœŸœŸœŸœKŸœŸœŸœ#˜ΪKšœŸœ˜ Kšœ ˜ šŸ˜K˜aKšŸœ ŸœŸœ˜1KšŸœ ŸœŸœ˜'šŸœ˜Kš‘ œe™…KšœŸœ˜KšœŸœ  ˜!Kšœ Ÿœ˜K˜šœŸœ˜(Kšœ Ÿœ ˜(Kšœ ˜ Kšœ  ˜%K˜K˜—K˜šŸœŸœŸœ Ÿ˜KšŸœ#Ÿœ#˜LKšŸœ˜—KšŸœŸœŸœ˜0K˜0Kš‘!™!šŸœŸœŸœŸ˜#šŸœ%Ÿœ˜-K˜KšŸœ˜K˜—šŸ˜KšŸœ˜$—KšŸœ˜—K˜—šŸ˜˜šœŸœ˜(Kšœ!˜!Kšœ ˜ Kšœ ˜ K˜K˜—K˜——KšŸœ˜—K˜K˜K˜—š žœŸœŸœŸœ<Ÿœ#˜’K™₯K™/Kšœ ˜ KšœŸœ˜ šŸ˜Kšœ‘œ+˜QKšŸœ ŸœŸœ˜1KšŸœ ŸœŸœ˜'šŸœ˜Kšœ Ÿœ˜KšœŸœ˜KšœŸœ˜KšœŸœ  ˜!K˜Kš‘*™*šŸœŸœŸœ Ÿ˜KšŸœ#Ÿœ#˜LKšŸœ˜—KšŸœŸœŸœ˜0K˜šœŸœ˜(Kšœ!˜!Kšœ ˜ Kšœ ˜ Kšœ˜K˜—K˜—šŸ˜šœ˜šœŸœ˜(Kšœ!˜!Kšœ ˜ Kšœ ˜ Kšœ˜K˜—K˜——KšŸœ˜—K˜K˜—š žœŸœŸœŸœ<Ÿœ#˜’Kšœ ˜ KšœŸœ˜ šŸ˜Kšœ‘œ+˜QKšŸœ ŸœŸœ˜1KšŸœ ŸœŸœ˜'šŸœ˜KšŸœ˜Kš‘5™5KšœŸœ˜KšœŸœ˜KšœŸœ˜KšœŸœ  ˜!K˜Kš‘*™*šŸœŸœŸœ Ÿ˜KšŸœ#Ÿœ#˜LKšŸœ˜—KšŸœŸœŸœ˜0K˜šœŸœ˜(Kšœ!˜!Kšœ ˜ Kšœ ˜ Kšœ˜K˜—K˜—šŸ˜šœ˜šœŸœ˜(Kšœ!˜!Kšœ ˜ Kšœ ˜ Kšœ˜K˜—K˜——KšŸœ˜—K˜K™—š ž œŸœŸœ ŸœEŸœŸœ˜‘KšŸœ˜$K˜K˜—š ž œŸœŸœ ŸœEŸœŸœ˜KšŸœ˜$K˜K˜—š žœŸœŸœ ŸœEŸœŸœ˜”KšŸœ˜%K˜K˜—š ž œŸœŸœ/ŸœEŸœŸœ˜žšŸœŸœŸœ ˜;Kšœ ˜"K˜Kšœ Ÿœ˜Kšœ Ÿœ˜K˜—šŸœŸœ ˜DK˜&K˜Kšœ Ÿœ˜Kšœ Ÿœ˜K˜—šŸœ˜KšœIŸœ˜aK˜dK˜—K˜K˜—K™šœ™K™—šžœŸœŸœŸœKŸœŸœŸœ*Ÿœ˜ΒKšŸœŸœ6 2˜uKšœ)˜)šŸœŸ˜$šŸœ Ÿ˜*™KšœQ™Q—šœ˜K˜Q—šœ˜K˜a—KšŸœŸœ˜K˜——šŸœ˜Kšœ Ÿœ˜K˜ K˜—Kšœ(˜(K˜K˜—š žœŸœŸœŸœ<Ÿœ*Ÿœ˜°Kšœί™ίKšœ#˜#Kšœ˜Kšœ#Ÿœ˜'K˜šŸœ!Ÿœ#Ÿ œŸœ˜]K˜—Kšœ‘œ+˜[Kšœ$˜$K˜Kšœ‘œ.˜aK˜Kšœ‘œCŸœ˜|Kšœ#˜#K˜KšœŸœ'˜2Kšœ Ÿœ#˜0Kš‘œA˜UKšœ‘œ'˜SK˜K˜—š žœŸœŸœŸœ<Ÿœ*Ÿœ˜°Kšœί™ίKšœ˜Kšœ˜KšœŸœ˜K˜šŸœ!Ÿœ#Ÿ œŸœ˜]K˜—Kš‘~™~Kšœ‘œ.˜aK˜Kšœ‘œCŸœ˜|Kšœ#˜#K˜KšœŸœ'˜2Kšœ Ÿœ#˜0Kš‘œA˜UK˜K˜—šΠbn‘ŸœŸœŸœKŸœŸœŸœ*Ÿœ˜ΞKšœ²™²Kšœ˜Kšœ˜KšœŸœ˜Kš‘Πbk‘œ‘˜Kš Ÿœ!Ÿœ#ŸœŸœŸœ˜]K˜Kš‘}™}Kšœ‘œ3˜fK˜Kšœ‘œR˜„Kšœ#˜#K˜šŸœŸœŸœ˜8K˜Kšœ Ÿœ#˜0Kšœ6˜6K˜—šŸœ˜KšœŸœ'˜2Kšœ Ÿœ#˜0KšœU˜UK˜—K˜K˜—šžœŸœ&Ÿœ,‘œ ΟiœŸœ!ŸœŸœŸœŸœ˜ήKšœ˜KšœΪ™ΪKšœ™KšœŸœ”™ͺKšœŸœt™‰šž œŸœ6 ˜^Kšœ Ÿœ˜Kšœ Ÿœ ˜K˜9KšœŸœ˜šŸœŸœ˜ K˜(Kšœ @™[Kšœ%Ÿœ  ˜NKšŸœ2Ÿœ˜TK˜$K˜Kšœ!Ÿœ˜(K˜—K˜—šž œŸœN˜`Kšœ‘œc˜ƒšŸœ Ÿœ˜šŸœŸœ˜K˜$šŸœ2Ÿœ˜;K˜Kšœ˜—K˜EKšœ!Ÿœ˜(K˜—Kšœ˜—K˜—šžœ˜.Kš œŸœŸœŸœŸœŸœ™QKšœ Ÿœ!˜0Kšœ)˜)K˜K˜—K˜Kšœ˜Kšœ ŸœŸœ˜KšœŸœ˜Kšœ Ÿœ ˜/K˜Kšœ Ÿœ˜K˜"K˜šŸœŸœ˜Kšœ‘œ>˜VKšŸœ Ÿœ‘ œ>˜dK˜—K˜Kšœ$‘œ˜7K˜šŸœŸœŸœ˜Kš‘œ Ÿœ˜*KšŸœŸœŸœŸœ˜)K˜+Kšœ%˜%K˜—K˜šŸœ Ÿœ˜Kšœ.˜.Kšœ.˜.K˜—Kšœ !˜$K™—š žœŸœ&Ÿœ-ŸœŸœŸœ˜¨KšœŸœ ™Kšœ˜KšœŸœŸœ˜KšœŸœŸœŸœ˜(Kšœ ŸœŸœ˜K˜šŸœŸœŸœŸ˜ K˜KšŸœŸœŸœ œ˜YšŸœŸœŸœŸ˜K˜KšŸœŸœŸœ 5˜VK˜?K˜š ŸœŸœŸœ!ŸœŸœŸ˜EK˜K˜8K˜!šŸœ Ÿœ˜KšœŸœ˜/šœ!Ÿœ˜:Kšœ˜Kšœ˜Kšœ˜Kšœ˜—K˜%K˜#K˜$K˜šŸœ!Ÿœ˜)KšœŸœ˜;K˜K˜7K˜#Kšœžœ ;˜…K˜—šŸœŸœ!Ÿœ˜.KšœŸœ˜;K˜KšœŸœ8˜PK˜#Kšœžœ ;˜…K˜—KšŸœŸœ˜KšŸœ2Ÿœ˜TKšœ!Ÿœ˜(K˜—K˜KšŸœ˜—KšŸœ˜—KšŸœ˜—K˜K˜—š ž œŸœ&Ÿœ-ŸœŸœŸœ˜€KšœŸœ ™Kšœ˜Kšœ˜Kšœ ŸœŸœ˜K˜šŸœŸœŸœŸ˜ ˜KšŸœŸœŸœ˜*K˜-KšŸœŸœ ŸœŸœ˜K˜K˜8K˜!šŸœ Ÿœ˜KšœŸœ˜:K˜KšœŸœ˜/šœ!Ÿœ˜:Kšœ˜Kšœ Ÿœ˜Kšœ˜KšœŸœ˜—K˜K˜#K˜$K˜"Kš‘œ 1œ %™xKšœžœ 2˜|KšŸœ2Ÿœ˜TK˜7Kšœ!Ÿœ˜(K˜——KšŸœ˜—K˜K˜—š žœŸœIŸœ ŸœŸœŸœ˜§K™KKšœψ™ψKšœo™oKšœŸœfŸœ)™¨KšœŸœs™ˆšž œŸœA˜RK˜/šŸœŸœ˜K˜$K˜5K˜5šŸœ1Ÿœ˜;K˜—KšœŸœ˜K˜Kšœ!Ÿœ˜'K˜—K˜—šž œŸœE˜XšžœŸœŸœŸœ˜FKšœ Ÿœ ˜KšŸœŸœA˜`KšŸœe˜iKšœ˜—K˜4KšœŸœ"˜5KšœŸœ˜2šŸœŸœ˜K˜$K˜)KšŸœŸœ˜9KšŸœ6˜:KšœŸœ˜K˜Kšœ!Ÿœ˜'K˜—K˜—šž œŸœN˜`Kšœ ŸœŸœ˜K˜…šŸœ Ÿœ˜šŸœŸœ˜K˜$šŸœ2Ÿœ˜;K˜Kšœ˜—K˜EKšœ!Ÿœ˜'K˜—K˜—K˜—šžœ˜-Kš œŸœŸœŸœŸœŸœ™QKšœŸœ$˜1Kšœ&˜&K˜—šžœ˜,Kš œŸœŸœŸœŸœŸœ™QKšœ Ÿœ!˜0Kšœ)˜)K˜—K˜ K˜K˜Kšœ˜KšœŸœŸœ˜KšœŸœ˜)KšœŸœ˜K˜*K˜Kš‘ ™ Kšœ3˜3š Ÿœ‘ œŸœŸœ4ŸœŸœŸ˜dK˜KšœŸœ(˜5Kšœ*˜*KšŸœ˜—š Ÿœ‘œŸœŸœ3Ÿœ ŸœŸ˜[K˜KšœŸœ˜!Kšœ*˜*KšŸœ˜—š Ÿœ‘œŸœŸœ3Ÿœ ŸœŸ˜]K˜Kšœ Ÿœ,˜;Kšœ.˜.KšŸœ˜—Kš‘ ™ Kšœ$‘œ˜5K˜šŸœ Ÿœ˜Kšœ.˜.Kšœ-˜-K˜—Kšœ˜Kšœ ˜"K˜—š žœŸœIŸœŸœŸœ˜–K˜KšœŸœŸœ˜KšœŸœ˜(Kšœ Ÿœ˜K˜šž œŸœM˜_Kšœ ŸœŸœ˜Kš œŸœŸœŸœŸœ˜Kšœ ŸœŸœ˜šŸœ@Ÿœ˜HK˜OšŸœ ŸœŸœ˜K˜K•StartOfExpansion:[scene: GGModelTypes.Scene, slice: GGModelTypes.Slice]˜DK˜Kš‘œ‘œ˜K˜#Kšœ‘œ˜#K˜—šŸœŸœŸœŸœŸœŸœŸœŸ˜GK˜K–:[scene: GGModelTypes.Scene, slice: GGModelTypes.Slice]˜DK˜Kš‘œ‘ œ˜K˜#Kšœ‘œ˜#KšŸœ˜—K˜—K˜—šžœ˜,Kš œŸœŸœŸœŸœŸœ™QKšœ Ÿœ!˜0Kšœ(˜(K˜—K˜K˜K˜Kš‘ ™ Kšœ$‘œ˜5K˜K˜šŸœ Ÿœ˜Kšœ-˜-Kšœ,˜,K˜—Kšœ˜K˜—K˜K™*Kšž œŸœ˜K˜šžœŸœŸœ˜CKšœŸœ6˜@K˜ K˜K˜KšœŸœŸœ˜K˜Kšœ Ÿœ˜šŸœŸœŸœŸ˜!K˜KšœŸœ˜KšŸœ˜—K˜K˜—š žœŸœŸœ ŸœŸœ˜\KšœŸœ7˜AK˜ K˜ K˜K˜K˜ K˜Kšœ  ˜K˜"Kšœ Ÿœ˜šŸœŸœŸœŸ˜!K˜KšœŸœ˜KšŸœ˜—K˜K˜—šžœŸœŸœŸœ˜NKšœŸœ7˜AK˜ K˜ K˜K˜K˜ Kšœ  ˜K˜"Kšœ Ÿœ˜šŸœŸœŸœŸ˜!K˜KšœŸœ˜KšŸœ˜—K˜—˜Kšœγ™γšœŸœ7™KKšœŸœ&™@Kšœ3™3—Kšœ$™$šœŸœ™-KšœŸœ&™@Kšœ3™3—Kšœ¬™¬Kšœ€™€šœ3™3Kšœ Ÿœ5™AKšœ7™7—K™—š ž œŸœ.ŸœŸœŸœŸœ˜eKšœŸœ˜KšœŸœ˜šŸ˜šŸœŸœŸ˜KšœŸœ =˜VKšœŸœ˜KšœŸœ˜%KšŸœŸœ"˜3—šŸ˜šœ˜K˜KšŸœ Ÿœ"˜3KšœŸœ ˜K˜KšŸœŸœŸœ˜6KšŸœ˜K˜—šœ˜Kš‘@™@šŸœ ŸœŸ ˜%K˜KšœŸœ˜(KšœŸœ ˜KšŸœŸœŸœ˜6KšŸœ˜K˜"Kš‘.™.šŸ˜Kšœ Ÿœ˜Kšœ Ÿœ˜šŸœŸœŸœŸ˜!KšŸœŸœ%˜@KšŸœ˜—K˜K˜KšŸœ˜—K˜—šŸœ ˜K˜KšœŸœ˜$Kšœ Ÿœ˜K˜—K˜—šœ ˜ K˜KšœŸœ˜$K˜"K˜——KšŸœ˜—K˜K˜—šžœŸœ%ŸœŸœ˜PKšœŸœ˜KšœŸœ˜šŸ˜šŸœŸœŸ˜KšœŸœ !˜AKšœŸœ˜KšœŸœ˜%KšŸœŸœ"˜3—šŸ˜šœ˜K˜KšŸœŸœ*˜CKšœŸœ˜&K˜K˜K˜—šœ˜Kš‘@™@šŸœŸœŸ ˜-K˜KšœŸœ&˜@KšœŸœ˜&Kšœ Ÿœ˜Kš‘6™6šŸ˜Kšœ Ÿœ˜Kšœ ŸœŸœŸœ˜šŸœŸœŸœŸ˜!KšŸœŸœ-˜PKšŸœ˜—K˜K˜KšŸœ˜—K˜,K˜—šŸœ ˜K˜KšœŸœ˜4Kšœ Ÿœ˜K˜—K˜—šœ ˜ K˜KšœŸœ˜4Kšœ Ÿœ˜K˜——KšŸœ˜—K˜—K˜šžœŸœ&Ÿœ&˜išŸœŸœŸœŸ˜ K˜KšŸœ˜—K˜K˜—š žœŸœ&Ÿœ&Ÿœ*Ÿœ˜žKš‘U™UKšœŸœ˜KšœŸœ˜K˜K˜šŸœŸœŸœ Ÿ˜IprocšŸœŸœŸœ˜3NšŸœŸœŸœ˜3N˜N˜šŸœŸœ˜ N˜$N˜N˜—šŸœ˜N˜$N˜N˜—šŸ˜Nšœ ˜/šŸœŸœ Ÿœ Ÿ˜'N˜$N˜—NšŸœ˜ Nšœ ˜/šŸœŸœ Ÿœ Ÿ˜'N˜$N˜—NšŸœ˜ —NšŸœ˜K˜K˜——š žœŸœŸœ.ŸœŸœ*Ÿœ˜­Nšœh™hNšœŸœ˜N˜KšœŸœ$˜/Kšœ Ÿœ#˜0N˜N˜šŸœŸœŸœ Ÿ˜NšŸœŸœŸœ˜5NšŸœŸœŸœ˜2N˜šŸ˜NšŸœ5Ÿ˜?šœ  !˜*šœŸœ:˜OšŸœŸœ˜"N˜NšœŸœŸœ˜#N˜PN˜TNšŸœŸœŸœ˜2N™~šŸœ1Ÿ˜;NšœŸœ˜Nšœ Ÿœ˜Nšœ Ÿœ˜NšŸœŸœ˜—N˜—NšŸœŸœ˜—N˜—NšœŸœ˜NšœŸœ˜NšŸœŸœ˜šŸ˜šœ˜N˜N˜N˜Nš‘œ‘ œ ˜2N˜—˜N˜N˜N˜—˜N˜N˜N˜——NšŸœ˜——šŸ˜Nšœ ˜-šŸœŸœ Ÿœ Ÿ˜'N˜N˜—NšŸœ˜ Nšœ ˜-šŸœŸœ Ÿœ Ÿ˜'N˜N˜—NšŸœ˜ —NšŸœ˜Nšœ #˜&N˜—šž œŸœ&Ÿœ˜>K™iKšœ˜šŸœŸœŸœŸ˜&šŸœŸœŸœŸ˜&šŸœ+Ÿœ˜3K˜K˜ K˜K˜—KšŸœ˜—KšŸœ˜—˜K˜——šž œŸœ&Ÿœ˜>K™iKšœ˜šŸœŸœŸœŸ˜&šŸœŸœŸœŸ˜&šŸœ+Ÿœ˜3K˜K˜ K˜K˜—KšŸœ˜—KšŸœ˜—K˜K˜—šž œŸœ#Ÿœ˜:K™jKšœ˜šŸœŸœŸœŸ˜%šŸœŸœŸœŸ˜%šŸœ1Ÿœ˜9K˜K˜K˜K˜—KšŸœ˜—KšŸœ˜—K˜K˜—šž œŸœŸœ˜6K™}Kšœ˜šŸœŸœŸœŸ˜!šŸœŸœŸœŸ˜!šŸœ)Ÿœ˜1K˜K˜K˜K˜—KšŸœ˜—KšŸœ˜—K˜—K™™K™—š žœŸœŸœŸœŸœ˜\KšœŸœ˜ Kšœ ŸœŸœ˜K˜,šŸœŸ˜šœ ˜Kšœ Ÿœ˜!K˜EKšœ Ÿœ˜K˜—šœ ˜ Kšœ Ÿœ˜K˜?Kšœ Ÿœ˜K˜—KšŸœŸœŸœ˜ —K˜K˜—š ž œŸœŸœ ŸœŸœŸœ˜UK˜)šŸœŸ˜šœ ˜ KšœŸœ˜0Kšœ ŸœŸœ˜!K˜EšŸœŸœŸœ˜K˜K˜ KšŸœ˜K˜—Kšœ˜—šœ˜K˜ KšœŸœ(˜