GGAlignImpl.mesa
Created by Eric Bier on June 5, 1985 3:41:39 pm PDT.
Last edited by Bier on January 28, 1987 3:02:03 pm PST.
Pier, October 21, 1986 6:15:54 pm PDT
DIRECTORY
GGAlign, GGBasicTypes, AtomButtons, GGCaret, GGError, GGGravity, GGInterfaceTypes, GGModelTypes, GGObjects, GGOutline, GGSegmentTypes, GGSelect, GGStatistics, GGVector, GList, Process, Rope;
GGAlignImpl: CEDAR PROGRAM
IMPORTS AtomButtons, GGCaret, GGError, GGGravity, GGObjects, GGOutline, GGSelect, GGStatistics, GGVector, GList, Process
EXPORTS GGAlign = BEGIN
AlignmentObject: TYPE = GGModelTypes.AlignmentObject;
Angle: TYPE = GGBasicTypes.Angle;
Caret: TYPE = GGInterfaceTypes.Caret;
Circle: TYPE = GGBasicTypes.Circle;
ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator;
Edge: TYPE = GGBasicTypes.Edge;
EntityGenerator: TYPE = GGObjects.EntityGenerator;
FeatureData: TYPE = REF FeatureDataObj;
FeatureDataObj: TYPE = GGModelTypes.FeatureDataObj;
FilterOutlineProc: TYPE = GGAlign.FilterOutlineProc;
FilterSliceProc: TYPE = GGAlign.FilterSliceProc;
Filters: TYPE = GGInterfaceTypes.Filters;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
Joint: TYPE = GGModelTypes.Joint;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
Line: TYPE = GGBasicTypes.Line;
ObjectBag: TYPE = GGInterfaceTypes.ObjectBag;
Outline: TYPE = GGModelTypes.Outline;
OutlineDescriptor: TYPE = REF OutlineDescriptorObj;
OutlineDescriptorObj: TYPE = GGModelTypes.OutlineDescriptorObj;
Point: TYPE = GGBasicTypes.Point;
PointAndDone: TYPE = GGModelTypes.PointAndDone;
PointFilterProc: TYPE = GGAlign.PointFilterProc;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
ScalarButtonClient: TYPE = AtomButtons.ScalarButtonClient;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentFilterProc: TYPE = GGAlign.SegmentFilterProc;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SelectionClass: TYPE = GGInterfaceTypes.SelectionClass;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceGenerator: TYPE = GGModelTypes.SequenceGenerator;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = REF SliceDescriptorObj;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
SliceParts: TYPE = GGModelTypes.SliceParts;
Traj: TYPE = GGModelTypes.Traj;
TrajGenerator: TYPE = GGObjects.TrajGenerator;
TriggerBag: TYPE = REF TriggerBagObj;
TriggerBagObj: TYPE = GGInterfaceTypes.TriggerBagObj;
Vector: TYPE = GGBasicTypes.Vector;
Problem: SIGNAL [msg: Rope.ROPE] = GGError.Problem;
Computing the Triggers
The set of alignment objects is computed in two steps. First, we determine which joints, control points, segments, and slice points in the scene are current triggers; that is, may trigger the display of one or more alignment objects. We put these in an object bag called the Current Trigger bag. Next we determine the set of alignment objects each object in the Current Trigger bag will trigger. This set is represented by the selected slopes, angles, radii, and distances in the user interface. We filter the Current Trigger bag through these filters to build up an object bag called the Current Object bag. We next build another trigger bag, called the Scene Trigger bag, which contains those scene objects which are gravity active; that is, they can attract the caret. This bag is generated from the set of all scene objects by removing all objects which are moving or partly moving.
Starting today (June 25, 1986) two trigger bags will be maintained. The Current Trigger Bag contains objects which are hot, naturally or by heuristics, but are not moving, including the anchor. The Scene Trigger Bag contains those scene objects which are themselves gravity active, including the anchor. This bag is generated from the set of all scene objects by removing all objects which are moving.
The two trigger bags are recomputed at these times:
Current Triggers: Updated whenever an Add or Drag operation is performed, or whenever anything becomes hot or cold.
Scene Triggers: Updated whenever an Add or Drag operation is performed.
Computing the Objects
In parallel with the above, we also maintain an ObjectBag. The Current Object Bag is the Current Trigger Bag after it has been filtered through all current filters. There is no need for a Scene object bag, since the Scene Trigger Bag itself can be used for this purpose.
The object bag is recomputed at these times:
Current Objects. Updated whenever an Add or Drag operation is performed, whenever anything becomes hot or cold, or whenever the set of current filters changes.
Displaying the Objects
Starting today (June 25, 1986) alignment objects are shown at all times. An option to turn them off will be provided later. Hence the alignment objects must be redrawn whenver the Current Object Bag changes. It is drawn on a special foreground plane, so it need not be redrawn if it hasn't changed.
EntityNotFound: PUBLIC SIGNAL = CODE;
UnexpectedType: PUBLIC ERROR = CODE;
BrokenInvariant: PUBLIC ERROR = CODE;
[Artwork node; type 'ArtworkInterpress on' to command tool]
Compute the Alignment Objects as illustrated.
SetBagsForAction: PUBLIC PROC [gargoyleData: GargoyleData, atom: ATOM] = {
We have two trigger bags and one object bag to get into shape.
The Current 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 Current Object Bag is made by filtering the Current Trigger Bag through the currently selected filters.
The Scene Triggers 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.
currentTriggers: TriggerBag ← gargoyleData.hitTest.currentTriggerBag;
currentObjects: ObjectBag ← gargoyleData.hitTest.currentObjectBag;
sceneTriggers: TriggerBag ← gargoyleData.hitTest.sceneTriggerBag;
IF gargoyleData.camera.hideAlignments THEN {
GGGravity.FlushObjectBag[currentObjects];
SetSceneBagForAction[gargoyleData, atom];
}
ELSE {
GGStatistics.StartInterval[$SetBagsForAction, GGStatistics.GlobalTable[]];
Steps (1) and (2) Build the Current Trigger Bag
FlushTriggerBag[currentTriggers];
AddAllHotSequences[gargoyleData, currentTriggers];
AddAllHotSlices[gargoyleData, currentTriggers];
AddAnchorTrigger[gargoyleData, currentTriggers];
AddHeuristics[gargoyleData, atom, currentTriggers];
Step (3) Eliminate MOVING Objects
RemoveMoving[gargoyleData.scene, atom, currentTriggers];
Step (4) Compute the Alignment Objects
GGGravity.FlushObjectBag[currentObjects];
BuiltInFilters[currentTriggers, currentObjects, gargoyleData];
Steps (6) and (7) Compute the Gravity Active Scene Objects
SetSceneBagForAction[gargoyleData, atom];
AddAllMidpoints[sceneTriggers, currentObjects, gargoyleData]; -- make all stationary segment midpoints into alignment objects
GGStatistics.StopInterval[$SetBagsForAction, GGStatistics.GlobalTable[]];
};
};
[Artwork node; type 'ArtworkInterpress on' to command tool]
SetSceneBagForAction: PUBLIC PROC [gargoyleData: GargoyleData, atom: ATOM] = {
sceneTriggers: TriggerBag ← gargoyleData.hitTest.sceneTriggerBag;
Compute the Gravity Active Scene Objects
FlushTriggerBag[sceneTriggers];
AddAllTrajectories[gargoyleData, sceneTriggers];
AddAllSlices[gargoyleData, sceneTriggers];
AddAnchor[gargoyleData, sceneTriggers];
SELECT atom FROM
$CaretPos => NULL; -- nothing is about to move
$Drag => RemoveMoving[gargoyleData.scene, $Drag, sceneTriggers];
ENDCASE => ERROR;
ComputeAllBoundingBoxes[sceneTriggers];
};
SomeSelectedIsHot: PROC [scene: Scene] RETURNS [BOOL] = {
entityGen: EntityGenerator;
entityGen ← GGSelect.SelectedStuff[scene, normal];
FOR entity: REF ANY ← GGObjects.NextEntity[entityGen], GGObjects.NextEntity[entityGen] UNTIL entity = NIL DO
WITH entity SELECT FROM
sliceD: SliceDescriptor => IF GGSelect.IsSelectedInPart[sliceD.slice, scene, hot] THEN RETURN[TRUE];
outlineD: OutlineDescriptor => IF GGSelect.IsSelectedInPart[outlineD.slice, scene, hot] THEN RETURN[TRUE];
ENDCASE => ERROR;
ENDLOOP;
RETURN[FALSE];
};
UpdateBagsForAction: PUBLIC PROC [gargoyleData: GargoyleData, atom: ATOM] RETURNS [repaintNeeded: BOOL] = {
If we are about to do an Add, then we expect that the triggerBag is OK except that the moving trajectory's sequences are obsolete, the moving segment must be removed, and heuristic objects must be added. The sceneBag is OK except that moving objects must be removed. The objectBag should then be regenerated from the new triggerBag.
repaintNeeded is TRUE if the currentObject bag is altered.
currentTriggers: TriggerBag ← gargoyleData.hitTest.currentTriggerBag;
currentObjects: ObjectBag ← gargoyleData.hitTest.currentObjectBag;
sceneTriggers: TriggerBag ← gargoyleData.hitTest.sceneTriggerBag;
IF atom # $Drag THEN ERROR;
repaintNeeded ← SomeSelectedIsHot[gargoyleData.scene] OR AtomButtons.GetButtonState[gargoyleData.hitTest.heuristicsButton] = on;
IF gargoyleData.camera.hideAlignments THEN {
repaintNeeded ← FALSE;
GGGravity.FlushObjectBag[currentObjects];
UpdateSceneBagForAction[gargoyleData.scene, sceneTriggers, atom];
}
ELSE {
GGStatistics.StartInterval[$UpdateBagsForAction, GGStatistics.GlobalTable[]];
Steps (1) and (2) Build the Current Trigger Bag
AddHeuristics[gargoyleData, $Drag, currentTriggers];
RemoveMoving[gargoyleData.scene, $Drag, currentTriggers];
Step (4) Compute the Alignment Objects
GGGravity.FlushObjectBag[currentObjects];
BuiltInFilters[currentTriggers, currentObjects, gargoyleData];
Steps (6) and (7) Compute the Gravity Active Scene Objects
UpdateSceneBagForAction[gargoyleData.scene, sceneTriggers, $Drag];
AddAllMidpoints[sceneTriggers, currentObjects, gargoyleData]; -- make all stationary segment midpoints into alignment objects
GGStatistics.StopInterval[$UpdateBagsForAction, GGStatistics.GlobalTable[]];
};
};
UpdateSceneBagForAction: PROC [scene: Scene, sceneTriggers: TriggerBag, atom: ATOM] = {
SELECT atom FROM
$CaretPos => NULL; -- nothing is about to move
$Drag => RemoveMoving[scene, $Drag, sceneTriggers];
ENDCASE => ERROR;
GGStatistics.StartInterval[$ComputeBoundBoxes, GGStatistics.GlobalTable[]];
ComputeAllBoundingBoxes[sceneTriggers];
GGStatistics.StopInterval[$ComputeBoundBoxes, GGStatistics.GlobalTable[]];
};
ReplaceObsoleteOutlineTrigger: PUBLIC PROC [gargoyleData: GargoyleData, oldOutline: Outline, newOutline: Outline] = {
currentTriggers: TriggerBag ← gargoyleData.hitTest.currentTriggerBag;
currentObjects: ObjectBag ← gargoyleData.hitTest.currentObjectBag;
sceneTriggers: TriggerBag ← gargoyleData.hitTest.sceneTriggerBag;
feature: FeatureData;
hotD: OutlineDescriptor;
notFound: BOOL;
First, get rid of all references to the obsolete outline.
notFound ← FALSE;
UNTIL notFound DO
[sceneTriggers.outlines, notFound] ← DeleteOutline[oldOutline, sceneTriggers.outlines];
ENDLOOP;
notFound ← FALSE;
UNTIL notFound DO
[currentTriggers.outlines, notFound] ← DeleteOutline[oldOutline, currentTriggers.outlines];
ENDLOOP;
Next, add back any appropriate references to the new outline. Assume that the next operation will be a CaretPos.
If this traj is hot, add its hot parts to the triggerBag.
hotD ← GGSelect.FindSelectedOutline[newOutline, gargoyleData.scene, hot];
IF hotD # NIL THEN {
feature ← GGGravity.FeatureFromOutline[hotD.slice, GGOutline.CopyParts[hotD.slice, hotD.parts]];
AddFeature[feature, currentTriggers];
};
Add the whole object to the scene bag.
feature ← GGGravity.FeatureFromOutline[newOutline];
AddFeature[feature, sceneTriggers];
};
BuiltInFilters: PUBLIC PROC [triggerBag: TriggerBag, objectBag: ObjectBag, gargoyleData: GargoyleData] = {
Add Alignment Objects to the objectBag, using the triggers in the triggerBag.
anchorFeature: FeatureData;
sliceD: SliceDescriptor;
outlineD: OutlineDescriptor;
pointGen: PointGenerator;
pointPairGen: PointPairGenerator;
outPointGen: GGModelTypes.OutlinePointGenerator;
outPointPairGen: GGModelTypes.OutlinePointPairGenerator;
index: NAT;
filters: Filters ← gargoyleData.hitTest;
GGStatistics.StartInterval[$BuiltInFilters, GGStatistics.GlobalTable[]];
IF gargoyleData.camera.hideAlignments THEN {
GGGravity.FlushObjectBag[objectBag];
RETURN;
};
The anchor as a trigger.
anchorFeature ← triggerBag.anchor;
IF anchorFeature # NIL AND GGCaret.Exists[NARROW[anchorFeature.shape, Caret]] THEN {
point: Point;
point ← GGCaret.GetPoint[NARROW[anchorFeature.shape, Caret]];
[] ← JointFireRule[point, filters, objectBag];
};
Triggers from outlines.
FOR l: LIST OF FeatureData ← triggerBag.outlines, l.rest UNTIL l = NIL DO
outlineD ← NARROW[l.first.shape];
outPointGen ← outlineD.slice.class.pointsInDescriptor[outlineD];
FOR next: GGModelTypes.PointAndDone ← outlineD.slice.class.nextPoint[outPointGen], outlineD.slice.class.nextPoint[outPointGen] UNTIL next.done DO
[] ← JointFireRule[next.point, filters, objectBag];
ENDLOOP;
outPointPairGen ← outlineD.slice.class.pointPairsInDescriptor[outlineD];
index ← 0;
FOR next: GGModelTypes.PointPairAndDone ← outlineD.slice.class.nextPointPair[outPointPairGen], outlineD.slice.class.nextPointPair[outPointPairGen] UNTIL next.done DO
[] ← SegmentFireRule[index, next.lo, next.hi, filters, objectBag];
index ← index + 1;
ENDLOOP;
ENDLOOP;
Triggers from slices.
FOR l: LIST OF FeatureData ← triggerBag.slices, l.rest UNTIL l = NIL DO
sliceD ← NARROW[l.first.shape];
pointGen ← sliceD.slice.class.pointsInDescriptor[sliceD];
FOR next: GGModelTypes.PointAndDone ← sliceD.slice.class.nextPoint[pointGen], sliceD.slice.class.nextPoint[pointGen] UNTIL next.done DO
[] ← JointFireRule[next.point, filters, objectBag];
ENDLOOP;
pointPairGen ← sliceD.slice.class.pointPairsInDescriptor[sliceD];
index ← 0;
FOR next: GGModelTypes.PointPairAndDone ← sliceD.slice.class.nextPointPair[pointPairGen], sliceD.slice.class.nextPointPair[pointPairGen] UNTIL next.done DO
[] ← SegmentFireRule[9999, next.lo, next.hi, filters, objectBag];
index ← index + 1;
ENDLOOP;
ENDLOOP;
GGStatistics.StopInterval[$BuiltInFilters, GGStatistics.GlobalTable[]];
};
IncrementalFilters: PUBLIC PROC [trigger: FeatureData, objectBag: ObjectBag, gargoyleData: GargoyleData] RETURNS [alignObjects: LIST OF FeatureData] = {
A single new trigger has been added to the triggerBag. Add to the objectBag, all alignment objects generated by that trigger. Returns a list of the new alignment objects that were generated.
filters: Filters ← gargoyleData.hitTest;
alignObjects ← NIL;
IF gargoyleData.camera.hideAlignments THEN {
GGGravity.FlushObjectBag[objectBag];
RETURN;
};
WITH trigger.shape SELECT FROM
anchor: Caret => {
The anchor as a trigger.
IF GGCaret.Exists[anchor] THEN {
point: Point ← GGCaret.GetPoint[anchor];
[] ← JointFireRule[point, filters, objectBag];
};
};
outlineD: OutlineDescriptor => {
pointGen: GGModelTypes.OutlinePointGenerator;
pointPairGen: GGModelTypes.OutlinePointPairGenerator;
Triggers from a slice.
pointGen ← outlineD.slice.class.pointsInDescriptor[outlineD];
FOR next: GGModelTypes.PointAndDone ← outlineD.slice.class.nextPoint[pointGen], outlineD.slice.class.nextPoint[pointGen] UNTIL next.done DO
alignObjects ← NARROW[GList.Nconc[JointFireRule[next.point, filters, objectBag], alignObjects]];
ENDLOOP;
pointPairGen ← outlineD.slice.class.pointPairsInDescriptor[outlineD];
FOR next: GGModelTypes.PointPairAndDone ← outlineD.slice.class.nextPointPair[pointPairGen], outlineD.slice.class.nextPointPair[pointPairGen] UNTIL next.done DO
alignObjects ← NARROW[GList.Nconc[SegmentFireRule[9999, next.lo, next.hi, filters, objectBag], alignObjects]];
ENDLOOP;
};
sliceD: SliceDescriptor => {
pointGen: PointGenerator;
pointPairGen: PointPairGenerator;
Triggers from a slice.
pointGen ← sliceD.slice.class.pointsInDescriptor[sliceD];
FOR next: GGModelTypes.PointAndDone ← sliceD.slice.class.nextPoint[pointGen], sliceD.slice.class.nextPoint[pointGen] UNTIL next.done DO
alignObjects ← NARROW[GList.Nconc[JointFireRule[next.point, filters, objectBag], alignObjects]];
ENDLOOP;
pointPairGen ← sliceD.slice.class.pointPairsInDescriptor[sliceD];
FOR next: GGModelTypes.PointPairAndDone ← sliceD.slice.class.nextPointPair[pointPairGen], sliceD.slice.class.nextPointPair[pointPairGen] UNTIL next.done DO
alignObjects ← NARROW[GList.Nconc[SegmentFireRule[9999, next.lo, next.hi, filters, objectBag], alignObjects]];
ENDLOOP;
};
ENDCASE => ERROR;
};
(1) Put Hot Objects in the Trigger Bag.
AddAllHotSequences: PROC [gargoyleData: GargoyleData, triggerBag: TriggerBag] = {
feature: FeatureData;
copy: OutlineDescriptor;
outDGen: GGModelTypes.OutlineDescriptorGenerator;
outDGen ← GGSelect.SelectedOutlines[gargoyleData.scene, hot];
FOR outlineD: OutlineDescriptor ← GGSelect.NextOutlineDescriptor[outDGen], GGSelect.NextOutlineDescriptor[outDGen] UNTIL outlineD = NIL DO
copy ← NEW[OutlineDescriptorObj ← [outlineD.slice, GGOutline.CopyParts[outlineD.slice, outlineD.parts]]];
feature ← GGGravity.FeatureFromOutline[copy.slice, copy.parts];
AddFeature[feature, triggerBag];
ENDLOOP;
};
AddAllHotSlices: PROC [gargoyleData: GargoyleData, triggerBag: TriggerBag] = {
feature: FeatureData;
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[gargoyleData.scene, hot];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD = NIL DO
feature ← GGGravity.FeatureFromSlice[sliceD.slice, sliceD.parts];
AddFeature[feature, triggerBag];
ENDLOOP;
};
(2) Put More Objects in the Trigger Bag using Heuristics.
AddHeuristics: PROC [gargoyleData: GargoyleData, atom: ATOM, triggerBag: TriggerBag] = {
In the case of an Add operation, allow the caret chair to trigger even if it is not hot. In the case of a Drag operation, allow the chair being dragged to trigger even if not hot. A later call to RemoveMoving will keep only the stationary parts of the trajectories being dragged.
feature: FeatureData;
IF AtomButtons.GetButtonState[gargoyleData.hitTest.heuristicsButton] = off THEN RETURN;
SELECT atom FROM
$Drag => {
outDGen: GGModelTypes.OutlineDescriptorGenerator;
outDGen ← GGSelect.SelectedOutlines[gargoyleData.scene, normal];
FOR outlineD: OutlineDescriptor ← GGSelect.NextOutlineDescriptor[outDGen], GGSelect.NextOutlineDescriptor[outDGen] UNTIL outlineD = NIL DO
feature ← GGGravity.FeatureFromOutline[outlineD.slice, NIL];
AddFeature[feature, triggerBag];
ENDLOOP;
};
$CaretPos => NULL;
ENDCASE => ERROR UnexpectedType;
};
(3) Removing any Triggers which are MOVING.
RemoveMoving: PROC [scene: Scene, atom: ATOM, triggerBag: TriggerBag] = {
If we are about to drag all of the selected objects, then we must remove selected objects from the triggerBag. We must also remove segments which are adjacent to a moving joint (called "dangling" segments), and segments whose control points are selected.
If we are about to add a new point to a trajectory, then we must remove any references to the new joint or its adjacent segment.
Yipes! fixedParts has different meanings for Outlines and Slices. -- Bier, January 7, 1987
NotSelectedOrDanglingSeq: FilterOutlineProc = { -- PROC [outlineD: OutlineDescriptor];
trim off the selected or dangling parts from the sliceD parts
selSliceD: OutlineDescriptor ← GGSelect.FindSelectedOutline[outlineD.slice, gargoyleData.scene, normal];
IF selSliceD=NIL THEN RETURN; -- nothing to trim
outlineD.parts ← outlineD.slice.class.fixedParts[outlineD.slice, outlineD.parts, GGObjects.GetSelections[scene, normal]];
};
NotSelectedSlice: FilterSliceProc = { -- PROC [sliceD: SliceDescriptor]
trim off the selected parts from the sliceD parts
selSliceD: SliceDescriptor ← GGSelect.FindSelectedSlice[sliceD.slice, scene, normal];
IF selSliceD=NIL THEN RETURN; -- nothing to trim
sliceD.parts ← sliceD.slice.class.fixedParts[selSliceD.slice, selSliceD.parts, GGObjects.GetSelections[scene, normal]];
};
SELECT atom FROM
$Drag => DeleteTriggersFilter[triggerBag, NotSelectedOrDanglingSeq, NotSelectedSlice];
$CaretPos, $DragStartUp => NULL;
ENDCASE => ERROR;
};
DeleteTriggersFilter: PUBLIC PROC [triggerBag: TriggerBag, filterOutlineProc: FilterOutlineProc, filterSliceProc: FilterSliceProc] = {
When this procedure is called, all sequences in triggerBag are passed to filterSeqProc. filterSeqProc should return those parts which should NOT be deleted, returning NIL or an empty sequence if all parts should remain on the list. Similarly for slices in the triggerBag
FOR outList: LIST OF FeatureData ← triggerBag.outlines, outList.rest UNTIL outList = NIL DO
outlineD: OutlineDescriptor ← NARROW[outList.first.shape];
filterOutlineProc[outlineD]; -- bashes those parts of outlineD which should not remain
IF outlineD.slice.class.emptyParts[outlineD.slice, outlineD.parts] THEN {
triggerBag.outlines ← DeleteOutlineD[outlineD, triggerBag.outlines];
};
ENDLOOP;
FOR sliceList: LIST OF FeatureData ← triggerBag.slices, sliceList.rest UNTIL sliceList = NIL DO
sliceD: SliceDescriptor ← NARROW[sliceList.first.shape];
filterSliceProc[sliceD]; -- bashes those parts of slice which should not remain
IF sliceD.slice.class.emptyParts[sliceD.slice, sliceD.parts] THEN triggerBag.slices ← DeleteSlice[sliceD.slice, triggerBag.slices];
ENDLOOP;
};
(4) For Each Trigger, Construct the Appropriate Alignment Objects
JointFireRule: PROC [point: Point, filters: Filters, objectBag: ObjectBag] RETURNS [alignObjects: LIST OF FeatureData] = {
firstButton: ScalarButtonClient;
feature: FeatureData;
alignObjects ← NIL;
SlopeLine
firstButton ← filters.slopeHeader.scalarButtons;
FOR thisButton: ScalarButtonClient ← firstButton, thisButton.next UNTIL thisButton = NIL DO
IF thisButton.on THEN {
feature ← GGGravity.JointAddSlopeLine[
degrees: thisButton.value,
direction: GGVector.VectorFromAngle[thisButton.value],
point: point,
objectBag: objectBag
];
IF feature # NIL THEN alignObjects ← CONS[feature, alignObjects];
};
ENDLOOP;
Circle
firstButton ← filters.radiusHeader.scalarButtons;
FOR thisButton: ScalarButtonClient ← firstButton, thisButton.next UNTIL thisButton = NIL DO
IF thisButton.on THEN {
feature ← GGGravity.JointAddCircle[
radius: thisButton.value*filters.scaleUnit,
point: point,
objectBag: objectBag
];
IF feature # NIL THEN alignObjects ← CONS[feature, alignObjects];
};
ENDLOOP;
};
SegmentFireRule: PROC [segNum: NAT, lo, hi: Point, filters: Filters, objectBag: ObjectBag] RETURNS [alignObjects: LIST OF FeatureData] = {
firstButton: ScalarButtonClient;
line1, line2: FeatureData;
alignObjects ← NIL;
Parallel Lines at Given Distance
firstButton ← filters.distanceHeader.scalarButtons;
FOR thisButton: ScalarButtonClient ← firstButton, thisButton.next UNTIL thisButton = NIL DO
IF thisButton.on THEN {
[line1, line2] ← GGGravity.SegmentAddDistanceLines[ distance: thisButton.value*filters.scaleUnit, segNum: segNum, lo: lo, hi: hi, objectBag: objectBag];
IF line1 # NIL THEN alignObjects ← CONS[line1, alignObjects];
IF line2 # NIL THEN alignObjects ← CONS[line2, alignObjects];
};
ENDLOOP;
AngleLines
firstButton ← filters.angleHeader.scalarButtons;
FOR thisButton: ScalarButtonClient ← firstButton, thisButton.next UNTIL thisButton = NIL DO
IF thisButton.on THEN {
[line1, line2] ← GGGravity.SegmentAddTwoAngleLines[degrees: thisButton.value, segNum: segNum, lo: lo, hi: hi, objectBag: objectBag];
IF line1 # NIL THEN alignObjects ← CONS[line1, alignObjects];
IF line2 # NIL THEN alignObjects ← CONS[line2, alignObjects];
};
ENDLOOP;
};
(4') Hooks for the future when users can write their own alignment line generators.
UserDefinedPointFilter: PUBLIC PROC [triggerBag: TriggerBag, objectBag: ObjectBag, pointFilter: PointFilterProc] = {
jointGen: JointGenerator;
point: Point;
alignObjList: LIST OF AlignmentObject;
Filter the joints.
FOR l: LIST OF FeatureData ← triggerBag.seqs, l.rest UNTIL l = NIL DO
jointGen ← GGSequence.JointsInSequence[NARROW[l.first.shape, Sequence]];
FOR i: INT ← GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen] UNTIL i = -1 DO
point ← GGTraj.FetchJointPos[NARROW[l.first.shape, Sequence].traj, i];
alignObjList ← pointFilter[point];
FOR alignList: LIST OF AlignmentObject ← alignObjList, alignList.rest UNTIL alignList = NIL DO
WITH alignList.first SELECT FROM
line: Line => {
GGGravity.JointAddLine[line, point, i, NARROW[l.first.shape, Sequence].traj, objectBag];
};
circle: Circle => {
GGGravity.JointAddCircle[circle, point, i, NARROW[l.first.shape, Sequence].traj, objectBag];
};
ENDCASE => ERROR;
ENDLOOP;
ENDLOOP;
ENDLOOP;
};
UserDefinedSegmentFilter: PUBLIC PROC [triggerBag: TriggerBag, objectBag: ObjectBag, segmentFilter: SegmentFilterProc] = {
segGen: SegmentGenerator;
alignObjList: LIST OF AlignmentObject;
Filter the segments.
FOR l: LIST OF FeatureData ← triggerBag.seqs, l.rest UNTIL l = NIL DO
segGen ← GGSequence.SegmentsInSequence[NARROW[l.first.shape, Sequence]];
FOR next: SegAndIndex ← GGSequence.NextSegmentAndIndex[segGen], GGSequence.NextSegmentAndIndex[segGen] UNTIL next.seg = NIL DO
alignObjList ← segmentFilter[next.seg.lo, next.seg.hi];
FOR alignList: LIST OF AlignmentObject ← alignObjList, alignList.rest UNTIL alignList = NIL DO
WITH alignList.first SELECT FROM
line: Line => {
GGGravity.SegmentAddLine[line, point, next.index, NARROW[feature.shape, Sequence].traj, objectBag];
};
circle: Circle => {
GGGravity.SegmentAddCircle[circle, point, next.index, NARROW[feature.shape, Sequence].traj, objectBag];
};
ENDCASE => ERROR;
ENDLOOP;
ENDLOOP;
ENDLOOP;
};
Building a TriggerBag
CreateTriggerBag: PUBLIC PROC [] RETURNS [triggerBag: TriggerBag] = {
triggerBag ← NEW[TriggerBagObj ← [outlines: NIL, slices: NIL, intersectionPoints: NIL, anchor: NIL]];
};
FlushTriggerBag: PROC [triggerBag: TriggerBag] = {
triggerBag.outlines ← NIL;
triggerBag.slices ← NIL;
triggerBag.intersectionPoints ← NIL;
triggerBag.anchor ← NIL;
};
CopyTriggerBag: PUBLIC PROC [triggerBag: TriggerBag] RETURNS [copy: TriggerBag] = {
WHY DOES THIS PROC ONLY COPY SEQUENCES ??
finger: LIST OF FeatureData;
outlineD: OutlineDescriptor;
copyD: OutlineDescriptor;
copy ← NEW[TriggerBagObj ← [outlines: NIL]];
IF triggerBag.outlines = NIL THEN RETURN;
outlineD ← NARROW[triggerBag.outlines.first.shape];
copyD ← NEW[OutlineDescriptorObj ← [outlineD.slice, GGOutline.CopyParts[outlineD.slice, outlineD.parts]]];
finger ← copy.outlines ← LIST[GGGravity.FeatureFromOutline[copyD.slice, copyD.parts]];
FOR oldOutList: LIST OF FeatureData ← triggerBag.outlines.rest, oldOutList.rest UNTIL oldOutList = NIL DO
outlineD ← NARROW[oldOutList.first.shape];
copyD ← NEW[OutlineDescriptorObj ← [outlineD.slice, GGOutline.CopyParts[outlineD.slice, outlineD.parts]]];
finger.rest ← CONS[GGGravity.FeatureFromOutline[copyD.slice, copyD.parts], NIL];
finger ← finger.rest;
ENDLOOP;
};
PutBagInBag: PROC [from: TriggerBag, to: TriggerBag] = {
WHY DOES THIS PROC ONLY MOVE SEQUENCES AND SLICES ??
Does not copy from. CON'es its elements right into to.
outs, outStart: LIST OF FeatureData;
slices, sliceStart: LIST OF FeatureData;
outs ← outStart ← from.outlines;
UNTIL outs.rest = NIL DO outs ← outs.rest ENDLOOP;
outs.rest ← to.outlines;
to.outlines ← from.outlines;
slices ← sliceStart ← from.slices;
UNTIL slices.rest = NIL DO slices ← slices.rest ENDLOOP;
slices.rest ← to.slices;
to.slices ← from.slices;
};
AddOutlineTrigger: PROC [outlineD: OutlineDescriptor, triggerBag: TriggerBag] = {
[] ← CreateOutlineTrigger[outlineD, triggerBag];
};
CreateOutlineTrigger: PUBLIC PROC [outlineD: OutlineDescriptor, triggerBag: TriggerBag] RETURNS [feature: FeatureData] = {
oldD, copy: OutlineDescriptor;
unionParts: SliceParts;
oldD ← FindOldOutlineD[outlineD.slice, triggerBag.outlines];
IF oldD = NIL THEN {
copy ← NEW[OutlineDescriptorObj ← [outlineD.slice, GGOutline.CopyParts[outlineD.slice, outlineD.parts]]];
feature ← GGGravity.FeatureFromOutline[copy.slice, copy.parts];
triggerBag.outlines ← AppendFeature[feature, triggerBag.outlines];
}
ELSE {
unionParts ← outlineD.slice.class.unionParts[outlineD.slice, oldD.parts, outlineD.parts];
triggerBag.outlines ← DeleteOutlineD[oldD, triggerBag.outlines];
feature ← GGGravity.FeatureFromOutline[outlineD.slice, unionParts];
triggerBag.outlines ← AppendFeature[feature, triggerBag.outlines];
};
};
AddSliceTrigger: PROC [slice: Slice, triggerBag: TriggerBag] = {
allParts: SliceParts ← slice.class.newParts[slice, NIL, slice];
sliceD: SliceDescriptor ← NEW[SliceDescriptorObj ← [slice: slice, parts: allParts]];
[] ← CreateSliceTrigger[sliceD, triggerBag];
};
CreateSliceTrigger: PUBLIC PROC [sliceD: SliceDescriptor, triggerBag: TriggerBag] RETURNS [feature: FeatureData] = {
oldD: SliceDescriptor;
unionParts: SliceParts;
oldD ← FindOldSlice[sliceD.slice, triggerBag.slices];
IF oldD = NIL THEN {
copyD ← slice.class.copyDescriptor[oldD]; -- should be done some day
feature ← GGGravity.FeatureFromSlice[sliceD.slice, sliceD.parts];
triggerBag.slices ← AppendFeature[feature, triggerBag.slices];
}
ELSE {
unionParts ← sliceD.slice.class.unionParts[sliceD.slice, oldD.parts, sliceD.parts];
triggerBag.slices ← DeleteSlice[oldD.slice, triggerBag.slices];
feature ← GGGravity.FeatureFromSlice[sliceD.slice, unionParts];
triggerBag.slices ← AppendFeature[feature, triggerBag.slices];
};
};
AddAnchorTrigger: PROC [gargoyleData: GargoyleData, triggerBag: TriggerBag] = {
[] ← CreateAnchorTrigger[gargoyleData.anchor, triggerBag];
};
CreateAnchorTrigger: PUBLIC PROC [anchor: Caret, triggerBag: TriggerBag] RETURNS [feature: FeatureData] = {
feature ← GGGravity.FeatureFromAnchor[anchor];
AddFeature[feature, triggerBag];
};
DeleteAnchorTrigger: PUBLIC PROC [triggerBag: TriggerBag] = {
triggerBag.anchor ← NIL;
};
DeleteOutlineTrigger: PROC [outlineD: OutlineDescriptor, triggerBag: TriggerBag] = {
oldOut: OutlineDescriptor;
diff: SliceParts;
feature: FeatureData;
oldOut ← FindOldOutlineD[outlineD.slice, triggerBag.outlines];
IF oldOut#NIL THEN {
diff ← outlineD.slice.class.differenceParts[outlineD.slice, oldOut.parts, outlineD.parts];
triggerBag.outlines ← DeleteOutlineD[oldOut, triggerBag.outlines];
IF NOT outlineD.slice.class.emptyParts[outlineD.slice, diff] THEN {
feature ← GGGravity.FeatureFromOutline[outlineD.slice, diff];
triggerBag.outlines ← AppendFeature[feature, triggerBag.outlines];
};
};
};
DeleteSliceTrigger: PUBLIC PROC [slice: Slice, triggerBag: TriggerBag] = {
oldSliceD: SliceDescriptor;
oldSliceD ← FindOldSlice[slice, triggerBag.slices];
IF oldSliceD#NIL THEN triggerBag.slices ← DeleteSlice[oldSliceD.slice, triggerBag.slices];
};
For use in the glorious future.
TAddTriggersFilter: PUBLIC PROC [triggerBag: TriggerBag, filterOutlineProc: FilterOutlineProc, filterSliceProc: FilterSliceProc, gargoyleData: GargoyleData] = {ERROR;};
TAddTriggersFilter: PUBLIC PROC [triggerBag: TriggerBag, filterSeqProc: FilterSequenceProc, filterSliceProc: FilterSliceProc, gargoyleData: GargoyleData] = {
When this procedure is called, all sequences in the scene are passed to filterSeqProc to see if they should be added (in all or in part). filterSeqProc should return those parts which should be added. AddSequenceTrigger adds these parts to triggerBag. A similar things happens for slices using the filterSliceProc and calls to AddSliceTrigger
add: BOOL;
seq: Sequence;
sliceGen: SliceGenerator;
trajGen: TrajGenerator ← GGObjects.TrajsInScene[gargoyleData.scene];
FOR traj: Traj ← GGObjects.NextTraj[trajGen], GGObjects.NextTraj[trajGen] UNTIL traj = NIL DO
seq ← GGSequence.CreateComplete[traj];
filterSeqProc[seq]; -- keeps those parts of seq which should remain
AddSequenceTrigger[seq, triggerBag];
ENDLOOP;
sliceGen ← GGObjects.SlicesInScene[gargoyleData.scene];
FOR slice: Slice ← GGObjects.NextSlice[sliceGen], GGObjects.NextSlice[sliceGen] UNTIL slice = NIL DO
add ← filterSliceProc[slice];
IF add THEN AddSliceTrigger[slice, triggerBag];
ENDLOOP;
};
Add and Remove scene parts to/from a TriggerBag
AddFeature: PROC [featureData: FeatureData, triggerBag: TriggerBag] = {
Process.CheckForAbort[];
SELECT featureData.type FROM
outline => {
triggerBag.outlines ← CONS[featureData, triggerBag.outlines];
};
slice => {
triggerBag.slices ← CONS[featureData, triggerBag.slices];
};
anchor => {
triggerBag.anchor ← featureData;
};
ENDCASE => ERROR;
};
AddAllTrajectories: PROC [gargoyleData: GargoyleData, triggerBag: TriggerBag] = {
outlineGen: GGModelTypes.OutlineGenerator;
feature: FeatureData;
outlineGen ← GGObjects.OutlinesInScene[gargoyleData.scene];
FOR outline: Outline ← GGObjects.NextOutline[outlineGen], GGObjects.NextOutline[outlineGen] UNTIL outline = NIL DO
feature ← GGGravity.FeatureFromOutline[outline];
AddFeature[feature, triggerBag];
ENDLOOP;
};
AddAllSlices: PROC [gargoyleData: GargoyleData, triggerBag: TriggerBag] = {
feature: FeatureData;
sliceGen: SliceGenerator ← GGObjects.SlicesInScene[gargoyleData.scene];
FOR slice: Slice ← GGObjects.NextSlice[sliceGen], GGObjects.NextSlice[sliceGen] UNTIL slice = NIL DO
feature ← GGGravity.FeatureFromSlice[slice];
AddFeature[feature, triggerBag];
ENDLOOP;
};
AddAnchor: PROC [gargoyleData: GargoyleData, triggerBag: TriggerBag] = {
feature: FeatureData;
IF GGCaret.Exists[gargoyleData.anchor] THEN {
feature ← GGGravity.FeatureFromAnchor[gargoyleData.anchor];
AddFeature[feature, triggerBag];
};
};
AddAllMidpoints: PUBLIC PROC [sceneTriggers: TriggerBag, objectBag: ObjectBag, gargoyleData: GargoyleData] = {
Add to the object bag the midpoints of all segments and slices in the trigger bag.
SegmentMidpointRule: PROC [segNum: NAT, lo, hi: Point] = {
[] ← GGGravity.SegmentAddMidpoint[segNum: segNum, lo: lo, hi: hi, objectBag: objectBag];
};
sliceD: SliceDescriptor;
outlineD: OutlineDescriptor;
pointPairGen: PointPairGenerator;
outPointPairGen: GGModelTypes.OutlinePointPairGenerator;
objectBag.midpoints ← NIL;
IF gargoyleData.hitTest.midpointButton.state = off THEN RETURN;
Segments as triggers.
FOR l: LIST OF FeatureData ← sceneTriggers.outlines, l.rest UNTIL l = NIL DO
pairCount: NAT ← 0;
outlineD ← NARROW[l.first.shape];
outPointPairGen ← outlineD.slice.class.pointPairsInDescriptor[outlineD];
FOR next: GGModelTypes.PointPairAndDone ← outlineD.slice.class.nextPointPair[outPointPairGen], outlineD.slice.class.nextPointPair[outPointPairGen] UNTIL next.done DO
SegmentMidpointRule[pairCount, next.lo, next.hi];
pairCount ← pairCount+1;
ENDLOOP;
ENDLOOP;
Slices as triggers.
FOR l: LIST OF FeatureData ← sceneTriggers.slices, l.rest UNTIL l = NIL DO
pairCount: NAT ← 0;
sliceD ← NARROW[l.first.shape];
pointPairGen ← sliceD.slice.class.pointPairsInDescriptor[sliceD];
FOR next: GGModelTypes.PointPairAndDone ← sliceD.slice.class.nextPointPair[pointPairGen], sliceD.slice.class.nextPointPair[pointPairGen] UNTIL next.done DO
SegmentMidpointRule[pairCount, next.lo, next.hi];
pairCount ← pairCount+1;
ENDLOOP;
ENDLOOP;
};
FeatureList Utilities
FindOldOutlineD: PROC [outline: Outline, list: LIST OF FeatureData] RETURNS [oldSliceD: OutlineDescriptor] = {
FOR l: LIST OF FeatureData ← list, l.rest UNTIL l = NIL DO
IF NARROW[l.first.shape, OutlineDescriptor].slice = outline THEN RETURN [NARROW[l.first.shape]];
ENDLOOP;
RETURN [NIL];
};
FindOldSlice: PROC [slice: Slice, list: LIST OF FeatureData] RETURNS [oldSliceD: SliceDescriptor] = {
FOR l: LIST OF FeatureData ← list, l.rest UNTIL l = NIL DO
IF NARROW[l.first.shape, SliceDescriptor].slice = slice THEN RETURN [NARROW[l.first.shape]];
ENDLOOP;
RETURN [NIL];
};
FindSequenceAndNeighbors: PROC [entity: Sequence, entityList: LIST OF FeatureData] RETURNS [beforeEnt, ent, afterEnt: LIST OF FeatureData] = {
lastE: LIST OF FeatureData ← NIL;
eList: LIST OF FeatureData ← entityList;
IF eList = NIL THEN ERROR EntityNotFound;
UNTIL eList = NIL DO
IF NARROW[eList.first.shape, Sequence] = entity THEN {
beforeEnt ← lastE; ent ← eList; afterEnt ← eList.rest; RETURN};
lastE ← eList;
eList ← eList.rest;
ENDLOOP;
SIGNAL EntityNotFound;
};
FindOutlineAndNeighbors: PROC [entity: Outline, entityList: LIST OF FeatureData] RETURNS [beforeEnt, ent, afterEnt: LIST OF FeatureData] = {
lastE: LIST OF FeatureData ← NIL;
eList: LIST OF FeatureData ← entityList;
IF eList = NIL THEN ERROR EntityNotFound;
UNTIL eList = NIL DO
IF NARROW[eList.first.shape, OutlineDescriptor].slice = entity THEN {
beforeEnt ← lastE; ent ← eList; afterEnt ← eList.rest; RETURN};
lastE ← eList;
eList ← eList.rest;
ENDLOOP;
SIGNAL EntityNotFound;
};
FindOutlineDAndNeighbors: PROC [entity: Outline, entityList: LIST OF FeatureData] RETURNS [beforeEnt, ent, afterEnt: LIST OF FeatureData] = {
lastE: LIST OF FeatureData ← NIL;
eList: LIST OF FeatureData ← entityList;
IF eList = NIL THEN ERROR EntityNotFound;
UNTIL eList = NIL DO
IF NARROW[eList.first.shape, OutlineDescriptor].slice = entity THEN {
beforeEnt ← lastE; ent ← eList; afterEnt ← eList.rest; RETURN};
lastE ← eList;
eList ← eList.rest;
ENDLOOP;
SIGNAL EntityNotFound;
};
FindSliceAndNeighbors: PROC [entity: Slice, entityList: LIST OF FeatureData] RETURNS [beforeEnt, ent, afterEnt: LIST OF FeatureData] = {
lastE: LIST OF FeatureData ← NIL;
eList: LIST OF FeatureData ← entityList;
IF eList = NIL THEN ERROR EntityNotFound;
UNTIL eList = NIL DO
IF NARROW[eList.first.shape, SliceDescriptor].slice = entity THEN {
beforeEnt ← lastE; ent ← eList; afterEnt ← eList.rest; RETURN};
lastE ← eList;
eList ← eList.rest;
ENDLOOP;
SIGNAL EntityNotFound;
};
DeleteOutline: PROC [outline: Outline, featureList: LIST OF FeatureData] RETURNS [smallerList: LIST OF FeatureData, notFound: BOOLFALSE] = {
before, at, after: LIST OF FeatureData;
IF featureList = NIL THEN RETURN[NIL, TRUE];
[before, at, after] ← FindOutlineAndNeighbors[outline, featureList ! EntityNotFound => {notFound ← TRUE; CONTINUE}];
IF notFound THEN RETURN[featureList, TRUE];
IF before = NIL THEN smallerList ← after
ELSE {
before.rest ← after;
smallerList ← featureList;
};
};
DeleteOutlineD: PROC [outlineD: OutlineDescriptor, featureList: LIST OF FeatureData] RETURNS [smallerList: LIST OF FeatureData] = {
before, at, after: LIST OF FeatureData;
notFound: BOOLFALSE;
IF featureList = NIL THEN RETURN[NIL];
[before, at, after] ← FindOutlineDAndNeighbors[outlineD.slice, featureList ! EntityNotFound => {notFound ← TRUE; CONTINUE}];
IF notFound THEN RETURN[featureList];
IF before = NIL THEN smallerList ← after
ELSE {
before.rest ← after;
smallerList ← featureList;
};
};
DeleteSlice: PROC [slice: Slice, featureList: LIST OF FeatureData] RETURNS [smallerList: LIST OF FeatureData] = {
before, at, after: LIST OF FeatureData;
notFound: BOOLFALSE;
[before, at, after] ← FindSliceAndNeighbors[slice, featureList];
IF notFound THEN RETURN[featureList];
IF before = NIL THEN smallerList ← after
ELSE {
before.rest ← after;
smallerList ← featureList;
};
}; -- end of DeleteSlice
AppendFeature: PUBLIC PROC [feature: FeatureData, featureList: LIST OF FeatureData] RETURNS [biggerList: LIST OF FeatureData] = {
Process.CheckForAbort[];
biggerList ← CONS[feature, featureList];
};
Miscellaneous
ComputeAllBoundingBoxes: PROC [sceneTriggers: TriggerBag] = {
featureData: FeatureData;
outlineD: OutlineDescriptor;
FOR list: LIST OF FeatureData ← sceneTriggers.outlines, list.rest UNTIL list = NIL DO
featureData ← list.first;
outlineD ← NARROW[featureData.shape];
GGOutline.UpdateDescriptorBoundBoxes[outlineD: outlineD];
ENDLOOP;
};
InitStats: PROC [] = {
boundBoxes, bags: GGStatistics.Interval;
boundBoxes ← GGStatistics.CreateInterval[$ComputeBoundBoxes];
bags ← GGStatistics.CreateInterval[$UpdateBagsForAction, LIST[boundBoxes]];
GGStatistics.AddInterval[bags, GGStatistics.GlobalTable[]];
bags ← GGStatistics.CreateInterval[$SetBagsForAction, NIL];
GGStatistics.AddInterval[bags, GGStatistics.GlobalTable[]];
bags ← GGStatistics.CreateInterval[$BuiltInFilters, NIL];
GGStatistics.AddInterval[bags, GGStatistics.GlobalTable[]];
};
emptyTriggerBag: PUBLIC TriggerBag;
Init: PROC [] = {
emptyTriggerBag ← CreateTriggerBag[];
};
InitStats[];
Init[];
END.