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.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];
};
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: 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];
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:
BOOL ←
FALSE] = {
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: 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] = {
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: 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];
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
};
};
Box
EmptyParts:
PROC [slice: Slice, parts: SliceParts]
RETURNS [
BOOL] = {
GGModelTypes.SliceEmptyPartsProc
boxParts: BoxParts ← NARROW[parts]; -- NARROW for type checking
RETURN[IsEmpty[boxParts]];
};
Box
NewParts:
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]
};
Box
UnionParts:
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: 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] = {
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: 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, 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: 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;
};
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: 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];
};
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];
};
Circle
EmptyParts:
PROC [slice: Slice, parts: SliceParts]
RETURNS [
BOOL] = {
GGModelTypes.SliceEmptyPartsProc
circleParts: CircleParts ← NARROW[parts];
RETURN[IsEmpty[circleParts] ];
};
Circle
NewParts:
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]
};
Circle
UnionParts:
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]
};
Circle
DiffParts:
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: 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] = {
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:
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];
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:
BOOL ←
TRUE] = {
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:
BOOL ←
FALSE] = {
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.