@fimbul-works/vec
Version:
A comprehensive TypeScript vector math library providing 2D, 3D, and 4D vector operations with a focus on performance and type safety.
1,017 lines (1,016 loc) • 35.1 kB
JavaScript
const { cos, acos, atan2, sqrt, abs } = Math;
/**
* Represents a 4D vector with various operations.
*/
export class Vec4 {
#magnitude = 0;
#prevXYZW = new Float64Array([0, 0, 0, 0]);
#xyzw = new Float64Array(4);
/**
* Computes and updates the magnitude of the vector.
* @private
* @param x - The x-component of the vector.
* @param y - The y-component of the vector.
* @param z - The z-component of the vector.
* @param w - The w-component of the vector.
*/
#computeMagnitude(x = this.#xyzw[0], y = this.#xyzw[1], z = this.#xyzw[2], w = this.#xyzw[3]) {
if (x !== this.#prevXYZW[0] || y !== this.#prevXYZW[1] || z !== this.#prevXYZW[2] || w !== this.#prevXYZW[3]) {
this.#magnitude = sqrt(x ** 2 + y ** 2 + z ** 2 + w ** 2);
this.#prevXYZW[0] = x;
this.#prevXYZW[1] = y;
this.#prevXYZW[2] = z;
this.#prevXYZW[3] = w;
}
}
/**
* Creates a new Vec4 instance.
* @param x - The x-coordinate of the vector.
* @param y - The y-coordinate of the vector.
* @param z - The z-coordinate of the vector.
* @param w - The w-coordinate of the vector.
*/
constructor(x = 0, y = 0, z = 0, w = 0) {
this.#xyzw[0] = x;
this.#xyzw[1] = y;
this.#xyzw[2] = z;
this.#xyzw[3] = w;
this.#computeMagnitude(x, y, z, w);
}
/**
* Adds two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @returns A new Vec3 instance representing the sum.
*/
static add(v, w) {
return new Vec4(v.#xyzw[0] + w.#xyzw[0], v.#xyzw[1] + w.#xyzw[1], v.#xyzw[2] + w.#xyzw[2], v.#xyzw[3] + w.#xyzw[3]);
}
/**
* Subtracts one vector from another.
* @param v - The vector to subtract from.
* @param w - The vector to subtract.
* @returns A new Vec2 instance representing the difference.
*/
static subtract(v, w) {
return new Vec4(v.#xyzw[0] - w.#xyzw[0], v.#xyzw[1] - w.#xyzw[1], v.#xyzw[2] - w.#xyzw[2], v.#xyzw[3] - w.#xyzw[3]);
}
/**
* Multiplies one vector with another.
* @param v - The first vector.
* @param w - The second vector.
* @returns A new Vec4 instance representing the multiplied value.
*/
static multiply(v, w) {
return new Vec4(v.x * w.x, v.y * w.y, v.z * w.z, v.w * w.w);
}
/**
* Divides one vector with another.
* @param v - Divident.
* @param w - Divisor.
* @returns A new Vec4 instance representing the divided value.
*/
static divide(v, w) {
return new Vec4(w.x === 0 ? Number.POSITIVE_INFINITY : v.x / w.x, w.y === 0 ? Number.POSITIVE_INFINITY : v.y / w.y, v.z === 0 ? Number.POSITIVE_INFINITY : v.z / w.z, v.w === 0 ? Number.POSITIVE_INFINITY : v.w / w.w);
}
/**
* Calculates the angle between two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @returns The angle between the vectors in radians.
*/
static angleBetween(v, w) {
return acos((v.#xyzw[0] * w.#xyzw[0] + v.#xyzw[1] * w.#xyzw[1] + v.#xyzw[2] * w.#xyzw[2] + v.#xyzw[3] * w.#xyzw[3]) /
(v.#magnitude * w.#magnitude));
}
/**
* Calculates the Euclidean distance between two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @returns The distance between the vectors.
*/
static distance(v, w) {
return sqrt((v.#xyzw[0] - w.#xyzw[0]) ** 2 +
(v.#xyzw[1] - w.#xyzw[1]) ** 2 +
(v.#xyzw[2] - w.#xyzw[2]) ** 2 +
(v.#xyzw[3] - w.#xyzw[3]) ** 2);
}
/**
* Calculates the Chebyshev distance between two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @returns The Chebyshev distance between the vectors.
*/
static distanceChebyshev(v, w) {
const absX = abs(v.#xyzw[0] - w.#xyzw[0]);
const absY = abs(v.#xyzw[1] - w.#xyzw[1]);
const absZ = abs(v.#xyzw[2] - w.#xyzw[2]);
const absW = abs(v.#xyzw[3] - w.#xyzw[3]);
return absX >= absY && absX >= absZ && absX >= absW
? absX
: absY >= absZ && absY >= absW
? absY
: absZ >= absW
? absZ
: absW;
}
/**
* Calculates the Manhattan distance between two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @returns The Manhattan distance between the vectors.
*/
static distanceManhattan(v, w) {
return (abs(v.#xyzw[0] - w.#xyzw[0]) +
abs(v.#xyzw[1] - w.#xyzw[1]) +
abs(v.#xyzw[2] - w.#xyzw[2]) +
abs(v.#xyzw[3] - w.#xyzw[3]));
}
/**
* Calculates the Minkowski distance between two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @param p - The order of the Minkowski distance.
* @returns The Minkowski distance between the vectors.
*/
static distanceMinkowski(v, w, p) {
return ((abs(v.#xyzw[0] - w.#xyzw[0]) ** p +
abs(v.#xyzw[1] - w.#xyzw[1]) ** p +
abs(v.#xyzw[2] - w.#xyzw[2]) ** p +
abs(v.#xyzw[3] - w.#xyzw[3]) ** p) **
(1 / p));
}
/**
* Calculates the squared Euclidean distance between two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @returns The squared distance between the vectors.
*/
static distanceSq(v, w) {
return ((v.#xyzw[0] - w.#xyzw[0]) ** 2 +
(v.#xyzw[1] - w.#xyzw[1]) ** 2 +
(v.#xyzw[2] - w.#xyzw[2]) ** 2 +
(v.#xyzw[3] - w.#xyzw[3]) ** 2);
}
/**
* Calculates the dot product of two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @returns The dot product of the two vectors.
*/
static dot(v, w) {
return v.#xyzw[0] * w.#xyzw[0] + v.#xyzw[1] * w.#xyzw[1] + v.#xyzw[2] * w.#xyzw[2] + v.#xyzw[3] * w.#xyzw[3];
}
/**
* Reflects a vector across a normal vector.
* The normal vector should be normalized (unit length).
* @param v - The vector to reflect.
* @param normal - The normal vector to reflect across (must be normalized).
* @returns A new Vec4 instance representing the reflected vector.
*/
static reflect(v, normal) {
// R = V - 2(V·N)N
const dot = v.#xyzw[0] * normal.#xyzw[0] +
v.#xyzw[1] * normal.#xyzw[1] +
v.#xyzw[2] * normal.#xyzw[2] +
v.#xyzw[3] * normal.#xyzw[3];
return new Vec4(v.#xyzw[0] - 2 * dot * normal.#xyzw[0], v.#xyzw[1] - 2 * dot * normal.#xyzw[1], v.#xyzw[2] - 2 * dot * normal.#xyzw[2], v.#xyzw[3] - 2 * dot * normal.#xyzw[3]);
}
/**
* Creates an immutable Vec4-like object.
* @param x - The x-coordinate of the vector.
* @param y - The y-coordinate of the vector.
* @returns An immutable object with Vec4-like properties.
*/
static immutable(x = 0, y = 0, z = 0, w = 0) {
const data = new Float64Array(10);
data[0] = x;
data[1] = y;
data[2] = z;
data[3] = w;
const xyzSqSum = x ** 2 + y ** 2 + z ** 2;
data[4] = atan2(sqrt(y ** 2 + z ** 2 + w ** 2), x);
data[5] = atan2(sqrt(z ** 2 + w ** 2 + x ** 2), y);
data[6] = atan2(sqrt(w ** 2 + x ** 2 + y ** 2), z);
data[7] = atan2(sqrt(xyzSqSum), w);
const magnitudeSq = xyzSqSum + w ** 2;
data[8] = sqrt(magnitudeSq);
data[9] = magnitudeSq;
const isInfinite = x === +Number.POSITIVE_INFINITY ||
x === Number.NEGATIVE_INFINITY ||
y === +Number.POSITIVE_INFINITY ||
y === Number.NEGATIVE_INFINITY ||
z === +Number.POSITIVE_INFINITY ||
z === Number.NEGATIVE_INFINITY ||
w === +Number.POSITIVE_INFINITY ||
w === Number.NEGATIVE_INFINITY;
const _isNaN = Number.isNaN(x) && Number.isNaN(y) && Number.isNaN(z) && Number.isNaN(w);
const isZero = x === 0 && y === 0 && z === 0 && w === 0;
const xyzw = Object.freeze([...data.slice(0, 4)]);
return {
get x() {
return data[0];
},
get y() {
return data[1];
},
get z() {
return data[2];
},
get w() {
return data[3];
},
get xyzw() {
return xyzw;
},
get magnitude() {
return data[8];
},
get magnitudeSq() {
return data[10];
},
get angleW() {
return data[7];
},
get angleX() {
return data[4];
},
get angleY() {
return data[5];
},
get angleZ() {
return data[6];
},
get isInfinite() {
return isInfinite;
},
get isNaN() {
return _isNaN;
},
get isZero() {
return isZero;
},
};
}
/**
* Checks if a vector has infinite components.
* @param v - The vector to check.
* @returns True if the vector has infinite components, false otherwise.
*/
static isInfinite(v) {
const x = v.#xyzw[0];
const y = v.#xyzw[1];
const z = v.#xyzw[2];
const w = v.#xyzw[3];
return (x === +Number.POSITIVE_INFINITY ||
x === Number.NEGATIVE_INFINITY ||
y === +Number.POSITIVE_INFINITY ||
y === Number.NEGATIVE_INFINITY ||
z === +Number.POSITIVE_INFINITY ||
z === Number.NEGATIVE_INFINITY ||
w === +Number.POSITIVE_INFINITY ||
w === Number.NEGATIVE_INFINITY);
}
/**
* Checks if a vector has NaN components.
* @param v - The vector to check.
* @returns True if the vector has NaN components, false otherwise.
*/
static isNaN(v) {
return Number.isNaN(v.#xyzw[0]) || Number.isNaN(v.#xyzw[1]) || Number.isNaN(v.#xyzw[2]) || Number.isNaN(v.#xyzw[3]);
}
/**
* Checks if a vector is zero.
* @param v - The vector to check.
* @returns True if the vector is zero, false otherwise.
*/
static isZero(v) {
return v.#xyzw[0] === 0 && v.#xyzw[1] === 0 && v.#xyzw[2] === 0 && v.#xyzw[3] === 0;
}
/**
* Performs linear interpolation between two vectors.
* @param v - The first vector.
* @param w - The second vector.
* @param t - The interpolation parameter (0 to 1).
* @returns A new Vec2 instance representing the interpolated vector.
*/
static lerp(v, w, t) {
if (t > 1)
t = 1;
else if (t < 0)
t = 0;
const vX = v.#xyzw[0];
const vY = v.#xyzw[1];
const vZ = v.#xyzw[2];
const vW = v.#xyzw[3];
return new Vec4(vX + (w.#xyzw[0] - vX) * t, vY + (w.#xyzw[1] - vY) * t, vZ + (w.#xyzw[2] - vZ) * t, vW + (w.#xyzw[3] - vW) * t);
}
/**
* Negates a vector.
* @param v - The vector to negate.
* @returns A new Vec2 instance representing the negated vector.
*/
static negate(v) {
return new Vec4(-v.#xyzw[0], -v.#xyzw[1], -v.#xyzw[2], -v.#xyzw[3]);
}
/**
* Normalizes a vector.
* @param v - The vector to normalize.
* @returns A new Vec2 instance representing the normalized vector.
*/
static normalize(v) {
const m = v.#magnitude;
return new Vec4(v.#xyzw[0] / m, v.#xyzw[1] / m, v.#xyzw[2] / m, v.#xyzw[3] / m);
}
/**
* Projects one vector onto another.
* @param v - The vector to project.
* @param w - The vector to project onto.
* @returns A new Vec2 instance representing the projected vector.
*/
static project(v, w) {
const vM = v.#magnitude;
const wM = w.#magnitude;
const wX = w.#xyzw[0];
const wY = w.#xyzw[1];
const wZ = w.#xyzw[2];
const wW = w.#xyzw[3];
const f = vM * cos(acos((v.#xyzw[0] * wX + v.#xyzw[1] * wY + v.#xyzw[2] * wZ + v.#xyzw[3] * wW) / (vM * wM)));
return new Vec4((wX / wM) * f, (wY / wM) * f, (wZ / wM) * f, (wW / wM) * f);
}
/**
* Creates a random unit vector.
* @param random - A function that returns a random number between 0 and 1.
* @returns A new Vec4 instance representing a random unit vector.
*/
static random(random = Math.random) {
let x1;
let x2;
let x3;
let x4;
do {
x1 = random() * 2 - 1;
x2 = random() * 2 - 1;
x3 = random() * 2 - 1;
x4 = random() * 2 - 1;
} while (x1 ** 2 + x2 ** 2 >= 1 || x3 ** 2 + x4 ** 2 >= 1);
const f = sqrt((1 - x1 ** 2 - x2 ** 2) / (x3 ** 2 + x4 ** 2));
return new Vec4(x1, x2, x3 * f, x4 * f);
}
/**
* Checks if two vectors are equal.
* @param v - The first vector.
* @param w - The second vector.
* @returns True if the vectors are equal, false otherwise.
*/
static satisfyEquality(v, w) {
return (v.#xyzw[0] === w.#xyzw[0] && v.#xyzw[1] === w.#xyzw[1] && v.#xyzw[2] === w.#xyzw[2] && v.#xyzw[3] === w.#xyzw[3]);
}
/**
* Checks if two vectors are opposite.
* @param v - The first vector.
* @param w - The second vector.
* @returns True if the vectors are opposite, false otherwise.
*/
static satisfyOpposition(v, w) {
return (v.#xyzw[0] === -w.#xyzw[0] &&
v.#xyzw[1] === -w.#xyzw[1] &&
v.#xyzw[2] === -w.#xyzw[2] &&
v.#xyzw[3] === -w.#xyzw[3]);
}
/**
* Compares a vector with another vector using an epsilon value for floating-point comparison.
* @param v - The first vector.
* @param w - The second vector.
* @param epsilon - The maximum difference between components to consider them equal.
* @returns True if the vectors are equal within epsilon, false otherwise.
*/
static equals(v, w, epsilon = Number.EPSILON) {
return (abs(v.#xyzw[0] - w.#xyzw[0]) <= epsilon &&
abs(v.#xyzw[1] - w.#xyzw[1]) <= epsilon &&
abs(v.#xyzw[2] - w.#xyzw[2]) <= epsilon &&
abs(v.#xyzw[3] - w.#xyzw[3]) <= epsilon);
}
/**
* Scales a vector by a scalar value.
* @param v - The vector to scale.
* @param c - The scalar value.
* @returns A new Vec2 instance representing the scaled vector.
*/
static scale(v, c) {
return new Vec4(v.#xyzw[0] * c, v.#xyzw[1] * c, v.#xyzw[2] * c, v.#xyzw[3] * c);
}
/**
* Creates a zero vector.
* @returns A new Vec4 instance representing a zero vector.
*/
static zero() {
return new Vec4();
}
/**
* Creates a vector with all components set to 1.0.
* @returns A new Vec4 instance representing a vector with all components set to 1.0.
*/
static one() {
return new Vec4(1, 1, 1, 1);
}
/**
* Creates a Vec4 from an array.
* @returns A new Vec4 instance.
*/
static fromArray(arr) {
return new Vec4(arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0);
}
/**
* Creates a Vec4 from an object with x, y, z and w properties.
* @returns A new Vec4 instance.
*/
static fromObject(obj) {
return new Vec4(obj.x, obj.y, obj.z, obj.w);
}
/**
* Creates a Vec3 instance from a JSON-parsed object.
* @param json - The JSON-parsed object containing x and y properties.
* @returns A new Vec4 instance.
*/
static fromJSON(json) {
return Vec4.fromObject(json);
}
/**
* Gets the x-component of the vector.
* @returns The x-component.
*/
get x() {
return this.#xyzw[0];
}
/**
* Sets the x-component of the vector.
* @param x - The new x-component.
*/
set x(x) {
this.#xyzw[0] = x;
this.#computeMagnitude();
}
/**
* Gets the y-component of the vector.
* @returns The y-component.
*/
get y() {
return this.#xyzw[1];
}
/**
* Sets the z-component of the vector.
* @param y - The new z-component.
*/
set y(y) {
this.#xyzw[1] = y;
this.#computeMagnitude();
}
/**
* Gets the z-component of the vector.
* @returns The z-component.
*/
get z() {
return this.#xyzw[2];
}
/**
* Sets the z-component of the vector.
* @param z - The new z-component.
*/
set z(z) {
this.#xyzw[2] = z;
this.#computeMagnitude();
}
/**
* Gets the w-component of the vector.
* @returns The w-component.
*/
get w() {
return this.#xyzw[3];
}
/**
* Sets the w-component of the vector.
* @param w - The new w-component.
*/
set w(w) {
this.#xyzw[3] = w;
this.#computeMagnitude();
}
/**
* Gets a copy of the vector's components as an array.
* @returns An array containing the x, y, z and w components of the vector.
*/
get xyzw() {
return Array.from(this.#xyzw.slice());
}
/**
* Sets both components of the vector at once.
* @param xyzw - An array containing the new x, y and z components.
*/
set xyzw(xyzw) {
this.#xyzw[0] = xyzw[0];
this.#xyzw[1] = xyzw[1];
this.#xyzw[2] = xyzw[2];
this.#xyzw[3] = xyzw[3];
this.#computeMagnitude();
}
/**
* Gets the angle between the vector and the positive x-axis in radians.
* @returns The angle in radians, always in the range [0, 2π).
*/
get angleX() {
return atan2(sqrt(this.#xyzw[1] ** 2 + this.#xyzw[2] ** 2 + this.#xyzw[3] ** 2), this.#xyzw[0]);
}
/**
* Gets the angle between the vector and the positive y-axis in radians.
* @returns The angle in radians, always in the range [0, 2π).
*/
get angleY() {
return atan2(sqrt(this.#xyzw[2] ** 2 + this.#xyzw[3] ** 2 + this.#xyzw[0] ** 2), this.#xyzw[1]);
}
/**
* Gets the angle between the vector and the positive z-axis in radians.
* @returns The angle in radians, always in the range [0, 2π).
*/
get angleZ() {
return atan2(sqrt(this.#xyzw[3] ** 2 + this.#xyzw[0] ** 2 + this.#xyzw[1] ** 2), this.#xyzw[2]);
}
/**
* Gets the angle between the vector and the positive w-axis in radians.
* @returns The angle in radians, always in the range [0, 2π).
*/
get angleW() {
return atan2(sqrt(this.#xyzw[0] ** 2 + this.#xyzw[1] ** 2 + this.#xyzw[2] ** 2), this.#xyzw[3]);
}
/**
* Sets the magnitude (length) of the vector, maintaining its direction.
* @param m - The new magnitude.
*/
get magnitude() {
return this.#magnitude;
}
/**
* Gets the squared magnitude of the vector.
* This is faster to compute than the actual magnitude and is useful for comparisons.
* @returns The squared magnitude of the vector.
*/
get magnitudeSq() {
return this.#magnitude ** 2;
}
/**
* Sets the magnitude (length) of the vector, maintaining its direction.
* @param m - The new magnitude.
*/
set magnitude(m) {
const M = this.#magnitude;
this.#xyzw[0] = (this.#xyzw[0] / M) * m;
this.#xyzw[1] = (this.#xyzw[1] / M) * m;
this.#xyzw[2] = (this.#xyzw[2] / M) * m;
this.#xyzw[3] = (this.#xyzw[3] / M) * m;
this.#computeMagnitude();
}
/**
* Adds another vector to this vector.
* @param v - The vector to add.
* @returns This Vec4 instance for method chaining.
*/
add(v) {
this.#xyzw[0] += v.#xyzw[0];
this.#xyzw[1] += v.#xyzw[1];
this.#xyzw[2] += v.#xyzw[2];
this.#xyzw[3] += v.#xyzw[3];
this.#computeMagnitude();
return this;
}
/**
* Subtracts another vector from this vector.
* @param v - The vector to subtract.
* @returns This Vec4 instance for method chaining.
*/
subtract(v) {
this.#xyzw[0] -= v.#xyzw[0];
this.#xyzw[1] -= v.#xyzw[1];
this.#xyzw[2] -= v.#xyzw[2];
this.#xyzw[3] -= v.#xyzw[3];
this.#computeMagnitude();
return this;
}
/**
* Multiplies this vector with another vector.
* @param v - The vector to multiply with.
* @returns This Vec4 instance for method chaining.
*/
multiply(v) {
this.#xyzw[0] *= v.#xyzw[0];
this.#xyzw[1] *= v.#xyzw[1];
this.#xyzw[2] *= v.#xyzw[2];
this.#computeMagnitude();
return this;
}
/**
* Divides this vector with another vector.
* @param v - The vector to divide with.
* @returns This Vec4 instance for method chaining.
*/
divide(v) {
this.#xyzw[0] = v.#xyzw[0] === 0 ? Number.POSITIVE_INFINITY : this.#xyzw[0] / v.#xyzw[0];
this.#xyzw[1] = v.#xyzw[1] === 0 ? Number.POSITIVE_INFINITY : this.#xyzw[1] / v.#xyzw[1];
this.#xyzw[2] = v.#xyzw[2] === 0 ? Number.POSITIVE_INFINITY : this.#xyzw[2] / v.#xyzw[2];
this.#xyzw[3] = v.#xyzw[3] === 0 ? Number.POSITIVE_INFINITY : this.#xyzw[3] / v.#xyzw[3];
this.#computeMagnitude();
return this;
}
/**
* Calculates the angle between this vector and another vector.
* @param v - The other vector.
* @returns The angle between the vectors in radians.
*/
angleBetween(v) {
return acos((this.#xyzw[0] * v.#xyzw[0] +
this.#xyzw[1] * v.#xyzw[1] +
this.#xyzw[2] * v.#xyzw[2] +
this.#xyzw[3] * v.#xyzw[3]) /
(this.#magnitude * v.#magnitude));
}
/**
* Clamps the magnitude of this vector between a minimum and maximum value.
* @param min - The minimum magnitude.
* @param max - The maximum magnitude.
* @returns This Vec4 instance for method chaining.
*/
clamp(min, max) {
const m = this.#magnitude;
if (m > max) {
this.#xyzw[0] = (this.#xyzw[0] / m) * max;
this.#xyzw[1] = (this.#xyzw[1] / m) * max;
this.#xyzw[2] = (this.#xyzw[2] / m) * max;
this.#xyzw[3] = (this.#xyzw[3] / m) * max;
this.#computeMagnitude();
}
else if (m < min) {
this.#xyzw[0] = (this.#xyzw[0] / m) * min;
this.#xyzw[1] = (this.#xyzw[1] / m) * min;
this.#xyzw[2] = (this.#xyzw[2] / m) * min;
this.#xyzw[3] = (this.#xyzw[3] / m) * min;
this.#computeMagnitude();
}
return this;
}
/**
* Creates a copy of this vector.
* @returns A new Vec4 instance with the same components.
*/
clone() {
return new Vec4(this.#xyzw[0], this.#xyzw[1], this.#xyzw[2], this.#xyzw[3]);
}
/**
* Copies the components of another vector to this vector.
* @param v - The vector to copy from.
* @returns This Vec2 instance for method chaining.
*/
copy(v) {
this.#xyzw[0] = v.#xyzw[0];
this.#xyzw[1] = v.#xyzw[1];
this.#xyzw[2] = v.#xyzw[2];
this.#xyzw[3] = v.#xyzw[3];
this.#computeMagnitude();
return this;
}
/**
* Calculates the distance between this vector and another vector.
* @param v - The other vector.
* @returns The distance between the vectors.
*/
distance(v) {
return sqrt((this.#xyzw[0] - v.#xyzw[0]) ** 2 +
(this.#xyzw[1] - v.#xyzw[1]) ** 2 +
(this.#xyzw[2] - v.#xyzw[2]) ** 2 +
(this.#xyzw[3] - v.#xyzw[3]) ** 2);
}
/**
* Calculates the Chebyshev distance between this vector and another vector.
* @param v - The first vector.
* @returns The Chebyshev distance between the vectors.
*/
distanceChebyshev(v) {
const absX = abs(this.#xyzw[0] - v.#xyzw[0]);
const absY = abs(this.#xyzw[1] - v.#xyzw[1]);
const absZ = abs(this.#xyzw[2] - v.#xyzw[2]);
const absW = abs(this.#xyzw[3] - v.#xyzw[3]);
return absX >= absY && absX >= absZ && absX >= absW
? absX
: absY >= absZ && absY >= absW
? absY
: absZ >= absW
? absZ
: absW;
}
/**
* Calculates the Manhattan distance between this vector and another vector.
* @param v - The other vector.
* @returns The Manhattan distance between the vectors.
*/
distanceManhattan(v) {
return (abs(this.#xyzw[0] - v.#xyzw[0]) +
abs(this.#xyzw[1] - v.#xyzw[1]) +
abs(this.#xyzw[2] - v.#xyzw[2]) +
abs(this.#xyzw[3] - v.#xyzw[3]));
}
/**
* Calculates the Minkowski distance between thisvector and another vector.
* @param v - The other vector.
* @param p - The order of the Minkowski distance.
* @returns The Minkowski distance between the vectors.
*/
distanceMinkowski(v, p) {
return ((abs(this.#xyzw[0] - v.#xyzw[0]) ** p +
abs(this.#xyzw[1] - v.#xyzw[1]) ** p +
abs(this.#xyzw[2] - v.#xyzw[2]) ** p +
abs(this.#xyzw[3] - v.#xyzw[3]) ** p) **
(1 / p));
}
/**
* Calculates the squared distance between this vector and another vector.
* @param v - The other vector.
* @returns The squared distance between the vectors.
*/
distanceSq(v) {
return ((this.#xyzw[0] - v.#xyzw[0]) ** 2 +
(this.#xyzw[1] - v.#xyzw[1]) ** 2 +
(this.#xyzw[2] - v.#xyzw[2]) ** 2 +
(this.#xyzw[3] - v.#xyzw[3]) ** 2);
}
/**
* Calculates the dot product of this vector with another vector.
* @param v - The other vector.
* @returns The dot product of the vectors.
*/
dot(v) {
return (this.#xyzw[0] * v.#xyzw[0] + this.#xyzw[1] * v.#xyzw[1] + this.#xyzw[2] * v.#xyzw[2] + this.#xyzw[3] * v.#xyzw[3]);
}
/**
* Reflects this vector across a normal vector.
* The normal vector should be normalized (unit length).
* @param normal - The normal vector to reflect across (must be normalized).
* @returns This Vec4 instance for method chaining.
*/
reflect(normal) {
const dot = this.#xyzw[0] * normal.#xyzw[0] +
this.#xyzw[1] * normal.#xyzw[1] +
this.#xyzw[2] * normal.#xyzw[2] +
this.#xyzw[3] * normal.#xyzw[3];
this.#xyzw[0] -= 2 * dot * normal.#xyzw[0];
this.#xyzw[1] -= 2 * dot * normal.#xyzw[1];
this.#xyzw[2] -= 2 * dot * normal.#xyzw[2];
this.#xyzw[3] -= 2 * dot * normal.#xyzw[3];
this.#computeMagnitude();
return this;
}
/**
* Checks if this vector has infinite components.
* @returns True if the vector has infinite components, false otherwise.
*/
isInfinite() {
const x = this.#xyzw[0];
const y = this.#xyzw[1];
const z = this.#xyzw[2];
const w = this.#xyzw[3];
return (x === +Number.POSITIVE_INFINITY ||
x === Number.NEGATIVE_INFINITY ||
y === +Number.POSITIVE_INFINITY ||
y === Number.NEGATIVE_INFINITY ||
z === +Number.POSITIVE_INFINITY ||
z === Number.NEGATIVE_INFINITY ||
w === +Number.POSITIVE_INFINITY ||
w === Number.NEGATIVE_INFINITY);
}
/**
* Checks if this vector has NaN components.
* @returns True if the vector has NaN components, false otherwise.
*/
isNaN() {
return (Number.isNaN(this.#xyzw[0]) ||
Number.isNaN(this.#xyzw[1]) ||
Number.isNaN(this.#xyzw[2]) ||
Number.isNaN(this.#xyzw[3]));
}
/**
* Checks if this vector is zero.
* @returns True if the vector is zero, false otherwise.
*/
isZero() {
return this.#xyzw[0] === 0 && this.#xyzw[1] === 0 && this.#xyzw[2] === 0 && this.#xyzw[3] === 0;
}
/**
* Limits the maximum magnitude of this vector.
* @param max - The maximum magnitude.
* @returns This Vec2 instance for method chaining.
*/
limitMax(max) {
const m = this.#magnitude;
if (m > max) {
this.#xyzw[0] = (this.#xyzw[0] / m) * max;
this.#xyzw[1] = (this.#xyzw[1] / m) * max;
this.#xyzw[2] = (this.#xyzw[2] / m) * max;
this.#xyzw[3] = (this.#xyzw[3] / m) * max;
this.#computeMagnitude();
}
return this;
}
/**
* Limits the minimum magnitude of this vector.
* @param min - The minimum magnitude.
* @returns This Vec4 instance for method chaining.
*/
limitMin(min) {
const m = this.#magnitude;
if (m < min) {
this.#xyzw[0] = (this.#xyzw[0] / m) * min;
this.#xyzw[1] = (this.#xyzw[1] / m) * min;
this.#xyzw[2] = (this.#xyzw[2] / m) * min;
this.#xyzw[3] = (this.#xyzw[3] / m) * min;
this.#computeMagnitude();
}
return this;
}
/**
* Sets this vector to point towards another vector.
* @param v - The vector to look at.
* @returns This Vec4 instance for method chaining.
*/
lookAt(v) {
const m = this.#magnitude;
const vM = v.#magnitude;
this.#xyzw[0] = (v.#xyzw[0] / vM) * m;
this.#xyzw[1] = (v.#xyzw[1] / vM) * m;
this.#xyzw[2] = (v.#xyzw[2] / vM) * m;
this.#xyzw[3] = (v.#xyzw[3] / vM) * m;
this.#computeMagnitude();
return this;
}
/**
* Negates this vector.
* @returns This Vec4 instance for method chaining.
*/
negate() {
this.#xyzw[0] *= -1;
this.#xyzw[1] *= -1;
this.#xyzw[2] *= -1;
this.#xyzw[3] *= -1;
this.#computeMagnitude();
return this;
}
/**
* Normalizes this vector.
* @returns This Ve4 instance for method chaining.
*/
normalize() {
const m = this.#magnitude;
this.#xyzw[0] /= m;
this.#xyzw[1] /= m;
this.#xyzw[2] /= m;
this.#xyzw[3] /= m;
this.#computeMagnitude();
return this;
}
/**
* Projects this vector onto another vector.
* @param v - The vector to project onto.
* @returns This Vec4 instance for method chaining.
*/
project(v) {
const m = this.#magnitude;
const vM = v.#magnitude;
const vX = v.#xyzw[0];
const vY = v.#xyzw[1];
const vZ = v.#xyzw[2];
const vW = v.#xyzw[3];
const f = m * cos(acos((this.#xyzw[0] * vX + this.#xyzw[1] * vY + this.#xyzw[2] * vZ + this.#xyzw[3] * vW) / (m * vM)));
this.#xyzw[0] = (vX / vM) * f;
this.#xyzw[1] = (vY / vM) * f;
this.#xyzw[2] = (vZ / vM) * f;
this.#xyzw[3] = (vW / vM) * f;
this.#computeMagnitude();
return this;
}
/**
* Sets this vector to a random direction with the same magnitude.
* @param random - A function that returns a random number between 0 and 1.
* @returns This Vec4 instance for method chaining.
*/
random(random = Math.random) {
let x1;
let x2;
let x3;
let x4;
do {
x1 = random() * 2 - 1;
x2 = random() * 2 - 1;
x3 = random() * 2 - 1;
x4 = random() * 2 - 1;
} while (x1 ** 2 + x2 ** 2 >= 1 || x3 ** 2 + x4 ** 2 >= 1);
const f = sqrt((1 - x1 ** 2 - x2 ** 2) / (x3 ** 2 + x4 ** 2));
const m = this.#magnitude;
this.#xyzw[0] = m * x1;
this.#xyzw[1] = m * x2;
this.#xyzw[2] = m * x3 * f;
this.#xyzw[3] = m * x4 * f;
this.#computeMagnitude();
return this;
}
/**
* Checks if this vector is equal to another vector.
* @param v - The other vector.
* @returns True if the vectors are equal, false otherwise.
*/
satisfyEquality(v) {
return (this.#xyzw[0] === v.#xyzw[0] &&
this.#xyzw[1] === v.#xyzw[1] &&
this.#xyzw[2] === v.#xyzw[2] &&
this.#xyzw[3] === v.#xyzw[3]);
}
/**
* Checks if this vector is opposite to another vector.
* @param v - The other vector.
* @returns True if the vectors are opposite, false otherwise.
*/
satisfyOpposition(v) {
return (this.#xyzw[0] === -v.#xyzw[0] &&
this.#xyzw[1] === -v.#xyzw[1] &&
this.#xyzw[2] === -v.#xyzw[2] &&
this.#xyzw[3] === -v.#xyzw[3]);
}
/**
* Compares this vector with another vector using an epsilon value for floating-point comparison.
* @param v - The vector to compare with.
* @param epsilon - The maximum difference between components to consider them equal.
* @returns True if the vectors are equal within epsilon, false otherwise.
*/
equals(v, epsilon = Number.EPSILON) {
return (abs(this.#xyzw[0] - v.#xyzw[0]) <= epsilon &&
abs(this.#xyzw[1] - v.#xyzw[1]) <= epsilon &&
abs(this.#xyzw[2] - v.#xyzw[2]) <= epsilon &&
abs(this.#xyzw[3] - v.#xyzw[3]) <= epsilon);
}
/**
* Scales this vector by a scalar value.
* @param c - The scalar value.
* @returns This Vec4 instance for method chaining.
*/
scale(c) {
this.#xyzw[0] *= c;
this.#xyzw[1] *= c;
this.#xyzw[2] *= c;
this.#xyzw[3] *= c;
this.#computeMagnitude();
return this;
}
/**
* Sets this vector to zero.
* @returns This Vec4 instance for method chaining.
*/
zero() {
this.#xyzw[0] = 0;
this.#xyzw[1] = 0;
this.#xyzw[2] = 0;
this.#xyzw[3] = 0;
this.#computeMagnitude();
return this;
}
/**
* Makes the Vec4 instance iterable.
* @yields The x, y, z and w components of the vector.
*/
*[Symbol.iterator]() {
yield this.#xyzw[0];
yield this.#xyzw[1];
yield this.#xyzw[2];
yield this.#xyzw[3];
}
/**
* Returns a string representation of the vector.
* @returns A string in the format "Vec4(x, y, z, w)".
*/
toString() {
return `Vec4(${this.#xyzw[0]}, ${this.#xyzw[1]}, ${this.#xyzw[2]}, ${this.#xyzw[3]})`;
}
/**
* Converts the vector to a plain object.
* @returns An object with x, y, z and w properties.
*/
toObject() {
return {
x: this.#xyzw[0],
y: this.#xyzw[1],
z: this.#xyzw[2],
w: this.#xyzw[3],
};
}
/**
* Serializes the vector to a JSON-friendly format.
* @returns A JSON-friendly object representation of the vector.
*/
toJSON() {
return this.toObject();
}
}