/** * @module Vec2 * @fileOverview Contains Vec2 class. */ /** * @class * A two-dimensional vector. Used to represent coordinates in 2D space. */ class Vec2 { /** * Create a new Vec2 from ambiguous arguments. * @param {number|number[]|Vec2} args Arguments to create vector from * @returns {Vec2} Resulting vector * @constructor */ static from(...args) { if (args.length === 1) { if (typeof args[0] === "number") return new Vec2(args[0], args[0]); else if (args[0] instanceof Array) return this.fromArray(args[0]); else if (args[0] instanceof Vec2) return args[0].copy(); } else if (args.length === 2) return new Vec2(args[0], args[1]); return new Vec2(); } /** * Create a new Vec2 from an array. * @param {number[2]} array Array to create vector from * @returns {Vec2} Resulting vector */ static fromArray(array) { return new Vec2(array[0], array[1]); } /** * Create a new Vec2 from an angle * @param {number} rads Angle in radians * @returns {Vec2} Resulting vector */ static fromAngle(rads) { return new Vec2(Math.cos(rads), Math.sin(rads)).norm(); } /** * Create a random Vec2 within a specific radius. * @param {number} radius Radius to generate in * @returns {Vec2} Resulting random vector */ static random(radius = 1) { return new Vec2(Math.random(), Math.random()).norm().mulv(Math.random() * radius); } /** * Create a random Vec2 on the edge of a specific radius. * @param {number} radius Radius to generate on * @returns {Vec2} Resulting random vector */ static randomOuter(radius = 1) { return new Vec2( Math.random() - 0.5, Math.random() - 0.5 ).norm().mulv(radius); } /** * Given multiple vectors, find the lowest coordinates in both directions. * @param {...Vec2} vectors Vectors to compare * @returns {Vec2} Resulting minimum vector */ static min(...vectors) { let m = null; for (const vector of vectors) { if (m === null) { m = vector.copy(); continue; } m.x = Math.min(m.x, vector.x); m.y = Math.min(m.y, vector.y); } if (m === null) return new Vec2(); return m; } /** * Given multiple vectors, find the highest coordinates in both directions. * @param {...Vec2} vectors Vectors to compare * @returns {Vec2} Resulting maximum vector */ static max(...vectors) { let m = null; for (const vector of vectors) { if (m === null) { m = vector.copy(); continue; } m.x = Math.max(m.x, vector.x); m.y = Math.max(m.y, vector.y); } if (m === null) return new Vec2(); return m; } /** * Create a new Vec2. * @param {number} x X-value of the vector * @param {number} y Y-value of the vector */ constructor(x = 0, y = 0) { this.x = x; this.y = y; } /** * Get an exact copy of the vector. * @returns {Vec2} Copy of vector */ copy() { return new Vec2(this.x, this.y); } // // Addition & Subtraction // /** * Add two vectors together. * @param {Vec2} other Other vector to add * @returns {Vec2} New Vec2 with resulting values */ add(other) { return new Vec2(this.x + other.x, this.y + other.y); } /** * Add x and y values to the vector. * @param {number} x X-value to add * @param {number} y Y-value to add * @returns {Vec2} New Vec2 with resulting values */ addv(x, y) { return new Vec2(this.x + x, this.y + y); } /** * Subtract a vector from another. * @param {Vec2} other Other vector to subtract by * @returns {Vec2} New Vec2 with resulting values */ sub(other) { return new Vec2(this.x - other.x, this.y - other.y); } /** * Subtract x and y values from the vector. * @param {number} x X-value to subtract * @param {number} y Y-value to subtract * @returns {Vec2} New Vec2 with resulting values */ subv(x, y) { return new Vec2(this.x - x, this.y - y); } // // Multiplication & Division // /** * Multiply two vectors together. * @param {Vec2} other Other vector to multiply by * @returns {Vec2} New Vec2 with resulting values */ mul(other) { return new Vec2(this.x * other.x, this.y * other.y); } /** * Multiply the vector by a scalar. * @param {number} val Scalar value * @returns {Vec2} New Vec2 with scaled values */ mulv(val) { return new Vec2(this.x * val, this.y * val); } /** * Divide the vector by another. * @param {Vec2} other Other vector to divide by * @returns {Vec2} New Vec2 with resulting values */ div(other) { return new Vec2(this.x / other.x, this.y / other.y); } /** * Divide the vector by a scalar. * @param {number} val Scalar value * @returns {Vec2} New Vec2 with scaled values */ divv(val) { return new Vec2(this.x / val, this.y / val); } // // Calculations & Conversions // /** * Get the magnitude/length of the vector. * @returns {number} Magnitude of vector */ mag() { return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); } /** * Get the distance (magnitude) from the vector to another. * @param {Vec2} other Other Vec2 to get distance to * @returns {number} Distance between the two vectors */ dist(other) { return this.sub(other).mag(); } /** * Get the dot product of the vector with another. * @param {Vec2} other Other Vec2 to use * @returns {number} Dot product of the two vectors */ dot(other) { return this.x * other.x + this.y * other.y; } /** * Get the dot product of the vector with specific x and y values. * @param {number} x X-value to use * @param {number} y Y-value to use * @returns {number} Resulting dot product */ dotv(x, y) { return this.x * x + this.y * y; } /** * Get the angle of the vector (in radians). * @returns {number} Angle of vector */ angle() { if (this.isZero()) return 0; return Math.atan2(this.y, this.x); } /** * Get a new vector by rotating (in radians) the existing vector. * @param {number} rad Radians to rotate by * @returns {Vec2} Resulting rotated vector */ rot(rad) { const d = this.mag(); const angle = this.angle(); return new Vec2(Math.cos(angle + rad) * d, Math.sin(angle + rad) * d); } /** * Get the normalization of the vector. * @returns {Vec2} Normalized version of the vector */ norm() { const mag = this.mag(); if (mag === 0) return new Vec2(); return this.divv(mag); } /** * Get a vector denoting the signs of each axis. * @returns {Vec2} Sign vector */ sign() { return new Vec2(Math.sign(this.x), Math.sign(this.y)); } /** * Invert the vector (inverts the signs of the values). * @returns {Vec2} Inverted Vec2 */ invert() { return new Vec2(-this.x, -this.y); } /** * Calculate the perpendicular vector. * @returns {Vec2} Perpendicular vector */ perp() { return new Vec2(-this.y, this.x); } /** * Get the normal vector from another vector. * @param {Vec2} other Other Vec2 to use * @returns {Vec2} Resulting normal vector */ normWith(other) { return this.sub(other).norm().perp(); } /** * Floor the values of the vector. Can be used to round to a specified precision. * @param {number} v (Optional) Multiplies values by this value before flooring, then divides it afterwords. * Useful for rounding to a specific precision or specific decimal places. * @returns {Vec2} Resulting floored Vec2 */ floor(v = 1) { return new Vec2(Math.floor(this.x / v) * v, Math.floor(this.y / v) * v); } /** * Ceiling the values of the vector. Can be used to round to a specified precision. * @param {number} v (Optional) Multiplies values by this value before ceiling, then divides it afterwords. * Useful for rounding to a specific precision or specific decimal places. * @returns {Vec2} Resulting rounded up Vec2 */ ceil(v = 1) { return new Vec2(Math.ceil(this.x / v) * v, Math.ceil(this.y / v) * v); } /** * Convert the Vec2 to a standard array. * @returns {number[]} Vector as an array */ toArray() { return [ this.x, this.y ]; } /** * Convert the Vec2 to a gl-matrix array. * @returns {number[]} Gl-matrix array */ toGlArray() { const vector = vec2.create(); vec2.set(vector, this.x, this.y); return vector; } /** * Transform the vector using a gl-matrix matrix. * @param matrix Matrix to transform with * @returns {Vec2} Transformed vector */ transform(matrix) { const vector = this.toGlArray(); vec2.transformMat3(vector, vector, matrix); return Vec2.fromArray(vector); } /** * Inversely transform the vector using a gl-matrix matrix. * @param matrix Matrix to inversely transform with * @returns {Vec2} Inversely transformed vector */ inverseTransform(matrix) { mat3.invert(matrix, matrix); return this.transform(matrix); } /** * Convert to a translation matrix. * @returns Translation matrix */ toTranslationMatrix() { const vector = this.toGlArray(); const matrix = mat3.create(); mat3.fromTranslation(matrix, vector); return matrix; } /** * Convert to a scaling matrix. * @returns Scaling matrix */ toScaleMatrix() { const vector = this.toGlArray(); const matrix = mat3.create(); mat3.fromScaling(matrix, vector); return matrix; } /** * Project onto another vector. * @param {Vec2} other Vector to project onto * @returns {Vec2} Projected vector */ project(other) { return other.mulv(this.dot(other) / other.dot(other)); } /** * Mirror over another vector. * @param {Vec2} other Vector to mirror over * @returns {Vec2} Mirrored vector */ mirrorOver(other) { return this.project(other).mulv(2).sub(this); } // // Checks // /** * Check if the vector is zero or not (both values equal to zero). * @returns {boolean} Whether the vector is zero */ isZero() { return this.x === 0 && this.y === 0; } /** * Check if another vector is equal within an optionally specified range. * @param {Vec2} other Other vector to compare with * @param {number} range (Optional) Precision of the comparison. Set to 0.001 by default, this value is useful for * avoiding floating-point precision issues (defaults to 0.001). * @returns {boolean} Whether or not the vectors are equal, within the specified range. */ equals(other, range = 0.001) { return Math.abs(this.x - other.x) < range && Math.abs(this.y - other.y) < range; } // // Linear interpolation // /** * Perform linear interpolation between the vector and another. * @param {Vec2} other Other Vec2 to interpolate to * @param {number} alpha Progress between the values (0 - 1) * @returns {Vec2} Resulting Vec2 */ lerp(other, alpha) { let d = other.sub(this); d = d.mulv(alpha); return this.add(d); } /** * Perform linear interpolation between the vector and another, smoothly. * @param {Vec2} other Other Vec2 to interpolate to * @param {number} alpha Progress between the values (0 - 1) * @param {number} edge Amount (percentage) of the interpolation to smooth (0 - 1) * @returns {Vec2} Resulting Vec2 */ smoothLerp(other, alpha, edge = 0.1) { //if (alpha < edge) // alpha = alpha / edge //else if (alpha > 1 - edge) // alpha = (1 - alpha) / edge; alpha = alpha * alpha * (3 - 2 * alpha); return this.add(other.sub(this).mulv(alpha)); } } export default Vec2;