/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Curve
 */
import { assert } from "@itwin/core-bentley";
import { BSplineCurve3d, BSplineCurve3dBase } from "../../bspline/BSplineCurve";
import { Geometry } from "../../Geometry";
import { CoincidentGeometryQuery } from "../../geometry3d/CoincidentGeometryOps";
import { RecurseToCurvesGeometryHandler } from "../../geometry3d/GeometryHandler";
import { GrowableFloat64Array } from "../../geometry3d/GrowableFloat64Array";
import { Matrix3d } from "../../geometry3d/Matrix3d";
import { Point3d } from "../../geometry3d/Point3dVector3d";
import { Point4d } from "../../geometry4d/Point4d";
import { UnivariateBezier } from "../../numerics/BezierPolynomials";
import { CurveCurveIntersectionXYRRToRRD, Newton2dUnboundedWithDerivative } from "../../numerics/Newton";
import { AnalyticRoots, TrigPolynomial } from "../../numerics/Polynomials";
import { SmallSystem } from "../../numerics/SmallSystem";
import { Arc3d } from "../Arc3d";
import { CurveChainWithDistanceIndex } from "../CurveChainWithDistanceIndex";
import { CurveCollection } from "../CurveCollection";
import { CurveIntervalRole, CurveLocationDetail, CurveLocationDetailPair } from "../CurveLocationDetail";
import { CurvePrimitive } from "../CurvePrimitive";
import { LineSegment3d } from "../LineSegment3d";
import { LineString3d } from "../LineString3d";
import { Loop } from "../Loop";
import { Path } from "../Path";
import { ProxyCurve } from "../ProxyCurve";
import { TransitionSpiral3d } from "../spiral/TransitionSpiral3d";
// cspell:word XYRR
/**
 * Handler class for XY intersections between _geometryB and another geometry.
 * * Instances are initialized and called from CurveCurve.
 * * geometryB is saved for later reference.
 * @internal
 */
