@itwin/rpcinterface-full-stack-tests 4.4.0-dev.16 → 4.4.0-dev.18

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.
@@ -255343,6 +255343,22 @@ class Sample {
255343
255343
  }
255344
255344
  }
255345
255345
  }
255346
+ /** Create a point on the polar parametric curve r = cos(a * theta), aka "rose".
255347
+ * @param theta angle
255348
+ * @param a period multiplier. If odd, this is the petal count; if even, twice the number of petals.
255349
+ * @param z z-coordinate for output
255350
+ */
255351
+ static createRosePoint3d(theta, a, z = 0) {
255352
+ const r = Math.cos(a * theta);
255353
+ return _geometry3d_Point3dVector3d__WEBPACK_IMPORTED_MODULE_1__.Point3d.create(r * Math.cos(theta), r * Math.sin(theta), z);
255354
+ }
255355
+ /** Create a point on the polar parametric curve r = cos(a * theta), aka "rose".
255356
+ * @param theta angle
255357
+ * @param a period multiplier. If odd, this is the petal count; if even, twice the number of petals.
255358
+ */
255359
+ static createRosePoint2d(theta, a) {
255360
+ return _geometry3d_Point2dVector2d__WEBPACK_IMPORTED_MODULE_3__.Point2d.createFrom(Sample.createRosePoint3d(theta, a));
255361
+ }
255346
255362
  /**
255347
255363
  * Create a mesh surface from samples of a smooth function over [0,1]x[0,1].
255348
255364
  * @param size grid size; the number of intervals on each side of the unit square domain.
@@ -259916,15 +259932,15 @@ var HalfEdgeMask;
259916
259932
  * * A boundary edge with interior to one side, exterior to the other, will have EXTERIOR only on the outside.
259917
259933
  * * An edge inserted "within a purely exterior face" can have EXTERIOR on both sides.
259918
259934
  * * An interior edge (such as added during triangulation) will have no EXTERIOR bits.
259919
- * * Visualization can be found at geometry/internaldocs/Graph.md
259920
- */
259935
+ */
259936
+ // Visualization can be found at geometry/internaldocs/Graph.md
259921
259937
  HalfEdgeMask[HalfEdgeMask["EXTERIOR"] = 1] = "EXTERIOR";
259922
259938
  /**
259923
259939
  * Mask commonly set (on both sides) of original geometry edges that are transition from outside to inside.
259924
259940
  * * At the moment of creating an edge from primary user boundary loop coordinates, the fact that an edge is BOUNDARY
259925
259941
  * is often clear even though there is uncertainty about which side should be EXTERIOR.
259926
- * * Visualization can be found at geometry/internaldocs/Graph.md
259927
259942
  */
259943
+ // Visualization can be found at geometry/internaldocs/Graph.md
259928
259944
  HalfEdgeMask[HalfEdgeMask["BOUNDARY_EDGE"] = 2] = "BOUNDARY_EDGE";
259929
259945
  /**
259930
259946
  * Mask commonly set (on both sides) of original geometry edges, but NOT indicating that the edge is certainly a
@@ -261491,6 +261507,7 @@ __webpack_require__.r(__webpack_exports__);
261491
261507
  /* harmony export */ "HalfEdgeGraphSearch": () => (/* binding */ HalfEdgeGraphSearch),
261492
261508
  /* harmony export */ "HalfEdgeMaskTester": () => (/* binding */ HalfEdgeMaskTester)
261493
261509
  /* harmony export */ });
261510
+ /* harmony import */ var _geometry3d_Range__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../geometry3d/Range */ "../../core/geometry/lib/esm/geometry3d/Range.js");
261494
261511
  /* harmony import */ var _Graph__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Graph */ "../../core/geometry/lib/esm/topology/Graph.js");
261495
261512
  /* harmony import */ var _SignedDataSummary__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./SignedDataSummary */ "../../core/geometry/lib/esm/topology/SignedDataSummary.js");
261496
261513
  /* harmony import */ var _XYParitySearchContext__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./XYParitySearchContext */ "../../core/geometry/lib/esm/topology/XYParitySearchContext.js");
@@ -261504,11 +261521,11 @@ __webpack_require__.r(__webpack_exports__);
261504
261521
 
261505
261522
 
261506
261523
 
261507
- /**
261508
- */
261524
+
261525
+ /** Class to test match of half edge mask. */
261509
261526
  class HalfEdgeMaskTester {
261510
261527
  /**
261511
- *
261528
+ * Constructor
261512
261529
  * @param mask mask to test in `testEdge` function
261513
261530
  * @param targetValue value to match for true return
261514
261531
  */
@@ -261516,47 +261533,27 @@ class HalfEdgeMaskTester {
261516
261533
  this._targetMask = mask;
261517
261534
  this._targetValue = targetValue;
261518
261535
  }
261519
- /** Return true if the value of the targetMask matches the targetValue */
261536
+ /** Return true if the value of the targetMask matches the targetValue. */
261520
261537
  testEdge(edge) {
261521
261538
  return edge.isMaskSet(this._targetMask) === this._targetValue;
261522
261539
  }
261523
261540
  }
