<> <> <> <> <> <<>> 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 [ <> box: BoundBox, <> 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] = { <> 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] = { <> RETURN[IF cluster.class.type#$Box THEN NIL ELSE NARROW[cluster.data, BoxData].box]; }; <> <> <> <> <> <> <> <> <<};>> <<>> <<>> <> BoxBoundBox: PROC [cluster: Cluster] = { <> 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] = { <> 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] = { <> 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] = { <> <> 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] = { <> <> 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] = { <> <> 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; <> 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] = { <> 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] = { <> 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] = { <> 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] = { <> 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] = { <> 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] = { <> 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] = { <> <> 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] = { <> 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.