"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RegionOps = exports.RegionBinaryOpType = exports.ConsolidateAdjacentCurvePrimitivesOptions = void 0;
/** @packageDocumentation
 * @module Curve
 */
const core_bentley_1 = require("@itwin/core-bentley");
const Geometry_1 = require("../Geometry");
const FrameBuilder_1 = require("../geometry3d/FrameBuilder");
const GrowableXYZArray_1 = require("../geometry3d/GrowableXYZArray");
const IndexedXYZCollection_1 = require("../geometry3d/IndexedXYZCollection");
const Point3dArrayCarrier_1 = require("../geometry3d/Point3dArrayCarrier");
const Point3dVector3d_1 = require("../geometry3d/Point3dVector3d");
const PolygonOps_1 = require("../geometry3d/PolygonOps");
const PolylineCompressionByEdgeOffset_1 = require("../geometry3d/PolylineCompressionByEdgeOffset");
const Range_1 = require("../geometry3d/Range");
const Ray3d_1 = require("../geometry3d/Ray3d");
const SortablePolygon_1 = require("../geometry3d/SortablePolygon");
const Transform_1 = require("../geometry3d/Transform");
const MomentData_1 = require("../geometry4d/MomentData");
const PolyfaceBuilder_1 = require("../polyface/PolyfaceBuilder");
const Graph_1 = require("../topology/Graph");
const HalfEdgeGraphSearch_1 = require("../topology/HalfEdgeGraphSearch");
const Merging_1 = require("../topology/Merging");
const Triangulation_1 = require("../topology/Triangulation");
const CurveCollection_1 = require("./CurveCollection");
const CurveCurve_1 = require("./CurveCurve");
const CurveOps_1 = require("./CurveOps");
const CurvePrimitive_1 = require("./CurvePrimitive");
const CurveWireMomentsXYZ_1 = require("./CurveWireMomentsXYZ");
const GeometryQuery_1 = require("./GeometryQuery");
const ChainCollectorContext_1 = require("./internalContexts/ChainCollectorContext");
const PolygonOffsetContext_1 = require("./internalContexts/PolygonOffsetContext");
const TransferWithSplitArcs_1 = require("./internalContexts/TransferWithSplitArcs");
const LineSegment3d_1 = require("./LineSegment3d");
const LineString3d_1 = require("./LineString3d");
const Loop_1 = require("./Loop");
const OffsetOptions_1 = require("./OffsetOptions");
const ParityRegion_1 = require("./ParityRegion");
const Path_1 = require("./Path");
const ConsolidateAdjacentPrimitivesContext_1 = require("./Query/ConsolidateAdjacentPrimitivesContext");
const CurveSplitContext_1 = require("./Query/CurveSplitContext");
const InOutTests_1 = require("./Query/InOutTests");
const PlanarSubdivision_1 = require("./Query/PlanarSubdivision");
const RegionMomentsXY_1 = require("./RegionMomentsXY");
const RegionOpsClassificationSweeps_1 = require("./RegionOpsClassificationSweeps");
const UnionRegion_1 = require("./UnionRegion");
/**
 * * Options to control method [[RegionOps.consolidateAdjacentPrimitives]].
 * @public
 */
class ConsolidateAdjacentCurvePrimitivesOptions {
    /** True to consolidate adjacent linear geometry into a single LineString3d. */
    consolidateLinearGeometry = true;
    /** True to consolidate contiguous compatible arcs into a single Arc3d. */
    consolidateCompatibleArcs = true;
    /**
     * True to attempt consolidation of the first and last primitives of a [[Loop]] or physically closed linestring data,
     * allowing location of the seam to change.
     */
    consolidateLoopSeam = false;
    /** Disable LineSegment3d and LineString3d point compression. */
    disableLinearCompression = false;
    /** Tolerance for detecting identical points. */
    duplicatePointTolerance = Geometry_1.Geometry.smallMetricDistance;
    /** Tolerance for removing interior colinear points (if `!disableLinearCompression`). */
    colinearPointTolerance = Geometry_1.Geometry.smallMetricDistance;
}
exports.ConsolidateAdjacentCurvePrimitivesOptions = ConsolidateAdjacentCurvePrimitivesOptions;
/**
 * Enumeration of the binary operation types for Boolean operations.
 * @see [[RegionOps.regionBooleanXY]], [[RegionOps.polygonBooleanXYToLoops]], [[RegionOps.polygonBooleanXYToPolyface]].
 * @public
 */
var RegionBinaryOpType;
(function (RegionBinaryOpType) {
    RegionBinaryOpType[RegionBinaryOpType["Union"] = 0] = "Union";
    RegionBinaryOpType[RegionBinaryOpType["Parity"] = 1] = "Parity";
    RegionBinaryOpType[RegionBinaryOpType["Intersection"] = 2] = "Intersection";
    RegionBinaryOpType[RegionBinaryOpType["AMinusB"] = 3] = "AMinusB";
    RegionBinaryOpType[RegionBinaryOpType["BMinusA"] = 4] = "BMinusA";
})(RegionBinaryOpType || (exports.RegionBinaryOpType = RegionBinaryOpType = {}));
/**
 * Class `RegionOps` has static members for calculations on regions (areas).
 * * Regions are represented by these [[CurveCollection]] subclasses:
 *   * [[Loop]] -- a single loop
 *   * [[ParityRegion]] -- a collection of loops, interpreted by parity rules.
 * The common "One outer loop and many inner loops" is a parity region.
 *   * [[UnionRegion]] -- a collection of `Loop` and `ParityRegion` objects understood as a (probably disjoint) union.
 * * Most of the methods in this class:
 *   * Ignore z-coordinates, so callers should ensure that input geometry has been rotated parallel to the xy-plane.
 *   * Assume consistent Loop orientation: "solid" Loops are counterclockwise; "hole" Loops are clockwise.
 * @public
 */
