@itwin/core-geometry 4.4.0-dev.15 → 4.4.0-dev.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/lib/cjs/serialization/GeometrySamples.d.ts +11 -0
  2. package/lib/cjs/serialization/GeometrySamples.d.ts.map +1 -1
  3. package/lib/cjs/serialization/GeometrySamples.js +16 -0
  4. package/lib/cjs/serialization/GeometrySamples.js.map +1 -1
  5. package/lib/cjs/topology/Graph.d.ts +1 -3
  6. package/lib/cjs/topology/Graph.d.ts.map +1 -1
  7. package/lib/cjs/topology/Graph.js +3 -3
  8. package/lib/cjs/topology/Graph.js.map +1 -1
  9. package/lib/cjs/topology/HalfEdgeGraphSearch.d.ts +81 -76
  10. package/lib/cjs/topology/HalfEdgeGraphSearch.d.ts.map +1 -1
  11. package/lib/cjs/topology/HalfEdgeGraphSearch.js +126 -111
  12. package/lib/cjs/topology/HalfEdgeGraphSearch.js.map +1 -1
  13. package/lib/cjs/topology/SignedDataSummary.d.ts +13 -13
  14. package/lib/cjs/topology/SignedDataSummary.d.ts.map +1 -1
  15. package/lib/cjs/topology/SignedDataSummary.js +3 -3
  16. package/lib/cjs/topology/SignedDataSummary.js.map +1 -1
  17. package/lib/cjs/topology/XYParitySearchContext.d.ts +27 -21
  18. package/lib/cjs/topology/XYParitySearchContext.d.ts.map +1 -1
  19. package/lib/cjs/topology/XYParitySearchContext.js +73 -71
  20. package/lib/cjs/topology/XYParitySearchContext.js.map +1 -1
  21. package/lib/esm/serialization/GeometrySamples.d.ts +11 -0
  22. package/lib/esm/serialization/GeometrySamples.d.ts.map +1 -1
  23. package/lib/esm/serialization/GeometrySamples.js +16 -0
  24. package/lib/esm/serialization/GeometrySamples.js.map +1 -1
  25. package/lib/esm/topology/Graph.d.ts +1 -3
  26. package/lib/esm/topology/Graph.d.ts.map +1 -1
  27. package/lib/esm/topology/Graph.js +3 -3
  28. package/lib/esm/topology/Graph.js.map +1 -1
  29. package/lib/esm/topology/HalfEdgeGraphSearch.d.ts +81 -76
  30. package/lib/esm/topology/HalfEdgeGraphSearch.d.ts.map +1 -1
  31. package/lib/esm/topology/HalfEdgeGraphSearch.js +126 -111
  32. package/lib/esm/topology/HalfEdgeGraphSearch.js.map +1 -1
  33. package/lib/esm/topology/SignedDataSummary.d.ts +13 -13
  34. package/lib/esm/topology/SignedDataSummary.d.ts.map +1 -1
  35. package/lib/esm/topology/SignedDataSummary.js +3 -3
  36. package/lib/esm/topology/SignedDataSummary.js.map +1 -1
  37. package/lib/esm/topology/XYParitySearchContext.d.ts +27 -21
  38. package/lib/esm/topology/XYParitySearchContext.d.ts.map +1 -1
  39. package/lib/esm/topology/XYParitySearchContext.js +73 -71
  40. package/lib/esm/topology/XYParitySearchContext.js.map +1 -1
  41. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"HalfEdgeGraphSearch.js","sourceRoot":"","sources":["../../../src/topology/HalfEdgeGraphSearch.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\n/** @packageDocumentation\r\n * @module Topology\r\n */\r\n\r\nimport { HalfEdge, HalfEdgeGraph, HalfEdgeMask, HalfEdgeToBooleanFunction, NodeToNumberFunction } from \"./Graph\";\r\nimport { SignedDataSummary } from \"./SignedDataSummary\";\r\nimport { XYParitySearchContext } from \"./XYParitySearchContext\";\r\n\r\n/**\r\n * Interface for an object that executes boolean tests on edges.\r\n */\r\nexport interface HalfEdgeTestObject {\r\n testEdge(h: HalfEdge): boolean;\r\n}\r\n/**\r\n */\r\nexport class HalfEdgeMaskTester {\r\n private _targetMask: HalfEdgeMask;\r\n private _targetValue: boolean;\r\n /**\r\n *\r\n * @param mask mask to test in `testEdge` function\r\n * @param targetValue value to match for true return\r\n */\r\n public constructor(mask: HalfEdgeMask, targetValue: boolean = true) {\r\n this._targetMask = mask;\r\n this._targetValue = targetValue;\r\n }\r\n /** Return true if the value of the targetMask matches the targetValue */\r\n public testEdge(edge: HalfEdge): boolean {\r\n return edge.isMaskSet(this._targetMask) === this._targetValue;\r\n }\r\n\r\n}\r\n// Search services for HalfEdgeGraph\r\nexport class HalfEdgeGraphSearch {\r\n\r\n /**\r\n * * for each node of face, set the mask push to allNodesStack\r\n * * push the faceSeed on onePerFaceStack[]\r\n */\r\n private static pushAndMaskAllNodesInFace(faceSeed: HalfEdge, mask: number, allNodeStack: HalfEdge[], onePerFaceStack: HalfEdge[]) {\r\n onePerFaceStack.push(faceSeed);\r\n faceSeed.collectAroundFace((node: HalfEdge) => {\r\n node.setMask(mask);\r\n allNodeStack.push(node);\r\n });\r\n }\r\n\r\n /**\r\n * Search an array of faceSeed nodes for the face with the most negative area.\r\n * @param oneCandidateNodePerFace array containing one node from each face to be considered.\r\n * @returns node on the minimum area face, or undefined if no such face (e.g., all faces have zero area).\r\n */\r\n public static findMinimumAreaFace(oneCandidateNodePerFace: HalfEdgeGraph | HalfEdge[], faceAreaFunction?: NodeToNumberFunction): HalfEdge | undefined {\r\n const summary = HalfEdgeGraphSearch.collectFaceAreaSummary(oneCandidateNodePerFace, false, faceAreaFunction);\r\n return summary.largestNegativeItem;\r\n }\r\n\r\n /**\r\n * static method for face area computation -- useful as function parameter in collect FaceAreaSummary.\r\n * * This simply calls `node.signedFaceArea ()`\r\n * @param node instance for signedFaceArea call.\r\n */\r\n public static signedFaceArea(node: HalfEdge): number { return node.signedFaceArea(); }\r\n /**\r\n *\r\n * Return a summary structure data about face (or other numeric quantity if the caller's areaFunction returns other value)\r\n * * The default areaFunction computes area of polygonal face.\r\n * * Callers with curved edge graphs must supply their own area function.\r\n * @param source graph or array of nodes to examine\r\n * @param collectAllNodes flag to pass to the SignedDataSummary constructor to control collection of nodes.\r\n * @param areaFunction function to all to obtain area (or other numeric value)\r\n */\r\n public static collectFaceAreaSummary(source: HalfEdgeGraph | HalfEdge[], collectAllNodes: boolean = false,\r\n areaFunction: NodeToNumberFunction = (node) => HalfEdgeGraphSearch.signedFaceArea(node)): SignedDataSummary<HalfEdge> {\r\n const result = new SignedDataSummary<HalfEdge>(collectAllNodes);\r\n let allFaces: HalfEdge[];\r\n\r\n if (source instanceof HalfEdgeGraph)\r\n allFaces = source.collectFaceLoops();\r\n else\r\n allFaces = source;\r\n\r\n for (const node of allFaces) {\r\n const area = areaFunction(node);\r\n result.announceItem(node, area);\r\n }\r\n return result;\r\n }\r\n\r\n /**\r\n * * Test if the graph is triangulated.\r\n * * Return false if:\r\n * * Positive area face with more than 3 edges\r\n * * more than 1 negative area face with `allowMultipleNegativeAreaFaces` false\r\n * * 2-edge faces are ignored.\r\n */\r\n public static isTriangulatedCCW(source: HalfEdgeGraph | HalfEdge[], allowMultipleNegativeAreaFaces: boolean = true, numPositiveExceptionsAllowed = 0): boolean {\r\n let allFaces: HalfEdge[];\r\n\r\n if (source instanceof HalfEdgeGraph)\r\n allFaces = source.collectFaceLoops();\r\n else\r\n allFaces = source;\r\n let numNegative = 0;\r\n let numPositiveExceptions = 0;\r\n for (const node of allFaces) {\r\n const numEdges = node.countEdgesAroundFace();\r\n if (numEdges >= 3) {\r\n const area = node.signedFaceArea();\r\n if (area > 0) {\r\n if (numEdges > 3) {\r\n numPositiveExceptions++;\r\n if (numPositiveExceptions > numPositiveExceptionsAllowed)\r\n return false;\r\n }\r\n } else {\r\n numNegative++;\r\n if (numNegative > 1) {\r\n if (!allowMultipleNegativeAreaFaces)\r\n return false;\r\n }\r\n }\r\n }\r\n }\r\n return true;\r\n }\r\n\r\n /**\r\n * Search to all accessible faces from given seed.\r\n * * The returned array contains one representative node in each face of the connected component.\r\n * * If (nonnull) parity mask is given, on return:\r\n * * It is entirely set or entirely clear around each face\r\n * * It is entirely set on all faces that are an even number of face-to-face steps away from the seed.\r\n * * It is entirely clear on all faces that are an odd number of face-to-face steps away from the seed.\r\n * @param seedEdge first edge to search.\r\n * @param visitMask mask applied to all faces as visited.\r\n * @param parityMask mask to apply (a) to first face, (b) to faces with alternating parity during the search.\r\n */\r\n private static parityFloodFromSeed(seedEdge: HalfEdge, visitMask: HalfEdgeMask, parityEdgeTester: HalfEdgeTestObject | undefined, parityMask: HalfEdgeMask): HalfEdge[] {\r\n const faces: HalfEdge[] = [];\r\n if (seedEdge.isMaskSet(visitMask)) return faces; // empty\r\n\r\n const allMasks = parityMask | visitMask;\r\n const stack: HalfEdge[] = [];\r\n // arbitrarily call the seed face exterior ... others will alternate as visited.\r\n HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(seedEdge, allMasks, stack, faces); // Start with exterior as mask\r\n while (stack.length > 0) {\r\n const p = stack.pop()!;\r\n const mate = p.edgeMate;\r\n if (!mate)\r\n continue;\r\n if (!mate.isMaskSet(visitMask)) {\r\n let newState = p.isMaskSet(parityMask);\r\n if (!parityEdgeTester || parityEdgeTester.testEdge(p))\r\n newState = !newState;\r\n HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(mate, newState ? allMasks : visitMask, stack, faces);\r\n }\r\n }\r\n return faces;\r\n }\r\n /**\r\n * * Search the given faces for the one with the minimum area.\r\n * * If the mask in that face is OFF, toggle it on (all half edges of) all the faces.\r\n * * In a properly merged planar subdivision there should be only one true negative area face per component.\r\n * @param graph parent graph\r\n * @param parityMask mask which was previously set with alternating parity, but with an arbitrary start face.\r\n * @param faces array of faces to search.\r\n */\r\n private static correctParityInSingleComponent(_graph: HalfEdgeGraph, mask: HalfEdgeMask, faces: HalfEdge[]) {\r\n const exteriorHalfEdge = HalfEdgeGraphSearch.findMinimumAreaFace(faces);\r\n if (!exteriorHalfEdge) {\r\n } else if (exteriorHalfEdge.isMaskSet(mask)) {\r\n // all should be well .. nothing to do.\r\n } else {\r\n // TOGGLE around the face (assuming all are consistent with the seed)\r\n for (const faceSeed of faces) {\r\n if (faceSeed.isMaskSet(mask)) {\r\n faceSeed.clearMaskAroundFace(mask);\r\n } else {\r\n faceSeed.setMaskAroundFace(mask);\r\n }\r\n }\r\n }\r\n }\r\n /** Apply correctParityInSingleComponent to each array in components. (Quick exit if mask in NULL_MASK) */\r\n private static correctParityInComponentArrays(graph: HalfEdgeGraph, mask: HalfEdgeMask, components: HalfEdge[][]) {\r\n if (mask === HalfEdgeMask.NULL_MASK)\r\n return;\r\n for (const facesInComponent of components)\r\n HalfEdgeGraphSearch.correctParityInSingleComponent(graph, mask, facesInComponent);\r\n }\r\n /**\r\n * Collect arrays gathering faces by connected component.\r\n * @param graph graph to inspect\r\n * @param parityEdgeTester (optional) function to test if an edge is a parity change (e.g., a boundary edge).\r\n * @param parityMask (optional, along with parityEdgeTester) mask to apply indicating parity. If this is Mask.NULL_MASK, there is no record of parity.\r\n */\r\n public static collectConnectedComponentsWithExteriorParityMasks(graph: HalfEdgeGraph, parityEdgeTester: HalfEdgeTestObject | undefined, parityMask: HalfEdgeMask = HalfEdgeMask.NULL_MASK): HalfEdge[][] {\r\n const components = [];\r\n const visitMask = HalfEdgeMask.VISITED;\r\n const allMasks = parityMask | visitMask;\r\n graph.clearMask(allMasks);\r\n for (const faceSeed of graph.allHalfEdges) {\r\n if (!faceSeed.isMaskSet(HalfEdgeMask.VISITED)) {\r\n const newFaces = HalfEdgeGraphSearch.parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask);\r\n components.push(newFaces);\r\n }\r\n }\r\n HalfEdgeGraphSearch.correctParityInComponentArrays(graph, parityMask, components);\r\n return components;\r\n }\r\n /**\r\n * Test if (x,y) is inside (1), on an edge (0) or outside (-1) a face.\r\n * @param seedNode any node on the face loop\r\n * @param x x coordinate of test point.\r\n * @param y y coordinate of test point.\r\n */\r\n public static pointInOrOnFaceXY(seedNode: HalfEdge, x: number, y: number): number | undefined {\r\n const context = new XYParitySearchContext(x, y);\r\n // walk around looking for an accepted node to start the search (seedNode is usually ok!)\r\n let nodeA = seedNode;\r\n let nodeB = seedNode.faceSuccessor;\r\n for (; ; nodeA = nodeB) {\r\n if (context.tryStartEdge(nodeA.x, nodeA.y, nodeB.x, nodeB.y))\r\n break;\r\n if (nodeB === seedNode) {\r\n // umm.. the face is all on the x axis?\r\n return context.classifyCounts();\r\n }\r\n nodeB = nodeA.faceSuccessor;\r\n }\r\n\r\n // nodeB is the real start node for search ... emit ends of each edge around the face,\r\n // stopping after emitting nodeB as an edge end.\r\n let node = nodeB.faceSuccessor;\r\n for (; ;) {\r\n if (!context.advance(node.x, node.y)) {\r\n return context.classifyCounts();\r\n }\r\n if (node === nodeB)\r\n break;\r\n node = node.faceSuccessor;\r\n }\r\n return context.classifyCounts();\r\n }\r\n /**\r\n * Announce nodes that are \"extended face boundary\" by conditions (usually mask of node and mate) in test functions.\r\n * * After each node, the next candidate in reached by looking \"around the head vertex loop\" for the next boundary.\r\n * * \"Around the vertex\" from nodeA means\r\n * * First look at nodeA.faceSuccessor;\r\n * * Then look at vertexPredecessor around that vertex loop.\r\n * * Each accepted node is passed to announceNode, and marked with the visit mask.\r\n * * The counter of the announceEdge function is zero for the first edge, then increases with each edge.\r\n * @param seed start node.\r\n * @param isBoundaryEdge\r\n * @param announceEdge\r\n */\r\n public static collectExtendedBoundaryLoopFromSeed(seed: HalfEdge, visitMask: HalfEdgeMask, isBoundaryEdge: HalfEdgeToBooleanFunction,\r\n announceEdge: (edge: HalfEdge, counter: number) => void) {\r\n let counter = 0;\r\n while (!seed.getMask(visitMask) && isBoundaryEdge(seed)) {\r\n announceEdge(seed, counter++);\r\n seed.setMask(visitMask);\r\n const vertexBase = seed.faceSuccessor;\r\n let candidateAroundVertex = vertexBase;\r\n for (; ;) {\r\n if (candidateAroundVertex.getMask(visitMask))\r\n return;\r\n if (isBoundaryEdge(candidateAroundVertex)) {\r\n seed = candidateAroundVertex;\r\n break;\r\n }\r\n candidateAroundVertex = candidateAroundVertex.vertexPredecessor;\r\n if (candidateAroundVertex === vertexBase)\r\n break;\r\n }\r\n }\r\n }\r\n /**\r\n * Collect arrays of nodes \"around the boundary\" of a graph with extraneous (non-boundary) edges.\r\n * * The \"boundary\" is nodes that do NOT have the exterior mask, but whose mates DO have the exterior mask.\r\n * * After each node, the next candidate in reached by looking \"around the head vertex loop\" for the next boundary.\r\n * * \"Around the vertex\" from nodeA means\r\n * * First look at nodeA.faceSuccessor;\r\n * * Then look at vertexPredecessor around that vertex loop.\r\n * * Each accepted node is passed to announceNode, and marked with the visit mask.\r\n * @param seed start node.\r\n * @param isBoundaryNode\r\n * @param announceNode\r\n */\r\n public static collectExtendedBoundaryLoopsInGraph(graph: HalfEdgeGraph, exteriorMask: HalfEdgeMask): HalfEdge[][] {\r\n const loops: HalfEdge[][] = [];\r\n const visitMask = graph.grabMask(true);\r\n const isBoundaryEdge = (edge: HalfEdge): boolean => {\r\n return edge.getMask(exteriorMask) === 0 && edge.edgeMate.getMask(exteriorMask) !== 0;\r\n };\r\n const announceEdgeInBoundary = (edge: HalfEdge, counter: number) => {\r\n if (counter === 0)\r\n loops.push([]);\r\n loops[loops.length - 1].push(edge);\r\n };\r\n for (const seed of graph.allHalfEdges) {\r\n this.collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge, announceEdgeInBoundary);\r\n }\r\n graph.dropMask(visitMask);\r\n return loops;\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"HalfEdgeGraphSearch.js","sourceRoot":"","sources":["../../../src/topology/HalfEdgeGraphSearch.ts"],"names":[],"mappings":"AAAA;;;+FAG+F;AAE/F;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAY,aAAa,EAAE,YAAY,EAAmD,MAAM,SAAS,CAAC;AACjH,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAQhE,6CAA6C;AAC7C,MAAM,OAAO,kBAAkB;IAG7B;;;;OAIG;IACH,YAAmB,IAAkB,EAAE,cAAuB,IAAI;QAChE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;IAClC,CAAC;IACD,0EAA0E;IACnE,QAAQ,CAAC,IAAc;QAC5B,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,YAAY,CAAC;IAChE,CAAC;CACF;AACD,+DAA+D;AAC/D,MAAM,OAAO,mBAAmB;IAC9B;;;;OAIG;IACI,MAAM,CAAC,cAAc,CAAC,IAAc;QACzC,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;IACD;;;;;;OAMG;IACI,MAAM,CAAC,sBAAsB,CAClC,MAAkC,EAClC,kBAA2B,KAAK,EAChC,eAAqC,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,cAAc,CAAC,IAAI,CAAC;QAEvF,MAAM,MAAM,GAAG,IAAI,iBAAiB,CAAW,eAAe,CAAC,CAAC;QAChE,IAAI,QAAoB,CAAC;QACzB,IAAI,MAAM,YAAY,aAAa;YACjC,QAAQ,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;;YAErC,QAAQ,GAAG,MAAM,CAAC;QACpB,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE;YAC3B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;SACjC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD;;;;OAIG;IACI,MAAM,CAAC,mBAAmB,CAC/B,uBAAmD,EAAE,gBAAuC;QAE5F,MAAM,OAAO,GAAG,mBAAmB,CAAC,sBAAsB,CAAC,uBAAuB,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC;QAC7G,OAAO,OAAO,CAAC,mBAAmB,CAAC;IACrC,CAAC;IACD;;;;;;OAMG;IACI,MAAM,CAAC,iBAAiB,CAC7B,MAAkC,EAClC,iCAA0C,IAAI,EAC9C,+BAAuC,CAAC;QAExC,IAAI,QAAoB,CAAC;QACzB,IAAI,MAAM,YAAY,aAAa;YACjC,QAAQ,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;;YAErC,QAAQ,GAAG,MAAM,CAAC;QACpB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,qBAAqB,GAAG,CAAC,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE;YAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC7C,IAAI,QAAQ,IAAI,CAAC,EAAE;gBACjB,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;gBACnC,IAAI,IAAI,GAAG,CAAC,EAAE;oBACZ,IAAI,QAAQ,GAAG,CAAC,EAAE;wBAChB,qBAAqB,EAAE,CAAC;wBACxB,IAAI,qBAAqB,GAAG,4BAA4B;4BACtD,OAAO,KAAK,CAAC;qBAChB;iBACF;qBAAM;oBACL,WAAW,EAAE,CAAC;oBACd,IAAI,WAAW,GAAG,CAAC,EAAE;wBACnB,IAAI,CAAC,8BAA8B;4BACjC,OAAO,KAAK,CAAC;qBAChB;iBACF;aACF;SACF;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD;;;;;;OAMG;IACK,MAAM,CAAC,yBAAyB,CACtC,QAAkB,EAAE,IAAY,EAAE,YAAwB,EAAE,eAA2B;QAEvF,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/B,QAAQ,CAAC,iBAAiB,CAAC,CAAC,IAAc,EAAE,EAAE;YAC5C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACnB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IACD;;;;;;;;;;;;;OAaG;IACK,MAAM,CAAC,mBAAmB,CAChC,QAAkB,EAClB,SAAuB,EACvB,gBAAgD,EAChD,UAAwB;QAExB,MAAM,KAAK,GAAe,EAAE,CAAC;QAC7B,IAAI,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;YAC/B,OAAO,KAAK,CAAC,CAAC,cAAc;QAC9B,MAAM,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;QACxC,MAAM,KAAK,GAAe,EAAE,CAAC;QAC7B,wDAAwD;QACxD,mBAAmB,CAAC,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAChF,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;YACvB,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;YACvB,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC;YACxB,IAAI,CAAC,IAAI;gBACP,SAAS;YACX,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE;gBAC9B,IAAI,QAAQ,GAAG,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;gBACvC,IAAI,CAAC,gBAAgB,IAAI,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;oBACnD,QAAQ,GAAG,CAAC,QAAQ,CAAC;gBACvB,mBAAmB,CAAC,yBAAyB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;aACpG;SACF;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD;;;;;;;;;OASG;IACK,MAAM,CAAC,8BAA8B,CAAC,UAAwB,EAAE,KAAiB;QACvF,MAAM,gBAAgB,GAAG,mBAAmB,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QACxE,IAAI,CAAC,gBAAgB,EAAE;YACrB,6CAA6C;SAC9C;aAAM,IAAI,gBAAgB,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE;YACjD,oCAAoC;SACrC;aAAM;YACL,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE;gBAC5B,IAAI,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE;oBAClC,QAAQ,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;iBAC1C;qBAAM;oBACL,QAAQ,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;iBACxC;aACF;SACF;IACH,CAAC;IACD,sHAAsH;IAC9G,MAAM,CAAC,8BAA8B,CAAC,UAAwB,EAAE,UAAwB;QAC9F,IAAI,UAAU,KAAK,YAAY,CAAC,SAAS;YACvC,OAAO;QACT,KAAK,MAAM,gBAAgB,IAAI,UAAU;YACvC,mBAAmB,CAAC,8BAA8B,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;IACrF,CAAC;IACD;;;;;;;;;;;;;OAaG;IACI,MAAM,CAAC,iDAAiD,CAC7D,KAAoB,EACpB,gBAAgD,EAChD,aAA2B,YAAY,CAAC,SAAS;QAEjD,+EAA+E;QAC/E,MAAM,UAAU,GAAG,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;QACvC,MAAM,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;QACxC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC1B,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,YAAY,EAAE;YACzC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE;gBAClC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,mBAAmB,CAAC,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,UAAU,CAAC,CAAC;gBAC5G,mGAAmG;gBACnG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;aAC3B;SACF;QACD,mBAAmB,CAAC,8BAA8B,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC3E,OAAO,UAAU,CAAC;IACpB,CAAC;IACD;;;;;;OAMG;IACI,MAAM,CAAC,iBAAiB,CAAC,QAAkB,EAAE,KAAa,EAAE,KAAa;QAC9E,MAAM,OAAO,GAAG,IAAI,qBAAqB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACxD,wFAAwF;QACxF,IAAI,KAAK,GAAG,QAAQ,CAAC;QACrB,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC;QACnC,QAAS,KAAK,GAAG,KAAK,EAAE;YACtB,IAAI,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;gBAC1D,MAAM;YACR,IAAI,KAAK,KAAK,QAAQ,EAAE;gBACtB,0DAA0D;gBAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;gBAC/D,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACxC;YACD,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC;SAC7B;QACD,iHAAiH;QACjH,IAAI,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC;QAChC,SAAU;YACR,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE;gBACtC,OAAO,OAAO,CAAC,cAAc,EAAE,CAAC;aACjC;YACD,IAAI,KAAK,KAAK,KAAK;gBACjB,MAAM;YACR,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC;SAC7B;QACD,OAAO,OAAO,CAAC,cAAc,EAAE,CAAC;IAClC,CAAC;IACD;;;;;;;;OAQG;IACI,MAAM,CAAC,mCAAmC,CAC/C,IAAc,EACd,SAAuB,EACvB,cAAyC,EACzC,sBAAiE;QAEjE,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE;YACvD,sBAAsB,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;YACxC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACxB,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC;YACtC,IAAI,qBAAqB,GAAG,UAAU,CAAC;YACvC,SAAU;gBACR,IAAI,qBAAqB,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,uBAAuB;oBACnE,OAAO;gBACT,IAAI,cAAc,CAAC,qBAAqB,CAAC,EAAE;oBACzC,IAAI,GAAG,qBAAqB,CAAC;oBAC7B,MAAM;iBACP;gBACD,qBAAqB,GAAG,qBAAqB,CAAC,iBAAiB,CAAC;gBAChE,IAAI,qBAAqB,KAAK,UAAU;oBACtC,MAAM,CAAC,8FAA8F;aACxG;SACF;IACH,CAAC;IACD;;;;;;;;;OASG;IACI,MAAM,CAAC,mCAAmC,CAAC,KAAoB,EAAE,YAA0B;QAChG,+EAA+E;QAC/E,MAAM,KAAK,GAAiB,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,cAAc,GAAG,CAAC,IAAc,EAAW,EAAE;YACjD,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACvF,CAAC,CAAC;QACF,MAAM,sBAAsB,GAAG,CAAC,IAAc,EAAE,OAAe,EAAE,EAAE;YACjE,IAAI,OAAO,KAAK,CAAC;gBACf,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjB,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC,CAAC;QACF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,YAAY,EAAE;YACrC,IAAI,CAAC,mCAAmC,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,sBAAsB,CAAC,CAAC;SACnG;QACD,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;CACF","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\n/** @packageDocumentation\r\n * @module Topology\r\n */\r\nimport { Range1d } from \"../geometry3d/Range\";\r\nimport { HalfEdge, HalfEdgeGraph, HalfEdgeMask, HalfEdgeToBooleanFunction, NodeToNumberFunction } from \"./Graph\";\r\nimport { SignedDataSummary } from \"./SignedDataSummary\";\r\nimport { XYParitySearchContext } from \"./XYParitySearchContext\";\r\n\r\n// cspell:word internaldocs\r\n\r\n/** Interface for an object that executes boolean tests on edges. */\r\nexport interface HalfEdgeTestObject {\r\n testEdge(h: HalfEdge): boolean;\r\n}\r\n/** Class to test match of half edge mask. */\r\nexport class HalfEdgeMaskTester {\r\n private _targetMask: HalfEdgeMask;\r\n private _targetValue: boolean;\r\n /**\r\n * Constructor\r\n * @param mask mask to test in `testEdge` function\r\n * @param targetValue value to match for true return\r\n */\r\n public constructor(mask: HalfEdgeMask, targetValue: boolean = true) {\r\n this._targetMask = mask;\r\n this._targetValue = targetValue;\r\n }\r\n /** Return true if the value of the targetMask matches the targetValue. */\r\n public testEdge(edge: HalfEdge): boolean {\r\n return edge.isMaskSet(this._targetMask) === this._targetValue;\r\n }\r\n}\r\n/** Class for different types of searches for HalfEdgeGraph. */\r\nexport class HalfEdgeGraphSearch {\r\n /**\r\n * Static method for face area computation -- useful as function parameter in `collectFaceAreaSummary`.\r\n * * This simply calls `node.signedFaceArea()`\r\n * @param node instance for signedFaceArea call.\r\n */\r\n public static signedFaceArea(node: HalfEdge): number {\r\n return node.signedFaceArea();\r\n }\r\n /**\r\n * Return a summary of face data (e.g., area) as computed by the callback on the faces of the graph.\r\n * * Callers with curved edge graphs must supply their own area function.\r\n * @param source graph or array of nodes to examine.\r\n * @param collectAllNodes flag to pass to the `SignedDataSummary` constructor to control collection of nodes.\r\n * @param areaFunction function to obtain area (or other numeric value). Default computes polygonal face area.\r\n */\r\n public static collectFaceAreaSummary(\r\n source: HalfEdgeGraph | HalfEdge[],\r\n collectAllNodes: boolean = false,\r\n areaFunction: NodeToNumberFunction = (node) => HalfEdgeGraphSearch.signedFaceArea(node),\r\n ): SignedDataSummary<HalfEdge> {\r\n const result = new SignedDataSummary<HalfEdge>(collectAllNodes);\r\n let allFaces: HalfEdge[];\r\n if (source instanceof HalfEdgeGraph)\r\n allFaces = source.collectFaceLoops();\r\n else\r\n allFaces = source;\r\n for (const node of allFaces) {\r\n const area = areaFunction(node);\r\n result.announceItem(node, area);\r\n }\r\n return result;\r\n }\r\n /**\r\n * Search the graph for the face with the most negative area.\r\n * @param oneCandidateNodePerFace graph or an array containing one node from each face to be considered.\r\n * @returns node on the negative area face with largest absolute area, or `undefined` if no negative area face.\r\n */\r\n public static findMinimumAreaFace(\r\n oneCandidateNodePerFace: HalfEdgeGraph | HalfEdge[], faceAreaFunction?: NodeToNumberFunction,\r\n ): HalfEdge | undefined {\r\n const summary = HalfEdgeGraphSearch.collectFaceAreaSummary(oneCandidateNodePerFace, false, faceAreaFunction);\r\n return summary.largestNegativeItem;\r\n }\r\n /**\r\n * Test if the graph is triangulated.\r\n * * Return `false` if:\r\n * * number of positive area faces with more than 3 edges is larger than `numPositiveExceptionsAllowed`.\r\n * * graph has more than 1 negative area face when `allowMultipleNegativeAreaFaces` is `false`.\r\n * * 2-edge faces are ignored.\r\n */\r\n public static isTriangulatedCCW(\r\n source: HalfEdgeGraph | HalfEdge[],\r\n allowMultipleNegativeAreaFaces: boolean = true,\r\n numPositiveExceptionsAllowed: number = 0,\r\n ): boolean {\r\n let allFaces: HalfEdge[];\r\n if (source instanceof HalfEdgeGraph)\r\n allFaces = source.collectFaceLoops();\r\n else\r\n allFaces = source;\r\n let numNegative = 0;\r\n let numPositiveExceptions = 0;\r\n for (const node of allFaces) {\r\n const numEdges = node.countEdgesAroundFace();\r\n if (numEdges >= 3) {\r\n const area = node.signedFaceArea();\r\n if (area > 0) {\r\n if (numEdges > 3) {\r\n numPositiveExceptions++;\r\n if (numPositiveExceptions > numPositiveExceptionsAllowed)\r\n return false;\r\n }\r\n } else {\r\n numNegative++;\r\n if (numNegative > 1) {\r\n if (!allowMultipleNegativeAreaFaces)\r\n return false;\r\n }\r\n }\r\n }\r\n }\r\n return true;\r\n }\r\n /**\r\n * Process a face during graph traversal.\r\n * @param faceSeed a node in the face.\r\n * @param mask mask to set on each node of the face.\r\n * @param allNodeStack array appended with each node of the face.\r\n * @param onePerFaceStack array appended with `faceSeed`.\r\n */\r\n private static pushAndMaskAllNodesInFace(\r\n faceSeed: HalfEdge, mask: number, allNodeStack: HalfEdge[], onePerFaceStack: HalfEdge[],\r\n ): void {\r\n onePerFaceStack.push(faceSeed);\r\n faceSeed.collectAroundFace((node: HalfEdge) => {\r\n node.setMask(mask);\r\n allNodeStack.push(node);\r\n });\r\n }\r\n /**\r\n * Traverse (via Depth First Search) to all accessible faces from the given seed.\r\n * @param faceSeed first node to start the traverse.\r\n * @param visitMask mask applied to all faces as visited.\r\n * @param parityEdgeTester function to test if an edge is adjacent to two faces of opposite parity, e.g., a boundary\r\n * edge that separates an \"interior\" face and an \"exterior\" face. If `parityEdgeTester` is not supplied and `parityMask`\r\n * is supplied, the default parity rule is to alternate parity state in a \"bullseye\" pattern starting at the seed face,\r\n * with each successive concentric ring of faces at constant topological distance from the seed face receiving the\r\n * opposite parity state of the previous ring.\r\n * @param parityMask mask to apply to the first face and faces that share the same parity as the first face, as\r\n * determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If (non-null) parity mask\r\n * is given, on return it is entirely set or entirely clear around each face.\r\n * @returns an array that contains one representative node in each face of the connected component.\r\n */\r\n private static parityFloodFromSeed(\r\n faceSeed: HalfEdge,\r\n visitMask: HalfEdgeMask,\r\n parityEdgeTester: HalfEdgeTestObject | undefined,\r\n parityMask: HalfEdgeMask,\r\n ): HalfEdge[] {\r\n const faces: HalfEdge[] = [];\r\n if (faceSeed.isMaskSet(visitMask))\r\n return faces; // empty array\r\n const allMasks = parityMask | visitMask;\r\n const stack: HalfEdge[] = [];\r\n // the seed face is arbitrarily assigned the parity mask\r\n HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(faceSeed, allMasks, stack, faces);\r\n while (stack.length > 0) {\r\n const p = stack.pop()!;\r\n const mate = p.edgeMate;\r\n if (!mate)\r\n continue;\r\n if (!mate.isMaskSet(visitMask)) {\r\n let newState = p.isMaskSet(parityMask);\r\n if (!parityEdgeTester || parityEdgeTester.testEdge(p))\r\n newState = !newState;\r\n HalfEdgeGraphSearch.pushAndMaskAllNodesInFace(mate, newState ? allMasks : visitMask, stack, faces);\r\n }\r\n }\r\n return faces;\r\n }\r\n /**\r\n * * Correct the parity mask in the faces of a component.\r\n * * It is assumed that the parity mask is applied _consistently_ throughout the supplied faces, but maybe\r\n * not _correctly_.\r\n * * A consistently applied parity mask is \"correct\" if it is set on the negative area (\"exterior\") face of\r\n * a connected component.\r\n * * This method finds a face with negative area and toggles the mask throughout the input faces if this face\r\n * lacks the parity mask.\r\n * * In a properly merged planar subdivision there should be only one true negative area face per component.\r\n */\r\n private static correctParityInSingleComponent(parityMask: HalfEdgeMask, faces: HalfEdge[]): void {\r\n const exteriorHalfEdge = HalfEdgeGraphSearch.findMinimumAreaFace(faces);\r\n if (!exteriorHalfEdge) {\r\n // graph has all degenerate faces; do nothing\r\n } else if (exteriorHalfEdge.isMaskSet(parityMask)) {\r\n // all should be well; nothing to do\r\n } else {\r\n for (const faceSeed of faces) {\r\n if (faceSeed.isMaskSet(parityMask)) {\r\n faceSeed.clearMaskAroundFace(parityMask);\r\n } else {\r\n faceSeed.setMaskAroundFace(parityMask);\r\n }\r\n }\r\n }\r\n }\r\n /** Apply `correctParityInSingleComponent` to each array in components (quick exit if `parityMask` is `NULL_MASK`). */\r\n private static correctParityInComponentArrays(parityMask: HalfEdgeMask, components: HalfEdge[][]): void {\r\n if (parityMask === HalfEdgeMask.NULL_MASK)\r\n return;\r\n for (const facesInComponent of components)\r\n HalfEdgeGraphSearch.correctParityInSingleComponent(parityMask, facesInComponent);\r\n }\r\n /**\r\n * Collect connected components of the graph (via Depth First Search).\r\n * @param graph graph to inspect.\r\n * @param parityEdgeTester (optional) function to test if an edge is adjacent to two faces of opposite parity,\r\n * e.g., a boundary edge that separates an \"interior\" face and an \"exterior\" face. If `parityEdgeTester` is not\r\n * supplied and `parityMask` is supplied, the default parity rule is to alternate parity state in a \"bullseye\"\r\n * pattern starting at the seed face, with each successive concentric ring of faces at constant topological\r\n * distance from the seed face receiving the opposite parity state of the previous ring.\r\n * @param parityMask (optional) mask to apply to the first face and faces that share the same parity as the\r\n * first face, as determined by the parity rule. If this is `NULL_MASK`, there is no record of parity. If\r\n * (non-null) parity mask is given, on return it is entirely set or entirely clear around each face.\r\n * @returns the components of the graph, each component represented by an array of nodes, one node per face\r\n * of the component. In other words, entry [i][j] is a HalfEdge in the j_th face loop of the i_th component.\r\n */\r\n public static collectConnectedComponentsWithExteriorParityMasks(\r\n graph: HalfEdgeGraph,\r\n parityEdgeTester: HalfEdgeTestObject | undefined,\r\n parityMask: HalfEdgeMask = HalfEdgeMask.NULL_MASK,\r\n ): HalfEdge[][] {\r\n // Illustration of the algorithm can be found at geometry/internaldocs/Graph.md\r\n const components = [];\r\n const visitMask = HalfEdgeMask.VISITED;\r\n const allMasks = parityMask | visitMask;\r\n graph.clearMask(allMasks);\r\n for (const faceSeed of graph.allHalfEdges) {\r\n if (!faceSeed.isMaskSet(visitMask)) {\r\n const newFaces = HalfEdgeGraphSearch.parityFloodFromSeed(faceSeed, visitMask, parityEdgeTester, parityMask);\r\n // parityFloodFromSeed does not return an empty array because it is called on an unvisited faceSeed\r\n components.push(newFaces);\r\n }\r\n }\r\n HalfEdgeGraphSearch.correctParityInComponentArrays(parityMask, components);\r\n return components;\r\n }\r\n /**\r\n * Test if test point (xTest,yTest) is inside/outside a face or on an edge.\r\n * @param seedNode any node on the face loop.\r\n * @param xTest x coordinate of the test point.\r\n * @param yTest y coordinate of the test point.\r\n * @returns 0 if ON, 1 if IN, -1 if OUT.\r\n */\r\n public static pointInOrOnFaceXY(seedNode: HalfEdge, xTest: number, yTest: number): number | undefined {\r\n const context = new XYParitySearchContext(xTest, yTest);\r\n // walk around looking for an accepted node to start the search (seedNode is usually ok)\r\n let nodeA = seedNode;\r\n let nodeB = seedNode.faceSuccessor;\r\n for (; ; nodeA = nodeB) {\r\n if (context.tryStartEdge(nodeA.x, nodeA.y, nodeB.x, nodeB.y))\r\n break;\r\n if (nodeB === seedNode) {\r\n // the test point and the face are all on line \"y = yTest\"\r\n const range = Range1d.createXX(nodeB.x, nodeB.faceSuccessor.x);\r\n return range.containsX(xTest) ? 0 : -1;\r\n }\r\n nodeB = nodeA.faceSuccessor;\r\n }\r\n // nodeB is the real start node for search, so stop when we revisit it. For each edge, accumulate parity and hits\r\n let nodeC = nodeB.faceSuccessor;\r\n for (; ;) {\r\n if (!context.advance(nodeC.x, nodeC.y)) {\r\n return context.classifyCounts();\r\n }\r\n if (nodeC === nodeB)\r\n break;\r\n nodeC = nodeC.faceSuccessor;\r\n }\r\n return context.classifyCounts();\r\n }\r\n /**\r\n * Collect boundary edges starting from `seed`.\r\n * * If `seed` is not a boundary node or is already visited, the function exists early.\r\n * @param seed start node.\r\n * @param visitMask mask to set on processed nodes.\r\n * @param isBoundaryEdge function to test if an edge in a boundary edge.\r\n * @param announceEdgeInBoundary callback invoked on each edge in the boundary loop in order. The counter is zero\r\n * for the first edge, and incremented with each successive edge.\r\n */\r\n public static collectExtendedBoundaryLoopFromSeed(\r\n seed: HalfEdge,\r\n visitMask: HalfEdgeMask,\r\n isBoundaryEdge: HalfEdgeToBooleanFunction,\r\n announceEdgeInBoundary: (edge: HalfEdge, counter: number) => void,\r\n ): void {\r\n let counter = 0;\r\n while (!seed.getMask(visitMask) && isBoundaryEdge(seed)) {\r\n announceEdgeInBoundary(seed, counter++);\r\n seed.setMask(visitMask);\r\n const vertexBase = seed.faceSuccessor;\r\n let candidateAroundVertex = vertexBase;\r\n for (; ;) {\r\n if (candidateAroundVertex.getMask(visitMask)) // end of boundary loop\r\n return;\r\n if (isBoundaryEdge(candidateAroundVertex)) {\r\n seed = candidateAroundVertex;\r\n break;\r\n }\r\n candidateAroundVertex = candidateAroundVertex.vertexPredecessor;\r\n if (candidateAroundVertex === vertexBase)\r\n break; // prevent infinite loop in case exteriorMask is not set on the edge mate of the boundary edge\r\n }\r\n }\r\n }\r\n /**\r\n * Collect boundary edges in the graph.\r\n * * A boundary edge is defined by `exteriorMask` being set on only its \"exterior\" edge mate.\r\n * * Each boundary edge is identified in the output by its edge mate that lacks `exteriorMask`.\r\n * * Each inner array is ordered in the output so that its boundary edges form a connected path. If `exteriorMask`\r\n * is preset consistently around each \"exterior\" face, these paths are loops.\r\n * @param graph the graph to query\r\n * @param exteriorMask mask preset on exactly one side of boundary edges\r\n * @returns array of boundary loops, each loop an array of the unmasked mates of boundary edges\r\n */\r\n public static collectExtendedBoundaryLoopsInGraph(graph: HalfEdgeGraph, exteriorMask: HalfEdgeMask): HalfEdge[][] {\r\n // Illustration of the algorithm can be found at geometry/internaldocs/Graph.md\r\n const loops: HalfEdge[][] = [];\r\n const visitMask = graph.grabMask(true);\r\n const isBoundaryEdge = (edge: HalfEdge): boolean => {\r\n return edge.getMask(exteriorMask) === 0 && edge.edgeMate.getMask(exteriorMask) !== 0;\r\n };\r\n const announceEdgeInBoundary = (edge: HalfEdge, counter: number) => {\r\n if (counter === 0)\r\n loops.push([]);\r\n loops[loops.length - 1].push(edge);\r\n };\r\n for (const seed of graph.allHalfEdges) {\r\n this.collectExtendedBoundaryLoopFromSeed(seed, visitMask, isBoundaryEdge, announceEdgeInBoundary);\r\n }\r\n graph.dropMask(visitMask);\r\n return loops;\r\n }\r\n}\r\n"]}
