"use strict";
/*---------------------------------------------------------------------------------------------
* 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
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnnounceTangentStrokeHandler = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const Geometry_1 = require("../../Geometry");
const Point3dVector3d_1 = require("../../geometry3d/Point3dVector3d");
const Ray3d_1 = require("../../geometry3d/Ray3d");
const Newton_1 = require("../../numerics/Newton");
const CurveLocationDetail_1 = require("../CurveLocationDetail");
const NewtonRtoRStrokeHandler_1 = require("./NewtonRtoRStrokeHandler");
/**
 * Context for searching for the tangent(s) to a CurvePrimitive.
 * @internal
 */
class AnnounceTangentStrokeHandler extends NewtonRtoRStrokeHandler_1.NewtonRtoRStrokeHandler {
    _curve;
    _announceTangent;
    _spacePoint;
    _vectorToEye;
    _distanceTol;
    _distanceTolSquared;
    // fraction and function value on one side of an interval that may bracket a root
    _fractionA = 0;
    _functionA = 0;
    // fraction and function value on the other side of an interval that may bracket a root
    _fractionB = 0;
    _functionB = 0;
    _numThisCurve = 0;
    // scratch vars to use within methods
    _fractionMRU;
    _curveMRU;
    _workRay;
    _workDetail;
    _newtonSolver;
    /** Constructor */
    constructor(spacePoint, announceTangent, options) {
        super();
        this._announceTangent = announceTangent;
        this._spacePoint = spacePoint;
        this._vectorToEye = options?.vectorToEye ?? Point3dVector3d_1.Vector3d.unitZ();
        this._distanceTol = options?.distanceTol ?? Geometry_1.Geometry.smallMetricDistance;
        this._distanceTolSquared = this._distanceTol * this._distanceTol;
        this._workRay = Ray3d_1.Ray3d.createZero();
        this.startCurvePrimitive(undefined);
        this._newtonSolver = new Newton_1.Newton1dUnboundedApproximateDerivative(this);
    }
    /** Specified by IStrokeHandler. */
    needPrimaryGeometryForStrokes() {
        return true;
    }
    /** Specified by IStrokeHandler. */
    startCurvePrimitive(curve) {
        this._curve = curve;
        this._fractionA = 0.0;
        this._numThisCurve = 0;
        this._functionA = 0.0;
    }
    /** Specified by IStrokeHandler. */
    endCurvePrimitive() {
    }
    /** Specified by IStrokeHandler. */
    announceIntervalForUniformStepStrokes(cp, numStrokes, fraction0, fraction1) {
        this.startCurvePrimitive(cp);
        if (numStrokes < 1)
            numStrokes = 1;
        const df = 1.0 / numStrokes;
        for (let i = 0; i <= numStrokes; i++) {
            const fraction = Geometry_1.Geometry.interpolate(fraction0, i * df, fraction1);
            cp.fractionToPointAndDerivative(fraction, this._workRay);
            this.announceRay(fraction, this._workRay);
        }
    }
    announceCandidate(cp, fraction, point) {
        if (this._parentCurvePrimitive)
            cp = this._parentCurvePrimitive;
        if (this._curveMRU === cp && Geometry_1.Geometry.isAlmostEqualOptional(this._fractionMRU, fraction, Geometry_1.Geometry.smallFloatingPoint))
            return; // avoid announcing duplicate tangents in succession (e.g., at interior stroke point)
        this._workDetail = CurveLocationDetail_1.CurveLocationDetail.createCurveFractionPoint(cp, fraction, point, this._workDetail);
        this._announceTangent(this._workDetail);
        this._fractionMRU = fraction;
        this._curveMRU = cp;
    }
    /** Specified by IStrokeHandler. */
    announceSegmentInterval(cp, point0, point1, _numStrokes, fraction0, fraction1) {
        let fraction;
        let point;
        const distance0 = this._spacePoint.distanceSquared(point0);
        const distance1 = this._spacePoint.distanceSquared(point1);
        if (distance0 < distance1) {
            fraction = fraction0;
            point = point0;
        }
        else {
            fraction = fraction1;
            point = point1;
        }
        // evaluate at midpoint; the endpoints may be at corners, which have ambiguous tangent
        const value = this.evaluateFunction(undefined, (fraction0 + fraction1) / 2, cp);
        if (value !== undefined && Geometry_1.Geometry.isDistanceWithinTol(value, this._distanceTol))
            this.announceCandidate(cp, fraction, point);
    }
    /**
     * Given a function `f` and (unordered) fractions `a` and `b`, search for and announce a root of `f` in this
     * fractional interval.
     * * This method searches for a root of `f` if and only if the stroke segment defined by `(a, f(a))` and
     * `(b, f(b))` has a root. This is a HEURISTIC: given continuous `f` between `a` and `b`, a root of the stroke
     * segment implies a root of `f`, but not vice-versa. Therefore, if the strokes are not sufficiently dense,
     * this method can miss a root of `f`.
     */
    searchInterval() {
        // directly announce at endpoint if we are extra certain it's a root; Newton can miss it if it has multiplicity > 1
        if (Geometry_1.Geometry.isDistanceWithinTol(this._functionA, this._distanceTolSquared))
            this.announceSolutionFraction(this._fractionA);
        if (Geometry_1.Geometry.isDistanceWithinTol(this._functionB, this._distanceTolSquared))
            this.announceSolutionFraction(this._fractionB);
        if (this._functionA * this._functionB < 0) {
            // by the Intermediate Value Theorem, a root lies between fractionA and fractionB; use Newton to find it.
            const fraction = Geometry_1.Geometry.inverseInterpolate(this._fractionA, this._functionA, this._fractionB, this._functionB);
            if (fraction) {
                this._newtonSolver.setX(fraction);
                if (this._newtonSolver.runIterations())
                    this.announceSolutionFraction(this._newtonSolver.getX());
            }
        }
    }
    announceSolutionFraction(fraction) {
        if (this._curve)
            this.announceCandidate(this._curve, fraction, this._curve.fractionToPoint(fraction));
    }
    /**
     * Evaluate the univariate real-valued function for which we are finding roots.
     * * For finding the tangents to curve `X` from point `Q` as seen in a view plane with normal `N`, this
     * function is `f(t) := (Q - X(t)) dot (X'(t) cross N)`. The second vector in the dot product defines a
     * _tangent plane_ at `X(t)`.
     * * Either `pointAndDerivative` must be defined, or both `fraction` and `curve`.
     * @param pointAndDerivative pre-evaluated curve
     * @param fraction fraction at which to evaluate `curve`
     * @param curve curve to evaluate at `fraction`
     * @returns distance of `Q` from the tangent plane at `X(t)`.
    */
    evaluateFunction(pointAndDerivative, fraction, curve) {
        if (pointAndDerivative)
            this._workRay.setFrom(pointAndDerivative);
        else if (fraction !== undefined && curve)
            this._workRay = curve.fractionToPointAndDerivative(fraction, this._workRay);
        else
            return undefined;
        const cross = this._vectorToEye.unitCrossProduct(this._workRay.direction); // normalized so we return true distance
        return cross ? cross.dotProductStartEnd(this._workRay.origin, this._spacePoint) : undefined;
    }
    /** Specified by NewtonRtoRStrokeHandler. */
    evaluate(fraction) {
        const curve = this._parentCurvePrimitive ?? this._curve;
        const value = this.evaluateFunction(undefined, fraction, curve);
        if (value === undefined)
            return false;
        this.currentF = value;
        return true;
    }
    announceRay(fraction, data) {
        this._functionB = this.evaluateFunction(data);
        this._fractionB = fraction;
        if (this._numThisCurve++ > 0) // after the first stroke point, a stroke segment is defined, so we have an interval
            this.searchInterval();
        this._functionA = this._functionB;
        this._fractionA = this._fractionB;
    }
    /** Specified by IStrokeHandler. */
    announcePointTangent(_point, _fraction, _tangent) {
        (0, core_bentley_1.assert)(false, "No callers expected. IStrokeHandler probably didn't need to specify this method.");
    }
}
exports.AnnounceTangentStrokeHandler = AnnounceTangentStrokeHandler;
//# sourceMappingURL=AnnounceTangentStrokeHandler.js.map