DIRECTORY Feedback, FeedbackTypes, FunctionCache, GGBasicTypes, GGBoundBox, GGCircles, GGCoreOps, GGCoreTypes, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGMUserProfile, GGParseIn, GGParseOut, GGProps, GGSegment, GGSegmentTypes, GGShapes, GGSlice, GGSliceOps, GGUtility, Imager, ImagerColor, ImagerPath, ImagerTransformation, IO, Lines2d, NodeStyle, RealFns, Rope, Vectors2d; GGSliceImplC: CEDAR PROGRAM IMPORTS Feedback, FunctionCache, GGBoundBox, GGCircles, GGCoreOps, GGMUserProfile, GGParseIn, GGParseOut, GGProps, GGSegment, GGShapes, GGSlice, GGSliceOps, GGUtility, Imager, ImagerPath, ImagerTransformation, IO, Lines2d, RealFns, Rope, Vectors2d EXPORTS GGSlice = BEGIN BoundBox: TYPE = REF BoundBoxObj; BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj; Camera: TYPE = GGModelTypes.Camera; Circle: TYPE = GGModelTypes.Circle; Color: TYPE = Imager.Color; DefaultData: TYPE = GGModelTypes.DefaultData; EditConstraints: TYPE = GGModelTypes.EditConstraints; MsgRouter: TYPE = FeedbackTypes.MsgRouter; HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent; Line: TYPE = GGCoreTypes.Line; Object: TYPE = Imager.Object; Orientation: TYPE = GGModelTypes.Orientation; Point: TYPE = GGModelTypes.Point; PointAndDone: TYPE = GGModelTypes.PointAndDone; PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj; PointWalkProc: TYPE = GGModelTypes.PointWalkProc; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGModelTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj; SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData; SelectionClass: TYPE = GGModelTypes.SelectionClass; SelectMode: TYPE = GGModelTypes.SelectMode; SequenceOfReal: TYPE = GGCoreTypes.SequenceOfReal; Slice: TYPE = GGModelTypes.Slice; SliceBoundBoxProc: TYPE = GGModelTypes.SliceBoundBoxProc; SliceClass: TYPE = GGModelTypes.SliceClass; SliceClassObj: TYPE = GGModelTypes.SliceClassObj; SliceObj: TYPE = GGModelTypes.SliceObj; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceParts: TYPE = GGModelTypes.SliceParts; StrokeEnd: TYPE = GGModelTypes.StrokeEnd; Transformation: TYPE = GGModelTypes.Transformation; Vector: TYPE = GGModelTypes.Vector; WalkProc: TYPE = GGModelTypes.WalkProc; MoveToProc: TYPE = ImagerPath.MoveToProc; LineToProc: TYPE = ImagerPath.LineToProc; CurveToProc: TYPE = ImagerPath.CurveToProc; ConicToProc: TYPE = ImagerPath.ConicToProc; ArcToProc: TYPE = ImagerPath.ArcToProc; 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) transform: Transformation, scale: Vector, -- cached value of ImagerTransformation.Factor[transform].s evenScaling: BOOL, simpleCircle: Circle, -- for CircleHitDataAsSimpleCurve inverse: Transformation, inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s startPoint: Point, -- original point on circumference when circle was created fillColor: Color, forward: BOOL _ TRUE, -- that is, should be traversed in the normal cw order savedPointSelections: ARRAY CirclePoints OF SelectedObjectData, -- in order, origin, left, top, right, bottom seg: 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 [ localView: Transformation, strokeWidth: REAL _ 0.0, strokeEnd: StrokeEnd _ round, dashed: BOOL _ FALSE, -- dashed stroke properties pattern: SequenceOfReal, offset: REAL _ 0.0, length: REAL _ 0.0, color: Imager.Color, fillColor: Imager.Color, fillIsSampled: BOOL _ FALSE, fillLocal: Transformation ]; BuildCircleSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { OPEN GGSlice; class _ NEW[SliceClassObj _ [ type: $Circle, unlink: GGSlice.UnlinkSlice, -- circle data doesn't have links getBoundBox: CircleGetBoundBox, getTransformedBoundBox: GGSlice.GenericTransformedBoundBox, getTightBox: CircleGetTightBox, copy: CircleCopy, restore: CircleRestore, buildPath: CircleBuildPath, drawBorder: CircleDrawBorder, drawParts: CircleDrawParts, drawTransform: CircleDrawTransform, drawSelectionFeedback: CircleDrawSelectionFeedback, drawAttractorFeedback: CircleDrawAttractorFeedback, saveSelections: CircleSaveSelections, remakeSelections: CircleRemakeSelections, transform: CircleTransform, describe: CircleDescribe, describeHit: CircleDescribeHit, fileout: CircleFileout, filein: CircleFilein, isEmptyParts: CircleIsEmptyParts, isCompleteParts: CircleIsCompleteParts, newParts: CircleNewParts, unionParts: CircleUnionParts, differenceParts: CircleDiffParts, movingParts: CircleMovingParts, augmentParts: CircleAugmentParts, alterParts: CircleAlterParts, setSelectedFields: NoOpSetSelectedFields, pointsInDescriptor: CirclePointsInDescriptor, walkPointsInDescriptor: CircleWalkPointsInDescriptor, pointPairsInDescriptor: NoOpPointPairsInDescriptor, segmentsInDescriptor: CircleSegmentsInDescriptor, walkSegments: CircleWalkSegments, nextPoint: CircleNextPoint, nextPointPair: NoOpNextPointPair, nextSegment: CircleNextSegment, closestPoint: CircleClosestPoint, closestJointToHitData: CircleClosestJointToHitData, closestPointAndTangent: NoOpClosestPointAndTangent, closestSegment: CircleClosestSegment, filledPathsUnderPoint: CircleFilledPathsUnderPoint, lineIntersection: CircleLineIntersection, circleIntersection: NoOpCircleIntersection, hitDataAsSimpleCurve: CircleHitDataAsSimpleCurve, setDefaults: CircleSetDefaults, setStrokeWidth: CircleSetStrokeWidth, getStrokeWidth: CircleGetStrokeWidth, setStrokeEnd: CircleSetStrokeEnd, getStrokeEnd: CircleGetStrokeEnd, setStrokeJoint: NoOpSetStrokeJoint, getStrokeJoint: NoOpGetStrokeJoint, setStrokeColor: CircleSetStrokeColor, getStrokeColor: CircleGetStrokeColor, setFillColor: CircleSetFillColor, getFillColor: CircleGetFillColor, setArrows: NoOpSetArrows, getArrows: NoOpGetArrows, setDashed: CircleSetDashed, getDashed: CircleGetDashed, setOrientation: CircleSetOrientation, getOrientation: CircleGetOrientation ]]; }; 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.startPoint _ outerPoint; circleData.seg _ GGSegment.MakeArc[[-1.0, 0.0], [1.0, 1.0], [-1.0, 0.0], NIL]; circleData.seg.lo _ origin; circleData.seg.hi _ outerPoint; circleData.seg.strokeWidth _ 2.0; circleData.seg.color _ Imager.black; slice _ NEW[SliceObj _ [ class: GGSlice.FetchSliceClass[$Circle], data: circleData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], tightBox: GGBoundBox.NullBoundBox[], boundBox: GGBoundBox.NullBoundBox[], boxValid: FALSE ]]; 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]]; sliceD _ GGSlice.DescriptorFromParts[slice, circleParts]; }; CircleGetParams: PUBLIC PROC [slice: Slice] RETURNS [origin: Point, outerPoint: Point, transform: Transformation] = { data: CircleData _ NARROW[slice.data]; origin _ data.seg.lo; outerPoint _ data.seg.hi; transform _ data.transform; }; CircleGetBoundBoxAux: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = { CircleFindBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = { strokeWidth: REAL _ NARROW[slice.data, CircleData].seg.strokeWidth; pad: REAL _ 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]]]; tightBox _ GGBoundBox.CreateBoundBox[origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius]; boundBox _ GGBoundBox.CopyBoundBox[tightBox]; GGBoundBox.EnlargeByOffset[boundBox, pad]; CircleSetScaling[slice]; }; [tightBox, boundBox] _ CircleFindBoundBox[slice, parts]; -- do the bound box update IF parts=NIL THEN { -- set up cache for fast case next time around GGSlice.KillBoundBox[slice.parent]; -- invalidate ancestor caches slice.boundBox _ boundBox; slice.tightBox _ tightBox; slice.boxValid _ TRUE; slice.tightBoxValid _ TRUE; }; }; CircleGetBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { IF slice.boxValid AND parts=NIL THEN RETURN[slice.boundBox]; -- fast case RETURN[CircleGetBoundBoxAux[slice, parts].boundBox]; -- cache update if possible }; CircleGetTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { IF slice.boxValid AND parts=NIL THEN RETURN[slice.tightBox]; -- fast case RETURN[CircleGetBoundBoxAux[slice, parts].tightBox]; -- cache update if possible }; CircleSetScaling: PROC [slice: Slice] = { epsilon: REAL = 1.0E-6; circleData: CircleData _ NARROW[slice.data]; origin: Point _ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]]; major: Point _ ImagerTransformation.Transform[circleData.transform, [1.0, 0.0]]; IF ABS[circleData.inverseScale.x - circleData.inverseScale.y] < epsilon THEN { GGCircles.FillCircleFromPointAndRadius[origin, circleData.scale.x, circleData.simpleCircle]; circleData.evenScaling _ TRUE; } ELSE circleData.evenScaling _ FALSE; circleData.seg.lo _ origin; circleData.seg.hi _ major; }; CircleCopy: PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [copy: LIST OF Slice] = { copySlice: Slice; circleData: CircleData _ NARROW[slice.data]; newData: CircleData; copyD: SliceDescriptor _ MakeCircleSlice[[0.0, 0.0], [1.0, 1.0]]; copySlice _ copyD.slice; newData _ NARROW[copySlice.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.seg _ GGSegment.CopySegment[circleData.seg]; newData.forward _ circleData.forward; FOR index: INTEGER IN [0..maxCirclePoints) DO newData.savedPointSelections[index] _ circleData.savedPointSelections[index]; ENDLOOP; GGSliceOps.SetFillColor[copySlice, NIL, GGCoreOps.CopyColor[circleData.fillColor], $Set, NIL]; GGProps.CopyAll[fromSlice: slice, toSlice: copySlice]; RETURN[LIST[copySlice]]; }; CircleRestore: PROC [from: Slice, to: Slice] = { IF to=NIL OR from=NIL THEN ERROR; IF to.class#from.class THEN ERROR; IF to.class.type#$Circle THEN ERROR; BEGIN fromData: CircleData _ NARROW[from.data]; toData: CircleData _ NARROW[to.data]; IF GGSlice.copyRestore THEN { toData.circle _ fromData.circle; toData.transform _ fromData.transform; toData.scale _ fromData.scale; toData.evenScaling _ fromData.evenScaling; toData.simpleCircle _ fromData.simpleCircle; toData.inverse _ fromData.inverse; toData.inverseScale _ fromData.inverseScale; toData.startPoint _ fromData.startPoint; toData.fillColor _ fromData.fillColor; toData.forward _ fromData.forward; toData.savedPointSelections _ fromData.savedPointSelections; toData.seg _ fromData.seg; } ELSE to.data _ from.data; to.selectedInFull _ from.selectedInFull; -- RECORD of BOOL to.normalSelectedParts _ NIL; -- caller must reselect to.hotSelectedParts _ NIL; -- caller must reselect to.activeSelectedParts _ NIL; -- caller must reselect to.matchSelectedParts _ NIL; -- caller must reselect to.tightBox^ _ from.tightBox^; to.tightBoxValid _ from.tightBoxValid; to.boundBox^ _ from.boundBox^; to.boxValid _ from.boxValid; to.onOverlay _ from.onOverlay; to.extraPoints _ from.extraPoints; to.priority _ from.priority; to.historyTop _ from.historyTop; END; }; CircleBuildPath: PROC [slice: Slice, transformParts: SliceParts, transform: Transformation, moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc, editConstraints: EditConstraints _ none] = { cpCount: INT _ 0; cpIndex: INT _ -1; edgePoint, pointInUnitSpace: Point; circleData: CircleData _ NARROW[slice.data]; circleTransformParts: CircleParts _ NARROW[transformParts]; newTransform: Transformation; noRubberband: BOOL _ circleTransformParts=NIL OR circleTransformParts.outline OR circleTransformParts.cpArray[0] OR IsEmpty[circleTransformParts]; -- means transform uniformly newTransform _ SELECT TRUE FROM transform=NIL => circleData.transform, -- don't move it IsEmpty[circleTransformParts] => circleData.transform, -- no parts to move ENDCASE => ImagerTransformation.Concat[circleData.transform, transform]; IF noRubberband THEN { ImagerPath.Transform[IF circleData.forward THEN CirclePath ELSE CirclePathReverse, newTransform, moveTo, lineTo, curveTo, conicTo, arcTo]; -- transform uniformly RETURN; }; FOR index: CirclePoints IN [1..maxCirclePoints) DO -- count circumference point IF circleTransformParts.cpArray[index] THEN { cpCount _ cpCount + 1; cpIndex _ index; }; ENDLOOP; IF cpCount > 1 THEN { ImagerPath.Transform[IF circleData.forward THEN CirclePath ELSE CirclePathReverse, newTransform, moveTo, lineTo, curveTo, conicTo, arcTo]; -- transform uniformly RETURN; }; edgePoint _ ImagerTransformation.Transform[newTransform, 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 _ ImagerTransformation.Transform[circleData.inverse, edgePoint]; newTransform _ ImagerTransformation.PreScale[circleData.transform, MAX[Vectors2d.Magnitude[pointInUnitSpace], circleLimit] ]; -- circles can't shrink to zero ImagerPath.Transform[IF circleData.forward THEN CirclePath ELSE CirclePathReverse, newTransform, moveTo, lineTo, curveTo, conicTo, arcTo]; }; CircleDrawBorder: PROC [slice: Slice, drawParts: SliceParts, transformParts: SliceParts, transform: Transformation, dc: Imager.Context, camera: GGModelTypes.Camera, quick: BOOL, editConstraints: EditConstraints _ none] = { OPEN ImagerTransformation; -- see calls to Concat, Transform, and PreScale DoCircleDrawBorder: PROC = { cpCount: INT _ 0; cpIndex: INT _ -1; edgePoint, pointInUnitSpace: Point; circleData: CircleData _ NARROW[slice.data]; circleDrawParts: CircleParts _ NARROW[drawParts]; circleTransformParts: CircleParts _ NARROW[transformParts]; newTransform: Transformation; noRubberband: BOOL _ circleTransformParts=NIL OR circleTransformParts.outline OR circleTransformParts.cpArray[0] OR IsEmpty[circleTransformParts]; newTransform _ SELECT TRUE FROM transform=NIL => circleData.transform, -- don't move it IsEmpty[circleTransformParts] => circleData.transform, -- no parts to move ENDCASE => Concat[circleData.transform, transform]; BEGIN IF circleDrawParts#NIL AND IsEmpty[circleDrawParts] THEN RETURN; -- draw no parts IF noRubberband THEN GOTO NoRubberBand; FOR index: CirclePoints IN [1..maxCirclePoints) DO -- count circumference point IF circleTransformParts.cpArray[index] THEN { cpCount _ cpCount + 1; cpIndex _ index; }; ENDLOOP; IF cpCount > 1 THEN GOTO NoRubberBand; edgePoint _ Transform[newTransform, 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]]; -- can't shrink to zero CachedCircleDraw[dc, circleData, newTransform, NIL, FALSE, FALSE]; EXITS NoRubberBand => { IF transform#NIL THEN Imager.ConcatT[dc, transform]; CachedCircleDraw[dc, circleData, circleData.transform, NIL, FALSE, FALSE]; }; END; }; Imager.DoSave[dc, DoCircleDrawBorder]; }; CircleDrawParts: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: Camera, quick: BOOL] = { DoCircleDrawOutline: PROC = { CachedCircleDraw[dc, circleData, circleData.transform, NIL, FALSE]; }; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; IF circleParts = NIL OR circleParts.outline THEN Imager.DoSave[dc, DoCircleDrawOutline]; }; CircleDrawTransform: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: Camera, transform: Transformation, editConstraints: EditConstraints] = { OPEN ImagerTransformation; DoCircleDrawTransform: PROC = { cpCount: INT _ 0; cpIndex: INT _ -1; edgePoint, pointInUnitSpace: Point; newTransform: Transformation; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; wholeThing: BOOL _ IsEmpty[circleParts]; -- NIL or really empty parts transformColor: BOOL _ TRUE; IF wholeThing OR circleParts.outline THEN transformColor _ TRUE -- move circle & color ELSE IF circleParts.cpArray[0] THEN transformColor _ FALSE -- move circle but not color ELSE { FOR index: CirclePoints IN [1..maxCirclePoints) DO -- find cp count and index of last cp IF circleParts.cpArray[index] THEN{ cpCount _ cpCount + 1; cpIndex _ index; }; ENDLOOP; IF cpCount > 1 THEN transformColor _ FALSE ELSE { IF cpCount = 0 AND NOT circleParts.cpArray[0] 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]]; -- can't shrink to zero CachedCircleDraw[dc, circleData, newTransform, NIL, FALSE]; RETURN; }; }; CachedCircleDraw[dc, circleData, circleData.transform, transform, transformColor]; }; Imager.DoSave[dc, DoCircleDrawTransform]; }; CircleDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: Camera, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { jointSize: REAL = GGModelTypes.jointSize; halfJointSize: REAL = GGModelTypes.halfJointSize; hotJointSize: REAL = GGModelTypes.hotJointSize; halfHotJointSize: REAL = GGModelTypes.halfHotJointSize; slowNormal, slowHot, completeNormal, completeHot: BOOL _ FALSE; firstJoint: Point; normalCircleParts, hotCircleParts: CircleParts; circleData: CircleData _ NARROW[slice.data]; transform: Transformation _ circleData.transform; IF caretIsMoving OR dragInProgress OR camera.quality=quality THEN RETURN; IF selectedParts=NIL AND hotParts=NIL THEN RETURN; normalCircleParts _ NARROW[selectedParts]; hotCircleParts _ NARROW[hotParts]; completeNormal _ normalCircleParts#NIL AND IsComplete[normalCircleParts]; completeHot _ hotCircleParts#NIL AND IsComplete[hotCircleParts]; slowNormal _ normalCircleParts#NIL AND (NOT quick OR (quick AND NOT completeNormal)); slowHot _ hotCircleParts#NIL AND (NOT quick OR (quick AND NOT completeHot)); IF slowNormal AND slowHot THEN DrawSelectionFeedbackAux[slice, circleData, normalCircleParts, hotCircleParts, dc, transform, camera] ELSE IF slowNormal THEN DrawSelectionFeedbackAux[slice, circleData, normalCircleParts, NIL, dc, transform, camera] ELSE IF slowHot THEN DrawSelectionFeedbackAux[slice, circleData, NIL, hotCircleParts, dc, transform, camera]; IF (NOT slowNormal AND completeNormal) OR (NOT slowHot AND completeHot) THEN { fullParts: CircleParts _ IF completeNormal THEN normalCircleParts ELSE hotCircleParts; [] _ GGSliceOps.GetTightBox[slice]; -- force update of internal data firstJoint _ ImagerTransformation.Transform[transform, [-1.0, 0.0]]; -- not the center point }; IF NOT slowHot AND completeHot THEN GGShapes.DrawQuickSelectedJoint[dc, firstJoint, hot, camera.cpScale]; IF NOT slowNormal AND completeNormal THEN GGShapes.DrawQuickSelectedJoint[dc, firstJoint, normal, camera.cpScale]; }; CircleDrawAttractorFeedback: PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress: BOOL, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints] = { OPEN ImagerTransformation; IF NOT (dragInProgress AND selectedParts#NIL) THEN { DoDrawFeedback: PROC = { t: Transformation _ circleData.transform; 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 hitParts.cpArray[0] THEN GGShapes.DrawCP[dc, pts[0], camera.cpScale]; FOR index: CirclePoints IN [1..maxCirclePoints) DO GGShapes.DrawCP[dc, pts[index], camera.cpScale]; ENDLOOP; }; circleData: CircleData _ NARROW[slice.data]; hitParts: CircleParts _ NARROW[attractorParts]; IF camera.quality#quality AND hitParts#NIL THEN Imager.DoSave[dc, DoDrawFeedback]; }; }; CircleBoxFromTransform: PROC [circleData: CircleData, transform: Transformation] RETURNS [r: Imager.Rectangle] = { pad: REAL _ circleData.seg.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]; }; FindImagerObject: PROC [circleData: CircleData, localView, fillLocal: Transformation, fillIt: BOOL] RETURNS [object: Object] = { EqualPattern: PROC [p1, p2: SequenceOfReal] RETURNS [BOOL _ FALSE] = { 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.seg; fillIsSampled: BOOL; thisFill: Imager.Color _ IF fillIt THEN circleData.fillColor ELSE NIL; 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.localView, localView, maxPixels] AND GGCoreOps.EquivalentColors[props.color, seg.color] AND GGCoreOps.EquivalentColors[props.fillColor, thisFill] AND props.fillIsSampled = (fillIsSampled _ IsSampled[thisFill]) AND (NOT fillIsSampled OR ImagerTransformation.CloseEnough[props.fillLocal, fillLocal, 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]; }; CreateProps: PROC [circleData: CircleData, localView, fillLocal: ImagerTransformation.Transformation, fillIt: BOOL _ TRUE] RETURNS [props: Props] = { seg: Segment _ circleData.seg; fillIsSampled: BOOL _ IsSampled[circleData.fillColor]; props _ NEW[PropsRec _ [ localView: ImagerTransformation.Copy[localView], strokeWidth: seg.strokeWidth, strokeEnd: seg.strokeEnd, dashed: seg.dashed, pattern: seg.pattern, offset: seg.offset, length: seg.length, color: seg.color, fillColor: IF fillIt THEN GGCoreOps.CopyColor[circleData.fillColor] ELSE NIL, fillIsSampled: fillIsSampled, fillLocal: fillLocal]]; }; IsSampled: PROC [color: Imager.Color] RETURNS [BOOL] = { IF color = NIL THEN RETURN [FALSE]; WITH color SELECT FROM sampledBlack: ImagerColor.SampledBlack => RETURN[TRUE]; sampledColor: ImagerColor.SampledColor => RETURN[TRUE]; ENDCASE => RETURN[FALSE]; }; CachedCircleDraw: PROC [dc: Imager.Context, circleData: CircleData, localView, viewView: Transformation, transformColor: BOOL, fillIt: BOOL _ TRUE] = { OPEN ImagerTransformation; -- see Transform and InverseTransform object: Imager.Object; position: Imager.VEC; circleProps: Props; sampledColorStays: BOOL _ IsSampled[circleData.fillColor] AND NOT transformColor; IF GGMUserProfile.GetTurboOn[] AND (NOT sampledColorStays OR viewView = NIL) THEN { fillLocal: Transformation; fillLocal _ FromFillToLocal[circleData.fillColor, localView]; object _ FindImagerObject[circleData, localView, fillLocal, fillIt]; IF object = NIL THEN { -- put circle in cache clipR: Imager.Rectangle _ CircleBoxFromTransform[circleData, localView]; props: Props; props _ CreateProps[circleData, localView, fillLocal, fillIt]; -- props of this Imager.Object object _ NEW[Imager.ObjectRep _ [draw: CircleDrawObject, clip: clipR, data: props]]; FunctionCache.Insert[cache, props, object, 60, $GGCircleObject]; }; circleProps _ NARROW[object.data]; position _ Transform[localView, InverseTransform[circleProps.localView, [0, 0]]]; IF viewView # NIL THEN Imager.ConcatT[dc, viewView]; Imager.DrawObject[context: dc, object: object, interactive: TRUE, position: position]; } ELSE OldCircleDrawOutline[dc, circleData, localView, viewView, transformColor, fillIt]; }; FromFillToLocal: PROC [color: Imager.Color, localView: Transformation] RETURNS [fillLocal: Transformation] = { OPEN ImagerTransformation; fillView: Transformation; WITH color SELECT FROM sampledBlack: ImagerColor.SampledBlack => { fillView _ sampledBlack.um; fillLocal _ Concat[fillView, Invert[localView]]; }; sampledColor: ImagerColor.SampledColor => { fillView _ sampledColor.um; fillLocal _ Concat[fillView, Invert[localView]]; }; ENDCASE => fillLocal _ NIL; }; CircleDrawObject: PROC [self: Imager.Object, context: Imager.Context] = { OPEN ImagerTransformation; FillItIn: PROC = { GGCoreOps.SetColor[context, circleProps.fillColor]; Imager.ConcatT[context, localView]; Imager.MaskFill[context, CirclePath, TRUE]; }; TransformedCirclePath: Imager.PathProc = { ImagerPath.Transform[CirclePath, localView, moveTo, lineTo, curveTo, conicTo, arcTo]; }; circleProps: Props _ NARROW[self.data]; localView: Transformation _ circleProps.localView; IF circleProps.fillColor # NIL THEN Imager.DoSave[context, FillItIn]; IF circleProps.color # NIL AND circleProps.strokeWidth#0.0 THEN { Imager.SetColor[context, circleProps.color]; 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, localView, viewView: ImagerTransformation.Transformation, transformColor: BOOL, fillIt: BOOL _ FALSE] = { TransformedCirclePath: Imager.PathProc = { ImagerPath.Transform[CirclePath, localView, moveTo, lineTo, curveTo, conicTo, arcTo]; }; FillItIn: PROC = { IF transformColor THEN GGCoreOps.SetColor[dc, circleData.fillColor, viewView] ELSE GGCoreOps.SetColor[dc, circleData.fillColor]; IF viewView # NIL THEN Imager.ConcatT[dc, viewView]; Imager.ConcatT[dc, localView]; Imager.MaskFill[dc, IF circleData.forward THEN CirclePath ELSE CirclePathReverse, TRUE] }; seg: Segment _ circleData.seg; IF fillIt AND circleData.fillColor#NIL THEN Imager.DoSave[dc, FillItIn]; IF seg.color#NIL AND seg.strokeWidth#0.0 THEN { IF viewView # NIL THEN Imager.ConcatT[dc, viewView]; Imager.SetColor[dc, seg.color]; Imager.SetStrokeWidth[dc, seg.strokeWidth]; Imager.SetStrokeEnd[dc, seg.strokeEnd]; Imager.SetStrokeJoint[dc, round]; 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]; }; }; CirclePath: Imager.PathProc = { -- this should come out cw left: Point _ [-1.0, 0.0]; top: Point _ [0.0, 1.0]; right: Point _ [1.0, 0.0]; bottom: Point _ [0.0, -1.0]; moveTo[left]; arcTo[top, right]; arcTo[bottom, left]; }; CirclePathReverse: Imager.PathProc = { -- this comes out ccw courtesy of the Imager leftSide: Point _ [-1.0, 0.0]; rightSide: Point _ [1.0, 0.0]; moveTo[leftSide]; arcTo[rightSide, leftSide]; }; DrawSelectionFeedbackAux: PROC [slice: Slice, circleData: CircleData, normalCircleParts: CircleParts, hotCircleParts: CircleParts, dc: Imager.Context, t: Transformation, camera: Camera] = { OPEN ImagerTransformation; DoDrawFeedback: PROC = { thisCPisHot, thisCPisSelected: BOOL _ FALSE; 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]]; 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, camera.cpScale]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[index], normal, camera.cpScale]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[index], camera.cpScale]; ENDLOOP; }; Imager.DoSave[dc, DoDrawFeedback]; }; CircleSaveSelections: PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] = { SetPointField: PROC [point: INTEGER, selected: BOOL, selectClass: SelectionClass] = { SELECT selectClass FROM normal => circleData.savedPointSelections[point].normal _ selected; hot => circleData.savedPointSelections[point].hot _ selected; active => circleData.savedPointSelections[point].active _ selected; match => circleData.savedPointSelections[point].match _ selected; ENDCASE; }; SetSegmentField: PROC [seg: Segment, selected: BOOL, selectClass: SelectionClass] = { SELECT selectClass FROM normal => seg.TselectedInFull.normal _ selected; hot => seg.TselectedInFull.hot _ selected; active => seg.TselectedInFull.active _ selected; match => seg.TselectedInFull.match _ selected; ENDCASE; }; dontClear: BOOL _ TRUE; circleParts: CircleParts _ NARROW[parts]; circleData: CircleData _ NARROW[slice.data]; IF circleParts=NIL THEN dontClear _ FALSE; FOR count: INTEGER IN [0..maxCirclePoints) DO SetPointField[count, dontClear AND circleParts.cpArray[count], selectClass]; ENDLOOP; SetSegmentField[circleData.seg, dontClear AND circleParts.outline, selectClass]; }; CircleRemakeSelections: PROC [slice: Slice, selectClass: SelectionClass] RETURNS [parts: SliceParts] = { GetPointField: PROC [point: INTEGER, selectClass: SelectionClass] RETURNS [BOOL] = { RETURN[SELECT selectClass FROM normal => circleData.savedPointSelections[point].normal, hot => circleData.savedPointSelections[point].hot, active => circleData.savedPointSelections[point].active, match => circleData.savedPointSelections[point].match, ENDCASE => FALSE]; }; GetSegmentField: PROC [seg: Segment, selectClass: SelectionClass] RETURNS [BOOL] = { RETURN[SELECT selectClass FROM normal => seg.TselectedInFull.normal, hot => seg.TselectedInFull.hot, active => seg.TselectedInFull.active, match => seg.TselectedInFull.match, ENDCASE => FALSE]; }; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NEW[CirclePartsObj]; FOR count: INTEGER IN [0..maxCirclePoints) DO circleParts.cpArray[count] _ GetPointField[count, selectClass]; ENDLOOP; circleParts.outline _ GetSegmentField[circleData.seg, selectClass]; parts _ IF IsEmpty[circleParts] THEN NIL ELSE circleParts; -- KAP. September 9, 1991 }; circleLimit: REAL _ 0.01; -- circleLimit made smaller. KAP. April 12, 1988 CircleTransform: PROC [slice: Slice, parts: SliceParts _ NIL, transform: Transformation, editConstraints: EditConstraints, history: HistoryEvent] = { isOrigin: BOOL _ FALSE; cpCount: INT _ 0; cpIndex: INT _ -1; originPoint, edgePoint, pointInUnitSpace: Point; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; BEGIN IF circleParts=NIL OR circleParts.cpArray[0] THEN isOrigin _ TRUE; FOR index: CirclePoints IN [1..maxCirclePoints) DO IF circleParts=NIL OR circleParts.cpArray[index] THEN{ cpCount _ cpCount + 1; cpIndex _ index; }; ENDLOOP; IF circleParts=NIL OR circleParts.outline THEN {-- all selected. Transform object and fill color circleData.transform _ ImagerTransformation.Concat[circleData.transform, transform]; circleData.fillColor _ GGCoreOps.TransformColor[circleData.fillColor, transform]; GOTO FinishTransform; }; IF isOrigin OR cpCount > 1 THEN { -- most is selected. Transform object, but no fill color circleData.transform _ ImagerTransformation.Concat[circleData.transform, transform]; GOTO FinishTransform; }; 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 GOTO FinishTransform; EXITS FinishTransform => { circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; CircleSetScaling[slice]; GGSlice.KillBoundBox[slice]; }; END; }; 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.seg.strokeWidth]] ]; f.PutRope[" strokeColor: "]; GGParseOut.WriteColor[f, circleData.seg.color]; f.PutRope[" fillColor: "]; GGParseOut.WriteColor[f, circleData.fillColor]; f.PutRope[" dashes: ( "]; GGParseOut.WriteBool[f, circleData.seg.dashed]; f.PutChar[IO.SP]; IF circleData.seg.dashed THEN { GGParseOut.WriteArrayOfReal[f, circleData.seg.pattern]; f.PutF[" %g %g ", [real[circleData.seg.offset]], [real[circleData.seg.length]]]; }; f.PutRope[") "]; f.PutRope[" props: ( "]; GGParseOut.WriteBool[f, circleData.seg.props#NIL]; IF circleData.seg.props#NIL THEN GGParseOut.WriteProps[f, circleData.seg.props] ELSE f.PutChar[IO.SP]; -- list of ROPE f.PutRope[") fwd: "]; GGParseOut.WriteBool[f, circleData.forward]; }; CircleFilein: PROC [f: IO.STREAM, version: REAL, router: MsgRouter, camera: Camera] RETURNS [slice: Slice] = { fwd, good: BOOL _ TRUE; circleData: CircleData; origin, outerPoint: Point; transform: 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.ReadWRope[f, "strokeWidth:"]; strokeWidth _ GGParseIn.ReadWReal[f]; GGParseIn.ReadWRope[f, "strokeColor:"]; strokeColor _ GGParseIn.ReadColor[f, version]; GGParseIn.ReadWRope[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.seg.strokeWidth _ strokeWidth; circleData.seg.color _ strokeColor; circleData.fillColor _ fillColor; IF version>=8704.03 THEN { -- read in dash patterns GGParseIn.ReadWRope[f, "dashes: ("]; circleData.seg.dashed _ GGParseIn.ReadBool[f, version].truth; IF circleData.seg.dashed THEN { circleData.seg.pattern _ GGParseIn.ReadArrayOfReal[f]; circleData.seg.offset _ GGParseIn.ReadWReal[f]; circleData.seg.length _ GGParseIn.ReadWReal[f]; }; GGParseIn.ReadWRope[f, ")"]; }; IF version>=8706.08 THEN { -- read in segment props hasProps: BOOL _ FALSE; props: LIST OF Rope.ROPE _ NIL; GGParseIn.ReadWRope[f, "props: ( "]; hasProps _ GGParseIn.ReadBool[f, version].truth; props _ IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL; GGParseIn.ReadWRope[f, ")"]; IF props#NIL THEN { FOR next: LIST OF Rope.ROPE _ props, next.rest UNTIL next=NIL DO circleData.seg.props _ CONS[next.first, circleData.seg.props]; ENDLOOP; }; IF version>=8802.04 THEN { -- read in forward bit GGParseIn.ReadWRope[f, "fwd:"]; [fwd, good] _ GGParseIn.ReadBool[f, version]; }; }; CircleSetScaling[slice]; circleData.forward _ fwd; }; CircleIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL _ FALSE] = { circleParts: CircleParts _ NARROW[sliceD.parts]; RETURN[IsEmpty[circleParts] ]; }; CircleIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL _ FALSE] = { 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, editConstraints: EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord] RETURNS [background, overlay, rubber, drag: SliceDescriptor] = { SomeCPMoving: PROC [circleParts: CircleParts] RETURNS [BOOL _ FALSE] = { 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; }; CircleAlterParts: PROC [sliceD: SliceDescriptor, action: ATOM] RETURNS [newD: SliceDescriptor] = { circleParts: CircleParts _ NARROW[sliceD.parts]; newParts: CircleParts; selectedCorner: INT _ -1; newParts _ NEW[CirclePartsObj _ [cpArray: ALL[FALSE], outline: FALSE]]; FOR i: INTEGER IN [0..maxCirclePoints) DO IF circleParts.cpArray[i] THEN { selectedCorner _ i; EXIT; }; ENDLOOP; SELECT action FROM $Forward => { IF selectedCorner > -1 THEN { selectedCorner _ (selectedCorner+1) MOD maxCirclePoints; newParts.cpArray[selectedCorner] _ TRUE; } ELSE ERROR; }; $Backward => { IF selectedCorner > -1 THEN { selectedCorner _ (selectedCorner+maxCirclePoints-1) MOD maxCirclePoints; newParts.cpArray[selectedCorner] _ TRUE; } ELSE ERROR; }; $Grow => RETURN[GGSliceOps.NewParts[sliceD.slice, NIL, slice]]; $ShrinkForward, $ShrinkBackward => RETURN[NIL]; -- no children to shrink to ENDCASE => ERROR; newD _ GGSlice.DescriptorFromParts[sliceD.slice, newParts]; }; 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; }; CircleWalkPointsInDescriptor: PROC [sliceD: SliceDescriptor, walkProc: PointWalkProc] = { parts: CircleParts _ NARROW[sliceD.parts]; circleData: CircleData _ NARROW[sliceD.slice.data]; done: BOOL _ FALSE; FOR index: CirclePoints IN CirclePoints DO IF parts.cpArray[index] THEN done _ walkProc[GetCirclePoint[circleData, index]]; IF done THEN RETURN; 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.seg, circleData.transform]; IF circleParts.outline THEN circleParts.cpArray _ ALL[TRUE]; -- select the whole thing sliceD _ GGSlice.DescriptorFromParts[slice, circleParts]; }; CircleNextPoint: PROC [slice: Slice, pointGen: PointGenerator] RETURNS [pointAndDone: PointAndDone] = { sliceD: SliceDescriptor _ pointGen.sliceD; circleData: CircleData _ NARROW[sliceD.slice.data]; 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; pointAndDone.point _ GetCirclePoint[circleData, index]; pointGen.index _ index+1; pointGen.toGo _ pointGen.toGo - 1; }; }; GetCirclePoint: PROC [circleData: CircleData, index: NAT] RETURNS [point: Point] = { OPEN ImagerTransformation; t: Transformation _ circleData.transform; SELECT index FROM 0 => point _ Transform[t, [0.0, 0.0]]; -- origin 1 => point _ Transform[t, [-1.0, 0.0]]; -- left 2 => point _ Transform[t, [0.0, 1.0]]; -- top 3 => point _ Transform[t, [1.0, 0.0]]; -- right 4 => point _ Transform[t, [0.0, -1.0]]; -- bottom ENDCASE => SIGNAL Problem[msg: "Broken Invariant"]; }; CircleNextSegment: PROC [slice: Slice, 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 Problem[msg: "Broken Invariant"]; seg _ circleData.seg; transform _ circleData.transform; segGen.toGo _ segGen.toGo-1; }; }; CircleClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point _ [0.0, 0.0], bestDist: REAL _ 0.0, bestNormal: Vector _ [0, -1], hitData: REF ANY, success: BOOL _ FALSE] = { IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[sliceD.slice], tolerance] THEN RETURN; BEGIN 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]; IF foundIndex > 0 THEN { IF Vectors2d.Distance[points[0], testPoint] > circleData.scale.x THEN { bestNormal _ Vectors2d.Sub[points[foundIndex], points[0]] } ELSE {bestNormal _ Vectors2d.Sub[points[0], points[foundIndex]]}; }; }; END; }; CircleClosestJointToHitData: PROC [sliceD: SliceDescriptor, mapPoint, testPoint: Point, hitData: REF ANY] RETURNS [jointD: SliceDescriptor, point: Point, normal: Vector _ [0,-1]] = { success: BOOL _ FALSE; newHitData: REF ANY; circleHitData: CircleHitData; circleParts: CircleParts _ NEW[CirclePartsObj _ [cpArray: ALL[FALSE], outline: FALSE]]; [point, ----, normal, newHitData, success] _ CircleClosestPoint[sliceD, testPoint, GGUtility.plusInfinity]; circleHitData _ NARROW[newHitData]; circleParts.cpArray[circleHitData.index] _ TRUE; circleParts.outline _ circleHitData.outline; jointD _ GGSlice.DescriptorFromParts[slice: sliceD.slice, parts: circleParts]; }; CircleClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point _ [0.0, 0.0], bestDist: REAL _ 0.0, bestNormal: Vector _ [0,-1], hitData: REF ANY, success: BOOL _ FALSE] = { IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[sliceD.slice], tolerance] THEN RETURN; BEGIN 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, [0,-1], 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 circleData.seg.color _ color; $ChangeHue => { newColor: Color _ GGUtility.ChangeHue[circleData.seg.color, color]; circleData.seg.color _ newColor; }; ENDCASE => ERROR; }; CircleGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color, isUnique: BOOL _ TRUE] = { circleData: CircleData _ NARROW[slice.data]; color _ circleData.seg.color; }; CircleSetFillColor: PROC [slice: Slice, parts: SliceParts, color: Color, setHow: ATOM, history: HistoryEvent] = { circleData: CircleData _ NARROW[slice.data]; SELECT setHow FROM $Set => circleData.fillColor _ color; $ChangeHue => { newColor: Color _ GGUtility.ChangeHue[circleData.fillColor, color]; circleData.fillColor _ newColor; }; ENDCASE => ERROR; }; CircleGetFillColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color, isUnique: BOOL _ TRUE] = { 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, history: HistoryEvent] = { circleParts: CircleParts _ NARROW[parts]; circleData: CircleData _ NARROW[slice.data]; IF circleParts=NIL OR circleParts.outline THEN { seg: Segment _ circleData.seg; 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, isUnique: BOOL _ TRUE] = { circleParts: CircleParts _ NARROW[parts]; circleData: CircleData _ NARROW[slice.data]; IF circleParts=NIL OR circleParts.outline THEN { seg: Segment _ circleData.seg; dashed _ seg.dashed; pattern _ seg.pattern; offset _ seg.offset; length _ seg.length; }; }; CircleSetOrientation: PROC [slice: Slice, parts: SliceParts, orientation: Orientation, history: HistoryEvent] RETURNS [success: BOOL _ TRUE] = { circleData: CircleData _ NARROW[slice.data]; SELECT orientation FROM cw => circleData.forward _ TRUE; -- forward => cw ccw => circleData.forward _ FALSE; reverse => circleData.forward _ NOT circleData.forward; ENDCASE => ERROR; }; CircleGetOrientation: PROC [slice: Slice, parts: SliceParts] RETURNS [orientation: Orientation, isUnique: BOOL _ TRUE] = { circleData: CircleData _ NARROW[slice.data]; RETURN[IF circleData.forward THEN cw ELSE ccw]; }; MakeComplete: PROC [circleParts: CircleParts] = { circleParts.cpArray _ ALL[TRUE]; circleParts.outline _ TRUE; }; IsComplete: PROC [circleParts: CircleParts] RETURNS [BOOL _ FALSE] = { RETURN[ circleParts#NIL AND circleParts.cpArray=ALL[TRUE] AND circleParts.outline]; }; IsEmpty: PROC [circleParts: CircleParts] RETURNS [BOOL _ FALSE] = { RETURN[circleParts = NIL OR (circleParts.cpArray=ALL[FALSE] AND circleParts.outline=FALSE)]; }; NoOpBoundBox: PUBLIC GGModelTypes.SliceBoundBoxProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpTransformedBoundBox: PUBLIC GGModelTypes.SliceTransformedBoundBoxProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpTightBox: PUBLIC GGModelTypes.SliceTightBoxProc = { box _ NIL; }; NoOpCopy: PUBLIC GGModelTypes.SliceCopyProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpRestore: PUBLIC GGModelTypes.SliceRestoreProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpBuildPath: PUBLIC GGModelTypes.SliceBuildPathProc = { }; NoOpDrawBorder: PUBLIC GGModelTypes.SliceDrawBorderProc = { }; NoOpDrawParts: PUBLIC GGModelTypes.SliceDrawPartsProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDrawTransform: PUBLIC GGModelTypes.SliceDrawTransformProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDrawSelectionFeedback: PUBLIC GGModelTypes.SliceDrawSelectionFeedbackProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDrawAttractorFeedback: PUBLIC GGModelTypes.SliceDrawAttractorFeedbackProc = { IF NOT (dragInProgress AND selectedParts#NIL) THEN { pointGen: PointGenerator; pointPairGen: PointPairGenerator; wholeSliceD: SliceDescriptor _ GGSliceOps.NewParts[slice, NIL, topLevel]; Imager.SetStrokeEnd[dc, round]; Imager.SetStrokeWidth[dc, 1.0]; Imager.SetColor[dc, Imager.black]; pointPairGen _ GGSliceOps.PointPairsInDescriptor[wholeSliceD]; FOR pointPairAndDone: PointPairAndDone _ GGSliceOps.NextPointPair[slice, pointPairGen], GGSliceOps.NextPointPair[slice, pointPairGen] UNTIL pointPairAndDone.done DO Imager.MaskVector[dc, pointPairAndDone.lo, pointPairAndDone.hi]; ENDLOOP; pointGen _ GGSliceOps.PointsInDescriptor[wholeSliceD]; FOR pointAndDone: PointAndDone _ GGSliceOps.NextPoint[slice, pointGen], GGSliceOps.NextPoint[slice, pointGen] UNTIL pointAndDone.done DO GGShapes.DrawCP[dc, pointAndDone.point, camera.cpScale]; ENDLOOP; }; }; NoOpAttractorFeedbackBoundBox: PUBLIC GGModelTypes.SliceAttractorFeedbackBoundBoxProc = { box _ GGBoundBox.CopyBoundBox[GGSliceOps.GetTightBox[slice, NIL]]; GGBoundBox.EnlargeByOffset[box, GGModelTypes.halfJointSize*camera.cpScale + 1.0]; }; NoOpSaveSelections: PUBLIC GGModelTypes.SliceSaveSelectionsProc = { }; NoOpRemakeSelections: PUBLIC GGModelTypes.SliceRemakeSelectionsProc = { }; NoOpSetOrientation: PUBLIC GGModelTypes.SliceSetOrientationProc = { RETURN[FALSE]; }; NoOpGetOrientation: PUBLIC GGModelTypes.SliceGetOrientationProc = { RETURN[ccw]; }; NoOpTransform: PUBLIC GGModelTypes.SliceTransformProc = { SIGNAL Problem[msg: "All slice classes must have this proc."]; }; NoOpDescribe: PUBLIC GGModelTypes.SliceDescribeProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpDescribeHit: PUBLIC GGModelTypes.SliceDescribeHitProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpFileout: PUBLIC GGModelTypes.SliceFileoutProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; NoOpFilein: PUBLIC GGModelTypes.SliceFileinProc = { SIGNAL Problem [msg: "All slice classes must have this proc."]; }; GenericTransformedBoundBox: PUBLIC GGModelTypes.SliceTransformedBoundBoxProc = { IF GGSliceOps.IsCompleteParts[GGSlice.DescriptorFromParts[slice, selectedParts]] THEN { box _ GGSliceOps.GetBoundBox[slice, NIL]; box _ GGBoundBox.BoundBoxOfBoundBox[box, transform]; } ELSE { copies: LIST OF Slice _ GGSliceOps.Copy[slice, NIL]; IF copies.rest#NIL THEN SIGNAL Problem [msg: "Failed to procure complete copy"]; GGSliceOps.Transform[copies.first, selectedParts, transform, none, NIL]; -- transform it box _ GGSliceOps.GetBoundBox[copies.first, movingParts]; -- now get the bound box of the moving parts }; }; Init: PROC [] = { cache _ FunctionCache.Create[maxEntries: 100]; }; cache: FunctionCache.Cache; Init[]; END. ŒGGSliceImplC.mesa Contents: Implements the Circle slice class and the NoOp procedures for slices (see also GGSliceImplF for more NoOps). Copyright Σ 1988 by Xerox Corporation. All rights reserved. Pier, September 9, 1991 5:30 pm PDT Bier, December 2, 1991 3:23 pm PST Kurlander, July 17, 1987 11:55:38 am PDT Eisenman, September 28, 1987 5:36:24 pm PDT Circle Slice Class transform needed for general case of rotated circles cpArray[origin, left, top, right, bottom]. 5 element array. Fundamentals Drawing Transforming Textual Description Parts Part Generators Hit Testing Style circleData.tightBox _ GGBoundBox.NullBoundBox[]; 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; CircleSetBoundBox[slice]; -- not necessary since boxValid is FALSE Circle Class Procedures Fundamentals since the circle can be an ellipse, we just can't find the radius. GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceBoundBoxProc This added to make WalkProc answer NO to the question: Are You Degenerate ? GGModelTypes.SliceCopyProc Just ignore parts and copy the whole circle. 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). GGModelTypes.SliceRestoreProc from and to must be nearly identically structured slices, probably because from was made as a copy of to. Restore the contents of "to", getting all non-REF values from "from". Good luck. to.nullDescriptor is always valid to.fullDescriptor is unused Class Procs for Drawing GGModelTypes.SliceBuildPathProc Rubberband the radius. If the circle is selected in entirety, Identify cases where transformation is applied to entire outline. Otherwise Rubberband. The only parts of a circle are its outline and its five control points. If the outline is selected, we draw the circle. GGModelTypes.SliceDrawTransformProc This is what makes circles behave specially. Depending on which parts are selected, the points are transformed and the circle is rubberbanded properly. The circle data itself is not modified. If parts = NIL then, transform whole circle (unless transform = NIL). Rubberband the circle. Transform the circle as a unit. GGModelTypes.SliceDrawSelectionFeedbackProc Auxiliary Procs for Drawing CompareProc: TYPE ~ PROC [argument: Domain] RETURNS [good: BOOL]; When this routine is called, we've done all of the hard work to find out how the circle (and its sampled color, if any) are to be transformed. Draw the circle, using the cache (if cacheIt). Sampled colors present an extra difficulty with caching. Note that position will be [0, 0], the first time this Imager.Object is drawn. Imager.Object.draw PROC NOT called with the dc transformation already set to object coordinates Imager.MaskFill[context, IF circleData.forward THEN CirclePath ELSE CirclePathReverse, TRUE]; -- do we need to be careful about this here? (We can't be making Interpress) Draw the Fill Draw the Stroke NOT called with the dc transformation already set to object coordinates GGModelTypes.SliceSaveSelectionsProc GGModelTypes.SliceRemakeSelectionsProc Transforming circleLimit: REAL _ 0.05; -- needed to prevent numerical instability of tiny circles GGModelTypes.SliceTransformProc Permanently transforms the circle. Depending on which parts are selected, the circle is transformed. Only one cp is selected OR no selections for a brand new circle This is the special case of a new circle being rubberbanded Textual Description GGModelTypes.SliceDescribeProc GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. GGModelTypes.SliceFileinProc slice _ MakeCircleSlice[origin, outerPoint, strokeWidth, strokeColor, fillColor].slice; fix up the new circle transforms in case they were skewed by uneven scaling CircleSetBoundBox[slice]; Parts GGModelTypes.SliceEmptyPartsProc GGModelTypes.SliceEmptyPartsProc GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceDifferencePartsProc GGModelTypes.SliceMovingPartsProc If center is moving, everything moves. If any edge CP is moving, everything but the center is moving. GGModelTypes.SliceAugmentPartsProc GGModelTypes.SliceAlterPartsProc Part Generators GGModelTypes.SliceWalkSegmentsProc GGModelTypes.SliceClosestPointProc Hit Testing GGModelTypes.SliceClosestJointToHitDataProc GGModelTypes.SliceClosestSegmentProc Find hit point on unit circle Transform back to world coords origin, left, top, right, bottom Style Utilities NoOp Class Routines Fundamentals Drawing Transforming Textual Description Filing NoOp Parts moved to GGSliceImplF. NoOp Style moved to GGSliceImplF. this proc is an expensive way to calculate the bound box of a transformed object make a complete copy that can then be transformed, transform it, then get its bound box an optimization. If the selected parts is complete then there is no need to make a copy of the object. Simply get the original complete bound box and transform the box. ΚCc•NewlineDelimiter – "cedar" style˜Icodešœ™šΟnœo™wKšœ<™KšΟb ™ —Kšœ˜Kšœ œ˜;K˜K˜˜Kš‘™—Kšœ˜Kšœ˜Kšœ˜K˜#K˜3K˜3Kšœ%˜%šœ)˜)Kš‘ ™ —šœ˜Kš‘™—Kšœ˜Kšœ˜Kšœ˜šœ˜Kš‘™—K˜!K˜'K˜K˜K˜!K˜K˜!K˜šœ)˜)Kš‘™—K˜-K˜5K˜3K˜1K˜!K˜K˜!˜Kš‘ ™ —Kšœ!˜!Kšœ3˜3Kšœ3˜3Kšœ%˜%Kšœ3˜3K˜)K˜+˜1Kš‘™—K˜K˜%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š‘;™;—šœIžœ˜NKš‘lΠbk‘&™”—Kšœ˜Kšœ˜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šœB™BKšœžœ(˜9Kšœ˜—K˜šœžœžœžœB˜uKšœžœ ˜&K˜K˜K˜K˜K˜—™K™—šœ ™ K™—•StartOfExpansion# -- [cluster: GGModelTypes.Cluster]šœžœ#žœ#˜g–# -- [cluster: GGModelTypes.Cluster]šœžœ#žœ#˜eKšœ žœžœ)˜CKšœžœ˜Kšœžœ ˜,Kšœ žœ ˜KšœB™BK˜QKšœQŸ˜VKšœQŸ ˜ZKšœžœžœV˜uKšœi˜iKšœ-˜-Kšœ*˜*Kšœ˜Kšœ˜K˜—Kšœ9Ÿ˜SšžœžœžœŸ.˜BKšœ$Ÿ˜AKšœ˜Kšœ˜Kšœžœ˜Kšœžœ˜K˜—K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šœžœ#žœ˜UKšœ™Kš žœžœžœžœžœŸ ˜IKšžœ/Ÿ˜PK˜K˜—–# -- [cluster: GGModelTypes.Cluster]šœžœ#žœ˜UKšœ™Kš žœžœžœžœžœŸ ˜IKšžœ/Ÿ˜PK˜K˜—–# -- [cluster: GGModelTypes.Cluster]šœžœ˜)Kšœ žœ ˜Kšœžœ ˜,K˜QKšœP˜PšžœžœBžœ˜NKšœ\˜\Kšœžœ˜K˜—šžœžœ˜$Kš‘#’‘&™K—K˜K˜K˜K˜—š  œžœ$žœžœžœžœ ˜ZKšœ™Kšœ,™,Kšœ˜Kšœžœ ˜,K˜KšœA˜AKšœ˜K™οKšœ žœ˜!K˜DK˜!K˜@K˜/K˜-Kšœ5˜5KšœN˜NK˜4K˜%šžœžœžœž˜-KšœM˜MKšžœ˜—Kšœ#žœ3žœ˜^Kšœ6˜6Kšžœžœ ˜Kšœ˜K˜—š œžœžœ˜0Kšœ™K™ΌKš žœžœžœžœžœžœ˜!Kšžœžœžœ˜"Kšžœžœžœ˜$šž˜Kšœžœ ˜)Kšœžœ ˜%K˜šžœ ‘ œžœ˜Kšœ Οt œ£˜ Kšœ£ œ £˜&Kšœ £ œ£˜Kšœ£ œ £˜*Kšœ£ œ £˜,Kšœ£ œ£˜"Kšœ£ œ £˜,Kšœ£ œ £˜(Kšœ£ œ £˜&Kšœ£ œ£˜"Kšœ£ œ£˜Kš žœžœžœžœžœžœ˜%K˜K˜—K–j[x: FunctionCache.Cache, compare: FunctionCache.CompareProc, clientID: FunctionCache.ClientID _ NIL]˜Kšœ žœ ˜Kšœžœ˜ Kšœ6‘ œ˜_Kš žœžœžœžœžœžœ˜+K˜K˜—š œžœ œ œ/žœžœžœ˜•K˜Kšœžœ#˜6K˜šœžœ ˜Kšœ œ! œ˜0Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kš œ žœžœ+žœžœ˜MKšœ˜Kšœ œ œ˜—K˜K˜—š œžœžœžœ˜8Kš žœ žœžœžœžœ˜#šžœžœž˜Iprocšœ*žœžœ˜7Lšœ*žœžœ˜7Lšžœžœžœ˜—K˜K˜—š‘œžœ3 œ œ"žœ žœžœ˜—KšœΎ™ΎK™:KšžœŸ%˜@Kšœ˜Kšœžœ˜Kšœ˜Kšœžœ#žœžœ˜QK˜šžœžœžœžœ œžœžœ˜SKšœ œ˜Kšœ œ. œ˜=K˜Kšœ+ œ œ ˜Dšžœ žœžœŸ˜.KšœB œ˜HKšœ ˜ K˜Kšœ% œ œ Ÿ˜]K˜Kšœ žœ‘œ˜TKšœ@˜@K˜—Kšœžœ˜"šœ œ$ œ ˜QK™N—Kš žœ œžœžœ œ˜4Kšœ<žœ˜VK˜—Kšžœ+ œ œ˜WKšœ˜K˜—š œžœ œžœ œ˜nKšžœ˜Kšœ œ˜šžœžœžœ˜šœ+˜+Lšœ œ˜Lšœ œ œ œ˜0L˜—šœ+˜+Lšœ œ˜Lšœ œ œ œ˜0L˜—Lšžœ œžœ˜—K˜K˜—šœžœ4˜JKšœž™KšžœD™GKšžœ˜šœžœ˜Kšœ3˜3Kšœ œ˜#K–P[context: Imager.Context, path: ImagerPath.PathProc, parity: BOOL _ FALSE]š œžœžœ žœžœO™ͺK–P[context: Imager.Context, path: ImagerPath.PathProc, parity: BOOL _ FALSE]šœ%žœ˜+K˜—šœ˜*Kšœ& œ+˜UKšœ˜—Kšœžœ ˜'Kšœ œ$ œ˜2K˜K™ Kšžœžœžœ"˜EK˜K™šžœžœžœžœ˜AKšœ,˜,Kšœ8˜8Kšœ5Ÿ%˜ZKšœ'Ÿ%˜Lšžœžœ˜š  œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœ.˜.Kšœz˜zKšœ˜—Kšžœ3žœ˜=K˜—Kšœ˜K˜—šœžœ3 œ œ7žœ žœžœ˜±KšžœD™Gšœ˜*Kšœ& œ+˜UKšœ˜—šœžœ˜Kšžœžœ2 œ˜MKšžœ.˜2Kš žœ œžœžœ œ˜4Kšœ œ˜K–P[context: Imager.Context, path: ImagerPath.PathProc, parity: BOOL _ FALSE]š œžœžœ žœžœ˜WK˜—Kšœ˜Kšžœžœžœžœ˜Hšžœ žœžœžœ˜/Kš žœ œžœžœ œ˜4Kšœ˜Kšœ+˜+Kšœ'˜'Kšœ!˜!šžœ žœ˜š  œžœžœžœžœ˜-Kšžœ ˜K˜—Kšœ&˜&Kšœe˜eKšœ˜—Kšžœ.žœ˜8K˜—Kšœ˜K˜—š œŸ˜:Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜K˜—šœŸ,˜SKšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜—šœžœŸ˜½Kšžœ˜šœžœ˜Kšœž œ˜,Kšœžœžœ˜KšœF˜FKšœE˜EKšœ#˜#šžœžœž˜2Kšœžœžœ˜CKšœ%žœžœ"˜NKšžœ žœA˜TKšžœžœD˜\Kš žœžœ žœžœžœ1˜aKšžœ˜—K˜—Kšœ"˜"K˜K˜—šœžœC˜]Kšœ$™$š œžœ žœ žœ#˜Všžœ ž˜KšœC˜CKšœ=˜=KšœC˜CKšœA˜AKšžœ˜—K˜—šœžœžœ#˜Všžœ ž˜Kšœ0˜0Kšœ*˜*Kšœ0˜0Kšœ.˜.Kšžœ˜—K˜—Kšœ žœžœ˜Kšœžœ˜)Kšœžœ ˜,Kšžœ žœžœ žœ˜*šžœžœžœž˜-Kšœžœ*˜LKšžœ˜—Kšœ*žœ#˜PK˜K˜—š€œžœ-žœ˜hKšœ&™&š  œžœ žœžœžœ˜Tšžœžœ ž˜Kšœ8˜8Kšœ2˜2Kšœ8˜8Kšœ6˜6Kšžœžœ˜—K˜—šœžœ-žœžœ˜Ušžœžœ ž˜Kšœ%˜%Kšœ˜Kšœ%˜%Kšœ#˜#Kšžœžœ˜—K˜—Kšœžœ ˜,Kšœžœ˜/šžœžœžœž˜-Kšœ?˜?Kšžœ˜—KšœC˜CJš œžœžœžœžœŸ˜TK˜—K™Kšœ ™ Kšœ žœ Ÿ:™TKšœ žœ Ÿ0˜JK˜šœžœ$žœY˜•Kšœ™Kšœd™dKšœ žœžœ˜Kšœ žœ˜Kšœ žœ˜K˜0Kšœžœ ˜,Kšœžœ˜)šž˜Kš žœ žœžœžœ žœ˜Bšžœžœž˜2šžœ žœžœžœ˜7Kšœ˜K˜K˜—Kšžœ˜—š žœ žœžœžœŸ0˜`KšœT˜TKšœQ˜QKšžœ˜K˜—šžœ žœ žœŸ9˜[KšœT˜TKšžœ˜K˜—Kšœžœ%™?KšœO˜Ošžœ žœžœ žœ˜&K™;K–6[v1: GGBasicTypes.Vector, v2: GGBasicTypes.Vector]šœˆŸ˜§Kšœ3Ÿ˜MK˜K˜—šžœŸ7˜>KšœAžœ ž˜TK˜K˜K˜K˜Kšžœžœ˜KšœBŸ˜WK˜—KšœQ˜QKšœKžœ8Ÿ˜₯Kšžœ˜šž˜šœ˜KšœM˜MKšœG˜GKšœR˜RKšœ˜Kšœ˜K˜——Kšžœ˜—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šœ2˜2Kšœ˜Kšœ/˜/Kšœ˜Kšœ/˜/Kšœ˜Lšœ:žœžœ˜Ašžœžœ˜Lšœ7˜7LšœP˜PLšœ˜—Kšœ˜Kšœ˜Lšœ-žœ˜2Lš žœžœžœ0žœ žœžœŸ˜vKšœ˜Kšœ,˜,K˜K™—š  œžœžœžœ žœ%žœ˜nKšœ™Kšœ žœžœ˜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šœ=˜=šžœžœ˜Kšœ6˜6Kšœ/˜/Kšœ/˜/K˜—Kšœ˜K˜—šžœžœŸ˜3Kšœ žœžœ˜Kš œžœžœžœžœ˜Kšœ$˜$Kšœ0˜0Kš œžœ žœžœžœ˜>Kšœ˜šžœžœžœ˜š žœžœžœžœžœžœž˜@Lšœžœ#˜>Lšžœ˜—K˜—šžœžœŸ˜1Kšœ˜Lšœ-˜-K˜—K˜—Kšœ™Kšœ˜K˜K˜—K™K™šœžœžœž œ˜MKšœ ™ Kšœžœ˜0Kšžœ˜K˜K˜—šœžœžœž œ˜PKšœ ™ 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šœ4žœ0™fš œžœžœž œ˜Hšžœžœžœž˜šžœžœ˜ 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˜—šœžœ#žœžœ˜bKšœ ™ Kšœžœ˜0Kšœ˜Kšœžœ˜Kš œ žœžœžœ žœ˜Gšžœžœžœž˜)šžœžœ˜ Kšœ˜Kšžœ˜K˜—Kšžœ˜—šžœž˜šœ ˜ šžœžœ˜Kšœ$žœ˜8Kšœ#žœ˜(K˜—Kšžœžœ˜ K˜—šœ˜šžœžœ˜Kšœ4žœ˜HKšœ#žœ˜(K˜—Kšžœžœ˜ K˜—Kšœ žœ#žœ ˜?Kšœ#žœžœŸ˜KKšžœžœ˜—Kšœ;˜;K˜K˜—K˜K™šœžœžœ˜_Kšœžœ˜*Kšœ žœ$žœ˜9šžœžœž˜*Kšžœžœ#˜?Kšžœ˜—K˜—K˜šœžœ7˜YKšœžœ˜*Kšœžœ˜3Kšœžœžœ˜šžœžœž˜*Kšžœžœ4˜PKšžœžœžœ˜Kšžœ˜—K˜K˜—šœžœžœ˜aKšœžœ˜*Kš œ žœžœ&žœžœ˜~Kšœžœžœžœ˜-Kšœ˜K˜—šœžœ$žœ˜aKšœ ‘™"Kšœžœ ˜,Kš œžœžœžœ žœ˜WKšœE˜EKš žœžœžœžœŸ˜VKšœžœ(˜9K˜K˜—šœžœ*žœ!˜gKšœ*˜*Kšœžœ˜3šžœ žœžœžœ˜+Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœžœ˜*Kšœžœ˜ Kšœžœ˜šžœ!žœžœž˜FKšžœžœžœŸ˜BKšžœ˜—Kšœ7˜7Kšœ˜Kšœ"˜"K˜—K˜K˜—šœžœ!žœžœ˜TKšžœ˜Kšœ)˜)šžœž˜Kšœ'Ÿ ˜0Kšœ(Ÿ˜/Kšœ'Ÿ˜-Kšœ'Ÿ˜/Kšœ(Ÿ ˜1Kšžœžœ"˜3—K˜K˜—šœžœ*žœ.˜vKšžœžœžœžœžœžœžœ˜6šžœ˜Kšœžœ˜:Kšžœžœžœ"˜>Kšœ˜Kšœ!˜!Kšœ˜K˜—K˜K˜—šœžœ8žœžœ+žœ/žœžœ žœžœ˜ήK™"šžœžœY˜_Kšžœžœ˜ —K˜šž˜KšœžœžœŸ!˜?Kšœžœ˜4Kšœ  œžœ˜'Kšœ žœ˜Kšœžœ˜3K˜Kšœžœ˜0Kšœ˜K˜Kšœ*˜*šžœžœžœž˜-Kšžœžœžœžœ˜,Kšœ˜Kšœ œ3˜<šžœ  œ  œžœ˜Kšœ œ  œ˜K˜K˜Kš žœ  œ  œžœ žœ˜.K˜—Kšžœ˜—šžœ žœ˜KšœžœFžœ˜kK˜#šžœžœ˜šžœ?žœ˜GKšœ<˜<—Kšžœ=˜AKšœ˜—Kšœ˜—Kšžœ˜—Kšœ˜—K˜K™ š œžœ@žœžœžœE˜ΆKšœ+™+Kšœ žœžœ˜Kšœ žœžœ˜Kšœ˜Kš œžœžœžœ žœ˜WKšœŸœ_˜kKšœžœ ˜#Kšœ+žœ˜0Kšœ#žœ˜,K–?[slice: GGModelTypes.Slice, parts: GGModelTypes.SliceParts]šœN˜NKšœ˜K™K˜—šœžœ8žœžœ+žœ.žœžœ žœžœ˜ίKšœ$™$KšžœžœY˜_Kšžœžœ˜ K˜šž˜Kšœ˜Kšœžœ˜0Kšœžœ˜3K˜"K˜Kšœžœ˜ K˜Kš žœžœžœžœžœžœ˜GK˜OKšœ+˜+Kš‘™Kšžœ žœ;˜MKš‘™K˜PK˜4šžœžœ˜Kšœžœ>žœ˜cKšœ žœ˜Kšœ1˜1K˜—Kšžœ˜—K˜K˜—šœžœžœ)žœžœ žœžœžœžœžœžœžœžœ˜ Kšœžœ ˜,Kšœ œD˜OKšœ žœžœ˜K˜Kšœ!žœžœ  œ˜cšžœ žœ˜Kšœ žœ9žœ˜NK˜—K˜K˜—š œžœ'žœ žœžœžœ ˜{Kšœžœ˜3Kšœžœ˜0Kšœ žœ ˜KšœB˜BKšœ žœžœ˜#KšœT˜Tšžœžœžœž˜ K–4[m: ImagerTransformation.Transformation, v: VEC]šœ žœU˜bKšžœ˜—K˜K˜—šœžœžœžœžœžœžœ˜dKšœžœ ˜/Kšœžœ ˜,Kš žœžœžœžœžœ˜.Kš žœžœžœžœžœ˜/Kšœ&˜&K˜K˜—š œžœžœ žœžœ ˜_Kšœ ™ Kšœ)˜)Kšœ;˜;Kšœ<˜K˜—šœžœ.˜MKšžœ8˜>K˜—š œžœ#˜7Kšœžœ˜ Kšœ˜—šœžœ˜/Kšžœ8˜>K˜—š œžœ"˜5Kšžœ8˜>K˜—K˜š‘™K™—š œžœ$˜9K˜—šœžœ%˜;K˜—š œžœ$˜9Kšžœ8˜>K˜—šœžœ(˜AKšžœ8˜>K˜—šœžœ0˜QKšžœ8˜>K˜K˜—šœžœ0˜Qš žœžœžœžœžœ˜4Jšœ˜Jšœ!˜!Jšœ:žœ ˜IJšœ˜Kšœ˜K˜"Jšœ>˜>šžœƒžœž˜€Kšœ@˜@Jšžœ˜—Jšœ6˜6šžœkžœž˜ˆJšœ8˜8Jšžœ˜—K˜—˜K˜——šœžœ4˜YKšœ<žœ˜BJšœQ˜QK˜K˜—šœžœ)˜CK˜K˜—šœžœ+˜GK˜K˜—šœžœ)˜CKšžœžœ˜K˜K˜—šœžœ)˜CKšžœ˜ K˜—K™šœ ™ K™—š œžœ$˜9Kšžœ8˜>K˜—K™šœ™K™—š œžœ#˜7Kšžœ9˜?K˜—šœžœ&˜=Kšžœ9˜?K˜—K™™K™—š œžœ"˜5Kšžœ9˜?K˜—š œžœ!˜3Kšžœ9˜?K˜—K™Kšœ!™!šœ!™!K™—šœžœ.˜PK™PKšŸW™WšžœOžœ˜WK™ͺKšœ$žœ˜)Kšœ4˜4K˜—šžœ˜Kšœžœžœ žœ˜4Kšžœ žœžœžœ2˜PKšœCžœŸ˜XKšœ9Ÿ,˜eK˜—K˜—K™šœžœ˜K–:[maxEntries: INT _ 10, maxTotalSize: INT _ 2147483647]˜.K˜—K˜K˜K˜K˜K˜Kšžœ˜K˜—…—πΪHΙ