UNPKG

x-math

Version:

Math library inspired by Google Closure library

484 lines (404 loc) 13 kB
// Original license here // http://docs.closure-library.googlecode.com/git/closure_goog_math_coordinate.js.source.html var XMath = require('./math'); /** * Class for representing coordinates and positions. * @param {number=} opt_x Left, defaults to 0. * @param {number=} opt_y Top, defaults to 0. * @constructor */ var Coordinate = function(opt_x, opt_y) { /** * X-value * @type {number} */ this.x = opt_x || 0; /** * Y-value * @type {number} */ this.y = opt_y || 0; }; /** * Returns a new copy of the coordinate. * @return {!Coordinate} A clone of this coordinate. */ Coordinate.prototype.clone = function() { return new Coordinate(this.x, this.y); }; /** * Returns a nice string representing the coordinate. * @return {string} In the form (50, 73). * @override */ Coordinate.prototype.toString = function() { return '(' + this.x + ', ' + this.y + ')'; }; Coordinate.prototype.toJSON = function() { return [this.x, this.y]; }; /** * Compares coordinates for equality. * @param {Coordinate} a A Coordinate. * @param {Coordinate} b A Coordinate. * @return {boolean} True iff the coordinates are equal, or if both are null. */ Coordinate.equals = function(a, b) { if (a == b) { return true; } if (!a || !b) { return false; } return a.x == b.x && a.y == b.y; }; /** * Returns the distance between two coordinates. * @param {!Coordinate} a A Coordinate. * @param {!Coordinate} b A Coordinate. * @return {number} The distance between {@code a} and {@code b}. */ Coordinate.distance = function(a, b) { var dx = a.x - b.x; var dy = a.y - b.y; return Math.sqrt(dx * dx + dy * dy); }; /** * Returns the magnitude of a coordinate. * @param {!Coordinate} a A Coordinate. * @return {number} The distance between the origin and {@code a}. */ Coordinate.magnitude = function(a) { return Math.sqrt(a.x * a.x + a.y * a.y); }; /** * Returns the angle from the origin to a coordinate. * @param {!Coordinate} a A Coordinate. * @return {number} The angle, in degrees, clockwise from the positive X * axis to {@code a}. */ Coordinate.azimuth = function(a) { return XMath.angle(0, 0, a.x, a.y); }; /** * Returns the squared distance between two coordinates. Squared distances can * be used for comparisons when the actual value is not required. * * Performance note: eliminating the square root is an optimization often used * in lower-level languages, but the speed difference is not nearly as * pronounced in JavaScript (only a few percent.) * * @param {!Coordinate} a A Coordinate. * @param {!Coordinate} b A Coordinate. * @return {number} The squared distance between {@code a} and {@code b}. */ Coordinate.squaredDistance = function(a, b) { var dx = a.x - b.x; var dy = a.y - b.y; return dx * dx + dy * dy; }; /** * Returns the difference between two coordinates as a new * Coordinate. * @param {!Coordinate} a A Coordinate. * @param {!Coordinate} b A Coordinate. * @return {!Coordinate} A Coordinate representing the difference * between {@code a} and {@code b}. */ Coordinate.difference = function(a, b) { return new Coordinate(a.x - b.x, a.y - b.y); }; /** * Returns the sum of two coordinates as a new Coordinate. * @param {!Coordinate} a A Coordinate. * @param {!Coordinate} b A Coordinate. * @return {!Coordinate} A Coordinate representing the sum of the two * coordinates. */ Coordinate.sum = function(a, b) { return new Coordinate(a.x + b.x, a.y + b.y); }; /** * Rounds the x and y fields to the next larger integer values. * @return {!Coordinate} This coordinate with ceil'd fields. */ Coordinate.prototype.ceil = function() { this.x = Math.ceil(this.x); this.y = Math.ceil(this.y); return this; }; /** * Rounds the x and y fields to the next smaller integer values. * @return {!Coordinate} This coordinate with floored fields. */ Coordinate.prototype.floor = function() { this.x = Math.floor(this.x); this.y = Math.floor(this.y); return this; }; /** * Rounds the x and y fields to the nearest integer values. * @return {!Coordinate} This coordinate with rounded fields. */ Coordinate.prototype.round = function() { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; }; /** * Translates this box by the given offsets. If a {@code Coordinate} * is given, then the x and y values are translated by the coordinate's x and y. * Otherwise, x and y are translated by {@code tx} and {@code opt_ty} * respectively. * @param {number|Coordinate} tx The value to translate x by or the * the coordinate to translate this coordinate by. * @param {number=} opt_ty The value to translate y by. * @return {!Coordinate} This coordinate after translating. */ Coordinate.prototype.translate = function(tx, opt_ty) { if (tx instanceof Coordinate) { this.x += tx.x; this.y += tx.y; } else { this.x += tx; if (XMath.isNumber(opt_ty)) { this.y += opt_ty; } } return this; }; /** * Scales this coordinate by the given scale factors. The x and y values are * scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} * is not given, then {@code sx} is used for both x and y. * @param {number} sx The scale factor to use for the x dimension. * @param {number=} opt_sy The scale factor to use for the y dimension. * @return {!Coordinate} This coordinate after scaling. */ Coordinate.prototype.scale = function(sx, opt_sy) { var sy = XMath.isNumber(opt_sy) ? opt_sy : sx; this.x *= sx; this.y *= sy; return this; }; // Originally in Vec2 // http://docs.closure-library.googlecode.com/git/closure_goog_math_vec2.js.source.html /** * @return {!Coordinate} A random unit-length vector. */ Coordinate.randomUnit = function() { var angle = Math.random() * Math.PI * 2; return new Coordinate(Math.cos(angle), Math.sin(angle)); }; /** * @return {!Coordinate} A random vector inside the unit-disc. */ Coordinate.random = function() { var mag = Math.sqrt(Math.random()); var angle = Math.random() * Math.PI * 2; return new Coordinate(Math.cos(angle) * mag, Math.sin(angle) * mag); }; /** * @return {!Coordinate} A random vector with x and y between 0 and 1. */ Coordinate.randomPositive = function() { return new Coordinate(Math.random(), Math.random()); }; /** * @return {!Coordinate} A new vector with the same coordinates as this one. * @override */ Coordinate.prototype.clone = function() { return new Coordinate(this.x, this.y); }; /** * Returns the magnitude of the vector measured from the origin. * @return {number} The length of the vector. */ Coordinate.prototype.magnitude = function() { return Math.sqrt(this.x * this.x + this.y * this.y); }; /** * Returns the squared magnitude of the vector measured from the origin. * NOTE(brenneman): Leaving out the square root is not a significant * optimization in JavaScript. * @return {number} The length of the vector, squared. */ Coordinate.prototype.squaredMagnitude = function() { return this.x * this.x + this.y * this.y; }; /** * Reverses the sign of the vector. Equivalent to scaling the vector by -1. * @return {!Coordinate} The inverted vector. */ Coordinate.prototype.invert = function() { this.x = -this.x; this.y = -this.y; return this; }; /** * Normalizes the current vector to have a magnitude of 1. * @return {!Coordinate} The normalized vector. */ Coordinate.prototype.normalize = function() { return this.scale(1 / this.magnitude()); }; /** * Adds another vector to this vector in-place. * @param {!Coordinate} b The vector to add. * @return {!Coordinate} This vector with {@code b} added. */ Coordinate.prototype.add = function(b) { this.x += b.x; this.y += b.y; return this; }; /** * Subtracts another vector from this vector in-place. * @param {!Coordinate} b The vector to subtract. * @return {!Coordinate} This vector with {@code b} subtracted. */ Coordinate.prototype.subtract = function(b) { this.x -= b.x; this.y -= b.y; return this; }; /** * Multiplies each value of this vector with corresponding value of another * vector. * @param {!Coordinate} b The vector to multiply with. * @return {!Coordinate} This vector with {@code b} multiplied with. */ Coordinate.prototype.multiply = function(b) { this.x *= b.x; this.y *= b.y; return this; }; /** * Rotates this vector in-place by a given angle, specified in radians. * @param {number} angle The angle, in radians. * @return {!Coordinate} This vector rotated {@code angle} radians. */ Coordinate.prototype.rotate = function(angle) { var cos = Math.cos(angle); var sin = Math.sin(angle); var newX = this.x * cos - this.y * sin; var newY = this.y * cos + this.x * sin; this.x = newX; this.y = newY; return this; }; /** * Rotates a vector by a given angle, specified in radians, relative to a given * axis rotation point. The returned vector is a newly created instance - no * in-place changes are done. * @param {!Coordinate} v A vector. * @param {!Coordinate} axisPoint The rotation axis point. * @param {number} angle The angle, in radians. * @return {!Coordinate} The rotated vector in a newly created instance. */ Coordinate.rotateAroundPoint = function(v, axisPoint, angle) { var res = v.clone(); return res.subtract(axisPoint).rotate(angle).add(axisPoint); }; /** * Compares this vector with another for equality. * @param {!Coordinate} b The other vector. * @return {boolean} Whether this vector has the same x and y as the given * vector. */ Coordinate.prototype.equals = function(b) { return this == b || !!b && this.x == b.x && this.y == b.y; }; /** * Sets each value of this coordinate to min of the value and the * corresponding one in given coordinate. * @param {!Coordinate} b A coordinate. * @return {!Coordinate} This coordinate as a minimal coordinate combination * with b. */ Coordinate.prototype.min = function(b) { this.x = Math.min(this.x, b.x); this.y = Math.min(this.y, b.y); return this; }; /** * Sets each value of this coordinate to max of the value and the * corresponding one in given coordinate. * @param {!Coordinate} b The second coordinate. * @return {!Coordinate} This coordinate as a maximal coordinate combination * with b. */ Coordinate.prototype.max = function(b) { this.x = Math.max(this.x, b.x); this.y = Math.max(this.y, b.y); return this; }; /** * Clamps this coordinate within the provided bounds. * @param {!Coordinate} min The minimum coordinates to return. * @param {!Coordinate} max The maximum coordinates to return. * @return {!Coordinate} This coordinate within bounds or * clamped to the nearest coordinate within the bounds. */ Coordinate.prototype.clamp = function(min, max) { this.x = Math.min(Math.max(this.x, min.x), max.x); this.y = Math.min(Math.max(this.y, min.y), max.y); return this; }; /** * Returns the dot-product of two vectors. * @param {!Coordinate} a The first vector. * @param {!Coordinate} b The second vector. * @return {number} The dot-product of the two vectors. */ Coordinate.dot = function(a, b) { return a.x * b.x + a.y * b.y; }; /** * Returns a new Vec2 that is the linear interpolant between vectors a and b at * scale-value x. * @param {!Coordinate} a Vector a. * @param {!Coordinate} b Vector b. * @param {number} x The proportion between a and b. * @return {!Coordinate} The interpolated vector. */ Coordinate.lerp = function(a, b, x) { return new Coordinate(XMath.lerp(a.x, b.x, x), XMath.lerp(a.y, b.y, x)); }; /** * Takes two coordinates and returns a new coordinate with x and y set to * max of the originals' x and y. * @param {!Coordinate} a The first coordinate. * @param {!Coordinate} b The second coordinate. * @return {!Coordinate} A maximal coordinate combination of a and b. */ Coordinate.max = function(a, b) { return new Coordinate(Math.max(a.x, b.x), Math.max(a.y, b.y)); }; /** * Takes two coordinates and returns a new coordinate with x and y set to * min of the originals' x and y. * @param {!Coordinate} a The first coordinate. * @param {!Coordinate} b The second coordinate. * @return {!Coordinate} A minimal coordinate combination of a and b. */ Coordinate.min = function(a, b) { return new Coordinate(Math.min(a.x, b.x), Math.min(a.y, b.y)); }; /** * Takes a coordinate and clamps it to within the provided bounds. * @param {!Coordinate} a The input coordinate. * @param {!Coordinate} min The minimum coordinates to return. * @param {!Coordinate} max The maximum coordinates to return. * @return {!Coordinate} The input coordinate if it is within bounds, or * the nearest coordinate within the bounds. */ Coordinate.clamp = function(a, min, max) { return new Coordinate(Math.min(Math.max(a.x, min.x), max.x), Math.min(Math.max(a.y, min.y), max.y)); }; module.exports = Coordinate;