GGBoxClusterImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on May 22, 1986 6:10:09 pm PDT
Contents: Implements the box cluster class.
Bier, May 1, 1986 2:32:28 pm PDT
DIRECTORY
GGBasicTypes, GGBoxCluster, GGBoundBox, GGCluster, GGLines, GGInterfaceTypes, GGModelTypes, GGShapes, GGParseIn, GGParseOut, GGTransform, Imager, ImagerTransformation, IO;
GGBoxClusterImpl: CEDAR PROGRAM
IMPORTS GGBoundBox, GGCluster, GGLines, GGShapes, GGParseIn, GGParseOut, GGTransform, Imager, ImagerTransformation, IO EXPORTS GGBoxCluster = BEGIN
Point: TYPE = GGBasicTypes.Point;
Cluster: TYPE = GGModelTypes.Cluster;
ClusterDescriptor: TYPE = GGModelTypes.ClusterDescriptor;
ClusterDescriptorObj: TYPE = GGModelTypes.ClusterDescriptorObj;
ClusterParts: TYPE = GGModelTypes.ClusterParts;
ClusterObj: TYPE = GGModelTypes.ClusterObj;
ClusterClass: TYPE = GGModelTypes.ClusterClass;
ClusterClassObj: TYPE = GGModelTypes.ClusterClassObj;
BoundBox: TYPE = GGModelTypes.BoundBox;
SelectMode: TYPE = GGModelTypes.SelectMode;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
Corner: TYPE = GGBoxCluster.Corner;
Edge: TYPE = GGBoxCluster.Edge;
BoxData: TYPE = REF BoxDataObj;
BoxDataObj: TYPE = RECORD [
N.B.: box has no "parts" data of its own. Make this work!
box: BoundBox,
transform needed for general case of non-rectilinear boxes
transform: ImagerTransformation.Transformation
];
BoxParts: TYPE = REF BoxPartsObj;
BoxPartsObj: TYPE = RECORD [
entire: BOOL, -- means entire box is selected (traj or topLevel)
corner: Corner, -- which corner of box is selected.
edge: Edge -- which edge of box is 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
];
BuildBoxClusterClass: PUBLIC PROC [] RETURNS [class: ClusterClass] = {
class ← NEW[ClusterClassObj ← [
type: $Box,
boundBox: BoxBoundBox,
copy: BoxCopy,
draw: BoxDraw,
drawTransform: BoxDrawTransform,
drawSelectionFeedback: BoxDrawSelectionFeedback,
transform: BoxTransform,
emptyParts: BoxEmptyParts,
newParts: BoxNewParts,
addParts: BoxAddParts,
removeParts: BoxRemoveParts,
closestPoint: BoxClosestPoint,
closestPointAndTangent: NIL,
closestSegment: BoxClosestSegment,
fileout: BoxFileout,
filein: BoxFilein
]];
};
MakeBoxCluster: PUBLIC PROC [box: BoundBox, corner: Corner, transform: ImagerTransformation.Transformation ← GGTransform.Identity[]] RETURNS [clusD: ClusterDescriptor] = {
requires a bound box input with loX<=hiX AND loY<=hiY
boxCluster: Cluster ← NIL;
boxData: BoxData ← NEW[BoxDataObj ← [box: box, transform: transform] ];
boxParts: BoxParts ← NEW[BoxPartsObj ← [entire: FALSE, corner: corner, edge: none] ];
IF box.loX > box.hiX OR box.loY > box.hiY THEN ERROR;
boxCluster ← NEW[ClusterObj ← [
class: GGCluster.FetchClusterClass[$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[boxCluster];
clusD ← NEW[ClusterDescriptorObj ← [boxCluster, boxParts] ];
};
GetBox: PUBLIC PROC [cluster: Cluster] RETURNS [box: BoundBox] = {
SHOULD THIS RETURN A COPY OF THE BOX ??
RETURN[IF cluster.class.type#$Box THEN NIL ELSE NARROW[cluster.data, BoxData].box];
};
SetBox: PUBLIC PROC [cluster: Cluster, box: BoundBox] = {
boxData: BoxData ← NARROW[cluster.data];
boxHitData: BoxHitData ← NARROW[cluster.hitData];
IF cluster.class.type#$Box THEN RETURN;
boxData.bBox ← box;
boxHitData.corner ← none;
boxHitData.edge ← none;
WHAT TO DO ABOUT TRANSFORMATION ??
};
Class Procedures
BoxBoundBox: PROC [cluster: Cluster] = {
GGModelTypes.ClusterBoundBoxProc
halfJoint: REAL = GGModelTypes.halfJointSize + 1;
boxData: BoxData ← NARROW[cluster.data];
box: BoundBox ← GGBoundBox.BoundBoxOfBoundBox[boxData.box, boxData.transform];
GGBoundBox.UpdateBoundBox[cluster.boundBox, box.loX-halfJoint, box.loY-halfJoint, box.hiX+halfJoint, box.hiY+halfJoint]
};
BoxCopy: PROC [cluster: Cluster] RETURNS [copy: Cluster] = {
GGModelTypes.ClusterCopyProc
boxData: BoxData ← NARROW[cluster.data];
box: BoundBox ← GGBoundBox.CopyBoundBox[boxData.box];
transform: Imager.Transformation ← ImagerTransformation.Copy[boxData.transform];
copy ← MakeBoxCluster[box, none, transform].cluster;
};
BoxDraw: PROC [cluster: Cluster, dc: Imager.Context, camera: GGInterfaceTypes.CameraData] = {
GGModelTypes.ClusterDrawProc
boxData: BoxData ← NARROW[cluster.data];
DoDrawBox: PROC = {
Imager.ConcatT[dc, boxData.transform];
GGBoundBox.DrawBoundBox[dc: dc, bBox: boxData.box];
};
Imager.DoSaveAll[dc, DoDrawBox];
};
BoxDrawTransform: PROC [cluster: Cluster, parts: ClusterParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.ClusterDrawTransformProc
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[cluster.data];
boxParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
inverse, totalTransform: ImagerTransformation.Transformation;
DoDrawBox: PROC = {
point ← [ box.loX, box.loY ];
oppositePoint ← [ box.hiX, box.hiY ];
Imager.ConcatT[dc, ImagerTransformation.Concat[boxData.transform, transform]];
GGShapes.DrawRectangle[dc, oppositePoint.x, oppositePoint.y, point.x, point.y];
};
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.corner=none AND boxParts.edge=none THEN RETURN; -- no parts. Legal.
IF boxParts.corner#none AND boxParts.edge#none THEN ERROR; -- both parts. Illegal
IF boxParts.corner#none THEN {
SELECT boxParts.corner 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.edge#none THEN {
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.edge 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 [cluster: Cluster, parts: ClusterParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = {
GGModelTypes.ClusterDrawSelectionFeedbackProc
if not quick, draws a diagonal line thru the selected box, then highlites selected joints/edges
boxData: BoxData ← NARROW[cluster.data];
boxParts: BoxParts ← NARROW[parts];
box: BoundBox ← boxData.box;
DoDrawFeedback: PROC = {
Imager.ConcatT[dc, boxData.transform];
IF NOT quick AND cluster.selectedInFull.normal THEN {
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 {
GGShapes.DrawRectangle[dc, box.loX, box.loY, box.hiX, box.hiY, 2.0];
RETURN;
};
SELECT boxParts.corner 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 => {}; -- maybe none
SELECT boxParts.edge 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 ];
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 ];
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 ];
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 ];
ENDCASE => {}; -- maybe none
};
Imager.DoSaveAll[dc, DoDrawFeedback];
};
BoxTransform: PROC [cluster: Cluster, parts: ClusterParts, transform: ImagerTransformation.Transformation] = {
GGModelTypes.ClusterTransformProc
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[cluster.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[cluster];
RETURN;
};
IF boxParts.corner=none AND boxParts.edge=none THEN RETURN; -- no parts. Legal.
IF boxParts.corner#none AND boxParts.edge#none THEN ERROR; -- both parts. Illegal
IF boxParts.corner#none THEN { -- transform a corner
SELECT boxParts.corner 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.edge#none 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.edge 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[cluster];
};
BoxEmptyParts: PROC [cluster: Cluster, parts: ClusterParts] RETURNS [BOOL] = {
GGModelTypes.ClusterEmptyPartsProc
boxParts: BoxParts ← NARROW[parts];
RETURN[boxParts.entire=FALSE AND boxParts.corner=none AND boxParts.edge=none];
};
BoxNewParts: PROC [cluster: Cluster, mode: SelectMode] RETURNS [parts: ClusterParts] = {
GGModelTypes.ClusterNewPartsProc
boxHitData: BoxHitData ← NARROW[cluster.hitData];
boxParts: BoxParts ← NEW[BoxPartsObj ← [entire: FALSE, corner: none, edge: none] ];
SELECT mode FROM
joint => boxParts.corner ← boxHitData.corner;
segment => boxParts.edge ← boxHitData.edge;
traj, topLevel => boxParts.entire ← TRUE;
ENDCASE => ERROR;
parts ← boxParts; -- RETURN[boxParts]
};
BoxAddParts: PROC [cluster: Cluster, parts: ClusterParts, mode: ExtendMode] RETURNS [newParts: ClusterParts] = {
GGModelTypes.ClusterAddPartsProc
boxHitData: BoxHitData ← NARROW[cluster.hitData];
boxParts: BoxParts ← NARROW[parts];
newBoxParts: BoxParts ← NEW[BoxPartsObj ← [entire: FALSE, corner: none, edge: none] ];
SELECT mode FROM
joint => newBoxParts.corner ← boxParts.corner; -- can't extend at joint level
segment => newBoxParts.edge ← boxParts.edge; -- can't extend at segment level
traj, topLevel => newBoxParts.entire ← TRUE;
ENDCASE => ERROR;
newParts ← newBoxParts; -- RETURN[newBoxParts]
};
BoxRemoveParts: PROC [cluster: Cluster, parts: ClusterParts, mode: ExtendMode] RETURNS [newParts: ClusterParts] = {
GGModelTypes.ClusterRemovePartsProc
boxHitData: BoxHitData ← NARROW[cluster.hitData];
boxParts: BoxParts ← NARROW[parts];
newBoxParts: BoxParts ← NEW[BoxPartsObj ← [entire: boxParts.entire, corner: boxParts.corner, edge: boxParts.edge] ];
SELECT mode FROM
joint => newBoxParts.corner ← IF boxHitData.corner=boxParts.corner THEN none ELSE boxParts.corner;
segment => newBoxParts.edge ← IF boxHitData.edge=boxParts.edge THEN none ELSE boxParts.edge;
traj, topLevel => newBoxParts.entire ← FALSE;
ENDCASE => ERROR;
newParts ← newBoxParts; -- RETURN[newBoxParts]
};
BoxClosestPoint: PROC [cluster: Cluster, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = {
GGModelTypes.ClusterClosestPointProc
index: NAT ← 99;
scale: REAL;
boxData: BoxData ← NARROW[cluster.data];
boxHitData: BoxHitData ← NARROW[cluster.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 [cluster: Cluster, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = {
GGModelTypes.ClusterClosestSegmentProc
seg: NAT ← 99;
scale: REAL;
boxData: BoxData ← NARROW[cluster.data];
boxHitData: BoxHitData ← NARROW[cluster.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 [cluster: Cluster, f: IO.STREAM] = {
GGModelTypes.ClusterFileoutProc
Write a description of yourself onto stream f.
boxData: BoxData ← NARROW[cluster.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 [cluster: Cluster] = {
GGModelTypes.ClusterFileinProc
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[1], p1[2], p2[1], p2[2] ];
cluster ← MakeBoxCluster[box, none, transform].cluster;
};
END.