/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module CartesianGeometry
 */
import { Geometry } from "../Geometry";
import { GrowableFloat64Array } from "../geometry3d/GrowableFloat64Array";
import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray";
import { Range1d } from "../geometry3d/Range";
import { Segment1d } from "../geometry3d/Segment1d";
import { ClipPlaneContainment, ClipUtilities } from "./ClipUtils";
import { ConvexClipPlaneSet } from "./ConvexClipPlaneSet";
/**
 * A collection of ConvexClipPlaneSets.
 * * A point is "in" the clip plane set if it is "in" one or more of  the ConvexClipPlaneSet
 * * Hence the boolean logic is that the ClipPlaneSet is a UNION of its constituents.
 * @public
 */
export class UnionOfConvexClipPlaneSets {
    _convexSets;
    /** (property accessor)  Return the (reference to the) array of `ConvexClipPlaneSet` */
    get convexSets() {
        return this._convexSets;
    }
    constructor() {
        this._convexSets = [];
    }
    /** Return an array with the `toJSON` form of each `ConvexClipPlaneSet` */
    toJSON() {
        const val = [];
        for (const convex of this._convexSets)
            val.push(convex.toJSON());
        return val;
    }
    /** Convert json `UnionOfConvexClipPlaneSets`, using `setFromJSON`. */
    static fromJSON(json, result) {
        result = result ? result : new UnionOfConvexClipPlaneSets();
        result._convexSets.length = 0;
        if (!Array.isArray(json))
            return result;
        for (const thisJson of json)
            result._convexSets.push(ConvexClipPlaneSet.fromJSON(thisJson));
        return result;
    }
    /** Create a `UnionOfConvexClipPlaneSets` with no members. */
    static createEmpty(result) {
        if (result) {
            result._convexSets.length = 0;
            return result;
        }
        return new UnionOfConvexClipPlaneSets();
    }
    /**
     * Return true if all member convex sets are almostEqual to corresponding members of other. This includes
     * identical order in array.
     * @param other clip plane to compare.
     */
    isAlmostEqual(other) {
        if (this._convexSets.length !== other._convexSets.length)
            return false;
        for (let i = 0; i < this._convexSets.length; i++)
            if (!this._convexSets[i].isAlmostEqual(other._convexSets[i]))
                return false;
        return true;
    }
    /** Create a `UnionOfConvexClipPlaneSets` with given `ConvexClipPlaneSet` members. */
    static createConvexSets(convexSets, result) {
        result = result ? result : new UnionOfConvexClipPlaneSets();
        for (const set of convexSets)
            result._convexSets.push(set);
        return result;
    }
    /** Return a deep copy. */
    clone(result) {
        result = result ? result : new UnionOfConvexClipPlaneSets();
        result._convexSets.length = 0;
        for (const convexSet of this._convexSets)
            result._convexSets.push(convexSet.clone());
        return result;
    }
    /**
     * Append `toAdd` to the array of `ConvexClipPlaneSet`.
     * * undefined toAdd is ignored.
     */
    addConvexSet(toAdd) {
        if (toAdd)
            this._convexSets.push(toAdd);
    }
    /**
     * Test if there is any intersection with a ray defined by origin and direction.
     * * Optionally record the range (null or otherwise) in caller-allocated result.
     * * If the ray is unbounded inside the clip, result can contain positive or negative
     * "Geometry.largeCoordinateResult" values.
     * * If no result is provide, there are no object allocations.
     * @param maximalRange optional Range1d to receive parameters along the ray.
     */
    hasIntersectionWithRay(ray, maximalRange) {
        if (maximalRange === undefined) {
            // if complete result is not requested, return after any hit.
            for (const planeSet of this._convexSets) {
                if (planeSet.hasIntersectionWithRay(ray))
                    return true;
            }
            return false;
        }
        maximalRange.setNull();
        const rangeA = Range1d.createNull();
        for (const planeSet of this._convexSets) {
            if (planeSet.hasIntersectionWithRay(ray, rangeA))
                maximalRange.extendRange(rangeA);
        }
        return !maximalRange.isNull;
    }
    /**
     * Return true if true is returned for any contained convex set returns true for
     * `convexSet.isPointInside (point, tolerance)`.
     */
    isPointInside(point) {
        for (const convexSet of this._convexSets) {
            if (convexSet.isPointInside(point)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Return true if true is returned for any contained convex set returns true for
     * `convexSet.isPointOnOrInside (point, tolerance)`.
     */
    isPointOnOrInside(point, tolerance = Geometry.smallMetricDistance) {
        for (const convexSet of this._convexSets) {
            if (convexSet.isPointOnOrInside(point, tolerance))
                return true;
        }
        return false;
    }
    /**
     * Return true if true is returned for any contained convex set returns true for
     * `convexSet.isSphereOnOrInside (point, tolerance)`.
     */
    isSphereInside(point, radius) {
        for (const convexSet of this._convexSets) {
            if (convexSet.isSphereInside(point, radius))
                return true;
        }
        return false;
    }
    /** Test if any part of a line segment is within the volume. */
    isAnyPointInOrOnFromSegment(segment) {
        for (const convexSet of this._convexSets) {
            if (convexSet.announceClippedSegmentIntervals(0.0, 1.0, segment.point0Ref, segment.point1Ref))
                return true;
        }
        return false;
    }
    // Intervals must be Segment1d array, as there may be multiple intervals along segment that pass through set regions,
    // and so splitting the intervals into segments aids in better organization
    /** Returns the fractions of the segment that pass through the set region, as 1 dimensional pieces. */
    appendIntervalsFromSegment(segment, intervals) {
        for (const convexSet of this._convexSets) {
            convexSet.announceClippedSegmentIntervals(0.0, 1.0, segment.point0Ref, segment.point1Ref, (fraction0, fraction1) => intervals.push(Segment1d.create(fraction0, fraction1)));
        }
    }
    /** Apply `transform` to all the ConvexClipPlaneSet's. */
    transformInPlace(transform) {
        for (const convexSet of this._convexSets) {
            convexSet.transformInPlace(transform);
        }
    }
    /** Returns 1, 2, or 3 based on whether point is strongly inside, ambiguous, or strongly outside respectively. */
    classifyPointContainment(points, onIsOutside) {
        for (const convexSet of this._convexSets) {
            const thisStatus = convexSet.classifyPointContainment(points, onIsOutside);
            if (thisStatus !== ClipPlaneContainment.StronglyOutside)
                return thisStatus;
        }
        return ClipPlaneContainment.StronglyOutside;
    }
    /**
     * Clip a polygon to the planes of the clip sets, returning new polygon boundaries.
     * * The output polygons may lie next to each other, or be disconnected.
     * * For a convex input polygon, the output polygon(s) are also convex.
     * * For non-convex input, the output polygon(s) may have double-back edges along plane intersections. This is still a
     * valid clip in a parity sense (overlapping regions cancel).
     * * This method differs from [[appendPolygonClip]] by clipping the same input polygon with each [[ConvexClipPlaneSet]]
     * in the instance, and returning only the inside pieces.
     * @param input polygon, usually convex. Unchanged.
     * @param output output polygon
     * @param work optional work array.
     * @param planeToSkip if this plane is found in the convex set, it is NOT applied.
     * This is useful when caller knows the polygon lies in one of the instance planes.
     * @param tolerance distance tolerance for "on plane" decision. Default value is [[Geometry.smallMetricDistance]].
     * @see appendPolygonClip
     */
    polygonClip(input, output, work, planeToSkip, tolerance = Geometry.smallMetricDistance) {
        output.length = 0;
        if (Array.isArray(input))
            input = GrowableXYZArray.create(input);
        if (!work)
            work = new GrowableXYZArray();
        for (const convexSet of this._convexSets) {
            const convexSetOutput = new GrowableXYZArray();
            convexSet.polygonClip(input, convexSetOutput, work, planeToSkip, tolerance);
            if (convexSetOutput.length !== 0)
                output.push(convexSetOutput);
        }
    }
    /**
     * Announce clipSegment() for each convexSet in this ClipPlaneSet.
     * * all clipPlaneSets are inspected.
     * * announced intervals are for each individual clipPlaneSet -- adjacent intervals are not consolidated.
     * @param f0 active interval start.
     * @param f1 active interval end.
     * @param pointA line segment start.
     * @param pointB line segment end.
     * @param announce function to announce interval.
     * @returns Return true if any announcements are made.
     */
    announceClippedSegmentIntervals(f0, f1, pointA, pointB, announce) {
        let numAnnounce = 0;
        for (const convexSet of this._convexSets) {
            if (convexSet.announceClippedSegmentIntervals(f0, f1, pointA, pointB, announce))
                numAnnounce++;
        }
        return numAnnounce > 0;
    }
    static _clipArcFractionArray = new GrowableFloat64Array();
    /**
     * Find parts of an arc that are inside any member clipper.
     * Announce each with `announce(startFraction, endFraction, this)`
     */
    announceClippedArcIntervals(arc, announce) {
        const breaks = UnionOfConvexClipPlaneSets._clipArcFractionArray;
        breaks.clear();
        for (const convexSet of this._convexSets) {
            for (const clipPlane of convexSet.planes) {
                clipPlane.appendIntersectionRadians(arc, breaks);
            }
        }
        arc.sweep.radiansArrayToPositivePeriodicFractions(breaks);
        return ClipUtilities.selectIntervals01(arc, breaks, this, announce);
    }
    /**
     * Collect the output from computePlanePlanePlaneIntersections in all the contained convex sets.
     * @param points (optional) array to which computed points are to be added.
     * @param rangeToExtend (optional) range to be extended by the computed points.
     * @param transform (optional) transform to apply to the accepted points.
     * @param testContainment if true, test each point to see if it is within the convex set (send false if confident
     * that the convex set is rectilinear set such as a slab. Send true if chiseled corners are possible).
     * @returns number of points.
     */
    computePlanePlanePlaneIntersectionsInAllConvexSets(points, rangeToExtend, transform, testContainment = true) {
        let n = 0;
        for (const convexSet of this._convexSets) {
            n += convexSet.computePlanePlanePlaneIntersections(points, rangeToExtend, transform, testContainment);
        }
        return n;
    }
    /**
     * Multiply all ClipPlanes DPoint4d by matrix.
     * @param matrix matrix to apply.
     * @param invert if true, use in verse of the matrix.
     * @param transpose if true, use the transpose of the matrix (or inverse, per invert parameter).
     * * Note that if matrixA is applied to all of space, the matrix to send to this method to get a corresponding effect
     * on the plane is the inverse transpose of matrixA.
     * * Callers that will apply the same matrix to many planes should pre-invert the matrix for efficiency.
     * * Both params default to true to get the full effect of transforming space.
     * @param matrix matrix to apply
     */
    multiplyPlanesByMatrix4d(matrix, invert = true, transpose = true) {
        if (invert) { // form inverse once here, reuse for all planes
            const inverse = matrix.createInverse();
            if (!inverse)
                return false;
            return this.multiplyPlanesByMatrix4d(inverse, false, transpose);
        }
        // (no inversion -- no failures possible)
        for (const convexSet of this._convexSets) {
            convexSet.multiplyPlanesByMatrix4d(matrix, false, transpose);
        }
        return true;
    }
    /** Recursively call `setInvisible` on all member convex sets. */
    setInvisible(invisible) {
        for (const convexSet of this._convexSets) {
            convexSet.setInvisible(invisible);
        }
    }
    /** add convex sets that accept points below `zLow` and above `zHigh` */
    addOutsideZClipSets(invisible, zLow, zHigh) {
        if (zLow) {
            const convexSet = ConvexClipPlaneSet.createEmpty();
            convexSet.addZClipPlanes(invisible, zLow);
            this._convexSets.push(convexSet);
        }
        if (zHigh) {
            const convexSet = ConvexClipPlaneSet.createEmpty();
            convexSet.addZClipPlanes(invisible, undefined, zHigh);
            this._convexSets.push(convexSet);
        }
    }
    /** Move convex sets from source.*/
    takeConvexSets(source) {
        let convexSet;
        while ((undefined !== (convexSet = source._convexSets.pop()))) {
            this._convexSets.push(convexSet);
        }
    }
    /**
     * Implement appendPolygonClip, as defined in interface PolygonClipper.
     * * This method differs from [[polygonClip]] by clipping the outside fragments resulting from the previous
     * [[ConvexClipPlaneSet]] with the following one, and returning both inside and outside pieces.
     * In this way, it always produces disjoint inside fragments, even if the clippers are not disjoint (uncommon).
     * @param xyz convex polygon. This is not changed.
     * @param insideFragments Array to receive "inside" fragments. Each fragment is a GrowableXYZArray grabbed from
     * the cache. This is NOT cleared.
     * @param outsideFragments Array to receive "outside" fragments. Each fragment is a GrowableXYZArray grabbed from
     * the cache. This is NOT cleared.
     * @param arrayCache cache for reusable GrowableXYZArray.
     * @see polygonClip
     */
    appendPolygonClip(xyz, insideFragments, outsideFragments, arrayCache) {
        const oldOutsideCount = outsideFragments.length;
        const oldInsideCount = insideFragments.length;
        let carryForwardA = [arrayCache.grabAndFill(xyz)];
        let carryForwardB = [];
        let tempAB;
        let shard;
        // At each convex set, carryForwardA is all the fragments that have been outside all previous convex sets.
        // Clip each such fragment to the current set, sending the outside parts to carryForwardB, which will got to the next clipper
        // The final surviving carryForward really is out.
        for (const c of this._convexSets) {
            while (undefined !== (shard = carryForwardA.pop())) {
                c.appendPolygonClip(shard, insideFragments, carryForwardB, arrayCache);
                arrayCache.dropToCache(shard);
            }
            tempAB = carryForwardB;
            carryForwardB = carryForwardA; // and that is empty
            carryForwardA = tempAB;
        }
        while (undefined !== (shard = carryForwardA.pop())) {
            outsideFragments.push(shard);
        }
        if (outsideFragments.length === oldOutsideCount)
            ClipUtilities.restoreSingletonInPlaceOfMultipleShards(insideFragments, oldInsideCount, xyz, arrayCache);
        else if (insideFragments.length === oldInsideCount)
            ClipUtilities.restoreSingletonInPlaceOfMultipleShards(outsideFragments, oldOutsideCount, xyz, arrayCache);
    }
}
/* FUNCTIONS SKIPPED DUE TO BSPLINES, VU, OR NON-USAGE IN NATIVE CODE----------------------------------------------------------------

Involves vu: skipping for now...
  public fromSweptPolygon(points: Point3d[], directions: Vector3d[]): ClipPlaneSet;
  public parseConcavePolygonPlanes(...)

Uses bsplines... skipping for now:
  public appendIntervalsClipPlaneSetFromCurve();

Uses bsplines... skipping for now:
  public isAnyPointInOrOnFrom();

Skipped fromSweptPolygon(...), which is overloaded function from first, due to presence of vu
  public fromSweptPolygon(points: Point3d[], directions: Vector3d[], shapes: Point3d[])
*/
//# sourceMappingURL=UnionOfConvexClipPlaneSets.js.map