"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.CurveCurveCloseApproachXY = void 0;
/** @packageDocumentation
 * @module Curve
 */
const core_bentley_1 = require("@itwin/core-bentley");
const BSplineCurve_1 = require("../../bspline/BSplineCurve");
const Geometry_1 = require("../../Geometry");
const GeometryHandler_1 = require("../../geometry3d/GeometryHandler");
const GrowableFloat64Array_1 = require("../../geometry3d/GrowableFloat64Array");
const Point3dVector3d_1 = require("../../geometry3d/Point3dVector3d");
const Range_1 = require("../../geometry3d/Range");
const Newton_1 = require("../../numerics/Newton");
const Polynomials_1 = require("../../numerics/Polynomials");
const SmallSystem_1 = require("../../numerics/SmallSystem");
const Arc3d_1 = require("../Arc3d");
const CurveChainWithDistanceIndex_1 = require("../CurveChainWithDistanceIndex");
const CurveCollection_1 = require("../CurveCollection");
const CurveLocationDetail_1 = require("../CurveLocationDetail");
const LineSegment3d_1 = require("../LineSegment3d");
const LineString3d_1 = require("../LineString3d");
// cspell:word XYRR currentdFdX
/**
 * Handler class for XY close approach between _geometryB and another geometry.
 * * Approach means the XY distance (z is ignored) between _geometryB and another geometry.
 * * Closest approach is a measure of the proximity of one curve to another. It's the length of the shortest line
 * segment perpendicular to both curves; if the curves intersect, the closest approach is zero. In the context of
 * this class, z-coordinates are ignored, so the closest approach is as seen in the top view. If you have coplanar
 * input curves and want to find closest approach in their plane, rotate them first into a plane parallel to the
 * xy-plane, then afterward, rotate the results back as required.
 * * Close approach can also be from a curve endpoint perpendicular to another curve or from a curve endpoint to
 * another curve endpoint.
 * * Instances are initialized and called from CurveCurve.
 * * geometryB is saved for later reference.
 * @internal
 */