@@ -4,34 +4,34 @@
4
4
  /**
5
5
  * Class to accumulate statistics about a stream of signed numbers with tag items.
6
6
  * * All sums, counts, extrema, and item values are initialized to zero in the constructor.
7
- * * Each call to `announceItem (item, value)` updates the various sums, counts, and extrema.
7
+ * * Each call to `announceItem(item, value)` updates the various sums, counts, and extrema.
8
8
  */
9
9
  export declare class SignedDataSummary<T> {
10
- /** sum of all positive area items */
10
+ /** Sum of all positive area items. */
11
11
  positiveSum: number;
12
- /** number of positive area items */
12
+ /** Number of positive area items. */
13
13
  numPositive: number;
14
- /** sum of negative area items */
14
+ /** Sum of negative area items. */
15
15
  negativeSum: number;
16
- /** number of negative area items */
16
+ /** Number of negative area items. */
17
17
  numNegative: number;
18
- /** number of zero area items */
18
+ /** Number of zero area items. */
19
19
  numZero: number;
20
- /** the tag item item with the largest positive data */
20
+ /** The tag item item with the largest positive data. */
21
21
  largestPositiveItem?: T;
22
- /** the tag item item with the most negative data */
22
+ /** The tag item item with the most negative data. */
23
23
  largestNegativeItem?: T;
24
24
  largestPositiveValue: number;
25
25
  largestNegativeValue: number;
26
- /** array of all negative area items */
26
+ /** Array of all negative area items. */
27
27
  negativeItemArray?: T[];
28
- /** array of zero area items */
28
+ /** Array of zero area items. */
29
29
  zeroItemArray?: T[];
30
- /** array of positive area items */
30
+ /** Array of positive area items. */
31
31
  positiveItemArray?: T[];
32
- /** setup with zero sums and optional arrays */
32
+ /** Setup with zero sums and optional arrays. */
33
33
  constructor(createArrays: boolean);
34
- /** update with an item and its data value. */
34
+ /** Update with an item and its data value. */
35
35
  announceItem(item: T, data: number): void;
36
36
  }
