aframe-aabb-collider-component.js 12 KB

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