import { degreesToRadians, radiansToDegrees } from '../graphics/arc.js';
/**
* The Vector class is ued to model a 2 or 3 dimensional vector.
* @class
*/
class Vector {
/**
* @constructor
* @param {number} [x] - x component of the vector
* @param {number} [y] - y component of the vector
* @param {number} [z] z component of the vector
*/
constructor(x = 0, y = 0, z = 0) {
/**
* The x component of the vector
* @type {number}
*/
this.x = x;
/**
* The y component of the vector
* @type {number}
*/
this.y = y;
/**
* The z component of the vector
* @type {number}
*/
this.z = z;
}
/**
* Add a vector to this one, modifying this one.
* @param {number|Vector|number[]} x the x component of the vector to be added.
* Alternatively, a Vector or list of numbers to add.
* @param {number} [y] the y component of the vector to be added
* @param {number} [z] the z component of the vector to be added
* @returns {Vector} this vector, modified
*/
add(x, y, z) {
if (x instanceof Vector) {
const vector = x;
this.x += vector.x || 0;
this.y += vector.y || 0;
this.z += vector.z || 0;
} else if (x instanceof Array) {
const array = x;
this.x += array[0] || 0;
this.y += array[1] || 0;
this.z += array[2] || 0;
} else {
this.x += x || 0;
this.y += y || 0;
this.z += z || 0;
}
return this;
}
/**
* Subtract a vector from this one, modifying this one.
* @param {number|Vector|number[]} x the x component of the vector to be subtracted
* Alternatively, a Vector or list of numbers to substract.
* @param {number} [y] the y component of the vector to be subtracted
* @param {number} [z] the z component of the vector to be subtracted
*
* @returns {Vector} this vector, modified
*/
subtract(x, y, z) {
if (x instanceof Vector) {
const vector = x;
this.x -= vector.x || 0;
this.y -= vector.y || 0;
this.z -= vector.z || 0;
} else if (x instanceof Array) {
const array = x;
this.x -= array[0] || 0;
this.y -= array[1] || 0;
this.z -= array[2] || 0;
} else {
this.x -= x || 0;
this.y -= y || 0;
this.z -= z || 0;
}
return this;
}
/**
* Multiply this vector by a vector, scalar, or array, modifying it in place and returning it.
* @param {number|Vector|number[]} x scalar to multiply the x component by
* Alternatively, a Vector or list of numbers to multiply
* @param {number} [y] scalar to multiply the y component by
* @param {number} [z] scalar to multiply the z component by
*
k
* @returns {Vector} this vector, modified
*/
multiply(x, y, z) {
if (x instanceof Vector) {
const vector = x;
this.x *= vector.x;
this.y *= vector.y;
this.z *= vector.z;
} else if (x instanceof Array) {
const array = x;
if (x.length === 1) {
this.x *= array[0];
this.y *= array[0];
this.z *= array[0];
} else if (x.length === 2) {
this.x *= array[0];
this.y *= array[1];
} else if (x.length === 3) {
this.x *= array[0];
this.y *= array[1];
this.z *= array[2];
}
} else if ([...arguments].every(arg => typeof arg === 'number')) {
if (arguments.length === 1) {
this.x *= x;
this.y *= x;
this.z *= x;
}
if (arguments.length === 2) {
this.x *= x;
this.y *= y;
}
if (arguments.length === 3) {
this.x *= x;
this.y *= y;
this.z *= z;
}
} else {
throw new TypeError('Invalid arguments for multiply.');
}
return this;
}
/**
* Make a copy of this Vector.
* @returns {Vector}
*/
clone() {
return new Vector(this.x, this.y, this.z);
}
/**
* Make a copy of this Vector.
* @returns {Vector}
*/
copy() {
return this.clone(arguments);
}
/**
* Normalizes a vector to length 1, making it a unit vector.
* This is done by dividing each component of the vector by its magnitude.
* @returns {Vector} this vector
*/
normalize() {
const magnitude = this.magnitude();
if (magnitude !== 0) {
this.multiply(1 / magnitude);
}
return this;
}
/**
* Returns the magnitude of this vector.
*/
magnitude() {
const x = this.x;
const y = this.y;
const z = this.z;
return Math.sqrt(x * x + y * y + z * z);
}
/**
* Calculate the angle of rotation for this vector.
* This only works for 2d vectors.
*/
heading() {
return radiansToDegrees(Math.atan2(this.y, this.x));
}
/**
* Set the heading of the vector.
* @param {number} heading - Heading in degrees
*/
setHeading(heading) {
const magnitude = this.magnitude();
const radians = degreesToRadians(heading);
this.x = magnitude * Math.cos(radians);
this.y = magnitude * Math.sin(radians);
return this;
}
/**
* Rotates the vector by angle degrees.
* @param {number} angle - Rotation in degrees
* @returns {Vector} this vector
*/
rotate(angle) {
const heading = this.heading() + angle;
const magnitude = this.magnitude();
const headingRadians = degreesToRadians(heading);
this.x = magnitude * Math.cos(headingRadians);
this.y = magnitude * Math.sin(headingRadians);
return this;
}
/**
* Calculate the dot product of two vectors.
* @returns {number} dot product
*/
dot(x, y, z = 1) {
if (x instanceof Vector) {
const vector = x;
return this.dot(vector.x, vector.y, vector.z);
}
return this.x * x + this.y * y + this.z * z;
}
/**
* Calculates a new Vector from the cross product of this vector and v.
* @param {Vector} v
* @returns {Vector}
*/
cross(v) {
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;
return new Vector(x, y, z);
}
/**
* Find the angle between two vectors.
* @param {Vector}
* @returns {number} angle in degrees
*/
angleBetween(vector) {
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
let angle = Math.acos(this.dot(vector) / (this.magnitude() * vector.magnitude()));
angle = angle * Math.sign(this.cross(vector).z || 1);
return radiansToDegrees(angle);
}
/**
* Returns the points of the vector as an array.
* @returns {Array.<number>} values as an array
*/
array() {
return [this.x, this.y, this.z];
}
}
export default Vector;