polygon.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import H from '../parts/Globals.js';
  2. import '../parts/Utilities.js';
  3. var deg2rad = H.deg2rad,
  4. find = H.find,
  5. isArray = H.isArray,
  6. isNumber = H.isNumber;
  7. /**
  8. * Alternative solution to correctFloat.
  9. * E.g H.correctFloat(123, 2) returns 120, when it should be 123.
  10. *
  11. * @private
  12. * @function correctFloat
  13. *
  14. * @param {number} number
  15. *
  16. * @param {number} precision
  17. *
  18. * @return {number}
  19. */
  20. var correctFloat = function (number, precision) {
  21. var p = isNumber(precision) ? precision : 14,
  22. magnitude = Math.pow(10, p);
  23. return Math.round(number * magnitude) / magnitude;
  24. };
  25. /**
  26. * Calculates the normals to a line between two points.
  27. *
  28. * @private
  29. * @function getNormals
  30. *
  31. * @param {Array<number,number>} p1
  32. * Start point for the line. Array of x and y value.
  33. *
  34. * @param {Array<number,number>} p2
  35. * End point for the line. Array of x and y value.
  36. *
  37. * @return {Array<Array<number,number>>}
  38. * Returns the two normals in an array.
  39. */
  40. var getNormals = function getNormal(p1, p2) {
  41. var dx = p2[0] - p1[0], // x2 - x1
  42. dy = p2[1] - p1[1]; // y2 - y1
  43. return [
  44. [-dy, dx],
  45. [dy, -dx]
  46. ];
  47. };
  48. /**
  49. * Calculates the dot product of two coordinates. The result is a scalar value.
  50. *
  51. * @private
  52. * @function dotProduct
  53. *
  54. * @param {Array<number,number>} a
  55. * The x and y coordinates of the first point.
  56. *
  57. * @param {Array<number,number>} b
  58. * The x and y coordinates of the second point.
  59. *
  60. * @return {number}
  61. * Returns the dot product of a and b.
  62. */
  63. var dotProduct = function dotProduct(a, b) {
  64. var ax = a[0],
  65. ay = a[1],
  66. bx = b[0],
  67. by = b[1];
  68. return ax * bx + ay * by;
  69. };
  70. /**
  71. * Projects a polygon onto a coordinate.
  72. *
  73. * @private
  74. * @function project
  75. *
  76. * @param {Array<Array<number,number>>} polygon
  77. * Array of points in a polygon.
  78. *
  79. * @param {Array<number,number>} target
  80. * The coordinate of pr
  81. *
  82. * @return {object}
  83. */
  84. var project = function project(polygon, target) {
  85. var products = polygon.map(function (point) {
  86. return dotProduct(point, target);
  87. });
  88. return {
  89. min: Math.min.apply(this, products),
  90. max: Math.max.apply(this, products)
  91. };
  92. };
  93. /**
  94. * Rotates a point clockwise around the origin.
  95. *
  96. * @private
  97. * @function rotate2DToOrigin
  98. *
  99. * @param {Array<number,number>} point
  100. * The x and y coordinates for the point.
  101. *
  102. * @param {number} angle
  103. * The angle of rotation.
  104. *
  105. * @return {Array<number,number>}
  106. * The x and y coordinate for the rotated point.
  107. */
  108. var rotate2DToOrigin = function (point, angle) {
  109. var x = point[0],
  110. y = point[1],
  111. rad = deg2rad * -angle,
  112. cosAngle = Math.cos(rad),
  113. sinAngle = Math.sin(rad);
  114. return [
  115. correctFloat(x * cosAngle - y * sinAngle),
  116. correctFloat(x * sinAngle + y * cosAngle)
  117. ];
  118. };
  119. /**
  120. * Rotate a point clockwise around another point.
  121. *
  122. * @private
  123. * @function rotate2DToPoint
  124. *
  125. * @param {Array<number,number>} point
  126. * The x and y coordinates for the point.
  127. *
  128. * @param {Array<number,numbner>} origin
  129. * The point to rotate around.
  130. *
  131. * @param {number} angle
  132. * The angle of rotation.
  133. *
  134. * @return {Array<number,number>}
  135. * The x and y coordinate for the rotated point.
  136. */
  137. var rotate2DToPoint = function (point, origin, angle) {
  138. var x = point[0] - origin[0],
  139. y = point[1] - origin[1],
  140. rotated = rotate2DToOrigin([x, y], angle);
  141. return [
  142. rotated[0] + origin[0],
  143. rotated[1] + origin[1]
  144. ];
  145. };
  146. var isAxesEqual = function (axis1, axis2) {
  147. return (
  148. axis1[0] === axis2[0] &&
  149. axis1[1] === axis2[1]
  150. );
  151. };
  152. var getAxesFromPolygon = function (polygon) {
  153. var points,
  154. axes = polygon.axes;
  155. if (!isArray(axes)) {
  156. axes = [];
  157. points = points = polygon.concat([polygon[0]]);
  158. points.reduce(
  159. function findAxis(p1, p2) {
  160. var normals = getNormals(p1, p2),
  161. axis = normals[0]; // Use the left normal as axis.
  162. // Check that the axis is unique.
  163. if (!find(axes, function (existing) {
  164. return isAxesEqual(existing, axis);
  165. })) {
  166. axes.push(axis);
  167. }
  168. // Return p2 to be used as p1 in next iteration.
  169. return p2;
  170. }
  171. );
  172. polygon.axes = axes;
  173. }
  174. return axes;
  175. };
  176. var getAxes = function (polygon1, polygon2) {
  177. // Get the axis from both polygons.
  178. var axes1 = getAxesFromPolygon(polygon1),
  179. axes2 = getAxesFromPolygon(polygon2);
  180. return axes1.concat(axes2);
  181. };
  182. var getPolygon = function (x, y, width, height, rotation) {
  183. var origin = [x, y],
  184. left = x - (width / 2),
  185. right = x + (width / 2),
  186. top = y - (height / 2),
  187. bottom = y + (height / 2),
  188. polygon = [
  189. [left, top],
  190. [right, top],
  191. [right, bottom],
  192. [left, bottom]
  193. ];
  194. return polygon.map(function (point) {
  195. return rotate2DToPoint(point, origin, -rotation);
  196. });
  197. };
  198. var getBoundingBoxFromPolygon = function (points) {
  199. return points.reduce(function (obj, point) {
  200. var x = point[0],
  201. y = point[1];
  202. obj.left = Math.min(x, obj.left);
  203. obj.right = Math.max(x, obj.right);
  204. obj.bottom = Math.max(y, obj.bottom);
  205. obj.top = Math.min(y, obj.top);
  206. return obj;
  207. }, {
  208. left: Number.MAX_VALUE,
  209. right: -Number.MAX_VALUE,
  210. bottom: -Number.MAX_VALUE,
  211. top: Number.MAX_VALUE
  212. });
  213. };
  214. var isPolygonsOverlappingOnAxis = function (axis, polygon1, polygon2) {
  215. var projection1 = project(polygon1, axis),
  216. projection2 = project(polygon2, axis),
  217. isOverlapping = !(
  218. projection2.min > projection1.max ||
  219. projection2.max < projection1.min
  220. );
  221. return !isOverlapping;
  222. };
  223. /**
  224. * Checks wether two convex polygons are colliding by using the Separating Axis
  225. * Theorem.
  226. *
  227. * @private
  228. * @function isPolygonsColliding
  229. *
  230. * @param {Array<Array<number,number>>} polygon1
  231. * First polygon.
  232. *
  233. * @param {Array<Array<number,number>>} polygon2
  234. * Second polygon.
  235. *
  236. * @return {boolean}
  237. * Returns true if they are colliding, otherwise false.
  238. */
  239. var isPolygonsColliding = function isPolygonsColliding(polygon1, polygon2) {
  240. var axes = getAxes(polygon1, polygon2),
  241. overlappingOnAllAxes = !find(axes, function (axis) {
  242. return isPolygonsOverlappingOnAxis(axis, polygon1, polygon2);
  243. });
  244. return overlappingOnAllAxes;
  245. };
  246. var movePolygon = function (deltaX, deltaY, polygon) {
  247. return polygon.map(function (point) {
  248. return [
  249. point[0] + deltaX,
  250. point[1] + deltaY
  251. ];
  252. });
  253. };
  254. var collision = {
  255. getBoundingBoxFromPolygon: getBoundingBoxFromPolygon,
  256. getPolygon: getPolygon,
  257. isPolygonsColliding: isPolygonsColliding,
  258. movePolygon: movePolygon,
  259. rotate2DToOrigin: rotate2DToOrigin,
  260. rotate2DToPoint: rotate2DToPoint
  261. };
  262. export default collision;