import { assert } from "../assert.js";
import Signal from "../events/signal/Signal.js";
import { EPSILON } from "../math/EPSILON.js";
import { epsilonEquals } from "../math/epsilonEquals.js";
import { sign } from "../math/sign.js";
import { computeHashFloat } from "../primitives/numbers/computeHashFloat.js";
import { v3_angle_between } from "./vec3/v3_angle_between.js";
import { v3_distance } from "./vec3/v3_distance.js";
import { v3_dot } from "./vec3/v3_dot.js";
import { v3_length } from "./vec3/v3_length.js";
import { v3_length_sqr } from "./vec3/v3_length_sqr.js";
import { v3_lerp } from "./vec3/v3_lerp.js";
import { v3_slerp } from "./vec3/v3_slerp.js";


/**
 * Class representing a 3D vector. A 3D vector is an ordered triplet of numbers (labeled x, y, and z), which can be used to represent a number of things, such as:
 *
 * A point in 3D space.
 * A direction and length in 3D space. The length will always be the Euclidean distance (straight-line distance) from `(0, 0, 0)` to `(x, y, z)` and the direction is also measured from `(0, 0, 0)` towards `(x, y, z)`.
 * Any arbitrary ordered triplet of numbers.
 * There are other things a 3D vector can be used to represent, such as momentum vectors and so on, however these are the most common uses.
 *
 * Iterating through a Vector3 instance will yield its components `(x, y, z)` in the corresponding order.
 *
 * Backed by a `Float64Array` of length 3, so instances are usable directly as typed-array views and indexed access `v[0]`, `v[1]`, `v[2]` aliases `v.x`, `v.y`, `v.z`.
 *
 * @implements Iterable<number>
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class Vector3 extends Float64Array {
    /**
     *
     * @param {number} [x=0]
     * @param {number} [y=0]
     * @param {number} [z=0]
     */
    constructor(x = 0, y = 0, z = 0) {
        assert.isNumber(x, 'x');
        assert.notNaN(x, 'x');

        assert.isNumber(y, 'y');
        assert.notNaN(y, 'y');

        assert.isNumber(z, 'z');
        assert.notNaN(z, 'z');

        super(3);

        this[0] = x;
        this[1] = y;
        this[2] = z;

        /**
         * Dispatches ( x, y, z, old_x, old_y, old_z )
         * @readonly
         * @type {Signal<number,number,number,number,number,number>}
         */
        this.onChanged = new Signal();
    }

    /**
     * Ensure that built-in `TypedArray` methods (`map`, `slice`, `subarray`, ...) do not
     * try to construct a `Vector3` via the buffer-form constructor.
     * @returns {Float64ArrayConstructor}
     */
    static get [Symbol.species]() {
        return Float64Array;
    }

    /**
     *
     * @returns {number}
     */
    get x() {
        return this[0];
    }

    /**
     *
     * @param {number} v
     */
    set x(v) {
        this[0] = v;
    }

    /**
     *
     * @returns {number}
     */
    get y() {
        return this[1];
    }

    /**
     *
     * @param {number} v
     */
    set y(v) {
        this[1] = v;
    }

    /**
     *
     * @returns {number}
     */
    get z() {
        return this[2];
    }

    /**
     *
     * @param {number} v
     */
    set z(v) {
        this[2] = v;
    }

    /**
     *
     * @param {number[]|Float32Array} array
     * @param {number} [offset]
     * @returns {this}
     */
    fromArray(array, offset = 0) {
        assert.defined(array, "array");
        assert.isNonNegativeInteger(offset, "offset");

        return this.set(
            array[offset],
            array[offset + 1],
            array[offset + 2]
        );
    }

    /**
     * @param {number[]|Float32Array|ArrayLike} [array]
     * @param {number} [offset]
     * @returns {number[]}
     */
    toArray(array = [], offset = 0) {
        assert.isNonNegativeInteger(offset, "offset");

        array[offset] = this[0];
        array[offset + 1] = this[1];
        array[offset + 2] = this[2];

        return array;
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @returns {this}
     */
    set(x, y, z) {
        assert.isNumber(x, "x");
        assert.isNumber(y, "y");
        assert.isNumber(z, "z");

        assert.notNaN(x, "x");
        assert.notNaN(y, "y");
        assert.notNaN(z, "z");

        const oldX = this[0];
        const oldY = this[1];
        const oldZ = this[2];

        if (x !== oldX || y !== oldY || z !== oldZ) {
            // change has occurred

            this[0] = x;
            this[1] = y;
            this[2] = z;

            if (this.onChanged.hasHandlers()) {
                this.onChanged.send6(
                    x, y, z,
                    oldX, oldY, oldZ
                );
            }

        }

        return this;
    }

    /**
     *
     * @param {number} v
     * @returns {this}
     */
    setScalar(v) {
        return this.set(v, v, v);
    }

    /**
     *
     * @param {number} v
     * @returns {this}
     */
    setX(v) {
        return this.set(v, this[1], this[2]);
    }

    /**
     *
     * @param {number} v
     * @returns {this}
     */
    setY(v) {
        return this.set(this[0], v, this[2]);
    }

    /**
     *
     * @param {number} v
     * @returns {this}
     */
    setZ(v) {
        return this.set(this[0], this[1], v);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @returns {this}
     */
    setXY(x, y) {
        return this.set(x, y, this[2]);
    }

    /**
     *
     * @param {number} x
     * @param {number} z
     * @returns {this}
     */
    setXZ(x, z) {
        return this.set(x, this[1], z);
    }

    /**
     *
     * @param {number} y
     * @param {number} z
     * @returns {this}
     */
    setYZ(y, z) {
        return this.set(this[0], y, z);
    }

    /**
     *
     * @param {Vector3} a
     * @param {Vector3} b
     * @returns {this}
     */
    addVectors(a, b) {
        const x = a.x + b.x;
        const y = a.y + b.y;
        const z = a.z + b.z;

        return this.set(x, y, z);
    }

    /**
     *
     * @param {Vector3} other
     * @returns {this}
     */
    add(other) {
        return this._add(other.x, other.y, other.z);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @returns {this}
     */
    _add(x, y, z) {
        return this.set(
            this[0] + x,
            this[1] + y,
            this[2] + z
        );
    }


    /**
     *
     * `this = this + other * scale`
     *
     * @param {Vector3} other
     * @param {number} scale
     * @returns {this}
     */
    addScaled(other, scale) {
        return this._add(
            other.x * scale,
            other.y * scale,
            other.z * scale
        );
    }

    /**
     *
     * @param {Vector3} a
     * @param {Vector3} b
     * @returns {this}
     */
    subVectors(a, b) {
        const x = a.x - b.x;
        const y = a.y - b.y;
        const z = a.z - b.z;

        return this.set(x, y, z);
    }

    /**
     *
     * @param {Vector3} other
     * @returns {this}
     */
    sub(other) {
        return this._sub(other.x, other.y, other.z);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @returns {this}
     */
    _sub(x, y, z) {
        const _x = this[0] - x;
        const _y = this[1] - y;
        const _z = this[2] - z;

        return this.set(_x, _y, _z);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @returns {this}
     */
    _multiply(x, y, z) {
        return this.set(this[0] * x, this[1] * y, this[2] * z);
    }

    /**
     *
     * @param {Vector3} other
     * @returns {this}
     */
    multiply(other) {
        return this._multiply(other.x, other.y, other.z);
    }

    /**
     *
     * @param {Vector3} a
     * @param {Vector3} b
     * @returns {this}
     */
    multiplyVectors(a, b) {
        return this.set(
            a.x * b.x,
            a.y * b.y,
            a.z * b.z
        );
    }

    /**
     * Component-wise division.
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @returns {this}
     */
    _divide(x, y, z) {
        return this.set(
            this[0] / x,
            this[1] / y,
            this[2] / z,
        )
    }

    /**
     * Component-wise division.
     * @param {Vector3} other
     * @returns {this}
     */
    divide(other) {
        return this._divide(other.x, other.y, other.z);
    }

    /**
     * Component-wise division.
     * `this = a / b`
     * @param {Vector3} a
     * @param {Vector3} b
     * @return {Vector3}
     */
    divideVectors(a, b) {
        return this.set(
            a.x / b.x,
            a.y / b.y,
            a.z / b.z
        );
    }

    /**
     * Add a scalar value to each component of the vector
     * @param {number} val
     * @returns {this}
     */
    addScalar(val) {
        return this.set(this[0] + val, this[1] + val, this[2] + val);
    }

    /**
     * Subtract scalar value from each component of the vector
     * @param {number} val
     * @returns {this}
     */
    subScalar(val) {
        return this.addScalar(-val);
    }

    /**
     *
     * @returns {Vector3}
     */
    clone() {
        return new Vector3(this[0], this[1], this[2]);
    }

    /**
     *
     * @param {number} val
     * @returns {this}
     */
    multiplyScalar(val) {
        assert.isNumber(val, "val");
        assert.notNaN(val, "val");

        return this.set(this[0] * val, this[1] * val, this[2] * val);
    }

    /**
     *
     * @returns {boolean}
     */
    isZero() {
        return this[0] === 0
            && this[1] === 0
            && this[2] === 0
            ;
    }

    /**
     * Compute cross-product of two vectors. Result is written to this vector.
     * @param {Vector3} other
     * @returns {this}
     */
    cross(other) {
        return this.crossVectors(this, other);
    }

    /**
     *
     * @param {Vector3} first
     * @param {Vector3} second
     * @returns {this}
     */
    crossVectors(first, second) {
        const ax = first.x, ay = first.y, az = first.z;
        const bx = second.x, by = second.y, bz = second.z;

        return this._crossVectors(
            ax, ay, az,
            bx, by, bz
        );

    }

    /**
     *
     * @param {number} ax
     * @param {number} ay
     * @param {number} az
     * @param {number} bx
     * @param {number} by
     * @param {number} bz
     * @returns {this}
     */
    _crossVectors(ax, ay, az, bx, by, bz) {
        const x = ay * bz - az * by;
        const y = az * bx - ax * bz;
        const z = ax * by - ay * bx;

        return this.set(x, y, z);
    }

    /**
     * Sets all components of the vector to absolute value (positive)
     * @returns {this}
     */
    abs() {
        return this.set(
            Math.abs(this[0]),
            Math.abs(this[1]),
            Math.abs(this[2])
        );
    }

    /**
     *
     * @param {Vector3} v
     * @returns {number}
     */
    dot(v) {
        return Vector3.dot(this, v);
    }

    /**
     * Computes length(magnitude) of the vector
     * @returns {number}
     */
    length() {
        return v3_length(this[0], this[1], this[2]);
    }

    /**
     * Computes squared length(magnitude) of the vector.
     * Note: this operation is faster than computing length, because it doesn't involve computing square root
     * @returns {number}
     */
    lengthSqr() {
        return v3_length_sqr(this[0], this[1], this[2]);
    }

    /**
     * Normalizes the vector, preserving its direction, but making magnitude equal to 1
     * @returns {this}
     */
    normalize() {
        // NOTE: call v3_length directly rather than via `this.length()`.
        // Float64Array exposes `length` as an exotic instance property that V8 inlines
        // through a fast path which bypasses the prototype chain; once an optimized
        // function caller has been specialized for Float64Array shape, `this.length`
        // is read as the array length (a number) rather than our prototype method.
        const l = v3_length(this[0], this[1], this[2]);

        if (l === 0) {
            //special case, can't normalize 0 length vector
            return this;
        }

        const m = 1 / l;

        return this.multiplyScalar(m);
    }

    /**
     * @param {number} [squared_error]
     * @return {boolean}
     */
    isNormalized(squared_error = 1e-5) {
        const length_sqr = this.lengthSqr();

        return epsilonEquals(length_sqr, 1, squared_error);
    }

    /**
     *
     * @param {Vector3|{x:number,y:number,z:number}} other
     * @returns {this}
     */
    copy(other) {
        return this.set(other.x, other.y, other.z);
    }


    /**
     * Negates every component of the vector making it {-x, -y, -z}
     * @returns {this}
     */
    negate() {
        return this.set(
            -this[0],
            -this[1],
            -this[2]
        );
    }

    /**
     *
     * @param {Vector3} other
     * @returns {number}
     */
    distanceTo(other) {
        return this._distanceTo(other.x, other.y, other.z);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @return {number}
     */
    _distanceTo(x, y, z) {
        return v3_distance(
            this[0], this[1], this[2],
            x, y, z
        );
    }

    /**
     * Squared distance between this vector and another. It is faster than computing real distance because no SQRT operation is needed.
     * @param {Vector3} other
     * @returns {number}
     */
    distanceSqrTo(other) {
        assert.defined(other, "other");

        return this._distanceSqrTo(other.x, other.y, other.z);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @return {number}
     */
    _distanceSqrTo(x, y, z) {
        return v3_length_sqr(
            this[0] - x,
            this[1] - y,
            this[2] - z
        );
    }

    /**
     * Angle between two vectors (co-planar) in radians
     * @param {Vector3} other
     * @returns {number}
     */
    angleTo(other) {
        return v3_angle_between(
            this[0], this[1], this[2],
            other.x, other.y, other.z
        );
    }

    /**
     *
     * @param {Quaternion} q
     * @returns {this}
     */
    applyQuaternion(q) {
        // NOTE: the logic is inlines for speed

        //transform point into quaternion

        const x = this[0];
        const y = this[1];
        const z = this[2];

        const qx = q.x;
        const qy = q.y;
        const qz = q.z;
        const qw = q.w;

        // calculate quat * vector

        const ix = qw * x + qy * z - qz * y;
        const iy = qw * y + qz * x - qx * z;
        const iz = qw * z + qx * y - qy * x;
        const iw = -qx * x - qy * y - qz * z;

        // calculate result * inverse quat

        const _x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
        const _y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
        const _z = iz * qw + iw * -qz + ix * -qy - iy * -qx;

        return this.set(_x, _y, _z);
    }

    /**
     * Set components X,Y,Z to values 1,0 or -1 based on the sign of their original value.
     * @example new Vector(5,0,-10).sign().equals(new Vector(1,0,-1)); //true
     * @returns {this}
     */
    sign() {
        return this.set(
            sign(this[0]),
            sign(this[1]),
            sign(this[2])
        );
    }

    /**
     * Linear interpolation
     * @param {Vector3} other
     * @param {number} fraction between 0 and 1
     * @returns {this}
     */
    lerp(other, fraction) {
        return this.lerpVectors(this, other, fraction);
    }

    /**
     *
     * @param {Vector3} a
     * @param {Vector3} b
     * @param {number} fraction
     * @returns {this}
     */
    lerpVectors(a, b, fraction) {
        v3_lerp(this, a.x, a.y, a.z, b.x, b.y, b.z, fraction);

        return this;
    }

    /**
     *
     * @param {Vector3} other
     * @param {number} fraction
     * @return {this}
     */
    slerp(other, fraction) {
        return this.slerpVectors(this, other, fraction);
    }

    /**
     * Spherical linear interpolation
     * @param {Vector3} a
     * @param {Vector3} b
     * @param {number} fraction
     * @returns {this}
     */
    slerpVectors(a, b, fraction) {
        v3_slerp(this, a.x, a.y, a.z, b.x, b.y, b.z, fraction);
        return this;
    }


    /**
     *
     * @param {ArrayLike<number>|number[]|Float32Array} m4
     * @returns {this}
     */
    applyMatrix4(m4) {
        const x = this[0];
        const y = this[1];
        const z = this[2];

        const w = 1 / (m4[3] * x + m4[7] * y + m4[11] * z + m4[15]);

        const _x = (m4[0] * x + m4[4] * y + m4[8] * z + m4[12]) * w;
        const _y = (m4[1] * x + m4[5] * y + m4[9] * z + m4[13]) * w;
        const _z = (m4[2] * x + m4[6] * y + m4[10] * z + m4[14]) * w;

        return this.set(_x, _y, _z);
    }

    /**
     * Assume current vector holds a direction, transform using a matrix to produce a new directional unit vector.
     * Note that the vector is normalized
     * @param {ArrayLike<number>|number[]|Float32Array} m4
     * @returns {this}
     */
    applyDirectionMatrix4(m4) {
        const x = this[0];
        const y = this[1];
        const z = this[2];

        // This is just 3x3 matrix multiplication
        const _x = m4[0] * x + m4[4] * y + m4[8] * z;
        const _y = m4[1] * x + m4[5] * y + m4[9] * z;
        const _z = m4[2] * x + m4[6] * y + m4[10] * z;

        // normalize the result
        const _l = 1 / v3_length(_x, _y, _z);

        return this.set(
            _x * _l,
            _y * _l,
            _z * _l
        );
    }

    /**
     *
     * @param {number[]|Float32Array} mat
     * @returns {this}
     */
    applyMatrix3(mat) {
        const x = this[0];
        const y = this[1];
        const z = this[2];

        const _x = mat[0] * x + mat[3] * y + mat[6] * z;
        const _y = mat[1] * x + mat[4] * y + mat[7] * z;
        const _z = mat[2] * x + mat[5] * y + mat[8] * z;

        return this.set(_x, _y, _z);
    }


    /**
     *
     * @param {ArrayLike<number>|number[]|Float32Array} matrix4 4x4 transform matrix
     * @returns {this}
     */
    setFromMatrixPosition(matrix4) {

        const _x = matrix4[12];
        const _y = matrix4[13];
        const _z = matrix4[14];

        return this.set(_x, _y, _z);
    }

    /**
     *
     * @param {Vector3} other
     * @returns {boolean}
     */
    equals(other) {
        return this._equals(other.x, other.y, other.z);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @return {boolean}
     */
    _equals(x, y, z) {
        return this[0] === x && this[1] === y && this[2] === z;
    }

    /**
     *
     * @param {Vector3} other
     * @param {number} [tolerance]
     * @return {boolean}
     */
    roughlyEquals(other, tolerance) {
        return this._roughlyEquals(other.x, other.y, other.z, tolerance);
    }

    /**
     *
     * @param {number} x
     * @param {number} y
     * @param {number} z
     * @param {number} [tolerance] acceptable deviation
     * @return {boolean}
     */
    _roughlyEquals(x, y, z, tolerance = EPSILON) {
        return epsilonEquals(this[0], x, tolerance)
            && epsilonEquals(this[1], y, tolerance)
            && epsilonEquals(this[2], z, tolerance);
    }

    /**
     * Apply component-wise `Math.round`
     * @returns {this}
     */
    round() {
        return this.set(
            Math.round(this[0]),
            Math.round(this[1]),
            Math.round(this[2]),
        );
    }

    /**
     * Apply component-wise `Math.floor`
     * @returns {this}
     */
    floor() {
        const x = Math.floor(this[0]);
        const y = Math.floor(this[1]);
        const z = Math.floor(this[2]);

        return this.set(x, y, z);
    }

    /**
     * Apply component-wise `Math.ceil`
     * @returns {this}
     */
    ceil() {
        return this.set(
            Math.ceil(this[0]),
            Math.ceil(this[1]),
            Math.ceil(this[2]),
        );
    }

    /**
     * Project this vector onto another
     * @param {Vector3} other
     * @returns {this}
     */
    projectOntoVector3(other) {
        const x0 = this[0];
        const y0 = this[1];
        const z0 = this[2];

        const x1 = other.x;
        const y1 = other.y;
        const z1 = other.z;

        return this._projectVectors(x0, y0, z0, x1, y1, z1);
    }

    /**
     * Project first vector onto second one
     * @param {number} x0
     * @param {number} y0
     * @param {number} z0
     * @param {number} x1
     * @param {number} y1
     * @param {number} z1
     * @returns {this}
     */
    _projectVectors(
        x0, y0, z0,
        x1, y1, z1
    ) {

        const d = v3_dot(x0, y0, z0, x1, y1, z1);

        const length_sqr = (x1 * x1 + y1 * y1 + z1 * z1);

        const m = d / length_sqr;

        const x = x1 * m;
        const y = y1 * m;
        const z = z1 * m;

        return this.set(x, y, z);
    }

    /**
     * Convert spherical coordinates to cartesian
     *
     * We assume Y-up coordinate system.
     *
     * @param {number} radius
     * @param {number} phi Also known as Azimuth
     * @param {number} theta Also known as Elevation
     * @returns {this}
     */
    setFromSphericalCoords(radius, phi, theta) {
        assert.isNumber(radius, 'radius');
        assert.notNaN(radius, 'radius');

        assert.isNumber(phi, 'phi');
        assert.notNaN(phi, 'phi');

        assert.isNumber(theta, 'theta');
        assert.notNaN(theta, 'theta');

        const sin_phi = Math.sin(phi);
        const cos_phi = Math.cos(phi);

        const sin_theta = Math.sin(theta);
        const cos_theta = Math.cos(theta);

        const _x = radius * sin_phi * sin_theta;
        const _y = radius * cos_phi;
        const _z = radius * sin_phi * cos_theta;

        return this.set(_x, _y, _z);
    }

    /**
     *
     * @param {function} processor
     * @param {*} [thisArg]
     * @returns {Vector3}
     */
    process(processor, thisArg) {

        processor.call(thisArg, this[0], this[1], this[2]);

        this.onChanged.add(processor, thisArg);

        return this;
    }

    toJSON() {
        return {
            x: this[0],
            y: this[1],
            z: this[2]
        };
    }

    /**
     *
     * @param {{x:number, y:number, z:number}|number} json
     */
    fromJSON(json) {
        if (typeof json === 'number') {
            // special case where input is a number
            this.setScalar(json);
        } else {
            this.copy(json);
        }
    }

    toString() {
        return `Vector3{ x:${this[0]}, y:${this[1]}, z:${this[2]} }`;
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     * @deprecated
     */
    toBinaryBuffer(buffer) {
        buffer.writeFloat64(this[0]);
        buffer.writeFloat64(this[1]);
        buffer.writeFloat64(this[2]);
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     * @deprecated
     */
    fromBinaryBuffer(buffer) {
        const x = buffer.readFloat64();
        const y = buffer.readFloat64();
        const z = buffer.readFloat64();

        this.set(x, y, z);
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     * @deprecated
     */
    toBinaryBufferFloat32(buffer) {
        buffer.writeFloat32(this[0]);
        buffer.writeFloat32(this[1]);
        buffer.writeFloat32(this[2]);
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     * @deprecated
     */
    fromBinaryBufferFloat32(buffer) {
        const x = buffer.readFloat32();
        const y = buffer.readFloat32();
        const z = buffer.readFloat32();

        this.set(x, y, z);
    }

    hash() {
        const x = computeHashFloat(this[0]);
        const y = computeHashFloat(this[1]);
        const z = computeHashFloat(this[2]);

        return x ^ (y << 1) ^ (z << 2);
    }

    /**
     * Dot product
     * @param {Vector3|Vector4} a
     * @param {Vector3|Vector4} b
     * @returns {number}
     */
    static dot(a, b) {
        return v3_dot(a.x, a.y, a.z, b.x, b.y, b.z);
    }

    /**
     *
     * @param {Vector3} a
     * @param {Vector3} b
     * @returns {number}
     */
    static distance(a, b) {
        return v3_length(a.x - b.x, a.y - b.y, a.z - b.z);
    }

    /**
     *
     * @param {number[]} input
     * @param {number} [offset]
     * @returns {Vector3}
     */
    static fromArray(input, offset = 0) {
        return new Vector3(
            input[offset],
            input[offset + 1],
            input[offset + 2]
        );
    }

    /**
     *
     * @param {number} value
     * @returns {Vector3}
     */
    static fromScalar(value) {
        return new Vector3(value, value, value);
    }
}

// Aliases

Vector3.prototype.distanceToSquared = Vector3.prototype.distanceSqrTo;

Vector3.prototype.lengthSq = Vector3.prototype.lengthSqr;
/**
 * @readonly
 * @deprecated Use {@link fromArray} instead
 */
Vector3.prototype.readFromArray = Vector3.prototype.fromArray;
/**
 * @readonly
 * @deprecated Use {@link toArray} instead
 */
Vector3.prototype.writeToArray = Vector3.prototype.toArray;
/**
 * @readonly
 * @deprecated use {@link toArray} instead
 */
Vector3.prototype.asArray = Vector3.prototype.toArray;


/**
 * @readonly
 * @type {Vector3}
 */
Vector3.zero = new Vector3(0, 0, 0);

/**
 * Useful for setting scale
 * @readonly
 * @type {Vector3}
 */
Vector3.one = new Vector3(1, 1, 1);

/**
 * Useful for setting scale
 * @readonly
 * @type {Vector3}
 */
Vector3.minus_one = new Vector3(-1, -1, -1);

/**
 * @readonly
 * @type {Vector3}
 */
Vector3.up = new Vector3(0, 1, 0);

/**
 * @readonly
 * @type {Vector3}
 */
Vector3.down = new Vector3(0, -1, 0);

/**
 * @readonly
 * @type {Vector3}
 */
Vector3.left = new Vector3(-1, 0, 0);

/**
 * @readonly
 * @type {Vector3}
 */
Vector3.right = new Vector3(1, 0, 0);

/**
 * @readonly
 * @type {Vector3}
 */
Vector3.forward = new Vector3(0, 0, 1);

/**
 * @readonly
 * @type {Vector3}
 */
Vector3.back = new Vector3(0, 0, -1);

/**
 * @readonly
 * @type {boolean}
 */
Vector3.prototype.isVector3 = true;

/**
 * @readonly
 * @type {string}
 */
Vector3.typeName = "Vector3";

export default Vector3;
