DIRECTORY GGBasicTypes, AtomButtons, GGCaret, GGCircles, GGLines, GGInterfaceTypes, GGModelTypes, GGMultiGravity, GGSegmentTypes, GGStatistics, GGUtility, GGVector, RealFns, Rope; GGMultiGravityImpl: CEDAR PROGRAM IMPORTS AtomButtons, GGCaret, GGCircles, GGLines, GGStatistics, GGVector, RealFns EXPORTS GGMultiGravity = BEGIN Arc: TYPE = GGBasicTypes.Arc; AlignmentCircle: TYPE = GGInterfaceTypes.AlignmentCircle; AlignmentLine: TYPE = GGInterfaceTypes.AlignmentLine; AlignmentPoint: TYPE = REF AlignmentPointObj; AlignmentPointObj: TYPE = GGInterfaceTypes.AlignmentPointObj; Caret: TYPE = GGInterfaceTypes.Caret; Circle: TYPE = GGBasicTypes.Circle; Edge: TYPE = GGBasicTypes.Edge; FeatureData: TYPE = REF FeatureDataObj; FeatureDataObj: TYPE = GGModelTypes.FeatureDataObj; GargoyleData: TYPE = GGInterfaceTypes.GargoyleData; GoodCurve: TYPE = REF GoodCurveObj; GoodCurveObj: TYPE = GGMultiGravity.GoodCurveObj; GoodPoint: TYPE = REF GoodPointObj; GoodPointObj: TYPE = GGMultiGravity.GoodPointObj; GoodPointType: TYPE = GGMultiGravity.GoodPointType; 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; ObjectBag: TYPE = REF ObjectBagObj; ObjectBagObj: TYPE = GGInterfaceTypes.ObjectBagObj; Outline: TYPE = GGModelTypes.Outline; OutlineDescriptor: TYPE = REF OutlineDescriptorObj; OutlineDescriptorObj: TYPE = GGModelTypes.OutlineDescriptorObj; Point: TYPE = GGBasicTypes.Point; 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; MultiGravityPool: TYPE = REF MultiGravityPoolObj; MultiGravityPoolObj: TYPE = RECORD [ distances: NearDistances, features: NearFeatures, bestpoints: BestPoints, bestcurves: BestCurves ]; BestCurves: TYPE = REF BestCurvesObj; BestCurvesObj: TYPE = RECORD [ size: NAT, max, min: REAL, overflow: BOOL, curves: SEQUENCE len: NAT OF GoodCurve]; BestPoints: TYPE = REF BestPointsObj; BestPointsObj: TYPE = RECORD [ size: NAT, max, min: REAL, overflow: BOOL, points: SEQUENCE len: NAT OF GoodPoint]; EmptyBag: PROC [objectBag: ObjectBag] RETURNS [BOOL] = { RETURN[ objectBag.slopeLines = NIL AND objectBag.angleLines = NIL AND objectBag.radiiCircles = NIL AND objectBag.distanceLines = NIL AND objectBag.midpoints = NIL AND objectBag.intersectionPoints = NIL]; }; EmptyTriggers: PROC [triggerBag: TriggerBag] RETURNS [BOOL] = { RETURN[ triggerBag.outlines = NIL AND 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, currentObjects: ObjectBag, activeObjects: TriggerBag, gargoyleData: GargoyleData, intersections: BOOL] RETURNS [resultPoint: Point, feature: FeatureData] = { ENABLE UNWIND => gargoyleData.multiGravityPool _ NewMultiGravityPool[]; -- in case an ABORT happened while pool was in use GGStatistics.StartInterval[$MultiMap, GGStatistics.GlobalTable[]]; SELECT AtomButtons.GetButtonState[gargoyleData.hitTest.gravButton] FROM on => SELECT gargoyleData.hitTest.gravityType FROM strictDistance => [resultPoint, feature] _ StrictDistance[testPoint, criticalR, currentObjects, activeObjects, gargoyleData]; innerCircle => [resultPoint, feature] _ InnerCircle[testPoint, criticalR, gargoyleData.hitTest.innerR, currentObjects, activeObjects, gargoyleData, intersections]; ENDCASE => ERROR; off => { resultPoint _ testPoint; feature _ NIL; }; ENDCASE => ERROR; GGStatistics.StopInterval[$MultiMap, GGStatistics.GlobalTable[]]; }; PrepareWinner: PROC [nearPointsAndCurves: NearPointsAndCurves, index: NAT] RETURNS [resultPoint: Point, feature: FeatureData] = { WITH nearPointsAndCurves[index] SELECT FROM goodPoint: GoodPoint => { resultPoint _ goodPoint.point; ExtractResultFromPoint[goodPoint, goodPoint.featureData]; -- stuffs hit info into featureData feature _ goodPoint.featureData; }; goodCurve: GoodCurve => { resultPoint _ goodCurve.point; ExtractResultFromCurve[goodCurve, goodCurve.featureData]; -- stuffs hit info into featureData feature _ goodCurve.featureData; }; ENDCASE => ERROR; }; StrictDistance: PUBLIC PROC [testPoint: Point, criticalR: REAL, objectBag: ObjectBag, sceneBag: TriggerBag, gargoyleData: GargoyleData] RETURNS [resultPoint: Point, feature: FeatureData] = { nearPointsAndCurves: NearPointsAndCurves; count: NAT; [nearPointsAndCurves, count] _ MultiStrictDistance[testPoint, criticalR, objectBag, sceneBag, gargoyleData]; IF count = 0 THEN RETURN [testPoint, NIL]; IF count = 1 THEN RETURN PrepareWinner[nearPointsAndCurves, 0] ELSE { distances: NearDistances _ NARROW[gargoyleData.multiGravityPool, MultiGravityPool].distances; features: NearFeatures _ NARROW[gargoyleData.multiGravityPool, MultiGravityPool].features; nearestDist: REAL; bestSceneObject: INT; neighborCount: NAT _ 1; s: REAL = 0.072; -- 1/1000 inches FOR i: NAT IN [0..count) DO WITH nearPointsAndCurves[i] SELECT FROM goodPoint: GoodPoint => { distances[i] _ goodPoint.dist; features[i] _ goodPoint.featureData}; goodCurve: GoodCurve => { distances[i] _ goodCurve.dist; features[i] _ goodCurve.featureData;}; ENDCASE => ERROR; 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]; }; }; InnerCircle: PUBLIC PROC [testPoint: Point, criticalR: REAL, innerR: REAL, currentObjects: ObjectBag, activeObjects: TriggerBag, gargoyleData: GargoyleData, intersections: BOOL] RETURNS [resultPoint: Point, feature: FeatureData] = { count: NAT; nearPointsAndCurves: NearPointsAndCurves; [nearPointsAndCurves, count] _ MultiInnerCircle[testPoint, criticalR, innerR, currentObjects, activeObjects, gargoyleData, intersections]; IF count = 0 THEN RETURN [testPoint, NIL]; IF count = 1 THEN RETURN PrepareWinner[nearPointsAndCurves, 0] ELSE { distances: NearDistances _ NARROW[gargoyleData.multiGravityPool, MultiGravityPool].distances; features: NearFeatures _ NARROW[gargoyleData.multiGravityPool, MultiGravityPool].features; neighborCount: NAT _ 1; s: REAL = 0.072; -- 1/1000 inches nearestDist: REAL; FOR i: NAT IN [0..count) DO WITH nearPointsAndCurves[i] SELECT FROM goodPoint: GoodPoint => { distances[i] _ goodPoint.dist; features[i] _ goodPoint.featureData}; goodCurve: GoodCurve => { distances[i] _ goodCurve.dist; features[i] _ goodCurve.featureData;}; ENDCASE => ERROR; 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; }; }; NearestNeighborsPlusSome: PROC [q: Point, initialD: REAL, currentObjects: ObjectBag, activeObjects: TriggerBag, anchor: Caret, gargoyleData: GargoyleData, distinguishedPointsOnly: BOOL _ FALSE] RETURNS [g: NearPointsAndCurves, count: NAT] = { }; MultiMap: PUBLIC PROC [testPoint: Point, criticalR: REAL, currentObjects: ObjectBag, activeObjects: TriggerBag, gargoyleData: GargoyleData, intersections: BOOL] RETURNS [nearPointsAndCurves: NearPointsAndCurves, count: NAT] = { ENABLE UNWIND => gargoyleData.multiGravityPool _ NewMultiGravityPool[]; -- in case an ABORT happened while pool was in use GGStatistics.StartInterval[$MultiMap, GGStatistics.GlobalTable[]]; SELECT AtomButtons.GetButtonState[gargoyleData.hitTest.gravButton] FROM on => SELECT gargoyleData.hitTest.gravityType FROM strictDistance => [nearPointsAndCurves, count] _ MultiStrictDistance[testPoint, criticalR, currentObjects, activeObjects, gargoyleData]; innerCircle => [nearPointsAndCurves, count] _ MultiInnerCircle[testPoint, criticalR, gargoyleData.hitTest.innerR, currentObjects, activeObjects, gargoyleData, intersections]; ENDCASE => ERROR; off => { nearPointsAndCurves _ NIL; count _ 0; }; ENDCASE => ERROR; GGStatistics.StopInterval[$MultiMap, GGStatistics.GlobalTable[]]; }; MultiStrictDistance: PUBLIC PROC [testPoint: Point, criticalR: REAL, currentObjects: ObjectBag, activeObjects: TriggerBag, gargoyleData: GargoyleData] RETURNS [nearPointsAndCurves: NearPointsAndCurves, count: NAT] = { bestCurves: BestCurves; bestPoints: BestPoints; pointCount, curveCount: NAT; IF EmptyBag[currentObjects] AND EmptyTriggers[activeObjects] THEN RETURN[NIL, 0]; [bestCurves, curveCount] _ CurvesInTolerance[currentObjects, activeObjects, testPoint, gargoyleData, criticalR]; SortCurves[bestCurves, curveCount]; [bestPoints, pointCount] _ PointsInTolerance[bestCurves, curveCount, currentObjects, activeObjects, testPoint, criticalR, gargoyleData, FALSE]; SortPoints[bestPoints, pointCount]; count _ MIN[pointCount + curveCount, MaxFeatures]; nearPointsAndCurves _ NEW[NearPointsAndCurvesObj[count]]; MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearPointsAndCurves, count]; }; MultiInnerCircle: PUBLIC PROC [testPoint: Point, criticalR: REAL, innerR: REAL, currentObjects: ObjectBag, activeObjects: TriggerBag, gargoyleData: GargoyleData, intersections: BOOL] RETURNS [nearPointsAndCurves: NearPointsAndCurves, count: NAT] = { bestCurves: BestCurves; bestPoints: BestPoints; pointCount, curveCount: NAT; IF EmptyBag[currentObjects] AND EmptyTriggers[activeObjects] THEN RETURN[NIL, 0]; [bestCurves, curveCount] _ CurvesInTolerance[currentObjects, activeObjects, testPoint, gargoyleData, criticalR]; SortCurves[bestCurves, curveCount]; [bestPoints, pointCount] _ PointsInTolerance[bestCurves, curveCount, currentObjects, activeObjects, testPoint, criticalR, gargoyleData, intersections]; 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]; }; }; ExtractResultFromCurve: PROC [curve: GoodCurve, feature: FeatureData] = { SELECT feature.type FROM outline => { feature.hitPart _ curve.hitData; feature.resultType _ outline; }; slice => { feature.hitPart _ curve.hitData; feature.resultType _ slice; }; slopeLine => feature.resultType _ slopeLine; angleLine => feature.resultType _ angleLine; radiiCircle => feature.resultType _ radiiCircle; distanceLine => feature.resultType _ distanceLine; ENDCASE => SIGNAL Problem[msg: "Unimplemented result type."]; }; ExtractResultFromPoint: PROC [goodPoint: GoodPoint, feature: FeatureData] = { SELECT goodPoint.type FROM outline => { feature.hitPart _ goodPoint.hitData; feature.resultType _ outline; }; slice => { feature.hitPart _ goodPoint.hitData; feature.resultType _ slice; }; intersectionPoint => { feature.hitPart _ NIL; feature.resultType _ intersectionPoint; }; midpoint => { feature.resultType _ midpoint; }; anchor => { feature.resultType _ anchor; }; ENDCASE => SIGNAL Problem [msg: "Unimplemented result type."]; }; FindIntersections: PROC [bestCurves: BestCurves, curveCount: NAT, thisPoint: GoodPoint, q: Point, tolerance: REAL, h: BestPoints] = { curveI, curveJ: GoodCurve; theseIPoints: LIST OF Point; thisTangency, tangentList: LIST OF BOOL; success: BOOL; 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.type _ intersectionPoint; thisPoint.dist _ GGVector.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; MergePoint[thisPoint, h]; }; tangentList _ tangentList.rest; ENDLOOP; ENDLOOP; ENDLOOP; }; PointsInTolerance: PROC [bestCurves: BestCurves, curveCount: NAT, objectBag: ObjectBag, sceneObjects: TriggerBag, q: Point, t: REAL, gargoyleData: GargoyleData, intersections: BOOL] RETURNS [h: BestPoints, pointCount: NAT] = { thisPoint: GoodPoint; ProcessPoint: PROC [thisPoint: GoodPoint, featureData: FeatureData] = { dSquared: REAL; dSquared _ GGVector.DistanceSquared[thisPoint.point, q]; thisPoint.hitData _ NIL; IF dSquared < tSquared THEN { thisPoint.dist _ RealFns.SqRt[dSquared]; thisPoint.featureData _ featureData; MergePoint[thisPoint, h]; }; }; ProcessSlice: PROC [sliceD: SliceDescriptor, thisPoint: GoodPoint, featureData: FeatureData] = { [thisPoint.point, thisPoint.dist, thisPoint.hitData, success] _ sliceD.slice.class.closestPoint[sliceD, q, t]; IF success THEN { IF thisPoint.dist < t THEN { thisPoint.featureData _ featureData; MergePoint[thisPoint, h]; }; }; }; ProcessOutline: PROC [outlineD: OutlineDescriptor, thisPoint: GoodPoint, featureData: FeatureData] = { [thisPoint.point, thisPoint.dist, thisPoint.hitData, success] _ outlineD.slice.class.closestPoint[outlineD, q, t]; IF success THEN { IF thisPoint.dist < t THEN { thisPoint.featureData _ featureData; MergePoint[thisPoint, h]; }; }; }; sliceD: SliceDescriptor; outlineD: OutlineDescriptor; featureData: FeatureData; success: BOOL; tSquared: REAL _ t*t; thisPoint _ NEW[GoodPointObj]; h _ BestPointsFromPool[gargoyleData]; IF intersections THEN FindIntersections[bestCurves, curveCount, thisPoint, q, t, h]; FOR midpoints: LIST OF FeatureData _ objectBag.midpoints, midpoints.rest UNTIL midpoints = NIL DO featureData _ midpoints.first; thisPoint.type _ midpoint; thisPoint.point _ NARROW[featureData.shape, AlignmentPoint].point; ProcessPoint[thisPoint, featureData]; ENDLOOP; FOR slices: LIST OF FeatureData _ sceneObjects.slices, slices.rest UNTIL slices = NIL DO featureData _ slices.first; thisPoint.type _ slice; sliceD _ NARROW[featureData.shape, SliceDescriptor]; ProcessSlice[sliceD, thisPoint, featureData]; ENDLOOP; FOR outlines: LIST OF FeatureData _ sceneObjects.outlines, outlines.rest UNTIL outlines = NIL DO featureData _ outlines.first; thisPoint.type _ outline; outlineD _ NARROW[featureData.shape]; ProcessOutline[outlineD, thisPoint, featureData]; ENDLOOP; featureData _ sceneObjects.anchor; IF featureData # NIL THEN { anchor: Caret; anchor _ NARROW[featureData.shape]; IF NOT GGCaret.Exists[anchor] THEN ERROR; thisPoint.type _ anchor; thisPoint.point _ GGCaret.GetPoint[anchor]; ProcessPoint[thisPoint, featureData]; }; pointCount _ h.size; }; CurvesInTolerance: PROC [objectBag: ObjectBag, sceneObjects: TriggerBag, q: Point, gargoyleData: GargoyleData, t: REAL] RETURNS [h: BestCurves, curveCount: NAT] = { ProcessLine: PROC [line: Line, thisCurve: GoodCurve, featureData: FeatureData] = { thisCurve.dist _ GGLines.LineDistance[q, line]; IF thisCurve.dist < t THEN { thisCurve.featureData _ featureData; thisCurve.point _ GGLines.PointProjectedOntoLine[q, line]; thisCurve.hitData _ NIL; added _ MergeCurve[thisCurve, h]; } }; ProcessCircle: PROC [circle: Circle, thisCurve: GoodCurve, 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; added _ MergeCurve[thisCurve, h]; }; }; ProcessSlice: PROC [sliceD: SliceDescriptor, thisCurve: GoodCurve, 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; added _ MergeCurve[thisCurve, h]; }; }; }; ProcessOutline: PROC [outlineD: OutlineDescriptor, thisCurve: GoodCurve, 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; added _ MergeCurve[thisCurve, h]; }; }; }; line: Line; circle: Circle; sliceD: SliceDescriptor; outlineD: OutlineDescriptor; featureData: FeatureData; added: BOOL; thisCurve: GoodCurve _ NEW[GoodCurveObj]; h _ BestCurvesFromPool[gargoyleData]; FOR slopeLines: LIST OF FeatureData _ objectBag.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 _ objectBag.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 _ objectBag.distanceLines, dLines.rest UNTIL dLines = NIL DO featureData _ dLines.first; line _ NARROW[featureData.shape]; ProcessLine[line, thisCurve, featureData]; ENDLOOP; FOR circles: LIST OF FeatureData _ objectBag.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 _ sceneObjects.slices, slices.rest UNTIL slices = NIL DO featureData _ slices.first; sliceD _ NARROW[featureData.shape, SliceDescriptor]; ProcessSlice[sliceD, thisCurve, featureData]; ENDLOOP; FOR outlines: LIST OF FeatureData _ sceneObjects.outlines, outlines.rest UNTIL outlines = NIL DO featureData _ outlines.first; outlineD _ NARROW[featureData.shape, OutlineDescriptor]; ProcessOutline[outlineD, thisCurve, featureData]; ENDLOOP; curveCount _ h.size; }; -- end CurvesInTolerance MergePoint: PROC [thisPoint: GoodPoint, h: BestPoints] = { d: REAL _ thisPoint.dist; n: NAT = MaxFeatures; SELECT TRUE FROM d > h.max AND h.size < n => {h.max _ d; GOTO Add}; d <= h.max AND 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 => { d: REAL _ thisPoint.dist; h[h.size]^ _ thisPoint^; h.size _ h.size + 1; h.min _ IF d < h.min THEN d ELSE h.min; }; AddAndComputeNewMax => { d: REAL _ thisPoint.dist; newMax: REAL; iMax: NAT; 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 FALSE; h.max _ newMax; }; NoChange => { }; }; MergeCurve: PROC [thisCurve: GoodCurve, h: BestCurves] RETURNS [added: BOOL] = { d: REAL _ thisCurve.dist; n: NAT _ MaxFeatures; SELECT TRUE FROM d > h.max AND h.size < n => {h.max _ d; GOTO Add}; d <= h.max AND 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 => { d: REAL _ thisCurve.dist; h[h.size]^ _ thisCurve^; h.size _ h.size + 1; h.min _ IF d < h.min THEN d ELSE h.min; added _ TRUE; }; AddAndComputeNewMax => { d: REAL _ thisCurve.dist; newMax: REAL; iMax: NAT; 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]^ _ thisCurve^; 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 FALSE; h.max _ newMax; added _ TRUE; }; NoChange => { added _ FALSE; }; }; 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: BestCurves, 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: BestCurves, curveCount: NAT] = { temp: GoodCurveObj; 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; }; ClassifyCurve: PROC [curve: GoodCurve] 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 _ sliceD.slice.class.hitDataAsSimpleCurve[sliceD.slice, hitData]; IF simpleCurve = NIL THEN { simpleCurve _ sliceD; class _ 5; RETURN; }; }; outline => { 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; }; }; 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: GoodCurve] 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] _ GGLines.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] _ GGLines.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] _ GGLines.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; }; MaxFeatures: NAT = 20; BestCurvesFromPool: PROC [gargoyleData: GargoyleData] RETURNS [h: BestCurves] = { h _ NARROW[gargoyleData.multiGravityPool, MultiGravityPool].bestcurves; h.size _ 0; h.max _ 0; h.min _ GGUtility.plusInfinity; h.overflow _ FALSE; FOR i: NAT IN [0..MaxFeatures-1] DO h[i].dist _ GGUtility.plusInfinity; h[i].featureData _ NIL; ENDLOOP; }; BestPointsFromPool: PROC [gargoyleData: GargoyleData] RETURNS [h: BestPoints] = { h _ NARROW[gargoyleData.multiGravityPool, MultiGravityPool].bestpoints; h.size _ 0; h.max _ 0; h.min _ GGUtility.plusInfinity; h.overflow _ FALSE; FOR i: NAT IN [0..MaxFeatures-1] DO h[i].dist _ GGUtility.plusInfinity; h[i].featureData _ NIL; ENDLOOP; }; 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[BestCurvesObj[MaxFeatures]]; FOR i: NAT IN [0..MaxFeatures) DO pool.bestpoints[i] _ NEW[GoodPointObj]; pool.bestcurves[i] _ NEW[GoodCurveObj]; ENDLOOP; RETURN[pool]; }; InitStats: PROC [] = { interval: GGStatistics.Interval; interval _ GGStatistics.CreateInterval[$MultiMap]; GGStatistics.AddInterval[interval, GGStatistics.GlobalTable[]]; }; InitStats[]; END. fGGMultiGravityImpl.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]Documentation>MultiGravity.tioga. Shared with GGGravityImpl 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, InnerCircle, 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. Someday, GoodPoint and GoodCurve 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; ResultFeatureType: TYPE = {joint, segment, controlPoint, slice, distanceLine, slopeLine, angleLine, symmetryLine, radiiCircle, intersectionPoint, midpoint, anchor}; Otherwise, let's do arbitration. Now we choose on the following basis: 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 MultiInnerCircle 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 the critical radius, 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 will consist of a mixture of points and curves. However, if criticalR = innerR THEN only points or only curves will be returned. midPoint features contain a reasonable segNum in their hitPart anchors contain the anchor point in their hitPart For each gravity active point, find its distance from the testpoint. Package this information up into the thisPoint record. Then call MergePoint, which will add this point to the list of best points, if appropriate. FOR iPoints: LIST OF FeatureData _ objectBag.intersectionPoints, iPoints.rest UNTIL iPoints = NIL DO featureData _ iPoints.first; thisPoint.type _ intersectionPoint; thisPoint.point _ NARROW[featureData.shape, AlignmentPoint].point; ProcessPoint[thisPoint, featureData]; ENDLOOP; Handle the anchor. For each slopeLine, find the distance of the slopeLine from the testpoint. Package this information up into the thisCurve record. Then call MergeCurve, which will add this curve to the list of best curves, if appropriate. Do not merge any curves that are farther away than tolerance. Sensitive Scene Objects. 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 FeatureType: TYPE = {sequence, slice, distanceLine, slopeLine, angleLine, symmetryLine, radiiCircle, intersectionPoint, midpoint, anchor}; The asymmetry here is OK. 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 Ê-î˜J˜Icodešœ™Kšœ Ïmœ1™šžœ˜Kš¡ ™ Kšœžœ<˜]Kšœžœ;˜ZKšœ žœ˜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˜—Kšœžœ™¤šÐbn ¡žœžœžœ žœcžœžœ/˜èKšœžœ˜ Kšœ)˜)Kšœ¡œ¡œ[˜ŠKšžœ žœžœ žœ˜*Kšžœ žœžœ&˜>šžœ˜Kš¡ ™ Kšœžœ<˜]Kšœžœ;˜ZKšœžœ˜Kšœžœ  ˜!Kšœ žœ˜šžœžœžœ ž˜šžœžœž˜'šœ˜Kšœ˜Kšœ%˜%—šœ˜Kšœ˜Kšœ&˜&—Kšžœžœ˜—Kšžœ˜—Kšœ˜šžœžœžœ ž˜Kšžœ žœ#˜IKšžœ˜—šžœžœžœ'˜GKš¡%™%Kš¡+™+Kš¡Z™Z—šžœžœžœž˜#šžœžœžœ˜@Kšžœ'˜-K˜—šž˜Kšžœžœ'˜9—Kšžœ˜—K˜—K˜—K™K™š Ÿœžœžœ|ž œžœ!žœ˜òK˜K˜K˜—Kšœ™K™šŸœžœžœžœcžœžœ3žœ˜ãKš¡E™EKšžœžœ< 2˜{KšœB˜Bšžœ=ž˜Gšœžœ"ž˜2šœ˜Kšœv˜v—šœ˜KšœŸ˜Ÿ—Kšžœžœ˜—šœ˜Kšœžœ˜Kšœ ˜ K˜—Kšžœžœ˜—KšœA˜AK˜K˜—š ŸœžœžœžœTžœ3žœ˜ÙKš¡Œ™ŒKšœ˜Kšœ˜Kšœžœ˜šžœžœž˜AKšžœžœ˜K˜—Kšœ¡œD˜pKšœ#˜#K˜Kšœ¡œ\žœ˜Kšœ#˜#K˜Kšœžœ'˜2Kšœžœ ˜9Kš¡œM˜aK˜K˜—š¢¡žœžœžœ žœcžœžœ3žœ˜ùKš¡¾™¾Kšœ˜Kšœ˜Kšœžœ˜Kš žœžœžœžœžœ˜QK˜Kšœ¡œD˜pKšœ#˜#K˜Kšœ¡œk˜—Kšœ#˜#K˜šžœžœžœ˜8Kšœ˜Kšœžœ ˜9KšœB˜BK˜—šžœ˜Kšœžœ'˜2Kšœžœ ˜9Kšœa˜aK˜—K˜K˜—šŸœžœ-˜Išžœž˜šœ ˜ Kšœžœ˜ Kšœ˜K˜—šœ ˜ Kšœžœ˜ Kšœ˜K˜—Kšœ,˜,Kšœ,˜,Kšœ0˜0Kšœ2˜2Kšžœžœ,˜=—K˜K˜—šŸœžœ1˜Mšžœž˜šœ ˜ Kšœžœ˜$Kšœ˜K˜—šœ ˜ Kšœžœ˜$Kšœ˜K˜—˜Kšœžœ˜Kšœ'˜'K˜—˜ K™>Kšœ˜K˜—˜ K™1Kšœ˜K˜—Kšžœžœ-˜>—K˜K˜—šŸœžœ&žœ-žœ˜…Kšœ˜Kšœžœžœ˜Kšœžœžœžœ˜(Kšœ žœ˜šžœžœžœž˜ Kšœ˜šžœžœžœž˜"Kšœ˜Kšœ?˜?Kšœ˜š žœžœžœ!žœžœž˜EKšœ˜Kšœ#˜#Kšœ4¡œ˜7Kšœ&˜&šžœ žœ˜Kšœžœ˜/šœ!žœ˜:Kšœ˜Kšœ˜Kšœ˜Kšœ˜—Kšœ%˜%Kšœ#˜#Kšœ$˜$Kš¡ œ˜K˜—Kšœ˜Kšžœ˜—Kšžœ˜—Kšžœ˜—K˜K˜—šŸœžœ&žœ2¡œ Ïiœžœ-žœžœžœ˜âKšœ˜KšœÙ™ÙšŸ œžœ5˜GKšœ žœ˜Kšœ8˜8Kšœžœ˜šžœžœ˜Kšœ(˜(Kšœ$˜$Kšœ˜K˜—K˜—šŸ œžœN˜`Kšœ¡œK£œ˜nšžœ žœ˜šžœ£œžœ˜Kšœ$˜$Kšœ˜K˜—Kšœ˜—K˜—šŸœžœR˜fKšœ¡œO£œ˜ršžœ žœ˜šžœ£œžœ˜Kšœ$˜$Kšœ˜K˜—Kšœ˜—K˜K˜—K˜K˜Kšœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœ%˜%K˜Kšžœžœ¡œ-˜TK˜š žœ¡œžœžœ:žœ žœž™dKšœ™Kšœ#™#Kšœžœ*™BKšœ%™%Kšžœ™—š žœ¡ œžœžœ3žœ žœž˜aKšœ˜Kšœ˜Kšœžœ*˜BKšœ%˜%Kšžœ˜—š žœ¡œžœžœ0žœ žœž˜XKšœ˜Kšœ˜Kšœ žœ%˜4Kšœ-˜-Kšžœ˜—š žœ¡œžœžœ4žœ žœž˜`Kšœ˜Kšœ˜Kšœ žœ˜%Kšœ1˜1Kšžœ˜Kš¡™—Kšœ"˜"šžœžœžœ˜K˜Kšœ žœ˜#Kšžœžœžœžœ˜)Kšœ˜Kšœ+˜+Kšœ%˜%K˜—Kšœ˜K˜K™—š Ÿœžœ[žœžœžœ˜¤Kšœž™žšŸ œžœA˜RKšœ/˜/šžœžœž˜Kšœ$˜$Kšœ:˜:Kšœžœ˜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˜š žœ¡ œžœžœ5žœžœž˜eKšœ˜Kšœžœ(˜5Kšœ*˜*Kšžœ˜—š žœ¡ œžœžœ5žœžœž˜eKšœ˜Kšœžœ(˜5Kšœ*˜*Kšžœ˜—š žœ¡œžœžœ4žœ žœž˜\Kšœ˜Kšœžœ˜!Kšœ*˜*Kšžœ˜—š žœ¡œžœžœ4žœ žœž˜^Kšœ˜Kšœ žœ,˜;Kšœ.˜.Kšžœ˜—Kš¡™š žœ¡œžœžœ0žœ žœž˜XKšœ˜Kšœ žœ%˜4Kšœ-˜-Kšžœ˜—š žœ¡œžœžœ4žœ žœž˜`Kšœ˜Kšœ žœ'˜8Kšœ1˜1Kšžœ˜—K˜Kšœ ˜K˜—šŸ œžœ*˜:Kšœžœ˜Kšœžœ˜šžœžœž˜Kšœ žœžœ˜2Kšœ žœžœ˜&Kšœ žœžœ˜5Kšœ žœžœ  *˜UKšœ žœžœžœ ˜?Kšžœžœ"˜3—šž˜šœ˜Kšœžœ˜K˜K˜Kšœžœ žœžœ˜'K˜—˜Kšœžœ˜Kšœžœ˜ Kšœžœ žœ˜K˜šžœžœžœž˜#Jšžœžœ"˜>Jšžœ˜—J˜J˜K˜šžœžœžœž˜#Jšžœžœ"˜>Jšžœ˜—J˜Jš œ žœžœžœžœžœ˜4J˜J˜—˜ J˜——K˜K˜K˜—šŸ œžœ'žœ žœ˜PKšœžœ˜Kšœžœ˜šžœžœž˜Kšœ žœžœ˜2Kšœ žœžœ˜&Kšœ žœžœ˜5Kšœ žœžœ  *˜UKšœ žœžœžœ ˜?Kšžœžœ"˜3—šž˜šœ˜Kšœžœ˜Kšœ˜Kšœ˜Kšœžœ žœžœ˜'Kšœžœ˜ K˜—˜Kšœžœ˜Kšœžœ˜ Kšœžœ žœ˜K˜šžœžœžœž˜#Jšžœžœ"˜>Jšžœ˜—J˜J˜K˜šžœžœžœž˜#Jšžœžœ"˜>Jšžœ˜—J˜Jš œ žœžœžœžœžœ˜4J˜Kšœžœ˜ J˜—˜ Jšœžœ˜J˜——K˜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šœ žœy™Šš Ÿ œžœžœ žœžœžœ˜UKšœ)˜)šžœž˜˜ Kšœžœ˜0Kšœ žœžœ˜!KšœM˜Mšžœžœžœ˜Kšœ˜Kšœ ˜ Kšžœ˜K˜—Kšœ˜—šœ ˜ Kš¡™Kšœžœ˜2Kšœ žœžœ˜!KšœM˜Mšžœžœžœ˜Kšœ ˜ Kšžœ˜K˜—Kšœ˜—šœ˜Kšœ ˜ Kšœžœ(˜˜Lš žœžœžœžœžœž˜@Kšœ žœžœ ˜!Kšžœ˜—J˜J˜—šŸœ˜Kšœžœ˜$Kšœžœ˜$K™$KšœI™IKšœI™IJšœ žœ˜J˜J˜—K™ KšŸ œžœ˜šŸœžœžœ˜QKšœžœ=˜GKšœ ˜ K˜ Kšœ˜Kšœ žœ˜šžœžœžœž˜#Jšœ#˜#Jšœžœ˜Jšžœ˜—J˜J˜—šŸœžœžœ˜QKšœžœ=˜GKšœ ˜ K˜ Kšœ˜Kšœ žœ˜šžœžœžœž˜#Jšœ#˜#Jšœžœ˜Jšžœ˜—J˜K˜K˜—š Ÿœž œžœžœ =˜rKšœžœ˜2Kšœžœ ˜4Kšœžœ˜2Kšœžœ˜2Kšœžœ˜2šžœžœžœž˜!Kšœžœ˜'Kšœžœ˜'Kšžœ˜—Kšžœ˜ K˜—K˜šŸ œžœ˜K˜ K˜2Kšœ?˜?K˜—K˜K˜ K˜Kšžœ˜J˜—…—x½