class RegionOps {
    /**
     * Return moment sums for a loop, parity region, or union region.
     * * The input region should lie in a plane parallel to the xy-plane, as z-coords will be ignored.
     * * If `rawMomentData` is the MomentData returned by computeXYAreaMoments, convert to principal axes and moments with
     * call `principalMomentData = MomentData.inertiaProductsToPrincipalAxes(rawMomentData.origin, rawMomentData.sums);`
     * * `rawMomentData.origin` is the centroid of `region`.
     * * `rawMomentData.sums.weight()` is the signed area of `region`.
     * @param region any [[Loop]], [[ParityRegion]], or [[UnionRegion]].
     */
    static computeXYAreaMoments(region) {
        const handler = new RegionMomentsXY_1.RegionMomentsXY();
        const result = region.dispatchToGeometryHandler(handler);
        if (result instanceof MomentData_1.MomentData) {
            result.shiftOriginAndSumsToCentroidOfSums();
            return result;
        }
        return undefined;
    }
    /**
     * Return an area tolerance for a given xy-range and optional distance tolerance.
     * @param range range of planar region to tolerance.
     * @param distanceTolerance optional absolute distance tolerance.
    */
    static computeXYAreaTolerance(range, distanceTolerance = Geometry_1.Geometry.smallMetricDistance) {
        // ensure the result is nonzero: we never want to report a zero-area loop as a signed-area loop
        if (distanceTolerance === 0)
            return Geometry_1.Geometry.smallFloatingPoint * 10; // observed area 2e-15 computed for a zero-area loop
        // If A = bh and e is distance tolerance, let A' be the region with b and h extended by half the tolerance.
        // Then A' := (b+e/2)(h+e/2) = A + e/2(b+h+e/2), which motivates our area tol = A'-A = e/2(b+h+e/2).
        const halfDistTol = 0.5 * Math.abs(distanceTolerance);
        return halfDistTol * (range.xLength() + range.yLength() + halfDistTol);
    }
    /**
     * Return a (signed) xy area for a region.
     * * The input region should lie in a plane parallel to the xy-plane, as z-coords will be ignored.
     * * For a non-self-intersecting Loop, the returned area is negative if and only if the Loop is oriented clockwise
     * with respect to the positive z-axis.
     * @param region any [[Loop]], [[ParityRegion]], or [[UnionRegion]].
     */
    static computeXYArea(region) {
        const handler = new RegionMomentsXY_1.RegionMomentsXY();
        const result = region.dispatchToGeometryHandler(handler);
        if (result instanceof MomentData_1.MomentData) {
            return result.quantitySum;
        }
        return undefined;
    }
    /**
     * Return MomentData with the sums of wire moments.
     * * The input curve should lie in a plane parallel to the xy-plane, as z-coords will be ignored.
     * * If `rawMomentData` is the MomentData returned by computeXYAreaMoments, convert to principal axes and moments with
     * call `principalMomentData = MomentData.inertiaProductsToPrincipalAxes (rawMomentData.origin, rawMomentData.sums);`
     * * `rawMomentData.origin` is the wire centroid of `curve`.
     * * `rawMomentData.sums.weight()` is the signed length of `curve`.
     * @param curve any [[CurveCollection]] or [[CurvePrimitive]].
     */
    static computeXYZWireMomentSums(curve) {
        const handler = new CurveWireMomentsXYZ_1.CurveWireMomentsXYZ();
        handler.visitLeaves(curve);
        const result = handler.momentData;
        result.shiftOriginAndSumsToCentroidOfSums();
        return result;
    }
    /**
     * Return a [[Ray3d]] with:
     * * `origin` is the centroid of the region,
     * * `direction` is a unit vector perpendicular to the region plane,
     * * `a` is the region area.
     * @param region the region to process. Can lie in any plane.
     * @param result optional pre-allocated result to populate and return.
     */
    static centroidAreaNormal(region, result) {
        const localToWorld = FrameBuilder_1.FrameBuilder.createRightHandedFrame(undefined, region);
        if (!localToWorld)
            return undefined;
        const normal = localToWorld.matrix.columnZ(result?.direction);
        const regionIsXY = normal.isParallelTo(Point3dVector3d_1.Vector3d.unitZ(), true);
        let regionXY = region;
        if (!regionIsXY) { // rotate the region to be parallel to the xy-plane
            regionXY = region.cloneTransformed(localToWorld.inverse());
            if (!regionXY)
                return undefined;
        }
        const momentData = RegionOps.computeXYAreaMoments(regionXY);
        if (!momentData)
            return undefined;
        const centroid = momentData.origin.clone(result?.origin);
        if (!regionIsXY) // rotate centroid back (area is unchanged)
            localToWorld.multiplyPoint3d(centroid, centroid);
        let area = momentData.sums.weight();
        if (area < 0.0) {
            area = -area;
            normal.scale(-1.0, normal);
        }
        if (!result)
            result = Ray3d_1.Ray3d.createCapture(centroid, normal);
        result.a = area;
        return result;
    }
    /**
     * Create loops in the graph.
     * @internal
     */
    static addLoopsToGraph(graph, data, announceIsolatedLoop) {
        if (data instanceof Loop_1.Loop) {
            const points = data.getPackedStrokes();
            if (points)
                this.addLoopsToGraph(graph, points, announceIsolatedLoop);
        }
        else if (data instanceof ParityRegion_1.ParityRegion) {
            for (const child of data.children) {
                const points = child.getPackedStrokes();
                if (points)
                    this.addLoopsToGraph(graph, points, announceIsolatedLoop);
            }
        }
        else if (data instanceof IndexedXYZCollection_1.IndexedXYZCollection) {
            const loopSeed = Triangulation_1.Triangulator.directCreateFaceLoopFromCoordinates(graph, data);
            if (loopSeed !== undefined)
                announceIsolatedLoop(graph, loopSeed);
        }
        else if (Array.isArray(data)) {
            if (data.length > 0) {
                if (Point3dVector3d_1.Point3d.isAnyImmediatePointType(data[0])) {
                    const loopSeed = Triangulation_1.Triangulator.directCreateFaceLoopFromCoordinates(graph, data);
                    if (loopSeed !== undefined)
                        announceIsolatedLoop(graph, loopSeed);
                }
                else if (data[0] instanceof IndexedXYZCollection_1.IndexedXYZCollection) {
                    for (const loop of data) {
                        const loopSeed = Triangulation_1.Triangulator.directCreateFaceLoopFromCoordinates(graph, loop);
                        if (loopSeed !== undefined)
                            announceIsolatedLoop(graph, loopSeed);
                    }
                }
                else {
                    for (const child of data) {
                        if (Array.isArray(child))
                            this.addLoopsToGraph(graph, child, announceIsolatedLoop);
                    }
                }
            }
        }
    }
    /**
     * Add multiple loops to a graph.
     * * Apply edgeTag and mask to each edge.
     * @internal
     */
    static addLoopsWithEdgeTagToGraph(graph, data, mask, edgeTag) {
        const loopSeeds = [];
        this.addLoopsToGraph(graph, data, (_graph, seed) => {
            if (seed) {
                loopSeeds.push(seed);
                seed.setMaskAndEdgeTagAroundFace(mask, edgeTag, true);
            }
        });
        if (loopSeeds.length > 0)
            return loopSeeds;
        return undefined;
    }
    /**
     * Given a graph just produced by booleans, convert to a polyface
     * * "just produced" implies exterior face markup.
     * @param graph
     * @param triangulate
     */
    static finishGraphToPolyface(graph, triangulate) {
        if (graph) {
            if (triangulate) {
                Triangulation_1.Triangulator.triangulateAllPositiveAreaFaces(graph);
                Triangulation_1.Triangulator.flipTriangles(graph);
            }
            return PolyfaceBuilder_1.PolyfaceBuilder.graphToPolyface(graph);
        }
        return undefined;
    }
    /**
     * Return a polyface containing the area intersection of two XY regions.
     * * Within each region, in and out is determined by parity rules.
     *   * Any face that is an odd number of crossings from the far outside is IN
     *   * Any face that is an even number of crossings from the far outside is OUT
     * @param loopsA first set of loops
     * @param loopsB second set of loops
     * @param triangulate whether to triangulate the result
     */
    static polygonXYAreaIntersectLoopsToPolyface(loopsA, loopsB, triangulate = false) {
        const graph = RegionOpsClassificationSweeps_1.RegionOpsFaceToFaceSearch.doPolygonBoolean(loopsA, loopsB, (inA, inB) => (inA && inB), this._graphCheckPointFunction);
        return this.finishGraphToPolyface(graph, triangulate);
    }
    /**
     * Return a polyface containing the area union of two XY regions.
     * * Within each region, in and out is determined by parity rules.
     *   * Any face that is an odd number of crossings from the far outside is IN
     *   * Any face that is an even number of crossings from the far outside is OUT
     * @param loopsA first set of loops
     * @param loopsB second set of loops
     * @param triangulate whether to triangulate the result
     */
    static polygonXYAreaUnionLoopsToPolyface(loopsA, loopsB, triangulate = false) {
        const graph = RegionOpsClassificationSweeps_1.RegionOpsFaceToFaceSearch.doPolygonBoolean(loopsA, loopsB, (inA, inB) => (inA || inB), this._graphCheckPointFunction);
        return this.finishGraphToPolyface(graph, triangulate);
    }
    /**
     * Return a polyface containing the area difference of two XY regions.
     * * Within each region, in and out is determined by parity rules.
     *   * Any face that is an odd number of crossings from the far outside is IN
     *   * Any face that is an even number of crossings from the far outside is OUT
     * @param loopsA first set of loops
     * @param loopsB second set of loops
     * @param triangulate whether to triangulate the result
     */
    static polygonXYAreaDifferenceLoopsToPolyface(loopsA, loopsB, triangulate = false) {
        const graph = RegionOpsClassificationSweeps_1.RegionOpsFaceToFaceSearch.doPolygonBoolean(loopsA, loopsB, (inA, inB) => (inA && !inB), this._graphCheckPointFunction);
        return this.finishGraphToPolyface(graph, triangulate);
    }
    /**
     * Return the region's simplest representation by stripping redundant parent(s).
     * * No Boolean operations are performed.
     * @param region input region (unchanged). Assumed to have at least one child.
     * @returns
     * * For a [[UnionRegion]] with exactly one child, return the child if it is a [[Loop]],
     * or if it is a [[ParityRegion]] with multiple children; otherwise return the `ParityRegion`'s `Loop`.
     * * For a `ParityRegion` with exactly one child, return the `Loop`.
     * * All other inputs returned unchanged.
     * @see [[simplifyRegion]]
     */
    static simplifyRegionType(region) {
        if (region instanceof UnionRegion_1.UnionRegion) {
            if (region.children.length === 1)
                return this.simplifyRegionType(region.children[0]);
        }
        else if (region instanceof ParityRegion_1.ParityRegion) {
            if (region.children.length === 1)
                return region.children[0];
        }
        return region;
    }
    /**
     * Simplify the region's parent/child hierarchy in place:
     * * Regions with exactly one child are simplified as per [[simplifyRegionType]].
     * * Regions without children are removed.
     * * No Boolean operations are performed.
     * @param region region to simplify in place
     * @returns reference to the updated input region, or `undefined` if no children.
     * @see [[simplifyRegionType]]
     */
    static simplifyRegion(region) {
        if (region instanceof Loop_1.Loop)
            return region.children.length > 0 ? region : undefined;
        // remove childless Parity/UnionRegion
        for (let i = 0; i < region.children.length; ++i) {
            const child = region.children[i];
            const newChild = this.simplifyRegion(child);
            if (!newChild) {
                region.children.splice(i--, 1);
            }
            else if (newChild !== child) {
                (0, core_bentley_1.assert)(!(newChild instanceof UnionRegion_1.UnionRegion));
                region.children.splice(i--, 1, newChild);
            }
        }
        if (region.children.length === 0)
            return undefined;
        // remove redundant Parity/UnionRegion parent
        if (region.children.length === 1)
            return region.children.splice(0, 1)[0];
        return region;
    }
    /**
     * Helper method to sample a z-coordinate from the input region(s).
     * * The assumption is that the input is horizontal.
     */
    static getZCoordinate(xyRegion) {
        const localToWorld = FrameBuilder_1.FrameBuilder.createRightHandedFrame(undefined, xyRegion);
        return localToWorld ? localToWorld.origin.z : 0;
    }
    /**
     * Return areas defined by a boolean operation.
     * @note For best results, input regions should have correctly oriented loops. See [[sortOuterAndHoleLoopsXY]].
     * @note A common use case of this method is to split (a region with) overlapping loops into a `UnionRegion` with
     * adjacent `Loop`s: `regionOut = RegionOps.regionBooleanXY(regionIn, undefined, RegionBinaryOpType.Union)`.
     * @note The Union operation does not currently attempt to return a region with minimal loops. This may change with
     * future development.
     * @param loopsA first set of loops (treated as a union)
     * @param loopsB second set of loops (treated as a union)
     * @param operation indicates Union, Intersection, Parity, AMinusB, or BMinusA
     * @param mergeToleranceOrOptions absolute distance tolerance for merging loops, or multiple options settings. Default value is [[Geometry.smallMetricDistance]].
     * @returns a region resulting from merging input loops and the boolean operation.
     */
    static regionBooleanXY(loopsA, loopsB, operation, mergeToleranceOrOptions = Geometry_1.Geometry.smallMetricDistance) {
        let mergeTolerance;
        let simplifyUnion;
        if (typeof mergeToleranceOrOptions === "number") {
            mergeTolerance = mergeToleranceOrOptions;
            simplifyUnion = false;
        }
        else {
            mergeTolerance = mergeToleranceOrOptions.mergeTolerance ?? Geometry_1.Geometry.smallMetricDistance;
            simplifyUnion = mergeToleranceOrOptions.simplifyUnion ?? false;
        }
        let result = UnionRegion_1.UnionRegion.create();
        const context = RegionOpsClassificationSweeps_1.RegionBooleanContext.create(RegionOpsClassificationSweeps_1.RegionGroupOpType.Union, RegionOpsClassificationSweeps_1.RegionGroupOpType.Union);
        context.addMembers(loopsA, loopsB);
        context.annotateAndMergeCurvesInGraph(mergeTolerance);
        const bridgeMask = Graph_1.HalfEdgeMask.BRIDGE_EDGE;
        const visitMask = context.graph.grabMask(false);
        const range = context.groupA.range().union(context.groupB.range());
        const areaTol = this.computeXYAreaTolerance(range, mergeTolerance);
        const z = RegionOps.getZCoordinate(operation === RegionBinaryOpType.BMinusA ? loopsB : loopsA);
        const options = { compress: true, closureTol: mergeTolerance, bridgeMask, visitMask, z };
        context.runClassificationSweep(operation, (_graph, face, faceType, area) => {
            // ignore danglers and null faces, but not 2-edge "banana" faces with nonzero area
            if (face.countEdgesAroundFace() < 2)
                return;
            if (Math.abs(area) < areaTol)
                return;
            if (faceType === 1) {
                const loopOrParityRegion = PlanarSubdivision_1.PlanarSubdivision.createLoopOrParityRegionInFace(face, options);
                if (loopOrParityRegion)
                    result.tryAddChild(loopOrParityRegion);
            }
        });
        context.graph.dropMask(visitMask);
        if (simplifyUnion && operation === RegionBinaryOpType.Union) {
            const signedLoops = RegionOps.constructAllXYRegionLoops(result);
            if (signedLoops) {
                const outerLoops = signedLoops.map((component) => component.negativeAreaLoops).flat();
                if (outerLoops.length > 0)
                    result = UnionRegion_1.UnionRegion.create(...outerLoops);
            }
        }
        return this.simplifyRegion(result);
    }
    /**
     * Return a polyface whose facets are a boolean operation between the input regions.
     * * Each of the two inputs is an array of multiple loops or parity regions.
     *   * Within each of these input arrays, the various entries (loop or set of loops) are interpreted as a union.
     * * In each "array of loops and parity regions", each entry inputA[i] or inputB[i] is one of:
     *    * A simple loop, e.g. array of Point3d.
     *    * Several simple loops, each of which is an array of Point3d.
     * @param inputA first set of loops
     * @param operation indicates Union, Intersection, Parity, AMinusB, or BMinusA
     * @param inputB second set of loops
     * @param triangulate whether to triangulate the result
     */
    static polygonBooleanXYToPolyface(inputA, operation, inputB, triangulate = false) {
        const graph = RegionOpsClassificationSweeps_1.RegionOpsFaceToFaceSearch.doBinaryBooleanBetweenMultiLoopInputs(inputA, RegionOpsClassificationSweeps_1.RegionGroupOpType.Union, operation, inputB, RegionOpsClassificationSweeps_1.RegionGroupOpType.Union, true);
        return this.finishGraphToPolyface(graph, triangulate);
    }
    /**
     * Return loops of linestrings around areas of a boolean operation between the input regions.
     * * Each of the two inputs is an array of multiple loops or parity regions.
     *   * Within each of these input arrays, the various entries (loop or set of loops) are interpreted as a union.
     * * In each "array of loops and parity regions", each entry inputA[i] or inputB[i] is one of:
     *    * A simple loop, e.g. array of Point3d.
     *    * Several simple loops, each of which is an array of Point3d.
     * @param inputA first set of loops
     * @param operation indicates Union, Intersection, Parity, AMinusB, or BMinusA
     * @param inputB second set of loops
     */
    static polygonBooleanXYToLoops(inputA, operation, inputB) {
        const graph = RegionOpsClassificationSweeps_1.RegionOpsFaceToFaceSearch.doBinaryBooleanBetweenMultiLoopInputs(inputA, RegionOpsClassificationSweeps_1.RegionGroupOpType.Union, operation, inputB, RegionOpsClassificationSweeps_1.RegionGroupOpType.Union, true);
        if (!graph)
            return undefined;
        const loopEdges = HalfEdgeGraphSearch_1.HalfEdgeGraphSearch.collectExtendedBoundaryLoopsInGraph(graph, Graph_1.HalfEdgeMask.EXTERIOR);
        const allLoops = [];
        for (const graphLoop of loopEdges) {
            const points = new GrowableXYZArray_1.GrowableXYZArray();
            for (const edge of graphLoop)
                points.pushXYZ(edge.x, edge.y, edge.z);
            points.pushWrap(1);
            const loop = Loop_1.Loop.create();
            loop.tryAddChild(LineString3d_1.LineString3d.createCapture(points));
            allLoops.push(loop);
        }
        return RegionOps.sortOuterAndHoleLoopsXY(allLoops);
    }
    /**
     * Construct a wire that is offset from the given polyline or polygon.
     * * This is a simple wire offset, not an area offset.
     * * Since z-coordinates are ignored, for best results the input points should lie in (a plane parallel to)
     * the xy-plane.
     * * The construction algorithm attempts to eliminate some self-intersections within the offsets, but does not
     * guarantee a simple area offset.
     * @param points a single loop or path
     * @param wrap true to include wraparound
     * @param offsetDistanceOrOptions offset distance (positive to left of curve, negative to right) or JointOptions
     * object.
     */
    static constructPolygonWireXYOffset(points, wrap, offsetDistanceOrOptions) {
        const context = new PolygonOffsetContext_1.PolygonWireOffsetContext();
        return context.constructPolygonWireXYOffset(points, wrap, offsetDistanceOrOptions);
    }
    /**
     * Construct curves that are offset from a Path or Loop as viewed in xy-plane (ignoring z).
     * * The construction will remove "some" local effects of features smaller than the offset distance, but will
     * not detect self intersection among widely separated edges.
     * * Visualization can be found at https://www.itwinjs.org/sandbox/SaeedTorabi/Offset
     * @param curves base curves.
     * @param offsetDistanceOrOptions offset distance (positive to left of curve, negative to right) or options object.
     */
    static constructCurveXYOffset(curves, offsetDistanceOrOptions) {
        const offsetOptions = OffsetOptions_1.OffsetOptions.create(offsetDistanceOrOptions);
        return CurveOps_1.CurveOps.constructCurveXYOffset(curves, offsetOptions);
    }
    /**
     * Test if point (x,y) is IN, OUT or ON a region.
     * @return (1) for in, (-1) for OUT, (0) for ON
     * @param curves input region
     * @param x x coordinate of point to test
     * @param y y coordinate of point to test
     */
    static testPointInOnOutRegionXY(curves, x, y) {
        return InOutTests_1.PointInOnOutContext.testPointInOnOutRegionXY(curves, x, y);
    }
    /**
     * Create curve collection of subtype determined by gaps between the input curves.
     * * If (a) wrap is requested and (b) all curves connect head-to-tail (including wraparound), assemble as a `loop`.
     * * If all curves connect head-to-tail except for closure, return a `Path`.
     * * If there are internal gaps, return a `BagOfCurves`
     * * If input array has zero length, return undefined.
     * @param curves input curves
     * @param wrap whether to create a Loop (true) or Path (false) if maximum gap is minimal
     * @param consolidateAdjacentPrimitives whether to simplify the result by calling [[consolidateAdjacentPrimitives]]
     */
    static createLoopPathOrBagOfCurves(curves, wrap = true, consolidateAdjacentPrimitives = false) {
        const n = curves.length;
        if (n === 0)
            return undefined;
        let maxGap = 0.0;
        let isPath = false;
        if (wrap)
            maxGap = Geometry_1.Geometry.maxXY(maxGap, curves[0].startPoint().distance(curves[n - 1].endPoint()));
        for (let i = 0; i + 1 < n; i++)
            maxGap = Geometry_1.Geometry.maxXY(maxGap, curves[i].endPoint().distance(curves[i + 1].startPoint()));
        let collection;
        if (Geometry_1.Geometry.isSmallMetricDistance(maxGap)) {
            collection = wrap ? Loop_1.Loop.create() : Path_1.Path.create();
            isPath = true;
        }
        else {
            collection = CurveCollection_1.BagOfCurves.create();
        }
        for (const c of curves)
            collection.tryAddChild(c);
        if (isPath && consolidateAdjacentPrimitives)
            RegionOps.consolidateAdjacentPrimitives(collection);
        return collection;
    }
    static _graphCheckPointFunction;
    /**
     * Announce Checkpoint function for use during booleans
     * @internal
     */
    static setCheckPointFunction(f) {
        this._graphCheckPointFunction = f;
    }
    /**
     * Find all intersections among curves in `curvesToCut` and `cutterCurves` and return fragments of `curvesToCut`.
     * * For a `Loop`, `ParityRegion`, or `UnionRegion` in `curvesToCut`:
     *    * if it is never cut by any `cutter` curve, it will be left unchanged.
     *    * if cut, the input is downgraded to a set of `Path` curves joining at the cut points.
     * * All cutting is "as viewed in the xy plane"
     * @param curvesToCut input curves to be fragmented at intersections with `cutterCurves`
     * @param cutterCurves input curves to intersect with `curvesToCut`
     */
    static cloneCurvesWithXYSplits(curvesToCut, cutterCurves) {
        return CurveSplitContext_1.CurveSplitContext.cloneCurvesWithXYSplits(curvesToCut, cutterCurves);
    }
    /**
     * Create paths assembled from many curves.
     * * Assemble paths from consecutive curves NOT separated by either gaps or the split markup set by
     * [[cloneCurvesWithXYSplits]].
     * * Return simplest form -- single primitive, single path, or bag of curves.
     */
    static splitToPathsBetweenBreaks(source, makeClones) {
        if (source === undefined)
            return undefined;
        if (source instanceof CurvePrimitive_1.CurvePrimitive)
            return source;
        // source is a collection .  ..
        const primitives = source.collectCurvePrimitives();
        const chainCollector = new ChainCollectorContext_1.ChainCollectorContext(makeClones);
        for (const primitive of primitives) {
            chainCollector.announceCurvePrimitive(primitive);
        }
        return chainCollector.grabResult();
    }
    /**
     * Restructure curve fragments as Paths and Loops, and construct xy-offsets of the chains.
     * * If the inputs do not form Loop(s), the classification of offsets is suspect.
     * * For best offset results, the inputs should be parallel to the xy-plane.
     * @param fragments fragments to be chained and offset
     * @param offsetDistance offset distance, applied to both sides of each fragment to produce inside and outside xy-offset curves.
     * @param gapTolerance distance to be treated as "effectively zero" when assembling fragments head-to-tail
     * @returns object with named chains, insideOffsets, outsideOffsets
     */
    static collectInsideAndOutsideOffsets(fragments, offsetDistance, gapTolerance) {
        return CurveOps_1.CurveOps.collectInsideAndOutsideXYOffsets(fragments, offsetDistance, gapTolerance);
    }
    /**
     * Restructure curve fragments as Paths and Loops.
     * @param fragments fragments to be chained
     * @param gapTolerance distance to be treated as "effectively zero" when assembling fragments head-to-tail
     * @returns chains, possibly wrapped in a [[BagOfCurves]].
     */
    static collectChains(fragments, gapTolerance = Geometry_1.Geometry.smallMetricDistance) {
        return CurveOps_1.CurveOps.collectChains(fragments, gapTolerance);
    }
    /**
     * Find all intersections among curves in `curvesToCut` against the boundaries of `region` and return fragments
     * of `curvesToCut`.
     * * Break `curvesToCut` into parts inside, outside, and coincident.
     * @returns output object with all fragments split among `insideParts`, `outsideParts`, and `coincidentParts`
     */
    static splitPathsByRegionInOnOutXY(curvesToCut, region) {
        const result = { insideParts: [], outsideParts: [], coincidentParts: [] };
        const pathWithIntersectionMarkup = RegionOps.cloneCurvesWithXYSplits(curvesToCut, region);
        const splitPaths = RegionOps.splitToPathsBetweenBreaks(pathWithIntersectionMarkup, true);
        if (splitPaths instanceof CurveCollection_1.CurveCollection) {
            for (const child of splitPaths.children) {
                const pointOnChild = CurveCollection_1.CurveCollection.createCurveLocationDetailOnAnyCurvePrimitive(child);
                if (pointOnChild) {
                    const inOnOut = RegionOps.testPointInOnOutRegionXY(region, pointOnChild.point.x, pointOnChild.point.y);
                    pushToInOnOutArrays(child, inOnOut, result.outsideParts, result.coincidentParts, result.insideParts);
                }
            }
        }
        else if (splitPaths instanceof CurvePrimitive_1.CurvePrimitive) {
            const pointOnChild = CurveCollection_1.CurveCollection.createCurveLocationDetailOnAnyCurvePrimitive(splitPaths);
            if (pointOnChild) {
                const inOnOut = RegionOps.testPointInOnOutRegionXY(region, pointOnChild.point.x, pointOnChild.point.y);
                pushToInOnOutArrays(splitPaths, inOnOut, result.outsideParts, result.coincidentParts, result.insideParts);
            }
        }
        return result;
    }
    /**
     * If `data` is one of several forms of a rectangle, return its edge Transform.
     * * Points are considered a rectangle if, within the first 4 points:
     *     * vectors from 0 to 1 and 0 to 3 are perpendicular and have a non-zero cross product
     *     * vectors from 0 to 3 and 1 to 2 are the same
     * @param data points in one of several formats:
     *   * LineString
     *   * Loop containing rectangle content
     *   * Path containing rectangle content
     *   * Array of Point3d[]
     *   * IndexedXYZCollection
     * @param requireClosurePoint whether to require a 5th point equal to the 1st point.
     * @returns Transform with origin at one corner, x and y columns extending along two adjacent sides, and unit
     * normal in z column. If not a rectangle, return undefined.
     */
    static rectangleEdgeTransform(data, requireClosurePoint = true) {
        if (data instanceof LineString3d_1.LineString3d) {
            return this.rectangleEdgeTransform(data.packedPoints);
        }
        else if (data instanceof IndexedXYZCollection_1.IndexedXYZCollection) {
            let dataToUse;
            if (requireClosurePoint && data.length === 5) {
                if (!Geometry_1.Geometry.isSmallMetricDistance(data.distanceIndexIndex(0, 4)))
                    return undefined;
                dataToUse = data;
            }
            else if (!requireClosurePoint && data.length === 4) {
                dataToUse = data;
            }
            else if (data.length < (requireClosurePoint ? 5 : 4)) {
                return undefined;
            }
            else {
                dataToUse = GrowableXYZArray_1.GrowableXYZArray.create(data);
                PolylineCompressionByEdgeOffset_1.PolylineCompressionContext.compressInPlaceByShortEdgeLength(dataToUse, Geometry_1.Geometry.smallMetricDistance);
                if (dataToUse.length < (requireClosurePoint ? 5 : 4))
                    return undefined;
            }
            const vector01 = dataToUse.vectorIndexIndex(0, 1);
            const vector03 = dataToUse.vectorIndexIndex(0, 3);
            const vector12 = dataToUse.vectorIndexIndex(1, 2);
            const normalVector = vector01.crossProduct(vector03);
            if (normalVector.normalizeInPlace()
                && vector12.isAlmostEqual(vector03)
                && vector01.isPerpendicularTo(vector03)) {
                return Transform_1.Transform.createOriginAndMatrixColumns(dataToUse.getPoint3dAtUncheckedPointIndex(0), vector01, vector03, normalVector);
            }
        }
        else if (Array.isArray(data)) {
            return this.rectangleEdgeTransform(new Point3dArrayCarrier_1.Point3dArrayCarrier(data), requireClosurePoint);
        }
        else if (data instanceof Loop_1.Loop && data.children.length === 1 && data.children[0] instanceof LineString3d_1.LineString3d) {
            return this.rectangleEdgeTransform(data.children[0].packedPoints, true);
        }
        else if (data instanceof Path_1.Path && data.children.length === 1 && data.children[0] instanceof LineString3d_1.LineString3d) {
            return this.rectangleEdgeTransform(data.children[0].packedPoints, requireClosurePoint);
        }
        else if (data instanceof CurveCollection_1.CurveChain) {
            if (!data.checkForNonLinearPrimitives()) {
                // const linestring = LineString3d.create();
                const strokes = data.getPackedStrokes();
                if (strokes) {
                    return this.rectangleEdgeTransform(strokes);
                }
            }
        }
        return undefined;
    }
    /**
     * Look for and simplify:
     * * Contiguous `LineSegment3d` and `LineString3d` objects.
     *   * collect all points
     *   * eliminate duplicated points
     *   * eliminate points colinear with surrounding points
     *   * contiguous concentric circular or elliptic arcs
     *   * combine angular ranges
     * * This function can be used to compress adjacent LineSegment3ds into a LineString3d
     * @param curves Path or loop (or larger collection containing paths and loops) to be simplified
     * @param options options for tolerance and selective simplification.
     */
    static consolidateAdjacentPrimitives(curves, options) {
        const context = new ConsolidateAdjacentPrimitivesContext_1.ConsolidateAdjacentCurvePrimitivesContext(options);
        curves.dispatchToGeometryHandler(context);
    }
    /**
     * Reverse and reorder loops in the xy-plane for consistent orientation and containment.
     * @param loops multiple loops in any order and orientation, z-coordinates ignored.
     * * For best results, all overlaps should be containments, i.e., loop boundaries can touch, but should not cross.
     * @returns a region that captures the input pointers. This region is a:
     * * [[Loop]] if there is exactly one input loop. It is oriented counterclockwise.
     * * [[ParityRegion]] if input consists of exactly one outer loop with at least one hole loop.
     * Its first child is an outer loop oriented counterclockwise; all subsequent children are holes oriented
     * clockwise.
     * * [[UnionRegion]] if any other input configuration. Its children are individually ordered/oriented as in
     * the above cases.
     * @see [[PolygonOps.sortOuterAndHoleLoopsXY]]
     */
    static sortOuterAndHoleLoopsXY(loops) {
        const loopAndArea = [];
        for (const candidate of loops) {
            if (candidate instanceof Loop_1.Loop)
                SortablePolygon_1.SortablePolygon.pushLoop(loopAndArea, candidate);
            else if (candidate instanceof IndexedXYZCollection_1.IndexedXYZCollection) {
                const loop = Loop_1.Loop.createPolygon(candidate);
                SortablePolygon_1.SortablePolygon.pushLoop(loopAndArea, loop);
            }
        }
        return SortablePolygon_1.SortablePolygon.sortAsAnyRegion(loopAndArea);
    }
    /**
     * Collect inputs that are nominally closed: regions, and physically closed curves.
     * * Physically closed input curves are each returned wrapped in a Loop to facilitate xy-algorithms,
     * but outside this limited context, these Loops only makes sense if they are planar.
    */
    static collectRegionsAndClosedPrimitives(curves, tolerance = Geometry_1.Geometry.smallMetricDistance) {
        const regions = [];
        if (!Array.isArray(curves))
            curves = [curves];
        for (const curve of curves) {
            if (curve instanceof Loop_1.Loop || curve instanceof ParityRegion_1.ParityRegion || curve instanceof UnionRegion_1.UnionRegion) {
                regions.push(curve);
            }
            else if (curve instanceof Path_1.Path) {
                if (curve.isPhysicallyClosedCurve(tolerance))
                    regions.push(Loop_1.Loop.create(...curve.children));
            }
            else if (curve instanceof CurvePrimitive_1.CurvePrimitive) {
                if (curve.isPhysicallyClosedCurve(tolerance))
                    regions.push(Loop_1.Loop.create(curve));
            }
        }
        return regions;
    }
    /**
     * Find all xy-areas bounded by the unstructured, possibly intersecting curves.
     * * For best results, input curves should be parallel to the xy-plane, as z-coordinates are ignored.
     * * "Holes" implied/bounded by inputs are _not_ preserved/discovered in output; in particular [[ParityRegion]]
     * hole loops are treated like any other positive area loops.
     * * A common use case of this method is to assemble the bounding negative-area "exterior" loop for each connected
     * component of input curves. Passing `addBridges = true` decreases the number of connected components for nested
     * input [[Loop]]s, and thus increases the likelihood of returning exactly one exterior loop. (This is why the
     * default value for `addBridges` is `true`.)
     * @param curvesAndRegions Any collection of curves. Each [[AnyRegion]] contributes its children _stripped of
     * parity context_.
     * @param tolerance optional distance tolerance for coincidence. Default is [[Geometry.smallMetricDistance]].
     * @param addBridges whether to add line segments to connect nested input [[Loop]]s (default is `true`). When `false`,
     * no line segments are added to the input curves, but the number of output components may be greater than expected.
     * @returns array of [[SignedLoops]], each entry of which describes the areas bounded by a single connected component:
     *    * `positiveAreaLoops` contains "interior" loops, _including holes in ParityRegion input_. These loops have
     * positive area and counterclockwise orientation.
     *    * `negativeAreaLoops` contains (probably just one) "exterior" loop which is ordered clockwise.
     *    * `slivers` contains sliver loops that have zero area, such as appear between coincident curves.
     *    * `edges` contains a [[LoopCurveLoopCurve]] object for each component edge, collecting both loops adjacent
     * to the edge and a constituent curve in each.
     */
    static constructAllXYRegionLoops(curvesAndRegions, tolerance = Geometry_1.Geometry.smallMetricDistance, addBridges = true) {
        let primitives = RegionOps.collectCurvePrimitives(curvesAndRegions, undefined, true, true);
        primitives = TransferWithSplitArcs_1.TransferWithSplitArcs.clone(CurveCollection_1.BagOfCurves.create(...primitives)).children;
        const range = this.curveArrayRange(primitives);
        const areaTol = this.computeXYAreaTolerance(range, tolerance);
        if (addBridges) { // generate a temp graph to extract its bridge edges
            const context = RegionOpsClassificationSweeps_1.RegionBooleanContext.create(RegionOpsClassificationSweeps_1.RegionGroupOpType.Union, RegionOpsClassificationSweeps_1.RegionGroupOpType.Union);
            const regions = this.collectRegionsAndClosedPrimitives(curvesAndRegions, tolerance);
            if (regions.length > 0) {
                context.addMembers(regions, undefined);
                context.annotateAndMergeCurvesInGraph(tolerance);
                context.graph.announceEdges((_graph, edge) => {
                    if (edge.isMaskSet(Graph_1.HalfEdgeMask.BRIDGE_EDGE))
                        primitives.push(LineSegment3d_1.LineSegment3d.create(edge.getPoint3d(), edge.faceSuccessor.getPoint3d()));
                    return true;
                });
            }
        }
        const intersections = CurveCurve_1.CurveCurve.allIntersectionsAmongPrimitivesXY(primitives, tolerance);
        const graph = PlanarSubdivision_1.PlanarSubdivision.assembleHalfEdgeGraph(primitives, intersections, tolerance);
        return PlanarSubdivision_1.PlanarSubdivision.collectSignedLoopSetsInHalfEdgeGraph(graph, areaTol);
    }
    /**
     * Collect all `CurvePrimitives` in loosely typed input.
     * * Always recurses into primitives within explicit collections (Path, Loop, ParityRegion, UnionRegion).
     * * Optionally recurses into hidden primitives if `smallestPossiblePrimitives` is true.
     * @param candidates input curves
     * @param collectorArray optional pre-defined output array. If defined, it is NOT cleared: primitives are appended.
     * @param smallestPossiblePrimitives if true, recurse into the children of a [[CurveChainWithDistanceIndex]]. If
     * false, push the [[CurveChainWithDistanceIndex]] instead.
     * @param explodeLinestrings if true, push a [[LineSegment3d]] for each segment of a [[LineString3d]]. If false,
     * push the [[LineString3d]] instead.
     */
    static collectCurvePrimitives(candidates, collectorArray, smallestPossiblePrimitives = false, explodeLinestrings = false) {
        const results = collectorArray === undefined ? [] : collectorArray;
        if (candidates instanceof CurvePrimitive_1.CurvePrimitive) {
            candidates.collectCurvePrimitives(results, smallestPossiblePrimitives, explodeLinestrings);
        }
        else if (candidates instanceof CurveCollection_1.CurveCollection) {
            candidates.collectCurvePrimitives(results, smallestPossiblePrimitives, explodeLinestrings);
        }
        else if (Array.isArray(candidates)) {
            for (const c of candidates) {
                this.collectCurvePrimitives(c, results, smallestPossiblePrimitives, explodeLinestrings);
            }
        }
        return results;
    }
    /**
     * Copy primitive pointers from candidates to result array, replacing each [[LineString3d]] by newly constructed
     * instances of [[LineSegment3d]].
     * @param candidates input curves
     * @return copied (captured) inputs except for the linestrings, which are exploded
     */
    static expandLineStrings(candidates) {
        const result = [];
        for (const c of candidates) {
            if (c instanceof LineString3d_1.LineString3d) {
                for (let i = 0; i + 1 < c.packedPoints.length; i++) {
                    const q = c.getIndexedSegment(i);
                    if (q !== undefined)
                        result.push(q);
                }
            }
            else {
                result.push(c);
            }
        }
        return result;
    }
    /**
     * Return the overall range of given curves.
     * @param data candidate curves
     * @param worldToLocal transform to apply to data before computing its range
     */
    static curveArrayRange(data, worldToLocal) {
        const range = Range_1.Range3d.create();
        if (data instanceof GeometryQuery_1.GeometryQuery)
            data.extendRange(range, worldToLocal);
        else if (Array.isArray(data)) {
            for (const c of data) {
                if (c instanceof GeometryQuery_1.GeometryQuery)
                    c.extendRange(range, worldToLocal);
                else if (c instanceof Point3dVector3d_1.Point3d)
                    range.extendPoint(c, worldToLocal);
                else if (c instanceof GrowableXYZArray_1.GrowableXYZArray)
                    range.extendRange(c.getRange(worldToLocal));
                else if (Array.isArray(c))
                    range.extendRange(this.curveArrayRange(c, worldToLocal));
            }
        }
        return range;
    }
    /**
     * Triangulate a stroked Loop or ParityRegion and return the graph.
     * @param polygons polygons obtained by stroking a Loop or ParityRegion, z-coordinates ignored.
     * @returns triangulated graph
     */
    static triangulateStrokedRegionComponent(polygons) {
        let graph;
        if (Array.isArray(polygons)) {
            if (polygons.length === 0)
                return undefined;
            const firstEntry = polygons[0];
            if (Point3dVector3d_1.Point3d.isAnyImmediatePointType(firstEntry)) {
                graph = Triangulation_1.Triangulator.createTriangulatedGraphFromSingleLoop(polygons);
            }
            else if (polygons.length > 1) {
                let writablePolygons;
                if (firstEntry instanceof IndexedXYZCollection_1.IndexedReadWriteXYZCollection) {
                    writablePolygons = polygons;
                }
                else {
                    writablePolygons = [];
                    for (const polygon of polygons)
                        writablePolygons.push(GrowableXYZArray_1.GrowableXYZArray.create(polygon));
                }
                const sortedPolygons = PolygonOps_1.PolygonOps.sortOuterAndHoleLoopsXY(writablePolygons);
                if (sortedPolygons.length === 1) { // below requires exactly one outer loop!
                    if (graph = Triangulation_1.Triangulator.createTriangulatedGraphFromLoops(sortedPolygons[0]))
                        Triangulation_1.Triangulator.flipTriangles(graph);
                }
            }
            else {
                graph = Triangulation_1.Triangulator.createTriangulatedGraphFromSingleLoop(firstEntry);
            }
        }
        else {
            graph = Triangulation_1.Triangulator.createTriangulatedGraphFromSingleLoop(polygons);
        }
        if (!graph) {
            // Last resort: try full merge. Conveniently, multiple polygons are processed with parity logic.
            if (graph = RegionOpsClassificationSweeps_1.RegionOpsFaceToFaceSearch.doPolygonBoolean(polygons, [], (inA, _inB) => inA)) {
                if (Triangulation_1.Triangulator.triangulateAllPositiveAreaFaces(graph))
                    Triangulation_1.Triangulator.flipTriangles(graph);
            }
        }
        return graph;
    }
    /** Stroke a Loop or ParityRegion */
    static strokeRegionComponent(component, options) {
        const strokedComponent = component.cloneStroked(options);
        // package the stroked region as polygons
        const polygons = [];
        if (strokedComponent instanceof Loop_1.Loop) {
            if (strokedComponent.children.length > 0 && strokedComponent.children[0] instanceof LineString3d_1.LineString3d)
                polygons.push(strokedComponent.children[0].packedPoints); // expect only 1
        }
        else if (strokedComponent instanceof ParityRegion_1.ParityRegion) {
            for (const strokedLoop of strokedComponent.children) {
                if (strokedLoop.children.length > 0 && strokedLoop.children[0] instanceof LineString3d_1.LineString3d)
                    polygons.push(strokedLoop.children[0].packedPoints); // expect only 1
            }
        }
        return polygons;
    }
    /**
     * Triangulate a Loop or ParityRegion and return the graph.
     * @param component region, z-coordinates ignored
     * @param options how to stroke loops
     * @returns triangulated graph
     */
    static triangulateRegionComponent(component, options) {
        const polygons = this.strokeRegionComponent(component, options);
        return this.triangulateStrokedRegionComponent(polygons);
    }
    /**
     * Facet the region according to stroke options.
     * @note For best results, [[UnionRegion]] input should consist of non-overlapping children. See [[regionBooleanXY]].
     * @note For best results, [[ParityRegion]] input should be correctly oriented. See [[sortOuterAndHoleLoopsXY]].
     * @param region a closed xy-planar region, possibly with holes.
     * * The z-coordinates of the region are ignored. Caller is responsible for rotating the region into plane local coordinates beforehand, and reversing the rotation afterwards.
     * @param options primarily how to stroke the region boundary, but also how to facet the region interior.
     * * By default, a triangulation is returned, but if `options.maximizeConvexFacets === true`, edges between coplanar triangles are removed to return maximally convex facets.
     * @returns facets for the region, or undefined if facetting failed
     */
    static facetRegionXY(region, options) {
        let graph;
        if (region instanceof UnionRegion_1.UnionRegion) {
            for (const child of region.children) {
                const childGraph = RegionOps.triangulateRegionComponent(child, options);
                if (childGraph) {
                    if (!graph) {
                        graph = childGraph;
                    }
                    else {
                        // Graph concatenation without edge splits, clustering, and merge. We assume disjoint children so that at worst,
                        // components will have unshared adjacent exterior edges that remain after expandConvexFaces. Note that calling
                        // clusterAndMergeXYTheta here can create non-manifold topology!
                        graph.allHalfEdges.push(...childGraph.allHalfEdges);
                    }
                }
            }
        }
        else {
            graph = this.triangulateRegionComponent(region, options);
        }
        if (!graph)
            return undefined;
        if (options?.maximizeConvexFacets)
            Merging_1.HalfEdgeGraphOps.expandConvexFaces(graph);
        return PolyfaceBuilder_1.PolyfaceBuilder.graphToPolyface(graph, options);
    }
    /**
     * Decompose a polygon with optional holes into an array of convex polygons.
     * @param polygon polygon and hole loops, e.g., as returned by [[CurveCollection.cloneStroked]] on a Loop or ParityRegion. All z-coordinates are ignored.
     * @param maximize whether to return maximally convex polygons. If false, triangles are returned.
     * @returns array of convex polygons, or undefined if triangulation failed
    */
    static convexDecomposePolygonXY(polygon, maximize = true) {
        const graph = RegionOps.triangulateStrokedRegionComponent(polygon);
        if (!graph)
            return undefined;
        if (maximize)
            Merging_1.HalfEdgeGraphOps.expandConvexFaces(graph);
        const convexPolygons = [];
        graph.announceFaceLoops((_graph, seed) => {
            if (!seed.isMaskSet(Graph_1.HalfEdgeMask.EXTERIOR))
                convexPolygons.push(GrowableXYZArray_1.GrowableXYZArray.create(seed.collectAroundFace((node) => { return node.getPoint3d(); })));
            return true;
        });
        return convexPolygons;
    }
}
exports.RegionOps = RegionOps;
/** @internal */
function pushToInOnOutArrays(curve, select, arrayNegative, array0, arrayPositive) {
    if (select > 0)
        arrayPositive.push(curve);
    else if (select < 0)
        arrayNegative.push(curve);
    else
        array0.push(curve);
}
//# sourceMappingURL=RegionOps.js.map