/*---------------------------------------------------------------------------------------------
* 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 { Geometry } from "../../Geometry";
import { CubicEvaluator } from "./CubicEvaluator";
import { SimpleNewton } from "../../numerics/Newton";
/**
 * Czech cubic.
 * This is y = m*x^3 with
 * * x any point on the x axis.
 * * `fraction` along the spiral goes to `x = fraction * L`.
 * * m is gamma/(6RL).
 *    * 1/(6RL) is the leading term of the sine series.
 *    * `gamma = 2R/sqrt(4RR-LL)` pushes y down a little bit to simulate the lost series terms.
 * @internal
 */
export class CzechSpiralEvaluator extends CubicEvaluator {
    nominalLength1;
    nominalRadius1;
    /** Constructor is private. Caller responsible for cubicM validity. */
    constructor(length1, radius1, cubicM) {
        super(length1, cubicM);
        this.nominalLength1 = length1;
        this.nominalRadius1 = radius1;
    }
    /**
     * Return the scale factor between simple x^3 / (6RL) cubic and the czech correction.
     * * For typical case with l1/R1 smallish, this is just less than 1.0:
     *   (0.25==>0.99215), (0.15==>0.997184), (0.10==>0.998749), (0.05==>999687)
     */
    static gammaConstant(length1, radius1) {
        return 2.0 * radius1 / Math.sqrt(4.0 * radius1 * radius1 - length1 * length1);
    }
    /** Compute the Czech cubic constant. */
    static computeCubicM(length1, radius1) {
        const gamma = CzechSpiralEvaluator.gammaConstant(length1, radius1);
        // in the private update method, the LR values should have been vetted
        if (gamma === undefined)
            return undefined;
        // if radius is negative, it shows up in gamma; but the a signed denominator undoes it so take abs of denominator
        return gamma / Math.abs((6.0 * radius1 * length1));
    }
    static create(length1, radius1) {
        const m = this.computeCubicM(length1, radius1);
        if (m === undefined)
            return undefined;
        return new CzechSpiralEvaluator(length1, radius1, m);
    }
    scaleInPlace(scaleFactor) {
        this.nominalLength1 *= scaleFactor;
        this.nominalRadius1 *= scaleFactor;
        super.scaleInPlace(scaleFactor);
    }
    /** Return a deep copy of the evaluator. */
    clone() {
        return new CzechSpiralEvaluator(this.nominalLength1, this.nominalRadius1, this.cubicM);
    }
    /** Member by member matchup. */
    isAlmostEqual(other) {
        if (other instanceof CzechSpiralEvaluator) {
            return Geometry.isSameCoordinate(this.nominalLength1, other.nominalLength1)
                && Geometry.isSameCoordinate(this.nominalRadius1, other.nominalRadius1);
        }
        return false;
    }
    /**
     * Return a (fast but mediocre) approximation of spiral length as a function of x axis position.
     * * This x-to-distance relation is not as precise as the CurvePrimitive method moveSignedDistanceFromFraction.
     * * It is supported here for users interested in replicating the Czech distance mapping rather than the more accurate
     * CurvePrimitive measurements.
     * @param x distance along the x axis.
     */
    xToCzechApproximateDistance(x) {
        return CzechSpiralEvaluator.forwardL2R2Map(x, 1.0, this.nominalLength1, this.nominalRadius1);
    }
    /**
     * Return the inverse of the `forwardL2R2Map` function.
     * * The undefined result can only occur for distances outside the usual spirals.
     * @param d (approximate) distance along the spiral.
     */
    czechApproximateDistanceToX(d) {
        return CzechSpiralEvaluator.inverseL2R2Map(d, 1.0, this.nominalLength1, this.nominalRadius1);
    }
    /**
     * Evaluate a series expansion that is used with varying signs (plus or minus 1) in czech and italian spirals.
     * @param x distance along the x axis.
     */
    static forwardL2R2Map(x, sign, length, radius) {
        const l2 = length * length;
        const r2 = radius * radius;
        const Q = 4.0 * r2 - l2;
        const xx = x * x;
        return x * (1.0 + sign * xx * xx / (10.0 * Q * l2));
    }
    /**
     * Return the inverse of the `forwardL2R2Map` function.
     * * The undefined result can only occur for distances outside the usual spirals.
     * @param d (approximate) distance along the spiral.
     */
    static inverseL2R2Map(d, sign, length, radius) {
        const l2 = length * length;
        const r2 = radius * radius;
        const Q = 4.0 * r2 - l2;
        const a = sign / (10.0 * Q * l2);
        return SimpleNewton.runNewton1D(d, (x) => {
            const xx = x * x;
            return x * (1.0 + xx * xx * a) - d;
        }, (x) => {
            const xx = x * x;
            return 1.0 + 5 * xx * xx * a;
        });
    }
}
/**
 * Italian cubic.
 * This is y = m*x^3 with
 * * x any point on the x axis.
 * * `fraction` along the spiral goes to `x = fraction * L`.
 * * m is gamma/(6RL).
 *    * 1/(6RL) is the leading term of the sine series.
 *    * `gamma = 2R/sqrt(4RR-LL)` pushes y down a little bit to simulate the lost series terms.
 * @internal
 */
