GGSliceImplD.mesa
Contents: Implements some of the Traj slice class (see GGSliceImplE for more).
Copyright Ó 1987, 1988, 1989, 1991, 1992 by Xerox Corporation. All rights reserved.
Pier, July 17, 1991 11:25 am PDT
Bier, April 22, 1993 0:03 am PDT
Doug Wyatt, April 14, 1992 2:34 pm PDT
DIRECTORY
CodeTimer, Feedback, FeedbackTypes, FunctionCache, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGHistoryTypes, GGModelTypes, GGMUserProfile, GGOutline, GGProps, GGSegment, GGSegmentTypes, GGSequence, GGShapes, GGSlice, GGTraj, GGTransform, GGUtility, Imager, ImagerPath, ImagerTransformation, RealFns, Rope, Rosary, Vectors2d;
GGSliceImplD:
CEDAR
PROGRAM
IMPORTS CodeTimer, Feedback, FunctionCache, GGBoundBox, GGCoreOps, GGMUserProfile, GGProps, GGSegment, GGSequence, GGShapes, GGSlice, GGTraj, GGTransform, GGUtility, Imager, ImagerTransformation, RealFns, Rosary, Vectors2d
EXPORTS GGSlice = BEGIN
BitVector: TYPE = GGBasicTypes.BitVector;
BoundBox: TYPE = GGCoreTypes.BoundBox;
BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj;
Camera: TYPE = GGModelTypes.Camera;
Circle: TYPE = GGBasicTypes.Circle;
Color: TYPE = Imager.Color;
ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator;
DefaultData: TYPE = GGModelTypes.DefaultData;
EditConstraints: TYPE = GGModelTypes.EditConstraints;
MsgRouter: TYPE = FeedbackTypes.MsgRouter;
HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent;
HitType: TYPE = GGModelTypes.TrajPartType;
Joint: TYPE = GGSegmentTypes.Joint;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
JointObj: TYPE = GGSegmentTypes.JointObj;
Line: TYPE = GGCoreTypes.Line;
Object: TYPE = Imager.Object;
OutlineData: TYPE = GGOutline.OutlineData;
Point: TYPE = GGBasicTypes.Point;
PointAndDone: TYPE = GGModelTypes.PointAndDone;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj;
PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone;
Scene: TYPE = GGModelTypes.Scene;
SegAndIndex: TYPE = GGSequence.SegAndIndex;
SegCPSequence: TYPE = GGTraj.SegCPSequence;
SegCPSequenceObj: TYPE = GGTraj.SegCPSequenceObj;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentClass: TYPE = GGSegmentTypes.SegmentClass;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj;
SelectionClass: TYPE = GGSegmentTypes.SelectionClass;
SelectMode: TYPE = GGModelTypes.SelectMode;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceOfReal: TYPE = GGCoreTypes.SequenceOfReal;
Slice: TYPE = GGModelTypes.Slice;
SliceBoundBoxProc: TYPE = GGModelTypes.SliceBoundBoxProc;
SliceClass: TYPE = GGModelTypes.SliceClass;
SliceClassObj: TYPE = GGModelTypes.SliceClassObj;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceObj: TYPE = GGModelTypes.SliceObj;
SliceParts: TYPE = GGModelTypes.SliceParts;
StrokeEnd: TYPE = GGModelTypes.StrokeEnd;
StrokeJoint: TYPE = GGModelTypes.StrokeJoint;
Traj: TYPE = GGModelTypes.Traj;
TrajData: TYPE = GGModelTypes.TrajData;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
TrajHitData: TYPE = GGTraj.TrajHitData;
TrajHitDataObj: TYPE = GGTraj.TrajHitDataObj;
TrajParts: TYPE = GGModelTypes.TrajParts;
TrajPartsObj: TYPE = GGModelTypes.TrajPartsObj;
TrajPartType: TYPE = GGModelTypes.TrajPartType;
Transformation: TYPE = GGModelTypes.Transformation;
Vector: TYPE = GGBasicTypes.Vector;
WalkProc: TYPE = GGModelTypes.WalkProc;
MoveToProc: TYPE = ImagerPath.MoveToProc;
LineToProc: TYPE = ImagerPath.LineToProc;
CurveToProc: TYPE = ImagerPath.CurveToProc;
ConicToProc: TYPE = ImagerPath.ConicToProc;
ArcToProc: TYPE = ImagerPath.ArcToProc;
CPSequence: TYPE = REF CPSequenceObj;
CPSequenceObj:
TYPE =
RECORD [
cpsInSeg: SEQUENCE len: NAT OF SegCPSequence];
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
KillBoundBox:
PUBLIC
PROC [slice: Slice] = {
FOR nextSlice: Slice ¬ slice, nextSlice.parent
UNTIL nextSlice=
NIL
DO
nextSlice.boxValid ¬ FALSE;
nextSlice.tightBoxValid ¬ FALSE;
ENDLOOP;
};
KillBoundBoxOnly:
PUBLIC
PROC [slice: Slice] = {
FOR nextSlice: Slice ¬ slice, nextSlice.parent
UNTIL nextSlice=
NIL
DO
nextSlice.boxValid ¬ FALSE;
ENDLOOP;
};
KillTightBoxOnly:
PUBLIC
PROC [slice: Slice] = {
FOR nextSlice: Slice ¬ slice, nextSlice.parent
UNTIL nextSlice=
NIL
DO
nextSlice.tightBoxValid ¬ FALSE;
ENDLOOP;
};
TrajShapeChangeNotify:
PUBLIC
PROC [slice: Slice] = {
trajData: TrajData ¬ NARROW[slice.data];
KillBoundBox[slice];
trajData.polyline ¬ NIL;
};
Traj-Only Procs
BuildTrajSliceClass:
PUBLIC
PROC []
RETURNS [class: SliceClass] = {
class ¬
NEW[SliceClassObj ¬ [
type: $Traj ,
unlink: TrajUnlink,
Fundamentals
getBoundBox: TrajGetBoundBox,
getTransformedBoundBox: GGSlice.GenericTransformedBoundBox,
getTightBox: TrajGetTightBox,
copy: TrajCopy,
restore: TrajRestore,
Drawing
buildPath: TrajBuildPath,
drawBorder: TrajDrawBorder,
drawParts: TrajDrawParts,
drawTransform: TrajDrawTransform,
drawSelectionFeedback: TrajDrawSelectionFeedback,
drawAttractorFeedback: TrajDrawAttractorFeedback,
attractorFeedbackBoundBox: TrajAttractorFeedbackBoundBox,
saveSelections: TrajSaveSelections,
remakeSelections: TrajRemakeSelections,
Transforming
transform: TrajTransform,
Parts
isEmptyParts: TrajIsEmptyParts,
isCompleteParts: TrajIsCompleteParts,
newParts: TrajNewParts,
unionParts: TrajUnionParts,
differenceParts: TrajDiffParts,
movingParts: TrajMovingParts,
augmentParts: TrajAugmentParts,
alterParts: TrajAlterParts,
setSelectedFields: TrajSetSelectedFields
]];
GGSlice.BuildMoreTrajSliceClass[class]; -- kludge to split class procs across modules
};
TrajUnlink:
PROC [slice: Slice] = {
trajData: TrajData ¬ NARROW[slice.data];
GGSlice.UnlinkSlice[slice];
trajData.segments ¬ trajData.joints ¬ NIL;
};
TrajGetBoundBox:
PROC [slice: Slice, parts: SliceParts]
RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
IF slice.boxValid AND parts=NIL THEN RETURN[slice.boundBox] -- fast case
ELSE {
EnlargeBySegmentBox: GGSequence.SegmentWalkProc = {
SegmentWalkProc: TYPE = PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOL ← FALSE];
GGBoundBox.EnlargeByBox[bBox: box, by: seg.class.boundBox[seg]];
};
trajData: TrajData ¬ NARROW[slice.data];
trajParts: TrajParts ¬
NARROW[parts];
box ¬ GGBoundBox.NullBoundBox[];
IF parts=NIL THEN [] ¬ GGSequence.WalkSegmentsInTraj[trajData, EnlargeBySegmentBox]
ELSE [] ¬ GGSequence.WalkSegmentsInSequence[trajData, trajParts, EnlargeBySegmentBox];
IF parts=
NIL
THEN {
-- set up cache for fast case next time around
GGSlice.KillBoundBoxOnly[slice.parent]; -- invalidate ancestor caches
slice.boundBox ¬ box;
slice.boxValid ¬ TRUE;
};
};
};
TrajGetTightBox:
PROC [slice: Slice, parts: SliceParts]
RETURNS [box: BoundBox] = {
GGModelTypes.SliceBoundBoxProc
trajData: TrajData ¬ NARROW[slice.data];
IF slice.tightBoxValid AND parts=NIL THEN RETURN[slice.tightBox] -- fast case
ELSE {
EnlargeBySegmentTightBox: GGSequence.SegmentWalkProc = {
SegmentWalkProc: TYPE = PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOL ← FALSE];
GGBoundBox.EnlargeByBox[bBox: box, by: seg.class.tightBox[seg]];
};
trajData: TrajData ¬ NARROW[slice.data];
trajParts: TrajParts ¬ NARROW[parts];
box ¬ GGBoundBox.NullBoundBox[];
IF parts=NIL THEN [] ¬ GGSequence.WalkSegmentsInTraj[trajData, EnlargeBySegmentTightBox]
ELSE [] ¬ GGSequence.WalkSegmentsInSequence[trajData, trajParts, EnlargeBySegmentTightBox];
IF parts=
NIL
THEN {
-- set up cache for fast case next time around
GGSlice.KillTightBoxOnly[slice.parent]; -- invalidate ancestor caches
slice.tightBox ¬ box;
slice.tightBoxValid ¬ TRUE;
};
};
};
TrajCopy:
PROC [slice: Slice, parts: SliceParts ¬
NIL]
RETURNS [copy:
LIST
OF Slice] = {
GGModelTypes.SliceCopyProc
trajParts: TrajParts;
runs: GGSequence.SequenceGenerator;
IF parts=NIL THEN RETURN[LIST[CopyTraj[slice]]]; -- quickly copy the whole thing
trajParts ¬ NARROW[parts];
CodeTimer.StartInt[$CopyTraj, $Gargoyle];
runs ¬ GGSequence.RunsInSequence[trajParts].seqGen;
FOR nextSeq: TrajParts ¬ GGSequence.NextSequence[runs], GGSequence.NextSequence[runs]
UNTIL nextSeq=
NIL
DO
newSlice: Slice ¬ GGTraj.CopyTrajFromRun[slice, nextSeq];
NARROW[newSlice.data, TrajData].forward ¬ NARROW[slice.data, TrajData].forward;
GGProps.CopyAll[fromSlice: slice, toSlice: newSlice];
copy ¬ CONS[newSlice, copy];
ENDLOOP;
CodeTimer.StopInt[$CopyTraj, $Gargoyle];
};
CopyTraj:
PROC [original: Traj]
RETURNS [copy: Traj] = {
Rosary will call CopyEachSegment, which in turn maps CopySegmentAndBuild to each of the desired elements. The copies are made and are passed to procedure q of Rosary, which assembles them into a new Rosary.
trajData: TrajData ¬ NARROW[original.data];
originalSegments: Rosary.ROSARY ¬ trajData.segments;
desiredSegments: Rosary.Segment ¬ [originalSegments, 0, trajData.segCount];
extractedSegments: Rosary.ROSARY;
originalJoints: Rosary.ROSARY ¬ trajData.joints;
desiredJoints: Rosary.Segment ¬ [originalJoints, 0, GGTraj.HiJoint[original] + 1];
extractedJoints: Rosary.ROSARY;
CopyEachSegment:
PROC[q:
PROC[item: Rosary.Item, repeat:
INT ¬ 1]] = {
CopySegmentAndBuild:
PROC [item: Rosary.Item]
RETURNS [quit:
BOOL ¬
FALSE] = {
copy, oldSeg: Segment;
oldSeg ¬ NARROW[item];
copy ¬ GGSegment.CopySegment[oldSeg];
q[copy, 1];
};
[] ¬ Rosary.Map[desiredSegments, CopySegmentAndBuild];
};
CopyEachJoint:
PROC[q:
PROC[item: Rosary.Item, repeat:
INT ¬ 1]] = {
CopyJointAndBuild:
PROC [item: Rosary.Item]
RETURNS [quit:
BOOL ¬
FALSE] = {
copy, oldJoint: Joint;
oldJoint ¬ NARROW[item];
copy ← CopyJoint[oldJoint];
copy ¬ NEW[JointObj ¬ [point: oldJoint.point, TselectedInFull: oldJoint.TselectedInFull] ];
q[copy, 1];
};
[] ¬ Rosary.Map[desiredJoints, CopyJointAndBuild];
};
CodeTimer.StartInt[$CopyTraj, $Gargoyle];
extractedSegments ¬ Rosary.FromRuns[CopyEachSegment];
extractedJoints ¬ Rosary.FromRuns[CopyEachJoint];
copy ¬ GGTraj.CreateTrajFromData[trajData.role, trajData.segCount, extractedSegments, extractedJoints, NIL, trajData.visibleJoints, trajData.strokeJoint, trajData.loArrow, trajData.hiArrow];
NARROW[copy.data, TrajData].forward ¬ NARROW[original.data, TrajData].forward;
GGProps.CopyAll[fromSlice: original, toSlice: copy];
CodeTimer.StopInt[$CopyTraj, $Gargoyle];
};
TrajRestore:
PROC [from: Slice, to: Slice]
= {
GGModelTypes.SliceRestoreProc
from and to must be identically structured slices, probably because from was made as a copy of to. Restore the contents of from, getting all non-REF values from to. Good luck.
IF to=NIL OR from=NIL THEN ERROR;
IF to.class#from.class THEN ERROR;
IF to.class.type#$Traj THEN ERROR;
BEGIN
fromData: TrajData ¬ NARROW[from.data];
toData: TrajData ¬ NARROW[to.data];
ASSERT: trajDatas can be copied without following REF chains
toData ¬ fromData;
to.selectedInFull ¬ from.selectedInFull; -- RECORD of BOOL
to.normalSelectedParts ¬ NIL; -- caller must reselect
to.hotSelectedParts ¬ NIL; -- caller must reselect
to.activeSelectedParts ¬ NIL; -- caller must reselect
to.matchSelectedParts ¬ NIL; -- caller must reselect
to.nullDescriptor is always valid
to.fullDescriptor is unused
to.tightBox ¬ from.tightBox;
to.tightBoxValid ¬ from.tightBoxValid;
to.boundBox ¬ from.boundBox;
to.boxValid ¬ from.boxValid;
to.onOverlay ¬ from.onOverlay;
to.extraPoints ¬ from.extraPoints;
to.priority ¬ from.priority;
to.historyTop ¬ from.historyTop;
END;
};
BuildConstrainedPathSeg:
PROC [slice: Slice, transformParts: SliceParts, segNum:
NAT, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL, controlPoints: BitVector, editConstraints: EditConstraints, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc] = {
IF entire THEN seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo]
ELSE {
dirVec: Vector;
distance: REAL;
partialTransform, ctrlLoTrans, ctrlHiTrans: Transformation;
newJoint: Point;
ctrlPoint: Point;
ctrlPoints: BitVector ¬ NEW[GGBasicTypes.BitVectorObj[2]]; -- only need two for a Bezier segment
trajTransformParts: TrajParts ¬ NARROW[transformParts];
ctrlPoints[0] ¬ IF controlPoints=NIL THEN FALSE ELSE controlPoints[0];
ctrlPoints[1] ¬ IF controlPoints=NIL THEN FALSE ELSE controlPoints[1];
ctrlLoTrans ¬ IF ctrlPoints[0] THEN transform ELSE idTransform;
ctrlHiTrans ¬ IF ctrlPoints[1] THEN transform ELSE idTransform;
IF lo
AND
NOT ctrlPoints[0]
THEN {
-- Low side of segment
ctrlPoint ¬ seg.class.controlPointGet[seg, 0];
ctrlPoints[0] ¬ TRUE;
newJoint ¬ ImagerTransformation.Transform[transform, seg.lo];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, newJoint]];
dirVec ¬ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint];
IF editConstraints = tangent
THEN {
distance ¬ Vectors2d.Distance[newJoint, ctrlPoint];
IF dirVec#[0,0] THEN dirVec ¬ Vectors2d.Normalize[dirVec]; -- use exception
ctrlLoTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]];
}
ELSE ctrlLoTrans ¬ ImagerTransformation.PostTranslate[partialTransform, dirVec];
};
IF hi
AND
NOT ctrlPoints[1]
THEN {
-- High side of Segment
ctrlPoint ¬ seg.class.controlPointGet[seg, 1];
ctrlPoints[1] ¬ TRUE;
newJoint ¬ ImagerTransformation.Transform[transform, seg.hi];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, newJoint]];
dirVec ¬ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint];
IF editConstraints = tangent
THEN {
distance ¬ Vectors2d.Distance[newJoint, ctrlPoint];
IF dirVec#[0,0] THEN dirVec ¬ Vectors2d.Normalize[dirVec]; -- use exception
ctrlHiTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]];
}
ELSE ctrlHiTrans ¬ ImagerTransformation.PostTranslate[partialTransform, dirVec];
};
This does some extra work which would not be needed if the entire draw sequence routine were rewritten to use intersegment constraints
IF
NOT ctrlPoints[0]
AND
NOT lo
THEN {
prevSegNum: INT ¬ GGTraj.PreviousSegmentNum[slice, segNum];
IF prevSegNum#-1
AND GGTraj.FetchSegment[slice, prevSegNum].class.type = $Bezier
THEN {
Later should make more general with routines in the different segment classes that allow one to call for particular constraint parameters suited to the type.
IF trajTransformParts.controlPoints[prevSegNum][1]
THEN {
angle: REAL;
transformedPoint: Point;
otherCtrlPoint: Point ¬ seg.class.controlPointGet[GGTraj.FetchSegment[slice, prevSegNum], 1];
otherDirVector: Vector ¬ Vectors2d.VectorFromPoints[seg.lo, otherCtrlPoint];
ctrlPoint ¬ seg.class.controlPointGet[seg, 0];
dirVec ¬ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint];
distance ¬ Vectors2d.Magnitude[dirVec];
transformedPoint ¬ ImagerTransformation.Transform[transform, otherCtrlPoint];
angle ¬ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.lo, transformedPoint]];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.lo]];
ctrlPoints[0] ¬ TRUE;
IF editConstraints = tangent
THEN {
IF transformedPoint # seg.lo
AND otherDirVector # [0,0]
THEN {
--Don't flip based on other cps 0 length vec.
ctrlLoTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}; -- Else just use identity.
}
ELSE {
-- length
IF otherDirVector # [0,0]
THEN {
distance ¬ RealFns.SqRt[Vectors2d.DistanceSquared[seg.lo, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance;
ctrlLoTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}; -- Else just use identity.
};
};
};
};
IF
NOT ctrlPoints[1]
AND
NOT hi
THEN {
nextSegNum: INT ¬ GGTraj.FollowingSegmentNum[slice, segNum];
IF nextSegNum # -1
AND GGTraj.FetchSegment[slice, nextSegNum].class.type = $Bezier
THEN {
IF trajTransformParts.controlPoints[nextSegNum][0]
THEN {
angle: REAL;
transformedPoint: Point;
otherCtrlPoint: Point ¬ seg.class.controlPointGet[GGTraj.FetchSegment[slice, nextSegNum], 0];
otherDirVector: Vector ¬ Vectors2d.VectorFromPoints[seg.hi, otherCtrlPoint];
ctrlPoint ¬ seg.class.controlPointGet[seg, 1];
dirVec ¬ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint];
distance ¬ Vectors2d.Magnitude[dirVec];
transformedPoint ¬ ImagerTransformation.Transform[transform, otherCtrlPoint];
angle ¬ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.hi, transformedPoint]];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.hi]];
ctrlPoints[1] ¬ TRUE;
IF editConstraints = tangent
THEN {
IF transformedPoint # seg.hi
AND otherDirVector # [0,0]
THEN {
--Don't flip based on other cps 0 length vec.
ctrlHiTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}; -- Else just use identity.
}
ELSE {
-- length
IF otherDirVector # [0,0]
THEN {
distance ¬ RealFns.SqRt[Vectors2d.DistanceSquared[seg.hi, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance;
ctrlHiTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}; --Else just use identity.
};
};
};
};
GGSegment.BZSpecialTransformPath[seg, transform, lo, hi, ctrlPoints, ctrlLoTrans, ctrlHiTrans, curveTo];
};
};
Class Procs for Drawing
TrajBuildPath:
PROC [slice: Slice, transformParts: SliceParts, transform: ImagerTransformation.Transformation, moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc, editConstraints: EditConstraints ¬ none] = {
TransformParts:
PROC = {
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
trajParts: TrajParts ¬ NARROW[transformParts];
moveTo[IF trajParts.segments[0] OR trajParts.joints[0] THEN ImagerTransformation.Transform[transform, GGTraj.FetchJointPos[slice, 0]] ELSE GGTraj.FetchJointPos[slice, 0] ];
FOR index:
INT
IN [0..hiSegment]
DO
seg: Segment ¬ GGTraj.FetchSegment[slice, index];
IF seg.class.type=$Bezier AND editConstraints#none THEN BuildConstrainedPathSeg[slice, transformParts, index, seg, transform, trajParts.segments[index], trajParts.joints[index], trajParts.joints[(index+1) MOD trajParts.segments.len], trajParts.controlPoints[index], editConstraints, lineTo, curveTo, conicTo, arcTo]
ELSE seg.class.buildPathTransform[seg, transform, trajParts.segments[index], trajParts.joints[index], trajParts.joints[(index+1) MOD trajParts.segments.len], trajParts.controlPoints[index], lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
TransformEntire:
PROC = {
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
moveTo[ImagerTransformation.Transform[transform, GGTraj.FetchJointPos[slice, 0]] ];
FOR index:
INT
IN [0..hiSegment]
DO
seg: Segment ¬ GGTraj.FetchSegment[slice, index];
IF seg.class.type=$Bezier AND editConstraints#none THEN BuildConstrainedPathSeg[slice, transformParts, index, seg, transform, TRUE, TRUE, TRUE, NIL, editConstraints, lineTo, curveTo, conicTo, arcTo]
ELSE seg.class.buildPathTransform[seg, transform, TRUE, TRUE, TRUE, NIL, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
TransformNone:
PROC = {
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
moveTo[GGTraj.FetchJointPos[slice, 0]];
FOR index:
INT
IN [0..hiSegment]
DO
seg: Segment ¬ GGTraj.FetchSegment[slice, index];
IF seg.class.type=$Bezier AND editConstraints#none THEN BuildConstrainedPathSeg[seg, index, FALSE, FALSE, FALSE, NIL]
ELSE seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
IF transform#NIL THEN IF transformParts=NIL THEN TransformEntire[] ELSE TransformParts[]
ELSE TransformNone[];
};
Traj
DrawBorder:
PROC [slice: Slice, drawParts: SliceParts, transformParts: SliceParts, transform: Transformation, dc: Imager.Context, camera: Camera, quick:
BOOL, editConstraints: EditConstraints ¬ none] = {
Draw the border of the trajectory. Because the CPFeedback plane is reserved for stationary control points, we draw moving control points here. Joints aren't drawn.
DoTrajDrawBorder:
PROC = {
trajDrawParts: TrajParts ¬ NARROW[drawParts];
trajTransformParts: TrajParts ¬ NARROW[transformParts];
drawEntireTraj: BOOL;
transformNone: BOOL;
transformEntireTraj: BOOL;
CodeTimer.StartInt[$TrajDrawBorder, $Gargoyle];
drawEntireTraj ¬ trajDrawParts=NIL OR GGSequence.IsComplete[trajDrawParts];
transformNone ¬ transform=NIL;
transformEntireTraj ¬ NOT transformNone AND (trajTransformParts=NIL OR GGSequence.IsComplete[trajTransformParts]);
IF drawEntireTraj
AND (transformNone
OR (transformEntireTraj
AND ImagerTransformation.CloseToTranslation[transform, identity]))
THEN CachedDrawBorder[dc, slice, transform, trajTransformParts]
ELSE DrawBorderAux[slice, trajDrawParts, trajTransformParts, transform, dc, camera, editConstraints, drawEntireTraj, transformNone, transformEntireTraj];
CodeTimer.StopInt[$TrajDrawBorder, $Gargoyle];
};
Imager.DoSave[dc, DoTrajDrawBorder];
};
identity: Transformation ¬ ImagerTransformation.Scale[1.0];
TrajDrawParts:
PROC [slice: Slice, parts: SliceParts ¬
NIL, dc: Imager.Context, camera: Camera, quick:
BOOL] = {
GGModelTypes.SliceDrawPartsProc
cpCount: NAT;
seg: Segment;
trajData: TrajData ¬ NARROW[slice.data];
trajParts: TrajParts ¬ NARROW[parts];
IF trajParts=NIL THEN DrawTraj[dc, slice]
ELSE IF AllStrokePropsAndColorsEqual[slice] THEN DrawSingleStrokeTrajSeq[dc, slice, parts]
ELSE {
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
Imager.SetStrokeJoint[dc, trajData.strokeJoint];
FOR i:
INT
IN [0..hiSegment]
DO
IF trajParts.segments[i]
THEN {
seg ¬ GGTraj.FetchSegment[slice, i];
IF seg.strokeWidth=0.0 OR seg.color=NIL THEN LOOP;
Imager.SetStrokeWidth[dc, seg.strokeWidth];
Imager.SetStrokeEnd[dc, seg.strokeEnd];
Imager.SetColor[dc, seg.color];
SegMaskStroke[dc, seg];
cpCount ¬ seg.class.controlPointCount[seg];
FOR j:
NAT
IN [0..cpCount)
DO
IF trajParts.controlPoints[i][j]
THEN
GGShapes.DrawCP[dc, seg.class.controlPointGet[seg, j], camera.cpScale];
ENDLOOP;
};
ENDLOOP;
};
};
TrajDrawTransform:
PROC [slice: Slice, parts: SliceParts ¬
NIL, dc: Imager.Context, camera: Camera, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints] = {
GGModelTypes.SliceDrawTransformProc
This routine is not finished.
Should also draw the fill color (since this routine is only called when a Traj is top level).
TrajDrawBorder[slice, NIL, parts, transform, dc, camera, FALSE, editConstraints];
};
TrajDrawSelectionFeedback:
PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: Camera, dragInProgress, caretIsMoving, hideHot, quick:
BOOL] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
DoDrawFeedback:
PROC = {
segGen: GGModelTypes.SegmentGenerator;
jointGen: GGModelTypes.JointGenerator;
seg: Segment;
someNormal, someHot, thisCPisHot, thisCPisSelected: BOOL;
point: Point;
Draw the joints.
sliceTrajData: TrajData ¬ NARROW[slice.data];
selectedTrajParts: TrajParts ¬ NARROW[selectedParts];
hotTrajParts: TrajParts ¬ NARROW[hotParts];
jointGen ¬ GGSequence.JointsInTraj[sliceTrajData];
FOR i:
INT ¬ GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen]
UNTIL i = -1
DO
thisCPisHot ¬ hotTrajParts#NIL AND hotTrajParts.joints[i];
thisCPisSelected ¬ selectedTrajParts#NIL AND selectedTrajParts.joints[i];
point ¬ GGTraj.FetchJointPos[slice, i];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, point, hot, camera.cpScale];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, point, normal, camera.cpScale];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawJoint[dc, point, camera.cpScale];
ENDLOOP;
Draw the control points.
someNormal ¬ selectedTrajParts#NIL;
someHot ¬ hotTrajParts#NIL;
segGen ¬ GGSequence.SegmentsInTraj[sliceTrajData];
FOR next: GGSequence.SegAndIndex ¬ GGSequence.NextSegmentAndIndex[segGen], GGSequence.NextSegmentAndIndex[segGen]
UNTIL next.seg=
NIL
DO
i: NAT ¬ next.index;
seg ¬ next.seg;
IF someNormal
OR someHot
THEN {
FOR j:
INT
IN [0..seg.class.controlPointCount[seg])
DO
thisCPisHot ¬ hotTrajParts#NIL AND hotTrajParts.controlPoints[i][j];
thisCPisSelected ¬ selectedTrajParts#NIL AND selectedTrajParts.controlPoints[i][j];
point ¬ seg.class.controlPointGet[seg, j];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, point, hot, camera.cpScale];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, point, normal, camera.cpScale];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, point, camera.cpScale];
ENDLOOP;
};
ENDLOOP;
};
trajData: TrajData ¬ NARROW[slice.data];
IF caretIsMoving OR dragInProgress THEN RETURN;
IF selectedParts=NIL AND hotParts=NIL THEN RETURN;
IF camera.quality # quality THEN Imager.DoSave[dc, DoDrawFeedback];
};
TrajDrawAttractorFeedback:
PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress:
BOOL, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints] = {
success: BOOL ¬ FALSE;
partType: TrajPartType;
traj: Traj ¬ slice;
seg: Segment;
jointNum, segNum: NAT;
[success, partType, ----, ----, jointNum , ----, ----, seg, segNum] ¬ GGTraj.UnpackSimpleDescriptor[slice, attractorParts];
IF NOT success THEN ERROR; -- for debugging
SELECT partType
FROM
joint => {
-- highlight the cps and joints of the two adjacent segments
prev: INT ¬ GGTraj.PreviousSegmentNum[slice, jointNum];
previous: Segment ¬ GGTraj.PreviousSegment[slice, jointNum];
this: Segment ¬ IF jointNum <= GGTraj.HiSegment[slice] THEN GGTraj.FetchSegment[slice, jointNum] ELSE NIL;
IF selectedParts=
NIL
THEN {
IF previous#NIL THEN DrawCpsAndJoints[dc, previous, camera];
IF this#NIL THEN DrawCpsAndJoints[dc, this, camera];
}
ELSE {
If the selected attractor sequence contains either previous or next and dragInProgress, don't draw feedback
IF this#NIL AND NOT (dragInProgress AND GGSequence.ContainsSegmentParts[NARROW[selectedParts], jointNum]) THEN DrawCpsAndJoints[dc, this, camera, GGSlice.DescriptorFromParts[traj, selectedParts], jointNum, editConstraints];
IF previous#NIL AND NOT (dragInProgress AND GGSequence.ContainsSegmentParts[NARROW[selectedParts], GGTraj.PreviousSegmentNum[traj, jointNum]]) THEN DrawCpsAndJoints[dc, previous, camera, GGSlice.DescriptorFromParts[traj, selectedParts], prev, editConstraints];
};
};
segment, controlPoint => DrawCpsAndJoints[dc, seg, camera];
ENDCASE; -- none
};
TrajAttractorFeedbackBoundBox:
PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress:
BOOL, camera: Camera, editConstraints: EditConstraints]
RETURNS [box: BoundBox] = {
success: BOOL ¬ FALSE;
partType: TrajPartType;
traj: Traj ¬ slice;
seg: Segment;
jointNum, segNum: NAT;
box ¬ GGBoundBox.NullBoundBox[];
[success, partType, ----, ----, jointNum , ----, ----, seg, segNum] ¬ GGTraj.UnpackSimpleDescriptor[slice, attractorParts];
IF NOT success THEN ERROR; -- for debugging
SELECT partType
FROM
joint => {
-- highlight the cps and joints of the two adjacent segments
prev: INT ¬ GGTraj.PreviousSegmentNum[slice, jointNum];
previous: Segment ¬ GGTraj.PreviousSegment[slice, jointNum];
this: Segment ¬ IF jointNum <= GGTraj.HiSegment[slice] THEN GGTraj.FetchSegment[slice, jointNum] ELSE NIL;
IF selectedParts=
NIL
THEN {
IF previous#NIL THEN EnlargeByCpsAndJoints[box, previous, camera];
IF this#NIL THEN EnlargeByCpsAndJoints[box, this, camera];
}
ELSE {
If the selected attractor sequence contains either previous or next and dragInProgress, don't draw feedback
IF this#NIL AND NOT (dragInProgress AND GGSequence.ContainsSegmentParts[NARROW[selectedParts], jointNum]) THEN EnlargeByCpsAndJoints[box, this, camera, GGSlice.DescriptorFromParts[traj, selectedParts], jointNum, editConstraints];
IF previous#NIL AND NOT (dragInProgress AND GGSequence.ContainsSegmentParts[NARROW[selectedParts], GGTraj.PreviousSegmentNum[traj, jointNum]]) THEN EnlargeByCpsAndJoints[box, previous, camera, GGSlice.DescriptorFromParts[traj, selectedParts], prev, editConstraints];
};
};
segment, controlPoint => EnlargeByCpsAndJoints[box, seg, camera];
ENDCASE; -- none
GGBoundBox.EnlargeByOffset[box, GGModelTypes.halfJointSize*camera.cpScale +1];
};
The Imager Object Cache
Props: TYPE = REF PropsRec;
PropsRec:
TYPE =
RECORD [
slice: Slice,
hiSeg: NAT,
cpCount: SEQUENCE len: NAT OF NAT
];
FindImagerObject:
PROC [slice: Slice, trajData: TrajData]
RETURNS [object: Imager.Object, displacement: Vector ¬ [0,0]] = {
EqualPattern:
PROC [p1, p2: SequenceOfReal]
RETURNS [
BOOL ¬
FALSE] = {
IF p1.len#p2.len THEN RETURN[FALSE];
FOR i:
NAT
IN [0.. p1.len)
DO
IF p1[i]#p2[i] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
CompareProps: FunctionCache.CompareProc = {
cachedProps: Props ¬ NARROW[argument];
cachedSlice: Slice ¬ cachedProps.slice;
cachedHiSeg: NAT ¬ cachedProps.hiSeg;
cachedTrajData: TrajData ¬ NARROW[cachedSlice.data];
cachedSeg, seg: Segment;
epsilon: REAL = 0.001;
IF cachedHiSeg # hiSeg THEN RETURN[FALSE];
IF cachedTrajData.strokeJoint # trajData.strokeJoint THEN RETURN[FALSE];
FOR i:
NAT
IN [0..cachedHiSeg]
DO
cachedSeg ¬ GGTraj.FetchSegment[cachedSlice, i];
seg ¬ GGTraj.FetchSegment[slice, i];
Check segment types
IF seg.class # cachedSeg.class THEN RETURN[FALSE];
Check geometric properties
IF NOT GGSegment.SameShape[seg, cachedSeg] THEN RETURN[FALSE];
displacement ¬ Vectors2d.Sub[seg.lo, cachedSeg.lo];
IF NOT ClosePoints[Vectors2d.Add[cachedSeg.hi, displacement], seg.hi] THEN RETURN[FALSE];
IF cachedProps.cpCount[i] # seg.class.controlPointCount[seg] THEN RETURN[FALSE];
FOR j:
NAT
IN [0..cachedProps.cpCount[i])
DO
IF NOT ClosePoints[Vectors2d.Add[cachedSeg.class.controlPointGet[cachedSeg, j], displacement], seg.class.controlPointGet[seg, j]] THEN RETURN[FALSE];
ENDLOOP;
Check style properties
IF ABS[cachedSeg.strokeWidth-seg.strokeWidth] > epsilon THEN RETURN[FALSE];
IF cachedSeg.strokeEnd#seg.strokeEnd
THEN
RETURN[
FALSE];
IF NOT GGCoreOps.EquivalentColors[cachedSeg.color, seg.color] THEN RETURN[FALSE];
IF cachedSeg.dashed#seg.dashed THEN RETURN[FALSE];
IF cachedSeg.dashed
AND (
cachedSeg.offset#seg.offset OR
cachedSeg.length#seg.length OR
NOT EqualPattern[cachedSeg.pattern, seg.pattern]) THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
value: FunctionCache.Range;
ok: BOOL;
hiSeg: NAT ¬ GGTraj.HiSegment[slice];
[value, ok] ¬ FunctionCache.Lookup[x: cache, compare: CompareProps, clientID: $GGTrajObject];
RETURN [IF ok THEN NARROW[value] ELSE NIL, displacement];
};
ClosePoints:
PROC [p1, p2: Point]
RETURNS [
BOOL] = {
epsilon: REAL = 0.01; -- control points within 0.01 pixels should be plenty close enough
RETURN[ABS[p1.x-p2.x] < epsilon AND ABS[p1.y-p2.y] < epsilon];
};
CreateProps:
PROC [slice: Slice, trajData: TrajData]
RETURNS [props: Props] = {
hiSeg: NAT ¬ GGTraj.HiSegment[slice];
seg: Segment;
props ¬ NEW[PropsRec[hiSeg+1]];
props.slice ¬ TrajCopy[slice].first;
props.hiSeg ¬ hiSeg;
FOR i:
NAT
IN [0..hiSeg]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
props.cpCount[i] ¬ seg.class.controlPointCount[seg];
ENDLOOP;
};
Auxiliary Procs for Drawing
CachedDrawBorder:
PROC [dc: Imager.Context, slice: Slice, view
View: Transformation, trajTransformParts: TrajParts] = {
OPEN ImagerTransformation; -- see Transform and InverseTransform
object: Imager.Object;
position: Imager.VEC;
trajProps: Props;
trajData: TrajData ¬ NARROW[slice.data];
IF GGMUserProfile.GetTurboOn[]
THEN {
[object, position] ¬ FindImagerObject[slice, trajData];
IF object =
NIL
THEN {
-- put traj in cache
clipR: Imager.Rectangle ¬
GGBoundBox.RectangleFromBoundBox[TrajGetBoundBox[slice, NIL]];
props: Props ¬ CreateProps[slice, trajData]; -- props of this Imager.Object
object ¬ NEW[Imager.ObjectRep ¬ [draw: TrajDrawObject, clip: clipR, data: props]];
position ¬ [0,0];
FunctionCache.Insert[cache, props, object, 60, $GGTrajObject];
};
trajProps ¬ NARROW[object.data];
IF viewView # NIL THEN Imager.ConcatT[dc, viewView];
Imager.DrawObject[context: dc, object: object, interactive: TRUE, position: position];
}
ELSE DrawBorderAux[slice, NIL, trajTransformParts, viewView, dc, NIL, none, TRUE, viewView = NIL, viewView # NIL];
};
TrajDrawObject:
PROC [self: Imager.Object, context: Imager.Context] = {
trajProps: Props ¬ NARROW[self.data];
DrawBorderAux[trajProps.slice, NIL, NIL, NIL, context, NIL, none, TRUE, TRUE, FALSE];
};
DrawBorderAux:
PROC [slice: Slice, trajDrawParts: TrajParts, trajTransformParts: TrajParts, transform: Transformation, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints ¬ none, drawEntireTraj, transformNone, transformEntireTraj:
BOOL] = {
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
IF drawEntireTraj
AND AllStrokePropsAndColorsEqual[slice]
THEN {
TrajDrawBorderSingleStroke[slice, trajTransformParts, transform, dc, editConstraints];
IF transform#
NIL
THEN DrawRubberControlPoints[dc, trajTransformParts, slice, transform, camera];
RETURN;
};
FOR i:
INT
IN [0..hiSegment]
DO
IF drawEntireTraj
OR trajDrawParts.segments[i]
THEN {
-- draw this segment
seg: Segment ¬ GGTraj.FetchSegment[slice, i];
IF seg.strokeWidth=0.0 OR seg.color=NIL THEN LOOP;
Imager.SetStrokeJoint[dc, bevel]; -- some contexts require that this be set to something
Imager.SetStrokeEnd[dc, seg.strokeEnd];
Imager.SetStrokeWidth[dc, seg.strokeWidth];
Imager.SetColor[dc, seg.color];
SELECT
TRUE
FROM
transformNone =>
MaskStrokeTransform[
dc: dc, seg: seg, transform: transform,
entire: FALSE, lo: FALSE, hi: FALSE, controlPoints: NIL, -- nothing transformed
slice: slice, transformParts: trajTransformParts, segNum: i, camera: camera, editConstraints: editConstraints];
transformEntireTraj =>
MaskStrokeTransform[
dc: dc, seg: seg, transform: transform,
entire: TRUE, lo: TRUE, hi: TRUE, controlPoints: NIL, -- everything transformed
slice: slice, transformParts: trajTransformParts, segNum: i, camera: camera, editConstraints: editConstraints];
ENDCASE =>
MaskStrokeTransform[
dc: dc, seg: seg, transform: transform,
entire: trajTransformParts.segments[i], lo: trajTransformParts.joints[i], hi: trajTransformParts.joints[GGTraj.FollowingJoint[slice, i]], controlPoints: trajTransformParts.controlPoints[i],
slice: slice, transformParts: trajTransformParts, segNum: i, camera: camera, editConstraints: editConstraints];
};
ENDLOOP;
};
TrajDrawBorderSingleStroke:
PROC [slice: Slice, trajParts: TrajParts, transform: Transformation, dc: Imager.Context, editConstraints: EditConstraints] = {
BuildPathNoTransform: Imager.PathProc = {
seg: Segment;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
firstPoint: Point ¬ GGTraj.FetchJointPos[slice, 0];
moveTo[firstPoint];
FOR i:
INT
IN [0..hiSegment]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
BuildPathTransform: Imager.PathProc = {
seg: Segment;
entire, lo, hi: BOOL ¬ FALSE;
controlPoints: BitVector;
firstPoint: Point;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
Move to first joint.
entire ¬ trajParts=NIL OR trajParts.segments[0];
lo ¬ trajParts=NIL OR trajParts.joints[0];
firstPoint ¬ GGTraj.FetchJointPos[slice, 0];
IF entire OR lo THEN firstPoint ¬ ImagerTransformation.Transform[transform, firstPoint];
moveTo[firstPoint];
Build the segment paths.
FOR i:
INT
IN [0..hiSegment]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
entire ¬ trajParts=NIL OR trajParts.segments[i];
lo ¬ trajParts=NIL OR trajParts.joints[i];
hi ¬ trajParts=NIL OR trajParts.joints[GGTraj.FollowingJoint[slice, i]];
controlPoints ¬ trajParts.controlPoints[i];
IF seg.class.type = $Bezier
AND editConstraints # none
THEN {
BuildConstrainedPathSeg[slice, trajParts, i, seg, transform, entire, lo, hi, controlPoints, editConstraints, lineTo, curveTo, conicTo, arcTo];
Still needs to be updated to draw moving control points.
}
ELSE {
seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo];
};
ENDLOOP;
};
PatternProc: PROC [i: NAT] RETURNS [REAL] = {RETURN[pattern[i]];};
trajData: TrajData ¬ NARROW[slice.data];
firstSeg: Segment ¬ GGTraj.FetchSegment[slice, 0];
strokeWidth: REAL ¬ firstSeg.strokeWidth;
pattern: SequenceOfReal ¬ firstSeg.pattern;
IF strokeWidth # 0.0
AND firstSeg.color #
NIL
THEN {
Imager.SetColor[dc, firstSeg.color];
Imager.SetStrokeWidth[dc, strokeWidth];
Imager.SetStrokeEnd[dc, firstSeg.strokeEnd];
Imager.SetStrokeJoint[dc, trajData.strokeJoint];
IF firstSeg.dashed
THEN
Imager.MaskDashedStroke[dc,
IF transform=NIL THEN BuildPathNoTransform ELSE BuildPathTransform,
pattern.len, PatternProc, firstSeg.offset, firstSeg.length]
ELSE
Imager.MaskStroke[dc,
IF transform=NIL THEN BuildPathNoTransform ELSE BuildPathTransform,
trajData.role=fence OR trajData.role=hole];
};
};
-- end TrajDrawBorderSingleStroke
DrawRubberControlPoints:
PROC [dc: Imager.Context, trajParts: TrajParts, slice: Slice, transform: ImagerTransformation.Transformation, camera: Camera] = {
Draw any of the moving control points of rubber-banding segments.
controlPoints: BitVector;
seg: Segment;
cpCount: NAT;
cPointsInSeq: CPSequence;
cPointsInSeg: SegCPSequence;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
drawForTraj, drawForSeg: BOOL ¬ FALSE;
IF trajParts = NIL THEN RETURN;
Figure out which control points of each segment to draw.
FOR i:
INT
IN [0..hiSegment]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
controlPoints ¬ trajParts.controlPoints[i];
cpCount ¬ seg.class.controlPointCount[seg];
IF
NOT trajParts.segments[i]
THEN {
FOR j:
NAT
IN [0..cpCount)
DO
IF controlPoints[j]
THEN {
IF NOT drawForTraj THEN cPointsInSeq ¬ NEW[CPSequenceObj[GGTraj.HiSegment[slice] + 1]];
IF NOT drawForSeg THEN cPointsInSeg ¬ NEW[SegCPSequenceObj[cpCount]];
cPointsInSeg[j].cPoint ¬ ImagerTransformation.Transform[transform, seg.class.controlPointGet[seg, j]];
cPointsInSeg[j].show ¬ TRUE;
drawForTraj ¬ drawForSeg ¬ TRUE;
}
ELSE IF drawForSeg THEN cPointsInSeg[j].show ¬ FALSE;
ENDLOOP;
IF drawForSeg
THEN {
cPointsInSeq[i] ¬ cPointsInSeg;
drawForSeg ¬ FALSE;
};
};
ENDLOOP;
Draw the rubber-banding control points.
IF cPointsInSeq#
NIL
THEN {
FOR i:
NAT
IN [0..cPointsInSeq.len)
DO
IF
NOT trajParts.segments[i]
AND
NOT cPointsInSeq[i]=
NIL
THEN {
FOR j:
NAT
IN [0..cPointsInSeq[i].len)
DO
cPoint: Point ¬ cPointsInSeq[i][j].cPoint;
IF cPointsInSeq[i][j].show THEN GGShapes.DrawCP[dc, cPoint, camera.cpScale];
Perhaps these CPs should be drawn transparent.
ENDLOOP;
};
ENDLOOP;
};
}; -- end of DrawRubberControlPoints
<<TrajDrawBorderSingleStrokeNoTransform:
PROC [slice: Slice, dc: Imager.Context] = {
BuildPathNoTransform: Imager.PathProc = {
seg: Segment;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
firstPoint: Point ¬ GGTraj.FetchJointPos[slice, 0];
moveTo[firstPoint];
FOR i:
INT
IN [0..hiSegment]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
PatternProc: PROC [i: NAT] RETURNS [REAL] = {RETURN[pattern[i]];};
trajData: TrajData ¬ NARROW[slice.data];
firstSeg: Segment ¬ GGTraj.FetchSegment[slice, 0];
strokeWidth: REAL ¬ firstSeg.strokeWidth;
pattern: SequenceOfReal ¬ firstSeg.pattern;
IF strokeWidth # 0.0
AND firstSeg.color #
NIL
THEN {
Imager.SetColor[dc, firstSeg.color];
Imager.SetStrokeWidth[dc, strokeWidth];
Imager.SetStrokeEnd[dc, firstSeg.strokeEnd];
Imager.SetStrokeJoint[dc, trajData.strokeJoint];
IF firstSeg.dashed
THEN Imager.MaskDashedStroke[dc, BuildPathNoTransform, pattern.len, PatternProc, firstSeg.offset, firstSeg.length]
ELSE Imager.MaskStroke[dc, BuildPathNoTransform, trajData.role = fence OR trajData.role = hole];
};
}; -- end TrajDrawBorderSingleStrokeNoTransform
>>
DrawSingleStrokeTraj:
PROC [dc: Imager.Context, slice: Slice] = {
BuildPath: Imager.PathProc = {
seg: Segment;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
firstPoint: Point ¬ GGTraj.FetchJointPos[slice, 0];
moveTo[firstPoint];
FOR i:
INT
IN [0..hiSegment]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
ENDLOOP;
};
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
trajData: TrajData ¬ NARROW[slice.data];
firstSeg: Segment ¬ GGTraj.FetchSegment[slice, 0];
strokeWidth: REAL ¬ firstSeg.strokeWidth;
pattern: SequenceOfReal ¬ firstSeg.pattern;
IF strokeWidth=0.0 OR firstSeg.color=NIL THEN RETURN;
Imager.SetStrokeWidth[dc, strokeWidth];
Imager.SetStrokeEnd[dc, firstSeg.strokeEnd];
Imager.SetStrokeJoint[dc, trajData.strokeJoint];
Imager.SetColor[dc, firstSeg.color];
IF firstSeg.dashed THEN Imager.MaskDashedStroke[dc, BuildPath, pattern.len, PatternProc, firstSeg.offset, firstSeg.length]
ELSE Imager.MaskStroke[dc, BuildPath, trajData.role = fence OR trajData.role = hole];
}; -- end DrawSingleStrokeTraj
DrawTraj:
PROC [dc: Imager.Context, slice: Slice] = {
Let Q be a logical variable corresponding to <ggData.camera.quality = quality>.
Let S correspond to <segment J is selected>.
Let O correspond to <segment J is on the overlay plane>.
Then segment J is drawn thick when: qSo.
Segment J is drawn thin otherwise (i.e. when Q + s + O).
DoDrawTraj:
PROC [dc: Imager.Context] = {
seg: Segment;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
Imager.SetStrokeJoint[dc, trajData.strokeJoint];
FOR i:
INT
IN [0..hiSegment]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
IF seg.strokeWidth#0.0
AND seg.color#
NIL
THEN {
Imager.SetStrokeWidth[dc, seg.strokeWidth];
Imager.SetStrokeEnd[dc, seg.strokeEnd];
Imager.SetColor[dc, seg.color];
MaskStroke[dc, seg];
};
ENDLOOP;
};
trajData: TrajData ¬ NARROW[slice.data];
IF AllStrokePropsAndColorsEqual[slice] THEN DrawSingleStrokeTraj[dc, slice]
ELSE DoDrawTraj[dc]; -- why no DoSave here ??
};
DrawSingleStrokeTrajSeq:
PROC [dc: Imager.Context, slice: Slice, parts: SliceParts ¬
NIL] = {
BuildPath: Imager.PathProc = {
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
FOR i:
INT
IN [0..hiSegment]
DO
IF trajParts=
NIL
OR trajParts.segments[i]
THEN {
seg: Segment ¬ GGTraj.FetchSegment[slice, i];
moveTo[seg.lo];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
};
ENDLOOP;
};
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
trajData: TrajData ¬ NARROW[slice.data];
trajParts: TrajParts ¬ NARROW[parts];
firstSeg: Segment ¬ GGTraj.FetchSegment[slice, 0];
pattern: SequenceOfReal ¬ firstSeg.pattern;
IF firstSeg.strokeWidth=0.0 OR firstSeg.color=NIL THEN RETURN;
Imager.SetStrokeWidth[dc, firstSeg.strokeWidth];
Imager.SetStrokeEnd[dc, firstSeg.strokeEnd];
Imager.SetStrokeJoint[dc, trajData.strokeJoint];
Imager.SetColor[dc, firstSeg.color];
IF firstSeg.dashed THEN Imager.MaskDashedStroke[dc, BuildPath, pattern.len, PatternProc, firstSeg.offset, firstSeg.length]
ELSE Imager.MaskStroke[dc, BuildPath, trajData.role = fence OR trajData.role = hole];
};
DrawCpsAndJoints:
PROC [dc: Imager.Context, seg: Segment, camera: Camera, selSeq: Sequence ¬
NIL, segNum:
NAT ¬ 999, editConstraints: EditConstraints ¬ none] = {
point: Point;
GGShapes.DrawJoint[dc, seg.lo, camera.cpScale];
GGShapes.DrawJoint[dc, seg.hi, camera.cpScale];
FOR j:
INT
IN [0..seg.class.controlPointCount[seg])
DO
IF editConstraints = none
OR
NOT GGSequence.IsConstrained[selSeq, segNum, j, editConstraints]
THEN {
point ¬ seg.class.controlPointGet[seg, j];
GGShapes.DrawCP[dc, point, camera.cpScale];
};
ENDLOOP;
};
EnlargeByCpsAndJoints:
PROC [box: BoundBox, seg: Segment, camera: Camera, selSeq: Sequence ¬
NIL, segNum:
NAT ¬ 999, editConstraints: EditConstraints ¬ none] = {
point: Point;
GGBoundBox.EnlargeByPoint[box, seg.lo];
GGBoundBox.EnlargeByPoint[box, seg.hi];
FOR j:
INT
IN [0..seg.class.controlPointCount[seg])
DO
IF editConstraints = none
OR
NOT GGSequence.IsConstrained[selSeq, segNum, j, editConstraints]
THEN {
point ¬ seg.class.controlPointGet[seg, j];
GGBoundBox.EnlargeByPoint[box, point];
};
ENDLOOP;
};
TrajSaveSelections:
PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] = {
GGModelTypes.SliceSaveSelectionsProc
trajParts: TrajParts ¬ NARROW[parts];
IF trajParts=
NIL
THEN GGTraj.ClearSelection[slice, selectClass]
ELSE GGTraj.SaveSelectionInParts[slice, trajParts, selectClass];
};
TrajRemakeSelections:
PROC [slice: Slice, selectClass: SelectionClass]
RETURNS [parts: SliceParts] = {
GGModelTypes.SliceRemakeSelectionsProc
RETURN[GGTraj.RemakeSelection[slice, selectClass]];
};
DrawConstrained:
PROC [dc: Imager.Context, slice: Slice, transformParts: SliceParts, segNum:
NAT, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL, controlPoints: BitVector, editConstraints: EditConstraints, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc]
RETURNS [cPointsInSeg: SegCPSequence] = {
IF entire THEN seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo]
ELSE {
selSeq: TrajParts ¬ NARROW[transformParts];
dirVec: Vector;
newJoint: Point;
distance: REAL;
partialTransform, ctrlLoTrans, ctrlHiTrans: Transformation;
ctrlPoint: Point;
ctrlPoints: BitVector ¬ NEW[GGBasicTypes.BitVectorObj[2]];
ctrlPoints[0] ¬ controlPoints[0];
ctrlPoints[1] ¬ controlPoints[1];
ctrlLoTrans ¬ IF ctrlPoints[0] THEN transform ELSE idTransform;
ctrlHiTrans ¬ IF ctrlPoints[1] THEN transform ELSE idTransform;
IF lo
AND
NOT ctrlPoints[0]
THEN {
-- Low side of segment
ctrlPoint ¬ seg.class.controlPointGet[seg, 0];
ctrlPoints[0] ¬ TRUE;
newJoint ¬ ImagerTransformation.Transform[transform, seg.lo];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, newJoint]];
dirVec ¬ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint];
IF editConstraints = tangent
THEN {
distance ¬ Vectors2d.Distance[newJoint, ctrlPoint];
IF dirVec # [0,0] THEN dirVec ¬ Vectors2d.Normalize[dirVec]; -- use exception
ctrlLoTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]];
}
ELSE ctrlLoTrans ¬ ImagerTransformation.PostTranslate[partialTransform, dirVec];
};
IF hi
AND
NOT ctrlPoints[1]
THEN {
-- High side of Segment
ctrlPoint ¬ seg.class.controlPointGet[seg, 1];
ctrlPoints[1] ¬ TRUE;
newJoint ¬ ImagerTransformation.Transform[transform, seg.hi];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, newJoint]];
dirVec ¬ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint];
IF editConstraints = tangent
THEN {
distance ¬ Vectors2d.Distance[newJoint, ctrlPoint];
IF dirVec # [0,0] THEN dirVec ¬ Vectors2d.Normalize[dirVec]; -- use exception
ctrlHiTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]];
}
ELSE ctrlHiTrans ¬ ImagerTransformation.PostTranslate[partialTransform, dirVec];
};
This does some extra work which would not be needed if the entire draw sequence routine were rewritten to use intersegment constraits
IF
NOT ctrlPoints[0]
AND
NOT lo
THEN {
prevSegNum: INT ¬ GGTraj.PreviousSegmentNum[slice, segNum];
IF prevSegNum # -1
AND GGTraj.FetchSegment[slice, prevSegNum].class.type = $Bezier
THEN {
Later should make more general with routines in the different segment classes that allow one to call for particular constraint parameters suited to the type.
IF selSeq.controlPoints[prevSegNum][1]
THEN {
angle: REAL;
transformedPoint: Point;
otherCtrlPoint: Point ¬ seg.class.controlPointGet[GGTraj.FetchSegment[slice, prevSegNum], 1];
otherDirVector: Vector ¬ Vectors2d.VectorFromPoints[seg.lo, otherCtrlPoint];
ctrlPoint ¬ seg.class.controlPointGet[seg, 0];
dirVec ¬ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint];
distance ¬ Vectors2d.Magnitude[dirVec];
transformedPoint ¬ ImagerTransformation.Transform[transform, otherCtrlPoint];
angle ¬ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.lo, transformedPoint]];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.lo]];
ctrlPoints[0] ¬ TRUE;
IF editConstraints = tangent
THEN {
IF transformedPoint # seg.lo
AND otherDirVector # [0,0]
THEN {
--Don't flip based on other cps 0 length vec.
ctrlLoTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}; -- Else just use identity.
}
ELSE {
-- length
IF otherDirVector # [0,0]
THEN {
distance ¬ RealFns.SqRt[Vectors2d.DistanceSquared[seg.lo, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance;
ctrlLoTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}; -- Else just use identity.
};
};
};
};
IF
NOT ctrlPoints[1]
AND
NOT hi
THEN {
nextSegNum: INT ¬ GGTraj.FollowingSegmentNum[slice, segNum];
IF nextSegNum # -1
AND GGTraj.FetchSegment[slice, nextSegNum].class.type = $Bezier
THEN {
IF selSeq.controlPoints[nextSegNum][0]
THEN {
angle: REAL;
transformedPoint: Point;
otherCtrlPoint: Point ¬ seg.class.controlPointGet[GGTraj.FetchSegment[slice, nextSegNum], 0];
otherDirVector: Vector ¬ Vectors2d.VectorFromPoints[seg.hi, otherCtrlPoint];
ctrlPoint ¬ seg.class.controlPointGet[seg, 1];
dirVec ¬ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint];
distance ¬ Vectors2d.Magnitude[dirVec];
transformedPoint ¬ ImagerTransformation.Transform[transform, otherCtrlPoint];
angle ¬ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.hi, transformedPoint]];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.hi]];
ctrlPoints[1] ¬ TRUE;
IF editConstraints = tangent
THEN {
IF transformedPoint # seg.hi
AND otherDirVector # [0,0]
THEN {
--Don't flip based on other cps 0 length vec.
ctrlHiTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}; -- Else just use identity.
}
ELSE {
-- length
IF otherDirVector # [0,0]
THEN {
distance ¬ RealFns.SqRt[Vectors2d.DistanceSquared[seg.hi, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance;
ctrlHiTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}; --Else just use identity.
};
};
};
};
GGSegment.BZSpecialTransformPath[seg, transform, lo, hi, ctrlPoints, ctrlLoTrans, ctrlHiTrans, curveTo];
IF NOT (ctrlPoints[0] OR ctrlPoints[1]) THEN RETURN[NIL];
cPointsInSeg ¬ NEW[SegCPSequenceObj[2]]; -- For Beziers
IF ctrlPoints[0]
THEN {
cPointsInSeg[0].cPoint ¬ ImagerTransformation.Transform[ctrlLoTrans, seg.class.controlPointGet[seg, 0]];
cPointsInSeg[0].show ¬ TRUE;
}
ELSE cPointsInSeg[0].show ¬ FALSE;
IF ctrlPoints[1]
THEN {
cPointsInSeg[1].cPoint ¬ ImagerTransformation.Transform[ctrlHiTrans, seg.class.controlPointGet[seg, 1]];
cPointsInSeg[1].show ¬ TRUE;
}
ELSE cPointsInSeg[1].show ¬ FALSE;
RETURN[cPointsInSeg];
};
};
SegMaskStroke:
PROC [dc: Imager.Context, seg: Segment] = {
MaskPath: Imager.PathProc = {
[moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc]
moveTo[seg.lo];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
};
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ¬ seg.pattern;
IF seg.dashed THEN Imager.MaskDashedStroke[dc, MaskPath, pattern.len, PatternProc, seg.offset, seg.length]
ELSE Imager.MaskStroke[dc, MaskPath, FALSE];
};
MaskStroke:
PROC [dc: Imager.Context, seg: Segment] = {
MaskPath: Imager.PathProc = {
moveTo[seg.lo];
seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo];
};
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ¬ seg.pattern;
IF seg.dashed THEN Imager.MaskDashedStroke[dc, MaskPath, pattern.len, PatternProc, seg.offset, seg.length]
ELSE Imager.MaskStroke[dc, MaskPath, FALSE];
MaskStrokeTransform:
PROC [dc: Imager.Context, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL, controlPoints: BitVector, slice: Slice, transformParts: TrajParts, segNum:
NAT, camera: Camera, editConstraints: EditConstraints] = {
Draw the segment, transforming those parts described by entire, lo, hi, and controlPoints. If the segment is being rubber-banded, draw its moving control points.
MaskPath: Imager.PathProc = {
loPoint: Point;
cpCount: NAT ¬ seg.class.controlPointCount[seg];
loPoint ¬ IF entire OR lo THEN ImagerTransformation.Transform[transform, seg.lo] ELSE seg.lo;
moveTo[loPoint];
IF seg.class.type=$Bezier AND editConstraints#none THEN [] ¬ DrawConstrained[dc, slice, transformParts, segNum, seg, transform, entire, lo, hi, controlPoints, editConstraints, lineTo, curveTo, conicTo, arcTo]
ELSE seg.class.buildPathTransform[seg, transform, entire, lo, hi, controlPoints, lineTo, curveTo, conicTo, arcTo];
IF transform#
NIL
AND
NOT entire
THEN {
-- might be rubber-banding
FOR j:
NAT
IN [0..cpCount)
DO
IF controlPoints=
NIL
OR controlPoints[j]
THEN {
-- it is rubber-banding
cPoint: Point ¬ ImagerTransformation.Transform[transform, seg.class.controlPointGet[seg, j]];
GGShapes.DrawCP[dc, cPoint, camera.cpScale];
}
ENDLOOP;
};
};
PatternProc:
PROC [i:
NAT]
RETURNS [
REAL] = {
RETURN[pattern[i]];
};
pattern: SequenceOfReal ¬ seg.pattern;
IF seg.dashed THEN Imager.MaskDashedStroke[dc, MaskPath, pattern.len, PatternProc, seg.offset, seg.length]
ELSE Imager.MaskStroke[dc, MaskPath, FALSE];
};
AllStrokePropsAndColorsEqual:
PROC [slice: Slice]
RETURNS [
BOOL ¬
FALSE] = {
seg: Segment;
firstSeg: Segment ¬ GGTraj.FetchSegment[slice, 0];
width: REAL ¬ firstSeg.strokeWidth;
end: StrokeEnd ¬ firstSeg.strokeEnd;
color: Color ¬ firstSeg.color;
dashed: BOOL ¬ firstSeg.dashed;
pattern: SequenceOfReal ¬ firstSeg.pattern;
offset: REAL ¬ firstSeg.offset;
length: REAL ¬ firstSeg.length;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
FOR i:
INT
IN [1..hiSegment]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
IF seg.dashed # dashed THEN RETURN[FALSE];
IF seg.strokeEnd # end THEN RETURN[FALSE];
IF seg.strokeWidth # width THEN RETURN[FALSE];
IF NOT GGCoreOps.EquivalentColors[color, seg.color] THEN RETURN[FALSE];
IF NOT dashed THEN LOOP;
IF seg.offset # offset THEN RETURN[FALSE];
IF seg.length # length THEN RETURN[FALSE];
IF NOT GGUtility.EquivalentPatterns[seg.pattern, pattern] THEN RETURN[FALSE];
REPEAT
FINISHED => RETURN[TRUE];
ENDLOOP;
};
SegmentSelected:
PROC [segNum:
NAT, selectedSeq: TrajParts]
RETURNS [
BOOL ¬
FALSE] = {
RETURN[selectedSeq.segments[segNum]];
};
TrajTransform:
PROC [slice: Slice, parts: SliceParts ¬
NIL, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints, history: HistoryEvent] = {
GGModelTypes.SliceTransformProc
joint: Joint;
seg: Segment;
trajData: TrajData ¬ NARROW[slice.data];
trajParts: TrajParts ¬ NARROW[parts];
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
hiJoint: NAT ¬ GGTraj.HiJoint[slice];
SELECT trajData.role
FROM
open => TransformSequenceOpen[slice, parts, transform, editConstraints];
fence, hole => TransformSequenceClosed[slice, parts, transform, editConstraints];
ENDCASE => ERROR;
FOR i:
NAT
IN [0..hiJoint]
DO
IF trajParts=
NIL
OR trajParts.joints[i]
THEN {
joint ¬ NARROW[Rosary.Fetch[trajData.joints, i]];
joint.point ¬ GGTransform.Transform[transform, joint.point];
};
ENDLOOP;
TrajShapeChangeNotify[slice];
IF trajParts = NIL THEN RETURN;
Update CPs moved explicitly.
FOR i:
NAT
IN [0..hiSegment]
DO
cpCount: NAT ¬ trajParts.controlPoints[i].len;
seg ¬ NARROW[Rosary.Fetch[trajData.segments, i]];
FOR j:
NAT
IN [0..cpCount)
DO
IF trajParts.controlPoints[i][j]
AND
NOT trajParts.segments[i]
THEN {
seg.class.controlPointMoved[seg, transform, j];
};
ENDLOOP;
ENDLOOP;
};
TransformSequenceOpen:
PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints] = {
trajParts: TrajParts ¬ NARROW[parts];
seg: Segment;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
FOR i:
NAT
IN [0..hiSegment]
DO
seg ¬ GGTraj.FetchSegment[slice, i];
IF trajParts=
NIL
OR trajParts.segments[i]
THEN {
GGSegment.TransformSegment[seg, transform];
}
ELSE {
IF seg.class.type = $Bezier
AND editConstraints # none
THEN {
TransformConstrained[slice, parts, i, seg, transform, editConstraints];
}
ELSE {
IF trajParts.joints[i] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]];
IF trajParts.joints[i+1] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]];
};
};
ENDLOOP;
};
TransformSequenceClosed:
PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints] = {
A tricky operation. All selected segments are transformed. Then, all segments which are not selected but which touch a selected joint are told that the joint has moved.
trajParts: TrajParts ¬ NARROW[parts];
seg: Segment;
hiSegment: NAT ¬ GGTraj.HiSegment[slice];
FOR i:
NAT
IN [0..hiSegment)
DO
seg ¬ GGTraj.FetchSegment[slice, i];
IF trajParts=
NIL
OR trajParts.segments[i]
THEN {
GGSegment.TransformSegment[seg, transform];
}
ELSE {
IF seg.class.type = $Bezier
AND editConstraints # none
THEN {
TransformConstrained[slice, parts, i, seg, transform, editConstraints]; -- Same as open since not End.
}
ELSE {
IF trajParts.joints[i] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]];
IF trajParts.joints[i+1] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]];
};
};
ENDLOOP;
seg ¬ GGTraj.FetchSegment[slice, hiSegment];
IF trajParts=
NIL
OR trajParts.segments[hiSegment]
THEN {
GGSegment.TransformSegment[seg, transform];
}
ELSE {
IF seg.class.type = $Bezier
AND editConstraints # none
THEN {
TransformConstrained[slice, parts, hiSegment, seg, transform, editConstraints]; -- Same as open
}
ELSE {
IF trajParts.joints[hiSegment] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]];
IF trajParts.joints[0] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]];
};
};
};
TransformConstrained:
PROC [slice: Slice, parts: SliceParts, segNum:
NAT, seg: Segment, transform:ImagerTransformation.Transformation, editConstraints: GGModelTypes.EditConstraints] = {
nextJointNum: NAT;
dirVec: Vector;
distance: REAL;
ctrlPoint: Point;
ctrlTrans, partialTransform: ImagerTransformation.Transformation;
selSeq: TrajParts ¬ NARROW[parts];
IF selSeq.joints[segNum]
AND
NOT selSeq.controlPoints[segNum][0]
THEN {
-- Low side of segment
ctrlPoint ¬ seg.class.controlPointGet[seg, 0];
dirVec ¬ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint];
GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.lo]];
IF editConstraints = tangent
THEN {
-- ??Should this really behave differently?
distance ¬ Vectors2d.Distance[seg.lo, ctrlPoint];
IF dirVec # [0,0] THEN dirVec ¬ Vectors2d.Normalize[dirVec]; -- use exception
ctrlTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]];
}
ELSE ctrlTrans ¬ ImagerTransformation.PostTranslate[partialTransform, dirVec];
seg.class.controlPointMoved[seg, ctrlTrans, 0];
}
ELSE {
IF selSeq.controlPoints[segNum][0]
THEN {
seg.class.controlPointMoved[seg, transform, 0]; -- Don't move here!!
IF selSeq.joints[segNum] THEN GGSegment.MoveEndPointSegment[seg, TRUE, GGTransform.Transform[transform, seg.lo]];
}
ELSE {
--CP not selected (and not joint selected) so see if constrained movement.
prevSegNum: INT ¬ GGTraj.PreviousSegmentNum[slice, segNum];
IF prevSegNum # -1
AND GGTraj.FetchSegment[slice, prevSegNum].class.type = $Bezier
THEN {
Later should make more general with routines in the different segment classes that allow one to call for particular constraint parameters suited to the type.
IF selSeq.controlPoints[prevSegNum][1]
THEN {
angle: REAL;
transformedPoint: Point;
otherCtrlPoint: Point ¬ seg.class.controlPointGet[GGTraj.FetchSegment[slice, prevSegNum], 1];
otherDirVector: Vector ¬ Vectors2d.VectorFromPoints[seg.lo, otherCtrlPoint];
ctrlPoint ¬ seg.class.controlPointGet[seg, 0];
dirVec ¬ Vectors2d.VectorFromPoints[seg.lo, ctrlPoint];
distance ¬ Vectors2d.Magnitude[dirVec];
transformedPoint ¬ ImagerTransformation.Transform[transform, otherCtrlPoint];
angle ¬ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.lo, transformedPoint]];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.lo]];
IF editConstraints = tangent
THEN {
IF transformedPoint # seg.lo
AND otherDirVector # [0,0]
THEN {
-- Don't flip based on 0 length vec.
ctrlTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}
ELSE ctrlTrans ¬ ImagerTransformation.Scale[1]; -- Identity.
}
ELSE {
-- length
IF otherDirVector # [0,0]
THEN {
distance ¬ RealFns.SqRt[Vectors2d.DistanceSquared[seg.lo, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance;
ctrlTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}
ELSE ctrlTrans ¬ ImagerTransformation.Scale[1]; -- Identity.
};
seg.class.controlPointMoved[seg, ctrlTrans, 0];
};
};
};
};
Similar code for the high side of the segment.
nextJointNum ¬ GGTraj.FollowingJoint[slice, segNum];
IF selSeq.joints[nextJointNum]
AND
NOT selSeq.controlPoints[segNum][1]
THEN {
-- High side of Segment
ctrlPoint ¬ seg.class.controlPointGet[seg, 1];
dirVec ¬ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint];
GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.hi]];
IF editConstraints = tangent
THEN {
distance ¬ Vectors2d.Distance[seg.hi, ctrlPoint];
IF dirVec # [0,0] THEN dirVec ¬ Vectors2d.Normalize[dirVec]; -- use exception
ctrlTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[dirVec,distance]];
}
ELSE ctrlTrans ¬ ImagerTransformation.PostTranslate[partialTransform, dirVec];
seg.class.controlPointMoved[seg, ctrlTrans, 1]; -- For Beziers
}
ELSE {
IF selSeq.controlPoints[segNum][1]
THEN {
seg.class.controlPointMoved[seg, transform, 1];
IF selSeq.joints[nextJointNum] THEN GGSegment.MoveEndPointSegment[seg, FALSE, GGTransform.Transform[transform, seg.hi]];
}
ELSE {
--CP not selected (and not joint selected) so see if constrained movement.
nextSegNum: INT ¬ GGTraj.FollowingSegmentNum[slice, segNum];
IF nextSegNum # -1
AND GGTraj.FetchSegment[slice, nextSegNum].class.type = $Bezier
THEN {
IF selSeq.controlPoints[nextSegNum][0]
THEN {
angle: REAL;
transformedPoint: Point;
otherCtrlPoint: Point ¬ seg.class.controlPointGet[GGTraj.FetchSegment[slice, nextSegNum], 0];
otherDirVector: Vector ¬ Vectors2d.VectorFromPoints[seg.hi, otherCtrlPoint];
ctrlPoint ¬ seg.class.controlPointGet[seg, 1];
dirVec ¬ Vectors2d.VectorFromPoints[seg.hi, ctrlPoint];
distance ¬ Vectors2d.Magnitude[dirVec];
transformedPoint ¬ ImagerTransformation.Transform[transform, otherCtrlPoint];
angle ¬ Vectors2d.SmallestAngleBetweenVectors[otherDirVector, Vectors2d.VectorFromPoints[seg.hi, transformedPoint]];
partialTransform ¬ ImagerTransformation.Translate[Vectors2d.VectorFromPoints[ctrlPoint, seg.hi]];
IF editConstraints = tangent
THEN {
IF transformedPoint # seg.hi
AND otherDirVector # [0,0]
THEN {
-- Don't flip based on 0 length vec.
ctrlTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}
ELSE ctrlTrans ¬ ImagerTransformation.Scale[1]; -- Identity
}
ELSE {
-- length
IF otherDirVector # [0,0]
THEN {
distance ¬ RealFns.SqRt[Vectors2d.DistanceSquared[seg.hi, transformedPoint] / Vectors2d.MagnitudeSquared[otherDirVector]] * distance;
ctrlTrans ¬ ImagerTransformation.PostTranslate[partialTransform, Vectors2d.Scale[Vectors2d.VectorPlusAngle[dirVec, angle], distance]];
}
ELSE ctrlTrans ¬ ImagerTransformation.Scale[1]; -- Identity
};
seg.class.controlPointMoved[seg, ctrlTrans, 1];
};
};
};
};
};
TrajIsEmptyParts:
PROC [sliceD: SliceDescriptor]
RETURNS [
BOOL ¬
TRUE] = {
trajParts: TrajParts ¬ NARROW[sliceD.parts];
RETURN[trajParts=NIL OR GGSequence.IsEmpty[trajParts]];
};
TrajIsCompleteParts:
PROC [sliceD: SliceDescriptor]
RETURNS [
BOOL ¬
FALSE] = {
trajParts: TrajParts ¬ NARROW[sliceD.parts];
RETURN[trajParts#NIL AND GGSequence.IsComplete[trajParts]];
};
TrajNewParts:
PROC [slice: Slice, hitData:
REF
ANY, mode: SelectMode]
RETURNS [sliceD: SliceDescriptor] = {
GGModelTypes.SliceNewPartsProc
trajParts: TrajParts;
trajData: TrajData ¬ NARROW[slice.data];
trajHitData: TrajHitData ¬ NARROW[hitData];
SELECT mode
FROM
literal => {
hitType: HitType;
segNum, cpNum, jointNum: INT;
[hitType, segNum, cpNum, jointNum, ----] ¬ GGTraj.UnpackHitData[hitData];
trajParts
¬
SELECT hitType
FROM
joint => GGSequence.CreateFromJoint[trajData, jointNum],
controlPoint => GGSequence.CreateFromControlPoint[trajData, segNum, cpNum],
segment => GGSequence.CreateSimpleFromSegment[trajData, segNum],
ENDCASE => ERROR;
};
none => {
trajParts ¬ GGSequence.CreateEmpty[trajData];
};
slice => {
trajParts ¬ GGSequence.CreateComplete[trajData];
};
joint => {
SELECT trajHitData.hitType
FROM
joint => {
-- hit a joint
jointNum: NAT ¬ trajHitData.jointNum;
trajParts ¬ GGSequence.CreateJointToJoint[trajData, jointNum, jointNum];
};
segment => {
-- we are in the middle of a segment. Select nearest joint or cp
success: BOOL ¬ FALSE;
segNum, cpNum, jointNum: NAT;
jointPoint, cpPoint, caretPt: Point;
seg: Segment;
segNum ¬ trajHitData.segNum;
seg ¬ GGTraj.FetchSegment[slice, segNum];
caretPt ¬ trajHitData.hitPoint;
jointNum ¬ NearestJointToHitSpot[caretPt, slice, -1, segNum, segment];
jointPoint ¬ GGTraj.FetchJointPos[slice, jointNum];
[cpPoint, ----, cpNum, success] ¬ seg.class.closestControlPoint[seg, caretPt, GGUtility.plusInfinity];
IF NOT success THEN trajParts ¬ GGSequence.CreateJointToJoint[trajData, jointNum, jointNum]
ELSE {
cpDist: REAL ¬ Vectors2d.DistanceSquared[cpPoint, caretPt];
jointDist: REAL ¬ Vectors2d.DistanceSquared[jointPoint, caretPt];
trajParts ¬ IF cpDist < jointDist THEN GGSequence.CreateFromControlPoint[trajData, segNum, cpNum]
ELSE GGSequence.CreateJointToJoint[trajData, jointNum, jointNum];
};
};
controlPoint => {
segNum, cpNum: NAT;
segNum ¬ trajHitData.segNum;
cpNum ¬ trajHitData.cpNum;
trajParts ¬ GGSequence.CreateFromControlPoint[trajData, segNum, cpNum];
};
ENDCASE => ERROR;
};
segment => {
SELECT trajHitData.hitType
FROM
joint => {
jointNum: NAT ¬ trajHitData.jointNum;
this: INT ¬ IF jointNum>GGTraj.HiSegment[slice] THEN -1 ELSE jointNum;
previous: INT ¬ GGTraj.PreviousSegmentNum[slice, jointNum];
trajParts ¬ GGSequence.CreateFromSegment[trajData, IF this#-1 THEN this ELSE previous];
};
segment, controlPoint => {
trajParts ¬ GGSequence.CreateFromSegment[trajData, trajHitData.segNum];
};
ENDCASE => ERROR;
};
traj, topLevel => {
trajParts ¬ GGSequence.CreateComplete[trajData];
};
ENDCASE => ERROR;
sliceD ¬ GGSlice.DescriptorFromParts[slice, trajParts];
};
NearestJointToHitSpot:
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
TrajUnionParts:
PROC [partsA: SliceDescriptor, partsB: SliceDescriptor]
RETURNS [aPlusB: SliceDescriptor] = {
GGModelTypes.SliceUnionPartsProc
aPlusB ¬ GGSequence.Union[partsA, partsB];
};
TrajDiffParts:
PROC [partsA: SliceDescriptor, partsB: SliceDescriptor]
RETURNS [aMinusB: SliceDescriptor] = {
GGModelTypes.SliceDifferencePartsProc
aMinusB ¬ GGSequence.Difference[partsA, partsB];
};
TrajMovingParts:
PROC [slice: Slice, selectedParts: SliceParts, editConstraints: EditConstraints, bezierDrag: GGModelTypes.BezierDragRecord]
RETURNS [background, overlay, rubber, drag: SliceDescriptor] = {
GGModelTypes.SliceMovingPartsProc
bkgdParts, overParts, rubberParts, dragParts: TrajParts;
[bkgdParts, overParts, rubberParts, dragParts] ¬ GGSequence.TrajMovingParts[slice, selectedParts, editConstraints, bezierDrag];
background ¬ GGSlice.DescriptorFromParts[slice, bkgdParts];
overlay ¬ GGSlice.DescriptorFromParts[slice, overParts];
rubber ¬ GGSlice.DescriptorFromParts[slice, rubberParts];
drag ¬ GGSlice.DescriptorFromParts[slice, dragParts];
};
TrajAugmentParts:
PROC [sliceD: SliceDescriptor, selectClass: SelectionClass]
RETURNS [more: SliceDescriptor] = {
GGModelTypes.SliceAugmentPartsProc
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 SliceDescriptor which may mean trouble. -- Bier, March 9, 1987
trajParts: TrajParts ¬ NARROW[sliceD.parts];
IF selectClass = normal THEN GGSequence.FillInJoints[trajParts];
GGSequence.FillInControlPoints[trajParts];
more ¬ GGSlice.DescriptorFromParts[sliceD.slice, trajParts];
};
TrajAlterParts:
PROC [sliceD: SliceDescriptor, action:
ATOM]
RETURNS [alteredD: SliceDescriptor] = {
GGModelTypes.SliceAlterPartsProc
slice: Slice ¬ sliceD.slice;
trajData: TrajData ¬ NARROW[slice.data];
trajParts: TrajParts ¬ NARROW[sliceD.parts];
isACP: BOOL ¬ FALSE;
partType: TrajPartType;
jointNum, cpNum, segNum: INT ¬ 999;
SELECT action
FROM
$Forward, $Backward => {
IF TrajIsCompleteParts[sliceD] THEN RETURN[NIL];
[success, partType, trajData, ----, jointNum, ----, cpNum, ----, segNum] ← GGTraj.UnpackSimpleDescriptor[sliceD.slice, sliceD.parts];
Assert: selection is either one joint, one CP, or a segment and its end joints.
Unfortunately, it is not a simple descriptor
IF GGSequence.CountSegmentsInSequence[trajData, trajParts]=0
THEN [isACP, segNum, cpNum, jointNum] ¬ GGSequence.UnpackOnePointSequence[trajParts]
ELSE segNum ¬ GGSequence.UnpackOneSegmentSequence[trajParts];
SELECT
TRUE
FROM
jointNum#999 => {
IF action = $Forward
THEN
jointNum ¬ GGTraj.FollowingJoint[slice, jointNum]
ELSE jointNum ¬ PreviousJoint[slice, jointNum];
alteredD ¬ IF jointNum = -1 THEN NIL
ELSE GGSlice.DescriptorFromParts[slice, GGSequence.CreateFromJoint[trajData, jointNum]];
};
isACP => {
alteredD ¬ sliceD; -- Don't walk for now
};
segNum#999 => {
IF action = $Forward
THEN
segNum ¬ GGTraj.FollowingSegmentNum[slice, segNum]
ELSE segNum ¬ GGTraj.PreviousSegmentNum[slice, segNum];
alteredD ¬ IF segNum = -1 THEN NIL
ELSE GGSlice.DescriptorFromParts[slice, GGSequence.CreateFromSegment[trajData, segNum]];
};
ENDCASE => ERROR;
};
$Grow => RETURN[TrajNewParts[sliceD.slice, NIL, slice]];
$ShrinkForward, $ShrinkBackward => RETURN[NIL];
ENDCASE => ERROR;
};
PreviousJoint:
PROC [slice: Slice, index:
INT]
RETURNS [nextIndex:
INT] = {
trajData: TrajData ¬ NARROW[slice.data];
SELECT trajData.role
FROM
open => nextIndex ¬ index - 1;
fence, hole => nextIndex ¬ (index + trajData.segCount -1) MOD trajData.segCount;
ENDCASE => ERROR;
};
TrajSetSelectedFields:
PROC [sliceD: SliceDescriptor, selected:
BOOL, selectClass: SelectionClass] = {
joint: Joint;
jointGen: JointGenerator;
segGen: SegmentGenerator;
trajParts: TrajParts ¬ NARROW[sliceD.parts];
trajData: TrajData ¬ NARROW[sliceD.slice.data];
SELECT selectClass
FROM
normal => trajData.selectedInPart.normal ¬ selected;
hot => trajData.selectedInPart.hot ¬ selected;
active => trajData.selectedInPart.active ¬ selected;
match => trajData.selectedInPart.match ¬ selected;
ENDCASE;
jointGen ¬ GGSequence.JointsInSequence[trajParts];
Joint Fields.
FOR jointNum:
INT ¬ GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen]
UNTIL jointNum = -1
DO
joint ¬ GGTraj.FetchJoint[sliceD.slice, jointNum];
SELECT selectClass
FROM
normal => joint.TselectedInFull.normal ¬ selected;
hot => joint.TselectedInFull.hot ¬ selected;
active => joint.TselectedInFull.active ¬ selected;
match => joint.TselectedInFull.match ¬ selected;
ENDCASE;
ENDLOOP;
Segment Fields.
segGen ¬ GGSequence.SegmentsInSequence[trajData, trajParts];
FOR seg: Segment ¬ GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen]
UNTIL seg =
NIL
DO
SELECT selectClass
FROM
normal => seg.TselectedInFull.normal ¬ selected;
hot => seg.TselectedInFull.hot ¬ selected;
active => seg.TselectedInFull.active ¬ selected;
match => seg.TselectedInFull.match ¬ selected;
ENDCASE;
ENDLOOP;
};
idTransform: Transformation;
cache: FunctionCache.Cache;
Init:
PROC = {
idTransform ¬ ImagerTransformation.Create[1,0,0,0,1,0]; -- identity;
cache ¬ FunctionCache.Create[maxEntries: 100];
};
InitStats[];
Init[];
END.