import { Clamp, WithinEpsilon } from "./math.scalar.functions.js";
import { Vector2, Vector3, Quaternion, Matrix } from "./math.vector.pure.js";
import { Epsilon } from "./math.constants.js";
/**
 * Defines potential orientation for back face culling
 */
export var Orientation;
(function (Orientation) {
    /**
     * Clockwise
     */
    Orientation[Orientation["CW"] = 0] = "CW";
    /** Counter clockwise */
    Orientation[Orientation["CCW"] = 1] = "CCW";
})(Orientation || (Orientation = {}));
/** Class used to represent a Bezier curve */
export class BezierCurve {
    /**
     * Returns the cubic Bezier interpolated value (float) at "t" (float) from the given x1, y1, x2, y2 floats
     * @param t defines the time
     * @param x1 defines the left coordinate on X axis
     * @param y1 defines the left coordinate on Y axis
     * @param x2 defines the right coordinate on X axis
     * @param y2 defines the right coordinate on Y axis
     * @returns the interpolated value
     */
    static Interpolate(t, x1, y1, x2, y2) {
        if (t === 0) {
            return 0;
        }
        // Extract X (which is equal to time here)
        const f0 = 1 - 3 * x2 + 3 * x1;
        const f1 = 3 * x2 - 6 * x1;
        const f2 = 3 * x1;
        let refinedT = t;
        for (let i = 0; i < 5; i++) {
            const refinedT2 = refinedT * refinedT;
            const refinedT3 = refinedT2 * refinedT;
            const x = f0 * refinedT3 + f1 * refinedT2 + f2 * refinedT;
            const slope = 1.0 / (3.0 * f0 * refinedT2 + 2.0 * f1 * refinedT + f2);
            refinedT -= (x - t) * slope;
            refinedT = Math.min(1, Math.max(0, refinedT));
        }
        // Resolve cubic bezier for the given x
        return 3 * Math.pow(1 - refinedT, 2) * refinedT * y1 + 3 * (1 - refinedT) * Math.pow(refinedT, 2) * y2 + Math.pow(refinedT, 3);
    }
}
/**
 * Defines angle representation
 */
export class Angle {
    /**
     * Creates an Angle object of "radians" radians (float).
     * @param radians the angle in radians
     */
    constructor(radians) {
        this._radians = radians;
        if (this._radians < 0.0) {
            this._radians += 2.0 * Math.PI;
        }
    }
    /**
     * Get value in degrees
     * @returns the Angle value in degrees (float)
     */
    degrees() {
        return (this._radians * 180.0) / Math.PI;
    }
    /**
     * Get value in radians
     * @returns the Angle value in radians (float)
     */
    radians() {
        return this._radians;
    }
    /**
     * Gets a new Angle object with a value of the angle (in radians) between the line connecting the two points and the x-axis
     * @param a defines first point as the origin
     * @param b defines point
     * @returns a new Angle
     */
    static BetweenTwoPoints(a, b) {
        const delta = b.subtract(a);
        const theta = Math.atan2(delta.y, delta.x);
        return new Angle(theta);
    }
    /**
     * Gets the angle between the two vectors
     * @param a defines first vector
     * @param b defines vector
     * @returns Returns an new Angle between 0 and PI
     */
    static BetweenTwoVectors(a, b) {
        let product = a.lengthSquared() * b.lengthSquared();
        if (product === 0) {
            return new Angle(Math.PI / 2);
        }
        product = Math.sqrt(product);
        let cosVal = a.dot(b) / product;
        cosVal = Clamp(cosVal, -1, 1);
        const angle = Math.acos(cosVal);
        return new Angle(angle);
    }
    /**
     * Gets a new Angle object from the given float in radians
     * @param radians defines the angle value in radians
     * @returns a new Angle
     */
    static FromRadians(radians) {
        return new Angle(radians);
    }
    /**
     * Gets a new Angle object from the given float in degrees
     * @param degrees defines the angle value in degrees
     * @returns a new Angle
     */
    static FromDegrees(degrees) {
        return new Angle((degrees * Math.PI) / 180.0);
    }
}
/**
 * This represents an arc in a 2d space.
 */
export class Arc2 {
    /**
     * Creates an Arc object from the three given points : start, middle and end.
     * @param startPoint Defines the start point of the arc
     * @param midPoint Defines the middle point of the arc
     * @param endPoint Defines the end point of the arc
     */
    constructor(
    /** Defines the start point of the arc */
    startPoint, 
    /** Defines the mid point of the arc */
    midPoint, 
    /** Defines the end point of the arc */
    endPoint) {
        this.startPoint = startPoint;
        this.midPoint = midPoint;
        this.endPoint = endPoint;
        const temp = Math.pow(midPoint.x, 2) + Math.pow(midPoint.y, 2);
        const startToMid = (Math.pow(startPoint.x, 2) + Math.pow(startPoint.y, 2) - temp) / 2;
        const midToEnd = (temp - Math.pow(endPoint.x, 2) - Math.pow(endPoint.y, 2)) / 2;
        const det = (startPoint.x - midPoint.x) * (midPoint.y - endPoint.y) - (midPoint.x - endPoint.x) * (startPoint.y - midPoint.y);
        this.centerPoint = new Vector2((startToMid * (midPoint.y - endPoint.y) - midToEnd * (startPoint.y - midPoint.y)) / det, ((startPoint.x - midPoint.x) * midToEnd - (midPoint.x - endPoint.x) * startToMid) / det);
        this.radius = this.centerPoint.subtract(this.startPoint).length();
        this.startAngle = Angle.BetweenTwoPoints(this.centerPoint, this.startPoint);
        const a1 = this.startAngle.degrees();
        let a2 = Angle.BetweenTwoPoints(this.centerPoint, this.midPoint).degrees();
        let a3 = Angle.BetweenTwoPoints(this.centerPoint, this.endPoint).degrees();
        // angles correction
        if (a2 - a1 > +180.0) {
            a2 -= 360.0;
        }
        if (a2 - a1 < -180.0) {
            a2 += 360.0;
        }
        if (a3 - a2 > +180.0) {
            a3 -= 360.0;
        }
        if (a3 - a2 < -180.0) {
            a3 += 360.0;
        }
        this.orientation = a2 - a1 < 0 ? 0 /* Orientation.CW */ : 1 /* Orientation.CCW */;
        this.angle = Angle.FromDegrees(this.orientation === 0 /* Orientation.CW */ ? a1 - a3 : a3 - a1);
    }
}
/**
 * Represents a 2D path made up of multiple 2D points
 */