export class CurveCurveIntersectXY extends RecurseToCurvesGeometryHandler {
    _extendA0;
    _extendA1;
    _geometryB;
    _extendB0;
    _extendB1;
    _results;
    _worldToLocalPerspective;
    _worldToLocalAffine;
    _coincidentGeometryContext;
    static _workPointA0H = Point4d.create();
    static _workPointA1H = Point4d.create();
    static _workPointB0H = Point4d.create();
    static _workPointB1H = Point4d.create();
    static _workPointAA0 = Point3d.create();
    static _workPointAA1 = Point3d.create();
    static _workPointBB0 = Point3d.create();
    static _workPointBB1 = Point3d.create();
    static _workPointA0 = Point3d.create();
    static _workPointA1 = Point3d.create();
    static _workPointB0 = Point3d.create();
    static _workPointB1 = Point3d.create();
    _xyzwA0;
    _xyzwA1;
    _xyzwPlane;
    _xyzwB;
    /**
     * The constructor.
     * @param worldToLocal optional transform (possibly perspective) to project to xy plane for intersection.
     * @param extendA flag for extension of the other geometry.
     * @param geometryB second curve for intersection. Saved for reference by specific handler methods.
     * @param extendB flag for extension of geometryB.
     * @param tolerance optional distance tolerance for coincidence.
     */
    constructor(worldToLocal, extendA, geometryB, extendB, tolerance = Geometry.smallMetricDistance) {
        super();
        this._extendA0 = extendA;
        this._extendA1 = extendA;
        this._geometryB = geometryB instanceof ProxyCurve ? geometryB.proxyCurve : geometryB;
        this._extendB0 = extendB;
        this._extendB1 = extendB;
        this._worldToLocalPerspective = undefined;
        this._worldToLocalAffine = undefined;
        if (worldToLocal !== undefined && !worldToLocal.isIdentity()) {
            this._worldToLocalAffine = worldToLocal.asTransform;
            if (!this._worldToLocalAffine)
                this._worldToLocalPerspective = worldToLocal.clone();
        }
        this._coincidentGeometryContext = CoincidentGeometryQuery.create(tolerance);
        this._results = [];
    }
    /** Reset the geometry, leaving all other parts unchanged (and preserving accumulated intersections). */
    resetGeometry(geometryB) {
        this._geometryB = geometryB;
    }
    /** Accept the fraction if it falls inside (possibly extended) fraction range. */
    acceptFraction(extend0, fraction, extend1, fractionTol = 1.0e-12) {
        // Note that default tol is tighter than Geometry.smallFraction. We aggressively toss intersections past endpoints.
        if (!extend0 && fraction < -fractionTol)
            return false;
        if (!extend1 && fraction > 1.0 + fractionTol)
            return false;
        return true;
    }
    /** Test the fraction by strict parameter, but allow toleranced distance test at ends. */
    acceptFractionOnLine(extend0, fraction, extend1, pointA, pointB, tolerance = Geometry.smallMetricDistance) {
        if (!extend0 && fraction < 0) {
            return Geometry.isDistanceWithinTol(fraction * pointA.distanceXY(pointB), tolerance);
        }
        else if (!extend1 && fraction > 1.0)
            return Geometry.isDistanceWithinTol((fraction - 1.0) * pointA.distanceXY(pointB), tolerance);
        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;
    }
    /**
     * Record the pre-computed intersection between two curves. Filter by extension rules. Record with fraction mapping.
     * @param localFractionA intersection fraction local to the subcurve of cpA between fractionA0 and fractionA1
     * @param cpA the first curve
     * @param fractionA0 start of the subcurve of cpA
     * @param fractionA1 end of the subcurve of cpA
     * @param localFractionB intersection fraction local to the subcurve of cpB between fractionB0 and fractionB1
     * @param cpB the second curve
     * @param fractionB0 start of the subcurve of cpB
     * @param fractionB1 end of the subcurve of cpB
     * @param reversed whether to reverse the details in the recorded intersection pair
     * @param intervalDetails optional data for a coincident segment intersection
     */
    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.interpolate(fractionA0, intervalDetails.detailA.fraction, fractionA1);
            globalFractionB = Geometry.interpolate(fractionB0, intervalDetails.detailB.fraction, fractionB1);
            globalFractionA1 = Geometry.interpolate(fractionA0, intervalDetails.detailA.fraction1, fractionA1);
            globalFractionB1 = Geometry.interpolate(fractionB0, intervalDetails.detailB.fraction1, fractionB1);
        }
        else {
            globalFractionA = globalFractionA1 = Geometry.interpolate(fractionA0, localFractionA, fractionA1);
            globalFractionB = globalFractionB1 = Geometry.interpolate(fractionB0, localFractionB, fractionB1);
        }
        // ignore duplicate of most recent pair
        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.createCurveFractionPoint(cpA, globalFractionA, cpA.fractionToPoint(globalFractionA));
        const detailB = CurveLocationDetail.createCurveFractionPoint(cpB, globalFractionB, cpB.fractionToPoint(globalFractionB));
        if (isInterval) {
            detailA.captureFraction1Point1(globalFractionA1, cpA.fractionToPoint(globalFractionA1));
            detailB.captureFraction1Point1(globalFractionB1, cpB.fractionToPoint(globalFractionB1));
        }
        else {
            detailA.setIntervalRole(CurveIntervalRole.isolated);
            detailB.setIntervalRole(CurveIntervalRole.isolated);
        }
        if (reversed) {
            this._results.push(new CurveLocationDetailPair(detailB, detailA));
        }
        else {
            this._results.push(new CurveLocationDetailPair(detailA, detailB));
        }
    }
    /**
     * Emit recordPoint for multiple pairs (on full curve).
     * @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 true to have order reversed in final structures.
     */
    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);
            }
        }
    }
    /** Compute intersection of two line segments. Filter by extension rules. Record with fraction mapping. */
    computeSegmentSegment3D(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, cpB, extendB0, pointB0, fractionB0, pointB1, fractionB1, extendB1, reversed) {
        const aDir = { x: pointA1.x - pointA0.x, y: pointA1.y - pointA0.y };
        const bDir = { x: pointB1.x - pointB0.x, y: pointB1.y - pointB0.y };
        const tol = this._coincidentGeometryContext.tolerance;
        const fractions = SmallSystem.lineSegmentXYUVIntersectionUnbounded(pointA0, aDir, pointB0, bDir, tol);
        if (!fractions)
            return;
        if (fractions.f1) { // the lines are coincident
            const detailA = CurveLocationDetail.createCurveFractionPoint(undefined, fractions.f0.x, pointA0.interpolate(fractions.f0.x, pointA1));
            detailA.captureFraction1Point1(fractions.f1.x, pointA0.interpolate(fractions.f1.x, pointA1));
            const detailB = CurveLocationDetail.createCurveFractionPoint(undefined, fractions.f0.y, pointB0.interpolate(fractions.f0.y, pointB1));
            detailB.captureFraction1Point1(fractions.f1.y, pointB0.interpolate(fractions.f1.y, pointB1));
            const overlap = CurveLocationDetailPair.createCapture(detailA, detailB);
            if (this._coincidentGeometryContext.clampCoincidentOverlapToSegmentBounds(overlap, pointA0, pointA1, pointB0, pointB1, extendA0, extendA1, extendB0, extendB1))
                this.recordPointWithLocalFractions(overlap.detailA.fraction, cpA, fractionA0, fractionA1, overlap.detailB.fraction, cpB, fractionB0, fractionB1, reversed, overlap);
        }
        else { // the lines have a transverse intersection
            if (this.acceptFractionOnLine(extendA0, fractions.f0.x, extendA1, pointA0, pointA1, tol)) {
                if (this.acceptFractionOnLine(extendB0, fractions.f0.y, extendB1, pointB0, pointB1, tol))
                    this.recordPointWithLocalFractions(fractions.f0.x, cpA, fractionA0, fractionA1, fractions.f0.y, cpB, fractionB0, fractionB1, reversed);
            }
        }
    }
    /**
     * Compute intersection of projected homogeneous line segments. Filter by extension rules. Record with
     * fraction mapping. Assumes caller knows the _worldToLocal is present.
     */
    computeSegmentSegment3DH(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, cpB, extendB0, pointB0, fractionB0, pointB1, fractionB1, extendB1, reversed) {
        const hA0 = CurveCurveIntersectXY._workPointA0H;
        const hA1 = CurveCurveIntersectXY._workPointA1H;
        const hB0 = CurveCurveIntersectXY._workPointB0H;
        const hB1 = CurveCurveIntersectXY._workPointB1H;
        this._worldToLocalPerspective.multiplyPoint3d(pointA0, 1, hA0);
        this._worldToLocalPerspective.multiplyPoint3d(pointA1, 1, hA1);
        this._worldToLocalPerspective.multiplyPoint3d(pointB0, 1, hB0);
        this._worldToLocalPerspective.multiplyPoint3d(pointB1, 1, hB1);
        const fractionAB = SmallSystem.lineSegment3dHXYTransverseIntersectionUnbounded(hA0, hA1, hB0, hB1);
        if (fractionAB !== undefined) {
            const fractionA = fractionAB.x;
            const fractionB = fractionAB.y;
            if (this.acceptFraction(extendA0, fractionA, extendA1) && this.acceptFraction(extendB0, fractionB, extendB1)) {
                // final fraction acceptance uses original world points, with perspective-aware fractions
                this.recordPointWithLocalFractions(fractionA, cpA, fractionA0, fractionA1, fractionB, cpB, fractionB0, fractionB1, reversed);
            }
        }
    }
    // Caller accesses data from a line segment and passes to here.
    // The line segment in question might be (a) a full line segment or (b) a fragment within a linestring.
    // The fraction and extend parameters allow all combinations to be passed in.
    // This method applies transform.
    dispatchSegmentSegment(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, cpB, extendB0, pointB0, fractionB0, pointB1, fractionB1, extendB1, reversed) {
        if (this._worldToLocalAffine) {
            // non-perspective projection
            CurveCurveIntersectXY.setTransformedWorkPoints(this._worldToLocalAffine, pointA0, pointA1, pointB0, pointB1);
            this.computeSegmentSegment3D(cpA, extendA0, CurveCurveIntersectXY._workPointA0, fractionA0, CurveCurveIntersectXY._workPointA1, fractionA1, extendA1, cpB, extendB0, CurveCurveIntersectXY._workPointB0, fractionB0, CurveCurveIntersectXY._workPointB1, fractionB1, extendB1, reversed);
        }
        else if (this._worldToLocalPerspective) {
            this.computeSegmentSegment3DH(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, cpB, extendB0, pointB0, fractionB0, pointB1, fractionB1, extendB1, reversed);
        }
        else {
            this.computeSegmentSegment3D(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, cpB, extendB0, pointB0, fractionB0, pointB1, fractionB1, extendB1, reversed);
        }
    }
    // Caller accesses data from a linestring or segment and passes it here.
    // The line segment in question might be (a) a full line segment or (b) a fragment within a linestring.
    // The fraction and extend parameters allow all combinations to be passed in.
    dispatchSegmentArc(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, arc, extendB0, extendB1, reversed) {
        const tol2 = this._coincidentGeometryContext.tolerance * this._coincidentGeometryContext.tolerance;
        const cosines = new GrowableFloat64Array(2);
        const sines = new GrowableFloat64Array(2);
        const radians = new GrowableFloat64Array(2);
        // Arc: X = C + cU + sV
        // Line:  contains points A0,A1
        // Arc point colinear with line 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.
        if (this._worldToLocalPerspective) {
            const data = arc.toTransformedPoint4d(this._worldToLocalPerspective);
            const radians0 = data.sweep.fractionToRadians(0);
            const pointB0H = data.center.plus2Scaled(data.vector0, Math.cos(radians0), data.vector90, Math.sin(radians0));
            const radians1 = data.sweep.fractionToRadians(1);
            const pointB1H = data.center.plus2Scaled(data.vector0, Math.cos(radians1), data.vector90, Math.sin(radians1));
            const pointA0H = this._worldToLocalPerspective.multiplyPoint3d(pointA0, 1);
            const pointA1H = this._worldToLocalPerspective.multiplyPoint3d(pointA1, 1);
            const alpha = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.center);
            const beta = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector0);
            const gamma = Geometry.tripleProductPoint4dXYW(pointA0H, pointA1H, data.vector90);
            let numRoots = AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians);
            const closeApproach = (0 === numRoots);
            if (closeApproach)
                numRoots = 1; // we returned the arc's closest approach as the first "root"; if within tolerance and at endpoints, we record it
            const acceptSolution = (iRoot, checkOnlyEndPointDistance = false) => {
                const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(iRoot), data.vector90, sines.atUncheckedIndex(iRoot));
                let fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(iRoot), extendB0);
                let fLine = SmallSystem.lineSegment3dHXYClosestPointUnbounded(pointA0H, pointA1H, arcPoint);
                if (fLine === undefined)
                    return undefined;
                if (!checkOnlyEndPointDistance && this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1))
                    return { fLine, fArc };
                // check for an endpoint intersection that is beyond parametric tolerance but within point tolerance
                fLine = fLine < 0.5 ? 0 : 1;
                fArc = data.sweep.fractionToSignedPeriodicFraction(fArc) < 0.5 ? 0 : 1;
                const pointAH = fLine ? pointA1H : pointA0H;
                const pointBH = fArc ? pointB1H : pointB0H;
                const dist2 = pointAH.realDistanceSquaredXY(pointBH);
                return (dist2 !== undefined && Geometry.isDistanceWithinTol(dist2, tol2)) ? { fLine, fArc } : undefined;
            };
            for (let i = 0; i < numRoots; i++) {
                const result = acceptSolution(i, closeApproach);
                if (result)
                    this.recordPointWithLocalFractions(result.fLine, cpA, fractionA0, fractionA1, result.fArc, arc, 0, 1, reversed);
            }
        }
        else {
            const data = arc.toTransformedVectors(this._worldToLocalAffine);
            const radians0 = data.sweep.fractionToRadians(0);
            const pointB0Local = data.center.plus2Scaled(data.vector0, Math.cos(radians0), data.vector90, Math.sin(radians0));
            const radians1 = data.sweep.fractionToRadians(1);
            const pointB1Local = data.center.plus2Scaled(data.vector0, Math.cos(radians1), data.vector90, Math.sin(radians1));
            let pointA0Local = pointA0;
            let pointA1Local = pointA1;
            if (this._worldToLocalAffine) {
                pointA0Local = this._worldToLocalAffine.multiplyPoint3d(pointA0);
                pointA1Local = this._worldToLocalAffine.multiplyPoint3d(pointA1);
            }
            const alpha = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.center, 1);
            const beta = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector0, 0);
            const gamma = Geometry.tripleProductXYW(pointA0Local, 1, pointA1Local, 1, data.vector90, 0);
            let numRoots = AnalyticRoots.appendImplicitLineUnitCircleIntersections(alpha, beta, gamma, cosines, sines, radians);
            const closeApproach = (0 === numRoots);
            if (closeApproach)
                numRoots = 1; // we returned the arc's closest approach as the first "root"; if within tolerance and at endpoints, we record it
            const acceptSolution = (iRoot, checkOnlyEndPointDistance = false) => {
                const arcPoint = data.center.plus2Scaled(data.vector0, cosines.atUncheckedIndex(iRoot), data.vector90, sines.atUncheckedIndex(iRoot));
                let fArc = data.sweep.radiansToSignedFraction(radians.atUncheckedIndex(iRoot), extendB0);
                let fLine = SmallSystem.lineSegment3dXYClosestPointUnbounded(pointA0Local, pointA1Local, arcPoint);
                if (fLine === undefined)
                    return undefined;
                if (!checkOnlyEndPointDistance && this.acceptFraction(extendA0, fLine, extendA1) && this.acceptFraction(extendB0, fArc, extendB1))
                    return { fLine, fArc };
                // check for an endpoint intersection that is beyond parametric tolerance but within point tolerance
                fLine = fLine < 0.5 ? 0 : 1;
                fArc = data.sweep.fractionToSignedPeriodicFraction(fArc) < 0.5 ? 0 : 1;
                const pointALocal = fLine ? pointA1Local : pointA0Local;
                const pointBLocal = fArc ? pointB1Local : pointB0Local;
                const dist2 = pointALocal.distanceSquaredXY(pointBLocal);
                return Geometry.isDistanceWithinTol(dist2, tol2) ? { fLine, fArc } : undefined;
            };
            for (let i = 0; i < numRoots; i++) {
                const result = acceptSolution(i, closeApproach);
                if (result)
                    this.recordPointWithLocalFractions(result.fLine, cpA, fractionA0, fractionA1, result.fArc, arc, 0, 1, reversed);
            }
        }
    }
    /**
     * Compute the intersection of two xy-arcs.
     * * Each matrix has [U V C] in (x,y,w) form from homogeneous projection (local to world).
     * * Arcs are ordered so that matrixA is better conditioned.
     */
    dispatchArcArcThisOrder(cpA, // arc closer to being circular
    matrixA, extendA0, extendA1, cpB, matrixB, extendB0, extendB1, reversed) {
        // inverseA transforms arcA to its local coordinates, where it is the unit xy-circle
        const inverseA = matrixA.inverse();
        if (inverseA) {
            // localB defines the arc formed by transforming arcB into the local coordinates of arcA
            const localB = inverseA.multiplyMatrixMatrix(matrixB);
            const ellipseRadians = [];
            const circleRadians = [];
            // find the intersection of the transformed arcs
            TrigPolynomial.solveUnitCircleHomogeneousEllipseIntersection(localB.coffs[2], localB.coffs[5], localB.coffs[8], // center xyw
            localB.coffs[0], localB.coffs[3], localB.coffs[6], // vector0 xyw
            localB.coffs[1], localB.coffs[4], localB.coffs[7], // vector90 xyw
            ellipseRadians, circleRadians);
            const tol2 = this._coincidentGeometryContext.tolerance * this._coincidentGeometryContext.tolerance;
            // the intersections are transform-invariant, so the solution angles apply directly to the input arcs
            for (let i = 0; i < ellipseRadians.length; i++) {
                let fractionA = cpA.sweep.radiansToSignedFraction(circleRadians[i], extendA0);
                let fractionB = cpB.sweep.radiansToSignedFraction(ellipseRadians[i], extendB0);
                if (this.acceptFraction(extendA0, fractionA, extendA1) && this.acceptFraction(extendB0, fractionB, extendB1)) {
                    this.recordPointWithLocalFractions(fractionA, cpA, 0, 1, fractionB, cpB, 0, 1, reversed);
                }
                else { // check for endpoint intersection beyond angular tolerance but within point tolerance
                    fractionA = cpA.sweep.fractionToSignedPeriodicFraction(fractionA) < 0.5 ? 0 : 1;
                    fractionB = cpB.sweep.fractionToSignedPeriodicFraction(fractionB) < 0.5 ? 0 : 1;
                    const endPointA = cpA.fractionToPoint(fractionA, CurveCurveIntersectXY._workPointAA0);
                    const endPointB = cpB.fractionToPoint(fractionB, CurveCurveIntersectXY._workPointBB0);
                    const dist2 = endPointA.distanceSquaredXY(endPointB);
                    if (Geometry.isDistanceWithinTol(dist2, tol2))
                        this.recordPointWithLocalFractions(fractionA, cpA, 0, 1, fractionB, cpB, 0, 1, reversed);
                }
            }
        }
    }
    /**
     * We have 2 xy-arcs.
     * 1- We pick the arc that is closest to circular (larger condition number is closer to circular).
     * 2- Transform it to local coords, where it becomes the unit xy-circle.
     * 3- Use the same map to transform the other arc.
     * 4- Find the intersection of arc and unit circle.
     * 5- Convert intersection angles to fractions and record intersections.
     */
    dispatchArcArc(cpA, extendA0, extendA1, cpB, extendB0, extendB1, reversed) {
        // overlap handling. perspective is not handled.
        if (this._coincidentGeometryContext && !this._worldToLocalPerspective && !this._worldToLocalAffine) {
            const pairs = this._coincidentGeometryContext.coincidentArcIntersectionXY(cpA, cpB, true);
            if (pairs) {
                this.recordPairs(cpA, cpB, pairs, reversed);
                return;
            }
        }
        // look for isolated intersections
        let matrixA;
        let matrixB;
        if (this._worldToLocalPerspective) {
            const dataA = cpA.toTransformedPoint4d(this._worldToLocalPerspective);
            const dataB = cpB.toTransformedPoint4d(this._worldToLocalPerspective);
            matrixA = Matrix3d.createColumnsXYW(dataA.vector0, dataA.vector0.w, dataA.vector90, dataA.vector90.w, dataA.center, dataA.center.w);
            matrixB = Matrix3d.createColumnsXYW(dataB.vector0, dataB.vector0.w, dataB.vector90, dataA.vector90.w, dataB.center, dataB.center.w);
        }
        else {
            const dataA = cpA.toTransformedVectors(this._worldToLocalAffine);
            const dataB = cpB.toTransformedVectors(this._worldToLocalAffine);
            matrixA = Matrix3d.createColumnsXYW(dataA.vector0, 0, dataA.vector90, 0, dataA.center, 1);
            matrixB = Matrix3d.createColumnsXYW(dataB.vector0, 0, dataB.vector90, 0, dataB.center, 1);
        }
        const conditionA = matrixA.conditionNumber();
        const conditionB = matrixB.conditionNumber();
        // order the arcs so that the first one we pass in is closer to circular
        if (conditionA > conditionB)
            this.dispatchArcArcThisOrder(cpA, matrixA, extendA0, extendA1, cpB, matrixB, extendB0, extendB1, reversed);
        else
            this.dispatchArcArcThisOrder(cpB, matrixB, extendB0, extendB1, cpA, matrixA, extendA0, extendA1, !reversed);
    }
    /** Compute the intersection of an arc and a B-spline curve. */
    dispatchArcBsplineCurve3d(cpA, extendA0, extendA1, cpB, extendB0, extendB1, reversed) {
        // Arc: X = C + cU + sV
        // implicitize the arc as viewed.  This "3d" matrix is homogeneous "XYW" not "xyz"
        let matrixA;
        if (this._worldToLocalPerspective) {
            const dataA = cpA.toTransformedPoint4d(this._worldToLocalPerspective);
            matrixA = Matrix3d.createColumnsXYW(dataA.vector0, dataA.vector0.w, dataA.vector90, dataA.vector90.w, dataA.center, dataA.center.w);
        }
        else {
            const dataA = cpA.toTransformedVectors(this._worldToLocalAffine);
            matrixA = Matrix3d.createColumnsXYW(dataA.vector0, 0, dataA.vector90, 0, dataA.center, 1);
        }
        // The worldToLocal has moved the arc vectors into local space.
        // matrixA captures the xyw parts (ignoring z)
        const matrixAInverse = matrixA.inverse();
        if (matrixAInverse) {
            const orderF = cpB.order; // order of the beziers for simple coordinates
            const orderG = 2 * orderF - 1; // order of the (single) bezier for squared coordinates.
            const coffF = new Float64Array(orderF);
            const univariateBezierG = new UnivariateBezier(orderG);
            const axx = matrixAInverse.at(0, 0);
            const axy = matrixAInverse.at(0, 1);
            const axz = 0.0;
            const axw = matrixAInverse.at(0, 2);
            const ayx = matrixAInverse.at(1, 0);
            const ayy = matrixAInverse.at(1, 1);
            const ayz = 0.0;
            const ayw = matrixAInverse.at(1, 2);
            const awx = matrixAInverse.at(2, 0);
            const awy = matrixAInverse.at(2, 1);
            const awz = 0.0;
            const aww = matrixAInverse.at(2, 2);
            let bezier;
            for (let spanIndex = 0;; spanIndex++) {
                bezier = cpB.getSaturatedBezierSpan3dH(spanIndex, bezier);
                if (!bezier)
                    break;
                if (this._worldToLocalPerspective)
                    bezier.tryMultiplyMatrix4dInPlace(this._worldToLocalPerspective);
                else if (this._worldToLocalAffine)
                    bezier.tryTransformInPlace(this._worldToLocalAffine);
                univariateBezierG.zero();
                bezier.poleProductsXYZW(coffF, axx, axy, axz, axw);
                univariateBezierG.addSquaredSquaredBezier(coffF, 1.0);
                bezier.poleProductsXYZW(coffF, ayx, ayy, ayz, ayw);
                univariateBezierG.addSquaredSquaredBezier(coffF, 1.0);
                bezier.poleProductsXYZW(coffF, awx, awy, awz, aww);
                univariateBezierG.addSquaredSquaredBezier(coffF, -1.0);
                const roots = univariateBezierG.roots(0.0, true);
                if (roots) {
                    for (const root of roots) {
                        const fractionB = bezier.fractionToParentFraction(root);
                        // The univariate bezier (which has been transformed by the view transform) evaluates into xyw space
                        const bcurvePoint4d = bezier.fractionToPoint4d(root);
                        const c = bcurvePoint4d.dotProductXYZW(axx, axy, axz, axw);
                        const s = bcurvePoint4d.dotProductXYZW(ayx, ayy, ayz, ayw);
                        const arcFraction = cpA.sweep.radiansToSignedFraction(Math.atan2(s, c), extendA0);
                        if (this.acceptFraction(extendA0, arcFraction, extendA1) &&
                            this.acceptFraction(extendB0, fractionB, extendB1)) {
                            this.recordPointWithLocalFractions(arcFraction, cpA, 0, 1, fractionB, cpB, 0, 1, reversed);
                        }
                    }
                }
            }
        }
    }
    /** Apply the transformation to bezier curves. Optionally construct ranges. */
    transformBeziers(beziers) {
        if (this._worldToLocalAffine) {
            for (const bezier of beziers)
                bezier.tryTransformInPlace(this._worldToLocalAffine);
        }
        else if (this._worldToLocalPerspective) {
            for (const bezier of beziers)
                bezier.tryMultiplyMatrix4dInPlace(this._worldToLocalPerspective);
        }
    }
    getRanges(beziers) {
        const ranges = [];
        ranges.length = 0;
        for (const b of beziers) {
            ranges.push(b.range());
        }
        return ranges;
    }
    dispatchBezierBezierStrokeFirst(bezierA, bcurveA, strokeCountA, bezierB, bcurveB, _strokeCountB, univariateBezierB, // caller-allocated for univariate coefficients
    reversed) {
        if (!this._xyzwA0)
            this._xyzwA0 = Point4d.create();
        if (!this._xyzwA1)
            this._xyzwA1 = Point4d.create();
        if (!this._xyzwPlane)
            this._xyzwPlane = Point4d.create();
        if (!this._xyzwB)
            this._xyzwB = Point4d.create();
        /*
        const roots = univariateBezierG.roots(0.0, true);
        if (roots) {
          for (const root of roots) {
            const fractionB = bezier.fractionToParentFraction(root);
            // The univariate bezier (which has been transformed by the view transform) evaluates into xyw space
            const bcurvePoint4d = bezier.fractionToPoint4d(root);
            const c = bcurvePoint4d.dotProductXYZW(axx, axy, axz, axw);
            const s = bcurvePoint4d.dotProductXYZW(ayx, ayy, ayz, ayw);
            const arcFraction = cpA.sweep.radiansToSignedPeriodicFraction(Math.atan2(s, c));
            if (this.acceptFraction(extendA, arcFraction, extendA) && this.acceptFraction(extendB, fractionB, extendB)) {
              this.recordPointWithLocalFractions(
                arcFraction, cpA, 0, 1, fractionB, cpB, 0, 1, reversed,
              );
            }
          }
        */
        bezierA.fractionToPoint4d(0.0, this._xyzwA0);
        let f0 = 0.0;
        let f1;
        const intervalTolerance = 1.0e-5;
        const df = 1.0 / strokeCountA;
        for (let i = 1; i <= strokeCountA; i++, f0 = f1, this._xyzwA0.setFrom(this._xyzwA1)) {
            f1 = i * df;
            bezierA.fractionToPoint4d(f1, this._xyzwA1);
            Point4d.createPlanePointPointZ(this._xyzwA0, this._xyzwA1, this._xyzwPlane);
            bezierB.poleProductsXYZW(univariateBezierB.coffs, this._xyzwPlane.x, this._xyzwPlane.y, this._xyzwPlane.z, this._xyzwPlane.w);
            let errors = 0;
            const roots = univariateBezierB.roots(0.0, true);
            if (roots) {
                for (const r of roots) {
                    let bezierBFraction = r;
                    bezierB.fractionToPoint4d(bezierBFraction, this._xyzwB);
                    const segmentAFraction = SmallSystem.lineSegment3dHXYClosestPointUnbounded(this._xyzwA0, this._xyzwA1, this._xyzwB);
                    if (segmentAFraction !== undefined && Geometry.isIn01WithTolerance(segmentAFraction, intervalTolerance)) {
                        let bezierAFraction = Geometry.interpolate(f0, segmentAFraction, f1);
                        // We have a near intersection at fractions on the two beziers
                        // Iterate on the curves for a true intersection
                        const xyMatchingFunction = new CurveCurveIntersectionXYRRToRRD(bezierA, bezierB);
                        const newtonSearcher = new Newton2dUnboundedWithDerivative(xyMatchingFunction);
                        newtonSearcher.setUV(bezierAFraction, bezierBFraction);
                        if (newtonSearcher.runIterations()) {
                            bezierAFraction = newtonSearcher.getU();
                            bezierBFraction = newtonSearcher.getV();
                        }
                        const bcurveAFraction = bezierA.fractionToParentFraction(bezierAFraction);
                        const bcurveBFraction = bezierB.fractionToParentFraction(bezierBFraction);
                        if (false) { // verify results
                            const xyzA0 = bezierA.fractionToPoint(bezierAFraction);
                            const xyzA1 = bcurveA.fractionToPoint(bcurveAFraction);
                            const xyzB0 = bezierB.fractionToPoint(bezierBFraction);
                            const xyzB1 = bcurveB.fractionToPoint(bcurveBFraction);
                            if (!xyzA0.isAlmostEqualXY(xyzA1))
                                errors++;
                            if (!xyzB0.isAlmostEqualXY(xyzB1))
                                errors++;
                            if (errors > 0 && !xyzA0.isAlmostEqual(xyzB0))
                                errors++;
                            if (errors > 0 && !xyzA1.isAlmostEqual(xyzB1))
                                errors++;
                        }
                        if (this.acceptFraction(false, bcurveAFraction, false) && this.acceptFraction(false, bcurveBFraction, false)) {
                            this.recordPointWithLocalFractions(bcurveAFraction, bcurveA, 0, 1, bcurveBFraction, bcurveB, 0, 1, reversed);
                        }
                    }
                }
            }
        }
    }
    dispatchBSplineCurve3dBSplineCurve3d(bcurveA, bcurveB, reversed) {
        const bezierSpanA = bcurveA.collectBezierSpans(true);
        const bezierSpanB = bcurveB.collectBezierSpans(true);
        const numA = bezierSpanA.length;
        const numB = bezierSpanB.length;
        this.transformBeziers(bezierSpanA);
        this.transformBeziers(bezierSpanB);
        const rangeA = this.getRanges(bezierSpanA);
        const rangeB = this.getRanges(bezierSpanB);
        const orderA = bcurveA.order;
        const orderB = bcurveB.order;
        const univariateCoffsA = new UnivariateBezier(orderA);
        const univariateCoffsB = new UnivariateBezier(orderB);
        for (let a = 0; a < numA; a++) {
            for (let b = 0; b < numB; b++) {
                if (rangeA[a].intersectsRangeXY(rangeB[b])) {
                    const strokeCountA = bezierSpanA[a].computeStrokeCountForOptions();
                    const strokeCountB = bezierSpanB[b].computeStrokeCountForOptions();
                    if (strokeCountA < strokeCountB)
                        this.dispatchBezierBezierStrokeFirst(bezierSpanA[a], bcurveA, strokeCountA, bezierSpanB[b], bcurveB, strokeCountB, univariateCoffsB, reversed);
                    else
                        this.dispatchBezierBezierStrokeFirst(bezierSpanB[b], bcurveB, strokeCountB, bezierSpanA[a], bcurveA, strokeCountA, univariateCoffsA, !reversed);
                }
            }
        }
    }
    /**
     * Apply the projection transform (if any) to (xyz, w).
     * @param xyz xyz parts of input point.
     * @param w weight to use for homogeneous effects.
     */
    projectPoint(xyz, w = 1.0) {
        if (this._worldToLocalPerspective)
            return this._worldToLocalPerspective.multiplyPoint3d(xyz, w);
        if (this._worldToLocalAffine)
            return this._worldToLocalAffine.multiplyXYZW(xyz.x, xyz.y, xyz.z, w);
        return Point4d.createFromPointAndWeight(xyz, w);
    }
    mapNPCPlaneToWorld(npcPlane, worldPlane) {
        // for NPC pointY, Y^ * H = 0 is "on" plane H.  (Hat is transpose)
        // NPC Y is A*X for our transform A and worldPointX.
        // hence (A X)^ * H = 0
        // hence X^ * A^ * H = 0
        // hence K = A^ * H
        if (this._worldToLocalAffine) {
            this._worldToLocalAffine.multiplyTransposeXYZW(npcPlane.x, npcPlane.y, npcPlane.z, npcPlane.w, worldPlane);
        }
        else if (this._worldToLocalPerspective) {
            this._worldToLocalPerspective.multiplyTransposePoint4d(npcPlane, worldPlane);
        }
        else {
            npcPlane.clone(worldPlane);
        }
    }
    // Caller accesses data from segment and bsplineCurve.
    // Selects the best conditioned arc (in xy parts) as "circle after inversion".
    // Solves the arc-arc equations.
    dispatchSegmentBsplineCurve(cpA, extendA0, pointA0, fractionA0, pointA1, fractionA1, extendA1, bcurve, extendB0, extendB1, reversed) {
        const pointA0H = this.projectPoint(pointA0);
        const pointA1H = this.projectPoint(pointA1);
        const planeCoffs = Point4d.createPlanePointPointZ(pointA0H, pointA1H);
        this.mapNPCPlaneToWorld(planeCoffs, planeCoffs);
        // NOW .. we have a plane in world space.  Intersect it with the bspline:
        const intersections = [];
        bcurve.appendPlaneIntersectionPoints(planeCoffs, intersections);
        // intersections has WORLD points with bspline fractions.
        // (the bspline fractions are all good 0..1 fractions within the spline).
        // accept those that are within the segment range.
        for (const detail of intersections) {
            const fractionB = detail.fraction;
            const curvePoint = detail.point;
            const curvePointH = this.projectPoint(curvePoint);
            const lineFraction = SmallSystem.lineSegment3dHXYClosestPointUnbounded(pointA0H, pointA1H, curvePointH);
            if (lineFraction !== undefined) {
                if (this.acceptFraction(extendA0, lineFraction, extendA1) && this.acceptFraction(extendB0, fractionB, extendB1)) {
                    this.recordPointWithLocalFractions(lineFraction, cpA, fractionA0, fractionA1, fractionB, bcurve, 0, 1, reversed);
                }
            }
        }
    }
    /** Low level dispatch of linestring with (beziers of) a bspline curve. */
    dispatchLineStringBSplineCurve(lsA, extendA0, extendA1, curveB, extendB0, extendB1, reversed) {
        const numA = lsA.numPoints();
        if (numA > 1) {
            const dfA = 1.0 / (numA - 1);
            let fA0;
            let fA1;
            fA0 = 0.0;
            const pointA0 = CurveCurveIntersectXY._workPointA0;
            const pointA1 = CurveCurveIntersectXY._workPointA1;
            lsA.pointAt(0, pointA0);
            for (let iA = 1; iA < numA; iA++, pointA0.setFrom(pointA1), fA0 = fA1) {
                lsA.pointAt(iA, pointA1);
                fA1 = iA * dfA;
                this.dispatchSegmentBsplineCurve(lsA, iA === 1 && extendA0, pointA0, fA0, pointA1, fA1, (iA + 1) === numA && extendA1, curveB, extendB0, extendB1, reversed);
            }
        }
        return undefined;
    }
    /** Detail computation for segment intersecting linestring. */
    computeSegmentLineString(lsA, extendA0, extendA1, lsB, extendB0, extendB1, reversed) {
        const pointA0 = lsA.point0Ref;
        const pointA1 = lsA.point1Ref;
        const pointB0 = CurveCurveIntersectXY._workPointBB0;
        const pointB1 = CurveCurveIntersectXY._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.dispatchSegmentSegment(lsA, extendA0, pointA0, 0.0, pointA1, 1.0, extendA1, lsB, ib === 1 && extendB0, pointB0, fB0, pointB1, fB1, (ib + 1) === numB && extendB1, reversed);
            }
        }
        return undefined;
    }
    /** Detail computation for arc intersecting linestring. */
    computeArcLineString(arcA, extendA0, extendA1, lsB, extendB0, extendB1, reversed) {
        const pointB0 = CurveCurveIntersectXY._workPointBB0;
        const pointB1 = CurveCurveIntersectXY._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, ib === 1 && extendB0, pointB0, fB0, pointB1, fB1, (ib + 1) === numB && extendB1, arcA, extendA0, extendA1, !reversed);
            }
        }
        return undefined;
    }
    /** Detail computation for linestring intersecting linestring. */
    computeLineStringLineString(lsA, extendA0, extendA1, lsB, extendB0, extendB1, reversed) {
        const pointA0 = CurveCurveIntersectXY._workPointAA0;
        const pointA1 = CurveCurveIntersectXY._workPointAA1;
        const pointB0 = CurveCurveIntersectXY._workPointBB0;
        const pointB1 = CurveCurveIntersectXY._workPointBB1;
        const numA = lsA.numPoints();
        const numB = lsB.numPoints();
        if (numA > 1 && numB > 1) {
            lsA.pointAt(0, pointA0);
            const dfA = 1.0 / (numA - 1);
            const dfB = 1.0 / (numB - 1);
            let fA0 = 0.0;
            let fB0;
            let fA1;
            let 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);
                lsB.pointAt(0, pointB0);
                for (let ib = 1; ib < numB; ib++, pointB0.setFrom(pointB1), fB0 = fB1) {
                    lsB.pointAt(ib, pointB1);
                    fB1 = ib * dfB;
                    this.dispatchSegmentSegment(lsA, ia === 1 && extendA0, pointA0, fA0, pointA1, fA1, (ia + 1) === numA && extendA1, lsB, ib === 1 && extendB0, pointB0, fB0, pointB1, fB1, (ib + 1) === numB && extendB1, reversed);
                }
            }
        }
    }
    static setTransformedWorkPoints(transform, pointA0, pointA1, pointB0, pointB1) {
        transform.multiplyPoint3d(pointA0, this._workPointA0);
        transform.multiplyPoint3d(pointA1, this._workPointA1);
        transform.multiplyPoint3d(pointB0, this._workPointB0);
        transform.multiplyPoint3d(pointB1, this._workPointB1);
    }
    /**
     * Low level dispatch of curve collection.
     * We take care of extend variables of geometry's children here if geometry is Path or Loop.
     */
    dispatchCurveCollection(geomA, geomAHandler) {
        const geomB = this._geometryB; // save
        if (!geomB || !geomB.children || !(geomB instanceof CurveCollection))
            return;
        const children = geomB.children;
        const extendB0 = this._extendB0; // save
        const extendB1 = this._extendB1; // save
        for (let i = 0; i < children.length; i++) {
            this.resetGeometry(children[i]);
            if (geomB instanceof Path && children.length > 1) {
                if (i === 0)
                    this._extendB1 = false; // first child can only extend from start
                else if (i === children.length - 1)
                    this._extendB0 = false; // last child can only extend from end
                else
                    this._extendB0 = this._extendB1 = false; // middle children cannot extend
            }
            else if (geomB instanceof Loop) {
                this._extendB0 = this._extendB1 = false; // Loops cannot extend
            }
            geomAHandler(geomA);
            this._extendB0 = extendB0; // restore
            this._extendB1 = extendB1; // restore
        }
        this.resetGeometry(geomB); // restore
    }
    /**
     * Low level dispatch of CurveChainWithDistanceIndex.
     * We take care of extend variables of geometry's children here if geometry.path is Path or Loop.
     */
    dispatchCurveChainWithDistanceIndex(geomA, geomAHandler) {
        if (!this._geometryB || !(this._geometryB instanceof CurveChainWithDistanceIndex))
            return;
        if (geomA instanceof CurveChainWithDistanceIndex) {
            assert(false, "call handleCurveChainWithDistanceIndex(geomA) instead");
        }
        const index0 = this._results.length;
        const geomB = this._geometryB; // save
        this.resetGeometry(geomB.path);
        this.dispatchCurveCollection(geomA, geomAHandler);
        this.resetGeometry(geomB); // restore
        this._results = CurveChainWithDistanceIndex.convertChildDetailToChainDetail(this._results, index0, undefined, geomB, true);
    }
    /**
     * Invoke `child.dispatchToGeometryHandler(this)` for each child in the array returned by the query `g.children`.
     * We take care of extend variables of geometry's children here if geometry is Path or Loop.
     */
    handleChildren(g) {
        const children = g.children;
        const extendA0 = this._extendA0; // save
        const extendA1 = this._extendA1; // save
        if (children)
            for (let i = 0; i < children.length; i++) {
                if (g instanceof Path && children.length > 1) {
                    if (i === 0)
                        this._extendA1 = false; // first child can only extend from start
                    else if (i === children.length - 1)
                        this._extendA0 = false; // last child can only extend from end
                    else
                        this._extendA0 = this._extendA1 = false; // middle children cannot extend
                }
                else if (g instanceof Loop) {
                    this._extendA0 = this._extendA1 = false; // Loops cannot extend
                }
                children[i].dispatchToGeometryHandler(this);
                this._extendA0 = extendA0; // restore
                this._extendA1 = extendA1; // restore
            }
    }
    /** Double dispatch handler for strongly typed segment. */
    handleLineSegment3d(segmentA) {
        if (this._geometryB instanceof LineSegment3d) {
            const segmentB = this._geometryB;
            this.dispatchSegmentSegment(segmentA, this._extendA0, segmentA.point0Ref, 0.0, segmentA.point1Ref, 1.0, this._extendA1, segmentB, this._extendB0, segmentB.point0Ref, 0.0, segmentB.point1Ref, 1.0, this._extendB1, false);
        }
        else if (this._geometryB instanceof LineString3d) {
            this.computeSegmentLineString(segmentA, this._extendA0, this._extendA1, this._geometryB, this._extendB0, this._extendB1, false);
        }
        else if (this._geometryB instanceof Arc3d) {
            this.dispatchSegmentArc(segmentA, this._extendA0, segmentA.point0Ref, 0.0, segmentA.point1Ref, 1.0, this._extendA1, this._geometryB, this._extendB0, this._extendB1, false);
        }
        else if (this._geometryB instanceof BSplineCurve3d) {
            this.dispatchSegmentBsplineCurve(segmentA, this._extendA0, segmentA.point0Ref, 0.0, segmentA.point1Ref, 1.0, this._extendA1, this._geometryB, this._extendB0, this._extendB1, false);
        }
        else if (this._geometryB instanceof TransitionSpiral3d) {
            const spiralApproximation = LineString3d.create();
            this._geometryB.emitStrokes(spiralApproximation);
            const numPreviousResults = this._results.length;
            this.computeSegmentLineString(segmentA, this._extendA0, this._extendA1, spiralApproximation, false, false, false);
            const numberOfNewResults = this._results.length - numPreviousResults;
            this.refineSpiralResultsByNewton(segmentA, this._geometryB, numberOfNewResults);
        }
        else if (this._geometryB instanceof CurveCollection) {
            this.dispatchCurveCollection(segmentA, this.handleLineSegment3d.bind(this));
        }
        else if (this._geometryB instanceof CurveChainWithDistanceIndex) {
            this.dispatchCurveChainWithDistanceIndex(segmentA, this.handleLineSegment3d.bind(this));
        }
        return undefined;
    }
    /** Double dispatch handler for strongly typed linestring. */
    handleLineString3d(lsA) {
        if (this._geometryB instanceof LineString3d) {
            const lsB = this._geometryB;
            this.computeLineStringLineString(lsA, this._extendA0, this._extendA1, lsB, this._extendB0, this._extendB1, false);
        }
        else if (this._geometryB instanceof LineSegment3d) {
            this.computeSegmentLineString(this._geometryB, this._extendB0, this._extendB1, lsA, this._extendA0, this._extendA1, true);
        }
        else if (this._geometryB instanceof Arc3d) {
            this.computeArcLineString(this._geometryB, this._extendB0, this._extendB1, lsA, this._extendA0, this._extendA1, true);
        }
        else if (this._geometryB instanceof BSplineCurve3d) {
            this.dispatchLineStringBSplineCurve(lsA, this._extendA0, this._extendA1, this._geometryB, this._extendB0, this._extendB1, false);
        }
        else if (this._geometryB instanceof TransitionSpiral3d) {
            const spiralApproximation = LineString3d.create();
            this._geometryB.emitStrokes(spiralApproximation);
            const numPreviousResults = this._results.length;
            this.computeLineStringLineString(lsA, this._extendA0, this._extendA1, spiralApproximation, false, false, false);
            const numberOfNewResults = this._results.length - numPreviousResults;
            this.refineSpiralResultsByNewton(lsA, this._geometryB, numberOfNewResults);
        }
        else if (this._geometryB instanceof CurveCollection) {
            this.dispatchCurveCollection(lsA, this.handleLineString3d.bind(this));
        }
        else if (this._geometryB instanceof CurveChainWithDistanceIndex) {
            this.dispatchCurveChainWithDistanceIndex(lsA, this.handleLineString3d.bind(this));
        }
        return undefined;
    }
    /** Double dispatch handler for strongly typed arc. */
    handleArc3d(arc0) {
        if (this._geometryB instanceof LineSegment3d) {
            this.dispatchSegmentArc(this._geometryB, this._extendB0, this._geometryB.point0Ref, 0.0, this._geometryB.point1Ref, 1.0, this._extendB1, arc0, this._extendA0, this._extendA1, true);
        }
        else if (this._geometryB instanceof LineString3d) {
            this.computeArcLineString(arc0, this._extendA0, this._extendA1, this._geometryB, this._extendB0, this._extendB1, false);
        }
        else if (this._geometryB instanceof Arc3d) {
            this.dispatchArcArc(arc0, this._extendA0, this._extendA1, this._geometryB, this._extendB0, this._extendB1, false);
        }
        else if (this._geometryB instanceof BSplineCurve3d) {
            this.dispatchArcBsplineCurve3d(arc0, this._extendA0, this._extendA1, this._geometryB, this._extendB0, this._extendB1, false);
        }
        else if (this._geometryB instanceof TransitionSpiral3d) {
            const spiralApproximation = LineString3d.create();
            this._geometryB.emitStrokes(spiralApproximation);
            const numPreviousResults = this._results.length;
            this.computeArcLineString(arc0, this._extendA0, this._extendA1, spiralApproximation, false, false, false);
            const numberOfNewResults = this._results.length - numPreviousResults;
            this.refineSpiralResultsByNewton(arc0, this._geometryB, numberOfNewResults);
        }
        else if (this._geometryB instanceof CurveCollection) {
            this.dispatchCurveCollection(arc0, this.handleArc3d.bind(this));
        }
        else if (this._geometryB instanceof 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) {
            this.dispatchSegmentBsplineCurve(this._geometryB, this._extendB0, this._geometryB.point0Ref, 0.0, this._geometryB.point1Ref, 1.0, this._extendB1, curve, this._extendA0, this._extendA1, true);
        }
        else if (this._geometryB instanceof LineString3d) {
            this.dispatchLineStringBSplineCurve(this._geometryB, this._extendB0, this._extendB1, curve, this._extendA0, this._extendA1, true);
        }
        else if (this._geometryB instanceof Arc3d) {
            this.dispatchArcBsplineCurve3d(this._geometryB, this._extendB0, this._extendB1, curve, this._extendA0, this._extendA1, true);
        }
        else if (this._geometryB instanceof BSplineCurve3dBase) {
            this.dispatchBSplineCurve3dBSplineCurve3d(curve, this._geometryB, false);
        }
        else if (this._geometryB instanceof TransitionSpiral3d) {
            const spiralApproximation = LineString3d.create();
            this._geometryB.emitStrokes(spiralApproximation);
            const numPreviousResults = this._results.length;
            this.dispatchLineStringBSplineCurve(spiralApproximation, false, false, curve, this._extendA0, this._extendA1, true);
            const numberOfNewResults = this._results.length - numPreviousResults;
            this.refineSpiralResultsByNewton(curve, this._geometryB, numberOfNewResults);
        }
        else if (this._geometryB instanceof CurveCollection) {
            this.dispatchCurveCollection(curve, this.handleBSplineCurve3d.bind(this));
        }
        else if (this._geometryB instanceof CurveChainWithDistanceIndex) {
            this.dispatchCurveChainWithDistanceIndex(curve, this.handleBSplineCurve3d.bind(this));
        }
        return undefined;
    }
    /**
     * Assuming the tail of `this._results` holds the given number of approximate solutions to the curve-spiral
     * xy-intersection problem, replace each with its Newton refinement, unless it doesn't converge, in which case
     * it is removed.
     * @param spiral The transition spiral.
     * @param otherCurve The other curve primitive.
     * @param numberOfNewResults The number of results in the tail of `this._results` to be refined.
     * @param reversed Whether the spiral is geometryA (true) or geometryB (false).
     */
    refineSpiralResultsByNewton(otherCurve, spiral, numberOfNewResults, reversed = false) {
        const resultsToBeRefined = this._results.slice(this._results.length - numberOfNewResults);
        this._results.length -= numberOfNewResults; // keep already refined results
        for (const detail of resultsToBeRefined) {
            let spiralFraction = reversed ? detail.detailA.fraction : detail.detailB.fraction;
            let otherFraction = reversed ? detail.detailB.fraction : detail.detailA.fraction;
            const xyMatchingFunction = new CurveCurveIntersectionXYRRToRRD(spiral, otherCurve);
            const newtonSearcher = new Newton2dUnboundedWithDerivative(xyMatchingFunction);
            newtonSearcher.setUV(spiralFraction, otherFraction);
            if (newtonSearcher.runIterations()) {
                spiralFraction = newtonSearcher.getU();
                otherFraction = newtonSearcher.getV();
                if (this.acceptFraction(false, spiralFraction, false) && this.acceptFraction(false, otherFraction, false))
                    this.recordPointWithLocalFractions(otherFraction, otherCurve, 0, 1, spiralFraction, spiral, 0, 1, reversed);
            }
        }
    }
    /** Double dispatch handler for strongly typed spiral curve. */
    handleTransitionSpiral(spiral) {
        if (this._geometryB instanceof CurvePrimitive) { // this also handles CurveChainWithDistanceIndex
            const spiralApproximation = LineString3d.create();
            spiral.emitStrokes(spiralApproximation);
            const numPreviousResults = this._results.length;
            this.handleLineString3d(spiralApproximation);
            const numberOfNewResults = this._results.length - numPreviousResults;
            this.refineSpiralResultsByNewton(this._geometryB, spiral, numberOfNewResults, true);
        }
        else if (this._geometryB instanceof CurveCollection) {
            this.dispatchCurveCollection(spiral, this.handleTransitionSpiral.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.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;
    }
}
//# sourceMappingURL=CurveCurveIntersectXY.js.map