DIRECTORY AtomButtonsTypes, Feedback, FunctionCache, GGBasicTypes, GGBoundBox, GGCircles, GGInterfaceTypes, GGModelTypes, GGSegment, GGSegmentTypes, GGParseIn, GGParseOut, GGShapes, GGSlice, GGUtility, Imager, ImagerPath, ImagerTransformation, IO, Lines2d, NodeStyle, RealFns, Rope, Vectors2d, ViewerClasses; GGSliceImplC: CEDAR PROGRAM IMPORTS Feedback, FunctionCache, GGBoundBox, GGCircles, GGParseIn, GGParseOut, GGSegment, GGShapes, GGSlice, Imager, ImagerPath, ImagerTransformation, IO, Lines2d, RealFns, Rope, Vectors2d EXPORTS GGSlice = BEGIN OPEN GGModelTypes; -- for SliceProc types Object: TYPE = Imager.Object; Color: TYPE = Imager.Color; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem; CircleData: TYPE = REF CircleDataObj; CircleDataObj: TYPE = RECORD [ circle: Circle, -- a representation of a unit circle (for the convenience of GGCircles.LineMeetsCircle) tightBox: BoundBox, transform: ImagerTransformation.Transformation, scale: Vector, -- cached value of ImagerTransformation.Factor[transform].s evenScaling: BOOL, simpleCircle: Circle, -- for CircleHitDataAsSimpleCurve inverse: ImagerTransformation.Transformation, inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s startPoint: Point, -- original point on circumference when circle was created fillColor: Color, segment: Segment ]; MaxCirclePoints: INTEGER = 5; CirclePoints: TYPE = [0..MaxCirclePoints); -- origin, left, top, right, bottom CircleParts: TYPE = REF CirclePartsObj; CirclePartsObj: TYPE = RECORD [ cpArray: ARRAY CirclePoints OF BOOL, -- used when origin or CPs are selected outline: BOOL -- TRUE if outline itself is in parts ]; CircleHitData: TYPE = REF CircleHitDataObj; CircleHitDataObj: TYPE = RECORD [ index: [-1..MaxCirclePoints), -- records index of which origin or CP is hit. -1 => no hit hitPoint: Point, outline: BOOL -- TRUE if outline itself is hit ]; Props: TYPE = REF PropsRec; PropsRec: TYPE = RECORD [ transform: Transformation, strokeWidth: REAL _ 0.0, strokeEnd: StrokeEnd _ round, dashed: BOOL _ FALSE, -- dashed stroke properties pattern: SequenceOfReal, offset: REAL _ 0.0, length: REAL _ 0.0, data: REF ]; BuildCircleSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { OPEN GGSlice; class _ NEW[SliceClassObj _ [ type: $Circle, getBoundBox: CircleBoundBox, getTightBox: CircleTightBox, copy: CircleCopy, drawParts: CircleDrawParts, drawTransform: CircleDrawTransform, drawSelectionFeedback: CircleDrawSelectionFeedback, drawAttractorFeedback: NoOpDrawAttractorFeedback, transform: CircleTransform, describe: CircleDescribe, describeHit: CircleDescribeHit, fileout: CircleFileout, filein: CircleFilein, isEmptyParts: CircleIsEmptyParts, isCompleteParts: CircleIsCompleteParts, newParts: CircleNewParts, unionParts: CircleUnionParts, differenceParts: CircleDiffParts, movingParts: CircleMovingParts, augmentParts: CircleAugmentParts, setSelectedFields: NoOpSetSelectedFields, pointsInDescriptor: CirclePointsInDescriptor, pointPairsInDescriptor: NoOpPointPairsInDescriptor, segmentsInDescriptor: CircleSegmentsInDescriptor, walkSegments: CircleWalkSegments, nextPoint: CircleNextPoint, nextPointPair: NoOpNextPointPair, nextSegment: CircleNextSegment, closestPoint: CircleClosestPoint, closestJointToHitData: NoOpClosestJointToHitData, closestPointAndTangent: NIL, closestSegment: CircleClosestSegment, lineIntersection: CircleLineIntersection, circleIntersection: NoOpCircleIntersection, hitDataAsSimpleCurve: CircleHitDataAsSimpleCurve, setDefaults: CircleSetDefaults, setStrokeWidth: CircleSetStrokeWidth, getStrokeWidth: CircleGetStrokeWidth, setStrokeEnd: NoOpSetStrokeEnd, getStrokeEnd: NoOpGetStrokeEnd, setStrokeJoint: NoOpSetStrokeJoint, getStrokeJoint: NoOpGetStrokeJoint, setStrokeColor: CircleSetStrokeColor, getStrokeColor: CircleGetStrokeColor, setFillColor: CircleSetFillColor, getFillColor: CircleGetFillColor, setArrows: NoOpSetArrows, getArrows: NoOpGetArrows, setDashed: CircleSetDashed, getDashed: CircleGetDashed ]]; }; MakeCircleSlice: PUBLIC PROC [origin: Point, outerPoint: Point] RETURNS [sliceD: SliceDescriptor] = { circleData: CircleData _ NEW[CircleDataObj]; circleParts: CircleParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE ]]; slice: Slice; circleData.circle _ GGCircles.CircleFromPointAndRadius[[0,0], 1.0]; circleData.tightBox _ GGBoundBox.NullBoundBox[]; circleData.startPoint _ outerPoint; circleData.segment _ GGSegment.MakeArc[[-1.0, 0.0], [1.0, 1.0], [-1.0, 0.0], NIL]; circleData.segment.lo _ origin; circleData.segment.hi _ outerPoint; circleData.segment.strokeWidth _ 2.0; circleData.segment.color _ Imager.black; slice _ NEW[SliceObj _ [ class: GGSlice.FetchSliceClass[$Circle], data: circleData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0] ]]; slice.nullDescriptor _ GGSlice.DescriptorFromParts[slice, NIL]; IF origin=outerPoint THEN circleData.startPoint _ [origin.x+20.0, origin.y+20.0]; -- prevent degenerate circle circleData.transform _ ImagerTransformation.PreScale[ImagerTransformation.Translate[origin], Vectors2d.Distance[origin, circleData.startPoint]]; circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; circleData.evenScaling _ TRUE; circleData.simpleCircle _ GGCircles.CircleFromPointAndRadius[origin, Vectors2d.Distance[origin, circleData.startPoint]]; CircleSetBoundBox[slice]; sliceD _ NEW[SliceDescriptorObj _ [slice, circleParts] ]; [] _ CircleClosestPoint[sliceD, outerPoint, 999.0]; -- only purpose of this call is to set up the HitData so that the first call to SelectSlice (in GGMouseEventImplA.EndCircle) works }; CircleBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { RETURN[slice.boundBox]; }; CircleTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { RETURN[slice.boundBox]; }; CircleSetBoundBox: PROC [slice: Slice] = { cpHalf: REAL _ GGModelTypes.halfJointSize + 1; strokeWidth: REAL _ NARROW[slice.data, CircleData].segment.strokeWidth; pad: REAL _ MAX[cpHalf, strokeWidth]; circleData: CircleData _ NARROW[slice.data]; epsilon: REAL = 1.0E-6; origin: Point _ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]]; minor: Point _ ImagerTransformation.Transform[circleData.transform, [0.0, 1.0]]; -- p1 major: Point _ ImagerTransformation.Transform[circleData.transform, [1.0, 0.0]]; -- p0, p2 radius: REAL _ RealFns.SqRt[MAX[Vectors2d.DistanceSquared[minor, origin], Vectors2d.DistanceSquared[major, origin]]]; GGBoundBox.UpdateBoundBox[circleData.tightBox, origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius]; GGBoundBox.UpdateCopyBoundBox[bBox: slice.boundBox, from: circleData.tightBox]; GGBoundBox.EnlargeByOffset[slice.boundBox, pad]; IF ABS[circleData.inverseScale.x - circleData.inverseScale.y] < epsilon THEN { origin: Point; radius: REAL; origin _ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]]; radius _ circleData.scale.x; circleData.evenScaling _ TRUE; GGCircles.FillCircleFromPointAndRadius[origin, radius, circleData.simpleCircle]; } ELSE circleData.evenScaling _ FALSE; circleData.segment.lo _ origin; circleData.segment.hi _ major; }; CircleCopy: PROC [slice: Slice] RETURNS [copy: Slice] = { circleData: CircleData _ NARROW[slice.data]; newData: CircleData; copyD: SliceDescriptor _ MakeCircleSlice[[0.0, 0.0], [1.0, 1.0]]; copy _ copyD.slice; newData _ NARROW[copy.data]; newData.transform _ ImagerTransformation.Copy[circleData.transform]; newData.scale _ circleData.scale; newData.inverse _ ImagerTransformation.Copy[circleData.inverse]; newData.inverseScale _ circleData.inverseScale; newData.evenScaling _ circleData.evenScaling; newData.simpleCircle _ GGCircles.CreateEmptyCircle[]; GGCircles.CopyCircle[from: circleData.simpleCircle, to: newData.simpleCircle]; newData.segment _ GGSegment.CopySegment[circleData.segment]; slice.class.setFillColor[copy, circleData.fillColor]; CircleSetBoundBox[copy]; }; CircleDrawParts: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: CameraData, quick: BOOL] = { DoCircleDrawOutline: PROC = { CircleDrawOutline[dc, circleData, circleData.transform, TRUE]; }; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; IF circleParts = NIL OR circleParts.outline THEN Imager.DoSaveAll[dc, DoCircleDrawOutline]; }; CircleDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { OPEN ImagerTransformation; DoDrawFeedback: PROC = { thisCPisHot, thisCPisSelected: BOOL; pts: ARRAY[0..4] OF Point; pts[0] _ Transform[t, [0.0, 0.0]]; pts[1] _ Transform[t, [-1.0, 0.0]]; pts[2] _ Transform[t, [0.0, 1.0]]; pts[3] _ Transform[t, [1.0, 0.0]]; pts[4] _ Transform[t, [0.0, -1.0]]; IF NOT quick AND IsComplete[normalCircleParts] THEN { }; FOR index: CirclePoints IN [0..MaxCirclePoints) DO thisCPisHot _ hotCircleParts#NIL AND hotCircleParts.cpArray[index]; thisCPisSelected _ normalCircleParts#NIL AND normalCircleParts.cpArray[index]; IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[index], hot]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[index], normal]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[index]]; ENDLOOP; }; normalCircleParts, hotCircleParts: CircleParts; circleData: CircleData _ NARROW[slice.data]; t: ImagerTransformation.Transformation _ circleData.transform; IF caretIsMoving OR dragInProgress THEN RETURN; IF selectedParts = NIL AND hotParts = NIL THEN RETURN; normalCircleParts _ NARROW[selectedParts]; hotCircleParts _ NARROW[hotParts]; IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback]; }; cpHalf: REAL = GGModelTypes.halfJointSize + 1; useCache: BOOL _ TRUE; showBox: BOOL _ FALSE; -- draws bBox for every new cache entry. For debugging CircleBoxFromTransform: PROC [circleData: CircleData, transform: Transformation] RETURNS [r: Imager.Rectangle] = INLINE { pad: REAL _ MAX[cpHalf, circleData.segment.strokeWidth]; origin: Point _ ImagerTransformation.Transform[transform, [0.0, 0.0]]; minor: Point _ ImagerTransformation.Transform[transform, [0.0, 1.0]]; -- p1 major: Point _ ImagerTransformation.Transform[transform, [1.0, 0.0]]; -- p0, p2 radius: REAL _ RealFns.SqRt[MAX[Vectors2d.DistanceSquared[minor, origin], Vectors2d.DistanceSquared[major, origin]]]; box: BoundBox_ GGBoundBox.CreateBoundBox[origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius]; GGBoundBox.EnlargeByOffset[box, pad]; r _ GGBoundBox.RectangleFromBoundBox[box]; }; LookUpCircle: PROC [circleData: CircleData, transform: Transformation] RETURNS [object: Object] = { EqualPattern: PROC [p1, p2: SequenceOfReal] RETURNS [BOOL] = INLINE { IF p1.len#p2.len THEN RETURN[FALSE]; FOR i: NAT IN [0.. p1.len) DO IF p1[i]#p2[i] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; CompareProps: FunctionCache.CompareProc = { props: Props _ NARROW[argument]; seg: Segment _ circleData.segment; IF props.strokeWidth=seg.strokeWidth AND props.strokeEnd=seg.strokeEnd AND props.dashed=seg.dashed AND props.offset=seg.offset AND props.length=seg.length AND ImagerTransformation.CloseToTranslation[props.transform, transform, maxPixels] AND (NOT props.dashed OR EqualPattern[props.pattern, seg.pattern]) THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; value: FunctionCache.Range; maxPixels: REAL _ 10000.0; ok: BOOL; [value, ok] _ FunctionCache.Lookup[x: cache, compare: CompareProps, clientID: $GGCircleObject]; RETURN [IF ok THEN NARROW[value] ELSE NIL]; }; CircleDrawOutline: PROC [dc: Imager.Context, circleData: CircleData, transform: ImagerTransformation.Transformation, cacheIt: BOOL _ FALSE] = { object: Imager.Object; IF NOT useCache THEN { OldCircleDrawOutline[dc, circleData, transform]; RETURN; }; object _ LookUpCircle[circleData, transform]; IF object = NIL THEN { -- put it in the cache clipR: Imager.Rectangle _ CircleBoxFromTransform[circleData, transform]; seg: Segment _ circleData.segment; props: Props _ NEW[PropsRec _ [ transform: ImagerTransformation.Copy[transform], strokeWidth: seg.strokeWidth, strokeEnd: round, dashed: seg.dashed, pattern: seg.pattern, offset: seg.offset, length: seg.length, data: NIL] ]; IF showBox THEN { Imager.SetGray[dc, 0.2]; Imager.MaskRectangle[dc, clipR]; }; object _ NEW[Imager.ObjectRep _ [draw: CircleDrawObject, clip: clipR, data: props]]; FunctionCache.Insert[cache, props, object, 60, $GGCircleObject]; }; BEGIN CirclePath: Imager.PathProc = { leftSide: Point _ [-1.0, 0.0]; rightSide: Point _ [1.0, 0.0]; moveTo[[leftSide.x, leftSide.y]]; arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]]; }; FillItIn: PROC = { Imager.SetColor[dc, circleData.fillColor]; Imager.ConcatT[dc, transform]; Imager.MaskFill[dc, CirclePath, TRUE] }; circleProps: Props _ NARROW[object.data]; position: Imager.VEC _ ImagerTransformation.Transform[transform, ImagerTransformation.InverseTransform[circleProps.transform, [0, 0]]]; IF circleData.fillColor#NIL THEN Imager.DoSave[dc, FillItIn]; IF circleData.segment.color#NIL AND circleData.segment.strokeWidth#0.0 THEN { Imager.SetColor[dc, circleData.segment.color]; Imager.DrawObject[context: dc, object: object, interactive: TRUE, position: position]; }; END; }; CircleDrawObject: PROC [self: Imager.Object, context: Imager.Context] = { CirclePath: Imager.PathProc = { moveTo[[leftSide.x, leftSide.y]]; arcTo[[rightSide.x, rightSide.y], [leftSide.x, leftSide.y]]; }; TransformedCirclePath: Imager.PathProc = { ImagerPath.Transform[CirclePath, transform, moveTo, lineTo, curveTo, conicTo, arcTo]; }; circleProps: Props _ NARROW[self.data]; transform: Transformation _ circleProps.transform; leftSide: Point _ [-1.0, 0.0]; rightSide: Point _ [1.0, 0.0]; IF circleProps.strokeWidth#0.0 THEN { Imager.SetStrokeWidth[context, circleProps.strokeWidth]; Imager.SetStrokeEnd[context, circleProps.strokeEnd]; -- needed to make object caching work Imager.SetStrokeJoint[context, round]; -- needed to make object caching work IF circleProps.dashed THEN { PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; pattern: SequenceOfReal _ circleProps.pattern; Imager.MaskDashedStroke[context, TransformedCirclePath, pattern.len, PatternProc, circleProps.offset, circleProps.length]; } ELSE Imager.MaskStroke[context, TransformedCirclePath, TRUE]; }; }; OldCircleDrawOutline: PROC [dc: Imager.Context, circleData: CircleData, transform: ImagerTransformation.Transformation] = { CirclePath: Imager.PathProc = { moveTo[[leftSide.x, leftSide.y]]; arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]]; }; TransformedCirclePath: Imager.PathProc = { ImagerPath.Transform[CirclePath, transform, moveTo, lineTo, curveTo, conicTo, arcTo]; }; FillItIn: PROC = { Imager.SetColor[dc, circleData.fillColor]; Imager.ConcatT[dc, transform]; Imager.MaskFill[dc, CirclePath, TRUE] }; seg: Segment _ circleData.segment; leftSide: Point _ [-1.0, 0.0]; rightSide: Point _ [1.0, 0.0]; IF circleData.fillColor#NIL THEN Imager.DoSaveAll[dc, FillItIn]; IF seg.color#NIL AND seg.strokeWidth#0.0 THEN { Imager.SetColor[dc, seg.color]; Imager.SetStrokeWidth[dc, seg.strokeWidth]; IF seg.dashed THEN { PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; pattern: SequenceOfReal _ seg.pattern; Imager.MaskDashedStroke[dc, TransformedCirclePath, pattern.len, PatternProc, seg.offset, seg.length]; } ELSE Imager.MaskStroke[dc, TransformedCirclePath, TRUE]; }; }; CircleDrawTransform: PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { OPEN ImagerTransformation; originSelected: BOOL _ FALSE; cpCount: INT _ 0; cpIndex: INT _ -1; edgePoint, pointInUnitSpace: Point; newTransform: Transformation; slice: Slice _ sliceD.slice; circleData: CircleData _ NARROW[sliceD.slice.data]; circleParts: CircleParts _ NARROW[sliceD.parts]; IF circleParts.cpArray[0] THEN originSelected _ TRUE; FOR index: CirclePoints IN [1..MaxCirclePoints) DO IF circleParts.cpArray[index] THEN{ cpCount _ cpCount + 1; cpIndex _ index; }; ENDLOOP; IF originSelected OR cpCount > 1 OR circleParts.outline THEN { -- treat as complete circle selected CircleDrawOutline[dc, circleData, Concat[circleData.transform, transform], TRUE]; RETURN; }; IF cpCount = 0 AND NOT originSelected THEN ERROR; edgePoint _ Transform[Concat[circleData.transform, transform], SELECT cpIndex FROM 1 => [-1.0, 0.0], 2 => [0.0, 1.0], 3 => [1.0, 0.0], 4 => [0.0, -1.0], ENDCASE => ERROR]; pointInUnitSpace _ Transform[circleData.inverse, edgePoint]; newTransform _ PreScale[circleData.transform, MAX[Vectors2d.Magnitude[pointInUnitSpace], circleLimit] ]; -- circles can't shrink to zero CircleDrawOutline[dc, circleData, newTransform, FALSE]; }; circleLimit: REAL _ 0.05; -- needed to prevent numerical instability of tiny circles CircleTransform: PROC [sliceD: SliceDescriptor, transform: ImagerTransformation.Transformation] = { isOrigin: BOOL _ FALSE; cpCount: INT _ 0; cpIndex: INT _ -1; originPoint, edgePoint, pointInUnitSpace: Point; circleData: CircleData _ NARROW[sliceD.slice.data]; circleParts: CircleParts _ NARROW[sliceD.parts]; IF circleParts.cpArray[0] THEN isOrigin _ TRUE; FOR index: CirclePoints IN [1..MaxCirclePoints) DO IF circleParts.cpArray[index] THEN{ cpCount _ cpCount + 1; cpIndex _ index; }; ENDLOOP; IF isOrigin OR cpCount > 1 THEN { -- treat as though complete circle is selected circleData.transform _ ImagerTransformation.Concat[circleData.transform, transform]; circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; CircleSetBoundBox[sliceD.slice]; RETURN; }; originPoint _ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]]; IF cpCount = 0 AND NOT isOrigin THEN { edgePoint _ Vectors2d.Add[v1: Vectors2d.Sub[v1: circleData.startPoint, v2: originPoint], v2: ImagerTransformation.Factor[transform].t]; -- edgepoint relative to origin edgePoint _ Vectors2d.Add[originPoint, edgePoint]; -- make edgepoint absolute } ELSE { -- this is the normal case when a single CP is selected edgePoint _ ImagerTransformation.Transform[circleData.transform, SELECT cpIndex FROM 1 => [-1.0, 0.0], 2 => [0.0, 1.0], 3 => [1.0, 0.0], 4 => [0.0, -1.0], ENDCASE => ERROR]; edgePoint _ ImagerTransformation.Transform[transform, edgePoint]; -- move the edgepoint }; pointInUnitSpace _ ImagerTransformation.Transform[circleData.inverse, edgePoint]; circleData.transform _ ImagerTransformation.PreScale[circleData.transform, MAX[Vectors2d.Magnitude[pointInUnitSpace], circleLimit] ]; -- circles can't shrink to zero circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; CircleSetBoundBox[sliceD.slice]; }; CircleDescribeHit: PROC [slice: Slice, hitData: REF ANY] RETURNS [rope: Rope.ROPE] = { circleHitData: CircleHitData _ NARROW[hitData]; prefix: Rope.ROPE; IF circleHitData.index#-1 THEN {prefix _ IF circleHitData.index = 0 THEN "origin" ELSE "circumference point"} ELSE IF circleHitData.outline THEN prefix _ "border" ELSE ERROR; rope _ Rope.Concat[prefix, " of a Circle slice"]; }; CircleDescribe: PROC [sliceD: SliceDescriptor] RETURNS [rope: Rope.ROPE] = { prefix: Rope.ROPE; cpCount: INT _ 0; circleParts: CircleParts; IF sliceD.parts = NIL THEN RETURN["a Circle slice"]; circleParts _ NARROW[sliceD.parts]; FOR index: CirclePoints IN [0..MaxCirclePoints) DO IF circleParts.cpArray[index] THEN cpCount _ cpCount + 1; ENDLOOP; IF cpCount > 1 THEN RETURN[ "multiple parts of a Circle slice"]; IF cpCount=0 THEN prefix _ IF circleParts.outline THEN "outline" ELSE "nowhere" ELSE prefix _ IF circleParts.cpArray[0] THEN "origin" ELSE "circumference point"; rope _ Rope.Concat[prefix, " of a Circle slice"]; }; CircleFileout: PROC [slice: Slice, f: IO.STREAM] = { circleData: CircleData _ NARROW[slice.data]; GGParseOut.WriteTransformation[f, circleData.transform]; f.PutRope[" strokeWidth: "]; f.PutF["%g", [real[circleData.segment.strokeWidth]] ]; f.PutRope[" strokeColor: "]; GGParseOut.WriteColor[f, circleData.segment.color]; f.PutRope[" fillColor: "]; GGParseOut.WriteColor[f, circleData.fillColor]; f.PutRope[" dashes: ( "]; GGParseOut.WriteBOOL[f, circleData.segment.dashed]; f.PutChar[IO.SP]; IF circleData.segment.dashed THEN { GGParseOut.WriteArrayOfReal[f, circleData.segment.pattern]; f.PutF[" %g %g ", [real[circleData.segment.offset]], [real[circleData.segment.length]]]; }; f.PutRope[") "]; }; CircleFilein: PROC [f: IO.STREAM, version: REAL, feedback: FeedbackData] RETURNS [slice: Slice] = { circleData: CircleData; origin, outerPoint: Point; transform: ImagerTransformation.Transformation; strokeWidth: REAL; strokeColor, fillColor: Color; transform _ GGParseIn.ReadTransformation[f]; origin _ ImagerTransformation.Transform[m: transform, v: [0.0, 0.0] ]; outerPoint _ ImagerTransformation.Transform[m: transform, v: [0.0, 1.0] ]; GGParseIn.ReadBlankAndRope[f, "strokeWidth:"]; strokeWidth _ GGParseIn.ReadBlankAndReal[f]; GGParseIn.ReadBlankAndRope[f, "strokeColor:"]; strokeColor _ GGParseIn.ReadColor[f, version]; GGParseIn.ReadBlankAndRope[f, "fillColor:"]; fillColor _ GGParseIn.ReadColor[f, version]; slice _ MakeCircleSlice[origin, outerPoint].slice; circleData _ NARROW[slice.data]; circleData.transform _ transform; circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; circleData.segment.strokeWidth _ strokeWidth; circleData.segment.color _ strokeColor; circleData.fillColor _ fillColor; IF version>=8704.03 THEN { -- read in dash patterns GGParseIn.ReadBlankAndRope[f, "dashes: ("]; circleData.segment.dashed _ GGParseIn.ReadBOOL[f, version].truth; IF circleData.segment.dashed THEN { circleData.segment.pattern _ GGParseIn.ReadArrayOfReal[f]; circleData.segment.offset _ GGParseIn.ReadBlankAndReal[f]; circleData.segment.length _ GGParseIn.ReadBlankAndReal[f]; }; GGParseIn.ReadBlankAndRope[f, ")"]; }; CircleSetBoundBox[slice]; }; CircleIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = { circleParts: CircleParts _ NARROW[sliceD.parts]; RETURN[IsEmpty[circleParts] ]; }; CircleIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = { circleParts: CircleParts _ NARROW[sliceD.parts]; RETURN[IsComplete[circleParts] ]; }; NearestCirclePoint: PROC [slice: Slice, point: Point] RETURNS [index: [0..4] ] = { circleHitData: CircleHitData; success: BOOL; hitData: REF ANY; sliceD: SliceDescriptor _ CircleNewParts[slice, NIL, slice]; [----, ----, hitData, success] _ CircleClosestPoint[sliceD: sliceD, testPoint: point, tolerance: GGUtility.plusInfinity]; IF NOT success THEN ERROR; circleHitData _ NARROW[hitData]; index _ circleHitData.index; IF index = -1 THEN ERROR; }; CircleNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [sliceD: SliceDescriptor] = { circleHitData: CircleHitData _ NARROW[hitData]; circleParts: CircleParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE] ]; SELECT mode FROM literal => { IF circleHitData.index#-1 THEN circleParts.cpArray[circleHitData.index] _ TRUE ELSE IF circleHitData.outline THEN circleParts.outline _ TRUE; }; joint => { IF circleHitData.index#-1 THEN circleParts.cpArray[circleHitData.index] _ TRUE ELSE IF circleHitData.outline THEN { index: [-1..4] _ NearestCirclePoint[slice, circleHitData.hitPoint]; circleParts.cpArray[index] _ TRUE; }; }; controlPoint => circleParts.cpArray _ ALL[TRUE]; segment, traj, topLevel, slice => MakeComplete[circleParts]; none => { -- leave circleParts empty }; ENDCASE => ERROR; sliceD _ GGSlice.DescriptorFromParts[slice, circleParts]; }; CircleUnionParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aPlusB: SliceDescriptor] = { circlePartsA: CircleParts _ NARROW[partsA.parts]; circlePartsB: CircleParts _ NARROW[partsB.parts]; newParts: CircleParts; IF partsA.parts = NIL THEN RETURN[partsB]; IF partsB.parts = NIL THEN RETURN[partsA]; IF IsEmpty[circlePartsA] THEN RETURN[partsB]; IF IsEmpty[circlePartsB] THEN RETURN[partsA]; IF IsComplete[circlePartsA] THEN RETURN[partsA]; IF IsComplete[circlePartsB] THEN RETURN[partsB]; newParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE ] ]; FOR i: CirclePoints IN CirclePoints DO newParts.cpArray[i] _ circlePartsA.cpArray[i] OR circlePartsB.cpArray[i]; ENDLOOP; newParts.outline _ circlePartsA.outline OR circlePartsB.outline; aPlusB _ GGSlice.DescriptorFromParts[partsA.slice, newParts]; }; CircleDiffParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aMinusB: SliceDescriptor] = { circlePartsA: CircleParts _ NARROW[partsA.parts]; circlePartsB: CircleParts _ NARROW[partsB.parts]; newParts: CircleParts; IF partsA = NIL OR partsB = NIL THEN ERROR; IF partsA.parts = NIL OR partsB.parts = NIL THEN RETURN[partsA]; newParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE ] ]; FOR i: CirclePoints IN CirclePoints DO newParts.cpArray[i] _ IF circlePartsB.cpArray[i] THEN FALSE ELSE circlePartsA.cpArray[i]; ENDLOOP; newParts.outline _ IF circlePartsB.outline THEN FALSE ELSE circlePartsA.outline; aMinusB _ GGSlice.DescriptorFromParts[partsA.slice, newParts]; }; CircleMovingParts: PROC [slice: Slice, selectedParts: SliceParts] RETURNS [background, overlay, rubber, drag: SliceDescriptor] = { SomeCPMoving: PROC [circleParts: CircleParts] RETURNS [BOOL] = { FOR i: INTEGER IN [1..4] DO IF circleParts.cpArray[i] THEN { RETURN[TRUE]; }; ENDLOOP; RETURN[FALSE]; }; circleParts: CircleParts _ NARROW[selectedParts]; nullD: SliceDescriptor _ slice.nullDescriptor; newParts: CircleParts; backgroundParts: CircleParts; IF IsEmpty[circleParts] THEN { background _ overlay _ rubber _ drag _ nullD; RETURN; }; IF IsComplete[circleParts] THEN { background _ overlay _ rubber _ nullD; drag _ GGSlice.DescriptorFromParts[slice, circleParts]; RETURN; }; newParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE] ]; IF circleParts.cpArray[0] THEN { newParts.cpArray _ ALL[TRUE]; newParts.outline _ TRUE; background _ overlay _ rubber _ nullD; drag _ GGSlice.DescriptorFromParts[slice, newParts]; } ELSE IF SomeCPMoving[circleParts] THEN { overlay _ drag _ nullD; newParts.cpArray _ ALL[TRUE]; newParts.cpArray[0] _ FALSE; newParts.outline _ TRUE; rubber _ GGSlice.DescriptorFromParts[slice, newParts]; backgroundParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE] ]; backgroundParts.cpArray[0] _ TRUE; background _ GGSlice.DescriptorFromParts[slice, backgroundParts]; } ELSE { -- nothing is moving newParts.cpArray _ ALL[TRUE]; newParts.outline _ TRUE; background _ GGSlice.DescriptorFromParts[slice, newParts]; overlay _ rubber _ drag _ nullD; }; }; CircleAugmentParts: PROC [sliceD: SliceDescriptor, selectClass: SelectionClass] RETURNS [more: SliceDescriptor] = { more _ sliceD; }; CirclePointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = { parts: CircleParts _ NARROW[sliceD.parts]; pointGen _ NEW[PointGeneratorObj _ [sliceD, 0, 0, NIL] ]; FOR index: CirclePoints IN CirclePoints DO IF parts.cpArray[index] THEN pointGen.toGo _ pointGen.toGo + 1; ENDLOOP; }; CircleSegmentsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = { parts: CircleParts _ NARROW[sliceD.parts]; segGen _ NEW[SegmentGeneratorObj _ [traj: NIL, toGo: 0, index: 0, touched: 0, seq: NIL, completeSeq: FALSE, sliceD: sliceD] ]; segGen.toGo _ IF parts.outline THEN 1 ELSE 0; }; CircleWalkSegments: PROC [slice: Slice, walkProc: WalkProc] RETURNS [sliceD: SliceDescriptor] = { circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NEW[CirclePartsObj _ [cpArray: ALL[FALSE], outline: FALSE]]; circleParts.outline _ walkProc[circleData.segment, circleData.transform]; sliceD _ NEW[SliceDescriptorObj _ [slice, circleParts] ]; }; CircleNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = { sliceD: SliceDescriptor _ pointGen.sliceD; slice: Slice _ sliceD.slice; circleData: CircleData _ NARROW[slice.data]; t: ImagerTransformation.Transformation _ circleData.transform; IF pointGen=NIL OR pointGen.toGo = 0 THEN { pointAndDone.done _ TRUE; RETURN; } ELSE { parts: CircleParts _ NARROW[sliceD.parts]; index: NAT; pointAndDone.done _ FALSE; FOR index _ pointGen.index, index+1 UNTIL index>=LAST[CirclePoints] DO IF parts.cpArray[index] THEN EXIT; -- find the next included point ENDLOOP; SELECT index FROM 0 => pointAndDone.point _ ImagerTransformation.Transform[t, [0.0, 0.0]]; -- origin 1 => pointAndDone.point _ ImagerTransformation.Transform[t, [-1.0, 0.0]]; -- left 2 => pointAndDone.point _ ImagerTransformation.Transform[t, [0.0, 1.0]]; -- top 3 => pointAndDone.point _ ImagerTransformation.Transform[t, [1.0, 0.0]]; -- right 4 => pointAndDone.point _ ImagerTransformation.Transform[t, [0.0, -1.0]]; -- bottom ENDCASE => SIGNAL Feedback.Problem[msg: "Broken Invariant"]; pointGen.index _ index+1; pointGen.toGo _ pointGen.toGo - 1; }; }; CircleNextSegment: PROC [segGen: SegmentGenerator] RETURNS [seg: Segment, transform: Transformation] = { IF segGen=NIL OR segGen.toGo = 0 THEN RETURN[NIL, NIL] ELSE { circleData: CircleData _ NARROW[segGen.sliceD.slice.data]; IF segGen.toGo#1 THEN SIGNAL Feedback.Problem[msg: "Broken Invariant"]; seg _ circleData.segment; transform _ circleData.transform; segGen.toGo _ segGen.toGo-1; }; }; CircleClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { sliceBBox: BoundBox _ sliceD.slice.boundBox; toleranceBox: GGBasicTypes.BoundBoxObj _ [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates IF PointIsInBox[testPoint, toleranceBox] THEN { points: ARRAY[0..4] OF Point; -- points in Gargoyle coordinates nextDist2, bestDist2: REAL _ GGUtility.plusInfinity; tolerance2: REAL _ tolerance*tolerance; foundIndex: INTEGER _ 0; circleData: CircleData _ NARROW[sliceD.slice.data]; circleHitData: CircleHitData; circleParts: CircleParts _ NARROW[sliceD.parts]; nextPoint: Point; points _ CirclePointsFromData[circleData]; FOR index: INTEGER IN [0..MaxCirclePoints) DO IF NOT circleParts.cpArray[index] THEN LOOP; nextPoint _ points[index]; nextDist2 _ Vectors2d.DistanceSquared[testPoint, nextPoint]; IF nextDist2 < bestDist2 THEN { bestDist2 _ nextDist2; foundIndex _ index; bestPoint _ nextPoint; IF bestDist2 < tolerance2 THEN success _ TRUE; }; ENDLOOP; IF success THEN { hitData _ circleHitData _ NEW[CircleHitDataObj _ [index: foundIndex, hitPoint: bestPoint, outline: FALSE] ]; bestDist _ RealFns.SqRt[bestDist2]; }; }; }; CircleClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { sliceBBox: BoundBox _ sliceD.slice.boundBox; toleranceBox: GGBasicTypes.BoundBoxObj _ [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates IF PointIsInBox[testPoint, toleranceBox] THEN { circleHitData: CircleHitData; circleParts: CircleParts _ NARROW[sliceD.parts]; circleData: CircleData _ NARROW[sliceD.slice.data]; pointOnCircle: Point _ [0.0, 0.0]; localTestpoint: Point; dist: REAL; IF NOT circleParts.outline THEN RETURN[[0,0], 0.0, NIL, FALSE]; localTestpoint _ ImagerTransformation.Transform[circleData.inverse, testPoint]; dist _ Vectors2d.Magnitude[localTestpoint]; IF dist > 0.0 THEN pointOnCircle _ Vectors2d.Scale[localTestpoint, 1.0/dist]; bestPoint _ ImagerTransformation.Transform[circleData.transform, pointOnCircle]; bestDist _ Vectors2d.Distance[testPoint, bestPoint]; IF bestDist box.hiX OR test.y < box.loY OR test.y > box.hiY) ]; }; CirclePointsFromData: PROC [circleData: CircleData] RETURNS [points: ARRAY [0..4] OF Point] = { t: ImagerTransformation.Transformation _ circleData.transform; points[0] _ ImagerTransformation.Transform[t, [0.0, 0.0] ]; points[1] _ ImagerTransformation.Transform[t, [-1.0, 0.0] ]; points[2] _ ImagerTransformation.Transform[t, [0.0, 1.0] ]; points[3] _ ImagerTransformation.Transform[t, [1.0, 0.0] ]; points[4] _ ImagerTransformation.Transform[t, [0.0, -1.0] ]; }; CircleSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData] = { circleData: CircleData _ NARROW[slice.data]; GGSegment.SetDefaults[circleData.segment, defaults]; circleData.fillColor _ defaults.fillColor; CircleSetBoundBox[slice]; }; CircleSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] RETURNS [box: BoundBox] = { circleData: CircleData _ NARROW[slice.data]; circleData.segment.strokeWidth _ strokeWidth; CircleSetBoundBox[slice]; RETURN[slice.boundBox]; }; CircleGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = { circleData: CircleData _ NARROW[slice.data]; strokeWidth _ circleData.segment.strokeWidth; }; CircleSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Color] = { circleData: CircleData _ NARROW[slice.data]; circleData.segment.color _ color; }; CircleGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color] = { circleData: CircleData _ NARROW[slice.data]; color _ circleData.segment.color; }; CircleSetFillColor: PROC [slice: Slice, color: Color] = { circleData: CircleData _ NARROW[slice.data]; circleData.fillColor _ color; }; CircleGetFillColor: PROC [slice: Slice] RETURNS [color: Color] = { circleData: CircleData _ NARROW[slice.data]; color _ circleData.fillColor; }; CircleSetDashed: PROC [slice: Slice, parts: SliceParts, dashed: BOOL _ FALSE, pattern: SequenceOfReal _ NIL, offset: REAL _ 0.0, length: REAL _ -1.0] = { circleParts: CircleParts _ NARROW[parts]; circleData: CircleData _ NARROW[slice.data]; IF circleParts.outline THEN { seg: Segment _ circleData.segment; seg.dashed _ dashed; seg.pattern _ pattern; seg.offset _ offset; seg.length _ length; }; }; CircleGetDashed: PROC [slice: Slice, parts: SliceParts] RETURNS [dashed: BOOL _ FALSE, pattern: SequenceOfReal, offset, length: REAL] = { circleParts: CircleParts _ NARROW[parts]; circleData: CircleData _ NARROW[slice.data]; IF circleParts.outline THEN { seg: Segment _ circleData.segment; dashed _ seg.dashed; pattern _ seg.pattern; offset _ seg.offset; length _ seg.length; }; }; MakeComplete: PROC [circleParts: CircleParts] = { circleParts.cpArray _ ALL[TRUE]; circleParts.outline _ TRUE; }; IsComplete: PROC [circleParts: CircleParts] RETURNS [BOOL] = { IF circleParts = NIL THEN RETURN[FALSE]; RETURN[ circleParts.cpArray=ALL[TRUE] AND circleParts.outline]; }; AllEdges: PROC [circleParts: CircleParts] RETURNS [BOOL] = { RETURN[circleParts.outline]; }; IsEmpty: PROC [circleParts: CircleParts] RETURNS [BOOL] = { RETURN[circleParts.cpArray=ALL[FALSE] AND circleParts.outline=FALSE]; }; Init: PROC [] = { cache _ FunctionCache.Create[maxEntries: 100]; }; cache: FunctionCache.Cache; NoOpBoundBox: PUBLIC SliceBoundBoxProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpTightBox: PUBLIC SliceTightBoxProc = { box _ NIL; }; NoOpCopy: PUBLIC SliceCopyProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDrawParts: PUBLIC SliceDrawPartsProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDrawTransform: PUBLIC SliceDrawTransformProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDrawSelectionFeedback: PUBLIC SliceDrawSelectionFeedbackProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDrawAttractorFeedback: PUBLIC SliceDrawAttractorFeedbackProc = { slice: Slice _ sliceD.slice; pointGen: GGModelTypes.PointGenerator; IF dragInProgress AND selectedParts # NIL THEN {} ELSE { pointGen _ slice.class.pointsInDescriptor[sliceD]; FOR pointAndDone: GGModelTypes.PointAndDone _ slice.class.nextPoint[pointGen], slice.class.nextPoint[pointGen] UNTIL pointAndDone.done DO GGShapes.DrawCP[dc, pointAndDone.point]; ENDLOOP; }; }; NoOpTransform: PUBLIC SliceTransformProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDescribe: PUBLIC SliceDescribeProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpDescribeHit: PUBLIC SliceDescribeHitProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpFileout: PUBLIC SliceFileoutProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpFilein: PUBLIC SliceFileinProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpIsEmptyParts: PUBLIC SliceIsEmptyPartsProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; RETURN[TRUE]; }; NoOpIsCompleteParts: PUBLIC SliceIsCompletePartsProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; RETURN[FALSE]; }; NoOpNewParts: PUBLIC SliceNewPartsProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpUnionParts: PUBLIC SliceUnionPartsProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpDifferenceParts: PUBLIC SliceDifferencePartsProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpMovingParts: PUBLIC SliceMovingPartsProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpAugmentParts: PUBLIC SliceAugmentPartsProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpSetSelectedFields: PUBLIC SliceSetSelectedFieldsProc = { }; NoOpPointsInDescriptor: PUBLIC SlicePointsInDescriptorProc = { pointGen _ NIL; }; NoOpPointPairsInDescriptor: PUBLIC SlicePointPairsInDescriptorProc = { pointPairGen _ NIL; }; NoOpSegmentsInDescriptor: PUBLIC SliceSegmentsInDescriptorProc = { segGen _ NIL; }; NoOpWalkSegments: PUBLIC SliceWalkSegmentsProc = { sliceD _ slice.class.newParts[slice, NIL, none]; }; NoOpNextPoint: PUBLIC SliceNextPointProc = { pointAndDone.done _ TRUE; }; NoOpNextPointPair: PUBLIC SliceNextPointPairProc = { pointPairAndDone.done _ TRUE; }; NoOpNextSegment: PUBLIC SliceNextSegmentProc = { seg _ NIL; }; NoOpClosestPoint: PUBLIC SliceClosestPointProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpClosestJointToHitData: PUBLIC SliceClosestJointToHitDataProc = { [point, ----, ----, ----] _ sliceD.slice.class.closestPoint[sliceD, mapPoint, GGUtility.plusInfinity]; jointD _ sliceD; }; NoOpClosestPointAndTangent: PUBLIC SliceClosestPointAndTangentProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpClosestSegment: PUBLIC SliceClosestSegmentProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpLineIntersection: PUBLIC SliceLineIntersectionProc = { points _ NIL; pointCount _ 0; }; NoOpCircleIntersection: PUBLIC SliceCircleIntersectionProc = { points _ NIL; pointCount _ 0; }; NoOpHitDataAsSimpleCurve: PUBLIC SliceHitDataAsSimpleCurveProc = { simpleCurve _ NIL; }; NoOpSetDefaults: PUBLIC SliceSetDefaultsProc = { }; NoOpSetStrokeWidth: PUBLIC SliceSetStrokeWidthProc = { }; NoOpGetStrokeWidth: PUBLIC SliceGetStrokeWidthProc = { }; NoOpSetStrokeEnd: PUBLIC SliceSetStrokeEndProc = { }; NoOpGetStrokeEnd: PUBLIC SliceGetStrokeEndProc = { }; NoOpSetStrokeJoint: PUBLIC SliceSetStrokeJointProc = { }; NoOpGetStrokeJoint: PUBLIC SliceGetStrokeJointProc = { }; NoOpSetStrokeColor: PUBLIC SliceSetStrokeColorProc = { }; NoOpGetStrokeColor: PUBLIC SliceGetStrokeColorProc = { }; NoOpSetFillColor: PUBLIC SliceSetFillColorProc = { }; NoOpGetFillColor: PUBLIC SliceGetFillColorProc = { }; NoOpSetArrows: PUBLIC SliceSetArrowsProc = { }; NoOpGetArrows: PUBLIC SliceGetArrowsProc = { }; NoOpSetDashed: PUBLIC SliceSetDashedProc = { }; NoOpGetDashed: PUBLIC SliceGetDashedProc = { }; Init[]; END. `GGSliceImplC.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last edited by Bier on September 30, 1986 Contents: Implementations of NoOp procedures for slices. Circle Slice Class transform needed for general case of rotated circles cpArray[origin, left, top, right, bottom]. 5 element array. color: Imager.Color, -- don't use colors because Imager.DrawObject doesn't cache colors fillColor: Imager.Color, Fundamentals Drawing Transforming Textual Description Parts Part Generators Hit Testing Style N.B. arc segments don't represent a shape. Only for style. For convenience, we store the origin and outerPoint in the segment points. This is to make WalkProc answer NO to the question: Are You Degenerate ? circleData.fillColor _ fillColor; Class Procedures Fundamentals GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceBoundBoxProc since the circle can be an ellipse, we just can't find the radius. This added to make WalkProc answer NO to the question: Are You Degenerate ? GGModelTypes.SliceCopyProc copy _ MakeCircleSlice[[0.0, 0.0], [1.0, 1.0], circleData.segment.strokeWidth, circleData.segment.color, circleData.fillColor].slice; copy transform and inverse to the new structure (interface to makecircle requires origin and radius, which is inadequate since "circle" could actually have been transformed into an ellipse. Thus we passed a meaningless origin and radius). following code not needed since CopySegment did the work. slice.class.setStrokeColor[copy, copyD.parts, circleData.segment.color]; -- cheat slice.class.setstrokeWidth[copy, copyD.parts, circleData.segment.strokeWidth]; -- cheat Drawing GGModelTypes.SliceDrawPartsProc GGModelTypes.SliceDrawSelectionFeedbackProc strokeWidth: REAL _ 2.0*(IF circleData.strokeWidth=0.0 THEN 1.0 ELSE circleData.strokeWidth); CircleDrawOutline[dc, slice, circleData, circleData.transform, strokeWidth, IF circleData.strokeColor#NIL THEN circleData.strokeColor ELSE Imager.black]; EqualColor: PROC [c1, c2: Color] RETURNS [BOOL] ~ { epsilon: REAL = 1.0E-3; r1, g1, b1: REAL; r2, g2, b2: REAL; IF c1=NIL AND c2=NIL THEN RETURN[TRUE]; -- no color IF c1=NIL AND c2#NIL THEN RETURN[FALSE]; -- no match IF c1#NIL AND c2=NIL THEN RETURN[FALSE]; -- no match [r1, g1, b1] _ GGUtility.ExtractRGB[c1]; [r2, g2, b2] _ GGUtility.ExtractRGB[c2]; RETURN[ABS[r1-r2]˜ͺ—K˜šΟn œžœž˜Kšžœžœ#˜ΌKšžœ ž˜—˜KšžœΟc˜)K˜Kšœžœ˜Kšœžœ˜K˜—KšŸœžœžœ žœ˜;K˜K˜K™Kšœ žœžœ˜%šœžœžœ˜Kšœ4™4Kšœ W˜hK˜Kšœ/˜/Kšœ ;˜KKšœ žœ˜Kšœ !˜7Kšœ-˜-Kšœ 9˜PKšœ :˜NK˜K˜K˜—K˜KšŸœžœ˜Kšœžœ #˜NKšœ žœžœ˜'šœžœžœ˜šœ žœžœžœ '˜LKšœ;™;Kšœ žœ %˜3—K˜K˜—Kšœžœžœ˜+šœžœžœ˜!Kšœ ;˜YK˜Kšœ žœ  ˜.K˜K˜—Kšœžœžœ ˜šœ žœžœ˜K˜Kšœ žœ˜Kšœ˜Kšœžœžœ ˜1Kšœ˜Kšœžœ˜Kšœžœ˜KšœW™WKšœ™Kšœž˜ K˜K˜—K˜šŸœžœžœžœ˜EKšžœ ˜ šœžœ˜˜KšΟb ™ —Kšœ˜K˜˜Kš‘™—Kšœ˜K˜#K˜3˜1Kš‘ ™ —šœ˜Kš‘™—Kšœ˜Kšœ˜Kšœ˜šœ˜Kš‘™—K˜!K˜'K˜K˜K˜!K˜K˜!šœ)˜)Kš‘™—K˜-K˜3K˜1K˜!K˜K˜!˜Kš‘ ™ —Kšœ!˜!K˜1Kšœžœ˜Kšœ%˜%K˜)K˜+˜1Kš‘™—K˜K˜%K˜%K˜K˜K˜#K˜#K˜%K˜%K˜!K˜!K˜K˜K˜K˜K˜—Kšœ˜K˜—šŸœžœžœ$žœ˜eKšœžœ˜,Kš œžœžœžœžœ˜FKšœ ˜ KšœC˜CKšœ0˜0šœ  œ  œ ˜#Kš‘;™;—šœMžœ˜RKš‘”™”—Kšœ˜Kšœ#˜#Kšœ%˜%Kšœ(˜(Kšœ!™!šœžœ ˜K˜(Kšœ˜Kšœžœ˜ Kšœžœžœžœ˜&Kšœ,˜,Kšœ˜—Kšœ:žœ˜?Kšžœžœ9 ˜nKšœ‘˜‘KšœM˜MKšœG˜GKšœR˜RKšœžœ˜Kšœx˜xKšœ˜Kšœ žœ-˜9Kšœ4 ‚˜ΆKšœ˜K˜—K™K™Kšœ ™ •StartOfExpansion# -- [cluster: GGModelTypes.Cluster]šŸœžœ#žœ˜RKšœ™Kšžœ˜K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šŸœžœ#žœ˜RKšœ™Kšžœ˜K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šŸœžœ˜*Kšœžœ"˜.Kšœ žœžœ-˜GKšœžœžœ˜%Kšœžœ ˜,Kšœ žœ ˜KšœB™BK˜QK˜VK˜ZKšœžœžœV˜uKšœs˜sKšœO˜OKšœ0˜0šžœžœBžœ˜NKšœžœ˜KšœJ˜JKšœ˜Kšœžœ˜KšœP˜PK˜—šžœžœ˜$Kš‘K™K—K˜K˜K˜K˜—šŸ œžœžœ˜9Kšœ™Kšœžœ ˜,K˜Kšœ†™†KšœA˜AKšœ˜K™οKšœ žœ ˜K˜DK˜!K˜@K˜/K˜-Kšœ5˜5KšœN˜NK˜<šœ5˜5Kš‘ œ ‘™9—KšœI ™QKšœO ™WKšœ˜Kšœ˜K˜—K™Kšœ™šŸœžœ$žœ1žœ˜vKšœ™šŸœžœ˜Kšœ8žœ˜>K˜—Kšœžœ ˜,Kšœžœ˜)Kšžœžœžœžœ+˜[K˜K˜—šŸœžœ˜žœ˜ΒKšžœ˜Kšœ+™+šŸœžœ˜Kšœžœ˜$Kšœžœžœ˜KšœF˜FKšœE˜EKšœ#˜#šžœžœžœžœ˜5Kš œ žœžœžœžœ™]Kš œLžœžœžœžœ™™K˜—šžœžœž˜2Kšœžœžœ˜CKšœ%žœžœ"˜NKšžœ žœ1˜DKšžœžœ4˜LKš žœžœ žœžœžœ!˜QKšžœ˜—K˜—K˜/Kšœžœ ˜,Kšœ>˜>K˜Kšžœžœžœžœ˜/Kš žœžœžœ žœžœžœ˜6Kšœžœ˜*Kšœžœ ˜"Kšžœžœ&˜DK˜K˜—Kšœžœ"˜.Kšœ žœžœ˜Kšœ žœžœ 6˜MšŸœžœ5žœžœ˜yKšœžœžœ)˜8K˜FK˜KK˜OKšœžœžœV˜uKšœm˜mKšœ%˜%KšœŸœ˜*K˜K˜—šŸ œžœ5žœ˜cšŸ œžœžœžœ™3Kšœ žœ ™Kšœ žœ™Kšœ žœ™Kšžœžœžœžœžœžœžœ  ™3Kšžœžœžœžœžœžœžœ  ™4Kšžœžœžœžœžœžœžœ  ™4Kšœ(™(Kšœ(™(Kš žœžœžœžœžœžœ™JK™—š Ÿ œžœžœžœžœ˜EKšžœžœžœžœ˜$šžœžœžœž˜Kšžœžœžœžœ˜#Kšžœ˜—Kšžœžœ˜ K˜—šŸ œ˜+Kš œ žœžœžœžœ™AKšœžœ ˜ Kšœ"˜"Kšžœ#ž˜(Kšœž˜!Kšœž˜Kšœž˜Kšœž˜Kšœ9‘ œ ž˜RKšœ#ž™&Kšœ2ž™5Kšœžœžœ‘ œ˜>Kš žœžœžœžœžœžœ˜%K˜K˜—K–j[x: FunctionCache.Cache, compare: FunctionCache.CompareProc, clientID: FunctionCache.ClientID _ NIL]˜Kšœ žœ ˜Kšœžœ˜ Kšœ6‘ œ˜_Kš žœžœžœžœžœžœ˜+K˜K˜—šŸœžœgžœžœ˜K˜K˜šžœžœ žœ˜Kšœ0˜0Kšžœ˜Kšœ˜—Kšœ-˜-šžœ žœžœ ˜.Kš‘H˜HK˜"šœžœ ˜K˜0Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ™ Kšœ ™ Kšœžœ˜ —šžœ žœ˜K˜Kšœ ˜ Kšœ˜—Kšœ žœ‘œ˜TKšœ@˜@K˜—šž˜šŸ œ˜Kšœ˜Kšœ˜Kšœ!˜!Kšœ>˜>Kšœ˜—šŸœžœ˜Kšœ*˜*Kšœ˜K–P[context: Imager.Context, path: ImagerPath.PathProc, parity: BOOL _ FALSE]šœ žœ˜%K˜—Kšœžœ˜)Kš‘Πbk‘s˜‡K™:Kšžœžœžœ˜=šžœžœžœ$žœ˜MKšœ.˜.Kšœ<žœ˜VK˜—Kšžœ˜—Kšœ˜K˜—šŸœžœ4˜JK™K™GšŸ œ˜Kšœ!˜!Kšœ=˜=Kšœ˜—šŸœ˜*KšœU˜UKšœ˜—Kšœžœ ˜'Kšœ2˜2Kšœ˜Kšœ˜šžœžœ˜%KšœQ™QKšœ8˜8Kšœ5 %˜ZKšœ' %˜Lšžœžœ˜š Ÿ œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœ.˜.Kšœz˜zKšœ˜—Kšžœ3žœ˜=K˜—Kšœ˜K˜—šŸœžœb˜|K™GšŸ œ˜Kšœ!˜!Kšœ>˜>Kšœ˜—šŸœ˜*KšœU˜UKšœ˜—šŸœžœ˜Kšœ*˜*Kšœ˜K–P[context: Imager.Context, path: ImagerPath.PathProc, parity: BOOL _ FALSE]šœ žœ˜%K˜—Kšœ"˜"Kšœ˜Kšœ˜Kšžœžœžœ ˜@šžœ žœžœžœ˜/Kšœ˜Kšœ+˜+šžœ žœ˜š Ÿ œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœ&˜&Kšœe˜eKšœ˜—Kšžœ.žœ˜8K˜—Kšœ˜K˜—šŸœžœv˜Kšœ#™#K™ΐKšžœ˜Kšœžœžœ˜Kšœ žœ˜Kšœ žœ˜K˜#Kšœ˜Kšœ˜Kšœžœ˜3Kšœžœ˜0Kšžœžœžœ˜5šžœžœž˜2šžœžœ˜$Kšœ˜K˜K˜—šžœ˜Kš‘™——š žœžœ žœžœ $˜dKšœKžœ˜QKšžœ˜šœ˜Kš‘™——Kš žœ žœžœžœžœ˜1šœ?žœ ž˜RK˜K˜K˜K˜Kšžœžœ˜—Kšœ<˜KšœAžœ ž˜TK˜K˜K˜K˜Kšžœžœ˜KšœB ˜WK˜—KšœQ˜QKšœKžœ8 ˜₯KšœM˜MKšœG˜GKšœR˜RKšœ ˜ K˜—K™Kšœ™š Ÿœžœžœžœžœ žœ˜VKšœžœ ˜/Kšœ žœ˜Kš žœžœ žœžœ žœ˜mKšžœžœžœ˜4Kšžœžœ˜ Kšœ1˜1K˜K˜—šŸœžœžœ žœ˜LK™Kšœ žœ˜Kšœ žœ˜Kšœ˜Kšžœžœžœžœ˜4Kšœžœ˜#šžœžœž˜2Kšžœžœ˜9Kšžœ˜—Kšžœ žœžœ&˜@Kš žœ žœ žœžœ žœ ˜OKšžœ žœžœ žœ˜QKšœ1˜1K˜K˜—šŸ œžœžœžœ˜4Kšœ™K™.Kšœžœ ˜,Kšœ8˜8Kšœ˜Kšœ6˜6Kšœ˜Kšœ3˜3Kšœ˜Kšœ/˜/Kšœ˜Iprocšœ>žœžœ˜Ešžœžœ˜#Lšœ;˜;LšœX˜XLšœ˜—Kšœ˜K˜K™—š Ÿ œžœžœžœ žœžœ˜cKšœ™Kšœ˜Kšœ˜Kšœ/˜/Kšœ žœ˜Kšœ˜Kšœ,˜,K–4[m: ImagerTransformation.Transformation, v: VEC]šœF˜FK–4[m: ImagerTransformation.Transformation, v: VEC]šœJ˜JKšœ.˜.Kšœ,˜,Kšœ.˜.Kšœ.˜.Kšœ,˜,Kšœ,˜,KšœW™Wšœ2˜2KšœK™K—Kšœ žœ ˜ K˜!KšœM˜MKšœG˜GKšœR˜RKšœ-˜-Kšœ'˜'Kšœ!˜!šžœžœ ˜3Kšœ+˜+KšœA˜Ašžœžœ˜#Kšœ:˜:Kšœ:˜:Kšœ:˜:K˜—Kšœ#˜#K˜—Kšœ˜K˜—K™Kšœ ™ šŸœžœžœžœ˜EKšœ ™ Kšœžœ˜0Kšžœ˜K˜K˜—šŸœžœžœžœ˜HKšœ ™ Kšœžœ˜0Kšžœ˜"K˜K˜—šŸœžœžœ˜RKšœ˜Kšœ žœ˜Kšœ žœžœ˜Kšœ0žœ ˜K˜—šœ ž˜ Kšžœžœ,ž˜Nšžœžœžœ˜$KšœC˜CKšœžœ˜"K˜—K˜—Kšœ&žœžœ˜0Kšœ<˜<šœ  ˜$K˜—Kšžœžœ˜—Kšœ9˜9K˜K˜—šŸœžœ4žœ˜oKšœ ™ Kšœžœ˜1Kšœžœ˜1Kšœ˜Kšžœžœžœžœ ˜*Kšžœžœžœžœ ˜*Kšžœžœžœ ˜-Kšžœžœžœ ˜-Kšžœžœžœ ˜0Kšžœžœžœ ˜0K˜Kš œ žœžœžœžœ˜7šžœžœž˜&Kšœ.žœ˜IKšžœ˜—Kšœ(žœ˜@Kšœ=˜=Kšœ˜K˜—šŸœžœ4žœ˜oKšœ%™%Kšœžœ˜1Kšœžœ˜1Kšœ˜Kš žœ žœžœ žœžœžœ˜+Kš žœžœžœžœžœžœ ˜@Kš œ žœžœžœžœ˜7šžœžœž˜&Kš œžœžœžœžœ˜YKšžœ˜—Kš œžœžœžœžœ˜PKšœ>˜>K˜K˜—šŸœžœ+žœ9˜‚Kšœ!™!K™fšŸ œžœžœžœ˜@šžœžœžœž˜šžœžœ˜ Kšžœžœž˜ K˜—Kšžœ˜—Kšžœžœ˜K˜—Kšœžœ˜1Kšœ.˜.Kšœ˜Kšœ˜K˜šžœžœ˜Kšœ-˜-Kšžœ˜K˜—šžœžœ˜!Kšœ&˜&Kšœ7˜7Kšžœ˜K˜—K˜Kš œ žœžœžœžœ˜6šžœžœ˜ Kšœžœžœ˜Kšœžœ˜Kšœ&˜&Kšœ4˜4K˜—šžœžœžœ˜(Kšœ˜Kšœžœžœ˜Kšœžœ˜Kšœžœ˜Kšœ6˜6Kš œžœžœžœžœ˜=Kšœžœ˜"KšœA˜AK˜—šžœ ˜Kšœžœžœ˜Kšœžœ˜Kšœ:˜:Kšœ ˜ K˜—K˜K˜—šŸœžœ8žœ˜sKšœ"™"Kšœ˜K˜K˜—šŸœžœžœ˜_Kšœžœ˜*Kšœ žœ$žœ˜9šžœžœž˜*Kšžœžœ#˜?Kšžœ˜ —K˜K˜—šŸœžœžœ˜aKšœžœ˜*Kš œ žœžœ&žœžœ˜~Kšœžœžœžœ˜-Kšœ˜K˜—šŸœžœ$žœ˜aKšœ ‘™"Kšœžœ ˜,Kš œžœžœžœ žœ˜WKšœI˜IKšœ žœ-˜9K˜K˜—šŸœžœžœ.˜fKšœ*˜*Kšœ˜Kšœžœ ˜,Kšœ>˜>šžœ žœžœžœ˜+Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœžœ˜*Kšœžœ˜ Kšœžœ˜šžœ!žœžœž˜FKšžœžœžœ ˜BKšžœ˜—šžœž˜KšœI  ˜RKšœJ ˜QKšœI ˜OKšœI ˜QKšœJ  ˜SKšžœžœ+˜<—Kšœ˜Kšœ"˜"K˜—K˜K˜—šŸœžœžœ.˜hKšžœžœžœžœžœžœžœ˜6šžœ˜Kšœžœ˜:Kšžœžœžœ+˜GKšœ˜Kšœ!˜!Kšœ˜K˜—K˜K˜—šŸœžœ8žœžœžœ žœžœ žœžœ˜­K™"Kšœ žœ !™1K˜,KšœŽžœžœ ˜΅šžœ'žœ˜/Kšœžœžœ !˜?Kšœžœ˜4Kšœ Οuœžœ˜'Kšœ žœ˜Kšœžœ˜3K˜Kšœžœ˜0Kšœ˜K˜Kšœ*˜*šžœžœžœž˜-Kšžœžœžœžœ˜,Kšœ˜Kšœ£œ3˜<šžœ £œ £œžœ˜Kšœ£œ £œ˜K˜K˜Kš žœ £œ £œžœ žœ˜.K˜—Kšžœ˜—šžœ žœ˜KšœžœFžœ˜lK˜#K˜—K˜—K˜K˜—šŸœžœ8žœžœžœ žœžœ žœžœ˜―Kšœ$™$Kšœ žœ !™1K˜,KšœŽžœžœ ˜΅šžœ'žœ˜/Kšœ˜Kšœžœ˜0Kšœžœ˜3K˜"K˜Kšœž˜ K˜Kš žœžœžœžœ žœžœ˜?K˜OKšœ+˜+Kš‘™Kšžœ žœ;˜MKš‘™K˜PK˜4šžœžœ˜Kšœžœ>žœ˜cKšœ žœ˜K˜—K˜—K˜K˜—šŸœžœžœ'žœ žœžœžœ˜~Kšœžœ˜3Kšœžœ˜0Kšœ žœ ˜KšœB˜BKšœ žœžœ˜#KšœT˜TKšœ žœ˜ šžœžœžœž˜ K–4[m: ImagerTransformation.Transformation, v: VEC]šœ žœU˜bKšžœ˜—K˜K˜—šŸœžœžœžœžœžœžœ˜dKšœžœ ˜/Kšœžœ ˜,Kš žœžœžœžœžœ˜.Kš žœžœžœžœžœ˜/Kšœ&˜&K˜K˜—š Ÿ œžœžœ.žœžœ˜ZKš žœžœžœžœžœ˜]K˜K˜—š Ÿœžœžœ žœžœ ˜_Kšœ ™ Kšœ>˜>Kšœ;˜;Kšœ<˜Kš žœžœžœžœžœ˜(Kšžœžœžœžœ˜?K˜K˜—šŸœžœžœžœ˜K˜—šŸ œžœ˜*Kšœžœ˜ Kšœ˜—šŸœžœ˜"Kšžœ8˜>K˜Kš‘™—šŸ œžœ˜,Kšžœ8˜>K˜—šŸœžœ˜4Kšžœ8˜>K˜—šŸœžœ#˜DKšžœ8˜>K˜—šŸœžœ#˜DJšœ˜Jšœ&˜&Jšžœžœžœžœ˜1šžœ˜Jšœ2˜2šžœlžœž˜‰Jšœ(˜(Jšžœ˜—J˜—K˜K™Kš‘ ™ —šŸ œžœ˜,Kšžœ8˜>K˜Kš‘™—šŸ œžœ˜*Kšžœ9˜?K˜—šŸœžœ˜0Kšžœ9˜?K˜—šŸ œžœ˜(Kšžœ9˜?K˜—šŸ œžœ˜&Kšžœ9˜?K˜Kš‘™—šŸœžœ˜2Kšžœ9˜?Kšžœžœ˜ K˜—šŸœžœ˜8Kšžœ9˜?Kšžœžœ˜K˜—šŸ œžœ˜*Kšžœ9˜?K˜—šŸœžœ˜.Kšžœ9˜?K˜—šŸœžœ˜8Kšžœ9˜?K˜—šŸœžœ˜0Kšžœ9˜?K˜—šŸœžœ˜2Kšžœ9˜?K˜—šŸœžœ˜Kšœ žœ˜K˜—šŸœžœ$˜FKšœžœ˜K˜—šŸœžœ"˜BKšœ žœ˜ K˜—šŸœžœ˜2Kšœ%žœ˜0K˜—šŸ œžœ˜,Kšœžœ˜K˜—šŸœžœ˜4Kšœžœ˜K˜—šŸœžœ˜0Kšœžœ˜ K˜—šŸœžœ˜2Kšžœ9˜?K˜—šŸœžœ#˜DKšœf˜fKšœ˜K˜—šŸœžœ$˜FKšžœ9˜?K˜—šŸœžœ˜6Kšžœ9˜?K˜—šŸœžœ˜:Kšœ žœ˜K˜—šŸœžœ ˜>Kšœ žœ˜K˜—šŸœžœ"˜BKšœžœ˜K˜K™Kš‘™—šŸœžœ˜0KšžœI™OK˜—šŸœžœ˜6KšžœI™OK˜—šŸœžœ˜6KšžœI™OK˜—šŸœžœ˜2KšžœI™OK˜—šŸœžœ˜2KšžœI™OK˜—šŸœžœ˜6KšžœI™OK˜—šŸœžœ˜6KšžœI™OK˜—šŸœžœ˜6Kšžœ9™?K˜—šŸœžœ˜6Kšžœ9™?K˜—šŸœžœ˜2KšžœI™OK˜—šŸœžœ˜2KšžœI™OK˜—šŸ œžœ˜,KšžœI™OK˜—šŸ œžœ˜,KšžœI™OK˜—šŸ œžœ˜,KšžœI™OK˜—šŸ œžœ˜,KšžœI™OK˜K˜—K˜K˜Kšžœ˜K˜—…—‘ψζ©