@itwin/core-geometry 4.4.0-dev.15 → 4.4.0-dev.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/serialization/GeometrySamples.d.ts +11 -0
- package/lib/cjs/serialization/GeometrySamples.d.ts.map +1 -1
- package/lib/cjs/serialization/GeometrySamples.js +16 -0
- package/lib/cjs/serialization/GeometrySamples.js.map +1 -1
- package/lib/cjs/topology/Graph.d.ts +1 -3
- package/lib/cjs/topology/Graph.d.ts.map +1 -1
- package/lib/cjs/topology/Graph.js +3 -3
- package/lib/cjs/topology/Graph.js.map +1 -1
- package/lib/cjs/topology/HalfEdgeGraphSearch.d.ts +81 -76
- package/lib/cjs/topology/HalfEdgeGraphSearch.d.ts.map +1 -1
- package/lib/cjs/topology/HalfEdgeGraphSearch.js +126 -111
- package/lib/cjs/topology/HalfEdgeGraphSearch.js.map +1 -1
- package/lib/cjs/topology/SignedDataSummary.d.ts +13 -13
- package/lib/cjs/topology/SignedDataSummary.d.ts.map +1 -1
- package/lib/cjs/topology/SignedDataSummary.js +3 -3
- package/lib/cjs/topology/SignedDataSummary.js.map +1 -1
- package/lib/cjs/topology/XYParitySearchContext.d.ts +27 -21
- package/lib/cjs/topology/XYParitySearchContext.d.ts.map +1 -1
- package/lib/cjs/topology/XYParitySearchContext.js +73 -71
- package/lib/cjs/topology/XYParitySearchContext.js.map +1 -1
- package/lib/esm/serialization/GeometrySamples.d.ts +11 -0
- package/lib/esm/serialization/GeometrySamples.d.ts.map +1 -1
- package/lib/esm/serialization/GeometrySamples.js +16 -0
- package/lib/esm/serialization/GeometrySamples.js.map +1 -1
- package/lib/esm/topology/Graph.d.ts +1 -3
- package/lib/esm/topology/Graph.d.ts.map +1 -1
- package/lib/esm/topology/Graph.js +3 -3
- package/lib/esm/topology/Graph.js.map +1 -1
- package/lib/esm/topology/HalfEdgeGraphSearch.d.ts +81 -76
- package/lib/esm/topology/HalfEdgeGraphSearch.d.ts.map +1 -1
- package/lib/esm/topology/HalfEdgeGraphSearch.js +126 -111
- package/lib/esm/topology/HalfEdgeGraphSearch.js.map +1 -1
- package/lib/esm/topology/SignedDataSummary.d.ts +13 -13
- package/lib/esm/topology/SignedDataSummary.d.ts.map +1 -1
- package/lib/esm/topology/SignedDataSummary.js +3 -3
- package/lib/esm/topology/SignedDataSummary.js.map +1 -1
- package/lib/esm/topology/XYParitySearchContext.d.ts +27 -21
- package/lib/esm/topology/XYParitySearchContext.d.ts.map +1 -1
- package/lib/esm/topology/XYParitySearchContext.js +73 -71
- package/lib/esm/topology/XYParitySearchContext.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,125 +1,130 @@
|
|
|
1
|
-
/** @packageDocumentation
|
|
2
|
-
* @module Topology
|
|
3
|
-
*/
|
|
4
1
|
import { HalfEdge, HalfEdgeGraph, HalfEdgeMask, HalfEdgeToBooleanFunction, NodeToNumberFunction } from "./Graph";
|
|
5
2
|
import { SignedDataSummary } from "./SignedDataSummary";
|
|
6
|
-
/**
|
|
7
|
-
* Interface for an object that executes boolean tests on edges.
|
|
8
|
-
*/
|
|
3
|
+
/** Interface for an object that executes boolean tests on edges. */
|
|
9
4
|
export interface HalfEdgeTestObject {
|
|
10
5
|
testEdge(h: HalfEdge): boolean;
|
|
11
6
|
}
|
|
12
|
-
/**
|
|
13
|
-
*/
|
|
7
|
+
/** Class to test match of half edge mask. */
|
|
14
8
|
export declare class HalfEdgeMaskTester {
|
|
15
9
|
private _targetMask;
|
|
16
10
|
private _targetValue;
|
|
17
11
|
/**
|
|
18
|
-
*
|
|
12
|
+
* Constructor
|
|
19
13
|
* @param mask mask to test in `testEdge` function
|
|
20
14
|
* @param targetValue value to match for true return
|
|
21
15
|
*/
|
|
22
16
|
constructor(mask: HalfEdgeMask, targetValue?: boolean);
|
|
23
|
-
/** Return true if the value of the targetMask matches the targetValue */
|
|
17
|
+
/** Return true if the value of the targetMask matches the targetValue. */
|
|
24
18
|
testEdge(edge: HalfEdge): boolean;
|
|
25
19
|
}
|
|
20
|
+
/** Class for different types of searches for HalfEdgeGraph. */
|
|
26
21
|
export declare class HalfEdgeGraphSearch {
|
|
27
22
|
/**
|
|
28
|
-
*
|
|
29
|
-
* *
|
|
30
|
-
*/
|
|
31
|
-
private static pushAndMaskAllNodesInFace;
|
|
32
|
-
/**
|
|
33
|
-
* Search an array of faceSeed nodes for the face with the most negative area.
|
|
34
|
-
* @param oneCandidateNodePerFace array containing one node from each face to be considered.
|
|
35
|
-
* @returns node on the minimum area face, or undefined if no such face (e.g., all faces have zero area).
|
|
36
|
-
*/
|
|
37
|
-
static findMinimumAreaFace(oneCandidateNodePerFace: HalfEdgeGraph | HalfEdge[], faceAreaFunction?: NodeToNumberFunction): HalfEdge | undefined;
|
|
38
|
-
/**
|
|
39
|
-
* static method for face area computation -- useful as function parameter in collect FaceAreaSummary.
|
|
40
|
-
* * This simply calls `node.signedFaceArea ()`
|
|
23
|
+
* Static method for face area computation -- useful as function parameter in `collectFaceAreaSummary`.
|
|
24
|
+
* * This simply calls `node.signedFaceArea()`
|
|
41
25
|
* @param node instance for signedFaceArea call.
|
|
42
26
|
*/
|
|
43
27
|
static signedFaceArea(node: HalfEdge): number;
|
|
44
28
|
/**
|
|
45
|
-
*
|
|
46
|
-
* Return a summary structure data about face (or other numeric quantity if the caller's areaFunction returns other value)
|
|
47
|
-
* * The default areaFunction computes area of polygonal face.
|
|
29
|
+
* Return a summary of face data (e.g., area) as computed by the callback on the faces of the graph.
|
|
48
30
|
* * Callers with curved edge graphs must supply their own area function.
|
|
49
|
-
* @param source graph or array of nodes to examine
|
|
50
|
-
* @param collectAllNodes flag to pass to the SignedDataSummary constructor to control collection of nodes.
|
|
51
|
-
* @param areaFunction function to
|
|
31
|
+
* @param source graph or array of nodes to examine.
|
|
32
|
+
* @param collectAllNodes flag to pass to the `SignedDataSummary` constructor to control collection of nodes.
|
|
33
|
+
* @param areaFunction function to obtain area (or other numeric value). Default computes polygonal face area.
|
|
52
34
|
*/
|
|
53
35
|
static collectFaceAreaSummary(source: HalfEdgeGraph | HalfEdge[], collectAllNodes?: boolean, areaFunction?: NodeToNumberFunction): SignedDataSummary<HalfEdge>;
|
|
54
36
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
|
|
37
|
+
* Search the graph for the face with the most negative area.
|
|
38
|
+
* @param oneCandidateNodePerFace graph or an array containing one node from each face to be considered.
|
|
39
|
+
* @returns node on the negative area face with largest absolute area, or `undefined` if no negative area face.
|
|
40
|
+
*/
|
|
41
|
+
static findMinimumAreaFace(oneCandidateNodePerFace: HalfEdgeGraph | HalfEdge[], faceAreaFunction?: NodeToNumberFunction): HalfEdge | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Test if the graph is triangulated.
|
|
44
|
+
* * Return `false` if:
|
|
45
|
+
* * number of positive area faces with more than 3 edges is larger than `numPositiveExceptionsAllowed`.
|
|
46
|
+
* * graph has more than 1 negative area face when `allowMultipleNegativeAreaFaces` is `false`.
|
|
59
47
|
* * 2-edge faces are ignored.
|
|
60
48
|
*/
|
|
61
49
|
static isTriangulatedCCW(source: HalfEdgeGraph | HalfEdge[], allowMultipleNegativeAreaFaces?: boolean, numPositiveExceptionsAllowed?: number): boolean;
|
|
62
50
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
* Process a face during graph traversal.
|
|
52
|
+
* @param faceSeed a node in the face.
|
|
53
|
+
* @param mask mask to set on each node of the face.
|
|
54
|
+
* @param allNodeStack array appended with each node of the face.
|
|
55
|
+
* @param onePerFaceStack array appended with `faceSeed`.
|
|
56
|
+
*/
|
|
57
|
+
private static pushAndMaskAllNodesInFace;
|
|
58
|
+
/**
|
|
59
|
+
* Traverse (via Depth First Search) to all accessible faces from the given seed.
|
|
60
|
+
* @param faceSeed first node to start the traverse.
|
|
70
61
|
* @param visitMask mask applied to all faces as visited.
|
|
71
|
-
* @param
|
|
62
|
+
* @param parityEdgeTester function to test if an edge is adjacent to two faces of opposite parity, e.g., a boundary
|
|
63
|
+
* edge that separates an "interior" face and an "exterior" face. If `parityEdgeTester` is not supplied and `parityMask`
|
|
64
|
+
* is supplied, the default parity rule is to alternate parity state in a "bullseye" pattern starting at the seed face,
|
|
65
|
+
* with each successive concentric ring of faces at constant topological distance from the seed face receiving the
|
|
66
|
+
* opposite parity state of the previous ring.
|
|
67
|
+
* @param parityMask mask to apply to the first face and faces that share the same parity as the first face, as
|
|
68
|
+
* determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If (non-null) parity mask
|
|
69
|
+
* is given, on return it is entirely set or entirely clear around each face.
|
|
70
|
+
* @returns an array that contains one representative node in each face of the connected component.
|
|
72
71
|
*/
|
|
73
72
|
private static parityFloodFromSeed;
|
|
74
73
|
/**
|
|
75
|
-
* *
|
|
76
|
-
* *
|
|
74
|
+
* * Correct the parity mask in the faces of a component.
|
|
75
|
+
* * It is assumed that the parity mask is applied _consistently_ throughout the supplied faces, but maybe
|
|
76
|
+
* not _correctly_.
|
|
77
|
+
* * A consistently applied parity mask is "correct" if it is set on the negative area ("exterior") face of
|
|
78
|
+
* a connected component.
|
|
79
|
+
* * This method finds a face with negative area and toggles the mask throughout the input faces if this face
|
|
80
|
+
* lacks the parity mask.
|
|
77
81
|
* * In a properly merged planar subdivision there should be only one true negative area face per component.
|
|
78
|
-
* @param graph parent graph
|
|
79
|
-
* @param parityMask mask which was previously set with alternating parity, but with an arbitrary start face.
|
|
80
|
-
* @param faces array of faces to search.
|
|
81
82
|
*/
|
|
82
83
|
private static correctParityInSingleComponent;
|
|
83
|
-
/** Apply correctParityInSingleComponent to each array in components
|
|
84
|
+
/** Apply `correctParityInSingleComponent` to each array in components (quick exit if `parityMask` is `NULL_MASK`). */
|
|
84
85
|
private static correctParityInComponentArrays;
|
|
85
86
|
/**
|
|
86
|
-
* Collect
|
|
87
|
-
* @param graph graph to inspect
|
|
88
|
-
* @param parityEdgeTester (optional) function to test if an edge is
|
|
89
|
-
*
|
|
87
|
+
* Collect connected components of the graph (via Depth First Search).
|
|
88
|
+
* @param graph graph to inspect.
|
|
89
|
+
* @param parityEdgeTester (optional) function to test if an edge is adjacent to two faces of opposite parity,
|
|
90
|
+
* e.g., a boundary edge that separates an "interior" face and an "exterior" face. If `parityEdgeTester` is not
|
|
91
|
+
* supplied and `parityMask` is supplied, the default parity rule is to alternate parity state in a "bullseye"
|
|
92
|
+
* pattern starting at the seed face, with each successive concentric ring of faces at constant topological
|
|
93
|
+
* distance from the seed face receiving the opposite parity state of the previous ring.
|
|
94
|
+
* @param parityMask (optional) mask to apply to the first face and faces that share the same parity as the
|
|
95
|
+
* first face, as determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If
|
|
96
|
+
* (non-null) parity mask is given, on return it is entirely set or entirely clear around each face.
|
|
97
|
+
* @returns the components of the graph, each component represented by an array of nodes, one node per face
|
|
98
|
+
* of the component. In other words, entry [i][j] is a HalfEdge in the j_th face loop of the i_th component.
|
|
90
99
|
*/
|
|
91
100
|
static collectConnectedComponentsWithExteriorParityMasks(graph: HalfEdgeGraph, parityEdgeTester: HalfEdgeTestObject | undefined, parityMask?: HalfEdgeMask): HalfEdge[][];
|
|
92
101
|
/**
|
|
93
|
-
* Test if (
|
|
94
|
-
* @param seedNode any node on the face loop
|
|
95
|
-
* @param
|
|
96
|
-
* @param
|
|
102
|
+
* Test if test point (xTest,yTest) is inside/outside a face or on an edge.
|
|
103
|
+
* @param seedNode any node on the face loop.
|
|
104
|
+
* @param xTest x coordinate of the test point.
|
|
105
|
+
* @param yTest y coordinate of the test point.
|
|
106
|
+
* @returns 0 if ON, 1 if IN, -1 if OUT.
|
|
97
107
|
*/
|
|
98
|
-
static pointInOrOnFaceXY(seedNode: HalfEdge,
|
|
108
|
+
static pointInOrOnFaceXY(seedNode: HalfEdge, xTest: number, yTest: number): number | undefined;
|
|
99
109
|
/**
|
|
100
|
-
*
|
|
101
|
-
* *
|
|
102
|
-
* * "Around the vertex" from nodeA means
|
|
103
|
-
* * First look at nodeA.faceSuccessor;
|
|
104
|
-
* * Then look at vertexPredecessor around that vertex loop.
|
|
105
|
-
* * Each accepted node is passed to announceNode, and marked with the visit mask.
|
|
106
|
-
* * The counter of the announceEdge function is zero for the first edge, then increases with each edge.
|
|
110
|
+
* Collect boundary edges starting from `seed`.
|
|
111
|
+
* * If `seed` is not a boundary node or is already visited, the function exists early.
|
|
107
112
|
* @param seed start node.
|
|
108
|
-
* @param
|
|
109
|
-
* @param
|
|
113
|
+
* @param visitMask mask to set on processed nodes.
|
|
114
|
+
* @param isBoundaryEdge function to test if an edge in a boundary edge.
|
|
115
|
+
* @param announceEdgeInBoundary callback invoked on each edge in the boundary loop in order. The counter is zero
|
|
116
|
+
* for the first edge, and incremented with each successive edge.
|
|
110
117
|
*/
|
|
111
|
-
static collectExtendedBoundaryLoopFromSeed(seed: HalfEdge, visitMask: HalfEdgeMask, isBoundaryEdge: HalfEdgeToBooleanFunction,
|
|
118
|
+
static collectExtendedBoundaryLoopFromSeed(seed: HalfEdge, visitMask: HalfEdgeMask, isBoundaryEdge: HalfEdgeToBooleanFunction, announceEdgeInBoundary: (edge: HalfEdge, counter: number) => void): void;
|
|
112
119
|
/**
|
|
113
|
-
* Collect
|
|
114
|
-
* *
|
|
115
|
-
* *
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* @
|
|
121
|
-
* @param isBoundaryNode
|
|
122
|
-
* @param announceNode
|
|
120
|
+
* Collect boundary edges in the graph.
|
|
121
|
+
* * A boundary edge is defined by `exteriorMask` being set on only its "exterior" edge mate.
|
|
122
|
+
* * Each boundary edge is identified in the output by its edge mate that lacks `exteriorMask`.
|
|
123
|
+
* * Each inner array is ordered in the output so that its boundary edges form a connected path. If `exteriorMask`
|
|
124
|
+
* is preset consistently around each "exterior" face, these paths are loops.
|
|
125
|
+
* @param graph the graph to query
|
|
126
|
+
* @param exteriorMask mask preset on exactly one side of boundary edges
|
|
127
|
+
* @returns array of boundary loops, each loop an array of the unmasked mates of boundary edges
|
|
123
128
|
*/
|
|
124
129
|
static collectExtendedBoundaryLoopsInGraph(graph: HalfEdgeGraph, exteriorMask: HalfEdgeMask): HalfEdge[][];
|
|
125
130
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HalfEdgeGraphSearch.d.ts","sourceRoot":"","sources":["../../../src/topology/HalfEdgeGraphSearch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HalfEdgeGraphSearch.d.ts","sourceRoot":"","sources":["../../../src/topology/HalfEdgeGraphSearch.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACjH,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAKxD,oEAAoE;AACpE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAChC;AACD,6CAA6C;AAC7C,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,YAAY,CAAU;IAC9B;;;;OAIG;gBACgB,IAAI,EAAE,YAAY,EAAE,WAAW,GAAE,OAAc;IAIlE,0EAA0E;IACnE,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO;CAGzC;AACD,+DAA+D;AAC/D,qBAAa,mBAAmB;IAC9B;;;;OAIG;WACW,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM;IAGpD;;;;;;OAMG;WACW,sBAAsB,CAClC,MAAM,EAAE,aAAa,GAAG,QAAQ,EAAE,EAClC,eAAe,GAAE,OAAe,EAChC,YAAY,GAAE,oBAAyE,GACtF,iBAAiB,CAAC,QAAQ,CAAC;IAa9B;;;;OAIG;WACW,mBAAmB,CAC/B,uBAAuB,EAAE,aAAa,GAAG,QAAQ,EAAE,EAAE,gBAAgB,CAAC,EAAE,oBAAoB,GAC3F,QAAQ,GAAG,SAAS;IAIvB;;;;;;OAMG;WACW,iBAAiB,CAC7B,MAAM,EAAE,aAAa,GAAG,QAAQ,EAAE,EAClC,8BAA8B,GAAE,OAAc,EAC9C,4BAA4B,GAAE,MAAU,GACvC,OAAO;IA6BV;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;IASxC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IA2BlC;;;;;;;;;OASG;IACH,OAAO,CAAC,MAAM,CAAC,8BAA8B;IAgB7C,sHAAsH;IACtH,OAAO,CAAC,MAAM,CAAC,8BAA8B;IAM7C;;;;;;;;;;;;;OAaG;WACW,iDAAiD,CAC7D,KAAK,EAAE,aAAa,EACpB,gBAAgB,EAAE,kBAAkB,GAAG,SAAS,EAChD,UAAU,GAAE,YAAqC,GAChD,QAAQ,EAAE,EAAE;IAgBf;;;;;;OAMG;WACW,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA2BrG;;;;;;;;OAQG;WACW,mCAAmC,CAC/C,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,YAAY,EACvB,cAAc,EAAE,yBAAyB,EACzC,sBAAsB,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,GAChE,IAAI;IAoBP;;;;;;;;;OASG;WACW,mCAAmC,CAAC,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,GAAG,QAAQ,EAAE,EAAE;CAkBlH"}
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
/** @packageDocumentation
|
|
6
6
|
* @module Topology
|
|
7
7
|
*/
|
|
8
|
+
import { Range1d } from "../geometry3d/Range";
|
|
8
9
|
import { HalfEdgeGraph, HalfEdgeMask } from "./Graph";
|
|
9
10
|
import { SignedDataSummary } from "./SignedDataSummary";
|
|
10
11
|
import { XYParitySearchContext } from "./XYParitySearchContext";
|
|
11
|
-
/**
|
|
12
|
-
*/
|
|
12
|
+
/** Class to test match of half edge mask. */
|
|
13
13
|
export class HalfEdgeMaskTester {
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Constructor
|
|
16
16
|
* @param mask mask to test in `testEdge` function
|
|
17
17
|
* @param targetValue value to match for true return
|
|
18
18
|
*/
|
|
@@ -20,47 +20,27 @@ export class HalfEdgeMaskTester {
|
|
|
20
20
|
this._targetMask = mask;
|
|
21
21
|
this._targetValue = targetValue;
|
|
22
22
|
}
|
|
23
|
-
/** Return true if the value of the targetMask matches the targetValue */
|
|
23
|
+
/** Return true if the value of the targetMask matches the targetValue. */
|
|
24
24
|
testEdge(edge) {
|
|
25
25
|
return edge.isMaskSet(this._targetMask) === this._targetValue;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
/** Class for different types of searches for HalfEdgeGraph. */
|
|
29
29
|
export class HalfEdgeGraphSearch {
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
* *
|
|
33
|
-
*/
|
|
34
|
-
static pushAndMaskAllNodesInFace(faceSeed, mask, allNodeStack, onePerFaceStack) {
|
|
35
|
-
onePerFaceStack.push(faceSeed);
|
|
36
|
-
faceSeed.collectAroundFace((node) => {
|
|
37
|
-
node.setMask(mask);
|
|
38
|
-
allNodeStack.push(node);
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Search an array of faceSeed nodes for the face with the most negative area.
|
|
43
|
-
* @param oneCandidateNodePerFace array containing one node from each face to be considered.
|
|
44
|
-
* @returns node on the minimum area face, or undefined if no such face (e.g., all faces have zero area).
|
|
45
|
-
*/
|
|
46
|
-
static findMinimumAreaFace(oneCandidateNodePerFace, faceAreaFunction) {
|
|
47
|
-
const summary = HalfEdgeGraphSearch.collectFaceAreaSummary(oneCandidateNodePerFace, false, faceAreaFunction);
|
|
48
|
-
return summary.largestNegativeItem;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* static method for face area computation -- useful as function parameter in collect FaceAreaSummary.
|
|
52
|
-
* * This simply calls `node.signedFaceArea ()`
|
|
31
|
+
* Static method for face area computation -- useful as function parameter in `collectFaceAreaSummary`.
|
|
32
|
+
* * This simply calls `node.signedFaceArea()`
|
|
53
33
|
* @param node instance for signedFaceArea call.
|
|
54
34
|
*/
|
|
55
|
-
static signedFaceArea(node) {
|
|
35
|
+
static signedFaceArea(node) {
|
|
36
|
+
return node.signedFaceArea();
|
|
37
|
+
}
|
|
56
38
|
/**
|
|
57
|
-
*
|
|
58
|
-
* Return a summary structure data about face (or other numeric quantity if the caller's areaFunction returns other value)
|
|
59
|
-
* * The default areaFunction computes area of polygonal face.
|
|
39
|
+
* Return a summary of face data (e.g., area) as computed by the callback on the faces of the graph.
|
|
60
40
|
* * Callers with curved edge graphs must supply their own area function.
|
|
61
|
-
* @param source graph or array of nodes to examine
|
|
62
|
-
* @param collectAllNodes flag to pass to the SignedDataSummary constructor to control collection of nodes.
|
|
63
|
-
* @param areaFunction function to
|
|
41
|
+
* @param source graph or array of nodes to examine.
|
|
42
|
+
* @param collectAllNodes flag to pass to the `SignedDataSummary` constructor to control collection of nodes.
|
|
43
|
+
* @param areaFunction function to obtain area (or other numeric value). Default computes polygonal face area.
|
|
64
44
|
*/
|
|
65
45
|
static collectFaceAreaSummary(source, collectAllNodes = false, areaFunction = (node) => HalfEdgeGraphSearch.signedFaceArea(node)) {
|
|
66
46
|
const result = new SignedDataSummary(collectAllNodes);
|
|
@@ -76,10 +56,19 @@ export class HalfEdgeGraphSearch {
|
|
|
76
56
|
return result;
|
|
77
57
|
}
|
|
78
58
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
59
|
+
* Search the graph for the face with the most negative area.
|
|
60
|
+
* @param oneCandidateNodePerFace graph or an array containing one node from each face to be considered.
|
|
61
|
+
* @returns node on the negative area face with largest absolute area, or `undefined` if no negative area face.
|
|
62
|
+
*/
|
|
63
|
+
static findMinimumAreaFace(oneCandidateNodePerFace, faceAreaFunction) {
|
|
64
|
+
const summary = HalfEdgeGraphSearch.collectFaceAreaSummary(oneCandidateNodePerFace, false, faceAreaFunction);
|
|
65
|
+
return summary.largestNegativeItem;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Test if the graph is triangulated.
|
|
69
|
+
* * Return `false` if:
|
|
70
|
+
* * number of positive area faces with more than 3 edges is larger than `numPositiveExceptionsAllowed`.
|
|
71
|
+
* * graph has more than 1 negative area face when `allowMultipleNegativeAreaFaces` is `false`.
|
|
83
72
|
* * 2-edge faces are ignored.
|
|
84
73
|
*/
|
|
85
74
|
static isTriangulatedCCW(source, allowMultipleNegativeAreaFaces = true, numPositiveExceptionsAllowed = 0) {
|
|
@@ -113,24 +102,41 @@ export class HalfEdgeGraphSearch {
|
|
|
113
102
|
return true;
|
|
114
103
|
}
|
|
115
104
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
|
|
122
|
-
|
|
105
|
+
* Process a face during graph traversal.
|
|
106
|
+
* @param faceSeed a node in the face.
|
|
107
|
+
* @param mask mask to set on each node of the face.
|
|
108
|
+
* @param allNodeStack array appended with each node of the face.
|
|
109
|
+
* @param onePerFaceStack array appended with `faceSeed`.
|
|
110
|
+
*/
|
|
111
|
+
static pushAndMaskAllNodesInFace(faceSeed, mask, allNodeStack, onePerFaceStack) {
|
|
112
|
+
onePerFaceStack.push(faceSeed);
|
|
113
|
+
faceSeed.collectAroundFace((node) => {
|
|
114
|
+
node.setMask(mask);
|
|
115
|
+
allNodeStack.push(node);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Traverse (via Depth First Search) to all accessible faces from the given seed.
|
|
120
|
+
* @param faceSeed first node to start the traverse.
|
|
123
121
|
* @param visitMask mask applied to all faces as visited.
|
|
124
|
-
* @param
|
|
122
|
+
* @param parityEdgeTester function to test if an edge is adjacent to two faces of opposite parity, e.g., a boundary
|
|
123
|
+
* edge that separates an "interior" face and an "exterior" face. If `parityEdgeTester` is not supplied and `parityMask`
|
|
124
|
+
* is supplied, the default parity rule is to alternate parity state in a "bullseye" pattern starting at the seed face,
|
|
125
|
+
* with each successive concentric ring of faces at constant topological distance from the seed face receiving the
|
|
126
|
+
* opposite parity state of the previous ring.
|
|
127
|
+
* @param parityMask mask to apply to the first face and faces that share the same parity as the first face, as
|
|
128
|
+
* determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If (non-null) parity mask
|
|
129
|
+
* is given, on return it is entirely set or entirely clear around each face.
|
|
130
|
+
* @returns an array that contains one representative node in each face of the connected component.
|
|
125
131
|
*/
|
|
126
|
-
static parityFloodFromSeed(
|
|
132
|
+
static parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask) {
|
|
127
133
|
const faces = [];
|
|
128
|
-
if (
|
|
129
|
-
return faces; // empty
|
|
134
|
+
if (faceSeed.isMaskSet(visitMask))
|
|
135
|
+
return faces; // empty array
|
|
130
136
|
const allMasks = parityMask | visitMask;
|
|
131
137
|
const stack = [];
|
|
132
|
-
//
|
|
133
|
-
HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(
|
|
138
|
+
// the seed face is arbitrarily assigned the parity mask
|
|
139
|
+
HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(faceSeed, allMasks, stack, faces);
|
|
134
140
|
while (stack.length > 0) {
|
|
135
141
|
const p = stack.pop();
|
|
136
142
|
const mate = p.edgeMate;
|
|
@@ -146,113 +152,123 @@ export class HalfEdgeGraphSearch {
|
|
|
146
152
|
return faces;
|
|
147
153
|
}
|
|
148
154
|
/**
|
|
149
|
-
* *
|
|
150
|
-
* *
|
|
155
|
+
* * Correct the parity mask in the faces of a component.
|
|
156
|
+
* * It is assumed that the parity mask is applied _consistently_ throughout the supplied faces, but maybe
|
|
157
|
+
* not _correctly_.
|
|
158
|
+
* * A consistently applied parity mask is "correct" if it is set on the negative area ("exterior") face of
|
|
159
|
+
* a connected component.
|
|
160
|
+
* * This method finds a face with negative area and toggles the mask throughout the input faces if this face
|
|
161
|
+
* lacks the parity mask.
|
|
151
162
|
* * In a properly merged planar subdivision there should be only one true negative area face per component.
|
|
152
|
-
* @param graph parent graph
|
|
153
|
-
* @param parityMask mask which was previously set with alternating parity, but with an arbitrary start face.
|
|
154
|
-
* @param faces array of faces to search.
|
|
155
163
|
*/
|
|
156
|
-
static correctParityInSingleComponent(
|
|
164
|
+
static correctParityInSingleComponent(parityMask, faces) {
|
|
157
165
|
const exteriorHalfEdge = HalfEdgeGraphSearch.findMinimumAreaFace(faces);
|
|
158
166
|
if (!exteriorHalfEdge) {
|
|
167
|
+
// graph has all degenerate faces; do nothing
|
|
159
168
|
}
|
|
160
|
-
else if (exteriorHalfEdge.isMaskSet(
|
|
161
|
-
// all should be well
|
|
169
|
+
else if (exteriorHalfEdge.isMaskSet(parityMask)) {
|
|
170
|
+
// all should be well; nothing to do
|
|
162
171
|
}
|
|
163
172
|
else {
|
|
164
|
-
// TOGGLE around the face (assuming all are consistent with the seed)
|
|
165
173
|
for (const faceSeed of faces) {
|
|
166
|
-
if (faceSeed.isMaskSet(
|
|
167
|
-
faceSeed.clearMaskAroundFace(
|
|
174
|
+
if (faceSeed.isMaskSet(parityMask)) {
|
|
175
|
+
faceSeed.clearMaskAroundFace(parityMask);
|
|
168
176
|
}
|
|
169
177
|
else {
|
|
170
|
-
faceSeed.setMaskAroundFace(
|
|
178
|
+
faceSeed.setMaskAroundFace(parityMask);
|
|
171
179
|
}
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
182
|
}
|
|
175
|
-
/** Apply correctParityInSingleComponent to each array in components
|
|
176
|
-
static correctParityInComponentArrays(
|
|
177
|
-
if (
|
|
183
|
+
/** Apply `correctParityInSingleComponent` to each array in components (quick exit if `parityMask` is `NULL_MASK`). */
|
|
184
|
+
static correctParityInComponentArrays(parityMask, components) {
|
|
185
|
+
if (parityMask === HalfEdgeMask.NULL_MASK)
|
|
178
186
|
return;
|
|
179
187
|
for (const facesInComponent of components)
|
|
180
|
-
HalfEdgeGraphSearch.correctParityInSingleComponent(
|
|
188
|
+
HalfEdgeGraphSearch.correctParityInSingleComponent(parityMask, facesInComponent);
|
|
181
189
|
}
|
|
182
190
|
/**
|
|
183
|
-
* Collect
|
|
184
|
-
* @param graph graph to inspect
|
|
185
|
-
* @param parityEdgeTester (optional) function to test if an edge is
|
|
186
|
-
*
|
|
191
|
+
* Collect connected components of the graph (via Depth First Search).
|
|
192
|
+
* @param graph graph to inspect.
|
|
193
|
+
* @param parityEdgeTester (optional) function to test if an edge is adjacent to two faces of opposite parity,
|
|
194
|
+
* e.g., a boundary edge that separates an "interior" face and an "exterior" face. If `parityEdgeTester` is not
|
|
195
|
+
* supplied and `parityMask` is supplied, the default parity rule is to alternate parity state in a "bullseye"
|
|
196
|
+
* pattern starting at the seed face, with each successive concentric ring of faces at constant topological
|
|
197
|
+
* distance from the seed face receiving the opposite parity state of the previous ring.
|
|
198
|
+
* @param parityMask (optional) mask to apply to the first face and faces that share the same parity as the
|
|
199
|
+
* first face, as determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If
|
|
200
|
+
* (non-null) parity mask is given, on return it is entirely set or entirely clear around each face.
|
|
201
|
+
* @returns the components of the graph, each component represented by an array of nodes, one node per face
|
|
202
|
+
* of the component. In other words, entry [i][j] is a HalfEdge in the j_th face loop of the i_th component.
|
|
187
203
|
*/
|
|
188
204
|
static collectConnectedComponentsWithExteriorParityMasks(graph, parityEdgeTester, parityMask = HalfEdgeMask.NULL_MASK) {
|
|
205
|
+
// Illustration of the algorithm can be found at geometry/internaldocs/Graph.md
|
|
189
206
|
const components = [];
|
|
190
207
|
const visitMask = HalfEdgeMask.VISITED;
|
|
191
208
|
const allMasks = parityMask | visitMask;
|
|
192
209
|
graph.clearMask(allMasks);
|
|
193
210
|
for (const faceSeed of graph.allHalfEdges) {
|
|
194
|
-
if (!faceSeed.isMaskSet(
|
|
211
|
+
if (!faceSeed.isMaskSet(visitMask)) {
|
|
195
212
|
const newFaces = HalfEdgeGraphSearch.parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask);
|
|
213
|
+
// parityFloodFromSeed does not return an empty array because it is called on an unvisited faceSeed
|
|
196
214
|
components.push(newFaces);
|
|
197
215
|
}
|
|
198
216
|
}
|
|
199
|
-
HalfEdgeGraphSearch.correctParityInComponentArrays(
|
|
217
|
+
HalfEdgeGraphSearch.correctParityInComponentArrays(parityMask, components);
|
|
200
218
|
return components;
|
|
201
219
|
}
|
|
202
220
|
/**
|
|
203
|
-
* Test if (
|
|
204
|
-
* @param seedNode any node on the face loop
|
|
205
|
-
* @param
|
|
206
|
-
* @param
|
|
221
|
+
* Test if test point (xTest,yTest) is inside/outside a face or on an edge.
|
|
222
|
+
* @param seedNode any node on the face loop.
|
|
223
|
+
* @param xTest x coordinate of the test point.
|
|
224
|
+
* @param yTest y coordinate of the test point.
|
|
225
|
+
* @returns 0 if ON, 1 if IN, -1 if OUT.
|
|
207
226
|
*/
|
|
208
|
-
static pointInOrOnFaceXY(seedNode,
|
|
209
|
-
const context = new XYParitySearchContext(
|
|
210
|
-
// walk around looking for an accepted node to start the search (seedNode is usually ok
|
|
227
|
+
static pointInOrOnFaceXY(seedNode, xTest, yTest) {
|
|
228
|
+
const context = new XYParitySearchContext(xTest, yTest);
|
|
229
|
+
// walk around looking for an accepted node to start the search (seedNode is usually ok)
|
|
211
230
|
let nodeA = seedNode;
|
|
212
231
|
let nodeB = seedNode.faceSuccessor;
|
|
213
232
|
for (;; nodeA = nodeB) {
|
|
214
233
|
if (context.tryStartEdge(nodeA.x, nodeA.y, nodeB.x, nodeB.y))
|
|
215
234
|
break;
|
|
216
235
|
if (nodeB === seedNode) {
|
|
217
|
-
//
|
|
218
|
-
|
|
236
|
+
// the test point and the face are all on line "y = yTest"
|
|
237
|
+
const range = Range1d.createXX(nodeB.x, nodeB.faceSuccessor.x);
|
|
238
|
+
return range.containsX(xTest) ? 0 : -1;
|
|
219
239
|
}
|
|
220
240
|
nodeB = nodeA.faceSuccessor;
|
|
221
241
|
}
|
|
222
|
-
// nodeB is the real start node for search
|
|
223
|
-
|
|
224
|
-
let node = nodeB.faceSuccessor;
|
|
242
|
+
// nodeB is the real start node for search, so stop when we revisit it. For each edge, accumulate parity and hits
|
|
243
|
+
let nodeC = nodeB.faceSuccessor;
|
|
225
244
|
for (;;) {
|
|
226
|
-
if (!context.advance(
|
|
245
|
+
if (!context.advance(nodeC.x, nodeC.y)) {
|
|
227
246
|
return context.classifyCounts();
|
|
228
247
|
}
|
|
229
|
-
if (
|
|
248
|
+
if (nodeC === nodeB)
|
|
230
249
|
break;
|
|
231
|
-
|
|
250
|
+
nodeC = nodeC.faceSuccessor;
|
|
232
251
|
}
|
|
233
252
|
return context.classifyCounts();
|
|
234
253
|
}
|
|
235
254
|
/**
|
|
236
|
-
*
|
|
237
|
-
* *
|
|
238
|
-
* * "Around the vertex" from nodeA means
|
|
239
|
-
* * First look at nodeA.faceSuccessor;
|
|
240
|
-
* * Then look at vertexPredecessor around that vertex loop.
|
|
241
|
-
* * Each accepted node is passed to announceNode, and marked with the visit mask.
|
|
242
|
-
* * The counter of the announceEdge function is zero for the first edge, then increases with each edge.
|
|
255
|
+
* Collect boundary edges starting from `seed`.
|
|
256
|
+
* * If `seed` is not a boundary node or is already visited, the function exists early.
|
|
243
257
|
* @param seed start node.
|
|
244
|
-
* @param
|
|
245
|
-
* @param
|
|
258
|
+
* @param visitMask mask to set on processed nodes.
|
|
259
|
+
* @param isBoundaryEdge function to test if an edge in a boundary edge.
|
|
260
|
+
* @param announceEdgeInBoundary callback invoked on each edge in the boundary loop in order. The counter is zero
|
|
261
|
+
* for the first edge, and incremented with each successive edge.
|
|
246
262
|
*/
|
|
247
|
-
static collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge,
|
|
263
|
+
static collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge, announceEdgeInBoundary) {
|
|
248
264
|
let counter = 0;
|
|
249
265
|
while (!seed.getMask(visitMask) && isBoundaryEdge(seed)) {
|
|
250
|
-
|
|
266
|
+
announceEdgeInBoundary(seed, counter++);
|
|
251
267
|
seed.setMask(visitMask);
|
|
252
268
|
const vertexBase = seed.faceSuccessor;
|
|
253
269
|
let candidateAroundVertex = vertexBase;
|
|
254
270
|
for (;;) {
|
|
255
|
-
if (candidateAroundVertex.getMask(visitMask))
|
|
271
|
+
if (candidateAroundVertex.getMask(visitMask)) // end of boundary loop
|
|
256
272
|
return;
|
|
257
273
|
if (isBoundaryEdge(candidateAroundVertex)) {
|
|
258
274
|
seed = candidateAroundVertex;
|
|
@@ -260,23 +276,22 @@ export class HalfEdgeGraphSearch {
|
|
|
260
276
|
}
|
|
261
277
|
candidateAroundVertex = candidateAroundVertex.vertexPredecessor;
|
|
262
278
|
if (candidateAroundVertex === vertexBase)
|
|
263
|
-
break;
|
|
279
|
+
break; // prevent infinite loop in case exteriorMask is not set on the edge mate of the boundary edge
|
|
264
280
|
}
|
|
265
281
|
}
|
|
266
282
|
}
|
|
267
283
|
/**
|
|
268
|
-
* Collect
|
|
269
|
-
* *
|
|
270
|
-
* *
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* @
|
|
276
|
-
* @param isBoundaryNode
|
|
277
|
-
* @param announceNode
|
|
284
|
+
* Collect boundary edges in the graph.
|
|
285
|
+
* * A boundary edge is defined by `exteriorMask` being set on only its "exterior" edge mate.
|
|
286
|
+
* * Each boundary edge is identified in the output by its edge mate that lacks `exteriorMask`.
|
|
287
|
+
* * Each inner array is ordered in the output so that its boundary edges form a connected path. If `exteriorMask`
|
|
288
|
+
* is preset consistently around each "exterior" face, these paths are loops.
|
|
289
|
+
* @param graph the graph to query
|
|
290
|
+
* @param exteriorMask mask preset on exactly one side of boundary edges
|
|
291
|
+
* @returns array of boundary loops, each loop an array of the unmasked mates of boundary edges
|
|
278
292
|
*/
|
|
279
293
|
static collectExtendedBoundaryLoopsInGraph(graph, exteriorMask) {
|
|
294
|
+
// Illustration of the algorithm can be found at geometry/internaldocs/Graph.md
|
|
280
295
|
const loops = [];
|
|
281
296
|
const visitMask = graph.grabMask(true);
|
|
282
297
|
const isBoundaryEdge = (edge) => {
|