import _ from 'lodash';
import '../../assert/js/assert.js';
import '../../globals.js';
// Copyright 2013-2024, University of Colorado Boulder
/**
 * Basic 2-dimensional vector, represented as (x,y).  Values can be numeric, or NaN or infinite.
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
import Pool from '../../phet-core/js/Pool.js';
import IOType from '../../tandem/js/types/IOType.js';
import NumberIO from '../../tandem/js/types/NumberIO.js';
import dot from './dot.js';
import Utils from './Utils.js';
import Vector3 from './Vector3.js';
const ADDING_ACCUMULATOR = (vector, nextVector) => {
    return vector.add(nextVector);
};
class Vector2 {
    /**
     * Creates a 2-dimensional vector with the specified X and Y values.
     *
     * @param x - X coordinate
     * @param y - Y coordinate
     */
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    /**
     * The magnitude (Euclidean/L2 Norm) of this vector, i.e. $\sqrt{x^2+y^2}$.
     */
    getMagnitude() {
        return Math.sqrt(this.magnitudeSquared);
    }
    get magnitude() { return this.getMagnitude(); }
    /**
     * The squared magnitude (square of the Euclidean/L2 Norm) of this vector, i.e. $x^2+y^2$.
     */
    getMagnitudeSquared() {
        return this.x * this.x + this.y * this.y;
    }
    get magnitudeSquared() { return this.getMagnitudeSquared(); }
    /**
     * The Euclidean distance between this vector (treated as a point) and another point.
     */
    distance(point) {
        return Math.sqrt(this.distanceSquared(point));
    }
    /**
     * The Euclidean distance between this vector (treated as a point) and another point (x,y).
     */
    distanceXY(x, y) {
        const dx = this.x - x;
        const dy = this.y - y;
        return Math.sqrt(dx * dx + dy * dy);
    }
    /**
     * The squared Euclidean distance between this vector (treated as a point) and another point.
     */
    distanceSquared(point) {
        const dx = this.x - point.x;
        const dy = this.y - point.y;
        return dx * dx + dy * dy;
    }
    /**
     * The squared Euclidean distance between this vector (treated as a point) and another point with coordinates (x,y).
     */
    distanceSquaredXY(x, y) {
        const dx = this.x - x;
        const dy = this.y - y;
        return dx * dx + dy * dy;
    }
    /**
     * The dot-product (Euclidean inner product) between this vector and another vector v.
     */
    dot(v) {
        return this.x * v.x + this.y * v.y;
    }
    /**
     * The dot-product (Euclidean inner product) between this vector and another vector (x,y).
     */
    dotXY(x, y) {
        return this.x * x + this.y * y;
    }
    /**
     * The angle $\theta$ of this vector, such that this vector is equal to
     * $$ u = \begin{bmatrix} r\cos\theta \\ r\sin\theta \end{bmatrix} $$
     * for the magnitude $r \ge 0$ of the vector, with $\theta\in(-\pi,\pi]$
     */
    getAngle() {
        return Math.atan2(this.y, this.x);
    }
    get angle() {
        return this.getAngle();
    }
    /**
     * The angle between this vector and another vector, in the range $\theta\in[0, \pi]$.
     *
     * Equal to $\theta = \cos^{-1}( \hat{u} \cdot \hat{v} )$ where $\hat{u}$ is this vector (normalized) and $\hat{v}$
     * is the input vector (normalized).
     */
    angleBetween(v) {
        const thisMagnitude = this.magnitude;
        const vMagnitude = v.magnitude;
        // @ts-expect-error TODO: import with circular protection https://github.com/phetsims/dot/issues/96
        return Math.acos(dot.clamp((this.x * v.x + this.y * v.y) / (thisMagnitude * vMagnitude), -1, 1));
    }
    /**
     * Exact equality comparison between this vector and another vector.
  
     * @returns - Whether the two vectors have equal components
     */
    equals(other) {
        return this.x === other.x && this.y === other.y;
    }
    /**
     * Approximate equality comparison between this vector and another vector.
     *
     * @returns - Whether difference between the two vectors has no component with an absolute value greater than epsilon.
     */
    equalsEpsilon(other, epsilon) {
        if (!epsilon) {
            epsilon = 0;
        }
        return Math.max(Math.abs(this.x - other.x), Math.abs(this.y - other.y)) <= epsilon;
    }
    /**
     * Returns false if either component is NaN, infinity, or -infinity. Otherwise returns true.
     */
    isFinite() {
        return isFinite(this.x) && isFinite(this.y);
    }
    /*---------------------------------------------------------------------------*
     * Immutables
     *---------------------------------------------------------------------------*/
    /**
     * Creates a copy of this vector, or if a vector is passed in, set that vector's values to ours.
     *
     * This is the immutable form of the function set(), if a vector is provided. This will return a new vector, and
     * will not modify this vector.
     *
     * @param [vector] - If not provided, creates a new Vector2 with filled in values. Otherwise, fills in the
     *                   values of the provided vector so that it equals this vector.
     */
    copy(vector) {
        if (vector) {
            return vector.set(this);
        }
        else {
            return v2(this.x, this.y);
        }
    }
    /**
     * The scalar value of the z-component of the equivalent 3-dimensional cross product:
     * $$ f( u, v ) = \left( \begin{bmatrix} u_x \\ u_y \\ 0 \end{bmatrix} \times \begin{bmatrix} v_x \\ v_y \\ 0 \end{bmatrix} \right)_z = u_x v_y - u_y v_x $$
     */
    crossScalar(v) {
        return this.x * v.y - this.y * v.x;
    }
    /**
     * Normalized (re-scaled) copy of this vector such that its magnitude is 1. If its initial magnitude is zero, an
     * error is thrown.
     *
     * This is the immutable form of the function normalize(). This will return a new vector, and will not modify this
     * vector.
     */
    normalized() {
        const mag = this.magnitude;
        if (mag === 0) {
            throw new Error('Cannot normalize a zero-magnitude vector');
        }
        else {
            return v2(this.x / mag, this.y / mag);
        }
    }
    /**
     * Returns a copy of this vector with each component rounded by Utils.roundSymmetric.
     *
     * This is the immutable form of the function roundSymmetric(). This will return a new vector, and will not modify
     * this vector.
     */
    roundedSymmetric() {
        return this.copy().roundSymmetric();
    }
    /**
     * Re-scaled copy of this vector such that it has the desired magnitude. If its initial magnitude is zero, an error
     * is thrown. If the passed-in magnitude is negative, the direction of the resulting vector will be reversed.
     *
     * This is the immutable form of the function setMagnitude(). This will return a new vector, and will not modify
     * this vector.
     */
    withMagnitude(magnitude) {
        return this.copy().setMagnitude(magnitude);
    }
    /**
     * Copy of this vector, scaled by the desired scalar value.
     *
     * This is the immutable form of the function multiplyScalar(). This will return a new vector, and will not modify
     * this vector.
     */
    timesScalar(scalar) {
        return v2(this.x * scalar, this.y * scalar);
    }
    /**
     * Same as timesScalar.
     *
     * This is the immutable form of the function multiply(). This will return a new vector, and will not modify
     * this vector.
     */
    times(scalar) {
        return this.timesScalar(scalar);
    }
    /**
     * Copy of this vector, multiplied component-wise by the passed-in vector v.
     *
     * This is the immutable form of the function componentMultiply(). This will return a new vector, and will not modify
     * this vector.
     */
    componentTimes(v) {
        return v2(this.x * v.x, this.y * v.y);
    }
    /**
     * Addition of this vector and another vector, returning a copy.
     *
     * This is the immutable form of the function add(). This will return a new vector, and will not modify
     * this vector.
     */
    plus(v) {
        return v2(this.x + v.x, this.y + v.y);
    }
    /**
     * Addition of this vector and another vector (x,y), returning a copy.
     *
     * This is the immutable form of the function addXY(). This will return a new vector, and will not modify
     * this vector.
     */
    plusXY(x, y) {
        return v2(this.x + x, this.y + y);
    }
    /**
     * Addition of this vector with a scalar (adds the scalar to every component), returning a copy.
     *
     * This is the immutable form of the function addScalar(). This will return a new vector, and will not modify
     * this vector.
     */
    plusScalar(scalar) {
        return v2(this.x + scalar, this.y + scalar);
    }
    /**
     * Subtraction of this vector by another vector v, returning a copy.
     *
     * This is the immutable form of the function subtract(). This will return a new vector, and will not modify
     * this vector.
     */
    minus(v) {
        return v2(this.x - v.x, this.y - v.y);
    }
    /**
     * Subtraction of this vector by another vector (x,y), returning a copy.
     *
     * This is the immutable form of the function subtractXY(). This will return a new vector, and will not modify
     * this vector.
     */
    minusXY(x, y) {
        return v2(this.x - x, this.y - y);
    }
    /**
     * Subtraction of this vector by a scalar (subtracts the scalar from every component), returning a copy.
     *
     * This is the immutable form of the function subtractScalar(). This will return a new vector, and will not modify
     * this vector.
     */
    minusScalar(scalar) {
        return v2(this.x - scalar, this.y - scalar);
    }
    /**
     * Division of this vector by a scalar (divides every component by the scalar), returning a copy.
     *
     * This is the immutable form of the function divideScalar(). This will return a new vector, and will not modify
     * this vector.
     */
    dividedScalar(scalar) {
        return v2(this.x / scalar, this.y / scalar);
    }
    /**
     * Negated copy of this vector (multiplies every component by -1).
     *
     * This is the immutable form of the function negate(). This will return a new vector, and will not modify
     * this vector.
     */
    negated() {
        return v2(-this.x, -this.y);
    }
    /**
     * Rotated by -pi/2 (perpendicular to this vector), returned as a copy.
     */
    getPerpendicular() {
        return v2(this.y, -this.x);
    }
    get perpendicular() {
        return this.getPerpendicular();
    }
    /**
     * Rotated by an arbitrary angle, in radians. Returned as a copy.
     *
     * This is the immutable form of the function rotate(). This will return a new vector, and will not modify
     * this vector.
     *
     * @param angle - In radians
     */
    rotated(angle) {
        const newAngle = this.angle + angle;
        const mag = this.magnitude;
        return v2(mag * Math.cos(newAngle), mag * Math.sin(newAngle));
    }
    /**
     * Mutable method that rotates this vector about an x,y point.
     *
     * @param x - origin of rotation in x
     * @param y - origin of rotation in y
     * @param angle - radians to rotate
     * @returns this for chaining
     */
    rotateAboutXY(x, y, angle) {
        const dx = this.x - x;
        const dy = this.y - y;
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);
        this.x = x + dx * cos - dy * sin;
        this.y = y + dx * sin + dy * cos;
        return this;
    }
    /**
     * Same as rotateAboutXY but with a point argument.
     */
    rotateAboutPoint(point, angle) {
        return this.rotateAboutXY(point.x, point.y, angle);
    }
    /**
     * Immutable method that returns a new Vector2 that is rotated about the given point.
     *
     * @param x - origin for rotation in x
     * @param y - origin for rotation in y
     * @param angle - radians to rotate
     */
    rotatedAboutXY(x, y, angle) {
        return v2(this.x, this.y).rotateAboutXY(x, y, angle);
    }
    /**
     * Immutable method that returns a new Vector2 rotated about the given point.
     */
    rotatedAboutPoint(point, angle) {
        return this.rotatedAboutXY(point.x, point.y, angle);
    }
    /**
     * A linear interpolation between this vector (ratio=0) and another vector (ratio=1).
     *
     * @param vector
     * @param ratio - Not necessarily constrained in [0, 1]
     */
    blend(vector, ratio) {
        return v2(this.x + (vector.x - this.x) * ratio, this.y + (vector.y - this.y) * ratio);
    }
    /**
     * The average (midpoint) between this vector and another vector.
     */
    average(vector) {
        return this.blend(vector, 0.5);
    }
    /**
     * Take a component-based mean of all vectors provided.
     */
    static average(vectors) {
        const added = _.reduce(vectors, ADDING_ACCUMULATOR, new Vector2(0, 0));
        return added.divideScalar(vectors.length);
    }
    /**
     * Debugging string for the vector.
     */
    toString() {
        return `Vector2(${this.x}, ${this.y})`;
    }
    /**
     * Converts this to a 3-dimensional vector, with the z-component equal to 0.
     */
    toVector3() {
        return new Vector3(this.x, this.y, 0);
    }
    /*---------------------------------------------------------------------------*
     * Mutables
     * - all mutation should go through setXY / setX / setY
     *---------------------------------------------------------------------------*/
    /**
     * Sets all of the components of this vector, returning this.
     */
    setXY(x, y) {
        this.x = x;
        this.y = y;
        return this;
    }
    /**
     * Sets the x-component of this vector, returning this.
     */
    setX(x) {
        this.x = x;
        return this;
    }
    /**
     * Sets the y-component of this vector, returning this.
     */
    setY(y) {
        this.y = y;
        return this;
    }
    /**
     * Sets this vector to be a copy of another vector.
     *
     * This is the mutable form of the function copy(). This will mutate (change) this vector, in addition to returning
     * this vector itself.
     */
    set(v) {
        return this.setXY(v.x, v.y);
    }
    /**
     * Sets the magnitude of this vector. If the passed-in magnitude is negative, this flips the vector and sets its
     * magnitude to abs( magnitude ).
     *
     * This is the mutable form of the function withMagnitude(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    setMagnitude(magnitude) {
        const scale = magnitude / this.magnitude;
        return this.multiplyScalar(scale);
    }
    /**
     * Adds another vector to this vector, changing this vector.
     *
     * This is the mutable form of the function plus(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    add(v) {
        return this.setXY(this.x + v.x, this.y + v.y);
    }
    /**
     * Adds another vector (x,y) to this vector, changing this vector.
     *
     * This is the mutable form of the function plusXY(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    addXY(x, y) {
        return this.setXY(this.x + x, this.y + y);
    }
    /**
     * Adds a scalar to this vector (added to every component), changing this vector.
     *
     * This is the mutable form of the function plusScalar(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    addScalar(scalar) {
        return this.setXY(this.x + scalar, this.y + scalar);
    }
    /**
     * Subtracts this vector by another vector, changing this vector.
     *
     * This is the mutable form of the function minus(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    subtract(v) {
        return this.setXY(this.x - v.x, this.y - v.y);
    }
    /**
     * Subtracts this vector by another vector (x,y), changing this vector.
     *
     * This is the mutable form of the function minusXY(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    subtractXY(x, y) {
        return this.setXY(this.x - x, this.y - y);
    }
    /**
     * Subtracts this vector by a scalar (subtracts each component by the scalar), changing this vector.
     *
     * This is the mutable form of the function minusScalar(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    subtractScalar(scalar) {
        return this.setXY(this.x - scalar, this.y - scalar);
    }
    /**
     * Multiplies this vector by a scalar (multiplies each component by the scalar), changing this vector.
     *
     * This is the mutable form of the function timesScalar(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    multiplyScalar(scalar) {
        return this.setXY(this.x * scalar, this.y * scalar);
    }
    /**
     * Multiplies this vector by a scalar (multiplies each component by the scalar), changing this vector.
     * Same as multiplyScalar.
     *
     * This is the mutable form of the function times(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    multiply(scalar) {
        return this.multiplyScalar(scalar);
    }
    /**
     * Multiplies this vector by another vector component-wise, changing this vector.
     *
     * This is the mutable form of the function componentTimes(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    componentMultiply(v) {
        return this.setXY(this.x * v.x, this.y * v.y);
    }
    /**
     * Divides this vector by a scalar (divides each component by the scalar), changing this vector.
     *
     * This is the mutable form of the function dividedScalar(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    divideScalar(scalar) {
        return this.setXY(this.x / scalar, this.y / scalar);
    }
    /**
     * Negates this vector (multiplies each component by -1), changing this vector.
     *
     * This is the mutable form of the function negated(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    negate() {
        return this.setXY(-this.x, -this.y);
    }
    /**
     * Normalizes this vector (rescales to where the magnitude is 1), changing this vector.
     *
     * This is the mutable form of the function normalized(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     */
    normalize() {
        const mag = this.magnitude;
        if (mag === 0) {
            throw new Error('Cannot normalize a zero-magnitude vector');
        }
        else {
            return this.divideScalar(mag);
        }
    }
    /**
     * Rounds each component of this vector with Utils.roundSymmetric.
     *
     * This is the mutable form of the function roundedSymmetric(). This will mutate (change) this vector, in addition
     * to returning the vector itself.
     */
    roundSymmetric() {
        return this.setXY(Utils.roundSymmetric(this.x), Utils.roundSymmetric(this.y));
    }
    /**
     * Rotates this vector by the angle (in radians), changing this vector.
     *
     * This is the mutable form of the function rotated(). This will mutate (change) this vector, in addition to
     * returning this vector itself.
     *
     * @param angle - In radians
     */
    rotate(angle) {
        const newAngle = this.angle + angle;
        const mag = this.magnitude;
        return this.setXY(mag * Math.cos(newAngle), mag * Math.sin(newAngle));
    }
    /**
     * Sets this vector's value to be the x,y values matching the given magnitude and angle (in radians), changing
     * this vector, and returning itself.
     *
     * @param magnitude
     * @param angle - In radians
     */
    setPolar(magnitude, angle) {
        return this.setXY(magnitude * Math.cos(angle), magnitude * Math.sin(angle));
    }
    /**
     * Returns a duck-typed object meant for use with tandem/phet-io serialization. Although this is redundant with
     * stateSchema, it is a nice feature of such a heavily-used type to be able to call toStateObject directly on the type.
     *
     * @returns - see stateSchema for schema
     */
    toStateObject() {
        return {
            x: this.x,
            y: this.y
        };
    }
    freeToPool() {
        Vector2.pool.freeToPool(this);
    }
    // static methods
    /**
     * Returns a Vector2 with the specified magnitude $r$ and angle $\theta$ (in radians), with the formula:
     * $$ f( r, \theta ) = \begin{bmatrix} r\cos\theta \\ r\sin\theta \end{bmatrix} $$
     */
    static createPolar(magnitude, angle) {
        return new Vector2(0, 0).setPolar(magnitude, angle);
    }
    /**
     * Constructs a Vector2 from a duck-typed object, for use with tandem/phet-io deserialization.
     *
     * @param stateObject - see stateSchema for schema
     */
    static fromStateObject(stateObject) {
        return v2(stateObject.x, stateObject.y);
    }
    /**
     * Allocation-free implementation that gets the angle between two vectors
     *
     * @returns the angle between the vectors
     */
    static getAngleBetweenVectors(startVector, endVector) {
        const dx = endVector.x - startVector.x;
        const dy = endVector.y - startVector.y;
        return Math.atan2(dy, dx);
    }
    /**
     * Allocation-free way to get the distance between vectors.
     *
     * @returns the angle between the vectors
     */
    static getDistanceBetweenVectors(startVector, endVector) {
        const dx = endVector.x - startVector.x;
        const dy = endVector.y - startVector.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}
