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.
SensitiveTrajectories: 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;
};
SensitiveUnSelectedPieces: 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:
1) All.
Triggers.
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;
jointNum: NAT;
Triggers.
[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.