DIRECTORY AtomButtons, CodeTimer, GGBasicTypes, GGCaret, GGCircles, GGInterfaceTypes, GGModelTypes, GGMultiGravity, GGSegmentTypes, GGState, GGUtility, Lines2d, Real, RealFns, Rope, Vectors2d; GGMultiGravityImpl: CEDAR PROGRAM IMPORTS AtomButtons, CodeTimer, GGCaret, GGCircles, 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; Caret: TYPE = GGInterfaceTypes.Caret; Circle: TYPE = GGBasicTypes.Circle; Edge: TYPE = GGBasicTypes.Edge; FeatureData: TYPE = REF FeatureDataObj; FeatureDataObj: TYPE = GGModelTypes.FeatureDataObj; GGData: TYPE = GGInterfaceTypes.GGData; GoodPoint: TYPE = REF GoodPointObj; GoodPointObj: TYPE = GGMultiGravity.GoodPointObj; JointGenerator: TYPE = GGModelTypes.JointGenerator; Line: TYPE = GGBasicTypes.Line; NearDistances: TYPE = REF NearDistancesObj; NearDistancesObj: TYPE = GGMultiGravity.NearDistancesObj; NearFeatures: TYPE = REF NearFeaturesObj; NearFeaturesObj: TYPE = GGMultiGravity.NearFeaturesObj; NearPoints: TYPE = REF NearPointsObj; NearPointsAndCurves: TYPE = REF NearPointsAndCurvesObj; NearPointsAndCurvesObj: TYPE = GGMultiGravity.NearPointsAndCurvesObj; NearPointsObj: TYPE = GGMultiGravity.NearPointsObj; Outline: TYPE = GGModelTypes.Outline; OutlineDescriptor: TYPE = REF OutlineDescriptorObj; OutlineDescriptorObj: TYPE = GGModelTypes.OutlineDescriptorObj; OutlinePointPairGenerator: TYPE = GGModelTypes.OutlinePointPairGenerator; Point: TYPE = GGBasicTypes.Point; PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; Sequence: TYPE = GGModelTypes.Sequence; Slice: TYPE = GGModelTypes.Slice; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; TriggerBag: TYPE = REF TriggerBagObj; TriggerBagObj: TYPE = GGInterfaceTypes.TriggerBagObj; BestPoints: TYPE = REF BestPointsObj; BestPointsObj: TYPE = RECORD [ size: NAT, max, min: REAL, 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]; MultiGravityPool: TYPE = REF MultiGravityPoolObj; MultiGravityPoolObj: TYPE = RECORD [ distances: NearDistances, features: NearFeatures, bestpoints: BestPoints, bestcurves: BestPoints ]; EmptyBag: PROC [alignBag: AlignBag] RETURNS [BOOL] = { RETURN[ alignBag.slopeLines = NIL AND alignBag.angleLines = NIL AND alignBag.radiiCircles = NIL AND alignBag.distanceLines = NIL AND alignBag.midpoints = NIL AND alignBag.intersectionPoints = NIL AND alignBag.anchor = NIL]; }; EmptyTriggers: PROC [triggerBag: TriggerBag] RETURNS [BOOL] = { RETURN[ triggerBag.slices = NIL AND triggerBag.intersectionPoints = NIL AND triggerBag.anchor = NIL ]; }; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE; Map: PUBLIC PROC [testPoint: Point, criticalR: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, useAlignBag: BOOL] RETURNS [resultPoint: Point, feature: FeatureData, hitData: REF ANY] = { ENABLE UNWIND => ggData.multiGravityPool _ NewMultiGravityPool[]; -- in case an ABORT happened while pool was in use CodeTimer.StartInt[$MultiMap, $Gargoyle]; IF GGState.Gravity[ggData] THEN { SELECT ggData.hitTest.gravityType FROM strictDistance => [resultPoint, feature, hitData] _ StrictDistance[testPoint, criticalR, alignBag, sceneBag, ggData]; innerCircle => [resultPoint, feature, hitData] _ PointsPreferred[testPoint, criticalR, ggData.hitTest.innerR, alignBag, sceneBag, ggData, useAlignBag]; ENDCASE => ERROR; } ELSE { resultPoint _ testPoint; feature _ NIL; }; CodeTimer.StopInt[$MultiMap, $Gargoyle]; }; StrictDistance: PUBLIC PROC [testPoint: Point, criticalR: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [resultPoint: Point, feature: FeatureData, hitData: REF ANY] = { nearPointsAndCurves: NearPointsAndCurves; count: NAT; [nearPointsAndCurves, count] _ MultiStrictDistance[testPoint, criticalR, alignBag, sceneBag, ggData]; IF count = 0 THEN RETURN [testPoint, NIL, NIL]; IF count = 1 THEN RETURN PrepareWinner[nearPointsAndCurves, 0] ELSE { mgp: MultiGravityPool _ NARROW[ggData.multiGravityPool, MultiGravityPool]; distances: NearDistances _ mgp.distances; features: NearFeatures _ mgp.features; nearestDist: REAL _ -1; bestSceneObject: INT _ -1; neighborCount: NAT _ 1; s: REAL = 0.072; -- 1/1000 inches FOR i: NAT IN [0..count) DO goodPoint: GoodPoint _ nearPointsAndCurves[i]; distances[i] _ goodPoint.dist; features[i] _ goodPoint.featureData; ENDLOOP; nearestDist _ distances[0]; FOR i: NAT IN [1..count) DO IF distances[i] - nearestDist < s THEN neighborCount _ neighborCount + 1; ENDLOOP; IF neighborCount = 1 THEN RETURN PrepareWinner[nearPointsAndCurves, 0]; bestSceneObject _ -1; RETURN PrepareWinner[nearPointsAndCurves, 0]; }; }; PointsPreferred: PUBLIC PROC [testPoint: Point, criticalR: REAL, innerR: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, useAlignBag: BOOL] RETURNS [resultPoint: Point, feature: FeatureData, hitData: REF ANY] = { count: NAT; nearPointsAndCurves: NearPointsAndCurves; [nearPointsAndCurves, count] _ MultiPointsPreferred[testPoint, criticalR, innerR, alignBag, sceneBag, ggData, useAlignBag]; IF count = 0 THEN RETURN [testPoint, NIL, NIL]; IF count = 1 THEN RETURN PrepareWinner[nearPointsAndCurves, 0] ELSE { mgp: MultiGravityPool _ NARROW[ggData.multiGravityPool, MultiGravityPool]; distances: NearDistances _ mgp.distances; features: NearFeatures _ mgp.features; neighborCount: NAT _ 1; s: REAL = 0.072; -- 1/1000 inches nearestDist: REAL _ -1; FOR i: NAT IN [0..count) DO goodPoint: GoodPoint _ nearPointsAndCurves[i]; distances[i] _ goodPoint.dist; features[i] _ goodPoint.featureData; ENDLOOP; nearestDist _ distances[0]; FOR i: NAT IN [1..count) DO IF distances[i] - nearestDist < s THEN neighborCount _ neighborCount + 1; ENDLOOP; IF neighborCount = 1 THEN RETURN PrepareWinner[nearPointsAndCurves, 0]; FOR i: NAT IN [0..neighborCount) DO IF features[i].type = outline OR features[i].type = slice THEN { RETURN PrepareWinner[nearPointsAndCurves, i]; }; REPEAT FINISHED => RETURN PrepareWinner[nearPointsAndCurves, 0]; ENDLOOP; }; }; PrepareWinner: PROC [nearPointsAndCurves: NearPointsAndCurves, index: NAT] RETURNS [resultPoint: Point, feature: FeatureData, hitData: REF ANY] = { goodPoint: GoodPoint _ nearPointsAndCurves[index]; resultPoint _ goodPoint.point; feature _ goodPoint.featureData; hitData _ goodPoint.hitData; }; NearestNeighborsPlusSome: PROC [q: Point, initialD: REAL, alignBag: AlignBag, sceneBag: TriggerBag, anchor: Caret, ggData: GGData, distinguishedPointsOnly: BOOL _ FALSE] RETURNS [g: NearPointsAndCurves, count: NAT] = { }; MultiMap: PUBLIC PROC [testPoint: Point, criticalR: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, useAlignBag: BOOL] RETURNS [nearPointsAndCurves: NearPointsAndCurves, count: NAT] = { ENABLE UNWIND => ggData.multiGravityPool _ NewMultiGravityPool[]; -- in case an ABORT happened while pool was in use CodeTimer.StartInt[$MultiMap, $Gargoyle]; SELECT AtomButtons.GetButtonState[ggData.hitTest.gravButton] FROM on => SELECT ggData.hitTest.gravityType FROM strictDistance => [nearPointsAndCurves, count] _ MultiStrictDistance[testPoint, criticalR, alignBag, sceneBag, ggData]; innerCircle => [nearPointsAndCurves, count] _ MultiPointsPreferred[testPoint, criticalR, ggData.hitTest.innerR, alignBag, sceneBag, ggData, useAlignBag]; ENDCASE => ERROR; off => { nearPointsAndCurves _ NIL; count _ 0; }; ENDCASE => ERROR; CodeTimer.StopInt[$MultiMap, $Gargoyle]; }; MultiStrictDistance: PUBLIC PROC [testPoint: Point, criticalR: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [nearPointsAndCurves: NearPointsAndCurves, count: NAT] = { bestCurves: BestPoints; bestPoints: BestPoints; pointCount, curveCount: NAT; IF EmptyBag[alignBag] AND EmptyTriggers[sceneBag] THEN RETURN[NIL, 0]; [bestCurves, curveCount] _ CurvesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, criticalR, 0]; SortCurves[bestCurves, curveCount]; [bestPoints, pointCount] _ PointsInNeighborhoodPlus[bestCurves, curveCount, alignBag, sceneBag, testPoint, criticalR, ggData, FALSE]; SortPoints[bestPoints, pointCount]; count _ MIN[pointCount + curveCount, MaxFeatures]; nearPointsAndCurves _ NEW[NearPointsAndCurvesObj[count]]; MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearPointsAndCurves, count]; }; MultiPointsPreferred: PUBLIC PROC [testPoint: Point, criticalR: REAL, innerR: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, useAlignBag: BOOL] RETURNS [nearPointsAndCurves: NearPointsAndCurves, count: NAT] = { bestCurves: BestPoints; bestPoints: BestPoints; pointCount, curveCount: NAT; IF EmptyBag[alignBag] AND EmptyTriggers[sceneBag] THEN RETURN[NIL, 0]; [bestCurves, curveCount] _ CurvesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, criticalR, innerR]; SortCurves[bestCurves, curveCount]; [bestPoints, pointCount] _ PointsInNeighborhoodPlus[bestCurves, curveCount, alignBag, sceneBag, testPoint, criticalR, ggData, useAlignBag]; SortPoints[bestPoints, pointCount]; IF pointCount > 0 AND bestPoints[0].dist < innerR THEN { count _ pointCount; nearPointsAndCurves _ NEW[NearPointsAndCurvesObj[count]]; NearPointsFromPoints[bestPoints, pointCount, nearPointsAndCurves]; } ELSE { count _ MIN[pointCount + curveCount, MaxFeatures]; nearPointsAndCurves _ NEW[NearPointsAndCurvesObj[count]]; MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearPointsAndCurves, count]; }; }; PointsInNeighborhoodPlus: PROC [bestCurves: BestPoints, curveCount: NAT, alignBag: AlignBag, sceneBag: TriggerBag, q: Point, t: REAL, ggData: GGData, useAlignBag: BOOL] 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.featureData _ featureData; dTol _ MergePoint[thisPoint, h, dTol]; }; }; ProcessSlice: PROC [sliceD: SliceDescriptor, thisPoint: GoodPoint, featureData: FeatureData] = { [thisPoint.point, thisPoint.dist, thisPoint.hitData, success] _ sliceD.slice.class.closestPoint[sliceD, q, dTol]; IF success THEN { IF thisPoint.dist < dTol THEN { thisPoint.featureData _ featureData; dTol _ MergePoint[thisPoint, h, dTol]; }; }; }; sliceD: SliceDescriptor; featureData: FeatureData; success: BOOL _ FALSE; dTol: REAL _ t; midpoints: BOOL _ GGState.Midpoints[ggData]; thisPoint _ NEW[GoodPointObj]; h _ BestPointsFromPool[ggData, t]; IF useAlignBag THEN dTol _ FindIntersections[bestCurves, curveCount, thisPoint, q, dTol, h]; IF useAlignBag AND midpoints THEN dTol _ FindMidpoints[bestCurves, curveCount, thisPoint, q, dTol, h]; FOR slices: LIST OF FeatureData _ sceneBag.slices, slices.rest UNTIL slices = NIL DO featureData _ slices.first; sliceD _ NARROW[featureData.shape, SliceDescriptor]; ProcessSlice[sliceD, thisPoint, featureData]; ENDLOOP; 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]; }; }; FindIntersections: PROC [bestCurves: BestPoints, curveCount: NAT, thisPoint: GoodPoint, q: Point, tolerance: REAL, h: BestPoints] RETURNS [dTol: REAL] = { curveI, curveJ: GoodPoint; theseIPoints: LIST OF Point; thisTangency, tangentList: LIST OF BOOL; success: BOOL; dTol _ tolerance; FOR i: NAT IN [0..curveCount) DO curveI _ bestCurves[i]; FOR j: NAT IN [i+1..curveCount) DO curveJ _ bestCurves[j]; [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 <= tolerance; 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; IF curveI.featureData.type = outline OR curveI.featureData.type = slice THEN { thisPoint.hitData _ curveI.hitData; } ELSE IF curveJ.featureData.type = outline OR curveJ.featureData.type = slice THEN { thisPoint.hitData _ curveJ.hitData; } ELSE thisPoint.hitData _ NIL; dTol _ MergePoint[thisPoint, h, dTol]; }; tangentList _ tangentList.rest; ENDLOOP; ENDLOOP; ENDLOOP; }; FindMidpoints: PROC [bestCurves: BestPoints, curveCount: NAT, thisPoint: GoodPoint, q: Point, tolerance: REAL, h: BestPoints] RETURNS [dTol: REAL] = { curve: GoodPoint; midpoint: Point; success: BOOL; dTol _ tolerance; FOR i: NAT IN [0..curveCount) DO curve _ bestCurves[i]; IF curve.featureData.type # outline AND 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 <= tolerance; IF success THEN { 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; dTol _ MergePoint[thisPoint, h, dTol]; }; 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 < t THEN { thisCurve.featureData _ featureData; thisCurve.point _ Lines2d.DropPerpendicular[q, line]; thisCurve.hitData _ NIL; dTol _ MergeCurve[thisCurve, h, dTol]; } }; ProcessCircle: PROC [circle: Circle, thisCurve: GoodPoint, featureData: FeatureData] = { thisCurve.dist _ GGCircles.CircleDistance[q, circle]; IF thisCurve.dist < t THEN { thisCurve.featureData _ featureData; thisCurve.point _ GGCircles.PointProjectedOntoCircle[q, circle]; thisCurve.hitData _ NIL; dTol _ MergeCurve[thisCurve, h, dTol]; }; }; ProcessSlice: PROC [sliceD: SliceDescriptor, thisCurve: GoodPoint, featureData: FeatureData] = { success: BOOL; [thisCurve.point, thisCurve.dist, thisCurve.hitData, success] _ sliceD.slice.class.closestSegment[sliceD, q, t]; IF success THEN { IF thisCurve.dist < t THEN { thisCurve.featureData _ featureData; dTol _ MergeCurve[thisCurve, h, dTol]; }; }; }; line: Line; circle: Circle; sliceD: SliceDescriptor; featureData: FeatureData; added: BOOL _ FALSE; thisCurve: GoodPoint _ NEW[GoodPointObj]; dTol: REAL _ t; h _ BestCurvesFromPool[ggData, t, innerR]; FOR slopeLines: LIST OF FeatureData _ alignBag.slopeLines, slopeLines.rest UNTIL slopeLines = NIL DO featureData _ slopeLines.first; line _ NARROW[featureData.shape, AlignmentLine].line; ProcessLine[line, thisCurve, featureData]; ENDLOOP; 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; FOR slices: LIST OF FeatureData _ sceneBag.slices, slices.rest UNTIL slices = NIL DO featureData _ slices.first; sliceD _ NARROW[featureData.shape, SliceDescriptor]; ProcessSlice[sliceD, thisCurve, featureData]; ENDLOOP; curveCount _ h.size; IF h.overflow THEN { CodeTimer.StartInt[$CurveOverflow, $Gargoyle]; CodeTimer.StopInt[$CurveOverflow, $Gargoyle]; }; }; -- end CurvesInNeighborhoodPlus MaxFeatures: NAT _ 20; BestCurvesFromPool: PROC [ggData: GGData, t: REAL, innerR: REAL] RETURNS [h: BestPoints] = { h _ NARROW[ggData.multiGravityPool, MultiGravityPool].bestcurves; h.size _ 0; h.max _ 0; 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-1] 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; 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-1] DO h[i].dist _ Real.LargestNumber; h[i].featureData _ NIL; ENDLOOP; }; useNewMerge: BOOL _ TRUE; MergePoint: PROC [thisPoint: GoodPoint, h: BestPoints, t: REAL] RETURNS [dTol: REAL] = { IF useNewMerge THEN RETURN NewMergePoint[thisPoint, h] ELSE RETURN OldMergeObject[thisPoint, h, t]; }; MergeCurve: PROC [thisPoint: GoodPoint, h: BestPoints, t: REAL] RETURNS [dTol: REAL] = { IF useNewMerge THEN RETURN NewMergeCurve[thisPoint, h] ELSE RETURN OldMergeObject[thisPoint, h, t]; }; OldMergeObject: PROC [thisPoint: GoodPoint, h: BestPoints, t: REAL] RETURNS [dTol: REAL] = { d: REAL _ thisPoint.dist; n: NAT = MaxFeatures; dTol _ t; BEGIN SELECT TRUE FROM h.size < n => GOTO Add; d < h.max AND h.size = n => GOTO AddAndComputeNewMax; d > h.max AND h.size = n => GOTO NoChange; -- we already have n and this is no better d = h.max AND h.size = n => {h.overflow _ TRUE; GOTO NoChange}; ENDCASE => SIGNAL Problem[msg: "Impossible case."]; EXITS Add => { h[h.size]^ _ thisPoint^; h.size _ h.size + 1; h.min _ MIN[h.min, d]; h.max _ MAX[h.max, d]; }; AddAndComputeNewMax => { iMax: NAT; newMax: REAL _ 0.0; bestDist: REAL; iMax _ 0; bestDist _ 0.0; FOR i: NAT IN [0..MaxFeatures-1] DO IF h[i].dist > bestDist THEN {iMax _ i; bestDist _ h[i].dist}; ENDLOOP; h[iMax].dist _ d; h[iMax]^ _ thisPoint^; iMax _ 0; bestDist _ 0.0; FOR i: NAT IN [0..MaxFeatures-1] DO IF h[i].dist > bestDist THEN {iMax _ i; bestDist _ h[i].dist}; ENDLOOP; newMax _ h[iMax].dist; h.overflow _ IF newMax # h.max THEN TRUE ELSE h.overflow; h.max _ newMax; }; NoChange => { }; END; }; NewMergePoint: PROC [thisPoint: GoodPoint, h: BestPoints] RETURNS [dTol: REAL] = { d: REAL _ thisPoint.dist; n: NAT = MaxFeatures; BEGIN SELECT TRUE FROM h.size < n => GOTO Add; h.size = n AND d <= h.dTol => GOTO ReplaceOrOverflow; h.size = n AND d > h.dTol => GOTO Toss; -- the caller is not taking our hints and is passing us trash ENDCASE => SIGNAL Problem[msg: "Impossible case."]; EXITS Add => { h[h.size]^ _ thisPoint^; h.size _ h.size + 1; h.min _ MIN[h.min, d]; dTol _ h.dTol _ h.min+h.s; }; ReplaceOrOverflow => { iWorst: NAT; worstDist: REAL _ 0.0; iWorst _ 0; worstDist _ 0.0; FOR i: NAT IN [0..MaxFeatures-1] DO IF h[i].dist > worstDist THEN {iWorst _ i; worstDist _ h[i].dist}; ENDLOOP; IF d < worstDist THEN { -- do the replace h[iWorst].dist _ d; h[iWorst]^ _ thisPoint^; h.bestTossed _ MIN[h.bestTossed, worstDist]; h.min _ MIN[h.min, d]; dTol _ h.dTol _ h.min+h.s; h.overflow _ h.bestTossed <= dTol; } 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; }; NewMergeCurve: PROC [thisPoint: GoodPoint, h: BestPoints] RETURNS [dTol: REAL] = { d: REAL _ thisPoint.dist; n: NAT = MaxFeatures; BEGIN SELECT TRUE FROM h.size < n => GOTO Add; h.size = n AND d <= h.dTol => GOTO ReplaceOrOverflow; h.size = n AND d > h.dTol => GOTO Toss; -- the caller is not taking our hints and is passing us trash ENDCASE => SIGNAL Problem[msg: "Impossible case."]; EXITS Add => { h[h.size]^ _ thisPoint^; h.size _ h.size + 1; h.min _ MIN[h.min, d]; dTol _ h.dTol _ MAX[h.min+h.s, h.innerR]; }; ReplaceOrOverflow => { iWorst: NAT; worstDist: REAL _ 0.0; iWorst _ 0; worstDist _ 0.0; FOR i: NAT IN [0..MaxFeatures-1] DO IF h[i].dist > worstDist THEN {iWorst _ i; worstDist _ h[i].dist}; ENDLOOP; IF d < worstDist THEN { -- do the replace h[iWorst].dist _ d; h[iWorst]^ _ thisPoint^; h.bestTossed _ MIN[h.bestTossed, worstDist]; h.min _ MIN[h.min, d]; dTol _ h.dTol _ MAX[h.min+h.s, h.innerR]; h.overflow _ h.bestTossed <= dTol; } 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; }; NearPointsFromPoints: PROC [bestPoints: BestPoints, pointCount: NAT, nearPointsAndCurves: NearPointsAndCurves] = { FOR i: NAT IN [0..pointCount-1] DO nearPointsAndCurves[i] _ bestPoints[i]; ENDLOOP; }; MergePointsAndCurves: PROC [bestPoints: BestPoints, pointCount: NAT, bestCurves: BestPoints, curveCount: NAT, nearPointsAndCurves: NearPointsAndCurves, count: NAT] = { pointIndex, curveIndex: NAT; pointDist, curveDist: REAL; pointIndex _ 0; curveIndex _ 0; FOR i: NAT IN [0..count-1] 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 { nearPointsAndCurves[i] _ bestPoints[pointIndex]; pointIndex _ pointIndex + 1; } ELSE { nearPointsAndCurves[i] _ bestCurves[curveIndex]; curveIndex _ curveIndex + 1; }; REPEAT NoMorePoints => { -- finish up with Curves data FOR k: NAT _ i, k+1 UNTIL k >= count DO nearPointsAndCurves[k] _ bestCurves[curveIndex]; curveIndex _ curveIndex + 1; ENDLOOP}; NoMoreCurves => { -- finish up with points data FOR k: NAT _ i, k+1 UNTIL k >= count DO nearPointsAndCurves[k] _ bestPoints[pointIndex]; pointIndex _ pointIndex + 1; ENDLOOP}; ENDLOOP; }; SortPoints: PROC [bestPoints: BestPoints, pointCount: NAT] = { temp: GoodPointObj; FOR i: NAT IN [0..pointCount-2] DO FOR j: NAT IN [1..pointCount-i-1] 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: GoodPointObj; FOR i: NAT IN [0..curveCount-2] DO FOR j: NAT IN [1..curveCount-i-1] DO IF bestCurves[j-1].dist > bestCurves[j].dist THEN { temp _ bestCurves[j]^; bestCurves[j]^ _ bestCurves[j-1]^; bestCurves[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 outline, slice => { sliceD: SliceDescriptor _ NARROW[feature.shape]; hitData: REF ANY _ curve.hitData; simpleCurve _ sliceD.slice.class.hitDataAsSimpleCurve[sliceD.slice, hitData]; IF simpleCurve = NIL THEN { simpleCurve _ sliceD; class _ 5; 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; 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..5] OF ARRAY [0..5] OF IntersectionProc = [ [NoOpI, NIL, NIL, NIL, NIL, NIL], -- 0) NoOp [NoOpI, LinLinI, NIL, NIL, NIL, NIL], -- 1) Line [NoOpI, CirLinI, CirCirI, NIL, NIL, NIL], -- 2) Circle [NoOpI, EdgLinI, EdgCirI, EdgEdgI, NIL, NIL], -- 3) Edge [NoOpI, ArcLinI, ArcCirI, ArcEdgI, ArcArcI, NIL], -- 4) Arc [NoOpI, SlcLinI, SlcCirI, NoOpI, NoOpI, NoOpI] -- 5) Slice ]; NoOpI: IntersectionProc = { iPoints _ NIL; tangency _ NIL; }; LinLinI: IntersectionProc = { l1: Line _ NARROW[c1]; l2: Line _ NARROW[c2]; point: Point; parallel: BOOL; [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; [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; [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; [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; [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; [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; [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; [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; [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; [points, hitCount] _ GGCircles.ArcMeetsArc[arc1, arc2]; FOR i: NAT IN [1..hitCount] DO iPoints _ CONS[points[i], iPoints]; tangency _ CONS[tangent, tangency]; ENDLOOP; }; SlcLinI: IntersectionProc = { sliceD: SliceDescriptor _ NARROW[c1]; line: Line _ NARROW[c2]; [iPoints, ----] _ sliceD.slice.class.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, ----] _ sliceD.slice.class.circleIntersection[sliceD, circle]; FOR list: LIST OF Point _ iPoints, list.rest UNTIL list = NIL DO tangency _ CONS[FALSE, tangency]; ENDLOOP; }; SeqLineI: IntersectionProc = { outlineD: OutlineDescriptor _ NARROW[c1]; line: Line _ NARROW[c2]; [iPoints, ----] _ outlineD.slice.class.lineIntersection[outlineD, line]; FOR list: LIST OF Point _ iPoints, list.rest UNTIL list = NIL DO tangency _ CONS[FALSE, tangency]; ENDLOOP; }; SeqCircleI: IntersectionProc = { outlineD: OutlineDescriptor _ NARROW[c1]; circle: Circle _ NARROW[c2]; [iPoints, ----] _ outlineD.slice.class.circleIntersection[outlineD, circle]; FOR list: LIST OF Point _ iPoints, list.rest UNTIL list = NIL DO tangency _ CONS[FALSE, tangency]; ENDLOOP; }; SeqSeqI: IntersectionProc = { od1: OutlineDescriptor _ NARROW[c1]; od2: OutlineDescriptor _ NARROW[c2]; iPoints _ NIL; }; NewMultiGravityPool: PUBLIC PROC [] RETURNS [REF]= { -- reuseable storage for BestPointAndCurve proc to avoid NEWs pool: MultiGravityPool _ NEW[MultiGravityPoolObj]; pool.distances _ NEW[NearDistancesObj[MaxFeatures]]; pool.features _ NEW[NearFeaturesObj[MaxFeatures]]; pool.bestpoints _ NEW[BestPointsObj[MaxFeatures]]; pool.bestcurves _ NEW[BestPointsObj[MaxFeatures]]; FOR i: NAT IN [0..MaxFeatures) DO pool.bestpoints[i] _ NEW[GoodPointObj]; pool.bestcurves[i] _ NEW[GoodPointObj]; ENDLOOP; RETURN[pool]; }; InitStats: PROC [] = { interval: CodeTimer.Interval; interval _ CodeTimer.CreateInterval[$MultiMap]; CodeTimer.AddInt[interval, $Gargoyle]; interval _ CodeTimer.CreateInterval[$CurveOverflow]; CodeTimer.AddInt[interval, $Gargoyle]; -- counting break interval _ CodeTimer.CreateInterval[$PointOverflow]; CodeTimer.AddInt[interval, $Gargoyle]; -- counting break }; InitStats[]; END. ���*@��GGMultiGravityImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last edited by Bier on June 3, 1986 2:42:42 pm PDT Contents: Performs hit testing similar to GGGravity. 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]<Gargoyle>Documentation>MultiGravity.tioga. Shared with GGGravityImpl triggerBag.outlines = NIL AND 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 useAlignBag is TRUE, compute the intersections of the objects that are in the bags. Someday, GoodPoint and GoodPoint should become a single variant record and distances will be unnecessary. Otherwise, let's do 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. FOR i: NAT IN [0..neighborCount) DO IF features[i].type = outline OR features[i].type = slice THEN { SELECT features[i].resultType FROM joint, controlPoint, intersectionPoint => RETURN PrepareWinner[nearPointsAndCurves, i]; ENDCASE => IF bestSceneObject = -1 THEN bestSceneObject _ i; }; REPEAT FINISHED => { IF bestSceneObject >= 0 THEN RETURN PrepareWinner[nearPointsAndCurves, bestSceneObject] ELSE RETURN PrepareWinner[nearPointsAndCurves, 0]; }; ENDLOOP; Otherwise, let's do arbitration. 1) Prefer scene objects to alignment lines. Later, we will prefer objects that say the testpoint is "inside" them to those that don't. Multi-Gravity Routines Dispatches to MultiStrictDistance or MultiPointsPreferred as appropriate. Returns up to MaxFeatures closest features, their closest points, and their distances from the testpoint. Features outside of the critical radius, criticalR, will not be included. The results will be located in nearPointsAndCurves[0] .. nearPointsAndCurves[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 nearPointsAndCurves[0] .. nearPointsAndCurves[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, nearPointsAndCurves may consist of a mixture of points and curves. For each gravity active point, find its distance from the testpoint. Package this information up into the thisPoint record. Then call MergeObject, which will add this point to the list of best points, if appropriate. When PointsInNeighborhoodPlus 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. ProcessOutline: PROC [outlineD: OutlineDescriptor, thisPoint: GoodPoint, featureData: FeatureData] = { [thisPoint.point, thisPoint.dist, thisPoint.hitData, success] _ outlineD.slice.class.closestPoint[outlineD, q, dTol]; IF success THEN { IF thisPoint.dist < dTol THEN { thisPoint.featureData _ featureData; dTol _ MergePoint[thisPoint, h, dTol]; }; }; }; outlineD: OutlineDescriptor; FOR midpoints: LIST OF FeatureData _ alignBag.midpoints, midpoints.rest UNTIL midpoints = NIL DO featureData _ midpoints.first; thisPoint.point _ NARROW[featureData.shape, AlignmentPoint].point; ProcessPoint[thisPoint, featureData]; ENDLOOP; FOR outlines: LIST OF FeatureData _ sceneBag.outlines, outlines.rest UNTIL outlines = NIL DO featureData _ outlines.first; outlineD _ NARROW[featureData.shape]; ProcessOutline[outlineD, thisPoint, featureData]; ENDLOOP; Handle the anchor. 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 MergeObject, 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). ProcessOutline: PROC [outlineD: OutlineDescriptor, thisCurve: GoodPoint, featureData: FeatureData] = { success: BOOL; [thisCurve.point, thisCurve.dist, thisCurve.hitData, success] _ outlineD.slice.class.closestSegment[outlineD, q, t]; IF success THEN { IF thisCurve.dist < t THEN { thisCurve.featureData _ featureData; dTol _ MergeCurve[thisCurve, h, dTol]; }; }; }; outlineD: OutlineDescriptor; Align Bag Scene Bag. FOR outlines: LIST OF FeatureData _ sceneBag.outlines, outlines.rest UNTIL outlines = NIL DO featureData _ outlines.first; outlineD _ NARROW[featureData.shape, OutlineDescriptor]; ProcessOutline[outlineD, thisCurve, featureData]; ENDLOOP; Maintaining the Nearest-Neighbor Structure Alias useNewMerge _ GGMultiGravityImpl.useNewMerge _ TRUE; OldMergeObject 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. h.max is the maximum value of dist(q, x) for x element of h. If overflow is FALSE, then h contains at least one representative of the objects at the farthest distance from q, and all representatives of closer distances. Replace the worst element with the new one. Find the new worst element. If the new worst element isn't so bad as before, then we have had to throw away all elements at some distance. This is overflow. BestPointsObj: TYPE = RECORD [ size: NAT, max, min: REAL, 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]; NewMergeObject 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) <= h.min+h.s. h may contain other objects as well. If overflow is TRUE, all objects in h satisfy h.min <= dist(o, q) <= h.min+h.s 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 NewMergePoint is just going to throw them away. When NewMergePoint returns, h.dTol = t if h.size = 0, h.dTol = h.min + h.s otherwise. Replace the worst element with the new one if the new is better. NewMergeCurve 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]. 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] 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 NewMergeCurve is just going to throw them away. When NewMergeCurve returns, h.dTol = t if h.size = 0, h.dTol = MAX[h.min+h.s, innerR] otherwise. Replace the worst element with the new one if the new is better. Merge the bestPoints and the bestCurves. There will be count elements in the result. 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: Computing Intersections 0 => { pointPairGen: OutlinePointPairGenerator; segmentD: OutlineDescriptor _ NARROW[simpleCurve]; pointPairGen _ segmentD.slice.class.pointPairsInDescriptor[segmentD]; ppAndDone _ segmentD.slice.class.nextPointPair[pointPairGen]; IF ppAndDone.done THEN RETURN[[0,0], FALSE] ELSE { midpoint _ Vectors2d.Scale[Vectors2d.Add[ppAndDone.lo, ppAndDone.hi], 0.5]; success _ TRUE; }; }; outline => { The asymmetry here is OK. sliceD: OutlineDescriptor _ NARROW[feature.shape]; hitData: REF ANY _ curve.hitData; simpleCurve _ sliceD.slice.class.hitDataAsSimpleCurve[sliceD.slice, hitData]; IF simpleCurve = NIL THEN { class _ 0; RETURN; }; }; 0) NoOp 1) Line 2) Circle 3) Edge 4) Arc 5) 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]; simpleCurve1, simpleCurve2: REF ANY; simpleCurve1 _ od1.slice.class.hitDataAsSimpleCurve[od1.slice, hitData1]; simpleCurve2 _ od2.slice.class.hitDataAsSimpleCurve[od2.slice, hitData2]; Utilities �Ê2a��˜�J˜�Icodešœ™Kšœ Ïmœ1™<Kšœ2™2Kšœ¨™¨K™�šÏk ˜ Jšœ¶˜¶—K˜�KšÏnœžœž˜!K˜�JšžœQ˜XKšžœž˜˜�Kšœ žœžœ ˜!Kšœ žœ ˜1Kšœžœ$˜9Kšœžœ"˜5Kšœžœžœ˜-Kšœžœ&˜=Kšœžœ˜Kšœžœ˜%Kšœžœ˜#Kšœžœ˜Kšœ žœžœ˜'Kšœžœ˜3Kšœžœ˜'Kšœžœžœ˜#Kšœžœ˜1Kšœžœ˜3Kšœžœ˜Kšœžœžœ˜+Kšœžœ#˜9Kšœžœžœ˜)Kšœžœ"˜7Kšœžœžœ˜%Kšœžœžœ˜7Kšœžœ)˜EKšœžœ ˜3Kšœ žœ˜%Kšœžœžœ˜3Kšœžœ%˜?Kšœžœ*˜IKšœžœ˜!Kšœžœ!˜7Kšœžœ#˜;Kšœ žœ˜'Kšœžœ!˜7Kšœ žœ˜'Kšœžœ˜!Kšœžœ ˜5Kšœžœžœ˜%Kšœžœ"˜5K˜�Kšœžœžœ˜%šœžœžœ˜Kšœžœ˜ Kšœ žœ˜KšœžœÏc?˜QKšœžœ ˜Kšœžœ S˜aKšœžœ ›˜¤Kšœ žœ˜Kšœžœžœžœ˜(—šœžœžœ˜1šœžœžœ˜$Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜—K˜�K˜�——K˜�Kšœ™K™�šŸœžœžœžœ˜6šžœ˜Kšœžœž˜Kšœžœž˜Kšœžœž˜Kšœžœž˜ Kšœžœž˜Kšœžœž˜%Kšœžœ˜—K˜K˜�—šŸ œžœžœžœ˜?šžœ˜Kšœžœž™Kšœžœž˜Kšœ žœž˜'Kšœž˜K˜—K˜—K˜�Kš Ÿœžœžœžœžœ˜/K˜�K™˜�I artworkFigure• Interpressš Interpress/Xerox/3.0 f j k j¡¥“Ä��WB ¤ ¨ ªœ¡£É ¢ ¨ r j º ¢ ¨¡¡¨ÄWB�� ¤ ¨ r j¡¥“Ä��WB ¤ ¨¡¡¨ r jÄWÁ�Á ¤ÄDB�CÄ�Œ5��r ¢ ¥ ¨¡¡¨ÄŒZ¯“Ÿ ™¡ Ÿ ¡“¡¸ k é r jÄ Ù¤ ¤Ä�˜j��eÄ[‹�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Ä Ù¤ ¤ÄÇa�SÄAh�/ ¢ ¥ ¨ r j¡¡¨Ÿ ™¡ Ÿ ¡“¡¡ ¡™ k é¡¡¨ÄI\¯“Ÿ ™¡ Ÿ ¡“¡¸ k é r jÄ�› ¤ÄDB�CÄ�Œ5��r ¢ ¥ ¨¡¡¨Ä�›Ž¯“Ÿ ™¡ Ÿ ¡“¡¸ k é r jÄ@¼�ñ ¤ÄDB�CÄ�Œ5��r ¢ ¥ ¨¡¡¨Ä�ñ ^¯“Ÿ ™¡ Ÿ ¡“¡¸ k 飯“ £¡ˆ¡¡ÅXeroxÅResearchÅ RGBLinear£¡¡¦ ç • ” ç“¢·“¢°“ÄUÞ�UÕ™ÄDB�CÄ�Œ5��r—§Õ—˜ 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•Artwork Interpress•Bounds60.0 mm xmin 0.0 mm ymin 93.83888 mm xmax 88.9 mm ymax –G91.72222 mm topLeading 91.72222 mm topIndent 1.411111 mm bottomLeading šŸ=™=IartworkCaption™^—šŸœžœžœžœIžœžœ5žœžœ˜ÊKšœÈ™ÈKšžœžœ6 2˜uKšœ)˜)šžœžœ˜!šžœž˜&šœ˜Kšœc˜c—šœ˜Kšœˆ˜ˆ—Kšžœžœ˜—K˜—šžœ˜Kšœ˜Kšœ žœ˜K˜—Kšœ(˜(K˜K˜�K™�—šŸœžœžœžœ<žœ5žœžœ˜ÂKšœi™iKšœ)˜)Kšœžœ˜KšœÏbœ3˜eKš žœžœžœ žœžœ˜/Kšžœžœžœ&˜>šžœ˜Kš¡ ™ Kšœžœ,˜JKšœ)˜)Kšœ&˜&Kšœ žœ˜Kšœžœ˜Kšœžœ˜Kšœžœ ˜!šžœžœžœž˜Kšœžœž˜.Kšœ˜Kšœ$˜$Kšžœ˜—Kšœ˜šžœžœžœž˜Kšžœ žœ#˜IKšžœ˜—šžœžœžœ'˜GKš¡V™VKš¡+™+Kš¡™Kš¡Z™Z—K˜šžœžœžœž™#šžœžœžœ™@šžœž™"šœ)™)Kšžœ'™-—Kšžœžœžœ™<—K™—šž™šžœž™ šžœž™Kšžœ4™:—Kšžœ'™2K™——Kšžœ™—Kšžœ'˜-K˜—K˜—šŸœžœžœžœ žœIžœžœ5žœžœ˜äKšœžœ˜Kšœ)˜)Kšœ{˜{Kš žœžœžœ žœžœ˜/Kšžœžœžœ&˜>šžœ˜Kš¡ ™ Kšœžœ,˜JKšœ)˜)Kšœ&˜&Kšœžœ˜Kšœžœ ˜!Kšœ žœ˜šžœžœžœž˜Kšœžœ˜.Kšœ˜Kšœ$˜$Kšžœ˜—Kšœ˜šžœžœžœž˜Kšžœ žœ#˜IKšžœ˜—Kšžœžœžœ'˜GKš¡+™+Kš¡Z™Zšžœžœžœž˜#šžœžœžœ˜@Kšžœ'˜-K˜—šž˜Kšžœžœ'˜9—Kšžœ˜—K˜—K˜—K™�šŸ œžœ3žœžœ5žœžœ˜“Kšœžœž˜2Kšœ˜Kšœ ˜ Kšœ˜K˜K˜�—šŸœžœžœdžœžœ!žœ˜ÚK˜�K˜K˜�—K™�Kšœ™K™�šŸœžœžœžœIžœžœ3žœ˜ÉKš¡I™IKšžœžœ6 2˜uKšœ)˜)šžœ7ž˜Ašœžœž˜,šœ˜Kšœe˜e—šœ˜KšœŠ˜Š—Kšžœžœ˜—šœ˜Kšœžœ˜Kšœ ˜ K˜—Kšžœžœ˜—Kšœ(˜(K˜K˜�—šŸœžœžœžœ<žœ3žœ˜ÁKš¡Œ™ŒKšœ˜Kšœ˜Kšœžœ˜šžœžœž˜6Kšžœžœ˜K˜�—Kšœ¡œ6˜iKšœ#˜#K˜�Kšœ¡œKžœ˜…Kšœ#˜#K˜�Kšœžœ'˜2Kšœžœ ˜9Kš¡œM˜aK˜K˜�—šÐbn¡žœžœžœ žœIžœžœ3žœ˜ãKš¡Ö™ÖKšœ˜Kšœ˜Kšœžœ˜Kš žœžœžœžœžœ˜FK˜�Kšœ¡œ;˜nKšœ#˜#K˜�Kšœ¡œX˜‹Kšœ#˜#K˜�šžœžœžœ˜8Kšœ˜Kšœžœ ˜9KšœB˜BK˜—šžœ˜Kšœžœ'˜2Kšœžœ ˜9Kšœa˜aK˜—K˜K˜�—K˜�šŸœžœ&žœ,¡œ Ïiœžœžœžœžœ˜ÕKšœ˜KšœÚ™ÚKšœ‚™‚K™ªKšœ‰™‰šŸœžœ6 ˜^Kšœ žœ˜Kšœ žœ ˜Kšœ9˜9Kšœžœ˜šžœžœ˜ Kšœ(˜(Kšœ$˜$Kšœ&˜&K˜—K˜—šŸœžœN˜`Kšœ¡œQ˜qšžœ žœ˜šžœžœ˜Kšœ$˜$Kšœ&˜&K˜—Kšœ˜—K˜—šŸœžœR™fKšœ¡œU™ušžœ žœ™šžœžœ™Kšœ$™$Kšœ&™&K™—Kšœ™—K™K™�—K˜K™Kšœ˜Kšœ žœžœ˜Kšœžœ˜Kšœžœ˜,K˜�Kšœžœ˜Kšœ"˜"K˜�šžœ ž˜KšœH˜H—šžœ žœž˜!KšœD˜D—K˜�š žœ¡ œžœžœ2žœ žœž™`Kšœ™Kšœžœ*™BKšœ%™%Kšžœ™—š žœ¡œžœžœ,žœ žœž˜TKšœ˜Kšœ žœ%˜4Kšœ-˜-Kšžœ˜—š žœ¡œžœžœ0žœžœž™\Kšœ™Kšœžœ™%Kšœ1™1Kšžœ™Kš¡™—Kšœ˜šžœžœžœ˜Kšœžœ˜*Kšžœžœžœžœ˜)Kšœ+˜+Kšœ%˜%K˜—Kšœ˜šžœžœ˜Kšœ.˜.Kšœ.˜.K˜—K˜K™�—šŸœžœ&žœ-žœžœžœ˜šKšœ˜Kšœžœžœ˜Kšœžœžœžœ˜(Kšœ žœ˜Kšœ˜šžœžœžœž˜ Kšœ˜šžœžœžœž˜"Kšœ˜Kšœ?˜?Kšœ˜šžœžœžœ!žœžœž˜EKšœ˜Kšœ8˜8Kšœ&˜&šžœ žœ˜Kšœžœ˜/šœ!žœ˜:Kšœ˜Kšœ˜Kšœ˜Kšœ˜—Kšœ%˜%Kšœ#˜#Kšœ$˜$šžœ#žœ!žœ˜NKšœ#˜#K˜—šžœžœ#žœ!žœ˜SKšœ#˜#K˜—Kšžœžœ˜Kšœ&˜&K˜—Kšœ˜Kšžœ˜—Kšžœ˜—Kšžœ˜—K˜K˜�—šŸ œžœ&žœ-žœžœžœ˜–Kšœ˜Kšœ˜Kšœ žœ˜Kšœ˜šžœžœžœž˜ šœ˜Kšžœ"žœ žœžœ˜QKšœ-˜-Kšžœžœ žœžœ˜Kš¡œ˜Kš¡œ'¡œ˜8Kšœ&˜&šžœ žœ˜Kšœžœ˜/šœ!žœ˜:Kšœ˜Kšœ žœ˜Kšœ˜Kšœžœ˜—Kšœ˜Kšœ#˜#Kš¡œ˜$Kš¡œ˜"Kšœ&˜&K˜——Kšžœ˜—K˜K˜�—šŸœžœIžœ žœžœžœ˜§Kšœø™øKšœo™oKšœ¨™¨K™ˆšŸœžœA˜RKšœ/˜/šžœžœž˜Kšœ$˜$Kšœ5˜5Kšœžœ˜Kšœ&˜&K˜—K˜—šŸ œžœE˜XKšœ5˜5šžœžœ˜Kšœ$˜$Kšœ@˜@Kšœžœ˜Kšœ&˜&K˜—K˜—šŸœžœN˜`Kšœ žœ˜Kšœp˜pšžœ žœ˜šžœžœ˜Kšœ$˜$Kšœ&˜&K˜—K˜—K˜—šŸœžœR™fKšœ žœ™Kšœt™tšžœ žœ™šžœžœ™Kšœ$™$Kšœ&™&K™—K™—K™K™�—K˜K˜K˜K™Kšœ˜Kšœžœ˜Kšœžœ˜)Kšœžœ˜Kšœ*˜*K˜�Kš¡ ™ š žœ¡ œžœžœ4žœžœž˜dKšœ˜Kšœžœ(˜5Kšœ*˜*Kšžœ˜—š žœ¡ œžœžœ4žœžœž˜dKšœ˜Kšœžœ(˜5Kšœ*˜*Kšžœ˜—š žœ¡œžœžœ3žœ žœž˜[Kšœ˜Kšœžœ˜!Kšœ*˜*Kšžœ˜—š žœ¡œžœžœ3žœžœž˜]Kšœ˜Kšœ žœ,˜;Kšœ.˜.Kšžœ˜—Kš¡ ™ š žœ¡œžœžœ,žœ žœž˜TKšœ˜Kšœ žœ%˜4Kšœ-˜-Kšžœ˜—š žœ¡œžœžœ0žœžœž™\Kšœ™Kšœžœ'™8Kšœ1™1Kšžœ™—K˜šžœžœ˜Kšœ.˜.Kšœ.˜.K˜—Kšœ ˜"K˜�—K˜�K™*K˜�KšŸœžœ˜š Ÿœžœžœ žœžœ˜\Kšœžœ7˜AKšœ˜K˜ Kšœ˜K˜Kšœ˜Kšœ ˜Kšœ"˜"Kšœ žœ˜šžœžœžœž˜#Jšœ˜Jšœžœ˜Jšžœ˜—J˜J˜�—šŸœžœžœžœ˜NKšœžœ7˜AKšœ˜K˜ Kšœ˜K˜Kšœ ˜Kšœ"˜"Kšœ žœ˜šžœžœžœž˜#Jšœ˜Jšœžœ˜Jšžœ˜—J˜J˜�—Kšœ žœžœ˜Kšœ5žœ™:š Ÿ œžœ*žœžœžœ˜XKšžœ žœžœ˜6Kšžœžœ!˜,K˜—š Ÿ œžœ*žœžœžœ˜XKšžœ žœžœ˜6Kšžœžœ!˜,K˜K˜�K˜�—Kšœ¤™¤K™žš Ÿœžœ*žœžœžœ˜\Kšœžœ˜Kšœžœ˜Kšœ ˜ šž˜šžœžœž˜Kšœžœ˜Kšœ žœžœ˜5Kšœ žœžœ *˜UKšœ žœžœžœ˜?Kšžœžœ"˜3—šž˜šœ˜Kšœ˜Kšœ˜Kšœžœ˜Kšœžœ˜K˜—˜Kšœžœ˜ Kšœžœ˜Kšœ žœ˜Kš¡+™+Kšœ˜šžœžœžœž˜#Jšžœžœ"˜>Jšžœ˜—J˜Jšœ˜Kš¡™K˜šžœžœžœž˜#Jšžœžœ"˜>Jšžœ˜—J˜Jš¡™Jš œ žœžœžœžœ˜9J˜J˜—˜ J˜——Jšžœ˜—K˜K˜�—šœžœžœ™Kšœžœ™ Kšœ žœ™Kšœžœ ›™¤Kšœ žœ™Kšœžœžœžœ™(K™�—Kšœæ™æK™“K™ûKšœ¦™¦KšœU™UK™�šŸ œžœ'žœžœ˜RKšœžœ˜Kšœžœ˜šž˜šžœžœž˜Kšœžœ˜Kšœžœžœ˜5Kšœžœžœ =˜eKšžœžœ"˜3—šž˜šœ˜Kšœ˜Kšœ˜Kšœžœ˜Jšœ˜K˜—šœ˜Kšœžœ˜Kšœžœ˜Kš¡@™@Kšœ˜šžœžœžœž˜#Jšžœžœ%˜BJšžœ˜—šžœžœ ˜)Jšœ˜Jšœ˜Jšœžœ˜,Kšœžœ˜Jšœ˜Jšœ"˜"J˜—šžœ ˜Jšœ˜Jšœžœ˜$Jšœ žœ˜J˜—J˜—šœ ˜ Jšœ˜Jšœžœ˜$Jšœ"˜"J˜——Jšžœ˜—K˜K˜�—K™�Kšœå™åKšœžœOžœ:™ KšœEžœÀ™ˆKšœ¦™¦Kšœ`™`K™�šŸ œžœ'žœžœ˜RKšœžœ˜Kšœžœ˜šž˜šžœžœž˜Kšœžœ˜Kšœžœžœ˜5Kšœžœžœ =˜eKšžœžœ"˜3—šž˜šœ˜Kšœ˜Kšœ˜Kšœžœ˜Jšœžœ˜)K˜—šœ˜Kšœžœ˜Kšœžœ˜Kš¡@™@Kšœ˜šžœžœžœž˜#Jšžœžœ%˜BJšžœ˜—šžœžœ ˜)Jšœ˜Jšœ˜Jšœžœ˜,Kšœžœ˜Jšœžœ˜)Jšœ"˜"J˜—šžœ ˜Jšœ˜Jšœžœ˜$Jšœ žœ˜J˜—J˜—šœ ˜ Jšœ˜Jšœžœ˜$Jšœ"˜"J˜——Jšžœ˜—K˜K˜�—šŸœžœ&žœ/˜ršžœžœžœž˜"Kšœ'˜'Kšžœ˜—K˜K˜�—š Ÿœžœ&žœ&žœ3žœ˜§Kš¡U™UKšœžœ˜Kšœžœ˜K˜K˜šžœžœžœž˜Iprocšžœžœžœ˜3Nšžœžœžœ˜3Nšœ˜N˜šžœžœ˜ Nšœ0˜0Nšœ˜N˜—šžœ˜Nšœ0˜0N˜N˜—šž˜Nšœ ˜/šžœžœ žœž˜'Nšœ0˜0Nšœ˜—Nšžœ˜ Nšœ ˜/šžœžœ žœž˜'Nšœ0˜0Nšœ˜—Nšžœ˜ —Nšžœ˜K˜—K˜�—šŸ œžœ&žœ˜>K™iKšœ˜šžœžœžœž˜"šžœžœžœž˜$šžœ+žœ˜3Kšœ˜Kšœ"˜"Kšœ˜K˜—Kšžœ˜—Kšžœ˜—˜K˜�——šŸ œžœ&žœ˜>K™iKšœ˜šžœžœžœž˜"šžœžœžœž˜$šžœ+žœ˜3Kšœ˜Kšœ"˜"Kšœ˜K˜—Kšžœ˜—Kšžœ˜—K˜K™�—K™�K™K™�š Ÿœžœžœžœžœ˜\Kšœžœ˜Kšœ žœžœ˜Kšœ,˜,šžœž˜™Kšœ(™(Kšœžœ™2KšœE™EKšœ=™=Kšžœžœžœžœ™+šžœ™KšœK™KKšœ žœ™K™—K™—˜Kšœ žœ˜!KšœE˜EKšœ žœ˜K˜—˜ Kšœžœ˜Kšœ?˜?Kšœ žœ˜K˜—Kšžœžœžœ˜ —K˜K˜�—šŸ œžœžœ žœžœžœ˜UKšœ)˜)šžœž˜šœ˜Kšœžœ˜0Kšœ žœžœ˜!KšœM˜Mšžœžœžœ˜Kšœ˜Kšœ ˜ Kšžœ˜K˜—Kšœ˜—šœ™Kš¡™Kšœžœ™2Kšœ žœžœ™!KšœM™Mšžœžœžœ™Kšœ ™ Kšžœ™K™—Kšœ™—šœ˜Kšœ ˜ Kšœžœ(˜<Kšžœ˜K˜—šœ˜Kšœ ˜ Kšœžœ$˜8Kšžœ˜K˜—šœ˜Kšœ ˜ Kšœžœ˜*Kšžœ˜K˜—Kšžœžœžœ˜3—šžœ žœž˜Kšœ˜Kšœ˜Kšœ˜Kšžœžœ˜—K˜K˜�—šŸœžœžœžœžœžœžœžœ˜fKšœžœ˜ Kšœžœžœ˜$Kšœ1˜1Kšœ1˜1šžœž˜$Kšœa˜a—šž˜Kšœa˜a—K˜K˜�—Kšœžœžœ žœžœžœžœžœžœžœžœ˜iš Ÿœžœžœžœžœ˜IK™1Kšœžœžœžœžœžœ ˜.Kšœ¡œžœžœžœžœ ˜1Kšœ¡œ¡œžœžœžœ ˜6Kšœ ¡œ¡œ¡œžœžœ ˜:Kšœ¡œ¡œ¡œ¡œžœ ˜;Kšœ¡œ¡œ ˜:Kšœ˜—K˜�šŸœ˜Kšœ žœ˜Kšœžœ˜K˜K˜�—š¢œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœžœ˜Kšœžœ˜K˜ Kšœ žœ˜Kšœ2˜2Kšžœžœ žœžœžœžœ˜DKšžœžœ žœ˜%J˜J˜�—š¢œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœžœ˜Kšœ žœ˜Kšœžœžœ˜Kšœ˜Kšœ žœ˜KšœF˜Fšžœžœžœž˜Kšœ žœ˜#Kšœžœ˜#Kšžœ˜—J˜J˜�—š¢œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœ˜Kšœ žœ˜KšœL˜Lšžœžœžœž˜Kšœ žœ˜#Kšœžœ˜#Kšžœ˜—J˜J˜�—š¢œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœ žœ˜Kšœ žœ˜K˜ Kšœžœ˜Kšœ3˜3Kš žœžœžœžœžœ˜AKšžœžœ žœ˜%J˜J˜�—š¢œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœ žœ˜Kšœžœ˜Kšœžœžœ˜Kšœ˜Kšœ žœ˜KšœF˜Fšžœžœžœž˜Kšœ žœ˜#Kšœžœ˜#Kšžœ˜—J˜J˜�—š¡œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœžœ˜Kšœžœ˜K˜ Kšœžœ˜Kšœ/˜/Kšžœžœžœžœžœžœ˜AKšžœžœ žœ˜%J˜J˜�—š¢œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœžœ˜Kšœ žœ˜Kšœžœžœ˜Kšœ˜Kšœ žœ˜Kšœ@˜@šžœžœžœž˜Kšœ žœ˜#Kšœžœ˜#Kšžœ˜—J˜J˜�—š¢œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœ˜Kšœ žœ˜KšœD˜Dšžœžœžœž˜Kšœ žœ˜#Kšœžœ˜#Kšžœ˜—J˜J˜�—š¡œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœžœ˜Kšœ žœ˜Kšœžœžœ˜Kšœ˜Kšœ žœ˜Kšœ@˜@šžœžœžœž˜Kšœ žœ˜#Kšœžœ˜#Kšžœ˜—J˜J˜�—š¡œ˜Kšœžœžœ žœžœžœžœžœžœžœžœ™iKšœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœ˜Kšœ žœ˜Kšœ7˜7šžœžœžœž˜Kšœ žœ˜#Kšœžœ˜#Kšžœ˜—J˜—K˜�šŸœ˜Kšœžœ˜%Kšœ žœ˜Jšœ œ6˜Dšžœžœžœžœžœž˜@Kšœžœžœ˜!Kšžœ˜—K˜—šŸœ˜Kšœžœ˜%Kšœžœ˜JšœH˜Hšžœžœžœžœžœž˜@Kšœžœžœ˜!Kšžœ˜—K˜—K˜�šŸœ˜Kšœžœ˜)Kšœ žœ˜JšœH˜Hšžœžœžœžœžœž˜@Kšœžœžœ˜!Kšžœ˜—J˜J˜�—šŸ œ˜ Kšœžœ˜)Kšœžœ˜Jšœ œ>˜Lšžœžœžœžœžœž˜@Kšœžœžœ˜!Kšžœ˜—J˜J˜�—šŸœ˜Kšœžœ˜$Kšœžœ˜$K™$KšœI™IKšœI™IJšœ žœ˜J˜J˜�—K™�K™ K˜�š Ÿœžœžœžœ =˜rKšœžœ˜2Kšœžœ ˜4Kšœžœ˜2Kšœžœ˜2Kšœžœ˜2šžœžœžœž˜!Kšœžœ˜'Kšœžœ˜'Kšžœ˜—Kšžœ˜ K˜—K˜�šŸ œžœ˜Kšœ˜Kšœ/˜/Kšœ&˜&Kšœ4˜4Kšœ' ˜8Kšœ4˜4Kšœ' ˜8K˜—K˜�K˜K˜�Kšžœ˜J˜�—�…—����z¨��×I��