Vector2.pool = new Pool(Vector2, {
    maxSize: 1000,
    initialize: Vector2.prototype.setXY,
    defaultArguments: [0, 0]
});
export default Vector2;
// (read-only) - Helps to identify the dimension of the vector
Vector2.prototype.isVector2 = true;
Vector2.prototype.dimension = 2;
dot.register('Vector2', Vector2);
const v2 = Vector2.pool.create.bind(Vector2.pool);
dot.register('v2', v2);
class ImmutableVector2 extends Vector2 {
    /**
     * Throw errors whenever a mutable method is called on our immutable vector
     */
    static mutableOverrideHelper(mutableFunctionName) {
        ImmutableVector2.prototype[mutableFunctionName] = () => {
            throw new Error(`Cannot call mutable method '${mutableFunctionName}' on immutable Vector2`);
        };
    }
}
ImmutableVector2.mutableOverrideHelper('setXY');
ImmutableVector2.mutableOverrideHelper('setX');
ImmutableVector2.mutableOverrideHelper('setY');
Vector2.ZERO = assert ? new ImmutableVector2(0, 0) : new Vector2(0, 0);
Vector2.X_UNIT = assert ? new ImmutableVector2(1, 0) : new Vector2(1, 0);
Vector2.Y_UNIT = assert ? new ImmutableVector2(0, 1) : new Vector2(0, 1);
const STATE_SCHEMA = {
    x: NumberIO,
    y: NumberIO
};
Vector2.Vector2IO = new IOType('Vector2IO', {
    valueType: Vector2,
    stateSchema: STATE_SCHEMA,
    toStateObject: (vector2) => vector2.toStateObject(),
    fromStateObject: (stateObject) => Vector2.fromStateObject(stateObject),
    documentation: 'A numerical object with x and y properties, like {x:3,y:4}'
});
export { v2 };
//# sourceMappingURL=Vector2.js.map