aframe-aabb-collider-component.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. /******/ (function(modules) { // webpackBootstrap
  2. /******/ // The module cache
  3. /******/ var installedModules = {};
  4. /******/ // The require function
  5. /******/ function __webpack_require__(moduleId) {
  6. /******/ // Check if module is in cache
  7. /******/ if(installedModules[moduleId])
  8. /******/ return installedModules[moduleId].exports;
  9. /******/ // Create a new module (and put it into the cache)
  10. /******/ var module = installedModules[moduleId] = {
  11. /******/ exports: {},
  12. /******/ id: moduleId,
  13. /******/ loaded: false
  14. /******/ };
  15. /******/ // Execute the module function
  16. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  17. /******/ // Flag the module as loaded
  18. /******/ module.loaded = true;
  19. /******/ // Return the exports of the module
  20. /******/ return module.exports;
  21. /******/ }
  22. /******/ // expose the modules object (__webpack_modules__)
  23. /******/ __webpack_require__.m = modules;
  24. /******/ // expose the module cache
  25. /******/ __webpack_require__.c = installedModules;
  26. /******/ // __webpack_public_path__
  27. /******/ __webpack_require__.p = "";
  28. /******/ // Load entry module and return exports
  29. /******/ return __webpack_require__(0);
  30. /******/ })
  31. /************************************************************************/
  32. /******/ ([
  33. /* 0 */
  34. /***/ (function(module, exports) {
  35. /* global AFRAME, THREE */
  36. if (typeof AFRAME === 'undefined') {
  37. throw new Error('Component attempted to register before AFRAME was available.');
  38. }
  39. // Configuration for the MutationObserver used to refresh the whitelist.
  40. // Listens for addition/removal of elements and attributes within the scene.
  41. var OBSERVER_CONFIG = {
  42. childList: true,
  43. attributes: true,
  44. subtree: true
  45. };
  46. /**
  47. * Implement AABB collision detection for entities with a mesh.
  48. * https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box
  49. *
  50. * @property {string} objects - Selector of entities to test for collision.
  51. */
  52. AFRAME.registerComponent('aabb-collider', {
  53. schema: {
  54. collideNonVisible: {default: false},
  55. debug: {default: false},
  56. enabled: {default: true},
  57. interval: {default: 80},
  58. objects: {default: ''}
  59. },
  60. init: function () {
  61. this.centerDifferenceVec3 = new THREE.Vector3();
  62. this.clearedIntersectedEls = [];
  63. this.closestIntersectedEl = null;
  64. this.boundingBox = new THREE.Box3();
  65. this.boxCenter = new THREE.Vector3();
  66. this.boxHelper = new THREE.BoxHelper();
  67. this.boxMax = new THREE.Vector3();
  68. this.boxMin = new THREE.Vector3();
  69. this.hitClosestClearEventDetail = {};
  70. this.hitClosestEventDetail = {};
  71. this.intersectedEls = [];
  72. this.objectEls = [];
  73. this.newIntersectedEls = [];
  74. this.prevCheckTime = undefined;
  75. this.previousIntersectedEls = [];
  76. this.setDirty = this.setDirty.bind(this);
  77. this.observer = new MutationObserver(this.setDirty);
  78. this.dirty = true;
  79. this.hitStartEventDetail = {intersectedEls: this.newIntersectedEls};
  80. },
  81. play: function () {
  82. this.observer.observe(this.el.sceneEl, OBSERVER_CONFIG);
  83. this.el.sceneEl.addEventListener('object3dset', this.setDirty);
  84. this.el.sceneEl.addEventListener('object3dremove', this.setDirty);
  85. },
  86. remove: function () {
  87. this.observer.disconnect();
  88. this.el.sceneEl.removeEventListener('object3dset', this.setDirty);
  89. this.el.sceneEl.removeEventListener('object3dremove', this.setDirty);
  90. },
  91. tick: function (time) {
  92. var boxHelper;
  93. var boundingBox = this.boundingBox;
  94. var centerDifferenceVec3 = this.centerDifferenceVec3;
  95. var clearedIntersectedEls = this.clearedIntersectedEls;
  96. var closestCenterDifference;
  97. var newClosestEl;
  98. var intersectedEls = this.intersectedEls;
  99. var el = this.el;
  100. var i;
  101. var newIntersectedEls = this.newIntersectedEls;
  102. var objectEls = this.objectEls;
  103. var prevCheckTime = this.prevCheckTime;
  104. var previousIntersectedEls = this.previousIntersectedEls;
  105. var self = this;
  106. if (!this.data.enabled) { return; }
  107. // Only check for intersection if interval time has passed.
  108. if (prevCheckTime && (time - prevCheckTime < this.data.interval)) { return; }
  109. // Update check time.
  110. this.prevCheckTime = time;
  111. if (this.dirty) { this.refreshObjects(); }
  112. // Update the bounding box to account for rotations and position changes.
  113. boundingBox.setFromObject(el.object3D);
  114. this.boxMin.copy(boundingBox.min);
  115. this.boxMax.copy(boundingBox.max);
  116. boundingBox.getCenter(this.boxCenter);
  117. if (this.data.debug) {
  118. this.boxHelper.setFromObject(el.object3D);
  119. if (!this.boxHelper.parent) { el.sceneEl.object3D.add(this.boxHelper); }
  120. }
  121. copyArray(previousIntersectedEls, intersectedEls);
  122. // Populate intersectedEls array.
  123. intersectedEls.length = 0;
  124. for (i = 0; i < objectEls.length; i++) {
  125. if (objectEls[i] === this.el) { continue; }
  126. // Don't collide with non-visible if flag set.
  127. if (!this.data.collideNonVisible && !objectEls[i].getAttribute('visible')) {
  128. // Remove box helper if debug flag set and has box helper.
  129. if (this.data.debug) {
  130. boxHelper = objectEls[i].object3D.boxHelper;
  131. if (boxHelper) {
  132. el.sceneEl.object3D.remove(boxHelper);
  133. objectEls[i].object3D.boxHelper = null;
  134. }
  135. }
  136. continue;
  137. }
  138. // Check for interection.
  139. if (this.isIntersecting(objectEls[i])) { intersectedEls.push(objectEls[i]); }
  140. }
  141. // Get newly intersected entities.
  142. newIntersectedEls.length = 0;
  143. for (i = 0; i < intersectedEls.length; i++) {
  144. if (previousIntersectedEls.indexOf(intersectedEls[i]) === -1) {
  145. newIntersectedEls.push(intersectedEls[i]);
  146. }
  147. }
  148. // Emit cleared events on no longer intersected entities.
  149. clearedIntersectedEls.length = 0;
  150. for (i = 0; i < previousIntersectedEls.length; i++) {
  151. if (intersectedEls.indexOf(previousIntersectedEls[i]) !== -1) { continue; }
  152. if (!previousIntersectedEls[i].hasAttribute('aabb-collider')) {
  153. previousIntersectedEls[i].emit('hitend');
  154. }
  155. clearedIntersectedEls.push(previousIntersectedEls[i]);
  156. }
  157. // Emit events on intersected entities. Do this after the cleared events.
  158. for (i = 0; i < newIntersectedEls.length; i++) {
  159. if (newIntersectedEls[i] === this.el) { continue; }
  160. if (newIntersectedEls[i].hasAttribute('aabb-collider')) { continue; }
  161. newIntersectedEls[i].emit('hitstart');
  162. }
  163. // Calculate closest intersected entity based on centers.
  164. for (i = 0; i < intersectedEls.length; i++) {
  165. if (intersectedEls[i] === this.el) { continue; }
  166. centerDifferenceVec3
  167. .copy(intersectedEls[i].object3D.boundingBoxCenter)
  168. .sub(this.boxCenter);
  169. if (closestCenterDifference === undefined ||
  170. centerDifferenceVec3.length() < closestCenterDifference) {
  171. closestCenterDifference = centerDifferenceVec3.length();
  172. newClosestEl = intersectedEls[i];
  173. }
  174. }
  175. // Emit events for the new closest entity and the old closest entity.
  176. if (!intersectedEls.length && this.closestIntersectedEl) {
  177. // No intersected entities, clear any closest entity.
  178. this.hitClosestClearEventDetail.el = this.closestIntersectedEl;
  179. this.closestIntersectedEl.emit('hitclosestclear');
  180. this.closestIntersectedEl = null;
  181. el.emit('hitclosestclear', this.hitClosestClearEventDetail);
  182. } else if (newClosestEl !== this.closestIntersectedEl) {
  183. // Clear the previous closest entity.
  184. if (this.closestIntersectedEl) {
  185. this.hitClosestClearEventDetail.el = this.closestIntersectedEl;
  186. this.closestIntersectedEl.emit('hitclosestclear', this.hitClosestClearEventDetail);
  187. }
  188. if (newClosestEl) {
  189. // Emit for the new closest entity.
  190. newClosestEl.emit('hitclosest');
  191. this.closestIntersectedEl = newClosestEl;
  192. this.hitClosestEventDetail.el = newClosestEl;
  193. el.emit('hitclosest', this.hitClosestEventDetail);
  194. }
  195. }
  196. if (clearedIntersectedEls.length) {
  197. el.emit('hitend');
  198. }
  199. if (newIntersectedEls.length) {
  200. el.emit('hitstart', this.hitStartEventDetail);
  201. }
  202. },
  203. /**
  204. * AABB collision detection.
  205. * 3D version of https://www.youtube.com/watch?v=ghqD3e37R7E
  206. */
  207. isIntersecting: (function () {
  208. var boundingBox = new THREE.Box3();
  209. return function (el) {
  210. var isIntersecting;
  211. var boxHelper;
  212. var boxMin;
  213. var boxMax;
  214. boundingBox.setFromObject(el.object3D);
  215. if (this.data.debug) {
  216. if (!el.object3D.boxHelper) {
  217. el.object3D.boxHelper = new THREE.BoxHelper(
  218. el.object3D, new THREE.Color(Math.random(), Math.random(), Math.random()));
  219. el.sceneEl.object3D.add(el.object3D.boxHelper);
  220. }
  221. el.object3D.boxHelper.setFromObject(el.object3D);
  222. }
  223. boxMin = boundingBox.min;
  224. boxMax = boundingBox.max;
  225. el.object3D.boundingBoxCenter = el.object3D.boundingBoxCenter || new THREE.Vector3();
  226. boundingBox.getCenter(el.object3D.boundingBoxCenter);
  227. return (this.boxMin.x <= boxMax.x && this.boxMax.x >= boxMin.x) &&
  228. (this.boxMin.y <= boxMax.y && this.boxMax.y >= boxMin.y) &&
  229. (this.boxMin.z <= boxMax.z && this.boxMax.z >= boxMin.z);
  230. };
  231. })(),
  232. /**
  233. * Mark the object list as dirty, to be refreshed before next raycast.
  234. */
  235. setDirty: function () {
  236. this.dirty = true;
  237. },
  238. /**
  239. * Update list of objects to test for intersection.
  240. */
  241. refreshObjects: function () {
  242. var data = this.data;
  243. // If objects not defined, intersect with everything.
  244. this.objectEls = data.objects
  245. ? this.el.sceneEl.querySelectorAll(data.objects)
  246. : this.el.sceneEl.children;
  247. this.dirty = false;
  248. }
  249. });
  250. function copyArray (dest, source) {
  251. var i;
  252. dest.length = 0;
  253. for (i = 0; i < source.length; i++) { dest[i] = source[i]; }
  254. }
  255. /***/ })
  256. /******/ ]);