"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.ClipShape = exports.ClipPrimitive = exports.ClipMaskXYZRangePlanes = void 0;
/** @packageDocumentation
 * @module CartesianGeometry
 */
const Geometry_1 = require("../Geometry");
const Point2dVector2d_1 = require("../geometry3d/Point2dVector2d");
const Point3dVector3d_1 = require("../geometry3d/Point3dVector3d");
const PolygonOps_1 = require("../geometry3d/PolygonOps");
const Transform_1 = require("../geometry3d/Transform");
const Graph_1 = require("../topology/Graph");
const Triangulation_1 = require("../topology/Triangulation");
const ClipPlane_1 = require("./ClipPlane");
const ClipUtils_1 = require("./ClipUtils");
const ConvexClipPlaneSet_1 = require("./ConvexClipPlaneSet");
const UnionOfConvexClipPlaneSets_1 = require("./UnionOfConvexClipPlaneSets");
const AlternatingConvexClipTree_1 = require("./AlternatingConvexClipTree");
const PointHelpers_1 = require("../geometry3d/PointHelpers");
const PolylineOps_1 = require("../geometry3d/PolylineOps");
// cspell:word zlow
// cspell:word zhigh
/**
 * Bit mask type for referencing subsets of 6 planes of range box.
 * @public
 */
var ClipMaskXYZRangePlanes;
(function (ClipMaskXYZRangePlanes) {
    /** no planes */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["None"] = 0] = "None";
    /** low x plane */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["XLow"] = 1] = "XLow";
    /** high x plane */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["XHigh"] = 2] = "XHigh";
    /** low y plane */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["YLow"] = 4] = "YLow";
    /** high y plane */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["YHigh"] = 8] = "YHigh";
    /** low z plane */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["ZLow"] = 16] = "ZLow";
    /** high z plane */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["ZHigh"] = 32] = "ZHigh";
    /** all x and y planes, neither z plane */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["XAndY"] = 15] = "XAndY";
    /** all 6 planes */
    ClipMaskXYZRangePlanes[ClipMaskXYZRangePlanes["All"] = 63] = "All";
})(ClipMaskXYZRangePlanes || (exports.ClipMaskXYZRangePlanes = ClipMaskXYZRangePlanes = {}));
/**
 * * ClipPrimitive is a base class for clipping implementations that use
 *   * A UnionOfConvexClipPlaneSets designated "clipPlanes".
 *   * An "invisible" flag.
 * * When constructed directly, objects of type ClipPrimitive (directly, not through a derived class) will have just planes.
 * * Derived classes (e.g. ClipShape) carry additional data such as a swept shape.
 * * ClipPrimitive can be constructed with no planes.
 *     * Derived class is responsible for filling the plane sets.
 *     * At discretion of derived classes, plane construction can be done at construction time or "on demand when" queries
 * call `ensurePlaneSets ()`
 * * ClipPrimitive can be constructed directly with planes (and no derived class).
 * * The prevailing use is via a ClipShape derived class.
 *    * The ClipShape has an "isMask" property
 *       * isMask === false means the plane sets should cover the inside of its polygon.
 *       * isMask === true means the plane sets should cover the outside of its polygon.
 *  * Note that the ClipShape's `isMask` property and the ClipPrimitive's `isInvisible` property are distinct controls.
 *     * In normal usage, callers get "outside" clip behavior using ONLY the ClipShape isMask property.
 *     * The ClipShape happens to pass the _invisible bit down to ClipPlanes that it creates.
 *         * At that level, the flag controls whether the cut edges are produced on the plane
 * @public
 */
