GGBoxSliceImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on June 10, 1986 6:04:32 pm PDT
Contents: Implements the box slice class.
Bier, June 3, 1986 2:02:06 pm PDT
DIRECTORY
GGBasicTypes, GGBoxSlice, GGBoundBox, GGSlice, GGLines, GGInterfaceTypes, GGModelTypes, GGShapes, GGParseIn, GGParseOut, GGTransform, Imager, ImagerTransformation, IO, Rope;
GGBoxSliceImpl: CEDAR PROGRAM
IMPORTS GGBoundBox, GGSlice, GGLines, GGShapes, GGParseIn, GGParseOut, GGTransform, Imager, ImagerTransformation, IO EXPORTS GGBoxSlice = BEGIN
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = REF PointGeneratorObj;
PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj;
PointPairGenerator: TYPE = REF PointPairGeneratorObj;
PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
SliceParts: TYPE = GGModelTypes.SliceParts;
SliceObj: TYPE = GGModelTypes.SliceObj;
SliceClass: TYPE = GGModelTypes.SliceClass;
SliceClassObj: TYPE = GGModelTypes.SliceClassObj;
BoundBox: TYPE = GGModelTypes.BoundBox;
SelectMode: TYPE = GGModelTypes.SelectMode;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
Corner: TYPE = GGBoxSlice.Corner;
Edge: TYPE = GGBoxSlice.Edge;
BoxData: TYPE = REF BoxDataObj;
BoxDataObj: TYPE = RECORD [
box: BoundBox,
transform needed for general case of rotated boxes
transform: ImagerTransformation.Transformation,
strokeWidths: ARRAY [1..4] OF REAL -- in order left, top, right, bottom
];
BoxParts: TYPE = REF BoxPartsObj;
BoxPartsObj: TYPE = RECORD [
entire: BOOL, -- means entire box is selected (traj or topLevel)
corners: LIST OF Corner, -- which corners of box are selected.
edges: LIST OF Edge -- which edges of box are selected.
];
BoxHitData: TYPE = REF BoxHitDataObj;
BoxHitDataObj: TYPE = RECORD [
corner: Corner, -- which corner of box is hit. Filled in by BoxClosestPoint, BoxClosestSegment
edge: Edge -- which edge of box is hit. Filled in by BoxClosestPoint, BoxClosestSegment
];
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE;
BuildBoxSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = {
class ← NEW[SliceClassObj ← [
type: $Box,
boundBox: BoxBoundBox,
copy: BoxCopy,
draw: BoxDraw,
drawTransform: BoxDrawTransform,
drawSelectionFeedback: BoxDrawSelectionFeedback,
transform: BoxTransform,
emptyParts: BoxEmptyParts,
newParts: BoxNewParts,
addParts: BoxAddParts,
removeParts: BoxRemoveParts,
pointsInDescriptor: BoxPointsInDescriptor,
pointPairsInDescriptor: BoxPointPairsInDescriptor,
nextPoint: BoxNextPoint,
nextPointPair: BoxNextPointPair,
closestPoint: BoxClosestPoint,
closestPointAndTangent: NIL,
closestSegment: BoxClosestSegment,
fileout: BoxFileout,
filein: BoxFilein,
setStrokeWidth: BoxSetStrokeWidth,
setStrokeColor: BoxSetStrokeColor,
setFillColor: BoxSetFillColor
]];
};
BoxPointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = {
pointGen ← NEW[PointGeneratorObj];
pointGen.sliceD ← sliceD;
pointGen.toGo ← 4;
pointGen.index ← 0; -- not used
pointGen.classSpecific ← NIL;
};
BoxPointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = {
parts: BoxParts ← NARROW[sliceD.parts];
pointGen ← NEW[PointGeneratorObj ← [sliceD, 0, 0, NIL] ]; -- toGo and index now used
IF parts.entire THEN pointGen.toGo ← 4
ELSE FOR corner: LIST OF Corner ← parts.corners, corner.rest UNTIL corner = NIL DO
pointGen.toGo ← pointGen.toGo + 1;
ENDLOOP;
};
BoxPointPairsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = {
parts: BoxParts ← NARROW[sliceD.parts];
pointPairGen ← NEW[PointPairGeneratorObj ← [sliceD, 0, 0, NIL] ]; -- toGo and index now used
IF parts.entire THEN pointPairGen.toGo ← 4
ELSE FOR edge: LIST OF Edge ← parts.edges, edge.rest UNTIL edge = NIL DO
pointPairGen.toGo ← pointPairGen.toGo + 1;
ENDLOOP;
};
BoxNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
sliceD: SliceDescriptor ← pointGen.sliceD;
slice: Slice ← sliceD.slice;
boxData: BoxData ← NARROW[slice.data];
t: ImagerTransformation.Transformation ← boxData.transform;
IF pointGen.toGo = 0 THEN {
pointAndDone.done ← TRUE;
}
ELSE {
pointAndDone.done ← FALSE;
SELECT pointGen.toGo FROM
4 => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]];
3 => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]];
2 => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]];
1 => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]];
ENDCASE => SIGNAL Problem[msg: "Broken Invariant"];
pointGen.toGo ← pointGen.toGo - 1;
};
};
BoxNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
sliceD: SliceDescriptor ← pointGen.sliceD;
slice: Slice ← sliceD.slice;
boxData: BoxData ← NARROW[slice.data];
t: ImagerTransformation.Transformation ← boxData.transform;
IF pointGen.toGo = 0 THEN {
pointAndDone.done ← TRUE;
}
ELSE {
boxParts: BoxParts ← NARROW[sliceD.parts];
corner: LIST OF Corner ← boxParts.corners;
pointAndDone.done ← FALSE;
IF boxParts.entire THEN corner ← LIST[ll,ul,ur,lr];
THROUGH [0..pointGen.index) DO corner ← corner.rest; ENDLOOP;
SELECT corner.first FROM
ll => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]];
ul => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]];
ur => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]];
lr => pointAndDone.point ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]];
none => NULL;
ENDCASE => SIGNAL Problem[msg: "Broken Invariant"];
pointGen.toGo ← pointGen.toGo - 1;
pointGen.index ← pointGen.index + 1;
};
};
BoxNextPointPair: PROC [pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = {
sliceD: SliceDescriptor ← pointPairGen.sliceD;
slice: Slice ← sliceD.slice;
boxData: BoxData ← NARROW[slice.data];
t: ImagerTransformation.Transformation ← boxData.transform;
IF pointPairGen.toGo = 0 THEN {
pointPairAndDone.done ← TRUE;
}
ELSE {
boxParts: BoxParts ← NARROW[sliceD.parts];
edge: LIST OF Edge ← boxParts.edges;
pointPairAndDone.done ← FALSE;
IF boxParts.entire THEN edge ← LIST[left,top,right,bottom];
THROUGH [0..pointPairGen.index) DO edge ← edge.rest; ENDLOOP;
SELECT edge.first FROM
left => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]];
};
top => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]];
};
right => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]];
};
bottom => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]];
};
none => NULL;
ENDCASE => SIGNAL Problem[msg: "Broken Invariant"];
pointPairGen.toGo ← pointPairGen.toGo - 1;
pointPairGen.index ← pointPairGen.index + 1;
};
};
BoxDraw: PROC [slice: Slice, dc: Imager.Context, camera: GGInterfaceTypes.CameraData] = {
GGModelTypes.SliceDrawProc
boxData: BoxData ← NARROW[slice.data];
box: BoundBox ← boxData.box;
DoDrawBox: PROC = {
Imager.ConcatT[dc, boxData.transform];
Imager.SetColor[dc, Imager.black];
Imager.SetStrokeWidth[dc, boxData.strokeWidths[1]];
Imager.MaskVector[dc, [box.loX, box.loY], [box.loX, box.hiY]];
Imager.SetStrokeWidth[dc, boxData.strokeWidths[2]];
Imager.MaskVector[dc, [box.loX, box.hiY], [box.hiX, box.hiY]];
Imager.SetStrokeWidth[dc, boxData.strokeWidths[3]];
Imager.MaskVector[dc, [box.hiX, box.hiY], [box.hiX, box.loY]];
Imager.SetStrokeWidth[dc, boxData.strokeWidths[4]];
Imager.MaskVector[dc, [box.hiX, box.loY], [box.loX, box.loY]];
};
Imager.DoSaveAll[dc, DoDrawBox];
};
BoxSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = {};
BoxSetFillColor: PROC [slice: Slice, color: Imager.Color] = {};
BoxSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = {
boxParts: BoxParts ← NARROW[parts];
boxData: BoxData ← NARROW[slice.data];
IF boxParts.entire THEN {
FOR i: NAT IN [1..4] DO boxData.strokeWidths[i] ← strokeWidth; ENDLOOP;
}
ELSE {
FOR part: LIST OF Edge ← boxParts.edges, part.rest UNTIL part=NIL DO
SELECT part.first FROM
left => boxData.strokeWidths[1] ← strokeWidth;
top => boxData.strokeWidths[2] ← strokeWidth;
right => boxData.strokeWidths[3] ← strokeWidth;
bottom => boxData.strokeWidths[4] ← strokeWidth;
none => NULL;
ENDCASE => ERROR;
ENDLOOP;
};
};
MakeBoxSlice: PUBLIC PROC [box: BoundBox, corner: Corner, transform: ImagerTransformation.Transformation ← GGTransform.Identity[], strokeWidth: REAL ← 1.0] RETURNS [sliceD: SliceDescriptor] = {
requires a bound box input with loX<=hiX AND loY<=hiY
boxSlice: Slice ← NIL;
boxData: BoxData ← NEW[BoxDataObj ← [box: box, transform: transform, strokeWidths: [strokeWidth, strokeWidth, strokeWidth, strokeWidth]] ];
boxParts: BoxParts ← NEW[BoxPartsObj ← [entire: FALSE, corners: LIST[corner], edges: NIL] ];
IF box.loX > box.hiX OR box.loY > box.hiY THEN ERROR;
boxSlice ← NEW[SliceObj ← [
class: GGSlice.FetchSliceClass[$Box],
data: boxData,
children: NIL,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE, FALSE],
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0],
hitData: NEW[BoxHitDataObj ← [corner: corner, edge: none ] ]
]];
BoxBoundBox[boxSlice];
sliceD ← NEW[SliceDescriptorObj ← [boxSlice, boxParts] ];
};
GetBox: PUBLIC PROC [slice: Slice] RETURNS [box: BoundBox] = {
SHOULD THIS RETURN A COPY OF THE BOX ??
RETURN[IF slice.class.type#$Box THEN NIL ELSE NARROW[slice.data, BoxData].box];
};
Class Procedures
BoxBoundBox: PROC [slice: Slice] = {
GGModelTypes.SliceBoundBoxProc
halfJoint: REAL = GGModelTypes.halfJointSize + 1;
boxData: BoxData ← NARROW[slice.data];
box: BoundBox ← GGBoundBox.BoundBoxOfBoundBox[boxData.box, boxData.transform];
GGBoundBox.UpdateBoundBox[slice.boundBox, box.loX-halfJoint, box.loY-halfJoint, box.hiX+halfJoint, box.hiY+halfJoint]
};
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, none, transform].slice;
copyData ← NARROW[copy.data];
FOR i: NAT IN [1..4] DO copyData.strokeWidths[i] ← boxData.strokeWidths[i] ENDLOOP;
};
BoxDrawSelected: PRIVATE PROC [slice: Slice, dc: Imager.Context, camera: GGInterfaceTypes.CameraData] = {
boxData: BoxData ← NARROW[slice.data];
box: BoundBox ← boxData.box;
DoDrawBox: PROC = {
Imager.ConcatT[dc, boxData.transform];
Imager.SetColor[dc, Imager.black];
Imager.SetStrokeWidth[dc, 2*boxData.strokeWidths[1]];
Imager.MaskVector[dc, [box.loX, box.loY], [box.loX, box.hiY]];
Imager.SetStrokeWidth[dc, 2*boxData.strokeWidths[2]];
Imager.MaskVector[dc, [box.loX, box.hiY], [box.hiX, box.hiY]];
Imager.SetStrokeWidth[dc, 2*boxData.strokeWidths[3]];
Imager.MaskVector[dc, [box.hiX, box.hiY], [box.hiX, box.loY]];
Imager.SetStrokeWidth[dc, 2*boxData.strokeWidths[4]];
Imager.MaskVector[dc, [box.hiX, box.loY], [box.loX, box.loY]];
};
Imager.DoSaveAll[dc, DoDrawBox];
};
BoxDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
This is what makes boxes behave specially. Depending on which parts are selected, the points are transformed and the box is rubberbanded properly. The box data itself is not modified.
point, oppositePoint: ImagerTransformation.VEC; -- really points, not vectors
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
inverse, totalTransform: ImagerTransformation.Transformation;
DoDrawBox: PROC = {
Imager.ConcatT[dc, ImagerTransformation.Concat[boxData.transform, transform]];
GGShapes.DrawRectangle[dc, box.hiX, box.hiY, box.loX, box.loY];
};
DoDrawRectangle: PROC = {
Imager.ConcatT[dc, boxData.transform];
GGShapes.DrawRectangle[dc, oppositePoint.x, oppositePoint.y, point.x, point.y];
};
IF box.null OR box.infinite THEN ERROR;
IF boxParts.entire THEN {
Imager.DoSaveAll[dc, DoDrawBox];
RETURN;
};
IF boxParts.corners=NIL AND boxParts.edges=NIL THEN RETURN; -- no parts. Legal.
IF (boxParts.corners#NIL AND boxParts.corners.rest#NIL) OR (boxParts.edges#NIL AND boxParts.edges.rest#NIL) THEN { -- more than one corner or more than one edge. Full transform.
Imager.DoSaveAll[dc, DoDrawBox];
RETURN;
};
IF boxParts.corners#NIL AND boxParts.edges#NIL THEN ERROR; -- can't mix corners and edges while dragging. Doesn't make sense; must be a selection error.
IF boxParts.corners#NIL THEN { -- one corner. Transform it.
SELECT boxParts.corners.first FROM
ll => { point ← [ box.loX, box.loY ]; oppositePoint ← [ box.hiX, box.hiY ]; };
ul => { point ← [ box.loX, box.hiY ]; oppositePoint ← [ box.hiX, box.loY ]; };
lr => { point ← [ box.hiX, box.loY ]; oppositePoint ← [ box.loX, box.hiY ]; };
ur => { point ← [ box.hiX, box.hiY ]; oppositePoint ← [ box.loX, box.loY ]; };
ENDCASE => ERROR;
inverse ← ImagerTransformation.Invert[m: boxData.transform];
totalTransform ← ImagerTransformation.Cat[boxData.transform, transform, inverse];
point ← ImagerTransformation.Transform[m: totalTransform, v: point]; -- transform the dragging point
}
ELSE IF boxParts.edges#NIL THEN { -- one edge. Transform it.
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 boxParts.edges.first FROM
left => {
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
};
right => {
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
};
bottom => {
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
};
top => {
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
};
ENDCASE => ERROR;
}
ELSE ERROR;
Imager.DoSaveAll[dc, DoDrawRectangle];
};
BoxDrawSelectionFeedback: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
if not quick, draws a diagonal line thru the selected box, then highlites selected joints/edges
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
DoDrawFeedback: PROC = {
Imager.ConcatT[dc, boxData.transform];
IF NOT quick AND slice.selectedInFull.normal THEN { -- draw diagonal thru box to mark it
line: GGLines.Line ← GGLines.LineFromPoints[ [box.loX, box.loY], [box.hiX, box.hiY] ];
GGShapes.DrawLine[dc: dc, line: line, clippedBy: [box.loX, box.loY, box.hiX-box.loX, box.hiY-box.loY], strokeWidth: 2.0 ]; -- uses DoSaveAll
};
IF boxParts.entire THEN {
BoxDrawSelected[slice, dc, camera]; -- uses DoSaveAll
RETURN;
};
FOR corner: LIST OF Corner ← boxParts.corners, corner.rest UNTIL corner=NIL DO
SELECT corner.first FROM
ll => GGShapes.DrawSelectedJoint[dc, [box.loX, box.loY] ];
ul => GGShapes.DrawSelectedJoint[dc, [box.loX, box.hiY] ];
lr => GGShapes.DrawSelectedJoint[dc, [box.hiX, box.loY] ];
ur => GGShapes.DrawSelectedJoint[dc, [box.hiX, box.hiY] ];
ENDCASE => ERROR;
ENDLOOP;
FOR edge: LIST OF Edge ← boxParts.edges, edge.rest UNTIL edge=NIL DO
SELECT edge.first FROM
left => GGShapes.DrawLine[dc: dc, line: GGLines.LineFromPoints[ [box.loX, box.loY], [box.loX, box.hiY] ], clippedBy: [box.loX, box.loY, box.hiX-box.loX, box.hiY-box.loY], strokeWidth: 2.0*boxData.strokeWidths[1] ];
top => GGShapes.DrawLine[dc: dc, line: GGLines.LineFromPoints[ [box.loX, box.hiY], [box.hiX, box.hiY] ], clippedBy: [box.loX, box.loY, box.hiX-box.loX, box.hiY-box.loY], strokeWidth: 2.0*boxData.strokeWidths[2] ];
right => GGShapes.DrawLine[dc: dc, line: GGLines.LineFromPoints[ [box.hiX, box.loY], [box.hiX, box.hiY] ], clippedBy: [box.loX, box.loY, box.hiX-box.loX, box.hiY-box.loY], strokeWidth: 2.0*boxData.strokeWidths[3] ];
bottom => GGShapes.DrawLine[dc: dc, line: GGLines.LineFromPoints[ [box.loX, box.loY], [box.hiX, box.loY] ], clippedBy: [box.loX, box.loY, box.hiX-box.loX, box.hiY-box.loY], strokeWidth: 2.0*boxData.strokeWidths[4] ];
ENDCASE => ERROR;
ENDLOOP;
};
Imager.DoSaveAll[dc, DoDrawFeedback];
};
BoxTransform: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceTransformProc
Permanently transforms the box. Depending on which parts are selected, the points are transformed and the box is grown/shrunk properly.
point, oppositePoint: ImagerTransformation.VEC; -- really points, not vectors
boxData: BoxData ← NARROW[slice.data];
boxParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
inverse, totalTransform: ImagerTransformation.Transformation;
IF box.null OR box.infinite THEN ERROR;
IF boxParts.entire THEN {
boxData.transform ← ImagerTransformation.Concat[boxData.transform, transform];
BoxBoundBox[slice];
RETURN;
};
IF boxParts.corners=NIL AND boxParts.edges=NIL THEN RETURN; -- no parts. Legal.
IF (boxParts.corners#NIL AND boxParts.corners.rest#NIL) OR (boxParts.edges#NIL AND boxParts.edges.rest#NIL) THEN { -- more than one corner or more than one edge. Full transform.
boxData.transform ← ImagerTransformation.Concat[boxData.transform, transform];
BoxBoundBox[slice];
RETURN;
};
IF boxParts.corners#NIL AND boxParts.edges#NIL THEN ERROR; -- can't mix corners and edges while dragging. Doesn't make sense; must be a selection error.
IF boxParts.corners#NIL THEN { -- one corner. Transform it.
SELECT boxParts.corners.first FROM
ll => { point ← [ box.loX, box.loY ]; oppositePoint ← [ box.hiX, box.hiY ]; };
ul => { point ← [ box.loX, box.hiY ]; oppositePoint ← [ box.hiX, box.loY ]; };
lr => { point ← [ box.hiX, box.loY ]; oppositePoint ← [ box.loX, box.hiY ]; };
ur => { point ← [ box.hiX, box.hiY ]; oppositePoint ← [ box.loX, box.loY ]; };
ENDCASE => ERROR;
inverse ← ImagerTransformation.Invert[m: boxData.transform];
totalTransform ← ImagerTransformation.Cat[boxData.transform, transform, inverse];
point ← ImagerTransformation.Transform[m: totalTransform, v: point]; -- transform the dragging point
}
ELSE IF boxParts.edges#NIL 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 boxParts.edges.first FROM
left => {
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
};
right => {
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
};
bottom => {
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
};
top => {
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
};
ENDCASE => ERROR;
}
ELSE ERROR;
requires a bound box result with loX<=hiX AND loY<=hiY
box^ ← [MIN[point.x, oppositePoint.x], MIN[point.y, oppositePoint.y], MAX[point.x, oppositePoint.x], MAX[point.y, oppositePoint.y], FALSE, FALSE];
BoxBoundBox[slice];
};
BoxEmptyParts: PROC [slice: Slice, parts: SliceParts] RETURNS [BOOL] = {
GGModelTypes.SliceEmptyPartsProc
boxParts: BoxParts ← NARROW[parts];
RETURN[boxParts.entire=FALSE AND boxParts.corners=NIL AND boxParts.edges=NIL];
};
BoxNewParts: PROC [slice: Slice, mode: SelectMode] RETURNS [parts: SliceParts] = {
GGModelTypes.SliceNewPartsProc
boxHitData: BoxHitData ← NARROW[slice.hitData];
boxParts: BoxParts ← NEW[BoxPartsObj ← [entire: FALSE, corners: NIL, edges: NIL] ];
SELECT mode FROM
joint => boxParts.corners ← LIST[boxHitData.corner];
segment => boxParts.edges ← LIST[boxHitData.edge];
traj, slice, topLevel => boxParts.entire ← TRUE;
ENDCASE => ERROR;
parts ← boxParts; -- RETURN[boxParts]
};
BoxAddParts: PROC [slice: Slice, parts: SliceParts, mode: ExtendMode] RETURNS [newParts: SliceParts] = {
GGModelTypes.SliceAddPartsProc
boxHitData: BoxHitData ← NARROW[slice.hitData];
boxParts: BoxParts ← NARROW[parts];
newBoxParts: BoxParts ← NEW[BoxPartsObj ← [entire: FALSE, corners: boxParts.corners, edges: boxParts.edges] ];
SELECT mode FROM
joint => newBoxParts.corners ← CONS[boxHitData.corner, boxParts.corners];
segment => newBoxParts.edges ← CONS[boxHitData.edge, boxParts.edges];
traj, topLevel => newBoxParts.entire ← TRUE;
ENDCASE => ERROR;
newParts ← newBoxParts; -- RETURN[newBoxParts]
};
BoxRemoveParts: PROC [slice: Slice, parts: SliceParts, mode: ExtendMode] RETURNS [newParts: SliceParts] = {
GGModelTypes.SliceRemovePartsProc
CornerRemove: PROC [corner: Corner, list: LIST OF Corner] RETURNS [newList: LIST OF Corner ← NIL] = {
FOR nextCorner: LIST OF Corner ← list, nextCorner.rest UNTIL nextCorner = NIL DO
IF nextCorner.first#corner THEN newList ← CONS[nextCorner.first, newList];
ENDLOOP;
};
EdgeRemove: PROC [edge: Edge, list: LIST OF Edge] RETURNS [newList: LIST OF Edge ← NIL] = {
FOR nextEdge: LIST OF Edge ← list, nextEdge.rest UNTIL nextEdge = NIL DO
IF nextEdge.first#edge THEN newList ← CONS[nextEdge.first, newList];
ENDLOOP;
};
boxHitData: BoxHitData ← NARROW[slice.hitData];
boxParts: BoxParts ← NARROW[parts];
newBoxParts: BoxParts ← NEW[BoxPartsObj ← [entire: boxParts.entire, corners: boxParts.corners, edges: boxParts.edges] ];
SELECT mode FROM
joint => newBoxParts.corners ← CornerRemove[boxHitData.corner, boxParts.corners];
segment => newBoxParts.edges ← EdgeRemove[boxHitData.edge, boxParts.edges];
traj, topLevel => newBoxParts.entire ← FALSE;
ENDCASE => ERROR;
newParts ← newBoxParts; -- RETURN[newBoxParts]
};
BoxClosestPoint: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = {
GGModelTypes.SliceClosestPointProc
index: NAT ← 99;
scale: REAL;
boxData: BoxData ← NARROW[slice.data];
boxHitData: BoxHitData ← NARROW[slice.hitData];
inverse: ImagerTransformation.Transformation;
inverse ← ImagerTransformation.Invert[boxData.transform];
testPoint ← GGTransform.Transform[inverse, testPoint];
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestPoint[boxData.box, testPoint];
bestPoint ← GGTransform.Transform[boxData.transform, bestPoint];
scale ← ImagerTransformation.Factor[boxData.transform].s.x;
bestDist ← bestDist*(1/scale);
boxHitData.corner ← IF success THEN SELECT index FROM
0 => Corner.ll, 1 => Corner.ul, 2 => Corner.ur, 3 => Corner.lr, ENDCASE => ERROR
ELSE Corner.none;
};
BoxClosestSegment: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = {
GGModelTypes.SliceClosestSegmentProc
seg: NAT ← 99;
scale: REAL;
boxData: BoxData ← NARROW[slice.data];
boxHitData: BoxHitData ← NARROW[slice.hitData];
inverse: ImagerTransformation.Transformation;
inverse ← ImagerTransformation.Invert[boxData.transform];
testPoint ← GGTransform.Transform[inverse, testPoint];
[bestDist, seg, bestPoint, success] ← GGBoundBox.NearestSegment[boxData.box, testPoint, tolerance];
bestPoint ← GGTransform.Transform[boxData.transform, bestPoint];
scale ← ImagerTransformation.Factor[boxData.transform].s.x;
bestDist ← bestDist*(1/scale);
boxHitData.edge ← IF success THEN SELECT seg FROM
0 => Edge.left, 1 => Edge.top, 2 => Edge.right, 3 => Edge.bottom, ENDCASE => ERROR
ELSE Edge.none;
};
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 ];
};
BoxFilein: PROC [f: IO.STREAM, version: REAL] RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
box: BoundBox;
p1, p2: Point;
transform: ImagerTransformation.Transformation;
GGParseIn.ReadBlank[f];
p1 ← GGParseIn.ReadPoint[f];
GGParseIn.ReadBlank[f];
p2 ← GGParseIn.ReadPoint[f];
GGParseIn.ReadBlank[f];
transform ← GGParseIn.ReadTransformation[f];
box ← GGBoundBox.CreateBoundBox[ p1.x, p1.y, p2.x, p2.y ];
slice ← MakeBoxSlice[box, none, transform].slice;
};
END.