import { lerp } from "./utils.js";
/**
* Class representing a Vector.
*/
export class Vector {
/**
* Create a vector from coordinates.
* @param {number} x - x
* @param {number} y - y
*/
constructor(x = 0, y = 0) {
if (typeof x !== 'number' || typeof y !== 'number') {
throw new TypeError("Vector components must be numbers.");
}
this.x = x;
this.y = y;
}
/**
* Create a vector from an array.
* @param {array} array [x, y]
* @returns {Vector}
*/
static fromArray(array) {
return new Vector(array[0], array[1]);
}
toArray() {
return [this.x, this.y];
}
/**
* Create a vector from an angle.
* @param {number} angle - The angle of the vector in radians.
* @param {number} [magnitude=1] - The magnitude of the vector.
* @returns {Vector}
*/
static fromAngle(angle, magnitude = 1) {
return new Vector(
Math.cos(angle),
Math.sin(angle)
).multiply(magnitude);
}
/**
* Add a vector to this vector.
* @param {Vector} vector
* @returns {Vector}
*/
add(vector) {
this.x += vector.x;
this.y += vector.y;
return this;
}
/**
* Check if this vector is equivalent to another vector.
* @param {Vector} vector
* @returns {boolean}
*/
equals(vector) {
return this.x === vector.x && this.y === vector.y;
}
/**
* Check if this vector is a point on a {@link Line}.
* @param {Line} line
* @returns {boolean}
*/
isOnLine(line) {
if ((line.b.x - line.a.x) * (this.y - line.a.y) !== (line.b.y - line.a.y) * (this.x - line.a.x)) {
return false;
}
return Math.min(line.a.x, line.b.x) <= this.x &&
this.x <= Math.max(line.a.x, line.b.x) &&
Math.min(line.a.y, line.b.y) <= this.y &&
this.y <= Math.max(line.a.y, line.b.y);
}
/**
* Subtract a vector from this vector.
* @param {Vector} vector
* @returns {Vector}
*/
subtract(vector) {
this.x -= vector.x;
this.y -= vector.y;
return this;
}
/**
* Multiply this vector by a scalar.
* @param {number} scalar
* @returns {Vector}
*/
multiply(scalar) {
this.x *= scalar;
this.y *= scalar;
return this;
}
/**
* Add two vectors.
* @param {Vector} v1
* @param {Vector} v2
* @returns {Vector}
*/
static add(v1, v2) {
return new Vector(v1.x + v2.x, v1.y + v2.y);
}
/**
* Subtract two vectors.
* @param {Vector} v1
* @param {Vector} v2
* @returns {Vector}
*/
static subtract(v1, v2) {
return new Vector(v1.x - v2.x, v1.y - v2.y);
}
/**
* Calculate the dot product of two vectors.
* @param {Vector} v1
* @param {Vector} v2
* @returns {number}
*/
static dot(v1, v2) {
return v1.x * v2.x + v1.y * v2.y;
}
/**
* Calculate the cross product of two 2D vectors.
* @param {Vector} v1
* @param {Vector} v2
* @returns {number}
*/
static cross(v1, v2) {
return v1.x * v2.y - v1.y * v2.x;
}
/**
* Get the magnitude of this vector.
* @returns {number} The magnitude (Euclidean distance) of this vector
*/
getMagnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
/**
* Set the magnitude of this vector.
* @param {number} magnitude
* @returns {Vector}
*/
setMagnitude(magnitude) {
return this.normalize().multiply(magnitude);
}
/**
* Calculate the distance to another vector from this vector
* @param {Vector} vector
* @returns {number}
*/
distance(vector) {
const delta = Vector.subtract(this, vector);
return delta.getMagnitude();
}
/**
* Calculate the distance between two vectors.
* @param {Vector} v1
* @param {Vector} v2
* @returns {number}
*/
static distance(v1, v2) {
const delta = Vector.subtract(v1, v2);
return delta.getMagnitude();
}
/**
* Rotate this vector by a specified angle.
* @param {number} angle - The angle to rotate the vector by, in radians.
* @returns {Vector} This vector after rotation.
*/
rotate(angle) {
const x = this.x * Math.cos(angle) - this.y * Math.sin(angle);
const y = this.x * Math.sin(angle) + this.y * Math.cos(angle);
this.x = x;
this.y = y;
return this;
}
/**
* Normalize this vector by setting it's magnitude to 1.
* @returns {Vector}
*/
normalize() {
const mag = this.getMagnitude();
if (mag === 0) {
throw new Error("Cannot normalize a zero vector.");
}
this.x /= mag;
this.y /= mag;
return this;
}
/**
* Make a copy of this vector.
* @returns {Vector}
*/
clone() {
return new Vector(this.x, this.y);
}
/**
* Get the angle of this vector with respect to the positive x-axis.
* @returns {number} Angle in radians
*/
getAngle() {
return Math.atan2(this.y, this.x);
}
/**
* Linear interpolation between vectors.
* @param {Vector} v1
* @param {Vector} v2
* @param {number} amount
* @returns {Vector}
*/
static lerp(v1, v2, amount) {
return new Vector(lerp(v1.x, v2.x, amount), lerp(v1.y, v2.y, amount));
}
/**
* Finds the `n` nearest neighbours to this vector, from a given array of vectors.
* @param {Array<Vector>} array - An array of Vectors to check.
* @param {number} n - The number of neighbours to find (must be > 0).
* @returns {Array<Vector>}
*/
nearestNeighbour(array, n) {
let neighbours = [];
for (let i = 0; i < n; i++) {
let record = Infinity;
let nearest = null;
for (let other of array) {
if (other != this && !neighbours.includes(other)) {
let dist = this.distance(other)
if (dist < record) {
nearest = other;
record = dist;
}
}
}
neighbours.push(nearest)
}
return neighbours;
}
}