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;
Trajectory-Only Routines
Creating
CreateTrajFromData: PUBLIC PROC [role: FenceHoleOpen, segCount: NAT, segments: Rosary.ROSARY, joints: Rosary.ROSARY, extraPoints: LIST OF Joint, visibleJoints: BOOLTRUE, strokeJoint: Imager.StrokeJoint ← round, loArrow, hiArrow: BOOLFALSE, 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: BOOLTRUE] = {
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;
};
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: BOOLEANFALSE] = {
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: BOOLEANFALSE] = {
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: BOOLEANFALSE] = {
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: BOOLEANFALSE] = {
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: BOOLTRUE] = {
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: BOOLFALSE;
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: BOOLFALSE] ~ {
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: BOOLFALSE] ~ {
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: NATSELECT 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: NATSELECT 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: BOOLFALSE] = {
traj: Traj ← descriptor.slice;
trajData: TrajData ← NARROW[traj.data];
trajParts: TrajParts ← NARROW[descriptor.parts];
thisDist2, bestDist2: REAL;
thisPoint: Point;
thisSuccess: BOOLFALSE;
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: BOOLFALSE] = {
traj: Traj ← descriptor.slice;
trajData: TrajData ← NARROW[traj.data];
trajParts: TrajParts ← NARROW[descriptor.parts];
thisDist2, bestDist2: REAL;
thisPoint: Point;
thisSuccess: BOOLFALSE;
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: BOOLFALSE] = {
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: BOOLFALSE] = {
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: BOOLFALSE] = {
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: BOOLFALSE;
ProcessSegment: PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOLFALSE] = {
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.