GGTrajImpl.mesa
Contents: Procedures to implement the Traj Slice Class. Trajectories consist of Segments.
Copyright Ó 1986, 1987, 1989, 1991, 1992 by Xerox Corporation. All rights reserved.
Bier, January 28, 1993 3:41 pm PST
Pier, July 6, 1992 1:36 pm PDT
Doug Wyatt, April 16, 1992 4:06 pm PDT
DIRECTORY
Feedback, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGSegment, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTraj, GGTrajTypes, GGTransform, GGUtility, Imager, ImagerTransformation, Lines2d, Lines2dTypes, RealFns, Rope, Rosary, SimpleFeedback, Vectors2d;
GGTrajImpl: CEDAR PROGRAM
IMPORTS Feedback, GGBoundBox, GGCoreOps, GGOutline, GGParent, GGSegment, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerTransformation, Lines2d, RealFns, Rosary, SimpleFeedback, Vectors2d
EXPORTS GGTraj = BEGIN
BitVector: TYPE = GGBasicTypes.BitVector;
BoundBox: TYPE = GGCoreTypes.BoundBox;
BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj;
Camera: TYPE = GGModelTypes.Camera;
Edge: TYPE = Lines2dTypes.Edge;
EditConstraints: TYPE = GGModelTypes.EditConstraints;
FeatureData: TYPE = GGInterfaceTypes.FeatureData;
FenceHoleOpen: TYPE = GGModelTypes.FenceHoleOpen;
GGData: TYPE = GGInterfaceTypes.GGData;
HitType: TYPE = GGModelTypes.TrajPartType;
Joint: TYPE = GGSegmentTypes.Joint;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
JointObj: TYPE = GGSegmentTypes.JointObj;
MakeCurveProc: TYPE = GGTrajTypes.MakeCurveProc;
OutlineData: TYPE = GGOutline.OutlineData;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
RunProc: TYPE = GGTrajTypes.RunProc;
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;
SelectedObjectData: TYPE = GGSegmentTypes.SelectedObjectData;
SelectionClass: TYPE = GGSegmentTypes.SelectionClass;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceGenerator: TYPE = GGSequence.SequenceGenerator;
SequenceOfReal: TYPE = GGCoreTypes.SequenceOfReal;
Slice: TYPE = GGModelTypes.Slice;
SliceClass: TYPE = GGModelTypes.SliceClass;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceObj: TYPE = GGModelTypes.SliceObj;
SliceParts: TYPE = GGModelTypes.SliceParts;
Traj: TYPE = GGModelTypes.Traj;
TrajData: TYPE = GGModelTypes.TrajData;
TrajDataObj: TYPE = GGModelTypes.TrajDataObj;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
TrajHitData: TYPE = GGTraj.TrajHitData;
TrajParts: TYPE = GGModelTypes.TrajParts;
TrajPartType: TYPE = GGModelTypes.TrajPartType;
TriggerBag: TYPE = GGInterfaceTypes.TriggerBag;
Vector: TYPE = GGBasicTypes.Vector;
Problem: ERROR [msg: Rope.ROPE] = Feedback.Problem;
Trajectory-Only Routines
Creating
CreateTrajFromData: PUBLIC PROC [role: FenceHoleOpen, segCount: NAT, segments: Rosary.ROSARY, joints: Rosary.ROSARY, extraPoints: LIST OF Joint, visibleJoints: BOOL ¬ TRUE, strokeJoint: Imager.StrokeJoint ¬ round, loArrow, hiArrow: BOOL ¬ FALSE, selectedInPart: SelectedObjectData ¬ [FALSE, FALSE, FALSE, FALSE]] RETURNS [slice: Slice] = {
Create a new trajectory from the given data. Used by various copy and concat routines.
trajData: TrajData ¬ NEW[TrajDataObj ¬ [role, segCount, segments, joints, extraPoints, visibleJoints, strokeJoint, loArrow, hiArrow, TRUE, selectedInPart, NIL, 9999.0]];
slice ¬ NEW[SliceObj ¬ [class: GGSlice.FetchSliceClass[$Traj], data: trajData]];
slice.boundBox ¬ GGBoundBox.NullBoundBox[];
slice.tightBox ¬ GGBoundBox.NullBoundBox[];
slice.nullDescriptor ¬ GGSlice.DescriptorFromParts[slice, NIL];
};
CreateTraj: PUBLIC PROC [point: Point] RETURNS [slice: Slice] = {
Create a new trajectory containing a single point.
firstJoint: Joint ¬ NEW[JointObj ¬ [point: point]];
trajData: TrajData ¬ NEW[TrajDataObj ¬ [role: open, segCount: 0, segments: NIL, joints: Rosary.FromItem[firstJoint], extraPoints: NIL, visibleJoints: FALSE, polylineTolerance: 9999.0]];
slice ¬ NEW[SliceObj ¬ [class: GGSlice.FetchSliceClass[$Traj], data: trajData, parent: NIL, normalSelectedParts: NIL, hotSelectedParts: NIL, activeSelectedParts: NIL, matchSelectedParts: NIL, nullDescriptor: NIL, fullDescriptor: NIL, tightBox: GGBoundBox.NullBoundBox[], boundBox: GGBoundBox.NullBoundBox[], boxValid: FALSE]];
slice.nullDescriptor ¬ GGSlice.DescriptorFromParts[slice, NIL];
};
AddSegment: PUBLIC PROC [slice: Slice, trajEnd: TrajEnd, seg: Segment, segEnd: TrajEnd] RETURNS [success: BOOL ¬ TRUE] = {
diff: Vector;
loJoint, hiJoint: Joint;
Slice must be of class $Traj. Translates seg so that the specified end of seg coincides with the specified end of traj. Mutates slice to add the new segment. Slice Descriptors must be updated by the caller.
trajData: TrajData ¬ NARROW[slice.data];
IF trajData.role = fence OR trajData.role = hole THEN {success ¬ FALSE; RETURN};
Orient and translate the segment
IF trajEnd = segEnd THEN GGSegment.ReverseSegment[seg];
diff ¬ IF trajEnd = lo THEN Vectors2d.Sub[FetchJointPos[slice, 0], seg.hi]
ELSE Vectors2d.Sub[FetchJointPos[slice, trajData.segCount], seg.lo];
GGSegment.TranslateSegment[seg, diff];
Add segment to an empty traj
IF trajData.segCount = 0 THEN {
trajData.segments ¬ Rosary.FromItem[seg];
loJoint ¬ NEW[JointObj ¬ [point: seg.lo]];
hiJoint ¬ NEW[JointObj ¬ [point: seg.hi]];
trajData.joints ¬ Rosary.FromList[LIST[loJoint, hiJoint]];
trajData.segCount ¬ 1;
slice.boundBox­ ¬ seg.class.boundBox[seg]­;
}
Add segment to an existing traj
ELSE {
IF trajEnd = lo THEN {
loJoint ¬ NEW[JointObj ¬ [point: seg.lo]];
trajData.joints ¬ Rosary.Concat[Rosary.FromItem[loJoint], trajData.joints];
trajData.segments ¬ Rosary.Concat[Rosary.FromItem[seg], trajData.segments];
}
ELSE {
hiJoint ¬ NEW[JointObj ¬ [point: seg.hi]];
trajData.joints ¬ Rosary.Concat[trajData.joints, Rosary.FromItem[hiJoint]];
trajData.segments ¬ Rosary.Concat[trajData.segments, Rosary.FromItem[seg]];
};
trajData.segCount ¬ trajData.segCount + 1;
GGSlice.KillBoundBox[slice];
};
};
CloseWithSegment: PUBLIC PROC [slice: Slice, seg: Segment, segEnd: TrajEnd] = {
Like AddSegment, except that here we rotate, scale, and translate seg as needed so that both of its endpoints coincide with the endpoints of traj (which must not already be closed). The endpoint correspondence is made so that the hi end of traj touches the named (segEnd) end of seg.
diff: Vector;
trajData: TrajData ¬ NARROW[slice.data]; -- NarrowRefFault if SliceType#$Traj
IF trajData.segCount = 0 THEN ERROR Problem[msg: "single closed segment not implemented"];
For now, just translate into place and assume size is ok.
IF segEnd = hi THEN GGSegment.ReverseSegment[seg];
diff ¬ Vectors2d.Sub[LastJointPos[slice], seg.lo];
GGSegment.TranslateSegment[seg, diff];
trajData.segments ¬ Rosary.Concat[trajData.segments, Rosary.FromItem[seg]];
trajData.segCount ¬ trajData.segCount + 1;
trajData.role ¬ fence;
GGSlice.KillBoundBox[slice];
};
CloseByDistorting: PUBLIC PROC [slice: Slice, distortEnd: TrajEnd] = {
Move either the first joint or the last joint (depending on the distortEnd argument) to coincide with the other end. This will reduce the number of joints in the trajectory by one (making all sequences which refer to this trajectory obsolete. Usually, no distortion will actually occur. The client is expected to call this routine when the joints already coincide.
seg: Segment;
loJoint, hiJoint: Joint;
trajData: TrajData ¬ NARROW[slice.data]; -- NarrowRefFault if SliceType#$Traj
loJoint ¬ FetchJoint[slice, 0];
hiJoint ¬ FetchJoint[slice, HiJoint[slice]];
SELECT distortEnd FROM
lo => {
seg ¬ FetchSegment[slice, 0];
loJoint.point ¬ hiJoint.point;
seg.lo ¬ hiJoint.point;
seg.class.endPointMoved[seg, TRUE, hiJoint.point];
};
hi => {
seg ¬ FetchSegment[slice, HiSegment[slice]];
hiJoint.point ¬ loJoint.point;
seg.hi ¬ loJoint.point;
seg.class.endPointMoved[seg, FALSE, loJoint.point];
};
ENDCASE => ERROR;
trajData.joints ¬ Rosary.Substr[trajData.joints, 0, HiJoint[slice]];
trajData.role ¬ fence;
GGSlice.KillBoundBox[slice];
};
SetSelectionBits: PROC [traj: Slice, selected: BOOL, selectClass: SelectionClass] = {
segGen: GGModelTypes.SegmentGenerator;
jointGen: GGModelTypes.JointGenerator;
joint: Joint;
trajData: TrajData ¬ NARROW[traj.data];
segGen ¬ GGSequence.SegmentsInTraj[trajData];
FOR seg: Segment ¬ GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen] UNTIL seg = NIL DO
SetSegmentField[seg, selected, selectClass];
FOR i: NAT IN [0..seg.class.controlPointCount[seg]) DO
seg.class.controlPointFieldSet[seg, i, selected, selectClass];
ENDLOOP;
ENDLOOP;
jointGen ¬ GGSequence.JointsInTraj[trajData];
FOR jointNum: INT ¬ GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen] UNTIL jointNum = -1 DO
joint ¬ FetchJoint[traj, jointNum];
SetJointField[joint, selected, selectClass];
ENDLOOP;
};
ReplaceFirstRun: PUBLIC PROC [trajD: SliceDescriptor, runProc: RunProc, segmentsOnly: BOOL, selectNewRuns: BOOL] RETURNS [newOutline: Slice] = {
Get the first run of trajD and replace it with the results of runProc.
IMPORTANT INVARIANT: When exiting from this loop, make sure that some part of trajD has been deselected from the active list. Otherwise, infinite loops are possible.
trajParts: TrajParts ¬ NARROW[trajD.parts];
traj: Slice ¬ trajD.slice;
run: SliceDescriptor;
runParts: TrajParts;
oldOutline: Slice ¬ GGParent.GetParent[traj];
Find the first run
IF GGSequence.IsComplete[trajParts] THEN {
run ¬ trajD;
runParts ¬ trajParts;
}
ELSE {
runParts ¬ GGSequence.FirstRun[trajParts];
IF runParts = NIL THEN GOTO NothingToReplace; -- The sequence has only control points.
run ¬ GGSlice.DescriptorFromParts[traj, runParts];
};
Replace the first run, as specified by the runProc.
IF runParts.segCount>0 OR NOT segmentsOnly THEN {
newRun: Slice ¬ runProc[run];
IF newRun#NIL THEN {
SetSelectionBits[newRun, FALSE, active]; -- don't process run twice
IF selectNewRuns THEN SetSelectionBits[newRun, TRUE, normal];
};
newOutline ¬ GGParent.GetParent[SpliceIn[run, newRun]];
}
ELSE GOTO NothingToReplace;
EXITS
NothingToReplace => {
SetSelectionBits[trajD.slice, FALSE, active]; -- nothing left to work on
newOutline ¬ NIL;
};
};
SegToSegMatchCp: PUBLIC PROC [seg: Segment, traj: Slice, makeCurve: MakeCurveProc, type: ATOM] = {
Replace segs of the old type with segs of the new type, maintaining control point data if possible
tSeg: Segment;
success: BOOL ¬ FALSE;
IF seg.class.type = type THEN { -- no need to change type or control points
IF type = $Conic THEN {
tSeg ¬ makeCurve[seg.lo, seg.hi, seg.props];
GGSegment.ConicSetControlPoint[tSeg, seg.class.controlPointGet[seg, 0]];
GGSegment.CopyLooks[seg, tSeg];
}
ELSE {
tSeg ¬ GGSegment.CopySegment[seg];
};
success ¬ AddSegment[traj, hi, tSeg, lo];
}
ELSE IF type = $Arc AND seg.class.controlPointCount[seg] = 1 THEN { -- if old and new segment types both have single control points, keep the control points the same
tSeg ¬ GGSegment.MakeArc[seg.lo, seg.class.controlPointGet[seg, 0], seg.hi, seg.props];
GGSegment.CopyLooks[seg, tSeg];
success ¬ AddSegment[traj, hi, tSeg , lo]
}
ELSE IF type = $Conic AND seg.class.controlPointCount[seg] = 1 THEN { -- if old and new segment types have single control points, keep the control points the same
tSeg ¬ makeCurve[seg.lo, seg.hi, seg.props];
GGSegment.ConicSetControlPoint[tSeg, seg.class.controlPointGet[seg, 0]];
GGSegment.CopyLooks[seg, tSeg];
success ¬ AddSegment[traj, hi, tSeg, lo]
}
ELSE { -- make segment of new type with old endpoint info
tSeg ¬ makeCurve[seg.lo, seg.hi, seg.props];
GGSegment.CopyLooks[seg, tSeg];
success ¬ AddSegment[traj, hi, tSeg, lo];
};
IF NOT success THEN ERROR;
};
SegAndCpsToSegs: PUBLIC PROC [seg: Segment, traj: Slice, makeCurve: MakeCurveProc, type: ATOM] = {
Replace old segments with segments of the new type, but if the old segment had control points, use these also as joints of new segments.
success: BOOL ¬ FALSE;
IF seg.class.type = type THEN -- if seg is already the right type, then we don't change it
success ¬ AddSegment[traj, hi, GGSegment.CopySegment[seg], lo]
ELSE { -- seg isn't the right type, so we convert all of its point/controlpoint spans
tSeg: Segment;
last, next: Point ¬ seg.lo;
FOR i:INT IN [0..seg.class.controlPointCount[seg]) DO
next ¬ seg.class.controlPointGet[seg, i];
tSeg ¬ makeCurve[last, next, seg.props];
GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST
success ¬ AddSegment[traj, hi, tSeg, lo];
IF NOT success THEN ERROR;
last ¬ next;
ENDLOOP;
tSeg ¬ makeCurve[last, seg.hi, seg.props];
GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST
success ¬ AddSegment[traj, hi, tSeg, lo];
IF NOT success THEN ERROR;
};
};
DeleteControlPoints: PUBLIC PROC [trajD: SliceDescriptor, scene: Scene] RETURNS [bBox: BoundBox] = {
Deletes selected control points and returns bounding box of area requiring refresh.
trajData: TrajData ¬ NARROW[trajD.slice.data];
trajParts: TrajParts ¬ NARROW[trajD.parts];
outlineOfTraj: Slice ¬ GGParent.GetParent[trajD.slice];
GGSlice.KillBoundBox[trajD.slice];
SaveSelection[trajD.slice, hot, scene]; -- to be restored by SubstituteForSegment
SaveSelection[trajD.slice, normal, scene];
FOR i: INT IN [0..trajData.segCount) DO
IF NOT GGUtility.AllFalse[trajParts.controlPoints[i]] AND NOT trajParts.segments[i] THEN {
oldSeg: Segment ¬ FetchSegment[trajD.slice, i];
IF oldSeg.class.type = $CubicSpline THEN {
newSeg: Segment ¬ GGSegment.CSControlPointDelete[oldSeg, trajParts.controlPoints[i]];
newRun: Slice ¬ CreateTraj[newSeg.lo];
GGSliceOps.SetStrokeJoint[newRun, NIL, trajData.strokeJoint, NIL];
IF NOT AddSegment[newRun, hi, newSeg, lo] THEN ERROR;
[] ¬ GGSelect.SubstituteForSegment[trajD.slice, i, newRun, scene]
}
ELSE {
SimpleFeedback.Append[$Gargoyle, oneLiner, $Apology, "Only delete control points from Cubic Splines"];
};
};
ENDLOOP;
bBox ¬ GGBoundBox.CopyBoundBox[GGSliceOps.GetBoundBox[outlineOfTraj]];
must make a copy because it may be mutated later
};
DeleteSequence: PUBLIC PROC [seq: SliceDescriptor] RETURNS [smallerOutline: Slice, openTrajOutlines: LIST OF Slice] = {
traj: Slice ¬ seq.slice;
trajData: TrajData ¬ NARROW[traj.data];
trajParts: TrajParts ¬ NARROW[seq.parts];
oldOutline: Slice ¬ GGParent.GetParent[traj];
SELECT trajData.role FROM
fence, hole => {
openTrajOutlines ¬ OutlinesOfTrajMinusSequence[seq]; -- pieces of traj become outlines
smallerOutline ¬ GGOutline.ReplaceChild[oldOutline, traj, NIL]; -- old outline gets smaller
};
open => {
openTrajOutlines ¬ OutlinesOfTrajMinusSequence[seq];
smallerOutline ¬ NIL;
};
ENDCASE => ERROR;
};
OutlinesOfTrajMinusSequence: PROC [seq: SliceDescriptor] RETURNS [newOutlines: LIST OF Slice] = {
keepParts: SliceDescriptor;
wholeTrajParts: TrajParts;
traj: Slice ¬ seq.slice;
trajData: TrajData ¬ NARROW[traj.data];
trajParts: TrajParts ¬ NARROW[seq.parts];
IF GGSequence.IsComplete[trajParts] THEN RETURN[NIL];
wholeTrajParts ¬ GGSequence.CreateComplete[trajData];
Cyclic segments must become acyclic
IF NOT GGSequence.IsEmpty[trajParts] AND trajData.segCount=1
THEN GGSegment.OpenUpSegment[FetchSegment[traj, 0]];
Compute a descriptor for the parts that should NOT be deleted.
keepParts ¬ GGSequence.Difference[GGSlice.DescriptorFromParts[traj, wholeTrajParts], seq];
newOutlines ¬ GroupPieces[keepParts];
};
GroupPieces: PROC [seq: SliceDescriptor] RETURNS [newOutlines: LIST OF Slice] = {
seqTraj: Slice ¬ seq.slice;
seqData: TrajData ¬ NARROW[seqTraj.data];
seqParts: TrajParts ¬ NARROW[seq.parts];
seqGen: SequenceGenerator;
newTraj: Slice;
newTrajData: TrajData;
newOutline, oldOutline: Slice;
joint: Joint;
oldOutline ¬ GGParent.GetParent[seqTraj];
[seqGen, ----] ¬ GGSequence.RunsInSequence[seqParts];
FOR run: TrajParts ¬ GGSequence.NextSequence[seqGen], GGSequence.NextSequence[seqGen] UNTIL run = NIL DO
IF run.segCount=0 THEN LOOP; -- special case needed by BS command.
newTraj ¬ CopyTrajFromRun[seqTraj, run];
Make sure the end joints of the run are not active selected. They can be normal or hot or match selected. This is important for the delete operation.
joint ¬ FetchJoint[newTraj, 0];
joint.TselectedInFull.active ¬ FALSE;
joint ¬ FetchJoint[newTraj, HiJoint[newTraj]];
joint.TselectedInFull.active ¬ FALSE;
Create the new outline.
newTrajData ¬ NARROW[newTraj.data];
newTrajData.role ¬ open;
newOutline ¬ GGOutline.CreateOutline[newTraj, GGSliceOps.GetFillColor[oldOutline, NIL].color];
newOutlines ¬ CONS[newOutline, newOutlines];
ENDLOOP;
};
Building from Parts
CopyTrajFromRun: PUBLIC PROC [slice: Slice, run: TrajParts] RETURNS [copy: Slice] = {
runDescriptor should be a run (a single consecutive set of segments and joints).
trajData: TrajData ¬ NARROW[slice.data];
trajParts: TrajParts ¬ run;
originalSegments: Rosary.ROSARY ¬ trajData.segments;
desiredSegments: Rosary.Segment;
loSegments, hiSegments, extractedSegments: Rosary.ROSARY;
originalJoints: Rosary.ROSARY ¬ trajData.joints;
desiredJoints: Rosary.Segment;
loJoints, hiJoints, extractedJoints: Rosary.ROSARY;
segGen: SegmentGenerator;
next: GGSequence.SegAndIndex;
s1, len1, s2, len2: INT;
jointCount: NAT;
newRole: GGModelTypes.FenceHoleOpen;
CopyEachSegment: PROC[q: PROC[item: Rosary.Item, repeat: INT ¬ 1]] = {
CopySegmentAndBuild: PROC [item: Rosary.Item] RETURNS [quit: BOOLEAN ¬ 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: BOOLEAN ¬ FALSE] = {
copy, oldJoint: Joint;
oldJoint ¬ NARROW[item];
copy ¬ CopyJoint[oldJoint];
q[copy, 1];
};
[] ¬ Rosary.Map[desiredJoints, CopyJointAndBuild];
};
segGen ¬ GGSequence.OrderedSegmentsInSequence[trajData, trajParts];
next ¬ GGSequence.NextSegmentAndIndex[segGen];
[s1, len1, s2, len2] ¬ GGCoreOps.BreakIntervalMODLen[next.index, trajParts.segCount, trajData.segCount];
IF s2 # -1 THEN {
desiredSegments ¬ [originalSegments, s1, len1];
loSegments ¬ Rosary.FromRuns[CopyEachSegment];
desiredSegments ¬ [originalSegments, s2, len2];
hiSegments ¬ Rosary.FromRuns[CopyEachSegment];
extractedSegments ¬ Rosary.Concat[hiSegments, loSegments];
}
ELSE {
desiredSegments ¬ [originalSegments, next.index, trajParts.segCount];
extractedSegments ¬ Rosary.FromRuns[CopyEachSegment];
};
jointCount ¬ trajParts.segCount + 1; -- ignore the joint information in the sequence. We just want all of the segments to have a joint on each side. This distinction is particularly important when a single joint of a closed trajectory is being deleted.
[s1, len1, s2, len2] ¬ GGCoreOps.BreakIntervalMODLen[next.index, jointCount, HiJoint[slice]+1];
IF s2 # -1 THEN {
desiredJoints ¬ [originalJoints, s1, len1];
loJoints ¬ Rosary.FromRuns[CopyEachJoint];
desiredJoints ¬ [originalJoints, s2, len2];
hiJoints ¬ Rosary.FromRuns[CopyEachJoint];
extractedJoints ¬ Rosary.Concat[hiJoints, loJoints];
}
ELSE {
desiredJoints ¬ [originalJoints, next.index, jointCount];
extractedJoints ¬ Rosary.FromRuns[CopyEachJoint];
};
newRole ¬ IF GGSequence.IsComplete[trajParts] AND trajData.role#open THEN fence ELSE open;
copy ¬ CreateTrajFromData[newRole, trajParts.segCount, extractedSegments, extractedJoints, NIL, trajData.visibleJoints, trajData.strokeJoint];
copy.props ¬ slice.props;
};
CopyTrajFromRange: PUBLIC PROC [slice: Slice, start: INT, len: INT] RETURNS [piece: Slice] = {
First we pick out the desired subsection. Then, 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.
original: TrajData ¬ NARROW[slice.data]; -- NarrowRefFault if SliceType#$Traj
originalSegments: Rosary.ROSARY ¬ original.segments;
desiredSegments: Rosary.Segment ¬ [originalSegments, start, len];
extractedSegments: Rosary.ROSARY;
originalJoints: Rosary.ROSARY ¬ original.joints;
desiredJoints: Rosary.Segment ¬ [originalJoints, start, len];
extractedJoints: Rosary.ROSARY;
CopyEachSegment: PROC[q: PROC[item: Rosary.Item, repeat: INT ¬ 1]] = {
CopySegmentAndBuild: PROC [item: Rosary.Item] RETURNS [quit: BOOLEAN ¬ 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: BOOLEAN ¬ FALSE] = {
copy, oldJoint: Joint;
oldJoint ¬ NARROW[item];
copy ¬ CopyJoint[oldJoint];
q[copy, 1];
};
[] ¬ Rosary.Map[desiredJoints, CopyJointAndBuild];
};
extractedSegments ¬ Rosary.FromRuns[CopyEachSegment];
extractedJoints ¬ Rosary.FromRuns[CopyEachJoint];
piece ¬ CreateTrajFromData[fence, len, extractedSegments, extractedJoints, NIL, original.visibleJoints, original.strokeJoint];
};
Concat: PUBLIC PROC [fixed: Slice, fixedEnd: TrajEnd, moving: Slice, movingEnd: TrajEnd] RETURNS [longer: Slice] = {
Moves moving so that the specified end moving coincides with the specified end fixed. In other words, moving is translated.
diff: Vector;
fixedTraj, movingTraj: TrajData;
fixed ¬ GGSliceOps.Copy[fixed].first;
moving ¬ GGSliceOps.Copy[moving].first;
IF fixedEnd = movingEnd THEN ReverseTraj[moving];
IF fixedEnd = hi THEN {
diff ¬ Vectors2d.Sub[FetchJointPos[fixed, HiJoint[fixed]], FetchJointPos[moving, 0]];
TranslateTraj[moving, diff];
fixedTraj ¬ NARROW[fixed.data];
movingTraj ¬ NARROW[moving.data];
longer ¬ CreateTrajFromData[
role: open,
segCount: fixedTraj.segCount + movingTraj.segCount,
segments: Rosary.Concat[fixedTraj.segments, movingTraj.segments],
joints: Rosary.Concat[fixedTraj.joints, Rosary.Substr[movingTraj.joints, 1]],
extraPoints: NIL,
visibleJoints: fixedTraj.visibleJoints,
strokeJoint: fixedTraj.strokeJoint,
selectedInPart: [FALSE, FALSE, FALSE, FALSE]
];
}
ELSE { -- fixedEnd = lo
diff ¬ Vectors2d.Sub[FetchJointPos[fixed, 0], FetchJointPos[moving, HiJoint[moving]]];
TranslateTraj[moving, diff];
fixedTraj ¬ NARROW[fixed.data];
movingTraj ¬ NARROW[moving.data];
longer ¬ CreateTrajFromData[
role: open,
segCount: fixedTraj.segCount + movingTraj.segCount,
segments: Rosary.Concat[movingTraj.segments, fixedTraj.segments],
joints: Rosary.Concat[movingTraj.joints, Rosary.Substr[fixedTraj.joints, 1]],
extraPoints: NIL,
visibleJoints: fixedTraj.visibleJoints,
strokeJoint: fixedTraj.strokeJoint,
selectedInPart: [FALSE, FALSE, FALSE, FALSE]
];
};
};
SpliceIn: PUBLIC PROC [runDescriptor: SliceDescriptor, slice: Slice] RETURNS [newSlice: Slice] = {
Run describes a part of its traj that is to be replaced by slice. The result is similar to Rope.Cat[Rope.Substr[...], newRope, Rope.Substr[...]] except that a Traj can be a circular structure.
oldTraj: TrajData ¬ NARROW[runDescriptor.slice.data]; -- traj to be replaced
newOutline: Slice;
oldOutline: Slice ¬ GGParent.GetParent[runDescriptor.slice];
SELECT oldTraj.role FROM
hole => {
newSlice ¬ SpliceInClosed[runDescriptor, slice];
NARROW[newSlice.data, TrajData].role ¬ hole;
newOutline ¬ GGOutline.ReplaceChild[oldOutline, runDescriptor.slice, newSlice];
newSlice.parent ¬ newOutline; -- gotta patch this in for return value
};
open => {
newSlice ¬ SpliceInOpen[runDescriptor, slice];
NARROW[newSlice.data, TrajData].role ¬ open;
newOutline ¬ GGOutline.CreateOutline[newSlice, GGSliceOps.GetFillColor[oldOutline, NIL].color];
newSlice.parent ¬ newOutline; -- gotta patch this in for return value
};
fence => {
newSlice ¬ SpliceInClosed[runDescriptor, slice];
NARROW[newSlice.data, TrajData].role ¬ fence;
newOutline ¬ GGOutline.ReplaceFirstChild[oldOutline, newSlice];
newSlice.parent ¬ newOutline; -- gotta patch this in for return value
};
ENDCASE => ERROR;
A hack to get arrows to work reasonably for now.
NARROW[newSlice.data, TrajData].loArrow ¬ oldTraj.loArrow;
NARROW[newSlice.data, TrajData].hiArrow ¬ oldTraj.hiArrow;
GGSlice.KillBoundBox[oldOutline];
GGSlice.KillBoundBox[newSlice];
};
ResetEndSelectionBits: PROC [slice: Slice, lo, hi: BOOL ¬ TRUE] = {
joint: Joint;
IF lo THEN {
joint ¬ FetchJoint[slice, 0];
joint.TselectedInFull.active ¬ FALSE;
};
IF hi THEN {
joint ¬ FetchJoint[slice, HiJoint[slice]];
joint.TselectedInFull.active ¬ FALSE;
};
};
SpliceInOpen: PROC [runDescriptor: SliceDescriptor, slice: Slice] RETURNS [newSlice: Slice] = {
runSlice: Slice ¬ runDescriptor.slice; -- contains traj to be replaced
runData: TrajData ¬ NARROW[runSlice.data]; -- contains traj parts to be replaced
runParts: TrajParts ¬ NARROW[runDescriptor.parts]; -- contains traj parts to be replaced
runGen: GGSequence.SequenceGenerator;
slice1, slice2: Slice;
remainder: Sequence;
remainderParts, run1, run2, run3: TrajParts;
wholeParts: TrajParts ¬ GGSequence.CreateComplete[runData];
wholeDescriptor: SliceDescriptor ¬ GGSlice.DescriptorFromParts[runSlice, wholeParts];
remainder ¬ GGSequence.Difference[wholeDescriptor, runDescriptor];
remainderParts ¬ NARROW[remainder.parts];
GGSequence.FillInJoints[remainderParts];
[runGen,----] ¬ GGSequence.RunsInSequence[remainderParts];
run1 ¬ GGSequence.NextSequence[runGen];
run2 ¬ GGSequence.NextSequence[runGen];
run3 ¬ GGSequence.NextSequence[runGen];
IF run3 # NIL THEN ERROR;
SELECT TRUE FROM
run1 = NIL => {
newSlice ¬ slice;
};
run2 = NIL => {
slice1 ¬ CopyTrajFromRun[runSlice, run1];
IF runParts.segments[0] THEN { -- includes lowest segment
ResetEndSelectionBits[slice: slice1, lo: TRUE, hi: FALSE];
newSlice ¬ Concat[slice, hi, slice1, lo]; -- order is important. traj1's lo joint is kept
}
ELSE {
ResetEndSelectionBits[slice: slice1, lo: FALSE, hi: TRUE];
newSlice ¬ Concat[slice, lo, slice1, hi]; -- order is important. traj1's hi joint is kept
};
};
ENDCASE => {
[Artwork node; type 'ArtworkInterpress on' to command tool]
slice1 ¬ CopyTrajFromRun[runSlice, run1];
ResetEndSelectionBits[slice: slice1, lo: FALSE, hi: TRUE];
slice2 ¬ CopyTrajFromRun[runSlice, run2];
ResetEndSelectionBits[slice: slice2, lo: TRUE, hi: FALSE];
newSlice ¬ Concat[slice, lo, slice1, hi];
newSlice ¬ Concat[newSlice, hi, slice2, lo];
};
To make sure the algorithm terminates, make sure the end joints of the run are not active selected. They can be normal or hot selected.
};
SpliceInClosed: PROC [runDescriptor: SliceDescriptor, slice: Slice] RETURNS [newSlice: Slice] = {
runSlice: Slice ¬ runDescriptor.slice; -- contains traj to be replaced
runData: TrajData ¬ NARROW[runSlice.data]; -- contains traj parts to be replaced
runParts: TrajParts ¬ NARROW[runDescriptor.parts]; -- contains traj parts to be replaced
runGen: GGSequence.SequenceGenerator;
slice1: Slice;
remainder: Sequence;
wholeParts, remainderParts, run1, run2: TrajParts;
wholeDescriptor: SliceDescriptor;
IF GGSequence.IsComplete[runParts] THEN RETURN[slice]; -- no need to splice.
wholeParts ¬ GGSequence.CreateComplete[runData];
wholeDescriptor ¬ GGSlice.DescriptorFromParts[runSlice, wholeParts];
remainder ¬ GGSequence.Difference[wholeDescriptor, runDescriptor];
remainderParts ¬ NARROW[remainder.parts];
GGSequence.FillInJoints[remainderParts];
[runGen, ----] ¬ GGSequence.RunsInSequence[remainderParts];
run1 ¬ GGSequence.NextSequence[runGen];
run2 ¬ GGSequence.NextSequence[runGen];
IF run1 = NIL OR run2 # NIL THEN ERROR;
slice1 ¬ CopyTrajFromRun[runSlice, run1];
ResetEndSelectionBits[slice1];
newSlice ¬ Concat[slice, lo, slice1, hi]; -- order is important because traj1's joints are kept.
CloseByDistorting[newSlice, lo];
[Artwork node; type 'ArtworkInterpress on' to command tool]
To make sure the algorithm terminates, make sure the end joints of the run are not active selected. They can be normal or hot selected.
};
Orientation
ReverseTraj: PUBLIC PROC [slice: Slice] = {
newSegments: Rosary.ROSARY;
newJoints: Rosary.ROSARY;
seg: Segment;
joint: Joint;
trajData: TrajData ¬ NARROW[slice.data];
FOR i: NAT IN [0..trajData.segCount) DO
seg ¬ FetchSegment[slice, i];
GGSegment.ReverseSegment[seg];
newSegments ¬ Rosary.Concat[Rosary.FromItem[seg], newSegments];
ENDLOOP;
FOR i: NAT IN [0..trajData.segCount] DO
joint ¬ IF trajData.role=open THEN FetchJoint[slice, i] ELSE FetchJoint[slice, (i MOD trajData.segCount)];
newJoints ¬ Rosary.Concat[Rosary.FromItem[joint], newJoints];
ENDLOOP;
trajData.segments ¬ newSegments;
trajData.joints ¬ newJoints;
};
IsClockwiseTraj: PUBLIC PROC [slice: Slice] RETURNS [BOOL] = {
RETURN [SignedArea[slice]>0];
};
IsClockwiseTrajTransformSeq: PUBLIC PROC [descriptor: SliceDescriptor, transform: ImagerTransformation.Transformation] RETURNS [BOOL] = {
RETURN [SignedAreaTransformSeq[descriptor, transform]>0];
};
Fundamental Routines
The lifetime of a trajectory goes something like this: It begins as a single point. The sole purpose of this point is to be an "endpoint" to which the first segment can be attached. Subsequent segments are also added to endpoints, so this unifies the creation process. In Gargoyle, trajectories never appear at the top level of a scene; they are always part of an outline. It is suggested, then that each CreateTraj call be followed immediately by a CreateOutline call.
Drawing
DrawPolyline: PUBLIC PROC [dc: Imager.Context, traj: Slice] = {
trajData: TrajData ¬ NARROW[traj.data];
PolyPathProc: Imager.PathProc = {
moveTo[wholePolyline[0]];
FOR i: NAT IN [1..wholePolyline.length) DO
lineTo[wholePolyline[i]];
ENDLOOP;
};
wholePolyline: GGSegmentTypes.PairSequence;
success: BOOL ¬ FALSE;
wholePolyline ¬ trajData.polyline;
IF wholePolyline = NIL THEN RETURN;
IF trajData.role = open THEN RETURN;
Imager.SetColor[dc, Imager.black];
Imager.SetStrokeJoint[dc, bevel];
Imager.SetStrokeWidth[dc, 1.0];
Imager.MaskStroke[dc, PolyPathProc, TRUE];
};
<<DrawPolyline: PUBLIC PROC [dc: Imager.Context, traj: Slice] = {
trajData: TrajData ¬ NARROW[traj.data];
hiSegment: NAT ¬ HiSegment[traj];
hiJoint: NAT ¬ HiJoint[traj];
thisPoly: GGSegmentTypes.PairSequence;
seg: Segment;
PolyPathProc: Imager.PathProc = {
seg ¬ FetchSegment[traj, 0];
thisPoly ¬ seg.class.asPolyline[seg, 10.0];
moveTo[thisPoly[0]];
FOR j: NAT IN [1..thisPoly.length) DO
lineTo[thisPoly[j]];
ENDLOOP;
FOR i: NAT IN [1..hiSegment] DO
seg ¬ FetchSegment[traj, i];
thisPoly ¬ seg.class.asPolyline[seg, 10.0];
FOR j: NAT IN [1..thisPoly.length) DO
lineTo[thisPoly[j]];
ENDLOOP;
ENDLOOP;
};
IF trajData.role = open THEN RETURN;
Imager.SetColor[dc, Imager.black];
Imager.MaskStroke[dc, PolyPathProc, TRUE];
};>>
Transforming
TranslateTraj: PROC [slice: Slice, vector: Vector] = {
transform: ImagerTransformation.Transformation ¬ ImagerTransformation.Translate[[vector.x, vector.y]];
seg: Segment;
joint: Joint;
trajData: TrajData ¬ NARROW[slice.data];
hiSegment: NAT ¬ HiSegment[slice];
hiJoint: NAT ¬ HiJoint[slice];
FOR i: NAT IN [0..hiSegment] DO
seg ¬ FetchSegment[slice, i];
GGSegment.TransformSegment[seg, transform];
ENDLOOP;
FOR i: NAT IN [0..hiJoint] DO
joint ¬ NARROW[Rosary.Fetch[trajData.joints, i]];
joint.point ¬ GGTransform.Transform[transform, joint.point];
ENDLOOP;
GGSlice.KillBoundBox[slice]; -- force bound box to update for now
};
ConstrainJoint: PUBLIC PROC [slice: Slice, editConstraints: EditConstraints, jointNum: NAT] RETURNS [constrained: BOOL ¬ FALSE] ~ {
newPos: Point;
loSeg, hiSeg: Segment;
cPointLo, cPointHi: Point;
joint: Joint;
trajData: TrajData ¬ NARROW[slice.data];
IF NOT IsEndJoint[slice, jointNum] THEN {
joint ¬ FetchJoint[slice, jointNum];
hiSeg ¬ FetchSegment[slice, jointNum];
loSeg ¬ PreviousSegment[slice, jointNum];
IF hiSeg.class.type = $Bezier AND loSeg.class.type = $Bezier THEN {
constrained ¬ TRUE;
cPointLo ¬ loSeg.class.controlPointGet[loSeg, 1];
cPointHi ¬ hiSeg.class.controlPointGet[hiSeg, 0];
IF editConstraints = length THEN {
newPos ¬ Vectors2d.Add[Vectors2d.Scale[Vectors2d.VectorFromPoints[cPointLo, cPointHi], 0.5], cPointLo];
}
ELSE { -- tangent
cpLine: Edge ¬ Lines2d.CreateEdge[cPointLo, cPointHi];
newPos ¬ Lines2d.NearestPointOnEdge[joint.point, cpLine];
};
joint.point ¬ newPos;
GGSegment.MoveEndPointSegment[hiSeg, TRUE, newPos];
GGSegment.MoveEndPointSegment[loSeg, FALSE, newPos];
GGSlice.KillBoundBox[slice];
};
};
};
ConstrainCP: PUBLIC PROC [slice: Slice, editConstraints: EditConstraints, segNum: NAT, cpNum: NAT] RETURNS [constrained: BOOL ¬ FALSE] ~ {
newPos: Point;
thisSeg, otherSeg: Segment;
thisCP, otherCP: Point;
joint: Joint;
otherSegNum, otherCPNum: INT;
trajData: TrajData ¬ NARROW[slice.data];
IF cpNum = 0 THEN {
otherSegNum ¬ PreviousSegmentNum[slice, segNum];
joint ¬ FetchJoint[slice, segNum];
otherCPNum ¬ 1;
}
ELSE {
otherSegNum ¬ FollowingSegmentNum[slice, segNum];
joint ¬ FetchJoint[slice, FollowingJoint[slice, segNum]];
otherCPNum ¬ 0;
};
IF otherSegNum # -1 THEN {
thisSeg ¬ FetchSegment[slice, segNum];
otherSeg ¬ FetchSegment[slice, otherSegNum];
IF thisSeg.class.type = $Bezier AND otherSeg.class.type = $Bezier THEN {
constrained ¬ TRUE;
thisCP ¬ thisSeg.class.controlPointGet[thisSeg, cpNum];
otherCP ¬ otherSeg.class.controlPointGet[otherSeg, otherCPNum];
IF editConstraints = length THEN {
newPos ¬ Vectors2d.Add[Vectors2d.Scale[Vectors2d.VectorFromPoints[otherCP, joint.point], 2.0], otherCP];
}
ELSE { -- tangent, can do 1 of 2 ways [drop perpendicular or same length].
dirVec: Vector ¬ Vectors2d.VectorFromPoints[otherCP, joint.point];
distance: REAL ¬ Vectors2d.Distance[joint.point, thisCP];
IF dirVec # [0,0] THEN dirVec ¬ Vectors2d.Normalize[dirVec]; -- use exception
dirVec ¬ Vectors2d.Scale[dirVec, distance];
newPos ¬ Vectors2d.Add[joint.point, dirVec];
};
GGSegment.BZControlPointMovedTo[thisSeg, newPos, cpNum];
GGSlice.KillBoundBox[slice];
};
};
};
closeness: INT ¬ -18; -- 18 bits of precision, determined by trial and error for GetIPEditable. KAP. July 6, 1992
MatchShape: PUBLIC PROC [traj1, traj2: Slice] RETURNS [BOOL] = {
segCount1: NAT ¬ HiSegment[traj1];
segCount2: NAT ¬ HiSegment[traj2];
trajData1: TrajData ¬ NARROW[traj1.data];
trajData2: TrajData ¬ NARROW[traj1.data];
seg1, seg2: Segment;
cpCount1: NAT;
closed1: BOOL ¬ trajData1.role = hole OR trajData1.role = fence;
closed2: BOOL ¬ trajData2.role = hole OR trajData2.role = fence;
IF segCount1 # segCount2 THEN RETURN[FALSE];
IF closed1 # closed2 THEN RETURN[FALSE];
FOR i: NAT IN [0..segCount1] DO
seg1 ¬ FetchSegment[traj1, i];
seg2 ¬ FetchSegment[traj2, i];
Check segment types
IF seg1.class # seg2.class THEN RETURN[FALSE];
Check geometric properties
IF NOT GGSegment.SameShape[seg1, seg2] THEN RETURN[FALSE];
IF seg1.hi # seg2.hi THEN RETURN[FALSE];
cpCount1 ¬ seg1.class.controlPointCount[seg1];
IF cpCount1 # seg2.class.controlPointCount[seg2] THEN RETURN[FALSE];
FOR j: NAT IN [0..cpCount1) DO
IF seg1.class.controlPointGet[seg1, j] # seg2.class.controlPointGet[seg2, j] THEN RETURN[FALSE];
ENDLOOP;
FOR j: NAT IN [0..cpCount1) DO
point1, point2: Point;
IF (point1 ¬ seg1.class.controlPointGet[seg1, j]) =
(point2 ¬ seg2.class.controlPointGet[seg2, j]) THEN LOOP;
Extra careful floating point fuzz check
IF RealFns.AlmostEqual[point1.x, point2.x, closeness] AND RealFns.AlmostEqual[point1.y, point2.y, closeness] THEN LOOP
ELSE RETURN[FALSE];
ENDLOOP;
ENDLOOP;
RETURN[TRUE];
};
Accessing Parts
FetchSegment: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [seg: Segment] = {
trajData: TrajData ¬ NARROW[slice.data];
seg ¬ NARROW[Rosary.Fetch[trajData.segments, index]];
};
FetchSegmentTraj: PUBLIC PROC [trajData: TrajData, index: NAT] RETURNS [seg: Segment] = {
seg ¬ NARROW[Rosary.Fetch[trajData.segments, index]];
};
FetchJoint: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [joint: Joint] = {
trajData: TrajData ¬ NARROW[slice.data];
joint ¬ NARROW[Rosary.Fetch[trajData.joints, index]];
};
FetchJointPos: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [point: Point] = {
trajData: TrajData ¬ NARROW[slice.data];
joint: Joint;
hiJoint: NAT ¬ SELECT trajData.role FROM
open => trajData.segCount,
fence, hole => trajData.segCount -1,
ENDCASE => ERROR;
IF index < 0 OR index > hiJoint THEN ERROR;
joint ¬ NARROW[Rosary.Fetch[trajData.joints, index]];
point ¬ joint.point;
};
FetchJointPosTraj: PUBLIC PROC [trajData: TrajData, index: NAT] RETURNS [point: Point] = {
joint: Joint;
hiJoint: NAT ¬ SELECT trajData.role FROM
open => trajData.segCount,
fence, hole => trajData.segCount -1,
ENDCASE => ERROR;
IF index < 0 OR index > hiJoint THEN ERROR;
joint ¬ NARROW[Rosary.Fetch[trajData.joints, index]];
point ¬ joint.point;
};
FetchJointNormal: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [normal: Vector] = {
normal ¬ [0,-1]; -- for now
};
LastJointPos: PUBLIC PROC [slice: Slice] RETURNS [point: Point] = {
trajData: TrajData ¬ NARROW[slice.data];
joint: Joint ¬ NARROW[Rosary.Fetch[trajData.joints, trajData.segCount]];
point ¬ joint.point
};
SetJointPos: PUBLIC PROC [slice: Slice, index: NAT, newPos: Point] = {
Moves the given joint and tells all interested segments that it has moved.
trajData: TrajData ¬ NARROW[slice.data];
joint: Joint ¬ NARROW[Rosary.Fetch[trajData.joints, index]];
segLeft, segRight: Segment;
joint.point ¬ newPos;
IF index > 0 THEN {
segLeft ¬ FetchSegment[slice, index-1];
segLeft.hi ¬ newPos;
};
IF index < trajData.segCount THEN {
segRight ¬ FetchSegment[slice, index];
segRight.lo ¬ newPos;
};
};
HiSegment: PUBLIC PROC [slice: Slice] RETURNS [highestIndex: NAT] = {
trajData: TrajData ¬ NARROW[slice.data];
highestIndex ¬ trajData.segCount - 1;
};
HiSegmentTraj: PUBLIC PROC [trajData: TrajData] RETURNS [highestIndex: NAT] = {
highestIndex ¬ trajData.segCount - 1;
};
HiJoint: PUBLIC PROC [slice: Slice] RETURNS [highestIndex: NAT] = {
trajData: TrajData ¬ NARROW[slice.data];
SELECT trajData.role FROM
open => highestIndex ¬ trajData.segCount;
fence, hole => highestIndex ¬ trajData.segCount -1;
ENDCASE => ERROR;
};
HiJointTraj: PUBLIC PROC [trajData: TrajData] RETURNS [highestIndex: NAT] = {
SELECT trajData.role FROM
open => highestIndex ¬ trajData.segCount;
fence, hole => highestIndex ¬ trajData.segCount -1;
ENDCASE => ERROR;
};
PreviousSegment: PUBLIC PROC [slice: Slice, segNum: NAT] RETURNS [prev: Segment] = {
trajData: TrajData ¬ NARROW[slice.data];
IF trajData.role = open THEN {
IF segNum = 0 THEN RETURN[NIL]
ELSE prev ¬ FetchSegment[slice, segNum - 1];
}
ELSE {
prev ¬ FetchSegment[slice, (segNum-1+trajData.segCount) MOD trajData.segCount];
};
};
PreviousSegmentNum: PUBLIC PROC [slice: Slice, segNum: NAT] RETURNS [prevNum: INT] = {
trajData: TrajData ¬ NARROW[slice.data];
prevNum ¬ IF trajData.role = open THEN segNum - 1 ELSE (segNum-1+trajData.segCount) MOD trajData.segCount;
};
FollowingSegmentNum: PUBLIC PROC [slice: Slice, segNum: NAT] RETURNS [followNum: INT] = {
trajData: TrajData ¬ NARROW[slice.data];
IF trajData.role = open THEN followNum ¬ IF segNum = HiSegment[slice] THEN -1 ELSE segNum + 1
ELSE followNum ¬ (segNum+1) MOD trajData.segCount;
};
FollowingJoint: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [nextIndex: INT] = {
trajData: TrajData ¬ NARROW[slice.data];
SELECT trajData.role FROM
open => nextIndex ¬ IF index = trajData.segCount THEN -1 ELSE index + 1;
fence, hole => nextIndex ¬ (index + 1) MOD trajData.segCount;
ENDCASE => ERROR;
};
IsEndJoint: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [BOOL] = {
trajData: TrajData ¬ NARROW[slice.data];
SELECT trajData.role FROM
open => RETURN[index = 0 OR index = trajData.segCount];
fence, hole => RETURN[FALSE];
ENDCASE => ERROR;
};
Use sparingly
Parts
SaveSelection: PUBLIC PROC [slice: Slice, selectClass: SelectionClass, scene: Scene] = {
seq: SliceDescriptor ¬ GGSelect.FindSelectedSlice[slice, selectClass];
IF seq=NIL THEN ClearSelection[slice, selectClass]
ELSE SaveSelectionInSequence[seq, selectClass];
};
SaveSelectionInSequence: PUBLIC PROC [descriptor: SliceDescriptor, selectClass: SelectionClass] = {
SaveSelectionInParts[descriptor.slice, descriptor.parts, selectClass];
};
SaveSelectionInParts: PUBLIC PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] = {
seg: Segment;
joint: Joint;
trajParts: TrajParts ¬ NARROW[parts];
hiSegment: NAT ¬ HiSegment[slice];
hiJoint: NAT ¬ HiJoint[slice];
FOR i: NAT IN [0..hiSegment] DO
seg ¬ FetchSegment[slice, i];
SetSegmentField[seg, trajParts.segments[i], selectClass];
FOR j: NAT IN [0..seg.class.controlPointCount[seg]) DO
SetControlPointField[seg, j, trajParts.controlPoints[i][j], selectClass];
ENDLOOP;
ENDLOOP;
FOR i: NAT IN [0..hiJoint] DO
joint ¬ FetchJoint[slice, i];
SetJointField[joint, trajParts.joints[i], selectClass];
ENDLOOP;
};
ClearSelection: PUBLIC PROC [slice: Slice, selectClass: SelectionClass] = {
seg: Segment;
joint: Joint;
hiSegment: NAT ¬ HiSegment[slice];
hiJoint: NAT ¬ HiJoint[slice];
FOR i: NAT IN [0..hiSegment] DO
seg ¬ FetchSegment[slice, i];
SetSegmentField[seg, FALSE, selectClass];
FOR j: NAT IN [0..seg.class.controlPointCount[seg]) DO
SetControlPointField[seg, j, FALSE, selectClass];
ENDLOOP;
ENDLOOP;
FOR i: NAT IN [0..hiJoint] DO
joint ¬ FetchJoint[slice, i];
SetJointField[joint, FALSE, selectClass];
ENDLOOP;
};
ClearSelections: PUBLIC PROC [slice: Slice] = {
seg: Segment;
joint: Joint;
hiSegment: NAT ¬ HiSegment[slice];
hiJoint: NAT ¬ HiJoint[slice];
FOR i: NAT IN [0..hiSegment] DO
seg ¬ FetchSegment[slice, i];
SetSegmentField[seg, FALSE, normal];
SetSegmentField[seg, FALSE, hot];
SetSegmentField[seg, FALSE, active];
SetSegmentField[seg, FALSE, match];
FOR j: NAT IN [0..seg.class.controlPointCount[seg]) DO
SetControlPointField[seg, j, FALSE, normal];
SetControlPointField[seg, j, FALSE, hot];
SetControlPointField[seg, j, FALSE, active];
SetControlPointField[seg, j, FALSE, match];
ENDLOOP;
ENDLOOP;
FOR i: NAT IN [0..hiJoint] DO
joint ¬ FetchJoint[slice, i];
SetJointField[joint, FALSE, normal];
SetJointField[joint, FALSE, hot];
SetJointField[joint, FALSE, active];
SetJointField[joint, FALSE, match];
ENDLOOP;
};
RemakeSelection: PUBLIC PROC [slice: Slice, selectClass: SelectionClass] RETURNS [parts: SliceParts] = {
trajData: TrajData ¬ NARROW[slice.data]; -- SHOULD THIS BE A COPY ??
trajParts: TrajParts ¬ NARROW[GGSequence.CreateEmpty[trajData]];
seg: Segment;
joint: Joint;
hiSegment: NAT ¬ HiSegment[slice];
hiJoint: NAT ¬ HiJoint[slice];
FOR i: NAT IN [0..hiSegment] DO
seg ¬ FetchSegment[slice, i];
trajParts.segments[i] ¬ GetSegmentField[seg, selectClass];
IF trajParts.segments[i] THEN trajParts.segCount ¬ trajParts.segCount + 1;
FOR j: NAT IN [0..seg.class.controlPointCount[seg]) DO
trajParts.controlPoints[i][j] ¬ GetControlPointField[seg, j, selectClass];
IF trajParts.controlPoints[i][j] THEN trajParts.controlPointCount ¬ trajParts.controlPointCount + 1;
ENDLOOP;
ENDLOOP;
FOR i: NAT IN [0..hiJoint] DO
joint ¬ FetchJoint[slice, i];
trajParts.joints[i] ¬ GetJointField[joint, selectClass];
IF trajParts.joints[i] THEN trajParts.jointCount ¬ trajParts.jointCount + 1;
ENDLOOP;
parts ¬ IF GGSequence.IsEmpty[trajParts] THEN NIL ELSE trajParts;
};
SetControlPointField: PROC [seg: Segment, cpNum: NAT, selected: BOOL, selectClass: SelectionClass] = {
seg.class.controlPointFieldSet[seg, cpNum, selected, selectClass];
};
GetControlPointField: PROC [seg: Segment, cpNum: NAT, selectClass: SelectionClass] RETURNS [selected: BOOL] = {
selected ¬ seg.class.controlPointFieldGet[seg, cpNum, selectClass];
};
GetJointField: PROC [joint: Joint, selectClass: SelectionClass] RETURNS [selected: BOOL] = {
SELECT selectClass FROM
normal => selected ¬ joint.TselectedInFull.normal;
hot => selected ¬ joint.TselectedInFull.hot;
active => selected ¬ joint.TselectedInFull.active;
match => selected ¬ joint.TselectedInFull.match;
ENDCASE;
};
GetSegmentField: PROC [seg: Segment, selectClass: SelectionClass] RETURNS [selected: BOOL] = {
SELECT selectClass FROM
normal => selected ¬ seg.TselectedInFull.normal;
hot => selected ¬ seg.TselectedInFull.hot;
active => selected ¬ seg.TselectedInFull.active;
match => selected ¬ seg.TselectedInFull.match;
ENDCASE;
};
SetJointField: PROC [joint: Joint, selected: BOOL, selectClass: SelectionClass] = {
SELECT selectClass FROM
normal => joint.TselectedInFull.normal ¬ selected;
hot => joint.TselectedInFull.hot ¬ selected;
active => joint.TselectedInFull.active ¬ selected;
match => joint.TselectedInFull.match ¬ selected;
ENDCASE;
};
SetSegmentField: PROC [seg: Segment, selected: BOOL, selectClass: SelectionClass] = {
SELECT selectClass FROM
normal => seg.TselectedInFull.normal ¬ selected;
hot => seg.TselectedInFull.hot ¬ selected;
active => seg.TselectedInFull.active ¬ selected;
match => seg.TselectedInFull.match ¬ selected;
ENDCASE;
};
Utilities
CopyJoint: PROC [joint: Joint] RETURNS [copy: Joint] = {
copy ¬ NEW[JointObj ¬ [point: joint.point, TselectedInFull: joint.TselectedInFull] ];
};
SetTrajRole: PUBLIC PROC [traj: Slice, role: FenceHoleOpen] = {
IF GGSliceOps.GetType[traj]=$Traj THEN NARROW[traj.data, TrajData].role ¬ role;
};
GetTrajRole: PUBLIC PROC [traj: Slice] RETURNS [role: FenceHoleOpen] = {
role ¬ NARROW[traj.data, TrajData].role;
};
Hit Testing
UnpackSimpleDescriptor: PUBLIC PROC [traj: Slice, parts: SliceParts] RETURNS [success: BOOL ¬ FALSE, partType: TrajPartType ¬ none, trajData: TrajData, joint: Joint ¬ NIL, jointNum: NAT ¬ 999, cp: Point ¬ [0,0], cpNum: NAT ¬ 999, seg: Segment ¬ NIL, segNum: NAT ¬ 999] = {
IF traj = NIL OR GGSliceOps.GetType[traj]#$Traj THEN RETURN;
[success, partType, trajData, joint, jointNum, cp, cpNum, seg, segNum] ¬ GGSequence.UnpackSimpleSequence[traj, parts];
};
UnpackHitData: PUBLIC PROC [hitData: REF ANY] RETURNS [hitType: HitType, segNum, cpNum, jointNum: INT, hitPoint: Point] = {
trajHitData: TrajHitData ¬ NARROW[hitData];
hitType ¬ trajHitData.hitType;
segNum ¬ trajHitData.segNum;
cpNum ¬ trajHitData.cpNum;
jointNum ¬ trajHitData.jointNum;
hitPoint ¬ trajHitData.hitPoint;
};
Utilities
Vector3D: TYPE = ARRAY [1..3] OF REAL;
PointToVector: PROC [point: Point] RETURNS [vector: Vector3D] = {
vector[1] ¬ point.x;
vector[2] ¬ point.y;
vector[3] ¬ 0;
};
CrossProduct: PROC [v1: Vector3D, v2: Vector3D] RETURNS [prodV: Vector3D] = {
| i j k |
| v1x v1y v1z |=(v1y*v2z - v1z*v2y) i + (v1z*v2x - v1x*v2z) j
| v2x v2y v2z |(v1x*v2y - v1y*v2x) k
prodV[1] ¬ v1[2]*v2[3] - v1[3]*v2[2];
prodV[2] ¬ v1[3]*v2[1] - v1[1]*v2[3];
prodV[3] ¬ v1[1]*v2[2] - v1[2]*v2[1];
};
GetPartialArea: PROC [pt1: Point, pt2: Point] RETURNS [partial: REAL] = {
D1: Vector3D ¬ PointToVector[pt1];
D2: Vector3D ¬ PointToVector[pt2];
areaVector: Vector3D ¬ CrossProduct[D2, D1];-- will only have a z component
RETURN [areaVector[3]];
};
SignedArea: PROC [slice: Slice] RETURNS [area: REAL ¬ 0.0] = {
Choose an arbitrary point P in the plane. Choose two consecutive joints (v1, v2) on the polygon described by the traj. Call the vector P to v1, D1. Call the distance P to v2, D2. The area of the triangle made by these three points is |D1|*|D2|*sin(angle between D1 and D2)/2.
This is just D2 x D1 where "x" is the cross product operator.
This will be positive if D2 is clockwise of D1.
If P is in the interior of poly for poly convex, and if we add up all of the areas we get by taking vertices pairwise in clockwise order, area will be positive. If we take them in counter-clockwise order, area will be negative. This turns out to be true whatever the position of P and even if poly is concave part of the time.
lastPoint, thisPoint: Point ¬ FetchJointPos[slice, 0];
hiSeg: NAT ¬ HiSegment[slice];
thisSeg: Segment;
FOR index: NAT IN [0..hiSeg] DO
thisSeg ¬ FetchSegment[slice, index];
FOR cpIndex: NAT IN [0..thisSeg.class.controlPointCount[thisSeg]) DO
area ¬ area + GetPartialArea[lastPoint, (thisPoint ¬ thisSeg.class.controlPointGet[thisSeg, cpIndex])];
lastPoint ¬ thisPoint;
ENDLOOP;
area ¬ area + GetPartialArea[lastPoint, (thisPoint ¬ FetchJointPos[slice, IF index=hiSeg THEN 0 ELSE index+1])];
lastPoint ¬ thisPoint;
ENDLOOP;
}; -- end of SignedArea
SignedAreaTransformSeq: PROC [descriptor: SliceDescriptor, transform: ImagerTransformation.Transformation] RETURNS [area: REAL ¬ 0.0] = {
lastPoint, thisPoint: Point;
trajParts: TrajParts ¬ NARROW[descriptor.parts];
hiSeg: NAT ¬ HiSegment[descriptor.slice];
thisSeg: Segment;
lastPoint ¬ FetchJointPos[descriptor.slice, 0];
IF trajParts.joints[0] THEN lastPoint ¬ ImagerTransformation.Transform[transform, lastPoint];
FOR index: NAT IN [0..hiSeg] DO
thisSeg ¬ FetchSegment[descriptor.slice, index];
FOR cpIndex: NAT IN [0..thisSeg.class.controlPointCount[thisSeg]) DO
thisPoint ¬ thisSeg.class.controlPointGet[thisSeg, cpIndex];
IF trajParts.controlPoints[index][cpIndex] THEN thisPoint ¬ ImagerTransformation.Transform[transform, thisPoint];
area ¬ area + GetPartialArea[lastPoint, thisPoint];
lastPoint ¬ thisPoint;
ENDLOOP;
thisPoint ¬ FetchJointPos[descriptor.slice, IF index=hiSeg THEN 0 ELSE index+1];
IF trajParts.joints[IF index=hiSeg THEN 0 ELSE index+1] THEN thisPoint ¬ ImagerTransformation.Transform[transform, thisPoint];
area ¬ area + GetPartialArea[lastPoint, thisPoint];
lastPoint ¬ thisPoint;
ENDLOOP;
}; -- end of SignedAreaTransformSeq
useBBox: BOOL = TRUE;
maxDistance: REAL = 99999.0;
noJoint: NAT = 9999;
noCP: NAT = 8888;
noSeg: NAT = 7777;
OnlyChild: PUBLIC PROC [slice: Slice] RETURNS [BOOL] = {
trajData: TrajData ¬ NARROW[slice.data]; -- NarrowRefFault if SliceType#$Traj
outline: Slice ¬ slice.parent;
outlineData: OutlineData ¬ NARROW[outline.data];
RETURN[outlineData.children.rest = NIL];
};
<<NearestSegment: PUBLIC PROC [testPoint: Point, descriptor: SliceDescriptor, tolerance: REAL] RETURNS [bestDist: REAL ¬ 0.0, bestSeg: NAT ¬ 0, bestPoint: Point ¬ [0.0, 0.0], bestNormal: Vector ¬ [0,-1], success: BOOL ¬ FALSE] = {
traj: Slice ¬ descriptor.slice;
trajData: TrajData ¬ NARROW[traj.data];
trajParts: TrajParts ¬ NARROW[descriptor.parts];
thisDist2, bestDist2: REAL;
thisPoint: Point;
thisSuccess: BOOL ¬ FALSE;
tolerance2: REAL ¬ tolerance*tolerance;
ProcessSegment: GGSequence.SegmentWalkProc = {
PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOLFALSE];
[thisPoint, thisSuccess] ¬ seg.class.closestPoint[seg, testPoint, tolerance];
IF thisSuccess THEN {
thisDist2 ¬ Vectors2d.DistanceSquared[thisPoint, testPoint];
IF thisDist2 < bestDist2 THEN {
bestDist2 ¬ thisDist2;
bestSeg ¬ index;
bestPoint ¬ thisPoint;
success ¬ TRUE;
};
};
};
IF useBBox THEN {
IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[traj], tolerance]
THEN RETURN; -- success=FALSE
};
These values may be returned if the sequence is a single joint
bestDist ¬ tolerance;
bestDist2 ¬ tolerance2;
bestSeg ¬ noSeg; -- magic number meaning no segment
bestPoint ¬ [-1.0, -1.0];
[] ¬ GGSequence.WalkSegmentsInSequence[trajData, trajParts, ProcessSegment];
IF success THEN { -- compute the surface normal
jointPoint: Point;
bestSegment: Segment;
diffX, diffY: REAL;
bestDist ¬ RealFns.SqRt[bestDist2];
bestNormal ¬ Vectors2d.Sub[testPoint, bestPoint];
IF bestSeg = 0 THEN {
jointPoint ¬ FetchJointPos[traj, 0];
diffX ¬ ABS[jointPoint.x - bestPoint.x];
diffY ¬ ABS[jointPoint.y - bestPoint.y];
bestSegment ¬ FetchSegment[traj, 0];
IF trajData.role = open AND diffX < 0.01 AND diffY < 0.01 THEN {
[bestNormal, ----] ¬ bestSegment.class.jointNormal[bestSegment, jointPoint, testPoint, FALSE];
};
};
IF bestSeg = HiSegment[traj] THEN {
jointPoint ¬ FetchJointPos[traj, HiJoint[traj]];
diffX ¬ ABS[jointPoint.x - bestPoint.x];
diffY ¬ ABS[jointPoint.y - bestPoint.y];
bestSegment ¬ FetchSegment[traj, bestSeg];
IF trajData.role = open AND diffX < 0.01 AND diffY < 0.01 THEN {
[bestNormal, ----] ¬ bestSegment.class.jointNormal[bestSegment, jointPoint, testPoint, TRUE];
};
};
};
};
>>
NearestSegment: PUBLIC PROC [testPoint: Point, descriptor: SliceDescriptor, tolerance: REAL] RETURNS [bestDist: REAL ¬ 0.0, bestSeg: NAT ¬ 0, bestPoint: Point ¬ [0.0, 0.0], bestNormal: Vector ¬ [0,-1], success: BOOL ¬ FALSE] = {
traj: Slice ¬ descriptor.slice;
trajData: TrajData ¬ NARROW[traj.data];
trajParts: TrajParts ¬ NARROW[descriptor.parts];
thisDist2, bestDist2: REAL;
thisPoint: Point;
thisSuccess: BOOL ¬ FALSE;
tolerance2: REAL ¬ tolerance*tolerance;
ProcessSegment: GGSequence.SegmentWalkProc = {
PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOLFALSE];
[thisPoint, thisSuccess] ¬ seg.class.closestPoint[seg, testPoint, tolerance];
IF thisSuccess THEN {
thisDist2 ¬ Vectors2d.DistanceSquared[thisPoint, testPoint];
IF thisDist2 < bestDist2 THEN {
bestDist2 ¬ thisDist2;
bestSeg ¬ index;
bestPoint ¬ thisPoint;
success ¬ TRUE;
};
};
};
IF useBBox THEN {
IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[traj], tolerance]
THEN RETURN; -- success=FALSE
};
These values may be returned if the sequence is a single joint
bestDist ¬ tolerance;
bestDist2 ¬ tolerance2;
bestSeg ¬ noSeg; -- magic number meaning no segment
bestPoint ¬ [-1.0, -1.0];
[] ¬ GGSequence.WalkSegmentsInSequence[trajData, trajParts, ProcessSegment];
IF success THEN { -- compute the surface normal
jointPoint: Point;
diffX, diffY: REAL;
bestDist ¬ RealFns.SqRt[bestDist2];
bestNormal ¬ Vectors2d.Sub[testPoint, bestPoint];
jointPoint ¬ FetchJointPos[traj, bestSeg];
diffX ¬ ABS[jointPoint.x - bestPoint.x];
diffY ¬ ABS[jointPoint.y - bestPoint.y];
IF trajData.role = open AND diffX < 0.01 AND diffY < 0.01 THEN {
bestSegment: Segment ¬ FetchSegment[traj, bestSeg];
bestNormal ¬ bestSegment.class.jointNormal[bestSegment, jointPoint, testPoint, FALSE].normal;
};
};
};
NearestJoint: PUBLIC PROC [testPoint: Point, descriptor: SliceDescriptor, tolerance: REAL] RETURNS [bestDist: REAL ¬ 0.0, bestJoint: NAT ¬ 0, bestPoint: Point ¬ [0.0, 0.0], bestNormal: Vector ¬ [0,-1], success: BOOL ¬ FALSE] = {
Finds the nearest joint of traj to testPoint (and its distance from testPoint).
traj: Slice ¬ descriptor.slice;
trajData: TrajData ¬ NARROW[traj.data];
trajParts: TrajParts ¬ NARROW[descriptor.parts];
tolerance2: REAL ¬ tolerance*tolerance;
thisDist2, bestDist2: REAL;
seg1, seg2: Segment;
normal1, normal2, tangent1, tangent2, direction: Vector;
ProcessJoint: PROC [traj: TrajData, jointPos: Point, index: NAT] RETURNS [done: BOOL ¬ FALSE] = {
thisDist2 ¬ Vectors2d.DistanceSquared[jointPos, testPoint];
IF thisDist2 < bestDist2 THEN {
bestDist2 ¬ thisDist2;
bestJoint ¬ index;
bestPoint ¬ jointPos;
success ¬ TRUE;
};
};
IF useBBox THEN {
IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[traj], tolerance]
THEN RETURN; -- success=FALSE
};
These values may be returned if no joints are described in the descriptor.
bestDist ¬ tolerance;
bestDist2 ¬ tolerance2;
bestJoint ¬ noJoint;
bestPoint ¬ [-1.0, -1.0];
GGSequence.WalkJointPositionsInSequence[trajData, trajParts, ProcessJoint];
IF success THEN { -- compute the normal
Same code as FindJointNormal in GGOutlineImplA:
bestDist ¬ RealFns.SqRt[bestDist2];
IF HiSegment[traj] < bestJoint THEN seg2 ¬
IF trajData.role = open THEN NIL ELSE FetchSegment[traj, 0]
ELSE seg2 ¬ FetchSegment[traj, bestJoint];
seg1 ¬ PreviousSegment[traj, bestJoint];
IF seg1 # NIL AND seg2 # NIL THEN {
[normal2, tangent2] ¬ seg2.class.jointNormal[seg2, bestPoint, testPoint , FALSE];
[normal1, tangent1] ¬ seg1.class.jointNormal[seg1, bestPoint, testPoint, TRUE];
direction ¬ Vectors2d.VectorFromPoints[bestPoint, testPoint];
bestNormal ¬ IF ABS[Vectors2d.SmallestAngleBetweenVectors[tangent1, direction]] < ABS[Vectors2d.SmallestAngleBetweenVectors[tangent2, direction]] THEN normal1 ELSE normal2;
}
ELSE {
IF seg1 # NIL THEN [bestNormal, ----] ¬ seg1.class.jointNormal[seg1, bestPoint, testPoint, TRUE];
IF seg2 # NIL THEN [bestNormal, ----] ¬ seg2.class.jointNormal[seg2, bestPoint, testPoint, FALSE];
};
};
};
NearestControlPoint: PUBLIC PROC [testPoint: Point, descriptor: SliceDescriptor, tolerance: REAL] RETURNS [bestDist: REAL ¬ 0.0, bestSeg: NAT ¬ 0, bestControlPoint: NAT ¬ 0, bestPoint: Point ¬ [0.0, 0.0], bestNormal: Vector ¬ [0,-1], success: BOOL ¬ FALSE] = {
Finds the nearest control point of seq (within tolerance) to testPoint (and its distance from testPoint).
SomeCP: PROC [i: NAT] RETURNS [BOOL] = {
cpCount: NAT ¬ trajParts.controlPoints[i].len;
FOR j: NAT IN [0..cpCount) DO
IF trajParts.controlPoints[i][j] THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
traj: Slice ¬ descriptor.slice;
trajData: TrajData ¬ NARROW[traj.data];
trajParts: TrajParts ¬ NARROW[descriptor.parts];
tolerance2: REAL ¬ tolerance*tolerance;
thisDist2, bestDist2: REAL;
thisControlPoint: NAT;
thisPoint: Point;
thisNormal: Vector;
thisSuccess: BOOL ¬ FALSE;
ProcessSegment: PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOL ¬ FALSE] = {
IF NOT SomeCP[index] THEN RETURN;
[thisPoint, thisNormal, thisControlPoint, thisSuccess] ¬ seg.class.closestControlPoint[seg, testPoint, tolerance];
IF thisSuccess THEN {
thisDist2 ¬ Vectors2d.DistanceSquared[thisPoint, testPoint];
IF thisDist2 < bestDist2 THEN {
bestDist2 ¬ thisDist2;
bestSeg ¬ index;
bestControlPoint ¬ thisControlPoint;
bestPoint ¬ thisPoint;
bestNormal ¬ thisNormal;
success ¬ TRUE;
};
};
};
IF useBBox THEN {
IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[traj], tolerance]
THEN RETURN; -- success=FALSE
};
bestDist ¬ tolerance;
bestDist2 ¬ tolerance2;
bestSeg ¬ noSeg;
bestControlPoint ¬ noCP; -- magic number meaning no CP
bestPoint ¬ [-1.0, -1.0];
[] ¬ GGSequence.WalkSegmentsInSequence[trajData, trajParts, ProcessSegment];
IF success THEN bestDist ¬ RealFns.SqRt[bestDist2];
};
Use sparingly:
IndexOfJoint: PUBLIC PROC [joint: Joint, slice: Slice] RETURNS [index: INT] = {
Returns -1 if it doesn't exist.
thisJoint: Joint;
FOR i: NAT IN [0..HiJoint[slice]] DO
thisJoint ¬ FetchJoint[slice, i];
IF thisJoint = joint THEN RETURN[i];
ENDLOOP;
RETURN[-1];
};
IndexOfSegment: PUBLIC PROC [segment: Segment, slice: Slice] RETURNS [index: INT] = {
Returns -1 if it doesn't exist.
thisSeg: Segment;
FOR i: NAT IN [0..HiSegment[slice]] DO
thisSeg ¬ FetchSegment[slice, i];
IF thisSeg = segment THEN RETURN[i];
ENDLOOP;
RETURN[-1];
};
END.