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 minorDS: REAL ¬ Vectors2d.DistanceSquared[minor, origin]; majorDS: REAL ¬ Vectors2d.DistanceSquared[major, origin]; tMax: REAL ¬ MAX[minorDS, majorDS]; radius: REAL ¬ RealFns.SqRt[tMax]; 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 minorDS: REAL ¬ Vectors2d.DistanceSquared[minor, origin]; majorDS: REAL ¬ Vectors2d.DistanceSquared[major, origin]; tMax: REAL ¬ MAX[minorDS, majorDS]; radius: REAL ¬ RealFns.SqRt[tMax]; 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.PutF1["%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: 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.ReadRope[f, "strokeWidth:"]; strokeWidth ¬ GGParseIn.ReadReal[f]; GGParseIn.ReadRope[f, "strokeColor:"]; strokeColor ¬ GGParseIn.ReadColor[f, version]; GGParseIn.ReadRope[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.ReadRope[f, "dashes: ("]; circleData.seg.dashed ¬ GGParseIn.ReadBool[f]; IF circleData.seg.dashed THEN { circleData.seg.pattern ¬ GGParseIn.ReadArrayOfReal[f]; circleData.seg.offset ¬ GGParseIn.ReadReal[f]; circleData.seg.length ¬ GGParseIn.ReadReal[f]; }; GGParseIn.ReadChar[f, ')]; }; IF version>=8706.08 THEN { -- read in segment props hasProps: BOOL ¬ FALSE; props: LIST OF Rope.ROPE ¬ NIL; GGParseIn.ReadRope[f, "props: ( "]; hasProps ¬ GGParseIn.ReadBool[f]; props ¬ IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL; GGParseIn.ReadChar[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.ReadRope[f, "fwd:"]; fwd ¬ GGParseIn.ReadBool[f]; }; }; 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]; -- returns new boundBox } 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 ¬ GGBoundBox.CopyBoundBox[GGSliceOps.GetBoundBox[copies.first, movingParts]]; }; }; Init: PROC [] = { cache ¬ FunctionCache.Create[maxEntries: 100]; }; cache: FunctionCache.Cache; Init[]; END. ΪGGSliceImplC.mesa Copyright Σ 1987, 1988, 1991, 1992 by Xerox Corporation. All rights reserved. Contents: Implements the Circle slice class and the NoOp procedures for slices (see also GGSliceImplF for more NoOps). Kurlander, July 17, 1987 11:55:38 am PDT Eisenman, September 28, 1987 5:36:24 pm PDT Pier, June 22, 1992 5:01 pm PDT Bier, December 3, 1992 5:29 pm PST Doug Wyatt, April 14, 1992 2:33 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. radius: REAL ¬ RealFns.SqRt[MAX[Vectors2d.DistanceSquared[minor, origin], Vectors2d.DistanceSquared[major, origin]]]; 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 radius: REAL ¬ RealFns.SqRt[MAX[Vectors2d.DistanceSquared[minor, origin], Vectors2d.DistanceSquared[major, origin]]]; 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. Note that the following code works ONLY for slices whose Parts do not contain references to other slices. The reason for this is that we call procedures in this code using a copy of the SLICE but the original PARTS; if those original Parts contain references to slices they will refer to the ORIGINAL slices not the copied slices and the originals will get transformed instead of the copy. We do this because it is deemed too onerous to construct Parts for the copy which mirror the Parts of the original. must make a copy because it may be mutated later ΚAk•NewlineDelimiter –(cedarcode) style˜codešœ™Kšœ ΟeœC™NKšΟnœo™wK™(Kšœ+™+K™K™"K™&K™—šΟk ˜ K˜ς—K˜šž œŸœŸ˜KšŸœΛŸœ#˜χKšŸœ Ÿ˜K˜Kšœ ŸœŸœ ˜!Kšœ Ÿœ˜,KšœŸœ˜#KšœŸœ˜#KšœŸœ˜Kšœ Ÿœ˜-KšœŸœ ˜5Kšœ Ÿœ˜*KšœŸœ˜1KšœŸœ˜KšœŸœ˜Kšœ Ÿœ˜-KšœŸœ˜!KšœŸœ˜/KšœŸœ!˜7KšœŸœ˜3KšœŸœ#˜;KšœŸœ"˜9KšœŸœ˜1KšœŸœ˜!Kšœ Ÿœ˜%KšœŸœ!˜7KšœŸœ$˜=KšœŸœ#˜;KšœŸœ˜3Kšœ Ÿœ˜+KšœŸœ˜2KšœŸœ˜!KšœŸœ"˜9Kšœ Ÿœ˜+KšœŸœ˜1Kšœ Ÿœ˜'KšœŸœ ˜5Kšœ Ÿœ˜+Kšœ Ÿœ˜)KšœŸœ˜3KšœŸœ˜#Kšœ Ÿœ˜'—˜Kšœ Ÿœ˜)Kšœ Ÿœ˜)Kšœ Ÿœ˜+Kšœ Ÿœ˜+Kšœ Ÿœ˜'—K˜KšžœŸœŸœ Ÿœ˜;K˜K™Kšœ ŸœŸœ˜%šœŸœŸœ˜Kšœ4™4KšœΟcW˜hKšœ˜Kšœ ;˜KKšœ Ÿœ˜Kšœ !˜7Kšœ˜Kšœ 9˜PKšœ :˜NK˜Kšœ ŸœŸœ 6˜LKšœŸœŸœ -˜mK˜ K˜—K˜KšœŸœ˜KšœŸœ #˜NKšœ ŸœŸœ˜'šœŸœŸœ˜šœ ŸœŸœŸœ '˜LKšœ;™;Kšœ Ÿœ %˜3—K˜K˜—KšœŸœŸœ˜+šœŸœŸœ˜!Kšœ ;˜YK˜Kšœ Ÿœ  ˜.K˜K˜—KšœŸœŸœ ˜šœ ŸœŸœ˜KšœΟuœ˜Kšœ Ÿœ˜K˜KšœŸœŸœ ˜1Kšœ˜KšœŸœ˜KšœŸœ˜Kšœ˜Kšœ˜KšœŸœŸœ˜Kšœ‘œ˜K˜—K˜šžœŸœŸœŸœ˜EKšŸœ ˜ šœŸœ˜K˜šœ !˜>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˜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˜MK˜GK˜RKšœŸœ˜K˜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šœ Ÿœ,˜9Kšœ Ÿœ,˜9KšœŸœŸœ˜#KšœŸœ˜"K˜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šŸœŸœBŸœ˜NKšœ\˜\KšœŸœ˜K˜—šŸœŸœ˜$Kš’#£’&™K—K˜K˜K˜K˜—š ž œŸœ$ŸœŸœŸœŸœ ˜ZKšœ™Kšœ,™,Kšœ˜KšœŸœ ˜,K˜K˜AK˜K™οKšœ Ÿœ˜!K˜DK˜!K˜@K˜/K˜-K˜5KšœN˜NK˜4K˜%šŸœŸœŸœŸ˜-K˜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˜FK˜EK˜#šŸœŸœŸ˜2KšœŸœŸœ˜CKšœ%ŸœŸœ"˜NKšŸœ ŸœA˜TKšŸœŸœD˜\Kš ŸœŸœ ŸœŸœŸœ1˜aKšŸœ˜—K˜—Kšœ"˜"K˜K˜—šžœŸœC˜]Kšœ$™$šž œŸœ Ÿœ Ÿœ#˜VšŸœ Ÿ˜K˜CK˜=K˜CK˜AKšŸœ˜—K˜—šžœŸœŸœ#˜VšŸœ Ÿ˜K˜0K˜*K˜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˜CKš œŸœŸœŸœŸœ ˜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˜TK˜QKšŸœ˜K˜—šŸœ Ÿœ Ÿœ 9˜[K˜TKšŸœ˜K˜—KšœŸœ%™?K˜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˜QKšœKŸœ8 ˜₯KšŸœ˜šŸ˜šœ˜K˜MK˜GK˜RKšœ˜Kšœ˜K˜——KšŸœ˜—K˜—K™Kšœ™š žœŸœŸœŸœŸœ Ÿœ˜VKšœŸœ ˜/Kšœ Ÿœ˜Kš ŸœŸœ ŸœŸœ Ÿœ˜mKšŸœŸœŸœ˜4KšŸœŸœ˜ K˜1K˜K˜—šžœŸœŸœ Ÿœ˜LK™Kšœ Ÿœ˜Kšœ Ÿœ˜Kšœ˜KšŸœŸœŸœŸœ˜4KšœŸœ˜#šŸœŸœŸ˜2KšŸœŸœ˜9KšŸœ˜—KšŸœ ŸœŸœ&˜@Kš Ÿœ Ÿœ ŸœŸœ Ÿœ ˜OKšŸœ ŸœŸœ Ÿœ˜QK˜1K˜K˜—šž œŸœŸœŸœ˜4Kšœ™K™.KšœŸœ ˜,Kšœ8˜8Kšœ˜K˜3Kšœ˜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]˜FK–4[m: ImagerTransformation.Transformation, v: VEC]˜JK˜&K˜$K˜&K˜.K˜$K˜,KšœW™W˜2KšœK™K—Kšœ Ÿœ ˜ K˜!K˜MK˜GK˜RK˜)K˜#K˜!šŸœŸœ ˜3K˜#K˜.šŸœŸœ˜K˜6K˜.K˜.K˜—K˜K˜—šŸœŸœ ˜3Kšœ ŸœŸœ˜Kš œŸœŸœŸœŸœ˜K˜#K˜!Kš œŸœ ŸœŸœŸœ˜>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˜CKšœŸœ˜"K˜—K˜—Kšœ&ŸœŸœ˜0Kšœ<˜<šœ  ˜$K˜—KšŸœŸœ˜—K˜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˜7KšŸœ˜K˜—K˜Kš œ ŸœŸœŸœŸœ˜6šŸœŸœ˜ KšœŸœŸœ˜KšœŸœ˜K˜&K˜4K˜—šŸœŸœŸœ˜(K˜KšœŸœŸœ˜KšœŸœ˜KšœŸœ˜K˜6Kš œŸœŸœŸœŸœ˜=KšœŸœ˜"K˜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˜EKš ŸœŸœŸœŸœ ˜VKšœŸœ(˜9K˜K˜—šžœŸœ*Ÿœ!˜gK˜*KšœŸœ˜3šŸœ ŸœŸœŸœ˜+KšœŸœ˜KšŸœ˜K˜—šŸœ˜KšœŸœ˜*KšœŸœ˜ KšœŸœ˜šŸœ!ŸœŸœŸ˜FKšŸœŸœŸœ ˜BKšŸœ˜—K˜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]˜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˜1K˜—KšŸœ˜—K˜K˜—šžœŸœŸœ)ŸœŸœ ŸœŸœŸœŸœŸœŸœŸœŸœ˜ KšœŸœ ˜,Kšœ‘œD˜OKšœ ŸœŸœ˜K˜Kšœ!ŸœŸœ ‘œ˜cšŸœ Ÿœ˜Kšœ Ÿœ9Ÿœ˜NK˜—K˜K˜—š žœŸœ'Ÿœ ŸœŸœŸœ ˜{KšœŸœ˜3KšœŸœ˜0Kšœ Ÿœ ˜K˜BKšœ ŸœŸœ˜#K˜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š ŸœŸœŸœŸœŸœ˜4Kšœ˜Kšœ!˜!Kšœ:Ÿœ ˜IKšœ˜Kšœ˜K˜"K˜>šŸœƒŸœŸ˜€Kšœ@˜@KšŸœ˜—K˜6šŸœkŸœŸ˜ˆKšœ8˜8KšŸœ˜—K˜—˜K˜——šžœŸœ4˜YKšœ<Ÿœ˜BKšœ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šœ5 ˜LK˜—šŸœ˜Kšœ#’œΤ™ϋKšœŸœŸœ Ÿœ˜4KšŸœ ŸœŸœŸœ2˜PKšœCŸœ ˜X˜QK™0—K˜—K˜—K™šžœŸœ˜K–:[maxEntries: INT _ 10, maxTotalSize: INT _ 2147483647]˜.K˜—K˜K˜K˜K˜K˜KšŸœ˜K˜—…—ρpJ΅