/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Geometry } from "../../Geometry";
import { Range3d } from "../../geometry3d/Range";
import { Segment1d } from "../../geometry3d/Segment1d";
import { Transform } from "../../geometry3d/Transform";
import { CurvePrimitive } from "../CurvePrimitive";
import { CurveOffsetXYHandler } from "../internalContexts/CurveOffsetXYHandler";
import { PlaneAltitudeRangeContext } from "../internalContexts/PlaneAltitudeRangeContext";
import { OffsetOptions } from "../OffsetOptions";
/**
 * TransitionSpiral3d is a base class for multiple variants of spirals.
 * * The menagerie of spiral types have 2 broad categories:
 *   * IntegratedSpiral3d -- a spiral whose direct function for curvature versus distance must be integrated to
 * determine x,y.
 *     * The IntegratedSpiral3d types are enumerated in `IntegratedSpiralTypes`.
 *   * DirectSpiral3d -- a spiral implemented with direct calculation of x,y from fractional position along the spiral.
 *     * The direct spiral types are enumerated in the `DirectSpiralType`.
 * * The method set for CurvePrimitive support includes a `handleTransitionSpiral(g: TransitionSpiral3d)` which receives
 * all the spiral types.
 * * The spiral class may impose expectations that its inflection is at the origin, with tangent along the x axis.
 *   * This is generally necessary for direct spirals.
 *   * This is not necessary for integrated spirals.
 * @public
 */
