@itwin/core-geometry 4.4.0-dev.2 → 4.4.0-dev.20
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/CHANGELOG.md +21 -1
- package/lib/cjs/geometry3d/YawPitchRollAngles.d.ts +7 -5
- package/lib/cjs/geometry3d/YawPitchRollAngles.d.ts.map +1 -1
- package/lib/cjs/geometry3d/YawPitchRollAngles.js +8 -6
- package/lib/cjs/geometry3d/YawPitchRollAngles.js.map +1 -1
- 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 +18 -10
- package/lib/cjs/topology/Graph.d.ts.map +1 -1
- package/lib/cjs/topology/Graph.js +20 -10
- 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/HalfEdgeGraphValidation.d.ts +23 -18
- package/lib/cjs/topology/HalfEdgeGraphValidation.d.ts.map +1 -1
- package/lib/cjs/topology/HalfEdgeGraphValidation.js +23 -18
- package/lib/cjs/topology/HalfEdgeGraphValidation.js.map +1 -1
- package/lib/cjs/topology/HalfEdgeNodeXYZUV.d.ts +17 -10
- package/lib/cjs/topology/HalfEdgeNodeXYZUV.d.ts.map +1 -1
- package/lib/cjs/topology/HalfEdgeNodeXYZUV.js +38 -17
- package/lib/cjs/topology/HalfEdgeNodeXYZUV.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/geometry3d/YawPitchRollAngles.d.ts +7 -5
- package/lib/esm/geometry3d/YawPitchRollAngles.d.ts.map +1 -1
- package/lib/esm/geometry3d/YawPitchRollAngles.js +8 -6
- package/lib/esm/geometry3d/YawPitchRollAngles.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 +18 -10
- package/lib/esm/topology/Graph.d.ts.map +1 -1
- package/lib/esm/topology/Graph.js +20 -10
- 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/HalfEdgeGraphValidation.d.ts +23 -18
- package/lib/esm/topology/HalfEdgeGraphValidation.d.ts.map +1 -1
- package/lib/esm/topology/HalfEdgeGraphValidation.js +23 -18
- package/lib/esm/topology/HalfEdgeGraphValidation.js.map +1 -1
- package/lib/esm/topology/HalfEdgeNodeXYZUV.d.ts +17 -10
- package/lib/esm/topology/HalfEdgeNodeXYZUV.d.ts.map +1 -1
- package/lib/esm/topology/HalfEdgeNodeXYZUV.js +38 -17
- package/lib/esm/topology/HalfEdgeNodeXYZUV.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 +5 -5
|
@@ -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"}
|
|
@@ -8,14 +8,14 @@ exports.HalfEdgeGraphSearch = exports.HalfEdgeMaskTester = void 0;
|
|
|
8
8
|
/** @packageDocumentation
|
|
9
9
|
* @module Topology
|
|
10
10
|
*/
|
|
11
|
+
const Range_1 = require("../geometry3d/Range");
|
|
11
12
|
const Graph_1 = require("./Graph");
|
|
12
13
|
const SignedDataSummary_1 = require("./SignedDataSummary");
|
|
13
14
|
const XYParitySearchContext_1 = require("./XYParitySearchContext");
|
|
14
|
-
/**
|
|
15
|
-
*/
|
|
15
|
+
/** Class to test match of half edge mask. */
|
|
16
16
|
class HalfEdgeMaskTester {
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* Constructor
|
|
19
19
|
* @param mask mask to test in `testEdge` function
|
|
20
20
|
* @param targetValue value to match for true return
|
|
21
21
|
*/
|
|
@@ -23,48 +23,28 @@ class HalfEdgeMaskTester {
|
|
|
23
23
|
this._targetMask = mask;
|
|
24
24
|
this._targetValue = targetValue;
|
|
25
25
|
}
|
|
26
|
-
/** Return true if the value of the targetMask matches the targetValue */
|
|
26
|
+
/** Return true if the value of the targetMask matches the targetValue. */
|
|
27
27
|
testEdge(edge) {
|
|
28
28
|
return edge.isMaskSet(this._targetMask) === this._targetValue;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
exports.HalfEdgeMaskTester = HalfEdgeMaskTester;
|
|
32
|
-
|
|
32
|
+
/** Class for different types of searches for HalfEdgeGraph. */
|
|
33
33
|
class HalfEdgeGraphSearch {
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
* *
|
|
37
|
-
*/
|
|
38
|
-
static pushAndMaskAllNodesInFace(faceSeed, mask, allNodeStack, onePerFaceStack) {
|
|
39
|
-
onePerFaceStack.push(faceSeed);
|
|
40
|
-
faceSeed.collectAroundFace((node) => {
|
|
41
|
-
node.setMask(mask);
|
|
42
|
-
allNodeStack.push(node);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Search an array of faceSeed nodes for the face with the most negative area.
|
|
47
|
-
* @param oneCandidateNodePerFace array containing one node from each face to be considered.
|
|
48
|
-
* @returns node on the minimum area face, or undefined if no such face (e.g., all faces have zero area).
|
|
49
|
-
*/
|
|
50
|
-
static findMinimumAreaFace(oneCandidateNodePerFace, faceAreaFunction) {
|
|
51
|
-
const summary = HalfEdgeGraphSearch.collectFaceAreaSummary(oneCandidateNodePerFace, false, faceAreaFunction);
|
|
52
|
-
return summary.largestNegativeItem;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* static method for face area computation -- useful as function parameter in collect FaceAreaSummary.
|
|
56
|
-
* * This simply calls `node.signedFaceArea ()`
|
|
35
|
+
* Static method for face area computation -- useful as function parameter in `collectFaceAreaSummary`.
|
|
36
|
+
* * This simply calls `node.signedFaceArea()`
|
|
57
37
|
* @param node instance for signedFaceArea call.
|
|
58
38
|
*/
|
|
59
|
-
static signedFaceArea(node) {
|
|
39
|
+
static signedFaceArea(node) {
|
|
40
|
+
return node.signedFaceArea();
|
|
41
|
+
}
|
|
60
42
|
/**
|
|
61
|
-
*
|
|
62
|
-
* Return a summary structure data about face (or other numeric quantity if the caller's areaFunction returns other value)
|
|
63
|
-
* * The default areaFunction computes area of polygonal face.
|
|
43
|
+
* Return a summary of face data (e.g., area) as computed by the callback on the faces of the graph.
|
|
64
44
|
* * Callers with curved edge graphs must supply their own area function.
|
|
65
|
-
* @param source graph or array of nodes to examine
|
|
66
|
-
* @param collectAllNodes flag to pass to the SignedDataSummary constructor to control collection of nodes.
|
|
67
|
-
* @param areaFunction function to
|
|
45
|
+
* @param source graph or array of nodes to examine.
|
|
46
|
+
* @param collectAllNodes flag to pass to the `SignedDataSummary` constructor to control collection of nodes.
|
|
47
|
+
* @param areaFunction function to obtain area (or other numeric value). Default computes polygonal face area.
|
|
68
48
|
*/
|
|
69
49
|
static collectFaceAreaSummary(source, collectAllNodes = false, areaFunction = (node) => HalfEdgeGraphSearch.signedFaceArea(node)) {
|
|
70
50
|
const result = new SignedDataSummary_1.SignedDataSummary(collectAllNodes);
|
|
@@ -80,10 +60,19 @@ class HalfEdgeGraphSearch {
|
|
|
80
60
|
return result;
|
|
81
61
|
}
|
|
82
62
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
|
|
63
|
+
* Search the graph for the face with the most negative area.
|
|
64
|
+
* @param oneCandidateNodePerFace graph or an array containing one node from each face to be considered.
|
|
65
|
+
* @returns node on the negative area face with largest absolute area, or `undefined` if no negative area face.
|
|
66
|
+
*/
|
|
67
|
+
static findMinimumAreaFace(oneCandidateNodePerFace, faceAreaFunction) {
|
|
68
|
+
const summary = HalfEdgeGraphSearch.collectFaceAreaSummary(oneCandidateNodePerFace, false, faceAreaFunction);
|
|
69
|
+
return summary.largestNegativeItem;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Test if the graph is triangulated.
|
|
73
|
+
* * Return `false` if:
|
|
74
|
+
* * number of positive area faces with more than 3 edges is larger than `numPositiveExceptionsAllowed`.
|
|
75
|
+
* * graph has more than 1 negative area face when `allowMultipleNegativeAreaFaces` is `false`.
|
|
87
76
|
* * 2-edge faces are ignored.
|
|
88
77
|
*/
|
|
89
78
|
static isTriangulatedCCW(source, allowMultipleNegativeAreaFaces = true, numPositiveExceptionsAllowed = 0) {
|
|
@@ -117,24 +106,41 @@ class HalfEdgeGraphSearch {
|
|
|
117
106
|
return true;
|
|
118
107
|
}
|
|
119
108
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
* Process a face during graph traversal.
|
|
110
|
+
* @param faceSeed a node in the face.
|
|
111
|
+
* @param mask mask to set on each node of the face.
|
|
112
|
+
* @param allNodeStack array appended with each node of the face.
|
|
113
|
+
* @param onePerFaceStack array appended with `faceSeed`.
|
|
114
|
+
*/
|
|
115
|
+
static pushAndMaskAllNodesInFace(faceSeed, mask, allNodeStack, onePerFaceStack) {
|
|
116
|
+
onePerFaceStack.push(faceSeed);
|
|
117
|
+
faceSeed.collectAroundFace((node) => {
|
|
118
|
+
node.setMask(mask);
|
|
119
|
+
allNodeStack.push(node);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Traverse (via Depth First Search) to all accessible faces from the given seed.
|
|
124
|
+
* @param faceSeed first node to start the traverse.
|
|
127
125
|
* @param visitMask mask applied to all faces as visited.
|
|
128
|
-
* @param
|
|
126
|
+
* @param parityEdgeTester function to test if an edge is adjacent to two faces of opposite parity, e.g., a boundary
|
|
127
|
+
* edge that separates an "interior" face and an "exterior" face. If `parityEdgeTester` is not supplied and `parityMask`
|
|
128
|
+
* is supplied, the default parity rule is to alternate parity state in a "bullseye" pattern starting at the seed face,
|
|
129
|
+
* with each successive concentric ring of faces at constant topological distance from the seed face receiving the
|
|
130
|
+
* opposite parity state of the previous ring.
|
|
131
|
+
* @param parityMask mask to apply to the first face and faces that share the same parity as the first face, as
|
|
132
|
+
* determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If (non-null) parity mask
|
|
133
|
+
* is given, on return it is entirely set or entirely clear around each face.
|
|
134
|
+
* @returns an array that contains one representative node in each face of the connected component.
|
|
129
135
|
*/
|
|
130
|
-
static parityFloodFromSeed(
|
|
136
|
+
static parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask) {
|
|
131
137
|
const faces = [];
|
|
132
|
-
if (
|
|
133
|
-
return faces; // empty
|
|
138
|
+
if (faceSeed.isMaskSet(visitMask))
|
|
139
|
+
return faces; // empty array
|
|
134
140
|
const allMasks = parityMask | visitMask;
|
|
135
141
|
const stack = [];
|
|
136
|
-
//
|
|
137
|
-
HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(
|
|
142
|
+
// the seed face is arbitrarily assigned the parity mask
|
|
143
|
+
HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(faceSeed, allMasks, stack, faces);
|
|
138
144
|
while (stack.length > 0) {
|
|
139
145
|
const p = stack.pop();
|
|
140
146
|
const mate = p.edgeMate;
|
|
@@ -150,113 +156,123 @@ class HalfEdgeGraphSearch {
|
|
|
150
156
|
return faces;
|
|
151
157
|
}
|
|
152
158
|
/**
|
|
153
|
-
* *
|
|
154
|
-
* *
|
|
159
|
+
* * Correct the parity mask in the faces of a component.
|
|
160
|
+
* * It is assumed that the parity mask is applied _consistently_ throughout the supplied faces, but maybe
|
|
161
|
+
* not _correctly_.
|
|
162
|
+
* * A consistently applied parity mask is "correct" if it is set on the negative area ("exterior") face of
|
|
163
|
+
* a connected component.
|
|
164
|
+
* * This method finds a face with negative area and toggles the mask throughout the input faces if this face
|
|
165
|
+
* lacks the parity mask.
|
|
155
166
|
* * In a properly merged planar subdivision there should be only one true negative area face per component.
|
|
156
|
-
* @param graph parent graph
|
|
157
|
-
* @param parityMask mask which was previously set with alternating parity, but with an arbitrary start face.
|
|
158
|
-
* @param faces array of faces to search.
|
|
159
167
|
*/
|
|
160
|
-
static correctParityInSingleComponent(
|
|
168
|
+
static correctParityInSingleComponent(parityMask, faces) {
|
|
161
169
|
const exteriorHalfEdge = HalfEdgeGraphSearch.findMinimumAreaFace(faces);
|
|
162
170
|
if (!exteriorHalfEdge) {
|
|
171
|
+
// graph has all degenerate faces; do nothing
|
|
163
172
|
}
|
|
164
|
-
else if (exteriorHalfEdge.isMaskSet(
|
|
165
|
-
// all should be well
|
|
173
|
+
else if (exteriorHalfEdge.isMaskSet(parityMask)) {
|
|
174
|
+
// all should be well; nothing to do
|
|
166
175
|
}
|
|
167
176
|
else {
|
|
168
|
-
// TOGGLE around the face (assuming all are consistent with the seed)
|
|
169
177
|
for (const faceSeed of faces) {
|
|
170
|
-
if (faceSeed.isMaskSet(
|
|
171
|
-
faceSeed.clearMaskAroundFace(
|
|
178
|
+
if (faceSeed.isMaskSet(parityMask)) {
|
|
179
|
+
faceSeed.clearMaskAroundFace(parityMask);
|
|
172
180
|
}
|
|
173
181
|
else {
|
|
174
|
-
faceSeed.setMaskAroundFace(
|
|
182
|
+
faceSeed.setMaskAroundFace(parityMask);
|
|
175
183
|
}
|
|
176
184
|
}
|
|
177
185
|
}
|
|
178
186
|
}
|
|
179
|
-
/** Apply correctParityInSingleComponent to each array in components
|
|
180
|
-
static correctParityInComponentArrays(
|
|
181
|
-
if (
|
|
187
|
+
/** Apply `correctParityInSingleComponent` to each array in components (quick exit if `parityMask` is `NULL_MASK`). */
|
|
188
|
+
static correctParityInComponentArrays(parityMask, components) {
|
|
189
|
+
if (parityMask === Graph_1.HalfEdgeMask.NULL_MASK)
|
|
182
190
|
return;
|
|
183
191
|
for (const facesInComponent of components)
|
|
184
|
-
HalfEdgeGraphSearch.correctParityInSingleComponent(
|
|
192
|
+
HalfEdgeGraphSearch.correctParityInSingleComponent(parityMask, facesInComponent);
|
|
185
193
|
}
|
|
186
194
|
/**
|
|
187
|
-
* Collect
|
|
188
|
-
* @param graph graph to inspect
|
|
189
|
-
* @param parityEdgeTester (optional) function to test if an edge is
|
|
190
|
-
*
|
|
195
|
+
* Collect connected components of the graph (via Depth First Search).
|
|
196
|
+
* @param graph graph to inspect.
|
|
197
|
+
* @param parityEdgeTester (optional) function to test if an edge is adjacent to two faces of opposite parity,
|
|
198
|
+
* e.g., a boundary edge that separates an "interior" face and an "exterior" face. If `parityEdgeTester` is not
|
|
199
|
+
* supplied and `parityMask` is supplied, the default parity rule is to alternate parity state in a "bullseye"
|
|
200
|
+
* pattern starting at the seed face, with each successive concentric ring of faces at constant topological
|
|
201
|
+
* distance from the seed face receiving the opposite parity state of the previous ring.
|
|
202
|
+
* @param parityMask (optional) mask to apply to the first face and faces that share the same parity as the
|
|
203
|
+
* first face, as determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If
|
|
204
|
+
* (non-null) parity mask is given, on return it is entirely set or entirely clear around each face.
|
|
205
|
+
* @returns the components of the graph, each component represented by an array of nodes, one node per face
|
|
206
|
+
* of the component. In other words, entry [i][j] is a HalfEdge in the j_th face loop of the i_th component.
|
|
191
207
|
*/
|
|
192
208
|
static collectConnectedComponentsWithExteriorParityMasks(graph, parityEdgeTester, parityMask = Graph_1.HalfEdgeMask.NULL_MASK) {
|
|
209
|
+
// Illustration of the algorithm can be found at geometry/internaldocs/Graph.md
|
|
193
210
|
const components = [];
|
|
194
211
|
const visitMask = Graph_1.HalfEdgeMask.VISITED;
|
|
195
212
|
const allMasks = parityMask | visitMask;
|
|
196
213
|
graph.clearMask(allMasks);
|
|
197
214
|
for (const faceSeed of graph.allHalfEdges) {
|
|
198
|
-
if (!faceSeed.isMaskSet(
|
|
215
|
+
if (!faceSeed.isMaskSet(visitMask)) {
|
|
199
216
|
const newFaces = HalfEdgeGraphSearch.parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask);
|
|
217
|
+
// parityFloodFromSeed does not return an empty array because it is called on an unvisited faceSeed
|
|
200
218
|
components.push(newFaces);
|
|
201
219
|
}
|
|
202
220
|
}
|
|
203
|
-
HalfEdgeGraphSearch.correctParityInComponentArrays(
|
|
221
|
+
HalfEdgeGraphSearch.correctParityInComponentArrays(parityMask, components);
|
|
204
222
|
return components;
|
|
205
223
|
}
|
|
206
224
|
/**
|
|
207
|
-
* Test if (
|
|
208
|
-
* @param seedNode any node on the face loop
|
|
209
|
-
* @param
|
|
210
|
-
* @param
|
|
225
|
+
* Test if test point (xTest,yTest) is inside/outside a face or on an edge.
|
|
226
|
+
* @param seedNode any node on the face loop.
|
|
227
|
+
* @param xTest x coordinate of the test point.
|
|
228
|
+
* @param yTest y coordinate of the test point.
|
|
229
|
+
* @returns 0 if ON, 1 if IN, -1 if OUT.
|
|
211
230
|
*/
|
|
212
|
-
static pointInOrOnFaceXY(seedNode,
|
|
213
|
-
const context = new XYParitySearchContext_1.XYParitySearchContext(
|
|
214
|
-
// walk around looking for an accepted node to start the search (seedNode is usually ok
|
|
231
|
+
static pointInOrOnFaceXY(seedNode, xTest, yTest) {
|
|
232
|
+
const context = new XYParitySearchContext_1.XYParitySearchContext(xTest, yTest);
|
|
233
|
+
// walk around looking for an accepted node to start the search (seedNode is usually ok)
|
|
215
234
|
let nodeA = seedNode;
|
|
216
235
|
let nodeB = seedNode.faceSuccessor;
|
|
217
236
|
for (;; nodeA = nodeB) {
|
|
218
237
|
if (context.tryStartEdge(nodeA.x, nodeA.y, nodeB.x, nodeB.y))
|
|
219
238
|
break;
|
|
220
239
|
if (nodeB === seedNode) {
|
|
221
|
-
//
|
|
222
|
-
|
|
240
|
+
// the test point and the face are all on line "y = yTest"
|
|
241
|
+
const range = Range_1.Range1d.createXX(nodeB.x, nodeB.faceSuccessor.x);
|
|
242
|
+
return range.containsX(xTest) ? 0 : -1;
|
|
223
243
|
}
|
|
224
244
|
nodeB = nodeA.faceSuccessor;
|
|
225
245
|
}
|
|
226
|
-
// nodeB is the real start node for search
|
|
227
|
-
|
|
228
|
-
let node = nodeB.faceSuccessor;
|
|
246
|
+
// nodeB is the real start node for search, so stop when we revisit it. For each edge, accumulate parity and hits
|
|
247
|
+
let nodeC = nodeB.faceSuccessor;
|
|
229
248
|
for (;;) {
|
|
230
|
-
if (!context.advance(
|
|
249
|
+
if (!context.advance(nodeC.x, nodeC.y)) {
|
|
231
250
|
return context.classifyCounts();
|
|
232
251
|
}
|
|
233
|
-
if (
|
|
252
|
+
if (nodeC === nodeB)
|
|
234
253
|
break;
|
|
235
|
-
|
|
254
|
+
nodeC = nodeC.faceSuccessor;
|
|
236
255
|
}
|
|
237
256
|
return context.classifyCounts();
|
|
238
257
|
}
|
|
239
258
|
/**
|
|
240
|
-
*
|
|
241
|
-
* *
|
|
242
|
-
* * "Around the vertex" from nodeA means
|
|
243
|
-
* * First look at nodeA.faceSuccessor;
|
|
244
|
-
* * Then look at vertexPredecessor around that vertex loop.
|
|
245
|
-
* * Each accepted node is passed to announceNode, and marked with the visit mask.
|
|
246
|
-
* * The counter of the announceEdge function is zero for the first edge, then increases with each edge.
|
|
259
|
+
* Collect boundary edges starting from `seed`.
|
|
260
|
+
* * If `seed` is not a boundary node or is already visited, the function exists early.
|
|
247
261
|
* @param seed start node.
|
|
248
|
-
* @param
|
|
249
|
-
* @param
|
|
262
|
+
* @param visitMask mask to set on processed nodes.
|
|
263
|
+
* @param isBoundaryEdge function to test if an edge in a boundary edge.
|
|
264
|
+
* @param announceEdgeInBoundary callback invoked on each edge in the boundary loop in order. The counter is zero
|
|
265
|
+
* for the first edge, and incremented with each successive edge.
|
|
250
266
|
*/
|
|
251
|
-
static collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge,
|
|
267
|
+
static collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge, announceEdgeInBoundary) {
|
|
252
268
|
let counter = 0;
|
|
253
269
|
while (!seed.getMask(visitMask) && isBoundaryEdge(seed)) {
|
|
254
|
-
|
|
270
|
+
announceEdgeInBoundary(seed, counter++);
|
|
255
271
|
seed.setMask(visitMask);
|
|
256
272
|
const vertexBase = seed.faceSuccessor;
|
|
257
273
|
let candidateAroundVertex = vertexBase;
|
|
258
274
|
for (;;) {
|
|
259
|
-
if (candidateAroundVertex.getMask(visitMask))
|
|
275
|
+
if (candidateAroundVertex.getMask(visitMask)) // end of boundary loop
|
|
260
276
|
return;
|
|
261
277
|
if (isBoundaryEdge(candidateAroundVertex)) {
|
|
262
278
|
seed = candidateAroundVertex;
|
|
@@ -264,23 +280,22 @@ class HalfEdgeGraphSearch {
|
|
|
264
280
|
}
|
|
265
281
|
candidateAroundVertex = candidateAroundVertex.vertexPredecessor;
|
|
266
282
|
if (candidateAroundVertex === vertexBase)
|
|
267
|
-
break;
|
|
283
|
+
break; // prevent infinite loop in case exteriorMask is not set on the edge mate of the boundary edge
|
|
268
284
|
}
|
|
269
285
|
}
|
|
270
286
|
}
|
|
271
287
|
/**
|
|
272
|
-
* Collect
|
|
273
|
-
* *
|
|
274
|
-
* *
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* @
|
|
280
|
-
* @param isBoundaryNode
|
|
281
|
-
* @param announceNode
|
|
288
|
+
* Collect boundary edges in the graph.
|
|
289
|
+
* * A boundary edge is defined by `exteriorMask` being set on only its "exterior" edge mate.
|
|
290
|
+
* * Each boundary edge is identified in the output by its edge mate that lacks `exteriorMask`.
|
|
291
|
+
* * Each inner array is ordered in the output so that its boundary edges form a connected path. If `exteriorMask`
|
|
292
|
+
* is preset consistently around each "exterior" face, these paths are loops.
|
|
293
|
+
* @param graph the graph to query
|
|
294
|
+
* @param exteriorMask mask preset on exactly one side of boundary edges
|
|
295
|
+
* @returns array of boundary loops, each loop an array of the unmasked mates of boundary edges
|
|
282
296
|
*/
|
|
283
297
|
static collectExtendedBoundaryLoopsInGraph(graph, exteriorMask) {
|
|
298
|
+
// Illustration of the algorithm can be found at geometry/internaldocs/Graph.md
|
|
284
299
|
const loops = [];
|
|
285
300
|
const visitMask = graph.grabMask(true);
|
|
286
301
|
const isBoundaryEdge = (edge) => {
|