shapes-utils.js (5831B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /** 8 * Get the distance between two points on a plane. 9 * 10 * @param {number} x1 the x coord of the first point 11 * @param {number} y1 the y coord of the first point 12 * @param {number} x2 the x coord of the second point 13 * @param {number} y2 the y coord of the second point 14 * @returns {number} the distance between the two points 15 */ 16 const getDistance = (x1, y1, x2, y2) => { 17 return Math.round(Math.hypot(x2 - x1, y2 - y1)); 18 }; 19 20 /** 21 * Determine if the given x/y coords are along the edge of the given ellipse. 22 * We allow for a small area around the edge that still counts as being on the edge. 23 * 24 * @param {number} x the x coordinate of the click 25 * @param {number} y the y coordinate of the click 26 * @param {number} cx the x coordinate of the center of the ellipse 27 * @param {number} cy the y coordinate of the center of the ellipse 28 * @param {number} rx the x radius of the ellipse 29 * @param {number} ry the y radius of the ellipse 30 * @param {number} clickWidthX the width of the area that counts as being on the edge 31 * along the x radius. 32 * @param {number} clickWidthY the width of the area that counts as being on the edge 33 * along the y radius. 34 * @returns {boolean} whether the click counts as being on the edge of the ellipse. 35 */ 36 const clickedOnEllipseEdge = ( 37 x, 38 y, 39 cx, 40 cy, 41 rx, 42 ry, 43 clickWidthX, 44 clickWidthY 45 ) => { 46 // The formula to determine if something is inside or on the edge of an ellipse is: 47 // (x - cx)^2/rx^2 + (y - cy)^2/ry^2 <= 1. If > 1, it's outside. 48 // We make two ellipses, adjusting rx and ry with clickWidthX and clickWidthY 49 // to allow for an area around the edge of the ellipse that can be clicked on. 50 // If the click was outside the inner ellipse and inside the outer ellipse, return true. 51 const inner = 52 (x - cx) ** 2 / (rx - clickWidthX) ** 2 + 53 (y - cy) ** 2 / (ry - clickWidthY) ** 2; 54 const outer = 55 (x - cx) ** 2 / (rx + clickWidthX) ** 2 + 56 (y - cy) ** 2 / (ry + clickWidthY) ** 2; 57 return inner >= 1 && outer <= 1; 58 }; 59 60 /** 61 * Get the distance between a point and a line defined by two other points. 62 * 63 * @param {number} x1 the x coordinate of the first point in the line 64 * @param {number} y1 the y coordinate of the first point in the line 65 * @param {number} x2 the x coordinate of the second point in the line 66 * @param {number} y2 the y coordinate of the second point in the line 67 * @param {number} x3 the x coordinate of the point for which the distance is found 68 * @param {number} y3 the y coordinate of the point for which the distance is found 69 * @returns {number} the distance between (x3,y3) and the line defined by 70 * (x1,y1) and (y1,y2) 71 */ 72 const distanceToLine = (x1, y1, x2, y2, x3, y3) => { 73 // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_two_points 74 const num = Math.abs((y2 - y1) * x3 - (x2 - x1) * y3 + x2 * y1 - y2 * x1); 75 const denom = getDistance(x1, y1, x2, y2); 76 return num / denom; 77 }; 78 79 /** 80 * Get the point on the line defined by points a,b that is closest to point c 81 * 82 * @param {number} ax the x coordinate of point a 83 * @param {number} ay the y coordinate of point a 84 * @param {number} bx the x coordinate of point b 85 * @param {number} by the y coordinate of point b 86 * @param {number} cx the x coordinate of point c 87 * @param {number} cy the y coordinate of point c 88 * @returns {Array} a 2 element array that contains the x/y coords of the projected point 89 */ 90 const projection = (ax, ay, bx, by, cx, cy) => { 91 // https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2 92 const ab = [bx - ax, by - ay]; 93 const ac = [cx - ax, cy - ay]; 94 const scalar = dotProduct(ab, ac) / dotProduct(ab, ab); 95 return [ax + scalar * ab[0], ay + scalar * ab[1]]; 96 }; 97 98 /** 99 * Get the dot product of two vectors, represented by arrays of numbers. 100 * 101 * @param {Array} a the first vector 102 * @param {Array} b the second vector 103 * @returns {number} the dot product of a and b 104 */ 105 const dotProduct = (a, b) => { 106 return a.reduce((prev, curr, i) => { 107 return prev + curr * b[i]; 108 }, 0); 109 }; 110 111 /** 112 * Determine if the given x/y coords are above the given point. 113 * 114 * @param {number} x the x coordinate of the click 115 * @param {number} y the y coordinate of the click 116 * @param {number} pointX the x coordinate of the center of the point 117 * @param {number} pointY the y coordinate of the center of the point 118 * @param {number} radiusX the x radius of the point 119 * @param {number} radiusY the y radius of the point 120 * @returns {boolean} whether the click was on the point 121 */ 122 const clickedOnPoint = (x, y, pointX, pointY, radiusX, radiusY) => { 123 return ( 124 x >= pointX - radiusX && 125 x <= pointX + radiusX && 126 y >= pointY - radiusY && 127 y <= pointY + radiusY 128 ); 129 }; 130 131 const roundTo = (value, exp) => { 132 // If the exp is undefined or zero... 133 if (typeof exp === "undefined" || +exp === 0) { 134 return Math.round(value); 135 } 136 value = +value; 137 exp = +exp; 138 // If the value is not a number or the exp is not an integer... 139 if (isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) { 140 return NaN; 141 } 142 // Shift 143 value = value.toString().split("e"); 144 value = Math.round(+(value[0] + "e" + (value[1] ? +value[1] - exp : -exp))); 145 // Shift back 146 value = value.toString().split("e"); 147 return +(value[0] + "e" + (value[1] ? +value[1] + exp : exp)); 148 }; 149 150 exports.getDistance = getDistance; 151 exports.clickedOnEllipseEdge = clickedOnEllipseEdge; 152 exports.distanceToLine = distanceToLine; 153 exports.projection = projection; 154 exports.clickedOnPoint = clickedOnPoint; 155 exports.roundTo = roundTo;