@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.
Files changed (66) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/lib/cjs/geometry3d/YawPitchRollAngles.d.ts +7 -5
  3. package/lib/cjs/geometry3d/YawPitchRollAngles.d.ts.map +1 -1
  4. package/lib/cjs/geometry3d/YawPitchRollAngles.js +8 -6
  5. package/lib/cjs/geometry3d/YawPitchRollAngles.js.map +1 -1
  6. package/lib/cjs/serialization/GeometrySamples.d.ts +11 -0
  7. package/lib/cjs/serialization/GeometrySamples.d.ts.map +1 -1
  8. package/lib/cjs/serialization/GeometrySamples.js +16 -0
  9. package/lib/cjs/serialization/GeometrySamples.js.map +1 -1
  10. package/lib/cjs/topology/Graph.d.ts +18 -10
  11. package/lib/cjs/topology/Graph.d.ts.map +1 -1
  12. package/lib/cjs/topology/Graph.js +20 -10
  13. package/lib/cjs/topology/Graph.js.map +1 -1
  14. package/lib/cjs/topology/HalfEdgeGraphSearch.d.ts +81 -76
  15. package/lib/cjs/topology/HalfEdgeGraphSearch.d.ts.map +1 -1
  16. package/lib/cjs/topology/HalfEdgeGraphSearch.js +126 -111
  17. package/lib/cjs/topology/HalfEdgeGraphSearch.js.map +1 -1
  18. package/lib/cjs/topology/HalfEdgeGraphValidation.d.ts +23 -18
  19. package/lib/cjs/topology/HalfEdgeGraphValidation.d.ts.map +1 -1
  20. package/lib/cjs/topology/HalfEdgeGraphValidation.js +23 -18
  21. package/lib/cjs/topology/HalfEdgeGraphValidation.js.map +1 -1
  22. package/lib/cjs/topology/HalfEdgeNodeXYZUV.d.ts +17 -10
  23. package/lib/cjs/topology/HalfEdgeNodeXYZUV.d.ts.map +1 -1
  24. package/lib/cjs/topology/HalfEdgeNodeXYZUV.js +38 -17
  25. package/lib/cjs/topology/HalfEdgeNodeXYZUV.js.map +1 -1
  26. package/lib/cjs/topology/SignedDataSummary.d.ts +13 -13
  27. package/lib/cjs/topology/SignedDataSummary.d.ts.map +1 -1
  28. package/lib/cjs/topology/SignedDataSummary.js +3 -3
  29. package/lib/cjs/topology/SignedDataSummary.js.map +1 -1
  30. package/lib/cjs/topology/XYParitySearchContext.d.ts +27 -21
  31. package/lib/cjs/topology/XYParitySearchContext.d.ts.map +1 -1
  32. package/lib/cjs/topology/XYParitySearchContext.js +73 -71
  33. package/lib/cjs/topology/XYParitySearchContext.js.map +1 -1
  34. package/lib/esm/geometry3d/YawPitchRollAngles.d.ts +7 -5
  35. package/lib/esm/geometry3d/YawPitchRollAngles.d.ts.map +1 -1
  36. package/lib/esm/geometry3d/YawPitchRollAngles.js +8 -6
  37. package/lib/esm/geometry3d/YawPitchRollAngles.js.map +1 -1
  38. package/lib/esm/serialization/GeometrySamples.d.ts +11 -0
  39. package/lib/esm/serialization/GeometrySamples.d.ts.map +1 -1
  40. package/lib/esm/serialization/GeometrySamples.js +16 -0
  41. package/lib/esm/serialization/GeometrySamples.js.map +1 -1
  42. package/lib/esm/topology/Graph.d.ts +18 -10
  43. package/lib/esm/topology/Graph.d.ts.map +1 -1
  44. package/lib/esm/topology/Graph.js +20 -10
  45. package/lib/esm/topology/Graph.js.map +1 -1
  46. package/lib/esm/topology/HalfEdgeGraphSearch.d.ts +81 -76
  47. package/lib/esm/topology/HalfEdgeGraphSearch.d.ts.map +1 -1
  48. package/lib/esm/topology/HalfEdgeGraphSearch.js +126 -111
  49. package/lib/esm/topology/HalfEdgeGraphSearch.js.map +1 -1
  50. package/lib/esm/topology/HalfEdgeGraphValidation.d.ts +23 -18
  51. package/lib/esm/topology/HalfEdgeGraphValidation.d.ts.map +1 -1
  52. package/lib/esm/topology/HalfEdgeGraphValidation.js +23 -18
  53. package/lib/esm/topology/HalfEdgeGraphValidation.js.map +1 -1
  54. package/lib/esm/topology/HalfEdgeNodeXYZUV.d.ts +17 -10
  55. package/lib/esm/topology/HalfEdgeNodeXYZUV.d.ts.map +1 -1
  56. package/lib/esm/topology/HalfEdgeNodeXYZUV.js +38 -17
  57. package/lib/esm/topology/HalfEdgeNodeXYZUV.js.map +1 -1
  58. package/lib/esm/topology/SignedDataSummary.d.ts +13 -13
  59. package/lib/esm/topology/SignedDataSummary.d.ts.map +1 -1
  60. package/lib/esm/topology/SignedDataSummary.js +3 -3
  61. package/lib/esm/topology/SignedDataSummary.js.map +1 -1
  62. package/lib/esm/topology/XYParitySearchContext.d.ts +27 -21
  63. package/lib/esm/topology/XYParitySearchContext.d.ts.map +1 -1
  64. package/lib/esm/topology/XYParitySearchContext.js +73 -71
  65. package/lib/esm/topology/XYParitySearchContext.js.map +1 -1
  66. 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
