DIRECTORY Atom, GGBasicTypes, GGBoundBox, GGCircles, GGError, GGLines, GGSlice, GGInterfaceTypes, GGModelTypes, GGObjects, GGShapes, GGParseOut, GGParseIn, GGTransform, GGUtility, GGVector, Imager, ImagerTransformation, ImagerColor, ImagerPath, IO, NodeStyle, RealFns, Rope, ViewerClasses; GGSliceImplA: CEDAR PROGRAM IMPORTS Atom, GGBoundBox, GGCircles, GGError, GGLines, GGObjects, GGSlice, GGParseIn, GGParseOut, GGShapes, GGTransform, GGVector, Imager, ImagerPath, IO, RealFns, ImagerTransformation, Rope EXPORTS GGSlice = BEGIN Scene: TYPE = GGModelTypes.Scene; BoundBox: TYPE = GGModelTypes.BoundBox; CameraData: TYPE = GGInterfaceTypes.CameraData; Circle: TYPE = GGBasicTypes.Circle; Color: TYPE = ImagerColor.Color; Corner: TYPE = GGSlice.Corner; DisplayStyle: TYPE = GGSlice.DisplayStyle; Edge: TYPE = GGBasicTypes.Edge; ExtendMode: TYPE = GGModelTypes.ExtendMode; Line: TYPE = GGBasicTypes.Line; Point: TYPE = GGBasicTypes.Point; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj; SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData; SelectionClass: TYPE = GGInterfaceTypes.SelectionClass; SelectMode: TYPE = GGModelTypes.SelectMode; 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; Viewer: TYPE = ViewerClasses.Viewer; Vector: TYPE = GGBasicTypes.Vector; SliceClassDef: TYPE = REF SliceClassDefObj; SliceClassDefObj: TYPE = RECORD[type: ATOM, class: SliceClass]; BoxData: TYPE = REF BoxDataObj; BoxDataObj: TYPE = RECORD [ box: BoundBox, -- this is not used as a bounding box. It is a representation of the shape. tightBox: BoundBox, -- a Manhattan aligned box, that contains this box. tightBox does NOT allow for stroke width or control points. transform: ImagerTransformation.Transformation, inverse: ImagerTransformation.Transformation, -- inverse of transform inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s strokeWidths: ARRAY [0..4) OF REAL, -- in order left, top, right, bottom strokeColors: ARRAY [0..4) OF Imager.Color, -- in order left, top, right, bottom fillColor: Imager.Color ]; 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. 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 ]; BuildBoxSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { OPEN GGSlice; class _ NEW[SliceClassObj _ [ type: $Box, getBoundBox: BoxGetBoundBox, getTightBox: BoxGetTightBox, copy: BoxCopy, drawParts: BoxDrawParts, drawTransform: BoxDrawTransform, drawSelectionFeedback: BoxDrawSelectionFeedback, drawAttractorFeedback: NoOpDrawAttractorFeedback, transform: BoxTransform, describe: BoxDescribe, fileout: BoxFileout, filein: BoxFilein, emptyParts: BoxEmptyParts, newParts: BoxNewParts, unionParts: BoxUnionParts, differenceParts: BoxDiffParts, movingParts: BoxMovingParts, fixedParts: BoxFixedParts, augmentParts: BoxAugmentParts, pointsInDescriptor: BoxPointsInDescriptor, pointPairsInDescriptor: BoxPointPairsInDescriptor, nextPoint: BoxNextPoint, nextPointPair: BoxNextPointPair, closestPoint: BoxClosestPoint, closestPointAndTangent: NoOpClosestPointAndTangent, closestSegment: BoxClosestSegment, lineIntersection: BoxLineIntersection, circleIntersection: NoOpCircleIntersection, hitDataAsSimpleCurve: BoxHitDataAsSimpleCurve, setStrokeWidth: BoxSetStrokeWidth, getStrokeWidth: BoxGetStrokeWidth, setStrokeColor: BoxSetStrokeColor, getStrokeColor: BoxGetStrokeColor, setFillColor: BoxSetFillColor, getFillColor: BoxGetFillColor ]]; }; MakeBoxSlice: PUBLIC PROC [box: BoundBox, corner: Corner, transform: ImagerTransformation.Transformation _ GGTransform.Identity[], strokeWidth: REAL _ 2.0] RETURNS [sliceD: SliceDescriptor] = { boxSlice: Slice; inverse: ImagerTransformation.Transformation _ ImagerTransformation.Invert[transform]; boxData: BoxData _ NEW[BoxDataObj _ [box: box, tightBox: GGBoundBox.NullBoundBox[], transform: transform, inverse: inverse, inverseScale: ImagerTransformation.Factor[inverse].s, strokeWidths: [strokeWidth, strokeWidth, strokeWidth, strokeWidth], strokeColors: [Imager.black, Imager.black, Imager.black, Imager.black], fillColor: NIL] ]; boxParts: BoxParts _ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE] ]; cIndex: INTEGER _ SELECT 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}; boxSlice _ NEW[SliceObj _ [ class: FetchSliceClass[$Box], data: boxData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], boundBox: GGBoundBox.NullBoundBox[] ]]; BoxSetBoundBox[boxSlice]; sliceD _ NEW[SliceDescriptorObj _ [boxSlice, boxParts] ]; }; BoxMaxStrokeWidth: PROC [boxData: BoxData] RETURNS [maxWidth: REAL] = { maxWidth _ boxData.strokeWidths[0]; FOR i: NAT IN [1..3] DO IF boxData.strokeWidths[i] > maxWidth THEN maxWidth _ boxData.strokeWidths[i]; ENDLOOP; }; BoxSetBoundBox: PROC [slice: Slice] = { cpHalf: REAL _ GGModelTypes.halfJointSize + 1; strokeWidth: REAL _ BoxMaxStrokeWidth[NARROW[slice.data]]; pad: REAL _ MAX[cpHalf, strokeWidth]; boxData: BoxData _ NARROW[slice.data]; GGBoundBox.UpdateBoundBoxOfBoundBox[boxData.tightBox, boxData.box, boxData.transform]; GGBoundBox.UpdateCopyBoundBox[bBox: slice.boundBox, from: boxData.tightBox]; GGBoundBox.EnlargeByOffset[slice.boundBox, pad]; }; BoxGetBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { RETURN[slice.boundBox]; }; BoxGetTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { boxData: BoxData _ NARROW[slice.data]; RETURN[boxData.box]; }; BoxCopy: PROC [slice: Slice] RETURNS [copy: Slice] = { copyData: BoxData; boxData: BoxData _ NARROW[slice.data]; box: BoundBox _ GGBoundBox.CopyBoundBox[boxData.box]; transform: Imager.Transformation _ ImagerTransformation.Copy[boxData.transform]; copy _ MakeBoxSlice[box, ur, transform].slice; -- does ur work here ?? copyData _ NARROW[copy.data]; FOR i: NAT IN [0..4) DO copyData.strokeWidths[i] _ boxData.strokeWidths[i]; copyData.strokeColors[i] _ boxData.strokeColors[i]; ENDLOOP; copyData.fillColor _ boxData.fillColor; }; BoxDrawParts: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick: BOOL] = { DoBoxDrawParts: PROC = { IF parts = NIL OR IsComplete[parts] THEN BoxDrawAll[dc, boxData, [boxData.box.loX, boxData.box.loY], [boxData.box.hiX, boxData.box.hiY], camera] ELSE { BoxPathProc: Imager.PathProc = { moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]]; }; BoxEdgeDraw: PROC [index: INTEGER, p1, p2: Point] = { IF boxData.strokeColors[index]=NIL OR boxData.strokeWidths[index]=0.0 THEN RETURN; Imager.SetStrokeWidth[dc, boxData.strokeWidths[index] ]; Imager.SetColor[dc, boxData.strokeColors[index] ]; Imager.MaskVector[dc, p1, p2]; }; boxParts: BoxParts _ NARROW[parts]; t: ImagerTransformation.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] ]; 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] ]; IF AllEdges[parts] AND boxData.fillColor#NIL THEN { Imager.SetColor[dc, boxData.fillColor]; Imager.MaskFill[dc, BoxPathProc ]; }; Imager.SetStrokeEnd[dc, square]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN BoxEdgeDraw[edge, pts[edge], pts[(edge + 1) MOD 4]]; ENDLOOP; }; }; boxData: BoxData _ NARROW[slice.data]; Imager.DoSaveAll[dc, DoBoxDrawParts]; }; BoxDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { DoDrawFeedback: PROC = { BoxEdgeFeedback: PROC [index: INTEGER, p1, p2: Point] = { Imager.SetStrokeWidth[dc, 2.0*(IF boxData.strokeWidths[index]#0.0 THEN boxData.strokeWidths[index] ELSE 1.0) ]; Imager.SetColor[dc, IF boxData.strokeColors[index]#NIL THEN boxData.strokeColors[index] ELSE Imager.black]; Imager.MaskVector[dc, p1, p2]; }; box: BoundBox _ boxData.box; t: ImagerTransformation.Transformation _ boxData.transform; 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]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[corner], normal]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[corner]]; 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]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, cc, normal]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, cc]; IF NOT quick THEN { Imager.SetStrokeJoint[dc, round]; Imager.SetStrokeEnd[dc, round]; FOR edge: INTEGER IN [0..4) DO IF hotBoxParts#NIL AND hotBoxParts.edges[edge] THEN BoxEdgeFeedback[edge, pts[edge], pts[(edge+1) MOD 4]]; ENDLOOP; }; }; normalBoxParts, hotBoxParts: BoxParts; boxData: BoxData _ NARROW[slice.data]; IF caretIsMoving OR dragInProgress THEN RETURN; IF selectedParts = NIL AND hotParts = NIL THEN RETURN; normalBoxParts _ NARROW[selectedParts]; hotBoxParts _ NARROW[hotParts]; IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback]; }; BoxDrawAll: PROC [dc: Imager.Context, boxData: BoxData, from: Point, to: Point, camera: CameraData --, drawCPs: BOOL _ TRUE--] = { BoxPathProc: Imager.PathProc = { moveTo[ll]; lineTo[ul]; lineTo[ur]; lineTo[lr]; }; BoxEdgeDraw: PROC [index: INTEGER, p1, p2: Point] = { IF boxData.strokeColors[index]=NIL OR boxData.strokeWidths[index]=0.0 THEN RETURN; Imager.SetStrokeWidth[dc, boxData.strokeWidths[index] ]; Imager.SetColor[dc, boxData.strokeColors[index] ]; Imager.MaskVector[dc, p1, p2]; }; t: ImagerTransformation.Transformation _ boxData.transform; tLL: Point _ [MIN[to.x, from.x], MIN[to.y, from.y] ]; tUR: Point _ [MAX[to.x, from.x], MAX[to.y, from.y] ]; ll: Point _ ImagerTransformation.Transform[t, tLL]; ul: Point _ ImagerTransformation.Transform[t, [tLL.x, tUR.y] ]; ur: Point _ ImagerTransformation.Transform[t, tUR]; lr: Point _ ImagerTransformation.Transform[t, [tUR.x, tLL.y] ]; IF boxData.fillColor#NIL THEN { Imager.SetColor[dc, boxData.fillColor]; Imager.MaskFill[dc, BoxPathProc ]; }; Imager.SetStrokeEnd[dc, square]; BoxEdgeDraw[0, ll, ul]; BoxEdgeDraw[1, ul, ur]; BoxEdgeDraw[2, ur, lr]; BoxEdgeDraw[3, lr, ll]; }; BoxDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { 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; cornerCount, edgeCount: INTEGER _ 0; EasyDraw: PROC = { Imager.ConcatT[dc, transform]; -- temporary transformation BoxDrawAll[dc, boxData, [box.loX, box.loY], [box.hiX, box.hiY], camera--, FALSE--]; }; HardDraw: PROC = { BoxDrawAll[dc, boxData, point, oppositePoint, camera--, FALSE--]; }; IF box.null OR box.infinite THEN ERROR; IF IsComplete[boxParts] THEN {Imager.DoSaveAll[dc, EasyDraw]; RETURN;}; IF IsEmpty[boxParts] THEN RETURN; -- no parts. Legal. IF (edgeCount _ CountEdges[boxParts.edges]) >= 2 OR (cornerCount _ CountCorners[boxParts.corners]) >= 3 OR boxParts.center THEN {Imager.DoSaveAll[dc, EasyDraw]; RETURN;}; IF edgeCount=1 THEN { -- one edge. Transform it. f: ImagerTransformation.FactoredTransformation _ ImagerTransformation.Factor[transform]; globalTranslate: ImagerTransformation.VEC _ f.t; mInverse: ImagerTransformation.Transformation _ boxData.inverse; localTranslate: ImagerTransformation.VEC _ ImagerTransformation.TransformVec[mInverse, globalTranslate]; totalTransform: ImagerTransformation.Transformation _ ImagerTransformation.Translate[localTranslate]; SELECT TRUE FROM boxParts.edges[0] => { -- left IF NOT (boxParts.corners[2] OR boxParts.corners[3]) THEN { 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 } ELSE {Imager.DoSaveAll[dc, EasyDraw]; RETURN;} }; boxParts.edges[1] => { -- top IF NOT (boxParts.corners[0] OR boxParts.corners[3]) THEN { 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 } ELSE {Imager.DoSaveAll[dc, EasyDraw]; RETURN;} }; boxParts.edges[2] => { -- right IF NOT (boxParts.corners[0] OR boxParts.corners[1]) THEN { 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 } ELSE {Imager.DoSaveAll[dc, EasyDraw]; RETURN;} }; boxParts.edges[3] => { -- bottom IF NOT (boxParts.corners[1] OR boxParts.corners[2]) THEN { 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 } ELSE {Imager.DoSaveAll[dc, EasyDraw]; RETURN;} }; ENDCASE => ERROR; } ELSE IF cornerCount IN [1..2] THEN { -- one corner or two isolated corners. Transform one of them. 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 }; Imager.DoSaveAll[dc, HardDraw]; }; BoxTransform: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = { 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; cornerCount, edgeCount: INTEGER _ 0; IF box.null OR box.infinite THEN ERROR; IF IsComplete[boxParts] THEN { boxData.transform _ ImagerTransformation.Concat[boxData.transform, transform]; boxData.inverse _ ImagerTransformation.Invert[boxData.transform]; boxData.inverseScale _ ImagerTransformation.Factor[boxData.inverse].s; BoxSetBoundBox[slice]; RETURN; }; IF IsEmpty[boxParts] THEN RETURN; -- no parts. Legal. IF (edgeCount _ CountEdges[boxParts.edges]) >= 2 OR (cornerCount _ CountCorners[boxParts.corners]) >= 3 OR boxParts.center THEN { -- more than one edge or more than two corners. Full transform. boxData.transform _ ImagerTransformation.Concat[boxData.transform, transform]; boxData.inverse _ ImagerTransformation.Invert[boxData.transform]; boxData.inverseScale _ ImagerTransformation.Factor[boxData.inverse].s; BoxSetBoundBox[slice]; RETURN; }; IF edgeCount=1 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 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 IN [1..2] THEN { -- one or two isolated corners. Pick one and transform it. 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]; BoxSetBoundBox[slice]; }; MakeComplete: PROC [parts: SliceParts] = { WITH parts SELECT FROM boxParts: BoxParts => { boxParts.corners _ ALL[TRUE]; boxParts.edges _ ALL[TRUE]; boxParts.center _ TRUE; }; circleParts: CircleParts => { circleParts.cpArray _ ALL[TRUE]; circleParts.outline _ TRUE; }; ENDCASE => ERROR; }; IsComplete: PROC [parts: SliceParts] RETURNS [BOOL] = { IF parts = NIL THEN RETURN[FALSE]; WITH parts SELECT FROM boxParts: BoxParts => RETURN[ boxParts.corners=ALL[TRUE] AND boxParts.edges=ALL[TRUE] AND boxParts.center ]; circleParts: CircleParts => RETURN[ circleParts.cpArray=ALL[TRUE] AND circleParts.outline]; ENDCASE => ERROR; }; AllEdges: PROC [parts: SliceParts] RETURNS [BOOL] = { WITH parts SELECT FROM boxParts: BoxParts => RETURN[boxParts.edges=ALL[TRUE] ]; circleParts: CircleParts => RETURN[circleParts.outline]; ENDCASE => ERROR; }; IsEmpty: PROC [parts: SliceParts] RETURNS [BOOL] = { WITH parts SELECT FROM boxParts: BoxParts => RETURN[ boxParts.corners=ALL[FALSE] AND boxParts.edges=ALL[FALSE] AND boxParts.center = FALSE]; circleParts: CircleParts => RETURN[ circleParts.cpArray=ALL[FALSE] AND circleParts.outline=FALSE]; ENDCASE => ERROR; }; CountCorners: PROC [a: CornerArray] RETURNS [count: INTEGER] = { count _ 0; FOR corner: INTEGER IN [0..4) DO IF a[corner] THEN count _ count+1; ENDLOOP; }; CountEdges: PROC [a: EdgeArray] RETURNS [count: INTEGER] = { count _ 0; FOR edge: INTEGER IN [0..4) DO IF a[edge] THEN count _ count+1; 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; }; 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; }; BoxNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = { IF pointGen=NIL OR pointGen.toGo = 0 THEN { pointAndDone.done _ TRUE; RETURN; } ELSE { sliceD: SliceDescriptor _ pointGen.sliceD; slice: Slice _ sliceD.slice; boxData: BoxData _ NARROW[slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; t: ImagerTransformation.Transformation _ boxData.transform; 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 availabe point REPEAT FINISHED => { IF NOT boxParts.center THEN index _ index + 1; }; ENDLOOP; SELECT index FROM -- index in [0..4) of next available point 0 => pointAndDone.point _ ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]]; 1 => pointAndDone.point _ ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]]; 2 => pointAndDone.point _ ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]]; 3 => pointAndDone.point _ ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]]; 4 => pointAndDone.point _ ImagerTransformation.Transform[t, [(boxData.box.loX + boxData.box.hiX)/2.0, (boxData.box.loY + boxData.box.hiY)/2.0]]; ENDCASE => SIGNAL GGError.Problem[msg: "Broken Invariant"]; 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 }; }; BoxNextPointPair: PROC [pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = { IF pointPairGen=NIL OR pointPairGen.toGo = 0 THEN { pointPairAndDone.done _ TRUE; RETURN; } ELSE { sliceD: SliceDescriptor _ pointPairGen.sliceD; slice: Slice _ sliceD.slice; boxData: BoxData _ NARROW[slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; t: ImagerTransformation.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 availabe 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 GGError.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 }; }; BoxEmptyParts: PROC [slice: Slice, parts: SliceParts] RETURNS [BOOL] = { boxParts: BoxParts _ NARROW[parts]; -- NARROW for type checking RETURN[IsEmpty[boxParts]]; }; NearestBoxPoint: PROC [slice: Slice, point: Point] RETURNS [index: [-1..3], center: BOOL] = { sliceParts: SliceParts _ BoxNewParts[slice, NIL, slice]; boxHitData: BoxHitData; success: BOOL; hitData: REF ANY; sliceD: SliceDescriptor _ NEW[SliceDescriptorObj _ [slice, sliceParts]]; [----, ----, 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 [parts: SliceParts] = { boxHitData: BoxHitData _ NARROW[hitData]; boxParts: BoxParts; parts _boxParts_ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]]; SELECT mode FROM 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 boxParts.edges[boxHitData.edge] _ TRUE; }; controlPoint => { boxParts.corners _ ALL[TRUE]; boxParts.center _ TRUE; }; traj, topLevel, slice => MakeComplete[boxParts]; none => { -- prefer center to corner to edge if true IF boxHitData.center#-1 THEN boxParts.center _ TRUE ELSE IF boxHitData.corner#-1 THEN boxParts.corners[boxHitData.corner] _ TRUE ELSE IF boxHitData.edge#-1 THEN boxParts.edges[boxHitData.edge] _ TRUE; }; ENDCASE => ERROR; }; BoxUnionParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aPlusB: SliceParts] = { boxPartsA: BoxParts _ NARROW[partsA]; boxPartsB: BoxParts _ NARROW[partsB]; newParts: BoxParts _ 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; RETURN[newParts]; }; BoxDiffParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aMinusB: SliceParts] = { boxPartsA: BoxParts _ NARROW[partsA]; boxPartsB: BoxParts _ NARROW[partsB]; newParts: BoxParts _ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE ] ]; IF partsB=NIL THEN {newParts^ _ boxPartsA^; aMinusB _ newParts; RETURN;}; 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; RETURN[newParts]; }; BoxMovingParts: PROC [slice: Slice, parts: SliceParts] RETURNS [moving: SliceParts] = { boxParts: BoxParts _ NARROW[parts]; newParts: BoxParts _ NEW[BoxPartsObj _ [corners: ALL[TRUE], edges: ALL[TRUE], center: TRUE ] ]; cornerCount: INTEGER _ CountCorners[boxParts.corners]; edgeCount: INTEGER _ CountEdges[boxParts.edges]; centerCount: INTEGER _ IF boxParts.center THEN 1 ELSE 0; IF (cornerCount+edgeCount)>=2 OR centerCount > 0 THEN {moving _ newParts; RETURN;}; -- everything moving IF cornerCount=1 THEN { -- turn off the opposite corner FOR i: INTEGER IN [0..4) DO IF boxParts.corners[i] THEN newParts.corners[(i+2) MOD 4] _ FALSE; ENDLOOP; } ELSE IF edgeCount=1 THEN { -- turn off the opposite edge and two corners FOR i: INTEGER IN [0..4) DO IF boxParts.edges[i] THEN { newParts.edges[(i+2) MOD 4] _ FALSE; newParts.corners[(i+2) MOD 4] _ FALSE; newParts.corners[(i+3) MOD 4] _ FALSE; }; ENDLOOP; } ELSE {newParts.edges _ ALL[FALSE]; newParts.corners _ ALL[FALSE]; }; -- nothing at all is moving moving _ newParts; -- RETURN[newParts] }; BoxFixedParts: PROC [slice: Slice, parts: SliceParts, selectedList: LIST OF REF ANY] RETURNS [fixed: SliceParts] = { moving: SliceParts _ BoxMovingParts[slice, parts]; allParts: BoxParts _ NEW[BoxPartsObj]; MakeComplete[allParts]; fixed _ BoxDiffParts[slice, allParts, moving]; }; BoxAugmentParts: PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] RETURNS [more: SliceParts] = { boxParts: BoxParts _ NARROW[parts]; newParts: BoxParts; newParts _ NEW[BoxPartsObj _ [corners: boxParts.corners, edges: boxParts.edges, center: boxParts.center] ]; FOR i: INTEGER IN [0..4) DO IF boxParts.edges[i] THEN { newParts.corners[i] _ TRUE; newParts.corners[(i+1) MOD 4] _ TRUE; }; ENDLOOP; RETURN[newParts]; }; BoxClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { sliceBBox: BoundBox _ sliceD.slice.boundBox; toleranceBox: GGBasicTypes.BoundBoxObj _ [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; IF PointIsInBox[testPoint, toleranceBox] THEN { 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: REAL _ ABS[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 _ GGVector.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 { bestPoint _ GGTransform.Transform[boxData.transform, bestPoint]; boxHitData.hitPoint _ bestPoint; bestDist _ GGVector.Distance[testPoint, bestPoint]; }; }; }; BoxClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { sliceBBox: BoundBox _ sliceD.slice.boundBox; toleranceBox: GGBasicTypes.BoundBoxObj _ [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates IF PointIsInBox[testPoint, toleranceBox] THEN { seg: NAT _ 9999; boxHitData: BoxHitData; boxData: BoxData _ NARROW[sliceD.slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; localTestpoint: Point _ GGTransform.Transform[boxData.inverse, testPoint]; localTolerance: REAL _ABS [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 _ GGVector.Distance[testPoint, bestPoint]; hitData _ boxHitData _ NEW[BoxHitDataObj _ [corner: -1, edge: seg, center: -1, hitPoint: bestPoint] ]; }; }; }; globalEdge: Edge; BoxLineIntersection: PUBLIC PROC [sliceD: SliceDescriptor, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT] = { 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 _ GGLines.LineTransform[line, boxData.inverse]; localPoints: LIST OF Point; localPoints _ NIL; pointCount _ 0; 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] _ GGLines.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] _ GGLines.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 _ GGLines.CreateEmptyEdge[]; FillBoxEdge[edge, bBox, boxHitData.edge]; GGLines.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 => GGLines.FillEdge[[bBox.loX, bBox.loY], [bBox.loX, bBox.hiY], edge]; 1 => GGLines.FillEdge[[bBox.loX, bBox.hiY], [bBox.hiX, bBox.hiY], edge]; 2 => GGLines.FillEdge[[bBox.hiX, bBox.hiY], [bBox.hiX, bBox.loY], edge]; 3 => GGLines.FillEdge[[bBox.hiX, bBox.loY], [bBox.loX, bBox.loY], edge]; ENDCASE => ERROR; }; 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"]; BoxDescribe: PROC [slice: Slice, parts: SliceParts] RETURNS [rope: Rope.ROPE] = { prefix: Rope.ROPE; boxParts: BoxParts _ NARROW[parts]; cornerCount: INTEGER _ CountCorners[boxParts.corners]; edgeCount: INTEGER _ CountEdges[boxParts.edges]; IF cornerCount+edgeCount>1 THEN RETURN["multiple parts of a Box slice"] ELSE { centerOnly: BOOL _ boxParts.center AND edgeCount=0 AND cornerCount=0; oneEdge: BOOL _ NOT boxParts.center AND edgeCount=1 AND cornerCount=0; oneCorner: BOOL _ NOT boxParts.center AND cornerCount=1 AND edgeCount=0; noParts: BOOL _ NOT 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] = { 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.strokeWidths[edge]]] ]; ENDLOOP; f.PutRope[") strokeColors: ( "]; FOR edge: INTEGER IN [0..4) DO GGParseOut.WriteColor[f, boxData.strokeColors[edge]]; f.PutChar[IO.SP]; ENDLOOP; f.PutRope[") fillColor: "]; GGParseOut.WriteColor[f, boxData.fillColor]; f.PutChar[IO.SP]; }; BoxFilein: PROC [f: IO.STREAM, version: REAL, feedback: Viewer] RETURNS [slice: Slice] = { boxData: BoxData; box: BoundBox; p1, p2: Point; transform: ImagerTransformation.Transformation; strokes: ARRAY [0..4) OF REAL; colors: ARRAY [0..4) OF Imager.Color; fill: Imager.Color; p1 _ GGParseIn.ReadPoint[f]; p2 _ GGParseIn.ReadPoint[f]; transform _ GGParseIn.ReadTransformation[f]; GGParseIn.ReadBlankAndRope[f, "strokeWidths: ("]; FOR edge: INTEGER IN [0..4) DO strokes[edge] _ GGParseIn.ReadBlankAndReal[f]; ENDLOOP; GGParseIn.ReadBlankAndRope[f, ") strokeColors: ("]; FOR edge: INTEGER IN [0..4) DO colors[edge] _ GGParseIn.ReadColor[f, version]; ENDLOOP; GGParseIn.ReadBlankAndRope[f, ") fillColor: "]; fill _ GGParseIn.ReadColor[f, version]; 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 boxData.strokeWidths[edge] _ strokes[edge]; boxData.strokeColors[edge] _ colors[edge]; ENDLOOP; boxData.fillColor _ fill; }; BoxSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN boxData.strokeColors[edge] _ color; ENDLOOP; }; BoxGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN color _ boxData.strokeColors[edge]; ENDLOOP; }; BoxSetFillColor: PROC [slice: Slice, color: Imager.Color] = { boxData: BoxData _ NARROW[slice.data]; boxData.fillColor _ color; }; BoxGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = { boxData: BoxData _ NARROW[slice.data]; color _ boxData.fillColor; }; BoxSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN boxData.strokeWidths[edge] _ strokeWidth; ENDLOOP; BoxSetBoundBox[slice]; }; BoxGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN strokeWidth _ boxData.strokeWidths[edge]; ENDLOOP; }; CircleData: TYPE = REF CircleDataObj; CircleDataObj: TYPE = RECORD [ circle: Circle, -- a representation of a unit circle (for the convenience of GGCircles.LineMeetsCircle) tightBox: BoundBox, transform: ImagerTransformation.Transformation, scale: Vector, -- cached value of ImagerTransformation.Factor[transform].s evenScaling: BOOL, simpleCircle: Circle, -- for CircleHitDataAsSimpleCurve inverse: ImagerTransformation.Transformation, inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s startPoint: Point, -- original point on circumference when circle was created strokeWidth: REAL, strokeColor: Imager.Color, fillColor: Imager.Color ]; MaxCirclePoints: INTEGER = 5; CirclePoints: TYPE = [0..MaxCirclePoints); -- origin, left, top, right, bottom CircleParts: TYPE = REF CirclePartsObj; CirclePartsObj: TYPE = RECORD [ cpArray: ARRAY CirclePoints OF BOOL, -- used when origin or CPs are selected outline: BOOL -- TRUE if outline itself is in parts ]; CircleHitData: TYPE = REF CircleHitDataObj; CircleHitDataObj: TYPE = RECORD [ index: [-1..MaxCirclePoints), -- records index of which origin or CP is hit. -1 => no hit hitPoint: Point, outline: BOOL -- TRUE if outline itself is hit ]; BuildCircleSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { OPEN GGSlice; class _ NEW[SliceClassObj _ [ type: $Circle, getBoundBox: CircleBoundBox, getTightBox: CircleTightBox, copy: CircleCopy, drawParts: CircleDrawParts, drawTransform: CircleDrawTransform, drawSelectionFeedback: CircleDrawSelectionFeedback, drawAttractorFeedback: NoOpDrawAttractorFeedback, transform: CircleTransform, describe: CircleDescribe, fileout: CircleFileout, filein: CircleFilein, emptyParts: CircleEmptyParts, newParts: CircleNewParts, unionParts: CircleUnionParts, differenceParts: CircleDiffParts, movingParts: CircleMovingParts, fixedParts: CircleFixedParts, augmentParts: CircleAugmentParts, pointsInDescriptor: CirclePointsInDescriptor, pointPairsInDescriptor: GGSlice.NoOpPointPairsInDescriptor, nextPoint: CircleNextPoint, nextPointPair: GGSlice.NoOpNextPointPair, closestPoint: CircleClosestPoint, closestPointAndTangent: NIL, closestSegment: CircleClosestSegment, lineIntersection: CircleLineIntersection, circleIntersection: NoOpCircleIntersection, hitDataAsSimpleCurve: CircleHitDataAsSimpleCurve, setStrokeWidth: CircleSetStrokeWidth, getStrokeWidth: CircleGetStrokeWidth, setStrokeColor: CircleSetStrokeColor, getStrokeColor: CircleGetStrokeColor, setFillColor: CircleSetFillColor, getFillColor: CircleGetFillColor ]]; }; MakeCircleSlice: PUBLIC PROC [origin: Point, outerPoint: Point, strokeWidth: REAL _ 2.0, strokeColor: Imager.Color _ Imager.black, fillColor: Imager.Color _ NIL] RETURNS [sliceD: SliceDescriptor] = { circleData: CircleData _ NEW[CircleDataObj]; circleParts: CircleParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE ]]; slice: Slice; circleData.circle _ GGCircles.CircleFromPointAndRadius[[0,0], 1.0]; circleData.tightBox _ GGBoundBox.NullBoundBox[]; circleData.startPoint _ outerPoint; circleData.strokeWidth _ strokeWidth; circleData.strokeColor _ strokeColor; circleData.fillColor _ fillColor; slice _ NEW[SliceObj _ [ class: FetchSliceClass[$Circle], data: circleData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0] ]]; IF origin=outerPoint THEN circleData.startPoint _ [origin.x+20.0, origin.y+20.0]; -- prevent degenerate circle circleData.transform _ ImagerTransformation.PreScale[ImagerTransformation.Translate[origin], GGVector.Distance[origin, circleData.startPoint]]; circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; circleData.evenScaling _ TRUE; circleData.simpleCircle _ GGCircles.CircleFromPointAndRadius[origin, GGVector.Distance[origin, circleData.startPoint]]; CircleSetBoundBox[slice]; sliceD _ NEW[SliceDescriptorObj _ [slice, circleParts] ]; [] _ CircleClosestPoint[sliceD, outerPoint, 999.0]; -- only purpose of this call is to set up the HitData so that the first call to SelectSlice (in GGMouseEventImplA.EndCircle) works }; CircleBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { RETURN[slice.boundBox]; }; CircleTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { RETURN[slice.boundBox]; }; CircleSetBoundBox: PROC [slice: Slice] = { cpHalf: REAL _ GGModelTypes.halfJointSize + 1; strokeWidth: REAL _ NARROW[slice.data, CircleData].strokeWidth; pad: REAL _ MAX[cpHalf, strokeWidth]; circleData: CircleData _ NARROW[slice.data]; epsilon: REAL = 1.0E-6; origin: Point _ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]]; minor: Point _ ImagerTransformation.Transform[circleData.transform, [0.0, 1.0]]; major: Point _ ImagerTransformation.Transform[circleData.transform, [1.0, 0.0]]; radius: REAL _ RealFns.SqRt[MAX[GGVector.DistanceSquared[minor, origin], GGVector.DistanceSquared[major, origin]]]; GGBoundBox.UpdateBoundBox[circleData.tightBox, origin.x-radius, origin.y-radius, origin.x+radius, origin.y+radius]; GGBoundBox.UpdateCopyBoundBox[bBox: slice.boundBox, from: circleData.tightBox]; GGBoundBox.EnlargeByOffset[slice.boundBox, pad]; IF ABS[circleData.inverseScale.x - circleData.inverseScale.y] < epsilon THEN { origin: Point; radius: REAL; origin _ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]]; radius _ circleData.scale.x; circleData.evenScaling _ TRUE; GGCircles.FillCircleFromPointAndRadius[origin, radius, circleData.simpleCircle]; } ELSE circleData.evenScaling _ FALSE; }; CircleCopy: PROC [slice: Slice] RETURNS [copy: Slice] = { circleData: CircleData _ NARROW[slice.data]; newData: CircleData; copy _ MakeCircleSlice [[0.0, 0.0], [1.0, 1.0], circleData.strokeWidth, circleData.strokeColor, circleData.fillColor].slice; newData _ NARROW[copy.data]; newData.transform _ ImagerTransformation.Copy[circleData.transform]; newData.scale _ circleData.scale; newData.inverse _ ImagerTransformation.Copy[circleData.inverse]; newData.inverseScale _ circleData.inverseScale; newData.evenScaling _ circleData.evenScaling; newData.simpleCircle _ GGCircles.CreateEmptyCircle[]; GGCircles.CopyCircle[from: circleData.simpleCircle, to: newData.simpleCircle]; }; CircleDrawParts: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick: BOOL] = { DoCircleDrawOutline: PROC = { CircleDrawOutline[dc, circleData, circleData.transform, circleData.strokeWidth, circleData.strokeColor]; }; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; IF circleParts = NIL OR circleParts.outline THEN Imager.DoSaveAll[dc, DoCircleDrawOutline]; }; CircleDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { OPEN ImagerTransformation; DoDrawFeedback: PROC = { thisCPisHot, thisCPisSelected: BOOL; pts: ARRAY[0..4] OF Point; pts[0] _ Transform[t, [0.0, 0.0]]; pts[1] _ Transform[t, [-1.0, 0.0]]; pts[2] _ Transform[t, [0.0, 1.0]]; pts[3] _ Transform[t, [1.0, 0.0]]; pts[4] _ Transform[t, [0.0, -1.0]]; IF NOT quick AND IsComplete[normalCircleParts] THEN { }; FOR index: CirclePoints IN [0..MaxCirclePoints) DO thisCPisHot _ hotCircleParts#NIL AND hotCircleParts.cpArray[index]; thisCPisSelected _ normalCircleParts#NIL AND normalCircleParts.cpArray[index]; IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[index], hot]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[index], normal]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[index]]; ENDLOOP; }; normalCircleParts, hotCircleParts: CircleParts; circleData: CircleData _ NARROW[slice.data]; t: ImagerTransformation.Transformation _ circleData.transform; IF caretIsMoving OR dragInProgress THEN RETURN; IF selectedParts = NIL AND hotParts = NIL THEN RETURN; normalCircleParts _ NARROW[selectedParts]; hotCircleParts _ NARROW[hotParts]; IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback]; }; CircleDrawOutline: PROC [dc: Imager.Context, circleData: CircleData, transform: ImagerTransformation.Transformation, strokeWidth: REAL, strokeColor: Color] = { CirclePath: Imager.PathProc = { moveTo[[leftSide.x, leftSide.y]]; arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]]; }; TransformedCirclePath: Imager.PathProc = { ImagerPath.Transform[CirclePath, transform, moveTo, lineTo, curveTo, conicTo, arcTo]; }; FillItIn: PROC = { Imager.SetColor[dc, circleData.fillColor]; Imager.ConcatT[dc, transform]; Imager.MaskFill[dc, CirclePath, TRUE] }; leftSide: Point _ [-1.0, 0.0]; rightSide: Point _ [1.0, 0.0]; IF circleData.fillColor#NIL THEN Imager.DoSaveAll[dc, FillItIn]; IF strokeColor#NIL AND strokeWidth#0.0 THEN { Imager.SetColor[dc, strokeColor]; Imager.SetStrokeWidth[dc, strokeWidth]; Imager.MaskStroke[dc, TransformedCirclePath, TRUE]; }; }; CircleDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { OPEN ImagerTransformation; originSelected: BOOL _ FALSE; cpCount: INT _ 0; cpIndex: INT _ -1; edgePoint, pointInUnitSpace: Point; newTransform: Transformation; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; IF circleParts.cpArray[0] THEN originSelected _ TRUE; FOR index: CirclePoints IN [1..MaxCirclePoints) DO IF circleParts.cpArray[index] THEN{ cpCount _ cpCount + 1; cpIndex _ index; }; ENDLOOP; IF originSelected OR cpCount > 1 THEN { -- treat as complete circle selected CircleDrawOutline[dc, circleData, Concat[circleData.transform, transform], circleData.strokeWidth, circleData.strokeColor]; RETURN; }; IF cpCount = 0 AND NOT originSelected THEN ERROR; edgePoint _ Transform[Concat[circleData.transform, transform], SELECT cpIndex FROM 1 => [-1.0, 0.0], 2 => [0.0, 1.0], 3 => [1.0, 0.0], 4 => [0.0, -1.0], ENDCASE => ERROR]; pointInUnitSpace _ Transform[circleData.inverse, edgePoint]; newTransform _ PreScale[circleData.transform, MAX[GGVector.Magnitude[pointInUnitSpace], 0.001] ]; CircleDrawOutline[dc, circleData, newTransform, circleData.strokeWidth, circleData.strokeColor]; }; CircleTransform: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = { isOrigin: BOOL _ FALSE; cpCount: INT _ 0; cpIndex: INT _ -1; originPoint, edgePoint, pointInUnitSpace: Point; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; IF circleParts.cpArray[0] THEN isOrigin _ TRUE; FOR index: CirclePoints IN [1..MaxCirclePoints) DO IF circleParts.cpArray[index] THEN{ cpCount _ cpCount + 1; cpIndex _ index; }; ENDLOOP; IF isOrigin OR cpCount > 1 THEN { -- treat as though complete circle is selected circleData.transform _ ImagerTransformation.Concat[circleData.transform, transform]; circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; CircleSetBoundBox[slice]; RETURN; }; originPoint _ ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]]; IF cpCount = 0 AND NOT isOrigin THEN { edgePoint _ GGVector.Add[v1: GGVector.Sub[v1: circleData.startPoint, v2: originPoint], v2: ImagerTransformation.Factor[transform].t]; -- edgepoint relative to origin edgePoint _ GGVector.Add[originPoint, edgePoint]; -- make edgepoint absolute } ELSE { -- this is the normal case when a single CP is selected edgePoint _ ImagerTransformation.Transform[circleData.transform, SELECT cpIndex FROM 1 => [-1.0, 0.0], 2 => [0.0, 1.0], 3 => [1.0, 0.0], 4 => [0.0, -1.0], ENDCASE => ERROR]; edgePoint _ ImagerTransformation.Transform[transform, edgePoint]; -- move the edgepoint }; pointInUnitSpace _ ImagerTransformation.Transform[circleData.inverse, edgePoint]; circleData.transform _ ImagerTransformation.PreScale[circleData.transform, MAX[GGVector.Magnitude[pointInUnitSpace], 0.01] ]; -- circles can't shrink to zero circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; CircleSetBoundBox[slice]; }; CircleDescribe: PROC [slice: Slice, parts: SliceParts] RETURNS [rope: Rope.ROPE] = { prefix: Rope.ROPE; cpCount: INT _ 0; circleParts: CircleParts _ NARROW[parts]; FOR index: CirclePoints IN [0..MaxCirclePoints) DO IF circleParts.cpArray[index] THEN cpCount _ cpCount + 1; ENDLOOP; IF cpCount > 1 THEN RETURN[ "multiple parts of a Circle slice"]; IF cpCount=0 THEN prefix _ IF circleParts.outline THEN "outline" ELSE "nowhere" ELSE prefix _ IF circleParts.cpArray[0] THEN "origin" ELSE "circumference point"; rope _ Rope.Concat[prefix, " of a Circle slice"]; }; CircleFileout: PROC [slice: Slice, f: IO.STREAM] = { circleData: CircleData _ NARROW[slice.data]; GGParseOut.WriteTransformation[f, circleData.transform]; f.PutRope[" strokeWidth: "]; f.PutF["%g", [real[circleData.strokeWidth]] ]; f.PutRope[" strokeColor: "]; GGParseOut.WriteColor[f, circleData.strokeColor]; f.PutRope[" fillColor: "]; GGParseOut.WriteColor[f, circleData.fillColor]; f.PutChar[IO.SP]; }; CircleFilein: PROC [f: IO.STREAM, version: REAL, feedback: Viewer] RETURNS [slice: Slice] = { circleData: CircleData; origin, outerPoint: Point; transform: ImagerTransformation.Transformation; strokeWidth: REAL; strokeColor, fillColor: Imager.Color; transform _ GGParseIn.ReadTransformation[f]; origin _ ImagerTransformation.Transform[m: transform, v: [0.0, 0.0] ]; outerPoint _ ImagerTransformation.Transform[m: transform, v: [0.0, 1.0] ]; GGParseIn.ReadBlankAndRope[f, "strokeWidth:"]; strokeWidth _ GGParseIn.ReadBlankAndReal[f]; GGParseIn.ReadBlankAndRope[f, "strokeColor:"]; strokeColor _ GGParseIn.ReadColor[f, version]; GGParseIn.ReadBlankAndRope[f, "fillColor:"]; fillColor _ GGParseIn.ReadColor[f, version]; slice _ MakeCircleSlice[origin, outerPoint, strokeWidth, strokeColor, fillColor].slice; circleData _ NARROW[slice.data]; circleData.transform _ transform; circleData.scale _ ImagerTransformation.SingularValues[circleData.transform]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; circleData.inverseScale _ ImagerTransformation.SingularValues[circleData.inverse]; CircleSetBoundBox[slice]; }; CircleEmptyParts: PROC [slice: Slice, parts: SliceParts] RETURNS [BOOL] = { circleParts: CircleParts _ NARROW[parts]; RETURN[IsEmpty[circleParts] ]; }; NearestCirclePoint: PROC [slice: Slice, point: Point] RETURNS [index: [0..4] ] = { sliceParts: SliceParts _ CircleNewParts[slice, NIL, slice]; circleHitData: CircleHitData; success: BOOL; hitData: REF ANY; sliceD: SliceDescriptor _ NEW[SliceDescriptorObj _ [slice, sliceParts]]; [----, ----, hitData, success] _ CircleClosestPoint[sliceD: sliceD, testPoint: point, tolerance: GGUtility.plusInfinity]; IF NOT success THEN ERROR; circleHitData _ NARROW[hitData]; index _ circleHitData.index; IF index = -1 THEN ERROR; }; CircleNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [parts: SliceParts] = { circleHitData: CircleHitData _ NARROW[hitData]; circleParts: CircleParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE] ]; SELECT mode FROM joint => { IF circleHitData.index#-1 THEN circleParts.cpArray[circleHitData.index] _ TRUE ELSE IF circleHitData.outline THEN { index: [-1..4] _ NearestCirclePoint[slice, circleHitData.hitPoint]; circleParts.cpArray[index] _ TRUE; }; }; controlPoint => circleParts.cpArray _ ALL[TRUE]; segment, traj, topLevel, slice => MakeComplete[circleParts]; none => { IF circleHitData.index#-1 THEN circleParts.cpArray[circleHitData.index] _ TRUE; IF circleHitData.outline THEN circleParts.outline _ TRUE; }; ENDCASE => ERROR; parts _ circleParts; -- RETURN[boxParts] }; CircleUnionParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aPlusB: SliceParts] = { circlePartsA: CircleParts _ NARROW[partsA]; circlePartsB: CircleParts _ NARROW[partsB]; newParts: CircleParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE ] ]; FOR i: CirclePoints IN CirclePoints DO newParts.cpArray[i] _ circlePartsA.cpArray[i] OR circlePartsB.cpArray[i]; ENDLOOP; newParts.outline _ circlePartsA.outline OR circlePartsB.outline; aPlusB _ newParts; -- RETURN[newParts] }; CircleDiffParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aMinusB: SliceParts] = { circlePartsA: CircleParts _ NARROW[partsA]; circlePartsB: CircleParts _ NARROW[partsB]; newParts: CircleParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE ] ]; FOR i: CirclePoints IN CirclePoints DO newParts.cpArray[i] _ IF circlePartsB.cpArray[i] THEN FALSE ELSE circlePartsA.cpArray[i]; ENDLOOP; newParts.outline _ IF circlePartsB.outline THEN FALSE ELSE circlePartsA.outline; aMinusB _ newParts; -- RETURN[newParts] }; CircleMovingParts: PROC [slice: Slice, parts: SliceParts] RETURNS [moving: SliceParts] = { circleParts: CircleParts _ NARROW[parts]; newParts: CircleParts _ NEW[CirclePartsObj _ [ALL[FALSE], FALSE] ]; IF circleParts.cpArray[0] THEN { newParts.cpArray _ ALL[TRUE]; newParts.outline _ TRUE; } ELSE FOR i: INTEGER IN [1..4] DO IF circleParts.cpArray[i] THEN { newParts.cpArray _ ALL[TRUE]; newParts.cpArray[0] _ FALSE; newParts.outline _ TRUE; EXIT; }; ENDLOOP; moving _ newParts; -- RETURN[newParts] }; CircleFixedParts: PROC [slice: Slice, parts: SliceParts, selectedList: LIST OF REF ANY] RETURNS [fixed: SliceParts] = { moving: SliceParts _ CircleMovingParts[slice, parts]; allParts: CircleParts _ NEW[CirclePartsObj]; MakeComplete[allParts]; fixed _ CircleDiffParts[slice, allParts, moving]; }; CircleAugmentParts: PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] RETURNS [more: SliceParts] = { more _ parts; }; CirclePointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = { parts: CircleParts _ NARROW[sliceD.parts]; pointGen _ NEW[PointGeneratorObj _ [sliceD, 0, 0, NIL] ]; FOR index: CirclePoints IN CirclePoints DO IF parts.cpArray[index] THEN pointGen.toGo _ pointGen.toGo + 1; ENDLOOP; }; CircleNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = { sliceD: SliceDescriptor _ pointGen.sliceD; slice: Slice _ sliceD.slice; circleData: CircleData _ NARROW[slice.data]; t: ImagerTransformation.Transformation _ circleData.transform; IF pointGen=NIL OR pointGen.toGo = 0 THEN { pointAndDone.done _ TRUE; RETURN; } ELSE { parts: CircleParts _ NARROW[sliceD.parts]; index: NAT; pointAndDone.done _ FALSE; FOR index _ pointGen.index, index+1 UNTIL index>=LAST[CirclePoints] DO IF parts.cpArray[index] THEN EXIT; -- find the next included point ENDLOOP; SELECT index FROM 0 => pointAndDone.point _ ImagerTransformation.Transform[t, [0.0, 0.0]]; -- origin 1 => pointAndDone.point _ ImagerTransformation.Transform[t, [-1.0, 0.0]]; -- left 2 => pointAndDone.point _ ImagerTransformation.Transform[t, [0.0, 1.0]]; -- top 3 => pointAndDone.point _ ImagerTransformation.Transform[t, [1.0, 0.0]]; -- right 4 => pointAndDone.point _ ImagerTransformation.Transform[t, [0.0, -1.0]]; -- bottom ENDCASE => SIGNAL GGError.Problem[msg: "Broken Invariant"]; pointGen.index _ index+1; pointGen.toGo _ pointGen.toGo - 1; }; }; CircleClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { sliceBBox: BoundBox _ sliceD.slice.boundBox; toleranceBox: GGBasicTypes.BoundBoxObj _ [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates IF PointIsInBox[testPoint, toleranceBox] THEN { nextPoint, origin, left, top, right, bottom: Point; -- points in Gargoyle coordinates nextDist2, bestDist2: REAL _ GGUtility.plusInfinity; tolerance2: REAL _ tolerance*tolerance; foundIndex: INTEGER _ 0; circleData: CircleData _ NARROW[sliceD.slice.data]; circleHitData: CircleHitData; [origin, left, top, right, bottom] _ CirclePointsFromData[circleData]; FOR index: INTEGER IN [0..MaxCirclePoints) DO nextPoint _ SELECT index FROM 0 => origin, 1 => left, 2 => top, 3 => right, 4 => bottom, ENDCASE => ERROR; nextDist2 _ GGVector.DistanceSquared[testPoint, nextPoint]; IF nextDist2 < bestDist2 THEN { bestDist2 _ nextDist2; foundIndex _ index; bestPoint _ nextPoint; IF bestDist2 < tolerance2 THEN success _ TRUE; }; ENDLOOP; IF success THEN { hitData _ circleHitData _ NEW[CircleHitDataObj _ [index: foundIndex, hitPoint: bestPoint, outline: FALSE] ]; bestDist _ RealFns.SqRt[bestDist2]; }; }; }; CircleClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { sliceBBox: BoundBox _ sliceD.slice.boundBox; toleranceBox: GGBasicTypes.BoundBoxObj _ [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates IF PointIsInBox[testPoint, toleranceBox] THEN { circleHitData: CircleHitData; circleData: CircleData _ NARROW[sliceD.slice.data]; pointOnCircle: Point _ [0.0, 0.0]; localTestpoint: Point _ ImagerTransformation.Transform[circleData.inverse, testPoint]; dist: REAL _ GGVector.Magnitude[localTestpoint]; IF dist > 0.0 THEN pointOnCircle _ GGVector.Scale[localTestpoint, 1.0/dist]; bestPoint _ ImagerTransformation.Transform[circleData.transform, pointOnCircle]; bestDist _ GGVector.Distance[testPoint, bestPoint]; IF bestDist box.hiX OR test.y < box.loY OR test.y > box.hiY) ]; }; CirclePointsFromData: PROC [circleData: CircleData] RETURNS [origin, left, top, right, bottom: Point] = { t: ImagerTransformation.Transformation _ circleData.transform; origin _ ImagerTransformation.Transform[t, [0.0, 0.0] ]; left _ ImagerTransformation.Transform[t, [-1.0, 0.0] ]; top _ ImagerTransformation.Transform[t, [0.0, 1.0] ]; right _ ImagerTransformation.Transform[t, [1.0, 0.0] ]; bottom _ ImagerTransformation.Transform[t, [0.0, -1.0] ]; }; CircleSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = { circleData: CircleData _ NARROW[slice.data]; circleData.strokeWidth _ strokeWidth; CircleSetBoundBox[slice]; }; CircleGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = { circleData: CircleData _ NARROW[slice.data]; strokeWidth _ circleData.strokeWidth; }; CircleSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = { circleData: CircleData _ NARROW[slice.data]; circleData.strokeColor _ color; }; CircleGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color] = { circleData: CircleData _ NARROW[slice.data]; color _ circleData.strokeColor; }; CircleSetFillColor: PROC [slice: Slice, color: Imager.Color] = { circleData: CircleData _ NARROW[slice.data]; circleData.fillColor _ color; }; CircleGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = { circleData: CircleData _ NARROW[slice.data]; color _ circleData.fillColor; }; FetchSliceClass: PUBLIC PROC [type: ATOM] RETURNS [class: SliceClass] = { FOR l: LIST OF SliceClassDef _ sliceClasses, l.rest UNTIL l=NIL DO IF l.first.type=type THEN RETURN[l.first.class]; ENDLOOP; SIGNAL GGError.Problem[msg: "Slice class not found."]; RETURN[NIL]; }; RegisterSliceClass: PUBLIC PROC [class: SliceClass] = { classDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: class.type, class: class]]; sliceClasses _ CONS[classDef, sliceClasses]; }; DeleteSlice: PUBLIC PROC [scene: Scene, slice: Slice] = { GGObjects.DeleteEntity[scene, slice]; }; CopySlice: PUBLIC PROC [slice: Slice] RETURNS [copy: Slice] = { RETURN[slice.class.copy[slice]]; }; Init: PROC [] = { textDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Text, class: GGSlice.BuildTextSliceClass[]]]; ipDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $IP, class: GGSlice.BuildIPSliceClass[]]]; boxDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Box, class: GGSlice.BuildBoxSliceClass[]]]; circleDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Circle, class: GGSlice.BuildCircleSliceClass[]]]; sliceClasses _ LIST[circleDef, boxDef, ipDef, textDef]; printPrefix _ Atom.MakeAtom["xerox/pressfonts/"]; screenPrefix _Atom.MakeAtom ["xerox/tiogafonts/"]; globalEdge _ GGLines.CreateEmptyEdge[]; }; sliceClasses: LIST OF SliceClassDef; printPrefix: ATOM; screenPrefix: ATOM; Init[]; END. ðGGSliceImplA.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last edited by Pier on January 15, 1987 2:22:23 pm PST Last edited by Bier on January 27, 1987 0:14:50 am PST Contents: Implements various slice classes in Gargoyle. Box Slice Class Fundamentals Drawing Transforming Textual Description Parts Part Generators Hit Testing Style requires a bound box input with loX<=hiX AND loY<=hiY Class Procedures GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceCopyProc GGModelTypes.SliceDrawPartsProc draw fill draw outline Imager.SetStrokeJoint[dc, round]; Imager.SetStrokeEnd[dc, round]; GGModelTypes.SliceDrawSelectionFeedbackProc draw fill draw outline Imager.SetStrokeJoint[dc, round]; Imager.SetStrokeEnd[dc, round]; draw control points IF camera.quality#quality AND drawCPs THEN { GGShapes.DrawCP[dc, ll ]; GGShapes.DrawCP[dc, ul ]; GGShapes.DrawCP[dc, ur ]; GGShapes.DrawCP[dc, lr ]; GGShapes.DrawCP[dc, GGVector.Scale[GGVector.Add[ll, ur], 0.5] ]; }; 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. total transformation has already occurred on point, oppositePoint More than one edge or more than two corners. Full transform. GGModelTypes.SliceTransformProc Permanently transforms the box. Depending on which parts are selected, the points are transformed and the box is grown/shrunk properly. GGModelTypes.SliceEmptyPartsProc GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc 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. GGModelTypes.SliceFixedPartsProc Start with every part and remove the ones that are moving GGModelTypes.SliceAugmentPartsProc For every edge, add its corresponding corners GGModelTypes.SliceClosestPointProc hitData _ NIL; -- automatically done by compiler GGModelTypes.SliceClosestSegmentProc hitData _ NIL; -- automatically done by compiler Find the intersections of the given line with bBox. pointCount will be at most 2. GGModelTypes.SliceDescribeProc GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. GGModelTypes.SliceFileinProc Circle Slice Class transform needed for general case of rotated circles cpArray[origin, left, top, right, bottom]. 5 element array. Fundamentals Drawing Transforming Textual Description Hit Testing Style Class Procedures Fundamentals GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceBoundBoxProc since the circle can be an ellipse, we just can't find the radius. GGModelTypes.SliceCopyProc copy transform and inverse to the new structure (interface to makecircle requires origin and radius, which is inadequate since "circle" could actually have been transformed into an ellipse. Thus we passed a meaningless origin and radius). Drawing GGModelTypes.SliceDrawPartsProc GGModelTypes.SliceDrawSelectionFeedbackProc strokeWidth: REAL _ 2.0*(IF circleData.strokeWidth=0.0 THEN 1.0 ELSE circleData.strokeWidth); CircleDrawOutline[dc, circleData, circleData.transform, strokeWidth, IF circleData.strokeColor#NIL THEN circleData.strokeColor ELSE Imager.black]; NOT called with the dc transformation already set to object coordinates GGModelTypes.SliceDrawTransformProc This is what makes circles behave specially. Depending on which parts are selected, the points are transformed and the circle is rubberbanded properly. The circle data itself is not modified. Drag the whole circle. Rubberband the radius. Transforming GGModelTypes.SliceTransformProc Permanently transforms the circle. Depending on which parts are selected, the circle is transformed. Only one cp is selected OR no selections for a brand new circle This is the special case of a new circle being rubberbanded Textual Description GGModelTypes.SliceDescribeProc GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. GGModelTypes.SliceFileinProc fix up the new circle transforms in case they were skewed by uneven scaling Hit Testing GGModelTypes.SliceEmptyPartsProc GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceDifferencePartsProc GGModelTypes.SliceMovingPartsProc If center is moving, everything moves. If any edge CP is moving, its fellow edge CP are moving GGModelTypes.SliceFixedPartsProc Start with every part and remove the ones that are moving GGModelTypes.SliceAugmentPartsProc DOESN'T DO A COPY. MAYBE IT SHOULD GGModelTypes.SliceClosestPointProc hitData _ NIL; -- automatically done by compiler GGModelTypes.SliceClosestSegmentProc hitData _ NIL; -- automatically done by compiler Find hit point on unit circle Transform back to world coords Style Class registration scene.ptrValid _ FALSE; scene.entities _ GGUtility.DeleteEntityFromList[slice, scene.entities]; ÊEW˜codešœ™Kšœ<™Kšœ"œœ ˜IKšœ œ2˜EKšœœ5˜MKš œœ œœœ"˜RKšœ˜—Kšœœœ˜5Kšœ"œœ˜@KšœW˜WKšœ œ)˜[context: Imager.Context, strokeJoint: Imager.StrokeJoint]šœ!˜!Kšœ˜šœœœ˜Kš œ œœœ/œ˜jKšœ˜—K˜—K˜—K˜&Kšœœ ˜&Kšœœœœ˜/Kš œœœ œœœ˜6Kšœœ˜'Kšœœ ˜Kšœœ&˜DK˜K˜—š ž œœSœ œœ˜‚Kšž œH˜Sšž œœ œ˜5Kš œœœ!œœ˜RKšœ8˜8Kšœ2˜2Kšœ˜K˜—Kšœ;˜;Kšœœœ˜5Kšœœœ˜5Kšœ3˜3Kšœ?˜?Kšœ3˜3šœ?˜?Kš¡ ™ —šœœœ˜Kšœ'˜'Kšœ"˜"K˜Kš¡ ™ —Kšœ!™!Kšœ™Kšœ ˜ Kšž œ ˜Kšž œ ˜Kšž œ ˜šž œ ˜Kš¡™—šœœ œ™,Kšœ™Kšœ™Kšœ™Kšœ™Kšœ@™@K™—K˜K˜—šžœœ~˜”Kšœ#™#K™¸Kšœ+œŸ˜MKšœœ ˜&Kšœœ˜#Kšœ˜Kšœ=˜=Kšœœ˜$šžœœ˜KšœŸ˜:KšœFœœ˜SK˜—šžœœ˜KšœA™AKšœ4œœ˜AK˜—Kšœ œœœ˜'Kšœœ"œ˜GšœœœŸ˜5Kšœ<™<—Kš œ/œ5œœ"œ˜ªšœ œŸ˜0KšœX˜XKšœ&œ˜0Kšœ@˜@Kšœ%œ@˜hKšœe˜ešœœ˜šœŸ˜šœœœœ˜:Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EK˜—Kšœ"œ˜.Kšœ˜—šœŸ˜šœœœœ˜:Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EK˜—Kšœ"œ˜.Kšœ˜—šœŸ˜ šœœœœ˜:Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EK˜—Kšœ"œ˜.Kšœ˜—šœŸ ˜!šœœœœ˜:Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EK˜—Kšœ"œ˜.Kšœ˜—Kšœœ˜—K˜—š œœ œœŸ=˜bšœœ˜Kšœ^˜^Kšœ^˜^Kšœ^˜^Kšœ^˜^Kšœœ˜—Kšœ˜KšœQ˜QKšœEŸ˜dK˜—Kšœ˜K˜K˜—šž œœV˜hKšœ™Kšœ‡™‡Kšœ+œŸ˜MKšœœ ˜&Kšœœ˜#Kšœ˜Kšœ=˜=Kšœœ˜$Kšœ œœœ˜'šœœ˜KšœN˜NKšœA˜AK–*[m: ImagerTransformation.Transformation]šœF˜FKšœ˜Kšœ˜K˜—KšœœœŸ˜5š œ/œ5œœŸ?˜ÁKšœN˜NKšœA˜AK–*[m: ImagerTransformation.Transformation]šœF˜FKšœ˜Kšœ˜K˜—šœ œŸ˜*KšœX˜XKšœ&œ˜0Kšœ_˜_Kšœ%œ@˜hKšœe˜ešœœ˜šœ˜Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EKšœ˜—šœ˜Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EKšœ˜—šœ˜Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EKšœ˜—šœ˜Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EKšœ˜—Kšœœ˜—K˜—š œœ œœŸ:˜_šœœ˜Kšœ_˜_Kšœ_˜_Kšœ_˜_Kšœ_˜_Kšœœ˜—Kšœ˜KšœQ˜QKšœEŸ˜dK˜—Kš œ œœœœœœ˜“Kšœ˜K˜K˜—šž œœ˜+šœœ˜šœ˜Kšœœœ˜Kšœœœ˜Kšœœ˜K˜—šœ˜Kšœœœ˜ Kšœœ˜K˜—Kšœœ˜—K˜K˜—šž œœœœ˜7Kš œ œœœœ˜"šœœ˜Kšœœœœœœœœ˜lKš œœœœœ˜[Kšœœ˜—K˜K˜—šžœœœœ˜5šœœ˜Kšœœœœ˜8Kšœœ˜8Kšœœ˜—K˜K˜K˜—šžœœœœ˜4šœœ˜Kšœœœœœœœœœ˜uKš œœœœœœ˜bKšœœ˜—K˜K˜—šž œœœ œ˜@K˜ šœ œœ˜ Kšœ œ˜"Kšœ˜—Kšœ˜K˜—šž œœœ œ˜˜>Kšœœ˜%Kšœœ˜$K˜—K˜—šœ ˜ Kšœœ˜3Kšœœœ#œ˜GK˜—šœ˜Kšœœœ˜Kšœœ˜K˜—Kšœ0˜0šœ Ÿ*˜4Kšœœ˜3Kšœœœ'˜LKšœœœ#œ˜GK˜—Kšœœ˜—K˜K˜—šž œœ8œ˜kKšœ ™ Kšœœ ˜%Kšœœ ˜%Kš œœœœ œœ œ˜bšœœœ˜Kšœ+œ˜CKšœ'œ˜=Kšœ˜—Kšœ#œ˜7Kšœ ˜Kšœ˜K˜—šž œœ8œ˜kKšœœ ˜%Kšœœ ˜%Kš œœœœ œœ œ˜bKšœœœ.œ˜Išœœœ˜Kš œœœœœ˜SKš œœœœœ˜MKšœ˜—Kš œœœœœ˜GKšœ ˜K˜K˜—šžœœ#œ˜WKšœ!™!K™WK™MK™jKšœœ˜#Kš œœœœ œœ œ˜_Kšœ œ"˜6Kšœ œ˜0Kš œ œœœœ˜8Kš œœœœŸ˜hšœœŸ˜7šœœœ˜Kšœœœœ˜BKšœ˜—K˜—šœœ œŸ-˜Hšœœœ˜šœœ˜Kšœœœ˜$Kšœœœ˜&Kšœœœ˜&K˜—Kšœ˜—K˜—Kš œœœœœŸ˜`KšœŸ˜&K˜K˜—šž œœ1œœœœœ˜tKšœ ™ K™9Kšœ2˜2Kšœœ˜&Kšœ˜Kšœ.˜.K˜K˜—šžœœ@œ˜sKšœ"™"Kšœœ˜#Kšœ˜Kšœ œ]˜kK™-šœœœ˜šœœ˜Kšœœ˜Kšœœœ˜%K˜—Kšœ˜—Kšœ ˜K˜K˜—šžœœ8œœœ œœ œœ˜ªKšœ"™"Kšœ œŸ!™1K˜,KšœŽœœ˜œšœ'œ˜/Kšœœ˜Kšœ˜Kšœœ˜-Kšœ˜Kšœœ˜*Kšœ˜Kšœ œ˜KšœJ˜JKšœœœ œ3Ÿ3˜ŽKšœw˜wšœ œ˜KšœœI˜cKšœ˜—šœœŸ/˜IKšœ<Ÿ˜KKšœ8˜8šœœœ˜;Kšœ˜Kšœ˜Kšœ œ˜&KšœœF˜eKšœ˜Kšœ œ˜K˜—K˜—šœ œ˜Kšœ@˜@Kšœ ˜ Kšœ3˜3K˜—K˜—K˜K˜—šžœœ8œœœ œœ œœ˜¬Kšœ$™$Kšœ œŸ!™1K˜,KšœŽœœŸ˜µšœ'œ˜/Kšœœ˜Kšœ˜Kšœœ˜-Kšœœ˜*KšœJ˜JKšœœœ œ3Ÿ2˜Kšœ}˜}šœ œ˜Kšœ@˜@Kšœ3˜3KšœœL˜fK˜—K˜—K˜K˜—K˜šžœœœ'œ œœœ˜{Kšœœ˜-Kšœœ˜*Kšœ˜Kšœœ˜ Kšœœœœ˜Kšœ œœ˜ Kšœ œ ˜Kšœ?˜?Kšœ œœ˜KšœR™RKšœœ˜Kšœ˜Kš œœœœ œœ˜1šœœœ˜Kš œœœœŸ,˜PKšœ!˜!KšœE˜EKšœœœ˜šœœ œ˜Kšœœ.œœ˜>Kšœœ.œœ˜>K˜—Kšœœ˜.Kšœ˜Kšœ œ˜š˜šœ˜ Kš œœœœŸ,˜UKšœ!˜!KšœE˜EKšœœœ˜šœ œ˜Kšœœ,œœ˜@Kšœœ,œœ˜@K˜—šœ œ˜Kšœœ,œœ˜@Kšœœ,œœ˜@K˜—Kšœœ˜.Kšœ˜Kšœ œ˜š˜Kšœœ˜ —K˜——Kšœ˜—Kšœœœ˜Kšœ œ˜ š œœœ œœ˜DK–4[m: ImagerTransformation.Transformation, v: VEC]šœ œN˜[Kšœ˜—K˜K˜—šžœœœœœœœ˜aKšœœ ˜)Kšœœ ˜&Kšœ˜K˜ Kšœœœœ˜)Kšœ!˜!Kšœ)˜)Kšœ9˜9Kšœ˜K˜K˜—šž œœœ%œ˜FKšœ œœœ˜)Kšœ˜KšœH˜HKšœH˜HKšœH˜HKšœH˜HKšœœ˜K˜K˜K˜—šœœœœ˜-K˜V—šœœœœ˜+K˜6—šž œœ#œ œ˜QK™Kšœ œ˜Kšœœ˜#Kšœ œ"˜6Kšœ œ˜0šœœœ"œ˜NKšœ œœ œ˜EKš œ œœœ œ˜FKš œ œœœœ ˜HKš œ œœœœ ˜Fšœœ˜˜šœœœ˜šœœ˜Kšœ˜Kšœ˜K˜—Kšœ˜—K˜—˜ šœœœ˜šœœ˜Kšœ˜Kšœ˜K˜—Kšœ˜—K˜—Kšœ ˜ Kšœ˜Kšœœ˜—Kšœ.˜.K˜—K˜K˜—šž œœœœ˜1Kšœ™K™.Kšœœ ˜&Kšœ?˜?Kšœ œœ˜Kšœ?˜?Kšœ œœ˜Kšœ5˜5Kšœ˜šœœœ˜Kšœ3˜3Kšœ˜—Kšœ ˜ šœœœ˜K˜5Kšœ œœ˜Kšœ˜—Kšœ˜K˜,Kšœ œœ˜K˜K˜—š ž œœœœ œœ˜ZKšœ™K˜K˜K˜Kšœ/˜/Kšœ œœœ˜Kšœœœ˜%Kšœ˜Kšœ˜Kšœ˜Kšœ,˜,Kšœ1˜1šœœœ˜Kšœ.˜.Kšœ˜—Kšœ3˜3šœœœ˜Kšœ/˜/Kšœ˜—Kšœ/˜/Kšœ'˜'Kšœ:˜:Kšœ/˜/Kšœ œ ˜šœœœ˜Kšœ+˜+Kšœ*˜*Kšœ˜—Kšœ˜K˜K˜—šžœœ;˜RKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ$˜@Kšœ˜—Kšœ˜K˜—šžœœ#œ˜[Kšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ$˜@Kšœ˜—Kšœ˜K˜—šžœœ(˜=Kšœœ ˜&Kšœ˜Kšœ˜K˜—šžœœœ˜FKšœœ ˜&Kšœ˜Kšœ˜K˜—šžœœ0œ˜PKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ*˜FKšœ˜—Kšœ˜K˜K˜—šžœœ#œœ˜YKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ*˜FKšœ˜—K˜—K™K™Kšœ œœ˜%šœœœ˜Kšœ4™4Kšœg˜gK˜Kšœ/˜/KšœŸ/œ Ÿ˜JKšœ œ˜KšœŸ!˜7Kšœ-˜-KšœŸ9˜OKšœŸ:˜MKšœ œ˜K˜K˜K˜—K˜Kšžœœ˜KšœœŸ#˜NKšœ œœ˜'šœœœ˜šœ œœœŸ'˜LKšœ;™;Kšœ œŸ%˜3—K˜K˜—Kšœœœ˜+šœœœ˜!KšœŸ;˜YK˜Kšœ œŸ ˜.K˜—K˜šžœœœœ˜EKšœ ˜ šœœ˜˜Kš¡ ™ —Kšœ˜K˜˜Kš¡™—Kšœ˜K˜#K˜3˜1Kš¡ ™ —šœ˜Kš¡™—Kšœ˜Kšœ˜šœ˜Kš¡ ™ —K˜K˜K˜K˜!K˜K˜K˜!K˜-K˜;K˜K˜)Kšœ!˜!Kšœœ˜Kšœ%˜%K˜)K˜+˜1Kšœ™—K˜%K˜%K˜%K˜%K˜!K˜ K˜—Kšœ˜K˜—š žœœœ1œLœœ˜ÇKšœœ˜,Kš œœœœœ˜FKšœ ˜ KšœC˜CKšœ0˜0Kšœ Ÿœ Ÿœ ˜#Kšœ%˜%Kšœ%˜%Kšœ!˜!šœœ ˜K˜ Kšœ˜Kšœœ˜ Kšœœœœ˜&Kšœ,˜,Kšœ˜—Kšœœ9Ÿ˜nKšœ˜KšœM˜MKšœG˜GKšœR˜RKšœœ˜Kšœw˜wKšœ˜Kšœ œ-˜9Kšœ4Ÿ‚˜¶Kšœ˜K˜—K™K™Kšœ ™ –# -- [cluster: GGModelTypes.Cluster]šžœœ#œ˜RKšœ™Kšœ˜K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šžœœ#œ˜RKšœ™Kšœ˜K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šžœœ˜*Kšœœ"˜.Kšœ œœ%˜?Kšœœœ˜%Kšœœ ˜,Kšœ œ ˜KšœB™BK˜QK˜PK˜PKšœœœT˜sKšœs˜sKšœO˜OKšœ0˜0šœœBœ˜NKšœœ˜KšœJ˜JKšœ˜Kšœœ˜KšœP˜PK˜—Kšœœ˜$K˜K˜—šž œœœ˜9Kšœ™Kšœœ ˜,K˜Kšœ}˜}K™ïKšœ œ ˜K˜DK˜!K˜@K˜/K˜-Kšœ5˜5KšœN˜NK˜K˜—K™Kšœ™šžœœRœ˜pKšœ™šžœœ˜Kšœh˜hK˜—Kšœœ ˜,Kšœœ˜)Kšœœœœ+˜[K˜K˜—šžœœ˜œ˜ÂKšœ˜Kšœ+™+šžœœ˜Kšœœ˜$Kšœœœ˜KšœF˜FKšœE˜EKšœ#˜#šœœœœ˜5Kš œ œœœœ™]Kš œEœœœœ™’K˜—šœœ˜2Kšœœœ˜CKšœ%œœ"˜NKšœ œ1˜DKšœœ4˜LKš œœ œœœ!˜QKšœ˜—K˜—K˜/Kšœœ ˜,Kšœ>˜>K˜Kšœœœœ˜/Kš œœœ œœœ˜6Kšœœ˜*Kšœœ ˜"Kšœœ&˜DK˜K˜—šžœœkœ˜ K™Gšž œ˜Kšœ!˜!Kšœ>˜>Kšœ˜—šžœ˜*KšœU˜UKšœ˜—šžœœ˜Kšœ*˜*Kšœ˜K–P[context: Imager.Context, path: ImagerPath.PathProc, parity: BOOL _ FALSE]šœ œ˜%K˜—Kšœ˜Kšœ˜Kšœœœ ˜@šœ œœœ˜-Kšœ!˜!Kšœ'˜'Kšœ-œ˜3K˜—Kšœ˜K˜—šžœœ~˜—Kšœ#™#K™ÀKšœ˜Kšœœœ˜Kšœ œ˜Kšœ œ˜K˜#Kšœ˜Kšœœ ˜,Kšœœ˜)K˜Kšœœœ˜5šœœ˜2šœœ˜$Kšœ˜K˜K˜—šœ˜Kš¡™——šœœ œŸ$˜MKšœ{˜{Kšœ˜šœ˜Kš¡™——Kš œ œœœœ˜1šœ?œ ˜RK˜K˜K˜K˜Kšœœ˜—Kšœ<˜KšœAœ ˜TK˜K˜K˜K˜Kšœœ˜KšœBŸ˜WK˜—KšœQ˜QKšœKœ0Ÿ˜KšœM˜MKšœG˜GKšœR˜RKšœ˜K˜K˜—K™Kšœ™šžœœ#œ œ˜TK™Kšœ œ˜Kšœ œ˜Kšœœ˜)šœœ˜2Kšœœ˜9Kšœ˜—Kšœ œœ&˜@Kš œ œ œœ œ ˜OKšœ œœ œ˜QKšœ1˜1K˜K˜—šž œœœœ˜4Kšœ™K™.Kšœœ ˜,Kšœ8˜8Kšœ˜Kšœ.˜.Kšœ˜Kšœ1˜1Kšœ˜Kšœ/˜/Kšœ œœ˜K˜K™—š ž œœœœ œœ˜]Kšœ™Kšœ˜Kšœ˜Kšœ/˜/Kšœ œ˜Kšœ%˜%Kšœ,˜,K–4[m: ImagerTransformation.Transformation, v: VEC]šœF˜FK–4[m: ImagerTransformation.Transformation, v: VEC]šœJ˜JKšœ.˜.Kšœ,˜,Kšœ.˜.Kšœ.˜.Kšœ,˜,Kšœ,˜,šœW˜WKšœK™K—Kšœ œ ˜ K˜!KšœM˜MKšœG˜GKšœR˜RKšœ˜K˜—K™Kšœ ™ šžœœ#œœ˜KKšœ ™ Kšœœ˜)Kšœ˜K˜K˜—šžœœœ˜RKšœ/œ ˜;Kšœ˜Kšœ œ˜Kšœ œœ˜Kšœœ+˜HK–\[sliceD: GGModelTypes.SliceDescriptor, testPoint: GGBasicTypes.Point, tolerance: REAL]šœy˜yKšœœ œœ˜Kšœœ ˜ Kšœ˜Kšœ œœ˜K˜K˜—š žœœœœœ˜gKšœ™Kšœœ ˜/Kš œœœœœ˜Fšœ˜šœ ˜ Kšœœ,˜Nšœœœ˜$KšœC˜CKšœœ˜"K˜—K˜—Kšœ&œœ˜0Kšœ<˜<šœ˜ Kšœœ,œ˜OKšœœœ˜9K˜—Kšœœ˜—KšœŸ˜(K˜K˜—šžœœ8œ˜nKšœ ™ Kšœœ ˜+Kšœœ ˜+Kš œœœœœ˜Dšœœ˜&Kšœ.œ˜IKšœ˜—Kšœ(œ˜@KšœŸ˜&Kšœ˜K˜—šžœœ8œ˜nKšœ%™%Kšœœ ˜+Kšœœ ˜+Kš œœœœœ˜Dšœœ˜&Kš œœœœœ˜YKšœ˜—Kš œœœœœ˜PKšœŸ˜'K˜K˜—šžœœ#œ˜ZKšœ!™!K™_Kšœœ˜)Kš œœœœœ˜Cšœœ˜ Kšœœœ˜Kšœœ˜K˜—š œœœœ˜ šœœ˜ Kšœœœ˜Kšœœ˜Kšœœ˜Kšœ˜K˜—Kšœ˜—KšœŸ˜&K˜K˜—šžœœ1œœœœœ˜wKšœ ™ K™9Kšœ5˜5Kšœœ˜,Kšœ˜Kšœ1˜1K˜K˜—šžœœ@œ˜vKšœ"™"K™"Kšœ ˜ K˜—šžœœœ˜_Kšœœ˜*Kšœ œ$œ˜9šœœ˜*Kšœœ#˜?Kšœ˜ —K˜K˜—šžœœœ.˜fKšœ*˜*Kšœ˜Kšœœ ˜,Kšœ>˜>šœ œœœ˜+Kšœœ˜Kšœ˜K˜—šœ˜Kšœœ˜*Kšœœ˜ Kšœœ˜šœ!œœ˜FKšœœœŸ˜BKšœ˜—šœ˜KšœIŸ ˜RKšœJŸ˜QKšœIŸ˜OKšœIŸ˜QKšœJŸ ˜SKšœœ*˜;—Kšœ˜Kšœ"˜"K˜—K˜K˜—šžœœ8œœœ œœ œœ˜­K™"Kšœ œŸ!™1K˜,KšœŽœœŸ˜µšœ'œ˜/Kšœ4Ÿ!˜UKšœœ˜4Kšœ Ïuœœ˜'Kšœ œ˜Kšœœ˜3K˜KšœF˜Fšœœœ˜-šœ œ˜Kšœ:˜:Kšœœ˜—Kšœ¢œ2˜;šœ ¢œ ¢œœ˜Kšœ¢œ ¢œ˜K˜K˜Kš œ ¢œ ¢œœ œ˜.K˜—Kšœ˜—šœ œ˜KšœœFœ˜lK˜#K˜—K˜—K˜K˜—šžœœ8œœœ œœ œœ˜¯Kšœ$™$Kšœ œŸ!™1K˜,KšœŽœœŸ˜µšœ'œ˜/Kšœ˜Kšœœ˜3K˜"K˜Všœœ&˜0Kš¡™—šœ œ:˜LKš¡™—K˜PK˜3šœœ˜Kšœœ>œ˜cKšœ œ˜K˜—K˜—K˜K˜—šžœœœ'œ œœœ˜~Kšœœ˜3Kšœœ˜0Kšœ œ ˜KšœB˜BKšœ œœ˜#KšœT˜TKšœ œ˜ šœœœ˜ K–4[m: ImagerTransformation.Transformation, v: VEC]šœ œU˜bKšœ˜—K˜K˜—šžœœœœœœœ˜dKšœœ ˜/Kšœœ ˜,Kš œœœœœ˜.Kš œœœœœ˜/Kšœ&˜&K˜K˜—š ž œœœ.œœ˜ZKš œœœœœ˜]K˜K˜—šžœœœ.˜iKšœ>˜>K˜8Kšœ7˜7Kšœ5˜5Kšœ7˜7Kšœ9˜9Kšœ˜K˜K˜—Kšœ™šžœœ0œ˜SKšœœ ˜,Kšœ%˜%Kšœ˜˜K˜——šžœœ#œœ˜\Kšœœ ˜,Kšœ%˜%˜K˜——šžœœ;˜UKšœœ ˜,Kšœ˜Kšœ˜K˜—šžœœ#œ˜^Kšœœ ˜,Kšœ˜Kšœ˜K˜—šžœœ(˜@Kšœœ ˜,Kšœ˜Kšœ˜K˜—šžœœœ˜IKšœœ ˜,Kšœ˜Kšœ˜—K˜K™K™š žœœœœœ˜Iš œœœ&œœ˜BKšœœœ˜0Kšœ˜—Kšœ0˜6Kšœœ˜ K˜K˜—šžœ œ˜7Kšœœ6˜SKšœœ˜,K˜K˜—šž œœœ"˜:Kšœœ™KšœG™GKšœ%˜%K˜K˜—šž œœœœ˜?Kšœ˜ Kšœ˜—K˜šžœœ˜KšœœI˜eKšœœE˜_KšœœG˜bKšœœM˜kKšœœ$˜7K˜Kšœ1˜1Kšœ2˜2K˜K˜'K˜—K˜Kšœœœ˜$Kšœ œ˜Kšœœ˜K˜K˜K˜Kšœ˜—…—ÿBX‰