UNPKG

@amandaghassaei/vector-math

Version:

A minimal vector math library to handle 2D/3D translations and rotations, written in TypeScript.

389 lines (352 loc) 11.6 kB
import type { Matrix3Readonly } from './Matrix3'; import type { THREE_Vector2 } from './THREE_types'; import { getStackTraceAsString } from './utils'; import { NUMERICAL_TOLERANCE } from './constants'; export type Vector2Readonly = { readonly x: number; readonly y: number; dot: (vec: Vector2Readonly | THREE_Vector2) => number; cross: (vec: Vector2Readonly | THREE_Vector2) => number; angle: () => number; lengthSq: () => number; length: () => number; distanceToSquared: (vec: Vector2Readonly | THREE_Vector2) => number; distanceTo: (vec: Vector2Readonly | THREE_Vector2) => number; angleTo: (vec: Vector2Readonly | THREE_Vector2) => number; angleToNormalized: (vec: Vector2Readonly | THREE_Vector2) => number; equals: (vec: Vector2Readonly | THREE_Vector2, tolerance?: number) => boolean; isZero: (tolerance?: number) => boolean; clone: () => Vector2; toArray: () => [number, number]; }; export class Vector2 { x: number; y: number; /** * @param x - Defaults to 0. * @param y - Defaults to 0. */ constructor(); constructor(x: number, y: number); constructor(x?: number, y?: number) { this.x = x || 0; this.y = y || 0; } /** * Set the contents of a Vector2. * @param x - x component. * @param y - y component. * @returns this */ set(x: number, y: number) { this.x = x; this.y = y; return this; } /** * Set the contents of a Vector3 from an array. * @param array - Array containing x, and y components. * @returns this */ setFromArray(array: readonly [number, number]) { this.x = array[0]; this.y = array[1]; return this; } /** * Fill all components of this Vector2 with the same value. * @param value - Value to fill all components with. * @returns */ fill(value: number) { this.x = value; this.y = value; return this; } /** * Add a Vector2 to this Vector2. * @param vec - Vector2 to add. * @returns this */ add(vec: Vector2Readonly | THREE_Vector2) { this.x += vec.x; this.y += vec.y; return this; } /** * Subtract a Vector2 from this Vector2. * @param vec - Vector2 to subtract. * @returns this */ sub(vec: Vector2Readonly | THREE_Vector2) { this.x -= vec.x; this.y -= vec.y; return this; } /** * Multiply this Vector2 by scalar value. * @param scalar - Scalar to multiply. * @returns this */ multiplyScalar(scalar: number) { this.x *= scalar; this.y *= scalar; return this; } /** * Divide this Vector2 by scalar value. * @param scalar - Scalar to divide. * @returns this */ divideScalar(scalar: number) { if (Math.abs(scalar) <= NUMERICAL_TOLERANCE()) console.warn( `Dividing by zero in Vector2.divideScalar(), stack trace:\n${getStackTraceAsString()}.` ); return this.multiplyScalar(1 / scalar); } /** * Returns the dot product of this Vector2 with another Vector2. * @param vec - Vector2 to dot with. * @returns The dot product. */ dot(vec: Vector2Readonly | THREE_Vector2) { return this.x * vec.x + this.y * vec.y; } /** * Returns the dot product of two Vector2s. * @param vec1 - First Vector2. * @param vec2 - Second Vector2. * @returns The dot product. */ static dot(vec1: Vector2Readonly | THREE_Vector2, vec2: Vector2Readonly | THREE_Vector2) { return vec1.x * vec2.x + vec1.y * vec2.y; } /** * Compute the 2D cross product (wedge product) with another Vector2. * @param vec - Vector2 to cross. * @returns The cross product. */ cross(vec: Vector2Readonly | THREE_Vector2) { return this.x * vec.y - this.y * vec.x; } /** * Compute the 2D cross product (wedge product) of two Vector2s. * @param vec1 - First Vector2. * @param vec2 - Second Vector2. * @returns The cross product. */ static cross(vec1: Vector2Readonly | THREE_Vector2, vec2: Vector2Readonly | THREE_Vector2) { return vec1.x * vec2.y - vec1.y * vec2.x; } /** * Get the angle of this Vector2. * Computes the angle in radians with respect to the positive x-axis. * Angle is always in range [0, 2 * Math.PI] (and 2 * Math.PI is slightly less than 2 * PI). * @returns The angle. */ angle() { return Math.atan2(-this.y, -this.x) + Math.PI; } /** * Returns the squared length of the Vector2. * @returns The squared length. */ lengthSq() { const lengthSq = this.dot(this); return lengthSq; } /** * Returns the length of the Vector2. * @returns The length. */ length() { return Math.sqrt(this.lengthSq()); } /** * Returns the squared distance between this Vector2 and another Vector2. * @param vec - Vector2 to measure distance to. * @returns The squared distance. */ distanceToSquared(vec: Vector2Readonly | THREE_Vector2) { const dx = this.x - vec.x; const dy = this.y - vec.y; return dx * dx + dy * dy; } /** * Returns the distance between this Vector2 and another Vector2. * @param vec - Vector2 to measure distance to. * @returns The distance. */ distanceTo(vec: Vector2Readonly | THREE_Vector2) { return Math.sqrt(this.distanceToSquared(vec)); } /** * Normalize the length of this Vector2. * @returns this */ normalize() { const length = this.length(); if (length <= NUMERICAL_TOLERANCE()) { console.warn( `Attempting to normalize zero length Vector2, stack trace:\n${getStackTraceAsString()}.` ); return this; } this.divideScalar(length); return this; } /** * Apply Matrix3 transformation to this Vector2. * @param matrix - Matrix3 to apply. * @returns this */ applyMatrix3(matrix: Matrix3Readonly) { if (matrix.isIdentity) return this; const x = this.x, y = this.y; const e = matrix.elements; this.x = e[0] * x + e[1] * y + e[2]; this.y = e[3] * x + e[4] * y + e[5]; return this; } /** * Linearly interpolate between this Vector2 and another Vector2. * @param vector - Vector2 to lerp to. * @param t - Interpolation factor between 0 and 1. * @returns this */ lerp(vector: Vector2Readonly | THREE_Vector2, t: number) { this.x += (vector.x - this.x) * t; this.y += (vector.y - this.y) * t; return this; } /** * Average this Vector2 with another Vector2. * @param vector - Vector2 to average with. * @returns this */ average(vector: Vector2Readonly | THREE_Vector2) { this.x = (this.x + vector.x) / 2; this.y = (this.y + vector.y) / 2; return this; } /** * Min this Vector3 with another Vector3. * @param vector - Vector3 to min with. * @returns this */ min(vector: Vector2Readonly | THREE_Vector2) { this.x = Math.min(this.x, vector.x); this.y = Math.min(this.y, vector.y); return this; } /** * Max this Vector2 with another Vector2. * @param vector - Vector2 to max with. * @returns this */ max(vector: Vector2Readonly | THREE_Vector2) { this.x = Math.max(this.x, vector.x); this.y = Math.max(this.y, vector.y); return this; } /** * Invert this Vector2. * @returns this */ invert() { this.x = -this.x; this.y = -this.y; return this; } /** * Calculate the angle between this Vector2 and another Vector2. * @param vector - Vector2 to calculate angle to. * @returns The angle between the vectors. */ angleTo(vector: Vector2Readonly | THREE_Vector2) { const theta = this.dot(vector) / Math.sqrt(this.lengthSq() * Vector2.dot(vector, vector)); return Math.acos(Math.min(Math.max(theta, -1), 1)); } /** * Calculate the angle between two Vector2s. * @param vec1 - First Vector2. * @param vec2 - Second Vector2. * @returns The angle between the vectors. */ static angleTo(vec1: Vector2Readonly | THREE_Vector2, vec2: Vector2Readonly | THREE_Vector2) { const theta = Vector2.dot(vec1, vec2) / Math.sqrt(Vector2.dot(vec1, vec1) * Vector2.dot(vec2, vec2)); return Math.acos(Math.min(Math.max(theta, -1), 1)); } /** * Calculate the angle between this (normalized) Vector2 and another (normalized) Vector2. * @param vector - Vector2 to calculate angle to. * @returns The angle between the vectors. */ angleToNormalized(vector: Vector2Readonly | THREE_Vector2) { const theta = this.dot(vector); return Math.acos(Math.min(Math.max(theta, -1), 1)); } /** * Calculate the angle between a (normalized) Vector2 and another (normalized) Vector2. * @param vec1 - First Vector2. * @param vec2 - Second Vector2. * @returns The angle between the vectors. */ static angleToNormalized(vec1: Vector2Readonly | THREE_Vector2, vec2: Vector2Readonly | THREE_Vector2) { const theta = Vector2.dot(vec1, vec2); return Math.acos(Math.min(Math.max(theta, -1), 1)); } /** * Copy the contents of a Vector2 to this Vector2. * @param vec - Vector2 to copy. * @returns this */ copy(vec: Vector2Readonly | THREE_Vector2) { this.x = vec.x; this.y = vec.y; return this; } /** * Test if this Vector2 equals another Vector2. * @param vec - Vector2 to test equality with. * @param tolerance - Optional numerical tolerance for equality check, defaults to global numerical tolerance. * @returns True if the vectors are equal. */ equals(vec: Vector2Readonly | THREE_Vector2, tolerance = NUMERICAL_TOLERANCE()) { return Math.abs(this.x - vec.x) <= tolerance && Math.abs(this.y - vec.y) <= tolerance; } /** * Test if two Vector2s are equal. * @param vec1 - First Vector2. * @param vec2 - Second Vector2. * @param tolerance - Optional numerical tolerance for equality check, defaults to global numerical tolerance. * @returns True if the vectors are equal. */ static equals(vec1: Vector2Readonly | THREE_Vector2, vec2: Vector2Readonly | THREE_Vector2, tolerance = NUMERICAL_TOLERANCE()) { return Math.abs(vec1.x - vec2.x) <= tolerance && Math.abs(vec1.y - vec2.y) <= tolerance; } /** * Test if this vector is the zero vector. * @param tolerance - Optional numerical tolerance for zero check, defaults to global numerical tolerance. * @returns True if the vector is the zero vector. */ isZero(tolerance = NUMERICAL_TOLERANCE()) { return this.lengthSq() <= tolerance * tolerance; } /** * Clone this Vector2 into a new Vector2. * @returns The cloned Vector2. */ clone() { return new Vector2(this.x, this.y); } /** * Returns an array containing the x and y components of this Vector3. * @returns The Vector2 as an array. */ toArray() { return [this.x, this.y] as [number, number]; } }