37
37
  //# sourceMappingURL=SignedDataSummary.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SignedDataSummary.d.ts","sourceRoot":"","sources":["../../../src/topology/SignedDataSummary.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH;;;;GAIG;AACH,qBAAa,iBAAiB,CAAC,CAAC;IAC9B,qCAAqC;IAC9B,WAAW,EAAE,MAAM,CAAC;IAC3B,oCAAoC;IAC7B,WAAW,EAAE,MAAM,CAAC;IAC3B,iCAAiC;IAC1B,WAAW,EAAE,MAAM,CAAC;IAC3B,oCAAoC;IAC7B,WAAW,EAAE,MAAM,CAAC;IAC3B,gCAAgC;IACzB,OAAO,EAAE,MAAM,CAAC;IACvB,uDAAuD;IAChD,mBAAmB,CAAC,EAAE,CAAC,CAAC;IAC/B,oDAAoD;IAC7C,mBAAmB,CAAC,EAAE,CAAC,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;IACpC,uCAAuC;IAChC,iBAAiB,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/B,+BAA+B;IACxB,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC;IAC3B,mCAAmC;IAC5B,iBAAiB,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/B,+CAA+C;gBAC5B,YAAY,EAAE,OAAO;IAUxC,8CAA8C;IACvC,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM;CAyB1C"}
