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.

975 lines (974 loc) 33.1 kB
const { sin, cos, acos, atan2, sqrt, abs } = Math; /** * Represents a 3D vector with various operations. */ export class Vec3 { #magnitude = 0; #prevXYZ = new Float64Array([0, 0, 0]); #xyz = new Float64Array(3); /** * 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. */ #computeMagnitude(x = this.#xyz[0], y = this.#xyz[1], z = this.#xyz[2]) { if (x !== this.#prevXYZ[0] || y !== this.#prevXYZ[1] || z !== this.#prevXYZ[2]) { this.#magnitude = sqrt(x ** 2 + y ** 2 + z ** 2); this.#prevXYZ[0] = x; this.#prevXYZ[1] = y; this.#prevXYZ[2] = z; } } /** * Creates a new Vec3 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. */ constructor(x = 0, y = 0, z = 0) { this.#xyz[0] = x; this.#xyz[1] = y; this.#xyz[2] = z; this.#computeMagnitude(x, y, z); } /** * 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 Vec3(v.#xyz[0] + w.#xyz[0], v.#xyz[1] + w.#xyz[1], v.#xyz[2] + w.#xyz[2]); } /** * 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 Vec3(v.#xyz[0] - w.#xyz[0], v.#xyz[1] - w.#xyz[1], v.#xyz[2] - w.#xyz[2]); } /** * Multiplies one vector with another. * @param v - The first vector. * @param w - The second vector. * @returns A new Vec3 instance representing the multiplied value. */ static multiply(v, w) { return new Vec3(v.x * w.x, v.y * w.y, v.z * w.z); } /** * Divides one vector with another. * @param v - Divident. * @param w - Divisor. * @returns A new Vec3 instance representing the divided value. */ static divide(v, w) { return new Vec3(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); } /** * 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.#xyz[0] * w.#xyz[0] + v.#xyz[1] * w.#xyz[1] + v.#xyz[2] * w.#xyz[2]) / (v.#magnitude * w.#magnitude)); } /** * Calculates the cross product of two vectors. * @param v - The first vector. * @param w - The second vector. * @returns The cross product of the two vectors. */ static cross(v, w) { const vX = v.#xyz[0]; const vY = v.#xyz[1]; const vZ = v.#xyz[2]; const wX = w.#xyz[0]; const wY = w.#xyz[1]; const wZ = w.#xyz[2]; return new Vec3(vY * wZ - vZ * wY, vZ * wX - vX * wZ, vX * wY - vY * wX); } /** * 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 Vec3 instance representing the reflected vector. */ static reflect(v, normal) { // R = V - 2(V·N)N const dot = v.#xyz[0] * normal.#xyz[0] + v.#xyz[1] * normal.#xyz[1] + v.#xyz[2] * normal.#xyz[2]; return new Vec3(v.#xyz[0] - 2 * dot * normal.#xyz[0], v.#xyz[1] - 2 * dot * normal.#xyz[1], v.#xyz[2] - 2 * dot * normal.#xyz[2]); } /** * 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.#xyz[0] - w.#xyz[0]) ** 2 + (v.#xyz[1] - w.#xyz[1]) ** 2 + (v.#xyz[2] - w.#xyz[2]) ** 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.#xyz[0] - w.#xyz[0]); const absY = abs(v.#xyz[1] - w.#xyz[1]); const absZ = abs(v.#xyz[2] - w.#xyz[2]); return absX >= absY && absX >= absZ ? absX : absY >= absZ ? absY : absZ; } /** * 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.#xyz[0] - w.#xyz[0]) + abs(v.#xyz[1] - w.#xyz[1]) + abs(v.#xyz[2] - w.#xyz[2]); } /** * 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.#xyz[0] - w.#xyz[0]) ** p + abs(v.#xyz[1] - w.#xyz[1]) ** p + abs(v.#xyz[2] - w.#xyz[2]) ** 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.#xyz[0] - w.#xyz[0]) ** 2 + (v.#xyz[1] - w.#xyz[1]) ** 2 + (v.#xyz[2] - w.#xyz[2]) ** 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.#xyz[0] * w.#xyz[0] + v.#xyz[1] * w.#xyz[1] + v.#xyz[2] * w.#xyz[2]; } /** * Creates a Vec3 from cylindrical coordinates. * @param r - Radial distance from the z-axis * @param phi - Azimuthal angle in the x-y plane from the x-axis * @param z - Height above the x-y plane * @returns New Vec3 instance */ static fromCylindricalCoords(r, phi, z) { return new Vec3(r * cos(phi), r * sin(phi), z); } /** * Creates a Vec3 from spherical coordinates. * @param r - Radial distance from origin * @param theta - Polar angle from the z-axis * @param phi - Azimuthal angle in the x-y plane from the x-axis * @returns New Vec3 instance */ static fromSphericalCoords(r, theta, phi) { return new Vec3(r * sin(theta) * cos(phi), r * sin(theta) * sin(phi), r * cos(theta)); } /** * Creates an immutable Vec3-like object. * @param x - The x-coordinate of the vector. * @param y - The y-coordinate of the vector. * @returns An immutable object with Vec3-like properties. */ static immutable(x = 0, y = 0, z = 0) { const data = new Float64Array(10); data[0] = x; data[1] = y; data[2] = z; const xySqSum = x ** 2 + y ** 2; data[4] = atan2(sqrt(y ** 2 + z ** 2), x); data[5] = atan2(sqrt(z ** 2 + x ** 2), y); data[6] = atan2(sqrt(xySqSum), z); const magnitudeSq = xySqSum + z ** 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; const _isNaN = Number.isNaN(x) && Number.isNaN(y) && Number.isNaN(z); const isZero = x === 0 && y === 0 && z === 0; const xyz = Object.freeze([...data.slice(0, 3)]); return { get x() { return data[0]; }, get y() { return data[1]; }, get z() { return data[2]; }, get xyz() { return xyz; }, get magnitude() { return data[8]; }, get magnitudeSq() { return data[10]; }, 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.#xyz[0]; const y = v.#xyz[1]; const z = v.#xyz[2]; 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); } /** * 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.#xyz[0]) || Number.isNaN(v.#xyz[1]) || Number.isNaN(v.#xyz[2]); } /** * 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.#xyz[0] === 0 && v.#xyz[1] === 0 && v.#xyz[2] === 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.#xyz[0]; const vY = v.#xyz[1]; const vZ = v.#xyz[2]; return new Vec3(vX + (w.#xyz[0] - vX) * t, vY + (w.#xyz[1] - vY) * t, vZ + (w.#xyz[2] - vZ) * t); } /** * Negates a vector. * @param v - The vector to negate. * @returns A new Vec2 instance representing the negated vector. */ static negate(v) { return new Vec3(-v.#xyz[0], -v.#xyz[1], -v.#xyz[2]); } /** * 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 Vec3(v.#xyz[0] / m, v.#xyz[1] / m, v.#xyz[2] / 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.#xyz[0]; const wY = w.#xyz[1]; const wZ = w.#xyz[2]; const f = vM * cos(acos((v.#xyz[0] * wX + v.#xyz[1] * wY + v.#xyz[2] * wZ) / (vM * wM))); return new Vec3((wX / wM) * f, (wY / wM) * f, (wZ / wM) * f); } /** * Creates a random unit vector. * @param random - A function that returns a random number between 0 and 1. * @returns A new Vec3 instance representing a random unit vector. */ static random(random = Math.random) { let x1; let x2; do { x1 = random() * 2 - 1; x2 = random() * 2 - 1; } while (x1 ** 2 + x2 ** 2 >= 1); const f = sqrt(1 - x1 ** 2 - x2 ** 2); return new Vec3(2 * x1 * f, 2 * x2 * f, 1 - 2 * (x1 ** 2 + x2 ** 2)); } /** * 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.#xyz[0] === w.#xyz[0] && v.#xyz[1] === w.#xyz[1] && v.#xyz[2] === w.#xyz[2]; } /** * 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.#xyz[0] === -w.#xyz[0] && v.#xyz[1] === -w.#xyz[1] && v.#xyz[2] === -w.#xyz[2]; } /** * 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.#xyz[0] - w.#xyz[0]) <= epsilon && abs(v.#xyz[1] - w.#xyz[1]) <= epsilon && abs(v.#xyz[2] - w.#xyz[2]) <= epsilon); } /** * Scales a vector by a scalar value. * @param v - The vector to scale. * @param c - The scalar value. * @returns A new Vec3 instance representing the scaled vector. */ static scale(v, c) { return new Vec3(v.#xyz[0] * c, v.#xyz[1] * c, v.#xyz[2] * c); } /** * Creates a zero vector. * @returns A new Vec3 instance representing a zero vector. */ static zero() { return new Vec3(); } /** * Creates a vector with all components set to 1.0. * @returns A new Vec3 instance representing a vector with all components set to 1.0. */ static one() { return new Vec3(1, 1, 1); } /** * Creates a Vec3 from an array. * @returns A new Vec3 instance. */ static fromArray(arr) { return new Vec3(arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0); } /** * Creates a Vec3 from an object with x, y and z properties. * @returns A new Vec3 instance. */ static fromObject(obj) { return new Vec3(obj.x, obj.y, obj.z); } /** * Creates a Vec3 instance from a JSON-parsed object. * @param json - The JSON-parsed object containing x and y properties. * @returns A new Vec3 instance. */ static fromJSON(json) { return Vec3.fromObject(json); } /** * Gets the x-component of the vector. * @returns The x-component. */ get x() { return this.#xyz[0]; } /** * Sets the x-component of the vector. * @param x - The new x-component. */ set x(x) { this.#xyz[0] = x; this.#computeMagnitude(); } /** * Gets the y-component of the vector. * @returns The y-component. */ get y() { return this.#xyz[1]; } /** * Sets the y-component of the vector. * @param y - The new y-component. */ set y(y) { this.#xyz[1] = y; this.#computeMagnitude(); } /** * Gets the z-component of the vector. * @returns The z-component. */ get z() { return this.#xyz[2]; } /** * Sets the z-component of the vector. * @param z - The new z-component. */ set z(z) { this.#xyz[2] = z; this.#computeMagnitude(); } /** * Gets a copy of the vector's components as an array. * @returns An array containing the x, y and z components of the vector. */ get xyz() { return Array.from(this.#xyz.slice()); } /** * Sets both components of the vector at once. * @param xyz - An array containing the new x, y and z components. */ set xyz(xyz) { this.#xyz[0] = xyz[0]; this.#xyz[1] = xyz[1]; this.#xyz[2] = xyz[2]; 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.#xyz[1] ** 2 + this.#xyz[2] ** 2), this.#xyz[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.#xyz[2] ** 2 + this.#xyz[0] ** 2), this.#xyz[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.#xyz[0] ** 2 + this.#xyz[1] ** 2), this.#xyz[2]); } /** * 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.#xyz[0] = (this.#xyz[0] / M) * m; this.#xyz[1] = (this.#xyz[1] / M) * m; this.#xyz[2] = (this.#xyz[2] / M) * m; this.#computeMagnitude(); } /** * Adds another vector to this vector. * @param v - The vector to add. * @returns This Vec3 instance for method chaining. */ add(v) { this.#xyz[0] += v.#xyz[0]; this.#xyz[1] += v.#xyz[1]; this.#xyz[2] += v.#xyz[2]; this.#computeMagnitude(); return this; } /** * Subtracts another vector from this vector. * @param v - The vector to subtract. * @returns This Vec3 instance for method chaining. */ subtract(v) { this.#xyz[0] -= v.#xyz[0]; this.#xyz[1] -= v.#xyz[1]; this.#xyz[2] -= v.#xyz[2]; this.#computeMagnitude(); return this; } /** * Multiplies this vector with another vector. * @param v - The vector to multiply with. * @returns This Vec3 instance for method chaining. */ multiply(v) { this.#xyz[0] *= v.#xyz[0]; this.#xyz[1] *= v.#xyz[1]; this.#xyz[2] *= v.#xyz[2]; this.#computeMagnitude(); return this; } /** * Divides this vector with another vector. * @param v - The vector to divide with. * @returns This Vec3 instance for method chaining. */ divide(v) { this.#xyz[0] = v.#xyz[0] === 0 ? Number.POSITIVE_INFINITY : this.#xyz[0] / v.#xyz[0]; this.#xyz[1] = v.#xyz[1] === 0 ? Number.POSITIVE_INFINITY : this.#xyz[1] / v.#xyz[1]; this.#xyz[2] = v.#xyz[2] === 0 ? Number.POSITIVE_INFINITY : this.#xyz[2] / v.#xyz[2]; 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.#xyz[0] * v.#xyz[0] + this.#xyz[1] * v.#xyz[1] + this.#xyz[2] * v.#xyz[2]) / (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 Vec3 instance for method chaining. */ clamp(min, max) { const m = this.#magnitude; if (m > max) { this.#xyz[0] = (this.#xyz[0] / m) * max; this.#xyz[1] = (this.#xyz[1] / m) * max; this.#xyz[2] = (this.#xyz[2] / m) * max; this.#computeMagnitude(); } else if (m < min) { this.#xyz[0] = (this.#xyz[0] / m) * min; this.#xyz[1] = (this.#xyz[1] / m) * min; this.#xyz[2] = (this.#xyz[2] / m) * min; this.#computeMagnitude(); } return this; } /** * Creates a copy of this vector. * @returns A new Vec3 instance with the same components. */ clone() { return new Vec3(this.#xyz[0], this.#xyz[1], this.#xyz[2]); } /** * Copies the components of another vector to this vector. * @param v - The vector to copy from. * @returns This Vec3 instance for method chaining. */ copy(v) { this.#xyz[0] = v.#xyz[0]; this.#xyz[1] = v.#xyz[1]; this.#xyz[2] = v.#xyz[2]; this.#computeMagnitude(); return this; } /** * 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.#xyz[0] * v.#xyz[0] + this.#xyz[1] * v.#xyz[1] + this.#xyz[2] * v.#xyz[2]; } /** * Calculates cross product between this vector and another vector. * @param v - The other vector. * @returns The distance between the vectors. */ cross(v) { const x = this.#xyz[0]; const y = this.#xyz[1]; const z = this.#xyz[2]; const vX = v.#xyz[0]; const vY = v.#xyz[1]; const vZ = v.#xyz[2]; this.#xyz[0] = y * vZ - z * vY; this.#xyz[1] = z * vX - x * vZ; this.#xyz[2] = x * vY - y * vX; this.#computeMagnitude(); return this; } /** * 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 Vec3 instance for method chaining. */ reflect(normal) { const dot = this.#xyz[0] * normal.#xyz[0] + this.#xyz[1] * normal.#xyz[1] + this.#xyz[2] * normal.#xyz[2]; this.#xyz[0] -= 2 * dot * normal.#xyz[0]; this.#xyz[1] -= 2 * dot * normal.#xyz[1]; this.#xyz[2] -= 2 * dot * normal.#xyz[2]; 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.#xyz[0] - v.#xyz[0]) ** 2 + (this.#xyz[1] - v.#xyz[1]) ** 2 + (this.#xyz[2] - v.#xyz[2]) ** 2); } /** * Calculates the Chebyshev distance between vector and another vector. * @param v - The other vector. * @returns The Chebyshev distance between the vectors. */ distanceChebyshev(v) { const absX = abs(this.#xyz[0] - v.#xyz[0]); const absY = abs(this.#xyz[1] - v.#xyz[1]); const absZ = abs(this.#xyz[2] - v.#xyz[2]); return absX >= absY && absX >= absZ ? absX : absY >= absZ ? absY : absZ; } /** * Calculates the Manhattan distance between vector and another vector. * @param v - The other vector. * @returns The Manhattan distance between the vectors. */ distanceManhattan(v) { return abs(this.#xyz[0] - v.#xyz[0]) + abs(this.#xyz[1] - v.#xyz[1]) + abs(this.#xyz[2] - v.#xyz[2]); } /** * Calculates the Minkowski distance between this vector 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.#xyz[0] - v.#xyz[0]) ** p + abs(this.#xyz[1] - v.#xyz[1]) ** p + abs(this.#xyz[2] - v.#xyz[2]) ** 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.#xyz[0] - v.#xyz[0]) ** 2 + (this.#xyz[1] - v.#xyz[1]) ** 2 + (this.#xyz[2] - v.#xyz[2]) ** 2; } /** * Checks if this vector has infinite components. * @returns True if the vector has infinite components, false otherwise. */ isInfinite() { const x = this.#xyz[0]; const y = this.#xyz[1]; const z = this.#xyz[2]; 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); } /** * Checks if this vector has NaN components. * @returns True if the vector has NaN components, false otherwise. */ isNaN() { return Number.isNaN(this.#xyz[0]) || Number.isNaN(this.#xyz[1]) || Number.isNaN(this.#xyz[2]); } /** * Checks if this vector is zero. * @returns True if the vector is zero, false otherwise. */ isZero() { return this.#xyz[0] === 0 && this.#xyz[1] === 0 && this.#xyz[2] === 0; } /** * Limits the maximum magnitude of this vector. * @param max - The maximum magnitude. * @returns This Vec3 instance for method chaining. */ limitMax(max) { const m = this.#magnitude; if (m > max) { this.#xyz[0] = (this.#xyz[0] / m) * max; this.#xyz[1] = (this.#xyz[1] / m) * max; this.#xyz[2] = (this.#xyz[2] / m) * max; this.#computeMagnitude(); } return this; } /** * Limits the minimum magnitude of this vector. * @param min - The minimum magnitude. * @returns This Vec3 instance for method chaining. */ limitMin(min) { const m = this.#magnitude; if (m < min) { this.#xyz[0] = (this.#xyz[0] / m) * min; this.#xyz[1] = (this.#xyz[1] / m) * min; this.#xyz[2] = (this.#xyz[2] / m) * min; this.#computeMagnitude(); } return this; } /** * Sets this vector to point towards another vector. * @param v - The vector to look at. * @returns This Vec3 instance for method chaining. */ lookAt(v) { const m = this.#magnitude; const vM = v.#magnitude; this.#xyz[0] = (v.#xyz[0] / vM) * m; this.#xyz[1] = (v.#xyz[1] / vM) * m; this.#xyz[2] = (v.#xyz[2] / vM) * m; this.#computeMagnitude(); return this; } /** * Negates this vector. * @returns This Vec3 instance for method chaining. */ negate() { this.#xyz[0] *= -1; this.#xyz[1] *= -1; this.#xyz[2] *= -1; this.#computeMagnitude(); return this; } /** * Normalizes this vector. * @returns This Vec3 instance for method chaining. */ normalize() { const m = this.#magnitude; this.#xyz[0] /= m; this.#xyz[1] /= m; this.#xyz[2] /= m; this.#computeMagnitude(); return this; } /** * Projects this vector onto another vector. * @param v - The vector to project onto. * @returns This Vec3 instance for method chaining. */ project(v) { const m = this.#magnitude; const vM = v.#magnitude; const vX = v.#xyz[0]; const vY = v.#xyz[1]; const vZ = v.#xyz[2]; const f = m * cos(acos((this.#xyz[0] * vX + this.#xyz[1] * vY + this.#xyz[2] * vZ) / (m * vM))); this.#xyz[0] = (vX / vM) * f; this.#xyz[1] = (vY / vM) * f; this.#xyz[2] = (vZ / 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 Vec3 instance for method chaining. */ random(random = Math.random) { let x1; let x2; do { x1 = random() * 2 - 1; x2 = random() * 2 - 1; } while (x1 ** 2 + x2 ** 2 >= 1); const f = sqrt(1 - x1 ** 2 - x2 ** 2); const m = this.#magnitude; this.#xyz[0] = m * 2 * x1 * f; this.#xyz[1] = m * 2 * x2 * f; this.#xyz[2] = m * (1 - 2 * (x1 ** 2 + x2 ** 2)); this.#computeMagnitude(); return this; } /** * Rotates this vector around the X-axis. * @param phi - The angle of rotation in radians. * @returns This Vec3 instance for method chaining. */ rotateX(phi) { const cosPhi = cos(phi); const sinPhi = sin(phi); const y = this.#xyz[1]; const z = this.#xyz[2]; this.#xyz[1] = y * cosPhi - z * sinPhi; this.#xyz[2] = y * sinPhi + z * cosPhi; this.#computeMagnitude(); return this; } /** * Rotates this vector around the Y-axis. * @param phi - The angle of rotation in radians. * @returns This Vec3 instance for method chaining. */ rotateY(phi) { const cosPhi = cos(phi); const sinPhi = sin(phi); const x = this.#xyz[0]; const z = this.#xyz[2]; this.#xyz[0] = x * cosPhi + z * sinPhi; this.#xyz[2] = x * sinPhi + z * cosPhi; this.#computeMagnitude(); return this; } /** * Rotates this vector around the Z-axis. * @param phi - The angle of rotation in radians. * @returns This Vec3 instance for method chaining. */ rotateZ(phi) { const cosPhi = cos(phi); const sinPhi = sin(phi); const x = this.#xyz[0]; const y = this.#xyz[1]; this.#xyz[0] = x * cosPhi - y * sinPhi; this.#xyz[1] = x * sinPhi + y * cosPhi; 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.#xyz[0] === v.#xyz[0] && this.#xyz[1] === v.#xyz[1] && this.#xyz[2] === v.#xyz[2]; } /** * 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.#xyz[0] === -v.#xyz[0] && this.#xyz[1] === -v.#xyz[1] && this.#xyz[2] === -v.#xyz[2]; } /** * 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.#xyz[0] - v.#xyz[0]) <= epsilon && abs(this.#xyz[1] - v.#xyz[1]) <= epsilon && abs(this.#xyz[2] - v.#xyz[2]) <= epsilon); } /** * Scales this vector by a scalar value. * @param c - The scalar value. * @returns This Vec3 instance for method chaining. */ scale(c) { this.#xyz[0] *= c; this.#xyz[1] *= c; this.#xyz[2] *= c; this.#computeMagnitude(); return this; } /** * Sets this vector to zero. * @returns This Vec3 instance for method chaining. */ zero() { this.#xyz[0] = 0; this.#xyz[1] = 0; this.#xyz[2] = 0; this.#computeMagnitude(); return this; } /** * Makes the Vec3 instance iterable. * @yields The x, y and z components of the vector. */ *[Symbol.iterator]() { yield this.#xyz[0]; yield this.#xyz[1]; yield this.#xyz[2]; } /** * Returns a string representation of the vector. * @returns A string in the format "Vec3(x, y, z)". */ toString() { return `Vec3(${this.#xyz[0]}, ${this.#xyz[1]}, ${this.#xyz[2]})`; } /** * Converts the vector to a plain object. * @returns An object with x, y and z properties. */ toObject() { return { x: this.#xyz[0], y: this.#xyz[1], z: this.#xyz[2] }; } /** * Serializes the vector to a JSON-friendly format. * @returns A JSON-friendly object representation of the vector. */ toJSON() { return this.toObject(); } }