class CurveCurveCloseApproachXY extends GeometryHandler_1.RecurseToCurvesGeometryHandler {
    _geometryB;
    setGeometryB(geometryB) {
        this._geometryB = geometryB;
    }
    /**
     * Maximum XY distance (z is ignored). Approach larger than this is not interesting.
     * This is caller defined and can be undefined.
     */
    _maxDistanceToAccept;
    /** Squared max distance. This is private, and is forced to at least small metric distance squared. */
    _maxDistanceSquared;
    /**
     * Start and end points of line segments that meet closest approach criteria, i.e., they are perpendicular to
     * both curves and their length is smaller than _maxDistanceToAccept.
     */
    _results;
    static _workPointAA0 = Point3dVector3d_1.Point3d.create();
    static _workPointAA1 = Point3dVector3d_1.Point3d.create();
    static _workPointBB0 = Point3dVector3d_1.Point3d.create();
    static _workPointBB1 = Point3dVector3d_1.Point3d.create();
    static _workPointB = Point3dVector3d_1.Point3d.create();
    /**
     * Constructor.
     * @param geometryB second curve for intersection. Saved for reference by specific handler methods.
     */
    constructor(geometryB) {
        super();
        this.setGeometryB(geometryB);
        this._maxDistanceSquared = Geometry_1.Geometry.smallMetricDistanceSquared;
        this._results = [];
    }
    /** Set the (possibly undefined) max XY distance (z is ignored) to accept. */
    set maxDistanceToAccept(value) {
        this._maxDistanceToAccept = value;
        if (this._maxDistanceToAccept !== undefined && this._maxDistanceToAccept > 0)
            this._maxDistanceSquared = this._maxDistanceToAccept * this._maxDistanceToAccept;
    }
    /** Access the (possibly undefined) max XY distance (z is ignored) to accept. */
    get maxDistanceToAccept() {
        return this._maxDistanceToAccept;
    }
    /** Ask if the maxDistanceToAccept value is defined and positive */
    get isMaxDistanceSet() {
        return this._maxDistanceToAccept !== undefined && this._maxDistanceToAccept > 0;
    }
    /** Reset the geometry and flags, leaving all other parts unchanged (and preserving accumulated intersections) */
    resetGeometry(geometryB) {
        this.setGeometryB(geometryB);
    }
    /** returns true if `fraction` is in [0,1] within tolerance */
    acceptFraction(fraction, fractionTol = 1.0e-12) {
        if (fraction < -fractionTol)
            return false;
        if (fraction > 1.0 + fractionTol)
            return false;
        return true;
    }
    /**
     * Return the results structure for the intersection calculation, structured as an array of CurveLocationDetailPair.
     * @param reinitialize if true, a new results structure is created for use by later calls.
     */
    grabPairedResults(reinitialize = false) {
        const result = this._results;
        if (reinitialize)
            this._results = [];
        return result;
    }
    /**
     * If distance between pointA and pointB is less than maxDistance, record CurveLocationDetailPair which is
     * the approach from pointA to pointB.
     */
    testAndRecordPointPairApproach(cpA, fA, pointA, cpB, fB, pointB, reversed) {
        const d2 = pointA.distanceSquaredXY(pointB);
        if (d2 < this._maxDistanceSquared) {
            const detailA = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(cpA, fA, pointA);
            const detailB = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(cpB, fB, pointB);
            const pair = CurveLocationDetail_1.CurveLocationDetailPair.createCapture(detailA, detailB);
            if (reversed)
                pair.swapDetails();
            this._results.push(pair);
        }
    }
    /**
     * Create a close approach pair if XY distance is within maxDistance.
     * @param localFractionA a fraction on first curve
     * @param cpA the first curve
     * @param fractionA0 start of the first curve
     * @param fractionA1 end of the first curve
     * @param localFractionB a fraction on second curve
     * @param cpB the second curve
     * @param fractionB0 start of the second curve
     * @param fractionB1 end of the second curve
     * @param reversed whether to reverse the details in the pair (e.g., so that detailB refers to geometryB).
     * @param intervalDetails optional CurveLocationDetailPair
     */
    recordPointWithLocalFractions(localFractionA, cpA, fractionA0, fractionA1, localFractionB, cpB, fractionB0, fractionB1, reversed, intervalDetails) {
        let globalFractionA, globalFractionB;
        let globalFractionA1, globalFractionB1;
        const isInterval = intervalDetails !== undefined &&
            intervalDetails.detailA.hasFraction1 &&
            intervalDetails.detailB.hasFraction1;
        if (isInterval) {
            globalFractionA = Geometry_1.Geometry.interpolate(fractionA0, intervalDetails.detailA.fraction, fractionA1);
            globalFractionB = Geometry_1.Geometry.interpolate(fractionB0, intervalDetails.detailB.fraction, fractionB1);
            globalFractionA1 = Geometry_1.Geometry.interpolate(fractionA0, intervalDetails.detailA.fraction1, fractionA1);
            globalFractionB1 = Geometry_1.Geometry.interpolate(fractionB0, intervalDetails.detailB.fraction1, fractionB1);
        }
        else {
            globalFractionA = globalFractionA1 = Geometry_1.Geometry.interpolate(fractionA0, localFractionA, fractionA1);
            globalFractionB = globalFractionB1 = Geometry_1.Geometry.interpolate(fractionB0, localFractionB, fractionB1);
        }
        // ignore duplicate of most recent approach
        const numPrevious = this._results.length;
        if (numPrevious > 0 && !isInterval) {
            const oldDetailA = this._results[numPrevious - 1].detailA;
            const oldDetailB = this._results[numPrevious - 1].detailB;
            if (reversed) {
                if (oldDetailB.isSameCurveAndFraction({ curve: cpA, fraction: globalFractionA }) &&
                    oldDetailA.isSameCurveAndFraction({ curve: cpB, fraction: globalFractionB }))
                    return;
            }
            else {
                if (oldDetailA.isSameCurveAndFraction({ curve: cpA, fraction: globalFractionA }) &&
                    oldDetailB.isSameCurveAndFraction({ curve: cpB, fraction: globalFractionB }))
                    return;
            }
        }
        const detailA = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(cpA, globalFractionA, cpA.fractionToPoint(globalFractionA));
        const detailB = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(cpB, globalFractionB, cpB.fractionToPoint(globalFractionB));
        if (isInterval) {
            detailA.captureFraction1Point1(globalFractionA1, cpA.fractionToPoint(globalFractionA1));
            detailB.captureFraction1Point1(globalFractionB1, cpB.fractionToPoint(globalFractionB1));
        }
        else {
            const d2 = detailA.point.distanceSquaredXY(detailB.point);
            if (d2 > this._maxDistanceSquared)
                return;
            detailA.setIntervalRole(CurveLocationDetail_1.CurveIntervalRole.isolated);
            detailB.setIntervalRole(CurveLocationDetail_1.CurveIntervalRole.isolated);
        }
        if (reversed) {
            this._results.push(new CurveLocationDetail_1.CurveLocationDetailPair(detailB, detailA));
        }
        else {
            this._results.push(new CurveLocationDetail_1.CurveLocationDetailPair(detailA, detailB));
        }
    }
    /**
     * Capture a close approach pair that has point and local fraction but not curve.
     * * Record the pair, each detail modified with global fraction and input curve.
     * * Pair is neither modified nor recorded if it would be a duplicate of the last recorded pair.
     * @param pair details computed with local fractions
     * @param cpA curveA
     * @param fractionA0 global start fraction on curveA
     * @param fractionA1 global end fraction on curveA
     * @param cpB curveB
     * @param fractionB0 global start fraction on curveB
     * @param fractionB1 global end fraction on curveB
     * @param reversed whether to reverse the details in the pair (e.g., so that detailB refers to geometryB).
     */
    capturePairWithLocalFractions(pair, cpA, fractionA0, fractionA1, cpB, fractionB0, fractionB1, reversed) {
        const globalFractionA = Geometry_1.Geometry.interpolate(fractionA0, pair.detailA.fraction, fractionA1);
        const globalFractionB = Geometry_1.Geometry.interpolate(fractionB0, pair.detailB.fraction, fractionB1);
        // ignore duplicate of most recent pair
        const numPrevious = this._results.length;
        if (numPrevious > 0) {
            const oldDetailA = this._results[numPrevious - 1].detailA;
            const oldDetailB = this._results[numPrevious - 1].detailB;
            if (reversed) {
                if (oldDetailB.isSameCurveAndFraction({ curve: cpA, fraction: globalFractionA }) &&
                    oldDetailA.isSameCurveAndFraction({ curve: cpB, fraction: globalFractionB }))
                    return;
            }
            else {
                if (oldDetailA.isSameCurveAndFraction({ curve: cpA, fraction: globalFractionA }) &&
                    oldDetailB.isSameCurveAndFraction({ curve: cpB, fraction: globalFractionB }))
                    return;
            }
        }
        // recompute the points just in case
        CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(cpA, globalFractionA, pair.detailA);
        CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(cpB, globalFractionB, pair.detailB);
        pair.detailA.setIntervalRole(CurveLocationDetail_1.CurveIntervalRole.isolated);
        pair.detailB.setIntervalRole(CurveLocationDetail_1.CurveIntervalRole.isolated);
        if (reversed)
            pair.swapDetails();
        this._results.push(pair);
    }
    /**
     * Emit recordPoint for multiple pairs (on full curve) if within maxDistance.
     * @param cpA first curve primitive (possibly different from curve in detailA, but fraction compatible)
     * @param cpB second curve primitive (possibly different from curve in detailA, but fraction compatible)
     * @param pairs array of pairs
     * @param reversed whether to reverse the details in the pair (e.g., so that detailB refers to geometryB).
     */
    recordPairs(cpA, cpB, pairs, reversed) {
        if (pairs !== undefined) {
            for (const p of pairs) {
                this.recordPointWithLocalFractions(p.detailA.fraction, cpA, 0, 1, p.detailB.fraction, cpB, 0, 1, reversed, p);
            }
        }
    }
    /**
     * Record fully assembled (but possibly reversed) detail pair.
     * @param detailA first detail
     * @param detailB second detail
     * @param reversed whether to reverse the details in the pair (e.g., so that detailB refers to geometryB).
     */
    captureDetailPair(detailA, detailB, reversed) {
        if (detailA && detailB) {
            if (reversed) {
                this._results.push(CurveLocationDetail_1.CurveLocationDetailPair.createCapture(detailB, detailA));
            }
            else {
                this._results.push(CurveLocationDetail_1.CurveLocationDetailPair.createCapture(detailA, detailB));
            }
        }
    }
    static updatePointToSegmentDistance(fractionA, pointA, pointB0, pointB1, fractionB, maxDistanceSquared, closestApproach) {
        let updated = false;
        if (fractionB < 0)
            fractionB = 0;
        else if (fractionB > 1)
            fractionB = 1;
        this._workPointB = pointB0.interpolate(fractionB, pointB1, this._workPointB);
        const distanceSquared = this._workPointB.distanceSquaredXY(pointA);
        if (distanceSquared <= Math.min(maxDistanceSquared, closestApproach.detailA.a)) {
            closestApproach.detailA.setFP(fractionA, pointA, undefined, distanceSquared);
            closestApproach.detailB.setFP(fractionB, this._workPointB, undefined, distanceSquared);
            updated = true;
        }
        return updated;
    }
    /**
     * Return fractions of close approach within maxDistance between two line segments (a0,a1) and (b0,b1).
     * * Math details can be found at core/geometry/internaldocs/Curve.md
     * @param a0 start point of line a
     * @param a1 end point of line a
     * @param b0 start point of line b
     * @param b1 end point of line b
     * @param maxDistanceSquared maximum distance squared (assumed to be positive)
     * @returns the fractional (not xy) coordinates in result.x and result.y. result.x is fraction on line a.
     * result.y is fraction on line b.
     */
    static segmentSegmentBoundedApproach(a0, a1, b0, b1, maxDistanceSquared) {
        const ux = a1.x - a0.x;
        const uy = a1.y - a0.y;
        const vx = b1.x - b0.x;
        const vy = b1.y - b0.y;
        const e00x = b0.x - a0.x;
        const e00y = b0.y - a0.y;
        const e01x = b1.x - a0.x;
        const e01y = b1.y - a0.y;
        const e10x = b0.x - a1.x;
        const e10y = b0.y - a1.y;
        const hab0 = Geometry_1.Geometry.crossProductXYXY(ux, uy, e00x, e00y);
        const hab1 = Geometry_1.Geometry.crossProductXYXY(ux, uy, e01x, e01y);
        const hba0 = -Geometry_1.Geometry.crossProductXYXY(vx, vy, e00x, e00y);
        const hba1 = -Geometry_1.Geometry.crossProductXYXY(vx, vy, e10x, e10y);
        if (hab0 * hab1 < 0.0 && hba0 * hba1 < 0.0) { // true intersection, strictly within both segments
            const fractionA = -hba0 / (hba1 - hba0);
            const fractionB = -hab0 / (hab1 - hab0);
            return CurveLocationDetail_1.CurveLocationDetailPair.createCapture(CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(undefined, fractionA, a0.interpolate(fractionA, a1)), CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(undefined, fractionB, b0.interpolate(fractionB, b1)));
        }
        // there's no intersection, so find the closest approach within maxDistance from an endpoint
        const closestApproach = new CurveLocationDetail_1.CurveLocationDetailPair();
        closestApproach.detailA.a = 2 * maxDistanceSquared; // init to an approach that's too far away
        let reversed = false;
        const uu = Geometry_1.Geometry.hypotenuseSquaredXY(ux, uy);
        if (hab0 * hab0 <= maxDistanceSquared * uu) { // test distance of b0 to u
            const fractionA = Geometry_1.Geometry.safeDivideFraction(Geometry_1.Geometry.dotProductXYXY(ux, uy, e00x, e00y), uu, 0.0);
            if (this.updatePointToSegmentDistance(0, b0, a0, a1, fractionA, maxDistanceSquared, closestApproach))
                reversed = true;
        }
        if (hab1 * hab1 <= maxDistanceSquared * uu) { // test distance of b1 to u
            const fractionA = Geometry_1.Geometry.safeDivideFraction(Geometry_1.Geometry.dotProductXYXY(ux, uy, e01x, e01y), uu, 0.0);
            if (this.updatePointToSegmentDistance(1, b1, a0, a1, fractionA, maxDistanceSquared, closestApproach))
                reversed = true;
        }
        const vv = Geometry_1.Geometry.hypotenuseSquaredXY(vx, vy);
        if (hba0 * hba0 <= maxDistanceSquared * vv) { // test distance of a0 to v
            const fractionB = Geometry_1.Geometry.safeDivideFraction(-Geometry_1.Geometry.dotProductXYXY(vx, vy, e00x, e00y), vv, 0.0);
            if (this.updatePointToSegmentDistance(0, a0, b0, b1, fractionB, maxDistanceSquared, closestApproach))
                reversed = false;
        }
        if (hba1 * hba1 <= maxDistanceSquared * vv) { // test distance of a1 to v
            const fractionB = Geometry_1.Geometry.safeDivideFraction(-Geometry_1.Geometry.dotProductXYXY(vx, vy, e10x, e10y), vv, 0.0);
            if (this.updatePointToSegmentDistance(1, a1, b0, b1, fractionB, maxDistanceSquared, closestApproach))
                reversed = false;
        }
        if (closestApproach.detailA.a > maxDistanceSquared)
            return undefined;
        if (reversed)
            closestApproach.swapDetails();
        return closestApproach;
    }
    /**
     * Check different combination of fractions on curveA and curveB. If distance between points at 2 fractions
     * is less than maxDistance, record CurveLocationDetailPair which is the approach between the 2 points.
     * Optionally, record close approaches of one curve's points if they fall between the other curve's points.
     * * If an input curve is a LineString3d, then the corresponding fractions must define a segment of the line string.
     * @param cpA curveA
     * @param fA0 fraction0 on curveA
     * @param fA1 fraction1 on curveA
     * @param testProjectionOnA whether to record projections of the given curveB points onto curveA
     * @param cpB curveB
     * @param fB0 fraction0 on curveB
     * @param fB1 fraction0 on curveB
     * @param testProjectionOnB whether to record projections of the given curveA points onto curveB
     * @param reversed whether to reverse the details in the pair (e.g., so that detailB refers to geometryB).
     */
    testAndRecordFractionalPairApproach(cpA, fA0, fA1, testProjectionOnA, cpB, fB0, fB1, testProjectionOnB, reversed) {
        const pointA0 = cpA.fractionToPoint(fA0);
        const pointA1 = cpA.fractionToPoint(fA1);
        const pointB0 = cpB.fractionToPoint(fB0);
        const pointB1 = cpB.fractionToPoint(fB1);
        this.testAndRecordPointPairApproach(cpA, fA0, pointA0, cpB, fB0, pointB0, reversed);
        this.testAndRecordPointPairApproach(cpA, fA1, pointA1, cpB, fB0, pointB0, reversed);
        this.testAndRecordPointPairApproach(cpA, fA0, pointA0, cpB, fB1, pointB1, reversed);
        this.testAndRecordPointPairApproach(cpA, fA1, pointA1, cpB, fB1, pointB1, reversed);
        if (testProjectionOnB) {
            this.testAndRecordProjection(cpA, fA0, pointA0, cpB, fB0, fB1, reversed);
            this.testAndRecordProjection(cpA, fA1, pointA1, cpB, fB0, fB1, reversed);
        }
        if (testProjectionOnA) {
            this.testAndRecordProjection(cpB, fB0, pointB0, cpA, fA0, fA1, !reversed);
            this.testAndRecordProjection(cpB, fB1, pointB1, cpA, fA0, fA1, !reversed);
        }
    }
    /**
     * Return XY closest approach between a curve primitive and a point.
     * Currently, this function only supports Arc3d and LineSegment.
     * Note that this function doesn't handle endpoints.
     */
    getPointCurveClosestApproachXYNewton(curveP, pointQ) {
        if (!(curveP instanceof Arc3d_1.Arc3d) && !(curveP instanceof LineSegment3d_1.LineSegment3d)) {
            (0, core_bentley_1.assert)(false, "getPointCurveClosestApproachXYNewton only supports Arc3d and LineSegment");
        }
        const seeds = [0.2, 0.4, 0.6, 0.8]; // HEURISTIC: arcs have up to 4 perpendiculars; lines have only 1
        const newtonEvaluator = new Newton_1.CurvePointCloseApproachXYRtoRD(curveP, pointQ);
        const newtonSearcher = new Newton_1.Newton1dUnbounded(newtonEvaluator, 100); // observed convergence to 1.0e-11 in 66 iters
        let minCloseApproachLength = Geometry_1.Geometry.largeCoordinateResult;
        let minCurvePFraction;
        let minPointP;
        for (const seed of seeds) {
            newtonSearcher.setX(seed);
            if (newtonSearcher.runIterations()) {
                const curvePFraction = newtonSearcher.getX();
                if (this.acceptFraction(curvePFraction)) {
                    const pointP = curveP.fractionToPoint(curvePFraction);
                    const closeApproachLength = pointP.distanceSquaredXY(pointQ);
                    if (closeApproachLength < minCloseApproachLength) {
                        minCloseApproachLength = closeApproachLength;
                        minCurvePFraction = curvePFraction;
                        minPointP = pointP;
                    }
                }
            }
        }
        if (minCurvePFraction && minPointP)
            return CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(curveP, minCurvePFraction, minPointP);
        return undefined;
    }
    /**
     * Find the closest xy approach between `pointA` and `cpB`. Add the approach if it's within `fB0` and `fB1`.
     * * Does not test the endpoints of `cpB`.
     * * The only types supported for `cpB` are Arc3d, LineSegment3d, and LineString3d.
     * * If `cpB` is a LineString3d, then the interval `[fB0, fB1]` must correspond to a segment of the line string.
    */
    testAndRecordProjection(cpA, fA, pointA, cpB, fB0, fB1, reversed) {
        let detail;
        if (cpB instanceof LineString3d_1.LineString3d) {
            const segParamsB = cpB.globalFractionToSegmentIndexAndLocalFraction(fB0 <= fB1 ? fB0 : fB1);
            const segIndexB = (segParamsB.fraction < 0.999999) ? segParamsB.index : segParamsB.index + 1;
            const segmentB = cpB.getIndexedSegment(segIndexB);
            if (segmentB && (detail = this.getPointCurveClosestApproachXYNewton(segmentB, pointA)))
                LineString3d_1.LineString3d.convertLocalToGlobalDetail(detail, segIndexB, cpB.numEdges(), cpB);
        }
        else {
            detail = this.getPointCurveClosestApproachXYNewton(cpB, pointA);
        }
        if (detail) {
            const fB = Geometry_1.Geometry.restrictToInterval(detail.fraction, fB0, fB1);
            if (fB === detail.fraction) { // if fraction is within fB0 and fB1
                this.testAndRecordPointPairApproach(cpA, fA, pointA, cpB, detail.fraction, detail.point, reversed);
            }
        }
    }
    /**
     * Compute closest xy approach of two line segments.
     * Filter by extension rules.
     * Record with fraction mapping.
     * * The fraction mappings allow portions of a linestring to be passed here.
     */
    computeSegmentSegment3D(cpA, pointA0, fractionA0, pointA1, fractionA1, cpB, pointB0, fractionB0, pointB1, fractionB1, reversed) {
        // compute a pair with fractions local to segments
        const approach = CurveCurveCloseApproachXY.segmentSegmentBoundedApproach(pointA0, pointA1, pointB0, pointB1, this._maxDistanceSquared);
        // adjust the pair to refer to input curves and global fractions, then record it if new
        if (approach) {
            approach.detailA.setCurve(cpA);
            approach.detailB.setCurve(cpB);
            this.capturePairWithLocalFractions(approach, cpA, fractionA0, fractionA1, cpB, fractionB0, fractionB1, reversed);
        }
    }
    /** Low level dispatch of segment with segment. */
    dispatchSegmentSegment(cpA, pointA0, fractionA0, pointA1, fractionA1, cpB, pointB0, fractionB0, pointB1, fractionB1, reversed) {
        this.computeSegmentSegment3D(cpA, pointA0, fractionA0, pointA1, fractionA1, cpB, pointB0, fractionB0, pointB1, fractionB1, reversed);
    }
    /**
     * Compute the perpendiculars between a line segment and an arc, without extending either curve.
     * * One or two perpendiculars will be found.
     * * Each perpendicular segment starts or ends on the arc where the arc tangent is parallel to the line tangent.
     * * Perpendiculars from an endpoint are not explicitly computed.
     * @param cpA line segment or line string; if it is a line string, then the fractions must specify a segment
     * @param pointA0 start point of the segment
     * @param fractionA0 fraction of the start of the segment
     * @param pointA1 end point of the segment
     * @param fractionA1 fraction of the end of the segment
     * @param arc the arc
     * @param reversed swap the details in the recorded pair (default: false)
     */
    allPerpendicularsSegmentArcBounded(cpA, pointA0, fractionA0, pointA1, fractionA1, arc, reversed = false) {
        const dotUT = arc.vector0.crossProductStartEndXY(pointA0, pointA1);
        const dotVT = arc.vector90.crossProductStartEndXY(pointA0, pointA1);
        const parallelRadians = Math.atan2(dotVT, dotUT);
        for (const radians1 of [parallelRadians, parallelRadians + Math.PI]) {
            const arcPoint = arc.radiansToPoint(radians1);
            const fArc = arc.sweep.radiansToSignedPeriodicFraction(radians1);
            if (this.acceptFraction(fArc)) { // reject solution outside arc sweep
                const fLine = SmallSystem_1.SmallSystem.lineSegment3dXYClosestPointUnbounded(pointA0, pointA1, arcPoint);
                if (fLine !== undefined && this.acceptFraction(fLine))
                    this.recordPointWithLocalFractions(fLine, cpA, fractionA0, fractionA1, fArc, arc, 0, 1, reversed);
            }
        }
    }
    /**
     * Low level dispatch of line segment with arc.
     * Find close approaches within maxDistance between a line segments (pointA0, pointA1) and an arc.
     * To consider:
     * 1) intersection between arc and segment.
     * 2) endpoints to endpoints or endpoints projection to the other curve.
     * 3) arc tangent parallel to line segment (or line string).
     * @param cpA curve A (line segment or line string; if it is a line string, then the fractions must specify a segment)
     * @param pointA0 start point of the segment
     * @param fractionA0 fraction of the start of the segment
     * @param pointA1 end point of the segment
     * @param fractionA1 fraction of the end of the segment
     * @param arc the arc
     * @param reversed whether to reverse the details in the pair (e.g., so that detailB refers to geometryB).
     */
    dispatchSegmentArc(cpA, pointA0, fractionA0, pointA1, fractionA1, arc, reversed) {
        // 1) intersection between arc and line segment (or string).
        // Suppose:
        // Arc: X = C + cU + sV where c = cos(theta) and s = sin(theta)
        // Line: contains points A0 and A1
        // The arc intersects the line at point X if det(A0, A1, X) = 0 with homogeneous xyw points and vectors.
        // With equational X:  det(A0, A1, C) + c*det(A0, A1, U) + s*det(A0, A1, V) = 0.
        // solve for theta.
        // evaluate points.
        // project back to line.
        let intersectionFound = false;
        const data = arc.toTransformedVectors();
        const pointA0Local = pointA0;
        const pointA1Local = pointA1;
        const alpha = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.center, 1); //  det(A0, A1, C)
        const beta = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector0, 0); // det(A0, A1, U)
        const gamma = Geometry_1.Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector90, 0); // det(A0, A1, V)
        const cosines = new GrowableFloat64Array_1.GrowableFloat64Array(2);
        const sines = new GrowableFloat64Array_1.GrowableFloat64Array(2);
        const radians = new GrowableFloat64Array_1.GrowableFloat64Array(2);
        const numRoots = Polynomials_1.AnalyticRoots.appendImplicitLineUnitCircleIntersections(// solve the equation
        alpha, beta, gamma, cosines, sines, radians);
        for (let i = 0; i < numRoots; i++) {
            const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(i), data.vector90, sines.atUncheckedIndex(i));
            const arcFraction = data.sweep.radiansToSignedPeriodicFraction(radians.atUncheckedIndex(i));
            const lineFraction = SmallSystem_1.SmallSystem.lineSegment3dXYClosestPointUnbounded(pointA0Local, pointA1Local, arcPoint);
            // only add if the point is within the start and end fractions of both line segment and arc
            if (lineFraction !== undefined && this.acceptFraction(lineFraction) && this.acceptFraction(arcFraction)) {
                this.recordPointWithLocalFractions(lineFraction, cpA, fractionA0, fractionA1, arcFraction, arc, 0, 1, reversed);
                intersectionFound = true;
            }
        }
        if (intersectionFound)
            return;
        // 2) endpoints to endpoints or endpoints projection to the other curve.
        this.testAndRecordFractionalPairApproach(cpA, fractionA0, fractionA1, true, arc, 0, 1, true, reversed);
        // 3) arc tangent parallel to line segment (or string).
        // If line does not intersect the arc, then the closest (and/or the furthest) point on arc to the line is a
        // point where the tangent line on arc at that point is parallel to the line.
        this.allPerpendicularsSegmentArcBounded(cpA, pointA0, fractionA0, pointA1, fractionA1, arc, reversed);
    }
    /**
     * Compute segments perpendicular to two elliptical arcs, without extending either curve.
     * * Perpendiculars from an endpoint are not explicitly computed.
     * * Intersections are also found by this search: they are reported as zero-length segments.
     * @param reversed swap the details in the recorded pair (default: false)
     */
    allPerpendicularsArcArcBounded(arc0, arc1, reversed = false) {
        const newtonEvaluator = new Newton_1.CurveCurveCloseApproachXYRRtoRRD(arc0, arc1);
        // HEURISTIC: 2 ellipses have up to 8 perpendiculars and up to 4 intersections
        const seedDelta = 1 / 10; // denominator 9 fails the unit test
        const seedStart = seedDelta / 2;
        const newtonSearcher = new Newton_1.Newton2dUnboundedWithDerivative(newtonEvaluator, 100); // observed convergence to 1.0e-11 in 49 iters
        for (let seedU = seedStart; seedU < 1; seedU += seedDelta) {
            for (let seedV = seedStart; seedV < 1; seedV += seedDelta) {
                newtonSearcher.setUV(seedU, seedV);
                if (newtonSearcher.runIterations()) {
                    const frac0 = newtonSearcher.getU();
                    const frac1 = newtonSearcher.getV();
                    if (this.acceptFraction(frac0) && this.acceptFraction(frac1)) {
                        this.recordPointWithLocalFractions(frac0, arc0, 0, 1, frac1, arc1, 0, 1, reversed);
                    }
                }
            }
        }
    }
    /** Low level dispatch of arc with Arc3d. */
    dispatchArcArc(cpA, cpB, reversed) {
        const rangeA = cpA.range();
        const rangeB = cpB.range();
        rangeA.expandInPlace(this._maxDistanceToAccept);
        if (!rangeB.intersectsRangeXY(rangeA))
            return;
        // 1) endpoints to endpoints or endpoints projection to the other curve
        this.testAndRecordFractionalPairApproach(cpA, 0, 1, true, cpB, 0, 1, true, reversed);
        // 2) perpendicular line between 2 arcs (includes intersections)
        this.allPerpendicularsArcArcBounded(cpA, cpB, reversed);
    }
    /** Low level dispatch of arc with (beziers of) a bspline curve */
    dispatchArcBsplineCurve3d(cpA, cpB, reversed) {
        const ls = LineString3d_1.LineString3d.create();
        cpB.emitStrokes(ls);
        this.computeArcLineString(cpA, ls, reversed);
    }
    /** Low level dispatch of (beziers of) a bspline curve with (beziers of) a bspline curve */
    dispatchBSplineCurve3dBSplineCurve3d(bcurveA, bcurveB, reversed) {
        const lsA = LineString3d_1.LineString3d.create();
        bcurveA.emitStrokes(lsA);
        const lsB = LineString3d_1.LineString3d.create();
        bcurveB.emitStrokes(lsB);
        this.computeLineStringLineString(lsA, lsB, reversed);
    }
    /** Low level dispatch of linestring with (beziers of) a bspline curve */
    dispatchLineStringBSplineCurve(lsA, curveB, reversed) {
        const lsB = LineString3d_1.LineString3d.create();
        curveB.emitStrokes(lsB);
        this.computeLineStringLineString(lsA, lsB, reversed);
    }
    /** Low level dispatch of segment with (beziers of) a bspline curve */
    dispatchSegmentBsplineCurve(segA, curveB, reversed) {
        const lsB = LineString3d_1.LineString3d.create();
        curveB.emitStrokes(lsB);
        this.computeSegmentLineString(segA, lsB, reversed);
    }
    /** Detail computation for segment approaching linestring. */
    computeSegmentLineString(segA, lsB, reversed) {
        const numB = lsB.numPoints();
        const deltaFracB = Geometry_1.Geometry.safeDivideFraction(1, numB - 1, 0);
        const pointA0 = segA.point0Ref;
        const pointA1 = segA.point1Ref;
        const pointB0 = CurveCurveCloseApproachXY._workPointBB0;
        const pointB1 = CurveCurveCloseApproachXY._workPointBB1;
        for (let i = 0; i < numB - 1; ++i) {
            const fB0 = i * deltaFracB; // global linestring fractions
            const fB1 = (i + 1 === numB - 1) ? 1.0 : (i + 1) * deltaFracB; // make sure we nail the end fraction
            lsB.packedPoints.getPoint3dAtUncheckedPointIndex(i, pointB0);
            lsB.packedPoints.getPoint3dAtUncheckedPointIndex(i + 1, pointB1);
            this.dispatchSegmentSegment(segA, pointA0, 0.0, pointA1, 1.0, lsB, pointB0, fB0, pointB1, fB1, reversed);
        }
    }
    /** Detail computation for arc approaching linestring. */
    computeArcLineString(arcA, lsB, reversed) {
        const rangeA = arcA.range();
        const rangeB = lsB.range();
        rangeA.expandInPlace(this._maxDistanceToAccept);
        if (!rangeB.intersectsRangeXY(rangeA))
            return;
        const pointB0 = CurveCurveCloseApproachXY._workPointBB0;
        const pointB1 = CurveCurveCloseApproachXY._workPointBB1;
        const numB = lsB.numPoints();
        if (numB > 1) {
            const dfB = 1.0 / (numB - 1);
            let fB0;
            let fB1;
            fB0 = 0.0;
            lsB.pointAt(0, pointB0);
            for (let ib = 1; ib < numB; ib++, pointB0.setFrom(pointB1), fB0 = fB1) {
                lsB.pointAt(ib, pointB1);
                fB1 = ib * dfB;
                this.dispatchSegmentArc(lsB, pointB0, fB0, pointB1, fB1, arcA, !reversed);
            }
        }
        return undefined;
    }
    /** Low level dispatch of curve collection. */
    dispatchCurveCollection(geomA, geomAHandler) {
        const geomB = this._geometryB; // save
        if (!geomB || !geomB.children || !(geomB instanceof CurveCollection_1.CurveCollection))
            return;
        for (const child of geomB.children) {
            this.resetGeometry(child);
            geomAHandler(geomA);
        }
        this._geometryB = geomB; // restore
    }
    /** Low level dispatch to geomA given a CurveChainWithDistanceIndex in geometryB. */
    dispatchCurveChainWithDistanceIndex(geomA, geomAHandler) {
        if (!this._geometryB || !(this._geometryB instanceof CurveChainWithDistanceIndex_1.CurveChainWithDistanceIndex))
            return;
        if (geomA instanceof CurveChainWithDistanceIndex_1.CurveChainWithDistanceIndex) {
            (0, core_bentley_1.assert)(false, "call handleCurveChainWithDistanceIndex(geomA) instead");
        }
        const index0 = this._results.length;
        const geomB = this._geometryB; // save
        for (const child of geomB.path.children) {
            this.resetGeometry(child);
            geomAHandler(geomA);
        }
        this.resetGeometry(geomB); // restore
        this._results = CurveChainWithDistanceIndex_1.CurveChainWithDistanceIndex.convertChildDetailToChainDetail(this._results, index0, undefined, geomB, true);
    }
    /** Double dispatch handler for strongly typed segment. */
    handleLineSegment3d(segmentA) {
        if (this._geometryB instanceof LineSegment3d_1.LineSegment3d) {
            const segmentB = this._geometryB;
            this.dispatchSegmentSegment(segmentA, segmentA.point0Ref, 0.0, segmentA.point1Ref, 1.0, segmentB, segmentB.point0Ref, 0.0, segmentB.point1Ref, 1.0, false);
        }
        else if (this._geometryB instanceof LineString3d_1.LineString3d) {
            this.computeSegmentLineString(segmentA, this._geometryB, false);
        }
        else if (this._geometryB instanceof Arc3d_1.Arc3d) {
            this.dispatchSegmentArc(segmentA, segmentA.point0Ref, 0.0, segmentA.point1Ref, 1.0, this._geometryB, false);
        }
        else if (this._geometryB instanceof BSplineCurve_1.BSplineCurve3d) {
            this.dispatchSegmentBsplineCurve(segmentA, this._geometryB, false);
        }
        else if (this._geometryB instanceof CurveCollection_1.CurveCollection) {
            this.dispatchCurveCollection(segmentA, this.handleLineSegment3d.bind(this));
        }
        else if (this._geometryB instanceof CurveChainWithDistanceIndex_1.CurveChainWithDistanceIndex) {
            this.dispatchCurveChainWithDistanceIndex(segmentA, this.handleLineSegment3d.bind(this));
        }
        return undefined;
    }
    /**
     * Set bits for comparison to range xy
     * * bit 0x01 => x smaller than range.low.x
     * * bit 0x02 => x larger than range.high.x
     * * bit 0x04 => y smaller than range.low.y
     * * bit 0x08 => y larger than range.high.y
     * * If we divide XY plane into 9 areas using the range, the function returns 0 for points
     * inside the range. Below is other binary numbers returned by the function for all 9 areas:
     *   1001 | 1000 | 1010
     *   ------------------
     *    1   |  0   |  10
     *   ------------------
     *   101  | 100  | 110
     * @param xy point to test
     * @param range range for comparison
     */
    classifyBitsPointRangeXY(x, y, range) {
        let result = 0;
        if (x < range.low.x)
            result = 0x01;
        else if (x > range.high.x)
            result = 0x02;
        // note the OR operation
        if (y < range.low.y)
            result |= 0x04;
        else if (y > range.high.y)
            result |= 0x08;
        return result;
    }
    /** Low level dispatch of line string with line string. */
    computeLineStringLineString(lsA, lsB, reversed) {
        const rangeA = lsA.range();
        const rangeB = lsB.range();
        rangeA.expandInPlace(this._maxDistanceToAccept);
        if (!rangeB.intersectsRangeXY(rangeA))
            return;
        let bitB0;
        let bitB1;
        const rangeA1 = Range_1.Range3d.createNull();
        const pointA0 = CurveCurveCloseApproachXY._workPointAA0;
        const pointA1 = CurveCurveCloseApproachXY._workPointAA1;
        const pointB0 = CurveCurveCloseApproachXY._workPointBB0;
        const pointB1 = CurveCurveCloseApproachXY._workPointBB1;
        const numA = lsA.numPoints();
        const numB = lsB.numPoints();
        if (numA > 1 && numB > 1) {
            const dfA = 1.0 / (numA - 1);
            const dfB = 1.0 / (numB - 1);
            let fA0 = 0.0;
            let fA1, fB0, fB1;
            lsA.pointAt(0, pointA0);
            for (let ia = 1; ia < numA; ia++, pointA0.setFrom(pointA1), fA0 = fA1) {
                fA1 = ia * dfA;
                fB0 = 0.0;
                lsA.pointAt(ia, pointA1);
                // rangeA1 is around line segment [A0,A1] expanded by max distance
                rangeA1.setNull();
                rangeA1.extendPoint(pointA0);
                rangeA1.extendPoint(pointA1);
                rangeA1.expandInPlace(this._maxDistanceToAccept);
                if (rangeA1.intersectsRangeXY(rangeB)) {
                    lsB.pointAt(0, pointB0);
                    bitB0 = this.classifyBitsPointRangeXY(pointB0.x, pointB0.y, rangeA1);
                    for (let ib = 1; ib < numB; ib++, pointB0.setFrom(pointB1), fB0 = fB1, bitB0 = bitB1) {
                        lsB.pointAt(ib, pointB1);
                        bitB1 = this.classifyBitsPointRangeXY(pointB1.x, pointB1.y, rangeA1);
                        fB1 = ib * dfB;
                        // DO NOT study the segment in detail if both bitB bits are on for any of the 4 planes
                        // (i.e., no intersection between rangeA1 and the range around line segment [B0,B1])
                        if ((bitB0 & bitB1) === 0)
                            this.dispatchSegmentSegment(lsA, pointA0, fA0, pointA1, fA1, lsB, pointB0, fB0, pointB1, fB1, reversed);
                    }
                }
            }
        }
    }
    /** Double dispatch handler for strongly typed linestring. */
    handleLineString3d(lsA) {
        if (this._geometryB instanceof LineString3d_1.LineString3d) {
            const lsB = this._geometryB;
            this.computeLineStringLineString(lsA, lsB, false);
        }
        else if (this._geometryB instanceof LineSegment3d_1.LineSegment3d) {
            this.computeSegmentLineString(this._geometryB, lsA, true);
        }
        else if (this._geometryB instanceof Arc3d_1.Arc3d) {
            this.computeArcLineString(this._geometryB, lsA, true);
        }
        else if (this._geometryB instanceof BSplineCurve_1.BSplineCurve3d) {
            this.dispatchLineStringBSplineCurve(lsA, this._geometryB, false);
        }
        else if (this._geometryB instanceof CurveCollection_1.CurveCollection) {
            this.dispatchCurveCollection(lsA, this.handleLineString3d.bind(this));
        }
        else if (this._geometryB instanceof CurveChainWithDistanceIndex_1.CurveChainWithDistanceIndex) {
            this.dispatchCurveChainWithDistanceIndex(lsA, this.handleLineString3d.bind(this));
        }
        return undefined;
    }
    /** Double dispatch handler for strongly typed arc. */
    handleArc3d(arc0) {
        if (this._geometryB instanceof LineSegment3d_1.LineSegment3d) {
            this.dispatchSegmentArc(this._geometryB, this._geometryB.point0Ref, 0.0, this._geometryB.point1Ref, 1.0, arc0, true);
        }
        else if (this._geometryB instanceof LineString3d_1.LineString3d) {
            this.computeArcLineString(arc0, this._geometryB, false);
        }
        else if (this._geometryB instanceof Arc3d_1.Arc3d) {
            this.dispatchArcArc(arc0, this._geometryB, false);
        }
        else if (this._geometryB instanceof BSplineCurve_1.BSplineCurve3d) {
            this.dispatchArcBsplineCurve3d(arc0, this._geometryB, false);
        }
        else if (this._geometryB instanceof CurveCollection_1.CurveCollection) {
            this.dispatchCurveCollection(arc0, this.handleArc3d.bind(this));
        }
        else if (this._geometryB instanceof CurveChainWithDistanceIndex_1.CurveChainWithDistanceIndex) {
            this.dispatchCurveChainWithDistanceIndex(arc0, this.handleArc3d.bind(this));
        }
        return undefined;
    }
    /** Double dispatch handler for strongly typed bspline curve. */
    handleBSplineCurve3d(curve) {
        if (this._geometryB instanceof LineSegment3d_1.LineSegment3d) {
            this.dispatchSegmentBsplineCurve(this._geometryB, curve, true);
        }
        else if (this._geometryB instanceof LineString3d_1.LineString3d) {
            this.dispatchLineStringBSplineCurve(this._geometryB, curve, true);
        }
        else if (this._geometryB instanceof Arc3d_1.Arc3d) {
            this.dispatchArcBsplineCurve3d(this._geometryB, curve, true);
        }
        else if (this._geometryB instanceof BSplineCurve_1.BSplineCurve3dBase) {
            this.dispatchBSplineCurve3dBSplineCurve3d(curve, this._geometryB, false);
        }
        else if (this._geometryB instanceof CurveCollection_1.CurveCollection) {
            this.dispatchCurveCollection(curve, this.handleBSplineCurve3d.bind(this));
        }
        else if (this._geometryB instanceof CurveChainWithDistanceIndex_1.CurveChainWithDistanceIndex) {
            this.dispatchCurveChainWithDistanceIndex(curve, this.handleBSplineCurve3d.bind(this));
        }
        return undefined;
    }
    /** Double dispatch handler for strongly typed CurveChainWithDistanceIndex. */
    handleCurveChainWithDistanceIndex(chain) {
        super.handleCurveChainWithDistanceIndex(chain);
        // if _geometryB is also a CurveChainWithDistanceIndex, it will already have been converted by dispatchCurveChainWithDistanceIndex
        this._results = CurveChainWithDistanceIndex_1.CurveChainWithDistanceIndex.convertChildDetailToChainDetail(this._results, 0, chain, undefined, true);
    }
    /** Double dispatch handler for strongly typed homogeneous bspline curve .. */
    handleBSplineCurve3dH(_curve) {
        /*
        //NEEDS WORK -- make "dispatch" methods tolerant of both 3d and 3dH.
        // "easy" if both present BezierCurve3dH span loaders
        if (this._geometryB instanceof LineSegment3d) {
          this.dispatchSegmentBsplineCurve(
            this._geometryB, this._extendB, this._geometryB.point0Ref, 0.0, this._geometryB.point1Ref, 1.0, this._extendB,
            curve, this._extendA, true);
        } else if (this._geometryB instanceof LineString3d) {
          this.dispatchLineStringBSplineCurve(this._geometryB, this._extendB, curve, this._extendA, true);
        } else if (this._geometryB instanceof Arc3d) {
          this.dispatchArcBsplineCurve3d(this._geometryB, this._extendB, curve, this._extendA, true);
        }
        */
        return undefined;
    }
}
exports.CurveCurveCloseApproachXY = CurveCurveCloseApproachXY;
//# sourceMappingURL=CurveCurveCloseApproachXY.js.map