GGAlignImpl.mesa
Contents: Contains routines for building and maintaining sets of gravity active scene objects and alignment objects, called "bags".
Copyright Ó 1988 by Xerox Corporation. All rights reserved.
Pier, August 12, 1988 4:48:20 pm PDT
Bier, March 5, 1992 2:40 pm PST
Doug Wyatt, June 24, 1988 10:44:55 am PDT
DIRECTORY
CardTab, CodeTimer, Draw2d, Feedback, GGAlign, GGBasicTypes, GGCaret, GGCircleCache, GGCircles, GGControlPanelTypes, GGCoreTypes, GGDragTypes, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGRefresh, GGRefreshTypes, GGScene, GGSegmentTypes, GGSelect, GGSequence, GGShapes, GGSlice, GGSliceOps, GGState, GGTraj, GGUtility, Imager, ImagerBackdoor, Lines2d, Process, Real, RealFns, RefTab, Rope, Vectors2d;
GGAlignImpl:
CEDAR
PROGRAM
IMPORTS CardTab, CodeTimer, Draw2d, Feedback, GGCaret, GGCircleCache, GGCircles, GGParent, GGRefresh, GGSliceOps, GGScene, GGSelect, GGSequence, GGShapes, GGSlice, GGState, GGTraj, GGUtility, Imager, ImagerBackdoor, Lines2d, Process, Real, RealFns, RefTab, Vectors2d
EXPORTS GGAlign, GGInterfaceTypes = BEGIN
DragDataObj: PUBLIC TYPE = GGDragTypes.DragDataObj; -- exported to GGInterfaceTypes
AlignBag: TYPE = GGInterfaceTypes.AlignBag;
AlignBagObj: TYPE = GGInterfaceTypes.AlignBagObj;
AlignmentCircle: TYPE = GGInterfaceTypes.AlignmentCircle;
AlignmentCircleObj: TYPE = GGInterfaceTypes.AlignmentCircleObj;
AlignmentLine: TYPE = GGInterfaceTypes.AlignmentLine;
AlignmentLineObj: TYPE = GGInterfaceTypes.AlignmentLineObj;
AlignmentObject: TYPE = GGModelTypes.AlignmentObject;
AlignmentPoint: TYPE = REF AlignmentPointObj;
AlignmentPointObj: TYPE = GGInterfaceTypes.AlignmentPointObj;
Angle: TYPE = GGBasicTypes.Angle;
BezierDragRecord: TYPE = GGInterfaceTypes.BezierDragRecord;
Caret: TYPE = GGInterfaceTypes.Caret;
Circle: TYPE = GGBasicTypes.Circle;
ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes
ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator;
Edge: TYPE = GGBasicTypes.Edge;
EditConstraints: TYPE = GGModelTypes.EditConstraints;
FeatureData: TYPE = REF FeatureDataObj;
FeatureDataObj: TYPE = GGModelTypes.FeatureDataObj;
FeatureWalkProc: TYPE = GGAlign.FeatureWalkProc;
Filters: TYPE = GGInterfaceTypes.Filters;
FilterSliceProc: TYPE = GGAlign.FilterSliceProc;
FilterType: TYPE = GGAlign.FilterType;
GGData: TYPE = GGInterfaceTypes.GGData;
Joint: TYPE = GGModelTypes.Joint;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
Line: TYPE = GGCoreTypes.Line;
OutlineData: TYPE = GGOutline.OutlineData;
Point: TYPE = GGBasicTypes.Point;
PointAndDone: TYPE = GGModelTypes.PointAndDone;
PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
PointWalkProc: TYPE = GGModelTypes.PointWalkProc;
RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SelectionClass: TYPE = GGSegmentTypes.SelectionClass;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceGenerator: TYPE = GGSequence.SequenceGenerator;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
SliceParts: TYPE = GGModelTypes.SliceParts;
Traj: TYPE = GGModelTypes.Traj;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
TrajData: TYPE = GGModelTypes.TrajData;
TriggerBag: TYPE = REF TriggerBagObj;
TriggerBagObj: TYPE = GGInterfaceTypes.TriggerBagObj;
Vector: TYPE = GGBasicTypes.Vector;
Problem: SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
EntityNotFound: PUBLIC SIGNAL = CODE;
UnexpectedType: PUBLIC ERROR = CODE;
BrokenInvariant: PUBLIC ERROR = CODE;
The TriggerBag, the SceneBag and the AlignBag
The set of alignment objects is computed in three steps (see Figure 1). First, we determine which joints, control points, and segments in the scene are triggers. We put these in triggerBag. Next we determine which scene objects are gravity active. We put these in sceneBag. Finally, we determine the set of alignment objects from the triggerBag using the current filters. This set is represented by the selected slopes, angles, radii, and distances in the user interface.
The bags are recomputed at these times:
triggerBag: Updated whenever an Add or Drag operation is performed, or whenever anything becomes hot or cold.
sceneBag: Updated whenever an Add or Drag operation is performed.
alignBag. Updated whenever an Add or Drag operation is performed, whenever anything becomes hot or cold, or whenever the set of current filters changes.
The bags are represented as follows:
triggerBag and sceneBag: several LIST OF FeatureData. The FeatureData will point to a SliceDescriptor or the anchor.
alignBag: several LIST OF FeatureData. The FeatureData will point to an AlignmentLine, an AlignmentCircle, an AlignmentPoint, or a Caret (e.g. the anchor).
[Artwork node; type 'ArtworkInterpress on' to command tool]
TriggerBag
emptyTriggerBag: PUBLIC TriggerBag;
CreateTriggerBag:
PUBLIC
PROC []
RETURNS [triggerBag: TriggerBag] = {
triggerBag ←
NEW[TriggerBagObj ← [
slices: RefTab.Create[mod: 211],
anchor: NIL]];
};
FlushTriggerBag:
PUBLIC
PROC [triggerBag: TriggerBag] = {
RefTab.Erase[triggerBag.slices];
triggerBag.anchor ← NIL;
};
EmptyTriggerBag:
PUBLIC PROC [triggerBag: TriggerBag]
RETURNS [
BOOL] = {
RETURN[
EmptyTriggerSlices[triggerBag] AND
triggerBag.anchor = NIL
];
};
EmptyTriggerSlices:
PROC [triggerBag: TriggerBag]
RETURNS [empty:
BOOL ←
FALSE] = {
SliceFeatureFound: RefTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
IF val = NIL THEN ERROR;
quit ← TRUE;
};
empty ← NOT RefTab.Pairs[triggerBag.slices, SliceFeatureFound];
};
CopyTriggerBag:
PUBLIC
PROC [to, from: TriggerBag] = {
FlushTriggerBag[to];
CopyTriggerSlices[to.slices, from.slices];
to.anchor ← from.anchor;
};
CopyTriggerSlices:
PROC [toTable, fromTable: RefTab.Ref] = {
CopySliceFeatures: RefTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
Just copy the pointer to the list in each bucket. This works because we use non-destructive list operations on the copied bag.
inserted: BOOL ← RefTab.Insert[toTable, key, val];
IF NOT inserted THEN ERROR;
};
RefTab.Erase[toTable];
[] ← RefTab.Pairs[fromTable, CopySliceFeatures];
};
WalkSliceTriggers:
PUBLIC
PROC [triggerBag: TriggerBag, walkProc: FeatureWalkProc] = {
DoForTrigger: RefTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
FOR list:
LIST
OF FeatureData ←
NARROW[val], list.rest
UNTIL list =
NIL
DO
quit ← walkProc[list.first];
IF quit THEN EXIT;
ENDLOOP;
};
[] ← RefTab.Pairs[triggerBag.slices, DoForTrigger];
};
ListSliceTriggers:
PROC [triggerBag: TriggerBag]
RETURNS [features:
LIST
OF FeatureData] = {
DoMakeList:
PROC [feature: FeatureData]
RETURNS [done:
BOOL ←
FALSE] = {
[features, ptr] ← GGUtility.AddFeatureData[feature, features, ptr];
};
ptr: LIST OF FeatureData;
WalkSliceTriggers[triggerBag, DoMakeList];
};
FindFeature:
PROC [triggerBag: TriggerBag, slice: Slice]
RETURNS [feature: FeatureData ←
NIL] = {
val: REF;
found: BOOL ← FALSE;
[found, val] ← RefTab.Fetch[triggerBag.slices, slice];
IF NOT found THEN RETURN[NIL];
FOR list:
LIST
OF FeatureData ←
NARROW[val], list.rest
UNTIL list =
NIL
DO
IF NARROW[list.first.shape, SliceDescriptor].slice = slice THEN RETURN[list.first];
ENDLOOP;
};
ReplaceFeature:
PROC [triggerBag: TriggerBag, oldFeature, newFeature: FeatureData] = {
Do non-destructive list operations to replace the old feature with the new one.
val: REF;
key: REF ← NARROW[oldFeature.shape, SliceDescriptor].slice;
found: BOOL ← FALSE;
[found, val] ← RefTab.Fetch[triggerBag.slices, key];
IF found
AND val #
NIL
THEN {
list: LIST OF FeatureData ← NARROW[val];
list ← RemoveFeatureFromList[oldFeature, list];
list ← CONS[newFeature, list];
[] ← RefTab.Store[triggerBag.slices, key, list];
}
ELSE ERROR;
};
DeleteFeature:
PROC [triggerBag: TriggerBag, oldFeature: FeatureData] = {
Do non-destructive list operations to delete the old feature.
val: REF;
key: REF ← NARROW[oldFeature.shape, SliceDescriptor].slice;
found: BOOL ← FALSE;
[found, val] ← RefTab.Fetch[triggerBag.slices, key];
IF found
AND val #
NIL
THEN {
list: LIST OF FeatureData ← NARROW[val];
list ← RemoveFeatureFromList[oldFeature, list];
IF list = NIL THEN [] ← RefTab.Delete[triggerBag.slices, key]
ELSE [] ← RefTab.Store[triggerBag.slices, key, list];
}
ELSE ERROR;
};
AppendFeature:
PUBLIC
PROC [feature: FeatureData, sliceTriggers: RefTab.Ref] = {
Do non-destructive list operations to add the new feature to the hash table.
val: REF;
slice: Slice ← NARROW[feature.shape, SliceDescriptor].slice;
found: BOOL ← FALSE;
Process.CheckForAbort[];
[found, val] ← RefTab.Fetch[sliceTriggers, slice];
IF found
AND val #
NIL
THEN {
list: LIST OF FeatureData ← NARROW[val];
list ← DeleteSliceFromList[slice, list]; -- may be needed later
list ← CONS[feature, list];
[] ← RefTab.Store[sliceTriggers, slice, list];
}
ELSE {
list: LIST OF FeatureData ← LIST[feature];
[] ← RefTab.Insert[sliceTriggers, slice, list];
};
};
FeatureFromSlice:
PUBLIC
PROC [slice: Slice, parts: SliceParts ←
NIL]
RETURNS [feature: FeatureData] = {
sliceD: SliceDescriptor ← IF parts=NIL THEN GGSliceOps.NewParts[slice, NIL, slice] ELSE GGSlice.DescriptorFromParts[slice, parts];
feature ← NEW[FeatureDataObj];
feature.type ← slice;
feature.shape ← sliceD;
};
FeatureFromAnchor:
PUBLIC
PROC [anchor: Caret]
RETURNS [feature: FeatureData] = {
feature ← NEW[FeatureDataObj];
feature.type ← anchor; -- that is, anchor => the member of the enumerated type
feature.shape ← anchor; -- that is, anchor => the parameter
};
FillStaticTriggerBag:
PUBLIC
PROC [anchor: Caret, scene: Scene, heuristics:
BOOL, triggerBag: TriggerBag] = {
AddAnchorTrigger[anchor, triggerBag];
AddAllHotSlices[scene, triggerBag];
};
FillDynamicTriggerBag:
PUBLIC
PROC [anchor: Caret, scene: Scene, heuristics:
BOOL, triggerBag: TriggerBag, editConstraints: EditConstraints, bezierDrag: BezierDragRecord] = {
AddAnchorTrigger[anchor, triggerBag];
AddAllHotSlices[scene, triggerBag];
[] ← AddHeuristics[scene, heuristics, $Drag, triggerBag, editConstraints, bezierDrag];
[] ← RemoveMoving[scene, triggerBag, editConstraints, bezierDrag];
};
SceneBag
FillStaticSceneBag:
PUBLIC
PROC [scene: Scene, sceneBag: TriggerBag] = {
AddAllSlices[scene, sceneBag];
};
FillDynamicSceneBag:
PUBLIC
PROC [scene: Scene, sceneBag: TriggerBag, editConstraints: GGModelTypes.EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord] = {
AddAllSlices[scene, sceneBag];
[] ← RemoveMoving[scene, sceneBag, editConstraints, bezierDrag];
};
AlignBag
emptyAlignBag: PUBLIC AlignBag;
CreateAlignBag:
PUBLIC
PROC []
RETURNS [alignBag: AlignBag] = {
alignBag ←
NEW[AlignBagObj ← [
slopeLineTable: CardTab.Create[mod: 211],
angleLines: NIL,
radiiCircles: NIL,
distanceLines: NIL,
anchor: NIL
]];
};
CopyAlignBag:
PROC [to, from: AlignBag] = {
CopySlopeLineTable[toTable: to.slopeLineTable, fromTable: from.slopeLineTable];
to.angleLines ← GGUtility.CopyFeatureDataList[from.angleLines];
to.radiiCircles ← GGUtility.CopyFeatureDataList[from.radiiCircles];
to.distanceLines ← GGUtility.CopyFeatureDataList[from.distanceLines];
to.anchor ← from.anchor;
};
EmptyAlignBag:
PUBLIC PROC [alignBag: AlignBag]
RETURNS [
BOOL] = {
RETURN[
EmptySlopeLines[alignBag] AND
alignBag.angleLines = NIL AND
alignBag.radiiCircles = NIL AND
alignBag.distanceLines = NIL AND
alignBag.anchor = NIL];
};
CopySlopeLineTable:
PROC [toTable, fromTable: CardTab.Ref] = {
CopyFeatures: CardTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
inserted: BOOL ← CardTab.Insert[toTable, key, val];
IF NOT inserted THEN ERROR;
};
CardTab.Erase[toTable];
[] ← CardTab.Pairs[fromTable, CopyFeatures];
};
EmptySlopeLines:
PROC [alignBag: AlignBag]
RETURNS [empty:
BOOL ←
FALSE] = {
FeatureFound: CardTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
IF val = NIL THEN ERROR;
quit ← TRUE;
};
empty ← NOT CardTab.Pairs[alignBag.slopeLineTable, FeatureFound];
};
FlushAlignBag:
PUBLIC
PROC [alignBag: AlignBag] = {
FlushSlopeLines[alignBag];
alignBag.angleLines ← NIL;
alignBag.radiiCircles ← NIL;
alignBag.distanceLines ← NIL;
alignBag.anchor ← NIL;
};
TriggersPerSlopeLine:
PROC [ggData: GGData]
RETURNS [lines, min, max:
CARD, avg:
REAL] = {
Count the number of triggers per slope line.
Count: CardTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
list: LIST OF FeatureData ← NARROW[val];
aLine: AlignmentLine;
triggersPerSlope: CARD ← 0;
FOR l:
LIST
OF FeatureData ← list, l.rest
UNTIL l =
NIL
DO
lines ← lines + 1;
aLine ← NARROW[l.first.shape];
triggersPerSlope ← 0;
FOR pointList:
LIST
OF Point ← aLine.triggerPoints, pointList.rest
UNTIL pointList =
NIL
DO
triggersPerSlope ← triggersPerSlope + 1;
ENDLOOP;
min ← MIN[min, triggersPerSlope];
max ← MAX[max, triggersPerSlope];
totalTriggers ← totalTriggers + triggersPerSlope;
ENDLOOP; -- next slope
};
totalTriggers: REAL ← 0.0;
lines ← 0;
min ← LAST[CARD];
max ← 0;
[] ← CardTab.Pairs[ggData.hitTest.alignBag.slopeLineTable, Count];
avg ← totalTriggers/lines;
};
FlushSlopeLines:
PROC [alignBag: AlignBag] = {
CardTab.Erase[alignBag.slopeLineTable];
};
FillStaticAlignBag:
PUBLIC
PROC [triggerBag: TriggerBag, sceneBag: TriggerBag, ggData: GGData, hideAlignments:
BOOL, alignBag: AlignBag] = {
BuiltInFilters[triggerBag, ggData, hideAlignments, alignBag];
};
FillDynamicAlignBag:
PUBLIC
PROC [triggerBag: TriggerBag, sceneBag: TriggerBag, ggData: GGData, hideAlignments:
BOOL, action:
ATOM, alignBag: AlignBag] = {
BuiltInFilters[triggerBag, ggData, hideAlignments, alignBag];
};
Slopes, Circles, Angles, Distance Lines, and Midpoints
AddAlignment:
PROC [featureData: FeatureData, alignBag: AlignBag] = {
Process.CheckForAbort[];
SELECT featureData.type
FROM
distanceLine => alignBag.distanceLines ← CONS[featureData, alignBag.distanceLines];
slopeLine => AddSlopeLine[featureData, alignBag];
angleLine => alignBag.angleLines ← CONS[featureData, alignBag.angleLines];
radiiCircle => alignBag.radiiCircles ← CONS[featureData, alignBag.radiiCircles];
anchor => alignBag.anchor ← featureData;
ENDCASE => SIGNAL Problem[msg: "Unimplemented feature type"];
};
AddSlopeLine:
PROC [featureData: FeatureData, alignBag: AlignBag] = {
line: Line ← NARROW[featureData.shape, AlignmentLine].line;
distance: REAL ← line.d;
key: CARD ← KeyFromDistance[distance];
val: REF;
found: BOOL ← FALSE;
[found, val] ← CardTab.Fetch[alignBag.slopeLineTable, key];
IF
NOT found
THEN {
list: LIST OF FeatureData ← LIST[featureData];
[] ← CardTab.Insert[alignBag.slopeLineTable, key, list];
}
ELSE {
list: LIST OF FeatureData ← NARROW[val];
list ← CONS[featureData, list];
[] ← CardTab.Store[alignBag.slopeLineTable, key, list];
};
};
JointAddSlopeLine:
PUBLIC
PROC [degrees:
REAL, point: Point, alignBag: AlignBag]
RETURNS [feature: FeatureData] = {
The angle (degrees) and direction vector are the global information. lineList is used to avoid redundant lines.
line: Line;
coincident: FeatureData;
cosine, sine, distance: REAL;
[coincident, cosine, sine, distance] ← LineThru[point, degrees, alignBag.slopeLineTable];
IF coincident #
NIL
THEN {
alignmentLine: AlignmentLine ← NARROW[coincident.shape];
alignmentLine.triggerPoints ← CONS[point, alignmentLine.triggerPoints];
feature ← NIL;
}
ELSE {
alignmentLine: AlignmentLine;
line ← Lines2d.LineFromCoefficients[sine, cosine, distance];
alignmentLine ← NEW[AlignmentLineObj ← [line: line, degrees: degrees, triggerPoints: CONS[point, NIL]]];
feature ← NEW[FeatureDataObj];
feature.type ← slopeLine;
feature.shape ← alignmentLine;
AddAlignment[feature, alignBag];
};
};
WalkSlopeLines:
PUBLIC
PROC [alignBag: AlignBag, walkProc: FeatureWalkProc] = {
WalkAction: CardTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
FOR list:
LIST
OF FeatureData ←
NARROW[val], list.rest
UNTIL list =
NIL
DO
quit ← walkProc[list.first];
IF quit THEN EXIT;
ENDLOOP;
};
[] ← CardTab.Pairs[alignBag.slopeLineTable, WalkAction];
};
ListOfSlopeLines:
PROC [alignBag: AlignBag]
RETURNS [slopeLines:
LIST
OF FeatureData ←
NIL] = {
ConsSlopeLine: CardTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
FOR list:
LIST
OF FeatureData ←
NARROW[val], list.rest
UNTIL list =
NIL
DO
slopeLines ← CONS[list.first, slopeLines];
ENDLOOP;
};
[] ← CardTab.Pairs[alignBag.slopeLineTable, ConsSlopeLine];
};
epsilon: REAL = 1.0e-3;
epsilonInverse:
REAL = 1.0e+3;
KeysFromDistance:
PROC [distance:
REAL]
RETURNS [keys:
ARRAY[0..2]
OF
CARD] = {
int: INT;
distance ← distance * epsilonInverse;
int ← Real.Floor[distance];
keys[0] ← LOOPHOLE[int];
keys[1] ← LOOPHOLE[int+1];
keys[2] ← LOOPHOLE[int-1];
};
KeyFromDistance:
PROC [distance:
REAL]
RETURNS [key:
CARD] = {
For epsilon = 1.0e-3, we wish to truncate distance down to the nearest multiple of epsilon.
For example, 1.0012 => 1.001.
Note: distance can be negative.
int: INT;
distance ← distance * epsilonInverse; -- giving 1001.2 in our example
int ← Real.Floor[distance];
key ← LOOPHOLE[int];
};
LineThru:
PROC [point: Point, degrees:
REAL, table: CardTab.Ref]
RETURNS [coincident: FeatureData, cosine, sine, distance:
REAL] = {
line: Line;
keys: ARRAY[0..2] OF CARD;
val: REF;
found: BOOL ← FALSE;
[distance, cosine, sine] ← DistanceFromOrigin[point, degrees];
keys ← KeysFromDistance[distance];
FOR i:
NAT
IN [0..2]
DO
[found, val] ← CardTab.Fetch[table, keys[i]];
IF NOT found THEN LOOP;
FOR l:
LIST
OF FeatureData ←
NARROW[val], l.rest
UNTIL l =
NIL
DO
line ← NARROW[l.first.shape, AlignmentLine].line;
IF
NARROW[l.first.shape, AlignmentLine].degrees = degrees AND
Lines2d.LineDistance[point, line] < epsilon
THEN {coincident ← l.first; RETURN}
ENDLOOP;
ENDLOOP;
coincident ← NIL;
};
JointAddCircle:
PUBLIC
PROC [radius:
REAL, point: Point, alignBag: AlignBag]
RETURNS [feature: FeatureData] = {
radius is global. [point, jointNum, traj] describes the joint.
Each circle remembers all of the joints at its center.
circle: Circle;
coincident: FeatureData;
coincident ← SameCircle[point, radius, alignBag.radiiCircles];
IF coincident #
NIL
THEN {
alignmentCircle: AlignmentCircle ← NARROW[coincident.shape];
alignmentCircle.triggerPoints ← CONS[point, alignmentCircle.triggerPoints];
feature ← NIL;
}
ELSE {
alignmentCircle: AlignmentCircle;
circle ← GGCircles.CircleFromPointAndRadius[point, radius];
alignmentCircle ← NEW[AlignmentCircleObj ← [circle: circle, triggerPoints: CONS[point, NIL]]];
feature ← NEW[FeatureDataObj];
feature.type ← radiiCircle;
feature.shape ← alignmentCircle;
AddAlignment[feature, alignBag];
};
};
SameCircle:
PROC [point: Point, radius:
REAL, list:
LIST
OF FeatureData]
RETURNS [coincident: FeatureData] = {
circle: Circle;
FOR l:
LIST
OF FeatureData ← list, l.rest
UNTIL l =
NIL
DO
circle ← NARROW[l.first.shape, AlignmentCircle].circle;
IF
RealFns.AlmostEqual[circle.origin.x, point.x, -10] AND
RealFns.AlmostEqual[circle.origin.y, point.y, -10] AND
RealFns.AlmostEqual[circle.radius, radius, -10]
ENDLOOP;
RETURN[NIL];
};
DistanceFromOrigin:
PROC [pt1: Point, degrees:
REAL]
RETURNS [distance, c, s:
REAL] = {
Exactly mimics Lines2d.FillLineFromPointAndAngle
c ← RealFns.CosDeg[degrees];
s ← RealFns.SinDeg[degrees];
distance ← pt1.y*c - pt1.x*s;
};
SegmentAddTwoAngleLines:
PUBLIC
PROC [degrees:
REAL, segNum:
NAT, lo, hi: Point, alignBag: AlignBag]
RETURNS [line1, line2: FeatureData] = {
loLine: Line ← Lines2d.LineFromPointAndAngle[pt: lo, degrees: Vectors2d.AngleFromVector[[x: hi.x-lo.x, y: hi.y-lo.y]]+degrees];
hiLine: Line ← Lines2d.LineFromPointAndAngle[pt: hi, degrees: Vectors2d.AngleFromVector[[x: lo.x-hi.x, y: lo.y-hi.y]]+degrees];
line1 ← NEW[FeatureDataObj];
line1.type ← angleLine;
line1.shape ← NEW[AlignmentLineObj ← [line: loLine, degrees: degrees, triggerPoints: NIL]];
AddAlignment[line1, alignBag];
line2 ← NEW[FeatureDataObj];
line2.type ← angleLine;
line2.shape ← NEW[AlignmentLineObj ← [line: hiLine, degrees: degrees, triggerPoints: NIL]];
AddAlignment[line2, alignBag];
};
SegmentAddDistanceLines:
PUBLIC
PROC [distance:
REAL, segNum:
NAT, lo, hi: Point, alignBag: AlignBag]
RETURNS [line1, line2: FeatureData] = {
No global information is needed. [segNum, traj] describes the segment.
line, leftLine, rightLine: Line;
line ← Lines2d.LineFromPoints[lo, hi];
leftLine ← Lines2d.LineLeftOfLine[line, distance];
line1 ← NEW[FeatureDataObj];
line1.type ← distanceLine;
line1.shape ← leftLine;
AddAlignment[line1, alignBag];
IF
ABS[distance] > 0.0
THEN {
rightLine ← Lines2d.LineRightOfLine[line, distance];
line2 ← NEW[FeatureDataObj];
line2.type ← distanceLine;
line2.shape ← rightLine;
AddAlignment[line2, alignBag];
}
ELSE line2 ← NIL;
};
Filling all Three Bags at Once
SetStaticBags:
PUBLIC
PROC [ggData: GGData] = {
We have two trigger bags and one object bag to get into shape.
The Trigger Bag should already contain the anchor, and all hot objects. For now, however, we will put these into the bag here. Next, we remove all objects which are going to be moving during the upcoming operation. For selection operations, we remove all triggers.
The Scene Bag will be empty. We fill it with all of the scene objects. Then, we remove those objects which are going to be moving during the upcoming operation.
The Align Bag is made by filtering the Trigger Bag through the currently selected filters.
triggerBag: TriggerBag ← ggData.hitTest.triggerBag;
alignBag: AlignBag ← ggData.hitTest.alignBag;
sceneBag: TriggerBag ← ggData.hitTest.sceneBag;
scene: Scene ← ggData.scene;
anchor: Caret ← ggData.anchor;
heuristics: BOOL ← GGState.GetHeuristics[ggData];
filters: Filters ← ggData.hitTest;
hideAlignments: BOOL ← NOT GGState.GetShowAlignments[ggData];
IF hideAlignments
THEN {
FlushAlignBag[alignBag];
FlushTriggerBag[sceneBag];
FillStaticSceneBag[scene, sceneBag];
}
ELSE {
CodeTimer.StartInt[$SetStaticBags, $Gargoyle];
Fill TriggerBag
FlushTriggerBag[triggerBag];
FillStaticTriggerBag[anchor, scene, heuristics, triggerBag];
Fill Scene Bag
FlushTriggerBag[sceneBag];
FillStaticSceneBag[scene, sceneBag];
Fill Align Bag
FlushAlignBag[alignBag];
FillStaticAlignBag[triggerBag, sceneBag, ggData, hideAlignments, alignBag];
CodeTimer.StopInt[$SetStaticBags, $Gargoyle];
};
};
SetStaticTriggerAndAlignBags:
PUBLIC
PROC [ggData: GGData] = {
triggerBag: TriggerBag ← ggData.hitTest.triggerBag;
alignBag: AlignBag ← ggData.hitTest.alignBag;
scene: Scene ← ggData.scene;
anchor: Caret ← ggData.anchor;
heuristics: BOOL ← GGState.GetHeuristics[ggData];
filters: Filters ← ggData.hitTest;
hideAlignments: BOOL ← NOT GGState.GetShowAlignments[ggData];
IF hideAlignments
THEN {
FlushAlignBag[alignBag];
}
ELSE {
sceneBag: TriggerBag ← ggData.hitTest.sceneBag;
Fill TriggerBag
FlushTriggerBag[triggerBag];
FillStaticTriggerBag[anchor, scene, heuristics, triggerBag];
Fill Align Bag
FlushAlignBag[alignBag];
FillStaticAlignBag[triggerBag, sceneBag, ggData, hideAlignments, alignBag];
};
};
SetDynamicBags:
PUBLIC
PROC [ggData: GGData, action:
ATOM] = {
We have two trigger bags and one object bag to get into shape.
The Trigger Bag should already contain the anchor, and all hot objects. For now, however, we will put these into the bag here. Next, we remove all objects which are going to be moving during the upcoming operation. For selection operations, we remove all triggers.
The Scene Bag will be empty. We fill it with all of the scene objects. Then, we remove those objects which are going to be moving during the upcoming operation.
The Align Bag is made by filtering the Trigger Bag through the currently selected filters.
triggerBag: TriggerBag ← ggData.hitTest.triggerBag;
alignBag: AlignBag ← ggData.hitTest.alignBag;
sceneBag: TriggerBag ← ggData.hitTest.sceneBag;
scene: Scene ← ggData.scene;
anchor: Caret ← ggData.anchor;
heuristics: BOOL ← GGState.GetHeuristics[ggData];
filters: Filters ← ggData.hitTest;
hideAlignments: BOOL ← NOT GGState.GetShowAlignments[ggData];
IF hideAlignments
THEN {
FlushAlignBag[alignBag];
FlushTriggerBag[sceneBag];
FlushTriggerBag[triggerBag]; -- added March 30, 1987. KAP
FillDynamicSceneBag[scene, sceneBag, ggData.drag.editConstraints, ggData.drag.bezierDrag];
}
ELSE {
CodeTimer.StartInt[$SetDynamicBags, $Gargoyle];
Fill TriggerBag
FlushTriggerBag[triggerBag];
FillDynamicTriggerBag[anchor, scene, heuristics, triggerBag, ggData.drag.editConstraints, ggData.drag.bezierDrag];
Fill Scene Bag
FlushTriggerBag[sceneBag];
FillDynamicSceneBag[scene, sceneBag, ggData.drag.editConstraints, ggData.drag.bezierDrag];
Fill Align Bag
FlushAlignBag[alignBag];
FillDynamicAlignBag[triggerBag, sceneBag, ggData, hideAlignments, action, alignBag];
CodeTimer.StopInt[$SetDynamicBags, $Gargoyle];
};
};
StaticToDynamicBags:
PUBLIC
PROC [ggData: GGData, saveForeground:
BOOL ←
TRUE] = {
We are about to drag things around. Here is what that means for each of the bags:
Trigger Bag: AddHeuristics may add some things. RemoveMoving may remove some things.
Scene Bag: RemoveMoving will remove some things.
Align Bag: Moving parts will no longer trigger alignment lines.
Foreground plane: If hot objects are moving, remake it from scratch. Otherwise, augment it with any objects generated by the automatic rule.
repaintForeground is TRUE if alignBag is altered.
scene: Scene ← ggData.scene;
bd: BezierDragRecord ← ggData.drag.bezierDrag;
ec: EditConstraints ← ggData.drag.editConstraints;
triggerBag: TriggerBag ← ggData.hitTest.triggerBag;
sceneBag: TriggerBag ← ggData.hitTest.sceneBag;
alignBag: AlignBag ← ggData.hitTest.alignBag;
heuristics: BOOL ← GGState.GetHeuristics[ggData];
hideAlignments: BOOL ← NOT GGState.GetShowAlignments[ggData];
hotMoving: BOOL ← SomeSelectedIsHot[scene];
IF ggData.camera.hideAlignments
THEN {
-- No alignments. Just build sceneBag.
FlushAlignBag[alignBag];
CopyTriggerBag[to: ggData.hitTest.oldTriggerBag, from: triggerBag];
CopyTriggerBag[to: ggData.hitTest.oldSceneBag, from: sceneBag];
[] ← RemoveMoving[scene, sceneBag, ec, bd];
}
ELSE {
autoAdded: BOOL ← FALSE;
autoTriggers: LIST OF SliceDescriptor ← NIL;
filterLists: FilterLists ← GGState.GetFilterLists[ggData];
someAlignmentsActive: BOOL ← NOT EmptyFilterLists[filterLists];
CodeTimer.StartInt[$StaticToDynamicBags, $Gargoyle];
Incrementally Update TriggerBag
IF someAlignmentsActive
THEN {
CopyTriggerBag[to: ggData.hitTest.oldTriggerBag, from: triggerBag];
[] ← RemoveMoving[scene, triggerBag, ec, bd];
[autoAdded, autoTriggers] ← AddHeuristics[scene, heuristics, $Drag, triggerBag, ec, bd];
ggData.hitTest.oldTriggerBagOK ← TRUE;
}
ELSE ggData.hitTest.oldTriggerBagOK ← FALSE;
Incrementally Update SceneBag
CopyTriggerBag[to: ggData.hitTest.oldSceneBag, from: sceneBag];
[] ← RemoveMoving[scene, sceneBag, ec, bd];
Incrementally Update AlignBag
IF autoAdded
OR hotMoving
THEN {
ggData.hitTest.alignBagIsOldAlignBag ← FALSE;
IF hotMoving
THEN {
-- all is lost
ggData.hitTest.oldAlignBagOK ← FALSE;
FlushAlignBag[alignBag];
BuiltInFilters[triggerBag, ggData, hideAlignments, alignBag];
GGRefresh.UpdateForeground[ggData, TRUE];
}
ELSE {
-- no hot parts are moving
alignObjects: LIST OF FeatureData;
ggData.hitTest.oldAlignBagOK ← TRUE;
CopyAlignBag[to: ggData.hitTest.oldAlignBag, from: alignBag];
FOR list:
LIST
OF SliceDescriptor ← autoTriggers, list.rest
UNTIL list =
NIL
DO
alignObjects ← GGUtility.FeatureDataNconc[IncrementalFilterSlice[list.first, filterLists, hideAlignments, alignBag], alignObjects];
ENDLOOP;
IF saveForeground
THEN {
GGRefresh.SaveForeground[ggData];
SaveLineTable[ggData];
};
GGRefresh.NoteNewForeground[ggData, alignObjects];
};
}
ELSE {
-- leave the Align Bag alone. Don't bother saving foreground plane.
ggData.hitTest.alignBagIsOldAlignBag ← TRUE;
ggData.hitTest.oldAlignBagOK ← FALSE;
};
CodeTimer.StopInt[$StaticToDynamicBags, $Gargoyle];
};
};
DynamicToStaticBags:
PUBLIC
PROC [ggData: GGData, restoreForeground:
BOOL ←
TRUE] = {
alignBag: AlignBag ← ggData.hitTest.alignBag;
filters: Filters ← ggData.hitTest;
hideAlignments: BOOL ← NOT GGState.GetShowAlignments[ggData];
CodeTimer.StartInt[$DynamicToStaticBags, $Gargoyle];
Use the old TriggerBag and SceneBag.
IF ggData.hitTest.oldTriggerBagOK THEN SwapOldAndNewTriggerBags[ggData];
SwapOldAndNewSceneBags[ggData];
Restore AlignBag
IF ggData.hitTest.alignBagIsOldAlignBag THEN {} -- already restored
ELSE {
IF ggData.hitTest.oldAlignBagOK
THEN {
-- no hot were moving
SwapOldAndNewAlignBags[ggData];
IF restoreForeground
THEN {
GGRefresh.RestoreForeground[ggData];
RestoreLineTable[ggData];
};
}
ELSE {
-- hot were moving. Just remake the alignBag
FlushAlignBag[alignBag];
BuiltInFilters[ggData.hitTest.triggerBag, ggData, hideAlignments, alignBag];
GGRefresh.UpdateForeground[ggData, TRUE];
};
};
CodeTimer.StopInt[$DynamicToStaticBags, $Gargoyle];
};
SwapOldAndNewAlignBags:
PROC [ggData: GGData] = {
tempBag: AlignBag;
tempBag ← ggData.hitTest.alignBag;
ggData.hitTest.alignBag ← ggData.hitTest.oldAlignBag;
ggData.hitTest.oldAlignBag ← tempBag;
};
SwapOldAndNewTriggerBags:
PROC [ggData: GGData] = {
tempBag: TriggerBag;
tempBag ← ggData.hitTest.triggerBag;
ggData.hitTest.triggerBag ← ggData.hitTest.oldTriggerBag;
ggData.hitTest.oldTriggerBag ← tempBag;
};
SwapOldAndNewSceneBags:
PROC [ggData: GGData] = {
tempBag: TriggerBag;
tempBag ← ggData.hitTest.sceneBag;
ggData.hitTest.sceneBag ← ggData.hitTest.oldSceneBag;
ggData.hitTest.oldSceneBag ← tempBag;
};
UpdateBagsForAdd:
PUBLIC
PROC [oldAncestor: Slice, newAncestorD: SliceDescriptor, trajEnd: TrajEnd, ggData: GGData]
RETURNS [repaintForeground:
BOOL ←
FALSE] = {
Looks like repaintForeground isn't used currently.
This routine takes that bags from one static state to another static state, where the difference is that a line segment has been added.
The bags should be updated as follows:
Trigger: Remove old outline descriptor. Add new hotness descriptor for newAncestor.
Align: Remove mentions of old outline. Use filters to add new mentions.
Scene: Remove old outline descriptor. Add a complete descriptor for newAncestor.
triggerBag: TriggerBag ← ggData.hitTest.triggerBag;
sceneBag: TriggerBag ← ggData.hitTest.sceneBag;
alignBag: AlignBag ← ggData.hitTest.alignBag;
scene: Scene ← ggData.scene;
newHot, oldHot, newWhole, oldWhole, hotMinusOldHot: SliceDescriptor;
hideAlignments: BOOL ← NOT GGState.GetShowAlignments[ggData];
alignObjects: LIST OF FeatureData;
Fixing the TriggerBag.
oldHot ← RemoveEntireHotSlice[oldAncestor, triggerBag];
newHot ← GGSelect.FindSelectedSlice[newAncestorD.slice, hot];
We can ignore Heuristics because there is no motion.
We don't need to remove moving parts because there aren't any.
IF newHot # NIL THEN [] ← AddSliceFeature[newHot, triggerBag];
Fixing the SceneBag.
oldWhole ← RemoveEntireHotSlice[oldAncestor, sceneBag];
newWhole ← GGSliceOps.NewParts[newAncestorD.slice, NIL, slice];
[] ← AddSliceFeature[newWhole, sceneBag];
Fixing the AlignBag.
We assume that the bags were set up static before this. Hence, there are two simple possibilities. Either the newly added segment is hot, or it isn't.
IF newHot = NIL THEN hotMinusOldHot ← NIL
ELSE {
maskD: SliceDescriptor; -- for a traj
outD: SliceDescriptor; -- for the corresponding outline
traj: Traj ← GGParent.FirstIncludedChild[newAncestorD.slice, newAncestorD.parts, leaf, $Traj].slice;
trajData: TrajData ← NARROW[traj.data];
filterLists: FilterLists ← GGState.GetFilterLists[ggData];
IF trajEnd = lo THEN maskD ← GGSequence.Union[GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromSegment[trajData, 0]], GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromJoint[trajData, 0]]]
ELSE maskD ← GGSequence.Union[GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromSegment[trajData, GGTraj.HiSegment[traj]]], GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromJoint[trajData, GGTraj.HiJoint[traj]]]];
outD ← GGParent.TopLevelDescriptorFromChildDescriptor[maskD];
hotMinusOldHot ← GGSliceOps.DifferenceParts[newHot, GGSliceOps.DifferenceParts[newWhole, outD]];
alignObjects ← IncrementalFilterSlice[hotMinusOldHot, filterLists, hideAlignments, alignBag];
};
};
UpdateBagsForNewSlices:
PUBLIC
PROC [newSlices:
LIST
OF Slice, ggData: GGData] = {
Assuming that the new slices cannot be hot, the bags should be updated as follows:
Trigger: No change.
Scene: Add a complete descriptor for each new slice.
triggerBag: TriggerBag ← ggData.hitTest.triggerBag;
sceneBag: TriggerBag ← ggData.hitTest.sceneBag;
alignBag: AlignBag ← ggData.hitTest.alignBag;
scene: Scene ← ggData.scene;
Fixing the SceneBag
FOR list:
LIST
OF Slice ← newSlices, list.rest
UNTIL list =
NIL
DO
firstSlice: Slice ← list.first;
wholeD: SliceDescriptor ← GGSliceOps.NewParts[firstSlice, NIL, slice];
[] ← AddSliceFeature[wholeD, sceneBag];
ENDLOOP;
};
The Filter Routines shown in the figure as boxes and ovals
AddAnchorTrigger:
PROC [anchor: Caret, triggerBag: TriggerBag] = {
[] ← CreateAnchorTrigger[anchor, triggerBag];
};
AddAllHotSlices:
PROC [scene: Scene, triggerBag: TriggerBag] = {
feature: FeatureData;
AddHotToTriggerBag:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ←
FALSE] = {
feature ← FeatureFromSlice[sliceD.slice, sliceD.parts];
AddFeature[feature, triggerBag];
};
[] ← GGScene.WalkSelectedSlices[scene, first, AddHotToTriggerBag, hot];
};
AddHeuristics:
PROC [scene: Scene, heuristics:
BOOL, atom:
ATOM, triggerBag: TriggerBag, ec: EditConstraints, bd: BezierDragRecord]
RETURNS [autoAdded:
BOOL ←
FALSE, autoTriggers:
LIST
OF SliceDescriptor ←
NIL] = {
feature: FeatureData;
IF NOT heuristics THEN RETURN;
SELECT atom
FROM
$Drag => {
AddToTriggerBag:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ←
FALSE] = {
stationaryD: SliceDescriptor;
oldFeature: FeatureData;
IF GGSliceOps.IsCompleteParts[sliceD] THEN RETURN; -- the whole thing is moving
stationaryD ← StationaryParts[sliceD, scene, ec, bd];
IF GGSliceOps.IsEmptyParts[stationaryD] THEN RETURN; -- the whole thing is moving
oldFeature ← FindFeature[triggerBag, stationaryD.slice];
IF oldFeature #
NIL
THEN {
hotD: SliceDescriptor ← NARROW[oldFeature.shape];
newlyHotD: SliceDescriptor ← GGSliceOps.DifferenceParts[stationaryD, hotD];
totalD: SliceDescriptor;
IF
NOT GGSliceOps.IsEmptyParts[newlyHotD]
THEN {
totalD ← GGSliceOps.UnionParts[stationaryD, hotD];
feature ← FeatureFromSlice[totalD.slice, totalD.parts];
ReplaceFeature[triggerBag, oldFeature, feature];
autoTriggers ← CONS[newlyHotD, autoTriggers];
autoAdded ← TRUE;
};
}
ELSE {
feature ← FeatureFromSlice[stationaryD.slice, stationaryD.parts];
AddFeature[feature, triggerBag];
autoTriggers ← CONS[stationaryD, autoTriggers];
autoAdded ← TRUE;
};
};
[] ← GGScene.WalkSelectedSlices[scene, first, AddToTriggerBag, normal];
};
$CaretPos => NULL;
ENDCASE => ERROR UnexpectedType;
};
StationaryParts:
PROC [selectedD: SliceDescriptor, scene: Scene, ec: EditConstraints, bd: BezierDragRecord]
RETURNS [stationaryD: SliceDescriptor] = {
background, overlay, rubber, drag, move, totalD: SliceDescriptor;
totalD ← GGSliceOps.NewParts[selectedD.slice, NIL, slice];
[background, overlay, rubber, drag] ← GGSliceOps.MovingParts[selectedD.slice, selectedD.parts, ec, bd];
move ← GGSliceOps.UnionParts[rubber, drag];
IF move = NIL THEN RETURN[totalD];
stationaryD ← GGSliceOps.DifferenceParts[totalD, move];
};
StationaryTriggerParts:
PUBLIC
PROC [hotD, selSliceD: SliceDescriptor, editConstraints: EditConstraints, bezierDrag: BezierDragRecord]
RETURNS [stationary: SliceDescriptor, someRemoved:
BOOL ←
FALSE] = {
background, overlay, rubber, drag, move: SliceDescriptor;
IF selSliceD = NIL THEN RETURN[hotD]; -- clearly nothing is moving
[background, overlay, rubber, drag] ← GGSliceOps.MovingParts[selSliceD.slice, selSliceD.parts, editConstraints, bezierDrag];
move ← GGSliceOps.UnionParts[rubber, drag];
IF move = NIL THEN RETURN[hotD, FALSE];
stationary ← GGSliceOps.DifferenceParts[hotD, move];
IF stationary # hotD THEN someRemoved ← TRUE;
};
RemoveMoving:
PROC [scene: Scene, bag: TriggerBag, ec: EditConstraints, bd: BezierDragRecord]
RETURNS [someRemoved:
BOOL ←
FALSE] = {
RemoveMovingSlice should return those parts which should be included in the new bag.
For each moving slice, see if it is in the bag. If so, replace it by its moving parts. "bag" may be either the Scene Bag or the Trigger Bag.
thisRemoved: BOOL ← FALSE;
DoRemoveMoving:
PROC [selectedD: SliceDescriptor]
RETURNS [done:
BOOL ←
FALSE] = {
hotSliceD, stationaryD: SliceDescriptor;
oldFeature: FeatureData;
oldFeature ← FindFeature[bag, selectedD.slice];
IF oldFeature = NIL THEN RETURN;
hotSliceD ← NARROW[oldFeature.shape];
[stationaryD, thisRemoved] ← StationaryTriggerParts[hotSliceD, selectedD, ec, bd];
IF thisRemoved
THEN {
-- nothing was removed
newFeature: FeatureData;
someRemoved ← TRUE;
IF
NOT GGSliceOps.IsEmptyParts[stationaryD]
THEN {
newFeature ← FeatureFromSlice[stationaryD.slice, stationaryD.parts];
ReplaceFeature[bag, oldFeature, newFeature];
}
ELSE DeleteFeature[bag, oldFeature];
};
};
[] ← GGScene.WalkSelectedSlices[scene, first, DoRemoveMoving, normal];
};
FilterLists: TYPE = REF FilterListsObj;
FilterListsObj:
TYPE = GGState.FilterListsObj;
EmptyFilterLists:
PROC [filterLists: FilterLists]
RETURNS [
BOOL] = {
RETURN[filterLists.onSlopes=NIL AND filterLists.onRadii = NIL AND filterLists.onAngles=NIL AND filterLists.onDistances = NIL];
};
FilterListsFromFilter:
PROC [value:
REAL, filterType: FilterType]
RETURNS [filterLists: FilterLists] = {
filterLists ← NEW[FilterListsObj ← [NIL, NIL, NIL, NIL]];
SELECT filterType
FROM
slope => filterLists.onSlopes ← LIST[value];
radius => filterLists.onRadii ← LIST[value];
angle => filterLists.onAngles ← LIST[value];
distance => filterLists.onDistances ← LIST[value];
ENDCASE => ERROR;
};
BuiltInFilters:
PUBLIC
PROC [triggerBag: TriggerBag, ggData: GGData, hideAlignments:
BOOL, alignBag: AlignBag] = {
Add Alignment Objects to the alignBag, using the triggers in the triggerBag.
filterLists: FilterLists;
anchor: Caret;
anchorFeature: FeatureData;
CodeTimer.StartInt[$BuiltInFilters, $Gargoyle];
anchorFeature ← triggerBag.anchor;
IF hideAlignments
THEN {
FlushAlignBag[alignBag];
IF anchorFeature#NIL AND GGCaret.Exists[(anchor ← NARROW[anchorFeature.shape, Caret])] THEN AddAnchorObject[anchorFeature, alignBag];
RETURN;
};
filterLists ← GGState.GetFilterLists[ggData]; -- filterLists are faster to loop over than filters
IF EmptyFilterLists[filterLists]
THEN {
The anchor as the only trigger.
IF anchorFeature#NIL AND GGCaret.Exists[(anchor ← NARROW[anchorFeature.shape, Caret])] THEN AddAnchorObject[anchorFeature, alignBag];
}
ELSE {
DoPointFireRule: PointWalkProc = {
PointWalkProc: TYPE = PROC [point: Point] RETURNS [done: BOOL ← FALSE];
[] ← PointFireRule[point, filterLists, alignBag];
};
DoForSlice: FeatureWalkProc = {
FeatureWalkProc: PROC [feature: FeatureData] RETURNS [done: BOOL ← FALSE];
sliceD ← NARROW[feature.shape];
GGSliceOps.WalkPointsInDescriptor[sliceD, DoPointFireRule];
pointPairGen ← GGSliceOps.PointPairsInDescriptor[sliceD];
FOR next: PointPairAndDone ← GGSliceOps.NextPointPair[sliceD.slice, pointPairGen], GGSliceOps.NextPointPair[sliceD.slice, pointPairGen]
UNTIL next.done
DO
[] ← SegmentFireRule[9999, next.lo, next.hi, filterLists, alignBag];
ENDLOOP;
};
sliceD: SliceDescriptor;
pointPairGen: PointPairGenerator;
The anchor as a trigger.
IF anchorFeature#
NIL
THEN {
anchor ← NARROW[anchorFeature.shape, Caret];
IF GGCaret.Exists[anchor]
THEN {
AddAnchorObject[anchorFeature, alignBag];
[] ← AnchorFireRule[anchor, filterLists, alignBag];
};
};
The slices as triggers.
WalkSliceTriggers[triggerBag, DoForSlice];
};
CodeTimer.StopInt[$BuiltInFilters, $Gargoyle];
};
Incremental Addition versions of the Filter Routines shown in the figure as boxes and ovals.
CreateAnchorTrigger:
PUBLIC
PROC [anchor: Caret, triggerBag: TriggerBag]
RETURNS [feature: FeatureData] = {
feature ← FeatureFromAnchor[anchor];
AddFeature[feature, triggerBag];
};
AddSliceFeature:
PUBLIC
PROC [sliceD: SliceDescriptor, triggerBag: TriggerBag]
RETURNS [newFeature: FeatureData] = {
oldD, unionD: SliceDescriptor;
oldFeature: FeatureData;
[oldD, oldFeature] ← FindOldSlice[sliceD.slice, triggerBag.slices];
IF oldD =
NIL
THEN {
newFeature ← FeatureFromSlice[sliceD.slice, sliceD.parts];
AppendFeature[newFeature, triggerBag.slices];
}
ELSE {
unionD ← GGSliceOps.UnionParts[oldD, sliceD];
newFeature ← FeatureFromSlice[sliceD.slice, unionD.parts];
ReplaceFeature[triggerBag, oldFeature, newFeature];
};
};
IncrementalFilterSlice:
PROC [sliceD: SliceDescriptor, filterLists: FilterLists, hideAlignments:
BOOL, alignBag: AlignBag]
RETURNS [alignObjects:
LIST
OF FeatureData] = {
SplineInPointFeatures: PointWalkProc = {
PointWalkProc: TYPE = PROC [point: Point] RETURNS [done: BOOL ← FALSE];
alignObjects ←
GGUtility.FeatureDataNconc[PointFireRule[point, filterLists, alignBag], alignObjects];
};
IF filterLists.onSlopes #
NIL
OR filterLists.onRadii #
NIL
THEN
GGSliceOps.WalkPointsInDescriptor[sliceD, SplineInPointFeatures];
IF filterLists.onAngles #
NIL
OR filterLists.onDistances #
NIL
THEN {
pointPairGen: PointPairGenerator ← GGSliceOps.PointPairsInDescriptor[sliceD];
FOR next: PointPairAndDone ← GGSliceOps.NextPointPair[sliceD.slice, pointPairGen], GGSliceOps.NextPointPair[sliceD.slice, pointPairGen]
UNTIL next.done
DO
alignObjects ← GGUtility.FeatureDataNconc[SegmentFireRule[9999, next.lo, next.hi, filterLists, alignBag], alignObjects];
ENDLOOP;
};
};
IncrementalNewTrigger:
PUBLIC
PROC [trigger: FeatureData, filterLists: FilterLists, hideAlignments:
BOOL, alignBag: AlignBag]
RETURNS [alignObjects:
LIST
OF FeatureData] = {
A single new trigger has been added to the triggerBag. Add to the alignBag, all alignment objects generated by that trigger. Returns a list of the new alignment objects that were generated.
alignObjects ← NIL;
IF hideAlignments
THEN {
FlushAlignBag[alignBag];
}
ELSE {
WITH trigger.shape
SELECT
FROM
sliceD: SliceDescriptor => {
alignObjects ← IncrementalFilterSlice[sliceD, filterLists, hideAlignments, alignBag];
};
anchor: Caret => {
The anchor as a trigger.
IF GGCaret.Exists[anchor]
THEN {
point: Point ← GGCaret.GetPoint[anchor];
[] ← PointFireRule[point, filterLists, alignBag];
};
};
ENDCASE => ERROR;
};
};
IncrementalNewFilter:
PUBLIC
PROC [value:
REAL, filterType: FilterType, triggerBag: TriggerBag, hideAlignments:
BOOL, alignBag: AlignBag]
RETURNS [alignObjects:
LIST
OF FeatureData ←
NIL] = {
A new alignment value has been activated. Create new alignObjects and add them to the alignBag.
filterLists: FilterLists ← FilterListsFromFilter[value, filterType];
DoPointFireRule: PointWalkProc = {
PointWalkProc: TYPE = PROC [point: Point] RETURNS [done: BOOL ← FALSE];
alignObjects ← GGUtility.FeatureDataNconc[PointFireRule[point, filterLists, alignBag], alignObjects];
};
DoForSlice: FeatureWalkProc = {
FeatureWalkProc: PROC [feature: FeatureData] RETURNS [done: BOOL ← FALSE];
sliceD ← NARROW[feature.shape];
SELECT filterType
FROM
slope, radius => {
GGSliceOps.WalkPointsInDescriptor[sliceD, DoPointFireRule];
};
angle, distance => {
pointPairGen ← GGSliceOps.PointPairsInDescriptor[sliceD];
FOR next: PointPairAndDone ← GGSliceOps.NextPointPair[sliceD.slice, pointPairGen], GGSliceOps.NextPointPair[sliceD.slice, pointPairGen]
UNTIL next.done
DO
alignObjects ← GGUtility.FeatureDataNconc[SegmentFireRule[9999, next.lo, next.hi, filterLists, alignBag], alignObjects];
ENDLOOP;
};
ENDCASE => ERROR;
};
sliceD: SliceDescriptor;
pointPairGen: PointPairGenerator;
anchor: Caret;
anchorFeature: FeatureData;
IF hideAlignments THEN RETURN;
The anchor as a trigger.
anchorFeature ← triggerBag.anchor;
IF anchorFeature #
NIL
THEN {
anchor ← NARROW[anchorFeature.shape, Caret];
IF GGCaret.Exists[anchor]
THEN {
AddAnchorObject[anchorFeature, alignBag];
alignObjects ← GGUtility.FeatureDataNconc[AnchorFireRule[anchor, filterLists, alignBag], alignObjects];
};
};
The slices as triggers.
WalkSliceTriggers[triggerBag, DoForSlice];
};
Incremental Deletion versions of the Filter Routines shown in the figure as boxes and ovals.
RemoveAnchorTrigger:
PUBLIC
PROC [triggerBag: TriggerBag] = {
triggerBag.anchor ← NIL;
};
RemoveEntireHotSlice:
PUBLIC
PROC [slice: Slice, triggerBag: TriggerBag]
RETURNS [oldSliceD: SliceDescriptor]= {
oldFeature: FeatureData;
[oldSliceD, oldFeature] ← FindOldSlice[slice, triggerBag.slices];
IF oldSliceD#
NIL
THEN {
DeleteFeature[triggerBag, oldFeature];
};
};
Other Routines
In support of building triggerBag and sceneBag.
AddFeature:
PROC [featureData: FeatureData, triggerBag: TriggerBag] = {
Process.CheckForAbort[];
SELECT featureData.type
FROM
slice => {
sliceD: SliceDescriptor ← NARROW[featureData.shape];
key: RefTab.Key ← sliceD.slice;
val: REF;
found: BOOL ← FALSE;
[found, val] ← RefTab.Fetch[triggerBag.slices, key];
IF
NOT found
THEN {
list: LIST OF FeatureData ← LIST[featureData];
[] ← RefTab.Insert[triggerBag.slices, key, list];
}
ELSE {
list: LIST OF FeatureData ← NARROW[val];
list ← CONS[featureData, list];
[] ← RefTab.Store[triggerBag.slices, key, list];
};
};
anchor => {
triggerBag.anchor ← featureData;
};
ENDCASE => ERROR;
};
In support of building sceneBag.
AddAllSlices:
PROC [scene: Scene, triggerBag: TriggerBag] = {
feature: FeatureData;
DoAddSlice:
PROC [slice: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
feature ← FeatureFromSlice[slice];
AddFeature[feature, triggerBag];
};
[] ← GGScene.WalkSlices[scene, first, DoAddSlice];
};
In support of building alignBag.
AddAnchorObject:
PROC [anchorFeature: FeatureData, alignBag: AlignBag] = {
alignBag.anchor ← anchorFeature;
};
PointFireRule:
PROC [point: Point, filterLists: FilterLists, alignBag: AlignBag]
RETURNS [alignObjects:
LIST
OF FeatureData] = {
feature: FeatureData;
alignObjects ← NIL;
FOR list:
LIST
OF
REAL ←
filterLists.onSlopes, list.rest
UNTIL list =
NIL
DO
feature ←
JointAddSlopeLine[
degrees: list.first,
point: point,
alignBag: alignBag
];
IF feature # NIL THEN alignObjects ← CONS[feature, alignObjects];
ENDLOOP;
FOR list:
LIST
OF
REAL ←
filterLists.onRadii, list.rest
UNTIL list =
NIL
DO
feature ←
JointAddCircle[
radius: list.first*filterLists.scaleUnit,
point: point,
alignBag: alignBag
];
IF feature # NIL THEN alignObjects ← CONS[feature, alignObjects];
ENDLOOP;
};
SegmentFireRule:
PROC [segNum:
NAT, lo, hi: Point, filterLists: FilterLists, alignBag: AlignBag]
RETURNS [alignObjects:
LIST
OF FeatureData] = {
line1, line2: FeatureData;
alignObjects ← NIL;
FOR list:
LIST
OF
REAL ←
filterLists.onDistances, list.rest
UNTIL list =
NIL
DO
[line1, line2] ← SegmentAddDistanceLines[distance: list.first*filterLists.scaleUnit, segNum: segNum, lo: lo, hi: hi, alignBag: alignBag];
IF line1 # NIL THEN alignObjects ← CONS[line1, alignObjects];
IF line2 # NIL THEN alignObjects ← CONS[line2, alignObjects];
ENDLOOP;
FOR list:
LIST
OF
REAL ←
filterLists.onAngles, list.rest
UNTIL list =
NIL
DO
[line1, line2] ← SegmentAddTwoAngleLines[degrees: list.first, segNum: segNum, lo: lo, hi: hi, alignBag: alignBag];
IF line1 # NIL THEN alignObjects ← CONS[line1, alignObjects];
IF line2 # NIL THEN alignObjects ← CONS[line2, alignObjects];
ENDLOOP;
};
AnchorFireRule:
PROC [anchor: Caret, filterLists: FilterLists, alignBag: AlignBag]
RETURNS [alignObjects:
LIST
OF FeatureData] = {
line1, line2: FeatureData;
point: Point ← GGCaret.GetPoint[anchor];
normal: Vector ← GGCaret.GetNormal[anchor];
lo: Point ← [point.x+normal.y, point.y-normal.x];
hi: Point ← [point.x-normal.y, point.y+normal.x];
alignObjects ← PointFireRule[point, filterLists, alignBag];
FOR list:
LIST
OF
REAL ←
filterLists.onDistances, list.rest
UNTIL list =
NIL
DO
[line1, line2] ← SegmentAddDistanceLines[distance: list.first*filterLists.scaleUnit, segNum: 9999, lo: lo, hi: hi, alignBag: alignBag];
IF line1 # NIL THEN alignObjects ← CONS[line1, alignObjects];
IF line2 # NIL THEN alignObjects ← CONS[line2, alignObjects];
ENDLOOP;
FOR list:
LIST
OF
REAL ←
filterLists.onAngles, list.rest
UNTIL list =
NIL
DO
degrees: REAL ← list.first;
loLine: Line ← Lines2d.LineFromPointAndAngle[pt: point, degrees: Vectors2d.AngleFromVector[normal]+90+degrees]; -- angle normal to anchor normal
line1 ← NEW[FeatureDataObj];
line1.type ← angleLine;
line1.shape ← NEW[AlignmentLineObj ← [line: loLine, degrees: degrees, triggerPoints: NIL]];
AddAlignment[line1, alignBag];
IF line1 # NIL THEN alignObjects ← CONS[line1, alignObjects];
ENDLOOP;
};
In support of building all three bags.
SomeSelectedIsHot:
PROC [scene: Scene]
RETURNS [hotFound:
BOOL ←
FALSE] = {
FindHot:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ←
FALSE] = {
IF GGSelect.IsSelectedInPart[sliceD.slice, scene,
hot]
THEN {
hotFound ← TRUE;
done ← TRUE;
};
};
[] ← GGScene.WalkSelectedSlices[scene, first, FindHot, normal];
};
FindOldSlice:
PROC [slice: Slice, sliceTriggers: RefTab.Ref]
RETURNS [oldSliceD: SliceDescriptor, oldFeature: FeatureData] = {
key: REF ← slice;
val: REF;
found: BOOL ← FALSE;
[found, val] ← RefTab.Fetch[sliceTriggers, key];
IF NOT found OR val = NIL THEN RETURN[NIL, NIL];
FOR list:
LIST
OF FeatureData ←
NARROW[val], list.rest
UNTIL list =
NIL
DO
oldFeature ← list.first;
oldSliceD ← NARROW[oldFeature.shape];
IF oldSliceD.slice = slice THEN RETURN;
ENDLOOP;
RETURN [NIL, NIL];
};
RemoveFeatureFromList:
PUBLIC
PROC [ref: FeatureData, list:
LIST
OF FeatureData]
RETURNS [val:
LIST
OF FeatureData ←
NIL] = {
z: LIST OF FeatureData ← NIL;
UNTIL list =
NIL
DO
IF list.first#ref
THEN
{IF val = NIL THEN {val ← CONS[list.first, NIL]; z ← val}
ELSE {z.rest ← CONS[list.first, z.rest]; z ← z.rest}};
list ← list.rest;
ENDLOOP;
}; -- of RemoveFeatureFromList
Drawing Alignment Objects
alignmentColor: Imager.Color ← ImagerBackdoor.MakeStipple[145065B];
checkerColor: Imager.Color ← ImagerBackdoor.MakeStipple[122645B];
alignmentColor: Imager.Color ← Imager.black; -- doing this buys less than 10% in performance
checkerColor: Imager.Color ← Imager.black;
CreateLineTable:
PUBLIC
PROC [ggData: GGData] = {
ggData.refresh.lineCache ← CardTab.Create[mod: 211];
ggData.refresh.savedLineTable ← CardTab.Create[mod: 211];
};
LineIsPresent:
PROC [ggData: GGData, line: Line]
RETURNS [present:
BOOL ←
FALSE] = {
LineCompare: PROC [argument: FunctionCache.Domain] RETURNS [good: BOOL] = {
thisLine: Line ← NARROW[argument];
RETURN[thisLine.d = line.d AND thisLine.theta = line.theta];
};
[----, present] ← FunctionCache.Lookup[ggData.refresh.lineCache, LineCompare];
key: CARD ← CardFromLine[line];
found: BOOL ← FALSE;
val: REF;
list: LIST OF Line;
[found, val] ← CardTab.Fetch[ggData.refresh.lineCache, key];
IF NOT found THEN RETURN[FALSE];
list ← NARROW[val];
FOR l:
LIST
OF Line ← list, l.rest
UNTIL l =
NIL
DO
IF l.first.d = line.d AND l.first.theta = line.theta THEN RETURN[TRUE];
ENDLOOP;
present ← FALSE;
};
Use these as the arguments to TableStatistics depending on the type of information desired:
ggData.refresh.lineCache
ggData.hitTest.alignBag.slopeLineTable
TableStatistics:
PROC [table: CardTab.Ref]
RETURNS [count
, min, max:
CARD, avg:
REAL] = {
Count: CardTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
list: LIST OF Line ← NARROW[val];
itemsInBucket: CARD ← 0;
count ← count + 1;
FOR l:
LIST
OF Line ← list, l.rest
UNTIL l =
NIL
DO
itemsInBucket ← itemsInBucket + 1;
ENDLOOP;
min ← MIN[min, itemsInBucket];
max ← MAX[max, itemsInBucket];
total ← total + itemsInBucket;
};
total: REAL ← 0.0;
count ← 0;
min ← LAST[CARD];
max ← 0;
[] ← CardTab.Pairs[table, Count];
avg ← total/count;
};
CopyLineList:
PUBLIC
PROC [l:
LIST
OF Line]
RETURNS [copyList:
LIST
OF Line] = {
z: LIST OF Line ← NIL;
IF l = NIL THEN RETURN[NIL];
copyList ← CONS[l.first, NIL];
z ← copyList;
UNTIL (l ← l.rest) =
NIL
DO
z.rest ← CONS[l.first, NIL];
z ← z.rest;
ENDLOOP;
};
SaveLineTable:
PROC [ggData: GGData] = {
CopyLines: CardTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOL ← FALSE];
inserted: BOOL ← CardTab.Insert[ggData.refresh.savedLineTable, key, val];
IF NOT inserted THEN ERROR;
};
CardTab.Erase[ggData.refresh.savedLineTable];
[] ← CardTab.Pairs[ggData.refresh.lineCache, CopyLines];
};
RestoreLineTable:
PROC [ggData: GGData] = {
temp: CardTab.Ref ← ggData.refresh.savedLineTable;
ggData.refresh.savedLineTable ← ggData.refresh.lineCache;
ggData.refresh.lineCache ← temp;
};
FlushLineTable:
PUBLIC
PROC [ggData: GGData] = {
FunctionCache.Flush[ggData.refresh.lineCache];
CardTab.Erase[ggData.refresh.lineCache];
};
CardFromLine:
PROC [line: Line]
RETURNS [key:
CARD] = {
key ← LOOPHOLE[line.d];
};
AddLineToTable:
PROC [ggData: GGData, line: Line] = {
FunctionCache.Insert[ggData.refresh.lineCache, line, NIL, 2];
key: CARD ← CardFromLine[line];
found, newValue: BOOL ← FALSE;
val: REF;
list: LIST OF Line;
[found, val] ← CardTab.Fetch[ggData.refresh.lineCache, key];
IF
NOT found
THEN {
-- empty bucket
list ← LIST[line];
newValue ← CardTab.Store[ggData.refresh.lineCache, key, list];
IF NOT newValue THEN ERROR;
}
ELSE {
-- collision, add to bucket
list ← NARROW[val];
list ← CONS[line, list];
newValue ← CardTab.Store[ggData.refresh.lineCache, key, list];
IF newValue THEN ERROR;
};
};
DrawFeatureList:
PUBLIC
PROC [dc: Imager.Context, alignObjects:
LIST
OF FeatureData, ggData: GGData] = {
DrawLineAux:
PROC [line: Line] = {
-- uses FunctionCache to avoid duplication
IF LineIsPresent[ggData, line] THEN RETURN
ELSE AddLineToTable[ggData, line];
IF
ABS[line.theta] < 1.0
OR
ABS[line.theta - 90.0] < 1.0
THEN {
-- horizontal or vertical
Imager.SetColor[dc, checkerColor];
GGShapes.DrawLine[dc, line, rect, 0.0, zip]; -- 0 width lines go fast
Imager.SetColor[dc, alignmentColor];
}
ELSE GGShapes.DrawLine[dc, line, rect, 0.0, zip]; -- 0 width lines go fast
};
DrawCircleAux:
PROC [circle: Circle] = {
-- uses GGCircleCache
cachedCircle: GGCircleCache.CachedCircle;
IF (cachedCircle ← GGCircleCache.Lookup[ggData.controls.radiusCircleCache, circle.radius])#NIL THEN GGCircleCache.DrawCachedCircle[dc, circle.origin, cachedCircle] -- cache hit
ELSE {
-- cache miss. Attempt a cache entry and retry
GGCircleCache.Insert[ggData.controls.radiusCircleCache, circle.radius];
IF (cachedCircle ← GGCircleCache.Lookup[ggData.controls.radiusCircleCache, circle.radius])#NIL THEN GGCircleCache.DrawCachedCircle[dc, circle.origin, cachedCircle]
ELSE GGShapes.DrawCircle[dc, circle];
};
};
rect: Imager.Rectangle ← GGState.GetViewport[ggData];
zip: Draw2d.Zip;
Imager.SetStrokeWidth[dc, 0.0]; -- to convince Draw2d.GetZip that everything is ok
zip ← Draw2d.GetZip[dc];
Imager.SetStrokeWidth[dc, 1.0];
Imager.SetColor[dc, alignmentColor];
FOR list:
LIST
OF FeatureData ← alignObjects, list.rest
UNTIL list =
NIL
DO
WITH list.first.shape
SELECT
FROM
slopeLine: AlignmentLine => DrawLineAux[slopeLine.line];
angleLine: AlignmentLine => DrawLineAux[angleLine.line];
circle: AlignmentCircle => IF useCache THEN DrawCircleAux[circle.circle] ELSE GGShapes.DrawCircle[dc, circle.circle];
distanceLine: Line => GGShapes.DrawLine[dc, distanceLine, rect];
ENDCASE => ERROR;
ENDLOOP;
Draw2d.ReleaseZip[zip];
};
HasVisibleObjects:
PUBLIC
PROC [alignBag: AlignBag]
RETURNS [
BOOL] = {
RETURN[
NOT (
EmptySlopeLines[alignBag] AND
alignBag.angleLines = NIL AND
alignBag.radiiCircles = NIL AND
alignBag.distanceLines = NIL)];
};
DrawAlignBagRegardless:
PUBLIC
PROC [dc: Imager.Context, alignBag: AlignBag, ggData: GGData] = {
slopeLines: LIST OF FeatureData;
Draws all objects in the object bag regardless of how they have been marked.
IF alignBag = NIL THEN RETURN;
slopeLines ← ListOfSlopeLines[alignBag];
DrawFeatureList[dc, slopeLines, ggData];
DrawFeatureList[dc, alignBag.angleLines, ggData];
DrawFeatureList[dc, alignBag.radiiCircles, ggData];
DrawFeatureList[dc, alignBag.distanceLines, ggData];
};
MakeFiltersGarbage:
PUBLIC
PROC [filters: Filters] = {
Ruthlessly destroy the various filter bags so the Cedar gargbage collector can sweep up.
Wizards only.
UnlinkBag:
PROC [bag: TriggerBag] = {
DoUnLink: FeatureWalkProc = {
WITH feature.shape
SELECT
FROM
sd: SliceDescriptor =>
IF sd.slice#
NIL
THEN {
GGSlice.UnlinkSlice[sd.slice];
sd.slice ← NIL;
};
ENDCASE;
};
IF bag=NIL THEN RETURN;
WalkSliceTriggers[bag, DoUnLink];
};
UnlinkBag[filters.triggerBag];
UnlinkBag[filters.oldTriggerBag];
UnlinkBag[filters.sceneBag];
UnlinkBag[filters.oldSceneBag];
};
Initialization
InitStats: PROC [] = {
interval: CodeTimer.Interval;
interval ← CodeTimer.CreateInterval[$StaticToDynamicBags];
CodeTimer.AddInt[interval, $Gargoyle];
interval ← CodeTimer.CreateInterval[$DynamicToStaticBags];
CodeTimer.AddInt[interval, $Gargoyle];
interval ← CodeTimer.CreateInterval[$SetDynamicBags, NIL];
CodeTimer.AddInt[interval, $Gargoyle];
interval ← CodeTimer.CreateInterval[$SetStaticBags, NIL];
CodeTimer.AddInt[interval, $Gargoyle];
interval ← CodeTimer.CreateInterval[$BuiltInFilters, NIL];
CodeTimer.AddInt[interval, $Gargoyle];
};
Init:
PROC [] = {
emptyTriggerBag ← CreateTriggerBag[];
emptyAlignBag ← CreateAlignBag[];
};
InitStats[];
Init[];
END.