UNPKG

@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
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(); } }