export class Path2 {
    /**
     * Creates a Path2 object from the starting 2D coordinates x and y.
     * @param x the starting points x value
     * @param y the starting points y value
     */
    constructor(x, y) {
        this._points = new Array();
        this._length = 0.0;
        /**
         * If the path start and end point are the same
         */
        this.closed = false;
        this._points.push(new Vector2(x, y));
    }
    /**
     * Adds a new segment until the given coordinates (x, y) to the current Path2.
     * @param x the added points x value
     * @param y the added points y value
     * @returns the updated Path2.
     */
    addLineTo(x, y) {
        if (this.closed) {
            return this;
        }
        const newPoint = new Vector2(x, y);
        const previousPoint = this._points[this._points.length - 1];
        this._points.push(newPoint);
        this._length += newPoint.subtract(previousPoint).length();
        return this;
    }
    /**
     * Adds _numberOfSegments_ segments according to the arc definition (middle point coordinates, end point coordinates, the arc start point being the current Path2 last point) to the current Path2.
     * @param midX middle point x value
     * @param midY middle point y value
     * @param endX end point x value
     * @param endY end point y value
     * @param numberOfSegments (default: 36)
     * @returns the updated Path2.
     */
    addArcTo(midX, midY, endX, endY, numberOfSegments = 36) {
        if (this.closed) {
            return this;
        }
        const startPoint = this._points[this._points.length - 1];
        const midPoint = new Vector2(midX, midY);
        const endPoint = new Vector2(endX, endY);
        const arc = new Arc2(startPoint, midPoint, endPoint);
        let increment = arc.angle.radians() / numberOfSegments;
        if (arc.orientation === 0 /* Orientation.CW */) {
            increment *= -1;
        }
        let currentAngle = arc.startAngle.radians() + increment;
        for (let i = 0; i < numberOfSegments; i++) {
            const x = Math.cos(currentAngle) * arc.radius + arc.centerPoint.x;
            const y = Math.sin(currentAngle) * arc.radius + arc.centerPoint.y;
            this.addLineTo(x, y);
            currentAngle += increment;
        }
        return this;
    }
    /**
     * Adds _numberOfSegments_ segments according to the quadratic curve definition to the current Path2.
     * @param controlX control point x value
     * @param controlY control point y value
     * @param endX end point x value
     * @param endY end point y value
     * @param numberOfSegments (default: 36)
     * @returns the updated Path2.
     */
    addQuadraticCurveTo(controlX, controlY, endX, endY, numberOfSegments = 36) {
        if (this.closed) {
            return this;
        }
        const equation = (t, val0, val1, val2) => {
            const res = (1.0 - t) * (1.0 - t) * val0 + 2.0 * t * (1.0 - t) * val1 + t * t * val2;
            return res;
        };
        const startPoint = this._points[this._points.length - 1];
        for (let i = 0; i <= numberOfSegments; i++) {
            const step = i / numberOfSegments;
            const x = equation(step, startPoint.x, controlX, endX);
            const y = equation(step, startPoint.y, controlY, endY);
            this.addLineTo(x, y);
        }
        return this;
    }
    /**
     * Adds _numberOfSegments_ segments according to the bezier curve definition to the current Path2.
     * @param originTangentX tangent vector at the origin point x value
     * @param originTangentY tangent vector at the origin point y value
     * @param destinationTangentX tangent vector at the destination point x value
     * @param destinationTangentY tangent vector at the destination point y value
     * @param endX end point x value
     * @param endY end point y value
     * @param numberOfSegments (default: 36)
     * @returns the updated Path2.
     */
    addBezierCurveTo(originTangentX, originTangentY, destinationTangentX, destinationTangentY, endX, endY, numberOfSegments = 36) {
        if (this.closed) {
            return this;
        }
        const equation = (t, val0, val1, val2, val3) => {
            const res = (1.0 - t) * (1.0 - t) * (1.0 - t) * val0 + 3.0 * t * (1.0 - t) * (1.0 - t) * val1 + 3.0 * t * t * (1.0 - t) * val2 + t * t * t * val3;
            return res;
        };
        const startPoint = this._points[this._points.length - 1];
        for (let i = 0; i <= numberOfSegments; i++) {
            const step = i / numberOfSegments;
            const x = equation(step, startPoint.x, originTangentX, destinationTangentX, endX);
            const y = equation(step, startPoint.y, originTangentY, destinationTangentY, endY);
            this.addLineTo(x, y);
        }
        return this;
    }
    /**
     * Defines if a given point is inside the polygon defines by the path
     * @param point defines the point to test
     * @returns true if the point is inside
     */
    isPointInside(point) {
        let isInside = false;
        const count = this._points.length;
        for (let p = count - 1, q = 0; q < count; p = q++) {
            let edgeLow = this._points[p];
            let edgeHigh = this._points[q];
            let edgeDx = edgeHigh.x - edgeLow.x;
            let edgeDy = edgeHigh.y - edgeLow.y;
            if (Math.abs(edgeDy) > Number.EPSILON) {
                // Not parallel
                if (edgeDy < 0) {
                    edgeLow = this._points[q];
                    edgeDx = -edgeDx;
                    edgeHigh = this._points[p];
                    edgeDy = -edgeDy;
                }
                if (point.y < edgeLow.y || point.y > edgeHigh.y) {
                    continue;
                }
                if (point.y === edgeLow.y && point.x === edgeLow.x) {
                    return true;
                }
                else {
                    const perpEdge = edgeDy * (point.x - edgeLow.x) - edgeDx * (point.y - edgeLow.y);
                    if (perpEdge === 0) {
                        return true;
                    }
                    if (perpEdge < 0) {
                        continue;
                    }
                    isInside = !isInside;
                }
            }
            else {
                // parallel or collinear
                if (point.y !== edgeLow.y) {
                    continue;
                }
                if ((edgeHigh.x <= point.x && point.x <= edgeLow.x) || (edgeLow.x <= point.x && point.x <= edgeHigh.x)) {
                    return true;
                }
            }
        }
        return isInside;
    }
    /**
     * Closes the Path2.
     * @returns the Path2.
     */
    close() {
        this.closed = true;
        return this;
    }
    /**
     * Gets the sum of the distance between each sequential point in the path
     * @returns the Path2 total length (float).
     */
    length() {
        let result = this._length;
        if (this.closed) {
            const lastPoint = this._points[this._points.length - 1];
            const firstPoint = this._points[0];
            result += firstPoint.subtract(lastPoint).length();
        }
        return result;
    }
    /**
     * Gets the area of the polygon defined by the path
     * @returns area value
     */
    area() {
        const n = this._points.length;
        let value = 0.0;
        for (let p = n - 1, q = 0; q < n; p = q++) {
            value += this._points[p].x * this._points[q].y - this._points[q].x * this._points[p].y;
        }
        return value * 0.5;
    }
    /**
     * Gets the points which construct the path
     * @returns the Path2 internal array of points.
     */
    getPoints() {
        return this._points;
    }
    /**
     * Retrieves the point at the distance aways from the starting point
     * @param normalizedLengthPosition the length along the path to retrieve the point from
     * @returns a new Vector2 located at a percentage of the Path2 total length on this path.
     */
    getPointAtLengthPosition(normalizedLengthPosition) {
        if (normalizedLengthPosition < 0 || normalizedLengthPosition > 1) {
            return Vector2.Zero();
        }
        const lengthPosition = normalizedLengthPosition * this.length();
        let previousOffset = 0;
        for (let i = 0; i < this._points.length; i++) {
            const j = (i + 1) % this._points.length;
            const a = this._points[i];
            const b = this._points[j];
            const bToA = b.subtract(a);
            const nextOffset = bToA.length() + previousOffset;
            if (lengthPosition >= previousOffset && lengthPosition <= nextOffset) {
                const dir = bToA.normalize();
                const localOffset = lengthPosition - previousOffset;
                return new Vector2(a.x + dir.x * localOffset, a.y + dir.y * localOffset);
            }
            previousOffset = nextOffset;
        }
        return Vector2.Zero();
    }
    /**
     * Creates a new path starting from an x and y position
     * @param x starting x value
     * @param y starting y value
     * @returns a new Path2 starting at the coordinates (x, y).
     */
    static StartingAt(x, y) {
        return new Path2(x, y);
    }
}
/**
 * Represents a 3D path made up of multiple 3D points
 * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/path3D
 */