// see internaldocs/Spiral.md for more info
export class TransitionSpiral3d extends CurvePrimitive {
    /** String name of spiral type. */
    _spiralType;
    /** Original defining properties. */
    _designProperties;
    /** Placement transform. */
    _localToWorld;
    /**
     * Fractional interval for the "active" part of a containing spiral.
     * (the radius, angle, and length conditions define a complete spiral, and some portion of it is "active")
     */
    _activeFractionInterval;
    /** Return (reference to) the active portion of the reference spiral. */
    get activeFractionInterval() {
        return this._activeFractionInterval;
    }
    /** (Reference to) placement transform. */
    get localToWorld() {
        return this._localToWorld;
    }
    constructor(spiralType, localToWorld, activeFractionInterval, designProperties) {
        super();
        this._spiralType = spiralType ? spiralType : "unknownSpiralType";
        this._designProperties = designProperties;
        this._localToWorld = localToWorld;
        this._activeFractionInterval = activeFractionInterval ? activeFractionInterval : Segment1d.create(0, 1);
    }
    get spiralType() {
        return this._spiralType;
    }
    /** Return 1/r with convention that exact zero input returns 0 (straight line, zero curvature, infinite radius). */
    static radiusToCurvature(radius) {
        return (radius === 0.0) ? 0.0 : 1.0 / radius;
    }
    /** Return 1/k with convention that near-zero input returns 0 (straight line, zero curvature, infinite radius). */
    static curvatureToRadius(curvature) {
        if (Math.abs(curvature) < Geometry.smallAngleRadians)
            return 0.0;
        return 1.0 / curvature;
    }
    /** Return (if possible) a spiral which is a portion of this curve. */
    clonePartialCurve(fractionA, fractionB) {
        const spiralB = this.clone();
        const globalFractionA = this._activeFractionInterval.fractionToPoint(fractionA);
        const globalFractionB = this._activeFractionInterval.fractionToPoint(fractionB);
        spiralB._activeFractionInterval.set(globalFractionA, globalFractionB);
        spiralB.refreshComputedProperties();
        return spiralB;
    }
    /** Clone with a transform applied.  */
    cloneTransformed(transform) {
        const result = this.clone();
        result.tryTransformInPlace(transform); // we're confident it will always work
        return result;
    }
    /** Return the average of the start and end curvatures. */
    static averageCurvature(radiusLimits) {
        return 0.5 * (TransitionSpiral3d.radiusToCurvature(radiusLimits.x0) + TransitionSpiral3d.radiusToCurvature(radiusLimits.x1));
    }
    /**
     * Given two radii (or zeros for 0 curvature) return the average curvature.
     * @param r0 start radius, or 0 for line.
     * @param r1 end radius, or 0 for line.
     */
    static averageCurvatureR0R1(r0, r1) {
        return 0.5 * (TransitionSpiral3d.radiusToCurvature(r0) + TransitionSpiral3d.radiusToCurvature(r1));
    }
    /**
     * Given two radii (or zeros for 0 curvature) return the curvature at the given fraction.
     * @param r0 start radius, or 0 for line.
     * @param fraction fractional position between the curvatures defined by r0 and r1.
     * @param r1 end radius, or 0 for line.
     */
    static interpolateCurvatureR0R1(r0, fraction, r1) {
        return Geometry.interpolate(TransitionSpiral3d.radiusToCurvature(r0), fraction, TransitionSpiral3d.radiusToCurvature(r1));
    }
    /** Return the arc length of a transition spiral with given sweep and radius pair. */
    static radiusRadiusSweepRadiansToArcLength(radius0, radius1, sweepRadians) {
        return Math.abs(sweepRadians / TransitionSpiral3d.averageCurvatureR0R1(radius0, radius1));
    }
    /** Return the turn angle for spiral of given length between two radii */
    static radiusRadiusLengthToSweepRadians(radius0, radius1, arcLength) {
        return TransitionSpiral3d.averageCurvatureR0R1(radius0, radius1) * arcLength;
    }
    /** Return the end radius for spiral of given start radius, length, and turn angle. */
    static radius0LengthSweepRadiansToRadius1(radius0, arcLength, sweepRadians) {
        return TransitionSpiral3d.curvatureToRadius((2.0 * sweepRadians / arcLength) - TransitionSpiral3d.radiusToCurvature(radius0));
    }
    /** Return the start radius for spiral of given end radius, length, and turn angle. */
    static radius1LengthSweepRadiansToRadius0(radius1, arcLength, sweepRadians) {
        return TransitionSpiral3d.curvatureToRadius((2.0 * sweepRadians / arcLength) - TransitionSpiral3d.radiusToCurvature(radius1));
    }
    /** Return the original defining properties (if any) saved by the constructor. */
    get designProperties() {
        return this._designProperties;
    }
    /**
     * * If transformA is rigid with uniform scale, apply the rigid part of transformA to the localToWorld transform and
     * return the scale and rigid separation.
     * * If not rigid, do nothing and return undefined.
     * * Also apply the scale factor to the designProperties.
     * @param transformA
     */
    applyRigidPartOfTransform(transformA) {
        const rigidData = transformA.matrix.factorRigidWithSignedScale();
        if (rigidData !== undefined) {
            // [sQ a][R b] = [sQ*R sQb+a]
            // but we save it as [Q*R sQb+a] with spiral data scaled by s
            const transformC0 = transformA.multiplyTransformTransform(this.localToWorld);
            // but pull the scale part out of the matrix
            const matrixC = rigidData.rigidAxes.multiplyMatrixMatrix(this.localToWorld.matrix);
            this._localToWorld = Transform.createOriginAndMatrix(transformC0.origin, matrixC);
            if (this.designProperties)
                this.designProperties.applyScaleFactor(rigidData.scale);
            return rigidData;
        }
        return undefined;
    }
    /**
     * Construct an offset of the instance curve as viewed in the xy-plane (ignoring z).
     * * No attempt is made to join the offsets of smaller constituent primitives. To construct a fully joined offset for an
     * aggregate instance (e.g., LineString3d, CurveChainWithDistanceIndex), use RegionOps.constructCurveXYOffset() instead.
     * @param offsetDistanceOrOptions offset distance (positive to left of the instance curve), or options object.
     */
    constructOffsetXY(offsetDistanceOrOptions) {
        const options = OffsetOptions.create(offsetDistanceOrOptions);
        const handler = new CurveOffsetXYHandler(this, options.leftOffsetDistance);
        this.emitStrokableParts(handler, options.strokeOptions);
        return handler.claimResult();
    }
    /** Extend the range by the strokes of the spiral. */
    extendRange(rangeToExtend, transform) {
        rangeToExtend.extendRange(this.rangeBetweenFractions(0.0, 1.0, transform));
    }
    /**
     * Return the range of spiral between fractions of the activeStrokes.
     * * Use activeStrokes point count times interval factor for initial evaluation count, but do at least 5.
     */
    rangeBetweenFractions(fractionA, fractionB, transform) {
        const strokes = this.activeStrokes;
        if (undefined === strokes)
            return Range3d.createNull();
        let count = Math.ceil(strokes.numPoints() * Math.abs(fractionB - fractionA));
        count = Geometry.clamp(5, count, 30);
        return this.rangeBetweenFractionsByCount(fractionA, fractionB, count, transform, 0.5);
    }
    /**
     * Project instance geometry (via dispatch) onto the given ray, and return the extreme fractional parameters of
     * projection.
     * @param ray ray onto which the instance is projected. A `Vector3d` is treated as a `Ray3d` with zero origin.
     * @param lowHigh optional receiver for output
     * @returns range of fractional projection parameters onto the ray, where 0.0 is start of the ray and 1.0 is the
     * end of the ray.
     */
    projectedParameterRange(ray, lowHigh) {
        return PlaneAltitudeRangeContext.findExtremeFractionsAlongDirection(this, ray, lowHigh);
    }
}
//# sourceMappingURL=TransitionSpiral3d.js.map