"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.StringifiedClipVector = exports.ClipVector = void 0;
/** @packageDocumentation
 * @module CartesianGeometry
 */
const core_bentley_1 = require("@itwin/core-bentley");
const LineSegment3d_1 = require("../curve/LineSegment3d");
const Geometry_1 = require("../Geometry");
const Range_1 = require("../geometry3d/Range");
const Transform_1 = require("../geometry3d/Transform");
const BooleanClipNode_1 = require("./BooleanClipNode");
const ClipPrimitive_1 = require("./ClipPrimitive");
const ClipUtils_1 = require("./ClipUtils");
/**
 * Class holding an array structure of shapes defined by `ClipPrimitive`
 * * The `ClipVector` defines an intersection of the member `ClipPrimitive` regions.
 * * In the most common usage, one of the `ClipPrimitive` will be an outer region, and all others are holes with marker
 * flag indicating that the outside of each hole is live.
 * @public
 */
class ClipVector {
    _clips;
    /**
     * Range acting as first filter.
     * * This is understood as overall range limit, not as precise planes.
     * * Applying any rotation to the whole ClipVector generally expands this range, rather than exactly transforming
     * its planes.
     */
    boundingRange = Range_1.Range3d.createNull();
    /** Returns a reference to the array of ClipShapes. */
    get clips() { return this._clips; }
    constructor(clips) {
        this._clips = clips ? clips : [];
    }
    /** Returns true if this ClipVector contains a ClipPrimitive. */
    get isValid() { return this._clips.length > 0; }
    /** Create a ClipVector with an empty set of ClipShapes. */
    static createEmpty(result) {
        if (result) {
            result._clips.length = 0;
            return result;
        }
        return new ClipVector();
    }
    /** Create a ClipVector from an array of ClipPrimitives (or derived classes) (capture the pointers) */
    static createCapture(clips, result) {
        if (result) {
            result._clips = clips;
            return result;
        }
        return new ClipVector(clips);
    }
    /** Create a ClipVector from (clones of) an array of ClipPrimitives */
    static create(clips, result) {
        const clipClones = [];
        for (const clip of clips)
            clipClones.push(clip.clone());
        return ClipVector.createCapture(clipClones, result);
    }
    /** Create a deep copy of another ClipVector */
    clone(result) {
        const retVal = result ? result : new ClipVector();
        retVal._clips.length = 0;
        for (const clip of this._clips) {
            retVal._clips.push(clip.clone());
        }
        retVal.boundingRange.setFrom(this.boundingRange);
        return retVal;
    }
    /** Parse this ClipVector into a JSON object. */
    toJSON() {
        if (!this.isValid)
            return [];
        return this.clips.map((clip) => clip.toJSON());
    }
    /** Parse a JSON object into a new ClipVector. */
    static fromJSON(json, result) {
        result = result ? result : new ClipVector();
        result.clear();
        if (!Array.isArray(json))
            return result;
        try {
            for (const clip of json) {
                const clipPrim = ClipPrimitive_1.ClipPrimitive.fromJSON(clip);
                if (clipPrim)
                    result._clips.push(clipPrim);
            }
        }
        catch {
            result.clear();
        }
        return result;
    }
    /** Empties out the array of ClipShapes. */
    clear() {
        this._clips.length = 0;
    }
    /** Append a deep copy of the given ClipPrimitive to this ClipVector. */
    appendClone(clip) {
        this._clips.push(clip.clone());
    }
    /** Append a reference of the given ClipPrimitive to this ClipVector. */
    appendReference(clip) {
        this._clips.push(clip);
    }
    /** Create and append a new ClipPrimitive to the array given a shape as an array of points. Returns true if successful. */
    appendShape(shape, zLow, zHigh, transform, isMask = false, invisible = false) {
        const clip = ClipPrimitive_1.ClipShape.createShape(shape, zLow, zHigh, transform, isMask, invisible);
        if (!clip)
            return false;
        this._clips.push(clip);
        return true;
    }
    /** Returns true if the given point lies inside all of this ClipVector's ClipShapes (by rule of intersection).*/
    pointInside(point, onTolerance = Geometry_1.Geometry.smallMetricDistanceSquared) {
        return this.isPointOnOrInside(point, onTolerance);
    }
    /**
     * Method from [[Clipper]] interface.
     * * Implement as dispatch to clipPlaneSets as supplied by derived class.
     */
    isPointOnOrInside(point, onTolerance = Geometry_1.Geometry.smallMetricDistanceSquared) {
        if (!this.boundingRange.isNull && !this.boundingRange.containsPoint(point))
            return false;
        for (const clip of this._clips) {
            if (!clip.pointInside(point, onTolerance))
                return false;
        }
        return true;
    }
    // Proxy object to implement line and arc clip.
    _clipNodeProxy;
    ensureProxyClipNode() {
        if (this._clipNodeProxy)
            return true;
        this._clipNodeProxy = new BooleanClipNode_1.BooleanClipNodeIntersection(true);
        let numChildren = 0;
        for (const child of this._clips) {
            const q = child.fetchClipPlanesRef();
            if (q) {
                numChildren++;
                this._clipNodeProxy.captureChild(q);
            }
        }
        return numChildren > 0;
    }
    /**
     * Method from [[Clipper]] interface.
     * * Implement as dispatch to clipPlaneSets as supplied by derived class.
     */
    announceClippedSegmentIntervals(f0, f1, pointA, pointB, announce) {
        this.ensureProxyClipNode();
        if (this._clipNodeProxy)
            return this._clipNodeProxy.announceClippedSegmentIntervals(f0, f1, pointA, pointB, announce);
        return false;
    }
    /** Method from [[Clipper]] interface.
     * * Implement as dispatch to clipPlaneSets as supplied by derived class.
     */
    announceClippedArcIntervals(arc, announce) {
        this.ensureProxyClipNode();
        if (this._clipNodeProxy)
            return this._clipNodeProxy.announceClippedArcIntervals(arc, announce);
        return false;
    }
    /** Execute polygon clip as intersection of the child primitives. */
    appendPolygonClip(xyz, insideFragments, outsideFragments, arrayCache) {
        this.ensureProxyClipNode();
        if (this._clipNodeProxy)
            this._clipNodeProxy.appendPolygonClip(xyz, insideFragments, outsideFragments, arrayCache);
    }
    /**
     * Transforms this ClipVector to a new coordinate-system.
     * Note that if the transform has rotate and scale the boundingRange member expands.
     * Returns true if successful.
     */
    transformInPlace(transform) {
        for (const clip of this._clips)
            if (clip.transformInPlace(transform) === false)
                return false;
        if (!this.boundingRange.isNull)
            transform.multiplyRange(this.boundingRange, this.boundingRange);
        return true;
    }
    /**
     * A simple way of packaging this ClipVector's ClipShape points into a multidimensional array, while also
     * taking into account each ClipPrimitive's individual transforms.
     * ClipPrimitives OTHER THAN ClipShape are ignored.
     * Information out:
     *  - All of the loop points are stored in the multidimensional Point3d array given (will return unchanged upon failure).
     *  - If given a transform, will be set from the transformFromClip of the first ClipPrimitive.
     *  - The ClipMask of the final ClipPrimitive is stored in the returned array at index 0.
     *  - The last valid zLow found is stored in the returned array at index 1.
     *  - The last valid zHigh found is stored in the returned array at index 2.
     */
    extractBoundaryLoops(loopPoints, transform) {
        let clipM = ClipPrimitive_1.ClipMaskXYZRangePlanes.None;
        let zBack = -Number.MAX_VALUE;
        let zFront = Number.MAX_VALUE;
        const retVal = [];
        let nLoops = 0;
        if (this._clips.length === 0)
            return retVal;
        let firstClipShape;
        const deltaTrans = Transform_1.Transform.createIdentity();
        for (const clip of this._clips) {
            if (clip instanceof ClipPrimitive_1.ClipShape) {
                if (firstClipShape !== undefined && clip !== firstClipShape) { // Is not the first iteration
                    let fwdTrans = Transform_1.Transform.createIdentity();
                    let invTrans = Transform_1.Transform.createIdentity();
                    if (firstClipShape.transformValid && clip.transformValid) {
                        fwdTrans = clip.transformFromClip.clone();
                        invTrans = firstClipShape.transformToClip.clone();
                    }
                    deltaTrans.setFrom(invTrans.multiplyTransformTransform(fwdTrans));
                }
                if (!firstClipShape)
                    firstClipShape = clip;
                loopPoints[nLoops] = [];
                if (clip.polygon !== undefined) {
                    clipM = ClipPrimitive_1.ClipMaskXYZRangePlanes.XAndY;
                    if (clip.zHighValid) {
                        clipM = clipM | ClipPrimitive_1.ClipMaskXYZRangePlanes.ZHigh;
                        zFront = clip.zHigh;
                    }
                    if (clip.zLowValid) {
                        clipM = clipM | ClipPrimitive_1.ClipMaskXYZRangePlanes.ZLow;
                        zBack = clip.zLow;
                    }
                    for (const point of clip.polygon)
                        loopPoints[nLoops].push(point.clone());
                    deltaTrans.multiplyPoint3dArray(loopPoints[nLoops], loopPoints[nLoops]);
                    nLoops++;
                }
            }
        }
        retVal.push(clipM);
        retVal.push(zBack);
        retVal.push(zFront);
        if (transform && firstClipShape)
            transform.setFrom(firstClipShape.transformFromClip);
        return retVal;
    }
    /** Sets this ClipVector and all of its members to the visibility specified. */
    setInvisible(invisible) {
        for (const clip of this._clips)
            clip.setInvisible(invisible);
    }
    /**
     * For every clip, parse the member point array into the member clip plane object (only for clipPlanes member,
     * not the mask)
     */
    parseClipPlanes() {
        for (const clip of this._clips)
            clip.fetchClipPlanesRef();
    }
    /**
     * 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
     * @returns false if matrix inversion fails.
     */
    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 inverse necessary -- lower level cannot fail.
        for (const clip of this._clips)
            clip.multiplyPlanesByMatrix4d(matrix, false, transpose);
        return true;
    }
    /**
     * Determines whether the given points fall inside or outside this set of ClipShapes. If any set is defined
     * by masking planes, checks the mask planes only, provided that ignoreMasks is false. Otherwise, checks the
     * _clipPlanes member.
     */
    classifyPointContainment(points, ignoreMasks = false) {
        let currentContainment = ClipUtils_1.ClipPlaneContainment.Ambiguous;
        for (const primitive of this._clips) {
            const thisContainment = primitive.classifyPointContainment(points, ignoreMasks);
            if (ClipUtils_1.ClipPlaneContainment.Ambiguous === thisContainment)
                return ClipUtils_1.ClipPlaneContainment.Ambiguous;
            if (ClipUtils_1.ClipPlaneContainment.Ambiguous === currentContainment)
                currentContainment = thisContainment;
            else if (currentContainment !== thisContainment)
                return ClipUtils_1.ClipPlaneContainment.Ambiguous;
        }
        return currentContainment;
    }
    /**
     * Determines whether a 3D range lies inside or outside this set of ClipShapes. If any set is defined by masking planes,
     * checks the mask planes only, provided that ignoreMasks is false. Otherwise, checks the clip planes member.
     */
    classifyRangeContainment(range, ignoreMasks) {
        const corners = range.corners();
        return this.classifyPointContainment(corners, ignoreMasks);
    }
    /**
     * For an array of points (making up a LineString), tests whether the segment between each point lies inside the
     * ClipVector.
     * If true, returns true immediately.
     */
    isAnyLineStringPointInside(points) {
        for (const clip of this._clips) {
            const clipPlaneSet = clip.fetchClipPlanesRef();
            if (clipPlaneSet !== undefined) {
                for (let i = 0; i + 1 < points.length; i++) {
                    const segment = LineSegment3d_1.LineSegment3d.create(points[i], points[i + 1]);
                    if (clipPlaneSet.isAnyPointInOrOnFromSegment(segment))
                        return true;
                }
            }
        }
        return false;
    }
    /** Note: Line segments are used to represent 1 dimensional intervals here, rather than segments. */
    sumSizes(intervals, begin, end) {
        let s = 0.0;
        for (let i = begin; i < end; i++)
            s += (intervals[i].x1 - intervals[i].x0);
        return s;
    }
    static _TARGET_FRACTION_SUM = 0.99999999;
    /**
     * For an array of points that make up a LineString, develops a line segment between each point pair,
     * and returns true if all segments lie inside this ClipVector.
     */
    isLineStringCompletelyContained(points) {
        const clipIntervals = [];
        for (let i = 0; i + 1 < points.length; i++) {
            const segment = LineSegment3d_1.LineSegment3d.create(points[i], points[i + 1]);
            let fractionSum = 0.0;
            let index0 = 0;
            for (const clip of this._clips) {
                const clipPlaneSet = clip.fetchClipPlanesRef();
                if (clipPlaneSet !== undefined) {
                    clipPlaneSet.appendIntervalsFromSegment(segment, clipIntervals);
                    const index1 = clipIntervals.length;
                    fractionSum += this.sumSizes(clipIntervals, index0, index1);
                    index0 = index1;
                    // ASSUME primitives are non-overlapping...
                    if (fractionSum >= ClipVector._TARGET_FRACTION_SUM)
                        break;
                }
            }
            if (fractionSum < ClipVector._TARGET_FRACTION_SUM)
                return false;
        }
        return true;
    }
    /**
     * Serializes this ClipVector to a compact string representation appropriate for transmission as part of a URL.
     * Chiefly used for requesting [Tile]($frontend)s with section cut facets.
     * UnionOfConvexClipPlaneSets is obtained for each ClipPrimitive. The encoding is as follows:
     *  ClipVector:
     *    ClipPrimitive[]
     *    _
     *  ClipPrimitive:
     *    invisible: 0|1
     *    ConvexClipPlaneSet[]
     *    _
     *  ConvexClipPlaneSet:
     *    ClipPlane[]
     *    _
     *  ClipPlane:
     *    flags: 0|1|2|3, where 1=invisible and 2=interior
     *    inwardNormal: Number[3]
     *    distance: Number
     *  Number:
     *    number
     *    _
     */
    toCompactString() {
        function formatNumber(num) {
            return `${num.toString()}_`;
        }
        function formatVector3d(vec) {
            return `${formatNumber(vec.x)}${formatNumber(vec.y)}${formatNumber(vec.z)}`;
        }
        function formatFlags(flags) {
            const f = flags.toString();
            (0, core_bentley_1.assert)(1 === f.length);
            return f;
        }
        function formatPlane(plane) {
            let flags = plane.invisible ? 1 : 0;
            flags |= (plane.interior ? 2 : 0);
            return `${formatFlags(flags)}${formatVector3d(plane.inwardNormalRef)}${formatNumber(plane.distance)}`;
        }
        function formatPlaneSet(set) {
            let planes = "";
            for (const plane of set.planes)
                planes = `${planes}${formatPlane(plane)}`;
            return `${planes}_`;
        }
        function formatPrimitive(prim) {
            const flags = prim.invisible ? 1 : 0;
            let str = flags.toString();
            (0, core_bentley_1.assert)(1 === str.length);
            const union = prim.fetchClipPlanesRef();
            if (union) {
                for (const s of union.convexSets)
                    str = `${str}${formatPlaneSet(s)}`;
            }
            return `${str}_`;
        }
        let result = "";
        for (const primitive of this.clips)
            result = `${result}${formatPrimitive(primitive)}`;
        return `${result}_`;
    }
}
exports.ClipVector = ClipVector;
/**
 * Bundles a ClipVector with its compact string representation.
 * @note The string representation is computed once; the ClipVector is assumed not to be subsequently modified.
 * @alpha
 */
var StringifiedClipVector;
(function (StringifiedClipVector) {
    /** Create from a ClipVector.
     * @param clip The ClipVector to stringify.
     * @returns The input ClipVector with its compact string representation, or undefined if the input is undefined or empty.
     * @note The string representation is computed once; the ClipVector is assumed not to be subsequently modified.
     */
    function fromClipVector(clip) {
        if (!clip || !clip.isValid)
            return undefined;
        const ret = clip;
        if (undefined === ret.clipString)
            ret.clipString = clip.toCompactString();
        const stringified = ret;
        (0, core_bentley_1.assert)(undefined !== stringified.clipString);
        return stringified;
    }
    StringifiedClipVector.fromClipVector = fromClipVector;
})(StringifiedClipVector || (exports.StringifiedClipVector = StringifiedClipVector = {}));
//# sourceMappingURL=ClipVector.js.map