GGSliceImplA.mesa
Copyright c 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on April 23, 1987 3:50:32 pm PDT
Last edited by Bier on April 30, 1987 11:38:57 pm PDT
Contents: Implements various slice classes in Gargoyle.
DIRECTORY
Atom, AtomButtonsTypes, Feedback, GGBasicTypes, GGBoundBox, GGInterfaceTypes, GGModelTypes, GGScene, GGSegment, GGSegmentTypes, GGParseIn, GGParseOut, GGShapes, GGSlice, GGTransform, GGUtility, Imager, ImagerTransformation, IO, Lines2d, NodeStyle, Rope, Vectors2d, ViewerClasses;
GGSliceImplA:
CEDAR
PROGRAM
IMPORTS Atom, Feedback, GGBoundBox, GGScene, GGParseIn, GGParseOut, GGSegment, GGShapes, GGSlice, GGTransform, Imager, ImagerTransformation, IO, Lines2d, Rope, Vectors2d
EXPORTS GGSlice = BEGIN
BoundBox: TYPE = GGModelTypes.BoundBox;
CameraData: TYPE = GGInterfaceTypes.CameraData;
Circle: TYPE = GGBasicTypes.Circle;
Color: TYPE = Imager.Color;
Corner: TYPE = GGSlice.Corner;
DefaultData: TYPE = GGInterfaceTypes.DefaultData;
Edge: TYPE = GGBasicTypes.Edge;
EntityGenerator: TYPE = GGModelTypes.EntityGenerator;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
FactoredTransformation: TYPE = ImagerTransformation.FactoredTransformation;
FeedbackData: TYPE = AtomButtonsTypes.FeedbackData;
Line: TYPE = GGBasicTypes.Line;
Object: TYPE = Imager.Object;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj;
SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData;
SelectionClass: TYPE = GGInterfaceTypes.SelectionClass;
SelectMode: TYPE = GGModelTypes.SelectMode;
SequenceOfReal: TYPE = GGBasicTypes.SequenceOfReal;
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;
StrokeEnd: TYPE = Imager.StrokeEnd;
Transformation: TYPE = ImagerTransformation.Transformation;
Vector: TYPE = GGBasicTypes.Vector;
Viewer: TYPE = ViewerClasses.Viewer;
WalkProc: TYPE = GGModelTypes.WalkProc;
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
fillColor: Color,
segments: ARRAY [0..4) OF Segment -- in order left, top, right, bottom
];
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
];
Problem: SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
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,
describeHit: BoxDescribeHit,
fileout: BoxFileout,
emptyParts: BoxEmptyParts,
newParts: BoxNewParts,
unionParts: BoxUnionParts,
differenceParts: BoxDiffParts,
movingParts: BoxMovingParts,
augmentParts: BoxAugmentParts,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: BoxPointsInDescriptor,
pointPairsInDescriptor: BoxPointPairsInDescriptor,
segmentsInDescriptor: BoxSegmentsInDescriptor,
walkSegments: BoxWalkSegments,
nextPoint: BoxNextPoint,
nextPointPair: BoxNextPointPair,
nextSegment: BoxNextSegment,
Hit Testing
closestPoint: BoxClosestPoint,
closestJointToHitData: NoOpClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: BoxClosestSegment,
lineIntersection: BoxLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: BoxHitDataAsSimpleCurve,
Style
setDefaults: BoxSetDefaults,
setStrokeWidth: BoxSetStrokeWidth,
getStrokeWidth: BoxGetStrokeWidth,
setStrokeEnd: BoxSetStrokeEnd,
getStrokeEnd: BoxGetStrokeEnd,
setStrokeJoint: NoOpSetStrokeJoint,
getStrokeJoint: NoOpGetStrokeJoint,
setStrokeColor: BoxSetStrokeColor,
getStrokeColor: BoxGetStrokeColor,
setFillColor: BoxSetFillColor,
getFillColor: BoxGetFillColor,
setArrows: NoOpSetArrows,
getArrows: NoOpGetArrows,
setDashed: BoxSetDashed,
getDashed: BoxGetDashed
]];
};
MakeBoxSlice:
PUBLIC
PROC [box: BoundBox, corner: Corner, transform: ImagerTransformation.Transformation ← GGTransform.Identity[]]
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, 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};
FOR i:
NAT
IN [0..4)
DO
newSeg: Segment ← GGSegment.MakeLine[p0: BoxPointFromIndex[boxData.box, i], p1: BoxPointFromIndex[boxData.box, (i+1) MOD 4], props: NIL ];
newSeg.strokeWidth ← 2.0;
newSeg.strokeEnd ← round;
newSeg.dashed ← FALSE;
newSeg.color ← Imager.black;
boxData.segments[i] ← newSeg; -- N.B. line ends in segment updated by BoxSetBoundBox
ENDLOOP;
boxSlice ←
NEW[SliceObj ← [
class: FetchSliceClass[$Box],
data: boxData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
boundBox: GGBoundBox.NullBoundBox[]
]];
boxSlice.nullDescriptor ← GGSlice.DescriptorFromParts[boxSlice, NIL];
BoxSetBoundBox[boxSlice];
sliceD ← NEW[SliceDescriptorObj ← [boxSlice, boxParts] ];
};
BoxMaxStrokeWidth:
PROC [boxData: BoxData]
RETURNS [maxWidth:
REAL] = {
maxWidth ← boxData.segments[0].strokeWidth;
FOR i:
NAT
IN [1..3]
DO
maxWidth ← MAX[maxWidth, boxData.segments[i].strokeWidth];
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];
FOR i:
NAT
IN [0..4)
DO
lo: Point ← BoxPointFromIndex[boxData.box, i];
hi: Point ← BoxPointFromIndex[boxData.box, (i+1) MOD 4];
IF lo#boxData.segments[i].lo THEN boxData.segments[i].class.endPointMoved[boxData.segments[i], TRUE, lo];
IF hi#boxData.segments[i].hi THEN boxData.segments[i].class.endPointMoved[boxData.segments[i], FALSE, hi];
ENDLOOP;
};
BoxPointFromIndex:
PROC [box: BoundBox, index: [0..4)]
RETURNS [point: Point] = {
IF index NOT IN [0..4) THEN ERROR;
RETURN[
SELECT index
FROM
0 => [box.loX, box.loY],
1 => [box.loX, box.hiY],
2 => [box.hiX, box.hiY],
3 => [box.hiX, box.loY],
ENDCASE => [-999.9, -999.9]
];
};
Fundamentals
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.tightBox];
};
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.segments[i] ← GGSegment.CopySegment[seg: boxData.segments[i]];
ENDLOOP;
copyData.fillColor ← boxData.fillColor;
BoxSetBoundBox[copy];
};
Drawing
BoxDrawParts:
PROC [slice: Slice, parts: SliceParts ←
NIL, dc: Imager.Context, camera: CameraData, quick:
BOOL] = {
GGModelTypes.SliceDrawPartsProc
DoBoxDrawParts:
PROC = {
boxParts: BoxParts ← NARROW[parts];
IF boxParts = NIL OR IsComplete[boxParts] 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] = {
seg: Segment ← boxData.segments[index];
IF seg.color=NIL OR seg.strokeWidth=0.0 THEN RETURN
ELSE {
Imager.SetStrokeWidth[dc, seg.strokeWidth ];
Imager.SetStrokeEnd[dc, seg.strokeEnd ];
Imager.SetColor[dc, seg.color ];
IF seg.dashed
THEN {
EdgePathProc: Imager.PathProc = {moveTo[p1]; lineTo[p2]; };
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ← seg.pattern;
Imager.MaskDashedStroke[dc, EdgePathProc, pattern.len, PatternProc, seg.offset, seg.length];
}
ELSE Imager.MaskVector[dc, p1, p2];
};
};
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[boxParts]
AND boxData.fillColor#
NIL
THEN {
Imager.SetColor[dc, boxData.fillColor];
Imager.MaskFill[dc, BoxPathProc ];
};
draw outline
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.DoSave[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] = {
seg: Segment ← boxData.segments[index];
Imager.SetStrokeWidth[dc, 2.0*(IF seg.strokeWidth#0.0 THEN seg.strokeWidth ELSE 1.0)];
Imager.SetStrokeEnd[dc, seg.strokeEnd];
Imager.SetColor[dc, IF seg.color#NIL THEN seg.color 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, square];
Imager.SetStrokeEnd[dc, round]; -- set in BoxEdgeFeedback
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.DoSave[dc, DoDrawFeedback];
};
BoxDrawAll:
PROC [dc: Imager.Context, boxData: BoxData, from: Point, to: Point, camera: CameraData
--, drawCPs:
BOOL ←
TRUE--] = {
BoxPathProc: Imager.PathProc={moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]];};
BoxEdgeDraw:
PROC [index:
INTEGER, p1, p2: Point] = {
seg: Segment ← boxData.segments[index];
IF seg.color=NIL OR seg.strokeWidth=0.0 THEN RETURN
ELSE {
Imager.SetStrokeWidth[dc, seg.strokeWidth ];
Imager.SetStrokeEnd[dc, seg.strokeEnd ];
Imager.SetColor[dc, seg.color ];
IF seg.dashed
THEN {
EdgePathProc: Imager.PathProc = {moveTo[p1]; lineTo[p2]; };
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ← seg.pattern;
Imager.MaskDashedStroke[dc, EdgePathProc, pattern.len, PatternProc, seg.offset, seg.length];
}
ELSE 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] ];
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 boxData.fillColor#
NIL
THEN {
Imager.SetColor[dc, boxData.fillColor];
Imager.MaskFill[dc, BoxPathProc ];
};
draw outline
FOR edge:
INTEGER
IN [0..4)
DO
BoxEdgeDraw[edge, pts[edge], pts[(edge + 1) MOD 4]];
ENDLOOP;
};
BoxDrawTransform:
PROC [sliceD: SliceDescriptor, 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.
[Artwork node; type 'Artwork on' to command tool]
HardDraw:
PROC = {
total transformation has already occurred on point, oppositePoint
BoxDrawAll[dc, boxData, point, oppositePoint, camera--, FALSE--];
};
EasyDraw:
PROC = {
Imager.ConcatT[dc, transform];
BoxDrawAll[dc, boxData, [box.loX, box.loY], [box.hiX, box.hiY], camera--, FALSE--];
};
point, oppositePoint, tWorld, tBox: Point;
boxData: BoxData ← NARROW[sliceD.slice.data];
boxParts: BoxParts ← NARROW[sliceD.parts];
box: BoundBox ← boxData.box;
totalTransform, worldBox: Transformation;
cornerCount, edgeCount, edgeNum, cornerNum: INTEGER ← 0;
IF box.null OR box.infinite THEN ERROR;
BEGIN
IF IsComplete[boxParts] THEN GOTO FullTransform;
IF IsEmpty[boxParts] THEN RETURN;
[edgeCount, edgeNum] ← CountEdges[boxParts.edges];
[cornerCount, cornerNum] ← CountCorners[boxParts.corners];
IF edgeCount >= 2 OR cornerCount >= 3 OR boxParts.center THEN GOTO FullTransform;
worldBox ← boxData.inverse;
tWorld is the arrow shown in the figure.
tWorld ← [transform.c, transform.f]; -- the translation components of transform
tBox ← ImagerTransformation.TransformVec[worldBox, tWorld];
IF edgeCount=1
THEN {
-- one edge. Transform it.
lo, hi: NAT;
[lo, hi] ← CornersOfEdge[edgeNum];
IF NOT (boxParts.corners[lo] AND boxParts.corners[hi]) THEN GOTO FullTransform;
oppositePoint ← BoxPoint[box, OppositeCorner[lo]];
point ← BoxPoint[box, lo];
SELECT edgeNum
FROM
0, 2 => -- left and right -- point ← [point.x + tBox.x, point.y];
1, 3 => -- top and bottom -- point ← [point.x, point.y + tBox.y];
ENDCASE => ERROR;
}
ELSE IF cornerCount = 2 THEN GOTO FullTransform -- not quite right if corners adjacent
ELSE {
-- one corner.
point ← BoxPoint[box, cornerNum];
oppositePoint ← BoxPoint[box, OppositeCorner[cornerNum]];
totalTransform ← ImagerTransformation.Cat[boxData.transform, transform, worldBox];
point ← ImagerTransformation.Transform[m: totalTransform, v: point];
};
Imager.DoSave[dc, HardDraw];
EXITS
FullTransform => {
Imager.DoSave[dc, EasyDraw];
};
END;
};
Transforming
BoxTransform:
PROC [sliceD: SliceDescriptor, 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[sliceD.slice.data];
boxParts: BoxParts ← NARROW[sliceD.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[sliceD.slice];
RETURN;
};
IF IsEmpty[boxParts] THEN RETURN; -- no parts. Legal.
IF (edgeCount ← CountEdges[boxParts.edges].count) >= 2
OR (cornerCount ← CountCorners[boxParts.corners].count) >= 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[sliceD.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[sliceD.slice];
};
Textual Description
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"];
BoxDescribeHit:
PROC [slice: Slice, hitData:
REF
ANY]
RETURNS [rope: Rope.
ROPE] = {
boxHitData: BoxHitData ← NARROW[hitData];
prefix: Rope.ROPE;
IF boxHitData.corner#-1 THEN prefix ← boxCornerRopes[boxHitData.corner]
ELSE IF boxHitData.center#-1 THEN prefix ← "center"
ELSE IF boxHitData.edge#-1 THEN prefix ← boxEdgeRopes[boxHitData.edge];
rope ← Rope.Concat[prefix, " of a Box slice"];
};
BoxDescribe:
PROC [sliceD: SliceDescriptor]
RETURNS [rope: Rope.
ROPE] = {
GGModelTypes.SliceDescribeProc
prefix: Rope.ROPE;
boxParts: BoxParts;
cornerCount: INTEGER;
edgeCount: INTEGER;
IF sliceD.parts = NIL THEN RETURN["multiple parts of a Box slice"];
boxParts ← NARROW[sliceD.parts];
cornerCount ← CountCorners[boxParts.corners].count;
edgeCount ← CountEdges[boxParts.edges].count;
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.segments[edge].strokeWidth]] ];
ENDLOOP;
f.PutRope[") strokeEnds: ( "];
FOR edge:
INTEGER
IN [0..4)
DO
GGParseOut.WriteStrokeEnd[f, boxData.segments[edge].strokeEnd];
f.PutChar[IO.SP];
ENDLOOP;
f.PutRope[") strokeColors: ( "];
FOR edge:
INTEGER
IN [0..4)
DO
GGParseOut.WriteColor[f, boxData.segments[edge].color];
f.PutChar[IO.SP];
ENDLOOP;
f.PutRope[") fillColor: "];
GGParseOut.WriteColor[f, boxData.fillColor];
f.PutRope[" dashes: ( "];
FOR edge:
INTEGER
IN [0..4)
DO
seg: Segment ← boxData.segments[edge];
GGParseOut.WriteBOOL[f, seg.dashed]; f.PutChar[IO.SP];
IF seg.dashed
THEN {
GGParseOut.WriteArrayOfReal[f, seg.pattern];
f.PutF[" %g %g ", [real[seg.offset]], [real[seg.length]]];
};
ENDLOOP;
f.PutRope[") "];
};
BoxFilein:
PROC [f:
IO.
STREAM, version:
REAL, feedback: FeedbackData]
RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
boxData: BoxData;
box: BoundBox;
p1, p2: Point;
transform: ImagerTransformation.Transformation;
strokeWidths: ARRAY [0..4) OF REAL;
ends: ARRAY [0..4) OF StrokeEnd;
colors: ARRAY [0..4) OF Color;
dashes: ARRAY [0..4) OF BOOL ← ALL[FALSE];
patterns: ARRAY [0..4) OF SequenceOfReal;
offsets: ARRAY [0..4) OF REAL;
lengths: ARRAY [0..4) OF REAL;
fill: Color;
p1 ← GGParseIn.ReadPoint[f];
p2 ← GGParseIn.ReadPoint[f];
transform ← GGParseIn.ReadTransformation[f];
GGParseIn.ReadBlankAndRope[f, "strokeWidths: ("];
FOR edge:
INTEGER
IN [0..4)
DO
strokeWidths[edge] ← GGParseIn.ReadBlankAndReal[f];
ENDLOOP;
IF version>=8702.26
THEN {
-- read in strokeEnds
GGParseIn.ReadBlankAndRope[f, ") strokeEnds: ("];
FOR edge:
INTEGER
IN [0..4)
DO
ends[edge] ← GGParseIn.ReadStrokeEnd[f];
ENDLOOP;
}
ELSE ends ← [round, round, round, round];
GGParseIn.ReadBlankAndRope[f, ") strokeColors: ("];
FOR edge:
INTEGER
IN [0..4)
DO
colors[edge] ← GGParseIn.ReadColor[f, version];
ENDLOOP;
GGParseIn.ReadBlankAndRope[f, ") fillColor: "];
fill ← GGParseIn.ReadColor[f, version];
IF version>=8704.03
THEN {
-- read in dash patterns
GGParseIn.ReadBlankAndRope[f, "dashes: ("];
FOR edge:
INTEGER
IN [0..4)
DO
dashes[edge] ← GGParseIn.ReadBOOL[f, version].truth;
IF dashes[edge]
THEN {
patterns[edge] ← GGParseIn.ReadArrayOfReal[f];
offsets[edge] ← GGParseIn.ReadBlankAndReal[f];
lengths[edge] ← GGParseIn.ReadBlankAndReal[f];
};
ENDLOOP;
GGParseIn.ReadBlankAndRope[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.segments[edge].strokeWidth ← strokeWidths[edge];
boxData.segments[edge].strokeEnd ← ends[edge];
boxData.segments[edge].color ← colors[edge];
boxData.segments[edge].dashed ← dashes[edge];
IF dashes[edge]
THEN {
boxData.segments[edge].pattern ← patterns[edge];
boxData.segments[edge].offset ← offsets[edge];
boxData.segments[edge].length ← lengths[edge];
};
ENDLOOP;
boxData.fillColor ← fill;
BoxSetBoundBox[slice];
};
Parts
MakeComplete:
PROC [boxParts: BoxParts] = {
boxParts.corners ← ALL[TRUE];
boxParts.edges ← ALL[TRUE];
boxParts.center ← TRUE;
};
IsComplete:
PROC [boxParts: BoxParts]
RETURNS [
BOOL] = {
IF boxParts =
NIL
THEN
RETURN[
FALSE];
RETURN[ boxParts.corners=ALL[TRUE] AND boxParts.edges=ALL[TRUE] AND boxParts.center ];
};
AllEdges:
PROC [boxParts: BoxParts]
RETURNS [
BOOL] = {
RETURN[boxParts.edges=ALL[TRUE] ];
};
IsEmpty:
PROC [boxParts: BoxParts]
RETURNS [
BOOL] = {
RETURN[boxParts.corners=ALL[FALSE] AND boxParts.edges=ALL[FALSE] AND boxParts.center = FALSE];
};
CountCorners:
PROC [a: CornerArray]
RETURNS [count:
INTEGER, cornerNum:
INTEGER] = {
cornerNum is the number of the last corner counted, if any. Otherwise cornerNum is undefined.
count ← 0;
cornerNum ← -1;
FOR corner:
INTEGER
IN [0..4)
DO
IF a[corner] THEN {count ← count+1; cornerNum ← corner};
ENDLOOP;
};
CountEdges:
PROC [a: EdgeArray]
RETURNS [count:
INTEGER, edgeNum:
INTEGER] = {
edgeNum is the number of the last edge counted, if any. Otherwise edgeNum is undefined.
count ← 0;
edgeNum ← -1;
FOR edge:
INTEGER
IN [0..4)
DO
IF a[edge] THEN {count ← count+1; edgeNum ← edge};
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;
};
BoxSegmentsInDescriptor:
PROC [sliceD: SliceDescriptor]
RETURNS [segGen: SegmentGenerator] = {
parts: BoxParts ← NARROW[sliceD.parts];
segGen ← NEW[SegmentGeneratorObj ← [traj: NIL, toGo: 0, index: 0, touched: 0, seq: NIL, completeSeq: FALSE, sliceD: sliceD] ];
FOR edge:
INTEGER
IN [0..4)
DO
IF parts.edges[edge] THEN segGen.toGo ← segGen.toGo + 1;
ENDLOOP;
};
BoxWalkSegments:
PROC [slice: Slice, walkProc: WalkProc]
RETURNS [sliceD: SliceDescriptor] = {
GGModelTypes.SliceWalkSegmentsProc
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]];
FOR i:
NAT
IN [0..4)
DO
boxParts.edges[i] ← walkProc[boxData.segments[i], NIL];
ENDLOOP;
sliceD ← NEW[SliceDescriptorObj ← [slice, boxParts] ];
};
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 available 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 Feedback.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 available 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 Feedback.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
};
};
BoxNextSegment:
PUBLIC
PROC [segGen: SegmentGenerator]
RETURNS [seg: Segment, transform: Transformation ←
NIL] = {
IF segGen=NIL OR segGen.toGo = 0 THEN RETURN[NIL, NIL]
ELSE {
boxData: BoxData ← NARROW[segGen.sliceD.slice.data];
boxParts: BoxParts ← NARROW[segGen.sliceD.parts];
index: INTEGER ← -1;
FOR index ← segGen.index, index+1
UNTIL index >=4
DO
IF boxParts.edges[index] THEN EXIT; -- index will point to next available edge
ENDLOOP;
IF index>=4 THEN SIGNAL Feedback.Problem[msg: "Broken Invariant"];
seg ← boxData.segments[index];
segGen.toGo ← segGen.toGo-1;
segGen.index ← IF segGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available segment in the generator
};
};
BoxEmptyParts:
PROC [sliceD: SliceDescriptor]
RETURNS [
BOOL] = {
GGModelTypes.SliceEmptyPartsProc
boxParts: BoxParts ← NARROW[sliceD.parts];
RETURN[IsEmpty[boxParts]];
};
NearestBoxPoint:
PROC [slice: Slice, point: Point]
RETURNS [index: [-1..3], center:
BOOL] = {
boxHitData: BoxHitData;
success: BOOL;
hitData: REF ANY;
sliceD: SliceDescriptor ← BoxNewParts[slice, NIL, slice];
[----, ----, 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 [sliceD: SliceDescriptor] = {
GGModelTypes.SliceNewPartsProc
boxHitData: BoxHitData ← NARROW[hitData];
boxParts: BoxParts;
boxParts← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]];
SELECT mode
FROM
literal => {
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 boxParts.edges[boxHitData.edge] ← TRUE
ELSE ERROR;
};
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 => {
-- leave boxParts empty
};
ENDCASE => ERROR;
sliceD ← GGSlice.DescriptorFromParts[slice, boxParts];
};
BoxUnionParts:
PROC [partsA: SliceDescriptor, partsB: SliceDescriptor]
RETURNS [aPlusB: SliceDescriptor] = {
GGModelTypes.SliceUnionPartsProc
boxPartsA: BoxParts ← NARROW[partsA.parts];
boxPartsB: BoxParts ← NARROW[partsB.parts];
newParts: BoxParts;
IF partsA.parts = NIL THEN RETURN[partsB];
IF partsB.parts = NIL THEN RETURN[partsA];
IF IsEmpty[boxPartsA] THEN RETURN[partsB];
IF IsEmpty[boxPartsB] THEN RETURN[partsA];
IF IsComplete[boxPartsA] THEN RETURN[partsA];
IF IsComplete[boxPartsB] THEN RETURN[partsB];
newParts ← 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;
aPlusB ← GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
BoxDiffParts:
PROC [partsA: SliceDescriptor, partsB: SliceDescriptor]
RETURNS [aMinusB: SliceDescriptor] = {
boxPartsA: BoxParts ← NARROW[partsA.parts];
boxPartsB: BoxParts ← NARROW[partsB.parts];
newParts: BoxParts;
IF partsA = NIL OR partsB = NIL THEN ERROR;
IF partsA.parts = NIL OR partsB.parts = NIL THEN RETURN[partsA];
newParts ← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE ] ];
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;
aMinusB ← GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
CornersOfEdge:
PROC [edge:
NAT]
RETURNS [lo, hi:
NAT] = {
SELECT edge
FROM
IN [0..3] => {lo ← edge; hi ← (edge + 1) MOD 4};
ENDCASE => ERROR;
};
OppositeCorner:
PROC [corner:
NAT]
RETURNS [opCorner:
NAT] = {
SELECT corner
FROM
IN [0..3] => opCorner ← (corner + 2) MOD 4;
ENDCASE => ERROR;
};
BoxPoint:
PROC [box: BoundBox, corner:
NAT]
RETURNS [point: Point] = {
point
← SELECT corner
FROM
0 => [box.loX, box.loY],
1 => [box.loX, box.hiY],
2 => [box.hiX, box.hiY],
3 => [box.hiX, box.loY],
ENDCASE => ERROR;
};
BoxMovingParts:
PROC [slice: Slice, selectedParts: SliceParts]
RETURNS [background, overlay, rubber, drag: SliceDescriptor] = {
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.
If the box is filled and only partly moving, its stationary parts go on the overlay plane. Otherwise, they may be left in the background.
boxParts: BoxParts ← NARROW[selectedParts];
boxData: BoxData ← NARROW[slice.data];
overlayParts: BoxParts;
filled: BOOL ← boxData.fillColor # NIL;
nullD: SliceDescriptor ← slice.nullDescriptor;
newParts: BoxParts;
cornerCount, cornerNum, edgeCount, edgeNum: INTEGER;
centerCount: INTEGER ← IF boxParts.center THEN 1 ELSE 0;
IF IsEmpty[boxParts]
THEN {
background ← overlay ← rubber ← drag ← nullD;
RETURN;
};
IF IsComplete[boxParts]
THEN {
background ← overlay ← rubber ← nullD;
drag ← GGSlice.DescriptorFromParts[slice, boxParts];
RETURN;
};
[cornerCount, cornerNum] ← CountCorners[boxParts.corners];
[edgeCount, edgeNum] ← CountEdges[boxParts.edges];
newParts ← NEW[BoxPartsObj ← [corners:ALL[TRUE], edges:ALL[TRUE], center:TRUE]];
BEGIN
IF edgeCount >= 2 OR cornerCount >= 3 OR centerCount > 0 THEN GOTO EverythingMoves;
IF edgeCount = 1
THEN {
lo, hi: NAT;
IF cornerCount # 2 THEN GOTO EverythingMoves;
[lo, hi] ← CornersOfEdge[edgeNum];
IF NOT boxParts.corners[lo] OR NOT boxParts.corners[hi] THEN GOTO EverythingMoves;
BEGIN
wholeD: SliceDescriptor ← slice.class.newParts[slice, NIL, slice];
newParts.edges[(edgeNum+2) MOD 4] ← FALSE;
newParts.corners[(edgeNum+2) MOD 4] ← FALSE;
newParts.corners[(edgeNum+3) MOD 4] ← FALSE;
background ← drag ← nullD;
rubber ← GGSlice.DescriptorFromParts[slice, newParts];
overlay ← BoxDiffParts[wholeD, rubber];
IF NOT filled THEN {background ← overlay; overlay ← nullD};
END;
}
ELSE IF cornerCount = 1
THEN {
-- all but the opposite corner
newParts.corners[(cornerNum+2) MOD 4] ← FALSE;
background ← drag ← nullD;
rubber ← GGSlice.DescriptorFromParts[slice, newParts];
overlayParts ← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]];
overlayParts.corners[cornerNum] ← TRUE;
overlay ← GGSlice.DescriptorFromParts[slice, overlayParts];
IF NOT filled THEN {background ← overlay; overlay ← nullD};
}
ELSE {
-- nothing is moving
background ← GGSlice.DescriptorFromParts[slice, newParts];
rubber ← overlay ← drag ← nullD;
};
EXITS
EverythingMoves => {
background ← overlay ← rubber ← nullD;
drag ← GGSlice.DescriptorFromParts[slice, newParts];
};
END;
};
BoxAugmentParts:
PROC [sliceD: SliceDescriptor, selectClass: SelectionClass]
RETURNS [more: SliceDescriptor] = {
GGModelTypes.SliceAugmentPartsProc
boxParts: BoxParts ← NARROW[sliceD.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;
more ← GGSlice.DescriptorFromParts[sliceD.slice, newParts];
};
BoxClosestPoint:
PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point, bestDist:
REAL, hitData:
REF
ANY, success:
BOOL ←
FALSE] = {
GGModelTypes.SliceClosestPointProc
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE];
IF GGBoundBox.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 ← Vectors2d.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 ← Vectors2d.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 GGBoundBox.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 ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← boxHitData ← NEW[BoxHitDataObj ← [corner: -1, edge: seg, center: -1, hitPoint: bestPoint] ];
};
};
};
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 ← Lines2d.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] ← Lines2d.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] ← Lines2d.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 ← Lines2d.CreateEmptyEdge[];
FillBoxEdge[edge, bBox, boxHitData.edge];
Lines2d.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 => Lines2d.FillEdge[[bBox.loX, bBox.loY], [bBox.loX, bBox.hiY], edge];
1 => Lines2d.FillEdge[[bBox.loX, bBox.hiY], [bBox.hiX, bBox.hiY], edge];
2 => Lines2d.FillEdge[[bBox.hiX, bBox.hiY], [bBox.hiX, bBox.loY], edge];
3 => Lines2d.FillEdge[[bBox.hiX, bBox.loY], [bBox.loX, bBox.loY], edge];
ENDCASE => ERROR;
};
Box Looks
BoxSetStrokeColor:
PROC [slice: Slice, parts: SliceParts, color: Color] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge:
INTEGER
IN [0..4)
DO
IF boxParts.edges[edge] THEN boxData.segments[edge].color ← color;
ENDLOOP;
};
BoxGetStrokeColor:
PROC [slice: Slice, parts: SliceParts]
RETURNS [color: Color] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge:
INTEGER
IN [0..4)
DO
IF boxParts.edges[edge] THEN color ← boxData.segments[edge].color;
ENDLOOP;
};
BoxSetFillColor:
PROC [slice: Slice, color: Color] = {
boxData: BoxData ← NARROW[slice.data];
boxData.fillColor ← color;
};
BoxGetFillColor:
PROC [slice: Slice]
RETURNS [color: Color] = {
boxData: BoxData ← NARROW[slice.data];
color ← boxData.fillColor;
};
BoxSetStrokeWidth:
PROC [slice: Slice, parts: SliceParts, strokeWidth:
REAL]
RETURNS [box: BoundBox] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge:
INTEGER
IN [0..4)
DO
IF boxParts.edges[edge] THEN boxData.segments[edge].strokeWidth ← strokeWidth;
ENDLOOP;
BoxSetBoundBox[slice];
box ← slice.boundBox;
};
BoxSetDefaults:
PROC [slice: Slice, parts: SliceParts, defaults: DefaultData] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge:
INTEGER
IN [0..4)
DO
IF boxParts.edges[edge] THEN GGSegment.SetDefaults[boxData.segments[edge], defaults];
ENDLOOP;
boxData.fillColor ← defaults.fillColor;
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.segments[edge].strokeWidth;
ENDLOOP;
};
BoxSetStrokeEnd:
PROC [slice: Slice, parts: SliceParts, strokeEnd: StrokeEnd] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge:
INTEGER
IN [0..4)
DO
IF boxParts.edges[edge] THEN boxData.segments[edge].strokeEnd ← strokeEnd;
ENDLOOP;
};
BoxGetStrokeEnd:
PROC [slice: Slice, parts: SliceParts]
RETURNS [strokeEnd: StrokeEnd] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge:
INTEGER
IN [0..4)
DO
IF boxParts.edges[edge] THEN strokeEnd ← boxData.segments[edge].strokeEnd;
ENDLOOP;
};
BoxSetDashed:
PROC [slice: Slice, parts: SliceParts, dashed:
BOOL ← FALSE, pattern: SequenceOfReal ←
NIL, offset:
REAL ← 0.0, length:
REAL ← -1.0] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge:
INTEGER
IN [0..4)
DO
IF boxParts.edges[edge]
THEN {
seg: Segment ← boxData.segments[edge];
seg.dashed ← dashed;
seg.pattern ← pattern;
seg.offset ← offset;
seg.length ← length;
};
ENDLOOP;
};
BoxGetDashed:
PROC [slice: Slice, parts: SliceParts]
RETURNS [dashed:
BOOL ← FALSE, pattern: SequenceOfReal, offset, length:
REAL] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge:
INTEGER
IN [0..4)
DO
IF boxParts.edges[edge]
THEN {
seg: Segment ← boxData.segments[edge];
dashed ← seg.dashed;
pattern ← seg.pattern;
offset ← seg.offset;
length ← seg.length;
};
ENDLOOP;
};
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 Feedback.Problem[msg: "Slice class not found."];
RETURN[NIL];
};
UpdateDescriptorBoundBoxes:
PUBLIC
PROC [sliceD: SliceDescriptor] = {
IF sliceD.slice.class.type = $Outline THEN SIGNAL Problem[msg: "Time to have this proc call GGOutline.UpdateDescriptorBoundBoxes"];
};
RegisterSliceClass:
PUBLIC PROC [class: SliceClass] = {
classDef: SliceClassDef ← NEW[SliceClassDefObj ← [type: class.type, class: class]];
sliceClasses ← CONS[classDef, sliceClasses];
};
DescriptorFromParts:
PUBLIC
PROC [slice: Slice, parts: SliceParts]
RETURNS [sliceD: SliceDescriptor] = {
sliceD ← NEW[SliceDescriptorObj ← [slice, parts]];
};
SetDefaults:
PUBLIC
PROC [slice: Slice, parts: SliceParts, defaults: DefaultData] = {
slice.class.setDefaults[slice, parts, defaults];
};
CopySlice:
PUBLIC
PROC [slice: Slice]
RETURNS [copy: Slice] = {
RETURN[slice.class.copy[slice]];
};
DeleteSlice:
PUBLIC
PROC [scene: Scene, slice: Slice] = {
scene.ptrValid ← FALSE;
scene.entities ← GGUtility.DeleteEntityFromList[slice, scene.entities];
GGScene.DeleteEntity[scene, slice];
};
SegmentsInDescriptor:
PUBLIC
PROC [sliceD: SliceDescriptor]
RETURNS [segGen: SegmentGenerator] = {
IF sliceD.slice.class.type=$Outline THEN SIGNAL Problem[msg: "GGSlice.SegmentsInSlice called with Outline slice. ILLEGAL"]
ELSE segGen ← sliceD.slice.class.segmentsInDescriptor[sliceD];
};
NextSegment:
PUBLIC PROC [segGen: SegmentGenerator]
RETURNS [next: Segment] = {
next ← segGen.sliceD.slice.class.nextSegment[segGen].seg;
};
WalkSegments:
PUBLIC PROC [slice: Slice, walkProc: WalkProc]
RETURNS [sliceD: SliceDescriptor] = {
Calls the slice class walkSegments proc.
The walkProc is called with each segment in the slice. If walkProc returns TRUE, the part of the slice that corresponds to the segment will be in the SliceDescriptor returned by WalkSegments.
IF slice.class.type=$Outline THEN SIGNAL Problem[msg: "GGSlice.WalkSegments called with Outline slice. ILLEGAL"]
ELSE sliceD ← slice.class.walkSegments[slice, walkProc];
};
EntitiesInSlice:
PUBLIC PROC [slice: Slice]
RETURNS [entityGenerator: EntityGenerator] = {
SIGNAL Problem[msg: "GGSlice.EntitiesInSlice NYI"];
};
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 ← Lines2d.CreateEmptyEdge[];
};
sliceClasses: LIST OF SliceClassDef;
printPrefix: ATOM;
screenPrefix: ATOM;
globalEdge: Edge;
Init[];
END.