GGMultiGravityImpl.mesa
Copyright Ó 1986, 1987, 1989, 1992 by Xerox Corporation. All rights reserved.
Contents: Performs hit testing similar to uniGravity. Instead of returning a single nearest feature, we return the N (or fewer) nearest features which are within a given tolerance distance from the test point. The algorithm used is described in [Cyan]<Gargoyle>Documentation>MultiGravity.tioga.
Pier, February 18, 1992 5:16 pm PST
Bier, April 11, 1990 2:39:57 pm PDT
Eisenman, August 11, 1987 6:14:13 pm PDT
Doug Wyatt, April 10, 1992 12:09 pm PDT
DIRECTORY
CodeTimer, Cubic2, CubicPaths, GGAlign, GGBasicTypes, GGCaret, GGCircles, GGCoreTypes, GGInterfaceTypes, GGModelTypes, GGMultiGravity, GGParent, GGScene, GGSegmentTypes, GGSliceOps, GGState, GGUtility, Lines2d, Real, RealFns, Rope, Vectors2d;
GGMultiGravityImpl: CEDAR PROGRAM
IMPORTS CodeTimer, CubicPaths, GGAlign, GGCaret, GGCircles, GGParent, GGScene, GGSliceOps, GGState, Lines2d, RealFns, Vectors2d
EXPORTS GGMultiGravity = BEGIN
AlignBag: TYPE = REF AlignBagObj;
AlignBagObj: TYPE = GGInterfaceTypes.AlignBagObj;
AlignmentCircle: TYPE = GGInterfaceTypes.AlignmentCircle;
AlignmentLine: TYPE = GGInterfaceTypes.AlignmentLine;
AlignmentPoint: TYPE = REF AlignmentPointObj;
AlignmentPointObj: TYPE = GGInterfaceTypes.AlignmentPointObj;
Arc: TYPE = GGBasicTypes.Arc;
Bezier: TYPE = Cubic2.Bezier;
BezierRef: TYPE = REF Bezier;
Caret: TYPE = GGInterfaceTypes.Caret;
Circle: TYPE = GGBasicTypes.Circle;
Edge: TYPE = GGBasicTypes.Edge;
FeatureCycler: TYPE = REF FeatureCyclerObj;
FeatureCyclerObj: TYPE = GGInterfaceTypes.FeatureCyclerObj;
FeatureData: TYPE = REF FeatureDataObj;
FeatureDataObj: TYPE = GGModelTypes.FeatureDataObj;
GGData: TYPE = GGInterfaceTypes.GGData;
GoodPoint: TYPE = REF GoodPointObj;
GoodPointObj: TYPE = GGInterfaceTypes.GoodPointObj;
GravityType: TYPE = GGInterfaceTypes.GravityType;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
Line: TYPE = GGCoreTypes.Line;
NearVertexEdgeAndFaces: TYPE = REF NearVertexEdgeAndFacesObj;
NearVertexEdgeAndFacesObj: TYPE = GGInterfaceTypes.NearVertexEdgeAndFacesObj;
Point: TYPE = GGBasicTypes.Point;
PointPairAndDone: TYPE = GGModelTypes.PointPairAndDone;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
Sequence: TYPE = GGModelTypes.Sequence;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
TriggerBag: TYPE = REF TriggerBagObj;
TriggerBagObj: TYPE = GGInterfaceTypes.TriggerBagObj;
Vector: TYPE = GGBasicTypes.Vector;
BestPoints: TYPE = REF BestPointsObj;
BestPointsObj: TYPE = RECORD [
size: NAT,
max, min: REAL,
maxIndex: NAT,
bestTossed: REAL, -- the distance of the closest object that has been thrown away
dTol: REAL, -- min + s
innerR: REAL, -- find all curves within this radius even if they are not neighbors of the nearest
s: REAL, -- the size of neighborhoods. BestPoints should contain all objects that have been seen such that min <= dist(o, q) <= min+s, unless BestPoints overflows.
overflow: BOOL,
points: SEQUENCE len: NAT OF GoodPoint];
BestFaces: TYPE = REF BestFacesObj;
BestFacesObj: TYPE = RECORD [
size: NAT,
maxPriority, minPriority: INT,
minIndex: NAT,
bestPriorityTossed: INT,
priorityTol: INT, -- the farthest overlap level that we are interested in
overflow: BOOL,
faces: SEQUENCE len: NAT OF GoodPoint];
MultiGravityPool: TYPE = REF MultiGravityPoolObj;
MultiGravityPoolObj: TYPE = RECORD [
bestpoints: BestPoints,
bestcurves: BestPoints,
bestfaces: BestFaces
];
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE;
Arbitration
[Artwork node; type 'ArtworkInterpress on' to command tool]
We arbitrate between those points which are within an epsilon-width ring of the nearest point.
Map: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
Dispatches to StrictDistance, PointsPreferred, 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.
ENABLE UNWIND => {
ggData.multiGravityPool ¬ NewMultiGravityPool[];
In case an ABORT happened while pool was in use
};
gravityType: GravityType ¬ GGState.GetGravityType[ggData];
CodeTimer.StartInt[$MultiMap, $Gargoyle];
IF GGState.GetGravity[ggData] THEN {
SELECT gravityType FROM
pointsPreferred =>
[resultPoint, normal, feature, hitData] ¬ PointsPreferred[testPoint, t, alignBag, sceneBag, ggData, intersections];
linesPreferred, facesPreferred =>
[resultPoint, normal, feature, hitData] ¬ LinesPreferred[testPoint, t, alignBag, sceneBag, ggData];
ENDCASE => ERROR;
}
ELSE {
resultPoint ¬ testPoint;
feature ¬ NIL;
normal ¬ [0,-1];
};
CodeTimer.StopInt[$MultiMap, $Gargoyle];
};
PointsPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
Calls MultiPointsPreferred and picks a single "best" feature. If there are no good features within radius t of testPoint, then resultPoint = testPoint and feature = hitData = NIL. If intersections is TRUE then compute all pair-wise intersections of gravity-active curves and count them as distinguished points.
count: NAT;
nearVEF: NearVertexEdgeAndFaces;
[nearVEF, count] ¬ MultiPointsPreferred[testPoint, t, alignBag, sceneBag, ggData, intersections];
IF count = 0 THEN RETURN [testPoint, [0,-1], NIL, NIL];
IF count = 1 THEN RETURN PrepareWinner[nearVEF, 0]
ELSE {
Otherwise, let's do arbitration.
neighborCount: NAT ¬ 1;
s: REAL = 0.072; -- 1/1000 inches
nearestDist: REAL ¬ -1;
nearestDist ¬ nearVEF[0].dist;
FOR i: NAT IN [1..count) DO
IF nearVEF[i].dist - nearestDist < s THEN neighborCount ¬ neighborCount + 1;
ENDLOOP;
IF neighborCount = 1 THEN RETURN PrepareWinner[nearVEF, 0];
Prefer scene objects to alignment lines.
Later, we will prefer objects that say the testpoint is "inside" them.
FOR i: NAT IN [0..neighborCount) DO
IF nearVEF[i].featureData.type = slice THEN {
RETURN PrepareWinner[nearVEF, i];
};
REPEAT
FINISHED => RETURN PrepareWinner[nearVEF, 0];
ENDLOOP;
};
};
LinesPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
Calls MultiLinesPreferred and picks a single "best" feature. If there are no good features within radius t of testPoint, then resultPoint = testPoint and feature = hitData = NIL.
nearVEF: NearVertexEdgeAndFaces;
count: NAT;
[nearVEF, count] ¬ MultiLinesPreferred[testPoint, t, alignBag, sceneBag, ggData];
IF count = 0 THEN RETURN [testPoint, [0,-1], NIL, NIL];
IF count = 1 THEN RETURN PrepareWinner[nearVEF, 0]
ELSE {
This code will be used when we wish to do more sophisticated arbitration:
<<nearestDist: REAL ¬ -1;
bestSceneObject: INT ¬ -1;
neighborCount: NAT ¬ 1;
s: REAL = 0.072; -- 1/1000 inches
nearestDist ¬ nearVEF[0].dist;
FOR i: NAT IN [1..count) DO
IF nearVEF[i].dist - nearestDist < s THEN neighborCount ¬ neighborCount + 1;
ENDLOOP;
IF neighborCount = 1 THEN RETURN PrepareWinner[nearVEF, 0];
We have more than one "equally close" features. Now we choose on the following basis:
1) Prefer scene objects to alignment lines.
2) Prefer points to lines.
Later, we will prefer objects that say the testpoint is "inside" them to those that don't.
bestSceneObject ¬ -1;>>
RETURN PrepareWinner[nearVEF, 0];
};
};
FacesPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
Calls MultiFacesPreferred and picks a single "best" feature. If there is a filled region that contains the cursor point, it choose the nearest such filled region. Otherwise, it behaves just like LinesPreferred gravity.
nearVEF: NearVertexEdgeAndFaces;
count: NAT;
[nearVEF, count] ¬ MultiFacesPreferred[testPoint, t, alignBag, sceneBag, ggData];
IF count = 0 THEN RETURN [testPoint, [0,-1], NIL, NIL];
RETURN PrepareWinner[nearVEF, 0]
};
PrepareWinner: PROC [nearVEF: NearVertexEdgeAndFaces, index: NAT] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
goodPoint: GoodPoint ¬ nearVEF[index];
resultPoint ¬ goodPoint.point;
normal ¬ goodPoint.normal;
feature ¬ goodPoint.featureData;
hitData ¬ goodPoint.hitData;
};
Cycling Gravity
EmptyCycler: PUBLIC PROC [testPoint: Point] RETURNS [featureCycler: FeatureCycler] = {
featureCycler ¬ NEW[FeatureCyclerObj ¬ [
nearVEF: NIL,
count: 0,
index: -1,
testPoint: testPoint
]];
};
MapCycler: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [featureCycler: FeatureCycler] = {
ENABLE UNWIND => {
ggData.multiGravityPool ¬ NewMultiGravityPool[];
In case an ABORT happened while pool was in use
};
gravityType: GravityType ¬ GGState.GetGravityType[ggData];
IF GGState.GetGravity[ggData] THEN {
SELECT gravityType FROM
pointsPreferred =>
featureCycler ¬ PointsPreferredCycler[testPoint, t, alignBag, sceneBag, ggData, intersections];
linesPreferred, facesPreferred =>
featureCycler ¬ LinesPreferredCycler[testPoint, t, alignBag, sceneBag, ggData];
ENDCASE => ERROR;
}
ELSE {
featureCycler ¬ EmptyCycler[testPoint];
};
};
CopyVEF: PROC [vef: NearVertexEdgeAndFaces, count: NAT] RETURNS [copy: NearVertexEdgeAndFaces] = {
oldGoodPoint, newGoodPoint: GoodPoint;
copy ¬ NEW[NearVertexEdgeAndFacesObj[count]];
FOR i: NAT IN [0..count) DO
oldGoodPoint ¬ vef[i];
newGoodPoint ¬ NEW[GoodPointObj ¬ [
dist: oldGoodPoint.dist,
point: oldGoodPoint.point,
normal: oldGoodPoint.normal,
featureData: oldGoodPoint.featureData,
hitData: oldGoodPoint.hitData,
dimension: oldGoodPoint.dimension]];
copy[i] ¬ newGoodPoint;
ENDLOOP;
};
PointsPreferredCycler: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE, maxDimension: [0..1] ¬ 1] RETURNS [featureCycler: FeatureCycler] = {
count: NAT;
nearVEF: NearVertexEdgeAndFaces;
BEGIN
[nearVEF, count] ¬ MultiPointsPreferred[testPoint, t, alignBag, sceneBag, ggData, intersections];
IF count = 0 THEN RETURN[EmptyCycler[testPoint]];
IF count = 1 THEN GOTO MakeSimpleCycler
ELSE {
Otherwise, let's do arbitration. Find the nearest slice if one is close enough. Otherwise, start with the nearest alignment object.
neighborCount: NAT ¬ 1;
s: REAL = 0.072; -- 1/1000 inches
nearestDist: REAL ¬ -1;
featureCycler ¬ NEW[FeatureCyclerObj ¬ [
nearVEF: NIL, -- will be filled in below
count: count,
index: -1, -- will be filled in below
testPoint: testPoint
]];
nearestDist ¬ nearVEF[0].dist;
FOR i: NAT IN [1..count) DO
IF nearVEF[i].dist - nearestDist < s THEN neighborCount ¬ neighborCount + 1;
ENDLOOP;
IF neighborCount = 1 THEN GOTO MakeSimpleCycler;
featureCycler.nearVEF ¬ CopyVEF[nearVEF, count];
Start cycling at the first slice.
FOR i: NAT IN [0..neighborCount) DO
IF nearVEF[i].featureData.type = slice THEN {
featureCycler.index ¬ i;
RETURN;
};
REPEAT
FINISHED => featureCycler.index ¬ 0;
ENDLOOP;
};
EXITS
MakeSimpleCycler => {
featureCycler ¬ NEW[FeatureCyclerObj ¬ [
nearVEF: CopyVEF[nearVEF, count],
count: count,
index: 0,
testPoint: testPoint
]];
};
END;
};
LinesPreferredCycler: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [featureCycler: FeatureCycler] = {
Find the nearest line and any that are almost as near, if any lines exist with radius t. Choose one of these lines. If the winner is an edge, use its curve normal.
If no object is found, use the cursor position.
nearVEF: NearVertexEdgeAndFaces;
count: NAT;
BEGIN
[nearVEF, count] ¬ MultiLinesPreferred[testPoint, t, alignBag, sceneBag, ggData];
IF count = 0 THEN RETURN[EmptyCycler[testPoint]];
IF count = 1 THEN GOTO MakeSimpleCycler
ELSE {
nearestDist: REAL ¬ -1;
bestSceneObject: INT ¬ -1;
neighborCount: NAT ¬ 1;
s: REAL = 0.072; -- 1/1000 inches
nearestDist ¬ nearVEF[0].dist;
Find how many nearest neighbors there are.
FOR i: NAT IN [1..count) DO
IF nearVEF[i].dist - nearestDist < s THEN neighborCount ¬ neighborCount + 1;
ENDLOOP;
IF neighborCount = 1 THEN GOTO MakeSimpleCycler;
bestSceneObject ¬ -1;
featureCycler ¬ NEW[FeatureCyclerObj ¬ [
nearVEF: CopyVEF[nearVEF, count],
count: count,
index: 0,
testPoint: testPoint
]];
};
EXITS
MakeSimpleCycler => {
featureCycler ¬ NEW[FeatureCyclerObj ¬ [
nearVEF: CopyVEF[nearVEF, count],
count: count,
index: 0,
testPoint: testPoint
]];
};
END;
};
FacesPreferredCycler: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [featureCycler: FeatureCycler] = {
nearVEF: NearVertexEdgeAndFaces;
count: NAT;
BEGIN
[nearVEF, count] ¬ MultiFacesPreferred[testPoint, t, alignBag, sceneBag, ggData];
IF count = 0 THEN RETURN[EmptyCycler[testPoint]];
IF count = 1 THEN GOTO MakeSimpleCycler
ELSE {
GOTO MakeSimpleCycler;
This may come in handy some day for extra arbitration
<<nearestDist: REAL ¬ -1;
bestSceneObject: INT ¬ -1;
neighborCount: NAT ¬ 1;
s: REAL = 0.072; -- 1/1000 inches
nearestDist ¬ nearVEF[0].dist;
Find how many nearest neighbors there are.
FOR i: NAT IN [1..count) DO
IF nearVEF[i].dist - nearestDist < s THEN neighborCount ¬ neighborCount + 1;
ENDLOOP;
IF neighborCount = 1 THEN GOTO MakeSimpleCycler;
bestSceneObject ¬ -1;
featureCycler ¬ NEW[FeatureCyclerObj ¬ [
nearVEF: CopyVEF[nearVEF, count],
count: count,
index: 0,
testPoint: testPoint
]];>>
};
EXITS
MakeSimpleCycler => {
featureCycler ¬ NEW[FeatureCyclerObj ¬ [
nearVEF: CopyVEF[nearVEF, count],
count: count,
index: 0,
testPoint: testPoint
]];
};
END;
};
FirstFeature: PUBLIC PROC [featureCycler: FeatureCycler] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
RETURN GetFeature[featureCycler, 0];
};
NextFeature: PUBLIC PROC [featureCycler: FeatureCycler] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
RETURN GetFeature[featureCycler, 1];
};
PreviousFeature: PUBLIC PROC [featureCycler: FeatureCycler] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
RETURN GetFeature[featureCycler, -1];
};
GetFeature: PUBLIC PROC [featureCycler: FeatureCycler, move: [-1..1]] RETURNS [resultPoint: Point, normal: Vector, feature: FeatureData, hitData: REF ANY] = {
IF featureCycler = NIL THEN { -- return a backdrop position
resultPoint ¬ [0.0, 0.0]; -- bogus
normal ¬ [0,-1];
feature ¬ NIL;
hitData ¬ NIL;
}
ELSE IF featureCycler.count = 0 THEN { -- return a backdrop position
resultPoint ¬ featureCycler.testPoint;
normal ¬ [0,-1];
feature ¬ NIL;
hitData ¬ NIL;
}
ELSE {
featureCycler.index ¬ (featureCycler.index + move + featureCycler.count) MOD featureCycler.count;
[resultPoint, normal, feature, hitData] ¬ PrepareWinner[featureCycler.nearVEF, featureCycler.index];
};
};
Multi-Gravity Routines
MultiMap: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [nearVEF: NearVertexEdgeAndFaces, count: NAT] = {
ENABLE UNWIND => ggData.multiGravityPool ¬ NewMultiGravityPool[]; -- in case an ABORT happened while pool was in use
CodeTimer.StartInt[$MultiMap, $Gargoyle];
IF GGState.GetGravity[ggData] THEN {
SELECT GGState.GetGravityType[ggData] FROM
facesPreferred =>
[nearVEF, count] ← MultiFacesPreferred[testPoint, t, alignBag, sceneBag, ggData];
linesPreferred =>
[nearVEF, count] ¬ MultiLinesPreferred[testPoint, t, alignBag, sceneBag, ggData];
pointsPreferred =>
[nearVEF, count] ¬ MultiPointsPreferred[testPoint, t, alignBag, sceneBag, ggData, intersections];
ENDCASE => ERROR;
}
ELSE {
nearVEF ¬ NIL;
count ¬ 0;
};
CodeTimer.StopInt[$MultiMap, $Gargoyle];
};
MultiFacesPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [nearVEF: NearVertexEdgeAndFaces, count: NAT] = {
Returns up to MaxFeatures closest features, their closest points, and their distances from the testpoint. Features outside of criticalR, will not be included. The results will be located in nearVEF[0] .. nearVEF[count-1].
bestCurves, bestPoints: BestPoints;
bestFaces: BestFaces;
faceCount, curveCount, pointCount: NAT;
IF GGAlign.EmptyAlignBag[alignBag] AND GGAlign.EmptyTriggerBag[sceneBag] THEN RETURN[NIL, 0];
[bestFaces, faceCount] ¬ FacesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, t];
SortByOverlap[bestFaces, faceCount];
[bestCurves, curveCount] ¬ CurvesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, t, 0];
[bestPoints, pointCount] ¬ VertsInNeighborhoodPlus[bestCurves, curveCount, alignBag, sceneBag, testPoint, t, ggData, FALSE];
SortPoints[bestPoints, pointCount];
count ¬ MIN[pointCount + curveCount, MaxFeatures];
nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[count]];
MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearVEF, count];
[nearVEF, count] ¬ MergeByOverlapAndDistance[bestFaces, faceCount, nearVEF, count];
};
MultiLinesPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData] RETURNS [nearVEF: NearVertexEdgeAndFaces, count: NAT] = {
Returns up to MaxFeatures closest features, their closest points, and their distances from the testpoint. Features outside of criticalR, will not be included. The results will be located in nearVEF[0] .. nearVEF[count-1].
bestCurves: BestPoints;
bestPoints: BestPoints;
pointCount, curveCount: NAT;
IF GGAlign.EmptyAlignBag[alignBag] AND GGAlign.EmptyTriggerBag[sceneBag] THEN RETURN[NIL, 0];
Caution: bestCurves and bestPoints are allocated from a pool. Copy them before returning them to the user (e.g. in a cycler).
[bestCurves, curveCount] ¬ CurvesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, t, 0];
[bestPoints, pointCount] ¬ VertsInNeighborhoodPlus[bestCurves, curveCount, alignBag, sceneBag, testPoint, t, ggData, FALSE];
SortPoints[bestPoints, pointCount];
count ¬ MIN[pointCount + curveCount, MaxFeatures];
nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[count]];
MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearVEF, count];
};
MultiPointsPreferred: PUBLIC PROC [testPoint: Point, t: REAL, alignBag: AlignBag, sceneBag: TriggerBag, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [nearVEF: NearVertexEdgeAndFaces, count: NAT] = {
Returns up to MaxFeatures closest features, their closest points, and their distances from the testpoint. Features outside of criticalR, will not be included. The results will be located in nearVEF[0] .. nearVEF[count-1]. If any points are within the inner radius innerR, then only points (e.g. vertices, control points, and intersection points) will be mentioned. Otherwise, nearVEF may consist of a mixture of points and curves.
bestCurves: BestPoints;
bestPoints: BestPoints;
pointCount, curveCount: NAT;
innerR: REAL ¬ t/2.0;
IF GGAlign.EmptyAlignBag[alignBag] AND GGAlign.EmptyTriggerBag[sceneBag] THEN RETURN[NIL, 0];
Caution: bestCurves and bestPoints are allocated from a pool. Copy them before returning them to the user (e.g. in a cycler)
[bestCurves, curveCount] ¬ CurvesInNeighborhoodPlus[alignBag, sceneBag, testPoint, ggData, t, innerR];
[bestPoints, pointCount] ¬ VertsInNeighborhoodPlus[bestCurves, curveCount, alignBag, sceneBag, testPoint, t, ggData, intersections];
SortPoints[bestPoints, pointCount];
IF pointCount > 0 AND bestPoints[0].dist < innerR THEN {
count ¬ pointCount;
nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[count]];
NearPointsFromPoints[bestPoints, pointCount, nearVEF];
}
ELSE {
count ¬ MIN[pointCount + curveCount, MaxFeatures];
nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[count]];
MergePointsAndCurves[bestPoints, pointCount, bestCurves, curveCount, nearVEF, count];
};
};
VertsInNeighborhoodPlus: PROC [bestCurves: BestPoints, curveCount: NAT, alignBag: AlignBag, sceneBag: TriggerBag, q: Point, t: REAL, ggData: GGData, intersections: BOOL ¬ FALSE] RETURNS [h: BestPoints, pointCount: NAT] = {
thisPoint: GoodPoint;
For each gravity active point, find its distance from the testpoint. Package this information up into the thisPoint record. Then call AddNeighbor, which will add this point to the list of best points, if appropriate.
When VertsInNeighborhoodPlus returns, h will contain a set of up to MaxFeatures points all of which are within a distance t of q.
If h.overflow is FALSE, h contains the nearest point in the scene (distance h.min from q) and all other points o such that dist(o, q) <= h.min + h.s, and dist(o, q) <= t.
If h.overflow is TRUE, there were more than MaxFeatures such points. In this case, h includes the nearest n such points n = MaxFeatures.
ProcessPoint: PROC [thisPoint: GoodPoint, featureData: FeatureData] = { -- used for the anchor
dSquared: REAL;
dTolSquared: REAL ¬ dTol*dTol;
dSquared ¬ Vectors2d.DistanceSquared[thisPoint.point, q];
thisPoint.hitData ¬ NIL;
IF dSquared < dTolSquared THEN {
thisPoint.dist ¬ RealFns.SqRt[dSquared];
thisPoint.normal ← [0,-1]; -- eventually this should just agree with the anchor orientation
thisPoint.normal ¬ GGCaret.GetNormal[NARROW[featureData.shape]]; -- the anchor
IF Vectors2d.MagnitudeSquared[thisPoint.normal] = 0 THEN thisPoint.normal ¬ [0, -1];
thisPoint.featureData ¬ featureData;
thisPoint.priority ¬ -1;
dTol ¬ AddNeighbor[thisPoint, h, FALSE];
};
};
ProcessSlice: PROC [sliceD: SliceDescriptor, thisPoint: GoodPoint, featureData: FeatureData] = {
[thisPoint.point, thisPoint.dist, thisPoint.normal, thisPoint.hitData, success] ¬ sliceD.slice.class.closestPoint[sliceD, q, dTol];
IF success THEN {
IF thisPoint.dist < dTol THEN {
thisPoint.featureData ¬ featureData;
IF Vectors2d.MagnitudeSquared[thisPoint.normal] = 0 THEN {
thisPoint.normal ¬ [0, -1];
};
thisPoint.priority ¬ GGScene.GetPriority[ggData.scene, sliceD.slice];
dTol ¬ AddNeighbor[thisPoint, h, FALSE];
};
};
};
DoForSliceTrigger: GGAlign.FeatureWalkProc = {
FeatureWalkProc: TYPE = PROC [feature: FeatureData] RETURNS [done: BOOLFALSE];
sliceD ¬ NARROW[feature.shape, SliceDescriptor];
ProcessSlice[sliceD, thisPoint, feature];
};
sliceD: SliceDescriptor;
featureData: FeatureData;
success: BOOL ¬ FALSE;
dTol: REAL ¬ t;
midpoints: BOOL ¬ GGState.GetMidpoints[ggData];
thisPoint ¬ NEW[GoodPointObj];
h ¬ BestPointsFromPool[ggData, t];
IF intersections THEN {
dTol ¬ FindIntersections[bestCurves, curveCount, thisPoint, q, dTol, h, ggData.scene];
IF midpoints THEN dTol ¬ FindMidpoints[bestCurves, curveCount, thisPoint, q, dTol, h, ggData.scene];
};
GGAlign.WalkSliceTriggers[sceneBag, DoForSliceTrigger];
featureData ¬ alignBag.anchor;
IF featureData # NIL THEN {
anchor: Caret ¬ NARROW[featureData.shape];
IF NOT GGCaret.Exists[anchor] THEN ERROR;
thisPoint.point ¬ GGCaret.GetPoint[anchor];
ProcessPoint[thisPoint, featureData];
};
pointCount ¬ h.size;
IF h.overflow THEN {
CodeTimer.StartInt[$PointOverflow, $Gargoyle];
CodeTimer.StopInt[$PointOverflow, $Gargoyle];
};
}; -- end of VertsInNeighborhoodPlus
FindIntersections: PROC [bestCurves: BestPoints, curveCount: NAT, thisPoint: GoodPoint, q: Point, tolerance: REAL, h: BestPoints, scene: Scene] RETURNS [dTol: REAL] = {
largeTolerance: REAL = 9999.00;
curveI, curveJ: GoodPoint;
theseIPoints: LIST OF Point;
thisTangency, tangentList: LIST OF BOOL;
success: BOOL ¬ FALSE;
dTol ¬ tolerance;
FOR i: NAT IN [0..curveCount) DO
curveI ¬ bestCurves[i];
IF curveI.dist > dTol THEN EXIT; -- this curve is too far away (and the rest are farther)
FOR j: NAT IN [0..i] DO
curveJ ¬ bestCurves[j];
IF curveJ.dist > dTol THEN LOOP; -- this curve is too far away to be worth considering
[theseIPoints, thisTangency] ¬ CurveMeetsCurve[curveI, curveJ];
tangentList ¬ thisTangency;
FOR list: LIST OF Point ¬ theseIPoints, list.rest UNTIL list = NIL DO
thisPoint.point ¬ list.first;
thisPoint.dist ¬ Vectors2d.Distance[thisPoint.point, q];
success ¬ thisPoint.dist <= dTol;
IF success THEN {
featureData: FeatureData ¬ NEW[FeatureDataObj];
alignmentPoint: AlignmentPoint ¬ NEW[AlignmentPointObj ¬ [
point: thisPoint.point,
tangent: tangentList.first,
curve1: curveI.featureData,
curve2: curveJ.featureData]];
featureData.type ¬ intersectionPoint;
featureData.shape ¬ alignmentPoint;
thisPoint.featureData ¬ featureData;
thisPoint.priority ¬ -1;
IF curveI.featureData.type = slice THEN {
sliceD: SliceDescriptor ¬ NARROW[curveI.featureData.shape];
slice: Slice ¬ sliceD.slice;
thisPoint.priority ¬ GGScene.GetPriority[scene, slice];
thisPoint.hitData ¬ curveI.hitData;
thisPoint.normal ¬ GGSliceOps.ClosestSegment[sliceD, q, dTol].bestNormal; -- find normal at nearest vertex. Works for straight edges
}
ELSE IF curveJ.featureData.type = slice THEN {
sliceD: SliceDescriptor ¬ NARROW[curveJ.featureData.shape];
slice: Slice ¬ sliceD.slice;
thisPoint.priority ¬ MIN[thisPoint.priority, GGScene.GetPriority[scene, slice]];
thisPoint.hitData ¬ curveJ.hitData;
thisPoint.normal ¬ GGSliceOps.ClosestSegment[sliceD, q, dTol].bestNormal; -- find normal at nearest vertex. Works for straight edges
}
ELSE thisPoint.hitData ¬ NIL;
IF Vectors2d.MagnitudeSquared[thisPoint.normal] = 0 THEN thisPoint.normal ¬ [0, -1];
dTol ¬ AddNeighbor[thisPoint, h, FALSE];
};
tangentList ¬ tangentList.rest;
ENDLOOP;
ENDLOOP;
ENDLOOP;
};
FindMidpoints: PROC [bestCurves: BestPoints, curveCount: NAT, thisPoint: GoodPoint, q: Point, tolerance: REAL, h: BestPoints, scene: Scene] RETURNS [dTol: REAL] = {
largeTolerance: REAL = 9999.00;
curve: GoodPoint;
midpoint: Point;
success: BOOL ¬ FALSE;
dTol ¬ tolerance;
FOR i: NAT IN [0..curveCount) DO
curve ¬ bestCurves[i];
IF curve.featureData.type#slice THEN LOOP;
[midpoint, success] ¬ ComputeMidpoint[curve];
IF NOT success THEN LOOP;
thisPoint.point ¬ midpoint;
thisPoint.dist ¬ Vectors2d.Distance[thisPoint.point, q];
success ¬ thisPoint.dist <= dTol;
IF success THEN {
sliceD: SliceDescriptor ¬ NARROW[curve.featureData.shape];
slice: Slice ¬ sliceD.slice;
featureData: FeatureData ¬ NEW[FeatureDataObj];
alignmentPoint: AlignmentPoint ¬ NEW[AlignmentPointObj ¬ [
point: thisPoint.point,
tangent: FALSE,
curve1: curve.featureData,
curve2: NIL]];
featureData.type ¬ midpoint;
featureData.shape ¬ alignmentPoint;
thisPoint.featureData ¬ featureData;
thisPoint.hitData ¬ curve.hitData;
thisPoint.normal ← [0, -1]; -- Should figure out midpoint's normal for slice.  -- Only edge and arc work now anyway.
thisPoint.normal ¬ GGSliceOps.ClosestSegment[sliceD, q, dTol].bestNormal; -- find normal at nearest vertex. Works for edges
IF Vectors2d.MagnitudeSquared[thisPoint.normal] = 0 THEN thisPoint.normal ¬ [0, -1];
thisPoint.priority ¬ GGScene.GetPriority[scene, slice];
dTol ¬ AddNeighbor[thisPoint, h, FALSE];
};
ENDLOOP;
};
CurvesInNeighborhoodPlus: PROC [alignBag: AlignBag, sceneBag: TriggerBag, q: Point, ggData: GGData, t: REAL, innerR: REAL] RETURNS [h: BestPoints, curveCount: NAT] = {
Returns the curves in the near band in order of increasing distance from q.
For each gravity active object, find the distance of the gravity active object from the testpoint. Package this information up into the thisCurve record. Then call AddNeighbor, which will add this curve to the list of best curves, if appropriate.
When CurvesInNeighborhood returns, h should contain some number of curves such that they are all within t of q.
If h.overflow is FALSE, then h contains the closest curve (at distance h.min) and all other curves o such that dist(o, q) < MAX[h.min + h.s, innerR] and dist(o, q) < t.
If h.overflow is TRUE then it wasn't possible to include all such curves. h contains the closest n such curves (where n = MaxFeatures).
ProcessLine: PROC [line: Line, thisCurve: GoodPoint, featureData: FeatureData] = {
thisCurve.dist ¬ Lines2d.LineDistance[q, line];
IF thisCurve.dist < dTol THEN {
thisCurve.featureData ¬ featureData;
thisCurve.point ¬ Lines2d.DropPerpendicular[q, line];
thisCurve.normal ¬ Vectors2d.Sub[q, thisCurve.point];
IF Vectors2d.MagnitudeSquared[thisCurve.normal] = 0 THEN {
thisCurve.normal ¬ [0, -1];};
thisCurve.hitData ¬ NIL;
thisCurve.priority ¬ -1;
dTol ¬ AddNeighbor[thisCurve, h, TRUE];
}
};
ProcessCircle: PROC [circle: Circle, thisCurve: GoodPoint, featureData: FeatureData] = {
QProjectedOntoCircle: PUBLIC PROC [] RETURNS [projectedPt: Point] = {
epsilon: REAL = 1.0e-5;
IF distQtoCenter < epsilon THEN projectedPt ¬ [circle.origin.x + circle.radius, circle.origin.y]
ELSE projectedPt ¬ Vectors2d.Add[circle.origin, Vectors2d.Scale[centerToQ, circle.radius/distQtoCenter]];
};
centerToQ: Vector ¬ Vectors2d.Sub[q, circle.origin];
distQtoCenter: REAL ¬ Vectors2d.Magnitude[centerToQ];
thisCurve.dist ¬ ABS[distQtoCenter-circle.radius];
IF thisCurve.dist < dTol THEN {
thisCurve.featureData ¬ featureData;
thisCurve.point ¬ QProjectedOntoCircle[];
IF thisCurve.dist = 0.0 THEN thisCurve.normal ¬ centerToQ
ELSE thisCurve.normal ¬ Vectors2d.Sub[q, thisCurve.point];
thisCurve.hitData ¬ NIL;
thisCurve.priority ¬ -1;
dTol ¬ AddNeighbor[thisCurve, h, TRUE];
};
};
ProcessSlice: PROC [sliceD: SliceDescriptor, thisCurve: GoodPoint, featureData: FeatureData] = {
success: BOOL ¬ FALSE;
[thisCurve.point, thisCurve.dist, thisCurve.normal, thisCurve.hitData, success] ¬ sliceD.slice.class.closestSegment[sliceD, q, dTol];
IF success THEN {
IF thisCurve.dist < dTol THEN {
thisCurve.featureData ¬ featureData;
IF Vectors2d.MagnitudeSquared[thisCurve.normal] = 0 THEN {
thisCurve.normal ¬ [0, -1];
};
thisCurve.priority ¬ GGScene.GetPriority[ggData.scene, sliceD.slice];
dTol ¬ AddNeighbor[thisCurve, h, TRUE];
};
};
};
ProcessSlopeLine: GGAlign.FeatureWalkProc = {
FeatureWalkProc: TYPE = PROC [feature: FeatureData] RETURNS [done: BOOLFALSE];
line ¬ NARROW[feature.shape, AlignmentLine].line;
ProcessLine[line, thisCurve, feature];
};
DoForSceneSlice: GGAlign.FeatureWalkProc = {
FeatureWalkProc: TYPE = PROC [feature: FeatureData] RETURNS [done: BOOLFALSE];
sliceD ¬ NARROW[feature.shape, SliceDescriptor];
ProcessSlice[sliceD, thisCurve, feature];
};
line: Line;
circle: Circle;
sliceD: SliceDescriptor;
featureData: FeatureData;
added: BOOL ¬ FALSE;
thisCurve: GoodPoint ¬ NEW[GoodPointObj];
dTol: REAL ¬ t;
h ¬ BestCurvesFromPool[ggData, t, innerR];
Align Bag
GGAlign.WalkSlopeLines[alignBag, ProcessSlopeLine];
FOR angleLines: LIST OF FeatureData ¬ alignBag.angleLines, angleLines.rest UNTIL angleLines = NIL DO
featureData ¬ angleLines.first;
line ¬ NARROW[featureData.shape, AlignmentLine].line;
ProcessLine[line, thisCurve, featureData];
ENDLOOP;
FOR dLines: LIST OF FeatureData ¬ alignBag.distanceLines, dLines.rest UNTIL dLines = NIL DO
featureData ¬ dLines.first;
line ¬ NARROW[featureData.shape];
ProcessLine[line, thisCurve, featureData];
ENDLOOP;
FOR circles: LIST OF FeatureData ¬ alignBag.radiiCircles, circles.rest UNTIL circles = NIL DO
featureData ¬ circles.first;
circle ¬ NARROW[featureData.shape, AlignmentCircle].circle;
ProcessCircle[circle, thisCurve, featureData];
ENDLOOP;
Scene Bag.
GGAlign.WalkSliceTriggers[sceneBag, DoForSceneSlice];
curveCount ¬ h.size;
IF h.overflow THEN {
CodeTimer.StartInt[$CurveOverflow, $Gargoyle];
CodeTimer.StopInt[$CurveOverflow, $Gargoyle];
};
SortCurves[h, curveCount];
}; -- end CurvesInNeighborhoodPlus
FacesInNeighborhoodPlus: PROC [alignBag: AlignBag, sceneBag: TriggerBag, q: Point, ggData: GGData, t: REAL] RETURNS [h: BestFaces, faceCount: NAT] = {
sliceD: SliceDescriptor;
added: BOOL ¬ FALSE;
thisFace: GoodPoint ¬ NEW[GoodPointObj];
priorityTol: INT ¬ -1;
ProcessSlice: PROC [sliceD: SliceDescriptor, thisFace: GoodPoint, featureData: FeatureData] = {
hitData: REF ANY;
moreHitDatas: LIST OF REF ANY;
success: BOOL ¬ TRUE;
IF GGScene.GetPriority[ggData.scene, sliceD.slice] >= priorityTol THEN {
[hitData, moreHitDatas] ¬ GGSliceOps.FilledPathsUnderPoint[sliceD.slice, q, t];
IF hitData # NIL THEN {
thisFace.point ¬ q;
thisFace.priority ¬ GGScene.GetPriority[ggData.scene, sliceD.slice];
thisFace.normal ¬ [0, -1];
thisFace.hitData ¬ hitData;
thisFace.featureData ¬ featureData;
priorityTol ¬ AddFace[thisFace, h];
};
FOR list: LIST OF REF ANY ¬ moreHitDatas, list.rest UNTIL list = NIL DO
thisFace.point ¬ q;
thisFace.priority ¬ GGScene.GetPriority[ggData.scene, sliceD.slice];
thisFace.normal ¬ [0, -1];
thisFace.hitData ¬ list.first;
thisFace.featureData ¬ featureData;
priorityTol ¬ AddFace[thisFace, h];
ENDLOOP;
};
};
DoForSceneSlice: GGAlign.FeatureWalkProc = {
FeatureWalkProc: TYPE = PROC [feature: FeatureData] RETURNS [done: BOOLFALSE];
sliceD ¬ NARROW[feature.shape, SliceDescriptor];
ProcessSlice[sliceD, thisFace, feature];
};
h ¬ BestFacesFromPool[ggData];
Scene Bag.
GGAlign.WalkSliceTriggers[sceneBag, DoForSceneSlice];
faceCount ¬ h.size;
IF h.overflow THEN {
CodeTimer.StartInt[$FaceOverflow, $Gargoyle];
CodeTimer.StopInt[$FaceOverflow, $Gargoyle];
};
SortFaces[h, faceCount];
};
Maintaining the Nearest-Neighbor Structure
MaxFeatures: NAT ¬ 20;
BestFacesFromPool: PROC [ggData: GGData] RETURNS [h: BestFaces] = {
h ¬ NARROW[ggData.multiGravityPool, MultiGravityPool].bestfaces;
h.size ¬ 0;
h.maxPriority ¬ -1;
h.minIndex ¬ 9999;
h.minPriority ¬ LAST[INT];
h.bestPriorityTossed ¬ -1;
h.overflow ¬ FALSE;
FOR i: NAT IN [0..MaxFeatures) DO
h[i].dist ¬ Real.LargestNumber;
h[i].featureData ¬ NIL;
ENDLOOP;
};
BestCurvesFromPool: PROC [ggData: GGData, t: REAL, innerR: REAL] RETURNS [h: BestPoints] = {
h ¬ NARROW[ggData.multiGravityPool, MultiGravityPool].bestcurves;
h.size ¬ 0;
h.max ¬ 0.0;
h.maxIndex ¬ 9999;
h.min ¬ Real.LargestNumber;
h.dTol ¬ t;
h.innerR ¬ innerR;
h.s ¬ 0.072; -- 1/1000 inches
h.bestTossed ¬ Real.LargestNumber;
h.overflow ¬ FALSE;
FOR i: NAT IN [0..MaxFeatures) DO
h[i].dist ¬ Real.LargestNumber;
h[i].featureData ¬ NIL;
ENDLOOP;
};
BestPointsFromPool: PROC [ggData: GGData, t: REAL] RETURNS [h: BestPoints] = {
h ¬ NARROW[ggData.multiGravityPool, MultiGravityPool].bestpoints;
h.size ¬ 0;
h.max ¬ 0.0;
h.maxIndex ¬ 9999;
h.min ¬ Real.LargestNumber;
h.dTol ¬ t;
h.s ¬ 0.072; -- 1/1000 inches
h.bestTossed ¬ Real.LargestNumber;
h.overflow ¬ FALSE;
FOR i: NAT IN [0..MaxFeatures) DO
h[i].dist ¬ Real.LargestNumber;
h[i].featureData ¬ NIL;
ENDLOOP;
};
AddNeighbor maintains these invariants: There is valid data in h[0] up to h[size-1]. Call these the Elements of h. All other components of h have dist = infinity. h.min is the minimum value of dist(q, x) for x element of h.
If overflow is FALSE, then h contains all objects o, seen so far, such that
h.min <= dist(o, q) <= MAX[h.min+h.s, innerR], if curve is TRUE,
h.min <= dist(o, q) <= h.min+h.s, if curve is FALSE
h may contain other objects as well.
If overflow is TRUE, all objects in h satisfy
h.min <= dist(o, q) <= MAX[h.min+h.s, innerR], if curve is TRUE,
h.min <= dist(o, q) <= h.min+h.s, if curve is FALSE
but there are one or more objects that satisfy this property that are not in h. Those objects that are not included are at least as far from q as the farthest object in h.
dTol is a hint to the caller that the caller need not pass in objects that are more than dTol units away from q, since AddNeighbor is just going to throw them away.
When AddNeighbor returns, h.dTol = t if h.size = 0,
h.dTol = MAX[h.min+h.s, innerR], if h.size > 0 and curve is TRUE,
h.dTol = h.min + h.s, if h.size > 0 and curve is FALSE.
AddNeighbor: PROC [thisPoint: GoodPoint, h: BestPoints, curve: BOOL ¬ FALSE] RETURNS [dTol: REAL] = {
d: REAL ¬ thisPoint.dist;
n: NAT = MaxFeatures;
BEGIN
SELECT TRUE FROM
d > h.dTol => GOTO Toss; -- the caller is not taking our hints and is passing us trash
h.size < n => GOTO Add;
h.size = n => GOTO ReplaceOrOverflow;
ENDCASE => SIGNAL Problem[msg: "Impossible case."];
EXITS
Add => {
h[h.size]­ ¬ thisPoint­;
IF d > h.max THEN {h.max ¬ d; h.maxIndex ¬ h.size};
h.min ¬ MIN[h.min, d];
h.size ¬ h.size + 1;
IF curve THEN dTol ¬ h.dTol ¬ MAX[h.min+h.s, h.innerR]
ELSE dTol ¬ h.dTol ¬ h.min+h.s;
};
ReplaceOrOverflow => {
Replace the worst element with the new one if the new is better.
IF d < h.max THEN { -- do the replace
h[h.maxIndex]­ ¬ thisPoint­;
h.bestTossed ¬ MIN[h.bestTossed, h.max];
h.min ¬ MIN[h.min, d];
IF curve THEN dTol ¬ h.dTol ¬ MAX[h.min+h.s, h.innerR]
ELSE dTol ¬ h.dTol ¬ h.min+h.s;
h.overflow ¬ h.bestTossed <= dTol;
Now, update h.max and h.maxIndex the hard way.
BEGIN
maxIndex: NAT ¬ 0;
maxDist: REAL ¬ 0.0;
FOR i: NAT IN [0..MaxFeatures) DO
IF h[i].dist > maxDist THEN {maxIndex ¬ i; maxDist ¬ h[i].dist};
ENDLOOP;
h.max ¬ maxDist;
h.maxIndex ¬ maxIndex;
END;
}
ELSE { -- toss the new item
dTol ¬ h.dTol;
h.bestTossed ¬ MIN[h.bestTossed, d];
h.overflow ¬ TRUE;
};
};
Toss => {
dTol ¬ h.dTol;
h.bestTossed ¬ MIN[h.bestTossed, d];
h.overflow ¬ h.bestTossed <= dTol;
};
END;
};
AddFace: PROC [thisFace: GoodPoint, h: BestFaces] RETURNS [priorityTol: INT] = {
d: INT ¬ thisFace.priority;
n: NAT = MaxFeatures;
BEGIN
SELECT TRUE FROM
d < h.priorityTol => GOTO Toss; -- the caller is passing us trash
h.size < n => GOTO Add;
h.size = n => GOTO ReplaceOrOverflow;
ENDCASE => SIGNAL Problem[msg: "Impossible case."];
EXITS
Add => {
h[h.size]­ ¬ thisFace­;
IF d < h.minPriority THEN {h.minPriority ¬ d; h.minIndex ¬ h.size};
h.maxPriority ¬ MAX[h.maxPriority, d];
h.size ¬ h.size + 1;
priorityTol ¬ h.priorityTol;
};
ReplaceOrOverflow => {
Replace the worst element with the new one if the new is better.
IF d > h.minPriority THEN { -- do the replace
h[h.minIndex]­ ¬ thisFace­;
h.bestPriorityTossed ¬ MAX[h.bestPriorityTossed, h.minPriority];
h.maxPriority ¬ MAX[h.maxPriority, d];
h.overflow ¬ TRUE;
Now, update h.minPriority and h.minIndex the hard way.
BEGIN
minIndex: NAT ¬ 0;
minPriority: INT ¬ LAST[INT];
FOR i: NAT IN [0..MaxFeatures) DO
IF h[i].priority < minPriority THEN {minIndex ¬ i; minPriority ¬ h[i].priority};
ENDLOOP;
h.minPriority ¬ minPriority;
h.minIndex ¬ minIndex;
END;
priorityTol ¬ h.priorityTol ¬ h.minPriority;
}
ELSE { -- toss the new item
priorityTol ¬ h.priorityTol;
h.bestPriorityTossed ¬ MAX[h.bestPriorityTossed, d];
h.overflow ¬ TRUE;
};
};
Toss => {
priorityTol ¬ h.priorityTol;
h.bestPriorityTossed ¬ MAX[h.bestPriorityTossed, d];
h.overflow ¬ TRUE;
};
END;
};
NearPointsFromPoints: PROC [bestPoints: BestPoints, pointCount: NAT, nearVEF: NearVertexEdgeAndFaces] = {
FOR i: NAT IN [0..pointCount) DO
nearVEF[i] ¬ bestPoints[i];
ENDLOOP;
};
MergePointsAndCurves: PROC [bestPoints: BestPoints, pointCount: NAT, bestCurves: BestPoints, curveCount: NAT, nearVEF: NearVertexEdgeAndFaces, count: NAT] = {
Merge the bestPoints and the bestCurves. There will be count elements in the result.
pointIndex, curveIndex: NAT;
pointDist, curveDist: REAL;
pointIndex ¬ 0;
curveIndex ¬ 0;
FOR i: INTEGER IN [0..count) DO
IF pointIndex >= pointCount THEN GOTO NoMorePoints;
IF curveIndex >= curveCount THEN GOTO NoMoreCurves;
pointDist ¬ bestPoints[i].dist;
curveDist ¬ bestCurves[i].dist;
IF pointDist <= curveDist THEN {
nearVEF[i] ¬ bestPoints[pointIndex];
pointIndex ¬ pointIndex + 1;
}
ELSE {
nearVEF[i] ¬ bestCurves[curveIndex];
curveIndex ¬ curveIndex + 1;
};
REPEAT
NoMorePoints => { -- finish up with Curves data
FOR k: NAT ¬ i, k+1 UNTIL k >= count DO
nearVEF[k] ¬ bestCurves[curveIndex];
curveIndex ¬ curveIndex + 1;
ENDLOOP};
NoMoreCurves => { -- finish up with points data
FOR k: NAT ¬ i, k+1 UNTIL k >= count DO
nearVEF[k] ¬ bestPoints[pointIndex];
pointIndex ¬ pointIndex + 1;
ENDLOOP};
ENDLOOP;
};
MergeByOverlapAndDistance: PROC [faces: BestFaces, faceCount: NAT, curves: NearVertexEdgeAndFaces, curveCount: NAT] RETURNS [nearVEF: NearVertexEdgeAndFaces, total: NAT] = {
Merge the two sorted lists together classifying the segments by the AND of the Classifs for each segment
facePtr, curvePtr: NAT;
total ¬ MIN[curveCount+faceCount, MaxFeatures];
nearVEF ¬ NEW[NearVertexEdgeAndFacesObj[total]];
facePtr ¬ curvePtr ¬ 0;
FOR i: NAT IN[0..total) DO
IF curvePtr >= curveCount THEN GOTO CurvePtrWentOver;
IF facePtr >= faceCount THEN GOTO FacePtrWentOver;
BEGIN
SELECT faces[facePtr].priority - curves[curvePtr].priority FROM
= 0 => { -- they're part of the same slice
slice: Slice ¬ NARROW[faces[facePtr].featureData.shape, SliceDescriptor].slice;
IF GGParent.IsParent[slice] THEN {
facePath, curvePath: Slice;
faceHitData, curveHitData: REF ANY;
[facePath, faceHitData] ¬ GGParent.PathOfHitData[slice, faces[facePtr].hitData];
[curvePath, curveHitData] ¬ GGParent.PathOfHitData[slice, curves[curvePtr].hitData];
IF facePath = curvePath THEN GOTO RemoveDuplicate;
We have different paths within the same trajectory or cluster. Choose whichever is closer in the overlap order of the cluster
SELECT GGParent.ComparePriorities[facePath, curvePath] FROM
less => GOTO CurveIsCloser;
greater => GOTO FaceIsCloser;
equal => GOTO RemoveDuplicate;
ENDCASE => ERROR;
}
ELSE GOTO RemoveDuplicate;
};
> 0 => GOTO FaceIsCloser;
< 0 => GOTO CurveIsCloser;
ENDCASE => ERROR;
EXITS
RemoveDuplicate => {
nearVEF[i] ¬ curves[curvePtr];
curvePtr ¬ curvePtr + 1;
facePtr ¬ facePtr + 1;
total ¬ total - 1; -- a duplicate has been removed
};
FaceIsCloser => {
nearVEF[i] ¬ faces[facePtr];
facePtr ¬ facePtr + 1;
};
CurveIsCloser => {
nearVEF[i] ¬ curves[curvePtr];
curvePtr ¬ curvePtr + 1;
};
END;
REPEAT
CurvePtrWentOver => { -- finish up with faces
FOR k: NAT ¬ i, k+1 UNTIL k >= total DO
nearVEF[k] ¬ faces[facePtr];
facePtr ¬ facePtr + 1;
ENDLOOP};
FacePtrWentOver => { -- finish up with curves
FOR k: NAT ¬ i, k+1 UNTIL k >= total DO
nearVEF[k] ¬ curves[curvePtr];
curvePtr ¬ curvePtr + 1;
ENDLOOP};
ENDLOOP;
}; -- end of MergeByOverlapAndDistance
SortPoints: PROC [bestPoints: BestPoints, pointCount: NAT] = {
Sort the points in order of increasing distance. Since n is likely to be small, bubble sort is sensible.
temp: GoodPoint;
FOR i: INTEGER IN [0..pointCount-2] DO
FOR j: INTEGER IN [1..pointCount-i) DO
IF bestPoints[j-1].dist > bestPoints[j].dist THEN {
temp ¬ bestPoints[j];
bestPoints[j] ¬ bestPoints[j-1];
bestPoints[j-1] ¬ temp;
};
ENDLOOP;
ENDLOOP;
};
SortCurves: PROC [bestCurves: BestPoints, curveCount: NAT] = {
Sort the curves in order of increasing distance. Since n is likely to be small, bubble sort is sensible.
temp: GoodPoint;
FOR i: INTEGER IN [0..curveCount-2] DO
FOR j: INTEGER IN [1..curveCount-i) DO
IF bestCurves[j-1].dist > bestCurves[j].dist THEN {
temp ¬ bestCurves[j];
bestCurves[j] ¬ bestCurves[j-1];
bestCurves[j-1] ¬ temp;
};
ENDLOOP;
ENDLOOP;
};
SortFaces: PROC [bestFaces: BestFaces, faceCount: NAT] = {
Sort the curves in order of descreasing priority. Since n is likely to be small, bubble sort is sensible.
temp: GoodPoint;
FOR i: INTEGER IN [0..faceCount-2] DO
FOR j: INTEGER IN [1..faceCount-i) DO
IF bestFaces[j-1].priority < bestFaces[j].priority THEN {
temp ¬ bestFaces[j];
bestFaces[j] ¬ bestFaces[j-1];
bestFaces[j-1] ¬ temp;
};
ENDLOOP;
ENDLOOP;
};
SortByOverlap: PROC [faces: BestFaces, count: NAT] = {
Sort the vertices, edges, and faces in order of decreasing priority. Since n is likely to be small, bubble sort is sensible.
temp: GoodPoint;
FOR i: INTEGER IN [0..count-2] DO
FOR j: INTEGER IN [1..count-i) DO
IF faces[j-1].priority < faces[j].priority THEN {
temp ¬ faces[j];
faces[j] ¬ faces[j-1];
faces[j-1] ¬ temp;
};
ENDLOOP;
ENDLOOP;
};
Computing Intersections
ComputeMidpoint: PROC [curve: GoodPoint] RETURNS [midpoint: Point, success: BOOL ¬ TRUE] = {
class: NAT;
simpleCurve: REF ANY;
[class, simpleCurve] ¬ ClassifyCurve[curve];
SELECT class FROM
3 => { -- edge
edge: Edge ¬ NARROW[simpleCurve];
midpoint ¬ Vectors2d.Scale[Vectors2d.Add[edge.start, edge.end], 0.5];
success ¬ TRUE;
};
4 => { -- arc
arc: Arc ¬ NARROW[simpleCurve];
midpoint ¬ Vectors2d.Scale[Vectors2d.Add[arc.p0, arc.p2], 0.5];
success ¬ TRUE;
};
ENDCASE => RETURN[[0,0], FALSE];
};
ClassifyCurve: PROC [curve: GoodPoint] RETURNS [class: NAT, simpleCurve: REF ANY] = {
feature: FeatureData ¬ curve.featureData;
SELECT feature.type FROM
slice => {
sliceD: SliceDescriptor ¬ NARROW[feature.shape];
hitData: REF ANY ¬ curve.hitData;
simpleCurve ¬ GGSliceOps.HitDataAsSimpleCurve[sliceD.slice, hitData];
IF simpleCurve = NIL THEN {
simpleCurve ¬ sliceD;
class ¬ 6;
RETURN;
};
};
radiiCircle => {
class ¬ 2;
simpleCurve ¬ NARROW[feature.shape, AlignmentCircle].circle;
RETURN;
};
slopeLine, angleLine => {
class ¬ 1;
simpleCurve ¬ NARROW[feature.shape, AlignmentLine].line;
RETURN;
};
distanceLine => {
class ¬ 1;
simpleCurve ¬ NARROW[feature.shape, Line];
RETURN;
};
ENDCASE => {class ¬ 0; simpleCurve ¬ NIL; RETURN};
WITH simpleCurve SELECT FROM
circle: Circle => class ¬ 2;
edge: Edge => class ¬ 3;
arc: Arc => class ¬ 4;
cubic: BezierRef => class ¬ 5;
ENDCASE => ERROR;
};
CurveMeetsCurve: PROC [c1, c2: GoodPoint] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL] = {
typeOfCurve1, typeOfCurve2: NAT;
simpleCurve1, simpleCurve2: REF ANY;
[typeOfCurve1, simpleCurve1] ¬ ClassifyCurve[c1];
[typeOfCurve2, simpleCurve2] ¬ ClassifyCurve[c2];
IF typeOfCurve1 >= typeOfCurve2 THEN
[iPoints, tangency] ¬ ComputeIntersection[typeOfCurve1][typeOfCurve2][simpleCurve1, simpleCurve2]
ELSE
[iPoints, tangency] ¬ ComputeIntersection[typeOfCurve2][typeOfCurve1][simpleCurve2, simpleCurve1]
};
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
ComputeIntersection: ARRAY [0..6] OF ARRAY [0..6] OF IntersectionProc = [
0) NoOp 1) Line 2) Circle 3) Edge 4) Arc 5) Cubic 6) Slice
[NoOpI, NIL,  NIL,  NIL, NIL, NIL, NIL], -- 0) NoOp
[NoOpI, LinLinI,  NIL, NIL, NIL, NIL, NIL], -- 1) Line
[NoOpI, CirLinI, CirCirI, NIL, NIL, NIL, NIL], -- 2) Circle
[NoOpI,  EdgLinI,  EdgCirI, EdgEdgI, NIL, NIL, NIL], -- 3) Edge
[NoOpI, ArcLinI, ArcCirI, ArcEdgI, ArcArcI, NIL, NIL], -- 4) Arc
[NoOpI, CubLinI, NoOpI, CubEdgI, NoOpI, NoOpI, NIL], -- 5) Cubic
[NoOpI, SlcLinI, SlcCirI, NoOpI, NoOpI, NoOpI, NoOpI] -- 6) Slice
]; 
NoOpI: IntersectionProc = {
iPoints ¬ NIL;
tangency ¬ NIL;
};
LinLinI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
l1: Line ¬ NARROW[c1];
l2: Line ¬ NARROW[c2];
point: Point;
parallel: BOOL ¬ FALSE;
[point, parallel] ¬ Lines2d.LineMeetsLine[l1, l2];
IF NOT parallel THEN {iPoints ¬ LIST[point]; tangency ¬ LIST[FALSE]}
ELSE {iPoints ¬ NIL; tangency ¬ NIL};
};
CirLinI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
circle: Circle ¬ NARROW[c1];
line: Line ¬ NARROW[c2];
points: ARRAY [1..2] OF Point;
hitCount: [0..2];
tangent: BOOL ¬ FALSE;
[points, hitCount, tangent] ¬ GGCircles.CircleMeetsLine[circle, line];
FOR i: NAT IN [1..hitCount] DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent, tangency];
ENDLOOP;
};
CirCirI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
circle1: Circle ¬ NARROW[c1];
circle2: Circle ¬ NARROW[c2];
points: ARRAY [1..2] OF Point;
hitCount: [0..2];
tangent: BOOL ¬ FALSE;
[points, hitCount, tangent] ¬ GGCircles.CircleMeetsCircle[circle1, circle2];
FOR i: NAT IN [1..hitCount] DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent, tangency];
ENDLOOP;
};
EdgLinI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
edge: Edge ¬ NARROW[c1];
line: Line ¬ NARROW[c2];
point: Point;
noHit: BOOL ¬ FALSE;
[point, noHit] ¬ Lines2d.LineMeetsEdge[line, edge];
IF NOT noHit THEN {iPoints ¬ LIST[point]; tangency ¬ LIST[FALSE]}
ELSE {iPoints ¬ NIL; tangency ¬ NIL};
};
EdgCirI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
edge: Edge ¬ NARROW[c1];
circle: Circle ¬ NARROW[c2];
points: ARRAY [1..2] OF Point;
hitCount: [0..2];
tangent: BOOL ¬ FALSE;
[points, hitCount, tangent] ¬ GGCircles.CircleMeetsEdge[circle, edge];
FOR i: NAT IN [1..hitCount] DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent, tangency];
ENDLOOP;
};
EdgEdgI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
e1: Edge ¬ NARROW[c1];
e2: Edge ¬ NARROW[c2];
point: Point;
noHit: BOOL ¬ FALSE;
[point, noHit] ¬ Lines2d.EdgeMeetsEdge[e1, e2];
IF NOT noHit THEN {iPoints ¬ LIST[point]; tangency ¬ LIST[FALSE]}
ELSE {iPoints ¬ NIL; tangency ¬ NIL};
};
ArcLinI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
arc: Arc ¬ NARROW[c1];
line: Line ¬ NARROW[c2];
points: ARRAY [1..2] OF Point;
hitCount: [0..2];
tangent: BOOL ¬ FALSE;
[points, hitCount, tangent] ¬ GGCircles.ArcMeetsLine[arc, line];
FOR i: NAT IN [1..hitCount] DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent, tangency];
ENDLOOP;
};
ArcCirI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
arc: Arc ¬ NARROW[c1];
circle: Circle ¬ NARROW[c2];
points: ARRAY [1..2] OF Point;
hitCount: [0..2];
tangent: BOOL ¬ FALSE;
[points, hitCount, tangent] ¬ GGCircles.CircleMeetsArc[circle, arc];
FOR i: NAT IN [1..hitCount] DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent, tangency];
ENDLOOP;
};
ArcEdgI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
arc: Arc ¬ NARROW[c1];
edge: Edge ¬ NARROW[c2];
points: ARRAY [1..2] OF Point;
hitCount: [0..2];
tangent: BOOL ¬ FALSE;
[points, hitCount, tangent] ¬ GGCircles.ArcMeetsEdge[arc, edge];
FOR i: NAT IN [1..hitCount] DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent, tangency];
ENDLOOP;
};
ArcArcI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
arc1: Arc ¬ NARROW[c1];
arc2: Arc ¬ NARROW[c2];
points: ARRAY [1..2] OF Point;
hitCount: [0..2];
tangent: BOOL ¬ FALSE;
[points, hitCount] ← GGCircles.ArcMeetsArc[arc1, arc2];
[points, hitCount, tangent] ¬ GGCircles.ArcMeetsArc[arc1, arc2];
FOR i: NAT IN [1..hitCount] DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent, tangency];
ENDLOOP;
};
CubLinI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
bezier: BezierRef ¬ NARROW[c1];
line: Line ¬ NARROW[c2];
points: ARRAY [0..2] OF Point;
hitCount: [0..3];
tangent: ARRAY [0..2] OF BOOL ¬ ALL[FALSE];
[points, hitCount, tangent] ¬ CubicPaths.CubicMeetsLine[bezier­, -line.s, line.c, -line.d];
FOR i: NAT IN [0..hitCount) DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent[i], tangency];
ENDLOOP;
};
CubicMeetsEdge: PROC [bezier: BezierRef, edge: Edge] RETURNS [points: ARRAY [0..2] OF Point, hitCount: [0..3] ¬ 0, tangent: ARRAY [0..2] OF BOOL ¬ ALL[FALSE]] = {
linePoints: ARRAY [0..2] OF Point;
lineHitCount: [0..3];
lineTangents: ARRAY [0..2] OF BOOL ¬ ALL[FALSE];
[linePoints, lineHitCount, lineTangents] ¬ CubicPaths.CubicMeetsLine[bezier­, -edge.line.s, edge.line.c, -edge.line.d];
FOR i: NAT IN [0..lineHitCount) DO
IF Lines2d.OnEdge[linePoints[i], edge] THEN {
points[hitCount] ¬ linePoints[i];
tangent[hitCount] ¬ lineTangents[i];
hitCount ¬ hitCount + 1;
};
ENDLOOP;
};
CubEdgI: IntersectionProc = {
IntersectionProc: TYPE = PROC [c1, c2: REF ANY] RETURNS [iPoints: LIST OF Point, tangency: LIST OF BOOL];
bezier: BezierRef ¬ NARROW[c1];
edge: Edge ¬ NARROW[c2];
points: ARRAY [0..2] OF Point;
hitCount: [0..3];
tangent: ARRAY [0..2] OF BOOL ¬ ALL[FALSE];
[points, hitCount, tangent] ¬ CubicMeetsEdge[bezier, edge];
FOR i: NAT IN [0..hitCount) DO
iPoints ¬ CONS[points[i], iPoints];
tangency ¬ CONS[tangent[i], tangency];
ENDLOOP;
};
SlcLinI: IntersectionProc = {
sliceD: SliceDescriptor ¬ NARROW[c1];
line: Line ¬ NARROW[c2];
[iPoints, ----] ¬ GGSliceOps.LineIntersection[sliceD, line];
FOR list: LIST OF Point ¬ iPoints, list.rest UNTIL list = NIL DO
tangency ¬ CONS[FALSE, tangency];
ENDLOOP;
};
SlcCirI: IntersectionProc = {
sliceD: SliceDescriptor ¬ NARROW[c1];
circle: Circle ¬ NARROW[c2];
[iPoints, ----] ¬ GGSliceOps.CircleIntersection[sliceD, circle];
FOR list: LIST OF Point ¬ iPoints, list.rest UNTIL list = NIL DO
tangency ¬ CONS[FALSE, tangency];
ENDLOOP;
};
Utilities
NewMultiGravityPool: PUBLIC PROC [] RETURNS [REF]= { -- reuseable storage for BestPointAndCurve proc to avoid NEWs
pool: MultiGravityPool ¬ NEW[MultiGravityPoolObj];
pool.bestpoints ¬ NEW[BestPointsObj[MaxFeatures]];
pool.bestcurves ¬ NEW[BestPointsObj[MaxFeatures]];
pool.bestfaces ¬ NEW[BestFacesObj[MaxFeatures]];
FOR i: NAT IN [0..MaxFeatures) DO
pool.bestpoints[i] ¬ NEW[GoodPointObj];
pool.bestcurves[i] ¬ NEW[GoodPointObj];
pool.bestfaces[i] ¬ NEW[GoodPointObj];
ENDLOOP;
RETURN[pool];
};
END.