DIRECTORY Atom, GGBasicTypes, GGBoundBox, GGCircles, GGError, GGLines, GGSlice, GGInterfaceTypes, GGModelTypes, GGObjects, GGShapes, GGParseOut, GGParseIn, GGTransform, GGUtility, GGVector, Imager, ImagerTransformation, ImagerColor, IO, NodeStyle, RealFns, Rope, ViewerClasses; GGSliceImplA: CEDAR PROGRAM IMPORTS Atom, GGBoundBox, GGCircles, GGError, GGLines, GGObjects, GGSlice, GGParseIn, GGParseOut, GGShapes, GGTransform, GGVector, Imager, IO, RealFns, ImagerTransformation, Rope EXPORTS GGSlice = BEGIN Scene: TYPE = GGModelTypes.Scene; BoundBox: TYPE = GGModelTypes.BoundBox; CameraData: TYPE = GGInterfaceTypes.CameraData; Circle: TYPE = GGBasicTypes.Circle; 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; GGParseIn.ReadBlank[f]; p1 _ GGParseIn.ReadPoint[f]; GGParseIn.ReadBlank[f]; p2 _ GGParseIn.ReadPoint[f]; GGParseIn.ReadBlank[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 GGParseIn.ReadBlank[f]; colors[edge] _ GGParseIn.ReadColor[f]; ENDLOOP; GGParseIn.ReadBlankAndRope[f, ") fillColor: "]; fill _ GGParseIn.ReadColor[f]; 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 = { Imager.ConcatT[dc, circleData.transform]; CircleDrawOutline[dc, circleData]; }; 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] = { DoDrawFeedback: PROC = { DrawOutlineFeedback: PROC = { CirclePath: Imager.PathProc = { -- code copied from GGShapesImpl.DrawCircle moveTo[[leftSide.x, leftSide.y]]; arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]]; }; leftSide: Point _ [-1.0, 0.0]; rightSide: Point _ [1.0, 0.0]; strokeScale: REAL _ ABS[MAX[circleData.inverseScale.x, circleData.inverseScale.y]]; strokeWidth: REAL _ strokeScale*2.0*(IF circleData.strokeWidth=0.0 THEN 1.0 ELSE circleData.strokeWidth); Imager.ConcatT[dc, circleData.transform]; Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetColor[dc, IF circleData.strokeColor#NIL THEN circleData.strokeColor ELSE Imager.black]; Imager.MaskStroke[dc, CirclePath, TRUE]; }; thisCPisHot, thisCPisSelected: BOOL; pts: ARRAY[0..4] OF Point; pts[0] _ ImagerTransformation.Transform[t, [0.0, 0.0] ]; pts[1] _ ImagerTransformation.Transform[t, [-1.0, 0.0] ]; pts[2] _ ImagerTransformation.Transform[t, [0.0, 1.0] ]; pts[3] _ ImagerTransformation.Transform[t, [1.0, 0.0] ]; pts[4] _ ImagerTransformation.Transform[t, [0.0, -1.0] ]; IF NOT quick AND IsComplete[normalCircleParts] THEN Imager.DoSaveAll[dc, DrawOutlineFeedback]; 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]; }; CircleDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { isOrigin: BOOL _ FALSE; cpCount: INT _ 0; cpIndex: INT _ -1; originPoint, edgePoint, pointInUnitSpace: Point; newTransform: ImagerTransformation.Transformation; circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; EasyDrawOutline: PROC = { Imager.ConcatT[dc, ImagerTransformation.Concat[circleData.transform, transform]]; CircleDrawOutline[dc, circleData]; }; HardDrawOutline: PROC = { Imager.ConcatT[dc, newTransform]; CircleDrawOutline[dc, circleData]; }; 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 complete circle selected Imager.DoSaveAll[dc, EasyDrawOutline]; 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]; newTransform _ ImagerTransformation.PreScale[circleData.transform, MAX[GGVector.Magnitude[pointInUnitSpace], 0.01] ]; Imager.DoSaveAll[dc, HardDrawOutline]; }; CircleDrawOutline: PROC [dc: Imager.Context, circleData: CircleData] = { FillItIn: PROC = { Imager.SetColor[dc, circleData.fillColor]; Imager.MaskFill[dc, CirclePath, TRUE] }; CirclePath: Imager.PathProc = { -- code copied from GGShapesImpl.DrawCircle moveTo[[leftSide.x, leftSide.y]]; arcTo[[rightSide.x, rightSide.y], [ leftSide.x, leftSide.y]]; }; leftSide: Point _ [-1.0, 0.0]; rightSide: Point _ [1.0, 0.0]; IF circleData.fillColor#NIL THEN Imager.DoSaveAll[dc, FillItIn]; IF circleData.strokeColor#NIL AND circleData.strokeWidth#0.0 THEN { strokeScale: REAL _ ABS[MAX[circleData.inverseScale.x, circleData.inverseScale.y]]; Imager.SetColor[dc, circleData.strokeColor]; Imager.SetStrokeWidth[dc, circleData.strokeWidth*strokeScale]; Imager.MaskStroke[dc, CirclePath, TRUE]; }; }; 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; GGParseIn.ReadBlank[f]; 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:"]; GGParseIn.ReadBlank[f]; strokeColor _ GGParseIn.ReadColor[f]; GGParseIn.ReadBlankAndRope[f, "fillColor:"]; GGParseIn.ReadBlank[f]; fillColor _ GGParseIn.ReadColor[f]; 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 15, 1987 1:21:28 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 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. Only one cp is selected OR no selections for a brand new circle. Only rubberband radius This is the special case of a new circle being rubberbanded Circles can only be shrunk by a factor of 100 per scaling operation. called with the dc transformation already set to object coordinates DOES NOT USE DoSaveAll !! 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]; ΚF€˜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šœ˜Kšœ˜Kšœ,˜,Kšœ1˜1šœœœ˜Kšœ.˜.Kšœ˜—Kšœ3˜3šœœœ˜Kšœ˜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šœ)˜)Kšœ"˜"K˜—Kšœœ ˜,Kšœœ˜)Kšœœœœ+˜[K˜K˜—šžœœ˜œ˜ΒKšœ+™+šžœœ˜šžœœ˜šž œŸ+˜KKšœ!˜!Kšœ>˜>Kšœ˜—Kšœ˜Kšœ˜Kšœ œœœ8˜SKš œ œœœœ˜iKšœ)˜)Kšœ'˜'Kš œœœœœ˜aKšœ"œ˜(K˜—Kšœœ˜$Kšœœœ˜Kšœ8˜8Kšœ9˜9Kšœ8˜8Kšœ8˜8Kšœ9˜9Kšœœœœ,˜_šœœ˜2Kšœœœ˜CKšœ%œœ"˜NKšœ œ1˜DKšœœ4˜LKš œœ œœœ!˜QKšœ˜—K˜—K˜/Kšœœ ˜,Kšœ>˜>Kšœœœœ˜/Kš œœœ œœœ˜6Kšœœ˜*Kšœœ ˜"Kšœœ&˜DK˜K˜—šžœœ~˜—Kšœ#™#K™ΐKšœ œœ˜Kšœ œ˜Kšœ œ˜K˜0Kšœ2˜2Kšœœ ˜,Kšœœ˜)šžœœ˜KšœQ˜QKšœ"˜"K˜—šžœœ˜Kšœ!˜!Kšœ"˜"K˜—Kšœœ œ˜/šœœ˜2šœœ˜$Kšœ˜K˜K˜—Kšœ˜—šœ œ œŸ$˜GKšœ&˜&Kšœ˜K˜Kš‘X™X—KšœO˜Ošœ œœ œ˜&Kš‘;™;K–6[v1: GGBasicTypes.Vector, v2: GGBasicTypes.Vector]šœ†Ÿ˜₯Kšœ2Ÿ˜LK˜—šœŸ7˜>KšœAœ ˜TK˜K˜K˜K˜Kšœœ˜KšœBŸ˜WK˜—KšœQ˜QšœCœ/˜uKš‘D™D—Kšœ&˜&K˜K˜—šžœœ2˜IK™CK™šžœœ˜Kšœ*˜*K–P[context: Imager.Context, path: ImagerPath.PathProc, parity: BOOL _ FALSE]šœ œ˜%K˜—šž œŸ+˜KKšœ!˜!Kšœ>˜>Kšœ˜—Kšœ˜Kšœ˜Kšœœœ ˜@šœœœœ˜CKšœ œœœ8˜SKšœ,˜,Kšœ>˜>Kšœ"œ˜(K˜—Kšœ˜K˜—K™Kšœ ™ šžœœV˜kKšœ™Kšœd™dKšœ œœ˜Kšœ œ˜Kšœ œ˜K˜0Kšœœ ˜,Kšœœ˜)Kšœœ œ˜/šœœ˜2šœœ˜$Kšœ˜K˜K˜—Kšœ˜—šœ œ œŸ.˜QKšœT˜TKšœM˜MKšœG˜GKšœR˜RKšœ˜Kšœ˜K˜—K™?KšœO˜Ošœ œœ œ˜&K™;K–6[v1: GGBasicTypes.Vector, v2: GGBasicTypes.Vector]šœ†Ÿ˜₯Kšœ2Ÿ˜LK˜K˜—šœŸ7˜>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šœ,˜,K–4[m: ImagerTransformation.Transformation, v: VEC]šœF˜FK–4[m: ImagerTransformation.Transformation, v: VEC]šœJ˜JKšœ.˜.Kšœ,˜,Kšœ.˜.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šœ˜—…—ž^ή