GGSliceImpl.mesa
Copyright © 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.
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.ROPENIL,
worldPt: Point,
fontName: Rope.ROPENIL, -- 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.ROPENIL,
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];
};
NoOp Class Routines
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;
};
Text slice class procs
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,
getStrokeWidth: TextGetStrokeWidth,
setStrokeColor: TextSetStrokeColor,
getStrokeColor: TextGetStrokeColor,
setFillColor: TextSetFillColor
getFillColor: TextGetFillColor
]];
};
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] = {
A fontName is a font designation with family, size, and face, like Helvetica23BI or TimesRoman6.
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: BOOLFALSE;
fontStream: IO.STREAMIO.RIS[fontName];
[familyName, ----] ← IO.GetTokenRope[stream: fontStream, breakProc: DigitBreak ! IO.EndOfStream => SIGNAL FontNameError];
fontSize ← GetNumber[fontStream ! IO.EndOfStream => SIGNAL FontNameError];
can't user IO.GetInt because a string like Helvetica9BI causes it to fail!
[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] = {
A fontName is a font designation with family, size, and face, like Helvetica23BI or TimesRoman6
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];
};
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];
};
TextBoundBox: PROC [slice: Slice] = {
GGModelTypes.SliceBoundBoxProc
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];
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
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];
feedbackBoxes are used for hit testing and so are calculated from the smaller of the two display fonts
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] = {
GGModelTypes.SliceCopyProc
textData: TextData ← NARROW[slice.data];
copy ← MakeTextSlice[textData.rope, textData.fontName, textData.colorName, textData.worldPt];
IS THIS RIGHT: ??
copy.parent ← slice.parent;
copy.children ← slice.children;
};
TextDraw: PROC [slice: Slice, dc: Imager.Context, camera: CameraData] = {
GGModelTypes.SliceDrawProc
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] = {
GGModelTypes.SliceDrawTransformProc
Translates the origin. Rotation is not currently possible.
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] = {
GGModelTypes.SliceTransformProc
Translates the origin. Rotation is not currently possible.
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: BOOLFALSE] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
textData: TextData ← NARROW[slice.data];
GGBoundBox.DrawBoundBox[dc, textData.feedbackBox];
DrawTextJoints[slice, dc];
};
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] = {
GGModelTypes.SliceClosestPointProc
Used for hit testing. See comments in GGSlice.mesa
textData: TextData ← NARROW[slice.data];
[bestDist, ----, bestPoint] ← GGBoundBox.NearestPoint[textData.feedbackBox, testPoint];
success ← TRUE;
[bestDist, ----, bestPoint, success] ← GGBoundBox.NearestPoint[textData.feedbackBox, testPoint];
};
TextClosestSegment: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = {
GGModelTypes.SliceClosestSegmentProc
Used for hit testing. See comments in GGSlice.mesa
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] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
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] = {
GGModelTypes.SliceFileinProc
Read a description of yourself from stream f.
UnpackComplexFontName: PROC [fontName: Rope.ROPE, fontSize: REAL] RETURNS [new: Rope.ROPE] = {
Takes a complex name like "xerox/pressfonts/Helvetica-MIR" and a font size and returns a simple name like Helvetica10I
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;
Strings now surronded by quotes. Formerly surrounded by parens.
IF version > 8601.22 THEN {
rope ← f.GetRopeLiteral[];
}
ELSE {
GGParseIn.ReadBlankAndRope[f, "("];
rope ← GGParseIn.ReadBlankAndWord[f];
GGParseIn.ReadBlankAndRope[f, ")"];
};
We keep evoloving font names
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] = {};
TextGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = {
RETURN[0.0];
};
TextSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = {};
TextGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color] = {
RETURN[NIL];
};
TextSetFillColor: PROC [slice: Slice, color: Imager.Color] = {
textData: TextData ← NARROW[slice.data];
textData.color ← color;
};
TextGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = {
textData: TextData ← NARROW[slice.data];
RETURN[textData.color];
};
Box Slice Class
BoxData: TYPE = REF BoxDataObj;
BoxDataObj: TYPE = RECORD [
box: BoundBox,
transform needed for general case of rotated boxes
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 [
Filled in by BoxClosestPoint, BoxClosestSegment, in same clockwise order as CornerArray and EdgeArray. -1 means not hit
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,
getStrokeWidth: BoxGetStrokeWidth,
setStrokeColor: BoxSetStrokeColor,
getStrokeColor: BoxGetStrokeColor,
setFillColor: BoxSetFillColor
getFillColor: BoxGetFillColor
]];
};
MakeBoxSlice: PUBLIC PROC [box: BoundBox, corner: Corner, transform: ImagerTransformation.Transformation ← GGTransform.Identity[], strokeWidth: REAL ← 1.0] RETURNS [sliceD: SliceDescriptor] = {
requires a bound box input with loX<=hiX AND loY<=hiY
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: INTEGERSELECT 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] = {
SHOULD THIS RETURN A COPY OF THE BOX ??
RETURN[IF slice.class.type#$Box THEN NIL ELSE NARROW[slice.data, BoxData].box];
};
Class Procedures
BoxBoundBox: PROC [slice: Slice] = {
GGModelTypes.SliceBoundBoxProc
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] = {
GGModelTypes.SliceCopyProc
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] = {
GGModelTypes.SliceDrawProc
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] = {
called with the dc transformation already set to object coordinates
DOES NOT USE DoSaveAll !!
box: BoundBox ← boxData.box;
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, 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 ];
};
BoxDrawJointsAux: PROC [dc: Imager.Context, from, to: Point] = {
DOES NOT USE DoSaveAll !!
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] = {
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.
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 = {
total transformation has already occurred on point, oppositePoint
Imager.ConcatT[dc, boxData.transform];
BoxDrawOutline[dc, boxData, point, oppositePoint];
};
HardDrawJoints: PROC = {
total transformation has already occurred on point, oppositePoint
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 {
More than one corner or more than one edge. Full transform.
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] = {
GGModelTypes.SliceTransformProc
Permanently transforms the box. Depending on which parts are selected, the points are transformed and the box is grown/shrunk properly.
point, oppositePoint: ImagerTransformation.VEC; -- really points, not vectors
boxData: BoxData ← NARROW[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] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
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: REALMIN[tLL.x, tUL.x, tUR.x, tLR.x];
lY: REALMIN[tLL.y, tUL.y, tUR.y, tLR.y];
hX: REALMAX[tLL.x, tUL.x, tUR.x, tLR.x];
hY: REALMAX[tLL.y, tUL.y, tUR.y, tLR.y];
tBox: ImagerTransformation.Rectangle ← [lX, lY, hX-lX, hY-lY];
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;
};
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] = {
GGModelTypes.SliceEmptyPartsProc
boxParts: BoxParts ← NARROW[parts]; -- NARROW for type checking
RETURN[IsEmpty[boxParts]];
};
BoxNewParts: PROC [slice: Slice, mode: SelectMode] RETURNS [parts: SliceParts] = {
GGModelTypes.SliceNewPartsProc
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] = {
GGModelTypes.SliceUnionPartsProc
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] = {
GGModelTypes.SliceClosestPointProc
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] = {
GGModelTypes.SliceClosestSegmentProc
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] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
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] = {
GGModelTypes.SliceFileinProc
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;
};
BoxGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = { RETURN[NIL]; };
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;
};
Circle Slice Class
CircleData: TYPE = REF CircleDataObj;
CircleDataObj: TYPE = RECORD [
transform needed for general case of rotated circles
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
cpArray[origin, left, top, right, bottom]. 5 element array.
];
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,
getStrokeWidth: CircleGetStrokeWidth,
setStrokeColor: CircleSetStrokeColor,
getStrokeColor: CircleGetStrokeColor,
setFillColor: CircleSetFillColor
getFillColor: CircleGetFillColor
]];
};
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] ];
};
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
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] = {
GGModelTypes.SliceDrawProc
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] = {
called with the dc transformation already set to object coordinates
DOES NOT USE DoSaveAll !!
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: REALMAX[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] = {
DOES NOT USE DoSaveAll !!
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] = {
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.
isOrigin: BOOLFALSE;
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, ImagerTransformation.Concat[newTransform, transform]];
Imager.ConcatT[dc, newTransform];
CircleDrawOutline[dc, circleData];
};
HardDrawJoints: PROC = {
Imager.ConcatT[dc, newTransform];
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;
};
Only one cp is selected OR no selections for a brand new circle. Only rubberband radius
originPoint ← ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]];
IF cpCount = 0 AND NOT isOrigin THEN {
This is the special case of a new circle being rubberbanded
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] = {
GGModelTypes.SliceTransformProc
Permanently transforms the circle. Depending on which parts are selected, the circle is transformed.
isOrigin: BOOLFALSE;
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;
};
Only one cp is selected OR no selections for a brand new circle
originPoint ← ImagerTransformation.Transform[circleData.transform, [0.0, 0.0]];
IF cpCount = 0 AND NOT isOrigin THEN {
This is the special case of a new circle being rubberbanded
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] = {
GGModelTypes.SliceBoundBoxProc
halfJoint: REAL = GGModelTypes.halfJointSize + 1;
circleData: CircleData ← NARROW[slice.data];
since the circle can be an ellipse, we just can't find the radius.
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] = {
GGModelTypes.SliceCopyProc
circleData: CircleData ← NARROW[slice.data];
newData: CircleData;
copy ← MakeCircleSlice [[0.0, 0.0], [1.0, 1.0], circleData.strokeWidth, circleData.strokeColor, circleData.fillColor].slice;
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).
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] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
circleData: CircleData ← NARROW[slice.data];
circleParts: CircleParts ← NARROW[parts];
t: ImagerTransformation.Transformation ← circleData.transform;
isComplete: BOOLFALSE;
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];
};
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;
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] = {
GGModelTypes.SliceEmptyPartsProc
circleParts: CircleParts ← NARROW[parts];
RETURN[IsEmpty[circleParts] ];
};
CircleNewParts: PROC [slice: Slice, mode: SelectMode] RETURNS [parts: SliceParts] = {
GGModelTypes.SliceNewPartsProc
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] = {
GGModelTypes.SliceUnionPartsProc
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] = {
GGModelTypes.SliceDifferencePartsProc
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: BOOLTRUE;
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] = {
checks that point may be on a circle CP and updates circleHitData in place.
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: BOOLTRUE] = {
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];
Find hit point on unit circle
IF dist > 0.0 THEN pointOnCircle ← [pointInUnitSpace.x/dist, pointInUnitSpace.y/dist];
Transform back to world coords
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: BOOLTRUE] = {
Returns the closest control point and best distance (actually, CircleHitDataFromPoint does all the work)
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] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
circleData: CircleData ← NARROW[slice.data];
GGParseOut.WriteTransformation[f, circleData.transform];
DOES NOT WRITE OUT STYLE INFO YET
};
CircleFilein: PROC [f: IO.STREAM, version: REAL] RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
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] ];
DOES NOT READ IN STYLE INFO YET
slice ← MakeCircleSlice[origin, outerPoint].slice;
};
Interpress slice class procs
BuildIPSliceClass: PROC [] RETURNS [class: SliceClass] = {
class ← NEW[SliceClassObj ← [
type: $IP,
boundBox: IPBoundBox,
listBoxes: NIL,
draw: IPDraw,
drawTransform: IPDrawTransform,
select: IPSelect,
deselect: IPDeselect,
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] = {
GGModelTypes.SliceBoundBoxProc
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] = {
GGModelTypes.SliceDrawProc
ipData: IPData ← NARROW[slice.data];
IF selected THEN GGBoundBox.DrawBoundBox[dc, ipData.feedbackBox];
};
IPDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
ipData: IPData ← NARROW[slice.data];
tempPoint: Point ← GGTransform.Transform[transform, ipData.worldPt];
};
IPDrawSelectionFeedback: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick: BOOLFALSE] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
ipData: IPData ← NARROW[slice.data];
GGBoundBox.DrawBoundBox[dc, ipData.feedbackBox];
};
IPTransform: PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceTransformProc
Store the transformation in the slice. (For now, just allow translation)
ipData: IPData ← NARROW[slice.data];
ipData.worldPt ← GGTransform.Transform[transform, ipData.worldPt];
IPBoundBox[slice];
};
IPNewPartsProc: GGModelTypes.SliceNewPartsProc = {
GGModelTypes.SliceNewPartsProc
};
IPUnionPartsProc: GGModelTypes.SliceUnionPartsProc = {
GGModelTypes.SliceUnionPartsProc
};
IPDiffPartsProc: GGModelTypes.SliceDifferencePartsProc = {
GGModelTypes.SliceDifferencePartsProc
};
IPClosestPoint: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = {
GGModelTypes.SliceClosestPointProc
Used for hit testing. See comments in GGSlice.mesa
ipData: IPData ← NARROW[slice.data];
[bestDist, ----, bestPoint] ← GGBoundBox.NearestPoint[ipData.feedbackBox, testPoint];
success ← TRUE;
[bestDist, ----, bestPoint, success] ← GGBoundBox.NearestPoint[ipData.feedbackBox, testPoint];
};
IPClosestSegment: PROC [slice: Slice, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = {
GGModelTypes.SliceClosestSegmentProc
Used for hit testing. See comments in GGSlice.mesa
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] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
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] = {
GGModelTypes.SliceFileinProc
Read a description of yourself from stream f.
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] = {
To be used to reduce CreateCircle allocations.
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 𡤊tom.MakeAtom ["xerox/tiogafonts/"];
FOR i: NAT IN [0..MaxCircles) DO
globalCirclePool[i] ← GGCircles.CreateEmptyCircle[];
ENDLOOP;
globalCirclePoolIndex ← 0;
};
Init[];
END.