GGSliceImplA.mesa
Contents: Implements the Box slice class.
Bier, June 1, 1992 6:11 pm PDT
Pier, on May 22, 1992 12:09 pm PDT
Copyright c 1986 by Xerox Corporation. All rights reserved.
Box Slice Class
DIRECTORY
Feedback, FeedbackTypes, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGSegmentTypes, GGShapes, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerColor, ImagerPath, ImagerTransformation, IO, Lines2d, NodeStyle, PutGet, Real, Rope, TextNode, TiogaImager, Vectors2d;
GGSliceImplA: CEDAR PROGRAM
IMPORTS Feedback, GGBoundBox, GGCoreOps, GGParent, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGShapes, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerTransformation, IO, Lines2d, PutGet, Rope, TextNode, TiogaImager, Vectors2d
EXPORTS GGSlice = BEGIN
BoundBox: TYPE = REF BoundBoxObj;
BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj;
Camera: TYPE = GGInterfaceTypes.Camera;
Circle: TYPE = GGBasicTypes.Circle;
Color: TYPE = Imager.Color;
Corner: TYPE = GGSlice.Corner;
DefaultData: TYPE = GGInterfaceTypes.DefaultData;
EditConstraints: TYPE = GGModelTypes.EditConstraints;
Edge: TYPE = GGBasicTypes.Edge;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
FactoredTransformation: TYPE = ImagerTransformation.FactoredTransformation;
MsgRouter: TYPE = FeedbackTypes.MsgRouter;
HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent;
Line: TYPE = GGCoreTypes.Line;
Object: TYPE = Imager.Object;
Orientation: TYPE = GGModelTypes.Orientation;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj;
PointWalkProc: TYPE = GGModelTypes.PointWalkProc;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj;
SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData;
SelectionClass: TYPE = GGSegmentTypes.SelectionClass;
SelectMode: TYPE = GGModelTypes.SelectMode;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceOfReal: TYPE = GGCoreTypes.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;
StrokeJoint: TYPE = Imager.StrokeJoint;
Transformation: TYPE = ImagerTransformation.Transformation;
Vector: TYPE = GGBasicTypes.Vector;
WalkProc: TYPE = GGModelTypes.WalkProc;
MoveToProc: TYPE = ImagerPath.MoveToProc;
LineToProc: TYPE = ImagerPath.LineToProc;
CurveToProc: TYPE = ImagerPath.CurveToProc;
ConicToProc: TYPE = ImagerPath.ConicToProc;
ArcToProc: TYPE = ImagerPath.ArcToProc;
If this record is changed UPDATE BoxCopy and BoxRestore!
BoxData: TYPE = REF BoxDataObj;
BoxDataObj: TYPE = RECORD [
box: BoundBox, -- this is not used as a bounding box. It is a representation of the shape.
transform: Transformation,
inverse: Transformation, -- inverse of transform
inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s
fillColor: Color,
fillPixelArray: Imager.PixelArray ← NIL,
fillText: TextNode.Location,
formattedNodes: TiogaImager.FormattedNodes,
screenStyle: BOOLFALSE, -- refers to desired format of nodes
isScreenStyle: BOOLFALSE, -- refers to actual format of nodes
strokeJoint: Imager.StrokeJoint ← round,
forward: BOOLTRUE, -- that is, should be traversed in the normal order ll, ul, ur, lr
savedPointSelections: ARRAY [0..5) OF SelectedObjectData, -- in order, ll, ul, ur, lr, center
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.
centerIndex: INTEGER = 4;
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;
copyRestore: PUBLIC BOOLTRUE; -- temporary for test Restore procs.
BuildBoxSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = {
OPEN GGSlice;
class ← NEW[SliceClassObj ← [
type: $Box,
unlink: GGSlice.UnlinkSlice, -- boxData doesn't need unlinking
Fundamentals
getBoundBox: BoxGetBoundBox,
getTransformedBoundBox: GGSlice.GenericTransformedBoundBox,
getTightBox: BoxGetTightBox,
copy: BoxCopy,
restore: BoxRestore,
Drawing
buildPath: BoxBuildPath,
drawBorder: BoxDrawBorder,
drawParts: BoxDrawParts,
drawTransform: BoxDrawTransform,
drawSelectionFeedback: BoxDrawSelectionFeedback,
drawAttractorFeedback: BoxDrawAttractorFeedback,
attractorFeedbackBoundBox: BoxAttractorFeedbackBoundBox,
saveSelections: BoxSaveSelections,
remakeSelections: BoxRemakeSelections,
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,
alterParts: BoxAlterParts,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: BoxPointsInDescriptor,
walkPointsInDescriptor: BoxWalkPointsInDescriptor,
pointPairsInDescriptor: BoxPointPairsInDescriptor,
segmentsInDescriptor: BoxSegmentsInDescriptor,
walkSegments: BoxWalkSegments,
nextPoint: BoxNextPoint,
nextPointPair: BoxNextPointPair,
nextSegment: BoxNextSegment,
Hit Testing
closestPoint: BoxClosestPoint,
closestJointToHitData: BoxClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: BoxClosestSegment,
filledPathsUnderPoint: BoxFilledPathsUnderPoint,
lineIntersection: BoxLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: BoxHitDataAsSimpleCurve,
Style
setDefaults: BoxSetDefaults,
setStrokeWidth: BoxSetStrokeWidth,
getStrokeWidth: BoxGetStrokeWidth,
setStrokeEnd: BoxSetStrokeEnd,
getStrokeEnd: BoxGetStrokeEnd,
setStrokeJoint: BoxSetStrokeJoint,
getStrokeJoint: BoxGetStrokeJoint,
setStrokeColor: BoxSetStrokeColor,
getStrokeColor: BoxGetStrokeColor,
setFillColor: BoxSetFillColor,
getFillColor: BoxGetFillColor,
setArrows: NoOpSetArrows,
getArrows: NoOpGetArrows,
setDashed: BoxSetDashed,
getDashed: BoxGetDashed,
setOrientation: BoxSetOrientation,
getOrientation: BoxGetOrientation
]];
};
MakeBoxSlice: PUBLIC PROC [box: BoundBox, corner: Corner, transform: Transformation ← GGTransform.Identity[]] RETURNS [sliceD: SliceDescriptor] = {
requires a bound box input with loX<=hiX AND loY<=hiY
boxSlice: Slice;
inverse: Transformation ← ImagerTransformation.Invert[transform];
boxData: BoxData ← NEW[BoxDataObj ← [box: box, transform: transform, inverse: inverse, inverseScale: ImagerTransformation.Factor[inverse].s, fillColor: NIL, strokeJoint: round]];
fill color and other properties are filled in when the caller calls slice.setDefaults.
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 BoxSetSegments
ENDLOOP;
boxSlice ← NEW[SliceObj ← [
class: GGSlice.FetchSliceClass[$Box],
data: boxData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
tightBox: GGBoundBox.NullBoundBox[],
boundBox: GGBoundBox.NullBoundBox[],
boxValid: FALSE
]];
boxSlice.nullDescriptor ← GGSlice.DescriptorFromParts[boxSlice, NIL];
BoxSetSegments[boxSlice, boxParts]; -- make segment end points correspond to box vertices
BoxSetBoundBox not needed since boxValid=FALSE
sliceD ← GGSlice.DescriptorFromParts[boxSlice, boxParts];
};
MakeBoxFromMaskPixel: PUBLIC PROC [pa: Imager.PixelArray, color: Color, router: MsgRouter, transform: Transformation ← NIL] RETURNS [slice: Slice] = {
boxData: BoxData;
boundBox: BoundBox ← GGBoundBox.BoundBoxOfPixelArray[pa];
slice ← MakeBoxSlice[boundBox, none, transform].slice;
boxData ← NARROW[slice.data];
boxData.fillPixelArray ← pa;
boxData.fillColor ← color;
FOR i: NAT IN [0..4) DO
boxData.segments[i].color ← NIL;
ENDLOOP;
};
BoxFetchSegment: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [seg: Segment] = {
boxData: BoxData ← NARROW[slice.data];
seg ← boxData.segments[index];
};
SetBoxText: PUBLIC PROC [slice: Slice, loc: TextNode.Location, screenStyle: BOOLFALSE, history: HistoryEvent] = {
nilFormattedNodes: TiogaImager.FormattedNodes ← [NIL, [NIL, 0]];
SELECT GGSliceOps.GetType[slice] FROM
$Box => {
boxData: BoxData ← NARROW[slice.data];
box: BoundBox ← boxData.box;
IF IsChildOfOutline[slice] THEN { -- not top level or clustered box. Format loc directly.
boxData.formattedNodes ← IF loc.node#NIL THEN TiogaImager.FormatNodes[ loc, [box.hiX-box.loX, box.hiY-box.loY], screenStyle] ELSE nilFormattedNodes;
}
ELSE { -- root of document passed in to single box object. Format first child.
firstChildNode: TextNode.Ref ← TextNode.FirstChild[loc.node];
boxData.formattedNodes ← IF firstChildNode#NIL THEN TiogaImager.FormatNodes[ [firstChildNode, 0], [box.hiX-box.loX, box.hiY-box.loY], screenStyle] ELSE nilFormattedNodes;
};
boxData.screenStyle ← screenStyle;
boxData.isScreenStyle ← screenStyle;
boxData.fillText ← loc;
};
ENDCASE => NULL; -- benign in case other slice types get passed in
};
GetBoxText: PUBLIC PROC [slice: Slice] RETURNS [loc: TextNode.Location, screenStyle: BOOLFALSE] = {
SELECT GGSliceOps.GetType[slice] FROM
$Box => {
boxData: BoxData ← NARROW[slice.data];
loc ← boxData.fillText;
screenStyle ← boxData.screenStyle;
};
$Outline => {
outlineData: GGOutline.OutlineData ← NARROW[slice.data];
loc ← [outlineData.fillText, 0];
screenStyle ← outlineData.screenStyle;
};
ENDCASE;
};
GetBoxNodes: PUBLIC PROC [slice: Slice] RETURNS [nodes: TiogaImager.FormattedNodes] = {
WITH slice.data SELECT FROM
boxData: BoxData => nodes ← boxData.formattedNodes;
ENDCASE => nodes ← [NIL, [NIL, 0] ];
};
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;
};
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]
];
};
BoxSetSegments: PRIVATE PROC [slice: Slice, parts: SliceParts ← NIL] = {
Place segments at the proper coordinates, given the current boxData.box.
Ignore parts for now.
boxData: BoxData ← NARROW[slice.data];
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 GGSegment.MoveEndPointSegment[boxData.segments[i], TRUE, lo];
IF hi#boxData.segments[i].hi THEN GGSegment.MoveEndPointSegment[boxData.segments[i], FALSE, hi];
ENDLOOP;
};
IsChildOfOutline: PROC [slice: Slice] RETURNS [BOOL] = {
RETURN [NOT GGScene.IsTopLevel[slice] AND GGSliceOps.GetType[GGParent.GetParent[slice]]=$Outline];
};
Fundamentals
root2Over2: REAL = 0.707106816;
BoxGetBoundBoxAux: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = {
BoxFindBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = {
Ignore parts for now. Try including parts. Ken Pier. July 23, 1991
strokeWidth: REAL ← BoxMaxStrokeWidth[NARROW[slice.data]];
pad: REAL ← strokeWidth*root2Over2 + 1.0; -- allows for square ends and some mitered joints
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[parts];
Make tightBox from the actual box and transform
IF boxParts=NIL OR boxParts.center OR CountCorners[boxParts.corners].count>2 OR CountEdges[boxParts.edges].count>1 THEN { -- bound box is the whole box
tightBox ← GGBoundBox.BoundBoxOfBoundBox[boxData.box, boxData.transform];
}
ELSE { -- have to deal with corners and edges
FOR i: NAT IN [0..4) DO -- better be here only if <= 1 edge is selected
IF boxParts.edges[i] THEN {
p1: Point ← BoxPoint[boxData.box, i];
p2: Point ← BoxPoint[boxData.box, (i+1) MOD 4];
p1 ← ImagerTransformation.Transform[boxData.transform, p1];
p2 ← ImagerTransformation.Transform[boxData.transform, p2];
IF tightBox=NIL THEN tightBox ← GGBoundBox.CreateBoundBox[MIN[p1.x, p2.x], MIN[p1.y, p2.y], MAX[p1.x, p2.x], MAX[p1.y, p2.y] ] ELSE SIGNAL Problem[msg: "Broken Invariant"];
};
ENDLOOP;
FOR i: NAT IN [0..4) DO -- better be here only if <= 2 corners are selected
IF boxParts.corners[i] THEN {
p1: Point ← BoxPoint[boxData.box, i];
p1 ← ImagerTransformation.Transform[boxData.transform, p1];
IF tightBox=NIL THEN tightBox ← GGBoundBox.CreateBoundBox[p1.x, p1.y, p1.x+1.0, p1.y+1.0] ELSE GGBoundBox.EnlargeByPoint[tightBox, p1];
};
ENDLOOP;
};
boundBox ← GGBoundBox.CopyBoundBox[tightBox];
GGBoundBox.EnlargeByOffset[boundBox, pad];
BoxSetSegments[slice, parts];
};
[tightBox, boundBox] ← BoxFindBoundBox[slice, parts]; -- do the bound box update
IF parts=NIL THEN { -- set up cache for fast case next time around
GGSlice.KillBoundBox[slice.parent]; -- invalidate ancestor caches
slice.boundBox ← boundBox;
slice.tightBox ← tightBox;
slice.boxValid ← TRUE;
slice.tightBoxValid ← TRUE;
};
};
BoxGetBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
IF slice.boxValid AND parts=NIL THEN RETURN[slice.boundBox]; -- fast case
RETURN[BoxGetBoundBoxAux[slice, parts].boundBox]; -- cache update if possible
};
BoxGetTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
IF slice.boxValid AND parts=NIL THEN RETURN[slice.tightBox]; -- fast case
RETURN[BoxGetBoundBoxAux[slice, parts].tightBox]; -- cache update if possible
};
BoxCopy: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [copy: LIST OF Slice] = {
GGModelTypes.SliceCopyProc
Just ignore parts and copy the whole box.
copySlice: Slice;
copyData: BoxData;
boxData: BoxData ← NARROW[slice.data];
box: BoundBox ← GGBoundBox.CopyBoundBox[boxData.box];
transform: Transformation ← ImagerTransformation.Copy[boxData.transform];
copySlice ← MakeBoxSlice[box, ur, transform].slice; -- does ur work here ??
copyData ← NARROW[copySlice.data];
FOR i: NAT IN [0..4) DO
copyData.segments[i] ← GGSegment.CopySegment[seg: boxData.segments[i]];
copyData.savedPointSelections[i] ← boxData.savedPointSelections[i];
ENDLOOP;
copyData.savedPointSelections[4] ← boxData.savedPointSelections[4];
copyData.forward ← boxData.forward;
copyData.fillColor ← GGCoreOps.CopyColor[boxData.fillColor];
copyData.fillPixelArray ← boxData.fillPixelArray;
copyData.strokeJoint ← boxData.strokeJoint;
IF NOT IsChildOfOutline[slice] THEN SetBoxText[copySlice, boxData.fillText, boxData.screenStyle, NIL]; -- this only works because we have no way to edit the fillText so it is immutable!
Bound box update not needed since boxValid=FALSE in copy
GGProps.CopyAll[fromSlice: slice, toSlice: copySlice];
RETURN[LIST[copySlice]];
};
BoxRestore: PROC [from: Slice, to: Slice] = {
GGModelTypes.SliceRestoreProc
from and to must be nearly identically structured slices, probably because from was made as a copy of to. Restore the contents of "to", getting all non-REF values from "from". Good luck.
IF to=NIL OR from=NIL THEN ERROR;
IF to.class#from.class THEN ERROR;
IF to.class.type#$Box THEN ERROR;
BEGIN
fromData: BoxData ← NARROW[from.data];
toData: BoxData ← NARROW[to.data];
IF GGSlice.copyRestore THEN {
toData.box^ ← fromData.box^;
toData.transform ← fromData.transform;
toData.inverse ← fromData.inverse;
toData.inverseScale ← fromData.inverseScale;
toData.fillColor ← fromData.fillColor;
toData.fillText ← fromData.fillText;
toData.formattedNodes ← fromData.formattedNodes;
toData.screenStyle ← fromData.screenStyle;
toData.isScreenStyle ← fromData.isScreenStyle;
toData.strokeJoint ← fromData.strokeJoint;
toData.forward ← fromData.forward;
toData.savedPointSelections ← fromData.savedPointSelections;
toData.segments ← fromData.segments;
}
ELSE to.data ← from.data;
to.selectedInFull ← from.selectedInFull; -- RECORD of BOOL
to.normalSelectedParts ← NIL; -- caller must reselect
to.hotSelectedParts ← NIL; -- caller must reselect
to.activeSelectedParts ← NIL; -- caller must reselect
to.matchSelectedParts ← NIL; -- caller must reselect
to.nullDescriptor is always valid
to.fullDescriptor is unused
to.tightBox^ ← from.tightBox^;
to.tightBoxValid ← from.tightBoxValid;
to.boundBox^ ← from.boundBox^;
to.boxValid ← from.boxValid;
to.onOverlay ← from.onOverlay;
to.extraPoints ← from.extraPoints;
to.priority ← from.priority;
to.historyTop ← from.historyTop;
END;
};
Drawing
BoxBuildPath: PROC [slice: Slice, transformParts: SliceParts, transform: Transformation, moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc, editConstraints: EditConstraints ← none] = {
transformParts=NIL => complete parts. transform=NIL => don't transform any parts. Otherwise, transform those pieces of the slice indicated in transformParts by the tranformation in transform, then use the five callback procs to create the slice path.
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.
OPEN ImagerTransformation;
DoBoxPath: PROC [boxData: BoxData, plusTransform: Transformation, useBox: BOOL, box: BoundBox] = {
pts: ARRAY[0..3] OF Point;
boxLo, boxHi, tLL, tUR: Point;
fullTransform: Transformation;
boxLo ← IF useBox THEN [box.loX, box.loY] ELSE point;
boxHi ← IF useBox THEN [box.hiX, box.hiY] ELSE oppositePoint;
fullTransform ← IF plusTransform=NIL THEN boxData.transform ELSE ImagerTransformation.Concat[m: boxData.transform, n: plusTransform];
tLL ← [MIN[boxLo.x, boxHi.x], MIN[boxLo.y, boxHi.y] ];
tUR ← [MAX[boxLo.x, boxHi.x], MAX[boxLo.y, boxHi.y] ];
pts[0] ← Transform[fullTransform, tLL];
pts[1] ← Transform[fullTransform, [tLL.x, tUR.y] ];
pts[2] ← Transform[fullTransform, tUR];
pts[3] ← Transform[fullTransform, [tUR.x, tLL.y] ];
IF boxData.forward THEN {moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]];}
ELSE {moveTo[pts[3]]; lineTo[pts[2]]; lineTo[pts[1]]; lineTo[pts[0]];}
};
point, oppositePoint: Point;
boxData: BoxData ← NARROW[slice.data];
boxTransformParts: BoxParts ← NARROW[transformParts];
box: BoundBox ← boxData.box;
transformEntire, none: BOOLFALSE;
[point, oppositePoint, transformEntire, none] ← PointsForTransform[box, boxData, boxTransformParts, transform];
IF transformEntire
THEN DoBoxPath[boxData, transform, TRUE, box] -- do box path with additional transformation
ELSE IF none THEN DoBoxPath[boxData, NIL, TRUE, box]
ELSE DoBoxPath[boxData, NIL, FALSE, box]; -- transformations already done to point, oppositePoint
};
BoxDrawBorder: PROC [slice: Slice, drawParts: SliceParts, transformParts: SliceParts, transform: Transformation, dc: Imager.Context, camera: GGModelTypes.Camera, quick: BOOL, editConstraints: EditConstraints ← none] = {
drawParts=NIL => draw ALL parts. transformParts=NIL => transform ALL parts. transform=NIL => don't transform any parts. Otherwise, transform those pieces of the slice indicated in transformParts by the tranformation in transform, then draw those parts indicated in drawParts.
OPEN ImagerTransformation;
DrawBoxPath: PROC [boxData: BoxData, boxDrawParts: BoxParts, plusTransform: Transformation, useBox: BOOL, box: BoundBox] = {
pts: ARRAY[0..3] OF Point;
boxLo, boxHi: Point;
fullTransform: Transformation;
boxLo ← IF useBox THEN [box.loX, box.loY] ELSE point;
boxHi ← IF useBox THEN [box.hiX, box.hiY] ELSE oppositePoint;
fullTransform ← IF plusTransform=NIL
THEN boxData.transform
ELSE Concat[m: boxData.transform, n: plusTransform];
pts ← TransformBoxObj[[boxLo.x, boxLo.y, boxHi.x, boxHi.y, FALSE, FALSE], fullTransform];
IF AllStrokePropsAndColorsEqual[boxData] THEN {
BuildPath: Imager.PathProc = {
moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]]; lineTo[pts[0]];
};
MaskStrokeBoxPath[dc, boxData, BuildPath];
}
ELSE {
FOR edge: INTEGER IN [0..4) DO
IF drawAll OR boxDrawParts.edges[edge] THEN {
seg: Segment ← boxData.segments[edge];
GGSegment.DrawLine[dc, pts[edge], pts[(edge + 1) MOD 4], seg];
};
ENDLOOP;
};
};
DoBoxDrawBorder: PROC = {
boxData: BoxData ← NARROW[slice.data];
boxTransformParts: BoxParts ← NARROW[transformParts];
box: BoundBox ← boxData.box;
transformEntire, none: BOOLFALSE;
IF boxDrawParts#NIL AND IsEmpty[boxDrawParts] THEN RETURN;
[point, oppositePoint, transformEntire, none] ← PointsForTransform[box, boxData, boxTransformParts, transform];
IF transformEntire
THEN DrawBoxPath[boxData, boxDrawParts, transform, TRUE, box] -- do box path with additional finalTransform
ELSE IF none THEN DrawBoxPath[boxData, boxDrawParts, NIL, TRUE, box]
ELSE DrawBoxPath[boxData, boxDrawParts, NIL, FALSE, box]; -- transformations already done to point, oppositePoint
};
point, oppositePoint: Point ← [0.0, 0.0];
boxDrawParts: BoxParts ← NARROW[drawParts];
drawAll: BOOL ← boxDrawParts = NIL OR boxDrawParts.edges=ALL[TRUE];
Imager.DoSave[dc, DoBoxDrawBorder];
};
PointsForTransform: PROC [box: BoundBox, boxData: BoxData, boxTransformParts: BoxParts, transform: Transformation] RETURNS [point, oppositePoint: Point ← [0,0], transformEntire, none: BOOLFALSE] = {
[Artwork node; type 'Artwork on' to command tool]
tWorld, tBox: Point;
totalTransform, worldBox: Transformation;
cornerCount, edgeCount, edgeNum, cornerNum: INTEGER ← 0;
IF box.null OR box.infinite THEN ERROR;
BEGIN
IF boxTransformParts=NIL OR transform=NIL OR boxTransformParts.center OR boxTransformParts.edges=ALL[TRUE] THEN GOTO FullTransform;
IF IsEmpty[boxTransformParts] THEN {none ← TRUE; RETURN;};
[edgeCount, edgeNum] ← CountEdges[boxTransformParts.edges];
IF edgeCount >= 2 THEN GOTO FullTransform;
[cornerCount, cornerNum] ← CountCorners[boxTransformParts.corners];
IF cornerCount >= 3 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 (boxTransformParts.corners[lo] AND boxTransformParts.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 -- wrong code 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];
};
EXITS
FullTransform => transformEntire ← TRUE;
END;
};
TransformedBoxPoints: PROC [box: BoundBox, t: Transformation] RETURNS [pts: ARRAY[0..3] OF Point] = {
tLL: Point ← [MIN[box.loX, box.hiX], MIN[box.loY, box.hiY]];
tUR: Point ← [MAX[box.loX, box.hiX], MAX[box.loY, box.hiY]];
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]];
};
TransformBoxObj: PROC [box: BoundBoxObj, t: Transformation] RETURNS [pts: ARRAY[0..3] OF Point] = {
tLL: Point ← [MIN[box.loX, box.hiX], MIN[box.loY, box.hiY]];
tUR: Point ← [MAX[box.loX, box.hiX], MAX[box.loY, box.hiY]];
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] ];
};
FlipBoxStyle: PROC [slice: Slice, boxData: BoxData] = {
nilFormattedNodes: TiogaImager.FormattedNodes ← [NIL, [NIL, 0]];
loc: TextNode.Location ← GetBoxText[slice].loc;
box: BoundBox ← boxData.box;
IF IsChildOfOutline[slice] THEN { -- not top level or clustered box. Format loc directly.
boxData.formattedNodes ← IF loc.node#NIL THEN TiogaImager.FormatNodes[ loc, [box.hiX-box.loX, box.hiY-box.loY], NOT boxData.isScreenStyle] ELSE nilFormattedNodes;
}
ELSE { -- root of document passed in to single box object. Format first child.
firstChildNode: TextNode.Ref ← TextNode.FirstChild[loc.node];
boxData.formattedNodes ← IF firstChildNode#NIL THEN TiogaImager.FormatNodes[ [firstChildNode, 0], [box.hiX-box.loX, box.hiY-box.loY], NOT boxData.isScreenStyle] ELSE nilFormattedNodes;
};
boxData.isScreenStyle ← NOT boxData.isScreenStyle;
};
BoxDrawParts: PROC [slice: Slice, parts: SliceParts ← NIL, dc: Imager.Context, camera: Camera, quick: BOOL] = {
DoBoxDrawParts: PROC = {
BoxTextDraw: PROC = {
refPoint: Point ← [boxData.box.loX, boxData.box.hiY];
Imager.ConcatT[dc, boxData.transform];
Imager.SetColor[dc, Imager.black];
SELECT camera.displayStyle FROM
screen => IF NOT boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData];
print => SELECT boxData.screenStyle FROM
TRUE => IF NOT boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData];
FALSE => IF boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData];
ENDCASE;
ENDCASE;
TiogaImager.Render[box: boxData.formattedNodes.box, context: dc, position: refPoint];
};
boxParts: BoxParts ← NARROW[parts];
drawAll: BOOL ← boxParts = NIL OR boxParts.edges=ALL[TRUE];
pts: ARRAY[0..3] OF Point;
ptsComputed: BOOLFALSE;
Draw fill (including, sampled colors, pixel arrays, and text)
IF drawAll AND boxData.fillColor#NIL AND NOT quick THEN {
GGCoreOps.SetColor[dc, boxData.fillColor];
IF boxData.fillPixelArray = NIL
THEN {
BoxPathProc: Imager.PathProc = {
moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]];
};
pts ← TransformedBoxPoints[boxData.box, boxData.transform];
ptsComputed ← TRUE;
Imager.MaskFill[dc, BoxPathProc];
}
ELSE {
DoMaskPixel: PROC = {
Imager.ConcatT[dc, boxData.transform];
Imager.MaskPixel[dc, boxData.fillPixelArray];
};
Imager.DoSave[dc, DoMaskPixel];
};
};
IF drawAll AND boxData.fillText.node#NIL THEN Imager.DoSave[dc, BoxTextDraw];
Draw outline
IF AllStrokePropsAndColorsEqual[boxData] THEN DrawSingleStrokeBox[dc, slice]
ELSE {
IF NOT ptsComputed THEN pts ← TransformedBoxPoints[boxData.box, boxData.transform];
FOR edge: INTEGER IN [0..4) DO
IF drawAll OR boxParts.edges[edge] THEN {
seg: Segment ← boxData.segments[edge];
GGSegment.DrawLine[dc, pts[edge], pts[(edge + 1) MOD 4], seg];
};
ENDLOOP;
};
};
boxData: BoxData ← NARROW[slice.data];
Imager.DoSave[dc, DoBoxDrawParts];
};
BoxDrawTransform: PROC [slice: Slice, parts: SliceParts ← NIL, dc: Imager.Context, camera: Camera, transform: Transformation, editConstraints: EditConstraints] = {
Depending on which parts are selected, the points are transformed and the box is rubberbanded properly. The box data itself is not modified.
point, oppositePoint: Point;
boxData: BoxData ← NARROW[slice.data];
boxTransformParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
transformColor, transformEntire, none: BOOLFALSE;
IF IsComplete[boxTransformParts] THEN {
transformColor ← TRUE;
transformEntire ← TRUE;
}
ELSE IF IsEmpty[boxTransformParts] THEN {
transform ← NIL;
transformEntire ← TRUE;
}
ELSE [point, oppositePoint, transformEntire, none] ← PointsForTransform[box, boxData, boxTransformParts, transform];
IF transformEntire THEN {
TransformWholeBox: PROC = {
IF transform # NIL THEN {
IF transformColor THEN {
Imager.ConcatT[dc, transform];
GGCoreOps.SetColor[dc, boxData.fillColor];
}
ELSE {
GGCoreOps.SetColor[dc, boxData.fillColor];
Imager.ConcatT[dc, transform];
};
}
ELSE GGCoreOps.SetColor[dc, boxData.fillColor];
BoxDrawAll[dc, boxData, [box.loX, box.loY], [box.hiX, box.hiY], camera];
};
Imager.DoSave[dc, TransformWholeBox];
}
ELSE {
RubberbandBox: PROC = {
Total transformation has already occurred on point, oppositePoint
GGCoreOps.SetColor[dc, boxData.fillColor];
BoxDrawAll[dc, boxData, point, oppositePoint, camera];
};
Imager.DoSave[dc, RubberbandBox];
};
};
BoxDrawAttractorFeedback: PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress: BOOL, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints] = {
DoDrawAttractorFeedback: PROC = {
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[attractorParts]; -- attracting parts
drawAll: BOOL ← boxParts.center OR IsComplete[boxParts];
t: 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] ];
drawn: ARRAY[0..3] OF BOOLALL[FALSE];
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 feedback
FOR corner: INTEGER IN [0..4) DO
IF drawAll OR boxParts.corners[corner] THEN {
nextCorner: INTEGER ← (corner + 1) MOD 4;
prevCorner: INTEGER ← (corner + 3) MOD 4;
IF NOT drawn[corner] THEN {GGShapes.DrawCP[dc, pts[corner], camera.cpScale]; drawn[corner] ← TRUE;};
IF NOT drawn[nextCorner] THEN {GGShapes.DrawCP[dc, pts[nextCorner], camera.cpScale]; drawn[nextCorner] ← TRUE;};
IF NOT drawn[prevCorner] THEN {GGShapes.DrawCP[dc, pts[prevCorner], camera.cpScale]; drawn[prevCorner] ← TRUE;};
};
ENDLOOP;
IF drawAll THEN -- draw center
GGShapes.DrawCP[dc,
ImagerTransformation.Transform[t, [(boxData.box.loX+boxData.box.hiX)/2.0, (boxData.box.loY+boxData.box.hiY)/2.0]],
camera.cpScale];
IF NOT drawAll THEN {
FOR edge: INTEGER IN [0..4) DO
IF boxParts.edges[edge] THEN {
IF NOT drawn[edge] THEN GGShapes.DrawCP[dc, pts[edge], camera.cpScale];
IF NOT drawn[(edge + 1) MOD 4] THEN GGShapes.DrawCP[dc, pts[(edge + 1) MOD 4], camera.cpScale];
};
ENDLOOP;
};
};
IF NOT (dragInProgress AND selectedParts#NIL) AND camera.quality#quality THEN Imager.DoSave[dc, DoDrawAttractorFeedback];
};
BoxAttractorFeedbackBoundBox: PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress: BOOL, camera: Camera, editConstraints: EditConstraints] RETURNS [box: BoundBox] = {
box ← GGBoundBox.NullBoundBox[];
IF NOT (dragInProgress AND selectedParts#NIL) AND camera.quality#quality THEN {
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[attractorParts]; -- attracting parts
drawAll: BOOL ← boxParts.center OR IsComplete[boxParts];
t: 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] ];
drawn: ARRAY[0..3] OF BOOLALL[FALSE];
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] ];
FOR corner: INTEGER IN [0..4) DO
IF drawAll OR boxParts.corners[corner] THEN {
nextCorner: INTEGER ← (corner + 1) MOD 4;
prevCorner: INTEGER ← (corner + 3) MOD 4;
IF NOT drawn[corner] THEN {GGBoundBox.EnlargeByPoint[box, pts[corner]]; drawn[corner] ← TRUE};
IF NOT drawn[nextCorner] THEN {GGBoundBox.EnlargeByPoint[box, pts[nextCorner]]; drawn[nextCorner] ← TRUE;};
IF NOT drawn[prevCorner] THEN {GGBoundBox.EnlargeByPoint[box, pts[prevCorner]]; drawn[prevCorner] ← TRUE;};
};
ENDLOOP;
IF drawAll THEN -- draw center
GGBoundBox.EnlargeByPoint[box,
ImagerTransformation.Transform[t, [(boxData.box.loX+boxData.box.hiX)/2.0, (boxData.box.loY+boxData.box.hiY)/2.0]]];
IF NOT drawAll THEN {
FOR edge: INTEGER IN [0..4) DO
IF boxParts.edges[edge] THEN {
IF NOT drawn[edge] THEN GGBoundBox.EnlargeByPoint[box, pts[edge]];
IF NOT drawn[(edge + 1) MOD 4] THEN GGBoundBox.EnlargeByPoint[box, pts[(edge + 1) MOD 4]];
};
ENDLOOP;
};
GGBoundBox.EnlargeByOffset[box, GGModelTypes.halfJointSize*camera.cpScale +1];
};
};
BoxDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: Camera, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
slowNormal, slowHot, completeNormal, completeHot: BOOLFALSE;
firstJoint: Point;
normalBoxParts, hotBoxParts: BoxParts;
boxData: BoxData ← NARROW[slice.data];
transform: Transformation ← boxData.transform;
IF caretIsMoving OR dragInProgress OR camera.quality=quality THEN RETURN;
IF selectedParts=NIL AND hotParts=NIL THEN RETURN;
normalBoxParts ← NARROW[selectedParts];
hotBoxParts ← NARROW[hotParts];
completeNormal ← normalBoxParts#NIL AND IsComplete[normalBoxParts];
completeHot ← hotBoxParts#NIL AND IsComplete[hotBoxParts];
slowNormal ← normalBoxParts#NIL AND (NOT quick OR (quick AND NOT completeNormal));
slowHot ← hotBoxParts#NIL AND (NOT quick OR (quick AND NOT completeHot));
IF slowNormal AND slowHot THEN DrawSelectionFeedbackBox[slice, boxData, normalBoxParts, hotBoxParts, dc, transform, camera]
ELSE IF slowNormal THEN DrawSelectionFeedbackBox[slice, boxData, normalBoxParts, NIL, dc, transform, camera]
ELSE IF slowHot THEN DrawSelectionFeedbackBox[slice, boxData, NIL, hotBoxParts, dc, transform, camera];
IF (NOT slowNormal AND completeNormal) OR (NOT slowHot AND completeHot) THEN {
fullParts: BoxParts ← IF completeNormal THEN normalBoxParts ELSE hotBoxParts;
[] ← GGSliceOps.GetTightBox[slice]; -- force update of internal data
firstJoint ← ImagerTransformation.Transform[transform, [boxData.box.loX, boxData.box.loY]];
};
IF NOT slowHot AND completeHot THEN
GGShapes.DrawQuickSelectedJoint[dc, firstJoint, hot, camera.cpScale];
IF NOT slowNormal AND completeNormal THEN
GGShapes.DrawQuickSelectedJoint[dc, firstJoint, normal, camera.cpScale];
};
Drawing Utilities
RenderText: PUBLIC PROC [dc: Imager.Context, slice: Slice, camera: Camera, quick: BOOL] = {
DoRenderText: PROC [] = {
refPoint: Point ← [boxData.box.loX, boxData.box.hiY];
Imager.ConcatT[dc, boxData.transform];
Imager.SetColor[dc, Imager.black];
SELECT camera.displayStyle FROM
screen => IF NOT boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData];
print => SELECT boxData.screenStyle FROM
TRUE => IF NOT boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData];
FALSE => IF boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData];
ENDCASE;
ENDCASE;
TiogaImager.Render[box: boxData.formattedNodes.box, context: dc, position: refPoint];
};
boxData: BoxData ← NARROW[slice.data];
IF boxData.fillText.node#NIL THEN Imager.DoSave[dc, DoRenderText];
};
DrawSingleStrokeBox: PROC [dc: Imager.Context, slice: Slice] = {
BuildPath: Imager.PathProc = {
pts: ARRAY[0..3] OF Point ← TransformedBoxPoints[boxData.box, boxData.transform];
moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]]; lineTo[pts[0]];
};
boxData: BoxData ← NARROW[slice.data];
MaskStrokeBoxPath[dc, boxData, BuildPath];
}; -- end DrawSingleStrokeBox
MaskStrokeBoxPath: PROC [dc: Imager.Context, boxData: BoxData, pathProc: Imager.PathProc] = {
PatternProc: PROC [i: NAT] RETURNS [REAL] = {
RETURN[pattern[i]];
};
firstSeg: Segment ← boxData.segments[0];
strokeWidth: REAL ← firstSeg.strokeWidth;
pattern: SequenceOfReal ← firstSeg.pattern;
IF strokeWidth=0.0 OR firstSeg.color=NIL THEN RETURN;
Imager.SetStrokeWidth[dc, strokeWidth];
Imager.SetStrokeEnd[dc, firstSeg.strokeEnd];
Imager.SetStrokeJoint[dc, boxData.strokeJoint];
Imager.SetColor[dc, firstSeg.color];
IF firstSeg.dashed THEN Imager.MaskDashedStroke[dc, pathProc, pattern.len, PatternProc, firstSeg.offset, firstSeg.length]
ELSE Imager.MaskStroke[dc, pathProc, TRUE];
};
AllStrokePropsAndColorsEqual: PROC [boxData: BoxData] RETURNS [BOOL ← FALSE] = {
seg: Segment;
firstSeg: Segment ← boxData.segments[0];
width: REAL ← firstSeg.strokeWidth;
end: StrokeEnd ← firstSeg.strokeEnd;
color: Color ← firstSeg.color;
dashed: BOOL ← firstSeg.dashed;
pattern: SequenceOfReal ← firstSeg.pattern;
offset: REAL ← firstSeg.offset;
length: REAL ← firstSeg.length;
FOR i: INT IN [1..4) DO
seg ← boxData.segments[i];
IF seg.dashed # dashed THEN RETURN[FALSE];
IF seg.strokeEnd # end THEN RETURN[FALSE];
IF seg.strokeWidth # width THEN RETURN[FALSE];
IF NOT GGCoreOps.EquivalentColors[color, seg.color] THEN RETURN[FALSE];
IF NOT dashed THEN LOOP;
IF seg.offset # offset THEN RETURN[FALSE];
IF seg.length # length THEN RETURN[FALSE];
IF NOT GGUtility.EquivalentPatterns[seg.pattern, pattern] THEN RETURN[FALSE];
REPEAT
FINISHED => RETURN[TRUE];
ENDLOOP;
};
DrawSelectionFeedbackBox: PROC [slice: Slice, boxData: BoxData, normalBoxParts: BoxParts, hotBoxParts: BoxParts, dc: Imager.Context, t: Transformation, camera: Camera] = {
DoDrawFeedback: PROC = {
box: BoundBox ← boxData.box;
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, camera.cpScale];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[corner], normal, camera.cpScale];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[corner], camera.cpScale];
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, camera.cpScale];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, cc, normal, camera.cpScale];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, cc, camera.cpScale];
};
Imager.DoSave[dc, DoDrawFeedback];
};
BoxDrawAll: PROC [dc: Imager.Context, boxData: BoxData, from: Point, to: Point, camera: Camera] = {
BoxPath: Imager.PathProc={moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]];};
t: Transformation ← boxData.transform;
pts: ARRAY[0..3] OF Point;
pts ← TransformBoxObj[[loX: to.x, loY: to.y, hiX: from.x, hiY: from.y, null: FALSE, infinite: FALSE], t];
draw fill
IF boxData.fillColor#NIL THEN {
IF boxData.fillPixelArray = NIL
THEN Imager.MaskFill[dc, BoxPath]
ELSE {
DoMaskPixel: PROC = {
Imager.ConcatT[dc, t];
Imager.MaskPixel[dc, boxData.fillPixelArray];
};
};
};
draw outline
IF AllStrokePropsAndColorsEqual[boxData] THEN MaskStrokeBoxPath[dc, boxData, BoxPath]
ELSE {
FOR edge: INTEGER IN [0..4) DO
seg: Segment ← boxData.segments[edge];
GGSegment.DrawLine[dc, pts[edge], pts[(edge + 1) MOD 4], seg];
ENDLOOP;
};
};
BoxSaveSelections: PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] = {
GGModelTypes.SliceSaveSelectionsProc
SetPointField: PROC [point: INTEGER, selected: BOOL, selectClass: SelectionClass] = {
SELECT selectClass FROM
normal => boxData.savedPointSelections[point].normal ← selected;
hot => boxData.savedPointSelections[point].hot ← selected;
active => boxData.savedPointSelections[point].active ← selected;
match => boxData.savedPointSelections[point].match ← selected;
ENDCASE;
};
SetSegmentField: PROC [seg: Segment, selected: BOOL, selectClass: SelectionClass] = {
SELECT selectClass FROM
normal => seg.TselectedInFull.normal ← selected;
hot => seg.TselectedInFull.hot ← selected;
active => seg.TselectedInFull.active ← selected;
match => seg.TselectedInFull.match ← selected;
ENDCASE;
};
dontClear: BOOLTRUE;
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
IF boxParts=NIL THEN dontClear ← FALSE;
FOR count: INTEGER IN [0..4) DO
SetPointField[count, dontClear AND boxParts.corners[count], selectClass];
SetSegmentField[boxData.segments[count], dontClear AND boxParts.edges[count], selectClass];
ENDLOOP;
SetPointField[centerIndex, dontClear AND boxParts.center, selectClass];
};
BoxRemakeSelections: PROC [slice: Slice, selectClass: SelectionClass] RETURNS [parts: SliceParts] = {
GGModelTypes.SliceRemakeSelectionsProc
GetPointField: PROC [point: INTEGER, selectClass: SelectionClass] RETURNS [BOOL] = {
RETURN[SELECT selectClass FROM
normal => boxData.savedPointSelections[point].normal,
hot => boxData.savedPointSelections[point].hot,
active => boxData.savedPointSelections[point].active,
match => boxData.savedPointSelections[point].match,
ENDCASE => FALSE];
};
GetSegmentField: PROC [seg: Segment, selectClass: SelectionClass] RETURNS [BOOL] = {
RETURN[SELECT selectClass FROM
normal => seg.TselectedInFull.normal,
hot => seg.TselectedInFull.hot,
active => seg.TselectedInFull.active,
match => seg.TselectedInFull.match,
ENDCASE => FALSE];
};
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NEW[BoxPartsObj];
FOR count: INTEGER IN [0..4) DO
boxParts.corners[count] ← GetPointField[count, selectClass];
boxParts.edges[count] ← GetSegmentField[boxData.segments[count], selectClass];
ENDLOOP;
boxParts.center ← GetPointField[centerIndex, selectClass];
parts ← IF IsEmpty[boxParts] THEN NIL ELSE boxParts; -- KAP. September 9, 1991
};
Transforming
BoxTransform: PROC [slice: Slice, parts: SliceParts ← NIL, transform: Transformation, editConstraints: EditConstraints, history: HistoryEvent] = {
GGModelTypes.SliceTransformProc
Permanently transforms the box. Depending on which parts are selected, the points are transformed and the box is grown/shrunk properly.
BoxFullTransform: PROC [transformColor: BOOL] = {
boxData.transform ← ImagerTransformation.Concat[boxData.transform, transform];
boxData.inverse ← ImagerTransformation.Invert[boxData.transform];
boxData.inverseScale ← ImagerTransformation.Factor[boxData.inverse].s;
IF transformColor THEN
boxData.fillColor ← GGCoreOps.TransformColor[boxData.fillColor, transform];
BoxSetSegments[slice, parts];
GGSlice.KillBoundBox[slice];
};
point, oppositePoint: ImagerTransformation.VEC; -- really points, not vectors
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
inverse, totalTransform: Transformation;
cornerCount, edgeCount: INTEGER ← 0;
IF box.null OR box.infinite THEN ERROR;
IF boxParts=NIL OR IsComplete[boxParts] THEN {
BoxFullTransform[TRUE];
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.
BoxFullTransform[FALSE];
RETURN;
};
IF edgeCount=1 THEN { -- transform an edge
f: FactoredTransformation ← ImagerTransformation.Factor[transform];
globalTranslate: ImagerTransformation.VEC ← f.t;
mInverse: Transformation ← ImagerTransformation.Invert[boxData.transform];
localTranslate: ImagerTransformation.VEC ← ImagerTransformation.TransformVec[mInverse, globalTranslate];
totalTransform: 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=2 THEN { -- two isolated corners. Full transform. Not quite right if corners are adjacent.
BoxFullTransform[FALSE];
RETURN;
}
ELSE { -- one corner.
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];
BoxSetSegments[slice, parts];
GGSlice.KillBoundBox[slice];
IF boxData.fillText.node#NIL AND NOT IsChildOfOutline[slice] THEN {
Box is responsible for entire document. Following code optimized from SetBoxText
loc: TextNode.Location ← GGSlice.GetBoxText[slice].loc; -- includes root
firstChild: TextNode.Ref ← TextNode.FirstChild[loc.node];
boxData.formattedNodes ← TiogaImager.FormatNodes[ [firstChild, 0], [box.hiX-box.loX, box.hiY-box.loY], boxData.screenStyle] -- top level box 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[" pa: "];
GGParseOut.WritePixelArray[f, boxData.fillPixelArray];
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[" ) props: ( "];
FOR edge: INTEGER IN [0..4) DO
seg: Segment ← boxData.segments[edge];
f.PutRope["( "];
GGParseOut.WriteBool[f, seg.props#NIL];
IF seg.props#NIL THEN GGParseOut.WriteProps[f, seg.props] ELSE f.PutChar[IO.SP]; -- list of ROPE
f.PutRope[") "];
ENDLOOP;
f.PutRope[") fwd: "];
GGParseOut.WriteBool[f, boxData.forward];
f.PutRope[" strokeJoint: "];
GGParseOut.WriteStrokeJoint[f, boxData.strokeJoint];
BEGIN
loc: TextNode.Location;
screenStyle: BOOLFALSE;
[loc, screenStyle] ← GGSlice.GetBoxText[slice];
f.PutRope["\n fillText: "];
GGParseOut.WriteText[f, IF NOT IsChildOfOutline[slice] THEN loc.node ELSE NIL, screenStyle];
only write node data if not an Outline child. Otherwise, parent Outline takes care of it.
END;
};
BoxFilein: PROC [f: IO.STREAM, version: REAL, router: MsgRouter, camera: Camera] RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
boxData: BoxData;
box: BoundBox;
p1, p2: Point;
transform: 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;
props: ARRAY [0..4) OF LIST OF Rope.ROPEALL[NIL];
fill: Color;
pa: Imager.PixelArray ← NIL;
fwd, good, truth: BOOLTRUE;
strokeJoint: Imager.StrokeJoint ← round;
p1 ← GGParseIn.ReadPoint[f];
p2 ← GGParseIn.ReadPoint[f];
transform ← GGParseIn.ReadTransformation[f];
IF version > 8605.12 THEN {
GGParseIn.ReadWRope[f, "strokeWidths: ("];
FOR edge: INTEGER IN [0..4) DO
strokeWidths[edge] ← GGParseIn.ReadWReal[f];
ENDLOOP;
}
ELSE strokeWidths ← ALL[2.0];
IF version>=8702.26 THEN { -- read in strokeEnds
GGParseIn.ReadWRope[f, ") strokeEnds: ("];
FOR edge: INTEGER IN [0..4) DO
ends[edge] ← GGParseIn.ReadStrokeEnd[f];
ENDLOOP;
}
ELSE ends ← [round, round, round, round];
IF version > 8605.12 THEN {
GGParseIn.ReadWRope[f, ") strokeColors: ("];
FOR edge: INTEGER IN [0..4) DO
colors[edge] ← GGParseIn.ReadColor[f, version];
ENDLOOP;
GGParseIn.ReadWRope[f, ") fillColor: "];
fill ← GGParseIn.ReadColor[f, version];
}
ELSE {
colors ← ALL[Imager.black];
fill ← NIL;
};
IF version >= 8811.30 THEN { -- read in the pixel array
GGParseIn.ReadWRope[f, "pa:"];
pa ← GGParseIn.ReadPixelArray[f];
};
IF version>=8704.03 THEN { -- read in dash patterns
GGParseIn.ReadWRope[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.ReadWReal[f];
lengths[edge] ← GGParseIn.ReadWReal[f];
};
ENDLOOP;
GGParseIn.ReadWRope[f, ")"];
};
IF version>=8706.08 THEN { -- read in segment props
hasProps: BOOLFALSE;
GGParseIn.ReadWRope[f, "props: ("];
FOR edge: INTEGER IN [0..4) DO
GGParseIn.ReadWRope[f, "( "];
hasProps ← GGParseIn.ReadBool[f, version].truth;
props[edge] ← IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL;
GGParseIn.ReadWRope[f, ")"];
ENDLOOP;
GGParseIn.ReadWRope[f, ")"];
};
IF version>=8802.04 THEN { -- read in forward bit
GGParseIn.ReadWRope[f, "fwd:"];
[fwd, good] ← GGParseIn.ReadBool[f, version];
};
IF version>=8905.19 THEN { -- read in strokeJoint
GGParseIn.ReadWRope[f, "strokeJoint:"];
strokeJoint ← GGParseIn.ReadStrokeJoint[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
seg: Segment ← boxData.segments[edge];
seg.strokeWidth ← strokeWidths[edge];
seg.strokeEnd ← ends[edge];
seg.color ← colors[edge];
seg.dashed ← dashes[edge];
IF dashes[edge] THEN {
seg.pattern ← patterns[edge];
seg.offset ← offsets[edge];
seg.length ← lengths[edge];
};
FOR next: LIST OF Rope.ROPE ← props[edge], next.rest UNTIL next=NIL DO
seg.props ← CONS[next.first, seg.props];
ENDLOOP;
ENDLOOP;
boxData.fillColor ← fill;
boxData.fillPixelArray ← pa;
boxData.forward ← fwd;
boxData.strokeJoint ← strokeJoint;
IF version=8803.08 THEN { -- read in text fill in old format
[truth, good] ← GGParseIn.ReadBool[f, version];
IF NOT good THEN truth ← FALSE;
IF truth THEN {
fillText: Rope.ROPE;
ref: TextNode.Ref;
nodeSize, where: INT ← 0;
[truth, good] ← GGParseIn.ReadBool[f, version]; -- read screenStyle
IF version>=8803.24 THEN where ← GGParseIn.ReadWCARD[f]; -- read location index
nodeSize ← GGParseIn.ReadWCARD[f]; -- read number of chars in node
IF f.PeekChar[]= ' THEN []← f.GetChar[]; -- don't ask. Leave this in.
fillText ← f.GetRope[nodeSize, TRUE];
ref ← PutGet.FromRope[fillText];
GGSlice.SetBoxText[slice, [ref, where], IF good THEN truth ELSE FALSE, NIL];
};
};
IF version>=8803.24 THEN {
text: TextNode.Ref;
screenStyle: BOOLFALSE;
GGParseIn.ReadWRope[f, "fillText:"];
[text, screenStyle] ← GGParseIn.ReadText[f, version];
GGSlice.SetBoxText[slice, [text, 0], screenStyle, NIL];
};
};
Parts
MakeComplete: PROC [boxParts: BoxParts] = {
boxParts.corners ← ALL[TRUE];
boxParts.edges ← ALL[TRUE];
boxParts.center ← TRUE;
};
IsComplete: PROC [boxParts: BoxParts] RETURNS [BOOL ← FALSE] = {
RETURN[ boxParts#NIL AND boxParts.corners=ALL[TRUE] AND boxParts.edges=ALL[TRUE] AND boxParts.center ];
};
IsEmpty: PROC [boxParts: BoxParts] RETURNS [BOOL ← FALSE] = {
RETURN[boxParts=NIL OR (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;
};
BoxWalkPointsInDescriptor: PROC [sliceD: SliceDescriptor, walkProc: PointWalkProc] = {
parts: BoxParts ← NARROW[sliceD.parts];
boxData: BoxData ← NARROW[sliceD.slice.data];
done: BOOLFALSE;
FOR corner: INTEGER IN [0..4) DO
IF parts.corners[corner] THEN {
done ← walkProc[GetBoxPoint[boxData, corner]];
IF done THEN RETURN;
};
ENDLOOP;
IF parts.center THEN {
done ← walkProc[GetBoxPoint[boxData, 4]];
};
};
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] ← boxParts.corners[i] ← boxParts.corners[(i + 1) MOD 4] ← walkProc[boxData.segments[i], boxData.transform];
ENDLOOP;
IF boxParts.edges=ALL[TRUE] AND boxParts.corners=ALL[TRUE] THEN boxParts.center ← TRUE;
sliceD ← GGSlice.DescriptorFromParts[slice, boxParts];
};
BoxNextPoint: PROC [slice: Slice, pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
IF pointGen=NIL OR pointGen.toGo = 0 THEN {
pointAndDone.done ← TRUE;
RETURN;
}
ELSE {
sliceD: SliceDescriptor ← pointGen.sliceD;
boxData: BoxData ← NARROW[sliceD.slice.data];
boxParts: BoxParts ← NARROW[sliceD.parts];
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;
pointAndDone.point ← GetBoxPoint[boxData, index];
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
};
};
GetBoxPoint: PROC [boxData: BoxData, index: INTEGER] RETURNS [point: Point] = {
OPEN ImagerTransformation;
t: Transformation ← boxData.transform;
SELECT index FROM -- index in [0..4) of next available point
0 => point ← Transform[t, [boxData.box.loX, boxData.box.loY]];
1 => point ← Transform[t, [boxData.box.loX, boxData.box.hiY]];
2 => point ← Transform[t, [boxData.box.hiX, boxData.box.hiY]];
3 => point ← Transform[t, [boxData.box.hiX, boxData.box.loY]];
4 => point ← Transform[t, [(boxData.box.loX + boxData.box.hiX)/2.0, (boxData.box.loY + boxData.box.hiY)/2.0]];
ENDCASE => SIGNAL Problem[msg: "Broken Invariant"];
};
BoxNextPointPair: PROC [slice: Slice, pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = {
IF pointPairGen=NIL OR pointPairGen.toGo = 0 THEN {
pointPairAndDone.done ← TRUE;
RETURN;
}
ELSE {
sliceD: SliceDescriptor ← pointPairGen.sliceD;
boxData: BoxData ← NARROW[sliceD.slice.data];
boxParts: BoxParts ← NARROW[sliceD.parts];
t: 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 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 [slice: Slice, segGen: SegmentGenerator] RETURNS [seg: Segment, transform: Transformation] = {
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 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 ← FALSE] = {
boxParts: BoxParts ← NARROW[sliceD.parts];
RETURN[IsEmpty[boxParts]];
};
BoxIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL ← FALSE] = {
boxParts: BoxParts ← NARROW[sliceD.parts];
RETURN[IsComplete[boxParts]];
};
NearestBoxPoint: PROC [slice: Slice, point: Point] RETURNS [index: [-1..3], center: BOOL ← FALSE] = {
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 {
lo, hi: NAT;
boxParts.edges[boxHitData.edge] ← TRUE;
[lo, hi] ← CornersOfEdge[boxHitData.edge];
boxParts.corners[lo] ← boxParts.corners[hi] ← 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, editConstraints: EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord] 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 ← GGSliceOps.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];
};
BoxAlterParts: PROC [sliceD: SliceDescriptor, action: ATOM] RETURNS [newD: SliceDescriptor] = {
GGModelTypes.SliceAlterPartsProc
boxParts: BoxParts ← NARROW[sliceD.parts];
newParts: BoxParts;
selectedCorner: INT ← -1;
selectedEdge: INT ← -1;
newParts ← NEW[BoxPartsObj ← [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]];
FOR i: INTEGER IN [0..4) DO
IF boxParts.corners[i] THEN {
selectedCorner ← i;
EXIT;
};
ENDLOOP;
IF selectedCorner = -1 THEN {
FOR i: INTEGER IN [0..4) DO
IF boxParts.edges[i] THEN {
selectedEdge ← i;
EXIT;
};
ENDLOOP;
};
SELECT action FROM
$Forward => {
IF selectedCorner > -1 THEN {
IF selectedCorner = 3 THEN {
newParts.center ← TRUE;
}
ELSE {
selectedCorner ← selectedCorner+1;
newParts.corners[selectedCorner] ← TRUE;
};
}
ELSE IF selectedEdge > -1 THEN {
selectedEdge ← (selectedEdge+1) MOD 4;
newParts.edges[selectedEdge] ← TRUE;
}
ELSE newParts.corners[0] ← TRUE;
};
$Backward => {
IF selectedCorner > -1 THEN {
IF selectedCorner = 0 THEN {
newParts.center ← TRUE;
}
ELSE {
selectedCorner ← selectedCorner-1;
newParts.corners[selectedCorner] ← TRUE;
};
}
ELSE IF selectedEdge > -1 THEN {
selectedEdge ← (selectedEdge+3) MOD 4;
newParts.edges[selectedEdge] ← TRUE;
}
ELSE newParts.corners[3] ← TRUE;
};
$Grow => RETURN[GGSliceOps.NewParts[sliceD.slice, NIL, slice]];
$ShrinkForward, $ShrinkBackward => RETURN[NIL]; -- no children to shrink to
ENDCASE => ERROR;
newD ← GGSlice.DescriptorFromParts[sliceD.slice, newParts];
};
BoxClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point ← [0.0, 0.0], bestDist: REAL ← Real.LargestNumber, bestNormal: Vector ← [0, -1], hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[sliceD.slice], tolerance]
THEN RETURN;
BEGIN
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 {
IF boxHitData.corner # -1 THEN {
lxly, lxhy, hxly,hxhy: Point;
lxly ← GGTransform.Transform[boxData.transform, [box^.loX,box^.loY]];
lxhy ← GGTransform.Transform[boxData.transform, [box^.loX,box^.hiY]];
hxly ← GGTransform.Transform[boxData.transform, [box^.hiX,box^.loY]];
hxhy ← GGTransform.Transform[boxData.transform, [box^.hiX,box^.hiY]];
IF GGBoundBox.PointIsInBox[localTestPoint, box^] THEN {
normal1, normal2: Vector;
nearDir: Vector ← Vectors2d.VectorFromPoints[GGTransform.Transform[boxData.transform, bestPoint], testPoint];
IF boxHitData.corner = 0 THEN {
normal1 ← Vectors2d.VectorFromPoints[lxly,lxhy];
normal2 ← Vectors2d.VectorFromPoints[lxly,hxly];
};
IF boxHitData.corner = 1 THEN {
normal1 ← Vectors2d.VectorFromPoints[lxhy,lxly];
normal2 ← Vectors2d.VectorFromPoints[lxhy,hxhy];
};
IF boxHitData.corner = 2 THEN {
normal1 ← Vectors2d.VectorFromPoints[hxhy,lxhy];
normal2 ← Vectors2d.VectorFromPoints[hxhy,hxly];
};
IF boxHitData.corner = 3 THEN {
normal1 ← Vectors2d.VectorFromPoints[hxly,hxhy];
normal2 ← Vectors2d.VectorFromPoints[hxly,lxly];
};
Note: It might be best if the angle check is reversed when inside the box.
IF ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, nearDir]] > ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, nearDir]] THEN{
bestNormal ← normal1; }
ELSE {bestNormal ← normal2;};
}
ELSE {
normal1, normal2: Vector;
nearDir: Vector ← Vectors2d.VectorFromPoints[GGTransform.Transform[boxData.transform, bestPoint], testPoint];
IF boxHitData.corner = 0 THEN {
normal1 ← Vectors2d.VectorFromPoints[lxhy,lxly];
normal2 ← Vectors2d.VectorFromPoints[hxly,lxly];
};
IF boxHitData.corner = 1 THEN {
normal1 ← Vectors2d.VectorFromPoints[lxly,lxhy];
normal2 ← Vectors2d.VectorFromPoints[hxhy,lxhy];
};
IF boxHitData.corner = 2 THEN {
normal1 ← Vectors2d.VectorFromPoints[lxhy,hxhy];
normal2 ← Vectors2d.VectorFromPoints[hxly,hxhy];
};
IF boxHitData.corner = 3 THEN {
normal1 ← Vectors2d.VectorFromPoints[hxhy,hxly];
normal2 ← Vectors2d.VectorFromPoints[lxly,hxly];
};
IF ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, nearDir]] < ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, nearDir]] THEN{
bestNormal ← normal1;}
ELSE {bestNormal ← normal2;};
};
};
bestPoint ← GGTransform.Transform[boxData.transform, bestPoint];
boxHitData.hitPoint ← bestPoint;
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
};
END;
};
BoxClosestJointToHitData: PROC [sliceD: SliceDescriptor, mapPoint, testPoint: Point, hitData: REF ANY] RETURNS [jointD: SliceDescriptor, point: Point, normal: Vector ← [0,-1]] = {
GGModelTypes.SliceClosestJointToHitDataProc
hitD: REF ANY;
boxData: BoxData ← NARROW[sliceD.slice.data];
box: BoundBox ← boxData.box;
localTestPoint: Point ← GGTransform.Transform[boxData.inverse, testPoint];
[point, ----, normal, hitD, ----] ← BoxClosestPoint[sliceD, testPoint, GGUtility.plusInfinity];
jointD ← sliceD; -- this is a crock and WRONG!! but it saves work
};
BoxClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point ← [0.0, 0.0], bestDist: REAL ← Real.LargestNumber, bestNormal: Vector ← [0,-1], hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[sliceD.slice], tolerance]
THEN RETURN;
BEGIN
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] ];
bestNormal ← Vectors2d.Sub[testPoint, bestPoint];
};
END;
};
BoxFilledPathsUnderPoint: PUBLIC PROC [slice: Slice, point: Point, tolerance: REAL] RETURNS [hitData: REF ANYNIL, moreHitDatas: LIST OF REF ANYNIL] = {
boxData: BoxData ← NARROW[slice.data];
pointBox: Point ← ImagerTransformation.Transform[boxData.inverse, point];
box: BoundBox ← boxData.box;
success: BOOLFALSE;
success
boxData.fillColor # NIL AND
(boxData.box.infinite OR
(pointBox.x >= box.loX AND
pointBox.x <= box.hiX AND
pointBox.y >= box.loY AND
pointBox.y <= box.hiY));
IF success THEN {
hitData ← NEW[BoxHitDataObj ← [corner: -1, edge: -1, center: 0, hitPoint: point]];
};
};
BoxLineIntersection: PUBLIC PROC [sliceD: SliceDescriptor, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT ← 0] = {
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.
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, setHow: ATOM, history: HistoryEvent] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.edges[edge] THEN {
SELECT setHow FROM
$Set => boxData.segments[edge].color ← color;
$ChangeHue => {
newColor: Color ← GGUtility.ChangeHue[boxData.segments[edge].color, color];
boxData.segments[edge].color ← newColor;
};
ENDCASE => ERROR;
};
ENDLOOP;
};
BoxGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color, isUnique: BOOLTRUE] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
colorFound: BOOLFALSE;
thisColor: Color;
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.edges[edge] THEN {
IF colorFound THEN {
thisColor ← boxData.segments[edge].color;
IF NOT GGCoreOps.EquivalentColors[thisColor, color] THEN RETURN [color, FALSE];
}
ELSE {
colorFound ← TRUE;
color ← boxData.segments[edge].color;
};
};
ENDLOOP;
IF NOT colorFound THEN {
FOR corner: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.corners[corner] THEN {
IF colorFound THEN {
thisColor ← boxData.segments[corner].color;
IF NOT GGCoreOps.EquivalentColors[thisColor, color] THEN RETURN [color, FALSE];
}
ELSE {
colorFound ← TRUE;
color ← boxData.segments[corner].color;
};
};
ENDLOOP;
};
};
BoxSetFillColor: PROC [slice: Slice, parts: SliceParts, color: Color, setHow: ATOM, history: HistoryEvent] = {
boxData: BoxData ← NARROW[slice.data];
SELECT setHow FROM
$Set => boxData.fillColor ← color;
$ChangeHue => {
newColor: Color ← GGUtility.ChangeHue[boxData.fillColor, color];
boxData.fillColor ← newColor;
};
ENDCASE => ERROR;
};
BoxGetFillColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color, isUnique: BOOLTRUE] = {
boxData: BoxData ← NARROW[slice.data];
color ← boxData.fillColor;
};
BoxSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL, history: HistoryEvent] RETURNS [box: BoundBox] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.edges[edge] THEN boxData.segments[edge].strokeWidth ← strokeWidth;
ENDLOOP;
box ← GGSliceOps.GetBoundBox[slice, parts]; -- return a good bound box
};
BoxSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData, history: HistoryEvent] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.edges[edge] THEN GGSegment.SetDefaults[boxData.segments[edge], defaults];
ENDLOOP;
boxData.fillColor ← defaults.fillColor;
boxData.strokeJoint ← defaults.strokeJoint;
GGSlice.KillBoundBox[slice];
};
BoxGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL ← 17.0, isUnique: BOOLTRUE] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
found: BOOLFALSE;
this: REAL;
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.edges[edge] THEN {
IF found THEN {
this ← boxData.segments[edge].strokeWidth;
IF this#strokeWidth THEN RETURN [strokeWidth, FALSE];
}
ELSE {
found ← TRUE;
strokeWidth ← boxData.segments[edge].strokeWidth;
};
};
ENDLOOP;
IF NOT found THEN RETURN[-1.0, FALSE]; -- nothing selected
};
BoxSetStrokeEnd: PROC [slice: Slice, parts: SliceParts, strokeEnd: StrokeEnd, history: HistoryEvent] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.edges[edge] THEN boxData.segments[edge].strokeEnd ← strokeEnd;
ENDLOOP;
};
BoxSetStrokeJoint: PROC [slice: Slice, parts: SliceParts, strokeJoint: StrokeJoint, history: HistoryEvent] = {
boxData: BoxData ← NARROW[slice.data];
boxData.strokeJoint ← strokeJoint;
};
BoxGetStrokeJoint: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeJoint: StrokeJoint, isUnique: BOOLTRUE] = {
boxData: BoxData ← NARROW[slice.data];
RETURN[boxData.strokeJoint];
};
BoxGetStrokeEnd: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeEnd: StrokeEnd ← square, isUnique: BOOLTRUE] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
found: BOOLFALSE;
this: StrokeEnd;
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.edges[edge] THEN {
IF found THEN {
this ← boxData.segments[edge].strokeEnd;
IF this#strokeEnd THEN RETURN [strokeEnd, FALSE];
}
ELSE {
found ← TRUE;
strokeEnd ← boxData.segments[edge].strokeEnd;
};
};
ENDLOOP;
IF NOT found THEN RETURN[round, FALSE]; -- nothing selected
};
BoxSetDashed: PROC [slice: Slice, parts: SliceParts, dashed: BOOLFALSE, pattern: SequenceOfReal ← NIL, offset: REAL ← 0.0, length: REAL ← -1.0, history: HistoryEvent] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR 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: BOOLFALSE, pattern: SequenceOfReal, offset, length: REAL ← 0.0, isUnique: BOOLTRUE] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
found: BOOLFALSE;
FOR edge: INTEGER IN [0..4) DO
IF boxParts=NIL OR boxParts.edges[edge] THEN {
seg: Segment ← boxData.segments[edge];
IF found THEN {
IF dashed#seg.dashed OR (seg.dashed AND
(seg.offset # offset OR
seg.length # length OR
NOT GGUtility.EquivalentPatterns[seg.pattern, pattern]))
THEN RETURN[dashed, pattern, offset, length, FALSE];
}
ELSE {
found ← TRUE;
dashed ← seg.dashed;
pattern ← seg.pattern;
offset ← seg.offset;
length ← seg.length;
};
};
ENDLOOP;
IF NOT found THEN RETURN[FALSE, pattern, offset, length, FALSE]; -- nothing selected
};
BoxSetOrientation: PROC [slice: Slice, parts: SliceParts, orientation: Orientation, history: HistoryEvent] RETURNS [success: BOOLTRUE] = {
boxData: BoxData ← NARROW[slice.data];
SELECT orientation FROM
cw => boxData.forward ← TRUE;
ccw => boxData.forward ← FALSE;
reverse => boxData.forward ← NOT boxData.forward;
ENDCASE => ERROR;
};
BoxGetOrientation: PROC [slice: Slice, parts: SliceParts] RETURNS [orientation: Orientation, isUnique: BOOLTRUE] = {
boxData: BoxData ← NARROW[slice.data];
RETURN[IF boxData.forward THEN cw ELSE ccw];
};
globalEdge: Edge;
Init: PROC [] = {
globalEdge ← Lines2d.CreateEmptyEdge[];
};
Init[];
END.