class ClipPrimitive {
    /** The (union of) convex regions. */
    _clipPlanes;
    /** If true, pointInside inverts the sense of the pointInside for the _clipPlanes */
    _invisible;
    /**
     * Get a reference to the `UnionOfConvexClipPlaneSets`.
     * * It triggers construction of the sets by `this.ensurePlaneSets()`.
     * * Derived class typically caches the set on the first such call.
     */
    fetchClipPlanesRef() {
        this.ensurePlaneSets();
        return this._clipPlanes;
    }
    /** Ask if this primitive is a hole. */
    get invisible() {
        return this._invisible;
    }
    constructor(planeSet, isInvisible = false) {
        this._clipPlanes = planeSet;
        this._invisible = isInvisible;
    }
    /**
     * Create a ClipPrimitive, capturing the supplied plane set as the clip planes.
     * @param planes clipper
     * @param isInvisible set the invisible flag on the ClipPrimitive
     */
    static createCapture(planes, isInvisible = false) {
        let planeData;
        if (planes instanceof UnionOfConvexClipPlaneSets_1.UnionOfConvexClipPlaneSets)
            planeData = planes;
        if (planes instanceof ConvexClipPlaneSet_1.ConvexClipPlaneSet)
            planeData = UnionOfConvexClipPlaneSets_1.UnionOfConvexClipPlaneSets.createConvexSets([planes]);
        return new ClipPrimitive(planeData, isInvisible);
    }
    /** Emit json form of the clip planes */
    toJSON() {
        const planes = {};
        if (this._clipPlanes)
            planes.clips = this._clipPlanes.toJSON();
        if (this._invisible)
            planes.invisible = true;
        return { planes };
    }
    /**
     * Returns true if the planes are present.
     * * This can be false (for instance) if a ClipShape is holding a polygon but has not yet been asked to construct
     * the planes.
     */
    arePlanesDefined() {
        return this._clipPlanes !== undefined;
    }
    /** Return a deep clone. */
    clone() {
        const newPlanes = this._clipPlanes ? this._clipPlanes.clone() : undefined;
        const result = new ClipPrimitive(newPlanes, this._invisible);
        return result;
    }
    /**
     * Trigger (if needed) computation of plane sets (if applicable) in the derived class.
     * * Base class is no op.
     * * In derived class, on first call create planes sets from defining data (e.g. swept shape).
     * * In derived class, if planes are present leave them alone.
     */
    ensurePlaneSets() { }
    /**
     * Return true if the point lies inside/on this polygon (or not inside/on if this polygon is a mask). Otherwise,
     * return false.
     * * Note that a derived class may choose to (a) implement its own test using its defining data, or (b) accept
     * this implementation using planes that it inserted in the base class.
     */
    pointInside(point, onTolerance = Geometry_1.Geometry.smallMetricDistanceSquared) {
        this.ensurePlaneSets();
        let inside = true;
        if (this._clipPlanes)
            inside = this._clipPlanes.isPointOnOrInside(point, onTolerance);
        return inside;
    }
    /**
     * Method from [[Clipper]] interface.
     * * Implement as dispatch to clipPlaneSets as supplied by derived class.
     */
    isPointOnOrInside(point, onTolerance = Geometry_1.Geometry.smallMetricDistanceSquared) {
        this.ensurePlaneSets();
        let inside = true;
        if (this._clipPlanes)
            inside = this._clipPlanes.isPointOnOrInside(point, onTolerance);
        return inside;
    }
    /**
     * Method from [[Clipper]] interface.
     * * Implement as dispatch to clipPlaneSets as supplied by derived class.
     */
    announceClippedSegmentIntervals(f0, f1, pointA, pointB, announce) {
        this.ensurePlaneSets();
        let hasInsideParts = false;
        if (this._clipPlanes)
            hasInsideParts = this._clipPlanes.announceClippedSegmentIntervals(f0, f1, pointA, pointB, announce);
        return hasInsideParts;
    }
    /**
     * Method from [[Clipper]] interface.
     * * Implement as dispatch to clipPlaneSets as supplied by derived class.
     */
    announceClippedArcIntervals(arc, announce) {
        this.ensurePlaneSets();
        let hasInsideParts = false;
        if (this._clipPlanes)
            hasInsideParts = this._clipPlanes.announceClippedArcIntervals(arc, announce);
        return hasInsideParts;
    }
    /**
     * 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).
     * @param matrix matrix to apply.
     * * 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.
     */
    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);
        }
        if (this._clipPlanes)
            this._clipPlanes.multiplyPlanesByMatrix4d(matrix);
        return true;
    }
    /** Apply a transform to the clipper (e.g. transform all planes). */
    transformInPlace(transform) {
        if (this._clipPlanes)
            this._clipPlanes.transformInPlace(transform);
        return true;
    }
    /** Sets both the clip plane set and the mask set visibility */
    setInvisible(invisible) {
        this._invisible = invisible;
    }
    /**
     * Return true if any plane of the primary clipPlanes has (a) non-zero z component in its normal vector and
     * (b) finite distance from origin.
     */
    containsZClip() {
        if (this.fetchClipPlanesRef() !== undefined)
            for (const convexSet of this._clipPlanes.convexSets)
                for (const plane of convexSet.planes)
                    if (Math.abs(plane.inwardNormalRef.z) > 1.0e-6 && Math.abs(plane.distance) !== Number.MAX_VALUE)
                        return true;
        return false;
    }
    /**
     * Quick test of whether the given points fall completely inside or outside.
     * @param points points to test.
     * @param ignoreInvisibleSetting if true, do the test with the clip planes and return that, ignoring the
     * invisible setting.
     */
    classifyPointContainment(points, ignoreInvisibleSetting) {
        this.ensurePlaneSets();
        const planes = this._clipPlanes;
        let inside = ClipUtils_1.ClipPlaneContainment.StronglyInside;
        if (planes)
            inside = planes.classifyPointContainment(points, false);
        if (this._invisible && !ignoreInvisibleSetting)
            switch (inside) {
                case ClipUtils_1.ClipPlaneContainment.StronglyInside:
                    return ClipUtils_1.ClipPlaneContainment.StronglyOutside;
                case ClipUtils_1.ClipPlaneContainment.StronglyOutside:
                    return ClipUtils_1.ClipPlaneContainment.StronglyInside;
                case ClipUtils_1.ClipPlaneContainment.Ambiguous:
                    return ClipUtils_1.ClipPlaneContainment.Ambiguous;
            }
        return inside;
    }
    /**
     * Promote json object form to class instance.
     * * First try to convert to a ClipShape.
     * * then try as a standalone instance of the base class ClipPrimitive.
     */
    static fromJSON(json) {
        if (!json)
            return undefined;
        const shape = ClipShape.fromClipShapeJSON(json);
        if (shape)
            return shape;
        return ClipPrimitive.fromJSONClipPrimitive(json);
    }
    /** Specific converter producing the base class ClipPrimitive. */
    static fromJSONClipPrimitive(json) {
        const planes = json?.planes;
        if (!planes)
            return undefined;
        const clipPlanes = planes.clips ? UnionOfConvexClipPlaneSets_1.UnionOfConvexClipPlaneSets.fromJSON(planes.clips) : undefined;
        const invisible = undefined !== planes.invisible ? planes.invisible : false;
        return new ClipPrimitive(clipPlanes, invisible);
    }
}
exports.ClipPrimitive = ClipPrimitive;
/** Internal helper class holding XYZ components that serves as a representation of polygon edges defined by clip planes */
class PolyEdge {
    pointA;
    pointB;
    normal;
    constructor(origin, next, normal, z) {
        this.pointA = Point3dVector3d_1.Point3d.create(origin.x, origin.y, z);
        this.pointB = Point3dVector3d_1.Point3d.create(next.x, next.y, z);
        this.normal = normal;
    }
    // Assume both normals are unit length.
    // old logic: use difference of (previously computed) normals as perpendicular to bisector.
    static makeUnitPerpendicularToBisector(edgeA, edgeB, reverse) {
        let candidate = edgeB.normal.minus(edgeA.normal);
        if (candidate.normalize(candidate) === undefined) {
            candidate = Point3dVector3d_1.Vector3d.createStartEnd(edgeA.pointA, edgeB.pointB);
            if (candidate.normalize(candidate) === undefined)
                return undefined;
        }
        if (reverse)
            candidate.scale(-1.0, candidate);
        return candidate;
    }
}
/**
 * A clipping volume defined by a shape (an array of 3d points using only x and y dimensions).
 * May be given either a ClipPlaneSet to store directly, or an array of polygon points as well as other parameters
 * for parsing ClipPlanes from the shape later.
 * @public
 */
