UNPKG

3d-core-raub

Version:

An extensible Node3D core for desktop applications

638 lines (558 loc) 15.7 kB
'use strict'; /** * @typedef {object} Vec * A placeholder class for vector types. Not an actual class. For docs only. * When you see this type, you refer to the context and common sence * to understand which vector type is it: Vec2, Vec3, etc... */ /** * Two-dimensional vector * @note All 'ed() methods modify **INPLACE**, no-'ed methods - **MAKE A COPY** * @author Luis Blanco */ class Vec2 extends Array { /** * @constructs Vec2 * @desc Takes two numbers, or single array, or an object with `.x` and `.y` properties. * - If no arguments passed, constructs `Vec2(0, 0)`. * - If only one number is given, constructs `Vec2(x, x)`. * @arg {number|number[]|object} [x=0] * @arg {Number} [y=0] * @return {Vec} */ constructor() { super(); const args = arguments; this.x = 0; this.y = 0; if (!args.length) { return; } if (typeof args[0] === 'object') { if (args[0] === null) { return; } // [] or {} or Vec2 if (args[0].constructor === Array || args[0].constructor === Vec2) { this.x = args[0][0]; this.y = args[0][1]; } else if (typeof args[0].x === 'number' && typeof args[0].y === 'number') { this.x = args[0].x; this.y = args[0].y; } } else if (typeof args[0] === 'number') { if (isNaN(args[0])) { return; } // a,b or a,a this.x = args[0]; this.y = (typeof args[1] === 'number') ? args[1] : args[0]; } } /** * The value of vector's x-component * @return {Number} */ get x() { return this[0]; } set x(_x) { this[0] = _x; } /** * The value of vector's y-component * @return {Number} */ get y() { return this[1]; } set y(_y) { this[1] = _y; } /** * The **new** vector of the same type, constructed after this one's current data * @return {Vec} */ get clone() { return new this.constructor(this); } /** * The **new** vector, constructed as `Vec2(this.x, this.y)` * @return {Vec} */ get xy() { return new Vec2(this); } set xy(_xy) { this[0] = _xy[0]; this[1] = _xy[1]; } /** * The **new** vector, constructed as `Vec2(this.y, this.x)` * @return {Vec} */ get yx() { return new Vec2([this[1], this[0]]); } set yx(_yx) { this[0] = _yx[1]; this[1] = _yx[0]; } /** * Adds the components of `other` to those of `this`, and then chains self * @arg {Vec} other * @return {Vec} this */ plused(other) { this[0] += other[0]; this[1] += other[1]; return this; } /** * Adds the components of `other` to those of `this.clone`, and then chains it * @arg {Vec} other * @return {Vec} this.clone */ plus(other) { return this.clone.plused(other); } /** * Same as `.plused()` * @see plused * @return {Vec} this */ added(other) { return this.plused(other); } /** * Same as `.plus()` * @see plus * @return {Vec} this.clone */ add(other) { return this.clone.plused(other); } /** * Subtracts the components of `other` from those of `this`, and then chains self * @arg {Vec} other * @return {Vec} this */ minused(other) { this[0] -= other[0]; this[1] -= other[1]; return this; } /** * Subtracts the components of `other` from those of `this.clone`, and then chains it * @arg {Vec} other * @return {Vec} this.clone */ minus(other) { return this.clone.minused(other); } /** * Same as `.minused()` * @see minused * @return {Vec} this */ subed(other) { return this.minused(other); } /** * Same as `.minus()` * @see minus * @return {Vec} this.clone */ sub(other) { return this.clone.minused(other); } /** * Same as `.minused()` * @see minused * @return {Vec} this */ subtracted(other) { return this.minused(other); } /** * Same as `.minus()` * @see minus * @return {Vec} this.clone */ subtract(other) { return this.clone.minused(other); } /** * This is for the people who **sub-S-tract** * @see minused */ substracted() { throw 'Use subtract instead of sub-S-tract.'; } /** * This is for the people who **sub-S-tract** * @see minus */ substract() { throw 'Use subtract instead of sub-S-tract.'; } /** * Multiplies the components of `this` by those of `other`, and then chains self * @arg {Vec} other * @return {Vec} this */ muled(other) { this[0] *= other[0]; this[1] *= other[1]; return this; } /** * Multiplies the components of `this.clone` by those of `other`, and then chains it * @arg {Vec} other * @return {Vec} this.clone */ mul(other) { return this.clone.muled(other); } /** * Same as `.muled()` * @see muled * @return {Vec} this */ multiplied(other) { return this.muled(other); } /** * Same as `.mul()` * @see mul * @return {Vec} this.clone */ multiply(other) { return this.clone.muled(other); } /** * Same as `.muled()` * @see muled * @return {Vec} this */ crossed(other) { this[0] *= other[0]; this[1] *= other[1]; return this; } /** * Same as `.mul()` * @see mul * @return {Vec} this.clone */ cross(other) { return this.clone.crossed(other); } /** * Divides the components of `this` by those of `other`, and then chains self * @arg {Vec} other * @return {Vec} this */ dived(other) { this[0] /= other[0]; this[1] /= other[1]; return this; } /** * Divides the components of `this.clone` by those of `other`, and then chains it * @arg {Vec} other * @return {Vec} this.clone */ div(other) { return this.clone.dived(other); } /** * Same as `.dived()` * @see dived * @return {Vec} this */ divided(other) { return this.dived(other); } /** * Same as `.div()` * @see div * @return {Vec} this.clone */ divide(other) { return this.clone.dived(other); } /** * Stores per-component maximum between `other` and `this`, and then chains self * @arg {Vec} other * @return {Vec} this */ maxed(other) { this[0] = Math.max(this[0], other[0]); this[1] = Math.max(this[1], other[1]); return this; } /** * Stores in `this.clone` per-component maximum between `other` and `this.clone`, and then chains self * @arg {Vec} other * @return {Vec} this.clone */ max(other) { return this.clone.maxed(other); } /** * Stores per-component minimum between `other` and `this`, and then chains self * @arg {Vec} other * @return {Vec} this */ mined(other) { this[0] = Math.min(this[0], other[0]); this[1] = Math.min(this[1], other[1]); return this; } /** * Stores in `this.clone` per-component minimum between `other` and `this.clone`, and then chains self * @arg {Vec} other * @return {Vec} this.clone */ min(other) { return this.clone.mined(other); } /** * Negates the components of `this`, and then chains self * @return {Vec} this */ get neged() { this[0] = -this[0]; this[1] = -this[1]; return this; } /** * Negates the components of `this.clone`, and then chains it * @return {Vec} this.clone */ get neg() { return this.clone.neged; } /** * Scales (multiplies) the components of `this`, and then chains self * @arg {Number} scalar * @return {Vec} this */ scaled(scalar) { this[0] *= scalar; this[1] *= scalar; return this; } /** * Scales (multiplies) the components of `this.clone`, and then chains it * @arg {Number} scalar * @return {Vec} this.clone */ scale(scalar) { return this.clone.scaled(scalar); } /** * Rounds the components of `this`, and then chains self * @return {Vec} this */ get rounded() { this[0] = Math.round(this[0]); this[1] = Math.round(this[1]); return this; } /** * Rounds the components of `this.clone`, and then chains it * @return {Vec} this.clone */ get round() { return this.clone.rounded; } /** * Floors the components of `this`, and then chains self * @return {Vec} this */ get floored() { this[0] = Math.floor(this[0]); this[1] = Math.floor(this[1]); return this; } /** * Floors the components of `this.clone`, and then chains it * @return {Vec} this.clone */ get floor() { return this.clone.floored; } /** * Ceils the components of `this`, and then chains self * @return {Vec} this */ get ceiled() { this[0] = Math.ceil(this[0]); this[1] = Math.ceil(this[1]); return this; } /** * Ceils the components of `this.clone`, and then chains it * @return {Vec} this.clone */ get ceil() { return this.clone.ceiled; } /** * Divides the components of `this`, and then chains self * @arg {Number} scalar * @return {Vec} this */ fracted(scalar) { this[0] /= scalar; this[1] /= scalar; return this; } /** * Divides the components of `this.clone`, and then chains it * @arg {Number} scalar * @return {Vec} this.clone */ fract(scalar) { return this.clone.fracted(scalar); } /** * Tells if `this` is a zero-vector * @return {boolean} true if both `.x` and `.y` are 0. */ get isZero() { return this[0] === 0 && this[1] === 0; } /** * Tells if `cb()` returned true for every component of `this` * @arg {function} cb * @return {boolean} true when all `cb(component, i)` are true. */ cmp(cb) { return cb(this[0], 0) && cb(this[1], 1); } /** * Calculates the dot product with other vector * @arg {Vec} other * @return {Number} dot product */ dot(other) { return this[0] * other[0] + this[1] * other[1]; } /** * The squared length of this vector, works well for length comparisons, where sqrting is pointles * @return {Number} squared length */ get sqLen() { return this.dot(this); } /** * Same as `.sqLen` * @see sqLen * @return {Number} squared length */ get sqLength() { return this.sqLen; } /** * Same as `.sqLen` * @see sqLen * @return {Number} squared length */ get squareLength() { return this.sqLen; } /** * The length of this vector * @return {Number} length */ get len() { return Math.sqrt(this.sqLen); } /** * Same as `.len` * @see len * @return {Number} length */ get length() { return this.len; } /** * Same as `.len` * @see len * @return {Number} length */ get size() { return this.len; } /** * Calculates the euclidian distance to other Vec2 * @arg {Vec} other * @return {Number} distance */ dist(other) { return other.clone.minused(this).len; } /** * Same as `.dist` * @see dist * @return {Number} distance */ distance(other) { return this.dist(other); } /** * Calculates the square of euclidian distance to other Vec2 * Works well for length comparisons, where sqrting is pointles. * @arg {Vec} other * @return {Number} squared distance */ sqDist(other) { return other.clone.minused(this).sqLen; } /** * Same as `.sqDist` * @see sqDist * @return {Number} squared distance */ sqDistance(other) { return this.sqDist(other); } /** * Same as `.sqDist` * @see sqDist * @return {Number} squared distance */ squareDistance(other) { return this.sqDist(other); } /** * Copies the component values from `other` into `this` * @arg {Vec} other * @return {Vec} this */ copy(other) { this[0] = other[0]; this[1] = other[1]; return this; } /** * Returns a string representation of the vector * @return {String} string representation of the vector */ toString() { return 'Vec2(' + this[0] + ', ' + this[1] + ')'; } /** * Makes clockwise 90 degree rotated copy of `this` * @return {Vec} clockwise perpendicular */ get ortho() { return new Vec2(this[1], -this[0]); } /** * Same as `.ortho` * @see ortho * @return {Vec} clockwise perpendicular */ get orthoCw() { return this.ortho; } /** * Same as `.ortho` * @see ortho * @return {Vec} clockwise perpendicular */ get orthoClockwise() { return this.ortho; } /** * Makes **counter**-clockwise 90 degree rotated copy of `this` * @return {Vec} counter-clockwise perpendicular */ get orthoCcw() { return new Vec2(-this[1], this[0]); } /** * Same as `.orthoCcw` * @see orthoCcw * @return {Vec} counter-clockwise perpendicular */ get orthoCounterClockwise() { return this.orthoCcw; } /** * Make a cross product and only return `.z` component * @arg {Vec} other * @return {Number} cross length */ crossLen(other) { return this[0] * other[1] - this[1] * other[0]; } /** * Same as `.crossLen()` * @see crossLen * @return {Number} cross length */ crossLength(other) { return this.crossLen(other); } /** * Rotate `this` by an angle * @arg {Number} angle * @return {Vec} this */ rotated(angle) { if (angle === 0) { return this; } const c = Math.cos(angle); const s = Math.sin(angle); this[0] = c * this[0] - s * this[1]; this[1] = s * this[0] + c * this[1]; return this; } /** * Rotate `this.clone` by an angle * @arg {Number} angle * @return {Vec} this.clone */ rotate(angle) { return this.clone.rotated(angle); } /** * Compute centroid of a triangle spanned by vectors `this`, `b`, `c` * See http://easycalculation.com/analytical/learn-centroid.php * @arg {Vec} b * @arg {Vec} c * @return {Vec} this.clone */ centroid(b, c) { return this.clone.plused(b).plused(c).scaled(1 / 3); } /** * Normalizes `this`: makes it's length equal to 1. If current length is 0, does nothing * @return {Vec} this */ get normed() { const sqLen = this.sqLen; return sqLen > 0 ? this.scaled(1 / Math.sqrt(sqLen)) : this; } /** * Normalizes `this.clone`: makes it's length equal to 1. If current length is 0, does nothing * @return {Vec} this.clone */ get norm() { return this.clone.normed; } /** * Same as `.normed` * @see normed * @return {Vec} this */ get normalized() { return this.normed; } /** * Same as `.norm` * @see norm * @return {Vec} this.clone */ get normalize() { return this.clone.normed; } /** * Linearly interpolate/mix `this` against the `other` * @arg {Vec} other * @arg {Number} t Lerp factor * @return {Vec} this */ lerped(other, t) { return this.plused(other.minused(this).scaled(t)); } /** * Linearly interpolate/mix `this.clone` against the `other` * @arg {Vec} other * @arg {Number} t Lerp factor * @return {Vec} this.clone */ lerp(other, t) { return this.clone.lerped(other, t); } /** * Reflect `this` along the given normal * @arg {Vec} normal * @return {Vec} this */ reflected(normal) { return this.minused(normal.sceled(2 * this.dot(normal))); } /** * Reflect `this.clone` along the given normal * @arg {Vec} normal * @return {Vec} this.clone */ reflect(normal) { return this.clone.reflected(normal); } /** * Get the intersection point between two line segments * @static * @arg {Vec} p0 * @arg {Vec} p1 * @arg {Vec} p2 * @arg {Vec} p3 * @return {Vec} null if no intersection. */ getLineSegmentsIntersection(p0, p1, p2, p3) { var t = Vec2.getLineSegmentsIntersectionFraction(p0, p1, p2, p3); if (t < 0) { return null; } return new Vec2(p0[0] + (t * (p1[0] - p0[0])), p0[1] + (t * (p1[1] - p0[1]))); } /** * Get the intersection fraction between two line segments. * If successful, the intersection is at p0 + t * (p1 - p0). * @arg {Vec} p0 * @arg {Vec} p1 * @arg {Vec} p2 * @arg {Vec} p3 * @return {Number} A number between 0 and 1 if there was an intersection, otherwise -1 */ getLineSegmentsIntersectionFraction(p0, p1, p2, p3) { const s1X = p1[0] - p0[0]; const s1Y = p1[1] - p0[1]; const s2X = p3[0] - p2[0]; const s2Y = p3[1] - p2[1]; const s = (-s1Y * (p0[0] - p2[0]) + s1X * (p0[1] - p2[1])) / (-s2X * s1Y + s1X * s2Y); const t = ( s2X * (p0[1] - p2[1]) - s2Y * (p0[0] - p2[0])) / (-s2X * s1Y + s1X * s2Y); if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { return t; // Collision detected } return -1; // No collision } } module.exports = Vec2;