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: BOOLFALSE] = {
SliceFeatureFound: RefTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOLFALSE];
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: BOOLFALSE];
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: BOOLFALSE];
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: BOOLFALSE] = {
[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: BOOLFALSE;
[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: REFNARROW[oldFeature.shape, SliceDescriptor].slice;
found: BOOLFALSE;
[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: REFNARROW[oldFeature.shape, SliceDescriptor].slice;
found: BOOLFALSE;
[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: BOOLFALSE;
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: BOOLFALSE];
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: BOOLFALSE] = {
FeatureFound: CardTab.EachPairAction = {
EachPairAction: TYPE = PROC [key: Key, val: Val] RETURNS [quit: BOOLFALSE];
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: BOOLFALSE];
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: BOOLFALSE;
[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: BOOLFALSE];
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: BOOLFALSE];
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: BOOLFALSE;
[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]
THEN
RETURN[l.first];
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: BOOLNOT 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: BOOLNOT 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: BOOLNOT 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: BOOLTRUE] = {
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: BOOLNOT 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: BOOLFALSE;
autoTriggers: LIST OF SliceDescriptor ← NIL;
filterLists: FilterLists ← GGState.GetFilterLists[ggData];
someAlignmentsActive: BOOLNOT 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: BOOLTRUE] = {
alignBag: AlignBag ← ggData.hitTest.alignBag;
filters: Filters ← ggData.hitTest;
hideAlignments: BOOLNOT 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: BOOLFALSE] = {
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: BOOLNOT 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: BOOLFALSE] = {
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: BOOLFALSE, autoTriggers: LIST OF SliceDescriptor ← NIL] = {
feature: FeatureData;
IF NOT heuristics THEN RETURN;
SELECT atom FROM
$Drag => {
AddToTriggerBag: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
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: BOOLFALSE] = {
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: BOOLFALSE] = {
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: BOOLFALSE;
DoRemoveMoving: PROC [selectedD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
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: BOOLFALSE;
[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: BOOLFALSE] = {
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 REALfilterLists.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 REALfilterLists.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 REALfilterLists.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 REALfilterLists.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 REALfilterLists.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 REALfilterLists.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: BOOLFALSE] = {
FindHot: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
IF GGSelect.IsSelectedInPart[sliceD.slice, scene, hot] THEN {
hotFound ← TRUE;
done ← TRUE;
};
};
[] ← GGScene.WalkSelectedSlices[scene, first, FindHot, normal];
};
FeatureList Utilities
FindOldSlice: PROC [slice: Slice, sliceTriggers: RefTab.Ref] RETURNS [oldSliceD: SliceDescriptor, oldFeature: FeatureData] = {
key: REF ← slice;
val: REF;
found: BOOLFALSE;
[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;
useCache: BOOLTRUE;
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: BOOLFALSE] = {
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: BOOLFALSE;
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: BOOLFALSE];
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: BOOLFALSE];
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: BOOLFALSE;
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.