toosoon-utils
Version:
Utility functions & classes
728 lines (727 loc) • 19.7 kB
JavaScript
import { EPSILON, PI } from '../../constants';
import { clamp, lerp } from '../../maths';
/**
* Utility class for manipulating a 2D vectors
*
* @exports
* @class Vector2
* @implements Vector
*/
export default class Vector2 {
isVector2 = true;
type = 'Vector2';
/**
* X-axis value of this vector
*/
x;
/**
* Y-axis value of this vector
*/
y;
*[Symbol.iterator]() {
yield this.x;
yield this.y;
}
/**
* @param {number} [x=0] X-axis value
* @param {number} [y=0] Y-axis value
*/
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
/**
* Set this vector values
*
* @param {number} x X-axis value
* @param {number} y Y-axis value
* @returns {this}
*/
set(x, y) {
this.x = x;
this.y = y;
return this;
}
/**
* Set a given scalar value to all values of this vector
*
* @param {number} scalar Value to set for all values
* @returns {this}
*/
setScalar(scalar) {
this.x = scalar;
this.y = scalar;
return this;
}
/**
* Set this vector X-axis value
*
* @param {number} x X-axis value to set
* @returns {this}
*/
setX(x) {
this.x = x;
return this;
}
/**
* Set this vector Y-axis value
*
* @param {number} y Y-axis value to set
* @returns {this}
*/
setY(y) {
this.y = y;
return this;
}
/**
* Set a given value of this vector
*
* @param {string|number} index `0` equals to `x`, `1` equals to `y`
* @param {number} value Value to set
* @returns {this}
*/
setValue(index, value) {
switch (index) {
case 'x':
case 0:
this.x = value;
break;
case 'y':
case 1:
this.y = value;
break;
}
return this;
}
/**
* Return a value from this vector
*
* @param {string|number} index `0` equals to `x`, `1` equals to `y`
* @returns {number}
*/
getValue(index) {
switch (index) {
case 'x':
case 0:
return this.x;
case 'y':
case 1:
return this.y;
default:
return NaN;
}
}
/**
* Add a given vector to this vector
*
* @param {Vector2|Point2} vector Vector to add
* @returns {this}
*/
add([x, y]) {
this.x += x;
this.y += y;
return this;
}
/**
* Add a given scalar value to all values of this vector
*
* @param {number} scalar Scalar value to add
* @returns {this}
*/
addScalar(scalar) {
this.x += scalar;
this.y += scalar;
return this;
}
/**
* Subtract a given vector to this vector
*
* @param {Vector2|Point2} vector Vector to subtract
* @returns {this}
*/
sub([x, y]) {
this.x -= x;
this.y -= y;
return this;
}
/**
* Subtract a given scalar value to all values of this vector
*
* @param {number} scalar Scalar value to subtract
* @returns {this}
*/
subScalar(scalar) {
this.x -= scalar;
this.y -= scalar;
return this;
}
/**
* Multiply a given vector to this vector
*
* @param {Vector2|Point2} vector Vector to multiply
* @returns {this}
*/
multiply([x, y]) {
this.x *= x;
this.y *= y;
return this;
}
/**
* Multiply a given scalar value to all values of this vector
*
* @param {number} scalar Scalar value to multiply
* @returns {this}
*/
multiplyScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
return this;
}
/**
* Divide a given vector to this vector
*
* @param {Vector2|Point2} vector Vector to divide
* @returns {this}
*/
divide([x, y]) {
this.x /= x;
this.y /= y;
return this;
}
/**
* Divide a given scalar value to all values of this vector
*
* @param {number} scalar Scalar value to multiply
* @returns {this}
*/
divideScalar(scalar) {
this.x /= scalar;
this.y /= scalar;
return this;
}
/**
* Set this vector values to the min values compared to a given vector
*
* @param {Vector2|Point2} vector Vector to compare values with
* @returns {this}
*/
min([x, y]) {
this.x = Math.min(this.x, x);
this.y = Math.min(this.y, y);
return this;
}
/**
* Set this vector values to the max values compared to a given vector
*
* @param {Vector2|Point2} vector Vector to compare values with
* @returns {this}
*/
max([x, y]) {
this.x = Math.max(this.x, x);
this.y = Math.max(this.y, y);
return this;
}
/**
* Clamp this vector values to given boundaries
*
* @param {Vector2|Point2} min Minimum boundaries
* @param {Vector2|Point2} max Maximum boundaries
* @returns {this}
*/
clamp([minX, minY], [maxX, maxY]) {
this.x = clamp(this.x, minX, maxX);
this.y = clamp(this.y, minY, maxY);
return this;
}
/**
* Clamp this vector values to given scalar values
*
* @param {Vector2|Point2} min Minimum scalar boundary
* @param {Vector2|Point2} max Maximum scalar boundary
* @returns {this}
*/
clampScalar(min, max) {
this.x = clamp(this.x, min, max);
this.y = clamp(this.y, min, max);
return this;
}
// public clampLength(min: number, max: number) {
// const length = this.length();
// return this.divideScalar(length || 1).multiplyScalar(clamp(length, min, max));
// }
/**
* Round down to the nearest integer value this vector values
*
* @returns {this}
*/
floor() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
}
/**
* Round up to the nearest integer value this vector values
*
* @returns {this}
*/
ceil() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
}
/**
* Round to the nearest integer value this vector values
*
* @returns {this}
*/
round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
}
/**
* Remove any fractional digits of this vector values
*
* @returns {this}
*/
trunc() {
this.x = Math.trunc(this.x);
this.y = Math.trunc(this.y);
return this;
}
/**
* Set this vector values to their negative values
*
* @returns {this}
*/
negate() {
this.x = -this.x;
this.y = -this.y;
return this;
}
/**
* Rotate this vector around a given center by the given angle
*
* @param {Vector2|Point2} center Vector around which to rotate
* @param {number} angle Angle to rotate (in radians)
* @returns {this}
*/
rotateAround(center, angle) {
return this.set(...Vector2.rotate(this, center, angle));
}
/**
* Linearly interpolate this vector values towards a given vector values
*
* @param {number} t Normalized time value to interpolate
* @param {Vector2|Point2} vector Vector to interpolate values towards
* @returns {this}
*/
lerp(t, [x, y]) {
this.x += (x - this.x) * t;
this.y += (y - this.y) * t;
return this;
}
/**
* Convert this vector to a unit vector
*
* @returns {this}
*/
normalize() {
return this.divideScalar(this.length() || 1);
}
/**
* Transform this vector by a given matrix
*
* @param {DOMMatrix} matrix Matrix to apply
* @returns {this}
*/
applyMatrix(matrix) {
const { x, y } = matrix.transformPoint({ x: this.x, y: this.y });
this.x = x;
this.y = y;
return this;
}
/**
* Set this vector values to the same direction but with a given length
*
* @param {number} length Length value
* @returns {this}
*/
setLength(length) {
return this.normalize().multiplyScalar(length);
}
/**
* Calculate the Euclidean length of this vector
*
* @returns {number} Computed Euclidean length
*/
length() {
return Vector2.length(this);
}
/**
* Calculate the squared length of this vector
*
* @return {number} Computed squared length
*/
squaredLength() {
return Vector2.squaredLength(this);
}
/**
* Calculate the Manhattan length of this vector
*
* @return {number} Computed Manhattan length
*/
manhattanLength() {
return Vector2.manhattanLength(this);
}
/**
* Check if this vector is equal with a given vector
*
* @param {Vector2|Point2} vector Vector to check
* @returns {boolean} True if this vector is equal with the given vector, false otherwise
*/
equals(vector) {
return Vector2.equals(this, vector);
}
/**
* Check if this vector is collinear with given vectors
*
* @param {Vector2|Point2} vector1 First vector to check
* @param {Vector2|Point2} vector2 Second vector to check
* @returns {boolean} True if this vector is collinear with the given vectors, false otherwise
*/
collinear(vector1, vector2) {
return Vector2.collinear(this, vector1, vector2);
}
/**
* Calculate the dot product of a given vector with this vector
*
* @param {Vector2|Point2} vector Vector to compute the dot product with
* @returns {number} Computed dot product
*/
dot(vector) {
return Vector2.dot(this, vector);
}
/**
* Calculate the cross product of a given vector with this vector
*
* @param {Vector2|Point2} vector Vector to compute the cross product with
* @returns {number} Computed cross product
*/
cross(vector) {
return Vector2.cross(this, vector);
}
/**
* Calculate the angle of this vector with respect to the positive X-axis
*
* @returns {number} Computed angle (in radians)
*/
angle() {
return Vector2.angle(this);
}
/**
* Calculate the angle between a given vector and this vector
*
* @param {Vector2|Point2} vector Vector to compute the angle with
* @returns {number} Computed angle (in radians)
*/
angleTo(vector) {
const denominator = Math.sqrt(this.squaredLength() * Vector2.squaredLength(vector));
if (denominator === 0)
return PI / 2;
const theta = this.dot(vector) / denominator;
return Math.acos(clamp(theta, -1, 1));
}
/**
* Calculate the Euclidean distance from a given vector to this vector
*
* @param {Vector2|Point2} vector Vector to compute the distance to
* @returns {number} Computed Euclidean distance
*/
distanceTo(vector) {
return Vector2.distance(this, vector);
}
/**
* Calculate the squared distance from a given vector to this vector
*
* @param {Vector2|Point2} vector Vector to compute the squared distance to
* @returns {number} Computed squared distance
*/
squaredDistanceTo(vector) {
return Vector2.squaredDistance(this, vector);
}
/**
* Calculate the Manhattan distance from a given vector to this vector
*
* @param {Vector2|Point2} vector Vector to compute the Manhattan distance to
* @returns {number} Computed Manhattan distance
*/
manhattanDistanceTo(vector) {
return Vector2.manhattanDistance(this, vector);
}
/**
* Return this vector values into an array
*
* @returns {Point2}
*/
toArray() {
return [this.x, this.y];
}
/**
* Set this vector values from a given array
*
* @param {number[]} values Values to set
* @returns {this}
*/
fromArray([x, y]) {
this.x = x;
this.y = y;
return this;
}
/**
* Set this vector values from given circular coordinates
*
* @param {number} angle Angle (in radians)
* @param {number} [radius] Radius of the circle
* @returns {this}
*/
fromCircularCoords(angle, radius) {
return this.set(...Vector2.fromCircularCoords(angle, radius));
}
/**
* Copy the values of a given vector to this vector
*
* @param {Vector2|Point2} vector Vector to copy values from
* @returns {this}
*/
copy([x, y]) {
this.x = x;
this.y = y;
return this;
}
/**
* Create a new 2D vector with copied values from this vector
*
* @returns {Vector2}
*/
clone() {
return new Vector2(this.x, this.y);
}
/**
* X-axis value of this vector
*/
set width(width) {
this.x = width;
}
get width() {
return this.x;
}
/**
* Y-axis value of this vector
*/
set height(height) {
this.y = height;
}
get height() {
return this.y;
}
/**
* Add two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {Point2}
*/
static add([x1, y1], [x2, y2]) {
const x = x1 + x2;
const y = y1 + y2;
return [x, y];
}
/**
* Subtract two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {Point2}
*/
static sub([x1, y1], [x2, y2]) {
const x = x1 - x2;
const y = y1 - y2;
return [x, y];
}
/**
* Multiply two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {Point2}
*/
static multiply([x1, y1], [x2, y2]) {
const x = x1 * x2;
const y = y1 * y2;
return [x, y];
}
/**
* Divide two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {Point2}
*/
static divide([x1, y1], [x2, y2]) {
const x = x1 / x2;
const y = y1 / y2;
return [x, y];
}
/**
* Rotate a vector around a given center by a given angle
*
* @param {Vector2|Point2} vector Vector to rotate
* @param {Vector2|Point2} center Vector around which to rotate
* @param {number} angle Angle to rotate (in radians)
* @returns {Point2}
*/
static rotate([vx, vy], [cx, cy], angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const ox = vx - cx;
const oy = vy - cy;
const x = cx + ox * cos - oy * sin;
const y = cy + ox * sin + oy * cos;
return [x, y];
}
/**
* Linearly interpolate a point between two vectors
*
* @param {number} t Normalized time value to interpolate
* @param {Vector2|Point2} min Minimum boundaries
* @param {Vector2|Point2} max Maximum boundaries
* @returns {Point2}
*/
static lerp(t, [x1, y1], [x2, y2]) {
const x = lerp(t, x1, x2);
const y = lerp(t, y1, y2);
return [x, y];
}
/**
* Check if two vectors are equal to each other
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {boolean} True if the given vectors are equal, false otherwise
*/
static equals([x1, y1], [x2, y2]) {
return x1 === x2 && y1 === y2;
}
/**
* Check if three vectors are collinear (aligned on the same line)
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @param {Vector2|Point2} vector3 Third vector
* @returns {boolean} True if the given vectors are collinear, false otherwise
*/
static collinear([x1, y1], [x2, y2], [x3, y3]) {
return Math.abs((x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2)) <= EPSILON;
}
/**
* Calculate the dot product of two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {number} Computed dot product
*/
static dot([x1, y1], [x2, y2]) {
return x1 * x2 + y1 * y2;
}
/**
* Calculate the cross product of two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {number} Computed cross product
*/
static cross([x1, y1], [x2, y2]) {
return x1 * x2 - y1 * y2;
}
/**
* Calculate the angle of a given vector with respect to the positive X-axis
*
* @param {Vector2|Point2} vector Vector to compute angle from
* @returns {number} Computed angle (in radians)
*/
static angle([x, y]) {
return PI + Math.atan2(-y, -x);
}
/**
* Calculate the Euclidean distance between two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {number} Computed Euclidean distance
*/
static distance(vector1, vector2) {
return Math.sqrt(this.squaredDistance(vector1, vector2));
}
/**
* Calculate the squared distance between two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @returns {number} Computed squared distance
*/
static squaredDistance([x1, y1], [x2, y2]) {
const dx = x1 - x2;
const dy = y1 - y2;
return dx * dx + dy * dy;
}
/**
* Calculate the Manhattan distance between two vectors
*
* @param {Vector2|Point2} vector1 First vector
* @param {Vector2|Point2} vector2 Second vector
* @return {number} Computed Manhattan distance
*/
static manhattanDistance([x1, y1], [x2, y2]) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
/**
* Calculate the Euclidean length of a vector
*
* @param {Vector2|Point2} vector Vector to compute Euclidean length from
* @returns {number} Computed Euclidean length
*/
static length(vector) {
return Math.sqrt(Vector2.squaredLength(vector));
}
/**
* Calculate the squared length of a vector
*
* @param {Vector2|Point2} vector Vector to compute squared length from
* @returns {number} Computed squared length
*/
static squaredLength([x, y]) {
return x * x + y * y;
}
/**
* Calculate the Manhattan length of a vector
*
* @param {Vector2|Point2} vector Vector to compute Manhattan length from
* @return {number} Computed Manhattan length
*/
static manhattanLength([x, y]) {
return Math.abs(x) + Math.abs(y);
}
/**
* Convert circular coordinates to a 2D point on the surface of a circle
*
* @param {number} angle Angle (in radians)
* @param {number} [radius=1] Radius of the circle
* @returns {Point2}
*/
static fromCircularCoords(angle, radius = 1) {
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
return [x, y];
}
}