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];
};
Box
EmptyParts:
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];
};
Box
NewParts:
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]
};
Box
AddParts:
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]
};
Box
RemoveParts:
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.