GGSliceImplA.mesa
Copyright c 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on January 15, 1987 2:22:23 pm PST
Last edited by Bier on January 15, 1987 1:21:28 am PST
Contents: Implements various slice classes in Gargoyle.
DIRECTORY
Atom, GGBasicTypes, GGBoundBox, GGCircles, GGError, GGLines, GGSlice, GGInterfaceTypes, GGModelTypes, GGObjects, GGShapes, GGParseOut, GGParseIn, GGTransform, GGUtility, GGVector, Imager, ImagerTransformation, ImagerColor, IO, NodeStyle, RealFns, Rope, ViewerClasses;
GGSliceImplA: CEDAR PROGRAM
IMPORTS Atom, GGBoundBox, GGCircles, GGError, GGLines, GGObjects, GGSlice, GGParseIn, GGParseOut, GGShapes, GGTransform, GGVector, Imager, IO, RealFns, ImagerTransformation, Rope EXPORTS GGSlice = BEGIN
Scene: TYPE = GGModelTypes.Scene;
BoundBox: TYPE = GGModelTypes.BoundBox;
CameraData: TYPE = GGInterfaceTypes.CameraData;
Circle: TYPE = GGBasicTypes.Circle;
Corner: TYPE = GGSlice.Corner;
DisplayStyle: TYPE = GGSlice.DisplayStyle;
Edge: TYPE = GGBasicTypes.Edge;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
Line: TYPE = GGBasicTypes.Line;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj;
SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData;
SelectionClass: TYPE = GGInterfaceTypes.SelectionClass;
SelectMode: TYPE = GGModelTypes.SelectMode;
Slice: TYPE = GGModelTypes.Slice;
SliceClass: TYPE = GGModelTypes.SliceClass;
SliceClassObj: TYPE = GGModelTypes.SliceClassObj;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
SliceObj: TYPE = GGModelTypes.SliceObj;
SliceParts: TYPE = GGModelTypes.SliceParts;
Viewer: TYPE = ViewerClasses.Viewer;
Vector: TYPE = GGBasicTypes.Vector;
SliceClassDef: TYPE = REF SliceClassDefObj;
SliceClassDefObj: TYPE = RECORD[type: ATOM, class: SliceClass];
Box Slice Class
BoxData: TYPE = REF BoxDataObj;
BoxDataObj: TYPE = RECORD [
box: BoundBox, -- this is not used as a bounding box. It is a representation of the shape.
tightBox: BoundBox, -- a Manhattan aligned box, that contains this box. tightBox does NOT allow for stroke width or control points.
transform: ImagerTransformation.Transformation,
inverse: ImagerTransformation.Transformation, -- inverse of transform
inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s
strokeWidths: ARRAY [0..4) OF REAL, -- in order left, top, right, bottom
strokeColors: ARRAY [0..4) OF Imager.Color, -- in order left, top, right, bottom
fillColor: Imager.Color
];
CornerArray: TYPE = ARRAY [0..4) OF BOOL; -- ll, ul, ur, lr. Clockwise order of corners.
EdgeArray: TYPE = ARRAY [0..4) OF BOOL; -- left, top, right, bottom. Clockwise order of edges.
BoxParts: TYPE = REF BoxPartsObj;
BoxPartsObj: TYPE = RECORD [
corners: CornerArray, -- which corners of box are selected.
edges: EdgeArray, -- which edges of box are selected.
center: BOOL -- is the middle joint selected?
];
BoxHitData: TYPE = REF BoxHitDataObj;
BoxHitDataObj: TYPE = RECORD [
corner: [-1..3],
edge: [-1..3],
center: [-1..0],
hitPoint: Point
];
BuildBoxSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = {
OPEN GGSlice;
class ← NEW[SliceClassObj ← [
type: $Box,
Fundamentals
getBoundBox: BoxGetBoundBox,
getTightBox: BoxGetTightBox,
copy: BoxCopy,
Drawing
drawParts: BoxDrawParts,
drawTransform: BoxDrawTransform,
drawSelectionFeedback: BoxDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
Transforming
transform: BoxTransform,
Textual Description
describe: BoxDescribe,
fileout: BoxFileout,
filein: BoxFilein,
Parts
emptyParts: BoxEmptyParts,
newParts: BoxNewParts,
unionParts: BoxUnionParts,
differenceParts: BoxDiffParts,
movingParts: BoxMovingParts,
fixedParts: BoxFixedParts,
augmentParts: BoxAugmentParts,
Part Generators
pointsInDescriptor: BoxPointsInDescriptor,
pointPairsInDescriptor: BoxPointPairsInDescriptor,
nextPoint: BoxNextPoint,
nextPointPair: BoxNextPointPair,
Hit Testing
closestPoint: BoxClosestPoint,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: BoxClosestSegment,
lineIntersection: BoxLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: BoxHitDataAsSimpleCurve,
Style
setStrokeWidth: BoxSetStrokeWidth,
getStrokeWidth: BoxGetStrokeWidth,
setStrokeColor: BoxSetStrokeColor,
getStrokeColor: BoxGetStrokeColor,
setFillColor: BoxSetFillColor,
getFillColor: BoxGetFillColor
]];
};
MakeBoxSlice: PUBLIC PROC [box: BoundBox, corner: Corner, transform: ImagerTransformation.Transformation ← GGTransform.Identity[], strokeWidth: REAL ← 2.0] RETURNS [sliceD: SliceDescriptor] = {
requires a bound box input with loX<=hiX AND loY<=hiY
boxSlice: Slice;
inverse: ImagerTransformation.Transformation ← ImagerTransformation.Invert[transform];
boxData: BoxData ← NEW[BoxDataObj ← [box: box, tightBox: GGBoundBox.NullBoundBox[], transform: transform, inverse: inverse, inverseScale: ImagerTransformation.Factor[inverse].s, strokeWidths: [strokeWidth, strokeWidth, strokeWidth, strokeWidth], strokeColors: [Imager.black, Imager.black, Imager.black, Imager.black], fillColor: NIL] ];
boxParts: BoxParts ← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE] ];
cIndex: INTEGERSELECT corner FROM ll=>0, ul=>1, ur=>2, lr=>3,ENDCASE=>2;
boxParts.corners[cIndex] ← TRUE;
IF box.loX > box.hiX THEN {t: REAL ← box.loX; box.loX ← box.hiX; box.hiX ← t};
IF box.loY > box.hiY THEN {t: REAL ← box.loY; box.loY ← box.hiY; box.hiY ← t};
boxSlice ← NEW[SliceObj ← [
class: FetchSliceClass[$Box],
data: boxData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
boundBox: GGBoundBox.NullBoundBox[]
]];
BoxSetBoundBox[boxSlice];
sliceD ← NEW[SliceDescriptorObj ← [boxSlice, boxParts] ];
};
BoxMaxStrokeWidth: PROC [boxData: BoxData] RETURNS [maxWidth: REAL] = {
maxWidth ← boxData.strokeWidths[0];
FOR i: NAT IN [1..3] DO
IF boxData.strokeWidths[i] > maxWidth THEN maxWidth ← boxData.strokeWidths[i];
ENDLOOP;
};
BoxSetBoundBox: PROC [slice: Slice] = {
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REAL ← BoxMaxStrokeWidth[NARROW[slice.data]];
pad: REALMAX[cpHalf, strokeWidth];
boxData: BoxData ← NARROW[slice.data];
GGBoundBox.UpdateBoundBoxOfBoundBox[boxData.tightBox, boxData.box, boxData.transform];
GGBoundBox.UpdateCopyBoundBox[bBox: slice.boundBox, from: boxData.tightBox];
GGBoundBox.EnlargeByOffset[slice.boundBox, pad];
};
Class Procedures
BoxGetBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
BoxGetTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
boxData: BoxData ← NARROW[slice.data];
RETURN[boxData.box];
};
BoxCopy: PROC [slice: Slice] RETURNS [copy: Slice] = {
GGModelTypes.SliceCopyProc
copyData: BoxData;
boxData: BoxData ← NARROW[slice.data];
box: BoundBox ← GGBoundBox.CopyBoundBox[boxData.box];
transform: Imager.Transformation ← ImagerTransformation.Copy[boxData.transform];
copy ← MakeBoxSlice[box, ur, transform].slice; -- does ur work here ??
copyData ← NARROW[copy.data];
FOR i: NAT IN [0..4) DO
copyData.strokeWidths[i] ← boxData.strokeWidths[i];
copyData.strokeColors[i] ← boxData.strokeColors[i];
ENDLOOP;
copyData.fillColor ← boxData.fillColor;
};
BoxDrawParts: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick: BOOL] = {
GGModelTypes.SliceDrawPartsProc
DoBoxDrawParts: PROC = {
IF parts = NIL OR IsComplete[parts] THEN BoxDrawAll[dc, boxData, [boxData.box.loX, boxData.box.loY], [boxData.box.hiX, boxData.box.hiY], camera]
ELSE {
BoxPathProc: Imager.PathProc = { moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]]; };
BoxEdgeDraw: PROC [index: INTEGER, p1, p2: Point] = {
IF boxData.strokeColors[index]=NIL OR boxData.strokeWidths[index]=0.0 THEN RETURN;
Imager.SetStrokeWidth[dc, boxData.strokeWidths[index] ];
Imager.SetColor[dc, boxData.strokeColors[index] ];
Imager.MaskVector[dc, p1, p2];
};
boxParts: BoxParts ← NARROW[parts];
t: ImagerTransformation.Transformation ← boxData.transform;
tLL: Point ← [MIN[boxData.box.loX, boxData.box.hiX], MIN[boxData.box.loY, boxData.box.hiY] ];
tUR: Point ← [MAX[boxData.box.loX, boxData.box.hiX], MAX[boxData.box.loY, boxData.box.hiY] ];
pts: ARRAY[0..3] OF Point;
pts[0] ← ImagerTransformation.Transform[t, tLL];
pts[1] ← ImagerTransformation.Transform[t, [tLL.x, tUR.y] ];
pts[2] ← ImagerTransformation.Transform[t, tUR];
pts[3] ← ImagerTransformation.Transform[t, [tUR.x, tLL.y] ];
draw fill
IF AllEdges[parts] AND boxData.fillColor#NIL THEN {
Imager.SetColor[dc, boxData.fillColor];
Imager.MaskFill[dc, BoxPathProc ];
};
draw outline
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeEnd[dc, round];
Imager.SetStrokeEnd[dc, square];
FOR edge: INTEGER IN [0..4) DO
IF boxParts.edges[edge] THEN BoxEdgeDraw[edge, pts[edge], pts[(edge + 1) MOD 4]];
ENDLOOP;
};
};
boxData: BoxData ← NARROW[slice.data];
Imager.DoSaveAll[dc, DoBoxDrawParts];
};
BoxDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
DoDrawFeedback: PROC = {
BoxEdgeFeedback: PROC [index: INTEGER, p1, p2: Point] = {
Imager.SetStrokeWidth[dc, 2.0*(IF boxData.strokeWidths[index]#0.0 THEN boxData.strokeWidths[index] ELSE 1.0) ];
Imager.SetColor[dc, IF boxData.strokeColors[index]#NIL THEN boxData.strokeColors[index] ELSE Imager.black];
Imager.MaskVector[dc, p1, p2];
};
box: BoundBox ← boxData.box;
t: ImagerTransformation.Transformation ← boxData.transform;
cc: Point;
thisCPisHot, thisCPisSelected: BOOL;
pts: ARRAY[0..3] OF Point;
pts[0] ← ImagerTransformation.Transform[t, [box.loX, box.loY] ];
pts[1] ← ImagerTransformation.Transform[t, [box.loX, box.hiY] ];
pts[2] ← ImagerTransformation.Transform[t, [box.hiX, box.hiY] ];
pts[3] ← ImagerTransformation.Transform[t, [box.hiX, box.loY] ];
FOR corner: INTEGER IN [0..4) DO
thisCPisHot ← hotBoxParts#NIL AND hotBoxParts.corners[corner];
thisCPisSelected ← normalBoxParts#NIL AND normalBoxParts.corners[corner];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[corner], hot];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[corner], normal];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[corner]];
ENDLOOP;
thisCPisHot ← hotBoxParts#NIL AND hotBoxParts.center;
thisCPisSelected ← normalBoxParts#NIL AND normalBoxParts.center;
cc ← ImagerTransformation.Transform[t, [(box.loX+box.hiX)/2.0, (box.loY+box.hiY)/2.0]];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, cc, hot];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, cc, normal];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, cc];
IF NOT quick THEN {
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeEnd[dc, round];
FOR edge: INTEGER IN [0..4) DO
IF hotBoxParts#NIL AND hotBoxParts.edges[edge] THEN BoxEdgeFeedback[edge, pts[edge], pts[(edge+1) MOD 4]];
ENDLOOP;
};
};
normalBoxParts, hotBoxParts: BoxParts;
boxData: BoxData ← NARROW[slice.data];
IF caretIsMoving OR dragInProgress THEN RETURN;
IF selectedParts = NIL AND hotParts = NIL THEN RETURN;
normalBoxParts ← NARROW[selectedParts];
hotBoxParts ← NARROW[hotParts];
IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback];
};
BoxDrawAll: PROC [dc: Imager.Context, boxData: BoxData, from: Point, to: Point, camera: CameraData --, drawCPs: BOOLTRUE--] = {
BoxPathProc: Imager.PathProc = { moveTo[ll]; lineTo[ul]; lineTo[ur]; lineTo[lr]; };
BoxEdgeDraw: PROC [index: INTEGER, p1, p2: Point] = {
IF boxData.strokeColors[index]=NIL OR boxData.strokeWidths[index]=0.0 THEN RETURN;
Imager.SetStrokeWidth[dc, boxData.strokeWidths[index] ];
Imager.SetColor[dc, boxData.strokeColors[index] ];
Imager.MaskVector[dc, p1, p2];
};
t: ImagerTransformation.Transformation ← boxData.transform;
tLL: Point ← [MIN[to.x, from.x], MIN[to.y, from.y] ];
tUR: Point ← [MAX[to.x, from.x], MAX[to.y, from.y] ];
ll: Point ← ImagerTransformation.Transform[t, tLL];
ul: Point ← ImagerTransformation.Transform[t, [tLL.x, tUR.y] ];
ur: Point ← ImagerTransformation.Transform[t, tUR];
lr: Point ← ImagerTransformation.Transform[t, [tUR.x, tLL.y] ];
draw fill
IF boxData.fillColor#NIL THEN {
Imager.SetColor[dc, boxData.fillColor];
Imager.MaskFill[dc, BoxPathProc ];
};
draw outline
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeEnd[dc, round];
Imager.SetStrokeEnd[dc, square];
BoxEdgeDraw[0, ll, ul];
BoxEdgeDraw[1, ul, ur];
BoxEdgeDraw[2, ur, lr];
BoxEdgeDraw[3, lr, ll];
draw control points
IF camera.quality#quality AND drawCPs THEN {
GGShapes.DrawCP[dc, ll ];
GGShapes.DrawCP[dc, ul ];
GGShapes.DrawCP[dc, ur ];
GGShapes.DrawCP[dc, lr ];
GGShapes.DrawCP[dc, GGVector.Scale[GGVector.Add[ll, ur], 0.5] ];
};
};
BoxDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
This is what makes boxes behave specially. Depending on which parts are selected, the points are transformed and the box is rubberbanded properly. The box data itself is not modified.
point, oppositePoint: ImagerTransformation.VEC; -- really points, not vectors
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
inverse, totalTransform: ImagerTransformation.Transformation;
cornerCount, edgeCount: INTEGER ← 0;
EasyDraw: PROC = {
Imager.ConcatT[dc, transform]; -- temporary transformation
BoxDrawAll[dc, boxData, [box.loX, box.loY], [box.hiX, box.hiY], camera--, FALSE--];
};
HardDraw: PROC = {
total transformation has already occurred on point, oppositePoint
BoxDrawAll[dc, boxData, point, oppositePoint, camera--, FALSE--];
};
IF box.null OR box.infinite THEN ERROR;
IF IsComplete[boxParts] THEN {Imager.DoSaveAll[dc, EasyDraw]; RETURN;};
IF IsEmpty[boxParts] THEN RETURN; -- no parts. Legal.
More than one edge or more than two corners. Full transform.
IF (edgeCount ← CountEdges[boxParts.edges]) >= 2 OR (cornerCount ← CountCorners[boxParts.corners]) >= 3 OR boxParts.center THEN {Imager.DoSaveAll[dc, EasyDraw]; RETURN;};
IF edgeCount=1 THEN { -- one edge. Transform it.
f: ImagerTransformation.FactoredTransformation ← ImagerTransformation.Factor[transform];
globalTranslate: ImagerTransformation.VEC ← f.t;
mInverse: ImagerTransformation.Transformation ← boxData.inverse;
localTranslate: ImagerTransformation.VEC ← ImagerTransformation.TransformVec[mInverse, globalTranslate];
totalTransform: ImagerTransformation.Transformation ← ImagerTransformation.Translate[localTranslate];
SELECT TRUE FROM
boxParts.edges[0] => { -- left
IF NOT (boxParts.corners[2] OR boxParts.corners[3]) THEN {
point ← [ box.loX, 0 ];
oppositePoint ← [ box.hiX, box.hiY ];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
point ← [ point.x, box.loY]; -- result point is transformed in x only
}
ELSE {Imager.DoSaveAll[dc, EasyDraw]; RETURN;}
};
boxParts.edges[1] => { -- top
IF NOT (boxParts.corners[0] OR boxParts.corners[3]) THEN {
point ← [ 0, box.hiY ];
oppositePoint ← [ box.loX, box.loY ];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
point ← [ box.hiX, point.y]; -- result point is transformed in y only
}
ELSE {Imager.DoSaveAll[dc, EasyDraw]; RETURN;}
};
boxParts.edges[2] => { -- right
IF NOT (boxParts.corners[0] OR boxParts.corners[1]) THEN {
point ← [ box.hiX, 0 ];
oppositePoint ← [ box.loX, box.loY ];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
point ← [ point.x, box.hiY]; -- result point is transformed in x only
}
ELSE {Imager.DoSaveAll[dc, EasyDraw]; RETURN;}
};
boxParts.edges[3] => { -- bottom
IF NOT (boxParts.corners[1] OR boxParts.corners[2]) THEN {
point ← [ 0, box.loY ];
oppositePoint ← [ box.hiX, box.hiY ];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
point ← [ box.loX, point.y]; -- result point is transformed in y only
}
ELSE {Imager.DoSaveAll[dc, EasyDraw]; RETURN;}
};
ENDCASE => ERROR;
}
ELSE IF cornerCount IN [1..2] THEN { -- one corner or two isolated corners. Transform one of them.
SELECT TRUE FROM
boxParts.corners[0] => { point ← [ box.loX, box.loY ]; oppositePoint ← [ box.hiX, box.hiY ];};
boxParts.corners[1] => { point ← [ box.loX, box.hiY ]; oppositePoint ← [ box.hiX, box.loY ];};
boxParts.corners[2] => { point ← [ box.hiX, box.hiY ]; oppositePoint ← [ box.loX, box.loY ];};
boxParts.corners[3] => { point ← [ box.hiX, box.loY ]; oppositePoint ← [ box.loX, box.hiY ];};
ENDCASE => ERROR;
inverse ← boxData.inverse;
totalTransform ← ImagerTransformation.Cat[boxData.transform, transform, inverse];
point ← ImagerTransformation.Transform[m: totalTransform, v: point]; -- transform the dragging point
};
Imager.DoSaveAll[dc, HardDraw];
};
BoxTransform: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceTransformProc
Permanently transforms the box. Depending on which parts are selected, the points are transformed and the box is grown/shrunk properly.
point, oppositePoint: ImagerTransformation.VEC; -- really points, not vectors
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
inverse, totalTransform: ImagerTransformation.Transformation;
cornerCount, edgeCount: INTEGER ← 0;
IF box.null OR box.infinite THEN ERROR;
IF IsComplete[boxParts] THEN {
boxData.transform ← ImagerTransformation.Concat[boxData.transform, transform];
boxData.inverse ← ImagerTransformation.Invert[boxData.transform];
boxData.inverseScale ← ImagerTransformation.Factor[boxData.inverse].s;
BoxSetBoundBox[slice];
RETURN;
};
IF IsEmpty[boxParts] THEN RETURN; -- no parts. Legal.
IF (edgeCount ← CountEdges[boxParts.edges]) >= 2 OR (cornerCount ← CountCorners[boxParts.corners]) >= 3 OR boxParts.center THEN { -- more than one edge or more than two corners. Full transform.
boxData.transform ← ImagerTransformation.Concat[boxData.transform, transform];
boxData.inverse ← ImagerTransformation.Invert[boxData.transform];
boxData.inverseScale ← ImagerTransformation.Factor[boxData.inverse].s;
BoxSetBoundBox[slice];
RETURN;
};
IF edgeCount=1 THEN { -- transform an edge
f: ImagerTransformation.FactoredTransformation ← ImagerTransformation.Factor[transform];
globalTranslate: ImagerTransformation.VEC ← f.t;
mInverse: ImagerTransformation.Transformation ← ImagerTransformation.Invert[boxData.transform];
localTranslate: ImagerTransformation.VEC ← ImagerTransformation.TransformVec[mInverse, globalTranslate];
totalTransform: ImagerTransformation.Transformation ← ImagerTransformation.Translate[localTranslate];
SELECT TRUE FROM
boxParts.edges[0] => {
point ← [ box.loX, 0 ];
oppositePoint ← [ box.hiX, box.hiY ];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
point ← [ point.x, box.loY]; -- result point is transformed in x only
};
boxParts.edges[1] => {
point ← [ 0, box.hiY ];
oppositePoint ← [ box.loX, box.loY ];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
point ← [ box.hiX, point.y]; -- result point is transformed in y only
};
boxParts.edges[2] => {
point ← [ box.hiX, 0 ];
oppositePoint ← [ box.loX, box.loY ];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
point ← [ point.x, box.hiY]; -- result point is transformed in x only
};
boxParts.edges[3] => {
point ← [ 0, box.loY ];
oppositePoint ← [ box.hiX, box.hiY ];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
point ← [ box.loX, point.y]; -- result point is transformed in y only
};
ENDCASE => ERROR;
}
ELSE IF cornerCount IN [1..2] THEN { -- one or two isolated corners. Pick one and transform it.
SELECT TRUE FROM
boxParts.corners[0] => { point ← [ box.loX, box.loY ]; oppositePoint ← [ box.hiX, box.hiY ]; };
boxParts.corners[1] => { point ← [ box.loX, box.hiY ]; oppositePoint ← [ box.hiX, box.loY ]; };
boxParts.corners[2] => { point ← [ box.hiX, box.hiY ]; oppositePoint ← [ box.loX, box.loY ]; };
boxParts.corners[3] => { point ← [ box.hiX, box.loY ]; oppositePoint ← [ box.loX, box.hiY ]; };
ENDCASE => ERROR;
inverse ← boxData.inverse;
totalTransform ← ImagerTransformation.Cat[boxData.transform, transform, inverse];
point ← ImagerTransformation.Transform[m: totalTransform, v: point]; -- transform the dragging point
};
box^ ← [MIN[point.x, oppositePoint.x], MIN[point.y, oppositePoint.y], MAX[point.x, oppositePoint.x], MAX[point.y, oppositePoint.y], FALSE, FALSE];
BoxSetBoundBox[slice];
};
MakeComplete: PROC [parts: SliceParts] = {
WITH parts SELECT FROM
boxParts: BoxParts => {
boxParts.corners ← ALL[TRUE];
boxParts.edges ← ALL[TRUE];
boxParts.center ← TRUE;
};
circleParts: CircleParts => {
circleParts.cpArray ← ALL[TRUE];
circleParts.outline ← TRUE;
};
ENDCASE => ERROR;
};
IsComplete: PROC [parts: SliceParts] RETURNS [BOOL] = {
IF parts = NIL THEN RETURN[FALSE];
WITH parts SELECT FROM
boxParts: BoxParts => RETURN[ boxParts.corners=ALL[TRUE] AND boxParts.edges=ALL[TRUE] AND boxParts.center ];
circleParts: CircleParts => RETURN[ circleParts.cpArray=ALL[TRUE] AND circleParts.outline];
ENDCASE => ERROR;
};
AllEdges: PROC [parts: SliceParts] RETURNS [BOOL] = {
WITH parts SELECT FROM
boxParts: BoxParts => RETURN[boxParts.edges=ALL[TRUE] ];
circleParts: CircleParts => RETURN[circleParts.outline];
ENDCASE => ERROR;
};
IsEmpty: PROC [parts: SliceParts] RETURNS [BOOL] = {
WITH parts SELECT FROM
boxParts: BoxParts => RETURN[ boxParts.corners=ALL[FALSE] AND boxParts.edges=ALL[FALSE] AND boxParts.center = FALSE];
circleParts: CircleParts => RETURN[ circleParts.cpArray=ALL[FALSE] AND circleParts.outline=FALSE];
ENDCASE => ERROR;
};
CountCorners: PROC [a: CornerArray] RETURNS [count: INTEGER] = {
count ← 0;
FOR corner: INTEGER IN [0..4) DO
IF a[corner] THEN count ← count+1;
ENDLOOP;
};
CountEdges: PROC [a: EdgeArray] RETURNS [count: INTEGER] = {
count ← 0;
FOR edge: INTEGER IN [0..4) DO
IF a[edge] THEN count ← count+1;
ENDLOOP;
};
BoxPointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = {
parts: BoxParts ← NARROW[sliceD.parts];
pointGen ← NEW[PointGeneratorObj ← [sliceD, 0, 0, NIL] ]; -- toGo and index now used
FOR corner: INTEGER IN [0..4) DO
IF parts.corners[corner] THEN pointGen.toGo ← pointGen.toGo + 1;
ENDLOOP;
IF parts.center THEN pointGen.toGo ← pointGen.toGo + 1;
};
BoxPointPairsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = {
parts: BoxParts ← NARROW[sliceD.parts];
pointPairGen ← NEW[PointPairGeneratorObj ← [sliceD, 0, 0, NIL] ]; -- toGo and index now used
FOR edge: INTEGER IN [0..4) DO
IF parts.edges[edge] THEN pointPairGen.toGo ← pointPairGen.toGo + 1;
ENDLOOP;
};
BoxNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
IF pointGen=NIL OR pointGen.toGo = 0 THEN {
pointAndDone.done ← TRUE;
RETURN;
}
ELSE {
sliceD: SliceDescriptor ← pointGen.sliceD;
slice: Slice ← sliceD.slice;
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[sliceD.parts];
t: ImagerTransformation.Transformation ← boxData.transform;
index: INTEGER;
pointAndDone.done ← FALSE;
FOR index ← pointGen.index, index+1 UNTIL index >=4 DO
IF boxParts.corners[index] THEN EXIT; -- index will point to next availabe point
REPEAT
FINISHED => {
IF NOT boxParts.center THEN index ← index + 1;
};
ENDLOOP;
SELECT index FROM -- index in [0..4) of next available point
0 => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]];
1 => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]];
2 => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]];
3 => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]];
4 => pointAndDone.point ← ImagerTransformation.Transform[t, [(boxData.box.loX + boxData.box.hiX)/2.0, (boxData.box.loY + boxData.box.hiY)/2.0]];
ENDCASE => SIGNAL GGError.Problem[msg: "Broken Invariant"];
pointGen.toGo ← pointGen.toGo-1;
pointGen.index ← IF pointGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available point in the generator
};
};
BoxNextPointPair: PROC [pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = {
IF pointPairGen=NIL OR pointPairGen.toGo = 0 THEN {
pointPairAndDone.done ← TRUE;
RETURN;
}
ELSE {
sliceD: SliceDescriptor ← pointPairGen.sliceD;
slice: Slice ← sliceD.slice;
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[sliceD.parts];
t: ImagerTransformation.Transformation ← boxData.transform;
index: INTEGER ← -1;
pointPairAndDone.done ← FALSE;
FOR index ← pointPairGen.index, index+1 UNTIL index >=4 DO
IF boxParts.edges[index] THEN EXIT; -- index will point to next availabe edge
ENDLOOP;
SELECT index FROM -- index in [0..4) of next available edge
0 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]];
};
1 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]];
};
2 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]];
};
3 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]];
};
ENDCASE => SIGNAL GGError.Problem[msg: "Broken Invariant"];
pointPairGen.toGo ← pointPairGen.toGo - 1;
pointPairGen.index ← IF pointPairGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available pair in the generator
};
};
BoxEmptyParts: PROC [slice: Slice, parts: SliceParts] RETURNS [BOOL] = {
GGModelTypes.SliceEmptyPartsProc
boxParts: BoxParts ← NARROW[parts]; -- NARROW for type checking
RETURN[IsEmpty[boxParts]];
};
NearestBoxPoint: PROC [slice: Slice, point: Point] RETURNS [index: [-1..3], center: BOOL] = {
sliceParts: SliceParts ← BoxNewParts[slice, NIL, slice];
boxHitData: BoxHitData;
success: BOOL;
hitData: REF ANY;
sliceD: SliceDescriptor ← NEW[SliceDescriptorObj ← [slice, sliceParts]];
[----, ----, hitData, success] ← BoxClosestPoint[sliceD: sliceD, testPoint: point, tolerance: GGUtility.plusInfinity];
IF NOT success THEN ERROR;
boxHitData ← NARROW[hitData];
center ← boxHitData.center > -1;
index ← boxHitData.corner;
IF index = -1 AND NOT center THEN ERROR;
};
BoxNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [parts: SliceParts] = {
GGModelTypes.SliceNewPartsProc
boxHitData: BoxHitData ← NARROW[hitData];
boxParts: BoxParts;
parts 𡤋oxParts← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]];
SELECT mode FROM
joint => {
IF boxHitData.corner#-1 THEN boxParts.corners[boxHitData.corner] ← TRUE
ELSE IF boxHitData.center#-1 THEN boxParts.center ← TRUE
ELSE IF boxHitData.edge#-1 THEN {
index: [-1..3];
center: BOOL;
[index, center] ← NearestBoxPoint[slice, boxHitData.hitPoint];
IF center THEN boxParts.center ← TRUE
ELSE boxParts.corners[index] ← TRUE;
};
};
segment => {
IF boxHitData.center#-1 THEN MakeComplete[boxParts]
ELSE IF boxHitData.edge#-1 THEN boxParts.edges[boxHitData.edge] ← TRUE;
};
controlPoint => {
boxParts.corners ← ALL[TRUE];
boxParts.center ← TRUE;
};
traj, topLevel, slice => MakeComplete[boxParts];
none => { -- prefer center to corner to edge if true
IF boxHitData.center#-1 THEN boxParts.center ← TRUE
ELSE IF boxHitData.corner#-1 THEN boxParts.corners[boxHitData.corner] ← TRUE
ELSE IF boxHitData.edge#-1 THEN boxParts.edges[boxHitData.edge] ← TRUE;
};
ENDCASE => ERROR;
};
BoxUnionParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aPlusB: SliceParts] = {
GGModelTypes.SliceUnionPartsProc
boxPartsA: BoxParts ← NARROW[partsA];
boxPartsB: BoxParts ← NARROW[partsB];
newParts: BoxParts ← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE ] ];
FOR i: INTEGER IN [0..4) DO
newParts.corners[i] ← boxPartsA.corners[i] OR boxPartsB.corners[i];
newParts.edges[i] ← boxPartsA.edges[i] OR boxPartsB.edges[i];
ENDLOOP;
newParts.center ← boxPartsA.center OR boxPartsB.center;
RETURN[newParts];
};
BoxDiffParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aMinusB: SliceParts] = {
boxPartsA: BoxParts ← NARROW[partsA];
boxPartsB: BoxParts ← NARROW[partsB];
newParts: BoxParts ← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE ] ];
IF partsB=NIL THEN {newParts^ ← boxPartsA^; aMinusB ← newParts; RETURN;};
FOR i: INTEGER IN [0..4) DO
newParts.corners[i] ← IF boxPartsB.corners[i] THEN FALSE ELSE boxPartsA.corners[i];
newParts.edges[i] ← IF boxPartsB.edges[i] THEN FALSE ELSE boxPartsA.edges[i];
ENDLOOP;
newParts.center ← IF boxPartsB.center THEN FALSE ELSE boxPartsA.center;
RETURN[newParts];
};
BoxMovingParts: PROC [slice: Slice, parts: SliceParts] RETURNS [moving: SliceParts] = {
GGModelTypes.SliceMovingPartsProc
If anything other than only one corner or only one edge is moving, everything is moving
If only one corner is moving, everything except its opposite corner is moving
If only one edge is moving, everything excepts its opposite edge and the opposite edge corners are moving.
boxParts: BoxParts ← NARROW[parts];
newParts: BoxParts ← NEW[BoxPartsObj ← [corners: ALL[TRUE], edges: ALL[TRUE], center: TRUE ] ];
cornerCount: INTEGER ← CountCorners[boxParts.corners];
edgeCount: INTEGER ← CountEdges[boxParts.edges];
centerCount: INTEGERIF boxParts.center THEN 1 ELSE 0;
IF (cornerCount+edgeCount)>=2 OR centerCount > 0 THEN {moving ← newParts; RETURN;}; -- everything moving
IF cornerCount=1 THEN { -- turn off the opposite corner
FOR i: INTEGER IN [0..4) DO
IF boxParts.corners[i] THEN newParts.corners[(i+2) MOD 4] ← FALSE;
ENDLOOP;
}
ELSE IF edgeCount=1 THEN { -- turn off the opposite edge and two corners
FOR i: INTEGER IN [0..4) DO
IF boxParts.edges[i] THEN {
newParts.edges[(i+2) MOD 4] ← FALSE;
newParts.corners[(i+2) MOD 4] ← FALSE;
newParts.corners[(i+3) MOD 4] ← FALSE;
};
ENDLOOP;
}
ELSE {newParts.edges ← ALL[FALSE]; newParts.corners ← ALL[FALSE]; }; -- nothing at all is moving
moving ← newParts; -- RETURN[newParts]
};
BoxFixedParts: PROC [slice: Slice, parts: SliceParts, selectedList: LIST OF REF ANY] RETURNS [fixed: SliceParts] = {
GGModelTypes.SliceFixedPartsProc
Start with every part and remove the ones that are moving
moving: SliceParts ← BoxMovingParts[slice, parts];
allParts: BoxParts ← NEW[BoxPartsObj];
MakeComplete[allParts];
fixed ← BoxDiffParts[slice, allParts, moving];
};
BoxAugmentParts: PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] RETURNS [more: SliceParts] = {
GGModelTypes.SliceAugmentPartsProc
boxParts: BoxParts ← NARROW[parts];
newParts: BoxParts;
newParts ← NEW[BoxPartsObj ← [corners: boxParts.corners, edges: boxParts.edges, center: boxParts.center] ];
For every edge, add its corresponding corners
FOR i: INTEGER IN [0..4) DO
IF boxParts.edges[i] THEN {
newParts.corners[i] ← TRUE;
newParts.corners[(i+1) MOD 4] ← TRUE;
};
ENDLOOP;
RETURN[newParts];
};
BoxClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE];
IF PointIsInBox[testPoint, toleranceBox] THEN {
index: NAT ← 9999;
boxHitData: BoxHitData;
boxData: BoxData ← NARROW[sliceD.slice.data];
box: BoundBox ← boxData.box;
boxParts: BoxParts ← NARROW[sliceD.parts];
thisPoint: Point;
thisDist: REAL;
localTestPoint: Point ← GGTransform.Transform[boxData.inverse, testPoint];
localTolerance: REALABS[tolerance*MAX[boxData.inverseScale.x, boxData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestPoint[box, localTestPoint, localTolerance, boxParts.corners];
IF success THEN {
hitData ← boxHitData ← NEW[BoxHitDataObj ← [corner: index, edge: -1, center: -1, hitPoint: [0,0]]];
};
IF boxParts.center THEN { -- center is a candidate for closest point, too
thisPoint ← [(box.loX+box.hiX)/2.0, (box.loY+box.hiY)/2.0]; -- center point
thisDist ← GGVector.Distance[localTestPoint, thisPoint];
IF thisDist < bestDist AND thisDist < localTolerance THEN {
bestDist ← thisDist;
bestPoint ← thisPoint;
IF success THEN boxHitData.corner ← -1
ELSE hitData ← boxHitData ← NEW[BoxHitDataObj ← [corner: -1, edge: -1, center: -1, hitPoint: [0,0]]];
boxHitData.center ← 0;
success ← TRUE;
};
};
IF success THEN {
bestPoint ← GGTransform.Transform[boxData.transform, bestPoint];
boxHitData.hitPoint ← bestPoint;
bestDist ← GGVector.Distance[testPoint, bestPoint];
};
};
};
BoxClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
seg: NAT ← 9999;
boxHitData: BoxHitData;
boxData: BoxData ← NARROW[sliceD.slice.data];
boxParts: BoxParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[boxData.inverse, testPoint];
localTolerance: REALABS [tolerance*MAX[boxData.inverseScale.x, boxData.inverseScale.y]]; -- MAX function is not correct when box is skewed.
[bestDist, seg, bestPoint, success] ← GGBoundBox.NearestSegment[boxData.box, localTestpoint, localTolerance, boxParts.edges];
IF success THEN {
bestPoint ← GGTransform.Transform[boxData.transform, bestPoint];
bestDist ← GGVector.Distance[testPoint, bestPoint];
hitData ← boxHitData ← NEW[BoxHitDataObj ← [corner: -1, edge: seg, center: -1, hitPoint: bestPoint] ];
};
};
};
globalEdge: Edge;
BoxLineIntersection: PUBLIC PROC [sliceD: SliceDescriptor, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT] = {
boxData: BoxData ← NARROW[sliceD.slice.data];
boxParts: BoxParts ← NARROW[sliceD.parts];
bBox: BoundBox ← boxData.box;
noHit: BOOL;
hits: ARRAY[0..3] OF BOOL;
hitPoints: ARRAY[0..3] OF Point;
epsilon: REAL = 0.072;
localLine: Line ← GGLines.LineTransform[line, boxData.inverse];
localPoints: LIST OF Point;
Find the intersections of the given line with bBox. pointCount will be at most 2.
localPoints ← NIL;
pointCount ← 0;
FOR i: NAT IN [0..3] DO hits[i] ← FALSE; ENDLOOP;
FOR i: NAT IN [0..2] DO
IF NOT boxParts.edges[i] THEN LOOP; -- only intersect edges mentioned in sliceD.
FillBoxEdge[globalEdge, bBox, i];
[hitPoints[i], noHit] ← GGLines.LineMeetsEdge[localLine, globalEdge];
IF noHit THEN LOOP;
IF i>0 AND hits[i-1] THEN {
IF ABS[hitPoints[i].x - hitPoints[i-1].x] < epsilon THEN LOOP;
IF ABS[hitPoints[i].y - hitPoints[i-1].y] < epsilon THEN LOOP;
};
localPoints ← CONS[hitPoints[i], localPoints];
pointCount ← pointCount + 1;
hits[i] ← TRUE;
REPEAT
FINISHED => {
IF NOT boxParts.edges[3] THEN GOTO End; -- only intersect edges mentioned in sliceD.
FillBoxEdge[globalEdge, bBox, 3];
[hitPoints[3], noHit] ← GGLines.LineMeetsEdge[localLine, globalEdge];
IF noHit THEN GOTO End;
IF hits[2] THEN {
IF ABS[hitPoints[3].x - hitPoints[2].x] < epsilon THEN GOTO End;
IF ABS[hitPoints[3].y - hitPoints[2].y] < epsilon THEN GOTO End;
};
IF hits[0] THEN {
IF ABS[hitPoints[3].x - hitPoints[0].x] < epsilon THEN GOTO End;
IF ABS[hitPoints[3].y - hitPoints[0].y] < epsilon THEN GOTO End;
};
localPoints ← CONS[hitPoints[3], localPoints];
pointCount ← pointCount + 1;
hits[3] ← TRUE;
EXITS
End => NULL;
};
ENDLOOP;
IF pointCount > 2 THEN ERROR;
points ← NIL;
FOR list: LIST OF Point ← localPoints, list.rest UNTIL list = NIL DO
points ← CONS[ImagerTransformation.Transform[m: boxData.transform, v: list.first], points];
ENDLOOP;
};
BoxHitDataAsSimpleCurve: PROC [slice: Slice, hitData: REF ANY] RETURNS [simpleCurve: REF ANY] = {
boxHitData: BoxHitData ← NARROW[hitData];
boxData: BoxData ← NARROW[slice.data];
bBox: BoundBox ← boxData.box;
edge: Edge;
IF boxHitData.edge = -1 THEN RETURN[NIL];
edge ← GGLines.CreateEmptyEdge[];
FillBoxEdge[edge, bBox, boxHitData.edge];
GGLines.FillEdgeTransform[edge, boxData.transform, edge];
simpleCurve ← edge;
};
FillBoxEdge: PRIVATE PROC [edge: Edge, bBox: BoundBox, index: NAT] = {
IF bBox.null OR bBox.infinite THEN ERROR;
SELECT index FROM
0 => GGLines.FillEdge[[bBox.loX, bBox.loY], [bBox.loX, bBox.hiY], edge];
1 => GGLines.FillEdge[[bBox.loX, bBox.hiY], [bBox.hiX, bBox.hiY], edge];
2 => GGLines.FillEdge[[bBox.hiX, bBox.hiY], [bBox.hiX, bBox.loY], edge];
3 => GGLines.FillEdge[[bBox.hiX, bBox.loY], [bBox.loX, bBox.loY], edge];
ENDCASE => ERROR;
};
boxCornerRopes: ARRAY [0..4) OF Rope.ROPE = [
"lower left corner", "upper left corner", "upper right corner", "lower right corner"];
boxEdgeRopes: ARRAY [0..4) OF Rope.ROPE = [
"left edge", "top edge", "right edge", "bottom edge"];
BoxDescribe: PROC [slice: Slice, parts: SliceParts] RETURNS [rope: Rope.ROPE] = {
GGModelTypes.SliceDescribeProc
prefix: Rope.ROPE;
boxParts: BoxParts ← NARROW[parts];
cornerCount: INTEGER ← CountCorners[boxParts.corners];
edgeCount: INTEGER ← CountEdges[boxParts.edges];
IF cornerCount+edgeCount>1 THEN RETURN["multiple parts of a Box slice"] ELSE {
centerOnly: BOOL ← boxParts.center AND edgeCount=0 AND cornerCount=0;
oneEdge: BOOLNOT boxParts.center AND edgeCount=1 AND cornerCount=0;
oneCorner: BOOLNOT boxParts.center AND cornerCount=1 AND edgeCount=0;
noParts: BOOLNOT boxParts.center AND cornerCount=0 AND edgeCount=0;
SELECT TRUE FROM
oneCorner => {
FOR i: INTEGER IN [0..4) DO
IF boxParts.corners[i] THEN {
prefix ← boxCornerRopes[i];
EXIT;
};
ENDLOOP;
};
oneEdge => {
FOR i: INTEGER IN [0..4) DO
IF boxParts.edges[i] THEN {
prefix ← boxEdgeRopes[i];
EXIT;
};
ENDLOOP;
};
centerOnly => prefix ← "center";
noParts => prefix ← "NO parts";
ENDCASE => ERROR;
rope ← Rope.Concat[prefix, " of a Box slice"];
};
};
BoxFileout: PROC [slice: Slice, f: IO.STREAM] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
boxData: BoxData ← NARROW[slice.data];
GGParseOut.WritePoint[f, [ boxData.box.loX, boxData.box.loY] ];
f.PutChar[IO.SP];
GGParseOut.WritePoint[f, [ boxData.box.hiX, boxData.box.hiY] ];
f.PutChar[IO.SP];
GGParseOut.WriteTransformation[f, boxData.transform];
f.PutRope[" strokeWidths: ( "];
FOR edge: INTEGER IN [0..4) DO
f.PutF["%g ", [real[boxData.strokeWidths[edge]]] ];
ENDLOOP;
f.PutRope[") strokeColors: ( "];
FOR edge: INTEGER IN [0..4) DO
GGParseOut.WriteColor[f, boxData.strokeColors[edge]];
f.PutChar[IO.SP];
ENDLOOP;
f.PutRope[") fillColor: "];
GGParseOut.WriteColor[f, boxData.fillColor];
f.PutChar[IO.SP];
};
BoxFilein: PROC [f: IO.STREAM, version: REAL, feedback: Viewer] RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
boxData: BoxData;
box: BoundBox;
p1, p2: Point;
transform: ImagerTransformation.Transformation;
strokes: ARRAY [0..4) OF REAL;
colors: ARRAY [0..4) OF Imager.Color;
fill: Imager.Color;
GGParseIn.ReadBlank[f];
p1 ← GGParseIn.ReadPoint[f];
GGParseIn.ReadBlank[f];
p2 ← GGParseIn.ReadPoint[f];
GGParseIn.ReadBlank[f];
transform ← GGParseIn.ReadTransformation[f];
GGParseIn.ReadBlankAndRope[f, "strokeWidths: ("];
FOR edge: INTEGER IN [0..4) DO
strokes[edge] ← GGParseIn.ReadBlankAndReal[f];
ENDLOOP;
GGParseIn.ReadBlankAndRope[f, ") strokeColors: ("];
FOR edge: INTEGER IN [0..4) DO
GGParseIn.ReadBlank[f];
colors[edge] ← GGParseIn.ReadColor[f];
ENDLOOP;
GGParseIn.ReadBlankAndRope[f, ") fillColor: "];
fill ← GGParseIn.ReadColor[f];
box ← GGBoundBox.CreateBoundBox[ p1.x, p1.y, p2.x, p2.y ];
slice ← MakeBoxSlice[box, ur, transform].slice;
boxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
boxData.strokeWidths[edge] ← strokes[edge];
boxData.strokeColors[edge] ← colors[edge];
ENDLOOP;
boxData.fillColor ← fill;
};
BoxSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts.edges[edge] THEN boxData.strokeColors[edge] ← color;
ENDLOOP;
};
BoxGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts.edges[edge] THEN color ← boxData.strokeColors[edge];
ENDLOOP;
};
BoxSetFillColor: PROC [slice: Slice, color: Imager.Color] = {
boxData: BoxData ← NARROW[slice.data];
boxData.fillColor ← color;
};
BoxGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = {
boxData: BoxData ← NARROW[slice.data];
color ← boxData.fillColor;
};
BoxSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts.edges[edge] THEN boxData.strokeWidths[edge] ← strokeWidth;
ENDLOOP;
BoxSetBoundBox[slice];
};
BoxGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts.edges[edge] THEN strokeWidth ← boxData.strokeWidths[edge];
ENDLOOP;
};
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
strokeWidth: REAL,
strokeColor: Imager.Color,
fillColor: Imager.Color
];
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
];
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,
fileout: CircleFileout,
filein: CircleFilein,
Hit Testing
emptyParts: CircleEmptyParts,
newParts: CircleNewParts,
unionParts: CircleUnionParts,
differenceParts: CircleDiffParts,
movingParts: CircleMovingParts,
fixedParts: CircleFixedParts,
augmentParts: CircleAugmentParts,
pointsInDescriptor: CirclePointsInDescriptor,
pointPairsInDescriptor: GGSlice.NoOpPointPairsInDescriptor,
nextPoint: CircleNextPoint,
nextPointPair: GGSlice.NoOpNextPointPair,
closestPoint: CircleClosestPoint,
closestPointAndTangent: NIL,
closestSegment: CircleClosestSegment,
lineIntersection: CircleLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: CircleHitDataAsSimpleCurve,
Style
setStrokeWidth: CircleSetStrokeWidth,
getStrokeWidth: CircleGetStrokeWidth,
setStrokeColor: CircleSetStrokeColor,
getStrokeColor: CircleGetStrokeColor,
setFillColor: CircleSetFillColor,
getFillColor: CircleGetFillColor
]];
};
MakeCircleSlice: PUBLIC PROC [origin: Point, outerPoint: Point, strokeWidth: REAL ← 2.0, strokeColor: Imager.Color ← Imager.black, fillColor: Imager.Color ← NIL] RETURNS [sliceD: SliceDescriptor] = {
circleData: CircleData ← NEW[CircleDataObj];
circleParts: CircleParts ← NEW[CirclePartsObj ← [ALL[FALSE], FALSE ]];
slice: Slice;
circleData.circle ← GGCircles.CircleFromPointAndRadius[[0,0], 1.0];
circleData.tightBox ← GGBoundBox.NullBoundBox[];
circleData.startPointouterPoint;
circleData.strokeWidth ← strokeWidth;
circleData.strokeColor ← strokeColor;
circleData.fillColor ← fillColor;
slice ← NEW[SliceObj ← [
class: FetchSliceClass[$Circle],
data: circleData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0]
]];
IF origin=outerPoint THEN circleData.startPoint ← [origin.x+20.0, origin.y+20.0]; -- prevent degenerate circle
circleData.transform ← ImagerTransformation.PreScale[ImagerTransformation.Translate[origin], GGVector.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, GGVector.Distance[origin, circleData.startPoint]];
CircleSetBoundBox[slice];
sliceD ← NEW[SliceDescriptorObj ← [slice, circleParts] ];
[] ← CircleClosestPoint[sliceD, outerPoint, 999.0]; -- only purpose of this call is to set up the HitData so that the first call to SelectSlice (in GGMouseEventImplA.EndCircle) works
};
Class Procedures
Fundamentals
CircleBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
CircleTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
CircleSetBoundBox: PROC [slice: Slice] = {
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REALNARROW[slice.data, CircleData].strokeWidth;
pad: REALMAX[cpHalf, strokeWidth];
circleData: CircleData ← NARROW[slice.data];
epsilon: REAL = 1.0E-6;
since the circle can be an ellipse, we just can't find the radius.
origin: Point ← ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]];
minor: Point ← ImagerTransformation.Transform[circleData.transform, [0.0, 1.0]];
major: Point ← ImagerTransformation.Transform[circleData.transform, [1.0, 0.0]];
radius: REAL ← RealFns.SqRt[MAX[GGVector.DistanceSquared[minor, origin], GGVector.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.strokeWidth, circleData.strokeColor, circleData.fillColor].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];
};
Drawing
CircleDrawParts: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick: BOOL] = {
GGModelTypes.SliceDrawPartsProc
DoCircleDrawOutline: PROC = {
Imager.ConcatT[dc, circleData.transform];
CircleDrawOutline[dc, circleData];
};
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] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
DoDrawFeedback: PROC = {
DrawOutlineFeedback: PROC = {
CirclePath: Imager.PathProc = { -- code copied from GGShapesImpl.DrawCircle
moveTo[[leftSide.x, leftSide.y]];
arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]];
};
leftSide: Point ← [-1.0, 0.0];
rightSide: Point ← [1.0, 0.0];
strokeScale: REALABS[MAX[circleData.inverseScale.x, circleData.inverseScale.y]];
strokeWidth: REAL ← strokeScale*2.0*(IF circleData.strokeWidth=0.0 THEN 1.0 ELSE circleData.strokeWidth);
Imager.ConcatT[dc, circleData.transform];
Imager.SetStrokeWidth[dc, strokeWidth];
Imager.SetColor[dc, IF circleData.strokeColor#NIL THEN circleData.strokeColor ELSE Imager.black];
Imager.MaskStroke[dc, CirclePath, TRUE];
};
thisCPisHot, thisCPisSelected: BOOL;
pts: ARRAY[0..4] OF Point;
pts[0] ← ImagerTransformation.Transform[t, [0.0, 0.0] ];
pts[1] ← ImagerTransformation.Transform[t, [-1.0, 0.0] ];
pts[2] ← ImagerTransformation.Transform[t, [0.0, 1.0] ];
pts[3] ← ImagerTransformation.Transform[t, [1.0, 0.0] ];
pts[4] ← ImagerTransformation.Transform[t, [0.0, -1.0] ];
IF NOT quick AND IsComplete[normalCircleParts] THEN Imager.DoSaveAll[dc, DrawOutlineFeedback];
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];
};
CircleDrawTransform: PROC [slice: Slice, parts: SliceParts, 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.
isOrigin: BOOLFALSE;
cpCount: INT ← 0;
cpIndex: INT ← -1;
originPoint, edgePoint, pointInUnitSpace: Point;
newTransform: ImagerTransformation.Transformation;
circleData: CircleData ← NARROW[slice.data];
circleParts: CircleParts ← NARROW[parts];
EasyDrawOutline: PROC = {
Imager.ConcatT[dc, ImagerTransformation.Concat[circleData.transform, transform]];
CircleDrawOutline[dc, circleData];
};
HardDrawOutline: PROC = {
Imager.ConcatT[dc, newTransform];
CircleDrawOutline[dc, circleData];
};
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 complete circle selected
Imager.DoSaveAll[dc, EasyDrawOutline];
RETURN;
};
Only one cp is selected OR no selections for a brand new circle. Only rubberband radius
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 ← GGVector.Add[v1: GGVector.Sub[v1: circleData.startPoint, v2: originPoint], v2: ImagerTransformation.Factor[transform].t]; -- edgepoint relative to origin
edgePoint ← GGVector.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];
newTransform ← ImagerTransformation.PreScale[circleData.transform, MAX[GGVector.Magnitude[pointInUnitSpace], 0.01] ];
Circles can only be shrunk by a factor of 100 per scaling operation.
Imager.DoSaveAll[dc, HardDrawOutline];
};
CircleDrawOutline: PROC [dc: Imager.Context, circleData: CircleData] = {
called with the dc transformation already set to object coordinates
DOES NOT USE DoSaveAll !!
FillItIn: PROC = {
Imager.SetColor[dc, circleData.fillColor];
Imager.MaskFill[dc, CirclePath, TRUE]
};
CirclePath: Imager.PathProc = { -- code copied from GGShapesImpl.DrawCircle
moveTo[[leftSide.x, leftSide.y]];
arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]];
};
leftSide: Point ← [-1.0, 0.0];
rightSide: Point ← [1.0, 0.0];
IF circleData.fillColor#NIL THEN Imager.DoSaveAll[dc, FillItIn];
IF circleData.strokeColor#NIL AND circleData.strokeWidth#0.0 THEN {
strokeScale: REAL ABS[MAX[circleData.inverseScale.x, circleData.inverseScale.y]];
Imager.SetColor[dc, circleData.strokeColor];
Imager.SetStrokeWidth[dc, circleData.strokeWidth*strokeScale];
Imager.MaskStroke[dc, CirclePath, TRUE];
};
};
Transforming
CircleTransform: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceTransformProc
Permanently transforms the circle. Depending on which parts are selected, the circle is transformed.
isOrigin: BOOLFALSE;
cpCount: INT ← 0;
cpIndex: INT ← -1;
originPoint, edgePoint, pointInUnitSpace: Point;
circleData: CircleData ← NARROW[slice.data];
circleParts: CircleParts ← NARROW[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[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 ← GGVector.Add[v1: GGVector.Sub[v1: circleData.startPoint, v2: originPoint], v2: ImagerTransformation.Factor[transform].t]; -- edgepoint relative to origin
edgePoint ← GGVector.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[GGVector.Magnitude[pointInUnitSpace], 0.01] ]; -- 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[slice];
};
Textual Description
CircleDescribe: PROC [slice: Slice, parts: SliceParts] RETURNS [rope: Rope.ROPE] = {
GGModelTypes.SliceDescribeProc
prefix: Rope.ROPE;
cpCount: INT ← 0;
circleParts: CircleParts ← NARROW[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.strokeWidth]] ];
f.PutRope[" strokeColor: "];
GGParseOut.WriteColor[f, circleData.strokeColor];
f.PutRope[" fillColor: "];
GGParseOut.WriteColor[f, circleData.fillColor];
f.PutChar[IO.SP];
};
CircleFilein: PROC [f: IO.STREAM, version: REAL, feedback: Viewer] RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
circleData: CircleData;
origin, outerPoint: Point;
transform: ImagerTransformation.Transformation;
strokeWidth: REAL;
strokeColor, fillColor: Imager.Color;
GGParseIn.ReadBlank[f];
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:"];
GGParseIn.ReadBlank[f];
strokeColor ← GGParseIn.ReadColor[f];
GGParseIn.ReadBlankAndRope[f, "fillColor:"];
GGParseIn.ReadBlank[f];
fillColor ← GGParseIn.ReadColor[f];
slice ← MakeCircleSlice[origin, outerPoint, strokeWidth, strokeColor, fillColor].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];
CircleSetBoundBox[slice];
};
Hit Testing
CircleEmptyParts: PROC [slice: Slice, parts: SliceParts] RETURNS [BOOL] = {
GGModelTypes.SliceEmptyPartsProc
circleParts: CircleParts ← NARROW[parts];
RETURN[IsEmpty[circleParts] ];
};
NearestCirclePoint: PROC [slice: Slice, point: Point] RETURNS [index: [0..4] ] = {
sliceParts: SliceParts ← CircleNewParts[slice, NIL, slice];
circleHitData: CircleHitData;
success: BOOL;
hitData: REF ANY;
sliceD: SliceDescriptor ← NEW[SliceDescriptorObj ← [slice, sliceParts]];
[----, ----, 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 [parts: SliceParts] = {
GGModelTypes.SliceNewPartsProc
circleHitData: CircleHitData ← NARROW[hitData];
circleParts: CircleParts ← NEW[CirclePartsObj ← [ALL[FALSE], FALSE] ];
SELECT mode FROM
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 => {
IF circleHitData.index#-1 THEN circleParts.cpArray[circleHitData.index] ← TRUE;
IF circleHitData.outline THEN circleParts.outline ← TRUE;
};
ENDCASE => ERROR;
parts ← circleParts; -- RETURN[boxParts]
};
CircleUnionParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aPlusB: SliceParts] = {
GGModelTypes.SliceUnionPartsProc
circlePartsA: CircleParts ← NARROW[partsA];
circlePartsB: CircleParts ← NARROW[partsB];
newParts: CircleParts ← 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 ← newParts; -- RETURN[newParts]
};
CircleDiffParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aMinusB: SliceParts] = {
GGModelTypes.SliceDifferencePartsProc
circlePartsA: CircleParts ← NARROW[partsA];
circlePartsB: CircleParts ← NARROW[partsB];
newParts: CircleParts ← 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 ← newParts; -- RETURN[newParts]
};
CircleMovingParts: PROC [slice: Slice, parts: SliceParts] RETURNS [moving: SliceParts] = {
GGModelTypes.SliceMovingPartsProc
If center is moving, everything moves. If any edge CP is moving, its fellow edge CP are moving
circleParts: CircleParts ← NARROW[parts];
newParts: CircleParts ← NEW[CirclePartsObj ← [ALL[FALSE], FALSE] ];
IF circleParts.cpArray[0] THEN {
newParts.cpArray ← ALL[TRUE];
newParts.outline ← TRUE;
}
ELSE FOR i: INTEGER IN [1..4] DO
IF circleParts.cpArray[i] THEN {
newParts.cpArray ← ALL[TRUE];
newParts.cpArray[0] ← FALSE;
newParts.outline ← TRUE;
EXIT;
};
ENDLOOP;
moving ← newParts; -- RETURN[newParts]
};
CircleFixedParts: PROC [slice: Slice, parts: SliceParts, selectedList: LIST OF REF ANY] RETURNS [fixed: SliceParts] = {
GGModelTypes.SliceFixedPartsProc
Start with every part and remove the ones that are moving
moving: SliceParts ← CircleMovingParts[slice, parts];
allParts: CircleParts ← NEW[CirclePartsObj];
MakeComplete[allParts];
fixed ← CircleDiffParts[slice, allParts, moving];
};
CircleAugmentParts: PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] RETURNS [more: SliceParts] = {
GGModelTypes.SliceAugmentPartsProc
DOESN'T DO A COPY. MAYBE IT SHOULD
more ← parts;
};
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;
};
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 GGError.Problem[msg: "Broken Invariant"];
pointGen.index ← index+1;
pointGen.toGo ← pointGen.toGo - 1;
};
};
CircleClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
nextPoint, origin, left, top, right, bottom: 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;
[origin, left, top, right, bottom] ← CirclePointsFromData[circleData];
FOR index: INTEGER IN [0..MaxCirclePoints) DO
nextPoint ← SELECT index FROM
0 => origin, 1 => left, 2 => top, 3 => right, 4 => bottom,
ENDCASE => ERROR;
nextDist2 ← GGVector.DistanceSquared[testPoint, nextPoint];
IF nextDist2 < bestDist2 THEN {
bestDist2 ← nextDist2;
foundIndex ← index;
bestPoint ← nextPoint;
IF bestDist2 < tolerance2 THEN success ← TRUE;
};
ENDLOOP;
IF success THEN {
hitData ← circleHitData ← NEW[CircleHitDataObj ← [index: foundIndex, hitPoint: bestPoint, outline: FALSE] ];
bestDist ← RealFns.SqRt[bestDist2];
};
};
};
CircleClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
circleHitData: CircleHitData;
circleData: CircleData ← NARROW[sliceD.slice.data];
pointOnCircle: Point ← [0.0, 0.0];
localTestpoint: Point ← ImagerTransformation.Transform[circleData.inverse, testPoint];
dist: REAL ← GGVector.Magnitude[localTestpoint];
Find hit point on unit circle
IF dist > 0.0 THEN pointOnCircle ← GGVector.Scale[localTestpoint, 1.0/dist];
Transform back to world coords
bestPoint ← ImagerTransformation.Transform[circleData.transform, pointOnCircle];
bestDist ← GGVector.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 ← GGLines.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 [origin, left, top, right, bottom: Point] = {
t: ImagerTransformation.Transformation ← circleData.transform;
origin ← ImagerTransformation.Transform[t, [0.0, 0.0] ];
left ← ImagerTransformation.Transform[t, [-1.0, 0.0] ];
top ← ImagerTransformation.Transform[t, [0.0, 1.0] ];
right ← ImagerTransformation.Transform[t, [1.0, 0.0] ];
bottom ← ImagerTransformation.Transform[t, [0.0, -1.0] ];
};
Style
CircleSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = {
circleData: CircleData ← NARROW[slice.data];
circleData.strokeWidth ← strokeWidth;
CircleSetBoundBox[slice];
};
CircleGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = {
circleData: CircleData ← NARROW[slice.data];
strokeWidth ← circleData.strokeWidth;
};
CircleSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = {
circleData: CircleData ← NARROW[slice.data];
circleData.strokeColor ← color;
};
CircleGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color] = {
circleData: CircleData ← NARROW[slice.data];
color ← circleData.strokeColor;
};
CircleSetFillColor: PROC [slice: Slice, color: Imager.Color] = {
circleData: CircleData ← NARROW[slice.data];
circleData.fillColor ← color;
};
CircleGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = {
circleData: CircleData ← NARROW[slice.data];
color ← circleData.fillColor;
};
Class registration
FetchSliceClass: PUBLIC PROC [type: ATOM] RETURNS [class: SliceClass] = {
FOR l: LIST OF SliceClassDef ← sliceClasses, l.rest UNTIL l=NIL DO
IF l.first.type=type THEN RETURN[l.first.class];
ENDLOOP;
SIGNAL GGError.Problem[msg: "Slice class not found."];
RETURN[NIL];
};
RegisterSliceClass: PUBLIC PROC [class: SliceClass] = {
classDef: SliceClassDef ← NEW[SliceClassDefObj ← [type: class.type, class: class]];
sliceClasses ← CONS[classDef, sliceClasses];
};
DeleteSlice: PUBLIC PROC [scene: Scene, slice: Slice] = {
scene.ptrValid ← FALSE;
scene.entities ← GGUtility.DeleteEntityFromList[slice, scene.entities];
GGObjects.DeleteEntity[scene, slice];
};
CopySlice: PUBLIC PROC [slice: Slice] RETURNS [copy: Slice] = {
RETURN[slice.class.copy[slice]];
};
Init: PROC [] = {
textDef: SliceClassDef ← NEW[SliceClassDefObj ← [type: $Text, class: GGSlice.BuildTextSliceClass[]]];
ipDef: SliceClassDef ← NEW[SliceClassDefObj ← [type: $IP, class: GGSlice.BuildIPSliceClass[]]];
boxDef: SliceClassDef ← NEW[SliceClassDefObj ← [type: $Box, class: GGSlice.BuildBoxSliceClass[]]];
circleDef: SliceClassDef ← NEW[SliceClassDefObj ← [type: $Circle, class: GGSlice.BuildCircleSliceClass[]]];
sliceClasses ← LIST[circleDef, boxDef, ipDef, textDef];
printPrefix ← Atom.MakeAtom["xerox/pressfonts/"];
screenPrefix 𡤊tom.MakeAtom ["xerox/tiogafonts/"];
globalEdge ← GGLines.CreateEmptyEdge[];
};
sliceClasses: LIST OF SliceClassDef;
printPrefix: ATOM;
screenPrefix: ATOM;
Init[];
END.