GGGravity.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last edited by Bier on April 6, 1987 3:30:00 pm PDT
Contents: Procedures which take a test point and map it to a point on a nearby set of objects.
Pier, November 20, 1986 10:11:35 am PST
DIRECTORY
GGBasicTypes, GGModelTypes, GGInterfaceTypes, GGSegmentTypes, Imager;
Introduction
Given a test point and a set of gravity active objects (points, lines, circles, and slices), GGGravity finds the nearest gravity active object to a test point.
GGGravity is intended to support a number of Gargoyle dragging features:
1) Selection. In this case we are not interested in the exact point which GGGravity but only in the name of the object deemed to be close. For selection, the environment consists only of visible objects in the Gargoyle scene.
2) Caret placement. We may wish to place the caret on a visible object or on an interesting alignment line. In this case, we care both about the nearest object (e.g. so we can record a touching relationship) and the new point. The environment will consist both of visible objects and of special alignment lines.
3) Intelligent dragging. For each vertex or edge of the objects being dragged, we can perform the gravity mapping, looking for directions in which to move the draggee. This will require arbitrating between multiple attractions. Otherwise, this is like caret placement.
4) Interactive rotation. Some of the alignments of interest during interactive rotation are point alignments. For these GGGravity works as for intelligent dragging. Also, GGGravity can be used in reverse: The environment can be built from the moving object. The test points are present in the scene.
GGGravity cooperates with GGAlign. GGAlign takes a Gargoyle scene and a test object and extracts a set of environment objects which the user might want to snap to (See GGAlign for more details).
Here is a description of the algorithms used to find a nearest object:
It is assumed that the client has already culled from the entire space of environment objects a subset which are currently of interest. Call this the object bag. The client also passes a point testPoint which is to be mapped to a nearby environment object (or left alone if none are near). We consider only environment objects with pass within a radius criticalR of the test point.
These are the only general assumptions. This module implements several specific gravity mappings which obey these assumptions. The main difference between the gravity functions is in their treatment of enviroment points versus environment lines. Some make it easier to select points of intersection (at the cost of making it hard or impossible to select points on the intersecting lines which are near the point of intersection). The mappings considered are:
StrictDistance. The nearest environment object (within criticalR) is chosen, and testPoint is mapped to the nearest point on that object. This makes points of intersection almost impossible to select (unless the intersecting curves end at the point as for a polygon vertex).
InnerCircle. Another radius innerR (innerR < criticalR) is chosen in which environment points are favored. In particular, testPoint is mapped to the nearest environment point within innerR, if any. If there are none, than the closest line within innerR is chosen. If there are none, then we fall back on StrictDistance. InnerCircle makes points easy to select even in environments full of lines. However, selecting points on lines near points can be impossible.
Implementation
GGGravity builds a data structure which represents a number of points, lines, circles, and trajectories. It then answers the query "which objects are near this test point". GGGravity is optimized for the case where queries are more common than changes to the environment.
GGGravity: CEDAR DEFINITIONS = BEGIN
AlignmentObject: TYPE = GGModelTypes.AlignmentObject;
Angle: TYPE = GGBasicTypes.Angle;
BoundBox: TYPE = GGModelTypes.BoundBox;
Caret: TYPE = GGInterfaceTypes.Caret;
Circle: TYPE = GGBasicTypes.Circle;
Edge: TYPE = GGBasicTypes.Edge;
FeatureData: TYPE = GGInterfaceTypes.FeatureData;
GGData: TYPE = GGInterfaceTypes.GGData;
Line: TYPE = GGBasicTypes.Line;
AlignBag: TYPE = GGInterfaceTypes.AlignBag;
Outline: TYPE = GGModelTypes.Outline;
Point: TYPE = GGBasicTypes.Point;
Segment: TYPE = GGSegmentTypes.Segment;
Slice: TYPE = GGModelTypes.Slice;
SliceParts: TYPE = GGModelTypes.SliceParts;
Traj: TYPE = GGModelTypes.Traj;
TriggerBag: TYPE = GGInterfaceTypes.TriggerBag;
Vector: TYPE = GGBasicTypes.Vector;
Building the Trigger Bag
FeatureFromOutline: PROC [outline: Outline, parts: SliceParts ← NIL] RETURNS [feature: FeatureData];
FeatureFromSlice: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [feature: FeatureData];
FeatureFromAnchor: PROC [anchor: Caret] RETURNS [feature: FeatureData];
Building the Object Bag
JointAddSlopeLine: PROC [degrees: REAL, direction: Vector, point: Point, objectBag: AlignBag] RETURNS [feature: FeatureData];
Adds the new feature to objectBag and returns it for the caller's inspection.
JointAddCircle: PROC [radius: REAL, point: Point, objectBag: AlignBag] RETURNS [feature: FeatureData];
Adds the new feature to objectBag and returns it for the caller's inspection.
SegmentAddTwoAngleLines: PROC [degrees: REAL, segNum: NAT, lo, hi: Point, objectBag: AlignBag] RETURNS [line1, line2: FeatureData];
Adds the new feature to objectBag and returns it for the caller's inspection.
SegmentAddDistanceLines: PROC [distance: REAL, segNum: NAT, lo, hi: Point, objectBag: AlignBag] RETURNS [line1, line2: FeatureData];
Adds the new feature to objectBag and returns it for the caller's inspection.
SegmentAddMidpoint: PROC [segNum: NAT, lo, hi: Point, objectBag: AlignBag] RETURNS [feature: FeatureData];
Adds the new feature to objectBag and returns it for the caller's inspection.
Drawing Alignment Objects
DrawFeatureList: PROC [dc: Imager.Context, alignObjects: LIST OF FeatureData, ggData: GGData];
DrawAlignBagRegardless: PROC [dc: Imager.Context, objectBag: AlignBag, ggData: GGData];
Draws all objects in the object bag regardless of how they have been marked.
Gravity
UniMap: PROC [testPoint: Point, criticalR: REAL, currentObjects: AlignBag, activeObjects: TriggerBag, ggData: GGData, intersections: BOOL] RETURNS [resultPoint: Point, feature: FeatureData, hitData: REF ANY];
Dispatches to StrictDistance, InnerCircle, or does nothing depending on the currently selected gravity type. If intersections is TRUE, compute the intersections of the objects that are in the bags. This procedure always uses the old Uni-gravity.
Possible Future Offerings.
Symmetry Firing Rules: SymmetryAddLines, SymmetryAddPoints
SymmetryType: TYPE = {cyclic, mirror};
SymmetryGroup: TYPE = REF SymmetryGroupObj;
SymmetryGroupObj: TYPE = RECORD [
type: SymmetryType,
order: NAT
];
SymmetryAddLines: PROC [group: SymmetryGroup, entity: REF ANY, objectBag: AlignBag];
SymmetryAddPoints: PROC [group: SymmetryGroup, entity: REF ANY, objectBag: AlignBag];
For the future when application programmers may wish to add their own classes of AlignmentObject.
PointFilterProc: TYPE = PROC [Point] RETURNS [LIST OF AlignmentObject];
SegmentFilterProc: TYPE = PROC [lo, hi: Point] RETURNS [LIST OF AlignmentObject];
PointAddSlopeLine: PROC [point: Point] RETURNS [line: LIST OF AlignmentObject];
PointAddCircle: PROC [point: Point] RETURNS [circle: LIST OF AlignmentObject];
SegmentAddAngleLines: PROC [segNum: NAT, lo, hi: Point] RETURNS [lines: LIST OF AlignmentObject];
SegmentAddDistLines: PROC [segNum: NAT, lo, hi: Point] RETURNS [lines: LIST OF AlignmentObject];
NewGravityPool: PROC [] RETURNS [REF]; -- stored in GGData; temporary storage pool used by GGGravityImpl
END.