GGSliceImplC.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Bier on September 30, 1986
Contents: Implementations of NoOp procedures for slices.
DIRECTORY
AtomButtonsTypes, Feedback, FunctionCache, GGBasicTypes, GGBoundBox, GGCircles, GGInterfaceTypes, GGModelTypes, GGSegment, GGSegmentTypes, GGParseIn, GGParseOut, GGShapes, GGSlice, GGUtility, Imager, ImagerPath, ImagerTransformation, IO, Lines2d, NodeStyle, RealFns, Rope, Vectors2d, ViewerClasses;
GGSliceImplC: CEDAR PROGRAM
IMPORTS Feedback, FunctionCache, GGBoundBox, GGCircles, GGParseIn, GGParseOut, GGSegment, GGShapes, GGSlice, Imager, ImagerPath, ImagerTransformation, IO, Lines2d, RealFns, Rope, Vectors2d
EXPORTS GGSlice = BEGIN
OPEN GGModelTypes; -- for SliceProc types
Object: TYPE = Imager.Object;
Color: TYPE = Imager.Color;
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
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)
tightBox: BoundBox,
transform: ImagerTransformation.Transformation,
scale: Vector, -- cached value of ImagerTransformation.Factor[transform].s
evenScaling: BOOL,
simpleCircle: Circle, -- for CircleHitDataAsSimpleCurve
inverse: ImagerTransformation.Transformation,
inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s
startPoint: Point, -- original point on circumference when circle was created
fillColor: Color,
segment: Segment
];
MaxCirclePoints: INTEGER = 5;
CirclePoints: TYPE = [0..MaxCirclePoints); -- origin, left, top, right, bottom
CircleParts: TYPE = REF CirclePartsObj;
CirclePartsObj: TYPE = RECORD [
cpArray: ARRAY CirclePoints OF BOOL, -- used when origin or CPs are selected
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 [
transform: 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, -- don't use colors because Imager.DrawObject doesn't cache colors
fillColor: Imager.Color,
data: REF
];
BuildCircleSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = {
OPEN GGSlice;
class ← NEW[SliceClassObj ← [
type: $Circle,
Fundamentals
getBoundBox: CircleBoundBox,
getTightBox: CircleTightBox,
copy: CircleCopy,
Drawing
drawParts: CircleDrawParts,
drawTransform: CircleDrawTransform,
drawSelectionFeedback: CircleDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
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,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: CirclePointsInDescriptor,
pointPairsInDescriptor: NoOpPointPairsInDescriptor,
segmentsInDescriptor: CircleSegmentsInDescriptor,
walkSegments: CircleWalkSegments,
nextPoint: CircleNextPoint,
nextPointPair: NoOpNextPointPair,
nextSegment: CircleNextSegment,
Hit Testing
closestPoint: CircleClosestPoint,
closestJointToHitData: NoOpClosestJointToHitData,
closestPointAndTangent: NIL,
closestSegment: CircleClosestSegment,
lineIntersection: CircleLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: CircleHitDataAsSimpleCurve,
Style
setDefaults: CircleSetDefaults,
setStrokeWidth: CircleSetStrokeWidth,
getStrokeWidth: CircleGetStrokeWidth,
setStrokeEnd: NoOpSetStrokeEnd,
getStrokeEnd: NoOpGetStrokeEnd,
setStrokeJoint: NoOpSetStrokeJoint,
getStrokeJoint: NoOpGetStrokeJoint,
setStrokeColor: CircleSetStrokeColor,
getStrokeColor: CircleGetStrokeColor,
setFillColor: CircleSetFillColor,
getFillColor: CircleGetFillColor,
setArrows: NoOpSetArrows,
getArrows: NoOpGetArrows,
setDashed: CircleSetDashed,
getDashed: CircleGetDashed
]];
};
MakeCircleSlice: PUBLIC PROC [origin: Point, outerPoint: Point] RETURNS [sliceD: SliceDescriptor] = {
circleData: CircleData ← NEW[CircleDataObj];
circleParts: CircleParts ← NEW[CirclePartsObj ← [ALL[FALSE], FALSE ]];
slice: Slice;
circleData.circle ← GGCircles.CircleFromPointAndRadius[[0,0], 1.0];
circleData.tightBox ← GGBoundBox.NullBoundBox[];
circleData.startPointouterPoint;
N.B. arc segments don't represent a shape. Only for style.
circleData.segment ← 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.segment.lo ← origin;
circleData.segment.hi ← outerPoint;
circleData.segment.strokeWidth ← 2.0;
circleData.segment.color ← Imager.black;
circleData.fillColor ← fillColor;
slice ← NEW[SliceObj ← [
class: GGSlice.FetchSliceClass[$Circle],
data: circleData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0]
]];
slice.nullDescriptor ← GGSlice.DescriptorFromParts[slice, NIL];
IF origin=outerPoint THEN circleData.startPoint ← [origin.x+20.0, origin.y+20.0]; -- prevent degenerate circle
circleData.transform ← ImagerTransformation.PreScale[ImagerTransformation.Translate[origin], Vectors2d.Distance[origin, circleData.startPoint]];
circleData.scale ← ImagerTransformation.SingularValues[circleData.transform];
circleData.inverse ← ImagerTransformation.Invert[circleData.transform];
circleData.inverseScale ← ImagerTransformation.SingularValues[circleData.inverse];
circleData.evenScaling ← TRUE;
circleData.simpleCircle ← GGCircles.CircleFromPointAndRadius[origin, Vectors2d.Distance[origin, circleData.startPoint]];
CircleSetBoundBox[slice];
sliceD ← NEW[SliceDescriptorObj ← [slice, circleParts] ];
[] ← CircleClosestPoint[sliceD, outerPoint, 999.0]; -- only purpose of this call is to set up the HitData so that the first call to SelectSlice (in GGMouseEventImplA.EndCircle) works
};
Class Procedures
Fundamentals
CircleBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
CircleTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
CircleSetBoundBox: PROC [slice: Slice] = {
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REALNARROW[slice.data, CircleData].segment.strokeWidth;
pad: REALMAX[cpHalf, 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]]];
GGBoundBox.UpdateBoundBox[circleData.tightBox, origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius];
GGBoundBox.UpdateCopyBoundBox[bBox: slice.boundBox, from: circleData.tightBox];
GGBoundBox.EnlargeByOffset[slice.boundBox, pad];
IF ABS[circleData.inverseScale.x - circleData.inverseScale.y] < epsilon THEN {
origin: Point; radius: REAL;
origin ← ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]];
radius ← circleData.scale.x;
circleData.evenScaling ← TRUE;
GGCircles.FillCircleFromPointAndRadius[origin, radius, circleData.simpleCircle];
}
ELSE circleData.evenScaling ← FALSE;
This added to make WalkProc answer NO to the question: Are You Degenerate ?
circleData.segment.lo ← origin;
circleData.segment.hi ← major;
};
CircleCopy: PROC [slice: Slice] RETURNS [copy: Slice] = {
GGModelTypes.SliceCopyProc
circleData: CircleData ← NARROW[slice.data];
newData: CircleData;
copy ← MakeCircleSlice[[0.0, 0.0], [1.0, 1.0], circleData.segment.strokeWidth, circleData.segment.color, circleData.fillColor].slice;
copyD: SliceDescriptor ← MakeCircleSlice[[0.0, 0.0], [1.0, 1.0]];
copy ← 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[copy.data];
newData.transform ← ImagerTransformation.Copy[circleData.transform];
newData.scale ← circleData.scale;
newData.inverse ← ImagerTransformation.Copy[circleData.inverse];
newData.inverseScale ← circleData.inverseScale;
newData.evenScaling ← circleData.evenScaling;
newData.simpleCircle ← GGCircles.CreateEmptyCircle[];
GGCircles.CopyCircle[from: circleData.simpleCircle, to: newData.simpleCircle];
newData.segment ← GGSegment.CopySegment[circleData.segment];
slice.class.setFillColor[copy, circleData.fillColor];
following code not needed since CopySegment did the work.
slice.class.setStrokeColor[copy, copyD.parts, circleData.segment.color]; -- cheat
slice.class.setstrokeWidth[copy, copyD.parts, circleData.segment.strokeWidth]; -- cheat
CircleSetBoundBox[copy];
};
Drawing
CircleDrawParts: PROC [slice: Slice, parts: SliceParts ← NIL, dc: Imager.Context, camera: CameraData, quick: BOOL] = {
GGModelTypes.SliceDrawPartsProc
DoCircleDrawOutline: PROC = {
CircleDrawOutline[dc, circleData, circleData.transform, TRUE];
};
circleData: CircleData ← NARROW[slice.data];
circleParts: CircleParts ← NARROW[parts];
IF circleParts = NIL OR circleParts.outline THEN Imager.DoSaveAll[dc, DoCircleDrawOutline];
};
CircleDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = {
OPEN ImagerTransformation;
GGModelTypes.SliceDrawSelectionFeedbackProc
DoDrawFeedback: PROC = {
thisCPisHot, thisCPisSelected: BOOL;
pts: ARRAY[0..4] OF Point;
pts[0] ← Transform[t, [0.0, 0.0]]; pts[1] ← Transform[t, [-1.0, 0.0]];
pts[2] ← Transform[t, [0.0, 1.0]]; pts[3] ← Transform[t, [1.0, 0.0]];
pts[4] ← Transform[t, [0.0, -1.0]];
IF NOT quick AND IsComplete[normalCircleParts] THEN {
strokeWidth: REAL ← 2.0*(IF circleData.strokeWidth=0.0 THEN 1.0 ELSE circleData.strokeWidth);
CircleDrawOutline[dc, slice, circleData, circleData.transform, strokeWidth, IF circleData.strokeColor#NIL THEN circleData.strokeColor ELSE Imager.black];
};
FOR index: CirclePoints IN [0..MaxCirclePoints) DO
thisCPisHot ← hotCircleParts#NIL AND hotCircleParts.cpArray[index];
thisCPisSelected ← normalCircleParts#NIL AND normalCircleParts.cpArray[index];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[index], hot];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[index], normal];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[index]];
ENDLOOP;
};
normalCircleParts, hotCircleParts: CircleParts;
circleData: CircleData ← NARROW[slice.data];
t: ImagerTransformation.Transformation ← circleData.transform;
IF caretIsMoving OR dragInProgress THEN RETURN;
IF selectedParts = NIL AND hotParts = NIL THEN RETURN;
normalCircleParts ← NARROW[selectedParts];
hotCircleParts ← NARROW[hotParts];
IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback];
};
cpHalf: REAL = GGModelTypes.halfJointSize + 1;
useCache: BOOLTRUE;
showBox: BOOLFALSE; -- draws bBox for every new cache entry. For debugging
CircleBoxFromTransform: PROC [circleData: CircleData, transform: Transformation] RETURNS [r: Imager.Rectangle] = INLINE {
pad: REALMAX[cpHalf, circleData.segment.strokeWidth];
origin: Point ← ImagerTransformation.Transform[transform, [0.0, 0.0]];
minor: Point ← ImagerTransformation.Transform[transform, [0.0, 1.0]]; -- p1
major: Point ← ImagerTransformation.Transform[transform, [1.0, 0.0]]; -- p0, p2
radius: REAL ← RealFns.SqRt[MAX[Vectors2d.DistanceSquared[minor, origin], Vectors2d.DistanceSquared[major, origin]]];
box: BoundBox← GGBoundBox.CreateBoundBox[origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius];
GGBoundBox.EnlargeByOffset[box, pad];
r ← GGBoundBox.RectangleFromBoundBox[box];
};
LookUpCircle: PROC [circleData: CircleData, transform: Transformation] RETURNS [object: Object] = {
EqualColor: PROC [c1, c2: Color] RETURNS [BOOL] ~ {
epsilon: REAL = 1.0E-3;
r1, g1, b1: REAL;
r2, g2, b2: REAL;
IF c1=NIL AND c2=NIL THEN RETURN[TRUE]; -- no color
IF c1=NIL AND c2#NIL THEN RETURN[FALSE]; -- no match
IF c1#NIL AND c2=NIL THEN RETURN[FALSE]; -- no match
[r1, g1, b1] ← GGUtility.ExtractRGB[c1];
[r2, g2, b2] ← GGUtility.ExtractRGB[c2];
RETURN[ABS[r1-r2]<epsilon AND ABS[g1-g2]<epsilon AND ABS[b1-b2]<epsilon ];
};
EqualPattern: PROC [p1, p2: SequenceOfReal] RETURNS [BOOL] = INLINE {
IF p1.len#p2.len THEN RETURN[FALSE];
FOR i: NAT IN [0.. p1.len) DO
IF p1[i]#p2[i] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
CompareProps: FunctionCache.CompareProc = {
CompareProc: TYPE ~ PROC [argument: Domain] RETURNS [good: BOOL];
props: Props ← NARROW[argument];
seg: Segment ← circleData.segment;
IF props.strokeWidth=seg.strokeWidth AND
props.strokeEnd=seg.strokeEnd AND
props.dashed=seg.dashed AND
props.offset=seg.offset AND
props.length=seg.length AND
ImagerTransformation.CloseToTranslation[props.transform, transform, maxPixels] AND
EqualColor[props.color, seg.color] AND
EqualColor[props.fillColor, circleData.fillColor] AND
(NOT props.dashed OR EqualPattern[props.pattern, seg.pattern])
THEN RETURN[TRUE] ELSE RETURN[FALSE];
};
value: FunctionCache.Range;
maxPixels: REAL ← 10000.0;
ok: BOOL;
[value, ok] ← FunctionCache.Lookup[x: cache, compare: CompareProps, clientID: $GGCircleObject];
RETURN [IF ok THEN NARROW[value] ELSE NIL];
};
CircleDrawOutline: PROC [dc: Imager.Context, circleData: CircleData, transform: ImagerTransformation.Transformation, cacheIt: BOOLFALSE] = {
object: Imager.Object;
IF NOT useCache THEN {
OldCircleDrawOutline[dc, circleData, transform];
RETURN;
};
object ← LookUpCircle[circleData, transform];
IF object = NIL THEN { -- put it in the cache
clipR: Imager.Rectangle ← CircleBoxFromTransform[circleData, transform];
seg: Segment ← circleData.segment;
props: Props ← NEW[PropsRec ← [
transform: ImagerTransformation.Copy[transform],
strokeWidth: seg.strokeWidth,
strokeEnd: round,
dashed: seg.dashed,
pattern: seg.pattern,
offset: seg.offset,
length: seg.length,
color: seg.color, -- strokeColor
fillColor: circleData.fillColor,
data: NIL] ];
IF showBox THEN {
Imager.SetGray[dc, 0.2];
Imager.MaskRectangle[dc, clipR];
};
object ← NEW[Imager.ObjectRep ← [draw: CircleDrawObject, clip: clipR, data: props]];
FunctionCache.Insert[cache, props, object, 60, $GGCircleObject];
};
BEGIN
CirclePath: Imager.PathProc = {
leftSide: Point ← [-1.0, 0.0];
rightSide: Point ← [1.0, 0.0];
moveTo[[leftSide.x, leftSide.y]];
arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]];
};
FillItIn: PROC = {
Imager.SetColor[dc, circleData.fillColor];
Imager.ConcatT[dc, transform];
Imager.MaskFill[dc, CirclePath, TRUE]
};
circleProps: Props ← NARROW[object.data];
position: Imager.VEC ← ImagerTransformation.Transform[transform, ImagerTransformation.InverseTransform[circleProps.transform, [0, 0]]];
Do colors here so that Imager Object bitmap cache can hit.
IF circleData.fillColor#NIL THEN Imager.DoSave[dc, FillItIn];
IF circleData.segment.color#NIL AND circleData.segment.strokeWidth#0.0 THEN {
Imager.SetColor[dc, circleData.segment.color];
Imager.DrawObject[context: dc, object: object, interactive: TRUE, position: position];
};
END;
};
CircleDrawObject: PROC [self: Imager.Object, context: Imager.Context] = {
Imager.Object.draw PROC
NOT called with the dc transformation already set to object coordinates
CirclePath: Imager.PathProc = {
moveTo[[leftSide.x, leftSide.y]];
arcTo[[rightSide.x, rightSide.y], [leftSide.x, leftSide.y]];
};
TransformedCirclePath: Imager.PathProc = {
ImagerPath.Transform[CirclePath, transform, moveTo, lineTo, curveTo, conicTo, arcTo];
};
circleProps: Props ← NARROW[self.data];
transform: Transformation ← circleProps.transform;
leftSide: Point ← [-1.0, 0.0];
rightSide: Point ← [1.0, 0.0];
IF circleProps.strokeWidth#0.0 THEN {
Imager.SetColor[context, circleProps.color]; -- MOVED OUTSIDE THIS PROC TO CALLER
Imager.SetStrokeWidth[context, circleProps.strokeWidth];
Imager.SetStrokeEnd[context, circleProps.strokeEnd]; -- needed to make object caching work
Imager.SetStrokeJoint[context, round]; -- needed to make object caching work
IF circleProps.dashed THEN {
PatternProc: PROC [i: NAT] RETURNS [REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ← circleProps.pattern;
Imager.MaskDashedStroke[context, TransformedCirclePath, pattern.len, PatternProc, circleProps.offset, circleProps.length];
}
ELSE Imager.MaskStroke[context, TransformedCirclePath, TRUE];
};
};
OldCircleDrawOutline: PROC [dc: Imager.Context, circleData: CircleData, transform: ImagerTransformation.Transformation] = {
NOT called with the dc transformation already set to object coordinates
CirclePath: Imager.PathProc = {
moveTo[[leftSide.x, leftSide.y]];
arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]];
};
TransformedCirclePath: Imager.PathProc = {
ImagerPath.Transform[CirclePath, transform, moveTo, lineTo, curveTo, conicTo, arcTo];
};
FillItIn: PROC = {
Imager.SetColor[dc, circleData.fillColor];
Imager.ConcatT[dc, transform];
Imager.MaskFill[dc, CirclePath, TRUE]
};
seg: Segment ← circleData.segment;
leftSide: Point ← [-1.0, 0.0];
rightSide: Point ← [1.0, 0.0];
IF circleData.fillColor#NIL THEN Imager.DoSaveAll[dc, FillItIn];
IF seg.color#NIL AND seg.strokeWidth#0.0 THEN {
Imager.SetColor[dc, seg.color];
Imager.SetStrokeWidth[dc, seg.strokeWidth];
IF seg.dashed THEN {
PatternProc: PROC [i: NAT] RETURNS [REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ← seg.pattern;
Imager.MaskDashedStroke[dc, TransformedCirclePath, pattern.len, PatternProc, seg.offset, seg.length];
}
ELSE Imager.MaskStroke[dc, TransformedCirclePath, TRUE];
};
};
CircleDrawTransform: PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = {
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.
OPEN ImagerTransformation;
originSelected: BOOLFALSE;
cpCount: INT ← 0;
cpIndex: INT ← -1;
edgePoint, pointInUnitSpace: Point;
newTransform: Transformation;
slice: Slice ← sliceD.slice;
circleData: CircleData ← NARROW[sliceD.slice.data];
circleParts: CircleParts ← NARROW[sliceD.parts];
IF circleParts.cpArray[0] THEN originSelected ← TRUE;
FOR index: CirclePoints IN [1..MaxCirclePoints) DO
IF circleParts.cpArray[index] THEN{
cpCount ← cpCount + 1;
cpIndex ← index;
};
ENDLOOP;
Drag the whole circle.
IF originSelected OR cpCount > 1 OR circleParts.outline THEN { -- treat as complete circle selected
CircleDrawOutline[dc, circleData, Concat[circleData.transform, transform], TRUE];
RETURN;
};
Rubberband the radius.
IF cpCount = 0 AND NOT originSelected THEN ERROR;
edgePoint ← Transform[Concat[circleData.transform, transform], SELECT cpIndex FROM
1 => [-1.0, 0.0],
2 => [0.0, 1.0],
3 => [1.0, 0.0],
4 => [0.0, -1.0],
ENDCASE => ERROR];
pointInUnitSpace ← Transform[circleData.inverse, edgePoint];
newTransform ← PreScale[circleData.transform, MAX[Vectors2d.Magnitude[pointInUnitSpace], circleLimit] ]; -- circles can't shrink to zero
CircleDrawOutline[dc, circleData, newTransform, FALSE];
};
Transforming
circleLimit: REAL ← 0.05; -- needed to prevent numerical instability of tiny circles
CircleTransform: PROC [sliceD: SliceDescriptor, transform: ImagerTransformation.Transformation] = {
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[sliceD.slice.data];
circleParts: CircleParts ← NARROW[sliceD.parts];
IF circleParts.cpArray[0] THEN isOrigin ← TRUE;
FOR index: CirclePoints IN [1..MaxCirclePoints) DO
IF circleParts.cpArray[index] THEN{
cpCount ← cpCount + 1;
cpIndex ← index;
};
ENDLOOP;
IF isOrigin OR cpCount > 1 THEN { -- treat as though complete circle is selected
circleData.transform ← ImagerTransformation.Concat[circleData.transform, transform];
circleData.scale ← ImagerTransformation.SingularValues[circleData.transform];
circleData.inverse ← ImagerTransformation.Invert[circleData.transform];
circleData.inverseScale ← ImagerTransformation.SingularValues[circleData.inverse];
CircleSetBoundBox[sliceD.slice];
RETURN;
};
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
circleData.scale ← ImagerTransformation.SingularValues[circleData.transform];
circleData.inverse ← ImagerTransformation.Invert[circleData.transform];
circleData.inverseScale ← ImagerTransformation.SingularValues[circleData.inverse];
CircleSetBoundBox[sliceD.slice];
};
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.segment.strokeWidth]] ];
f.PutRope[" strokeColor: "];
GGParseOut.WriteColor[f, circleData.segment.color];
f.PutRope[" fillColor: "];
GGParseOut.WriteColor[f, circleData.fillColor];
f.PutRope[" dashes: ( "];
GGParseOut.WriteBOOL[f, circleData.segment.dashed]; f.PutChar[IO.SP];
IF circleData.segment.dashed THEN {
GGParseOut.WriteArrayOfReal[f, circleData.segment.pattern];
f.PutF[" %g %g ", [real[circleData.segment.offset]], [real[circleData.segment.length]]];
};
f.PutRope[") "];
};
CircleFilein: PROC [f: IO.STREAM, version: REAL, feedback: FeedbackData] RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
circleData: CircleData;
origin, outerPoint: Point;
transform: ImagerTransformation.Transformation;
strokeWidth: REAL;
strokeColor, fillColor: Color;
transform ← GGParseIn.ReadTransformation[f];
origin ← ImagerTransformation.Transform[m: transform, v: [0.0, 0.0] ];
outerPoint ← ImagerTransformation.Transform[m: transform, v: [0.0, 1.0] ];
GGParseIn.ReadBlankAndRope[f, "strokeWidth:"];
strokeWidth ← GGParseIn.ReadBlankAndReal[f];
GGParseIn.ReadBlankAndRope[f, "strokeColor:"];
strokeColor ← GGParseIn.ReadColor[f, version];
GGParseIn.ReadBlankAndRope[f, "fillColor:"];
fillColor ← GGParseIn.ReadColor[f, version];
slice ← MakeCircleSlice[origin, outerPoint, 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.segment.strokeWidth ← strokeWidth;
circleData.segment.color ← strokeColor;
circleData.fillColor ← fillColor;
IF version>=8704.03 THEN { -- read in dash patterns
GGParseIn.ReadBlankAndRope[f, "dashes: ("];
circleData.segment.dashed ← GGParseIn.ReadBOOL[f, version].truth;
IF circleData.segment.dashed THEN {
circleData.segment.pattern ← GGParseIn.ReadArrayOfReal[f];
circleData.segment.offset ← GGParseIn.ReadBlankAndReal[f];
circleData.segment.length ← GGParseIn.ReadBlankAndReal[f];
};
GGParseIn.ReadBlankAndRope[f, ")"];
};
CircleSetBoundBox[slice];
};
Hit Testing
CircleIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
GGModelTypes.SliceEmptyPartsProc
circleParts: CircleParts ← NARROW[sliceD.parts];
RETURN[IsEmpty[circleParts] ];
};
CircleIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
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] 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] = {
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;
};
CirclePointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = {
parts: CircleParts ← NARROW[sliceD.parts];
pointGen ← NEW[PointGeneratorObj ← [sliceD, 0, 0, NIL] ];
FOR index: CirclePoints IN CirclePoints DO
IF parts.cpArray[index] THEN pointGen.toGo ← pointGen.toGo + 1;
ENDLOOP;
};
CircleSegmentsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = {
parts: CircleParts ← NARROW[sliceD.parts];
segGen ← NEW[SegmentGeneratorObj ← [traj: NIL, toGo: 0, index: 0, touched: 0, seq: NIL, completeSeq: FALSE, sliceD: sliceD] ];
segGen.toGo ← IF parts.outline THEN 1 ELSE 0;
};
CircleWalkSegments: PROC [slice: Slice, walkProc: WalkProc] RETURNS [sliceD: SliceDescriptor] = {
GGModelTypes.SliceWalkSegmentsProc
circleData: CircleData ← NARROW[slice.data];
circleParts: CircleParts ← NEW[CirclePartsObj ← [cpArray: ALL[FALSE], outline: FALSE]];
circleParts.outline ← walkProc[circleData.segment, circleData.transform];
sliceD ← NEW[SliceDescriptorObj ← [slice, circleParts] ];
};
CircleNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
sliceD: SliceDescriptor ← pointGen.sliceD;
slice: Slice ← sliceD.slice;
circleData: CircleData ← NARROW[slice.data];
t: ImagerTransformation.Transformation ← circleData.transform;
IF pointGen=NIL OR pointGen.toGo = 0 THEN {
pointAndDone.done ← TRUE;
RETURN;
}
ELSE {
parts: CircleParts ← NARROW[sliceD.parts];
index: NAT;
pointAndDone.done ← FALSE;
FOR index ← pointGen.index, index+1 UNTIL index>=LAST[CirclePoints] DO
IF parts.cpArray[index] THEN EXIT; -- find the next included point
ENDLOOP;
SELECT index FROM
0 => pointAndDone.point ← ImagerTransformation.Transform[t, [0.0, 0.0]]; -- origin
1 => pointAndDone.point ← ImagerTransformation.Transform[t, [-1.0, 0.0]]; -- left
2 => pointAndDone.point ← ImagerTransformation.Transform[t, [0.0, 1.0]]; -- top
3 => pointAndDone.point ← ImagerTransformation.Transform[t, [1.0, 0.0]]; -- right
4 => pointAndDone.point ← ImagerTransformation.Transform[t, [0.0, -1.0]]; -- bottom
ENDCASE => SIGNAL Feedback.Problem[msg: "Broken Invariant"];
pointGen.index ← index+1;
pointGen.toGo ← pointGen.toGo - 1;
};
};
CircleNextSegment: PROC [segGen: SegmentGenerator] RETURNS [seg: Segment, transform: Transformation] = {
IF segGen=NIL OR segGen.toGo = 0 THEN RETURN[NIL, NIL]
ELSE {
circleData: CircleData ← NARROW[segGen.sliceD.slice.data];
IF segGen.toGo#1 THEN SIGNAL Feedback.Problem[msg: "Broken Invariant"];
seg ← circleData.segment;
transform ← circleData.transform;
segGen.toGo ← segGen.toGo-1;
};
};
CircleClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
points: ARRAY[0..4] OF Point; -- points in Gargoyle coordinates
nextDist2, bestDist2: REAL ← GGUtility.plusInfinity;
tolerance2: REAL ← tolerance*tolerance;
foundIndex: INTEGER ← 0;
circleData: CircleData ← NARROW[sliceD.slice.data];
circleHitData: CircleHitData;
circleParts: CircleParts ← NARROW[sliceD.parts];
nextPoint: Point;
points ← CirclePointsFromData[circleData];
FOR index: INTEGER IN [0..MaxCirclePoints) DO
IF NOT circleParts.cpArray[index] THEN LOOP;
nextPoint ← points[index];
nextDist2 ← Vectors2d.DistanceSquared[testPoint, nextPoint];
IF nextDist2 < bestDist2 THEN {
bestDist2 ← nextDist2;
foundIndex ← index;
bestPoint ← nextPoint;
IF bestDist2 < tolerance2 THEN success ← TRUE;
};
ENDLOOP;
IF success THEN {
hitData ← circleHitData ← NEW[CircleHitDataObj ← [index: foundIndex, hitPoint: bestPoint, outline: FALSE] ];
bestDist ← RealFns.SqRt[bestDist2];
};
};
};
CircleClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
circleHitData: CircleHitData;
circleParts: CircleParts ← NARROW[sliceD.parts];
circleData: CircleData ← NARROW[sliceD.slice.data];
pointOnCircle: Point ← [0.0, 0.0];
localTestpoint: Point;
dist: REAL;
IF NOT circleParts.outline THEN RETURN[[0,0], 0.0, NIL, FALSE];
localTestpoint ← ImagerTransformation.Transform[circleData.inverse, testPoint];
dist ← Vectors2d.Magnitude[localTestpoint];
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;
};
};
};
CircleLineIntersection: PUBLIC PROC [sliceD: SliceDescriptor, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT] = {
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];
points ← NIL;
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;
};
PointIsInBox: PRIVATE PROC [test: Point, box: GGBasicTypes.BoundBoxObj] RETURNS [BOOL] = {
RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ];
};
CirclePointsFromData: PROC [circleData: CircleData] RETURNS [points: ARRAY [0..4] OF Point] = {
origin, left, top, right, bottom
t: ImagerTransformation.Transformation ← circleData.transform;
points[0] ← ImagerTransformation.Transform[t, [0.0, 0.0] ];
points[1] ← ImagerTransformation.Transform[t, [-1.0, 0.0] ];
points[2] ← ImagerTransformation.Transform[t, [0.0, 1.0] ];
points[3] ← ImagerTransformation.Transform[t, [1.0, 0.0] ];
points[4] ← ImagerTransformation.Transform[t, [0.0, -1.0] ];
};
Style
CircleSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData] = {
circleData: CircleData ← NARROW[slice.data];
GGSegment.SetDefaults[circleData.segment, defaults];
circleData.fillColor ← defaults.fillColor;
CircleSetBoundBox[slice];
};
CircleSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] RETURNS [box: BoundBox] = {
circleData: CircleData ← NARROW[slice.data];
circleData.segment.strokeWidth ← strokeWidth;
CircleSetBoundBox[slice];
RETURN[slice.boundBox];
};
CircleGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = {
circleData: CircleData ← NARROW[slice.data];
strokeWidth ← circleData.segment.strokeWidth;
};
CircleSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Color] = {
circleData: CircleData ← NARROW[slice.data];
circleData.segment.color ← color;
};
CircleGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color] = {
circleData: CircleData ← NARROW[slice.data];
color ← circleData.segment.color;
};
CircleSetFillColor: PROC [slice: Slice, color: Color] = {
circleData: CircleData ← NARROW[slice.data];
circleData.fillColor ← color;
};
CircleGetFillColor: PROC [slice: Slice] RETURNS [color: Color] = {
circleData: CircleData ← NARROW[slice.data];
color ← circleData.fillColor;
};
CircleSetDashed: PROC [slice: Slice, parts: SliceParts, dashed: BOOL ← FALSE, pattern: SequenceOfReal ← NIL, offset: REAL ← 0.0, length: REAL ← -1.0] = {
circleParts: CircleParts ← NARROW[parts];
circleData: CircleData ← NARROW[slice.data];
IF circleParts.outline THEN {
seg: Segment ← circleData.segment;
seg.dashed ← dashed;
seg.pattern ← pattern;
seg.offset ← offset;
seg.length ← length;
};
};
CircleGetDashed: PROC [slice: Slice, parts: SliceParts] RETURNS [dashed: BOOL ← FALSE, pattern: SequenceOfReal, offset, length: REAL] = {
circleParts: CircleParts ← NARROW[parts];
circleData: CircleData ← NARROW[slice.data];
IF circleParts.outline THEN {
seg: Segment ← circleData.segment;
dashed ← seg.dashed;
pattern ← seg.pattern;
offset ← seg.offset;
length ← seg.length;
};
};
Utilities
MakeComplete: PROC [circleParts: CircleParts] = {
circleParts.cpArray ← ALL[TRUE];
circleParts.outline ← TRUE;
};
IsComplete: PROC [circleParts: CircleParts] RETURNS [BOOL] = {
IF circleParts = NIL THEN RETURN[FALSE];
RETURN[ circleParts.cpArray=ALL[TRUE] AND circleParts.outline];
};
AllEdges: PROC [circleParts: CircleParts] RETURNS [BOOL] = {
RETURN[circleParts.outline];
};
IsEmpty: PROC [circleParts: CircleParts] RETURNS [BOOL] = {
RETURN[circleParts.cpArray=ALL[FALSE] AND circleParts.outline=FALSE];
};
Init: PROC [] = {
cache ← FunctionCache.Create[maxEntries: 100];
};
cache: FunctionCache.Cache;
NoOp Class Routines
Fundamentals
NoOpBoundBox: PUBLIC SliceBoundBoxProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpTightBox: PUBLIC SliceTightBoxProc = {
box ← NIL;
};
NoOpCopy: PUBLIC SliceCopyProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
Drawing
NoOpDrawParts: PUBLIC SliceDrawPartsProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpDrawTransform: PUBLIC SliceDrawTransformProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpDrawSelectionFeedback: PUBLIC SliceDrawSelectionFeedbackProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpDrawAttractorFeedback: PUBLIC SliceDrawAttractorFeedbackProc = {
slice: Slice ← sliceD.slice;
pointGen: GGModelTypes.PointGenerator;
IF dragInProgress AND selectedParts # NIL THEN {}
ELSE {
pointGen ← slice.class.pointsInDescriptor[sliceD];
FOR pointAndDone: GGModelTypes.PointAndDone ← slice.class.nextPoint[pointGen], slice.class.nextPoint[pointGen] UNTIL pointAndDone.done DO
GGShapes.DrawCP[dc, pointAndDone.point];
ENDLOOP;
};
};
Transforming
NoOpTransform: PUBLIC SliceTransformProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
Textual Description
NoOpDescribe: PUBLIC SliceDescribeProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpDescribeHit: PUBLIC SliceDescribeHitProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpFileout: PUBLIC SliceFileoutProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpFilein: PUBLIC SliceFileinProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
Parts
NoOpIsEmptyParts: PUBLIC SliceIsEmptyPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
RETURN[TRUE];
};
NoOpIsCompleteParts: PUBLIC SliceIsCompletePartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
RETURN[FALSE];
};
NoOpNewParts: PUBLIC SliceNewPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpUnionParts: PUBLIC SliceUnionPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpDifferenceParts: PUBLIC SliceDifferencePartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpMovingParts: PUBLIC SliceMovingPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpAugmentParts: PUBLIC SliceAugmentPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpSetSelectedFields: PUBLIC SliceSetSelectedFieldsProc = {
};
NoOpPointsInDescriptor: PUBLIC SlicePointsInDescriptorProc = {
pointGen ← NIL;
};
NoOpPointPairsInDescriptor: PUBLIC SlicePointPairsInDescriptorProc = {
pointPairGen ← NIL;
};
NoOpSegmentsInDescriptor: PUBLIC SliceSegmentsInDescriptorProc = {
segGen ← NIL;
};
NoOpWalkSegments: PUBLIC SliceWalkSegmentsProc = {
sliceD ← slice.class.newParts[slice, NIL, none];
};
NoOpNextPoint: PUBLIC SliceNextPointProc = {
pointAndDone.done ← TRUE;
};
NoOpNextPointPair: PUBLIC SliceNextPointPairProc = {
pointPairAndDone.done ← TRUE;
};
NoOpNextSegment: PUBLIC SliceNextSegmentProc = {
seg ← NIL;
};
NoOpClosestPoint: PUBLIC SliceClosestPointProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpClosestJointToHitData: PUBLIC SliceClosestJointToHitDataProc = {
[point, ----, ----, ----] ← sliceD.slice.class.closestPoint[sliceD, mapPoint, GGUtility.plusInfinity];
jointD ← sliceD;
};
NoOpClosestPointAndTangent: PUBLIC SliceClosestPointAndTangentProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpClosestSegment: PUBLIC SliceClosestSegmentProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpLineIntersection: PUBLIC SliceLineIntersectionProc = {
points ← NIL; pointCount ← 0;
};
NoOpCircleIntersection: PUBLIC SliceCircleIntersectionProc = {
points ← NIL; pointCount ← 0;
};
NoOpHitDataAsSimpleCurve: PUBLIC SliceHitDataAsSimpleCurveProc = {
simpleCurve ← NIL;
};
Style
NoOpSetDefaults: PUBLIC SliceSetDefaultsProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetStrokeWidth: PUBLIC SliceSetStrokeWidthProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetStrokeWidth: PUBLIC SliceGetStrokeWidthProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetStrokeEnd: PUBLIC SliceSetStrokeEndProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetStrokeEnd: PUBLIC SliceGetStrokeEndProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetStrokeJoint: PUBLIC SliceSetStrokeJointProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetStrokeJoint: PUBLIC SliceGetStrokeJointProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetStrokeColor: PUBLIC SliceSetStrokeColorProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpGetStrokeColor: PUBLIC SliceGetStrokeColorProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpSetFillColor: PUBLIC SliceSetFillColorProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetFillColor: PUBLIC SliceGetFillColorProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetArrows: PUBLIC SliceSetArrowsProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetArrows: PUBLIC SliceGetArrowsProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetDashed: PUBLIC SliceSetDashedProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetDashed: PUBLIC SliceGetDashedProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
Init[];
END.