/**
* Simple 2D and 3D vector library made in pure js.
* @version 1.0.5
* @author Lorenzo Rossi - https://www.lorenzoros.si - https://github.com/lorossi/
* @license Attribution 4.0 International (CC BY 4.0)
*/
/**
* Create a vector
* @class
* @param {number} [0] x - The x value
* @param {number} [0] y - The y value
* @param {number} [0] z - The z value
* @return {Vector} - The new vector
* @example
* v1 = new Vector(1, 4, -3);
* @example
* v2 = new Vector(3, -5);
*/
class Vector {
constructor(x = 0, y = 0, z = 0) {
this._x = x;
this._y = y;
this._z = z;
return this;
}
/**
* Add a vector
* @param {Vector} v - The vector to be added
* @return {Vector} - The new vector
* @throws {TypeError} - If the argument is not a vector
* @example
* v1 = new Vector(1, -4, 12);
* v2 = new Vector(2, 9, -3);
* v1.add(v2);
* // v1 = Vector(3, 5, 9);
*/
add(v) {
if (!(v instanceof Vector))
throw new TypeError("The argument is not a vector");
this._x += v.x;
this._y += v.y;
this._z += v.z;
return this;
}
/**
* Subtract a vector
* @param {Vector} v - The vector to be subtracted
* @return {Vector} - The new vector
* @throws {TypeError} - If the argument is not a vector
* @example
* v1 = new Vector(10, -3, 12);
* v2 = new Vector(7, -8, 3);
* v1.sub(v2);
* // v1 = Vector(3, 5, 9);
*/
sub(v) {
if (!(v instanceof Vector))
throw new TypeError("The argument is not a vector");
this._x -= v.x;
this._y -= v.y;
this._z -= v.z;
return this;
}
/**
* Alias for sub
* @borrows subtract
*/
subtract(v) {
return this.sub(v);
}
/**
* Multiply by a vector or a scalar
* @param {Vector|number} v - The vector or scalar to be multiplied by
* @return {Vector} - The new vector
* @throws {TypeError} - If the argument is not a vector or a number
* @example
* v1 = new Vector(1, 2, 3);
* v2 = new Vector(2, 5, 0);
* v1.mult(v2);
* // v1 = Vector(2, 10, 0);
* @example
* v1 = new Vector(7, 4, 2);
* v1.mult(3);
* // v1 = Vector(21, 12, 6);
*/
mult(v) {
if (v instanceof Vector) {
this._x *= v.x;
this._y *= v.y;
this._z *= v.z;
return this;
} else if (typeof v === "number") {
this._x *= v;
this._y *= v;
this._z *= v;
return this;
}
throw new TypeError("The argument is not a vector or a number");
}
/**
* Alias for mult
* @borrows multiply
*/
multiply(v) {
return this.mult(v);
}
/**
* Divide by a vector or a scalar
* @param {Vector|number} v - The vector or scalar to be divided by
* @return {Vector} - The new vector
* @throws {TypeError} - If the argument is not a vector or a number
* v1 = new Vector(4, 12, 9);
* v2 = new Vector(4, 6, 3);
* v1.divide(v2);
* // v1 = Vector(1, 2, 3);
* @example
* v1 = new Vector(9, 3, 6);
* v1.divide(3);
* // v1 = Vector(3, 1, 2);
*/
divide(v) {
if (v instanceof Vector) {
this._x /= v.x;
this._y /= v.y;
this._z /= v.z;
return this;
} else if (typeof v === "number") {
this._x /= v;
this._y /= v;
this._z /= v;
return this;
}
throw new TypeError("The argument is not a vector or a number");
}
/**
* Alias for divide
* @borrows divide
*/
div(v) {
return this.divide(v);
}
/**
* Return minimum component of a vector
* @return {number} The smallest component
* @example
* v1 = new Vector(3, -8, 12);
* v1.min();
* // -8
*/
min() {
return Math.min(this._x, this._y, this._z);
}
/**
* Return maximum component of a vector
* @return {number} The biggest component
* @example
* v1 = new Vector(3, -8, 12);
* v1.max();
* // -12
*/
max() {
return Math.max(this._x, this._y, this._z);
}
/**
* Dot function
* @param {Vector} v - The vector to perform dot operation with
* @return {Vector} - The new vector
* @example
* v1 = new Vector(1, 4, 3);
* v2 = new Vector(2, -6, 9);
* v1.dot(v2);
* // return 5;
*/
dot(v) {
if (!(v instanceof Vector))
throw new TypeError("The argument is not a vector");
return this._x * v._x + this._y * v.y + this._z * v.z;
}
/**
* Cross function
* @param {Vector} v - The vector to perform cross operation with
* @return {Vector} - The new vector
* @throws {TypeError} - If the argument is not a vector
* @example
* v1 = new Vector(1, 4, 3);
* v2 = new Vector(2, -6, 9);
* v1.cross(v2);
* // v1 = Vector(54, -3, -14);
*/
cross(v) {
if (!(v instanceof Vector))
throw new TypeError("The argument is not a vector");
const x = this._y * v.z - this._z * v.y;
const y = this._z * v._x - this._x * v.z;
const z = this._x * v.y - this._y * v._x;
this._x = x;
this._y = y;
this._z = z;
return this;
}
/**
* Square distance between vectors
* @param {Vector} v - The vector whose distance will be calculated
* @return {number} Return a number containing the square distance
* @throws {TypeError} - If the argument is not a vector
* @example
* v1 = new Vector(1, 4, -3);
* v2 = new Vector(6, -6, 7);
* v1.distSq(v2);
* // return 225
*/
distSq(v) {
if (!(v instanceof Vector))
throw new TypeError("The argument is not a vector");
return (
Math.pow(this._x - v._x, 2) +
Math.pow(this._y - v.y, 2) +
Math.pow(this._z - v.z, 2)
);
}
/**
* Distance between vectors
* @param {Vector} v - The vector whose distance will be calculated
* @return {number} Return a number containing the distance
* @throws {TypeError} - If the argument is not a vector
* @example
* v1 = new Vector(1, 4, -3);
* v2 = new Vector(6, -6, 7);
* v1.dist(v2);
* // return 15
*/
dist(v) {
if (!(v instanceof Vector))
throw new TypeError("The argument is not a vector");
return Math.sqrt(this.distSq(v));
}
/**
* Angle between vectors
* @param {Vector} v - The vector whose contained angle will be calculated
* @return {number} Return a vector containing the angle in radians
* @throws {TypeError} - If the argument is not a vector
* @example
* v1 = new Vector(1, 4, -3);
* v2 = new Vector(6, -6, 7);
* v1.angleBetween(v2);
* // return 1.0888019833827516
*/
angleBetween(v) {
if (!(v instanceof Vector))
throw new TypeError("The argument is not a vector");
return Math.acos(this.dot(v) / (this.mag() * v.mag()));
}
/**
* Check if two vectors are equals
* @param {Vector} v - The vector that will be compared
* @return {boolean} Return true or false
* @throws {TypeError} - If the argument is not a vector
* @example
* v1 = new Vector(1, 4, -3);
* v2 = new Vector(6, -6, 7);
* v1.equals(v2);
* // return false;
*/
equals(v) {
if (!(v instanceof Vector))
throw new TypeError("The argument is not a vector");
return this._x == v._x && this._y == v.y && this._z == v.z;
}
/**
* Copy the vector into a new object
* @return {Vector} The new copied vector
* @example
* v1 = new Vector(8, 144, -32);
* v2 = v1.copy();
* // v2 = Vector(8, 144, -32);
*/
copy() {
return new Vector(this._x, this._y, this._z);
}
/**
* Limit the vector magnitude to a set value
* @param {number} s - The maximum magnitude
* @return {Vector} - The new vector
* @throws {TypeError} - If the argument is not a number
* @example
* v1 = new Vector(2, 0, 2);
* v1.limit(2);
* // v1 = Vector(1.414213562373095, 0, 1.414213562373095);
*/
limit(s) {
if (typeof s !== "number")
throw new TypeError("The argument is not a number");
let m = this.mag();
if (m > s) {
this.multiply(s / m);
return this;
}
}
/**
* Set the vector magnitude
* @param {number} s - Magnitude
* @return {Vector} - The new vector
* @throws {TypeError} - If the argument is not a number
* @example
* v1 = new Vector(2, 0, 2);
* v1.setMag(4);
* // v1 = Vector(2.82842712474619, 0, 2.82842712474619);
*/
setMag(s) {
if (typeof s !== "number")
throw new TypeError("The argument is not a number");
let m = this.mag();
this.multiply(s / m);
return this;
}
/**
* Rotate a vector by an angle in radians
* @param {number} t - The rotation angle
* @return {Vector} - The new vector
* @throws {TypeError} - If the argument is not a number
* @example
* v1 = new Vector(2, 1);
* v1.rotate(Math.PI);
* // v1 = Vector(-2, -1, 0);
*/
rotate(t) {
if (typeof t !== "number")
throw new TypeError("The argument is not a number");
let x = Math.cos(t) * this._x - Math.sin(t) * this._y;
let y = Math.sin(t) * this._x + Math.cos(t) * this._y;
this._x = x;
this._y = y;
return this;
}
/**
* Normalize a vector (its magnitude will be unitary)
* @return {Vector} - The new vector
* @example
* v1 = new Vector(5, 2, -4);
* v1.normalize();
* // v1 = Vector(0.7453559924999299, 0.29814239699997197, -0.5962847939999439);
*/
normalize() {
this.divide(this.mag());
return this;
}
/**
* Invert some (or all) components of the vector
* @param {boolean} x - The x component
* @param {boolean} y - The y component
* @param {boolean} z - The z component
* @return {Vector} - The new vector
* @example
* v1 = new Vector(4, -5, 7);
* v1.invert(true, true, true);
* // v1 = Vector(-4, 5, -7);
* @example
* v2 = new Vector(4, -1, -3);
* v2.invert(true, false);
* // v2 = Vector(-4, -1, -3);
* @example
* v3 = new Vector(4, -1, -3);
* v3.invert();
* // v3 = Vector(-4, 1, 3);
*/
invert(x, y, z) {
if (x === true) {
this._x *= -1;
}
if (y === true) {
this._y *= -1;
}
if (z === true) {
this._z *= -1;
}
if (x === undefined && y === undefined && z === undefined) {
this._x *= -1;
this._y *= -1;
this._z *= -1;
}
return this;
}
/**
* Invert the x component of the vector
* @return {Vector} - The new vector
* @example
* v1 = new Vector(4, -5, 7);
* v1.invertX();
* // v1 = Vector(-4, -5, 7);
*/
invertX() {
this.invert(true, false, false);
return this;
}
/**
* Invert the y component of the vector
* @return {Vector} - The new vector
* @example
* v1 = new Vector(4, -5, 7);
* v1.invertY();
* // v1 = Vector(4, 5, 7);
*/
invertY() {
this.invert(false, true, false);
return this;
}
/**
* Invert the z component of the vector
* @return {Vector} - The new vector
* @example
* v1 = new Vector(4, -5, 7);
* v1.invertZ();
* // v1 = Vector(4, -5, -7);
*/
invertZ() {
this.invert(false, false, true);
return this;
}
/**
* Calculate the vector magnitude
* @return {number} - The vector magnitude
* @example
* v1 = new Vector(6, -2, -1);
* v1.mag();
* // return 6.4031242374328485;
*/
mag() {
return Math.sqrt(this._x * this._x + this._y * this._y + this._z * this._z);
}
/**
* Alias for mag()
* @borrows mag
*/
magnitude() {
return this.mag();
}
/**
* Calculate the vector square magnitude
* @return {number} The vector square magnitude
* @example
* v1 = new Vector(6, -2, -1);
* v1.magSq();
* // return 41;
*/
magSq() {
return this._x * this._x + this._y * this._y + this._z * this._z;
}
/**
* Alias for magSq()
* @borrows magSq
*/
magnitudeSq() {
return this.magSq();
}
/**
* Calculate the vector heading (radians) - only for 2D vectors
* @return {number} The vector heading (radians)
* @example
* v1 = new Vector(3, 3);
* v1.heading2D();
* // return 0.7853981633974483
*/
heading2D() {
if (this._z !== 0) throw new Error("The vector is not 2D");
return Math.atan2(this._y, this._x);
}
/**
* Return a printable string of the vector
* @return {string} Printable string
* @example
* v1 = new Vector(3, 3, -4);
* v1.toString();
* // return "x: 3, y: 3, z: -4"
*/
toString() {
return `Vector(${this._x}, ${this._y}, ${this._z})`;
}
/**
* Return an array with the vector components
* @return {array} Array with the vector components
* @example
* v1 = new Vector(3, 3, -4);
* v1.toArray();
* // return [3, 3, -4]
*/
toArray() {
return [this._x, this._y, this._z];
}
/**
* Return an object with the vector components
* @return {object} Object with the vector components
* @example
* v1 = new Vector(3, 3, -4);
* v1.toObject();
* // return { x: 3, y: 3, z: -4 }
*/
toObject() {
return { x: this._x, y: this._y, z: this._z };
}
/**
* Create a 2D vector from its angle
* @static
* @param {number} [0] theta - Theta angle (radians)
* @return {Vector} - The new vector
* @example
* v1 = new Vector.fromAngle2D(2.42);
* // v1 = Vector(-0.7507546047254909,0.6605812012792007, 0);
*/
static fromAngle2D(theta = 0) {
return new Vector(Math.cos(theta), Math.sin(theta), 0);
}
/**
* Create a 3D vector from its angles
* @static
* @param {number} [0] theta - Theta angle (radians)
* @param {number} [0] phi - Phi angle (radians)
* @return {Vector} - The new vector
* @example
* v1 = new Vector.fromAngle2D(1.33, -2.44);
* // v1 = Vector(-0.1821516349441893, -0.6454349983343708, -0.7417778945292652);
*/
static fromAngle3D(theta = 0, phi = 0) {
return new Vector(
Math.cos(theta) * Math.cos(phi),
Math.sin(phi),
Math.sin(theta) * Math.cos(phi)
);
}
/**
* Create a random 2D vector
* @return {Vector} - The new vector
* @static
* @example
* v1 = new Vector.random2D();
* // v1 = Vector(0.2090564102081952, -0.977903582849998, 0);
*/
static random2D() {
let theta = Math.random() * 2 * Math.PI;
return Vector.fromAngle2D(theta);
}
/**
* Create a random 3D vector
* @return {Vector} - The new vector
* @static
* @example
* v1 = new Vector.random3D();
* // v1 = Vector(-0.7651693875628326, -0.43066633476756877, 0.47858365667309205);
*/
static random3D() {
let theta = Math.random() * 2 * Math.PI;
let phi = Math.random() * 2 * Math.PI;
return Vector.fromAngle3D(theta, phi);
}
/**
* Create a vector from an Array
* @param {Array} a - The array
* @return {Vector} - The new vector
* @static
* @example
* // return Vector(4, 5, 6)
* v = new Vector.fromArray([4, 5, 6])
* @example
* // return Vector(1, 7, 0)
* v = new Vector.fromArray([1, 7])
*/
static fromArray(a) {
a = a.fill(0, 3);
return new Vector(a[0], a[1], a[2]);
}
/**
* Create a vector from an object
* @return {Vector} - The new vector
* @static
* @example
* // return Vector(1, 5, 9)
* v = new Vector.fromArray([1, 5, 9]])
* @example
* // return Vector(3, 0, 4)
* v = new Vector.fromArray([3, 0, 4]])
*/
static fromObject(o) {
return new Vector(o.x, o.y, o.z);
}
/**
* Create a vector from its polar coordinates
* @param {number} r - The radius
* @param {number} theta - The theta angle (radians)
* @param {number} [0] phi - The phi angle (radians)
* @return {Vector} - The new vector
* @throws {Error} If the arguments are not numbers
* @static
*/
static fromPolar(r, theta, phi = 0) {
if (r < 0) throw new Error("The radius must be positive");
const x = r * Math.sin(theta) * Math.cos(phi);
const y = r * Math.sin(theta) * Math.sin(phi);
const z = r * Math.cos(theta);
return new Vector(x, y, z);
}
/**
* Create a vector from a string
* @param {string} s - The string
* @return {Vector} - The new vector
* @static
* @throws {Error} If the string is not in the correct format
*
*/
static fromString(s) {
const groups = s.match(/Vector\(([\s,0-9.-]+)\)/);
try {
const v = groups[1].split(",").fill(0, 3);
return new Vector(parseFloat(v[0]), parseFloat(v[1]), parseFloat(v[2]));
} catch (e) {
throw new Error("The string is not in the correct format");
}
}
/**
* Get the x component of the vector
* @return {number} The x component
*/
get x() {
return this._x;
}
/**
* Set the x component of the vector
* @param {number} nx - The new x component
*/
set x(nx) {
this._x = nx;
}
/**
* Get the y component of the vector
* @return {number} The y component
*/
get y() {
return this._y;
}
/**
* Set the y component of the vector
* @param {number} ny - The new y component
*/
set y(ny) {
this._y = ny;
}
/**
* Get the z component of the vector
* @return {number} The z component
*/
get z() {
return this._z;
}
/**
* Set the z component of the vector
* @param {number} nz - The new z component
*/
set z(nz) {
this._z = nz;
}
}