GGOutlineImplA.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Bier on June 2, 1986 5:10:01 pm PDT
Contents: Procedures to implement the Outline Slice Class in Gargoyle. Outlines consist of Trajectories which in turn consist of Segments. Outline is the most important slice class.
DIRECTORY
AtomButtonsTypes, Feedback, GGBasicTypes, GGBoundBox, GGInterfaceTypes, GGModelTypes, GGScene, GGOutline, GGParseIn, GGParseOut, GGSegmentTypes, GGSelect, GGSequence, GGShapes, GGTraj, GGUtility, Imager, ImagerColor, ImagerPath, ImagerTransformation, IO, Rope, Vectors2d, ViewerClasses;
GGOutlineImplA:
CEDAR
PROGRAM
IMPORTS Feedback, GGBoundBox, GGScene, GGOutline, GGParseIn, GGParseOut, GGSequence, GGShapes, GGTraj, GGUtility, Imager, ImagerColor, ImagerPath, ImagerTransformation, IO, Vectors2d
EXPORTS GGOutline = BEGIN
OPEN GGOutline;
BitVector: TYPE = GGBasicTypes.BitVector;
BoundBox: TYPE = GGBasicTypes.BoundBox;
CameraData: TYPE = GGModelTypes.CameraData;
Circle: TYPE = GGBasicTypes.Circle;
Color: TYPE = Imager.Color;
DefaultData: TYPE = GGModelTypes.DefaultData;
FeedbackData: TYPE = AtomButtonsTypes.FeedbackData;
StrokeJoint: TYPE = Imager.StrokeJoint;
StrokeEnd: TYPE = Imager.StrokeEnd;
ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator;
FeatureData: TYPE = GGInterfaceTypes.FeatureData;
GGData: TYPE = GGInterfaceTypes.GGData;
Joint: TYPE = GGSegmentTypes.Joint;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
Line: TYPE = GGBasicTypes.Line;
AlignBag: TYPE = GGInterfaceTypes.AlignBag;
Outline: TYPE = REF OutlineObj;
OutlineObj: TYPE = GGModelTypes.OutlineObj;
OutlineData: TYPE = REF OutlineDataObj;
OutlineDataObj: TYPE = GGOutline.OutlineDataObj;
OutlineClass: TYPE = REF OutlineClassObj;
OutlineClassObj: TYPE = GGModelTypes.OutlineClassObj;
OutlineDescriptor: TYPE = GGModelTypes.OutlineDescriptor;
OutlineHitData: TYPE = REF OutlineHitDataObj;
OutlineHitDataObj: TYPE = GGOutline.OutlineHitDataObj;
OutlineParts: TYPE = REF OutlinePartsObj;
OutlinePartsObj: TYPE = GGOutline.OutlinePartsObj;
OutlineSequence: TYPE = GGSelect.OutlineSequence;
OutlineSequenceGenerator: TYPE = GGSelect.OutlineSequenceGenerator;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SelectMode: TYPE = GGModelTypes.SelectMode;
SelectionClass: TYPE = GGSegmentTypes.SelectionClass;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceOfReal: TYPE = GGBasicTypes.SequenceOfReal;
SliceClass: TYPE = REF SliceClassObj;
SliceClassObj: TYPE = GGModelTypes.SliceClassObj;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceParts: TYPE = GGModelTypes.SliceParts;
Traj: TYPE = REF TrajObj;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
TrajGenerator: TYPE = REF TrajGeneratorObj;
TrajGeneratorObj: TYPE = GGModelTypes.TrajGeneratorObj;
TrajObj: TYPE = GGModelTypes.TrajObj;
TrajPartType: TYPE = GGModelTypes.TrajPartType;
TriggerBag: TYPE = GGInterfaceTypes.TriggerBag;
Vector: TYPE = GGBasicTypes.Vector;
Viewer: TYPE = ViewerClasses.Viewer;
OutlineDescribeProc: TYPE = GGModelTypes.OutlineDescribeProc;
OutlineFileoutProc: TYPE = GGModelTypes.OutlineFileoutProc;
OutlineFileinProc: TYPE = GGModelTypes.OutlineFileinProc;
OutlineUnionPartsProc: TYPE = GGModelTypes.OutlineUnionPartsProc;
OutlineDifferencePartsProc: TYPE = GGModelTypes.OutlineDifferencePartsProc;
OutlineAugmentPartsProc: TYPE = GGModelTypes.OutlineAugmentPartsProc;
OutlinePointsInDescriptorProc: TYPE = GGModelTypes.OutlinePointsInDescriptorProc;
OutlinePointPairsInDescriptorProc: TYPE = GGModelTypes.OutlinePointPairsInDescriptorProc;
OutlineNextPointProc: TYPE = GGModelTypes.OutlineNextPointProc;
OutlineNextPointPairProc: TYPE = GGModelTypes.OutlineNextPointPairProc;
OutlineClosestPointProc: TYPE = GGModelTypes.OutlineClosestPointProc;
OutlineClosestPointAndTangentProc: TYPE = GGModelTypes.OutlineClosestPointAndTangentProc;
OutlineClosestSegmentProc: TYPE = GGModelTypes.OutlineClosestSegmentProc;
OutlineSetArrowsProc: TYPE = GGModelTypes.OutlineSetArrowsProc;
OutlineGetArrowsProc: TYPE = GGModelTypes.OutlineGetArrowsProc;
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
fillColor: PUBLIC Imager.Color ← Imager.MakeGray[0.5];
The Outline Class
BuildOutlineSliceClass:
PUBLIC PROC []
RETURNS [class: OutlineClass] = {
class ←
NEW[OutlineClassObj ← [
type: $Outline,
Fundamentals
getBoundBox: OutlineBoundBox,
getTightBox: OutlineTightBox,
copy: OutlineCopy,
Drawing
drawParts: OutlineDrawParts,
drawTransform: OutlineDrawTransform,
drawSelectionFeedback: OutlineDrawSelectionFeedback,
drawAttractorFeedback: OutlineDrawAttractorFeedback,
Transforming
transform: OutlineTransform,
Textual Description
describe: OutlineDescribe,
describeHit: OutlineDescribeHit,
fileout: OutlineFileout,
filein: OutlineFilein,
Parts
emptyParts: OutlineEmptyParts,
newParts: OutlineNewParts,
unionParts: OutlineUnionParts,
differenceParts: OutlineDifferenceParts,
movingParts: OutlineMovingParts,
augmentParts: OutlineAugmentParts,
setSelectedFields: OutlineSetSelectedFields,
Part Generators
pointsInDescriptor: OutlinePointsInDescriptor,
pointPairsInDescriptor: OutlinePointPairsInDescriptor,
nextPoint: OutlineNextPoint,
nextPointPair: OutlineNextPointPair,
Hit Testing
closestPoint: OutlineClosestPoint,
closestJointToHitData: OutlineClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: OutlineClosestSegment,
lineIntersection: OutlineLineIntersection,
circleIntersection: OutlineCircleIntersection,
hitDataAsSimpleCurve: OutlineHitDataAsSimpleCurve,
Style
setDefaults: OutlineSetDefaults,
setStrokeWidth: OutlineSetStrokeWidth,
getStrokeWidth: OutlineGetStrokeWidth,
setStrokeEnd: OutlineSetStrokeEnd,
getStrokeEnd: OutlineGetStrokeEnd,
setStrokeJoint: OutlineSetStrokeJoint,
getStrokeJoint: OutlineGetStrokeJoint,
setStrokeColor: OutlineSetStrokeColor,
getStrokeColor: OutlineGetStrokeColor,
setFillColor: OutlineSetFillColor,
getFillColor: OutlineGetFillColor,
setArrows: NoOpSetArrows,
getArrows: NoOpGetArrows,
setDashed: OutlineSetDashed,
getDashed: OutlineGetDashed
]];
};
FetchSliceClass: PUBLIC PROC [name: ATOM] RETURNS [class: OutlineClass] = {
class ← globalOutlineClass;
};
FetchOutlineClass: PROC [] RETURNS [class: OutlineClass] = {
class ← globalOutlineClass;
};
CreateOutline:
PUBLIC
PROC [traj: Traj, fillColor: Color]
RETURNS [outline: Outline] = {
boundBox: BoundBox ← GGBoundBox.CopyBoundBox[traj.boundBox];
data: OutlineData ← NEW[OutlineDataObj ← [fillColor: fillColor, children: LIST[traj]] ];
outline ←
NEW[OutlineObj ← [
class: GGSlice.FetchSliceClass[$Outline],
data: data,
selectedInFull: [FALSE, FALSE, FALSE],
boundBox: boundBox]];
outline.nullDescriptor ← GGSlice.DescriptorFromParts[outline, CreateEmptyParts[outline]];
traj.parent ← outline;
IF traj.role = hole THEN traj.role ← fence;
};
Fundamentals
OutlineBoundBox:
PROC [slice: Outline, parts: SliceParts]
RETURNS [box: BoundBox] = {
IF parts = NIL THEN box ← slice.boundBox
ELSE {
outlineParts: OutlineParts ← NARROW[parts];
boxList: LIST OF BoundBox ← NIL;
thisBox: BoundBox;
FOR list:
LIST
OF Sequence ← outlineParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first #
NIL
THEN {
thisBox ← GGSequence.ComputeBoundBox[list.first];
boxList ← CONS[thisBox, boxList];
};
ENDLOOP;
box ← GGBoundBox.BoundBoxOfBoxes[boxList];
};
};
OutlineTightBox:
PROC [slice: Outline, parts: SliceParts]
RETURNS [box: BoundBox] = {
outlineParts: OutlineParts;
boxList: LIST OF BoundBox ← NIL;
thisBox: BoundBox;
IF parts = NIL THEN parts ← slice.class.newParts[slice, NIL, slice].parts;
outlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← outlineParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first #
NIL
THEN {
thisBox ← GGSequence.ComputeTightBox[list.first];
boxList ← CONS[thisBox, boxList];
};
ENDLOOP;
box ← GGBoundBox.BoundBoxOfBoxes[boxList];
};
UpdateBoundBox:
PUBLIC PROC [slice: Outline] = {
Computes it from traj boxes.
outlineBoxes: LIST OF BoundBox ← NIL;
trajGen: TrajGenerator;
IF slice=NIL THEN RETURN;
trajGen ← TrajsInOutline[slice];
FOR traj: Traj ← GGScene.NextTraj[trajGen], GGScene.NextTraj[trajGen]
UNTIL traj =
NIL
DO
outlineBoxes ← CONS[traj.boundBox, outlineBoxes];
ENDLOOP;
UpdateBoundBoxFromList[slice.boundBox, outlineBoxes];
};
UpdateBoundBoxFromList:
PROC [bBox: BoundBox, list:
LIST
OF BoundBox] = {
IF list = NIL THEN ERROR;
bBox.loX ← list.first.loX;
bBox.hiX ← list.first.hiX;
bBox.loY ← list.first.loY;
bBox.hiY ← list.first.hiY;
FOR bBoxList:
LIST
OF BoundBox ← list.rest, bBoxList.rest
UNTIL bBoxList =
NIL
DO
bBox.loX ← MIN[bBox.loX, bBoxList.first.loX];
bBox.hiX ← MAX[bBox.hiX, bBoxList.first.hiX];
bBox.loY ← MIN[bBox.loY, bBoxList.first.loY];
bBox.hiY ← MAX[bBox.hiY, bBoxList.first.hiY];
ENDLOOP;
};
OutlineCopy:
PROC [slice: Outline]
RETURNS [copy: Outline] = {
holeGen: TrajGenerator;
newTraj, fence: Traj;
fence ← GGOutline.FenceOfOutline[slice];
newTraj ← GGTraj.CopyTraj[fence];
copy ← CreateOutline[newTraj, slice.fillColor];
holeGen ← GGOutline.HolesOfOutline[slice];
FOR hole: Traj ← GGScene.NextTraj[holeGen], GGScene.NextTraj[holeGen]
UNTIL hole =
NIL
DO
newTraj ← GGTraj.CopyTraj[hole];
copy.children ← AppendTrajList[copy.children, LIST[newTraj]];
newTraj.parent ← copy;
ENDLOOP;
};
Drawing
OutlineDrawParts:
PROC [slice: Outline, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick:
BOOL] = {
GGModelTypes.OutlineDrawPartsProc
IF parts # NIL THEN ERROR Problem[msg: "OutlineDrawParts NYI"];
sliceD: SliceDescriptor ← GGSlice.DescriptorFromParts[slice, parts];
IF slice.class.isComplete[sliceD] THEN DrawOutline[dc, slice]
ELSE {
seqList: LIST OF Sequence ← GGOutline.SequencesOfOutline[slice]; -- why not sequenceGen??
FOR seq: Sequence ← seqList, seq.rest
UNTIL seq=
NIL
DO
GGTraj.DrawTrajSeq[seq.first];
ENDLOOP;
};
};
OutlineDrawTransform:
PROC [sliceD: OutlineDescriptor, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = {
sliceParts: OutlineParts ← NARROW[sliceD.parts];
BuildOutline: Imager.PathProc = {
PathProc: TYPE ~ PROC[moveTo: MoveToProc, lineTo: LineToProc,
curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc];
BuildPath: Imager.PathProc = {
seg: Segment;
firstPoint: Point ← GGTraj.FetchJointPos[traj, 0];
moveTo[ [firstPoint.x, firstPoint.y] ];
FOR i:
INT
IN [0..GGTraj.HiSegment[traj]]
DO
seg ← GGTraj.FetchSegment[traj, i];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
BuildPathTransformSeq: Imager.PathProc = {
seg: Segment;
firstPoint: Point ← GGTraj.FetchJointPos[traj, 0];
IF thisSeq.segments[0] OR thisSeq.joints[0] THEN firstPoint ← ImagerTransformation.Transform[transform, firstPoint];
moveTo[ [firstPoint.x, firstPoint.y] ];
FOR i:
INT
IN [0..GGTraj.HiSegment[traj]]
DO
seg ← GGTraj.FetchSegment[traj, i];
seg.class.buildPathTransform[seg, transform, thisSeq.segments[i], thisSeq.joints[i], thisSeq.joints[(i+1) MOD thisSeq.traj.segCount], thisSeq.controlPoints[i], lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
imagerHole: ImagerPath.Trajectory;
cc, holeCC: BOOL;
traj ← fence;
thisSeq ← FindSequenceInList[traj, sliceParts.seqs];
IF thisSeq #
NIL
THEN {
BuildPathTransformSeq[moveTo, lineTo, curveTo, conicTo, arcTo];
cc ← GGTraj.IsClockwiseTrajTransformSeq[thisSeq, transform];
}
ELSE {
BuildPath[moveTo, lineTo, curveTo, conicTo, arcTo];
cc ← GGTraj.IsClockwiseTraj[traj];
};
FOR holeList:
LIST
OF Traj ← slice.children.rest, holeList.rest
UNTIL holeList =
NIL
DO
traj ← holeList.first;
thisSeq ← FindSequenceInList[traj, sliceParts.seqs];
IF thisSeq #
NIL
THEN {
imagerHole ← ImagerPath.TrajectoryListFromPath[BuildPathTransformSeq].first;
holeCC ← GGTraj.IsClockwiseTrajTransformSeq[thisSeq, transform];
}
ELSE {
imagerHole ← ImagerPath.TrajectoryListFromPath[BuildPath].first;
holeCC ← GGTraj.IsClockwiseTraj[traj];
};
IF cc=holeCC THEN ImagerPath.MapTrajectoryBackward[imagerHole, moveTo, lineTo, curveTo, conicTo, arcTo]
ELSE ImagerPath.MapTrajectory[imagerHole, moveTo, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
traj: Traj;
thisSeq: Sequence;
slice: Outline ← sliceD.slice;
fence: Traj ← slice.children.first;
Fill in the outline if necessary.
IF fence.role = fence
AND slice.fillColor#
NIL
THEN {
transformedColor: Color;
WITH slice.fillColor
SELECT
FROM
cc: ImagerColor.ConstantColor => {
Imager.SetColor[dc, cc];
};
sc: ImagerColor.SampledColor => {
IF OutlineCompleteParts[slice, sliceD.parts]
THEN {
transformedColor ← ImagerColor.MakeSampledColor[pa: sc.pa, um: ImagerTransformation.Concat[sc.um, transform], colorOperator: sc.colorOperator];
Imager.SetColor[dc, transformedColor];
};
};
ENDCASE => ERROR;
Imager.MaskFill[dc, BuildOutline];
};
Draw the strokes.
thisSeq ← FindSequenceInList[fence, sliceParts.seqs];
IF thisSeq # NIL THEN GGTraj.DrawTrajTransformSeq[dc, thisSeq, transform] ELSE GGTraj.DrawTraj[dc, fence];
FOR holeList:
LIST
OF Traj ← slice.children.rest, holeList.rest
UNTIL holeList =
NIL
DO
thisSeq ← FindSequenceInList[holeList.first, sliceParts.seqs];
IF thisSeq # NIL THEN GGTraj.DrawTrajTransformSeq[dc, thisSeq, transform] ELSE GGTraj.DrawTraj[dc, holeList.first];
ENDLOOP;
};
OutlineDrawSelectionFeedback:
PROC [slice: Outline, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick:
BOOL] = {
normalOutlineParts, hotOutlineParts: OutlineParts;
normalList, hotList: LIST OF Sequence;
normalOutlineParts ← NARROW[selectedParts];
hotOutlineParts ← NARROW[hotParts];
IF caretIsMoving OR dragInProgress THEN RETURN;
IF selectedParts = NIL AND hotParts = NIL THEN RETURN;
IF selectedParts #
NIL
AND hotParts #
NIL
THEN {
hotList ← hotOutlineParts.seqs;
FOR normalList ← normalOutlineParts.seqs, normalList.rest
UNTIL normalList =
NIL
DO
IF normalList.first # NIL THEN GGTraj.DrawSelectionFeedback[normalList.first.traj, normalList.first, hotList.first, dc, camera, dragInProgress, caretIsMoving, hideHot, quick]
ELSE IF hotList.first # NIL THEN GGTraj.DrawSelectionFeedback[hotList.first.traj, normalList.first, hotList.first, dc, camera, dragInProgress, caretIsMoving, hideHot, quick];
hotList ← hotList.rest;
ENDLOOP;
}
ELSE
IF selectedParts #
NIL
THEN {
FOR normalList ← normalOutlineParts.seqs, normalList.rest
UNTIL normalList =
NIL
DO
IF normalList.first # NIL THEN GGTraj.DrawSelectionFeedback[normalList.first.traj, normalList.first, NIL, dc, camera, dragInProgress, caretIsMoving, hideHot, quick];
ENDLOOP;
}
ELSE {
FOR hotList ← hotOutlineParts.seqs, hotList.rest
UNTIL hotList =
NIL
DO
IF hotList.first # NIL THEN GGTraj.DrawSelectionFeedback[hotList.first.traj, NIL, hotList.first, dc, camera, dragInProgress, caretIsMoving, hideHot, quick];
ENDLOOP;
};
};
OutlineDrawAttractorFeedback:
PUBLIC
PROC [sliceD: OutlineDescriptor, selectedParts: SliceParts, dragInProgress:
BOOL, dc: Imager.Context, camera: CameraData] = {
success: BOOL;
partType: TrajPartType;
traj: Traj;
seg: Segment;
jointNum: NAT;
[success, partType, traj, ----, jointNum, ----, ----, seg] ← UnpackSimpleDescriptorOld[sliceD];
SELECT partType
FROM
joint => {
previous: Segment ← GGTraj.PreviousSegment[traj, jointNum];
this: Segment ← IF jointNum <= GGTraj.HiSegment[traj] THEN GGTraj.FetchSegment[traj, jointNum] ELSE NIL;
IF previous#NIL THEN DrawCpsAndJoints[dc, previous];
IF this#NIL THEN DrawCpsAndJoints[dc, this];
};
segment, controlPoint => DrawCpsAndJoints[dc, seg];
ENDCASE; -- none
};
Drawing Utilities
DrawOutline:
PROC [dc: Imager.Context, outline: Outline] = {
Fill the outline if necessary.
BuildOutline: Imager.PathProc = {
BuildPath: Imager.PathProc = {
Someday this will be a call to
path.class.buildPath[traj, moveTo, lineTo, curveTo, conicTo, arcTo];
BuildTrajPath[traj, moveTo, lineTo, curveTo, conicTo, arcTo];
};
imagerHole: ImagerPath.Trajectory;
cc: BOOL;
traj ← fence;
cc ← GGTraj.IsClockwiseTraj[traj];
path.class.buildPath[traj, moveTo, lineTo, curveTo, conicTo, arcTo];
BuildTrajPath[traj, moveTo, lineTo, curveTo, conicTo, arcTo];
FOR holeList:
LIST
OF Traj ← outline.children.rest, holeList.rest
UNTIL holeList =
NIL
DO
traj ← holeList.first;
imagerHole ← ImagerPath.TrajectoryListFromPath[BuildPath].first;
IF cc=GGTraj.IsClockwiseTraj[traj] THEN ImagerPath.MapTrajectoryBackward[imagerHole, moveTo, lineTo, curveTo, conicTo, arcTo]
ELSE ImagerPath.MapTrajectory[imagerHole, moveTo, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
fence, traj: Traj;
fence ← outline.children.first;
IF fence.role = fence
AND outline.fillColor#
NIL
THEN {
Imager.SetColor[dc, outline.fillColor];
Imager.MaskFill[dc, BuildOutline];
};
Draw the strokes.
GGTraj.DrawTraj[dc, fence];
Will be path.class.drawBorder[fence, dc, ggData];
FOR holeList:
LIST
OF Traj ← outline.children.rest, holeList.rest
UNTIL holeList =
NIL
DO
GGTraj.DrawTraj[dc, holeList.first];
Will be path.class.drawBorder[holeList.first, dc, ggData];
ENDLOOP;
};
FindSequenceInList:
PROC [traj: Traj, seqList:
LIST
OF Sequence]
RETURNS [seq: Sequence] = {
FOR list:
LIST
OF Sequence ← seqList, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL AND list.first.traj = traj THEN RETURN[list.first];
ENDLOOP;
RETURN[NIL];
};
BuildTrajPath:
PROC [traj: Traj, moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc,
curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = {
DoBuildTrajPath: Imager.PathProc = {
seg: Segment;
firstPoint: Point ← GGTraj.FetchJointPos[traj, 0];
moveTo[ [firstPoint.x, firstPoint.y] ];
FOR i:
INT
IN [0..GGTraj.HiSegment[traj]]
DO
seg ← GGTraj.FetchSegment[traj, i];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
DoBuildTrajPath[moveTo, lineTo, curveTo, conicTo, arcTo];
};
DrawCpsAndJoints:
PROC [dc: Imager.Context, seg: Segment] = {
point: Point;
GGShapes.DrawJoint[dc, seg.lo];
GGShapes.DrawJoint[dc, seg.hi];
FOR j:
INT
IN [0..seg.class.controlPointCount[seg])
DO
point ← seg.class.controlPointGet[seg, j];
GGShapes.DrawCP[dc, point];
ENDLOOP;
};
Transforming
OutlineTransform:
PUBLIC
PROC [sliceD: OutlineDescriptor, transform: ImagerTransformation.Transformation] = {
outlineParts: OutlineParts ← NARROW[sliceD.parts];
slice: Outline ← sliceD.slice;
transformedColor: Color;
FOR list:
LIST
OF Sequence ← outlineParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN GGTraj.TransformSequence[list.first, transform];
ENDLOOP;
UpdateBoundBox[slice];
IF slice.fillColor
# NIL THEN {
WITH slice.fillColor
SELECT
FROM
cc: ImagerColor.ConstantColor => {};
sc: ImagerColor.SampledColor => {
IF OutlineCompleteParts[slice, sliceD.parts]
THEN {
transformedColor ← ImagerColor.MakeSampledColor[pa: sc.pa, um: ImagerTransformation.Concat[sc.um, transform], colorOperator: sc.colorOperator];
slice.fillColor ← transformedColor;
};
};
ENDCASE => ERROR;
};
};
Textual Description
OneSequenceOnly:
PROC [realParts: OutlineParts]
RETURNS [theSeq: Sequence] = {
theSeq ← NIL;
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first #
NIL
THEN {
IF theSeq # NIL THEN RETURN[NIL]
ELSE theSeq ← list.first;
};
ENDLOOP;
};
OutlineDescribe:
PROC [sliceD: OutlineDescriptor]
RETURNS [rope: Rope.
ROPE] = {
realParts: OutlineParts;
theSeq: Sequence;
IF sliceD.parts = NIL THEN RETURN["an Outline"];
realParts ← NARROW[sliceD.parts];
IF (theSeq ← OneSequenceOnly[realParts]) #
NIL THEN {
rope ← GGSequence.Describe[theSeq];
}
ELSE {
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN RETURN["several parts of a multi-trajectory outline"];
ENDLOOP;
rope ← "No outline parts to speak of.";
};
};
OutlineDescribeHit:
PROC [slice: Outline, hitData:
REF
ANY]
RETURNS [rope: Rope.
ROPE] = {
outlineHitData: OutlineHitData ← NARROW[hitData];
rope ← GGTraj.DescribeHit[outlineHitData.traj, outlineHitData.hitType, outlineHitData.segNum, outlineHitData.cpNum, outlineHitData.jointNum];
};
OutlineFileout:
PROC [slice: Outline, f:
IO.
STREAM] = {
count: NAT;
f.PutF["Outline:\n"];
f.PutF["fillColor: "]; GGParseOut.WriteColor[f, slice.fillColor];
f.PutF[" strokeEnd: "]; GGParseOut.WriteStrokeEnd[f, slice.lineEnds];
f.PutChar[IO.CR];
count ← 0;
FOR trajList:
LIST
OF Traj ← slice.children, trajList.rest
UNTIL trajList =
NIL
DO
count ← count + 1;
ENDLOOP;
f.PutF["Trajectories: [%g]\n", [integer[count]]];
FOR trajList:
LIST
OF Traj ← slice.children, trajList.rest
UNTIL trajList =
NIL
DO
GGTraj.Fileout[f, trajList.first];
ENDLOOP;
f.PutChar[IO.CR];
};
OutlineFilein:
PROC [f:
IO.
STREAM, version:
REAL, feedback: FeedbackData]
RETURNS [slice: Outline] = {
fillColor: Color;
count: NAT;
fence, hole: Traj;
hasCircle: BOOL ← FALSE;
GGParseIn.ReadBlankAndRope[f, "fillColor:"];
fillColor ← GGParseIn.ReadColor[f, version];
IF version < 8702.26
THEN {
-- read and discard StrokeEnd from older formats
GGParseIn.ReadBlankAndRope[f, "strokeEnd:"];
[] ← GGParseIn.ReadStrokeEnd[f];
};
GGParseIn.ReadBlankAndRope[f, "Trajectories:"];
GGParseIn.ReadBlankAndRope[f, "["];
count ← GGParseIn.ReadBlankAndNAT[f];
GGParseIn.ReadBlankAndRope[f, "]"];
[fence, hasCircle] ← GGTraj.Filein[f, version];
IF hasCircle THEN fillColor ← NIL; -- needed for the transition from circle/disc class
slice ← GGOutline.CreateOutline[fence, fillColor];
FOR i:
NAT
IN [1..count-1]
DO
[hole, ----] ← GGTraj.Filein[f, version];
slice ← GGOutline.AddHole[slice, hole];
ENDLOOP;
};
Parts
IsEmpty:
PROC [realParts: OutlineParts]
RETURNS [
BOOL] = {
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL AND NOT GGSequence.IsEmpty[list.first] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
OutlineEmptyParts:
PROC [sliceD: OutlineDescriptor]
RETURNS [
BOOL] = {
realParts: OutlineParts ← NARROW[sliceD.parts];
RETURN[IsEmpty[realParts]];
};
CreateEmptyParts:
PROC [slice: Outline]
RETURNS [empty: SliceParts] = {
realParts: OutlineParts;
empty ← realParts ← NEW[OutlinePartsObj];
realParts.seqs ← NIL;
FOR list:
LIST
OF Traj ← slice.children, list.rest
UNTIL list =
NIL
DO
realParts.seqs ← CONS[NIL, realParts.seqs];
ENDLOOP;
};
IsComplete:
PROC [realParts: OutlineParts]
RETURNS [
BOOL] = {
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first = NIL THEN RETURN[FALSE];
IF NOT GGSequence.IsComplete[list.first] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
OutlineCompleteParts:
PROC [slice: Outline, parts: SliceParts]
RETURNS [
BOOL] = {
realParts: OutlineParts ← NARROW[parts];
RETURN[IsComplete[realParts]];
};
OutlineNewParts:
PROC [slice: Outline, hitData:
REF
ANY, mode: SelectMode]
RETURNS [sliceD: OutlineDescriptor] = {
realParts: OutlineParts;
ptr: LIST OF Sequence;
trajGen: GGModelTypes.TrajGenerator;
parts: SliceParts;
realParts ← NEW[OutlinePartsObj];
[realParts.seqs, ptr] ← GGUtility.StartSequenceList[];
SELECT mode
FROM
literal => {
traj: Traj;
hitType: GGModelTypes.TrajPartType;
segNum, cpNum, jointNum: INT;
seq: Sequence;
outlineHitData: OutlineHitData ← NARROW[hitData];
[traj, hitType, segNum, cpNum, jointNum] ← GGOutline.UnpackHitData[hitData];
SELECT hitType
FROM
joint => seq ← GGSequence.CreateFromJoint[traj, jointNum];
controlPoint => seq ← GGSequence.CreateFromControlPoint[traj, segNum, cpNum];
segment => seq ← GGSequence.CreateSimpleFromSegment[traj, segNum];
ENDCASE => ERROR;
sliceD ← DescriptorFromSequence[slice, seq];
};
none => {
outlineHitData: OutlineHitData ← NARROW[hitData];
sliceD ← GGSlice.DescriptorFromParts[slice, CreateEmptyParts[slice]];
};
slice => {
completeSeq: Sequence;
trajGen ← TrajsInOutline[slice];
FOR traj: Traj ← GGScene.NextTraj[trajGen], GGScene.NextTraj[trajGen]
UNTIL traj =
NIL
DO
completeSeq ← GGSequence.CreateComplete[traj];
[realParts.seqs, ptr] ← GGUtility.AddSequence[completeSeq, realParts.seqs, ptr];
ENDLOOP;
parts ← realParts;
};
joint => {
outlineHitData: OutlineHitData ← NARROW[hitData];
SELECT outlineHitData.hitType
FROM
joint => {
-- hit a joint
traj: Traj ← outlineHitData.traj;
jointNum: NAT ← outlineHitData.jointNum;
seq: Sequence ← GGSequence.CreateJointToJoint[traj, jointNum, jointNum];
parts ← PartsFromSequence[slice, seq];
};
segment => {
-- we are in the middle of a segment. Select nearest joint or cp
success: BOOL;
segNum, cpNum, jointNum: NAT;
jointPoint, cpPoint, caretPt: Point;
traj: Traj;
seg: Segment;
seq: Sequence;
traj ← outlineHitData.traj;
segNum ← outlineHitData.segNum;
seg ← GGTraj.FetchSegment[traj, segNum];
caretPt ← outlineHitData.hitPoint;
jointNum ← NearestJointToHitSpot[caretPt, traj, -1, segNum, segment];
jointPoint ← GGTraj.FetchJointPos[traj, jointNum];
[cpPoint, cpNum, success] ← seg.class.closestControlPoint[seg, caretPt, GGUtility.plusInfinity];
IF NOT success THEN seq ← GGSequence.CreateJointToJoint[traj, jointNum, jointNum]
ELSE {
cpDist: REAL ← Vectors2d.DistanceSquared[cpPoint, caretPt];
jointDist: REAL ← Vectors2d.DistanceSquared[jointPoint, caretPt];
seq ← IF cpDist < jointDist THEN GGSequence.CreateFromControlPoint[traj, segNum, cpNum]
ELSE GGSequence.CreateJointToJoint[traj, jointNum, jointNum];
};
parts ← PartsFromSequence[slice, seq];
};
controlPoint => {
traj: Traj;
segNum, cpNum: NAT;
controlPointSeq: Sequence;
traj ← outlineHitData.traj;
segNum ← outlineHitData.segNum;
cpNum ← outlineHitData.cpNum;
controlPointSeq ← GGSequence.CreateFromControlPoint[traj, segNum, cpNum];
parts ← PartsFromSequence[slice, controlPointSeq];
};
ENDCASE => ERROR;
};
segment => {
outlineHitData: OutlineHitData ← NARROW[hitData];
SELECT outlineHitData.hitType
FROM
joint => {
traj: Traj ← outlineHitData.traj;
jointNum: NAT ← outlineHitData.jointNum;
this: INT ← IF jointNum>GGTraj.HiSegment[traj] THEN -1 ELSE jointNum;
previous: INT ← GGTraj.PreviousSegmentNum[traj, jointNum];
parts ← PartsFromSequence[slice, GGSequence.CreateFromSegment[traj, IF this#-1 THEN this ELSE previous]];
};
segment, controlPoint => {
seq: Sequence ← GGSequence.CreateFromSegment[outlineHitData.traj, outlineHitData.segNum];
parts ← PartsFromSequence[slice, seq];
};
ENDCASE => ERROR;
};
traj => {
outlineHitData: OutlineHitData ← NARROW[hitData];
traj: Traj ← outlineHitData.traj;
seq: Sequence ← GGSequence.CreateComplete[traj];
parts ← PartsFromSequence[slice, seq];
};
topLevel => {
sliceD ← slice.class.newParts[slice, NIL, slice];
};
ENDCASE => ERROR;
IF sliceD = NIL THEN sliceD ← GGSlice.DescriptorFromParts[slice, parts];
};
OutlineUnionParts:
PROC [partsA: OutlineDescriptor, partsB: OutlineDescriptor]
RETURNS [aPlusB: OutlineDescriptor] = {
realPartsA: OutlineParts ← NARROW[partsA.parts];
realPartsB: OutlineParts ← NARROW[partsB.parts];
union: OutlineParts;
listA: LIST OF Sequence ← realPartsA.seqs;
listB: LIST OF Sequence ← realPartsB.seqs;
ptr: LIST OF Sequence;
IF partsA.parts = NIL THEN RETURN[partsB];
IF partsB.parts = NIL THEN RETURN[partsA];
IF IsEmpty[realPartsA] THEN RETURN[partsB];
IF IsEmpty[realPartsB] THEN RETURN[partsA];
IF IsComplete[realPartsA] THEN RETURN[partsA];
IF IsComplete[realPartsB] THEN RETURN[partsB];
union ← NEW[OutlinePartsObj];
[union.seqs, ptr] ← GGUtility.StartSequenceList[];
UNTIL listA =
NIL
DO
IF listA.first = NIL THEN [union.seqs, ptr] ← GGUtility.AddSequence[listB.first, union.seqs, ptr]
ELSE IF listB.first = NIL THEN [union.seqs, ptr] ← GGUtility.AddSequence[listA.first, union.seqs, ptr]
ELSE {
newSeq: Sequence ← GGSequence.Union[listA.first, listB.first];
[union.seqs, ptr] ← GGUtility.AddSequence[newSeq, union.seqs, ptr];
};
listA ← listA.rest;
listB ← listB.rest;
ENDLOOP;
aPlusB ← GGSlice.DescriptorFromParts[partsA.slice, union];
};
OutlineDifferenceParts:
PROC [partsA: OutlineDescriptor, partsB: OutlineDescriptor]
RETURNS [aMinusB: OutlineDescriptor] = {
realPartsA, realPartsB: OutlineParts;
diff: OutlineParts;
listA: LIST OF Sequence;
listB: LIST OF Sequence;
ptr: LIST OF Sequence;
IF partsA.parts = NIL OR partsB = NIL OR partsB.parts = NIL THEN RETURN[partsA];
realPartsA ← NARROW[partsA.parts];
realPartsB ← NARROW[partsB.parts];
listA ← realPartsA.seqs;
listB ← realPartsB.seqs;
diff ← NEW[OutlinePartsObj];
[diff.seqs, ptr] ← GGUtility.StartSequenceList[];
UNTIL listA =
NIL
DO
IF listA.first = NIL THEN [diff.seqs, ptr] ← GGUtility.AddSequence[NIL, diff.seqs, ptr]
ELSE IF listB.first = NIL THEN [diff.seqs, ptr] ← GGUtility.AddSequence[listA.first, diff.seqs, ptr]
ELSE {
newSeq: Sequence ← GGSequence.Difference[listA.first, listB.first];
[diff.seqs, ptr] ← GGUtility.AddSequence[newSeq, diff.seqs, ptr];
};
listA ← listA.rest;
listB ← listB.rest;
ENDLOOP;
aMinusB ← GGSlice.DescriptorFromParts[partsA.slice, diff];
};
OutlineMovingParts:
PROC [slice: Outline, selectedParts: SliceParts]
RETURNS [background, overlay, rubber, drag: OutlineDescriptor] = {
OPEN GGUtility;
Pass on the real work to GGSequence. Filled outlines have the property that if any of their subparts are on the overlay plane, then all of their parts are on the overlay plane.
outlineParts: OutlineParts ← NARROW[selectedParts];
bkgdParts, overParts, rubberParts, dragParts: OutlineParts;
bkgdSeq, overSeq, rubberSeq, dragSeq: Sequence;
bkgdPtr, overPtr, rubberPtr, dragPtr: LIST OF Sequence;
children: LIST OF Traj;
filled: BOOL ← slice.fillColor # NIL;
nullD: OutlineDescriptor ← slice.nullDescriptor;
IF IsEmpty[outlineParts]
THEN {
background ← overlay ← rubber ← drag ← nullD;
RETURN;
};
IF IsComplete[outlineParts]
THEN {
background ← overlay ← rubber ← nullD;
drag ← GGSlice.DescriptorFromParts[slice, selectedParts];
RETURN;
};
bkgdParts ← NEW[OutlinePartsObj];
overParts ← NEW[OutlinePartsObj];
rubberParts ← NEW[OutlinePartsObj];
dragParts ← NEW[OutlinePartsObj];
[bkgdParts.seqs, bkgdPtr] ← StartSequenceList[];
[overParts.seqs, overPtr] ← StartSequenceList[];
[rubberParts.seqs, rubberPtr] ← StartSequenceList[];
[dragParts.seqs, dragPtr] ← StartSequenceList[];
children ← slice.children;
FOR list:
LIST
OF Sequence ← outlineParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first =
NIL
THEN {
This trajectory is not at all selected. It will not move. If this outline is filled, put it on the overlay. Otherwise, leave it in the background.
[rubberParts.seqs, rubberPtr] ← AddSequence[NIL, rubberParts.seqs, rubberPtr];
[dragParts.seqs, dragPtr] ← AddSequence[NIL, dragParts.seqs, dragPtr];
IF filled
THEN {
overSeq ← GGSequence.CreateComplete[children.first];
[overParts.seqs, overPtr] ← AddSequence[overSeq, overParts.seqs, overPtr];
[bkgdParts.seqs, bkgdPtr] ← AddSequence[NIL, bkgdParts.seqs, bkgdPtr];
}
ELSE {
bkgdSeq ← GGSequence.CreateComplete[children.first];
[bkgdParts.seqs, bkgdPtr] ← AddSequence[bkgdSeq, bkgdParts.seqs, bkgdPtr];
[overParts.seqs, overPtr] ← AddSequence[NIL, overParts.seqs, overPtr];
};
}
ELSE {
If the outline is filled, it has no background parts. Otherwise, all of its non-moving parts can be left on the background.
[bkgdSeq, overSeq, rubberSeq, dragSeq] ← GGSequence.TrajMovingParts[list.first];
IF filled
THEN {
[bkgdParts.seqs, bkgdPtr] ← AddSequence[NIL, bkgdParts.seqs, bkgdPtr];
[overParts.seqs, overPtr] ← AddSequence[overSeq, overParts.seqs, overPtr];
[rubberParts.seqs, rubberPtr] ← AddSequence[rubberSeq, rubberParts.seqs, rubberPtr];
[dragParts.seqs, dragPtr] ← AddSequence[dragSeq, dragParts.seqs, dragPtr];
}
ELSE {
[bkgdParts.seqs, bkgdPtr] ← AddSequence[overSeq, bkgdParts.seqs, bkgdPtr];
[overParts.seqs, overPtr] ← AddSequence[NIL, overParts.seqs, overPtr];
[rubberParts.seqs, rubberPtr] ← AddSequence[rubberSeq, rubberParts.seqs, rubberPtr];
[dragParts.seqs, dragPtr] ← AddSequence[dragSeq, dragParts.seqs, dragPtr];
};
};
children ← children.rest;
ENDLOOP;
background ← GGSlice.DescriptorFromParts[slice, bkgdParts];
overlay ← GGSlice.DescriptorFromParts[slice, overParts];
rubber ← GGSlice.DescriptorFromParts[slice, rubberParts];
drag ← GGSlice.DescriptorFromParts[slice, dragParts];
};
NearestJointToHitSpot:
PUBLIC
PROC [caretPt: Point, traj: Traj, hitJointNum:
INT, hitSegNum:
INT, hitType: HitType]
RETURNS [jointNum:
NAT] = {
nextNum: NAT;
p1, p2: Point;
d1, d2: REAL;
SELECT hitType
FROM
joint => {
jointNum ← hitJointNum;
};
segment, controlPoint => {
nextNum ← GGTraj.FollowingJoint[traj, hitSegNum];
p1 ← GGTraj.FetchJointPos[traj, hitSegNum];
p2 ← GGTraj.FetchJointPos[traj, nextNum];
d1 ← Vectors2d.DistanceSquared[p1, caretPt];
d2 ← Vectors2d.DistanceSquared[p2, caretPt];
IF d1 <= d2 THEN jointNum ← hitSegNum
ELSE jointNum ← nextNum;
};
ENDCASE => ERROR Problem[msg: "NearestJointToHitSpot NYI"];
}; -- end of NearestJointToHitSpot
NearestJointToHitData:
PUBLIC PROC [hitData:
REF
ANY]
RETURNS [jointNum:
NAT, traj: Traj] = {
outlineHitData: OutlineHitData ← NARROW[hitData];
nextNum: NAT;
p1, p2: Point;
d1, d2: REAL;
traj ← outlineHitData.traj;
SELECT outlineHitData.hitType
FROM
joint => {
jointNum ← outlineHitData.jointNum;
};
segment, controlPoint => {
nextNum ← GGTraj.FollowingJoint[traj, outlineHitData.segNum];
p1 ← GGTraj.FetchJointPos[traj, outlineHitData.segNum];
p2 ← GGTraj.FetchJointPos[traj, nextNum];
d1 ← Vectors2d.DistanceSquared[p1, outlineHitData.hitPoint];
d2 ← Vectors2d.DistanceSquared[p2, outlineHitData.hitPoint];
IF d1 <= d2 THEN jointNum ← outlineHitData.segNum
ELSE jointNum ← nextNum;
};
ENDCASE => ERROR;
};
OutlineAugmentParts:
PROC [sliceD: OutlineDescriptor, selectClass: SelectionClass]
RETURNS [more: OutlineDescriptor] = {
If selectClass = normal, then for each segment mentioned in parts, place it, its adjacent joints, and its control points in more. If selectClass # normal then just place each segment and its control points in more.
This mutates the OutlineDescriptor which may mean trouble. -- Bier, March 9, 1987
sliceParts: OutlineParts ← NARROW[sliceD.parts];
FOR list:
LIST
OF Sequence ← sliceParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN GGSequence.AugmentParts[list.first, selectClass];
ENDLOOP;
more ← GGSlice.DescriptorFromParts[sliceD.slice, sliceParts];
};
OutlineSetSelectedFields:
PROC [sliceD: OutlineDescriptor, selected:
BOOL, selectClass: SelectionClass] = {
Set the selected fields of all of the joints and segments mentioned in sliceD.
sliceParts: OutlineParts ← NARROW[sliceD.parts];
FOR list:
LIST
OF Sequence ← sliceParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN GGTraj.SetSelectedFields[list.first, selected, selectClass];
ENDLOOP;
};
Hit Testing
PointGeneratorData: TYPE = REF PointGeneratorDataObj;
PointGeneratorDataObj:
TYPE =
RECORD [
seqPtr: LIST OF Sequence,
pointGen: GGModelTypes.PointGenerator -- the point generator for the first sequence of seqPtr
];
PointPairGeneratorData: TYPE = REF PointPairGeneratorDataObj;
PointPairGeneratorDataObj:
TYPE =
RECORD [
seqPtr: LIST OF Sequence,
pointGen: GGModelTypes.PointPairGenerator -- the pair generator for first sequence of seqPtr
];
OutlinePointsInDescriptor:
PUBLIC
PROC [sliceD: OutlineDescriptor]
RETURNS [pointGen: GGModelTypes.OutlinePointGenerator] = {
realParts: OutlineParts ← NARROW[sliceD.parts];
seqPtr: LIST OF Sequence ← realParts.seqs;
trajPointGen: GGModelTypes.PointGenerator;
pointGenData: PointGeneratorData;
trajPointGen ← IF seqPtr.first = NIL THEN NIL ELSE GGTraj.PointsInDescriptor[seqPtr.first];
pointGenData ← NEW[PointGeneratorDataObj ← [seqPtr: seqPtr, pointGen: trajPointGen]];
pointGen ← NEW[GGModelTypes.OutlinePointGeneratorObj ← [sliceD, 0, 0, pointGenData]];
};
OutlinePointPairsInDescriptor:
PUBLIC
PROC [sliceD: OutlineDescriptor]
RETURNS [pointPairGen: GGModelTypes.OutlinePointPairGenerator] = {
realParts: OutlineParts ← NARROW[sliceD.parts];
seqPtr: LIST OF Sequence ← realParts.seqs;
trajPointGen: GGModelTypes.PointPairGenerator;
pointGenData: PointPairGeneratorData;
trajPointGen ← IF seqPtr.first = NIL THEN NIL ELSE GGTraj.PointPairsInDescriptor[seqPtr.first];
pointGenData ← NEW[PointPairGeneratorDataObj ← [seqPtr: seqPtr, pointGen: trajPointGen]];
pointPairGen ← NEW[GGModelTypes.OutlinePointPairGeneratorObj ← [sliceD, 0, 0, pointGenData]];
};
OutlineNextPoint:
PUBLIC
PROC [pointGen: GGModelTypes.OutlinePointGenerator]
RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
pgd: PointGeneratorData ← NARROW[pointGen.classSpecific];
seqPtr: LIST OF Sequence ← pgd.seqPtr;
IF seqPtr = NIL THEN RETURN[[[0,0], TRUE]];
WHILE
TRUE
DO
IF seqPtr.first #
NIL
THEN {
pointAndDone ← GGTraj.NextPoint[pgd.pointGen];
IF
NOT pointAndDone.done
THEN
{
pgd.seqPtr ← seqPtr;
RETURN;
};
seqPtr ← seqPtr.rest; -- on to the next sequence
};
UNTIL seqPtr = NIL OR seqPtr.first # NIL DO seqPtr ← seqPtr.rest ENDLOOP;
IF seqPtr = NIL THEN RETURN[[[0,0], TRUE]];
pgd.pointGen ← GGTraj.PointsInDescriptor[seqPtr.first];
ENDLOOP;
};
OutlineNextPointPair:
PUBLIC
PROC [pointPairGen: GGModelTypes.OutlinePointPairGenerator]
RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = {
pgd: PointPairGeneratorData ← NARROW[pointPairGen.classSpecific];
seqPtr: LIST OF Sequence ← pgd.seqPtr;
IF seqPtr = NIL THEN RETURN[[[0,0], [0,0], TRUE]];
WHILE
TRUE
DO
IF seqPtr.first #
NIL
THEN {
pointPairAndDone ← GGTraj.NextPointPair[pgd.pointGen];
IF
NOT pointPairAndDone.done
THEN
{
pgd.seqPtr ← seqPtr;
RETURN;
};
seqPtr ← seqPtr.rest; -- on to the next sequence
};
UNTIL seqPtr = NIL OR seqPtr.first # NIL DO seqPtr ← seqPtr.rest ENDLOOP;
IF seqPtr = NIL THEN RETURN[[[0,0], [0,0], TRUE]];
pgd.pointGen ← GGTraj.PointPairsInDescriptor[seqPtr.first];
ENDLOOP;
};
GoodPointType: TYPE = {joint, intersectionPoint, midpoint, controlPoint, slice, none};
OutlineClosestPoint:
PROC [sliceD: OutlineDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point, bestDist:
REAL, hitData:
REF
ANY, success:
BOOL] = {
parts: OutlineParts ← NARROW[sliceD.parts];
thisDist: REAL;
thisSegNum, bestSegNum, thisCP, bestCP, thisJointNum, bestJointNum: NAT;
thisPoint: Point;
thisSuccess: BOOL;
bestTraj: Traj;
bestType: GoodPointType ← none;
outlineHitData: OutlineHitData;
success ← FALSE;
bestDist ← GGUtility.plusInfinity;
FOR list:
LIST
OF Sequence ← parts.seqs, list.rest
UNTIL list =
NIL
DO
[thisDist, thisSegNum, thisCP, thisPoint, thisSuccess] ← GGTraj.NearestControlPoint[testPoint, list.first, tolerance];
IF thisSuccess
AND thisDist < bestDist
THEN {
bestType ← controlPoint;
bestPoint ← thisPoint;
bestDist ← thisDist;
bestTraj ← list.first.traj;
bestSegNum ← thisSegNum;
bestCP ← thisCP;
success ← TRUE;
};
ENDLOOP;
FOR list:
LIST
OF Sequence ← parts.seqs, list.rest
UNTIL list =
NIL
DO
[thisDist, thisJointNum, thisPoint, thisSuccess] ← GGTraj.NearestJoint[testPoint, list.first, tolerance];
IF thisSuccess
AND thisDist < bestDist
THEN {
bestType ← joint;
bestPoint ← thisPoint;
bestDist ← thisDist;
bestTraj ← list.first.traj;
bestJointNum ← thisJointNum;
success ← TRUE;
};
ENDLOOP;
IF success
THEN {
SELECT bestType FROM
controlPoint => {
hitData ← outlineHitData ←
NEW[OutlineHitDataObj ← [
bestTraj, controlPoint, bestSegNum, bestCP, -1, bestPoint]];
};
joint => {
hitData ← outlineHitData ←
NEW[OutlineHitDataObj ← [
bestTraj, joint, -1, -1, bestJointNum, bestPoint]];
};
ENDCASE => ERROR;
};
};
OutlineClosestJointToHitData:
PUBLIC
PROC [sliceD: OutlineDescriptor, mapPoint: Point, hitData:
REF
ANY]
RETURNS [jointD: OutlineDescriptor, point: Point] = {
traj: Traj;
hitType: GGModelTypes.TrajPartType;
segNum, cpNum, jointNum: INT;
hitPoint: Point;
jointSeq: Sequence;
[traj, hitType, segNum, cpNum, jointNum, hitPoint] ← GGOutline.UnpackHitData[hitData];
SELECT hitType
FROM
joint => {
jointSeq ← GGSequence.CreateFromJoint[traj, jointNum];
point ← mapPoint;
};
controlPoint => {
jointSeq ← GGSequence.CreateFromControlPoint[traj, segNum, cpNum];
point ← mapPoint;
};
segment => {
success: BOOL;
jointPoint, cpPoint: Point;
seg: Segment;
seg ← GGTraj.FetchSegment[traj, segNum];
[jointNum, ----] ← GGOutline.NearestJointToHitData[hitData];
jointPoint ← GGTraj.FetchJointPos[traj, jointNum];
[cpPoint, cpNum, success] ← seg.class.closestControlPoint[seg, mapPoint, GGUtility.plusInfinity];
IF
NOT success
THEN {
-- its a joint for sure
jointSeq ← GGSequence.CreateFromJoint[traj, jointNum];
point ← jointPoint;
}
ELSE {
-- could be a cp instead of a joint
cpDist: REAL ← Vectors2d.DistanceSquared[cpPoint, mapPoint];
jointDist: REAL ← Vectors2d.DistanceSquared[jointPoint, mapPoint];
tisAJoint: BOOL ← jointDist <= cpDist;
IF tisAJoint
THEN {
jointSeq ← GGSequence.CreateFromJoint[traj, jointNum];
point ← jointPoint;
}
ELSE {
jointSeq ← GGSequence.CreateFromControlPoint[traj, segNum, cpNum];
point ← cpPoint;
};
};
};
ENDCASE => ERROR;
jointD ← GGOutline.DescriptorFromSequence[sliceD.slice, jointSeq];
};
OutlineClosestSegment:
PUBLIC
PROC [sliceD: OutlineDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point, bestDist:
REAL, hitData:
REF
ANY, success:
BOOL] = {
parts: OutlineParts ← NARROW[sliceD.parts];
thisDist: REAL;
thisSegNum, bestSegNum: NAT;
thisPoint: Point;
thisSuccess: BOOL;
bestTraj: Traj;
outlineHitData: OutlineHitData;
success ← FALSE;
bestDist ← GGUtility.plusInfinity;
FOR list:
LIST
OF Sequence ← parts.seqs, list.rest
UNTIL list =
NIL
DO
[thisDist, thisSegNum, thisPoint, thisSuccess] ← GGTraj.NearestSegment[testPoint, list.first, tolerance];
IF thisSuccess
AND thisDist < bestDist
THEN {
bestPoint ← thisPoint;
bestDist ← thisDist;
bestTraj ← list.first.traj;
bestSegNum ← thisSegNum;
success ← TRUE;
};
ENDLOOP;
IF success
THEN {
hitData ← outlineHitData ←
NEW[OutlineHitDataObj ← [
bestTraj, segment, bestSegNum, -1, -1, bestPoint]];
};
};
OutlineLineIntersection:
PUBLIC
PROC [sliceD: OutlineDescriptor, line: Line]
RETURNS [points:
LIST
OF Point, pointCount:
NAT] = {
Finds the intersection of the line with those slice parts mentioned in sliceD.
segGen: GGModelTypes.SegmentGenerator;
thesePoints: LIST OF Point;
thisCount: NAT;
seq: Sequence;
parts: OutlineParts ← NARROW[sliceD.parts];
points ← NIL;
pointCount ← 0;
FOR list:
LIST
OF Sequence ← parts.seqs, list.rest
UNTIL list =
NIL
DO
seq ← list.first;
IF seq = NIL THEN LOOP;
segGen ← GGSequence.SegmentsInSequence[seq];
FOR seg: Segment ← GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen]
UNTIL seg =
NIL
DO
[thesePoints, thisCount] ← seg.class.lineIntersection[seg, line];
FOR list:
LIST
OF Point ← thesePoints, list.rest
UNTIL list =
NIL
DO
points ← CONS[list.first, points];
ENDLOOP;
pointCount ← pointCount + thisCount;
ENDLOOP;
ENDLOOP;
};
OutlineCircleIntersection:
PROC [sliceD: OutlineDescriptor, circle: Circle]
RETURNS [points:
LIST
OF Point, pointCount:
NAT] = {
segGen: GGModelTypes.SegmentGenerator;
thesePoints: LIST OF Point;
thisCount: NAT;
seq: Sequence;
parts: OutlineParts ← NARROW[sliceD.parts];
points ← NIL;
pointCount ← 0;
FOR list:
LIST
OF Sequence ← parts.seqs, list.rest
UNTIL list =
NIL
DO
seq ← list.first;
IF seq = NIL THEN LOOP;
segGen ← GGSequence.SegmentsInSequence[seq];
FOR seg: Segment ← GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen]
UNTIL seg =
NIL
DO
[thesePoints, thisCount] ← seg.class.circleIntersection[seg, circle];
FOR list:
LIST
OF Point ← thesePoints, list.rest
UNTIL list =
NIL
DO
points ← CONS[list.first, points];
ENDLOOP;
ENDLOOP;
pointCount ← pointCount + thisCount;
ENDLOOP;
};
OutlineHitDataAsSimpleCurve:
PROC [slice: Outline, hitData:
REF
ANY]
RETURNS [simpleCurve:
REF
ANY] = {
simpleCurve will be of type Edge, Arc, etc. There will be Conic and Bezier types as well.
outlineHitData: OutlineHitData ← NARROW[hitData];
traj: Traj ← outlineHitData.traj;
SELECT outlineHitData.hitType
FROM
joint, controlPoint => RETURN[NIL];
segment => {
segNum: INT ← outlineHitData.segNum;
seg: Segment ← GGTraj.FetchSegment[traj, segNum];
simpleCurve ← seg.class.asSimpleCurve[seg, outlineHitData.hitPoint];
};
ENDCASE => ERROR;
};
Style
OutlineSetDefaults:
PROC [slice: Outline, parts: SliceParts, defaults: DefaultData] = {
realParts: OutlineParts ←
NARROW[parts];
maybe should implement parts=NIL to mean all parts
slice.fillColor ← defaults.fillColor;
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first #
NIL
THEN {
this is inefficient but easy to implement
seq: Sequence ← list.first;
GGSequence.SetStrokeWidth[seq, defaults.strokeWidth];
GGTraj.SetTrajStrokeJoint[seq.traj, defaults.strokeJoint];
GGSequence.SetStrokeEnd[seq, defaults.strokeEnd];
GGSequence.SetStrokeDashed[seq, defaults.dashed, GGUtility.CopyPattern[defaults.pattern], defaults.offset, defaults.length];
GGSequence.SetStrokeColor[seq, defaults.strokeColor];
};
ENDLOOP;
};
OutlineSetStrokeWidth:
PROC [slice: Outline, parts: SliceParts, strokeWidth:
REAL]
RETURNS [box: BoundBox] = {
Sets the stroke width of the named parts of slice to be strokeWidth.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN GGSequence.SetStrokeWidth[list.first, strokeWidth];
ENDLOOP;
The following is very conservative and wasteful
UpdateBoundBox[slice];
box ← slice.boundBox;
};
OutlineGetStrokeWidth:
PROC [slice: Outline, parts: SliceParts]
RETURNS [strokeWidth:
REAL] = {
Get the stroke width of the first segment you come to.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN RETURN[GGSequence.GetStrokeWidth[list.first]];
ENDLOOP;
RETURN[-1.0];
};
OutlineSetStrokeEnd:
PROC [slice: Outline, parts: SliceParts, strokeEnd: StrokeEnd] = {
Sets the stroke end of the named parts of slice to be strokeEnd.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN GGSequence.SetStrokeEnd[list.first, strokeEnd];
ENDLOOP;
};
OutlineGetStrokeEnd:
PROC [slice: Outline, parts: SliceParts]
RETURNS [strokeEnd: StrokeEnd] = {
Get the stroke end of the first segment you come to.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN RETURN[GGSequence.GetStrokeEnd[list.first]];
ENDLOOP;
RETURN[round];
};
OutlineSetStrokeJoint:
PROC [slice: Outline, parts: SliceParts, strokeJoint: StrokeJoint] = {
Sets the stroke Joint of the named parts of slice to be strokeEnd.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN GGTraj.SetTrajStrokeJoint[list.first.traj, strokeJoint];
ENDLOOP;
};
OutlineGetStrokeJoint:
PROC [slice: Outline, parts: SliceParts]
RETURNS [strokeJoint: StrokeJoint] = {
Get the stroke joint of the first segment you come to.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN RETURN[GGTraj.GetTrajStrokeJoint[list.first.traj]];
ENDLOOP;
RETURN[round];
};
OutlineSetStrokeColor:
PROC [slice: Outline, parts: SliceParts, color: Color] = {
Sets the stroke color of the named parts of slice to be color.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN GGSequence.SetStrokeColor[list.first, color];
ENDLOOP;
};
OutlineGetStrokeColor:
PROC [slice: Outline, parts: SliceParts]
RETURNS [color: Color] = {
Get the stroke color of the named parts of slice.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN RETURN[GGSequence.GetStrokeColor[list.first]];
ENDLOOP;
RETURN[NIL];
};
Outline
SetFillColor:
PROC [slice: Outline, color: Color] = {
Sets the fill color of the slice to be color.
slice.fillColor ← color;
};
OutlineGetFillColor:
PROC [slice: Outline]
RETURNS [color: Color] = {
Get the fill color of the slice.
color ← slice.fillColor;
};
OutlineSetDashed:
PUBLIC
PROC [slice: Outline, parts: SliceParts, dashed:
BOOL, pattern: SequenceOfReal ←
NIL, offset:
REAL ← 0.0, length:
REAL ← -1.0] = {
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first # NIL THEN GGSequence.SetStrokeDashed[list.first, dashed, pattern, offset, length];
ENDLOOP;
};
OutlineGetDashed:
PROC [slice: Outline, parts: SliceParts]
RETURNS [dashed:
BOOL, pattern: SequenceOfReal, offset, length:
REAL] = {
Get the dash pattern of the named parts of slice.
realParts: OutlineParts ← NARROW[parts];
FOR list:
LIST
OF Sequence ← realParts.seqs, list.rest
UNTIL list =
NIL
DO
IF list.first #
NIL
THEN
{
[dashed, pattern, offset, length] ← GGSequence.GetStrokeDashed[list.first];
RETURN;
};
ENDLOOP;
RETURN[FALSE, NIL, 0.0, 0.0];
};
Utility Routines
AppendTrajList:
PUBLIC PROC [list1, list2:
LIST
OF Traj]
RETURNS [result:
LIST
OF Traj] = {
pos: LIST OF Traj;
newCell: LIST OF Traj;
Non-destructive (copies the first list).
IF list1 = NIL THEN RETURN[list2];
result ← CONS[list1.first, NIL];
pos ← result;
FOR l:
LIST
OF Traj ← list1.rest, l.rest
UNTIL l =
NIL
DO
newCell ← CONS[l.first, NIL];
pos.rest ← newCell;
pos ← newCell;
ENDLOOP;
pos.rest ← list2;
};
globalOutlineClass: OutlineClass;
Init: PROC [] = {
globalOutlineClass ← MakeOutlineClass[];
};
Init[];
END.