GGSliceImplC.mesa
Copyright Ó 1987, 1988, 1991, 1992 by Xerox Corporation. All rights reserved.
Contents: Implements the Circle slice class and the NoOp procedures for slices (see also GGSliceImplF for more NoOps).
Kurlander, July 17, 1987 11:55:38 am PDT
Eisenman, September 28, 1987 5:36:24 pm PDT
Pier, June 22, 1992 5:01 pm PDT
Bier, December 3, 1992 5:29 pm PST
Doug Wyatt, April 14, 1992 2:33 pm PDT
DIRECTORY
Feedback, FeedbackTypes, FunctionCache, GGBasicTypes, GGBoundBox, GGCircles, GGCoreOps, GGCoreTypes, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGMUserProfile, GGParseIn, GGParseOut, GGProps, GGSegment, GGSegmentTypes, GGShapes, GGSlice, GGSliceOps, GGUtility, Imager, ImagerColor, ImagerPath, ImagerTransformation, IO, Lines2d, NodeStyle, RealFns, Rope, Vectors2d;
GGSliceImplC:
CEDAR
PROGRAM
IMPORTS Feedback, FunctionCache, GGBoundBox, GGCircles, GGCoreOps, GGMUserProfile, GGParseIn, GGParseOut, GGProps, GGSegment, GGShapes, GGSlice, GGSliceOps, GGUtility, Imager, ImagerPath, ImagerTransformation, IO, Lines2d, RealFns, Rope, Vectors2d
EXPORTS GGSlice = BEGIN
BoundBox: TYPE = REF BoundBoxObj;
BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj;
Camera: TYPE = GGModelTypes.Camera;
Circle: TYPE = GGModelTypes.Circle;
Color: TYPE = Imager.Color;
DefaultData: TYPE = GGModelTypes.DefaultData;
EditConstraints: TYPE = GGModelTypes.EditConstraints;
MsgRouter: TYPE = FeedbackTypes.MsgRouter;
HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent;
Line: TYPE = GGCoreTypes.Line;
Object: TYPE = Imager.Object;
Orientation: TYPE = GGModelTypes.Orientation;
Point: TYPE = GGModelTypes.Point;
PointAndDone: TYPE = GGModelTypes.PointAndDone;
PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj;
PointWalkProc: TYPE = GGModelTypes.PointWalkProc;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGModelTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj;
SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData;
SelectionClass: TYPE = GGModelTypes.SelectionClass;
SelectMode: TYPE = GGModelTypes.SelectMode;
SequenceOfReal: TYPE = GGCoreTypes.SequenceOfReal;
Slice: TYPE = GGModelTypes.Slice;
SliceBoundBoxProc: TYPE = GGModelTypes.SliceBoundBoxProc;
SliceClass: TYPE = GGModelTypes.SliceClass;
SliceClassObj: TYPE = GGModelTypes.SliceClassObj;
SliceObj: TYPE = GGModelTypes.SliceObj;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceParts: TYPE = GGModelTypes.SliceParts;
StrokeEnd: TYPE = GGModelTypes.StrokeEnd;
Transformation: TYPE = GGModelTypes.Transformation;
Vector: TYPE = GGModelTypes.Vector;
WalkProc: TYPE = GGModelTypes.WalkProc;
MoveToProc: TYPE = ImagerPath.MoveToProc;
LineToProc: TYPE = ImagerPath.LineToProc;
CurveToProc: TYPE = ImagerPath.CurveToProc;
ConicToProc: TYPE = ImagerPath.ConicToProc;
ArcToProc: TYPE = ImagerPath.ArcToProc;
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
Circle Slice Class
CircleData: TYPE = REF CircleDataObj;
CircleDataObj:
TYPE =
RECORD [
transform needed for general case of rotated circles
circle: Circle, -- a representation of a unit circle (for the convenience of GGCircles.LineMeetsCircle)
transform: Transformation,
scale: Vector, -- cached value of ImagerTransformation.Factor[transform].s
evenScaling: BOOL,
simpleCircle: Circle, -- for CircleHitDataAsSimpleCurve
inverse: Transformation,
inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s
startPoint: Point, -- original point on circumference when circle was created
fillColor: Color,
forward: BOOL ¬ TRUE, -- that is, should be traversed in the normal cw order
savedPointSelections: ARRAY CirclePoints OF SelectedObjectData, -- in order, origin, left, top, right, bottom
seg: Segment
];
maxCirclePoints: INTEGER = 5;
CirclePoints: TYPE = [0..maxCirclePoints); -- origin, left, top, right, bottom
CircleParts: TYPE = REF CirclePartsObj;
CirclePartsObj:
TYPE =
RECORD [
cpArray:
ARRAY CirclePoints
OF
BOOL,
-- used when origin or CPs are selected
cpArray[origin, left, top, right, bottom]. 5 element array.
outline: BOOL -- TRUE if outline itself is in parts
];
CircleHitData: TYPE = REF CircleHitDataObj;
CircleHitDataObj:
TYPE =
RECORD [
index: [-1..maxCirclePoints), -- records index of which origin or CP is hit. -1 => no hit
hitPoint: Point,
outline: BOOL -- TRUE if outline itself is hit
];
Props: TYPE = REF PropsRec;
PropsRec:
TYPE =
RECORD [
localView: Transformation,
strokeWidth: REAL ¬ 0.0,
strokeEnd: StrokeEnd ¬ round,
dashed: BOOL ¬ FALSE, -- dashed stroke properties
pattern: SequenceOfReal,
offset: REAL ¬ 0.0,
length: REAL ¬ 0.0,
color: Imager.Color,
fillColor: Imager.Color,
fillIsSampled: BOOL ¬ FALSE,
fillLocal: Transformation
];
BuildCircleSliceClass:
PUBLIC
PROC []
RETURNS [class: SliceClass] = {
OPEN GGSlice;
class ¬
NEW[SliceClassObj ¬ [
type: $Circle,
unlink: GGSlice.UnlinkSlice,
-- circle data doesn't have links
Fundamentals
getBoundBox: CircleGetBoundBox,
getTransformedBoundBox: GGSlice.GenericTransformedBoundBox,
getTightBox: CircleGetTightBox,
copy: CircleCopy,
restore: CircleRestore,
Drawing
buildPath: CircleBuildPath,
drawBorder: CircleDrawBorder,
drawParts: CircleDrawParts,
drawTransform: CircleDrawTransform,
drawSelectionFeedback: CircleDrawSelectionFeedback,
drawAttractorFeedback: CircleDrawAttractorFeedback,
saveSelections: CircleSaveSelections,
remakeSelections: CircleRemakeSelections,
Transforming
transform: CircleTransform,
Textual Description
describe: CircleDescribe,
describeHit: CircleDescribeHit,
fileout: CircleFileout,
filein: CircleFilein,
Parts
isEmptyParts: CircleIsEmptyParts,
isCompleteParts: CircleIsCompleteParts,
newParts: CircleNewParts,
unionParts: CircleUnionParts,
differenceParts: CircleDiffParts,
movingParts: CircleMovingParts,
augmentParts: CircleAugmentParts,
alterParts: CircleAlterParts,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: CirclePointsInDescriptor,
walkPointsInDescriptor: CircleWalkPointsInDescriptor,
pointPairsInDescriptor: NoOpPointPairsInDescriptor,
segmentsInDescriptor: CircleSegmentsInDescriptor,
walkSegments: CircleWalkSegments,
nextPoint: CircleNextPoint,
nextPointPair: NoOpNextPointPair,
nextSegment: CircleNextSegment,
Hit Testing
closestPoint: CircleClosestPoint,
closestJointToHitData: CircleClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: CircleClosestSegment,
filledPathsUnderPoint: CircleFilledPathsUnderPoint,
lineIntersection: CircleLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: CircleHitDataAsSimpleCurve,
Style
setDefaults: CircleSetDefaults,
setStrokeWidth: CircleSetStrokeWidth,
getStrokeWidth: CircleGetStrokeWidth,
setStrokeEnd: CircleSetStrokeEnd,
getStrokeEnd: CircleGetStrokeEnd,
setStrokeJoint: NoOpSetStrokeJoint,
getStrokeJoint: NoOpGetStrokeJoint,
setStrokeColor: CircleSetStrokeColor,
getStrokeColor: CircleGetStrokeColor,
setFillColor: CircleSetFillColor,
getFillColor: CircleGetFillColor,
setArrows: NoOpSetArrows,
getArrows: NoOpGetArrows,
setDashed: CircleSetDashed,
getDashed: CircleGetDashed,
setOrientation: CircleSetOrientation,
getOrientation: CircleGetOrientation
]];
};
MakeCircleSlice:
PUBLIC
PROC [origin: Point, outerPoint: Point]
RETURNS [sliceD: SliceDescriptor] = {
circleData: CircleData ¬ NEW[CircleDataObj];
circleParts: CircleParts ¬ NEW[CirclePartsObj ¬ [ALL[FALSE], FALSE ]];
slice: Slice;
circleData.circle ¬ GGCircles.CircleFromPointAndRadius[[0,0], 1.0];
circleData.tightBox ← GGBoundBox.NullBoundBox[];
circleData.startPoint ¬ outerPoint;
N.B. arc segments don't represent a shape. Only for style.
circleData.seg ¬ GGSegment.MakeArc[[-1.0, 0.0], [1.0, 1.0], [-1.0, 0.0],
NIL];
For convenience, we store the origin and outerPoint in the segment points. This is to make WalkProc answer NO to the question: Are You Degenerate ?
circleData.seg.lo ¬ origin;
circleData.seg.hi ¬ outerPoint;
circleData.seg.strokeWidth ¬ 2.0;
circleData.seg.color ¬ Imager.black;
circleData.fillColor ← fillColor;
slice ¬
NEW[SliceObj ¬ [
class: GGSlice.FetchSliceClass[$Circle],
data: circleData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
tightBox: GGBoundBox.NullBoundBox[],
boundBox: GGBoundBox.NullBoundBox[],
boxValid: FALSE
]];
slice.nullDescriptor ¬ GGSlice.DescriptorFromParts[slice, NIL];
IF origin=outerPoint THEN circleData.startPoint ¬ [origin.x+20.0, origin.y+20.0]; -- prevent degenerate circle
circleData.transform ¬ ImagerTransformation.PreScale[ImagerTransformation.Translate[origin], Vectors2d.Distance[origin, circleData.startPoint]];
circleData.scale ¬ ImagerTransformation.SingularValues[circleData.transform];
circleData.inverse ¬ ImagerTransformation.Invert[circleData.transform];
circleData.inverseScale ¬ ImagerTransformation.SingularValues[circleData.inverse];
circleData.evenScaling ¬ TRUE;
circleData.simpleCircle ¬ GGCircles.CircleFromPointAndRadius[origin, Vectors2d.Distance[origin, circleData.startPoint]];
CircleSetBoundBox[slice]; -- not necessary since boxValid is FALSE
sliceD ¬ GGSlice.DescriptorFromParts[slice, circleParts];
};
CircleGetParams:
PUBLIC
PROC [slice: Slice]
RETURNS [origin: Point, outerPoint: Point, transform: Transformation] = {
data: CircleData ¬ NARROW[slice.data];
origin ¬ data.seg.lo;
outerPoint ¬ data.seg.hi;
transform ¬ data.transform;
};
CircleGetBoundBoxAux:
PROC [slice: Slice, parts: SliceParts]
RETURNS [tightBox, boundBox: BoundBox] = {
CircleFindBoundBox:
PROC [slice: Slice, parts: SliceParts]
RETURNS [tightBox, boundBox: BoundBox] = {
strokeWidth: REAL ¬ NARROW[slice.data, CircleData].seg.strokeWidth;
pad: REAL ¬ strokeWidth;
circleData: CircleData ¬ NARROW[slice.data];
epsilon: REAL = 1.0E-6;
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]]];
minorDS: REAL ¬ Vectors2d.DistanceSquared[minor, origin];
majorDS: REAL ¬ Vectors2d.DistanceSquared[major, origin];
tMax: REAL ¬ MAX[minorDS, majorDS];
radius: REAL ¬ RealFns.SqRt[tMax];
tightBox ¬ GGBoundBox.CreateBoundBox[origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius];
boundBox ¬ GGBoundBox.CopyBoundBox[tightBox];
GGBoundBox.EnlargeByOffset[boundBox, pad];
CircleSetScaling[slice];
};
[tightBox, boundBox] ¬ CircleFindBoundBox[slice, parts]; -- do the bound box update
IF parts=
NIL
THEN {
-- set up cache for fast case next time around
GGSlice.KillBoundBox[slice.parent]; -- invalidate ancestor caches
slice.boundBox ¬ boundBox;
slice.tightBox ¬ tightBox;
slice.boxValid ¬ TRUE;
slice.tightBoxValid ¬ TRUE;
};
};
CircleGetBoundBox:
PROC [slice: Slice, parts: SliceParts]
RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
IF slice.boxValid AND parts=NIL THEN RETURN[slice.boundBox]; -- fast case
RETURN[CircleGetBoundBoxAux[slice, parts].boundBox]; -- cache update if possible
};
CircleGetTightBox:
PROC [slice: Slice, parts: SliceParts]
RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
IF slice.boxValid AND parts=NIL THEN RETURN[slice.tightBox]; -- fast case
RETURN[CircleGetBoundBoxAux[slice, parts].tightBox]; -- cache update if possible
};
CircleSetScaling:
PROC [slice: Slice] = {
epsilon: REAL = 1.0E-6;
circleData: CircleData ¬ NARROW[slice.data];
origin: Point ¬ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]];
major: Point ¬ ImagerTransformation.Transform[circleData.transform, [1.0, 0.0]];
IF
ABS[circleData.inverseScale.x - circleData.inverseScale.y] < epsilon
THEN {
GGCircles.FillCircleFromPointAndRadius[origin, circleData.scale.x, circleData.simpleCircle];
circleData.evenScaling ¬ TRUE;
}
ELSE circleData.evenScaling ¬
FALSE;
This added to make WalkProc answer NO to the question: Are You Degenerate ?
circleData.seg.lo ¬ origin;
circleData.seg.hi ¬ major;
};
CircleCopy:
PROC [slice: Slice, parts: SliceParts ¬
NIL]
RETURNS [copy:
LIST
OF Slice] = {
GGModelTypes.SliceCopyProc
Just ignore parts and copy the whole circle.
copySlice: Slice;
circleData: CircleData ¬ NARROW[slice.data];
newData: CircleData;
copyD: SliceDescriptor ¬ MakeCircleSlice[[0.0, 0.0], [1.0, 1.0]];
copySlice ¬ copyD.slice;
copy transform and inverse to the new structure (interface to makecircle requires origin and radius, which is inadequate since "circle" could actually have been transformed into an ellipse. Thus we passed a meaningless origin and radius).
newData ¬ NARROW[copySlice.data];
newData.transform ¬ ImagerTransformation.Copy[circleData.transform];
newData.scale ¬ circleData.scale;
newData.inverse ¬ ImagerTransformation.Copy[circleData.inverse];
newData.inverseScale ¬ circleData.inverseScale;
newData.evenScaling ¬ circleData.evenScaling;
newData.simpleCircle ¬ GGCircles.CreateEmptyCircle[];
GGCircles.CopyCircle[from: circleData.simpleCircle, to: newData.simpleCircle];
newData.seg ¬ GGSegment.CopySegment[circleData.seg];
newData.forward ¬ circleData.forward;
FOR index:
INTEGER
IN [0..maxCirclePoints)
DO
newData.savedPointSelections[index] ¬ circleData.savedPointSelections[index];
ENDLOOP;
GGSliceOps.SetFillColor[copySlice, NIL, GGCoreOps.CopyColor[circleData.fillColor], $Set, NIL];
GGProps.CopyAll[fromSlice: slice, toSlice: copySlice];
RETURN[LIST[copySlice]];
};
CircleRestore:
PROC [from: Slice, to: Slice]
= {
GGModelTypes.SliceRestoreProc
from and to must be nearly identically structured slices, probably because from was made as a copy of to. Restore the contents of "to", getting all non-REF values from "from". Good luck.
IF to=NIL OR from=NIL THEN ERROR;
IF to.class#from.class THEN ERROR;
IF to.class.type#$Circle THEN ERROR;
BEGIN
fromData: CircleData ¬ NARROW[from.data];
toData: CircleData ¬ NARROW[to.data];
IF GGSlice.
copyRestore
THEN {
toData.circle ¬ fromData.circle;
toData.transform ¬ fromData.transform;
toData.scale ¬ fromData.scale;
toData.evenScaling ¬ fromData.evenScaling;
toData.simpleCircle ¬ fromData.simpleCircle;
toData.inverse ¬ fromData.inverse;
toData.inverseScale ¬ fromData.inverseScale;
toData.startPoint ¬ fromData.startPoint;
toData.fillColor ¬ fromData.fillColor;
toData.forward ¬ fromData.forward;
toData.savedPointSelections ¬ fromData.savedPointSelections;
toData.seg ¬ fromData.seg;
}
ELSE to.data ¬ from.data;
to.selectedInFull ¬ from.selectedInFull; -- RECORD of BOOL
to.normalSelectedParts ¬ NIL; -- caller must reselect
to.hotSelectedParts ¬ NIL; -- caller must reselect
to.activeSelectedParts ¬ NIL; -- caller must reselect
to.matchSelectedParts ¬ NIL; -- caller must reselect
to.nullDescriptor is always valid
to.fullDescriptor is unused
to.tightBox ¬ from.tightBox;
to.tightBoxValid ¬ from.tightBoxValid;
to.boundBox ¬ from.boundBox;
to.boxValid ¬ from.boxValid;
to.onOverlay ¬ from.onOverlay;
to.extraPoints ¬ from.extraPoints;
to.priority ¬ from.priority;
to.historyTop ¬ from.historyTop;
END;
};
Class Procs for Drawing
CircleBuildPath:
PROC [slice: Slice, transformParts: SliceParts, transform: Transformation, moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc, editConstraints: EditConstraints ¬ none] = {
GGModelTypes.SliceBuildPathProc
cpCount: INT ¬ 0;
cpIndex: INT ¬ -1;
edgePoint, pointInUnitSpace: Point;
circleData: CircleData ¬ NARROW[slice.data];
circleTransformParts: CircleParts ¬ NARROW[transformParts];
newTransform: Transformation;
noRubberband: BOOL ¬ circleTransformParts=NIL OR circleTransformParts.outline OR circleTransformParts.cpArray[0] OR IsEmpty[circleTransformParts]; -- means transform uniformly
newTransform ¬
SELECT
TRUE
FROM
transform=NIL => circleData.transform, -- don't move it
IsEmpty[circleTransformParts] => circleData.transform, -- no parts to move
ENDCASE => ImagerTransformation.Concat[circleData.transform, transform];
IF noRubberband
THEN {
ImagerPath.Transform[IF circleData.forward THEN CirclePath ELSE CirclePathReverse, newTransform, moveTo, lineTo, curveTo, conicTo, arcTo]; -- transform uniformly
RETURN;
};
FOR index: CirclePoints
IN [1..maxCirclePoints)
DO
-- count circumference point
IF circleTransformParts.cpArray[index]
THEN {
cpCount ¬ cpCount + 1;
cpIndex ¬ index;
};
ENDLOOP;
IF cpCount > 1
THEN {
ImagerPath.Transform[IF circleData.forward THEN CirclePath ELSE CirclePathReverse, newTransform, moveTo, lineTo, curveTo, conicTo, arcTo]; -- transform uniformly
RETURN;
};
Rubberband the radius.
edgePoint ¬ ImagerTransformation.Transform[newTransform,
SELECT cpIndex
FROM
1 => [-1.0, 0.0],
2 => [0.0, 1.0],
3 => [1.0, 0.0],
4 => [0.0, -1.0],
ENDCASE => ERROR];
pointInUnitSpace ¬ ImagerTransformation.Transform[circleData.inverse, edgePoint];
newTransform ¬ ImagerTransformation.PreScale[circleData.transform, MAX[Vectors2d.Magnitude[pointInUnitSpace], circleLimit] ]; -- circles can't shrink to zero
ImagerPath.Transform[IF circleData.forward THEN CirclePath ELSE CirclePathReverse, newTransform, moveTo, lineTo, curveTo, conicTo, arcTo];
};
Circle
DrawBorder:
PROC [slice: Slice, drawParts: SliceParts, transformParts: SliceParts, transform: Transformation, dc: Imager.Context, camera: GGModelTypes.Camera, quick:
BOOL, editConstraints: EditConstraints ¬ none] = {
OPEN ImagerTransformation; -- see calls to Concat, Transform, and PreScale
If the circle is selected in entirety,
DoCircleDrawBorder:
PROC = {
cpCount: INT ¬ 0;
cpIndex: INT ¬ -1;
edgePoint, pointInUnitSpace: Point;
circleData: CircleData ¬ NARROW[slice.data];
circleDrawParts: CircleParts ¬ NARROW[drawParts];
circleTransformParts: CircleParts ¬ NARROW[transformParts];
newTransform: Transformation;
noRubberband: BOOL ¬ circleTransformParts=NIL OR circleTransformParts.outline OR circleTransformParts.cpArray[0] OR IsEmpty[circleTransformParts];
newTransform ¬
SELECT
TRUE
FROM
transform=NIL => circleData.transform, -- don't move it
IsEmpty[circleTransformParts] => circleData.transform, -- no parts to move
ENDCASE => Concat[circleData.transform, transform];
BEGIN
Identify cases where transformation is applied to entire outline.
IF circleDrawParts#NIL AND IsEmpty[circleDrawParts] THEN RETURN; -- draw no parts
IF noRubberband THEN GOTO NoRubberBand;
FOR index: CirclePoints
IN [1..maxCirclePoints)
DO
-- count circumference point
IF circleTransformParts.cpArray[index]
THEN {
cpCount ¬ cpCount + 1;
cpIndex ¬ index;
};
ENDLOOP;
IF cpCount > 1 THEN GOTO NoRubberBand;
Otherwise Rubberband.
edgePoint ¬ Transform[newTransform,
SELECT cpIndex
FROM
1 => [-1.0, 0.0],
2 => [0.0, 1.0],
3 => [1.0, 0.0],
4 => [0.0, -1.0],
ENDCASE => ERROR];
pointInUnitSpace ¬ Transform[circleData.inverse, edgePoint];
newTransform ¬ PreScale[circleData.transform,
MAX[Vectors2d.Magnitude[pointInUnitSpace], circleLimit]]; -- can't shrink to zero
CachedCircleDraw[dc, circleData, newTransform, NIL, FALSE, FALSE];
EXITS
NoRubberBand => {
IF transform#NIL THEN Imager.ConcatT[dc, transform];
CachedCircleDraw[dc, circleData, circleData.transform, NIL, FALSE, FALSE];
};
END;
};
Imager.DoSave[dc, DoCircleDrawBorder];
};
CircleDrawParts:
PROC [slice: Slice, parts: SliceParts ¬
NIL, dc: Imager.Context, camera: Camera, quick:
BOOL] = {
The only parts of a circle are its outline and its five control points. If the outline is selected, we draw the circle.
DoCircleDrawOutline:
PROC = {
CachedCircleDraw[dc, circleData, circleData.transform, NIL, FALSE];
};
circleData: CircleData ¬ NARROW[slice.data];
circleParts: CircleParts ¬ NARROW[parts];
IF circleParts = NIL OR circleParts.outline THEN Imager.DoSave[dc, DoCircleDrawOutline];
};
CircleDrawTransform:
PROC [slice: Slice, parts: SliceParts ¬
NIL, dc: Imager.Context, camera: Camera, transform: Transformation, editConstraints: EditConstraints] = {
GGModelTypes.SliceDrawTransformProc
This is what makes circles behave specially. Depending on which parts are selected, the points are transformed and the circle is rubberbanded properly. The circle data itself is not modified.
If parts = NIL then, transform whole circle (unless transform = NIL).
OPEN ImagerTransformation;
DoCircleDrawTransform:
PROC = {
cpCount: INT ¬ 0;
cpIndex: INT ¬ -1;
edgePoint, pointInUnitSpace: Point;
newTransform: Transformation;
circleData: CircleData ¬ NARROW[slice.data];
circleParts: CircleParts ¬ NARROW[parts];
wholeThing: BOOL ¬ IsEmpty[circleParts]; -- NIL or really empty parts
transformColor: BOOL ¬ TRUE;
IF wholeThing OR circleParts.outline THEN transformColor ¬ TRUE -- move circle & color
ELSE IF circleParts.cpArray[0] THEN transformColor ¬ FALSE -- move circle but not color
ELSE {
FOR index: CirclePoints
IN [1..maxCirclePoints)
DO
-- find cp count and index of last cp
IF circleParts.cpArray[index]
THEN{
cpCount ¬ cpCount + 1;
cpIndex ¬ index;
};
ENDLOOP;
IF cpCount > 1 THEN transformColor ¬ FALSE
ELSE {
Rubberband the circle.
IF cpCount = 0 AND NOT circleParts.cpArray[0] THEN ERROR;
edgePoint ¬ Transform[Concat[circleData.transform, transform],
SELECT cpIndex
FROM
1 => [-1.0, 0.0],
2 => [0.0, 1.0],
3 => [1.0, 0.0],
4 => [0.0, -1.0],
ENDCASE => ERROR];
pointInUnitSpace ¬ Transform[circleData.inverse, edgePoint];
newTransform ¬ PreScale[circleData.transform,
MAX[Vectors2d.Magnitude[pointInUnitSpace], circleLimit]]; -- can't shrink to zero
CachedCircleDraw[dc, circleData, newTransform, NIL, FALSE];
RETURN;
};
};
Transform the circle as a unit.
CachedCircleDraw[dc, circleData, circleData.transform, transform, transformColor];
};
Imager.DoSave[dc, DoCircleDrawTransform];
};
CircleDrawSelectionFeedback:
PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: Camera, dragInProgress, caretIsMoving, hideHot, quick:
BOOL] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
jointSize: REAL = GGModelTypes.jointSize;
halfJointSize: REAL = GGModelTypes.halfJointSize;
hotJointSize: REAL = GGModelTypes.hotJointSize;
halfHotJointSize: REAL = GGModelTypes.halfHotJointSize;
slowNormal, slowHot, completeNormal, completeHot: BOOL ¬ FALSE;
firstJoint: Point;
normalCircleParts, hotCircleParts: CircleParts;
circleData: CircleData ¬ NARROW[slice.data];
transform: Transformation ¬ circleData.transform;
IF caretIsMoving OR dragInProgress OR camera.quality=quality THEN RETURN;
IF selectedParts=NIL AND hotParts=NIL THEN RETURN;
normalCircleParts ¬ NARROW[selectedParts];
hotCircleParts ¬ NARROW[hotParts];
completeNormal ¬ normalCircleParts#NIL AND IsComplete[normalCircleParts];
completeHot ¬ hotCircleParts#NIL AND IsComplete[hotCircleParts];
slowNormal ¬ normalCircleParts#NIL AND (NOT quick OR (quick AND NOT completeNormal));
slowHot ¬ hotCircleParts#NIL AND (NOT quick OR (quick AND NOT completeHot));
IF slowNormal AND slowHot THEN DrawSelectionFeedbackAux[slice, circleData, normalCircleParts, hotCircleParts, dc, transform, camera]
ELSE IF slowNormal THEN DrawSelectionFeedbackAux[slice, circleData, normalCircleParts, NIL, dc, transform, camera]
ELSE IF slowHot THEN DrawSelectionFeedbackAux[slice, circleData, NIL, hotCircleParts, dc, transform, camera];
IF (
NOT slowNormal
AND completeNormal)
OR (
NOT slowHot
AND completeHot)
THEN {
fullParts: CircleParts ¬ IF completeNormal THEN normalCircleParts ELSE hotCircleParts;
[] ¬ GGSliceOps.GetTightBox[slice]; -- force update of internal data
firstJoint ¬ ImagerTransformation.Transform[transform, [-1.0, 0.0]]; -- not the center point
};
IF
NOT slowHot
AND completeHot
THEN
GGShapes.DrawQuickSelectedJoint[dc, firstJoint, hot, camera.cpScale];
IF
NOT slowNormal
AND completeNormal
THEN
GGShapes.DrawQuickSelectedJoint[dc, firstJoint, normal, camera.cpScale];
};
CircleDrawAttractorFeedback:
PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress:
BOOL, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints] = {
OPEN ImagerTransformation;
IF
NOT (dragInProgress
AND selectedParts#
NIL)
THEN {
DoDrawFeedback:
PROC = {
t: Transformation ¬ circleData.transform;
pts: ARRAY[0..4] OF Point;
pts[0] ¬ Transform[t, [0.0, 0.0]]; pts[1] ¬ Transform[t, [-1.0, 0.0]];
pts[2] ¬ Transform[t, [0.0, 1.0]]; pts[3] ¬ Transform[t, [1.0, 0.0]];
pts[4] ¬ Transform[t, [0.0, -1.0]];
IF hitParts.cpArray[0] THEN GGShapes.DrawCP[dc, pts[0], camera.cpScale];
FOR index: CirclePoints
IN [1..maxCirclePoints)
DO
GGShapes.DrawCP[dc, pts[index], camera.cpScale];
ENDLOOP;
};
circleData: CircleData ¬ NARROW[slice.data];
hitParts: CircleParts ¬ NARROW[attractorParts];
IF camera.quality#quality AND hitParts#NIL THEN Imager.DoSave[dc, DoDrawFeedback];
};
};
Auxiliary Procs for Drawing
CircleBoxFromTransform:
PROC [circleData: CircleData, transform: Transformation]
RETURNS [r: Imager.Rectangle] = {
pad: REAL ¬ circleData.seg.strokeWidth;
origin: Point ¬ ImagerTransformation.Transform[transform, [0.0, 0.0]];
minor: Point ¬ ImagerTransformation.Transform[transform, [0.0, 1.0]]; -- p1
major: Point ¬ ImagerTransformation.Transform[transform, [1.0, 0.0]]; -- p0, p2
radius: REAL ¬ RealFns.SqRt[MAX[Vectors2d.DistanceSquared[minor, origin], Vectors2d.DistanceSquared[major, origin]]];
minorDS: REAL ¬ Vectors2d.DistanceSquared[minor, origin];
majorDS: REAL ¬ Vectors2d.DistanceSquared[major, origin];
tMax: REAL ¬ MAX[minorDS, majorDS];
radius: REAL ¬ RealFns.SqRt[tMax];
box: BoundBox¬ GGBoundBox.CreateBoundBox[origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius];
GGBoundBox.EnlargeByOffset[box, pad];
r ¬ GGBoundBox.RectangleFromBoundBox[box];
};
FindImagerObject:
PROC [circleData: CircleData, local
View, fill
Local: Transformation, fillIt:
BOOL]
RETURNS [object: Object] = {
EqualPattern:
PROC [p1, p2: SequenceOfReal]
RETURNS [
BOOL ¬
FALSE] = {
IF p1.len#p2.len THEN RETURN[FALSE];
FOR i:
NAT
IN [0.. p1.len)
DO
IF p1[i]#p2[i] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
CompareProps: FunctionCache.CompareProc = {
CompareProc: TYPE ~ PROC [argument: Domain] RETURNS [good: BOOL];
props: Props ¬ NARROW[argument];
seg: Segment ¬ circleData.seg;
fillIsSampled: BOOL;
thisFill: Imager.Color ¬ IF fillIt THEN circleData.fillColor ELSE NIL;
IF props.strokeWidth=seg.strokeWidth AND
props.strokeEnd=seg.strokeEnd AND
props.dashed=seg.dashed AND
props.offset=seg.offset AND
props.length=seg.length AND
ImagerTransformation.CloseToTranslation[props.localView, localView, maxPixels] AND
GGCoreOps.EquivalentColors[props.color, seg.color] AND
GGCoreOps.EquivalentColors[props.fillColor, thisFill] AND
props.fillIsSampled = (fillIsSampled ¬ IsSampled[thisFill]) AND
(NOT fillIsSampled OR ImagerTransformation.CloseEnough[props.fillLocal, fillLocal, maxPixels]) AND
(NOT props.dashed OR EqualPattern[props.pattern, seg.pattern])
THEN RETURN[TRUE] ELSE RETURN[FALSE];
};
value: FunctionCache.Range;
maxPixels: REAL ¬ 10000.0;
ok: BOOL;
[value, ok] ¬ FunctionCache.Lookup[x: cache, compare: CompareProps, clientID: $GGCircleObject];
RETURN [IF ok THEN NARROW[value] ELSE NIL];
};
CreateProps:
PROC [circleData: CircleData, local
View, fill
Local: ImagerTransformation.Transformation, fillIt:
BOOL ¬
TRUE]
RETURNS [props: Props] = {
seg: Segment ¬ circleData.seg;
fillIsSampled: BOOL ¬ IsSampled[circleData.fillColor];
props ¬
NEW[PropsRec ¬ [
localView: ImagerTransformation.Copy[localView],
strokeWidth: seg.strokeWidth,
strokeEnd: seg.strokeEnd,
dashed: seg.dashed,
pattern: seg.pattern,
offset: seg.offset,
length: seg.length,
color: seg.color,
fillColor: IF fillIt THEN GGCoreOps.CopyColor[circleData.fillColor] ELSE NIL,
fillIsSampled: fillIsSampled,
fillLocal: fillLocal]];
};
IsSampled:
PROC [color: Imager.Color]
RETURNS [
BOOL] = {
IF color = NIL THEN RETURN [FALSE];
WITH color
SELECT
FROM
sampledBlack: ImagerColor.SampledBlack => RETURN[TRUE];
sampledColor: ImagerColor.SampledColor => RETURN[TRUE];
ENDCASE => RETURN[FALSE];
};
CachedCircleDraw:
PROC [dc: Imager.Context, circleData: CircleData, local
View, view
View: Transformation, transformColor:
BOOL, fillIt:
BOOL ¬
TRUE] = {
When this routine is called, we've done all of the hard work to find out how the circle (and its sampled color, if any) are to be transformed. Draw the circle, using the cache (if cacheIt).
Sampled colors present an extra difficulty with caching.
OPEN ImagerTransformation; -- see Transform and InverseTransform
object: Imager.Object;
position: Imager.VEC;
circleProps: Props;
sampledColorStays: BOOL ¬ IsSampled[circleData.fillColor] AND NOT transformColor;
IF GGMUserProfile.GetTurboOn[]
AND (
NOT sampledColorStays
OR view
View =
NIL)
THEN {
fillLocal: Transformation;
fillLocal ¬ FromFillToLocal[circleData.fillColor, localView];
object ¬ FindImagerObject[circleData, localView, fillLocal, fillIt];
IF object =
NIL
THEN {
-- put circle in cache
clipR: Imager.Rectangle ¬ CircleBoxFromTransform[circleData, localView];
props: Props;
props ¬ CreateProps[circleData, localView, fillLocal, fillIt]; -- props of this Imager.Object
object ¬ NEW[Imager.ObjectRep ¬ [draw: CircleDrawObject, clip: clipR, data: props]];
FunctionCache.Insert[cache, props, object, 60, $GGCircleObject];
};
circleProps ¬ NARROW[object.data];
position ¬ Transform[local
View, InverseTransform[circleProps.local
View, [0, 0]]];
Note that position will be [0, 0], the first time this Imager.Object is drawn.
IF viewView # NIL THEN Imager.ConcatT[dc, viewView];
Imager.DrawObject[context: dc, object: object, interactive: TRUE, position: position];
}
ELSE OldCircleDrawOutline[dc, circleData, localView, viewView, transformColor, fillIt];
};
FromFillToLocal:
PROC [color: Imager.Color, local
View: Transformation]
RETURNS [fill
Local: Transformation] = {
OPEN ImagerTransformation;
fillView: Transformation;
WITH color
SELECT
FROM
sampledBlack: ImagerColor.SampledBlack => {
fillView ¬ sampledBlack.um;
fillLocal ¬ Concat[fillView, Invert[localView]];
};
sampledColor: ImagerColor.SampledColor => {
fillView ¬ sampledColor.um;
fillLocal ¬ Concat[fillView, Invert[localView]];
};
ENDCASE => fillLocal ¬ NIL;
};
CircleDrawObject:
PROC [self: Imager.Object, context: Imager.Context] = {
Imager.Object.draw PROC
NOT called with the dc transformation already set to object coordinates
OPEN ImagerTransformation;
FillItIn:
PROC = {
GGCoreOps.SetColor[context, circleProps.fillColor];
Imager.ConcatT[context, localView];
Imager.MaskFill[context, IF circleData.forward THEN CirclePath ELSE CirclePathReverse, TRUE]; -- do we need to be careful about this here? (We can't be making Interpress)
Imager.MaskFill[context, CirclePath, TRUE];
};
TransformedCirclePath: Imager.PathProc = {
ImagerPath.Transform[CirclePath, localView, moveTo, lineTo, curveTo, conicTo, arcTo];
};
circleProps: Props ¬ NARROW[self.data];
localView: Transformation ¬ circleProps.localView;
Draw the Fill
IF circleProps.fillColor # NIL THEN Imager.DoSave[context, FillItIn];
Draw the Stroke
IF circleProps.color #
NIL
AND circleProps.strokeWidth#0.0
THEN {
Imager.SetColor[context, circleProps.color];
Imager.SetStrokeWidth[context, circleProps.strokeWidth];
Imager.SetStrokeEnd[context, circleProps.strokeEnd]; -- needed to make object caching work
Imager.SetStrokeJoint[context, round]; -- needed to make object caching work
IF circleProps.dashed
THEN {
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ¬ circleProps.pattern;
Imager.MaskDashedStroke[context, TransformedCirclePath, pattern.len, PatternProc, circleProps.offset, circleProps.length];
}
ELSE Imager.MaskStroke[context, TransformedCirclePath, TRUE];
};
};
OldCircleDrawOutline:
PROC [dc: Imager.Context, circleData: CircleData, local
View, view
View: ImagerTransformation.Transformation, transformColor:
BOOL, fillIt:
BOOL ¬
FALSE] = {
NOT called with the dc transformation already set to object coordinates
TransformedCirclePath: Imager.PathProc = {
ImagerPath.Transform[CirclePath, localView, moveTo, lineTo, curveTo, conicTo, arcTo];
};
FillItIn:
PROC = {
IF transformColor THEN GGCoreOps.SetColor[dc, circleData.fillColor, viewView]
ELSE GGCoreOps.SetColor[dc, circleData.fillColor];
IF viewView # NIL THEN Imager.ConcatT[dc, viewView];
Imager.ConcatT[dc, localView];
Imager.MaskFill[dc, IF circleData.forward THEN CirclePath ELSE CirclePathReverse, TRUE]
};
seg: Segment ¬ circleData.seg;
IF fillIt AND circleData.fillColor#NIL THEN Imager.DoSave[dc, FillItIn];
IF seg.color#
NIL
AND seg.strokeWidth#0.0
THEN {
IF viewView # NIL THEN Imager.ConcatT[dc, viewView];
Imager.SetColor[dc, seg.color];
Imager.SetStrokeWidth[dc, seg.strokeWidth];
Imager.SetStrokeEnd[dc, seg.strokeEnd];
Imager.SetStrokeJoint[dc, round];
IF seg.dashed
THEN {
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ¬ seg.pattern;
Imager.MaskDashedStroke[dc, TransformedCirclePath, pattern.len, PatternProc, seg.offset, seg.length];
}
ELSE Imager.MaskStroke[dc, TransformedCirclePath, TRUE];
};
};
CirclePath: Imager.PathProc = {
-- this should come out cw
left: Point ¬ [-1.0, 0.0];
top: Point ¬ [0.0, 1.0];
right: Point ¬ [1.0, 0.0];
bottom: Point ¬ [0.0, -1.0];
moveTo[left];
arcTo[top, right];
arcTo[bottom, left];
};
CirclePathReverse: Imager.PathProc = {
-- this comes out ccw courtesy of the Imager
leftSide: Point ¬ [-1.0, 0.0];
rightSide: Point ¬ [1.0, 0.0];
moveTo[leftSide];
arcTo[rightSide, leftSide];
};
DrawSelectionFeedbackAux:
PROC [slice: Slice, circleData: CircleData, normalCircleParts: CircleParts, hotCircleParts: CircleParts, dc: Imager.Context, t: Transformation, camera: Camera] = {
OPEN ImagerTransformation;
DoDrawFeedback:
PROC = {
thisCPisHot, thisCPisSelected: BOOL ¬ FALSE;
pts: ARRAY[0..4] OF Point;
pts[0] ¬ Transform[t, [0.0, 0.0]]; pts[1] ¬ Transform[t, [-1.0, 0.0]];
pts[2] ¬ Transform[t, [0.0, 1.0]]; pts[3] ¬ Transform[t, [1.0, 0.0]];
pts[4] ¬ Transform[t, [0.0, -1.0]];
FOR index: CirclePoints
IN [0..maxCirclePoints)
DO
thisCPisHot ¬ hotCircleParts#NIL AND hotCircleParts.cpArray[index];
thisCPisSelected ¬ normalCircleParts#NIL AND normalCircleParts.cpArray[index];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[index], hot, camera.cpScale];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[index], normal, camera.cpScale];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[index], camera.cpScale];
ENDLOOP;
};
Imager.DoSave[dc, DoDrawFeedback];
};
CircleSaveSelections:
PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] = {
GGModelTypes.SliceSaveSelectionsProc
SetPointField:
PROC [point:
INTEGER, selected:
BOOL, selectClass: SelectionClass] = {
SELECT selectClass
FROM
normal => circleData.savedPointSelections[point].normal ¬ selected;
hot => circleData.savedPointSelections[point].hot ¬ selected;
active => circleData.savedPointSelections[point].active ¬ selected;
match => circleData.savedPointSelections[point].match ¬ selected;
ENDCASE;
};
SetSegmentField:
PROC [seg: Segment, selected:
BOOL, selectClass: SelectionClass] = {
SELECT selectClass
FROM
normal => seg.TselectedInFull.normal ¬ selected;
hot => seg.TselectedInFull.hot ¬ selected;
active => seg.TselectedInFull.active ¬ selected;
match => seg.TselectedInFull.match ¬ selected;
ENDCASE;
};
dontClear: BOOL ¬ TRUE;
circleParts: CircleParts ¬ NARROW[parts];
circleData: CircleData ¬ NARROW[slice.data];
IF circleParts=NIL THEN dontClear ¬ FALSE;
FOR count:
INTEGER
IN [0..maxCirclePoints)
DO
SetPointField[count, dontClear AND circleParts.cpArray[count], selectClass];
ENDLOOP;
SetSegmentField[circleData.seg, dontClear AND circleParts.outline, selectClass];
};
CircleRemakeSelections:
PROC [slice: Slice, selectClass: SelectionClass]
RETURNS [parts: SliceParts] = {
GGModelTypes.SliceRemakeSelectionsProc
GetPointField:
PROC [point:
INTEGER, selectClass: SelectionClass]
RETURNS [
BOOL] = {
RETURN[
SELECT selectClass
FROM
normal => circleData.savedPointSelections[point].normal,
hot => circleData.savedPointSelections[point].hot,
active => circleData.savedPointSelections[point].active,
match => circleData.savedPointSelections[point].match,
ENDCASE => FALSE];
};
GetSegmentField:
PROC [seg: Segment, selectClass: SelectionClass]
RETURNS [
BOOL] = {
RETURN[
SELECT selectClass
FROM
normal => seg.TselectedInFull.normal,
hot => seg.TselectedInFull.hot,
active => seg.TselectedInFull.active,
match => seg.TselectedInFull.match,
ENDCASE => FALSE];
};
circleData: CircleData ¬ NARROW[slice.data];
circleParts: CircleParts ¬ NEW[CirclePartsObj];
FOR count:
INTEGER
IN [0..maxCirclePoints)
DO
circleParts.cpArray[count] ¬ GetPointField[count, selectClass];
ENDLOOP;
circleParts.outline ¬ GetSegmentField[circleData.seg, selectClass];
parts ¬ IF IsEmpty[circleParts] THEN NIL ELSE circleParts; -- KAP. September 9, 1991
};
Transforming
circleLimit: REAL ← 0.05; -- needed to prevent numerical instability of tiny circles
circleLimit: REAL ¬ 0.01; -- circleLimit made smaller. KAP. April 12, 1988
CircleTransform:
PROC [slice: Slice, parts: SliceParts ¬
NIL, transform: Transformation, editConstraints: EditConstraints, history: HistoryEvent] = {
GGModelTypes.SliceTransformProc
Permanently transforms the circle. Depending on which parts are selected, the circle is transformed.
isOrigin: BOOL ¬ FALSE;
cpCount: INT ¬ 0;
cpIndex: INT ¬ -1;
originPoint, edgePoint, pointInUnitSpace: Point;
circleData: CircleData ¬ NARROW[slice.data];
circleParts: CircleParts ¬ NARROW[parts];
BEGIN
IF circleParts=NIL OR circleParts.cpArray[0] THEN isOrigin ¬ TRUE;
FOR index: CirclePoints
IN [1..maxCirclePoints)
DO
IF circleParts=
NIL
OR circleParts.cpArray[index]
THEN{
cpCount ¬ cpCount + 1;
cpIndex ¬ index;
};
ENDLOOP;
IF circleParts=
NIL
OR circleParts.outline
THEN {
-- all selected. Transform object and fill color
circleData.transform ¬ ImagerTransformation.Concat[circleData.transform, transform];
circleData.fillColor ¬ GGCoreOps.TransformColor[circleData.fillColor, transform];
GOTO FinishTransform;
};
IF isOrigin
OR cpCount > 1
THEN {
-- most is selected. Transform object, but no fill color
circleData.transform ¬ ImagerTransformation.Concat[circleData.transform, transform];
GOTO FinishTransform;
};
Only one cp is selected OR no selections for a brand new circle
originPoint ¬ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]];
IF cpCount = 0
AND
NOT isOrigin
THEN {
This is the special case of a new circle being rubberbanded
edgePoint ¬ Vectors2d.Add[v1: Vectors2d.Sub[v1: circleData.startPoint, v2: originPoint], v2: ImagerTransformation.Factor[transform].t]; -- edgepoint relative to origin
edgePoint ¬ Vectors2d.Add[originPoint, edgePoint]; -- make edgepoint absolute
}
ELSE {
-- this is the normal case when a single CP is selected
edgePoint ¬ ImagerTransformation.Transform[circleData.transform, SELECT cpIndex FROM
1 => [-1.0, 0.0],
2 => [0.0, 1.0],
3 => [1.0, 0.0],
4 => [0.0, -1.0],
ENDCASE => ERROR];
edgePoint ¬ ImagerTransformation.Transform[transform, edgePoint]; -- move the edgepoint
};
pointInUnitSpace ¬ ImagerTransformation.Transform[circleData.inverse, edgePoint];
circleData.transform ¬ ImagerTransformation.PreScale[circleData.transform, MAX[Vectors2d.Magnitude[pointInUnitSpace], circleLimit] ]; -- circles can't shrink to zero
GOTO FinishTransform;
EXITS
FinishTransform => {
circleData.scale ¬ ImagerTransformation.SingularValues[circleData.transform];
circleData.inverse ¬ ImagerTransformation.Invert[circleData.transform];
circleData.inverseScale ¬ ImagerTransformation.SingularValues[circleData.inverse];
CircleSetScaling[slice];
GGSlice.KillBoundBox[slice];
};
END;
};
Textual Description
CircleDescribeHit:
PROC [slice: Slice, hitData:
REF
ANY]
RETURNS [rope: Rope.
ROPE] = {
circleHitData: CircleHitData ¬ NARROW[hitData];
prefix: Rope.ROPE;
IF circleHitData.index#-1 THEN {prefix ¬ IF circleHitData.index = 0 THEN "origin" ELSE "circumference point"}
ELSE IF circleHitData.outline THEN prefix ¬ "border"
ELSE ERROR;
rope ¬ Rope.Concat[prefix, " of a Circle slice"];
};
CircleDescribe:
PROC [sliceD: SliceDescriptor]
RETURNS [rope: Rope.
ROPE] = {
GGModelTypes.SliceDescribeProc
prefix: Rope.ROPE;
cpCount: INT ¬ 0;
circleParts: CircleParts;
IF sliceD.parts = NIL THEN RETURN["a Circle slice"];
circleParts ¬ NARROW[sliceD.parts];
FOR index: CirclePoints
IN [0..maxCirclePoints)
DO
IF circleParts.cpArray[index] THEN cpCount ¬ cpCount + 1;
ENDLOOP;
IF cpCount > 1 THEN RETURN[ "multiple parts of a Circle slice"];
IF cpCount=0 THEN prefix ¬ IF circleParts.outline THEN "outline" ELSE "nowhere"
ELSE prefix ¬ IF circleParts.cpArray[0] THEN "origin" ELSE "circumference point";
rope ¬ Rope.Concat[prefix, " of a Circle slice"];
};
CircleFileout:
PROC [slice: Slice, f:
IO.
STREAM] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
circleData: CircleData ¬ NARROW[slice.data];
GGParseOut.WriteTransformation[f, circleData.transform];
f.PutRope[" strokeWidth: "];
f.PutF1["%g", [real[circleData.seg.strokeWidth]] ];
f.PutRope[" strokeColor: "];
GGParseOut.WriteColor[f, circleData.seg.color];
f.PutRope[" fillColor: "];
GGParseOut.WriteColor[f, circleData.fillColor];
f.PutRope[" dashes: ( "];
GGParseOut.WriteBool[f, circleData.seg.dashed]; f.PutChar[IO.SP];
IF circleData.seg.dashed
THEN {
GGParseOut.WriteArrayOfReal[f, circleData.seg.pattern];
f.PutF[" %g %g ", [real[circleData.seg.offset]], [real[circleData.seg.length]]];
};
f.PutRope[") "];
f.PutRope[" props: ( "];
GGParseOut.WriteBool[f, circleData.seg.props#NIL];
IF circleData.seg.props#NIL THEN GGParseOut.WriteProps[f, circleData.seg.props] ELSE f.PutChar[IO.SP]; -- list of ROPE
f.PutRope[") fwd: "];
GGParseOut.WriteBool[f, circleData.forward];
};
CircleFilein:
PROC [f:
IO.
STREAM, version:
REAL, router: MsgRouter, camera: Camera]
RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
fwd: BOOL ¬ TRUE;
circleData: CircleData;
origin, outerPoint: Point;
transform: Transformation;
strokeWidth: REAL;
strokeColor, fillColor: Color;
transform ¬ GGParseIn.ReadTransformation[f];
origin ¬ ImagerTransformation.Transform[m: transform, v: [0.0, 0.0] ];
outerPoint ¬ ImagerTransformation.Transform[m: transform, v: [0.0, 1.0] ];
GGParseIn.ReadRope[f, "strokeWidth:"];
strokeWidth ¬ GGParseIn.ReadReal[f];
GGParseIn.ReadRope[f, "strokeColor:"];
strokeColor ¬ GGParseIn.ReadColor[f, version];
GGParseIn.ReadRope[f, "fillColor:"];
fillColor ¬ GGParseIn.ReadColor[f, version];
slice ← MakeCircleSlice[origin, outerPoint, strokeWidth, strokeColor, fillColor].slice;
slice ¬ MakeCircleSlice[origin, outerPoint].slice;
fix up the new circle transforms in case they were skewed by uneven scaling
circleData ¬ NARROW[slice.data];
circleData.transform ¬ transform;
circleData.scale ¬ ImagerTransformation.SingularValues[circleData.transform];
circleData.inverse ¬ ImagerTransformation.Invert[circleData.transform];
circleData.inverseScale ¬ ImagerTransformation.SingularValues[circleData.inverse];
circleData.seg.strokeWidth ¬ strokeWidth;
circleData.seg.color ¬ strokeColor;
circleData.fillColor ¬ fillColor;
IF version>=8704.03
THEN {
-- read in dash patterns
GGParseIn.ReadRope[f, "dashes: ("];
circleData.seg.dashed ¬ GGParseIn.ReadBool[f];
IF circleData.seg.dashed
THEN {
circleData.seg.pattern ¬ GGParseIn.ReadArrayOfReal[f];
circleData.seg.offset ¬ GGParseIn.ReadReal[f];
circleData.seg.length ¬ GGParseIn.ReadReal[f];
};
GGParseIn.ReadChar[f, ')];
};
IF version>=8706.08
THEN {
-- read in segment props
hasProps: BOOL ¬ FALSE;
props: LIST OF Rope.ROPE ¬ NIL;
GGParseIn.ReadRope[f, "props: ( "];
hasProps ¬ GGParseIn.ReadBool[f];
props ¬ IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL;
GGParseIn.ReadChar[f, ')];
IF props#
NIL
THEN {
FOR next:
LIST
OF Rope.
ROPE ¬ props, next.rest
UNTIL next=
NIL
DO
circleData.seg.props ¬ CONS[next.first, circleData.seg.props];
ENDLOOP;
};
IF version>=8802.04
THEN {
-- read in forward bit
GGParseIn.ReadRope[f, "fwd:"];
fwd ¬ GGParseIn.ReadBool[f];
};
};
CircleSetBoundBox[slice];
CircleSetScaling[slice];
circleData.forward ¬ fwd;
};
Parts
CircleIsEmptyParts:
PROC [sliceD: SliceDescriptor]
RETURNS [
BOOL ¬
FALSE] = {
GGModelTypes.SliceEmptyPartsProc
circleParts: CircleParts ¬ NARROW[sliceD.parts];
RETURN[IsEmpty[circleParts] ];
};
CircleIsCompleteParts:
PROC [sliceD: SliceDescriptor]
RETURNS [
BOOL ¬
FALSE] = {
GGModelTypes.SliceEmptyPartsProc
circleParts: CircleParts ¬ NARROW[sliceD.parts];
RETURN[IsComplete[circleParts] ];
};
NearestCirclePoint:
PROC [slice: Slice, point: Point]
RETURNS [index: [0..4] ] = {
circleHitData: CircleHitData;
success: BOOL;
hitData: REF ANY;
sliceD: SliceDescriptor ¬ CircleNewParts[slice, NIL, slice];
[----, ----, ----, hitData, success] ¬ CircleClosestPoint[sliceD: sliceD, testPoint: point, tolerance: GGUtility.plusInfinity];
IF NOT success THEN ERROR;
circleHitData ¬ NARROW[hitData];
index ¬ circleHitData.index;
IF index = -1 THEN ERROR;
};
CircleNewParts:
PROC [slice: Slice, hitData:
REF
ANY, mode: SelectMode]
RETURNS [sliceD: SliceDescriptor] = {
GGModelTypes.SliceNewPartsProc
circleHitData: CircleHitData ¬ NARROW[hitData];
circleParts: CircleParts ¬ NEW[CirclePartsObj ¬ [ALL[FALSE], FALSE] ];
SELECT mode
FROM
literal => {
IF circleHitData.index#-1 THEN circleParts.cpArray[circleHitData.index] ¬ TRUE
ELSE IF circleHitData.outline THEN circleParts.outline ¬ TRUE;
};
joint => {
IF circleHitData.index#-1 THEN circleParts.cpArray[circleHitData.index] ¬ TRUE
ELSE
IF circleHitData.outline
THEN {
index: [-1..4] ¬ NearestCirclePoint[slice, circleHitData.hitPoint];
circleParts.cpArray[index] ¬ TRUE;
};
};
controlPoint => circleParts.cpArray ¬ ALL[TRUE];
segment, traj, topLevel, slice => MakeComplete[circleParts];
none => {
-- leave circleParts empty
};
ENDCASE => ERROR;
sliceD ¬ GGSlice.DescriptorFromParts[slice, circleParts];
};
CircleUnionParts:
PROC [partsA: SliceDescriptor, partsB: SliceDescriptor]
RETURNS [aPlusB: SliceDescriptor] = {
GGModelTypes.SliceUnionPartsProc
circlePartsA: CircleParts ¬ NARROW[partsA.parts];
circlePartsB: CircleParts ¬ NARROW[partsB.parts];
newParts: CircleParts;
IF partsA.parts = NIL THEN RETURN[partsB];
IF partsB.parts = NIL THEN RETURN[partsA];
IF IsEmpty[circlePartsA] THEN RETURN[partsB];
IF IsEmpty[circlePartsB] THEN RETURN[partsA];
IF IsComplete[circlePartsA] THEN RETURN[partsA];
IF IsComplete[circlePartsB] THEN RETURN[partsB];
newParts ¬ NEW[CirclePartsObj ¬ [ALL[FALSE], FALSE ] ];
FOR i: CirclePoints
IN CirclePoints
DO
newParts.cpArray[i] ¬ circlePartsA.cpArray[i] OR circlePartsB.cpArray[i];
ENDLOOP;
newParts.outline ¬ circlePartsA.outline OR circlePartsB.outline;
aPlusB ¬ GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
CircleDiffParts:
PROC [partsA: SliceDescriptor, partsB: SliceDescriptor]
RETURNS [aMinusB: SliceDescriptor] = {
GGModelTypes.SliceDifferencePartsProc
circlePartsA: CircleParts ¬ NARROW[partsA.parts];
circlePartsB: CircleParts ¬ NARROW[partsB.parts];
newParts: CircleParts;
IF partsA = NIL OR partsB = NIL THEN ERROR;
IF partsA.parts = NIL OR partsB.parts = NIL THEN RETURN[partsA];
newParts ¬ NEW[CirclePartsObj ¬ [ALL[FALSE], FALSE ] ];
FOR i: CirclePoints
IN CirclePoints
DO
newParts.cpArray[i] ¬ IF circlePartsB.cpArray[i] THEN FALSE ELSE circlePartsA.cpArray[i];
ENDLOOP;
newParts.outline ¬ IF circlePartsB.outline THEN FALSE ELSE circlePartsA.outline;
aMinusB ¬ GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
CircleMovingParts:
PROC [slice: Slice, selectedParts: SliceParts, editConstraints: EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord]
RETURNS [background, overlay, rubber, drag: SliceDescriptor] = {
GGModelTypes.SliceMovingPartsProc
If center is moving, everything moves. If any edge CP is moving, everything but the center is moving.
SomeCPMoving:
PROC [circleParts: CircleParts]
RETURNS [
BOOL ¬
FALSE] = {
FOR i:
INTEGER
IN [1..4]
DO
IF circleParts.cpArray[i]
THEN {
RETURN[TRUE];
};
ENDLOOP;
RETURN[FALSE];
};
circleParts: CircleParts ¬ NARROW[selectedParts];
nullD: SliceDescriptor ¬ slice.nullDescriptor;
newParts: CircleParts;
backgroundParts: CircleParts;
IF IsEmpty[circleParts]
THEN {
background ¬ overlay ¬ rubber ¬ drag ¬ nullD;
RETURN;
};
IF IsComplete[circleParts]
THEN {
background ¬ overlay ¬ rubber ¬ nullD;
drag ¬ GGSlice.DescriptorFromParts[slice, circleParts];
RETURN;
};
newParts ¬ NEW[CirclePartsObj ¬ [ALL[FALSE], FALSE] ];
IF circleParts.cpArray[0]
THEN {
newParts.cpArray ¬ ALL[TRUE];
newParts.outline ¬ TRUE;
background ¬ overlay ¬ rubber ¬ nullD;
drag ¬ GGSlice.DescriptorFromParts[slice, newParts];
}
ELSE
IF SomeCPMoving[circleParts]
THEN {
overlay ¬ drag ¬ nullD;
newParts.cpArray ¬ ALL[TRUE];
newParts.cpArray[0] ¬ FALSE;
newParts.outline ¬ TRUE;
rubber ¬ GGSlice.DescriptorFromParts[slice, newParts];
backgroundParts ¬ NEW[CirclePartsObj ¬ [ALL[FALSE], FALSE] ];
backgroundParts.cpArray[0] ¬ TRUE;
background ¬ GGSlice.DescriptorFromParts[slice, backgroundParts];
}
ELSE {
-- nothing is moving
newParts.cpArray ¬ ALL[TRUE];
newParts.outline ¬ TRUE;
background ¬ GGSlice.DescriptorFromParts[slice, newParts];
overlay ¬ rubber ¬ drag ¬ nullD;
};
};
CircleAugmentParts:
PROC [sliceD: SliceDescriptor, selectClass: SelectionClass]
RETURNS [more: SliceDescriptor] = {
GGModelTypes.SliceAugmentPartsProc
more ¬ sliceD;
};
CircleAlterParts:
PROC [sliceD: SliceDescriptor, action:
ATOM]
RETURNS [newD: SliceDescriptor] = {
GGModelTypes.SliceAlterPartsProc
circleParts: CircleParts ¬ NARROW[sliceD.parts];
newParts: CircleParts;
selectedCorner: INT ¬ -1;
newParts ¬ NEW[CirclePartsObj ¬ [cpArray: ALL[FALSE], outline: FALSE]];
FOR i:
INTEGER
IN [0..maxCirclePoints)
DO
IF circleParts.cpArray[i]
THEN {
selectedCorner ¬ i;
EXIT;
};
ENDLOOP;
SELECT action
FROM
$Forward => {
IF selectedCorner > -1
THEN {
selectedCorner ¬ (selectedCorner+1) MOD maxCirclePoints;
newParts.cpArray[selectedCorner] ¬ TRUE;
}
ELSE ERROR;
};
$Backward => {
IF selectedCorner > -1
THEN {
selectedCorner ¬ (selectedCorner+maxCirclePoints-1) MOD maxCirclePoints;
newParts.cpArray[selectedCorner] ¬ TRUE;
}
ELSE ERROR;
};
$Grow => RETURN[GGSliceOps.NewParts[sliceD.slice, NIL, slice]];
$ShrinkForward, $ShrinkBackward => RETURN[NIL]; -- no children to shrink to
ENDCASE => ERROR;
newD ¬ GGSlice.DescriptorFromParts[sliceD.slice, newParts];
};
Part Generators
CirclePointsInDescriptor:
PROC [sliceD: SliceDescriptor]
RETURNS [pointGen: PointGenerator] = {
parts: CircleParts ¬ NARROW[sliceD.parts];
pointGen ¬ NEW[PointGeneratorObj ¬ [sliceD, 0, 0, NIL] ];
FOR index: CirclePoints
IN CirclePoints
DO
IF parts.cpArray[index] THEN pointGen.toGo ¬ pointGen.toGo + 1;
ENDLOOP;
};
CircleWalkPointsInDescriptor:
PROC [sliceD: SliceDescriptor, walkProc: PointWalkProc] = {
parts: CircleParts ¬ NARROW[sliceD.parts];
circleData: CircleData ¬ NARROW[sliceD.slice.data];
done: BOOL ¬ FALSE;
FOR index: CirclePoints
IN CirclePoints
DO
IF parts.cpArray[index] THEN done ¬ walkProc[GetCirclePoint[circleData, index]];
IF done THEN RETURN;
ENDLOOP;
};
CircleSegmentsInDescriptor:
PROC [sliceD: SliceDescriptor]
RETURNS [segGen: SegmentGenerator] = {
parts: CircleParts ¬ NARROW[sliceD.parts];
segGen ¬ NEW[SegmentGeneratorObj ¬ [traj: NIL, toGo: 0, index: 0, touched: 0, seq: NIL, completeSeq: FALSE, sliceD: sliceD] ];
segGen.toGo ¬ IF parts.outline THEN 1 ELSE 0;
};
CircleWalkSegments:
PROC [slice: Slice, walkProc: WalkProc]
RETURNS [sliceD: SliceDescriptor] = {
GGModelTypes.SliceWalkSegmentsProc
circleData: CircleData ¬ NARROW[slice.data];
circleParts: CircleParts ¬ NEW[CirclePartsObj ¬ [cpArray: ALL[FALSE], outline: FALSE]];
circleParts.outline ¬ walkProc[circleData.seg, circleData.transform];
IF circleParts.outline THEN circleParts.cpArray ¬ ALL[TRUE]; -- select the whole thing
sliceD ¬ GGSlice.DescriptorFromParts[slice, circleParts];
};
CircleNextPoint:
PROC [slice: Slice, pointGen: PointGenerator]
RETURNS [pointAndDone: PointAndDone] = {
sliceD: SliceDescriptor ¬ pointGen.sliceD;
circleData: CircleData ¬ NARROW[sliceD.slice.data];
IF pointGen=
NIL
OR pointGen.toGo = 0
THEN {
pointAndDone.done ¬ TRUE;
RETURN;
}
ELSE {
parts: CircleParts ¬ NARROW[sliceD.parts];
index: NAT;
pointAndDone.done ¬ FALSE;
FOR index ¬ pointGen.index, index+1
UNTIL index>=
LAST[CirclePoints]
DO
IF parts.cpArray[index] THEN EXIT; -- find the next included point
ENDLOOP;
pointAndDone.point ¬ GetCirclePoint[circleData, index];
pointGen.index ¬ index+1;
pointGen.toGo ¬ pointGen.toGo - 1;
};
};
GetCirclePoint:
PROC [circleData: CircleData, index:
NAT]
RETURNS [point: Point] = {
OPEN ImagerTransformation;
t: Transformation ¬ circleData.transform;
SELECT index
FROM
0 => point ¬ Transform[t, [0.0, 0.0]]; -- origin
1 => point ¬ Transform[t, [-1.0, 0.0]]; -- left
2 => point ¬ Transform[t, [0.0, 1.0]]; -- top
3 => point ¬ Transform[t, [1.0, 0.0]]; -- right
4 => point ¬ Transform[t, [0.0, -1.0]]; -- bottom
ENDCASE => SIGNAL Problem[msg: "Broken Invariant"];
};
CircleNextSegment:
PROC [slice: Slice, segGen: SegmentGenerator]
RETURNS [seg: Segment, transform: Transformation] = {
IF segGen=NIL OR segGen.toGo = 0 THEN RETURN[NIL, NIL]
ELSE {
circleData: CircleData ¬ NARROW[segGen.sliceD.slice.data];
IF segGen.toGo#1 THEN SIGNAL Problem[msg: "Broken Invariant"];
seg ¬ circleData.seg;
transform ¬ circleData.transform;
segGen.toGo ¬ segGen.toGo-1;
};
};
CircleClosestPoint:
PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point ¬ [0.0, 0.0], bestDist:
REAL ¬ 0.0, bestNormal: Vector ¬ [0, -1], hitData:
REF
ANY, success:
BOOL ¬
FALSE] = {
GGModelTypes.SliceClosestPointProc
IF
NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[sliceD.slice], tolerance]
THEN RETURN;
BEGIN
points: ARRAY[0..4] OF Point; -- points in Gargoyle coordinates
nextDist2, bestDist2: REAL ¬ GGUtility.plusInfinity;
tolerance2: REAL ¬ tolerance*tolerance;
foundIndex: INTEGER ¬ 0;
circleData: CircleData ¬ NARROW[sliceD.slice.data];
circleHitData: CircleHitData;
circleParts: CircleParts ¬ NARROW[sliceD.parts];
nextPoint: Point;
points ¬ CirclePointsFromData[circleData];
FOR index:
INTEGER
IN [0..maxCirclePoints)
DO
IF NOT circleParts.cpArray[index] THEN LOOP;
nextPoint ¬ points[index];
nextDist2 ¬ Vectors2d.DistanceSquared[testPoint, nextPoint];
IF 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];
IF foundIndex > 0
THEN {
IF Vectors2d.Distance[points[0], testPoint] > circleData.scale.x
THEN {
bestNormal ¬ Vectors2d.Sub[points[foundIndex], points[0]] }
ELSE {bestNormal ¬ Vectors2d.Sub[points[0], points[foundIndex]]};
};
};
END;
};
Hit Testing
CircleClosestJointToHitData:
PROC [sliceD: SliceDescriptor, mapPoint, testPoint: Point, hitData:
REF
ANY]
RETURNS [jointD: SliceDescriptor, point: Point, normal: Vector ¬ [0,-1]] = {
GGModelTypes.SliceClosestJointToHitDataProc
success: BOOL ¬ FALSE;
newHitData: REF ANY;
circleHitData: CircleHitData;
circleParts: CircleParts ¬ NEW[CirclePartsObj ¬ [cpArray: ALL[FALSE], outline: FALSE]];
[point, ----, normal, newHitData, success] ¬ CircleClosestPoint[sliceD, testPoint, GGUtility.plusInfinity];
circleHitData ¬ NARROW[newHitData];
circleParts.cpArray[circleHitData.index] ¬ TRUE;
circleParts.outline ¬ circleHitData.outline;
jointD ¬ GGSlice.DescriptorFromParts[slice: sliceD.slice, parts: circleParts];
};
CircleClosestSegment:
PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point ¬ [0.0, 0.0], bestDist:
REAL ¬ 0.0, bestNormal: Vector ¬ [0,-1], hitData:
REF
ANY, success:
BOOL ¬
FALSE] = {
GGModelTypes.SliceClosestSegmentProc
IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[sliceD.slice], tolerance]
THEN RETURN;
BEGIN
circleHitData: CircleHitData;
circleParts: CircleParts ¬ NARROW[sliceD.parts];
circleData: CircleData ¬ NARROW[sliceD.slice.data];
pointOnCircle: Point ¬ [0.0, 0.0];
localTestpoint: Point;
dist: REAL;
IF NOT circleParts.outline THEN RETURN[[0,0], 0.0, [0,-1], NIL, FALSE];
localTestpoint ¬ ImagerTransformation.Transform[circleData.inverse, testPoint];
dist ¬ Vectors2d.Magnitude[localTestpoint];
Find hit point on unit circle
IF dist > 0.0 THEN pointOnCircle ¬ Vectors2d.Scale[localTestpoint, 1.0/dist];
Transform back to world coords
bestPoint ¬ ImagerTransformation.Transform[circleData.transform, pointOnCircle];
bestDist ¬ Vectors2d.Distance[testPoint, bestPoint];
IF bestDist<tolerance
THEN {
hitData ¬ circleHitData ¬ NEW[CircleHitDataObj ¬ [index: -1, hitPoint: bestPoint, outline: TRUE] ];
success ¬ TRUE;
bestNormal ¬ Vectors2d.Sub[testPoint, bestPoint];
};
END;
};
CircleFilledPathsUnderPoint:
PUBLIC
PROC [slice: Slice, point: Point, tolerance:
REAL]
RETURNS [hitData:
REF
ANY ¬
NIL, moreHitDatas:
LIST
OF
REF
ANY ¬
NIL] = {
circleData: CircleData ¬ NARROW[slice.data];
pointCircle: Point ¬ ImagerTransformation.Transform[circleData.inverse, point];
success: BOOL ¬ FALSE;
success ¬ circleData.fillColor # NIL AND GGCircles.PointIsInCircle[pointCircle, circleData.circle];
IF success
THEN {
hitData ¬ NEW[CircleHitDataObj ¬ [index: 0, hitPoint: point, outline: FALSE]];
};
};
CircleLineIntersection:
PROC [sliceD: SliceDescriptor, line: Line]
RETURNS [points:
LIST
OF Point, pointCount:
NAT ¬ 0] = {
circleData: CircleData ¬ NARROW[sliceD.slice.data];
circleParts: CircleParts ¬ NARROW[sliceD.parts];
epsilon: REAL = 0.072;
localLine: Line ¬ Lines2d.LineTransform[line, circleData.inverse];
localPoints: ARRAY [1..2] OF Point;
[localPoints, pointCount] ¬ GGCircles.CircleMeetsLine[circleData.circle, localLine];
FOR i:
NAT
IN [1..pointCount]
DO
points ¬ CONS[ImagerTransformation.Transform[m: circleData.transform, v: localPoints[i]], points];
ENDLOOP;
};
CircleHitDataAsSimpleCurve:
PROC [slice: Slice, hitData:
REF
ANY]
RETURNS [simpleCurve:
REF
ANY] = {
circleHitData: CircleHitData ¬ NARROW[hitData];
circleData: CircleData ¬ NARROW[slice.data];
IF NOT circleHitData.outline THEN RETURN[NIL];
IF NOT circleData.evenScaling THEN RETURN[NIL];
simpleCurve ¬ circleData.simpleCircle;
};
CirclePointsFromData:
PROC [circleData: CircleData]
RETURNS [points:
ARRAY [0..4]
OF Point] = {
origin, left, top, right, bottom
t: Transformation ¬ circleData.transform;
points[0] ¬ ImagerTransformation.Transform[t, [0.0, 0.0] ];
points[1] ¬ ImagerTransformation.Transform[t, [-1.0, 0.0] ];
points[2] ¬ ImagerTransformation.Transform[t, [0.0, 1.0] ];
points[3] ¬ ImagerTransformation.Transform[t, [1.0, 0.0] ];
points[4] ¬ ImagerTransformation.Transform[t, [0.0, -1.0] ];
};
CircleSetDefaults:
PROC [slice: Slice, parts: SliceParts, defaults: DefaultData, history: HistoryEvent] = {
circleData: CircleData ¬ NARROW[slice.data];
GGSegment.SetDefaults[circleData.seg, defaults];
circleData.fillColor ¬ defaults.fillColor;
GGSlice.KillBoundBox[slice];
CircleSetStrokeWidth:
PROC [slice: Slice, parts: SliceParts, strokeWidth:
REAL, history: HistoryEvent]
RETURNS [box: BoundBox] = {
circleData: CircleData ¬ NARROW[slice.data];
IF circleData.seg.strokeWidth#strokeWidth
THEN {
circleData.seg.strokeWidth ¬ strokeWidth;
GGSlice.KillBoundBox[slice];
};
box ¬ GGBoundBox.CopyBoundBox[GGSliceOps.GetBoundBox[slice, parts]]; -- return a copy in case caller or descendants changes values in boundBox
CircleGetStrokeWidth:
PROC [slice: Slice, parts: SliceParts]
RETURNS [strokeWidth:
REAL, isUnique:
BOOL ¬
TRUE] = {
circleData: CircleData ¬ NARROW[slice.data];
strokeWidth ¬ circleData.seg.strokeWidth;
CircleSetStrokeEnd:
PROC [slice: Slice, parts: SliceParts, strokeEnd: StrokeEnd, history: HistoryEvent] = {
circleData: CircleData ¬ NARROW[slice.data];
circleData.seg.strokeEnd ¬ strokeEnd;
};
CircleGetStrokeEnd:
PROC [slice: Slice, parts: SliceParts]
RETURNS [strokeEnd: StrokeEnd, isUnique:
BOOL ¬
TRUE] = {
circleData: CircleData ¬ NARROW[slice.data];
RETURN[circleData.seg.strokeEnd, TRUE];
};
CircleSetStrokeColor:
PROC [slice: Slice, parts: SliceParts, color: Color, setHow:
ATOM, history: HistoryEvent] = {
circleData: CircleData ¬ NARROW[slice.data];
SELECT setHow
FROM
$Set => circleData.seg.color ¬ color;
$ChangeHue => {
newColor: Color ¬ GGUtility.ChangeHue[circleData.seg.color, color];
circleData.seg.color ¬ newColor;
};
ENDCASE => ERROR;
};
CircleGetStrokeColor:
PROC [slice: Slice, parts: SliceParts]
RETURNS [color: Color, isUnique:
BOOL ¬
TRUE] = {
circleData: CircleData ¬ NARROW[slice.data];
color ¬ circleData.seg.color;
};
CircleSetFillColor:
PROC [slice: Slice, parts: SliceParts, color: Color, setHow:
ATOM, history: HistoryEvent] = {
circleData: CircleData ¬ NARROW[slice.data];
SELECT setHow
FROM
$Set => circleData.fillColor ¬ color;
$ChangeHue => {
newColor: Color ¬ GGUtility.ChangeHue[circleData.fillColor, color];
circleData.fillColor ¬ newColor;
};
ENDCASE => ERROR;
};
CircleGetFillColor:
PROC [slice: Slice, parts: SliceParts]
RETURNS [color: Color, isUnique:
BOOL ¬
TRUE] = {
circleData: CircleData ¬ NARROW[slice.data];
color ¬ circleData.fillColor;
};
CircleSetDashed:
PROC [slice: Slice, parts: SliceParts, dashed:
BOOL ¬
FALSE, pattern: SequenceOfReal ¬
NIL, offset:
REAL ¬ 0.0, length:
REAL ¬ -1.0, history: HistoryEvent] = {
circleParts: CircleParts ¬ NARROW[parts];
circleData: CircleData ¬ NARROW[slice.data];
IF circleParts=
NIL
OR circleParts.outline
THEN {
seg: Segment ¬ circleData.seg;
seg.dashed ¬ dashed;
seg.pattern ¬ pattern;
seg.offset ¬ offset;
seg.length ¬ length;
};
};
CircleGetDashed:
PROC [slice: Slice, parts: SliceParts]
RETURNS [dashed:
BOOL ¬
FALSE, pattern: SequenceOfReal, offset, length:
REAL, isUnique:
BOOL ¬
TRUE] = {
circleParts: CircleParts ¬ NARROW[parts];
circleData: CircleData ¬ NARROW[slice.data];
IF circleParts=
NIL
OR circleParts.outline
THEN {
seg: Segment ¬ circleData.seg;
dashed ¬ seg.dashed;
pattern ¬ seg.pattern;
offset ¬ seg.offset;
length ¬ seg.length;
};
};
CircleSetOrientation:
PROC [slice: Slice, parts: SliceParts, orientation: Orientation, history: HistoryEvent]
RETURNS [success:
BOOL ¬
TRUE] = {
circleData: CircleData ¬ NARROW[slice.data];
SELECT orientation
FROM
cw => circleData.forward ¬ TRUE; -- forward => cw
ccw => circleData.forward ¬ FALSE;
reverse => circleData.forward ¬ NOT circleData.forward;
ENDCASE => ERROR;
};
CircleGetOrientation:
PROC [slice: Slice, parts: SliceParts]
RETURNS [orientation: Orientation, isUnique:
BOOL ¬
TRUE] = {
circleData: CircleData ¬ NARROW[slice.data];
RETURN[IF circleData.forward THEN cw ELSE ccw];
};
MakeComplete:
PROC [circleParts: CircleParts] = {
circleParts.cpArray ¬ ALL[TRUE];
circleParts.outline ¬ TRUE;
};
IsComplete:
PROC [circleParts: CircleParts]
RETURNS [
BOOL ¬
FALSE] = {
RETURN[ circleParts#NIL AND circleParts.cpArray=ALL[TRUE] AND circleParts.outline];
};
IsEmpty:
PROC [circleParts: CircleParts]
RETURNS [
BOOL ¬
FALSE] = {
RETURN[circleParts = NIL OR (circleParts.cpArray=ALL[FALSE] AND circleParts.outline=FALSE)];
};
NoOpBoundBox:
PUBLIC GGModelTypes.SliceBoundBoxProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpTransformedBoundBox:
PUBLIC GGModelTypes.SliceTransformedBoundBoxProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpTightBox:
PUBLIC GGModelTypes.SliceTightBoxProc = {
box ¬ NIL;
};
NoOpCopy:
PUBLIC GGModelTypes.SliceCopyProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpRestore:
PUBLIC GGModelTypes.SliceRestoreProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpBuildPath:
PUBLIC GGModelTypes.SliceBuildPathProc = {
};
NoOpDrawBorder:
PUBLIC GGModelTypes.SliceDrawBorderProc = {
};
NoOpDrawParts:
PUBLIC GGModelTypes.SliceDrawPartsProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpDrawTransform:
PUBLIC GGModelTypes.SliceDrawTransformProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpDrawSelectionFeedback:
PUBLIC GGModelTypes.SliceDrawSelectionFeedbackProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpDrawAttractorFeedback:
PUBLIC GGModelTypes.SliceDrawAttractorFeedbackProc = {
IF
NOT (dragInProgress
AND selectedParts#
NIL)
THEN {
pointGen: PointGenerator;
pointPairGen: PointPairGenerator;
wholeSliceD: SliceDescriptor ¬ GGSliceOps.NewParts[slice, NIL, topLevel];
Imager.SetStrokeEnd[dc, round];
Imager.SetStrokeWidth[dc, 1.0];
Imager.SetColor[dc, Imager.black];
pointPairGen ¬ GGSliceOps.PointPairsInDescriptor[wholeSliceD];
FOR pointPairAndDone: PointPairAndDone ¬ GGSliceOps.NextPointPair[slice, pointPairGen], GGSliceOps.NextPointPair[slice, pointPairGen]
UNTIL pointPairAndDone.done
DO
Imager.MaskVector[dc, pointPairAndDone.lo, pointPairAndDone.hi];
ENDLOOP;
pointGen ¬ GGSliceOps.PointsInDescriptor[wholeSliceD];
FOR pointAndDone: PointAndDone ¬ GGSliceOps.NextPoint[slice, pointGen], GGSliceOps.NextPoint[slice, pointGen]
UNTIL pointAndDone.done
DO
GGShapes.DrawCP[dc, pointAndDone.point, camera.cpScale];
ENDLOOP;
};
NoOpAttractorFeedbackBoundBox:
PUBLIC GGModelTypes.SliceAttractorFeedbackBoundBoxProc = {
box ¬ GGBoundBox.CopyBoundBox[GGSliceOps.GetTightBox[slice, NIL]];
GGBoundBox.EnlargeByOffset[box, GGModelTypes.halfJointSize*camera.cpScale + 1.0];
};
NoOpSaveSelections:
PUBLIC GGModelTypes.SliceSaveSelectionsProc = {
};
NoOpRemakeSelections:
PUBLIC GGModelTypes.SliceRemakeSelectionsProc = {
};
NoOpSetOrientation:
PUBLIC GGModelTypes.SliceSetOrientationProc = {
RETURN[FALSE];
};
NoOpGetOrientation:
PUBLIC GGModelTypes.SliceGetOrientationProc = {
RETURN[ccw];
};
NoOpTransform:
PUBLIC GGModelTypes.SliceTransformProc = {
SIGNAL Problem[msg: "All slice classes must have this proc."];
};
NoOpDescribe:
PUBLIC GGModelTypes.SliceDescribeProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpDescribeHit:
PUBLIC GGModelTypes.SliceDescribeHitProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpFileout:
PUBLIC GGModelTypes.SliceFileoutProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpFilein:
PUBLIC GGModelTypes.SliceFileinProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOp Parts moved to GGSliceImplF.
NoOp Style moved to GGSliceImplF.
GenericTransformedBoundBox:
PUBLIC GGModelTypes.SliceTransformedBoundBoxProc = {
this proc is an expensive way to calculate the bound box of a transformed object
make a complete copy that can then be transformed, transform it, then get its bound box
IF GGSliceOps.IsCompleteParts[GGSlice.DescriptorFromParts[slice, selectedParts]]
THEN {
an optimization. If the selected parts is complete then there is no need to make a copy of the object. Simply get the original complete bound box and transform the box.
box ¬ GGSliceOps.GetBoundBox[slice, NIL];
box ¬ GGBoundBox.BoundBoxOfBoundBox[box, transform]; -- returns new boundBox
}
ELSE {
Note that the following code works ONLY for slices whose Parts do not contain references to other slices. The reason for this is that we call procedures in this code using a copy of the SLICE but the original PARTS; if those original Parts contain references to slices they will refer to the ORIGINAL slices not the copied slices and the originals will get transformed instead of the copy. We do this because it is deemed too onerous to construct Parts for the copy which mirror the Parts of the original.
copies: LIST OF Slice ¬ GGSliceOps.Copy[slice, NIL];
IF copies.rest#NIL THEN SIGNAL Problem [msg: "Failed to procure complete copy"];
GGSliceOps.Transform[copies.first, selectedParts, transform, none, NIL]; -- transform it
box ¬ GGBoundBox.CopyBoundBox[GGSliceOps.GetBoundBox[copies.first, movingParts]];
must make a copy because it may be mutated later
};
};
Init:
PROC [] = {
cache ¬ FunctionCache.Create[maxEntries: 100];
};
cache: FunctionCache.Cache;
Init[];
END.