GGSliceImplD.mesa
Contents: Implements some of the Traj slice class (see GGSliceImplE for more).
Copyright Ó 1987, 1988, 1989 by Xerox Corporation. All rights reserved.
Pier, July 17, 1991 11:25 am PDT
Bier, December 2, 1991 3:35 pm PST
Doug Wyatt, December 18, 1989 4:07:21 pm PST
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
};
Traj Class Procs
Fundamentals
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: BOOLFALSE];
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: BOOLFALSE];
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: BOOLFALSE] = {
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: BOOLFALSE] = {
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.FromProcProc[CopyEachSegment];
extractedJoints ← Rosary.FromProcProc[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;
};
Drawing
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[];
};
TrajDrawBorder: 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: BOOLFALSE;
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: BOOLFALSE;
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, viewView: 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: BOOLFALSE;
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: BOOLFALSE;
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 [BOOLFALSE] = {
RETURN[selectedSeq.segments[segNum]];
};
Transforming
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];
};
};
};
};
};
Parts
TrajIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOLTRUE] = {
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: INTIF 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: BOOLFALSE;
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;
InitStats: PROC = {
interval: CodeTimer.Interval;
interval ← CodeTimer.CreateInterval[$CopyTraj];
CodeTimer.AddInt[interval, $Gargoyle];
interval ← CodeTimer.CreateInterval[$TrajDrawBorder];
CodeTimer.AddInt[interval, $Gargoyle];
};
Init: PROC = {
idTransform ← ImagerTransformation.Create[1,0,0,0,1,0]; -- identity;
cache ← FunctionCache.Create[maxEntries: 100];
};
InitStats[];
Init[];
END.