DIRECTORY Atom, Convert, FileNames, GGBasicTypes, GGBoundBox, GGCircles, GGSlice, GGInterfaceTypes, GGLines, GGModelTypes, GGShapes, GGParseOut, GGParseIn, GGTransform, GGVector, Imager, ImagerFont, ImagerTransformation, IO, MessageWindow, NodeStyle, NodeStyleFont, Real, RealFns, Rope; GGSliceImpl: CEDAR PROGRAM IMPORTS Atom, Convert, FileNames, GGBoundBox, GGCircles, GGLines, GGParseIn, GGParseOut, GGShapes, GGTransform, GGVector, Imager, ImagerFont, IO, MessageWindow, NodeStyleFont, Real, RealFns, Rope, ImagerTransformation EXPORTS GGSlice = BEGIN Circle: TYPE = GGBasicTypes.Circle; Point: TYPE = GGBasicTypes.Point; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj; SliceObj: TYPE = GGModelTypes.SliceObj; SliceClass: TYPE = GGModelTypes.SliceClass; SliceClassObj: TYPE = GGModelTypes.SliceClassObj; SliceParts: TYPE = GGModelTypes.SliceParts; Slice: TYPE = GGModelTypes.Slice; SelectMode: TYPE = GGModelTypes.SelectMode; ExtendMode: TYPE = GGModelTypes.ExtendMode; BoundBox: TYPE = GGModelTypes.BoundBox; SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj; CameraData: TYPE = GGInterfaceTypes.CameraData; DisplayStyle: TYPE = GGSlice.DisplayStyle; Corner: TYPE = GGSlice.Corner; 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. SliceClassDef: TYPE = REF SliceClassDefObj; SliceClassDefObj: TYPE = RECORD[type: ATOM, class: SliceClass]; TextData: TYPE = REF TextDataObj; TextDataObj: TYPE = RECORD [ rope: Rope.ROPE _ NIL, worldPt: Point, fontName: Rope.ROPE _ NIL, -- font description: e.g. Helvetica11BI fontFamily: ATOM, fontFace: NodeStyle.FontFace _ Regular, fontSize: REAL _ 10.0, printFont: ImagerFont.Font _ NIL, screenFont: ImagerFont.Font _ NIL, color: Imager.Color _ NIL, colorName: Rope.ROPE _ NIL, feedbackBox: BoundBox _ NIL ]; TextHitData: TYPE = REF TextHitDataObj; TextHitDataObj: TYPE = RECORD [ bestSeg: NAT _ 0 -- segment number from ClosestSegementProc ]; IPData: TYPE = REF IPDataObj; IPDataObj: TYPE = RECORD [ file: Rope.ROPE, worldPt: Point, feedbackBox: BoundBox, transform: ImagerTransformation.Transformation ]; IPHitData: TYPE = REF IPHitDataObj; IPHitDataObj: TYPE = RECORD [ bestSeg: NAT _ 0 -- segment number from ClosestSegementProc ]; NotFound: PUBLIC SIGNAL = CODE; FontNameError: PUBLIC SIGNAL = CODE; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE; 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 NotFound; RETURN[NIL]; }; NoOpPointsInDescriptor: PUBLIC PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = { pointGen _ NIL; }; NoOpPointPairsInDescriptor: PUBLIC PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = { pointPairGen _ NIL; }; NoOpNextPoint: PUBLIC PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = { pointAndDone.done _ TRUE; }; NoOpNextPointPair: PUBLIC PROC [pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = { pointPairAndDone.done _ TRUE; }; BuildTextSliceClass: PROC [] RETURNS [class: SliceClass] = { class _ NEW[SliceClassObj _ [ type: $Text, boundBox: TextBoundBox, copy: TextCopy, draw: TextDraw, drawTransform: TextDrawTransform, drawSelectionFeedback: TextDrawSelectionFeedback, transform: TextTransform, emptyParts: TextEmptyParts, newParts: TextNewParts, unionParts: TextUnionParts, differenceParts: TextDiffParts, pointsInDescriptor: NoOpPointsInDescriptor, pointPairsInDescriptor: NoOpPointPairsInDescriptor, nextPoint: NoOpNextPoint, nextPointPair: NoOpNextPointPair, closestPoint: TextClosestPoint, closestPointAndTangent: NIL, closestSegment: TextClosestSegment, fileout: TextFileout, filein: TextFilein, setStrokeWidth: TextSetStrokeWidth, setStrokeColor: TextSetStrokeColor, setFillColor: TextSetFillColor ]]; }; MakeTextSlice: PUBLIC PROC [text: Rope.ROPE, fontName: Rope.ROPE, colorName: Rope.ROPE, worldPt: Point] RETURNS [slice: Slice] = { feedbackBox: BoundBox _ GGBoundBox.CreateBoundBox[0,0,0,0]; textData: TextData _ NEW[TextDataObj _ [rope: text, worldPt: worldPt, color: Imager.black, colorName: colorName, feedbackBox: feedbackBox, fontFace: , fontSize: ]]; slice _ NEW[SliceObj _ [ class: FetchSliceClass[$Text], data: textData, children: NIL, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets filled in later onOverlay: FALSE, hitData: NEW[TextHitDataObj _ [] ] ]]; SetTextFont[slice, fontName]; }; GetText: PUBLIC PROC [slice: Slice] RETURNS [text: Rope.ROPE] = { RETURN[IF slice.class.type#$Text THEN NIL ELSE NARROW[slice.data, TextData].rope]; }; AppendText: PUBLIC PROC [slice: Slice, text: Rope.ROPE] = { textData: TextData; IF slice.class.type#$Text THEN RETURN; textData _ NARROW[slice.data]; textData.rope _ Rope.Concat[textData.rope, text]; -- update text slice.class.boundBox[slice]; -- update bound box }; BackspaceText: PUBLIC PROC [slice: Slice] = { textData: TextData; IF slice.class.type#$Text THEN RETURN; textData _ NARROW[slice.data]; textData.rope _ Rope.Substr[base: textData.rope, len: Rope.Length[textData.rope]-1]; -- update text slice.class.boundBox[slice]; -- update bound box }; DigitBreak: IO.BreakProc = { RETURN[IF char IN ['0..'9] THEN break ELSE other]; }; FontParamsFromFontName: PUBLIC PROC [fontName: Rope.ROPE] RETURNS [fontFamily: ATOM, fontFace: NodeStyle.FontFace, fontSize: REAL] = { GetNumber: PROC [s: IO.STREAM] RETURNS [x: REAL _ 0.0] = { c: CHAR; UNTIL (c _ IO.GetChar[s ! IO.EndOfStream => {c _ 'A; CONTINUE;}; ]) NOT IN ['0..'9] DO x _ x*10+(c-'0); ENDLOOP; }; familyName, face: Rope.ROPE; bold, italic: BOOL _ FALSE; fontStream: IO.STREAM _ IO.RIS[fontName]; [familyName, ----] _ IO.GetTokenRope[stream: fontStream, breakProc: DigitBreak ! IO.EndOfStream => SIGNAL FontNameError]; fontSize _ GetNumber[fontStream ! IO.EndOfStream => SIGNAL FontNameError]; [face, ----] _ IO.GetTokenRope[stream: fontStream, breakProc: IO.TokenProc ! IO.EndOfStream => {face _ ""; CONTINUE; };]; fontFamily _ Atom.MakeAtom[familyName]; SELECT Rope.Length[face] FROM 0 => NULL; 1 => bold _ Rope.Fetch[base: face, index: 0]='B; 2 => { bold _ Rope.Fetch[base: face, index: 0]='B; italic _ Rope.Fetch[base: face, index: 1]='I; }; ENDCASE => NULL; fontFace _ SELECT TRUE FROM bold AND italic => BoldItalic, bold => Bold, italic => Italic, ENDCASE => Regular; }; SetTextFont: PUBLIC PROC [slice: Slice, fontName: Rope.ROPE] = { textData: TextData; font: Imager.Font; fontFamily: ATOM; fontFace: NodeStyle.FontFace; fontSize: REAL; IF slice.class.type#$Text OR fontName=NIL THEN RETURN; textData _ NARROW[slice.data]; [fontFamily, fontFace, fontSize] _ FontParamsFromFontName[fontName ! FontNameError => GOTO Abort; ]; font _ NodeStyleFont.FontFromStyleParams[prefix: printPrefix, family: fontFamily, face: fontFace, size: fontSize, alphabets: CapsAndLower]; IF Rope.Find[s1: font.name, s2: Atom.GetPName[fontFamily], case: FALSE]=-1 THEN GOTO Abort ELSE textData.printFont _ font; --May do a font substitution which we don't want. textData.screenFont _ NodeStyleFont.FontFromStyleParams[prefix: screenPrefix, family: fontFamily, face: fontFace, size: fontSize, alphabets: CapsAndLower]; IF Rope.Find[s1: textData.screenFont.name, s2: Atom.GetPName[fontFamily], case: FALSE]=-1 THEN textData.screenFont _ textData.printFont; --Did a font substitution which we don't want. textData.fontName _ fontName; textData.fontFamily _ fontFamily; textData.fontFace _ fontFace; textData.fontSize _ fontSize; slice.class.boundBox[slice]; -- update bound box EXITS Abort => { MessageWindow.Append["Gargoyle: Font Not Found", TRUE]; MessageWindow.Blink[]; }; }; GetTextFont: PUBLIC PROC [slice: Slice] RETURNS [font: ImagerFont.Font, fontName: Rope.ROPE] = { textData: TextData; IF slice.class.type#$Text THEN RETURN[NIL, NIL]; textData _ NARROW[slice.data]; RETURN[textData.printFont, textData.fontName]; }; TextBoundBox: PROC [slice: Slice] = { textData: TextData _ NARROW[slice.data]; printExtents, screenExtents: ImagerFont.Extents; halfCP: REAL = GGModelTypes.halfJointSize + 1; printExtents _ ImagerFont.RopeBoundingBox[textData.printFont, textData.rope]; screenExtents _ ImagerFont.RopeBoundingBox[textData.screenFont, textData.rope]; GGBoundBox.UpdateBoundBox[slice.boundBox, textData.worldPt.x - screenExtents.leftExtent - halfCP, textData.worldPt.y - screenExtents.descent - halfCP, textData.worldPt.x + screenExtents.rightExtent + halfCP, textData.worldPt.y + screenExtents.ascent + halfCP]; GGBoundBox.UpdateBoundBox[textData.feedbackBox, textData.worldPt.x - printExtents.leftExtent, textData.worldPt.y - printExtents.descent, textData.worldPt.x + printExtents.rightExtent, textData.worldPt.y + printExtents.ascent]; }; TextCopy: PROC [slice: Slice] RETURNS [copy: Slice] = { textData: TextData _ NARROW[slice.data]; copy _ MakeTextSlice[textData.rope, textData.fontName, textData.colorName, textData.worldPt]; copy.parent _ slice.parent; copy.children _ slice.children; }; TextDraw: PROC [slice: Slice, dc: Imager.Context, camera: CameraData] = { textData: TextData _ NARROW[slice.data]; Imager.SetFont[dc, IF camera.displayStyle=screen THEN textData.screenFont ELSE textData.printFont]; Imager.SetXY[dc, [textData.worldPt.x, textData.worldPt.y]]; Imager.ShowRope[dc, textData.rope]; }; TextDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { textData: TextData _ NARROW[slice.data]; tempPoint: Point _ GGTransform.Transform[transform, textData.worldPt]; Imager.SetFont[dc, IF camera.displayStyle=screen THEN textData.screenFont ELSE textData.printFont]; Imager.SetXY[dc, [ tempPoint.x, tempPoint.y ] ]; Imager.ShowRope[dc, textData.rope]; }; TextTransform: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = { textData: TextData _ NARROW[slice.data]; textData.worldPt _ GGTransform.Transform[transform, textData.worldPt]; TextBoundBox[slice]; }; TextDrawSelectionFeedback: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick: BOOL _ FALSE] = { textData: TextData _ NARROW[slice.data]; GGBoundBox.DrawBoundBox[dc, textData.feedbackBox]; }; TextEmptyParts: PROC [slice: Slice, parts: SliceParts] RETURNS [BOOL] = { RETURN[TRUE] }; TextNewParts: PROC [slice: Slice, mode: SelectMode] RETURNS [parts: SliceParts] = { RETURN[NIL] }; TextUnionParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aPlusB: SliceParts] = { RETURN[NIL] }; TextDiffParts: PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts] RETURNS [aMinusB: SliceParts] = { RETURN[NIL] }; TextClosestPoint: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { textData: TextData _ NARROW[slice.data]; [bestDist, ----, bestPoint, success] _ GGBoundBox.NearestPoint[textData.feedbackBox, testPoint]; }; TextClosestSegment: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { textData: TextData _ NARROW[slice.data]; textHitData: TextHitData _ NARROW[slice.hitData]; [bestDist, textHitData.bestSeg, bestPoint, success] _ GGBoundBox.NearestSegment[textData.feedbackBox, testPoint, tolerance]; }; TextFileout: PROC [slice: Slice, f: IO.STREAM] = { textData: TextData _ NARROW[slice.data]; f.PutF["\"%g\" %g %g", [rope[textData.rope]], [rope[textData.fontName]], [real[textData.fontSize]] ]; f.PutF[" %g ", [rope[textData.colorName]] ]; GGParseOut.WritePoint[f, textData.worldPt]; }; TextFilein: PROC [f: IO.STREAM, version: REAL] RETURNS [slice: Slice] = { UnpackComplexFontName: PROC [fontName: Rope.ROPE, fontSize: REAL] RETURNS [new: Rope.ROPE] = { face: Rope.ROPE; new _ FileNames.GetShortName[fontName]; -- throw away "xerox/pressfonts/" face _ FileNames.Tail[new, '-]; -- get face component (MIR, BRR, ...) new _ Rope.Substr[base: new, start: 0, len: Rope.SkipTo[s: new, pos: 0, skip: "-"]]; -- throw away "-XXX" new _ Rope.Cat[new, Convert.RopeFromInt[Real.FixI[fontSize]], SELECT TRUE FROM Rope.Equal[face, "MRR", FALSE] => "", Rope.Equal[face, "BRR", FALSE] => "B", Rope.Equal[face, "MIR", FALSE] => "I", Rope.Equal[face, "BIR", FALSE] => "BI", ENDCASE => ERROR ]; }; rope, fontName, colorName: Rope.ROPE; fontSize: REAL; point: Point; IF version > 8601.22 THEN { rope _ f.GetRopeLiteral[]; } ELSE { GGParseIn.ReadBlankAndRope[f, "("]; rope _ GGParseIn.ReadBlankAndWord[f]; GGParseIn.ReadBlankAndRope[f, ")"]; }; IF version <= 8601.06 THEN { -- no font name at all fontName _ "Helvetica10"; fontSize _ 10.0; colorName _ "black"; } ELSE IF version <= 8601.27 THEN { -- a simple name like "Helvetica" or "Gacha" fontName _ GGParseIn.ReadBlankAndWord[f]; fontName _ Rope.Concat[fontName, "10"]; fontSize _ 10.0; colorName _ "black"; } ELSE IF version <= 8605.12 THEN { -- a complex name like "xerox/pressfonts/Helvetica-BIR" fontName _ GGParseIn.ReadBlankAndWord[f]; fontSize _ GGParseIn.ReadBlankAndReal[f]; colorName _ GGParseIn.ReadBlankAndWord[f]; fontName _ UnpackComplexFontName[fontName, fontSize]; } ELSE { -- a mixed mode name like Helvetica7BI or TimesRoman12 fontName _ GGParseIn.ReadBlankAndWord[f]; fontSize _ GGParseIn.ReadBlankAndReal[f]; colorName _ GGParseIn.ReadBlankAndWord[f]; }; GGParseIn.ReadBlank[f]; point _ GGParseIn.ReadPoint[f]; slice _ MakeTextSlice[rope, fontName, colorName, point]; }; TextSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = {}; TextSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = {}; TextSetFillColor: PROC [slice: Slice, color: Imager.Color] = { textData: TextData _ NARROW[slice.data]; textData.color _ color; }; BoxData: TYPE = REF BoxDataObj; BoxDataObj: TYPE = RECORD [ box: BoundBox, transform: ImagerTransformation.Transformation, inverse: ImagerTransformation.Transformation, -- inverse of transform 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 ]; BoxParts: TYPE = REF BoxPartsObj; BoxPartsObj: TYPE = RECORD [ corners: CornerArray, -- which corners of box are selected. edges: EdgeArray -- which edges of box are selected. ]; BoxHitData: TYPE = REF BoxHitDataObj; BoxHitDataObj: TYPE = RECORD [ corner: [-1..4), -- which corner of box is hit. edge: [-1..4) -- which edge of box is hit. ]; BuildBoxSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { class _ NEW[SliceClassObj _ [ type: $Box, boundBox: BoxBoundBox, copy: BoxCopy, draw: BoxDraw, drawTransform: BoxDrawTransform, drawSelectionFeedback: BoxDrawSelectionFeedback, transform: BoxTransform, emptyParts: BoxEmptyParts, newParts: BoxNewParts, unionParts: BoxUnionParts, differenceParts: BoxDiffParts, pointsInDescriptor: BoxPointsInDescriptor, pointPairsInDescriptor: BoxPointPairsInDescriptor, nextPoint: BoxNextPoint, nextPointPair: BoxNextPointPair, closestPoint: BoxClosestPoint, closestPointAndTangent: NIL, closestSegment: BoxClosestSegment, fileout: BoxFileout, filein: BoxFilein, setStrokeWidth: BoxSetStrokeWidth, setStrokeColor: BoxSetStrokeColor, setFillColor: BoxSetFillColor ]]; }; MakeBoxSlice: PUBLIC PROC [box: BoundBox, corner: Corner, transform: ImagerTransformation.Transformation _ GGTransform.Identity[], strokeWidth: REAL _ 1.0] RETURNS [sliceD: SliceDescriptor] = { boxSlice: Slice; boxData: BoxData _ NEW[BoxDataObj _ [box: box, transform: transform, inverse: ImagerTransformation.Invert[transform], 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]] ]; cIndex: INTEGER _ SELECT corner FROM ll=>0, ul=>1, ur=>2, lr=>3,ENDCASE=>-1; 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, children: NIL, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], hitData: NEW[BoxHitDataObj _ [corner: cIndex, edge: -1 ] ] ]]; BoxBoundBox[boxSlice]; sliceD _ NEW[SliceDescriptorObj _ [boxSlice, boxParts] ]; }; GetBox: PUBLIC PROC [slice: Slice] RETURNS [box: BoundBox] = { RETURN[IF slice.class.type#$Box THEN NIL ELSE NARROW[slice.data, BoxData].box]; }; BoxBoundBox: PROC [slice: Slice] = { halfJoint: REAL = GGModelTypes.halfJointSize + 1; boxData: BoxData _ NARROW[slice.data]; box: BoundBox _ GGBoundBox.BoundBoxOfBoundBox[boxData.box, boxData.transform]; GGBoundBox.UpdateBoundBox[slice.boundBox, box.loX-halfJoint, box.loY-halfJoint, box.hiX+halfJoint, box.hiY+halfJoint] }; 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; }; BoxDraw: PROC [slice: Slice, dc: Imager.Context, camera: GGInterfaceTypes.CameraData] = { DoDrawBoxOutline: PROC = { Imager.ConcatT[dc, boxData.transform]; BoxDrawOutline[dc, boxData, [boxData.box.loX, boxData.box.loY], [boxData.box.hiX, boxData.box.hiY]]; }; DoDrawBoxJoints: PROC = { Imager.ConcatT[dc, boxData.transform]; BoxDrawJointsAux[dc, [boxData.box.loX, boxData.box.loY], [boxData.box.hiX, boxData.box.hiY] ]; }; boxData: BoxData _ NARROW[slice.data]; Imager.DoSaveAll[dc, DoDrawBoxOutline]; IF camera.quality # quality THEN Imager.DoSaveAll[dc, DoDrawBoxJoints]; }; BoxDrawOutline: PROC [dc: Imager.Context, boxData: BoxData, from: Point, to: Point, width: REAL _ 1.0] = { strokeScale: REAL _ ImagerTransformation.Factor[boxData.inverse].s.x*width; IF boxData.fillColor#NIL THEN { Imager.SetColor[dc, boxData.fillColor]; Imager.MaskBox[dc, [from.x, from.y, to.x, to.y] ]; }; Imager.SetColor[dc, boxData.strokeColors[0]]; Imager.SetStrokeWidth[dc, strokeScale*boxData.strokeWidths[0]]; Imager.MaskVector[dc, [from.x, from.y], [from.x, to.y]]; Imager.SetColor[dc, boxData.strokeColors[1]]; Imager.SetStrokeWidth[dc, strokeScale*boxData.strokeWidths[1]]; Imager.MaskVector[dc, [from.x, to.y], [to.x, to.y]]; Imager.SetColor[dc, boxData.strokeColors[2]]; Imager.SetStrokeWidth[dc, strokeScale*boxData.strokeWidths[2]]; Imager.MaskVector[dc, [to.x, to.y], [to.x, from.y]]; Imager.SetColor[dc, boxData.strokeColors[3]]; Imager.SetStrokeWidth[dc, strokeScale*boxData.strokeWidths[3]]; Imager.MaskVector[dc, [to.x, from.y], [from.x, from.y]]; }; BoxDrawJointsAux: PROC [dc: Imager.Context, from, to: Point] = { tLL: Point _ from; tUR: Point _ to; tUL: Point _ [from.x, to.y]; tLR: Point _ [to.x, from.y]; Imager.SetStrokeWidth[dc, 1.0]; GGShapes.DrawJoint[dc, tLL ]; GGShapes.DrawJoint[dc, tUL ]; GGShapes.DrawJoint[dc, tUR ]; GGShapes.DrawJoint[dc, tLR ]; }; MakeComplete: PROC [parts: SliceParts] = { WITH parts SELECT FROM boxParts: BoxParts => { boxParts.corners _ ALL[TRUE]; boxParts.edges _ ALL[TRUE]; }; circleParts: CircleParts => circleParts.cpArray _ ALL[TRUE]; ENDCASE => ERROR; }; IsComplete: PROC [parts: SliceParts] RETURNS [BOOL] = { WITH parts SELECT FROM boxParts: BoxParts => RETURN[ boxParts.corners=ALL[TRUE] AND boxParts.edges=ALL[TRUE] ]; circleParts: CircleParts => RETURN[ circleParts.cpArray=ALL[TRUE] ]; ENDCASE => ERROR; }; IsEmpty: PROC [parts: SliceParts] RETURNS [BOOL] = { WITH parts SELECT FROM boxParts: BoxParts => RETURN[ boxParts.corners=ALL[FALSE] AND boxParts.edges=ALL[FALSE] ]; circleParts: CircleParts => RETURN[ circleParts.cpArray=ALL[FALSE] ]; ENDCASE => ERROR; }; BoxDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.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; EasyDrawOutline: PROC = { Imager.ConcatT[dc, ImagerTransformation.Concat[boxData.transform, transform]]; BoxDrawOutline[dc, boxData, [box.loX, box.loY], [box.hiX, box.hiY] ]; }; EasyDrawJoints: PROC = { Imager.ConcatT[dc, ImagerTransformation.Concat[boxData.transform, transform]]; BoxDrawJointsAux[dc, [box.loX, box.loY], [box.hiX, box.hiY]]; -- does NO tranformations }; HardDrawOutline: PROC = { Imager.ConcatT[dc, boxData.transform]; BoxDrawOutline[dc, boxData, point, oppositePoint]; }; HardDrawJoints: PROC = { Imager.ConcatT[dc, boxData.transform]; BoxDrawJointsAux[dc, point, oppositePoint]; }; IF box.null OR box.infinite THEN ERROR; IF IsComplete[boxParts] THEN { Imager.DoSaveAll[dc, EasyDrawOutline]; IF camera.quality # quality THEN Imager.DoSaveAll[dc, EasyDrawJoints]; RETURN; }; IF IsEmpty[boxParts] THEN RETURN; -- no parts. Legal. IF (cornerCount _ CountCorners[boxParts.corners])+(edgeCount _ CountEdges[boxParts.edges]) >= 2 THEN { Imager.DoSaveAll[dc, EasyDrawOutline]; IF camera.quality # quality THEN Imager.DoSaveAll[dc, EasyDrawJoints]; RETURN; }; IF boxParts.corners#ALL[FALSE] AND boxParts.edges#ALL[FALSE] THEN ERROR; -- can't mix corners and edges while dragging. Doesn't make sense; must be a selection error. IF cornerCount=1 THEN { -- one corner. 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 } ELSE 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 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] => { -- top point _ [ 0, box.hiY ]; oppositePoint _ [ box.loX, box.loY ]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; point _ [ box.hiX, point.y]; -- result point is transformed in y only }; boxParts.edges[2] => { -- right point _ [ box.hiX, 0 ]; oppositePoint _ [ box.loX, box.loY ]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; point _ [ point.x, box.hiY]; -- result point is transformed in x only }; boxParts.edges[3] => { -- bottom point _ [ 0, box.loY ]; oppositePoint _ [ box.hiX, box.hiY ]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; point _ [ box.loX, point.y]; -- result point is transformed in y only }; ENDCASE => ERROR; } ELSE ERROR; -- a corner and an edge simultaneously Imager.DoSaveAll[dc, HardDrawOutline]; IF camera.quality # quality THEN Imager.DoSaveAll[dc, HardDrawJoints]; }; 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; }; 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]; BoxBoundBox[slice]; RETURN; }; IF IsEmpty[boxParts] THEN RETURN; -- no parts. Legal. IF (cornerCount _ CountCorners[boxParts.corners])+(edgeCount _ CountEdges[boxParts.edges]) >= 2 THEN { -- more than one corner or more than one edge. Full transform. boxData.transform _ ImagerTransformation.Concat[boxData.transform, transform]; boxData.inverse _ ImagerTransformation.Invert[boxData.transform]; BoxBoundBox[slice]; RETURN; }; IF boxParts.corners#ALL[FALSE] AND boxParts.edges#ALL[FALSE] THEN ERROR; -- can't mix corners and edges while dragging. Doesn't make sense; must be a selection error. IF cornerCount=1 THEN { -- one corner. 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 _ ImagerTransformation.Invert[m: boxData.transform]; totalTransform _ ImagerTransformation.Cat[boxData.transform, transform, inverse]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; -- transform the dragging point } ELSE IF 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 ERROR; -- a corner and an edge simultaneously box^ _ [MIN[point.x, oppositePoint.x], MIN[point.y, oppositePoint.y], MAX[point.x, oppositePoint.x], MAX[point.y, oppositePoint.y], FALSE, FALSE]; BoxBoundBox[slice]; }; BoxDrawSelectionFeedback: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = { DoDrawFeedback: PROC = { boxData: BoxData _ NARROW[slice.data]; boxParts: BoxParts _ NARROW[parts]; box: BoundBox _ boxData.box; t: ImagerTransformation.Transformation _ boxData.transform; tLL: Point _ ImagerTransformation.Transform[t, [box.loX, box.loY] ]; tUL: Point _ ImagerTransformation.Transform[t, [box.loX, box.hiY] ]; tUR: Point _ ImagerTransformation.Transform[t, [box.hiX, box.hiY] ]; tLR: Point _ ImagerTransformation.Transform[t, [box.hiX, box.loY] ]; lX: REAL _ MIN[tLL.x, tUL.x, tUR.x, tLR.x]; lY: REAL _ MIN[tLL.y, tUL.y, tUR.y, tLR.y]; hX: REAL _ MAX[tLL.x, tUL.x, tUR.x, tLR.x]; hY: REAL _ MAX[tLL.y, tUL.y, tUR.y, tLR.y]; tBox: ImagerTransformation.Rectangle _ [lX, lY, hX-lX, hY-lY]; FOR corner: INTEGER IN [0..4) DO IF boxParts.corners[corner] THEN SELECT corner FROM 0 => GGShapes.DrawSelectedJoint[dc, tLL ]; 1 => GGShapes.DrawSelectedJoint[dc, tUL ]; 2 => GGShapes.DrawSelectedJoint[dc, tUR ]; 3 => GGShapes.DrawSelectedJoint[dc, tLR ]; ENDCASE => ERROR; ENDLOOP; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN SELECT edge FROM 0 => GGShapes.DrawLine[dc: dc, line: GGLines.LineFromPoints[ tLL, tUL ], clippedBy: tBox, strokeWidth: 2.0*boxData.strokeWidths[0] ]; 1 => GGShapes.DrawLine[dc: dc, line: GGLines.LineFromPoints[ tUL, tUR ], clippedBy: tBox, strokeWidth: 2.0*boxData.strokeWidths[1] ]; 2 => GGShapes.DrawLine[dc: dc, line: GGLines.LineFromPoints[ tUR, tLR ], clippedBy: tBox, strokeWidth: 2.0*boxData.strokeWidths[2] ]; 3 => GGShapes.DrawLine[dc: dc, line: GGLines.LineFromPoints[ tLR, tLL ], clippedBy: tBox, strokeWidth: 2.0*boxData.strokeWidths[3] ]; ENDCASE => ERROR; ENDLOOP; }; IF NOT camera.quality=quality THEN Imager.DoSaveAll[dc, DoDrawFeedback]; }; 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; }; 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 _ -1; 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 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]]; ENDCASE => SIGNAL 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 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]]; }; BoxNewParts: PROC [slice: Slice, mode: SelectMode] RETURNS [parts: SliceParts] = { boxHitData: BoxHitData _ NARROW[slice.hitData]; boxParts: BoxParts _ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE] ] ]; IF boxHitData.corner=-1 AND boxHitData.edge=-1 THEN ERROR; SELECT mode FROM joint => boxParts.corners[boxHitData.corner] _ TRUE; segment => boxParts.edges[boxHitData.edge] _ TRUE; traj, slice, topLevel => MakeComplete[boxParts]; ENDCASE => ERROR; parts _ boxParts; -- RETURN[boxParts] }; 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] ] ]; 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; aPlusB _ newParts; -- 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] ] ]; 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; aMinusB _ newParts; -- RETURN[newParts] }; BoxClosestPoint: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { index: NAT _ 99; scale: REAL; boxData: BoxData _ NARROW[slice.data]; boxHitData: BoxHitData _ NARROW[slice.hitData]; inverse: ImagerTransformation.Transformation _ boxData.inverse; testPoint _ GGTransform.Transform[inverse, testPoint]; [bestDist, index, bestPoint, success] _ GGBoundBox.NearestPoint[boxData.box, testPoint]; bestPoint _ GGTransform.Transform[boxData.transform, bestPoint]; scale _ ImagerTransformation.Factor[boxData.transform].s.x; bestDist _ bestDist*scale; boxHitData.corner _ IF success THEN index ELSE -1; }; BoxClosestSegment: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { seg: NAT _ 99; scale: REAL; boxData: BoxData _ NARROW[slice.data]; boxHitData: BoxHitData _ NARROW[slice.hitData]; inverse: ImagerTransformation.Transformation _ boxData.inverse; testPoint _ GGTransform.Transform[inverse, testPoint]; [bestDist, seg, bestPoint, success] _ GGBoundBox.NearestSegment[boxData.box, testPoint, tolerance]; bestPoint _ GGTransform.Transform[boxData.transform, bestPoint]; scale _ ImagerTransformation.Factor[boxData.transform].s.x; bestDist _ bestDist*scale; boxHitData.edge _ IF success THEN seg ELSE -1; }; 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 ]; }; BoxFilein: PROC [f: IO.STREAM, version: REAL] RETURNS [slice: Slice] = { box: BoundBox; p1, p2: Point; transform: ImagerTransformation.Transformation; GGParseIn.ReadBlank[f]; p1 _ GGParseIn.ReadPoint[f]; GGParseIn.ReadBlank[f]; p2 _ GGParseIn.ReadPoint[f]; GGParseIn.ReadBlank[f]; transform _ GGParseIn.ReadTransformation[f]; box _ GGBoundBox.CreateBoundBox[ p1.x, p1.y, p2.x, p2.y ]; slice _ MakeBoxSlice[box, none, transform].slice; }; 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; }; BoxSetFillColor: PROC [slice: Slice, color: Imager.Color] = { boxData: BoxData _ NARROW[slice.data]; boxData.fillColor _ color; }; 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; }; CircleData: TYPE = REF CircleDataObj; CircleDataObj: TYPE = RECORD [ transform: ImagerTransformation.Transformation, inverse: ImagerTransformation.Transformation, 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 ]; CircleHitData: TYPE = REF CircleHitDataObj; CircleHitDataObj: TYPE = RECORD [ index: [-1..MaxCirclePoints) -- records index of which origin or CP is hit. -1 => no hit ]; BuildCircleSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { class _ NEW[SliceClassObj _ [ type: $Circle, boundBox: CircleBoundBox, copy: CircleCopy, draw: CircleDraw, drawTransform: CircleDrawTransform, drawSelectionFeedback: CircleDrawSelectionFeedback, transform: CircleTransform, emptyParts: CircleEmptyParts, newParts: CircleNewParts, unionParts: CircleUnionParts, differenceParts: CircleDiffParts, pointsInDescriptor: CirclePointsInDescriptor, pointPairsInDescriptor: NoOpPointPairsInDescriptor, nextPoint: CircleNextPoint, nextPointPair: NoOpNextPointPair, closestPoint: CircleClosestPoint, closestPointAndTangent: NIL, closestSegment: CircleClosestSegment, fileout: CircleFileout, filein: CircleFilein, setStrokeWidth: CircleSetStrokeWidth, setStrokeColor: CircleSetStrokeColor, setFillColor: CircleSetFillColor ]]; }; MakeCircleSlice: PUBLIC PROC [origin: Point, outerPoint: Point, strokeWidth: REAL _ 1.0, strokeColor: Imager.Color _ Imager.black, fillColor: Imager.Color _ NIL] RETURNS [sliceD: SliceDescriptor] = { circleData: CircleData _ NEW[CircleDataObj _ [----, ----, outerPoint, strokeWidth, strokeColor, fillColor]]; circleParts: CircleParts _ NEW[CirclePartsObj _ [ALL[FALSE] ] ]; slice: Slice _ NEW[SliceObj _ [ class: FetchSliceClass[$Circle], data: circleData, children: NIL, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], hitData: NEW[CircleHitDataObj _ [index: -1] ] ]]; 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.inverse _ ImagerTransformation.Invert[circleData.transform]; CircleBoundBox[slice]; sliceD _ NEW[SliceDescriptorObj _ [slice, circleParts] ]; }; CircleSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = { circleData: CircleData _ NARROW[slice.data]; circleData.strokeWidth _ strokeWidth; }; CircleSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = { circleData: CircleData _ NARROW[slice.data]; circleData.strokeColor _ color; }; CircleSetFillColor: PROC [slice: Slice, color: Imager.Color] = { circleData: CircleData _ NARROW[slice.data]; circleData.fillColor _ color; }; 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 Problem[msg: "Broken Invariant"]; pointGen.index _ index+1; pointGen.toGo _ pointGen.toGo - 1; }; }; CircleDraw: PROC [slice: Slice, dc: Imager.Context, camera: GGInterfaceTypes.CameraData] = { DoDrawCircleOutline: PROC = { Imager.ConcatT[dc, circleData.transform]; CircleDrawOutline[dc, circleData]; }; DoDrawCircleJoints: PROC = { CircleDrawJointsAux[dc, circleData.transform]; }; circleData: CircleData _ NARROW[slice.data]; Imager.DoSaveAll[dc, DoDrawCircleOutline]; IF camera.quality # quality THEN Imager.DoSaveAll[dc, DoDrawCircleJoints]; }; CircleDrawOutline: PRIVATE PROC [dc: Imager.Context, circleData: CircleData, width: REAL _ 1.0] = { 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]; scale: Imager.VEC _ ImagerTransformation.Factor[circleData.inverse].s; strokeScale: REAL _ MAX[scale.x, scale.y]; Imager.SetColor[dc, circleData.strokeColor]; Imager.SetStrokeWidth[dc, width*circleData.strokeWidth*strokeScale]; Imager.SetStrokeEnd[dc, round]; Imager.MaskStroke[dc, CirclePath, TRUE]; IF circleData.fillColor#NIL THEN Imager.DoSaveAll[dc, FillItIn]; }; CircleDrawJointsAux: PRIVATE PROC [dc: Imager.Context, t: ImagerTransformation.Transformation] = { Imager.SetStrokeWidth[dc, 1.0]; GGShapes.DrawJoint[dc, ImagerTransformation.Transform[t, [0.0, 0.0]] ]; -- center GGShapes.DrawJoint[dc, ImagerTransformation.Transform[t, [-1.0, 0.0]] ]; -- left GGShapes.DrawJoint[dc, ImagerTransformation.Transform[t, [0.0, 1.0]] ]; -- top GGShapes.DrawJoint[dc, ImagerTransformation.Transform[t, [1.0, 0.0]] ]; -- right GGShapes.DrawJoint[dc, ImagerTransformation.Transform[t, [0.0, -1.0]] ]; -- bottom }; CircleDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.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]; }; EasyDrawJoints: PROC = { Imager.ConcatT[dc, transform]; CircleDrawJointsAux[dc, circleData.transform]; }; HardDrawOutline: PROC = { Imager.ConcatT[dc, newTransform]; CircleDrawOutline[dc, circleData]; }; HardDrawJoints: PROC = { CircleDrawJointsAux[dc, newTransform]; }; 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]; IF camera.quality # quality THEN Imager.DoSaveAll[dc, EasyDrawJoints]; 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, GGVector.Magnitude[pointInUnitSpace]]; Imager.DoSaveAll[dc, HardDrawOutline]; IF camera.quality # quality THEN Imager.DoSaveAll[dc, HardDrawJoints]; }; 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.inverse _ ImagerTransformation.Invert[circleData.transform]; CircleBoundBox[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, GGVector.Magnitude[pointInUnitSpace]]; circleData.inverse _ ImagerTransformation.Invert[circleData.transform]; CircleBoundBox[slice]; }; CircleBoundBox: PROC [slice: Slice] = { halfJoint: REAL = GGModelTypes.halfJointSize + 1; circleData: CircleData _ NARROW[slice.data]; 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[slice.boundBox, origin.x-radius-halfJoint, origin.y-radius-halfJoint, origin.x+radius+halfJoint, origin.y+radius+halfJoint]; -- new values }; 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.inverse _ ImagerTransformation.Copy[circleData.inverse]; }; CircleDrawSelectionFeedback: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = { circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; t: ImagerTransformation.Transformation _ circleData.transform; DoDrawFeedback: PROC = { DrawOutlineFeedback: PROC = { Imager.ConcatT[dc, circleData.transform]; CircleDrawOutline[dc, circleData, 2.0]; }; IF IsComplete[circleParts] THEN Imager.DoSaveAll[dc, DrawOutlineFeedback]; -- heavy circle outline FOR index: CirclePoints IN [0..MaxCirclePoints) DO IF circleParts.cpArray[index] THEN GGShapes.DrawSelectedJoint[dc, SELECT index FROM 0 => ImagerTransformation.Transform[t, [0.0, 0.0] ], 1 => ImagerTransformation.Transform[t, [-1.0, 0.0] ], 2 => ImagerTransformation.Transform[t, [0.0, 1.0] ], 3 => ImagerTransformation.Transform[t, [1.0, 0.0] ], 4 => ImagerTransformation.Transform[t, [0.0, -1.0] ], ENDCASE => ERROR]; ENDLOOP; }; IF camera.quality # quality THEN Imager.DoSaveAll[dc, DoDrawFeedback]; }; CircleEmptyParts: PROC [slice: Slice, parts: SliceParts] RETURNS [BOOL] = { circleParts: CircleParts _ NARROW[parts]; RETURN[IsEmpty[circleParts] ]; }; CircleNewParts: PROC [slice: Slice, mode: SelectMode] RETURNS [parts: SliceParts] = { circleHitData: CircleHitData _ NARROW[slice.hitData]; circleParts: CircleParts _ NEW[CirclePartsObj _ [cpArray: ALL[FALSE]] ]; SELECT mode FROM joint => IF circleHitData.index#-1 THEN circleParts.cpArray[circleHitData.index] _ TRUE; segment, traj, topLevel, slice => MakeComplete[circleParts]; 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] ] ]; FOR i: CirclePoints IN CirclePoints DO newParts.cpArray[i] _ circlePartsA.cpArray[i] OR circlePartsB.cpArray[i]; ENDLOOP; 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] ] ]; FOR i: CirclePoints IN CirclePoints DO newParts.cpArray[i] _ IF circlePartsB.cpArray[i] THEN FALSE ELSE circlePartsA.cpArray[i]; ENDLOOP; aMinusB _ newParts; -- RETURN[newParts] }; useBBox: BOOL _ TRUE; PointIsInBox: PRIVATE PROC [test: Point, box: GGBasicTypes.BoundBoxObj] RETURNS [BOOL] = { RETURN[ NOT (test.x < box.loX OR test.x > 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] ]; }; CircleHitDataFromPoint: PRIVATE PROC [point: Point, circleData: CircleData, circleHitData: CircleHitData] = { nextDist, bestDist: REAL; nextPoint, origin, left, top, right, bottom: Point; foundIndex: INTEGER _ 0; [origin, left, top, right, bottom] _ CirclePointsFromData[circleData]; bestDist _ GGVector.Distance[origin, point]; FOR index: INTEGER IN [1..MaxCirclePoints) DO nextPoint _ SELECT index FROM 1 => left, 2 => top, 3 => right, 4 => bottom, ENDCASE => origin; IF (nextDist _ GGVector.Distance[point, nextPoint]) < bestDist THEN { bestDist _ nextDist; foundIndex _ index; }; ENDLOOP; circleHitData.index _ foundIndex; -- and mark the chosen CP }; CircleClosestSegment: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL _ TRUE] = { circleData: CircleData _ NARROW[slice.data]; circleHitData: CircleHitData _ NARROW[slice.hitData]; pointOnCircle: Point _ [0.0, 0.0]; pointInUnitSpace: Point _ ImagerTransformation.Transform[circleData.inverse, testPoint]; dist: REAL _ GGVector.Magnitude[pointInUnitSpace]; IF dist > 0.0 THEN pointOnCircle _ [pointInUnitSpace.x/dist, pointInUnitSpace.y/dist]; bestPoint _ ImagerTransformation.Transform[circleData.transform, pointOnCircle]; bestDist _ GGVector.Distance[testPoint, bestPoint]; CircleHitDataFromPoint[bestPoint, circleData, circleHitData]; -- updates circleHitData in place RETURN; }; CircleClosestPoint: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL _ TRUE] = { circleData: CircleData _ NARROW[slice.data]; circleHitData: CircleHitData _ NARROW[slice.hitData]; nextDist: REAL; nextPoint, origin, left, top, right, bottom: Point; foundIndex: INTEGER _ 0; [origin, left, top, right, bottom] _ CirclePointsFromData[circleData]; bestDist _ GGVector.DistanceSquared[origin, testPoint]; bestPoint _ origin; FOR index: INTEGER IN [1..MaxCirclePoints) DO nextPoint _ SELECT index FROM 1 => left, 2 => top, 3 => right, 4 => bottom, ENDCASE => origin; IF (nextDist _ GGVector.DistanceSquared[testPoint, nextPoint]) < bestDist THEN { bestDist _ nextDist; foundIndex _ index; bestPoint _ nextPoint; }; ENDLOOP; circleHitData.index _ foundIndex; -- and mark the chosen CP bestDist _ RealFns.SqRt[bestDist]; }; CircleFileout: PROC [slice: Slice, f: IO.STREAM] = { circleData: CircleData _ NARROW[slice.data]; GGParseOut.WriteTransformation[f, circleData.transform]; }; CircleFilein: PROC [f: IO.STREAM, version: REAL] RETURNS [slice: Slice] = { origin, outerPoint: Point; transform: ImagerTransformation.Transformation; 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] ]; slice _ MakeCircleSlice[origin, outerPoint].slice; }; BuildIPSliceClass: PROC [] RETURNS [class: SliceClass] = { class _ NEW[SliceClassObj _ [ type: $IP, boundBox: IPBoundBox, draw: IPDraw, drawTransform: IPDrawTransform, drawSelectionFeedback: IPDrawSelectionFeedback, transform: IPTransform, newParts: IPNewPartsProc, unionParts: IPUnionPartsProc, differenceParts: IPDiffPartsProc, pointsInDescriptor: NoOpPointsInDescriptor, pointPairsInDescriptor: NoOpPointPairsInDescriptor, nextPoint: NoOpNextPoint, nextPointPair: NoOpNextPointPair, closestPoint: IPClosestPoint, closestPointAndTangent: NIL, closestSegment: IPClosestSegment, fileout: IPFileout, filein: IPFilein, setStrokeWidth: IPSetStrokeWidth, setStrokeColor: IPSetStrokeColor, setFillColor: IPSetFillColor ]]; }; IPSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = {}; IPSetFillColor: PROC [slice: Slice, color: Imager.Color] = {}; IPSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = { }; MakeIPSlice: PUBLIC PROC [fileName: Rope.ROPE, worldPt: Point] RETURNS [slice: Slice] = { ipData: IPData; feedbackBox: BoundBox; feedbackBox _ GGBoundBox.CreateBoundBox[0,0,0,0]; -- gets real values later in this proc. ipData _ NEW[IPDataObj _ [fileName, worldPt, feedbackBox]]; slice _ NEW[SliceObj _ [ class: FetchSliceClass[$IP], data: ipData, children: NIL, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set next line hitData: NEW[IPHitDataObj _ [] ] ]]; slice.class.boundBox[slice]; }; IPBoundBox: PROC [slice: Slice] = { ipData: IPData _ NARROW[slice.data]; rect: ImagerTransformation.Rectangle; halfCP: REAL = GGModelTypes.halfJointSize + 1; rect.x _ 0.0; rect.y _ 0.0; rect.w _ 300.0; rect.h _ 300.0; GGBoundBox.UpdateBoundBox[slice.boundBox, ipData.worldPt.x + rect.x - halfCP, ipData.worldPt.y + rect.y - halfCP, ipData.worldPt.x + rect.w + halfCP, ipData.worldPt.y + rect.h + halfCP]; GGBoundBox.UpdateBoundBox[ipData.feedbackBox, ipData.worldPt.x + rect.x, ipData.worldPt.y + rect.y, ipData.worldPt.x + rect.w, ipData.worldPt.y + rect.h]; }; IPDraw: PROC [slice: Slice, dc: Imager.Context, camera: CameraData] = { ipData: IPData _ NARROW[slice.data]; }; IPDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { ipData: IPData _ NARROW[slice.data]; tempPoint: Point _ GGTransform.Transform[transform, ipData.worldPt]; }; IPDrawSelectionFeedback: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick: BOOL _ FALSE] = { ipData: IPData _ NARROW[slice.data]; GGBoundBox.DrawBoundBox[dc, ipData.feedbackBox]; }; IPTransform: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = { ipData: IPData _ NARROW[slice.data]; ipData.worldPt _ GGTransform.Transform[transform, ipData.worldPt]; IPBoundBox[slice]; }; IPNewPartsProc: GGModelTypes.SliceNewPartsProc = { }; IPUnionPartsProc: GGModelTypes.SliceUnionPartsProc = { }; IPDiffPartsProc: GGModelTypes.SliceDifferencePartsProc = { }; IPClosestPoint: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { ipData: IPData _ NARROW[slice.data]; [bestDist, ----, bestPoint, success] _ GGBoundBox.NearestPoint[ipData.feedbackBox, testPoint]; }; IPClosestSegment: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { ipData: IPData _ NARROW[slice.data]; ipHitData: IPHitData _ NARROW[slice.hitData]; [bestDist, ipHitData.bestSeg, bestPoint, success] _ GGBoundBox.NearestSegment[ipData.feedbackBox, testPoint, tolerance]; }; IPFileout: PROC [slice: Slice, f: IO.STREAM] = { ipData: IPData _ NARROW[slice.data]; f.PutF["\"%g\"", [rope[ipData.file]]]; GGParseOut.WritePoint[f, ipData.worldPt]; }; IPFilein: PROC [f: IO.STREAM, version: REAL] RETURNS [slice: Slice] = { fileName: Rope.ROPE; point: Point; fileName _ f.GetRopeLiteral[]; GGParseIn.ReadBlank[f]; point _ GGParseIn.ReadPoint[f]; slice _ MakeIPSlice[fileName, point]; }; sliceClasses: LIST OF SliceClassDef; printPrefix: ATOM; screenPrefix: ATOM; MaxCircles: NAT = 3; globalCirclePool: ARRAY [0..MaxCircles-1] OF Circle; globalCirclePoolIndex: NAT; GetCircleFromPool: PROC [] RETURNS [circle: Circle] = { IF globalCirclePoolIndex = MaxCircles THEN ERROR; circle _ globalCirclePool[globalCirclePoolIndex]; globalCirclePoolIndex _ globalCirclePoolIndex + 1; }; ReturnCircleToPool: PROC [circle: Circle] = { IF globalCirclePoolIndex = 0 THEN ERROR; globalCirclePoolIndex _ globalCirclePoolIndex - 1; globalCirclePool[globalCirclePoolIndex] _ circle; }; Init: PRIVATE PROC [] = { textDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Text, class: BuildTextSliceClass[]]]; ipDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $IP, class: BuildIPSliceClass[]]]; boxDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Box, class: BuildBoxSliceClass[]]]; circleDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Circle, class: BuildCircleSliceClass[]]]; sliceClasses _ LIST[circleDef, boxDef, ipDef, textDef]; printPrefix _ Atom.MakeAtom["xerox/pressfonts/"]; screenPrefix _Atom.MakeAtom ["xerox/tiogafonts/"]; FOR i: NAT IN [0..MaxCircles) DO globalCirclePool[i] _ GGCircles.CreateEmptyCircle[]; ENDLOOP; globalCirclePoolIndex _ 0; }; Init[]; END. &pGGSliceImpl.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last edited by Pier on June 24, 1986 5:45:19 pm PDT Last edited by Bier on June 3, 1986 11:40:23 am PDT Contents: Implements various slice classes in Gargoyle. NoOp Class Routines Text slice class procs getStrokeWidth: TextGetStrokeWidth, getStrokeColor: TextGetStrokeColor, getFillColor: TextGetFillColor A fontName is a font designation with family, size, and face, like Helvetica23BI or TimesRoman6. can't user IO.GetInt because a string like Helvetica9BI causes it to fail! A fontName is a font designation with family, size, and face, like Helvetica23BI or TimesRoman6 SetTextColor: PUBLIC PROC [slice: Slice, color: Imager.Color, colorName: Rope.ROPE] = { textData: TextData; IF slice.class.type#$Text THEN RETURN; textData _ NARROW[slice.data]; textData.color _ color; textData.colorName _ colorName; }; GetTextColor: PUBLIC PROC [slice: Slice] RETURNS [color: Imager.Color, colorName: Rope.ROPE] = { textData: TextData; IF slice.class.type#$Text THEN RETURN[NIL, NIL]; textData _ NARROW[slice.data]; RETURN[textData.color, textData.colorName]; }; GGModelTypes.SliceBoundBoxProc ASSERT: extent elements are all positive, so subtract left and descent, add right and ascent boundBoxes are used for refresh and so are calculated from the larger of the two display fonts feedbackBoxes are used for hit testing and so are calculated from the smaller of the two display fonts GGModelTypes.SliceCopyProc IS THIS RIGHT: ?? GGModelTypes.SliceDrawProc GGModelTypes.SliceDrawTransformProc Translates the origin. Rotation is not currently possible. GGModelTypes.SliceTransformProc Translates the origin. Rotation is not currently possible. GGModelTypes.SliceDrawSelectionFeedbackProc DrawTextJoints[slice, dc]; GGModelTypes.SliceClosestPointProc Used for hit testing. See comments in GGSlice.mesa [bestDist, ----, bestPoint] _ GGBoundBox.NearestPoint[textData.feedbackBox, testPoint]; success _ TRUE; GGModelTypes.SliceClosestSegmentProc Used for hit testing. See comments in GGSlice.mesa GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. GGModelTypes.SliceFileinProc Read a description of yourself from stream f. Takes a complex name like "xerox/pressfonts/Helvetica-MIR" and a font size and returns a simple name like Helvetica10I Strings now surronded by quotes. Formerly surrounded by parens. We keep evoloving font names TextGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = { RETURN[0.0]; }; TextGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color] = { RETURN[NIL]; }; TextGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = { textData: TextData _ NARROW[slice.data]; RETURN[textData.color]; }; Box Slice Class transform needed for general case of rotated boxes Filled in by BoxClosestPoint, BoxClosestSegment, in same clockwise order as CornerArray and EdgeArray. -1 means not hit getStrokeWidth: BoxGetStrokeWidth, getStrokeColor: BoxGetStrokeColor, getFillColor: BoxGetFillColor requires a bound box input with loX<=hiX AND loY<=hiY SHOULD THIS RETURN A COPY OF THE BOX ?? Class Procedures GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceCopyProc GGModelTypes.SliceDrawProc called with the dc transformation already set to object coordinates DOES NOT USE DoSaveAll !! box: BoundBox _ boxData.box; BoxDrawJointsAux: PROC [dc: Imager.Context, boxData: BoxData] = { DOES NOT USE DoSaveAll !! box: BoundBox _ boxData.box; t: ImagerTransformation.Transformation _ boxData.transform; tLL: Point _ ImagerTransformation.Transform[t, [box.loX, box.loY] ]; tUL: Point _ ImagerTransformation.Transform[t, [box.loX, box.hiY] ]; tUR: Point _ ImagerTransformation.Transform[t, [box.hiX, box.hiY] ]; tLR: Point _ ImagerTransformation.Transform[t, [box.hiX, box.loY] ]; Imager.SetStrokeWidth[dc, 1.0]; GGShapes.DrawJoint[dc, tLL ]; GGShapes.DrawJoint[dc, tUL ]; GGShapes.DrawJoint[dc, tUR ]; GGShapes.DrawJoint[dc, tLR ]; }; DOES NOT USE DoSaveAll !! 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 total transformation has already occurred on point, oppositePoint More than one corner or more than one edge. 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.SliceDrawSelectionFeedbackProc IF NOT quick AND slice.selectedInFull.normal THEN { -- draw diagonal thru box to mark it line: GGLines.Line _ GGLines.LineFromPoints[tLL, tUR]; GGShapes.DrawLine[dc: dc, line: line, clippedBy: tBox, strokeWidth: 2.0 ]; -- uses DoSaveAll }; IF IsComplete[boxParts] THEN { BoxDrawSelected[slice, dc, camera]; -- uses DoSaveAll RETURN; }; GGModelTypes.SliceEmptyPartsProc GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceClosestPointProc GGModelTypes.SliceClosestSegmentProc GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. GGModelTypes.SliceFileinProc BoxGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = { RETURN[NIL]; }; Circle Slice Class transform needed for general case of rotated circles cpArray[origin, left, top, right, bottom]. 5 element array. getStrokeWidth: CircleGetStrokeWidth, getStrokeColor: CircleGetStrokeColor, getFillColor: CircleGetFillColor GetCircle: PUBLIC PROC [slice: Slice] RETURNS [circle: GGBasicTypes.Circle] = { IF slice.class.type#$Circle THEN RETURN[NIL] ELSE { circleData: CircleData _ NARROW[slice.data]; RETURN[NEW[GGBasicTypes.CircleObj _ [circleData.origin, circleData.radius] ] ]; }; Class Procedures GGModelTypes.SliceDrawProc called with the dc transformation already set to object coordinates DOES NOT USE DoSaveAll !! DOES NOT USE DoSaveAll !! 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. Imager.ConcatT[dc, ImagerTransformation.Concat[newTransform, transform]]; Imager.ConcatT[dc, newTransform]; 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 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 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). CircleDrawSelectionFeedback: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = { GGModelTypes.SliceDrawSelectionFeedbackProc circleData: CircleData _ NARROW[slice.data]; circleParts: CircleParts _ NARROW[parts]; t: ImagerTransformation.Transformation _ circleData.transform; isComplete: BOOL _ FALSE; DoDrawFeedback: PROC = { DrawOutlineFeedback: PROC = { Imager.ConcatT[dc, circleData.transform]; CircleDrawOutline[dc, circleData, 2.0]; }; IF (isComplete _ IsComplete[circleParts]) THEN Imager.DoSaveAll[dc, DrawOutlineFeedback]; -- circle outline IF camera.quality # quality THEN FOR index: CirclePoints IN [0..MaxCirclePoints) DO IF isComplete OR circleParts.cpArray[index] THEN GGShapes.DrawSelectedJoint[dc, SELECT index FROM 0 => ImagerTransformation.Transform[t, [0.0, 0.0] ], 1 => ImagerTransformation.Transform[t, [-1.0, 0.0] ], 2 => ImagerTransformation.Transform[t, [0.0, 1.0] ], 3 => ImagerTransformation.Transform[t, [1.0, 0.0] ], 4 => ImagerTransformation.Transform[t, [0.0, -1.0] ], ENDCASE => ERROR]; ENDLOOP; }; Imager.DoSaveAll[dc, DoDrawFeedback]; }; GGModelTypes.SliceDrawSelectionFeedbackProc GGModelTypes.SliceEmptyPartsProc GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceDifferencePartsProc checks that point may be on a circle CP and updates circleHitData in place. Find hit point on unit circle Transform back to world coords Returns the closest control point and best distance (actually, CircleHitDataFromPoint does all the work) GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. DOES NOT WRITE OUT STYLE INFO YET GGModelTypes.SliceFileinProc DOES NOT READ IN STYLE INFO YET Interpress slice class procs listBoxes: NIL, select: IPSelect, deselect: IPDeselect, GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceDrawProc IF selected THEN GGBoundBox.DrawBoundBox[dc, ipData.feedbackBox]; GGModelTypes.SliceDrawTransformProc GGModelTypes.SliceDrawSelectionFeedbackProc GGModelTypes.SliceTransformProc Store the transformation in the slice. (For now, just allow translation) GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceDifferencePartsProc GGModelTypes.SliceClosestPointProc Used for hit testing. See comments in GGSlice.mesa [bestDist, ----, bestPoint] _ GGBoundBox.NearestPoint[ipData.feedbackBox, testPoint]; success _ TRUE; GGModelTypes.SliceClosestSegmentProc Used for hit testing. See comments in GGSlice.mesa GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. GGModelTypes.SliceFileinProc Read a description of yourself from stream f. To be used to reduce CreateCircle allocations. Κ@ψ˜codešœ™Kšœ Οmœ1™[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]š žœ?žœžœžœžœ 1˜¬K–p[prefix: ATOM, family: ATOM, face: NodeStyle.FontFace, size: REAL, alphabets: NodeStyle.FontAlphabets]šœ›˜›K–>[s1: ROPE, s2: ROPE, pos1: INT _ 0, case: BOOL _ TRUE]šžœNžœžœ+ .˜·Kšœ˜Kšœ!˜!Kšœ˜Kšœ˜Kšœ ˜0šž˜šœ ˜ Kšœ1žœ˜7Kšœ˜K˜——K˜K˜—š ‘ œžœžœžœ(žœ˜`Kšœ˜Kš žœžœžœžœžœ˜0Kšœ žœ ˜Kšžœ(˜.K˜K˜—š‘ œžœžœ5žœ™WKšœ™Kšžœžœžœ™&Kšœ žœ ™Kšœ™Kšœ™K™K™—š ‘ œžœžœžœ'žœ™`Kšœ™Kš žœžœžœžœžœ™0Kšœ žœ ™Kšžœ%™+K™K™—š‘ œžœ˜%K™Kšœžœ ˜(Kšœ0˜0Kšœžœ"˜.KšœM˜MKšœO˜O™\Kš’^™^—šœ)˜)Kšœ7˜7Kšœ4˜4Kšœ8˜8Kšœ4˜4Kš’f™f—šœ/˜/Kšœ-˜-Kšœ*˜*Kšœ.˜.Kšœ*˜*—K˜K˜—š‘œžœžœ˜7Kšœ™Kšœžœ ˜(Kšœ]˜]Kšžœžœžœ™Kšœ˜K˜K˜K˜—š‘œžœ;˜IK™Kšœžœ ˜(Kšœžœžœžœ˜cKšœ;˜;Kšœ#˜#K˜K˜—š‘œžœ~˜•K™#Kšœ;™;Kšœžœ ˜(KšœF˜FKšœžœžœžœ˜cKšœ0˜0Kšœ#˜#K˜K˜—š‘ œžœV˜iK™Kšœ;™;Kšœžœ ˜(KšœF˜FKšœ˜K˜K˜—š‘œžœRžœžœ˜‚K™+Kšœžœ ˜(Kšœ2˜2Kšœ™K˜K˜—š‘œžœ#žœžœ˜IKšžœžœ˜ K˜—K˜š‘ œžœ"žœ˜SKšžœžœ˜ K˜K˜—š‘œžœ8žœ˜lKšžœžœ˜ K˜K˜—š‘ œžœ8žœ˜lKšžœžœ˜ K˜K˜—š ‘œžœ-žœžœ žœžœ˜†K™"Kšœ3™3Kšœžœ ˜(KšœW™WKšœ žœ™Kšœ  œQ˜`K˜K˜—š ‘œžœ-žœžœ žœžœ˜ˆKšœ$™$Kšœ3™3Kšœžœ ˜(Kšœžœ˜1Kšœ|˜|K˜K˜—š‘ œžœžœžœ˜2K™K™.Kšœžœ ˜(Kšœe˜eK˜,K˜+K˜K˜—š ‘ œžœžœžœ žœžœ˜IK™K™-š ‘œžœžœ žœžœ žœ˜^Kšœ Dœ,™vKšœ žœ˜Kšœ( !˜IKšœ  %˜EK–)[s: ROPE, pos: INT _ 0, skip: ROPE]šœU ˜išœ>žœžœž˜NKšœžœ˜%Kšœžœ ˜&Kšœžœ ˜&Kšœžœ ˜'Kšžœž˜K˜—K˜—Kšœ žœ˜%Kšœ žœ˜K˜ K™?šžœžœ˜Kšœ˜K˜—šžœ˜Kšœ#˜#K˜%K˜#K˜—K™šžœžœ ˜3Kšœ˜K˜K˜K˜—šžœžœžœ ,˜NK˜)Kšœ'˜'K˜K˜K˜—šžœžœžœ 7˜YK˜)K˜)K˜*K˜5K˜—šžœ 6˜=K˜)K˜)K˜*Kšœ˜—K˜K˜K˜Kšœ8˜8K˜K˜—Kš‘œžœ0žœ˜Sš‘œžœ#žœžœ™ZKšžœ™ Kšœ™—Kš‘œžœ=˜Uš‘œžœ#žœ™\Kšžœžœ™ Kšœ™—š‘œžœ(˜>Kšœžœ ˜(Kšœ˜Kšœ˜—š‘œžœžœ™GKšœžœ ™(Kšžœ™Kšœ™—K˜Kšœ™Kšœ žœžœ ˜šœ žœžœ˜Kšœ˜Kš’2™2Kšœ/˜/Kšœ. ˜EKšœžœžœžœ $˜HKšœžœžœ $˜PKšœ˜K˜—K˜Kšœ žœžœ ˜!šœ žœžœ˜Kšœ %˜;Kšœ #˜4K˜—K˜Kšœ žœžœ˜%šœžœžœ˜Kš /œH™wKšœ ˜/Kšœ ˜*K˜—K˜š‘œžœžœžœ˜Bšœžœ˜K˜ Kšœ˜K˜Kšœ˜K˜ K˜0Kšœ˜K˜K˜K˜K˜K˜*K˜2K˜K˜ Kšœ˜Kšœžœ˜Kšœ"˜"K˜K˜K˜"K™"K˜"K™"K˜K™K˜—šœ˜K˜—K˜—š ‘ œžœžœwžœžœ˜ΑK™5Kšœ˜Kšœžœχžœ˜”Kš œžœžœžœ žœžœ˜RKš œžœžœžœžœ˜LKšœžœ˜ Kšžœžœžœ,˜NKšžœžœžœ,˜Nšœ žœ ˜K˜Kšœ˜Kšœ žœ˜Kšœžœ˜ Kš œžœžœžœžœ˜-Kšœ-˜-Kšœ žœ.˜:Kšœ˜—Kšœ˜Kšœ žœ-˜9Kšœ˜K˜—š‘œžœžœžœ˜>K™'Kš žœžœžœžœžœžœ˜OKšœ˜K˜—K™–# -- [cluster: GGModelTypes.Cluster]š‘ œžœ˜$Kšœ™Kšœ žœ"˜1Kšœžœ ˜&KšœN˜NKšœv˜vK˜K˜—š‘œžœžœ˜6Kšœ™Kšœ˜Kšœžœ ˜&Kšœ5˜5KšœP˜PKšœ/ ˜FKšœ žœ ˜šžœžœžœžœ˜Kšœ3˜3Kšœ3˜3Kšžœ˜—Kšœ'˜'K˜K˜—š‘œžœL˜YKšœ™š‘œžœ˜Kšœ&˜&Kšœd˜dK˜—š‘œžœ˜Kšœ&˜&Kšœ^˜^K˜—Kšœžœ ˜&K˜'Kšžœžœ'˜GK˜K˜—š‘œžœGžœ ˜jK™CK™Kšœ™Kšœ žœ:˜Kšžœžœžœ˜Kšœ'˜'Kšœ2˜2K˜—Kšœ-˜-Kšœ?˜?Kšœ8˜8Kšœ-˜-Kšœ?˜?Kšœ4˜4Kšœ-˜-Kšœ?˜?Kšœ6˜6Kšœ-˜-Kšœ?˜?Kšœ9˜9K˜K˜—š‘œžœ+™AK™Kšœ™Kšœ;™;KšœD™DKšœD™DKšœD™DKšœD™DKšœ™Kšœ™Kšœ™Kšœ™Kšœ™K™K™—š‘œžœ*˜@K™Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜K˜—š‘ œžœ˜+šžœžœž˜šœ˜Kšœžœžœ˜Kšœžœžœ˜K˜—Kšœ2žœžœ˜ ˜WK˜—š‘œžœ˜KšœA™AKšœ&˜&Kšœ2˜2K˜—š‘œžœ˜KšœA™AKšœ&˜&Kšœ+˜+K˜—Kšžœ žœžœžœ˜'šžœžœ˜Kšœ‘œ˜&Kšžœžœ&˜FKšžœ˜K˜—Kšžœžœžœ ˜5šžœ^žœ˜fKš ;™;Kšœ‘œ˜&Kšžœžœ&˜FKšžœ˜K˜—Kšžœžœžœžœžœžœžœžœ ]˜¦šžœžœ ˜4šžœžœž˜Kšœ_˜_Kšœ_˜_Kšœ_˜_Kšœ_˜_Kšžœžœ˜—Kšœ˜KšœQ˜QKšœE ˜dK˜—šžœžœ žœ ˜5Kšœ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šžœžœ &˜2Kšœ&˜&Kšžœžœ&˜FK˜K˜—š‘ œžœžœ žœ˜@K˜ šžœ žœžœž˜ Kšžœ žœ˜"Kšžœ˜—Kšœ˜K˜—š‘ œžœžœ žœ˜˜₯KšœN˜NKšœA˜AKšœ˜Kšžœ˜K˜—Kšžœžœžœžœžœžœžœžœ ]˜¦šžœžœ ˜4šžœžœž˜Kšœ_˜_Kšœ_˜_Kšœ_˜_Kšœ_˜_Kšžœžœ˜—Kšœ<˜˜>š žœžœžœžœ $™XKšœ6™6KšœK ™\K™—šžœžœ™Kšœ$ ™5Kšžœ™K™—šžœ žœžœž˜ šžœžœžœž˜3Kšœ+˜+Kšœ+˜+Kšœ+˜+Kšœ+˜+Kšžœžœ˜—Kšžœ˜—šžœžœžœž˜šžœžœžœž˜-Kšœ†˜†Kšœ†˜†Kšœ†˜†Kšœ†˜†Kšžœžœ˜—Kšžœ˜—K˜—Kšžœžœžœ&˜HK˜K˜—š‘œžœžœ˜\Kšœžœ˜'Kšœ žœ$žœ ˜Tšžœ žœžœž˜ Kšžœžœ#˜@Kšžœ˜—K˜K˜—š‘œžœžœ'˜hKšœžœ˜'Kšœžœ(žœ ˜\šžœžœžœž˜Kšžœžœ+˜DKšžœ˜—K˜K˜—š‘ œžœžœ.˜cšžœ žœžœžœ˜+Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœ*˜*Kšœ˜Kšœžœ ˜&Kšœžœ˜*Kšœ;˜;Kšœžœ˜Kšœžœ˜šžœ!žœ ž˜6Kšžœžœžœ *˜PKšžœ˜—šžœžœ *˜=Kšœ`˜`Kšœ`˜`Kšœ`˜`Kšœ`˜`Kšžœžœ"˜3—Kšœ ˜ Kšœžœžœžœ  4˜nK˜—K˜K˜—š‘œžœ$žœ6˜wšžœžœžœžœ˜3Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœ.˜.Kšœ˜Kšœžœ ˜&Kšœžœ˜*Kšœ;˜;Kšœžœ˜Kšœžœ˜šžœ%žœ ž˜:Kšžœžœžœ )˜MKšžœ˜—šžœžœ )˜;šœ˜Kšœ\˜\Kšœ\˜\K˜—šœ˜Kšœ\˜\Kšœ\˜\K˜—šœ˜Kšœ\˜\Kšœ\˜\K˜—šœ˜Kšœ\˜\Kšœ\˜\K˜—Kšžœžœ"˜3—Kšœ*˜*Kšœžœžœžœ  3˜uK˜—K˜K˜—š ‘Ÿ œžœ#žœžœ˜HKšœ ™ Kšœžœ  ˜?Kšžœ˜K˜K˜—š‘Ÿœžœ"žœ˜RKšœ™Kšœžœ˜/Kš œžœžœžœ žœžœ˜SKšžœžœžœžœ˜:šžœž˜Kšœ/žœ˜4Kšœ-žœ˜2Kšœ0˜0Kšžœžœ˜—Kšœ ˜%K˜K˜—š‘Ÿ œžœ8žœ˜kKšœ ™ Kšœžœ ˜%Kšœžœ ˜%Kš œžœžœžœ žœžœ˜Sšžœžœžœž˜Kšœ+žœ˜CKšœ'žœ˜=Kšžœ˜—Kšœ ˜&Kšœ˜K˜—š‘ œžœ8žœ˜kKšœžœ ˜%Kšœžœ ˜%Kš œžœžœžœ žœžœ˜Sšžœžœžœž˜Kš œžœžœžœžœ˜SKš œžœžœžœžœ˜MKšžœ˜—Kšœ ˜'K˜K˜—š ‘œžœ-žœžœ žœžœ˜…Kšœ"™"Kšœžœ˜Kšœžœ˜ Kšœžœ ˜&Kšœžœ˜/Kšœ?˜?Kšœ6˜6KšœX˜XKšœ@˜@Kšœ;˜;Kšœ˜Kšœžœ žœžœ˜2K˜K˜—š ‘œžœ-žœžœ žœžœ˜‡Kšœ$™$Kšœžœ˜Kšœžœ˜ Kšœžœ ˜&Kšœžœ˜/Kšœ?˜?Kšœ6˜6Kšœc˜cKšœ@˜@Kšœ;˜;Kšœ˜Kšœžœ žœžœ˜.K˜K˜—š‘ œžœžœžœ˜1Kšœ™K™.Kšœžœ ˜&Kšœ?˜?Kšœ žœžœ˜Kšœ?˜?Kšœ žœžœ˜Kšœ6˜6K˜K˜—š ‘ œžœžœžœ žœžœ˜HKšœ™K˜K˜Kšœ/˜/Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ,˜,Kšœ:˜:Kšœ1˜1K˜K˜—š‘œžœ;˜RKšœžœ˜#Kšœžœ ˜&šžœžœžœž˜Kšžœžœ$˜@Kšžœ˜—Kšœ˜K˜—š‘œžœ(˜=Kšœžœ ˜&Kšœ˜Kšœ˜—š Ÿœžœžœžœžœ™VK˜—š‘œžœ0žœ˜PKšœžœ˜#Kšœžœ ˜&šžœžœžœž˜Kšžœžœ*˜FKšžœ˜—K˜—K™K™Kšœ žœžœ˜%šœžœžœ˜Kš’4™4Kšœ/˜/Kšœ-˜-Kšœ :˜MKšœ žœ˜K˜K˜K˜—K˜Kš‘œžœ˜Kšœžœ #˜NKšœ žœžœ˜'šœžœžœ˜šœ žœžœžœ '˜KKš ;™;—K˜—K˜Kšœžœžœ˜+šœžœžœ˜!Kšœ ;˜XK˜—K˜š‘œžœžœžœ˜Ešœžœ˜K˜Kšœ˜K˜Kšœ˜K˜#K˜3Kšœ˜K˜K˜K˜K˜!K˜-K˜3K˜K˜!Kšœ!˜!Kšœžœ˜Kšœ%˜%K˜K˜K˜%K™%K˜%K™%K˜ K™ K˜—Kšœ˜K˜—š ‘œžœžœ1žœLžœžœ˜ΗKšœžœ œ œ4˜lKšœžœžœžœ˜@šœžœ ˜K˜ Kšœ˜Kšœ žœ˜Kšœžœ˜ Kš œžœžœžœžœ˜-Kšœ-˜-Kšœ žœ!˜-Kšœ˜—Kšžœžœ9 ˜nKšœ˜KšœG˜GKšœ˜Kšœ žœ-˜9Kšœ˜K˜—š‘ œžœžœžœ"™OKšžœžœžœžœ™,šžœ™Kšœžœ ™,KšžœžœE™O—Kšœ™K˜—K™š‘œžœ0žœ˜SKšœžœ ˜,Kšœ%˜%˜K˜——š‘œžœ;˜UKšœžœ ˜,Kšœ˜Kšœ˜K˜—š‘œžœ(˜@Kšœžœ ˜,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šžœžœ"˜3—Kšœ˜Kšœ"˜"K˜—K˜K˜—š‘ œžœL˜\Kšœ™š‘œžœ˜Kšœ)˜)Kšœ"˜"K˜—š‘œžœ˜Kšœ.˜.K˜—Kšœžœ ˜,K˜*Kšžœžœ*˜JK˜K˜—š‘œžœžœ5žœ ˜dK™CK™š‘œžœ˜Kšœ*˜*K–P[context: Imager.Context, path: ImagerPath.PathProc, parity: BOOL _ FALSE]šœ žœ˜%K˜—š‘ œ +˜KKšœ!˜!Kšœ>˜>Kšœ˜—Kšœ˜Kšœ˜Kšœžœ5˜FKšœ žœžœ˜*Kšœ,˜,KšœD˜DK–5[dc: Imager.Context, circle: GGBasicTypes.Circle]šœ˜Kšœ"žœ˜(Kšžœžœžœ ˜@K˜K˜—š‘œžœžœB˜cK™K˜KšœH  ˜QKšœI ˜PKšœH ˜NKšœH ˜PKšœI  ˜RK˜K˜—šŸœžœ˜¨Kšœ#™#K™ΐKšœ žœžœ˜Kšœ žœ˜Kšœ žœ˜K˜0Kšœ2˜2Kšœžœ ˜,Kšœžœ˜)š‘œžœ˜KšœQ˜QKšœ"˜"K˜—š‘œžœ˜Kšœ˜Kšœ.˜.K˜—š‘œžœ˜KšœI™IKšœ!˜!Kšœ"˜"K˜—š‘œžœ˜Kšœ!™!Kšœ&˜&K˜—Kšžœžœ žœ˜/šžœžœž˜2šžœžœ˜$Kšœ˜K˜K˜—Kšžœ˜—šžœ žœ žœ $˜GKšœ&˜&Kšžœžœ&˜FKšžœ˜K˜—K™XKšœ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˜QKšœi˜iKšœ&˜&Kšžœžœ&˜FK˜K˜—š‘œžœV˜kKšœ™Kšœd™dKšœ žœžœ˜Kšœ žœ˜Kšœ žœ˜K˜0Kšœžœ ˜,Kšœžœ˜)Kšžœžœ žœ˜/šžœžœž˜2šžœžœ˜$Kšœ˜K˜K˜—Kšžœ˜—šžœ žœ žœ .˜QKšœT˜TKšœG˜GKšœ˜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šœq˜qKšœG˜GKšœ˜K˜K˜—–# -- [cluster: GGModelTypes.Cluster]š‘œžœ˜'Kšœ™Kšœ žœ"˜1Kšœžœ ˜,KšœB™BK˜QK˜PK˜PKšœžœžœT˜sKšœ—  ˜€K˜K˜—š‘ œžœžœ˜9Kšœ™Kšœžœ ˜,K˜Kšœ}˜}K™οKšœ žœ ˜K˜DK˜@K˜K˜—šŸœžœcžœ™Kšœ+™+Kšœžœ ™,Kšœžœ™)Kšœ>™>Kšœ žœžœ™š‘œžœ™š‘œžœ™Kšœ)™)K™'K™—Kšžœ(žœ, ™kš žœžœžœžœž™Sš žœ žœžœ žœž™aK™4K™5K™4K™4K™5Kšžœžœ™—Kšžœ™—K™—K™%K™K™—šŸœžœcžœ˜Kšœ+™+Kšœžœ ˜,Kšœžœ˜)Kšœ>˜>š‘œžœ˜š‘œžœ˜Kšœ)˜)K˜'K˜—Kšžœžœ, ˜bšžœžœž˜2šžœžœ žœž˜SK˜4K˜5K˜4K˜4K˜5Kšžœžœ˜—Kšžœ˜—K˜—Kšžœžœ&˜FK˜K˜—š ‘Ÿ œžœ#žœžœ˜KKšœ ™ Kšœžœ˜)Kšžœ˜K˜K˜—š‘Ÿœžœ"žœ˜UKšœ™Kšœžœ˜5Kšœžœžœžœ˜Hšžœž˜Kšœ žœžœ,žœ˜XKšœ<˜˜>K˜8Kšœ7˜7Kšœ5˜5Kšœ7˜7Kšœ9˜9Kšœ˜K˜—š‘œžœžœJ˜nKšœK™KKšœžœ˜Kšœ3˜3Kšœ žœ˜KšœF˜FKšœ,˜,šžœžœžœž˜-šœ žœž˜K˜ K˜ K˜ K˜ Kšžœ ˜—šžœ=žœ˜EKšœ˜K˜K˜—Kšžœ˜—Kšœ" ˜;Kšœ˜K˜—š‘œžœ-žœžœ žœžœžœ˜‘Kšœžœ ˜,Kšœžœ˜5K˜"K˜XKšœžœ(˜2K™Kšžœ žœD˜VK™K˜PK˜3Kšœ> !˜_Kšžœ˜K˜K˜—š‘œžœ-žœžœ žœžœžœ˜K™hKšœžœ ˜,Kšœžœ˜5Kšœ žœ˜Kšœ3˜3Kšœ žœ˜KšœF˜FKšœ7˜7K˜šžœžœžœž˜-šœ žœž˜K˜ K˜ K˜ K˜ Kšžœ ˜—šžœHžœ˜PKšœ˜K˜K˜K˜—Kšžœ˜—Kšœ" ˜;K˜"K˜K˜—K˜š‘ œžœžœžœ˜4Kšœ™K™.Kšœžœ ˜,šœ8˜8Kš’!™!—K˜K™—š ‘ œžœžœžœ žœžœ˜KKšœ™K˜Kšœ/˜/Kšœ˜Kšœ,˜,K–4[m: ImagerTransformation.Transformation, v: VEC]šœF˜F–4[m: ImagerTransformation.Transformation, v: VEC]šœJ˜JKš’™—Kšœ2˜2K˜—K˜K™š‘œžœžœ˜:šœžœ˜K˜ Kšœ˜Kšœ žœ™Kšœ ˜ K˜K™K™K˜/Kšœ˜K˜K˜K˜!K˜+K˜3K˜K˜!Kšœ˜Kšœžœ˜Kšœ!˜!K˜K˜K˜!K˜!K˜K˜—K˜K˜—Kš‘œžœ=˜SKš‘œžœ*˜>š‘œžœ0žœ˜O˜K˜——š ‘ œžœžœžœžœ˜YKšœ˜Kšœ˜Kšœ2 '˜YKšœ žœ/˜;šœžœ ˜K˜Kšœ ˜ Kšœ žœ˜Kšœžœ˜ Kš œžœžœžœžœ˜-Kšœ. ˜CKšœ žœ˜ Kšœ˜—Kšœ˜K˜K˜—š‘ œžœ˜#K™Kšœžœ ˜$K˜%Kšœžœ"˜.K˜K˜šœ)˜)Kšœ#˜#Kšœ#˜#Kšœ#˜#Kšœ$˜$—šœ-˜-Kšœ˜Kšœ˜Kšœ˜Kšœ˜—K˜K˜—š‘œžœ;˜GK™Kšœžœ ˜$Kšžœ žœ1™AK˜K˜—š‘œžœ~˜“K™#Kšœžœ ˜$KšœD˜DK˜K˜—š‘œžœRžœžœ˜€K™+Kšœžœ ˜$Kšœ0˜0K˜K˜—š‘ œžœV˜gK™J™IKšœžœ ˜$KšœB˜BKšœ˜K˜K˜—š‘œ’œ˜2Kšœ™K˜K˜—š‘œ’œ˜6Kšœ ™ K˜K˜—š‘œ’œ˜:Kšœ%™%K˜K˜—š ‘œžœ-žœžœ žœžœ˜„K™"Kšœ3™3Kšœžœ ˜$KšœU™UKšœ žœ™Kšœ  œO˜^K˜K˜—š ‘œžœ-žœžœ žœžœ˜‡Kšœ$™$Kšœ3™3Kšœžœ ˜$Kšœžœ˜-Kšœx˜xK˜K˜—š‘ œžœžœžœ˜0K™K™.Kšœžœ ˜$Kšœ&˜&Kšœ)˜)K˜K˜—š ‘œžœžœžœ žœžœ˜GK™K™-Kšœžœ˜K˜ Kšœ˜K˜K˜Kšœ%˜%K˜K˜—Kšœžœžœ˜$Kšœ žœ˜Kšœžœ˜K˜Kš‘ œžœ˜Kšœžœžœ˜4Kšœžœ˜K˜š‘œžœžœ˜7K™.Kšžœ$žœžœ˜1Kšœ1˜1K˜2K˜K˜—š‘œžœ˜-Kšžœžœžœ˜(K˜2Kšœ1˜1K˜K˜—š‘œžœžœ˜KšœžœA˜]Kšœžœ=˜WKšœžœ?˜ZKšœžœE˜cKšœžœ$˜7K˜Kšœ1˜1Kšœ2˜2K˜šžœžœžœž˜ Kšœ4˜4Kšžœ˜—Kšœ˜K˜—K˜K˜K˜Kšžœ˜—…—ξΞV6