export class ItalianSpiralEvaluator extends CubicEvaluator {
    nominalLength1;
    nominalRadius1;
    /** Compute the Italian cubic constant. */
    static computeCubicM(lengthXByForward, radius1) {
        return CzechSpiralEvaluator.computeCubicM(lengthXByForward, radius1);
    }
    /** Constructor is private. Caller responsible for cubicM validity. */
    constructor(length1, radius1, lengthX, cubicM) {
        super(lengthX, cubicM);
        this.nominalLength1 = length1;
        this.nominalRadius1 = radius1;
    }
    static create(length1, radius1) {
        // this seems goofy; lengthX from forward, then invert for another but that's what the native code does too
        const lengthX = CzechSpiralEvaluator.forwardL2R2Map(length1, -1.0, length1, radius1);
        const lengthX1 = CzechSpiralEvaluator.inverseL2R2Map(length1, 1.0, lengthX, radius1);
        if (lengthX1 === undefined)
            return undefined;
        const m = ItalianSpiralEvaluator.computeCubicM(lengthX, radius1);
        if (m === undefined)
            return undefined;
        return new ItalianSpiralEvaluator(length1, radius1, lengthX1, m);
    }
    scaleInPlace(scaleFactor) {
        this.nominalLength1 *= scaleFactor;
        this.nominalRadius1 *= scaleFactor;
        super.scaleInPlace(scaleFactor);
    }
    /** Return a deep copy of the evaluator. */
    clone() {
        return new ItalianSpiralEvaluator(this.nominalLength1, this.nominalRadius1, super.axisLength, this.cubicM);
    }
    /** Member by member matchup. */
    isAlmostEqual(other) {
        if (other instanceof ItalianSpiralEvaluator) {
            return Geometry.isSameCoordinate(this.nominalLength1, other.nominalLength1)
                && Geometry.isSameCoordinate(this.nominalRadius1, other.nominalRadius1);
        }
        return false;
    }
    /**
     * Return a (fast but mediocre) approximation of spiral length as a function of x axis position.
     * * This x-to-distance relation is not as precise as the CurvePrimitive method moveSignedDistanceFromFraction.
     * * It is supported here for users interested in replicating the Czech distance mapping rather than the more accurate
     * CurvePrimitive measurements.
     * @param x distance along the x axis.
     */
    distanceToItalianApproximateX(x) {
        return CzechSpiralEvaluator.forwardL2R2Map(x, -1.0, this.nominalLength1, this.nominalRadius1);
    }
    /**
     * Return the inverse of the `forwardL2R2Map` function.
     * * The undefined result can only occur for distances outside the usual spirals.
     * @param d (approximate) distance along the spiral.
     */
    xToItalianApproximateDistance(d) {
        return CzechSpiralEvaluator.inverseL2R2Map(d, -1.0, this.nominalLength1, this.nominalRadius1);
    }
}
//# sourceMappingURL=CzechSpiralEvaluator.js.map