class ClipShape extends ClipPrimitive {
    /** Points of the polygon, in the xy plane of the local coordinate system.  */
    _polygon;
    /** optional low z (in local coordinates). */
    _zLow;
    /** optional high z (in local coordinates). */
    _zHigh;
    /** true if this is considered a hole (keep geometry outside of the polygon). */
    _isMask;
    /** transform from local to world. */
    _transformFromClip;
    /** Transform from world to local. */
    _transformToClip;
    constructor(polygon = [], zLow, zHigh, transform, isMask = false, invisible = false) {
        super(undefined, invisible); // ClipPlaneSets will be set up later after storing points
        this._isMask = false;
        this._polygon = polygon;
        this.initSecondaryProps(isMask, zLow, zHigh, transform);
    }
    /** Returns true if this ClipShape is marked as invisible. */
    get invisible() {
        return this._invisible;
    }
    /** Return this transformFromClip, which may be undefined. */
    get transformFromClip() {
        return this._transformFromClip;
    }
    /** Return this transformToClip, which may be undefined. */
    get transformToClip() {
        return this._transformToClip;
    }
    /** Returns true if this ClipShape's transforms are currently set. */
    get transformValid() {
        return this.transformFromClip !== undefined;
    }
    /** Returns true if this ClipShape's lower z boundary is set. */
    get zLowValid() {
        return this._zLow !== undefined;
    }
    /** Returns true if this ClipShape's upper z boundary is set. */
    get zHighValid() {
        return this._zHigh !== undefined;
    }
    /** Return true if this ClipShape has a local to world transform */
    get transformIsValid() {
        return this._transformFromClip !== undefined;
    }
    /** Return this zLow, which may be undefined. */
    get zLow() {
        return this._zLow;
    }
    /** Return this zHigh, which may be undefined. */
    get zHigh() {
        return this._zHigh;
    }
    /** Returns a reference to this ClipShape's polygon array. */
    get polygon() {
        return this._polygon;
    }
    /** Returns true if this ClipShape is a masking set. */
    get isMask() {
        return this._isMask;
    }
    /** Sets the polygon points array of this ClipShape to the array given (by reference). */
    setPolygon(polygon) {
        // Add closure point
        if (!polygon[0].isAlmostEqual(polygon[polygon.length - 1]))
            polygon.push(polygon[0].clone());
        this._polygon = polygon;
    }
    /**
     * If the ClipShape's associated `UnionOfConvexClipPlaneSets` is defined, do nothing.
     * If the ClipShape's associated `UnionOfConvexClipPlaneSets` is undefined, generate it from the `ClipShape`
     * and transform.
     */
    ensurePlaneSets() {
        if (this._clipPlanes !== undefined)
            return;
        this._clipPlanes = UnionOfConvexClipPlaneSets_1.UnionOfConvexClipPlaneSets.createEmpty();
        this.parseClipPlanes(this._clipPlanes);
        if (this._transformFromClip)
            this._clipPlanes.transformInPlace(this._transformFromClip);
    }
    /**
     * Initialize the members of the ClipShape class that may at times be undefined.
     * zLow and zHigh default to Number.MAX_VALUE, and the transform defaults to an identity transform.
     */
    initSecondaryProps(isMask, zLow, zHigh, transform) {
        this._isMask = isMask;
        this._zLow = zLow;
        this._zHigh = zHigh;
        if (transform !== undefined) {
            this._transformFromClip = transform;
            this._transformToClip = transform.inverse(); // could be undefined
        }
        else {
            this._transformFromClip = Transform_1.Transform.createIdentity();
            this._transformToClip = Transform_1.Transform.createIdentity();
        }
    }
    /** Emit json object form */
    toJSON() {
        const shape = {
            points: this._polygon.map((pt) => pt.toJSON()),
        };
        if (this.invisible)
            shape.invisible = true;
        if (this._transformFromClip && !this._transformFromClip.isIdentity)
            shape.trans = this._transformFromClip.toJSON();
        if (this.isMask)
            shape.mask = true;
        if (typeof (this.zLow) !== "undefined" && this.zLow !== -Number.MAX_VALUE)
            shape.zlow = this.zLow;
        if (typeof (this.zHigh) !== "undefined" && this.zHigh !== Number.MAX_VALUE)
            shape.zhigh = this.zHigh;
        return { shape };
    }
    /** Parse `json` to a clip shape. */
    static fromClipShapeJSON(json, result) {
        const shape = json?.shape;
        if (!shape)
            return undefined;
        const points = shape.points ? shape.points.map((pt) => Point3dVector3d_1.Point3d.fromJSON(pt)) : [];
        const trans = shape.trans ? Transform_1.Transform.fromJSON(shape.trans) : undefined;
        const zLow = typeof shape.zlow === "number" ? shape.zlow : undefined;
        const zHigh = typeof shape.zhigh === "number" ? shape.zhigh : undefined;
        const isMask = typeof shape.mask === "boolean" && shape.mask;
        const invisible = typeof shape.invisible === "boolean" && shape.invisible;
        return ClipShape.createShape(points, zLow, zHigh, trans, isMask, invisible, result);
    }
    /** Returns a new ClipShape that is a deep copy of the ClipShape given */
    static createFrom(other, result) {
        const retVal = ClipShape.createEmpty(false, false, undefined, result);
        retVal._invisible = other._invisible;
        for (const point of other._polygon) {
            retVal._polygon.push(point.clone());
        }
        retVal._isMask = other._isMask;
        retVal._zLow = other._zLow;
        retVal._zHigh = other._zHigh;
        retVal._transformToClip = other._transformToClip ? other._transformToClip.clone() : undefined;
        retVal._transformFromClip = other._transformFromClip ? other._transformFromClip.clone() : undefined;
        return retVal;
    }
    /** Create a new ClipShape from an array of points that make up a 2d shape (stores a deep copy of these points). */
    static createShape(polygon = [], zLow, zHigh, transform, isMask = false, invisible = false, result) {
        if (polygon.length < 3)
            return undefined;
        const pPoints = polygon.slice(0);
        // Add closure point.
        if (pPoints[0].isAlmostEqual(pPoints[pPoints.length - 1]))
            pPoints[0].clone(pPoints[pPoints.length - 1]);
        else
            pPoints.push(pPoints[0].clone());
        if (result) {
            result._clipPlanes = undefined; // Start as undefined
            result._invisible = invisible;
            result._polygon = pPoints;
            result.initSecondaryProps(isMask, zLow, zHigh, transform);
            return result;
        }
        else {
            return new ClipShape(pPoints, zLow, zHigh, transform, isMask, invisible);
        }
    }
    /**
     * Create a ClipShape that exists as a 3 dimensional box of the range given. Optionally choose to
     * also store this shape's zLow and zHigh members from the range through the use of a RangePlaneBitMask.
     */
    static createBlock(extremities, clipMask, isMask = false, invisible = false, transform, result) {
        const low = extremities.low;
        const high = extremities.high;
        const blockPoints = [];
        for (let i = 0; i < 5; i++)
            blockPoints.push(Point3dVector3d_1.Point3d.create());
        blockPoints[0].x = blockPoints[3].x = blockPoints[4].x = low.x;
        blockPoints[1].x = blockPoints[2].x = high.x;
        blockPoints[0].y = blockPoints[1].y = blockPoints[4].y = low.y;
        blockPoints[2].y = blockPoints[3].y = high.y;
        return ClipShape.createShape(blockPoints, (ClipMaskXYZRangePlanes.None !== (clipMask & ClipMaskXYZRangePlanes.ZLow)) ? low.z : undefined, (ClipMaskXYZRangePlanes.None !== (clipMask & ClipMaskXYZRangePlanes.ZHigh)) ? high.z : undefined, transform, isMask, invisible, result);
    }
    /** Creates a new ClipShape with undefined members and a polygon points array of zero length. */
    static createEmpty(isMask = false, invisible = false, transform, result) {
        if (result) {
            result._clipPlanes = undefined;
            result._invisible = invisible;
            result._polygon.length = 0;
            result.initSecondaryProps(isMask, undefined, undefined, transform);
            return result;
        }
        return new ClipShape([], undefined, undefined, transform, isMask, invisible);
    }
    /** Checks to ensure that the member polygon has an area, and that the polygon is closed. */
    get isValidPolygon() {
        if (this._polygon.length < 3)
            return false;
        if (!this._polygon[0].isExactEqual(this._polygon[this._polygon.length - 1]))
            return false;
        return true;
    }
    /** Returns a deep copy of this instance of ClipShape, storing in an optional result. */
    clone(result) {
        return ClipShape.createFrom(this, result);
    }
    /**
     * Given the current polygon data, parses clip planes that together form an object, storing the result in
     * the set given, either clip planes or mask planes.
     */
    parseClipPlanes(set) {
        const points = this._polygon;
        if (points.length === 3 && !this._isMask && points[0].isExactEqual(points[points.length - 1])) {
            this.parseLinearPlanes(set, this._polygon[0], this._polygon[1]);
            return true;
        }
        if (!this.isMask) {
            const direction = PolygonOps_1.PolygonOps.testXYPolygonTurningDirections(this.polygon);
            if (direction !== 0) {
                this.parseConvexPolygonPlanes(set, this._polygon, direction, false);
                return true;
            }
        }
        // REMARK:  Pass all polygons to non-convex case.  It will funnel
        // to concave case as appropriate.
        this.parsePolygonPlanes(set, this._polygon, this.isMask);
        return true;
    }
    /**
     * Given a start and end point, populate the given UnionOfConvexClipPlaneSets with ConvexClipPlaneSets
     * defining the bounded region of linear planes. Returns true if successful.
     */
    parseLinearPlanes(set, start, end, cameraFocalLength) {
        // Handles the degenerate case of 2 distinct points (used by select by line).
        const normal = start.vectorTo(end);
        if (normal.magnitude() === 0.0)
            return false;
        normal.normalize(normal);
        const convexSet = ConvexClipPlaneSet_1.ConvexClipPlaneSet.createEmpty();
        if (cameraFocalLength === undefined) {
            const perpendicular = Point2dVector2d_1.Vector2d.create(-normal.y, normal.x);
            convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndPoint(Point3dVector3d_1.Vector3d.create(normal.x, normal.y), Point3dVector3d_1.Point3d.createFrom(start), this._invisible));
            convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndPoint(Point3dVector3d_1.Vector3d.create(-normal.x, -normal.y), Point3dVector3d_1.Point3d.createFrom(end), this._invisible));
            convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndPoint(Point3dVector3d_1.Vector3d.create(perpendicular.x, perpendicular.y), Point3dVector3d_1.Point3d.createFrom(start), this._invisible));
            convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndPoint(Point3dVector3d_1.Vector3d.create(-perpendicular.x, -perpendicular.y), Point3dVector3d_1.Point3d.createFrom(start), this._invisible));
        }
        else {
            const start3d = Point3dVector3d_1.Point3d.create(start.x, start.y, -cameraFocalLength);
            const end3d = Point3dVector3d_1.Point3d.create(end.x, end.y, -cameraFocalLength);
            const vecEnd3d = Point3dVector3d_1.Vector3d.createFrom(end3d);
            const perpendicular = vecEnd3d.crossProduct(Point3dVector3d_1.Vector3d.createFrom(start3d)).normalize();
            let endNormal = Point3dVector3d_1.Vector3d.createFrom(start3d).crossProduct(perpendicular).normalize();
            convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndDistance(perpendicular, 0.0, this._invisible));
            convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndDistance(endNormal, 0.0, this._invisible));
            perpendicular.negate();
            endNormal = vecEnd3d.crossProduct(perpendicular).normalize();
            convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndDistance(perpendicular, 0.0, this._invisible));
            convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndDistance(endNormal, 0.0, this._invisible));
        }
        convexSet.addZClipPlanes(this._invisible, this._zLow, this._zHigh);
        set.addConvexSet(convexSet);
        return true;
    }
    /**
     * Given a convex polygon defined as an array of points, populate the given UnionOfConvexClipPlaneSets with
     * ConvexClipPlaneSets defining the bounded region. Returns true if successful.
     */
    parseConvexPolygonPlanes(set, polygon, direction, buildExteriorClipper, cameraFocalLength) {
        const samePointTolerance = 1.0e-8; // This could possibly be replaced with more widely used constants
        const edges = [];
        //  const reverse = (direction < 0) !== this._isMask;
        const reverse = direction < 0;
        for (let i = 0; i < polygon.length - 1; i++) {
            const z = (cameraFocalLength === undefined) ? 0.0 : -cameraFocalLength;
            const dir = Point3dVector3d_1.Vector3d.createStartEnd(polygon[i], polygon[i + 1]);
            const magnitude = dir.magnitude();
            dir.normalize(dir);
            if (magnitude > samePointTolerance) {
                const normal = Point3dVector3d_1.Vector3d.create(reverse ? dir.y : -dir.y, reverse ? -dir.x : dir.x);
                edges.push(new PolyEdge(polygon[i], polygon[i + 1], normal, z));
            }
        }
        if (edges.length < 3) {
            return false;
        }
        if (buildExteriorClipper) {
            const last = edges.length - 1;
            for (let i = 0; i <= last; i++) {
                const edge = edges[i];
                const prevEdge = edges[i ? (i - 1) : last];
                const nextEdge = edges[(i === last) ? 0 : (i + 1)];
                const convexSet = ConvexClipPlaneSet_1.ConvexClipPlaneSet.createEmpty();
                const previousPerpendicular = PolyEdge.makeUnitPerpendicularToBisector(prevEdge, edge, !reverse);
                const nextPerpendicular = PolyEdge.makeUnitPerpendicularToBisector(edge, nextEdge, reverse);
                // Create three-sided fans from each edge.   Note we could define the correct region
                // with only two planes for edge, but cannot then designate the "interior" status of the edges accurately.
                if (previousPerpendicular)
                    convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndPoint(previousPerpendicular, edge.pointA, this._invisible, true));
                convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndPoint(edge.normal, edge.pointB, this._invisible, false));
                if (nextPerpendicular)
                    convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndPoint(nextPerpendicular, nextEdge.pointA, this._invisible, true));
                set.addConvexSet(convexSet);
                set.addOutsideZClipSets(this._invisible, this._zLow, this._zHigh);
            }
        }
        else {
            const convexSet = ConvexClipPlaneSet_1.ConvexClipPlaneSet.createEmpty();
            if (cameraFocalLength === undefined) {
                for (const edge of edges)
                    convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndPoint(Point3dVector3d_1.Vector3d.create(edge.normal.x, edge.normal.y), edge.pointA));
            }
            else {
                if (reverse)
                    for (const edge of edges)
                        convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndDistance(Point3dVector3d_1.Vector3d.createFrom(edge.pointA).crossProduct(Point3dVector3d_1.Vector3d.createFrom(edge.pointB)).normalize(), 0.0));
                else
                    for (const edge of edges)
                        convexSet.planes.push(ClipPlane_1.ClipPlane.createNormalAndDistance(Point3dVector3d_1.Vector3d.createFrom(edge.pointB).crossProduct(Point3dVector3d_1.Vector3d.createFrom(edge.pointA)).normalize(), 0.0));
            }
            convexSet.addZClipPlanes(this._invisible, this._zLow, this._zHigh);
            set.addConvexSet(convexSet);
        }
        return true;
    }
    /**
     * Given a (possibly non-convex) polygon defined as an array of points, populate the given UnionOfConvexClipPlaneSets
     * with multiple ConvexClipPlaneSets defining the bounded region. Returns true if successful.
     */
    parsePolygonPlanes(set, polygon, isMask, cameraFocalLength) {
        const cleanPolygon = PolylineOps_1.PolylineOps.compressDanglers(polygon, true);
        const announceFace = (_graph, edge) => {
            if (!edge.isMaskSet(Graph_1.HalfEdgeMask.EXTERIOR)) {
                const convexFacetPoints = edge.collectAroundFace((node) => {
                    if (!node.isMaskSet(Graph_1.HalfEdgeMask.EXTERIOR))
                        return Point3dVector3d_1.Point3d.create(node.x, node.y, 0);
                });
                // parseConvexPolygonPlanes expects a closed loop (pushing the reference doesn't matter)
                convexFacetPoints.push(convexFacetPoints[0].clone());
                const direction = PolygonOps_1.PolygonOps.testXYPolygonTurningDirections(convexFacetPoints); // ###TODO: Can we expect a direction coming out of graph facet?
                this.parseConvexPolygonPlanes(set, convexFacetPoints, direction, false, cameraFocalLength);
            }
            return true;
        };
        if (isMask) {
            const polygonA = PointHelpers_1.Point3dArray.clonePoint3dArray(cleanPolygon);
            const hullAndInlets = AlternatingConvexClipTree_1.AlternatingCCTreeNode.createHullAndInletsForPolygon(polygonA);
            const allLoops = hullAndInlets.extractLoops();
            if (allLoops.length === 0)
                return false;
            const hull = allLoops[0];
            const direction1 = PolygonOps_1.PolygonOps.testXYPolygonTurningDirections(hull); // ###TODO: Can we expect a direction coming out of graph facet?
            this.parseConvexPolygonPlanes(set, hull, -direction1, true, cameraFocalLength);
            for (let i = 1; i < allLoops.length; i++) {
                const triangulatedPolygon = Triangulation_1.Triangulator.createTriangulatedGraphFromSingleLoop(allLoops[i]);
                if (triangulatedPolygon) {
                    Triangulation_1.Triangulator.flipTriangles(triangulatedPolygon);
                    triangulatedPolygon.announceFaceLoops(announceFace);
                }
            }
            return true;
        }
        else {
            const triangulatedPolygon = Triangulation_1.Triangulator.createTriangulatedGraphFromSingleLoop(cleanPolygon);
            if (triangulatedPolygon === undefined)
                return false;
            Triangulation_1.Triangulator.flipTriangles(triangulatedPolygon);
            triangulatedPolygon.announceFaceLoops(announceFace);
        }
        return true;
    }
    /**
     * 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).
     * @param matrix matrix to apply.
     * * 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.
     */
    multiplyPlanesByMatrix4d(matrix, invert = true, transpose = true) {
        this.ensurePlaneSets();
        return super.multiplyPlanesByMatrix4d(matrix, invert, transpose);
    }
    /**
     * Apply `transform` to the local to world (`transformFromClip`) transform.
     * * The world to local transform (`transformToClip` is recomputed from the (changed) `transformToClip`
     * * the transform is passed to the base class to be applied to clip plane form of the clipper.
     */
    transformInPlace(transform) {
        if (transform.isIdentity)
            return true;
        super.transformInPlace(transform);
        if (this._transformFromClip)
            transform.multiplyTransformTransform(this._transformFromClip, this._transformFromClip);
        else
            this._transformFromClip = transform.clone();
        this._transformToClip = this._transformFromClip.inverse(); // could be undefined
        return true;
    }
    /**
     * Return true if
     * * at least one point is defined
     * * The local to world transform (transformFromClip) either
     *   * is undefined
     *   * has no xy parts in its column Z (local frame Z is parallel to global Z)
     */
    get isXYPolygon() {
        if (this._polygon.length === 0) // Note: This is a lenient check, as points array could also contain less than 3 points (not a polygon)
            return false;
        if (this._transformFromClip === undefined)
            return true;
        const zVector = this._transformFromClip.matrix.columnZ();
        return zVector.magnitudeXY() < 1.0e-8;
    }
    /** Transform the input point in place using this instance's `transformToClip` member */
    performTransformToClip(point) {
        if (this._transformToClip !== undefined)
            this._transformToClip.multiplyPoint3d(point, point);
    }
    /** Transform the input point in place using this instance's `transformFromClip` member */
    performTransformFromClip(point) {
        if (this._transformFromClip !== undefined)
            this._transformFromClip.multiplyPoint3d(point, point);
    }
}
exports.ClipShape = ClipShape;
//# sourceMappingURL=ClipPrimitive.js.map