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
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;
Circle Slice Class
CircleData: TYPE = REF CircleDataObj;
CircleDataObj: TYPE = RECORD [
transform needed for general case of rotated circles
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: BOOLTRUE, -- 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
cpArray[origin, left, top, right, bottom]. 5 element array.
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: BOOLFALSE, -- dashed stroke properties
pattern: SequenceOfReal,
offset: REAL ← 0.0,
length: REAL ← 0.0,
color: Imager.Color,
fillColor: Imager.Color,
fillIsSampled: BOOLFALSE,
fillLocal: Transformation
];
BuildCircleSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = {
OPEN GGSlice;
class ← NEW[SliceClassObj ← [
type: $Circle,
unlink: GGSlice.UnlinkSlice, -- circle data doesn't have links
Fundamentals
getBoundBox: CircleGetBoundBox,
getTransformedBoundBox: GGSlice.GenericTransformedBoundBox,
getTightBox: CircleGetTightBox,
copy: CircleCopy,
restore: CircleRestore,
Drawing
buildPath: CircleBuildPath,
drawBorder: CircleDrawBorder,
drawParts: CircleDrawParts,
drawTransform: CircleDrawTransform,
drawSelectionFeedback: CircleDrawSelectionFeedback,
drawAttractorFeedback: CircleDrawAttractorFeedback,
saveSelections: CircleSaveSelections,
remakeSelections: CircleRemakeSelections,
Transforming
transform: CircleTransform,
Textual Description
describe: CircleDescribe,
describeHit: CircleDescribeHit,
fileout: CircleFileout,
filein: CircleFilein,
Parts
isEmptyParts: CircleIsEmptyParts,
isCompleteParts: CircleIsCompleteParts,
newParts: CircleNewParts,
unionParts: CircleUnionParts,
differenceParts: CircleDiffParts,
movingParts: CircleMovingParts,
augmentParts: CircleAugmentParts,
alterParts: CircleAlterParts,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: CirclePointsInDescriptor,
walkPointsInDescriptor: CircleWalkPointsInDescriptor,
pointPairsInDescriptor: NoOpPointPairsInDescriptor,
segmentsInDescriptor: CircleSegmentsInDescriptor,
walkSegments: CircleWalkSegments,
nextPoint: CircleNextPoint,
nextPointPair: NoOpNextPointPair,
nextSegment: CircleNextSegment,
Hit Testing
closestPoint: CircleClosestPoint,
closestJointToHitData: CircleClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: CircleClosestSegment,
filledPathsUnderPoint: CircleFilledPathsUnderPoint,
lineIntersection: CircleLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: CircleHitDataAsSimpleCurve,
Style
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.tightBox ← GGBoundBox.NullBoundBox[];
circleData.startPoint ← outerPoint;
N.B. arc segments don't represent a shape. Only for style.
circleData.seg ← GGSegment.MakeArc[[-1.0, 0.0], [1.0, 1.0], [-1.0, 0.0], NIL];
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.seg.lo ← origin;
circleData.seg.hi ← outerPoint;
circleData.seg.strokeWidth ← 2.0;
circleData.seg.color ← Imager.black;
circleData.fillColor ← fillColor;
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]];
CircleSetBoundBox[slice]; -- not necessary since boxValid is FALSE
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;
};
Circle Class Procedures
Fundamentals
CircleGetBoundBoxAux: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = {
CircleFindBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = {
strokeWidth: REALNARROW[slice.data, CircleData].seg.strokeWidth;
pad: REAL ← strokeWidth;
circleData: CircleData ← NARROW[slice.data];
epsilon: REAL = 1.0E-6;
since the circle can be an ellipse, we just can't find the radius.
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] = {
GGModelTypes.SliceBoundBoxProc
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] = {
GGModelTypes.SliceBoundBoxProc
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;
This added to make WalkProc answer NO to the question: Are You Degenerate ?
circleData.seg.lo ← origin;
circleData.seg.hi ← major;
};
CircleCopy: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [copy: LIST OF Slice] = {
GGModelTypes.SliceCopyProc
Just ignore parts and copy the whole circle.
copySlice: Slice;
circleData: CircleData ← NARROW[slice.data];
newData: CircleData;
copyD: SliceDescriptor ← MakeCircleSlice[[0.0, 0.0], [1.0, 1.0]];
copySlice ← copyD.slice;
copy transform and inverse to the new structure (interface to makecircle requires origin and radius, which is inadequate since "circle" could actually have been transformed into an ellipse. Thus we passed a meaningless origin and radius).
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] = {
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.
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.nullDescriptor is always valid
to.fullDescriptor is unused
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;
};
Class Procs for Drawing
CircleBuildPath: PROC [slice: Slice, transformParts: SliceParts, transform: Transformation, moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc, editConstraints: EditConstraints ← none] = {
GGModelTypes.SliceBuildPathProc
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;
};
Rubberband the radius.
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
If the circle is selected in entirety,
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
Identify cases where transformation is applied to entire outline.
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;
Otherwise Rubberband.
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] = {
The only parts of a circle are its outline and its five control points. If the outline is selected, we draw the circle.
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] = {
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).
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: BOOLTRUE;
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 {
Rubberband the circle.
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;
};
};
Transform the circle as a unit.
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] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
jointSize: REAL = GGModelTypes.jointSize;
halfJointSize: REAL = GGModelTypes.halfJointSize;
hotJointSize: REAL = GGModelTypes.hotJointSize;
halfHotJointSize: REAL = GGModelTypes.halfHotJointSize;
slowNormal, slowHot, completeNormal, completeHot: BOOLFALSE;
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];
};
};
Auxiliary Procs for Drawing
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 = {
CompareProc: TYPE ~ PROC [argument: Domain] RETURNS [good: BOOL];
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: BOOLTRUE] 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: BOOLTRUE] = {
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.
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]]];
Note that position will be [0, 0], the first time this Imager.Object is drawn.
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 => fillLocalNIL;
};
CircleDrawObject: PROC [self: Imager.Object, context: Imager.Context] = {
Imager.Object.draw PROC
NOT called with the dc transformation already set to object coordinates
OPEN ImagerTransformation;
FillItIn: PROC = {
GGCoreOps.SetColor[context, circleProps.fillColor];
Imager.ConcatT[context, localView];
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)
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;
Draw the Fill
IF circleProps.fillColor # NIL THEN Imager.DoSave[context, FillItIn];
Draw the Stroke
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: BOOLFALSE] = {
NOT called with the dc transformation already set to object coordinates
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] = {
GGModelTypes.SliceSaveSelectionsProc
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: BOOLTRUE;
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] = {
GGModelTypes.SliceRemakeSelectionsProc
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
};
Transforming
circleLimit: REAL ← 0.05; -- needed to prevent numerical instability of tiny circles
circleLimit: REAL ← 0.01; -- circleLimit made smaller. KAP. April 12, 1988
CircleTransform: PROC [slice: Slice, parts: SliceParts ← NIL, transform: Transformation, editConstraints: EditConstraints, history: HistoryEvent] = {
GGModelTypes.SliceTransformProc
Permanently transforms the circle. Depending on which parts are selected, the circle is transformed.
isOrigin: BOOLFALSE;
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;
};
Only one cp is selected OR no selections for a brand new circle
originPoint ← ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]];
IF cpCount = 0 AND NOT isOrigin THEN {
This is the special case of a new circle being rubberbanded
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;
};
Textual Description
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] = {
GGModelTypes.SliceDescribeProc
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] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
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] = {
GGModelTypes.SliceFileinProc
fwd, good: BOOLTRUE;
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, strokeWidth, strokeColor, fillColor].slice;
slice ← MakeCircleSlice[origin, outerPoint].slice;
fix up the new circle transforms in case they were skewed by uneven scaling
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: BOOLFALSE;
props: LIST OF Rope.ROPENIL;
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];
};
};
CircleSetBoundBox[slice];
CircleSetScaling[slice];
circleData.forward ← fwd;
};
Parts
CircleIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL ← FALSE] = {
GGModelTypes.SliceEmptyPartsProc
circleParts: CircleParts ← NARROW[sliceD.parts];
RETURN[IsEmpty[circleParts] ];
};
CircleIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL ← FALSE] = {
GGModelTypes.SliceEmptyPartsProc
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] = {
GGModelTypes.SliceNewPartsProc
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] = {
GGModelTypes.SliceUnionPartsProc
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] = {
GGModelTypes.SliceDifferencePartsProc
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] = {
GGModelTypes.SliceMovingPartsProc
If center is moving, everything moves. If any edge CP is moving, everything but the center is moving.
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] = {
GGModelTypes.SliceAugmentPartsProc
more ← sliceD;
};
CircleAlterParts: PROC [sliceD: SliceDescriptor, action: ATOM] RETURNS [newD: SliceDescriptor] = {
GGModelTypes.SliceAlterPartsProc
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];
};
Part Generators
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: BOOLFALSE;
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] = {
GGModelTypes.SliceWalkSegmentsProc
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: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
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;
};
Hit Testing
CircleClosestJointToHitData: PROC [sliceD: SliceDescriptor, mapPoint, testPoint: Point, hitData: REF ANY] RETURNS [jointD: SliceDescriptor, point: Point, normal: Vector ← [0,-1]] = {
GGModelTypes.SliceClosestJointToHitDataProc
success: BOOLFALSE;
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: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
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];
Find hit point on unit circle
IF dist > 0.0 THEN pointOnCircle ← Vectors2d.Scale[localTestpoint, 1.0/dist];
Transform back to world coords
bestPoint ← ImagerTransformation.Transform[circleData.transform, pointOnCircle];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
IF bestDist<tolerance THEN {
hitData ← circleHitData ← NEW[CircleHitDataObj ← [index: -1, hitPoint: bestPoint, outline: TRUE] ];
success ← TRUE;
bestNormal ← Vectors2d.Sub[testPoint, bestPoint];
};
END;
};
CircleFilledPathsUnderPoint: PUBLIC PROC [slice: Slice, point: Point, tolerance: REAL] RETURNS [hitData: REF ANYNIL, moreHitDatas: LIST OF REF ANYNIL] = {
circleData: CircleData ← NARROW[slice.data];
pointCircle: Point ← ImagerTransformation.Transform[circleData.inverse, point];
success: BOOLFALSE;
success ← circleData.fillColor # NIL AND GGCircles.PointIsInCircle[pointCircle, circleData.circle];
IF success THEN {
hitData ← NEW[CircleHitDataObj ← [index: 0, hitPoint: point, outline: FALSE]];
};
};
CircleLineIntersection: PROC [sliceD: SliceDescriptor, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT ← 0] = {
circleData: CircleData ← NARROW[sliceD.slice.data];
circleParts: CircleParts ← NARROW[sliceD.parts];
epsilon: REAL = 0.072;
localLine: Line ← Lines2d.LineTransform[line, circleData.inverse];
localPoints: ARRAY [1..2] OF Point;
[localPoints, pointCount] ← GGCircles.CircleMeetsLine[circleData.circle, localLine];
FOR i: NAT IN [1..pointCount] DO
points ← CONS[ImagerTransformation.Transform[m: circleData.transform, v: localPoints[i]], points];
ENDLOOP;
};
CircleHitDataAsSimpleCurve: PROC [slice: Slice, hitData: REF ANY] RETURNS [simpleCurve: REF ANY] = {
circleHitData: CircleHitData ← NARROW[hitData];
circleData: CircleData ← NARROW[slice.data];
IF NOT circleHitData.outline THEN RETURN[NIL];
IF NOT circleData.evenScaling THEN RETURN[NIL];
simpleCurve ← circleData.simpleCircle;
};
CirclePointsFromData: PROC [circleData: CircleData] RETURNS [points: ARRAY [0..4] OF Point] = {
origin, left, top, right, bottom
t: Transformation ← circleData.transform;
points[0] ← ImagerTransformation.Transform[t, [0.0, 0.0] ];
points[1] ← ImagerTransformation.Transform[t, [-1.0, 0.0] ];
points[2] ← ImagerTransformation.Transform[t, [0.0, 1.0] ];
points[3] ← ImagerTransformation.Transform[t, [1.0, 0.0] ];
points[4] ← ImagerTransformation.Transform[t, [0.0, -1.0] ];
};
Style
CircleSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData, history: HistoryEvent] = {
circleData: CircleData ← NARROW[slice.data];
GGSegment.SetDefaults[circleData.seg, defaults];
circleData.fillColor ← defaults.fillColor;
GGSlice.KillBoundBox[slice];
};
CircleSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL, history: HistoryEvent] RETURNS [box: BoundBox] = {
circleData: CircleData ← NARROW[slice.data];
IF circleData.seg.strokeWidth#strokeWidth THEN {
circleData.seg.strokeWidth ← strokeWidth;
GGSlice.KillBoundBox[slice];
};
box ← GGSliceOps.GetBoundBox[slice, parts]; -- return a good bound box
};
CircleGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL, isUnique: BOOLTRUE] = {
circleData: CircleData ← NARROW[slice.data];
strokeWidth ← circleData.seg.strokeWidth;
};
CircleSetStrokeEnd: PROC [slice: Slice, parts: SliceParts, strokeEnd: StrokeEnd, history: HistoryEvent] = {
circleData: CircleData ← NARROW[slice.data];
circleData.seg.strokeEnd ← strokeEnd;
};
CircleGetStrokeEnd: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeEnd: StrokeEnd, isUnique: BOOLTRUE] = {
circleData: CircleData ← NARROW[slice.data];
RETURN[circleData.seg.strokeEnd, TRUE];
};
CircleSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Color, setHow: ATOM, history: HistoryEvent] = {
circleData: CircleData ← NARROW[slice.data];
SELECT setHow FROM
$Set => 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: BOOLTRUE] = {
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: BOOLTRUE] = {
circleData: CircleData ← NARROW[slice.data];
color ← circleData.fillColor;
};
CircleSetDashed: PROC [slice: Slice, parts: SliceParts, dashed: BOOLFALSE, 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: BOOLFALSE, pattern: SequenceOfReal, offset, length: REAL, isUnique: BOOLTRUE] = {
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: BOOLTRUE] = {
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: BOOLTRUE] = {
circleData: CircleData ← NARROW[slice.data];
RETURN[IF circleData.forward THEN cw ELSE ccw];
};
Utilities
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)];
};
NoOp Class Routines
Fundamentals
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."];
};
Drawing
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];
};
Transforming
NoOpTransform: PUBLIC GGModelTypes.SliceTransformProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
Textual Description
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."];
};
Filing
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."];
};
NoOp Parts moved to GGSliceImplF.
NoOp Style moved to GGSliceImplF.
GenericTransformedBoundBox: PUBLIC GGModelTypes.SliceTransformedBoundBoxProc = {
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
IF GGSliceOps.IsCompleteParts[GGSlice.DescriptorFromParts[slice, selectedParts]] THEN {
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.
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.