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: BOOL ← FALSE, -- 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
emptyParts: CircleEmptyParts,
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
.startPoint
← outerPoint;
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];
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: REAL ← NARROW[slice.data, CircleData].segment.strokeWidth;
pad: REAL ← MAX[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;
};
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: BOOL ← TRUE;
showBox: BOOL ← FALSE; -- draws bBox for every new cache entry. For debugging
CircleBoxFromTransform:
PROC [circleData: CircleData, transform: Transformation]
RETURNS [r: Imager.Rectangle] =
INLINE {
pad: REAL ← MAX[cpHalf, circleData.segment.strokeWidth];
origin: Point ← ImagerTransformation.Transform[transform, [0.0, 0.0]];
minor: Point ← ImagerTransformation.Transform[transform, [0.0, 1.0]]; -- p1
major: Point ← ImagerTransformation.Transform[transform, [1.0, 0.0]]; -- p0, p2
radius: REAL ← RealFns.SqRt[MAX[Vectors2d.DistanceSquared[minor, origin], Vectors2d.DistanceSquared[major, origin]]];
box: BoundBox← GGBoundBox.CreateBoundBox[origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius];
GGBoundBox.EnlargeByOffset[box, pad];
r ← GGBoundBox.RectangleFromBoundBox[box];
};
LookUpCircle:
PROC [circleData: CircleData, transform: Transformation]
RETURNS [object: Object] = {
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:
BOOL ←
FALSE] = {
object: Imager.Object;
IF
NOT useCache
THEN {
OldCircleDrawOutline[dc, circleData, transform];
RETURN;
};
object ← LookUpCircle[circleData, transform];
IF object =
NIL
THEN {
-- put it in the cache
clipR: Imager.Rectangle ← CircleBoxFromTransform[circleData, transform];
seg: Segment ← circleData.segment;
props: Props ←
NEW[PropsRec ← [
transform: ImagerTransformation.Copy[transform],
strokeWidth: seg.strokeWidth,
strokeEnd: round,
dashed: seg.dashed,
pattern: seg.pattern,
offset: seg.offset,
length: seg.length,
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: BOOL ← FALSE;
cpCount: INT ← 0;
cpIndex: INT ← -1;
edgePoint, pointInUnitSpace: Point;
newTransform: Transformation;
slice: Slice ← sliceD.slice;
circleData: CircleData ← NARROW[sliceD.slice.data];
circleParts: CircleParts ← NARROW[sliceD.parts];
IF circleParts.cpArray[0] THEN originSelected ← TRUE;
FOR index: CirclePoints
IN [1..MaxCirclePoints)
DO
IF circleParts.cpArray[index]
THEN{
cpCount ← cpCount + 1;
cpIndex ← index;
};
ENDLOOP;
Drag the whole circle.
IF originSelected
OR cpCount > 1
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: BOOL ← FALSE;
cpCount: INT ← 0;
cpIndex: INT ← -1;
originPoint, edgePoint, pointInUnitSpace: Point;
circleData: CircleData ← NARROW[sliceD.slice.data];
circleParts: CircleParts ← NARROW[sliceD.parts];
IF circleParts.cpArray[0] THEN isOrigin ← TRUE;
FOR index: CirclePoints
IN [1..MaxCirclePoints)
DO
IF circleParts.cpArray[index]
THEN{
cpCount ← cpCount + 1;
cpIndex ← index;
};
ENDLOOP;
IF isOrigin
OR cpCount > 1
THEN {
-- treat as though complete circle is selected
circleData.transform ← ImagerTransformation.Concat[circleData.transform, transform];
circleData.scale ← ImagerTransformation.SingularValues[circleData.transform];
circleData.inverse ← ImagerTransformation.Invert[circleData.transform];
circleData.inverseScale ← ImagerTransformation.SingularValues[circleData.inverse];
CircleSetBoundBox[sliceD.slice];
RETURN;
};
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
CircleEmptyParts:
PROC [sliceD: SliceDescriptor]
RETURNS [
BOOL] = {
GGModelTypes.SliceEmptyPartsProc
circleParts: CircleParts ← NARROW[sliceD.parts];
RETURN[IsEmpty[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:
BOOL ←
FALSE] = {
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 nextDist
2 < bestDist
2
THEN {
bestDist2 ← nextDist2;
foundIndex ← index;
bestPoint ← nextPoint;
IF bestDist2 < tolerance2 THEN success ← TRUE;
};
ENDLOOP;
IF success
THEN {
hitData ← circleHitData ← NEW[CircleHitDataObj ← [index: foundIndex, hitPoint: bestPoint, outline: FALSE] ];
bestDist ← RealFns.SqRt[bestDist2];
};
};
};
CircleClosestSegment:
PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point, bestDist:
REAL, hitData:
REF
ANY, success:
BOOL ←
FALSE] = {
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
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
NoOpEmptyParts:
PUBLIC SliceEmptyPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
RETURN[TRUE];
};
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.