GGTrajImpl.mesa
Contents: Procedures to implement the Traj Slice Class. Trajectories consist of Segments.
Copyright Ó 1986, 1987, 1989 by Xerox Corporation. All rights reserved.
Pier, February 18, 1992 5:22 pm PST
Bier, January 31, 1991 6:04 pm PST
Doug Wyatt, December 18, 1989 4:23:20 pm PST
DIRECTORY
Feedback, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGSegment, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTraj, GGTransform, GGUtility, Imager, ImagerTransformation, Lines2dTypes, Lines2d, RealFns, Rope, Rosary, Vectors2d;
GGTrajImpl:
CEDAR
PROGRAM
IMPORTS Feedback, GGBoundBox, GGCoreOps, GGOutline, GGParent, GGSegment, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerTransformation, Lines2d, RealFns, Rosary, 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;
OutlineData: TYPE = GGOutline.OutlineData;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
RunProc: TYPE = GGTraj.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;
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;
};
};
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: Traj ← 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 {
Feedback.AppendByName[$Gargoyle, oneLiner, $Apology, "Only delete control points from Cubic Splines"];
};
};
ENDLOOP;
bBox ← GGSliceOps.GetBoundBox[outlineOfTraj];
};
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: Traj ← seq.slice;
seqData: TrajData ← NARROW[seqTraj.data];
seqParts: TrajParts ← NARROW[seq.parts];
seqGen: SequenceGenerator;
newTraj: Traj;
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;
};
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.FromProcProc[CopyEachSegment];
desiredSegments ← [originalSegments, s2, len2];
hiSegments ← Rosary.FromProcProc[CopyEachSegment];
extractedSegments ← Rosary.Concat[hiSegments, loSegments];
}
ELSE {
desiredSegments ← [originalSegments, next.index, trajParts.segCount];
extractedSegments ← Rosary.FromProcProc[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.FromProcProc[CopyEachJoint];
desiredJoints ← [originalJoints, s2, len2];
hiJoints ← Rosary.FromProcProc[CopyEachJoint];
extractedJoints ← Rosary.Concat[hiJoints, loJoints];
}
ELSE {
desiredJoints ← [originalJoints, next.index, jointCount];
extractedJoints ← Rosary.FromProcProc[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.FromProcProc[CopyEachSegment];
extractedJoints ← Rosary.FromProcProc[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.
};
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];
};>>
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];
};
};
};
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;
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;
};
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;
};
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: Traj ← 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: BOOL ← FALSE];
[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: Traj ← 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: BOOL ← FALSE];
[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: Traj ← 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: Traj ← 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];
};
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.