toosoon-utils
Version:
Utility functions & classes
779 lines (778 loc) • 21.8 kB
JavaScript
import { EPSILON, PI } from '../../constants';
import { clamp, lerp } from '../../maths';
/**
* Utility class for manipulating a 3D vectors
*
* @exports
* @class Vector3
* @implements Vector
*/
export default class Vector3 {
isVector3 = true;
type = 'Vector3';
/**
* X-axis value of this vector
*/
x;
/**
* Y-axis value of this vector
*/
y;
/**
* Z-axis value of this vector
*/
z;
*[Symbol.iterator]() {
yield this.x;
yield this.y;
yield this.z;
}
/**
* @param {number} [x=0] X-axis value
* @param {number} [y=0] Y-axis value
* @param {number} [z=0] Z-axis value
*/
constructor(x = 0, y = 0, z = 0) {
this.x = x;
this.y = y;
this.z = z;
}
/**
* Set this vector values
*
* @param {number} x X-axis value
* @param {number} y Y-axis value
* @param {number} z Z-axis value
* @returns {this}
*/
set(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* Set a given scalar value to all values of this vector
*
* @param {number} scalar Value to set for all vector values
* @returns {this}
*/
setScalar(scalar) {
this.x = scalar;
this.y = scalar;
this.z = 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 this vector Z-axis value
*
* @param {number} z Z-axis value to set
* @returns {this}
*/
setZ(z) {
this.z = z;
return this;
}
/**
* Set a given value of this vector
*
* @param {string|number} index `0` equals to `x`, `1` equals to `y`, `2` equals to `z`
* @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;
case 'z':
case 2:
this.z = value;
break;
}
return this;
}
/**
* Return a value from the vector
*
* @param {string|number} index `0` equals to `x`, `1` equals to `y`, `2` equals to `z`
* @returns {number}
*/
getValue(index) {
switch (index) {
case 'x':
case 0:
return this.x;
case 'y':
case 1:
return this.y;
case 'z':
case 2:
return this.z;
default:
return NaN;
}
}
/**
* Add a given vector to this vector
*
* @param {Vector3|Point3} vector Vector to add
* @returns {this}
*/
add([x, y, z]) {
this.x += x;
this.y += y;
this.z += z;
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;
this.z += scalar;
return this;
}
/**
* Subtract a given vector to this vector
*
* @param {Vector3|Point3} vector Vector to subtract
* @returns {this}
*/
sub([x, y, z]) {
this.x -= x;
this.y -= y;
this.z -= z;
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;
this.z -= scalar;
return this;
}
/**
* Multiply a given vector to this vector
*
* @param {Vector3|Point3} vector Vector to multiply
* @returns {this}
*/
multiply([x, y, z]) {
this.x *= x;
this.y *= y;
this.z *= z;
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;
this.z *= scalar;
return this;
}
/**
* Divide a given vector to this vector
*
* @param {Vector3|Point3} vector Vector to divide
* @returns {this}
*/
divide([x, y, z]) {
this.x /= x;
this.y /= y;
this.z /= z;
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;
this.z /= scalar;
return this;
}
/**
* Set this vector values to the min values compared to a given vector
*
* @param {Vector3|Point3} vector Vector to compare values with
* @returns {this}
*/
min([x, y, z]) {
this.x = Math.min(this.x, x);
this.y = Math.min(this.y, y);
this.z = Math.min(this.z, z);
return this;
}
/**
* Set this vector values to the max values compared to a given vector
*
* @param {Vector3|Point3} vector Vector to compare values with
* @returns {this}
*/
max([x, y, z]) {
this.x = Math.max(this.x, x);
this.y = Math.max(this.y, y);
this.z = Math.max(this.z, z);
return this;
}
/**
* Clamp this vector values to given boundaries
*
* @param {Vector3|Point3} min Minimum boundaries
* @param {Vector3|Point3} max Maximum boundaries
* @returns {this}
*/
clamp([minX, minY, minZ], [maxX, maxY, maxZ]) {
this.x = clamp(this.x, minX, maxX);
this.y = clamp(this.y, minY, maxY);
this.z = clamp(this.z, minZ, maxZ);
return this;
}
/**
* Clamp this vector values to given scalar values
*
* @param {Vector3|Point3} min Minimum scalar boundary
* @param {Vector3|Point3} max Maximum scalar boundary
* @returns {this}
*/
clampScalar(min, max) {
this.x = clamp(this.x, min, max);
this.y = clamp(this.y, min, max);
this.z = clamp(this.z, 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);
this.z = Math.floor(this.z);
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);
this.z = Math.ceil(this.z);
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);
this.z = Math.round(this.z);
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);
this.z = Math.trunc(this.z);
return this;
}
/**
* Set this vector values to their negative values
*
* @returns {this}
*/
negate() {
this.x = -this.x;
this.y = -this.y;
this.z = -this.z;
return this;
}
/**
* Linearly interpolate this vector values towards a given vector values
*
* @param {number} t Normalized time value to interpolate
* @param {Vector3|Point3} vector Vector to interpolate values towards
* @returns {this}
*/
lerp(t, [x, y, z]) {
this.x += (x - this.x) * t;
this.y += (y - this.y) * t;
this.z += (z - this.z) * 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, z } = matrix.transformPoint({ x: this.x, y: this.y, z: this.z });
this.x = x;
this.y = y;
this.z = z;
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);
}
/**
* Project this vector onto a given vector
*
* @param {Vector3|Point3} vector Vector to project to
* @returns {this}
*/
projectOnVector(vector) {
const denominator = Vector3.squaredLength(vector);
if (denominator === 0)
return this.set(0, 0, 0);
const scalar = Vector3.dot(vector, this) / denominator;
return this.copy(vector).multiplyScalar(scalar);
}
/**
* Calculate the Euclidean length of this vector
*
* @returns {number} Computed Euclidean length
*/
length() {
return Vector3.length(this);
}
/**
* Calculate the squared length of this vector
*
* @return {number} Computed squared length
*/
squaredLength() {
return Vector3.squaredLength(this);
}
/**
* Calculate the Manhattan length of this vector
*
* @return {number} Computed Manhattan length
*/
manhattanLength() {
return Vector3.manhattanLength(this);
}
/**
* Check if this vector is equal with a given vector
*
* @param {Vector3|Point3} vector Vector to check
* @returns {boolean} True if this vector is equal with the given vector, false otherwise
*/
equals(vector) {
return Vector3.equals(this, vector);
}
/**
* Check if this vector is collinear with a given vectors
*
* @param {Vector3|Point3} vector1 First vector to check
* @param {Vector3|Point3} vector2 Second vector to check
* @returns {boolean} True if this vector is collinear with the given vectors, false otherwise
*/
collinear(vector1, vector2) {
return Vector3.collinear(this, vector1, vector2);
}
/**
* Calculate the dot product of a given vector with this vector
*
* @param {Vector3|Point3} vector Vector to compute the dot product with
* @returns {number} Computed dot product
*/
dot(vector) {
return Vector3.dot(this, vector);
}
/**
* Calculate the cross product of a given vector with this vector
*
* @param {Vector3|Point3} vector Vector to compute the cross product with
* @returns {Point3} Computed cross product
*/
cross(vector) {
return Vector3.cross(this, vector);
}
/**
* Calculate the angle between a given vector and this vector
*
* @param {Vector3|Point3} vector Vector to compute the angle with
* @returns {number} Computed angle (in radians)
*/
angleTo(vector) {
const denominator = Math.sqrt(this.squaredLength() * Vector3.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 {Vector3|Point3} vector Vector to compute the distance to
* @returns {number} Computed Euclidean distance
*/
distanceTo(vector) {
return Vector3.distance(this, vector);
}
/**
* Calculate the squared distance from a given vector to this vector
*
* @param {Vector3|Point3} vector Vector to compute the squared distance to
* @returns {number} Computed squared distance
*/
squaredDistanceTo(vector) {
return Vector3.squaredDistance(this, vector);
}
/**
* Calculate the Manhattan distance from a given vector to this vector
*
* @param {Vector3|Point3} vector Vector to compute the Manhattan distance to
* @returns {number} Computed Manhattan distance
*/
manhattanDistanceTo(vector) {
return Vector3.manhattanDistance(this, vector);
}
/**
* Return this vector values into an array
*
* @returns {Point3}
*/
toArray() {
return [this.x, this.y, this.z];
}
/**
* Set this vector values from a given array
*
* @param {number[]} values Values to set
* @returns
*/
fromArray([x, y, z]) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* Set this vector values from given spherical coordinates
*
* @param {number} radius Radius of the sphere
* @param {number} phi Polar angle from the y (up) axis : [0, PI]
* @param {number} theta Equator angle around the y (up) axis : [0, 2*PI]
* @returns {this}
*/
fromSphericalCoords(radius, phi, theta) {
const [x, y, z] = Vector3.fromSphericalCoords(radius, phi, theta);
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* Set this vector values from given cylindrical coordinates
*
* @param {number} radius Radius of the cylinder
* @param {number} theta Equator angle around the y (up) axis : [0, 2*PI]
* @param {number} y Y-axis value
* @returns {this}
*/
fromCylindricalCoords(radius, theta, y) {
const [x, _, z] = Vector3.fromCylindricalCoords(radius, theta, y);
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* Copy the values of a given vector to this vector
*
* @param {Vector3|Point3} vector Vector to copy values from
* @returns {this}
*/
copy([x, y, z]) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
/**
* Create a new 3D vector with copied values from this vector
*
* @returns {Vector3}
*/
clone() {
return new Vector3(this.x, this.y, this.z);
}
/**
* Add two vectors
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @returns {Point3}
*/
static add([x1, y1, z1], [x2, y2, z2]) {
const x = x1 + x2;
const y = y1 + y2;
const z = z1 + z2;
return [x, y, z];
}
/**
* Subtract two vectors
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @returns {Point3}
*/
static sub([x1, y1, z1], [x2, y2, z2]) {
const x = x1 - x2;
const y = y1 - y2;
const z = z1 - z2;
return [x, y, z];
}
/**
* Multiply two vectors
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @returns {Point3}
*/
static multiply([x1, y1, z1], [x2, y2, z2]) {
const x = x1 * x2;
const y = y1 * y2;
const z = z1 * z2;
return [x, y, z];
}
/**
* Divide two vectors
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @returns {Point3}
*/
static divide([x1, y1, z1], [x2, y2, z2]) {
const x = x1 / x2;
const y = y1 / y2;
const z = z1 / z2;
return [x, y, z];
}
/**
* Linearly interpolate a point between two vectors
*
* @param {number} t Normalized time value to interpolate
* @param {Vector3|Point3} min Minimum boundaries
* @param {Vector3|Point3} max Maximum boundaries
* @returns {Point3}
*/
static lerp(t, [x1, y1, z1], [x2, y2, z2]) {
const x = lerp(t, x1, x2);
const y = lerp(t, y1, y2);
const z = lerp(t, z1, z2);
return [x, y, z];
}
/**
* Check if two vectors are equal to each other
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @returns {boolean} True if the given vectors are equal, false otherwise
*/
static equals([x1, y1, z1], [x2, y2, z2]) {
return x1 === x2 && y1 === y2 && z1 === z2;
}
/**
* Check if three vectors are collinear (aligned on the same line)
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @param {Vector3|Point3} vector3 Third vector
* @returns {boolean} True if the given vectors are collinear, false otherwise
*/
static collinear([x1, y1, z1], [x2, y2, z2], [x3, y3, z3]) {
const v1 = [x2 - x1, y2 - y1, z2 - z1];
const v2 = [x3 - x1, y3 - y1, z3 - z1];
// prettier-ignore
const cross = [
v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0]
];
return Math.hypot(...cross) <= EPSILON;
}
/**
* Calculate the dot product of two vectors
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @returns {number} Computed dot product
*/
static dot([x1, y1, z1], [x2, y2, z2]) {
return x1 * x2 + y1 * y2 + z1 * z2;
}
/**
* Calculate the cross product of two vectors
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @returns {Point3} Computed cross product
*/
static cross([x1, y1, z1], [x2, y2, z2]) {
const x = y1 * z2 - z1 * y2;
const y = z1 * x2 - x1 * z2;
const z = x1 * y2 - y1 * x2;
return [x, y, z];
}
/**
* Calculate the Euclidean distance between two vectors
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} 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 {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @returns {number} Computed squared distance
*/
static squaredDistance([x1, y1, z1], [x2, y2, z2]) {
const dx = x1 - x2;
const dy = y1 - y2;
const dz = z1 - z2;
return dx * dx + dy * dy + dz * dz;
}
/**
* Calculate the Manhattan distance between two vectors
*
* @param {Vector3|Point3} vector1 First vector
* @param {Vector3|Point3} vector2 Second vector
* @return {number} Computed Manhattan distance
*/
static manhattanDistance([x1, y1, z1], [x2, y2, z2]) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2) + Math.abs(z1 - z2);
}
/**
* Calculate the Euclidean length of a vector
*
* @param {Vector3|Point3} vector Vector to compute Euclidean length from
* @returns {number} Computed Euclidean length
*/
static length(vector) {
return Math.sqrt(Vector3.squaredLength(vector));
}
/**
* Calculate the squared length of a vector
*
* @param {Vector3|Point3} vector Vector to compute squared length from
* @returns {number} Computed squared length
*/
static squaredLength([x, y, z]) {
return x * x + y * y + z * z;
}
/**
* Calculate the Manhattan length of a vector
*
* @param {Vector3|Point3} vector Vector to compute Manhattan length from
* @return {number} Computed Manhattan length
*/
static manhattanLength([x, y, z]) {
return Math.abs(x) + Math.abs(y) + Math.abs(z);
}
/**
* Convert spherical coordinates to a 3D point on the surface of a sphere
*
* @param {number} phi Polar angle from the y (up) axis : [0, PI]
* @param {number} theta Equator angle around the y (up) axis : [0, 2*PI]
* @param {number} [radius=1] Radius of the sphere
* @returns {Point3}
*/
static fromSphericalCoords(phi, theta, radius = 1) {
const x = radius * Math.sin(phi) * Math.sin(theta);
const y = radius * Math.cos(phi);
const z = radius * Math.sin(phi) * Math.cos(theta);
return [x, y, z];
}
/**
* Convert cylindrical coordinates to a 3D point on the surface of a cylinder
*
* @param {number} theta Equator angle around the y (up) axis : [0, 2*PI]
* @param {number} y Y-axis value
* @param {number} [radius=1] Radius of the cylinder
* @returns {Point3}
*/
static fromCylindricalCoords(theta, y, radius = 1) {
const x = radius * Math.sin(theta);
const z = radius * Math.cos(theta);
return [x, y, z];
}
}