GGAlignImpl.mesa
Created by Eric Bier on June 5, 1985 3:41:39 pm PDT.
Last edited by Bier on November 7, 1985 9:44:08 pm PST.
DIRECTORY
GGAlign,
GGCaret,
GGError,
GGGravity,
GGInterfaceTypes,
GGModelTypes,
GGObjects,
GGSelect,
GGVector;
GGAlignImpl:
CEDAR
PROGRAM
IMPORTS GGCaret, GGGravity, GGObjects, GGSelect, GGVector
EXPORTS GGAlign =
BEGIN
Angle: TYPE = GGModelTypes.Angle;
Edge: TYPE = GGModelTypes.Edge;
EntityGenerator: TYPE = GGObjects.EntityGenerator;
FeatureData: TYPE = REF FeatureDataObj;
FeatureDataObj: TYPE = GGGravity.FeatureDataObj;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
Joint: TYPE = GGModelTypes.Joint;
JointGenerator: TYPE = GGObjects.JointGenerator;
Line: TYPE = GGModelTypes.Line;
ObjectBag: TYPE = GGGravity.ObjectBag;
Outline: TYPE = GGModelTypes.Outline;
Point: TYPE = GGModelTypes.Point;
Scene: TYPE = GGModelTypes.Scene;
ScalarButtonClient: TYPE = GGInterfaceTypes.ScalarButtonClient;
SegAndIndex: TYPE = GGObjects.SegAndIndex;
SegmentGenerator: TYPE = GGObjects.SegmentGenerator;
SelectionClass: TYPE = GGInterfaceTypes.SelectionClass;
Sequence: TYPE = GGModelTypes.Sequence;
SymmetryGroup: TYPE = GGGravity.SymmetryGroup;
Traj: TYPE = GGModelTypes.Traj;
TrajGenerator: TYPE = GGObjects.TrajGenerator;
Vector: TYPE = GGModelTypes.Vector;
Trigger Classes and Firing Rules
Alignment requires identifying a set of alignment objects which can be snapped-to using the Gravity functions described in GGGravity.mesa. Almost all alignment objects are derived from a set of trigger objects, by a set of Firing Rules. For instance, if we draw horizontal lines thru all of the vertices of a trajectory, the vertices are the trigger objects, the horizontal lines are the alignment objects, and the rule "compute a horizontal line passing through each vertex x" is the firing rule.
Different trigger classes and different firing rules are appropriate under different circumstances. Our decision in Gargoyle is to let the user choose the firing rules manually, via menus. The same firing rules are applied under all circumstances. The set of trigger objects, however, depends on which action is being performed. AddItemsForAction associates a different atom for each situation requiring a different set of trigger objects. These atoms include $Drag $SelectPoint $Add and $SetupDrag.
Triggers: TYPE = {joint, segment, symmetryGroup, coordFrame};
Joint Firing Rules: SlopeLine, Circle, Vector
Segment Firing Rules: FourAngleLines, ColinearLine
Symmetry Firing Rules: SymmetryAddLines, SymmetryAddPoints
Coordinate Frame Firing Rules: AddPoint AddXLine AddYLine
JointFireRule:
PRIVATE
PROC [point: Point, jointNum:
NAT, traj: Traj, objectBag: ObjectBag, gargoyleData: GargoyleData] = {
firstButton: ScalarButtonClient;
SlopeLine
firstButton ← gargoyleData.hitTest.slopeButtons;
FOR thisButton: ScalarButtonClient ← firstButton, thisButton.next
UNTIL thisButton =
NIL
DO
IF thisButton.on
THEN
GGGravity.JointAddSlopeLine[
degrees: thisButton.value,
direction: GGVector.VectorFromAngle[thisButton.value],
point: point,
jointNum: jointNum,
traj: traj,
objectBag: objectBag];
ENDLOOP;
Circle
firstButton ← gargoyleData.hitTest.radiusButtons;
FOR thisButton: ScalarButtonClient ← firstButton, thisButton.next
UNTIL thisButton =
NIL
DO
IF thisButton.on
THEN
GGGravity.JointAddCircle[
radius: thisButton.value,
point: point,
jointNum: jointNum,
traj: traj,
objectBag: objectBag];
ENDLOOP;
Vector (not yet implemented)
};
SegmentFireRule:
PRIVATE
PROC [segNum:
NAT, traj: Traj, objectBag: ObjectBag, gargoyleData: GargoyleData] = {
firstButton: ScalarButtonClient;
Parallel Lines at Given Distance
firstButton ← gargoyleData.hitTest.distanceButtons;
FOR thisButton: ScalarButtonClient ← firstButton, thisButton.next
UNTIL thisButton =
NIL
DO
IF thisButton.on
THEN
GGGravity.
SegmentAddDistanceLines[
distance: thisButton.value,
segNum: segNum,
traj: traj,
objectBag: objectBag];
ENDLOOP;
};
SymmetryFireRule:
PRIVATE
PROC [group: SymmetryGroup, entity:
REF
ANY, objectBag: ObjectBag, gargoyleData: GargoyleData] = {
NotYetImplemented.
};
CoordinateFrameFireRule:
PRIVATE
PROC [objectBag: ObjectBag, gargoyleData: GargoyleData] = {
NotYetImplemented.
};
Special Rules for efficiency.
TrajectoryFireRule:
PRIVATE
PROC [hotTraj: Traj, objectBag: ObjectBag, gargoyleData: GargoyleData] = {
jointGen: JointGenerator;
segGen: SegmentGenerator;
point: Point;
Joints as triggers.
jointGen ← GGObjects.JointsInTraj[hotTraj];
FOR i:
INT ← GGObjects.NextJoint[jointGen], GGObjects.NextJoint[jointGen]
UNTIL i = -1
DO
point ← GGObjects.FetchJointPos[hotTraj, i];
JointFireRule[point, i, hotTraj, objectBag, gargoyleData];
ENDLOOP;
Segments as triggers.
segGen ← GGObjects.SegmentsInTraj[hotTraj];
FOR next: SegAndIndex ← GGObjects.NextSegmentAndIndex[segGen], GGObjects.NextSegmentAndIndex[segGen]
UNTIL next.seg =
NIL
DO
SegmentFireRule[next.index, hotTraj, objectBag, gargoyleData];
ENDLOOP;
};
SequenceFireRule:
PRIVATE
PROC [hotSeq: Sequence, objectBag: ObjectBag, gargoyleData: GargoyleData] = {
jointGen: JointGenerator;
segGen: SegmentGenerator;
point: Point;
Joints as triggers.
jointGen ← GGObjects.JointsInSequence[hotSeq];
FOR i:
INT ← GGObjects.NextJoint[jointGen], GGObjects.NextJoint[jointGen]
UNTIL i = -1
DO
point ← GGObjects.FetchJointPos[hotSeq.traj, i];
JointFireRule[point, i, hotSeq.traj, objectBag, gargoyleData];
ENDLOOP;
Segments as triggers.
segGen ← GGObjects.SegmentsInSequence[hotSeq];
FOR next: SegAndIndex ← GGObjects.NextSegmentAndIndex[segGen], GGObjects.NextSegmentAndIndex[segGen]
UNTIL next.seg =
NIL
DO
SegmentFireRule[next.index, hotSeq.traj, objectBag, gargoyleData];
ENDLOOP;
};
Filtering Triggers. Which triggers are active depends on which action is being performed. However, there is a commonality between actions. Often, the active triggers are those that are in a particular selected set. Or are of a particular type, e.g. Joint, Anchor, Segment, Symmetry, or coordinate frame. These procedures try to capture that commonality.
FilterSequenceExceptClass:
PRIVATE
PROC [seq: Sequence, gargoyleData: GargoyleData, objectBag: ObjectBag, exceptClass: SelectionClass] = {
jointGen: JointGenerator;
segGen: SegmentGenerator;
joint: Joint;
Joints as triggers.
jointGen ← GGObjects.JointsInSequence[seq];
FOR i:
INT ← GGObjects.NextJoint[jointGen], GGObjects.NextJoint[jointGen]
UNTIL i = -1
DO
joint ← GGObjects.FetchJoint[seq.traj, i];
IF GGSelect.IsSelected[joint, gargoyleData, exceptClass] THEN LOOP;
JointFireRule[joint.point, i, seq.traj, objectBag, gargoyleData];
ENDLOOP;
Segments as triggers.
segGen ← GGObjects.SegmentsInSequence[seq];
FOR next: SegAndIndex ← GGObjects.NextSegmentAndIndex[segGen], GGObjects.NextSegmentAndIndex[segGen]
UNTIL next.seg =
NIL
DO
IF GGSelect.IsSelected[next.seg, gargoyleData, exceptClass] THEN LOOP;
joint ← GGObjects.FetchJoint[seq.traj, next.index];
IF GGSelect.IsSelected[joint, gargoyleData, exceptClass] THEN LOOP;
joint ← GGObjects.FetchJoint[seq.traj, GGObjects.FollowingJoint[seq.traj, next.index]];
IF GGSelect.IsSelected[joint, gargoyleData, exceptClass] THEN LOOP;
SegmentFireRule[next.index, seq.traj, objectBag, gargoyleData];
ENDLOOP;
};
FilterSelectedTriggers:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag, selectClass: SelectionClass] = {
selectClass vertices and segments are triggers.
selectGen: EntityGenerator;
trajGen: TrajGenerator;
Add selected vertices and segments
selectGen ← GGSelect.SelectedEntities[gargoyleData, selectClass];
FOR entity:
REF
ANY ← GGObjects.NextEntity[selectGen], GGObjects.NextEntity[selectGen]
UNTIL entity =
NIL
DO
WITH entity SELECT FROM
hotOutline: Outline => {
trajGen ← GGObjects.TrajsInOutline[hotOutline];
FOR hotTraj: Traj ← GGObjects.NextTraj[trajGen], GGObjects.NextTraj[trajGen]
UNTIL hotTraj =
NIL
DO
TrajectoryFireRule[hotTraj, objectBag, gargoyleData];
ENDLOOP;
};
hotSeq: Sequence => SequenceFireRule[hotSeq, objectBag, gargoyleData];
ENDCASE => ERROR;
ENDLOOP;
symmetry groups not yet implemented.
};
FilterSelectedTriggersExcept:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag, selectClass: SelectionClass, exceptTraj: Traj] = {
selectClass vertices and segments are triggers.
selectGen: EntityGenerator;
trajGen: TrajGenerator;
Add selected vertices and segments
selectGen ← GGSelect.SelectedEntities[gargoyleData, selectClass];
FOR entity:
REF
ANY ← GGObjects.NextEntity[selectGen], GGObjects.NextEntity[selectGen]
UNTIL entity =
NIL
DO
WITH entity SELECT FROM
hotOutline: Outline => {
trajGen ← GGObjects.TrajsInOutline[hotOutline];
FOR hotTraj: Traj ← GGObjects.NextTraj[trajGen], GGObjects.NextTraj[trajGen]
UNTIL hotTraj =
NIL
DO
IF hotTraj = exceptTraj THEN LOOP;
TrajectoryFireRule[hotTraj, objectBag, gargoyleData];
ENDLOOP;
};
hotSeq: Sequence => {
IF hotSeq.traj = exceptTraj THEN LOOP;
SequenceFireRule[hotSeq, objectBag, gargoyleData];
};
ENDCASE => ERROR;
ENDLOOP;
symmetry groups not yet implemented.
};
FilterSelectedTriggersExceptClass:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag, selectClass, except: SelectionClass] = {
SelectClass vertices and segments are triggers.
selectGen: EntityGenerator;
trajGen: TrajGenerator;
Add selected vertices and segments, except those selected with the except class.
selectGen ← GGSelect.SelectedEntities[gargoyleData, selectClass];
FOR entity:
REF
ANY ← GGObjects.NextEntity[selectGen], GGObjects.NextEntity[selectGen]
UNTIL entity =
NIL
DO
WITH entity SELECT FROM
selOutline: Outline => {
IF GGSelect.IsSelected[selOutline, gargoyleData, except] THEN LOOP;
trajGen ← GGObjects.TrajsInOutline[selOutline];
FOR selTraj: Traj ← GGObjects.NextTraj[trajGen], GGObjects.NextTraj[trajGen]
UNTIL selTraj =
NIL
DO
TrajectoryFireRule[selTraj, objectBag, gargoyleData];
ENDLOOP;
};
selSeq: Sequence =>
FilterSequenceExceptClass[selSeq, gargoyleData, objectBag, except];
ENDCASE => ERROR;
ENDLOOP;
symmetry groups not yet implemented.
};
FilterUnSelectedPiecesNotHot:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag, selectClass: SelectionClass] = {
selectedGen: EntityGenerator;
union, diff, seq, whole: Sequence;
selectedGen ← GGSelect.SelectedEntities[gargoyleData, selectClass];
FOR selected:
REF
ANY ← GGObjects.NextEntity[selectedGen], GGObjects.NextEntity[selectedGen]
UNTIL selected =
NIL
DO
IF NOT ISTYPE[selected, Sequence] THEN LOOP;
seq ← NARROW[selected];
IF GGSelect.IsSelectedInPart[seq, gargoyleData, hot]
THEN
LOOP;
What we really want is to find the intersection of two sets: Not selected and not hot.
whole ← GGObjects.CreateCompleteSequence[seq.traj];
[union, diff] ← GGObjects.CombineSequences[whole, seq];
IF NOT GGObjects.IsEmptySequence[diff] THEN SequenceFireRule[diff, objectBag, gargoyleData];
ENDLOOP;
Sensitive. Procedures to choose which vertices and segments should themselves be gravity sensitive.
Sensitive
Trajectories:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag] = {
trajGen: TrajGenerator;
trajGen ← GGObjects.TrajsInScene[gargoyleData.scene];
FOR traj: Traj ← GGObjects.NextTraj[trajGen], GGObjects.NextTraj[trajGen]
UNTIL traj =
NIL
DO
GGGravity.AddTrajectory[traj, objectBag];
ENDLOOP;
};
SensitiveSelectedTrajectories:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag, selectClass: SelectionClass] = {
trajGen: TrajGenerator;
trajGen ← GGObjects.TrajsInScene[gargoyleData.scene];
FOR traj: Traj ← GGObjects.NextTraj[trajGen], GGObjects.NextTraj[trajGen]
UNTIL traj =
NIL
DO
IF GGSelect.IsSelectedInPart[traj, gargoyleData, selectClass] THEN GGGravity.AddTrajectory[traj, objectBag];
ENDLOOP;
};
SensitiveTrajectoriesExcept:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag, exceptTraj: Traj] = {
This filter is special since it ignores the menu settings (and hot objects) in determining fire rules. Only the trajectories themselves are added to the objectBag.
trajGen: TrajGenerator;
trajGen ← GGObjects.TrajsInScene[gargoyleData.scene];
FOR traj: Traj ← GGObjects.NextTraj[trajGen], GGObjects.NextTraj[trajGen]
UNTIL traj =
NIL
DO
IF traj = exceptTraj THEN LOOP;
GGGravity.AddTrajectory[traj, objectBag];
ENDLOOP;
};
SensitiveTrajectoriesNotSelected:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag] = {
All unselected trajectories (no part is selected) are added to the objectBag.
trajGen: TrajGenerator;
trajGen ← GGObjects.TrajsInScene[gargoyleData.scene];
FOR traj: Traj ← GGObjects.NextTraj[trajGen], GGObjects.NextTraj[trajGen]
UNTIL traj =
NIL
DO
IF GGSelect.IsSelectedInPart[traj, gargoyleData, normal] THEN LOOP;
GGGravity.AddTrajectory[traj, objectBag];
ENDLOOP;
};
Sensitive
UnSelectedPieces:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag, selectClass: SelectionClass] = {
selectedGen: EntityGenerator;
diff, seq, whole: Sequence;
selectedGen ← GGSelect.SelectedEntities[gargoyleData, selectClass];
FOR selected:
REF
ANY ← GGObjects.NextEntity[selectedGen], GGObjects.NextEntity[selectedGen]
UNTIL selected =
NIL
DO
IF NOT ISTYPE[selected, Sequence] THEN LOOP;
seq ← NARROW[selected];
IF GGSelect.IsSelectedInPart[seq, gargoyleData, hot]
THEN
LOOP;
What we really want is to find the intersection of two sets: Not selected and not hot.
whole ← GGObjects.CreateCompleteSequence[seq.traj];
[----, diff] ← GGObjects.CombineSequences[whole, seq];
IF NOT GGObjects.IsEmptySequence[diff] THEN GGGravity.AddSequence[diff, objectBag];
ENDLOOP;
The appropriate objectBags for different editing actions.
SelectPointMode:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag] = {
Triggers are:
1) All hot vertices, segments, symmetry groups.
Sensitive trajectories are:
FilterSelectedTriggers[gargoyleData, objectBag, hot];
Sensitive.
SensitiveTrajectories[gargoyleData, objectBag];
};
AddMode:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag] = {
Triggers are:
1) All hot vertices, segments, symmetry groups, except the caret trajectory.
2) All parts of the caret trajectory, except the segment being added.
Sensitive trajectories are:
1) All trajectories except the caret trajectory.
2) All parts of the caret trajectory, except the segment being added.
caretTraj: Traj;
caretSeq: Sequence;
[caretTraj, ----, ----, jointNum, ----] ← GGCaret.GetChair[gargoyleData.caret];
FilterSelectedTriggersExcept[gargoyleData, objectBag, hot, caretTraj];
IF caretTraj #
NIL
THEN
{
IF jointNum = 0
THEN
caretSeq ← GGObjects.CreateSimpleSequence[caretTraj, 1, GGObjects.HiJoint[caretTraj]]
ELSE
IF jointNum = GGObjects.HiJoint[caretTraj]
THEN
caretSeq ← GGObjects.CreateSimpleSequence[caretTraj, 0, GGObjects.HiJoint[caretTraj] - 1]
ELSE ERROR;
SequenceFireRule[caretSeq, objectBag, gargoyleData];
};
Sensitive.
SensitiveTrajectoriesExcept[gargoyleData, objectBag, caretTraj];
IF caretTraj # NIL THEN GGGravity.AddSequence[caretSeq, objectBag];
};
DragMode:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag] = {
Triggers are:
1) All hot vertices, segments, symmetry groups (except those being dragged).
2) All unselected pieces of selected trajectories.
Sensitive:
1) All unselected trajectories.
2) All unselected pieces of selected trajectories.
Triggers:
FilterSelectedTriggersExceptClass[gargoyleData, objectBag,
hot,
normal];
All hot vertices, segments, symmetry groups (except those being dragged) should trigger alignment lines.
FilterUnSelectedPiecesNotHot[gargoyleData, objectBag,
normal];
The "not hot" clause is added to avoid duplication.
Sensitive.
SensitiveTrajectoriesNotSelected[gargoyleData, objectBag];
SensitiveUnSelectedPieces[gargoyleData, objectBag, normal];
};
DragStartUpMode:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag] = {
Triggers are:
1) All selected vertices, segments, and symmetry groups.
2) All hot vertices, segments, symmetry groups.
Sensitive:
1) All selected vertices, segments.
2) All hot vertices, segments.
Warning: If an object is both selected and hot, this procedure may let it trigger twice.
Triggers:
FilterSelectedTriggers[gargoyleData, objectBag,
normal];
These are the objects about to be dragged. We want to make it easy to pick them up with the caret. We may also want to select their alignment lines.
FilterSelectedTriggers[gargoyleData, objectBag,
hot];
We also may want the caret to begin on an alignment line triggered by specially designated hot objects.
Sensitive:
SensitiveSelectedTrajectories[gargoyleData, objectBag, normal];
SensitiveSelectedTrajectories[gargoyleData, objectBag, hot];
};
SelectTrajectoryMode:
PRIVATE
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag] = {
Triggers are: none
Sensitive:
1) All vertices and segments.
SensitiveTrajectories[gargoyleData, objectBag];
};
The one procedure to call.
AddItemsForAction:
PUBLIC
PROC [gargoyleData: GargoyleData, objectBag: ObjectBag, action:
ATOM] = {
SELECT action
FROM
$SelectPoint => SelectPointMode[gargoyleData, objectBag];
$Add => AddMode[gargoyleData, objectBag];
$Drag => DragMode[gargoyleData, objectBag];
$DragStartUp => DragStartUpMode[gargoyleData, objectBag];
$SelectTrajectory => SelectTrajectoryMode[gargoyleData, objectBag];
ENDCASE => ERROR;
};
END.