261524
- // Search services for HalfEdgeGraph
261541
+ /** Class for different types of searches for HalfEdgeGraph. */
261525
261542
  class HalfEdgeGraphSearch {
261526
261543
  /**
261527
- * * for each node of face, set the mask push to allNodesStack
261528
- * * push the faceSeed on onePerFaceStack[]
261529
- */
261530
- static pushAndMaskAllNodesInFace(faceSeed, mask, allNodeStack, onePerFaceStack) {
261531
- onePerFaceStack.push(faceSeed);
261532
- faceSeed.collectAroundFace((node) => {
261533
- node.setMask(mask);
261534
- allNodeStack.push(node);
261535
- });
261536
- }
261537
- /**
261538
- * Search an array of faceSeed nodes for the face with the most negative area.
261539
- * @param oneCandidateNodePerFace array containing one node from each face to be considered.
261540
- * @returns node on the minimum area face, or undefined if no such face (e.g., all faces have zero area).
261541
- */
261542
- static findMinimumAreaFace(oneCandidateNodePerFace, faceAreaFunction) {
261543
- const summary = HalfEdgeGraphSearch.collectFaceAreaSummary(oneCandidateNodePerFace, false, faceAreaFunction);
261544
- return summary.largestNegativeItem;
261545
- }
261546
- /**
261547
- * static method for face area computation -- useful as function parameter in collect FaceAreaSummary.
261548
- * * This simply calls `node.signedFaceArea ()`
261544
+ * Static method for face area computation -- useful as function parameter in `collectFaceAreaSummary`.
261545
+ * * This simply calls `node.signedFaceArea()`
261549
261546
  * @param node instance for signedFaceArea call.
261550
261547
  */
261551
- static signedFaceArea(node) { return node.signedFaceArea(); }
261548
+ static signedFaceArea(node) {
261549
+ return node.signedFaceArea();
261550
+ }
261552
261551
  /**
261553
- *
261554
- * Return a summary structure data about face (or other numeric quantity if the caller's areaFunction returns other value)
261555
- * * The default areaFunction computes area of polygonal face.
261552
+ * Return a summary of face data (e.g., area) as computed by the callback on the faces of the graph.
261556
261553
  * * Callers with curved edge graphs must supply their own area function.
261557
- * @param source graph or array of nodes to examine
261558
- * @param collectAllNodes flag to pass to the SignedDataSummary constructor to control collection of nodes.
261559
- * @param areaFunction function to all to obtain area (or other numeric value)
261554
+ * @param source graph or array of nodes to examine.
261555
+ * @param collectAllNodes flag to pass to the `SignedDataSummary` constructor to control collection of nodes.
261556
+ * @param areaFunction function to obtain area (or other numeric value). Default computes polygonal face area.
261560
261557
  */
261561
261558
  static collectFaceAreaSummary(source, collectAllNodes = false, areaFunction = (node) => HalfEdgeGraphSearch.signedFaceArea(node)) {
261562
261559
  const result = new _SignedDataSummary__WEBPACK_IMPORTED_MODULE_0__.SignedDataSummary(collectAllNodes);
@@ -261572,10 +261569,19 @@ class HalfEdgeGraphSearch {
261572
261569
  return result;
261573
261570
  }
261574
261571
  /**
261575
- * * Test if the graph is triangulated.
261576
- * * Return false if:
261577
- * * Positive area face with more than 3 edges
261578
- * * more than 1 negative area face with `allowMultipleNegativeAreaFaces` false
261572
+ * Search the graph for the face with the most negative area.
261573
+ * @param oneCandidateNodePerFace graph or an array containing one node from each face to be considered.
261574
+ * @returns node on the negative area face with largest absolute area, or `undefined` if no negative area face.
261575
+ */
261576
+ static findMinimumAreaFace(oneCandidateNodePerFace, faceAreaFunction) {
261577
+ const summary = HalfEdgeGraphSearch.collectFaceAreaSummary(oneCandidateNodePerFace, false, faceAreaFunction);
261578
+ return summary.largestNegativeItem;
261579
+ }
261580
+ /**
261581
+ * Test if the graph is triangulated.
261582
+ * * Return `false` if:
261583
+ * * number of positive area faces with more than 3 edges is larger than `numPositiveExceptionsAllowed`.
261584
+ * * graph has more than 1 negative area face when `allowMultipleNegativeAreaFaces` is `false`.
261579
261585
  * * 2-edge faces are ignored.
261580
261586
  */
261581
261587
  static isTriangulatedCCW(source, allowMultipleNegativeAreaFaces = true, numPositiveExceptionsAllowed = 0) {
@@ -261609,24 +261615,41 @@ class HalfEdgeGraphSearch {
261609
261615
  return true;
261610
261616
  }
261611
261617
  /**
261612
- * Search to all accessible faces from given seed.
261613
- * * The returned array contains one representative node in each face of the connected component.
261614
- * * If (nonnull) parity mask is given, on return:
261615
- * * It is entirely set or entirely clear around each face
261616
- * * It is entirely set on all faces that are an even number of face-to-face steps away from the seed.
261617
- * * It is entirely clear on all faces that are an odd number of face-to-face steps away from the seed.
261618
- * @param seedEdge first edge to search.
261619
- * @param visitMask mask applied to all faces as visited.
261620
- * @param parityMask mask to apply (a) to first face, (b) to faces with alternating parity during the search.
261618
+ * Process a face during graph traversal.
261619
+ * @param faceSeed a node in the face.
261620
+ * @param mask mask to set on each node of the face.
261621
+ * @param allNodeStack array appended with each node of the face.
261622
+ * @param onePerFaceStack array appended with `faceSeed`.
261621
261623
  */
261622
- static parityFloodFromSeed(seedEdge, visitMask, parityEdgeTester, parityMask) {
261624
+ static pushAndMaskAllNodesInFace(faceSeed, mask, allNodeStack, onePerFaceStack) {
261625
+ onePerFaceStack.push(faceSeed);
261626
+ faceSeed.collectAroundFace((node) => {
261627
+ node.setMask(mask);
261628
+ allNodeStack.push(node);
261629
+ });
261630
+ }
261631
+ /**
261632
+ * Traverse (via Depth First Search) to all accessible faces from the given seed.
261633
+ * @param faceSeed first node to start the traverse.
261634
+ * @param visitMask mask applied to all faces as visited.
261635
+ * @param parityEdgeTester function to test if an edge is adjacent to two faces of opposite parity, e.g., a boundary
261636
+ * edge that separates an "interior" face and an "exterior" face. If `parityEdgeTester` is not supplied and `parityMask`
261637
+ * is supplied, the default parity rule is to alternate parity state in a "bullseye" pattern starting at the seed face,
261638
+ * with each successive concentric ring of faces at constant topological distance from the seed face receiving the
261639
+ * opposite parity state of the previous ring.
261640
+ * @param parityMask mask to apply to the first face and faces that share the same parity as the first face, as
261641
+ * determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If (non-null) parity mask
261642
+ * is given, on return it is entirely set or entirely clear around each face.
261643
+ * @returns an array that contains one representative node in each face of the connected component.
261644
+ */
261645
+ static parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask) {
261623
261646
  const faces = [];
261624
- if (seedEdge.isMaskSet(visitMask))
261625
- return faces; // empty
261647
+ if (faceSeed.isMaskSet(visitMask))
261648
+ return faces; // empty array
261626
261649
  const allMasks = parityMask | visitMask;
261627
261650
  const stack = [];
261628
- // arbitrarily call the seed face exterior ... others will alternate as visited.
261629
- HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(seedEdge, allMasks, stack, faces); // Start with exterior as mask
261651
+ // the seed face is arbitrarily assigned the parity mask
261652
+ HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(faceSeed, allMasks, stack, faces);
261630
261653
  while (stack.length > 0) {
261631
261654
  const p = stack.pop();
261632
261655
  const mate = p.edgeMate;
@@ -261642,113 +261665,123 @@ class HalfEdgeGraphSearch {
261642
261665
  return faces;
261643
261666
  }
261644
261667
  /**
261645
- * * Search the given faces for the one with the minimum area.
261646
- * * If the mask in that face is OFF, toggle it on (all half edges of) all the faces.
261668
+ * * Correct the parity mask in the faces of a component.
261669
+ * * It is assumed that the parity mask is applied _consistently_ throughout the supplied faces, but maybe
261670
+ * not _correctly_.
261671
+ * * A consistently applied parity mask is "correct" if it is set on the negative area ("exterior") face of
261672
+ * a connected component.
261673
+ * * This method finds a face with negative area and toggles the mask throughout the input faces if this face
261674
+ * lacks the parity mask.
261647
261675
  * * In a properly merged planar subdivision there should be only one true negative area face per component.
261648
- * @param graph parent graph
261649
- * @param parityMask mask which was previously set with alternating parity, but with an arbitrary start face.
261650
- * @param faces array of faces to search.
261651
261676
  */
261652
- static correctParityInSingleComponent(_graph, mask, faces) {
261677
+ static correctParityInSingleComponent(parityMask, faces) {
261653
261678
  const exteriorHalfEdge = HalfEdgeGraphSearch.findMinimumAreaFace(faces);
261654
261679
  if (!exteriorHalfEdge) {
261680
+ // graph has all degenerate faces; do nothing
261655
261681
  }
261656
- else if (exteriorHalfEdge.isMaskSet(mask)) {
261657
- // all should be well .. nothing to do.
261682
+ else if (exteriorHalfEdge.isMaskSet(parityMask)) {
261683
+ // all should be well; nothing to do
261658
261684
  }
261659
261685
  else {
261660
- // TOGGLE around the face (assuming all are consistent with the seed)
261661
261686
  for (const faceSeed of faces) {
261662
- if (faceSeed.isMaskSet(mask)) {
261663
- faceSeed.clearMaskAroundFace(mask);
261687
+ if (faceSeed.isMaskSet(parityMask)) {
261688
+ faceSeed.clearMaskAroundFace(parityMask);
261664
261689
  }
261665
261690
  else {
261666
- faceSeed.setMaskAroundFace(mask);
261691
+ faceSeed.setMaskAroundFace(parityMask);
261667
261692
  }
261668
261693
  }
261669
261694
  }
261670
261695
  }
261671
- /** Apply correctParityInSingleComponent to each array in components. (Quick exit if mask in NULL_MASK) */
261672
- static correctParityInComponentArrays(graph, mask, components) {
261673
- if (mask === _Graph__WEBPACK_IMPORTED_MODULE_1__.HalfEdgeMask.NULL_MASK)
261696
+ /** Apply `correctParityInSingleComponent` to each array in components (quick exit if `parityMask` is `NULL_MASK`). */
261697
+ static correctParityInComponentArrays(parityMask, components) {
261698
+ if (parityMask === _Graph__WEBPACK_IMPORTED_MODULE_1__.HalfEdgeMask.NULL_MASK)
261674
261699
  return;
261675
261700
  for (const facesInComponent of components)
261676
- HalfEdgeGraphSearch.correctParityInSingleComponent(graph, mask, facesInComponent);
261677
- }
261678
- /**
261679
- * Collect arrays gathering faces by connected component.
261680
- * @param graph graph to inspect
261681
- * @param parityEdgeTester (optional) function to test if an edge is a parity change (e.g., a boundary edge).
261682
- * @param parityMask (optional, along with parityEdgeTester) mask to apply indicating parity. If this is Mask.NULL_MASK, there is no record of parity.
261701
+ HalfEdgeGraphSearch.correctParityInSingleComponent(parityMask, facesInComponent);
261702
+ }
261703
+ /**
261704
+ * Collect connected components of the graph (via Depth First Search).
261705
+ * @param graph graph to inspect.
261706
+ * @param parityEdgeTester (optional) function to test if an edge is adjacent to two faces of opposite parity,
261707
+ * e.g., a boundary edge that separates an "interior" face and an "exterior" face. If `parityEdgeTester` is not
261708
+ * supplied and `parityMask` is supplied, the default parity rule is to alternate parity state in a "bullseye"
261709
+ * pattern starting at the seed face, with each successive concentric ring of faces at constant topological
261710
+ * distance from the seed face receiving the opposite parity state of the previous ring.
261711
+ * @param parityMask (optional) mask to apply to the first face and faces that share the same parity as the
261712
+ * first face, as determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If
261713
+ * (non-null) parity mask is given, on return it is entirely set or entirely clear around each face.
261714
+ * @returns the components of the graph, each component represented by an array of nodes, one node per face
261715
+ * of the component. In other words, entry [i][j] is a HalfEdge in the j_th face loop of the i_th component.
261683
261716
  */
261684
261717
  static collectConnectedComponentsWithExteriorParityMasks(graph, parityEdgeTester, parityMask = _Graph__WEBPACK_IMPORTED_MODULE_1__.HalfEdgeMask.NULL_MASK) {
261718
+ // Illustration of the algorithm can be found at geometry/internaldocs/Graph.md
261685
261719
  const components = [];
261686
261720
  const visitMask = _Graph__WEBPACK_IMPORTED_MODULE_1__.HalfEdgeMask.VISITED;
261687
261721
  const allMasks = parityMask | visitMask;
261688
261722
  graph.clearMask(allMasks);
261689
261723
  for (const faceSeed of graph.allHalfEdges) {
261690
- if (!faceSeed.isMaskSet(_Graph__WEBPACK_IMPORTED_MODULE_1__.HalfEdgeMask.VISITED)) {
261724
+ if (!faceSeed.isMaskSet(visitMask)) {
261691
261725
  const newFaces = HalfEdgeGraphSearch.parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask);
261726
+ // parityFloodFromSeed does not return an empty array because it is called on an unvisited faceSeed
261692
261727
  components.push(newFaces);
261693
261728
  }
261694
261729
  }
261695
- HalfEdgeGraphSearch.correctParityInComponentArrays(graph, parityMask, components);
261730
+ HalfEdgeGraphSearch.correctParityInComponentArrays(parityMask, components);
261696
261731
  return components;
261697
261732
  }
261698
261733
  /**
261699
- * Test if (x,y) is inside (1), on an edge (0) or outside (-1) a face.
261700
- * @param seedNode any node on the face loop
261701
- * @param x x coordinate of test point.
261702
- * @param y y coordinate of test point.
261734
+ * Test if test point (xTest,yTest) is inside/outside a face or on an edge.
261735
+ * @param seedNode any node on the face loop.
261736
+ * @param xTest x coordinate of the test point.
261737
+ * @param yTest y coordinate of the test point.
261738
+ * @returns 0 if ON, 1 if IN, -1 if OUT.
261703
261739
  */
261704
- static pointInOrOnFaceXY(seedNode, x, y) {
261705
- const context = new _XYParitySearchContext__WEBPACK_IMPORTED_MODULE_2__.XYParitySearchContext(x, y);
261706
- // walk around looking for an accepted node to start the search (seedNode is usually ok!)
261740
+ static pointInOrOnFaceXY(seedNode, xTest, yTest) {
261741
+ const context = new _XYParitySearchContext__WEBPACK_IMPORTED_MODULE_2__.XYParitySearchContext(xTest, yTest);
261742
+ // walk around looking for an accepted node to start the search (seedNode is usually ok)
261707
261743
  let nodeA = seedNode;
261708
261744
  let nodeB = seedNode.faceSuccessor;
261709
261745
  for (;; nodeA = nodeB) {
261710
261746
  if (context.tryStartEdge(nodeA.x, nodeA.y, nodeB.x, nodeB.y))
261711
261747
  break;
261712
261748
  if (nodeB === seedNode) {
261713
- // umm.. the face is all on the x axis?
261714
- return context.classifyCounts();
261749
+ // the test point and the face are all on line "y = yTest"
261750
+ const range = _geometry3d_Range__WEBPACK_IMPORTED_MODULE_3__.Range1d.createXX(nodeB.x, nodeB.faceSuccessor.x);
261751
+ return range.containsX(xTest) ? 0 : -1;
261715
261752
  }
261716
261753
  nodeB = nodeA.faceSuccessor;
261717
261754
  }
261718
- // nodeB is the real start node for search ... emit ends of each edge around the face,
261719
- // stopping after emitting nodeB as an edge end.
261720
- let node = nodeB.faceSuccessor;
261755
+ // nodeB is the real start node for search, so stop when we revisit it. For each edge, accumulate parity and hits
261756
+ let nodeC = nodeB.faceSuccessor;
261721
261757
  for (;;) {
261722
- if (!context.advance(node.x, node.y)) {
261758
+ if (!context.advance(nodeC.x, nodeC.y)) {
261723
261759
  return context.classifyCounts();
261724
261760
  }
261725
- if (node === nodeB)
261761
+ if (nodeC === nodeB)
261726
261762
  break;
261727
- node = node.faceSuccessor;
261763
+ nodeC = nodeC.faceSuccessor;
261728
261764
  }
261729
261765
  return context.classifyCounts();
261730
261766
  }
261731
261767
  /**
261732
- * Announce nodes that are "extended face boundary" by conditions (usually mask of node and mate) in test functions.
261733
- * * After each node, the next candidate in reached by looking "around the head vertex loop" for the next boundary.
261734
- * * "Around the vertex" from nodeA means
261735
- * * First look at nodeA.faceSuccessor;
261736
- * * Then look at vertexPredecessor around that vertex loop.
261737
- * * Each accepted node is passed to announceNode, and marked with the visit mask.
261738
- * * The counter of the announceEdge function is zero for the first edge, then increases with each edge.
261768
+ * Collect boundary edges starting from `seed`.
261769
+ * * If `seed` is not a boundary node or is already visited, the function exists early.
261739
261770
  * @param seed start node.
261740
- * @param isBoundaryEdge
261741
- * @param announceEdge
261771
+ * @param visitMask mask to set on processed nodes.
261772
+ * @param isBoundaryEdge function to test if an edge in a boundary edge.
261773
+ * @param announceEdgeInBoundary callback invoked on each edge in the boundary loop in order. The counter is zero
261774
+ * for the first edge, and incremented with each successive edge.
261742
261775
  */
261743
- static collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge, announceEdge) {
261776
+ static collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge, announceEdgeInBoundary) {
261744
261777
  let counter = 0;
261745
261778
  while (!seed.getMask(visitMask) && isBoundaryEdge(seed)) {
261746
- announceEdge(seed, counter++);
261779
+ announceEdgeInBoundary(seed, counter++);
261747
261780
  seed.setMask(visitMask);
261748
261781
  const vertexBase = seed.faceSuccessor;
261749
261782
  let candidateAroundVertex = vertexBase;
261750
261783
  for (;;) {
261751
- if (candidateAroundVertex.getMask(visitMask))
261784
+ if (candidateAroundVertex.getMask(visitMask)) // end of boundary loop
261752
261785
  return;
261753
261786
  if (isBoundaryEdge(candidateAroundVertex)) {
261754
261787
  seed = candidateAroundVertex;
@@ -261756,23 +261789,22 @@ class HalfEdgeGraphSearch {
261756
261789
  }
261757
261790
  candidateAroundVertex = candidateAroundVertex.vertexPredecessor;
261758
261791
  if (candidateAroundVertex === vertexBase)
261759
- break;
261792
+ break; // prevent infinite loop in case exteriorMask is not set on the edge mate of the boundary edge
261760
261793
  }
261761
261794
  }
261762
261795
  }
261763
261796
  /**
261764
- * Collect arrays of nodes "around the boundary" of a graph with extraneous (non-boundary) edges.
261765
- * * The "boundary" is nodes that do NOT have the exterior mask, but whose mates DO have the exterior mask.
261766
- * * After each node, the next candidate in reached by looking "around the head vertex loop" for the next boundary.
261767
- * * "Around the vertex" from nodeA means
261768
- * * First look at nodeA.faceSuccessor;
261769
- * * Then look at vertexPredecessor around that vertex loop.
261770
- * * Each accepted node is passed to announceNode, and marked with the visit mask.
261771
- * @param seed start node.
261772
- * @param isBoundaryNode
261773
- * @param announceNode
261797
+ * Collect boundary edges in the graph.
261798
+ * * A boundary edge is defined by `exteriorMask` being set on only its "exterior" edge mate.
261799
+ * * Each boundary edge is identified in the output by its edge mate that lacks `exteriorMask`.
261800
+ * * Each inner array is ordered in the output so that its boundary edges form a connected path. If `exteriorMask`
261801
+ * is preset consistently around each "exterior" face, these paths are loops.
261802
+ * @param graph the graph to query
261803
+ * @param exteriorMask mask preset on exactly one side of boundary edges
261804
+ * @returns array of boundary loops, each loop an array of the unmasked mates of boundary edges
261774
261805
  */
261775
261806
  static collectExtendedBoundaryLoopsInGraph(graph, exteriorMask) {
261807
+ // Illustration of the algorithm can be found at geometry/internaldocs/Graph.md
261776
261808
  const loops = [];
261777
261809
  const visitMask = graph.grabMask(true);
261778
261810
  const isBoundaryEdge = (edge) => {
@@ -264257,10 +264289,10 @@ __webpack_require__.r(__webpack_exports__);
264257
264289
  /**
264258
264290
  * Class to accumulate statistics about a stream of signed numbers with tag items.
264259
264291
  * * All sums, counts, extrema, and item values are initialized to zero in the constructor.
264260
- * * Each call to `announceItem (item, value)` updates the various sums, counts, and extrema.
264292
+ * * Each call to `announceItem(item, value)` updates the various sums, counts, and extrema.
264261
264293
  */
264262
264294
  class SignedDataSummary {
264263
- /** setup with zero sums and optional arrays */
264295
+ /** Setup with zero sums and optional arrays. */
264264
264296
  constructor(createArrays) {
264265
264297
  this.positiveSum = this.negativeSum = 0.0;
264266
264298
  this.numPositive = this.numNegative = this.numZero = 0.0;
@@ -264271,7 +264303,7 @@ class SignedDataSummary {
264271
264303
  this.zeroItemArray = [];
264272
264304
  }
264273
264305
  }
264274
- /** update with an item and its data value. */
264306
+ /** Update with an item and its data value. */
264275
264307
  announceItem(item, data) {
264276
264308
  if (data < 0) {
264277
264309
  this.numNegative++;
@@ -265496,127 +265528,129 @@ __webpack_require__.r(__webpack_exports__);
265496
265528
  * @module Topology
265497
265529
  */
265498
265530
  /**
265499
- * * XYParitySearchContext is an internal class for callers that can feed points (without extracting to array structures)
265531
+ * `XYParitySearchContext` is an internal class for callers that can feed points (without extracting to array structures)
265500
265532
  * * Most will be via static methods which handle a specific data source.
265501
- * * PolygonOps.classifyPointInPolygon (x,y,points: XAndY[])
265502
- * * HalfEdgeGraphSearch.pointInOrOnFaceXY (halfEdgeOnFace, x, y)
265533
+ * * PolygonOps.classifyPointInPolygon(x,y,points: XAndY[])
265534
+ * * HalfEdgeGraphSearch.pointInOrOnFaceXY(halfEdgeOnFace, x, y)
265503
265535
  * Use pattern:
265504
- * * Caller must be able walk around polygon producing x,y coordinates (possibly transformed from actual polygon)
265536
+ * * Caller must be able to walk around polygon producing x,y coordinates (possibly transformed from actual polygon).
265505
265537
  * * Caller announce edges to tryStartEdge until finding one acceptable to the search.
265506
265538
  * * Caller then passes additional points up to and including both x0,y0 and x1, y1 of the accepted start edge.
265507
265539
  * Call sequence is:
265508
- * `context = new XYParitySearchContext`
265509
- * `repeat { acquire edge (x0,y0) (x1,y1)} until context.tryStartEdge (x0,y0,x1,y1);`
265510
- * `for each (x,y) beginning AFTER x1,y1 and ending with (x1,y1) context.advance (x,y)`
265511
- * `return context.classifyCounts ();`
265540
+ * * `context = new XYParitySearchContext`
265541
+ * * `repeat { acquire edge (x0,y0) (x1,y1) } until context.tryStartEdge(x0,y0,x1,y1);`
265542
+ * * `for each (x,y) beginning AFTER x1,y1 and ending with (x1,y1) context.advance (x,y)`
265543
+ * * `return context.classifyCounts();`
265512
265544
  */
265513
265545
  class XYParitySearchContext {
265514
265546
  /**
265515
265547
  * Create a new searcher for specified test point.
265516
- * @param xTest x coordinate of test point
265517
- * @param yTest y coordinate of test point
265548
+ * @param xTest x coordinate of test point.
265549
+ * @param yTest y coordinate of test point.
265518
265550
  */
265519
265551
  constructor(xTest, yTest) {
265520
265552
  this.xTest = xTest;
265521
265553
  this.yTest = yTest;
265522
- this.u0 = this.v0 = this.u1 = this.v1 = 0; // Not valid for search -- caller must satisfy tryStartEdge !!!
265554
+ this.u0 = this.v0 = this.u1 = this.v1 = 0; // not valid for search; caller must satisfy tryStartEdge
265523
265555
  this.numLeftCrossing = this.numRightCrossing = 0;
265524
265556
  this.numHit = 0;
265525
265557
  }
265526
- /**
265527
- * test if x,y is a safe first coordinate to start the search.
265528
- * * safe start must have non-zero y so that final point test (return to x0,y0) does not need look back for exact crossing logic.
265529
- * @param x
265530
- * @param y
265531
- */
265558
+ /** Test if parity processing can begin with this edge. */
265532
265559
  tryStartEdge(x0, y0, x1, y1) {
265533
265560
  if (y0 !== this.yTest) {
265534
265561
  this.u0 = x0 - this.xTest;
265535
265562
  this.v0 = y0 - this.yTest;
265536
265563
  this.u1 = x1 - this.xTest;
265537
265564
  this.v1 = y1 - this.yTest;
265538
- return true;
265565
+ return true; // we won't need wraparound logic to process the final edge ending at (x0,y0)
265539
265566
  }
265540
265567
  return false;
265541
265568
  }
265542
- /** Return true if parity accumulation proceeded normally.
265543
- * Return false if interrupted for exact hit.
265544
- */
265545
- advance(x, y) {
265546
- const u = x - this.xTest;
265547
- const v = y - this.yTest;
265548
- const p = v * this.v1;
265569
+ /** Update local coordinates: the current edge becomes the previous edge. */
265570
+ updateUV01(u2, v2) {
265571
+ this.u0 = this.u1;
265572
+ this.v0 = this.v1;
265573
+ this.u1 = u2;
265574
+ this.v1 = v2;
265575
+ return true;
265576
+ }
265577
+ /**
265578
+ * Process the current edge ending at (x2,y2).
265579
+ * * Accumulate left/right parity of the test point wrt to the polygon. These counts track the number of polygon crossings
265580
+ * of the left and right horizontal rays emanating from the test point. After all edges are processed, if either count is
265581
+ * odd/even, the test point is inside/outside the polygon (see [[classifyCounts]]).
265582
+ * * Check whether the test point lies on the edge.
265583
+ * @returns whether caller should continue processing with the next edge. In particular, `false` if we have an exact hit.
265584
+ */
265585
+ advance(x2, y2) {
265586
+ // In this method we use local u,v coordinates obtained by translating the test point to the origin.
265587
+ // This simplifies our computations:
265588
+ // * left (right) parity is incremented if the current edge crosses the u-axis at u<0 (u>0)
265589
+ // * we have an exact hit if the current edge crosses the u-axis at u=0
265590
+ const u2 = x2 - this.xTest;
265591
+ const v2 = y2 - this.yTest;
265592
+ const p = v2 * this.v1;
265549
265593
  if (p > 0) {
265550
- // The common case -- skittering along above or below the x axis . . .
265551
- this.u0 = this.u1;
265552
- this.v0 = this.v1;
265553
- this.u1 = u;
265554
- this.v1 = v;
265555
- return true;
265594
+ // Current edge does not cross u-axis.
265595
+ return this.updateUV01(u2, v2);
265556
265596
  }
265557
265597
  if (p < 0) {
265558
- // crossing within (u1,v1) to (u,v)
265559
- // both v values are nonzero and of opposite sign, so this division is safe . . .
265560
- const fraction = -this.v1 / (v - this.v1);
265561
- const uCross = this.u1 + fraction * (u - this.u1);
265598
+ // Current edge crosses the u-axis at edge parameter 0 < lambda < 1 by the Intermediate Value Theorem.
265599
+ // Solve for lambda in 0 = v1 + lambda (v2 - v1), then use it to compute the u-value of the crossing.
265600
+ const lambda = -this.v1 / (v2 - this.v1);
265601
+ const uCross = this.u1 + lambda * (u2 - this.u1);
265562
265602
  if (uCross === 0.0) {
265563
- this.numHit++;
265603
+ this.numHit++; // Current edge crosses at the origin.
265564
265604
  return false;
265565
265605
  }
265566
265606
  if (uCross > 0)
265567
265607
  this.numRightCrossing++;
265568
265608
  else
265569
265609
  this.numLeftCrossing++;
265570
- this.u0 = this.u1;
265571
- this.v0 = this.v1;
265572
- this.u1 = u;
265573
- this.v1 = v;
265574
- return true;
265610
+ return this.updateUV01(u2, v2);
265575
265611
  }
265576
- // hard stuff -- one or more exact hits . . .
265577
- if (v === 0.0) {
265612
+ // At this point, at least one endpoint of the current edge lies on the u-axis.
265613
+ if (v2 === 0.0) {
265578
265614
  if (this.v1 === 0.0) {
265579
- // uh oh -- moving along x axis. Does it pass through xTest:
265580
- if (u * this.u1 <= 0.0) {
265581
- this.numHit++;
265615
+ if (u2 * this.u1 <= 0.0) {
265616
+ this.numHit++; // Current edge lies on u-axis and contains the origin.
265582
265617
  return false;
265583
265618
  }
265584
- // quietly moving along the scan line, both xy and x1y1 to same side of test point ...
265585
- // u0 and u1 remain unchanged !!!
265586
- this.u1 = u;
265587
- this.v1 = v;
265619
+ // Current edge lies on the u-axis to one side of the origin.
265620
+ // This edge doesn't contribute to parity computations, so advance past it.
265621
+ this.u1 = u2;
265622
+ this.v1 = v2;
265588
265623
  return true;
265589
265624
  }
265590
- // just moved onto the scan line ...
265591
- this.u0 = this.u1;
265592
- this.v0 = this.v1;
265593
- this.u1 = u;
265594
- this.v1 = v;
265595
- return true;
265625
+ if (u2 === 0.0) {
265626
+ this.numHit++; // Current edge ends at the origin.
265627
+ return false;
265628
+ }
265629
+ // Current edge ends on the u-axis away from the origin.
265630
+ return this.updateUV01(u2, v2);
265596
265631
  }
265597
- // fall out with v1 = 0
265598
- // both v0 and v are nonzero.
265599
- // any along-0 v values that have passed through are on the same side of xTest, so u1 determines crossing
265600
- const q = this.v0 * v;
265601
- if (this.u1 > 0) {
265602
- if (q < 0)
265603
- this.numRightCrossing++;
265632
+ // At this point, the current edge starts at the u-axis.
265633
+ if (this.u1 === 0.0) {
265634
+ this.numHit++; // Current edge starts at the origin.
265635
+ return false;
265604
265636
  }
265605
- else {
265606
- if (q < 0)
265637
+ // At this point, the current edge starts on the u-axis away from the origin.
265638
+ const q = this.v0 * v2;
265639
+ if (q < 0) {
265640
+ // The current edge and the previous edge lie on opposite sides of the u-axis, so we have a parity change.
265641
+ if (this.u1 > 0)
265642
+ this.numRightCrossing++;
265643
+ else
265607
265644
  this.numLeftCrossing++;
265608
265645
  }
265609
- this.u0 = this.u1;
265610
- this.v0 = this.v1;
265611
- this.u1 = u;
265612
- this.v1 = v;
265613
- return true;
265646
+ // The current edge and the previous edge lie on the same sides of the u-axis, so no parity change.
265647
+ return this.updateUV01(u2, v2);
265614
265648
  }
265615
265649
  /**
265616
265650
  * Return classification as ON, IN, or OUT according to hit and crossing counts.
265617
- * * Any nonzero hit count is ON
265651
+ * * Any nonzero hit count is ON.
265618
265652
  * * Otherwise IN if left crossing count is odd.
265619
- * @return 0 if ON, 1 if IN, -1 if OUT
265653
+ * @return 0 if ON, 1 if IN, -1 if OUT.
265620
265654
  */
265621
265655
  classifyCounts() {
265622
265656
  if (this.numHit > 0)
@@ -286200,7 +286234,7 @@ class TestContext {
286200
286234
  this.initializeRpcInterfaces({ title: this.settings.Backend.name, version: this.settings.Backend.version });
286201
286235
  const iModelClient = new imodels_client_management_1.IModelsClient({ api: { baseUrl: `https://${process.env.IMJS_URL_PREFIX ?? ""}api.bentley.com/imodels` } });
286202
286236
  await core_frontend_1.NoRenderApp.startup({
286203
- applicationVersion: "4.4.0-dev.16",
286237
+ applicationVersion: "4.4.0-dev.18",
286204
286238
  applicationId: this.settings.gprid,
286205
286239
  authorizationClient: new frontend_1.TestFrontendAuthorizationClient(this.adminUserAccessToken),
286206
286240
  hubAccess: new imodels_access_frontend_1.FrontendIModelsAccess(iModelClient),
@@ -305012,7 +305046,7 @@ module.exports = JSON.parse('{"name":"axios","version":"0.21.4","description":"P
305012
305046
  /***/ ((module) => {
305013
305047
 
305014
305048
  "use strict";
305015
- module.exports = JSON.parse('{"name":"@itwin/core-frontend","version":"4.4.0-dev.16","description":"iTwin.js frontend components","main":"lib/cjs/core-frontend.js","module":"lib/esm/core-frontend.js","typings":"lib/cjs/core-frontend","license":"MIT","scripts":{"build":"npm run -s copy:public && npm run -s build:cjs && npm run -s build:esm && npm run -s webpackWorkers && npm run -s copy:workers","build:cjs":"npm run -s copy:js:cjs && tsc 1>&2 --outDir lib/cjs","build:esm":"npm run -s copy:js:esm && tsc 1>&2 --module ES2020 --outDir lib/esm","clean":"rimraf lib .rush/temp/package-deps*.json","copy:public":"cpx \\"./src/public/**/*\\" ./lib/public","copy:js:cjs":"cpx \\"./src/**/*.js\\" ./lib/cjs","copy:js:esm":"cpx \\"./src/**/*.js\\" ./lib/esm","copy:workers":"cpx \\"./lib/workers/webpack/parse-imdl-worker.js\\" ./lib/public/scripts","docs":"betools docs --includes=../../generated-docs/extract --json=../../generated-docs/core/core-frontend/file.json --tsIndexFile=./core-frontend.ts --onlyJson --excludes=webgl/**/*,**/map/*.d.ts,**/tile/*.d.ts,**/*-css.ts","extract-api":"betools extract-api --entry=core-frontend && npm run extract-extension-api","extract-extension-api":"eslint -c extraction.eslint.config.js \\"./src/**/*.ts\\" 1>&2","lint":"eslint -f visualstudio \\"./src/**/*.ts\\" 1>&2","pseudolocalize":"betools pseudolocalize --englishDir ./src/public/locales/en --out ./public/locales/en-PSEUDO","test":"npm run -s webpackTests && certa -r chrome","cover":"npm -s test","test:debug":"certa -r chrome --debug","webpackTests":"webpack --config ./src/test/utils/webpack.config.js 1>&2 && npm run -s webpackTestWorker","webpackTestWorker":"webpack --config ./src/test/worker/webpack.config.js 1>&2 && cpx \\"./lib/test/test-worker.js\\" ./lib/test","webpackWorkers":"webpack --config ./src/workers/ImdlParser/webpack.config.js 1>&2"},"repository":{"type":"git","url":"https://github.com/iTwin/itwinjs-core.git","directory":"core/frontend"},"keywords":["Bentley","BIM","iModel","digital-twin","iTwin"],"author":{"name":"Bentley Systems, Inc.","url":"http://www.bentley.com"},"peerDependencies":{"@itwin/appui-abstract":"workspace:^4.4.0-dev.16","@itwin/core-bentley":"workspace:^4.4.0-dev.16","@itwin/core-common":"workspace:^4.4.0-dev.16","@itwin/core-geometry":"workspace:^4.4.0-dev.16","@itwin/core-orbitgt":"workspace:^4.4.0-dev.16","@itwin/core-quantity":"workspace:^4.4.0-dev.16"},"//devDependencies":["NOTE: All peerDependencies should also be listed as devDependencies since peerDependencies are not considered by npm install","NOTE: All tools used by scripts in this package must be listed as devDependencies"],"devDependencies":{"@itwin/appui-abstract":"workspace:*","@itwin/build-tools":"workspace:*","@itwin/core-bentley":"workspace:*","@itwin/core-common":"workspace:*","@itwin/core-geometry":"workspace:*","@itwin/core-orbitgt":"workspace:*","@itwin/core-quantity":"workspace:*","@itwin/certa":"workspace:*","@itwin/eslint-plugin":"4.0.0-dev.44","@types/chai":"4.3.1","@types/chai-as-promised":"^7","@types/mocha":"^10.0.6","@types/sinon":"^17.0.2","babel-loader":"~8.2.5","babel-plugin-istanbul":"~6.1.1","chai":"^4.3.10","chai-as-promised":"^7.1.1","cpx2":"^3.0.0","eslint":"^8.44.0","glob":"^7.1.2","mocha":"^10.2.0","nyc":"^15.1.0","rimraf":"^3.0.2","sinon":"^17.0.1","source-map-loader":"^4.0.0","typescript":"~5.0.2","typemoq":"^2.1.0","webpack":"^5.76.0"},"//dependencies":["NOTE: these dependencies should be only for things that DO NOT APPEAR IN THE API","NOTE: core-frontend should remain UI technology agnostic, so no react/angular dependencies are allowed"],"dependencies":{"@itwin/cloud-agnostic-core":"^2.1.0","@itwin/object-storage-core":"^2.1.0","@itwin/core-i18n":"workspace:*","@itwin/core-telemetry":"workspace:*","@itwin/webgl-compatibility":"workspace:*","@loaders.gl/core":"^3.1.6","@loaders.gl/draco":"^3.1.6","fuse.js":"^3.3.0","wms-capabilities":"0.4.0"},"nyc":{"extends":"./node_modules/@itwin/build-tools/.nycrc"}}');
305049
+ module.exports = JSON.parse('{"name":"@itwin/core-frontend","version":"4.4.0-dev.18","description":"iTwin.js frontend components","main":"lib/cjs/core-frontend.js","module":"lib/esm/core-frontend.js","typings":"lib/cjs/core-frontend","license":"MIT","scripts":{"build":"npm run -s copy:public && npm run -s build:cjs && npm run -s build:esm && npm run -s webpackWorkers && npm run -s copy:workers","build:cjs":"npm run -s copy:js:cjs && tsc 1>&2 --outDir lib/cjs","build:esm":"npm run -s copy:js:esm && tsc 1>&2 --module ES2020 --outDir lib/esm","clean":"rimraf lib .rush/temp/package-deps*.json","copy:public":"cpx \\"./src/public/**/*\\" ./lib/public","copy:js:cjs":"cpx \\"./src/**/*.js\\" ./lib/cjs","copy:js:esm":"cpx \\"./src/**/*.js\\" ./lib/esm","copy:workers":"cpx \\"./lib/workers/webpack/parse-imdl-worker.js\\" ./lib/public/scripts","docs":"betools docs --includes=../../generated-docs/extract --json=../../generated-docs/core/core-frontend/file.json --tsIndexFile=./core-frontend.ts --onlyJson --excludes=webgl/**/*,**/map/*.d.ts,**/tile/*.d.ts,**/*-css.ts","extract-api":"betools extract-api --entry=core-frontend && npm run extract-extension-api","extract-extension-api":"eslint -c extraction.eslint.config.js \\"./src/**/*.ts\\" 1>&2","lint":"eslint -f visualstudio \\"./src/**/*.ts\\" 1>&2","pseudolocalize":"betools pseudolocalize --englishDir ./src/public/locales/en --out ./public/locales/en-PSEUDO","test":"npm run -s webpackTests && certa -r chrome","cover":"npm -s test","test:debug":"certa -r chrome --debug","webpackTests":"webpack --config ./src/test/utils/webpack.config.js 1>&2 && npm run -s webpackTestWorker","webpackTestWorker":"webpack --config ./src/test/worker/webpack.config.js 1>&2 && cpx \\"./lib/test/test-worker.js\\" ./lib/test","webpackWorkers":"webpack --config ./src/workers/ImdlParser/webpack.config.js 1>&2"},"repository":{"type":"git","url":"https://github.com/iTwin/itwinjs-core.git","directory":"core/frontend"},"keywords":["Bentley","BIM","iModel","digital-twin","iTwin"],"author":{"name":"Bentley Systems, Inc.","url":"http://www.bentley.com"},"peerDependencies":{"@itwin/appui-abstract":"workspace:^4.4.0-dev.18","@itwin/core-bentley":"workspace:^4.4.0-dev.18","@itwin/core-common":"workspace:^4.4.0-dev.18","@itwin/core-geometry":"workspace:^4.4.0-dev.18","@itwin/core-orbitgt":"workspace:^4.4.0-dev.18","@itwin/core-quantity":"workspace:^4.4.0-dev.18"},"//devDependencies":["NOTE: All peerDependencies should also be listed as devDependencies since peerDependencies are not considered by npm install","NOTE: All tools used by scripts in this package must be listed as devDependencies"],"devDependencies":{"@itwin/appui-abstract":"workspace:*","@itwin/build-tools":"workspace:*","@itwin/core-bentley":"workspace:*","@itwin/core-common":"workspace:*","@itwin/core-geometry":"workspace:*","@itwin/core-orbitgt":"workspace:*","@itwin/core-quantity":"workspace:*","@itwin/certa":"workspace:*","@itwin/eslint-plugin":"4.0.0-dev.44","@types/chai":"4.3.1","@types/chai-as-promised":"^7","@types/mocha":"^10.0.6","@types/sinon":"^17.0.2","babel-loader":"~8.2.5","babel-plugin-istanbul":"~6.1.1","chai":"^4.3.10","chai-as-promised":"^7.1.1","cpx2":"^3.0.0","eslint":"^8.44.0","glob":"^7.1.2","mocha":"^10.2.0","nyc":"^15.1.0","rimraf":"^3.0.2","sinon":"^17.0.1","source-map-loader":"^4.0.0","typescript":"~5.0.2","typemoq":"^2.1.0","webpack":"^5.76.0"},"//dependencies":["NOTE: these dependencies should be only for things that DO NOT APPEAR IN THE API","NOTE: core-frontend should remain UI technology agnostic, so no react/angular dependencies are allowed"],"dependencies":{"@itwin/cloud-agnostic-core":"^2.1.0","@itwin/object-storage-core":"^2.2.2","@itwin/core-i18n":"workspace:*","@itwin/core-telemetry":"workspace:*","@itwin/webgl-compatibility":"workspace:*","@loaders.gl/core":"^3.1.6","@loaders.gl/draco":"^3.1.6","fuse.js":"^3.3.0","wms-capabilities":"0.4.0"},"nyc":{"extends":"./node_modules/@itwin/build-tools/.nycrc"}}');
305016
305050
 
305017
305051
  /***/ }),
305018
305052