export class Path3D {
    /**
     * new Path3D(path, normal, raw)
     * Creates a Path3D. A Path3D is a logical math object, so not a mesh.
     * please read the description in the tutorial : https://doc.babylonjs.com/features/featuresDeepDive/mesh/path3D
     * @param path an array of Vector3, the curve axis of the Path3D
     * @param firstNormal (options) Vector3, the first wanted normal to the curve. Ex (0, 1, 0) for a vertical normal.
     * @param raw (optional, default false) : boolean, if true the returned Path3D isn't normalized. Useful to depict path acceleration or speed.
     * @param alignTangentsWithPath (optional, default false) : boolean, if true the tangents will be aligned with the path.
     */
    constructor(
    /**
     * an array of Vector3, the curve axis of the Path3D
     */
    path, firstNormal = null, raw, alignTangentsWithPath = false) {
        this.path = path;
        this._curve = new Array();
        this._distances = new Array();
        this._tangents = new Array();
        this._normals = new Array();
        this._binormals = new Array();
        // holds interpolated point data
        this._pointAtData = {
            id: 0,
            point: Vector3.Zero(),
            previousPointArrayIndex: 0,
            position: 0,
            subPosition: 0,
            interpolateReady: false,
            interpolationMatrix: Matrix.Identity(),
        };
        for (let p = 0; p < path.length; p++) {
            this._curve[p] = path[p].clone(); // hard copy
        }
        this._raw = raw || false;
        this._alignTangentsWithPath = alignTangentsWithPath;
        this._compute(firstNormal, alignTangentsWithPath);
    }
    /**
     * Returns the Path3D array of successive Vector3 designing its curve.
     * @returns the Path3D array of successive Vector3 designing its curve.
     */
    getCurve() {
        return this._curve;
    }
    /**
     * Returns the Path3D array of successive Vector3 designing its curve.
     * @returns the Path3D array of successive Vector3 designing its curve.
     */
    getPoints() {
        return this._curve;
    }
    /**
     * @returns the computed length (float) of the path.
     */
    length() {
        return this._distances[this._distances.length - 1];
    }
    /**
     * Returns an array populated with tangent vectors on each Path3D curve point.
     * @returns an array populated with tangent vectors on each Path3D curve point.
     */
    getTangents() {
        return this._tangents;
    }
    /**
     * Returns an array populated with normal vectors on each Path3D curve point.
     * @returns an array populated with normal vectors on each Path3D curve point.
     */
    getNormals() {
        return this._normals;
    }
    /**
     * Returns an array populated with binormal vectors on each Path3D curve point.
     * @returns an array populated with binormal vectors on each Path3D curve point.
     */
    getBinormals() {
        return this._binormals;
    }
    /**
     * Returns an array populated with distances (float) of the i-th point from the first curve point.
     * @returns an array populated with distances (float) of the i-th point from the first curve point.
     */
    getDistances() {
        return this._distances;
    }
    /**
     * Returns an interpolated point along this path
     * @param position the position of the point along this path, from 0.0 to 1.0
     * @returns a new Vector3 as the point
     */
    getPointAt(position) {
        return this._updatePointAtData(position).point;
    }
    /**
     * Returns the tangent vector of an interpolated Path3D curve point at the specified position along this path.
     * @param position the position of the point along this path, from 0.0 to 1.0
     * @param interpolated (optional, default false) : boolean, if true returns an interpolated tangent instead of the tangent of the previous path point.
     * @returns a tangent vector corresponding to the interpolated Path3D curve point, if not interpolated, the tangent is taken from the precomputed tangents array.
     */
    getTangentAt(position, interpolated = false) {
        this._updatePointAtData(position, interpolated);
        return interpolated ? Vector3.TransformCoordinates(Vector3.Forward(), this._pointAtData.interpolationMatrix) : this._tangents[this._pointAtData.previousPointArrayIndex];
    }
    /**
     * Returns the tangent vector of an interpolated Path3D curve point at the specified position along this path.
     * @param position the position of the point along this path, from 0.0 to 1.0
     * @param interpolated (optional, default false) : boolean, if true returns an interpolated normal instead of the normal of the previous path point.
     * @returns a normal vector corresponding to the interpolated Path3D curve point, if not interpolated, the normal is taken from the precomputed normals array.
     */
    getNormalAt(position, interpolated = false) {
        this._updatePointAtData(position, interpolated);
        return interpolated ? Vector3.TransformCoordinates(Vector3.Right(), this._pointAtData.interpolationMatrix) : this._normals[this._pointAtData.previousPointArrayIndex];
    }
    /**
     * Returns the binormal vector of an interpolated Path3D curve point at the specified position along this path.
     * @param position the position of the point along this path, from 0.0 to 1.0
     * @param interpolated (optional, default false) : boolean, if true returns an interpolated binormal instead of the binormal of the previous path point.
     * @returns a binormal vector corresponding to the interpolated Path3D curve point, if not interpolated, the binormal is taken from the precomputed binormals array.
     */
    getBinormalAt(position, interpolated = false) {
        this._updatePointAtData(position, interpolated);
        return interpolated ? Vector3.TransformCoordinates(Vector3.UpReadOnly, this._pointAtData.interpolationMatrix) : this._binormals[this._pointAtData.previousPointArrayIndex];
    }
    /**
     * Returns the distance (float) of an interpolated Path3D curve point at the specified position along this path.
     * @param position the position of the point along this path, from 0.0 to 1.0
     * @returns the distance of the interpolated Path3D curve point at the specified position along this path.
     */
    getDistanceAt(position) {
        return this.length() * position;
    }
    /**
     * Returns the array index of the previous point of an interpolated point along this path
     * @param position the position of the point to interpolate along this path, from 0.0 to 1.0
     * @returns the array index
     */
    getPreviousPointIndexAt(position) {
        this._updatePointAtData(position);
        return this._pointAtData.previousPointArrayIndex;
    }
    /**
     * Returns the position of an interpolated point relative to the two path points it lies between, from 0.0 (point A) to 1.0 (point B)
     * @param position the position of the point to interpolate along this path, from 0.0 to 1.0
     * @returns the sub position
     */
    getSubPositionAt(position) {
        this._updatePointAtData(position);
        return this._pointAtData.subPosition;
    }
    /**
     * Returns the position of the closest virtual point on this path to an arbitrary Vector3, from 0.0 to 1.0
     * @param target the vector of which to get the closest position to
     * @returns the position of the closest virtual point on this path to the target vector
     */
    getClosestPositionTo(target) {
        let smallestDistance = Number.MAX_VALUE;
        let closestPosition = 0.0;
        for (let i = 0; i < this._curve.length - 1; i++) {
            const point = this._curve[i + 0];
            const tangent = this._curve[i + 1].subtract(point).normalize();
            const subLength = this._distances[i + 1] - this._distances[i + 0];
            const subPosition = Math.min((Math.max(Vector3.Dot(tangent, target.subtract(point).normalize()), 0.0) * Vector3.Distance(point, target)) / subLength, 1.0);
            const distance = Vector3.Distance(point.add(tangent.scale(subPosition * subLength)), target);
            if (distance < smallestDistance) {
                smallestDistance = distance;
                closestPosition = (this._distances[i + 0] + subLength * subPosition) / this.length();
            }
        }
        return closestPosition;
    }
    /**
     * Returns a sub path (slice) of this path
     * @param start the position of the fist path point, from 0.0 to 1.0, or a negative value, which will get wrapped around from the end of the path to 0.0 to 1.0 values
     * @param end the position of the last path point, from 0.0 to 1.0, or a negative value, which will get wrapped around from the end of the path to 0.0 to 1.0 values
     * @returns a sub path (slice) of this path
     */
    slice(start = 0.0, end = 1.0) {
        if (start < 0.0) {
            start = 1 - ((start * -1.0) % 1.0);
        }
        if (end < 0.0) {
            end = 1 - ((end * -1.0) % 1.0);
        }
        if (start > end) {
            const _start = start;
            start = end;
            end = _start;
        }
        const curvePoints = this.getCurve();
        const startPoint = this.getPointAt(start);
        let startIndex = this.getPreviousPointIndexAt(start);
        const endPoint = this.getPointAt(end);
        const endIndex = this.getPreviousPointIndexAt(end) + 1;
        const slicePoints = [];
        if (start !== 0.0) {
            startIndex++;
            slicePoints.push(startPoint);
        }
        slicePoints.push(...curvePoints.slice(startIndex, endIndex));
        if (end !== 1.0 || start === 1.0) {
            slicePoints.push(endPoint);
        }
        return new Path3D(slicePoints, this.getNormalAt(start), this._raw, this._alignTangentsWithPath);
    }
    /**
     * Forces the Path3D tangent, normal, binormal and distance recomputation.
     * @param path path which all values are copied into the curves points
     * @param firstNormal which should be projected onto the curve
     * @param alignTangentsWithPath (optional, default false) : boolean, if true the tangents will be aligned with the path
     * @returns the same object updated.
     */
    update(path, firstNormal = null, alignTangentsWithPath = false) {
        for (let p = 0; p < path.length; p++) {
            if (!this._curve[p]) {
                this._curve[p] = path[p].clone();
            }
            else {
                this._curve[p].x = path[p].x;
                this._curve[p].y = path[p].y;
                this._curve[p].z = path[p].z;
            }
        }
        this._curve.length = path.length;
        this._compute(firstNormal, alignTangentsWithPath);
        return this;
    }
    // private function compute() : computes tangents, normals and binormals
    _compute(firstNormal, alignTangentsWithPath = false) {
        const l = this._curve.length;
        if (l < 2) {
            return;
        }
        // first and last tangents
        this._tangents[0] = this._getFirstNonNullVector(0);
        if (!this._raw) {
            this._tangents[0].normalize();
        }
        this._tangents[l - 1] = this._curve[l - 1].subtract(this._curve[l - 2]);
        if (!this._raw) {
            this._tangents[l - 1].normalize();
        }
        // normals and binormals at first point : arbitrary vector with _normalVector()
        const tg0 = this._tangents[0];
        const pp0 = this._normalVector(tg0, firstNormal);
        this._normals[0] = pp0;
        if (!this._raw) {
            this._normals[0].normalize();
        }
        this._binormals[0] = Vector3.Cross(tg0, this._normals[0]);
        if (!this._raw) {
            this._binormals[0].normalize();
        }
        this._distances[0] = 0.0;
        // normals and binormals : next points
        let prev; // previous vector (segment)
        let cur; // current vector (segment)
        let curTang; // current tangent
        // previous normal
        let prevNor; // previous normal
        let prevBinor; // previous binormal
        for (let i = 1; i < l; i++) {
            // tangents
            prev = this._getLastNonNullVector(i);
            if (i < l - 1) {
                cur = this._getFirstNonNullVector(i);
                this._tangents[i] = alignTangentsWithPath ? cur : prev.add(cur);
                this._tangents[i].normalize();
            }
            this._distances[i] = this._distances[i - 1] + this._curve[i].subtract(this._curve[i - 1]).length();
            // normals and binormals
            // http://www.cs.cmu.edu/afs/andrew/scs/cs/15-462/web/old/asst2camera.html
            curTang = this._tangents[i];
            prevBinor = this._binormals[i - 1];
            this._normals[i] = Vector3.Cross(prevBinor, curTang);
            if (!this._raw) {
                if (this._normals[i].length() === 0) {
                    prevNor = this._normals[i - 1];
                    this._normals[i] = prevNor.clone();
                }
                else {
                    this._normals[i].normalize();
                }
            }
            this._binormals[i] = Vector3.Cross(curTang, this._normals[i]);
            if (!this._raw) {
                this._binormals[i].normalize();
            }
        }
        this._pointAtData.id = NaN;
    }
    // private function getFirstNonNullVector(index)
    // returns the first non null vector from index : curve[index + N].subtract(curve[index])
    _getFirstNonNullVector(index) {
        let i = 1;
        let nNVector = this._curve[index + i].subtract(this._curve[index]);
        while (nNVector.length() === 0 && index + i + 1 < this._curve.length) {
            i++;
            nNVector = this._curve[index + i].subtract(this._curve[index]);
        }
        return nNVector;
    }
    // private function getLastNonNullVector(index)
    // returns the last non null vector from index : curve[index].subtract(curve[index - N])
    _getLastNonNullVector(index) {
        let i = 1;
        let nLVector = this._curve[index].subtract(this._curve[index - i]);
        while (nLVector.length() === 0 && index > i + 1) {
            i++;
            nLVector = this._curve[index].subtract(this._curve[index - i]);
        }
        return nLVector;
    }
    // private function normalVector(v0, vt, va) :
    // returns an arbitrary point in the plane defined by the point v0 and the vector vt orthogonal to this plane
    // if va is passed, it returns the va projection on the plane orthogonal to vt at the point v0
    _normalVector(vt, va) {
        let normal0;
        let tgl = vt.length();
        if (tgl === 0.0) {
            tgl = 1.0;
        }
        if (va === undefined || va === null) {
            let point;
            if (!WithinEpsilon(Math.abs(vt.y) / tgl, 1.0, Epsilon)) {
                // search for a point in the plane
                point = new Vector3(0.0, -1.0, 0.0);
            }
            else if (!WithinEpsilon(Math.abs(vt.x) / tgl, 1.0, Epsilon)) {
                point = new Vector3(1.0, 0.0, 0.0);
            }
            else if (!WithinEpsilon(Math.abs(vt.z) / tgl, 1.0, Epsilon)) {
                point = new Vector3(0.0, 0.0, 1.0);
            }
            else {
                point = Vector3.Zero();
            }
            normal0 = Vector3.Cross(vt, point);
        }
        else {
            normal0 = Vector3.Cross(vt, va);
            Vector3.CrossToRef(normal0, vt, normal0);
        }
        normal0.normalize();
        return normal0;
    }
    /**
     * Updates the point at data for an interpolated point along this curve
     * @param position the position of the point along this curve, from 0.0 to 1.0
     * @param interpolateTNB
     * @interpolateTNB whether to compute the interpolated tangent, normal and binormal
     * @returns the (updated) point at data
     */
    _updatePointAtData(position, interpolateTNB = false) {
        // set an id for caching the result
        if (this._pointAtData.id === position) {
            if (!this._pointAtData.interpolateReady) {
                this._updateInterpolationMatrix();
            }
            return this._pointAtData;
        }
        else {
            this._pointAtData.id = position;
        }
        const curvePoints = this.getPoints();
        // clamp position between 0.0 and 1.0
        if (position <= 0.0) {
            return this._setPointAtData(0.0, 0.0, curvePoints[0], 0, interpolateTNB);
        }
        else if (position >= 1.0) {
            return this._setPointAtData(1.0, 1.0, curvePoints[curvePoints.length - 1], curvePoints.length - 1, interpolateTNB);
        }
        let previousPoint = curvePoints[0];
        let currentPoint;
        let currentLength = 0.0;
        const targetLength = position * this.length();
        for (let i = 1; i < curvePoints.length; i++) {
            currentPoint = curvePoints[i];
            const distance = Vector3.Distance(previousPoint, currentPoint);
            currentLength += distance;
            if (currentLength === targetLength) {
                return this._setPointAtData(position, 1.0, currentPoint, i, interpolateTNB);
            }
            else if (currentLength > targetLength) {
                const toLength = currentLength - targetLength;
                const diff = toLength / distance;
                const dir = previousPoint.subtract(currentPoint);
                const point = currentPoint.add(dir.scaleInPlace(diff));
                return this._setPointAtData(position, 1 - diff, point, i - 1, interpolateTNB);
            }
            previousPoint = currentPoint;
        }
        return this._pointAtData;
    }
    /**
     * Updates the point at data from the specified parameters
     * @param position where along the path the interpolated point is, from 0.0 to 1.0
     * @param subPosition
     * @param point the interpolated point
     * @param parentIndex the index of an existing curve point that is on, or else positionally the first behind, the interpolated point
     * @param interpolateTNB whether to compute the interpolated tangent, normal and binormal
     * @returns the (updated) point at data
     */
    _setPointAtData(position, subPosition, point, parentIndex, interpolateTNB) {
        this._pointAtData.point = point;
        this._pointAtData.position = position;
        this._pointAtData.subPosition = subPosition;
        this._pointAtData.previousPointArrayIndex = parentIndex;
        this._pointAtData.interpolateReady = interpolateTNB;
        if (interpolateTNB) {
            this._updateInterpolationMatrix();
        }
        return this._pointAtData;
    }
    /**
     * Updates the point at interpolation matrix for the tangents, normals and binormals
     */
    _updateInterpolationMatrix() {
        this._pointAtData.interpolationMatrix = Matrix.Identity();
        const parentIndex = this._pointAtData.previousPointArrayIndex;
        if (parentIndex !== this._tangents.length - 1) {
            const index = parentIndex + 1;
            const tangentFrom = this._tangents[parentIndex].clone();
            const normalFrom = this._normals[parentIndex].clone();
            const binormalFrom = this._binormals[parentIndex].clone();
            const tangentTo = this._tangents[index].clone();
            const normalTo = this._normals[index].clone();
            const binormalTo = this._binormals[index].clone();
            const quatFrom = Quaternion.RotationQuaternionFromAxis(normalFrom, binormalFrom, tangentFrom);
            const quatTo = Quaternion.RotationQuaternionFromAxis(normalTo, binormalTo, tangentTo);
            const quatAt = Quaternion.Slerp(quatFrom, quatTo, this._pointAtData.subPosition);
            quatAt.toRotationMatrix(this._pointAtData.interpolationMatrix);
        }
    }
}
/**
 * A Curve3 object is a logical object, so not a mesh, to handle curves in the 3D geometric space.
 * A Curve3 is designed from a series of successive Vector3.
 * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves
 */
