/** * A standalone point geometry with useful accessor, comparison, and * modification methods. * * @class * @param {number} x the x-coordinate. This could be longitude or screen pixels, or any other sort of unit. * @param {number} y the y-coordinate. This could be latitude or screen pixels, or any other sort of unit. * * @example * const point = new Point(-77, 38); */ export default function Point(x, y) { this.x = x; this.y = y; } Point.prototype = { /** * Clone this point, returning a new point that can be modified * without affecting the old one. * @return {Point} the clone */ clone() { return new Point(this.x, this.y); }, /** * Add this point's x & y coordinates to another point, * yielding a new point. * @param {Point} p the other point * @return {Point} output point */ add(p) { return this.clone()._add(p); }, /** * Subtract this point's x & y coordinates to from point, * yielding a new point. * @param {Point} p the other point * @return {Point} output point */ sub(p) { return this.clone()._sub(p); }, /** * Multiply this point's x & y coordinates by point, * yielding a new point. * @param {Point} p the other point * @return {Point} output point */ multByPoint(p) { return this.clone()._multByPoint(p); }, /** * Divide this point's x & y coordinates by point, * yielding a new point. * @param {Point} p the other point * @return {Point} output point */ divByPoint(p) { return this.clone()._divByPoint(p); }, /** * Multiply this point's x & y coordinates by a factor, * yielding a new point. * @param {number} k factor * @return {Point} output point */ mult(k) { return this.clone()._mult(k); }, /** * Divide this point's x & y coordinates by a factor, * yielding a new point. * @param {number} k factor * @return {Point} output point */ div(k) { return this.clone()._div(k); }, /** * Rotate this point around the 0, 0 origin by an angle a, * given in radians * @param {number} a angle to rotate around, in radians * @return {Point} output point */ rotate(a) { return this.clone()._rotate(a); }, /** * Rotate this point around p point by an angle a, * given in radians * @param {number} a angle to rotate around, in radians * @param {Point} p Point to rotate around * @return {Point} output point */ rotateAround(a, p) { return this.clone()._rotateAround(a, p); }, /** * Multiply this point by a 4x1 transformation matrix * @param {[number, number, number, number]} m transformation matrix * @return {Point} output point */ matMult(m) { return this.clone()._matMult(m); }, /** * Calculate this point but as a unit vector from 0, 0, meaning * that the distance from the resulting point to the 0, 0 * coordinate will be equal to 1 and the angle from the resulting * point to the 0, 0 coordinate will be the same as before. * @return {Point} unit vector point */ unit() { return this.clone()._unit(); }, /** * Compute a perpendicular point, where the new y coordinate * is the old x coordinate and the new x coordinate is the old y * coordinate multiplied by -1 * @return {Point} perpendicular point */ perp() { return this.clone()._perp(); }, /** * Return a version of this point with the x & y coordinates * rounded to integers. * @return {Point} rounded point */ round() { return this.clone()._round(); }, /** * Return the magnitude of this point: this is the Euclidean * distance from the 0, 0 coordinate to this point's x and y * coordinates. * @return {number} magnitude */ mag() { return Math.sqrt(this.x * this.x + this.y * this.y); }, /** * Judge whether this point is equal to another point, returning * true or false. * @param {Point} other the other point * @return {boolean} whether the points are equal */ equals(other) { return this.x === other.x && this.y === other.y; }, /** * Calculate the distance from this point to another point * @param {Point} p the other point * @return {number} distance */ dist(p) { return Math.sqrt(this.distSqr(p)); }, /** * Calculate the distance from this point to another point, * without the square root step. Useful if you're comparing * relative distances. * @param {Point} p the other point * @return {number} distance */ distSqr(p) { const dx = p.x - this.x, dy = p.y - this.y; return dx * dx + dy * dy; }, /** * Get the angle from the 0, 0 coordinate to this point, in radians * coordinates. * @return {number} angle */ angle() { return Math.atan2(this.y, this.x); }, /** * Get the angle from this point to another point, in radians * @param {Point} b the other point * @return {number} angle */ angleTo(b) { return Math.atan2(this.y - b.y, this.x - b.x); }, /** * Get the angle between this point and another point, in radians * @param {Point} b the other point * @return {number} angle */ angleWith(b) { return this.angleWithSep(b.x, b.y); }, /** * Find the angle of the two vectors, solving the formula for * the cross product a x b = |a||b|sin(θ) for θ. * @param {number} x the x-coordinate * @param {number} y the y-coordinate * @return {number} the angle in radians */ angleWithSep(x, y) { return Math.atan2( this.x * y - this.y * x, this.x * x + this.y * y); }, /** @param {[number, number, number, number]} m */ _matMult(m) { const x = m[0] * this.x + m[1] * this.y, y = m[2] * this.x + m[3] * this.y; this.x = x; this.y = y; return this; }, /** @param {Point} p */ _add(p) { this.x += p.x; this.y += p.y; return this; }, /** @param {Point} p */ _sub(p) { this.x -= p.x; this.y -= p.y; return this; }, /** @param {number} k */ _mult(k) { this.x *= k; this.y *= k; return this; }, /** @param {number} k */ _div(k) { this.x /= k; this.y /= k; return this; }, /** @param {Point} p */ _multByPoint(p) { this.x *= p.x; this.y *= p.y; return this; }, /** @param {Point} p */ _divByPoint(p) { this.x /= p.x; this.y /= p.y; return this; }, _unit() { this._div(this.mag()); return this; }, _perp() { const y = this.y; this.y = this.x; this.x = -y; return this; }, /** @param {number} angle */ _rotate(angle) { const cos = Math.cos(angle), sin = Math.sin(angle), x = cos * this.x - sin * this.y, y = sin * this.x + cos * this.y; this.x = x; this.y = y; return this; }, /** * @param {number} angle * @param {Point} p */ _rotateAround(angle, p) { const cos = Math.cos(angle), sin = Math.sin(angle), x = p.x + cos * (this.x - p.x) - sin * (this.y - p.y), y = p.y + sin * (this.x - p.x) + cos * (this.y - p.y); this.x = x; this.y = y; return this; }, _round() { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; }, constructor: Point }; /** * Construct a point from an array if necessary, otherwise if the input * is already a Point, return it unchanged. * @param {Point | [number, number] | {x: number, y: number}} p input value * @return {Point} constructed point. * @example * // this * var point = Point.convert([0, 1]); * // is equivalent to * var point = new Point(0, 1); */ Point.convert = function (p) { if (p instanceof Point) { return /** @type {Point} */ (p); } if (Array.isArray(p)) { return new Point(+p[0], +p[1]); } if (p.x !== undefined && p.y !== undefined) { return new Point(+p.x, +p.y); } throw new Error('Expected [x, y] or {x, y} point format'); };