- * * for each node of face, set the mask push to allNodesStack
29
- * * push the faceSeed on onePerFaceStack[]
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 all to obtain area (or other numeric value)
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
- * * Test if the graph is triangulated.
56
- * * Return false if:
57
- * * Positive area face with more than 3 edges
58
- * * more than 1 negative area face with `allowMultipleNegativeAreaFaces` false
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
- * Search to all accessible faces from given seed.
64
- * * The returned array contains one representative node in each face of the connected component.
65
- * * If (nonnull) parity mask is given, on return:
66
- * * It is entirely set or entirely clear around each face
67
- * * It is entirely set on all faces that are an even number of face-to-face steps away from the seed.
68
- * * It is entirely clear on all faces that are an odd number of face-to-face steps away from the seed.
69
- * @param seedEdge first edge to search.
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 parityMask mask to apply (a) to first face, (b) to faces with alternating parity during the search.
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
- * * Search the given faces for the one with the minimum area.
76
- * * If the mask in that face is OFF, toggle it on (all half edges of) all the faces.
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. (Quick exit if mask in NULL_MASK) */
84
+ /** Apply `correctParityInSingleComponent` to each array in components (quick exit if `parityMask` is `NULL_MASK`). */
84
85
  private static correctParityInComponentArrays;
85
86
  /**
86
- * Collect arrays gathering faces by connected component.
87
- * @param graph graph to inspect
88
- * @param parityEdgeTester (optional) function to test if an edge is a parity change (e.g., a boundary edge).
89
- * @param parityMask (optional, along with parityEdgeTester) mask to apply indicating parity. If this is Mask.NULL_MASK, there is no record of parity.
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 (x,y) is inside (1), on an edge (0) or outside (-1) a face.
94
- * @param seedNode any node on the face loop
95
- * @param x x coordinate of test point.
96
- * @param y y coordinate of test point.
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, x: number, y: number): number | undefined;
108
+ static pointInOrOnFaceXY(seedNode: HalfEdge, xTest: number, yTest: number): number | undefined;
99
109
  /**
100
- * Announce nodes that are "extended face boundary" by conditions (usually mask of node and mate) in test functions.
101
- * * After each node, the next candidate in reached by looking "around the head vertex loop" for the next boundary.
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 isBoundaryEdge
109
- * @param announceEdge
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, announceEdge: (edge: HalfEdge, counter: number) => void): void;
118
+ static collectExtendedBoundaryLoopFromSeed(seed: HalfEdge, visitMask: HalfEdgeMask, isBoundaryEdge: HalfEdgeToBooleanFunction, announceEdgeInBoundary: (edge: HalfEdge, counter: number) => void): void;
112
119
  /**
113
- * Collect arrays of nodes "around the boundary" of a graph with extraneous (non-boundary) edges.
114
- * * The "boundary" is nodes that do NOT have the exterior mask, but whose mates DO have the exterior mask.
115
- * * After each node, the next candidate in reached by looking "around the head vertex loop" for the next boundary.
116
- * * "Around the vertex" from nodeA means
117
- * * First look at nodeA.faceSuccessor;
118
- * * Then look at vertexPredecessor around that vertex loop.
119
- * * Each accepted node is passed to announceNode, and marked with the visit mask.
120
- * @param seed start node.
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":"AAKA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACjH,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAChC;AACD;GACG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAe;IAClC,OAAO,CAAC,YAAY,CAAU;IAC9B;;;;OAIG;gBACgB,IAAI,EAAE,YAAY,EAAE,WAAW,GAAE,OAAc;IAIlE,yEAAyE;IAClE,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO;CAIzC;AAED,qBAAa,mBAAmB;IAE9B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;IAQxC;;;;OAIG;WACW,mBAAmB,CAAC,uBAAuB,EAAE,aAAa,GAAG,QAAQ,EAAE,EAAE,gBAAgB,CAAC,EAAE,oBAAoB,GAAG,QAAQ,GAAG,SAAS;IAKrJ;;;;OAIG;WACW,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM;IACpD;;;;;;;;OAQG;WACW,sBAAsB,CAAC,MAAM,EAAE,aAAa,GAAG,QAAQ,EAAE,EAAE,eAAe,GAAE,OAAe,EACvG,YAAY,GAAE,oBAAyE,GAAG,iBAAiB,CAAC,QAAQ,CAAC;IAgBvH;;;;;;OAMG;WACW,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,QAAQ,EAAE,EAAE,8BAA8B,GAAE,OAAc,EAAE,4BAA4B,SAAI,GAAG,OAAO;IA+B9J;;;;;;;;;;OAUG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;IAsBlC;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,8BAA8B;IAgB7C,0GAA0G;IAC1G,OAAO,CAAC,MAAM,CAAC,8BAA8B;IAM7C;;;;;OAKG;WACW,iDAAiD,CAAC,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,GAAG,SAAS,EAAE,UAAU,GAAE,YAAqC,GAAG,QAAQ,EAAE,EAAE;IAcxM;;;;;OAKG;WACW,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IA4B7F;;;;;;;;;;;OAWG;WACW,mCAAmC,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,yBAAyB,EAClI,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI;IAoBzD;;;;;;;;;;;OAWG;WACW,mCAAmC,CAAC,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,GAAG,QAAQ,EAAE,EAAE;CAiBlH"}
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
- // Search services for HalfEdgeGraph
28
+ /** Class for different types of searches for HalfEdgeGraph. */
29
29
  export class HalfEdgeGraphSearch {
30
30
  /**
31
- * * for each node of face, set the mask push to allNodesStack
32
- * * push the faceSeed on onePerFaceStack[]
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) { return node.signedFaceArea(); }
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 all to obtain area (or other numeric value)
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
- * * Test if the graph is triangulated.
80
- * * Return false if:
81
- * * Positive area face with more than 3 edges
82
- * * more than 1 negative area face with `allowMultipleNegativeAreaFaces` false
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
- * Search to all accessible faces from given seed.
117
- * * The returned array contains one representative node in each face of the connected component.
118
- * * If (nonnull) parity mask is given, on return:
119
- * * It is entirely set or entirely clear around each face
120
- * * It is entirely set on all faces that are an even number of face-to-face steps away from the seed.
121
- * * It is entirely clear on all faces that are an odd number of face-to-face steps away from the seed.
122
- * @param seedEdge first edge to search.
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 parityMask mask to apply (a) to first face, (b) to faces with alternating parity during the search.
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(seedEdge, visitMask, parityEdgeTester, parityMask) {
132
+ static parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask) {
127
133
  const faces = [];
128
- if (seedEdge.isMaskSet(visitMask))
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
- // arbitrarily call the seed face exterior ... others will alternate as visited.
133
- HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(seedEdge, allMasks, stack, faces); // Start with exterior as mask
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
- * * Search the given faces for the one with the minimum area.
150
- * * If the mask in that face is OFF, toggle it on (all half edges of) all the faces.
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(_graph, mask, faces) {
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(mask)) {
161
- // all should be well .. nothing to do.
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(mask)) {
167
- faceSeed.clearMaskAroundFace(mask);
174
+ if (faceSeed.isMaskSet(parityMask)) {
175
+ faceSeed.clearMaskAroundFace(parityMask);
168
176
  }
169
177
  else {
170
- faceSeed.setMaskAroundFace(mask);
178
+ faceSeed.setMaskAroundFace(parityMask);
171
179
  }
172
180
  }
173
181
  }
174
182
  }
175
- /** Apply correctParityInSingleComponent to each array in components. (Quick exit if mask in NULL_MASK) */
176
- static correctParityInComponentArrays(graph, mask, components) {
177
- if (mask === HalfEdgeMask.NULL_MASK)
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(graph, mask, facesInComponent);
188
+ HalfEdgeGraphSearch.correctParityInSingleComponent(parityMask, facesInComponent);
181
189
  }
182
190
  /**
183
- * Collect arrays gathering faces by connected component.
184
- * @param graph graph to inspect
185
- * @param parityEdgeTester (optional) function to test if an edge is a parity change (e.g., a boundary edge).
186
- * @param parityMask (optional, along with parityEdgeTester) mask to apply indicating parity. If this is Mask.NULL_MASK, there is no record of parity.
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(HalfEdgeMask.VISITED)) {
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(graph, parityMask, components);
217
+ HalfEdgeGraphSearch.correctParityInComponentArrays(parityMask, components);
200
218
  return components;
201
219
  }
202
220
  /**
203
- * Test if (x,y) is inside (1), on an edge (0) or outside (-1) a face.
204
- * @param seedNode any node on the face loop
205
- * @param x x coordinate of test point.
206
- * @param y y coordinate of test point.
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, x, y) {
209
- const context = new XYParitySearchContext(x, y);
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
- // umm.. the face is all on the x axis?
218
- return context.classifyCounts();
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 ... emit ends of each edge around the face,
223
- // stopping after emitting nodeB as an edge end.
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(node.x, node.y)) {
245
+ if (!context.advance(nodeC.x, nodeC.y)) {
227
246
  return context.classifyCounts();
228
247
  }
229
- if (node === nodeB)
248
+ if (nodeC === nodeB)
230
249
  break;
231
- node = node.faceSuccessor;
250
+ nodeC = nodeC.faceSuccessor;
232
251
  }
233
252
  return context.classifyCounts();
234
253
  }
235
254
  /**
236
- * Announce nodes that are "extended face boundary" by conditions (usually mask of node and mate) in test functions.
237
- * * After each node, the next candidate in reached by looking "around the head vertex loop" for the next boundary.
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 isBoundaryEdge
245
- * @param announceEdge
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, announceEdge) {
263
+ static collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge, announceEdgeInBoundary) {
248
264
  let counter = 0;
249
265
  while (!seed.getMask(visitMask) && isBoundaryEdge(seed)) {
250
- announceEdge(seed, counter++);
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 arrays of nodes "around the boundary" of a graph with extraneous (non-boundary) edges.
269
- * * The "boundary" is nodes that do NOT have the exterior mask, but whose mates DO have the exterior mask.
270
- * * After each node, the next candidate in reached by looking "around the head vertex loop" for the next boundary.
271
- * * "Around the vertex" from nodeA means
272
- * * First look at nodeA.faceSuccessor;
273
- * * Then look at vertexPredecessor around that vertex loop.
274
- * * Each accepted node is passed to announceNode, and marked with the visit mask.
275
- * @param seed start node.
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) => {