export class Curve3 {
    /**
     * Returns a Curve3 object along a Quadratic Bezier curve : https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves#quadratic-bezier-curve
     * @param v0 (Vector3) the origin point of the Quadratic Bezier
     * @param v1 (Vector3) the control point
     * @param v2 (Vector3) the end point of the Quadratic Bezier
     * @param nbPoints (integer) the wanted number of points in the curve
     * @param ref (Curve3) the updated Curve3 ref
     * @returns the created Curve3
     */
    static CreateQuadraticBezier(v0, v1, v2, nbPoints, ref) {
        nbPoints = nbPoints > 2 ? nbPoints : 3;
        const bez = ref ? ref.getPoints() : [];
        const equation = (t, val0, val1, val2) => {
            return (1.0 - t) * (1.0 - t) * val0 + 2.0 * t * (1.0 - t) * val1 + t * t * val2;
        };
        for (let i = 0; i <= nbPoints; i++) {
            const x = equation(i / nbPoints, v0.x, v1.x, v2.x);
            const y = equation(i / nbPoints, v0.y, v1.y, v2.y);
            const z = equation(i / nbPoints, v0.z, v1.z, v2.z);
            if (bez[i]) {
                bez[i].copyFromFloats(x, y, z);
            }
            else {
                bez.push(new Vector3(x, y, z));
            }
        }
        bez.length = nbPoints + 1;
        return ref || new Curve3(bez);
    }
    /**
     * Returns a Curve3 object along a Cubic Bezier curve : https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves#cubic-bezier-curve
     * @param v0 (Vector3) the origin point of the Cubic Bezier
     * @param v1 (Vector3) the first control point
     * @param v2 (Vector3) the second control point
     * @param v3 (Vector3) the end point of the Cubic Bezier
     * @param nbPoints (integer) the wanted number of points in the curve
     * @param ref (Curve3) the updated Curve3 ref
     * @returns the created Curve3
     */
    static CreateCubicBezier(v0, v1, v2, v3, nbPoints, ref) {
        nbPoints = nbPoints > 3 ? nbPoints : 4;
        const bez = ref ? ref.getPoints() : [];
        const equation = (t, val0, val1, val2, val3) => {
            const res = (1.0 - t) * (1.0 - t) * (1.0 - t) * val0 + 3.0 * t * (1.0 - t) * (1.0 - t) * val1 + 3.0 * t * t * (1.0 - t) * val2 + t * t * t * val3;
            return res;
        };
        for (let i = 0; i <= nbPoints; i++) {
            const x = equation(i / nbPoints, v0.x, v1.x, v2.x, v3.x);
            const y = equation(i / nbPoints, v0.y, v1.y, v2.y, v3.y);
            const z = equation(i / nbPoints, v0.z, v1.z, v2.z, v3.z);
            if (bez[i]) {
                bez[i].copyFromFloats(x, y, z);
            }
            else {
                bez.push(new Vector3(x, y, z));
            }
        }
        bez.length = nbPoints + 1;
        return ref || new Curve3(bez);
    }
    /**
     * Returns a Curve3 object along a Hermite Spline curve : https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves#hermite-spline
     * @param p1 (Vector3) the origin point of the Hermite Spline
     * @param t1 (Vector3) the tangent vector at the origin point
     * @param p2 (Vector3) the end point of the Hermite Spline
     * @param t2 (Vector3) the tangent vector at the end point
     * @param nSeg (integer) the number of curve segments or nSeg + 1 points in the array
     * @param ref (Curve3) the updated Curve3 ref
     * @returns the created Curve3
     */
    static CreateHermiteSpline(p1, t1, p2, t2, nSeg, ref) {
        const hermite = ref ? ref.getPoints() : [];
        const step = 1.0 / nSeg;
        for (let i = 0; i <= nSeg; i++) {
            if (hermite[i]) {
                Vector3.HermiteToRef(p1, t1, p2, t2, i * step, hermite[i]);
            }
            else {
                hermite.push(Vector3.Hermite(p1, t1, p2, t2, i * step));
            }
        }
        hermite.length = nSeg + 1;
        return ref || new Curve3(hermite);
    }
    /**
     * Returns a Curve3 object along a CatmullRom Spline curve :
     * @param points (array of Vector3) the points the spline must pass through. At least, four points required
     * @param nbPoints (integer) the wanted number of points between each curve control points
     * @param closed (boolean) optional with default false, when true forms a closed loop from the points
     * @param ref (Curve3) the updated Curve3 ref
     * @returns the created Curve3
     */
    static CreateCatmullRomSpline(points, nbPoints, closed, ref) {
        const catmullRom = ref ? ref.getPoints() : [];
        const step = 1.0 / nbPoints;
        let amount = 0.0;
        if (closed) {
            const pointsCount = points.length;
            for (let i = 0; i < pointsCount; i++) {
                amount = 0;
                for (let c = 0; c < nbPoints; c++) {
                    if (catmullRom[i]) {
                        Vector3.CatmullRomToRef(points[i % pointsCount], points[(i + 1) % pointsCount], points[(i + 2) % pointsCount], points[(i + 3) % pointsCount], amount, catmullRom[i]);
                    }
                    else {
                        catmullRom.push(Vector3.CatmullRom(points[i % pointsCount], points[(i + 1) % pointsCount], points[(i + 2) % pointsCount], points[(i + 3) % pointsCount], amount));
                    }
                    amount += step;
                }
            }
            if (catmullRom[pointsCount]) {
                catmullRom[pointsCount].copyFrom(catmullRom[0]);
            }
            else {
                catmullRom.push(catmullRom[0]);
            }
        }
        else {
            const totalPoints = [];
            totalPoints.push(points[0].clone());
            totalPoints.push(...points);
            totalPoints.push(points[points.length - 1].clone());
            let i = 0;
            let index = 0;
            const pushPoint = () => {
                if (catmullRom[index]) {
                    Vector3.CatmullRomToRef(totalPoints[i], totalPoints[i + 1], totalPoints[i + 2], totalPoints[i + 3], amount, catmullRom[index]);
                }
                else {
                    catmullRom.push(Vector3.CatmullRom(totalPoints[i], totalPoints[i + 1], totalPoints[i + 2], totalPoints[i + 3], amount));
                }
            };
            for (; i < totalPoints.length - 3; i++) {
                amount = 0;
                for (let c = 0; c < nbPoints; c++) {
                    pushPoint();
                    amount += step;
                    index++;
                }
            }
            i--;
            pushPoint();
            index++;
            catmullRom.length = index;
        }
        return ref || new Curve3(catmullRom);
    }
    /**
     * Returns a Curve3 object along an arc through three vector3 points:
     * The three points should not be colinear. When they are the Curve3 is empty.
     * @param first (Vector3) the first point the arc must pass through.
     * @param second (Vector3) the second point the arc must pass through.
     * @param third (Vector3) the third point the arc must pass through.
     * @param steps (number) the larger the number of steps the more detailed the arc.
     * @param closed (boolean) optional with default false, when true forms the chord from the first and third point
     * @param fullCircle Circle (boolean) optional with default false, when true forms the complete circle through the three points
     * @param ref (Curve3) the updated Curve3 ref
     * @returns the created Curve3
     */
    static ArcThru3Points(first, second, third, steps = 32, closed = false, fullCircle = false, ref) {
        const arc = ref ? ref.getPoints() : [];
        const vec1 = second.subtract(first);
        const vec2 = third.subtract(second);
        const vec3 = first.subtract(third);
        const zAxis = Vector3.Cross(vec1, vec2);
        const len4 = zAxis.length();
        if (len4 < Math.pow(10, -8)) {
            if (ref) {
                arc.length = 0;
                return ref;
            }
            return new Curve3(arc); // colinear points arc is empty
        }
        const len1Sq = vec1.lengthSquared();
        const len2Sq = vec2.lengthSquared();
        const len3Sq = vec3.lengthSquared();
        const len4Sq = zAxis.lengthSquared();
        const len1 = vec1.length();
        const len2 = vec2.length();
        const len3 = vec3.length();
        const radius = (0.5 * len1 * len2 * len3) / len4;
        const dot1 = Vector3.Dot(vec1, vec3);
        const dot2 = Vector3.Dot(vec1, vec2);
        const dot3 = Vector3.Dot(vec2, vec3);
        const a = (-0.5 * len2Sq * dot1) / len4Sq;
        const b = (-0.5 * len3Sq * dot2) / len4Sq;
        const c = (-0.5 * len1Sq * dot3) / len4Sq;
        const center = first.scale(a).add(second.scale(b)).add(third.scale(c));
        const radiusVec = first.subtract(center);
        const xAxis = radiusVec.normalize();
        const yAxis = Vector3.Cross(zAxis, xAxis).normalize();
        let length = 0;
        const copyOrPush = (point) => {
            if (!arc[length]) {
                arc.push(point);
            }
            else {
                arc[length].copyFrom(point);
            }
        };
        if (fullCircle) {
            const dStep = (2 * Math.PI) / steps;
            for (let theta = 0; theta <= 2 * Math.PI; theta += dStep) {
                const point = center.add(xAxis.scale(radius * Math.cos(theta)).add(yAxis.scale(radius * Math.sin(theta))));
                copyOrPush(point);
                length++;
            }
            copyOrPush(first);
            length++;
        }
        else {
            const dStep = 1 / steps;
            let theta = 0;
            let point;
            do {
                point = center.add(xAxis.scale(radius * Math.cos(theta)).add(yAxis.scale(radius * Math.sin(theta))));
                copyOrPush(point);
                length++;
                theta += dStep;
            } while (!point.equalsWithEpsilon(third, radius * dStep * 1.1));
            copyOrPush(third);
            length++;
            if (closed) {
                copyOrPush(first);
                length++;
            }
        }
        arc.length = length;
        return ref || new Curve3(arc);
    }
    /**
     * A Curve3 object is a logical object, so not a mesh, to handle curves in the 3D geometric space.
     * A Curve3 is designed from a series of successive Vector3.
     * Tuto : https://doc.babylonjs.com/features/featuresDeepDive/mesh/drawCurves#curve3-object
     * @param points points which make up the curve
     */
    constructor(points) {
        this._length = 0.0;
        this._points = points;
        this._length = this._computeLength(points);
    }
    /**
     * @returns the Curve3 stored array of successive Vector3
     */
    getPoints() {
        return this._points;
    }
    /**
     * @returns the computed length (float) of the curve.
     */
    length() {
        return this._length;
    }
    /**
     * Returns a new instance of Curve3 object : var curve = curveA.continue(curveB);
     * This new Curve3 is built by translating and sticking the curveB at the end of the curveA.
     * curveA and curveB keep unchanged.
     * @param curve the curve to continue from this curve
     * @returns the newly constructed curve
     */
    continue(curve) {
        const lastPoint = this._points[this._points.length - 1];
        const continuedPoints = this._points.slice();
        const curvePoints = curve.getPoints();
        for (let i = 1; i < curvePoints.length; i++) {
            continuedPoints.push(curvePoints[i].subtract(curvePoints[0]).add(lastPoint));
        }
        const continuedCurve = new Curve3(continuedPoints);
        return continuedCurve;
    }
    /**
     * @param curve
     * @returns
     */
    copyFrom(curve) {
        const points = curve.getPoints();
        this._points.length = points.length;
        for (let i = 0; i < points.length; i++) {
            this._points[i] = this._points[i] ? this._points[i].copyFrom(points[i]) : points[i].clone();
        }
        return this;
    }
    _computeLength(path) {
        let l = 0;
        for (let i = 1; i < path.length; i++) {
            l += path[i].subtract(path[i - 1]).length();
        }
        return l;
    }
}
//# sourceMappingURL=math.path.js.map