GGSliceImplA.mesa
Copyright c 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on May 13, 1987 2:28:17 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, GGSegment, GGSegmentTypes, GGSequence, GGOutline, GGParseIn, GGParseOut, GGShapes, GGSlice, GGTransform, GGUtility, Imager, ImagerTransformation, IO, Lines2d, NodeStyle, Rope, Vectors2d, ViewerClasses;
GGSliceImplA: CEDAR PROGRAM
IMPORTS Atom, Feedback, GGBoundBox, GGOutline, GGParseIn, GGParseOut, GGSegment, GGSequence, 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;
OutlineParts: TYPE = GGOutline.OutlineParts;
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;
Sequence: TYPE = GGModelTypes.Sequence;
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 ← [
type: $Box,
Fundamentals
getBoundBox: BoxGetBoundBox,
getTightBox: BoxGetTightBox,
copy: BoxCopy,
Drawing
drawParts: BoxDrawParts,
drawTransform: BoxDrawTransform,
drawSelectionFeedback: BoxDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
Transforming
transform: BoxTransform,
Textual Description
describe: BoxDescribe,
describeHit: BoxDescribeHit,
fileout: BoxFileout,
filein: BoxFilein,
Parts
isEmptyParts: BoxIsEmptyParts,
isCompleteParts: BoxIsCompleteParts,
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: INTEGERSELECT corner FROM ll=>0, ul=>1, ur=>2, lr=>3,ENDCASE=>2;
boxParts.corners[cIndex] ← TRUE;
IF box.loX > box.hiX THEN {t: REAL ← box.loX; box.loX ← box.hiX; box.hiX ← t};
IF box.loY > box.hiY THEN {t: REAL ← box.loY; box.loY ← box.hiY; box.hiY ← t};
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: REALMAX[cpHalf, strokeWidth];
boxData: BoxData ← NARROW[slice.data];
GGBoundBox.UpdateBoundBoxOfBoundBox[boxData.tightBox, boxData.box, boxData.transform];
GGBoundBox.UpdateCopyBoundBox[bBox: slice.boundBox, from: boxData.tightBox];
GGBoundBox.EnlargeByOffset[slice.boundBox, pad];
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: BOOLTRUE--] = {
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: BOOLNOT boxParts.center AND edgeCount=1 AND cornerCount=0;
oneCorner: BOOLNOT boxParts.center AND cornerCount=1 AND edgeCount=0;
noParts: BOOLNOT boxParts.center AND cornerCount=0 AND edgeCount=0;
SELECT TRUE FROM
oneCorner => {
FOR i: INTEGER IN [0..4) DO
IF boxParts.corners[i] THEN {
prefix ← boxCornerRopes[i];
EXIT;
};
ENDLOOP;
};
oneEdge => {
FOR i: INTEGER IN [0..4) DO
IF boxParts.edges[i] THEN {
prefix ← boxEdgeRopes[i];
EXIT;
};
ENDLOOP;
};
centerOnly => prefix ← "center";
noParts => prefix ← "NO parts";
ENDCASE => ERROR;
rope ← Rope.Concat[prefix, " of a Box slice"];
};
};
BoxFileout: PROC [slice: Slice, f: IO.STREAM] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
boxData: BoxData ← NARROW[slice.data];
GGParseOut.WritePoint[f, [ boxData.box.loX, boxData.box.loY] ];
f.PutChar[IO.SP];
GGParseOut.WritePoint[f, [ boxData.box.hiX, boxData.box.hiY] ];
f.PutChar[IO.SP];
GGParseOut.WriteTransformation[f, boxData.transform];
f.PutRope[" strokeWidths: ( "];
FOR edge: INTEGER IN [0..4) DO
f.PutF["%g ", [real[boxData.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 BOOLALL[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
};
};
BoxIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
boxParts: BoxParts ← NARROW[sliceD.parts];
RETURN[IsEmpty[boxParts]];
};
BoxIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
boxParts: BoxParts ← NARROW[sliceD.parts];
RETURN[IsComplete[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: INTEGERIF 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: BOOLFALSE] = {
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: REALABS[tolerance*MAX[boxData.inverseScale.x, boxData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestPoint[box, localTestPoint, localTolerance, boxParts.corners];
IF success THEN {
hitData ← boxHitData ← NEW[BoxHitDataObj ← [corner: index, edge: -1, center: -1, hitPoint: [0,0]]];
};
IF boxParts.center THEN { -- center is a candidate for closest point, too
thisPoint ← [(box.loX+box.hiX)/2.0, (box.loY+box.hiY)/2.0]; -- center point
thisDist ← 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: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
sliceBBox: BoundBox ← sliceD.slice.boundBox;
toleranceBox: GGBasicTypes.BoundBoxObj ← [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF 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: REALABS [tolerance*MAX[boxData.inverseScale.x, boxData.inverseScale.y]]; -- MAX function is not correct when box is skewed.
[bestDist, seg, bestPoint, success] ← GGBoundBox.NearestSegment[boxData.box, localTestpoint, localTolerance, boxParts.edges];
IF success THEN {
bestPoint ← GGTransform.Transform[boxData.transform, bestPoint];
bestDist ← 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;
EXITS
End => NULL;
};
ENDLOOP;
IF pointCount > 2 THEN ERROR;
points ← NIL;
FOR list: LIST OF Point ← localPoints, list.rest UNTIL list = NIL DO
points ← CONS[ImagerTransformation.Transform[m: boxData.transform, v: list.first], points];
ENDLOOP;
};
BoxHitDataAsSimpleCurve: PROC [slice: Slice, hitData: REF ANY] RETURNS [simpleCurve: REF ANY] = {
boxHitData: BoxHitData ← NARROW[hitData];
boxData: BoxData ← NARROW[slice.data];
bBox: BoundBox ← boxData.box;
edge: Edge;
IF boxHitData.edge = -1 THEN RETURN[NIL];
edge ← 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];
};
UpdateDescriptorBoundBoxesOLDD: PUBLIC PROC [sliceD: SliceDescriptor] = {
IF sliceD.slice.class.type = $Outline THEN SIGNAL Problem[msg: "Time to have this proc call GGOutline.UpdateDescriptorBoundBoxes"];
};
UpdateDescriptorBoundBoxes: PUBLIC PROC [sliceD: SliceDescriptor] = {
Slice classes other than outlines don't use descriptor bound boxes !!
IF sliceD.slice.class.type = $Outline THEN {
parts: OutlineParts ← NARROW[sliceD.parts];
FOR list: LIST OF Sequence ← parts.seqs, list.rest UNTIL list = NIL DO
IF list.first # NIL THEN GGSequence.UpdateBoundBox[list.first];
ENDLOOP;
};
};
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]];
};
SegmentsInDescriptorOLDD: 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];
};
SegmentsInDescriptor: PUBLIC PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = {
segGen ← sliceD.slice.class.segmentsInDescriptor[sliceD];
};
NextSegment: PUBLIC PROC [segGen: SegmentGenerator] RETURNS [next: Segment] = {
next ← segGen.sliceD.slice.class.nextSegment[segGen].seg;
};
WalkSegmentsOLDD: 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];
};
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.
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[]]];
outlineDef: SliceClassDef ← NEW[SliceClassDefObj ← [type: $Outline, class: GGOutline.BuildOutlineSliceClass[]]];
sliceClasses ← LIST[outlineDef, 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.