Spice/AABB.js

  1. /**
  2. * @class
  3. * Axis-aligned bounding boxes with the constraint that they are cubes with equal sides.
  4. * @property {Lore.Vector3f} center - The center of this axis-aligned bounding box.
  5. * @property {number} radius - The radius of this axis-aligned bounding box.
  6. * @property {number} locCode - The location code of this axis-aligned bounding box in the octree.
  7. * @property {number} left - The distance of the left plane to the world ZY plane.
  8. * @property {number} right - The distance of the right plane to the world ZY plane.
  9. * @property {number} back - The distance of the back plane to the world XY plane.
  10. * @property {number} front - The distance of the front plane to the world XY plane.
  11. * @property {number} bottom - The distance of the bottom plane to the world XZ plane.
  12. * @property {number} top - The distance of the top plane to the world XZ plane.
  13. * @property {Array} neighbours - The neighbours of this axis-aligned bounding box in an an octree.
  14. * @property {Float32Array} min - An array specifying the minimum corner point (x, y, z) of the axis-aligned bounding box.
  15. * @property {Float32Array} max - An array specifying the maximum corner point (x, y, z) of the axis-aligned bounding box.
  16. * @constructor
  17. * @param {Lore.Vector3f} center - A radius for this axis-aligned bounding box.
  18. * @param {number} radius - A radius for this axis-aligned bounding box.
  19. */
  20. Lore.AABB = class AABB {
  21. constructor(center, radius) {
  22. this.center = center || new Lore.Vector3f();
  23. this.radius = radius || 0;
  24. this.locCode = 0;
  25. this.left = 0;
  26. this.right = 0;
  27. this.back = 0;
  28. this.front = 0;
  29. this.bottom = 0;
  30. this.top = 0;
  31. this.neighbours = new Array(6);
  32. this.min = new Float32Array(3);
  33. this.max = new Float32Array(3);
  34. this.updateDimensions();
  35. }
  36. /**
  37. * Calculates the distance of the axis-aligned bounding box's planes to the world planes.
  38. */
  39. updateDimensions() {
  40. let cx = this.center.components[0];
  41. let cy = this.center.components[1];
  42. let cz = this.center.components[2];
  43. this.min[0] = cx - this.radius;
  44. this.min[1] = cy - this.radius;
  45. this.min[2] = cz - this.radius;
  46. this.max[0] = cx + this.radius;
  47. this.max[1] = cy + this.radius;
  48. this.max[2] = cz + this.radius;
  49. // Precalculate to simplify ray test
  50. this.left = cx - this.radius;
  51. this.right = cx + this.radius;
  52. this.back = cz - this.radius;
  53. this.front = cz + this.radius;
  54. this.bottom = cy - this.radius;
  55. this.top = cy + this.radius;
  56. return this;
  57. }
  58. /**
  59. * Sets the location code of this axis-aligned bounding box.
  60. *
  61. * @param {number} locCode - The location code.
  62. */
  63. setLocCode(locCode) {
  64. this.locCode = locCode;
  65. return this;
  66. }
  67. /**
  68. * Gets the location code of this axis-aligned bounding box.
  69. *
  70. * @returns {number} The location code.
  71. */
  72. getLocCode() {
  73. return this.locCode;
  74. }
  75. /**
  76. * Tests whether or not this axis-aligned bounding box is intersected by a ray.
  77. *
  78. * @param {Lore.Vector3f} source - The source of the ray.
  79. * @param {Lore.Vector3f} dir - A normalized vector of the direction of the ray.
  80. * @param {number} dist - The maximum distance from the source that still counts as an intersect (the far property of the Lore.Raycaster object).
  81. * @returns {boolean} - Whether or not there is an intersect.
  82. */
  83. rayTest(source, inverseDir, dist) {
  84. // dir is the precomputed inverse of the direction of the ray,
  85. // this means that the costly divisions can be omitted
  86. let oc = source.components;
  87. let ic = inverseDir.components;
  88. let t0 = (this.left - oc[0]) * ic[0];
  89. let t1 = (this.right - oc[0]) * ic[0];
  90. let t2 = (this.bottom - oc[1]) * ic[1];
  91. let t3 = (this.top - oc[1]) * ic[1];
  92. let t4 = (this.back - oc[2]) * ic[2];
  93. let t5 = (this.front - oc[2]) * ic[2];
  94. let maxT = Math.min(Math.max(t0, t1), Math.max(t2, t3), Math.max(t4, t5));
  95. // Ray intersects in reverse direction, which means
  96. // that the box is behind the camera
  97. if (maxT < 0) {
  98. return false;
  99. }
  100. let minT = Math.max(Math.min(t0, t1), Math.min(t2, t3), Math.min(t4, t5));
  101. if (minT > maxT || minT > dist) {
  102. return false;
  103. }
  104. // Intersection happens when minT is larger or equal to maxT
  105. // and minT is smaller than the distance (distance == radius == ray.far)
  106. return true;
  107. }
  108. /**
  109. * Tests whether or not this axis-aligned bounding box is intersected by a cylinder. CAUTION: If this runs multi-threaded, it might fail.
  110. *
  111. * @param {Lore.Vector3f} source - The source of the ray.
  112. * @param {Lore.Vector3f} dir - A normalized vector of the direction of the ray.
  113. * @param {number} dist - The maximum distance from the source that still counts as an intersect (the far property of the Lore.Raycaster object).
  114. * @param {number} radius - The radius of the cylinder
  115. * @returns {boolean} - Whether or not there is an intersect.
  116. */
  117. cylinderTest(source, inverseDir, dist, radius) {
  118. // Instead of testing an actual cylinder against this aabb, we simply
  119. // expand the radius of the box temporarily.
  120. this.radius += radius;
  121. this.updateDimensions();
  122. // Do the normal ray intersection test
  123. let result = this.rayTest(source, inverseDir, dist);
  124. this.radius -= radius;
  125. this.updateDimensions();
  126. return result;
  127. }
  128. /**
  129. * Returns the square distance of this axis-aligned bounding box to the point supplied as an argument.
  130. *
  131. * @param {number} x - The x component of the point coordinate.
  132. * @param {number} y - The y component of the point coordinate.
  133. * @param {number} z - The z component of the point coordinate.
  134. * @returns {number} The square distance of this axis-aligned bounding box to the input point.
  135. */
  136. distanceToPointSq(x, y, z) {
  137. // From book, real time collision detection
  138. let sqDist = 0;
  139. let p = [x, y, z];
  140. // Add the distances for each axis
  141. for (var i = 0; i < 3; i++) {
  142. if (p[i] < this.min[i])
  143. sqDist += Math.pow(this.min[i] - p[i], 2);
  144. if (p[i] > this.max[i])
  145. sqDist += Math.pow(p[i] - this.max[i], 2);
  146. }
  147. return sqDist;
  148. }
  149. /**
  150. * Returns the box that is closest to the point (measured from center).
  151. *
  152. * @param {number} x - The x component of the point coordinate.
  153. * @param {number} y - The y component of the point coordinate.
  154. * @param {number} z - The z component of the point coordinate.
  155. * @returns {number} The square distance of this axis-aligned bounding box to the input point.
  156. */
  157. distanceFromCenterToPointSq(x, y, z) {
  158. let center = this.center.components;
  159. return Math.pow(center[0] - x, 2) + Math.pow(center[1] - y, 2) + Math.pow(center[2] - z, 2);
  160. }
  161. /**
  162. * Tests whether or not this axis-aligned bounding box overlaps or shares an edge or a vertex with another axis-aligned bounding box.
  163. * This method can also be used to assert whether or not two boxes are neighbours.
  164. *
  165. * @param {Lore.AABB} aabb - The axis-aligned bounding box to test against.
  166. * @returns {boolean} - Whether or not there is an overlap.
  167. */
  168. testAABB(aabb) {
  169. for (var i = 0; i < 3; i++) {
  170. if (this.max[i] < aabb.min[i] || this.min[i] > aabb.max[i]) {
  171. return false;
  172. }
  173. }
  174. return true;
  175. }
  176. /**
  177. * Creates a axis-aligned bounding box surrounding a set of vertices.
  178. *
  179. * @param {Uint32Array} vertices - The vertices which will all be inside the axis-aligned bounding box.
  180. * @returns {Lore.AABB} An axis-aligned bounding box surrounding the vertices.
  181. */
  182. static fromPoints(vertices) {
  183. let x = vertices[0];
  184. let y = vertices[1];
  185. let z = vertices[2];
  186. let min = new Lore.Vector3f(x, y, z);
  187. let max = new Lore.Vector3f(x, y, z);
  188. let minc = min.components;
  189. let maxc = max.components;
  190. for (var i = 1; i < vertices.length / 3; i++) {
  191. if (vertices[i * 3 + 0] < minc[0]) minc[0] = vertices[i * 3 + 0];
  192. if (vertices[i * 3 + 1] < minc[1]) minc[1] = vertices[i * 3 + 1];
  193. if (vertices[i * 3 + 2] < minc[2]) minc[2] = vertices[i * 3 + 2];
  194. if (vertices[i * 3 + 0] > maxc[0]) maxc[0] = vertices[i * 3 + 0];
  195. if (vertices[i * 3 + 1] > maxc[1]) maxc[1] = vertices[i * 3 + 1];
  196. if (vertices[i * 3 + 2] > maxc[2]) maxc[2] = vertices[i * 3 + 2];
  197. }
  198. // Calculate the radius in each direction
  199. let radii = new Lore.Vector3f.subtract(max, min);
  200. radii.multiplyScalar(0.5);
  201. let rx = radii.components[0];
  202. let ry = radii.components[1];
  203. let rz = radii.components[2];
  204. let center = new Lore.Vector3f(rx, ry, rz);
  205. center.add(min);
  206. // Since the octree always stores cubes, there is of course only
  207. // one radius - take the biggest one
  208. let radius = Math.max(rx, ry, rz);
  209. return new Lore.AABB(center, radius);
  210. }
  211. /**
  212. * Returns an array representing the 8 corners of the axis-aligned bounding box.
  213. *
  214. * @param {Lore.AABB} aabb An axis-aligned bounding box.
  215. * @returns {Array} An array containing the 8 corners of the axisa-aligned bunding box. E.g [[x, y, z], [x, y, z], ...]
  216. */
  217. static getCorners(aabb) {
  218. let c = aabb.center.components;
  219. let x = c[0];
  220. let y = c[1];
  221. let z = c[2];
  222. let r = aabb.radius;
  223. return [
  224. [x - r, y - r, z - r],
  225. [x - r, y - r, z + r],
  226. [x - r, y + r, z - r],
  227. [x - r, y + r, z + r],
  228. [x + r, y - r, z - r],
  229. [x + r, y - r, z + r],
  230. [x + r, y + r, z - r],
  231. [x + r, y + r, z + r]
  232. ]
  233. }
  234. /**
  235. * Clones an axis-aligned bounding box.
  236. *
  237. * @param {Lore.AABB} original - The axis-aligned bounding box to be cloned.
  238. * @returns {Lore.AABB} The cloned axis-aligned bounding box.
  239. */
  240. static clone(original) {
  241. let clone = new Lore.AABB();
  242. clone.back = original.back;
  243. clone.bottom = original.bottom;
  244. clone.center = new Lore.Vector3f(original.center.components[0],
  245. original.center.components[1], original.center.components[2]);
  246. clone.front = original.front;
  247. clone.left = original.left;
  248. clone.locCode = original.locCode;
  249. clone.max = original.max;
  250. clone.min = original.min;
  251. clone.radius = original.radius;
  252. clone.right = original.right;
  253. clone.top = original.top;
  254. return clone;
  255. }
  256. }