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 ← [
getBoundBox: BoxGetBoundBox,
getTightBox: BoxGetTightBox,
drawParts: BoxDrawParts,
drawTransform: BoxDrawTransform,
drawSelectionFeedback: BoxDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
Transforming
transform: BoxTransform,
Textual Description
describe: BoxDescribe,
fileout: BoxFileout,
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: INTEGER ← SELECT 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: REAL ← MAX[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:
BOOL ←
TRUE--] = {
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: INTEGER ← IF 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:
BOOL ←
FALSE] = {
GGModelTypes.SliceClosestPointProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE];
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: REAL ← ABS[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:
BOOL ←
FALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox]
THEN {
seg: NAT ← 9999;
boxHitData: BoxHitData;
boxData: BoxData ← NARROW[sliceD.slice.data];
boxParts: BoxParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[boxData.inverse, testPoint];
localTolerance: REAL ←ABS [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;
};
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: BOOL ← NOT boxParts.center AND edgeCount=1 AND cornerCount=0;
oneCorner: BOOL ← NOT boxParts.center AND cornerCount=1 AND edgeCount=0;
noParts: BOOL ← NOT 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.startPoint ← outerPoint;
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: REAL ← NARROW[slice.data, CircleData].strokeWidth;
pad: REAL ← MAX[cpHalf, strokeWidth];
circleData: CircleData ← NARROW[slice.data];
epsilon: REAL = 1.0E-6;
since the circle can be an ellipse, we just can't find the radius.
origin: Point ← ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]];
minor: Point ← ImagerTransformation.Transform[circleData.transform, [0.0, 1.0]];
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: REAL ← ABS[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: BOOL ← FALSE;
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: BOOL ← FALSE;
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:
BOOL ←
FALSE] = {
GGModelTypes.SliceClosestPointProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox]
THEN {
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 nextDist
2 < bestDist
2
THEN {
bestDist2 ← nextDist2;
foundIndex ← index;
bestPoint ← nextPoint;
IF bestDist2 < tolerance2 THEN success ← TRUE;
};
ENDLOOP;
IF success
THEN {
hitData ← circleHitData ← NEW[CircleHitDataObj ← [index: foundIndex, hitPoint: bestPoint, outline: FALSE] ];
bestDist ← RealFns.SqRt[bestDist2];
};
};
};
CircleClosestSegment:
PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point, bestDist:
REAL, hitData:
REF
ANY, success:
BOOL ←
FALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox]
THEN {
circleHitData: CircleHitData;
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.