"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.CoincidentGeometryQuery = void 0;
/** @packageDocumentation
 * @module CartesianGeometry
 */
const core_bentley_1 = require("@itwin/core-bentley");
const CurveLocationDetail_1 = require("../curve/CurveLocationDetail");
const Geometry_1 = require("../Geometry");
const SmallSystem_1 = require("../numerics/SmallSystem");
const AngleSweep_1 = require("./AngleSweep");
const Point3dVector3d_1 = require("./Point3dVector3d");
const Segment1d_1 = require("./Segment1d");
/**
 * `CoincidentGeometryQuery` has methods useful in testing for overlapping geometry.
 * * Each instance carries tolerance information that can be reused over extended call sequences.
 * * These methods are expected to be called internally by CurveCurve intersection methods.
 * @internal
 */
class CoincidentGeometryQuery {
    _vectorU;
    _vectorV;
    _point0;
    _point1;
    _tolerance;
    get tolerance() {
        return this._tolerance;
    }
    constructor(tolerance = Geometry_1.Geometry.smallMetricDistance) {
        this._tolerance = tolerance;
    }
    static create(tolerance = Geometry_1.Geometry.smallMetricDistance) {
        return new CoincidentGeometryQuery(tolerance);
    }
    /**
     * * Assign both the fraction and fraction1 values in the detail, possibly swapped.
     * * reevaluate the points as simple interpolation between given points.
     */
    static assignDetailInterpolatedFractionsAndPoints(detail, f0, f1, pointA, pointB, swap = false) {
        if (swap) {
            detail.fraction = f1;
            detail.fraction1 = f0;
        }
        else {
            detail.fraction = f0;
            detail.fraction1 = f1;
        }
        detail.point = pointA.interpolate(detail.fraction, pointB, detail.point);
        detail.point1 = pointA.interpolate(detail.fraction1, pointB, detail.point1);
    }
    /** Return a curve location detail with projection of a `spacePoint` to the line segment with `pointA` and `pointB` */
    projectPointToSegmentXY(spacePoint, pointA, pointB) {
        this._vectorU = Point3dVector3d_1.Vector3d.createStartEnd(pointA, pointB, this._vectorU);
        this._vectorV = Point3dVector3d_1.Vector3d.createStartEnd(pointA, spacePoint, this._vectorV);
        const uDotU = this._vectorU.dotProductXY(this._vectorU);
        const uDotV = this._vectorU.dotProductXY(this._vectorV);
        const fraction = Geometry_1.Geometry.safeDivideFraction(uDotV, uDotU, 0.0);
        return CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(undefined, fraction, pointA.interpolate(fraction, pointB));
    }
    /**
     * Given a detail pair representing the projection of each of two colinear line segments onto the other,
     * clamp the details (in place) to the line segments' endpoints according to the given flags.
     * @param overlap unbounded segment overlap as returned by [[coincidentSegmentRangeXY]], modified and returned
     * @param pointA0 start point of segment A
     * @param pointA1 end point of segment A
     * @param pointB0 start point of segment B
     * @param pointB1 end point of segment B
     * @param extendA0 whether to extend segment A beyond its start
     * @param extendA1 whether to extend segment A beyond its end
     * @param extendB0 whether to extend segment B beyond its start
     * @param extendB1 whether to extend segment B beyond its end
     * @return reference to the modified input, or undefined (leaving input untouched) if clamping would result in empty interval.
     */
    clampCoincidentOverlapToSegmentBounds(overlap, pointA0, pointA1, pointB0, pointB1, extendA0 = false, extendA1 = false, extendB0 = false, extendB1 = false) {
        const rangeA = Segment1d_1.Segment1d.create(overlap.detailA.fraction, overlap.detailA.hasFraction1 ? overlap.detailA.fraction1 : overlap.detailA.fraction);
        const rangeB = Segment1d_1.Segment1d.create(overlap.detailB.fraction, overlap.detailB.hasFraction1 ? overlap.detailB.fraction1 : overlap.detailB.fraction);
        const reversed = rangeA.signedDelta() < 0.0;
        const updateIntervalFromRangesAndInterpolatedPoints = () => {
            const a0 = rangeA.x0;
            const a1 = rangeA.x1;
            const b0 = rangeB.x0;
            const b1 = rangeB.x1;
            CoincidentGeometryQuery.assignDetailInterpolatedFractionsAndPoints(overlap.detailA, a0, a1, pointA0, pointA1, a0 > a1);
            CoincidentGeometryQuery.assignDetailInterpolatedFractionsAndPoints(overlap.detailB, b0, b1, pointB0, pointB1, b0 > b1);
            return overlap;
        };
        const haveIntervalA = rangeA.clampDirectedTo01(!extendA0, !extendA1, false);
        const haveIntervalB = rangeB.clampDirectedTo01(!extendB0, !extendB1, false);
        if (haveIntervalA && haveIntervalB) {
            if (Geometry_1.Geometry.isAlmostEqualNumber(rangeA.absoluteDelta(), rangeB.absoluteDelta(), Geometry_1.Geometry.smallFraction))
                return updateIntervalFromRangesAndInterpolatedPoints(); // intersection of partially clamped ranges
            else if (rangeA.clampDirectedTo01(true, true, false) && rangeB.clampDirectedTo01(true, true, false))
                return updateIntervalFromRangesAndInterpolatedPoints(); // intersection of fully clamped ranges
        }
        const collapseToSingleton = (pointA, pointB, atStartA, atStartB) => {
            pointA.clone(overlap.detailA.point);
            pointB.clone(overlap.detailB.point);
            overlap.detailA.fraction = atStartA ? 0.0 : 1.0;
            overlap.detailB.fraction = atStartB ? 0.0 : 1.0;
            overlap.detailA.collapseToStart();
            overlap.detailB.collapseToStart();
            return overlap;
        };
        const haveSingletonA = rangeA.clampDirectedTo01(true, true, true);
        const haveSingletonB = rangeB.clampDirectedTo01(true, true, true);
        if (haveSingletonA && haveSingletonB) { // intersection is a single point
            const point1 = overlap.detailA.point1 ?? overlap.detailA.point;
            if (reversed) {
                if (overlap.detailA.point.isAlmostEqual(pointA0, this.tolerance))
                    return collapseToSingleton(pointA0, pointB0, true, true);
                else if (point1.isAlmostEqual(pointA1, this.tolerance))
                    return collapseToSingleton(pointA1, pointB1, false, false);
            }
            else {
                if (point1.isAlmostEqual(pointA0, this.tolerance))
                    return collapseToSingleton(pointA0, pointB1, true, false);
                else if (overlap.detailA.point.isAlmostEqual(pointA1, this.tolerance))
                    return collapseToSingleton(pointA1, pointB0, false, true);
            }
        }
        return undefined; // no intersection
    }
    /**
     * Compute whether two line segments have a coincident overlap in xy.
     * * Project `pointA0` and `pointA1` onto the (infinite) line formed by `pointB0` and `pointB1` and vice versa.
     * * If all projection distances are sufficiently small, return a detail pair recording the coincident interval.
     * * Caller can follow up with [[clampCoincidentOverlapToSegmentBounds]] to restrict the returned interval by the segments' ranges.
     * @param pointA0 start point of segment A
     * @param pointA1 end point of segment A
     * @param pointB0 start point of segment B
     * @param pointB1 end point of segment B
     * @return detail pair for the coincident interval (`detailA` has fractions along segment A, and `detailB` has
     * fractions along segment B), or `undefined` if no coincidence.
     */
    coincidentSegmentRangeXY(pointA0, pointA1, pointB0, pointB1) {
        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 fractions = SmallSystem_1.SmallSystem.lineSegmentXYUVOverlapUnbounded(pointA0, aDir, pointB0, bDir, this._tolerance);
        if (!fractions || !fractions.f1)
            return undefined; // no overlap
        const detailA = CurveLocationDetail_1.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_1.CurveLocationDetail.createCurveFractionPoint(undefined, fractions.f0.y, pointB0.interpolate(fractions.f0.y, pointB1));
        detailB.captureFraction1Point1(fractions.f1.y, pointB0.interpolate(fractions.f1.y, pointB1));
        return CurveLocationDetail_1.CurveLocationDetailPair.createCapture(detailA, detailB);
    }
    /**
     * Create a CurveLocationDetailPair for a coincident interval of two overlapping curves
     * @param cpA curveA
     * @param cpB curveB
     * @param fractionsOnA coincident interval of curveB in fraction space of curveA
     * @param fractionB0 curveB start in fraction space of curveA
     * @param fractionB1 curveB end in fraction space of curveA
     * @param reverse whether curveB and curveA have opposite direction
     */
    createDetailPair(cpA, cpB, fractionsOnA, fractionB0, fractionB1, reverse) {
        const deltaB = fractionB1 - fractionB0;
        const g0 = Geometry_1.Geometry.conditionalDivideFraction(fractionsOnA.x0 - fractionB0, deltaB);
        const g1 = Geometry_1.Geometry.conditionalDivideFraction(fractionsOnA.x1 - fractionB0, deltaB);
        if (g0 !== undefined && g1 !== undefined) {
            const detailA = CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFractionFraction(cpA, fractionsOnA.x0, fractionsOnA.x1);
            const detailB = CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFractionFraction(cpB, g0, g1);
            if (reverse)
                detailA.swapFractionsAndPoints();
            return CurveLocationDetail_1.CurveLocationDetailPair.createCapture(detailA, detailB);
        }
        return undefined;
    }
    appendDetailPair(result, pair) {
        if (pair === undefined)
            return result;
        if (result === undefined)
            return [pair];
        result.push(pair);
        return result;
    }
    /**
     * Test if 2 arcs have coinciding portions.
     * @param arcA
     * @param arcB
     * @param _restrictToBounds
     * @return 0, 1, or 2 overlap points/intervals
     */
    coincidentArcIntersectionXY(arcA, arcB, _restrictToBounds = true) {
        let result;
        if (arcA.center.isAlmostEqual(arcB.center, this.tolerance)) {
            const matrixBToA = arcA.matrixRef.multiplyMatrixInverseMatrix(arcB.matrixRef);
            if (matrixBToA) {
                const ux = matrixBToA.at(0, 0);
                const uy = matrixBToA.at(1, 0);
                const vx = matrixBToA.at(0, 1);
                const vy = matrixBToA.at(1, 1);
                const ru = Geometry_1.Geometry.hypotenuseXY(ux, uy);
                const rv = Geometry_1.Geometry.hypotenuseXY(vx, vy);
                const dot = Geometry_1.Geometry.dotProductXYXY(ux, uy, vx, vy);
                const cross = Geometry_1.Geometry.crossProductXYXY(ux, uy, vx, vy);
                if (Geometry_1.Geometry.isAlmostEqualNumber(ru, 1.0)
                    && Geometry_1.Geometry.isAlmostEqualNumber(rv, 1.0)
                    && Geometry_1.Geometry.isAlmostEqualNumber(0, dot)) {
                    const alphaB0Radians = Math.atan2(uy, ux); // angular position of arcB 0 point in arcA sweep
                    const sweepDirection = cross > 0 ? 1.0 : -1.0; // 1 if arcB parameter space sweeps in same direction as arcA, -1 if opposite
                    const betaStartRadians = alphaB0Radians + sweepDirection * arcB.sweep.startRadians; // arcB start in arcA parameter space
                    const betaEndRadians = alphaB0Radians + sweepDirection * arcB.sweep.endRadians; // arcB end in arcA parameter space
                    const fractionSpacesReversed = (sweepDirection * arcA.sweep.sweepRadians * arcB.sweep.sweepRadians) < 0;
                    const sweepB = AngleSweep_1.AngleSweep.createStartEndRadians(betaStartRadians, betaEndRadians);
                    const sweepA = arcA.sweep;
                    const fractionPeriodA = sweepA.fractionPeriod();
                    const fractionB0 = sweepA.radiansToPositivePeriodicFraction(sweepB.startRadians); // arcB start in arcA fraction space
                    (0, core_bentley_1.assert)(fractionB0 >= 0.0);
                    const fractionSweep = sweepB.sweepRadians / sweepA.sweepRadians; // arcB sweep in arcA fraction space
                    const fractionB1 = fractionB0 + fractionSweep; // arcB end in arcA fraction space
                    const fractionSweepB = Segment1d_1.Segment1d.create(fractionB0, fractionB1);
                    /** lambda to add a coincident interval or isolated intersection, given inputs in arcA fraction space
                     * @param arcBInArcAFractionSpace span of arcB in arcA fraction space. On return, clamped to [0,1] if nontrivial.
                     * @param testStartOfArcA if no nontrivial coincident interval was found, look for an isolated intersection at the start (true) or end (false) of arcA
                     * @returns whether a detail pair was appended to result
                     */
                    const appendCoincidentIntersection = (arcBInArcAFractionSpace, testStartOfArcA) => {
                        const size = result ? result.length : 0;
                        const arcBStart = arcBInArcAFractionSpace.x0;
                        const arcBEnd = arcBInArcAFractionSpace.x1;
                        if (arcBInArcAFractionSpace.clampDirectedTo01() && !Geometry_1.Geometry.isSmallRelative(arcBInArcAFractionSpace.absoluteDelta())) {
                            result = this.appendDetailPair(result, this.createDetailPair(arcA, arcB, arcBInArcAFractionSpace, arcBStart, arcBEnd, fractionSpacesReversed));
                        }
                        else { // test isolated intersection
                            const testStartOfArcB = fractionSpacesReversed ? testStartOfArcA : !testStartOfArcA;
                            const arcAPt = this._point0 = testStartOfArcA ? arcA.startPoint(this._point0) : arcA.endPoint(this._point0);
                            const arcBPt = this._point1 = testStartOfArcB ? arcB.startPoint(this._point1) : arcB.endPoint(this._point1);
                            if (arcAPt.isAlmostEqual(arcBPt, this.tolerance)) {
                                const detailA = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(arcA, testStartOfArcA ? 0 : 1, arcAPt);
                                const detailB = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(arcB, testStartOfArcB ? 0 : 1, arcBPt);
                                result = this.appendDetailPair(result, CurveLocationDetail_1.CurveLocationDetailPair.createCapture(detailA, detailB));
                            }
                        }
                        return result !== undefined && result.length > size;
                    };
                    appendCoincidentIntersection(fractionSweepB, false); // compute overlap in strict interior, or at end of arcA
                    // check overlap at start of arcA with a periodic shift of fractionSweepB
                    if (fractionB1 >= fractionPeriodA)
                        appendCoincidentIntersection(Segment1d_1.Segment1d.create(fractionB0 - fractionPeriodA, fractionB1 - fractionPeriodA), true);
                    else if (fractionB0 === 0.0)
                        appendCoincidentIntersection(Segment1d_1.Segment1d.create(fractionB0 + fractionPeriodA, fractionB1 + fractionPeriodA), true);
                }
            }
        }
        return result;
    }
}
exports.CoincidentGeometryQuery = CoincidentGeometryQuery;
//# sourceMappingURL=CoincidentGeometryOps.js.map