1
+ {"version":3,"file":"SignedDataSummary.d.ts","sourceRoot":"","sources":["../../../src/topology/SignedDataSummary.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH;;;;GAIG;AACH,qBAAa,iBAAiB,CAAC,CAAC;IAC9B,sCAAsC;IAC/B,WAAW,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IAC9B,WAAW,EAAE,MAAM,CAAC;IAC3B,kCAAkC;IAC3B,WAAW,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IAC9B,WAAW,EAAE,MAAM,CAAC;IAC3B,iCAAiC;IAC1B,OAAO,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACjD,mBAAmB,CAAC,EAAE,CAAC,CAAC;IAC/B,qDAAqD;IAC9C,mBAAmB,CAAC,EAAE,CAAC,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;IACpC,wCAAwC;IACjC,iBAAiB,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/B,gCAAgC;IACzB,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC;IAC3B,oCAAoC;IAC7B,iBAAiB,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/B,gDAAgD;gBAC7B,YAAY,EAAE,OAAO;IAUxC,8CAA8C;IACvC,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM;CAyB1C"}
@@ -8,10 +8,10 @@
8
8
  /**
9
9
  * Class to accumulate statistics about a stream of signed numbers with tag items.
10
10
  * * All sums, counts, extrema, and item values are initialized to zero in the constructor.
11
- * * Each call to `announceItem (item, value)` updates the various sums, counts, and extrema.
11
+ * * Each call to `announceItem(item, value)` updates the various sums, counts, and extrema.
12
12
  */
13
13
  export class SignedDataSummary {
14
- /** setup with zero sums and optional arrays */
14
+ /** Setup with zero sums and optional arrays. */
15
15
  constructor(createArrays) {
16
16
  this.positiveSum = this.negativeSum = 0.0;
17
17
  this.numPositive = this.numNegative = this.numZero = 0.0;
@@ -22,7 +22,7 @@ export class SignedDataSummary {
22
22
  this.zeroItemArray = [];
23
23
  }
24
24
  }
25
- /** update with an item and its data value. */
25
+ /** Update with an item and its data value. */
26
26
  announceItem(item, data) {
27
27
  if (data < 0) {
28
28
  this.numNegative++;
@@ -1 +1 @@
1
- {"version":3,"file":"SignedDataSummary.js","sourceRoot":"","sources":["../../../src/topology/SignedDataSummary.ts"],"names":[],"mappings":"AAAA;;;+FAG+F;AAE/F;;GAEG;AACH;;;;GAIG;AACH,MAAM,OAAO,iBAAiB;IAuB5B,+CAA+C;IAC/C,YAAmB,YAAqB;QACtC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QAC1C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;QACzD,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,oBAAoB,GAAG,GAAG,CAAC;QAC5D,IAAI,YAAY,EAAE;YAChB,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;SACzB;IACH,CAAC;IACD,8CAA8C;IACvC,YAAY,CAAC,IAAO,EAAE,IAAY;QACvC,IAAI,IAAI,GAAG,CAAC,EAAE;YACZ,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;YACzB,IAAI,IAAI,CAAC,iBAAiB;gBACxB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,IAAI,GAAG,IAAI,CAAC,oBAAoB,EAAE;gBACpC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;gBACjC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;aACjC;SACF;aAAM,IAAI,IAAI,GAAG,CAAC,EAAE;YACnB,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;YACzB,IAAI,IAAI,CAAC,iBAAiB;gBACxB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,IAAI,GAAG,IAAI,CAAC,oBAAoB,EAAE;gBACpC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;gBACjC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;aACjC;SACF;aAAM;YACL,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,aAAa;gBACpB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACjC;IACH,CAAC;CACF","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\n/** @packageDocumentation\r\n * @module Topology\r\n */\r\n/**\r\n * Class to accumulate statistics about a stream of signed numbers with tag items.\r\n * * All sums, counts, extrema, and item values are initialized to zero in the constructor.\r\n * * Each call to `announceItem (item, value)` updates the various sums, counts, and extrema.\r\n */\r\nexport class SignedDataSummary<T> {\r\n /** sum of all positive area items */\r\n public positiveSum: number;\r\n /** number of positive area items */\r\n public numPositive: number;\r\n /** sum of negative area items */\r\n public negativeSum: number;\r\n /** number of negative area items */\r\n public numNegative: number;\r\n /** number of zero area items */\r\n public numZero: number;\r\n /** the tag item item with the largest positive data */\r\n public largestPositiveItem?: T;\r\n /** the tag item item with the most negative data */\r\n public largestNegativeItem?: T;\r\n public largestPositiveValue: number;\r\n public largestNegativeValue: number;\r\n /** array of all negative area items */\r\n public negativeItemArray?: T[];\r\n /** array of zero area items */\r\n public zeroItemArray?: T[];\r\n /** array of positive area items */\r\n public positiveItemArray?: T[];\r\n /** setup with zero sums and optional arrays */\r\n public constructor(createArrays: boolean) {\r\n this.positiveSum = this.negativeSum = 0.0;\r\n this.numPositive = this.numNegative = this.numZero = 0.0;\r\n this.largestPositiveValue = this.largestNegativeValue = 0.0;\r\n if (createArrays) {\r\n this.negativeItemArray = [];\r\n this.positiveItemArray = [];\r\n this.zeroItemArray = [];\r\n }\r\n }\r\n /** update with an item and its data value. */\r\n public announceItem(item: T, data: number) {\r\n if (data < 0) {\r\n this.numNegative++;\r\n this.negativeSum += data;\r\n if (this.negativeItemArray)\r\n this.negativeItemArray.push(item);\r\n if (data < this.largestNegativeValue) {\r\n this.largestNegativeValue = data;\r\n this.largestNegativeItem = item;\r\n }\r\n } else if (data > 0) {\r\n this.numPositive++;\r\n this.positiveSum += data;\r\n if (this.positiveItemArray)\r\n this.positiveItemArray.push(item);\r\n if (data > this.largestPositiveValue) {\r\n this.largestPositiveValue = data;\r\n this.largestPositiveItem = item;\r\n }\r\n } else {\r\n this.numZero++;\r\n if (this.zeroItemArray)\r\n this.zeroItemArray.push(item);\r\n }\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"SignedDataSummary.js","sourceRoot":"","sources":["../../../src/topology/SignedDataSummary.ts"],"names":[],"mappings":"AAAA;;;+FAG+F;AAE/F;;GAEG;AACH;;;;GAIG;AACH,MAAM,OAAO,iBAAiB;IAuB5B,gDAAgD;IAChD,YAAmB,YAAqB;QACtC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QAC1C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;QACzD,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,oBAAoB,GAAG,GAAG,CAAC;QAC5D,IAAI,YAAY,EAAE;YAChB,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC,iBAAiB,GAAG,EAAE,CAAC;YAC5B,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;SACzB;IACH,CAAC;IACD,8CAA8C;IACvC,YAAY,CAAC,IAAO,EAAE,IAAY;QACvC,IAAI,IAAI,GAAG,CAAC,EAAE;YACZ,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;YACzB,IAAI,IAAI,CAAC,iBAAiB;gBACxB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,IAAI,GAAG,IAAI,CAAC,oBAAoB,EAAE;gBACpC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;gBACjC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;aACjC;SACF;aAAM,IAAI,IAAI,GAAG,CAAC,EAAE;YACnB,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;YACzB,IAAI,IAAI,CAAC,iBAAiB;gBACxB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,IAAI,GAAG,IAAI,CAAC,oBAAoB,EAAE;gBACpC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;gBACjC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;aACjC;SACF;aAAM;YACL,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,aAAa;gBACpB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;SACjC;IACH,CAAC;CACF","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\n/** @packageDocumentation\r\n * @module Topology\r\n */\r\n/**\r\n * Class to accumulate statistics about a stream of signed numbers with tag items.\r\n * * All sums, counts, extrema, and item values are initialized to zero in the constructor.\r\n * * Each call to `announceItem(item, value)` updates the various sums, counts, and extrema.\r\n */\r\nexport class SignedDataSummary<T> {\r\n /** Sum of all positive area items. */\r\n public positiveSum: number;\r\n /** Number of positive area items. */\r\n public numPositive: number;\r\n /** Sum of negative area items. */\r\n public negativeSum: number;\r\n /** Number of negative area items. */\r\n public numNegative: number;\r\n /** Number of zero area items. */\r\n public numZero: number;\r\n /** The tag item item with the largest positive data. */\r\n public largestPositiveItem?: T;\r\n /** The tag item item with the most negative data. */\r\n public largestNegativeItem?: T;\r\n public largestPositiveValue: number;\r\n public largestNegativeValue: number;\r\n /** Array of all negative area items. */\r\n public negativeItemArray?: T[];\r\n /** Array of zero area items. */\r\n public zeroItemArray?: T[];\r\n /** Array of positive area items. */\r\n public positiveItemArray?: T[];\r\n /** Setup with zero sums and optional arrays. */\r\n public constructor(createArrays: boolean) {\r\n this.positiveSum = this.negativeSum = 0.0;\r\n this.numPositive = this.numNegative = this.numZero = 0.0;\r\n this.largestPositiveValue = this.largestNegativeValue = 0.0;\r\n if (createArrays) {\r\n this.negativeItemArray = [];\r\n this.positiveItemArray = [];\r\n this.zeroItemArray = [];\r\n }\r\n }\r\n /** Update with an item and its data value. */\r\n public announceItem(item: T, data: number) {\r\n if (data < 0) {\r\n this.numNegative++;\r\n this.negativeSum += data;\r\n if (this.negativeItemArray)\r\n this.negativeItemArray.push(item);\r\n if (data < this.largestNegativeValue) {\r\n this.largestNegativeValue = data;\r\n this.largestNegativeItem = item;\r\n }\r\n } else if (data > 0) {\r\n this.numPositive++;\r\n this.positiveSum += data;\r\n if (this.positiveItemArray)\r\n this.positiveItemArray.push(item);\r\n if (data > this.largestPositiveValue) {\r\n this.largestPositiveValue = data;\r\n this.largestPositiveItem = item;\r\n }\r\n } else {\r\n this.numZero++;\r\n if (this.zeroItemArray)\r\n this.zeroItemArray.push(item);\r\n }\r\n }\r\n}\r\n"]}
@@ -2,52 +2,58 @@
2
2
  * @module Topology
3
3
  */
4
4
  /**
5
- * * XYParitySearchContext is an internal class for callers that can feed points (without extracting to array structures)
5
+ * `XYParitySearchContext` is an internal class for callers that can feed points (without extracting to array structures)
6
6
  * * Most will be via static methods which handle a specific data source.
7
- * * PolygonOps.classifyPointInPolygon (x,y,points: XAndY[])
8
- * * HalfEdgeGraphSearch.pointInOrOnFaceXY (halfEdgeOnFace, x, y)
7
+ * * PolygonOps.classifyPointInPolygon(x,y,points: XAndY[])
8
+ * * HalfEdgeGraphSearch.pointInOrOnFaceXY(halfEdgeOnFace, x, y)
9
9
  * Use pattern:
10
- * * Caller must be able walk around polygon producing x,y coordinates (possibly transformed from actual polygon)
10
+ * * Caller must be able to walk around polygon producing x,y coordinates (possibly transformed from actual polygon).
11
11
  * * Caller announce edges to tryStartEdge until finding one acceptable to the search.
12
12
  * * Caller then passes additional points up to and including both x0,y0 and x1, y1 of the accepted start edge.
13
13
  * Call sequence is:
14
- * `context = new XYParitySearchContext`
15
- * `repeat { acquire edge (x0,y0) (x1,y1)} until context.tryStartEdge (x0,y0,x1,y1);`
16
- * `for each (x,y) beginning AFTER x1,y1 and ending with (x1,y1) context.advance (x,y)`
17
- * `return context.classifyCounts ();`
14
+ * * `context = new XYParitySearchContext`
15
+ * * `repeat { acquire edge (x0,y0) (x1,y1) } until context.tryStartEdge(x0,y0,x1,y1);`
16
+ * * `for each (x,y) beginning AFTER x1,y1 and ending with (x1,y1) context.advance (x,y)`
17
+ * * `return context.classifyCounts();`
18
18
  */
19
19
  export declare class XYParitySearchContext {
20
20
  xTest: number;
21
21
  yTest: number;
22
+ /** local x-coordinate of the start of the previous (or earlier) edge */
22
23
  u0: number;
24
+ /** local y-coordinate of the start of the previous (or earlier) edge */
23
25
  v0: number;
26
+ /** local x-coordinate of the end of the previous edge (and start of current edge) */
24
27
  u1: number;
28
+ /** local y-coordinate of the end of the previous edge (and start of current edge) */
25
29
  v1: number;
26
30
  numLeftCrossing: number;
27
31
  numRightCrossing: number;
28
32
  numHit: number;
29
33
  /**
30
34
  * Create a new searcher for specified test point.
31
- * @param xTest x coordinate of test point
32
- * @param yTest y coordinate of test point
35
+ * @param xTest x coordinate of test point.
36
+ * @param yTest y coordinate of test point.
33
37
  */
34
38
  constructor(xTest: number, yTest: number);
35
- /**
36
- * test if x,y is a safe first coordinate to start the search.
37
- * * 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.
38
- * @param x
39
- * @param y
40
- */
39
+ /** Test if parity processing can begin with this edge. */
41
40
  tryStartEdge(x0: number, y0: number, x1: number, y1: number): boolean;
42
- /** Return true if parity accumulation proceeded normally.
43
- * Return false if interrupted for exact hit.
41
+ /** Update local coordinates: the current edge becomes the previous edge. */
42
+ private updateUV01;
43
+ /**
44
+ * Process the current edge ending at (x2,y2).
45
+ * * Accumulate left/right parity of the test point wrt to the polygon. These counts track the number of polygon crossings
46
+ * of the left and right horizontal rays emanating from the test point. After all edges are processed, if either count is
47
+ * odd/even, the test point is inside/outside the polygon (see [[classifyCounts]]).
48
+ * * Check whether the test point lies on the edge.
49
+ * @returns whether caller should continue processing with the next edge. In particular, `false` if we have an exact hit.
44
50
  */
45
- advance(x: number, y: number): boolean;
51
+ advance(x2: number, y2: number): boolean;
46
52
  /**
47
53
  * Return classification as ON, IN, or OUT according to hit and crossing counts.
48
- * * Any nonzero hit count is ON
54
+ * * Any nonzero hit count is ON.
49
55
  * * Otherwise IN if left crossing count is odd.
50
- * @return 0 if ON, 1 if IN, -1 if OUT
56
+ * @return 0 if ON, 1 if IN, -1 if OUT.
51
57
  */
52
58
  classifyCounts(): number | undefined;
53
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"XYParitySearchContext.d.ts","sourceRoot":"","sources":["../../../src/topology/XYParitySearchContext.ts"],"names":[],"mappings":"AAKA;;GAEG;AAEH;;;;;;;;;;;;;;GAcG;AACH,qBAAa,qBAAqB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACtB;;;;OAIG;gBACgB,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAO/C;;;;;OAKG;IACI,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO;IAU5E;;OAEG;IACI,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO;IAqE7C;;;;;OAKG;IACI,cAAc,IAAI,MAAM,GAAG,SAAS;CAM5C"}
1
+ {"version":3,"file":"XYParitySearchContext.d.ts","sourceRoot":"","sources":["../../../src/topology/XYParitySearchContext.ts"],"names":[],"mappings":"AAKA;;GAEG;AAEH;;;;;;;;;;;;;;GAcG;AACH,qBAAa,qBAAqB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACjE,EAAE,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACjE,EAAE,EAAE,MAAM,CAAC;IAClB,qFAAqF;IAC9E,EAAE,EAAE,MAAM,CAAC;IAClB,qFAAqF;IAC9E,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACtB;;;;OAIG;gBACgB,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAO/C,0DAA0D;IACnD,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO;IAU5E,4EAA4E;IAC5E,OAAO,CAAC,UAAU;IAOlB;;;;;;;OAOG;IACI,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO;IAgE/C;;;;;OAKG;IACI,cAAc,IAAI,MAAM,GAAG,SAAS;CAM5C"}
@@ -6,127 +6,129 @@
6
6
  * @module Topology
7
7
  */
8
8
  /**
9
- * * XYParitySearchContext is an internal class for callers that can feed points (without extracting to array structures)
9
+ * `XYParitySearchContext` is an internal class for callers that can feed points (without extracting to array structures)
10
10
  * * Most will be via static methods which handle a specific data source.
11
- * * PolygonOps.classifyPointInPolygon (x,y,points: XAndY[])
12
- * * HalfEdgeGraphSearch.pointInOrOnFaceXY (halfEdgeOnFace, x, y)
11
+ * * PolygonOps.classifyPointInPolygon(x,y,points: XAndY[])
12
+ * * HalfEdgeGraphSearch.pointInOrOnFaceXY(halfEdgeOnFace, x, y)
13
13
  * Use pattern:
14
- * * Caller must be able walk around polygon producing x,y coordinates (possibly transformed from actual polygon)
14
+ * * Caller must be able to walk around polygon producing x,y coordinates (possibly transformed from actual polygon).
15
15
  * * Caller announce edges to tryStartEdge until finding one acceptable to the search.
16
16
  * * Caller then passes additional points up to and including both x0,y0 and x1, y1 of the accepted start edge.
17
17
  * Call sequence is:
18
- * `context = new XYParitySearchContext`
19
- * `repeat { acquire edge (x0,y0) (x1,y1)} until context.tryStartEdge (x0,y0,x1,y1);`
20
- * `for each (x,y) beginning AFTER x1,y1 and ending with (x1,y1) context.advance (x,y)`
21
- * `return context.classifyCounts ();`
18
+ * * `context = new XYParitySearchContext`
19
+ * * `repeat { acquire edge (x0,y0) (x1,y1) } until context.tryStartEdge(x0,y0,x1,y1);`
20
+ * * `for each (x,y) beginning AFTER x1,y1 and ending with (x1,y1) context.advance (x,y)`
21
+ * * `return context.classifyCounts();`
22
22
  */
23
23
  export class XYParitySearchContext {
24
24
  /**
25
25
  * Create a new searcher for specified test point.
26
- * @param xTest x coordinate of test point
27
- * @param yTest y coordinate of test point
26
+ * @param xTest x coordinate of test point.
27
+ * @param yTest y coordinate of test point.
28
28
  */
29
29
  constructor(xTest, yTest) {
30
30
  this.xTest = xTest;
31
31
  this.yTest = yTest;
32
- this.u0 = this.v0 = this.u1 = this.v1 = 0; // Not valid for search -- caller must satisfy tryStartEdge !!!
32
+ this.u0 = this.v0 = this.u1 = this.v1 = 0; // not valid for search; caller must satisfy tryStartEdge
33
33
  this.numLeftCrossing = this.numRightCrossing = 0;
34
34
  this.numHit = 0;
35
35
  }
36
- /**
37
- * test if x,y is a safe first coordinate to start the search.
38
- * * 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.
39
- * @param x
40
- * @param y
41
- */
36
+ /** Test if parity processing can begin with this edge. */
42
37
  tryStartEdge(x0, y0, x1, y1) {
43
38
  if (y0 !== this.yTest) {
44
39
  this.u0 = x0 - this.xTest;
45
40
  this.v0 = y0 - this.yTest;
46
41
  this.u1 = x1 - this.xTest;
47
42
  this.v1 = y1 - this.yTest;
48
- return true;
43
+ return true; // we won't need wraparound logic to process the final edge ending at (x0,y0)
49
44
  }
50
45
  return false;
51
46
  }
52
- /** Return true if parity accumulation proceeded normally.
53
- * Return false if interrupted for exact hit.
47
+ /** Update local coordinates: the current edge becomes the previous edge. */
48
+ updateUV01(u2, v2) {
49
+ this.u0 = this.u1;
50
+ this.v0 = this.v1;
51
+ this.u1 = u2;
52
+ this.v1 = v2;
53
+ return true;
54
+ }
55
+ /**
56
+ * Process the current edge ending at (x2,y2).
57
+ * * Accumulate left/right parity of the test point wrt to the polygon. These counts track the number of polygon crossings
58
+ * of the left and right horizontal rays emanating from the test point. After all edges are processed, if either count is
59
+ * odd/even, the test point is inside/outside the polygon (see [[classifyCounts]]).
60
+ * * Check whether the test point lies on the edge.
61
+ * @returns whether caller should continue processing with the next edge. In particular, `false` if we have an exact hit.
54
62
  */
55
- advance(x, y) {
56
- const u = x - this.xTest;
57
- const v = y - this.yTest;
58
- const p = v * this.v1;
63
+ advance(x2, y2) {
64
+ // In this method we use local u,v coordinates obtained by translating the test point to the origin.
65
+ // This simplifies our computations:
66
+ // * left (right) parity is incremented if the current edge crosses the u-axis at u<0 (u>0)
67
+ // * we have an exact hit if the current edge crosses the u-axis at u=0
68
+ const u2 = x2 - this.xTest;
69
+ const v2 = y2 - this.yTest;
70
+ const p = v2 * this.v1;
59
71
  if (p > 0) {
60
- // The common case -- skittering along above or below the x axis . . .
61
- this.u0 = this.u1;
62
- this.v0 = this.v1;
63
- this.u1 = u;
64
- this.v1 = v;
65
- return true;
72
+ // Current edge does not cross u-axis.
73
+ return this.updateUV01(u2, v2);
66
74
  }
67
75
  if (p < 0) {
68
- // crossing within (u1,v1) to (u,v)
69
- // both v values are nonzero and of opposite sign, so this division is safe . . .
70
- const fraction = -this.v1 / (v - this.v1);
71
- const uCross = this.u1 + fraction * (u - this.u1);
76
+ // Current edge crosses the u-axis at edge parameter 0 < lambda < 1 by the Intermediate Value Theorem.
77
+ // Solve for lambda in 0 = v1 + lambda (v2 - v1), then use it to compute the u-value of the crossing.
78
+ const lambda = -this.v1 / (v2 - this.v1);
79
+ const uCross = this.u1 + lambda * (u2 - this.u1);
72
80
  if (uCross === 0.0) {
73
- this.numHit++;
81
+ this.numHit++; // Current edge crosses at the origin.
74
82
  return false;
75
83
  }
76
84
  if (uCross > 0)
77
85
  this.numRightCrossing++;
78
86
  else
79
87
  this.numLeftCrossing++;
80
- this.u0 = this.u1;
81
- this.v0 = this.v1;
82
- this.u1 = u;
83
- this.v1 = v;
84
- return true;
88
+ return this.updateUV01(u2, v2);
85
89
  }
86
- // hard stuff -- one or more exact hits . . .
87
- if (v === 0.0) {
90
+ // At this point, at least one endpoint of the current edge lies on the u-axis.
91
+ if (v2 === 0.0) {
88
92
  if (this.v1 === 0.0) {
89
- // uh oh -- moving along x axis. Does it pass through xTest:
90
- if (u * this.u1 <= 0.0) {
91
- this.numHit++;
93
+ if (u2 * this.u1 <= 0.0) {
94
+ this.numHit++; // Current edge lies on u-axis and contains the origin.
92
95
  return false;
93
96
  }
94
- // quietly moving along the scan line, both xy and x1y1 to same side of test point ...
95
- // u0 and u1 remain unchanged !!!
96
- this.u1 = u;
97
- this.v1 = v;
97
+ // Current edge lies on the u-axis to one side of the origin.
98
+ // This edge doesn't contribute to parity computations, so advance past it.
99
+ this.u1 = u2;
100
+ this.v1 = v2;
98
101
  return true;
99
102
  }
100
- // just moved onto the scan line ...
101
- this.u0 = this.u1;
102
- this.v0 = this.v1;
103
- this.u1 = u;
104
- this.v1 = v;
105
- return true;
103
+ if (u2 === 0.0) {
104
+ this.numHit++; // Current edge ends at the origin.
105
+ return false;
106
+ }
107
+ // Current edge ends on the u-axis away from the origin.
108
+ return this.updateUV01(u2, v2);
106
109
  }
107
- // fall out with v1 = 0
108
- // both v0 and v are nonzero.
109
- // any along-0 v values that have passed through are on the same side of xTest, so u1 determines crossing
110
- const q = this.v0 * v;
111
- if (this.u1 > 0) {
112
- if (q < 0)
113
- this.numRightCrossing++;
110
+ // At this point, the current edge starts at the u-axis.
111
+ if (this.u1 === 0.0) {
112
+ this.numHit++; // Current edge starts at the origin.
113
+ return false;
114
114
  }
115
- else {
116
- if (q < 0)
115
+ // At this point, the current edge starts on the u-axis away from the origin.
116
+ const q = this.v0 * v2;
117
+ if (q < 0) {
118
+ // The current edge and the previous edge lie on opposite sides of the u-axis, so we have a parity change.
119
+ if (this.u1 > 0)
120
+ this.numRightCrossing++;
121
+ else
117
122
  this.numLeftCrossing++;
118
123
  }
119
- this.u0 = this.u1;
120
- this.v0 = this.v1;
121
- this.u1 = u;
122
- this.v1 = v;
123
- return true;
124
+ // The current edge and the previous edge lie on the same sides of the u-axis, so no parity change.
125
+ return this.updateUV01(u2, v2);
124
126
  }
125
127
  /**
126
128
  * Return classification as ON, IN, or OUT according to hit and crossing counts.
127
- * * Any nonzero hit count is ON
129
+ * * Any nonzero hit count is ON.
128
130
  * * Otherwise IN if left crossing count is odd.
129
- * @return 0 if ON, 1 if IN, -1 if OUT
131
+ * @return 0 if ON, 1 if IN, -1 if OUT.
130
132
  */
131
133
  classifyCounts() {
132
134
  if (this.numHit > 0)
@@ -1 +1 @@
1
- {"version":3,"file":"XYParitySearchContext.js","sourceRoot":"","sources":["../../../src/topology/XYParitySearchContext.ts"],"names":[],"mappings":"AAAA;;;+FAG+F;AAE/F;;GAEG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,qBAAqB;IAUhC;;;;OAIG;IACH,YAAmB,KAAa,EAAE,KAAa;QAC7C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,+DAA+D;QAC1G,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,CAAC;IACD;;;;;OAKG;IACI,YAAY,CAAC,EAAU,EAAE,EAAU,EAAE,EAAU,EAAE,EAAU;QAChE,IAAI,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE;YACrB,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAC1B,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAC1B,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAC1B,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAC1B,OAAO,IAAI,CAAC;SACb;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD;;OAEG;IACI,OAAO,CAAC,CAAS,EAAE,CAAS;QACjC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;QACtB,IAAI,CAAC,GAAG,CAAC,EAAE;YACT,sEAAsE;YACtE,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACZ,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACZ,OAAO,IAAI,CAAC;SACb;QACD,IAAI,CAAC,GAAG,CAAC,EAAE;YACT,mCAAmC;YACnC,iFAAiF;YACjF,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,QAAQ,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;YAClD,IAAI,MAAM,KAAK,GAAG,EAAE;gBAClB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,OAAO,KAAK,CAAC;aACd;YACD,IAAI,MAAM,GAAG,CAAC;gBACZ,IAAI,CAAC,gBAAgB,EAAE,CAAC;;gBAExB,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACZ,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACZ,OAAO,IAAI,CAAC;SACb;QACD,6CAA6C;QAC7C,IAAI,CAAC,KAAK,GAAG,EAAE;YACb,IAAI,IAAI,CAAC,EAAE,KAAK,GAAG,EAAE;gBACnB,6DAA6D;gBAC7D,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE;oBACtB,IAAI,CAAC,MAAM,EAAE,CAAC;oBACd,OAAO,KAAK,CAAC;iBACd;gBACD,sFAAsF;gBACtF,iCAAiC;gBACjC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;gBACZ,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;gBACZ,OAAO,IAAI,CAAC;aACb;YACD,oCAAoC;YACpC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACZ,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACZ,OAAO,IAAI,CAAC;SACb;QACD,uBAAuB;QACvB,6BAA6B;QAC7B,yGAAyG;QACzG,MAAM,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE;YACf,IAAI,CAAC,GAAG,CAAC;gBACP,IAAI,CAAC,gBAAgB,EAAE,CAAC;SAC3B;aAAM;YACL,IAAI,CAAC,GAAG,CAAC;gBACP,IAAI,CAAC,eAAe,EAAE,CAAC;SAC1B;QACD,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QAClB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QAClB,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACZ,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IACD;;;;;OAKG;IACI,cAAc;QACnB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YACjB,OAAO,CAAC,CAAC;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC3C,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;CACF","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\n/** @packageDocumentation\r\n * @module Topology\r\n */\r\n\r\n/**\r\n * * XYParitySearchContext is an internal class for callers that can feed points (without extracting to array structures)\r\n * * Most will be via static methods which handle a specific data source.\r\n * * PolygonOps.classifyPointInPolygon (x,y,points: XAndY[])\r\n * * HalfEdgeGraphSearch.pointInOrOnFaceXY (halfEdgeOnFace, x, y)\r\n * Use pattern:\r\n * * Caller must be able walk around polygon producing x,y coordinates (possibly transformed from actual polygon)\r\n * * Caller announce edges to tryStartEdge until finding one acceptable to the search.\r\n * * Caller then passes additional points up to and including both x0,y0 and x1, y1 of the accepted start edge.\r\n * Call sequence is:\r\n * `context = new XYParitySearchContext`\r\n * `repeat { acquire edge (x0,y0) (x1,y1)} until context.tryStartEdge (x0,y0,x1,y1);`\r\n * `for each (x,y) beginning AFTER x1,y1 and ending with (x1,y1) context.advance (x,y)`\r\n * `return context.classifyCounts ();`\r\n */\r\nexport class XYParitySearchContext {\r\n public xTest: number;\r\n public yTest: number;\r\n public u0: number; // local coordinates of recent point with nonzero v. Usually \"second last point\" but points can be skipped if y1 is zero\r\n public v0: number;\r\n public u1: number; // local coordinates of most recent point\r\n public v1: number;\r\n public numLeftCrossing: number;\r\n public numRightCrossing: number;\r\n public numHit: number;\r\n /**\r\n * Create a new searcher for specified test point.\r\n * @param xTest x coordinate of test point\r\n * @param yTest y coordinate of test point\r\n */\r\n public constructor(xTest: number, yTest: number) {\r\n this.xTest = xTest;\r\n this.yTest = yTest;\r\n this.u0 = this.v0 = this.u1 = this.v1 = 0; // Not valid for search -- caller must satisfy tryStartEdge !!!\r\n this.numLeftCrossing = this.numRightCrossing = 0;\r\n this.numHit = 0;\r\n }\r\n /**\r\n * test if x,y is a safe first coordinate to start the search.\r\n * * 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.\r\n * @param x\r\n * @param y\r\n */\r\n public tryStartEdge(x0: number, y0: number, x1: number, y1: number): boolean {\r\n if (y0 !== this.yTest) {\r\n this.u0 = x0 - this.xTest;\r\n this.v0 = y0 - this.yTest;\r\n this.u1 = x1 - this.xTest;\r\n this.v1 = y1 - this.yTest;\r\n return true;\r\n }\r\n return false;\r\n }\r\n /** Return true if parity accumulation proceeded normally.\r\n * Return false if interrupted for exact hit.\r\n */\r\n public advance(x: number, y: number): boolean {\r\n const u = x - this.xTest;\r\n const v = y - this.yTest;\r\n const p = v * this.v1;\r\n if (p > 0) {\r\n // The common case -- skittering along above or below the x axis . . .\r\n this.u0 = this.u1;\r\n this.v0 = this.v1;\r\n this.u1 = u;\r\n this.v1 = v;\r\n return true;\r\n }\r\n if (p < 0) {\r\n // crossing within (u1,v1) to (u,v)\r\n // both v values are nonzero and of opposite sign, so this division is safe . . .\r\n const fraction = -this.v1 / (v - this.v1);\r\n const uCross = this.u1 + fraction * (u - this.u1);\r\n if (uCross === 0.0) {\r\n this.numHit++;\r\n return false;\r\n }\r\n if (uCross > 0)\r\n this.numRightCrossing++;\r\n else\r\n this.numLeftCrossing++;\r\n this.u0 = this.u1;\r\n this.v0 = this.v1;\r\n this.u1 = u;\r\n this.v1 = v;\r\n return true;\r\n }\r\n // hard stuff -- one or more exact hits . . .\r\n if (v === 0.0) {\r\n if (this.v1 === 0.0) {\r\n // uh oh -- moving along x axis. Does it pass through xTest:\r\n if (u * this.u1 <= 0.0) {\r\n this.numHit++;\r\n return false;\r\n }\r\n // quietly moving along the scan line, both xy and x1y1 to same side of test point ...\r\n // u0 and u1 remain unchanged !!!\r\n this.u1 = u;\r\n this.v1 = v;\r\n return true;\r\n }\r\n // just moved onto the scan line ...\r\n this.u0 = this.u1;\r\n this.v0 = this.v1;\r\n this.u1 = u;\r\n this.v1 = v;\r\n return true;\r\n }\r\n // fall out with v1 = 0\r\n // both v0 and v are nonzero.\r\n // any along-0 v values that have passed through are on the same side of xTest, so u1 determines crossing\r\n const q = this.v0 * v;\r\n if (this.u1 > 0) {\r\n if (q < 0)\r\n this.numRightCrossing++;\r\n } else {\r\n if (q < 0)\r\n this.numLeftCrossing++;\r\n }\r\n this.u0 = this.u1;\r\n this.v0 = this.v1;\r\n this.u1 = u;\r\n this.v1 = v;\r\n return true;\r\n }\r\n /**\r\n * Return classification as ON, IN, or OUT according to hit and crossing counts.\r\n * * Any nonzero hit count is ON\r\n * * Otherwise IN if left crossing count is odd.\r\n * @return 0 if ON, 1 if IN, -1 if OUT\r\n */\r\n public classifyCounts(): number | undefined {\r\n if (this.numHit > 0)\r\n return 0;\r\n const parity = this.numLeftCrossing & 0x01;\r\n return (parity === 1) ? 1 : -1;\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"XYParitySearchContext.js","sourceRoot":"","sources":["../../../src/topology/XYParitySearchContext.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\n/** @packageDocumentation\r\n * @module Topology\r\n */\r\n\r\n/**\r\n * `XYParitySearchContext` is an internal class for callers that can feed points (without extracting to array structures)\r\n * * Most will be via static methods which handle a specific data source.\r\n * * PolygonOps.classifyPointInPolygon(x,y,points: XAndY[])\r\n * * HalfEdgeGraphSearch.pointInOrOnFaceXY(halfEdgeOnFace, x, y)\r\n * Use pattern:\r\n * * Caller must be able to walk around polygon producing x,y coordinates (possibly transformed from actual polygon).\r\n * * Caller announce edges to tryStartEdge until finding one acceptable to the search.\r\n * * Caller then passes additional points up to and including both x0,y0 and x1, y1 of the accepted start edge.\r\n * Call sequence is:\r\n * * `context = new XYParitySearchContext`\r\n * * `repeat { acquire edge (x0,y0) (x1,y1) } until context.tryStartEdge(x0,y0,x1,y1);`\r\n * * `for each (x,y) beginning AFTER x1,y1 and ending with (x1,y1) context.advance (x,y)`\r\n * * `return context.classifyCounts();`\r\n */\r\nexport class XYParitySearchContext {\r\n public xTest: number;\r\n public yTest: number;\r\n /** local x-coordinate of the start of the previous (or earlier) edge */\r\n public u0: number;\r\n /** local y-coordinate of the start of the previous (or earlier) edge */\r\n public v0: number;\r\n /** local x-coordinate of the end of the previous edge (and start of current edge) */\r\n public u1: number;\r\n /** local y-coordinate of the end of the previous edge (and start of current edge) */\r\n public v1: number;\r\n public numLeftCrossing: number;\r\n public numRightCrossing: number;\r\n public numHit: number;\r\n /**\r\n * Create a new searcher for specified test point.\r\n * @param xTest x coordinate of test point.\r\n * @param yTest y coordinate of test point.\r\n */\r\n public constructor(xTest: number, yTest: number) {\r\n this.xTest = xTest;\r\n this.yTest = yTest;\r\n this.u0 = this.v0 = this.u1 = this.v1 = 0; // not valid for search; caller must satisfy tryStartEdge\r\n this.numLeftCrossing = this.numRightCrossing = 0;\r\n this.numHit = 0;\r\n }\r\n /** Test if parity processing can begin with this edge. */\r\n public tryStartEdge(x0: number, y0: number, x1: number, y1: number): boolean {\r\n if (y0 !== this.yTest) {\r\n this.u0 = x0 - this.xTest;\r\n this.v0 = y0 - this.yTest;\r\n this.u1 = x1 - this.xTest;\r\n this.v1 = y1 - this.yTest;\r\n return true; // we won't need wraparound logic to process the final edge ending at (x0,y0)\r\n }\r\n return false;\r\n }\r\n /** Update local coordinates: the current edge becomes the previous edge. */\r\n private updateUV01(u2: number, v2: number) {\r\n this.u0 = this.u1;\r\n this.v0 = this.v1;\r\n this.u1 = u2;\r\n this.v1 = v2;\r\n return true;\r\n }\r\n /**\r\n * Process the current edge ending at (x2,y2).\r\n * * Accumulate left/right parity of the test point wrt to the polygon. These counts track the number of polygon crossings\r\n * of the left and right horizontal rays emanating from the test point. After all edges are processed, if either count is\r\n * odd/even, the test point is inside/outside the polygon (see [[classifyCounts]]).\r\n * * Check whether the test point lies on the edge.\r\n * @returns whether caller should continue processing with the next edge. In particular, `false` if we have an exact hit.\r\n */\r\n public advance(x2: number, y2: number): boolean {\r\n // In this method we use local u,v coordinates obtained by translating the test point to the origin.\r\n // This simplifies our computations:\r\n // * left (right) parity is incremented if the current edge crosses the u-axis at u<0 (u>0)\r\n // * we have an exact hit if the current edge crosses the u-axis at u=0\r\n const u2 = x2 - this.xTest;\r\n const v2 = y2 - this.yTest;\r\n const p = v2 * this.v1;\r\n if (p > 0) {\r\n // Current edge does not cross u-axis.\r\n return this.updateUV01(u2, v2);\r\n }\r\n if (p < 0) {\r\n // Current edge crosses the u-axis at edge parameter 0 < lambda < 1 by the Intermediate Value Theorem.\r\n // Solve for lambda in 0 = v1 + lambda (v2 - v1), then use it to compute the u-value of the crossing.\r\n const lambda = -this.v1 / (v2 - this.v1);\r\n const uCross = this.u1 + lambda * (u2 - this.u1);\r\n if (uCross === 0.0) {\r\n this.numHit++; // Current edge crosses at the origin.\r\n return false;\r\n }\r\n if (uCross > 0)\r\n this.numRightCrossing++;\r\n else\r\n this.numLeftCrossing++;\r\n return this.updateUV01(u2, v2);\r\n }\r\n // At this point, at least one endpoint of the current edge lies on the u-axis.\r\n if (v2 === 0.0) {\r\n if (this.v1 === 0.0) {\r\n if (u2 * this.u1 <= 0.0) {\r\n this.numHit++; // Current edge lies on u-axis and contains the origin.\r\n return false;\r\n }\r\n // Current edge lies on the u-axis to one side of the origin.\r\n // This edge doesn't contribute to parity computations, so advance past it.\r\n this.u1 = u2;\r\n this.v1 = v2;\r\n return true;\r\n }\r\n if (u2 === 0.0) {\r\n this.numHit++; // Current edge ends at the origin.\r\n return false;\r\n }\r\n // Current edge ends on the u-axis away from the origin.\r\n return this.updateUV01(u2, v2);\r\n }\r\n // At this point, the current edge starts at the u-axis.\r\n if (this.u1 === 0.0) {\r\n this.numHit++; // Current edge starts at the origin.\r\n return false;\r\n }\r\n // At this point, the current edge starts on the u-axis away from the origin.\r\n const q = this.v0 * v2;\r\n if (q < 0) {\r\n // The current edge and the previous edge lie on opposite sides of the u-axis, so we have a parity change.\r\n if (this.u1 > 0)\r\n this.numRightCrossing++;\r\n else\r\n this.numLeftCrossing++;\r\n }\r\n // The current edge and the previous edge lie on the same sides of the u-axis, so no parity change.\r\n return this.updateUV01(u2, v2);\r\n }\r\n /**\r\n * Return classification as ON, IN, or OUT according to hit and crossing counts.\r\n * * Any nonzero hit count is ON.\r\n * * Otherwise IN if left crossing count is odd.\r\n * @return 0 if ON, 1 if IN, -1 if OUT.\r\n */\r\n public classifyCounts(): number | undefined {\r\n if (this.numHit > 0)\r\n return 0;\r\n const parity = this.numLeftCrossing & 0x01;\r\n return (parity === 1) ? 1 : -1;\r\n }\r\n}\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itwin/core-geometry",
3
- "version": "4.4.0-dev.15",
3
+ "version": "4.4.0-dev.17",
4
4
  "description": "iTwin.js Core Geometry library",
5
5
  "main": "lib/cjs/core-geometry.js",
6
6
  "module": "lib/esm/core-geometry.js",
@@ -34,11 +34,11 @@
34
34
  "nyc": "^15.1.0",
35
35
  "rimraf": "^3.0.2",
36
36
  "typescript": "~5.0.2",
37
- "@itwin/build-tools": "4.4.0-dev.15"
37
+ "@itwin/build-tools": "4.4.0-dev.17"
38
38
  },
39
39
  "dependencies": {
40
40
  "flatbuffers": "~1.12.0",
41
- "@itwin/core-bentley": "4.4.0-dev.15"
41
+ "@itwin/core-bentley": "4.4.0-dev.17"
42
42
  },
43
43
  "nyc": {
44
44
  "extends": "./node_modules/@itwin/build-tools/.nycrc",