|
@@ -0,0 +1,303 @@
|
|
|
+/******/ (function(modules) { // webpackBootstrap
|
|
|
+/******/ // The module cache
|
|
|
+/******/ var installedModules = {};
|
|
|
+
|
|
|
+/******/ // The require function
|
|
|
+/******/ function __webpack_require__(moduleId) {
|
|
|
+
|
|
|
+/******/ // Check if module is in cache
|
|
|
+/******/ if(installedModules[moduleId])
|
|
|
+/******/ return installedModules[moduleId].exports;
|
|
|
+
|
|
|
+/******/ // Create a new module (and put it into the cache)
|
|
|
+/******/ var module = installedModules[moduleId] = {
|
|
|
+/******/ exports: {},
|
|
|
+/******/ id: moduleId,
|
|
|
+/******/ loaded: false
|
|
|
+/******/ };
|
|
|
+
|
|
|
+/******/ // Execute the module function
|
|
|
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
|
+
|
|
|
+/******/ // Flag the module as loaded
|
|
|
+/******/ module.loaded = true;
|
|
|
+
|
|
|
+/******/ // Return the exports of the module
|
|
|
+/******/ return module.exports;
|
|
|
+/******/ }
|
|
|
+
|
|
|
+
|
|
|
+/******/ // expose the modules object (__webpack_modules__)
|
|
|
+/******/ __webpack_require__.m = modules;
|
|
|
+
|
|
|
+/******/ // expose the module cache
|
|
|
+/******/ __webpack_require__.c = installedModules;
|
|
|
+
|
|
|
+/******/ // __webpack_public_path__
|
|
|
+/******/ __webpack_require__.p = "";
|
|
|
+
|
|
|
+/******/ // Load entry module and return exports
|
|
|
+/******/ return __webpack_require__(0);
|
|
|
+/******/ })
|
|
|
+/************************************************************************/
|
|
|
+/******/ ([
|
|
|
+/* 0 */
|
|
|
+/***/ (function(module, exports) {
|
|
|
+
|
|
|
+ /* global AFRAME, THREE */
|
|
|
+
|
|
|
+ if (typeof AFRAME === 'undefined') {
|
|
|
+ throw new Error('Component attempted to register before AFRAME was available.');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Configuration for the MutationObserver used to refresh the whitelist.
|
|
|
+ // Listens for addition/removal of elements and attributes within the scene.
|
|
|
+ var OBSERVER_CONFIG = {
|
|
|
+ childList: true,
|
|
|
+ attributes: true,
|
|
|
+ subtree: true
|
|
|
+ };
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Implement AABB collision detection for entities with a mesh.
|
|
|
+ * https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box
|
|
|
+ *
|
|
|
+ * @property {string} objects - Selector of entities to test for collision.
|
|
|
+ */
|
|
|
+ AFRAME.registerComponent('aabb-collider', {
|
|
|
+ schema: {
|
|
|
+ collideNonVisible: {default: false},
|
|
|
+ debug: {default: false},
|
|
|
+ enabled: {default: true},
|
|
|
+ interval: {default: 80},
|
|
|
+ objects: {default: ''}
|
|
|
+ },
|
|
|
+
|
|
|
+ init: function () {
|
|
|
+ this.centerDifferenceVec3 = new THREE.Vector3();
|
|
|
+ this.clearedIntersectedEls = [];
|
|
|
+ this.closestIntersectedEl = null;
|
|
|
+ this.boundingBox = new THREE.Box3();
|
|
|
+ this.boxCenter = new THREE.Vector3();
|
|
|
+ this.boxHelper = new THREE.BoxHelper();
|
|
|
+ this.boxMax = new THREE.Vector3();
|
|
|
+ this.boxMin = new THREE.Vector3();
|
|
|
+ this.hitClosestClearEventDetail = {};
|
|
|
+ this.hitClosestEventDetail = {};
|
|
|
+ this.intersectedEls = [];
|
|
|
+ this.objectEls = [];
|
|
|
+ this.newIntersectedEls = [];
|
|
|
+ this.prevCheckTime = undefined;
|
|
|
+ this.previousIntersectedEls = [];
|
|
|
+
|
|
|
+ this.setDirty = this.setDirty.bind(this);
|
|
|
+ this.observer = new MutationObserver(this.setDirty);
|
|
|
+ this.dirty = true;
|
|
|
+
|
|
|
+ this.hitStartEventDetail = {intersectedEls: this.newIntersectedEls};
|
|
|
+ },
|
|
|
+
|
|
|
+ play: function () {
|
|
|
+ this.observer.observe(this.el.sceneEl, OBSERVER_CONFIG);
|
|
|
+ this.el.sceneEl.addEventListener('object3dset', this.setDirty);
|
|
|
+ this.el.sceneEl.addEventListener('object3dremove', this.setDirty);
|
|
|
+ },
|
|
|
+
|
|
|
+ remove: function () {
|
|
|
+ this.observer.disconnect();
|
|
|
+ this.el.sceneEl.removeEventListener('object3dset', this.setDirty);
|
|
|
+ this.el.sceneEl.removeEventListener('object3dremove', this.setDirty);
|
|
|
+ },
|
|
|
+
|
|
|
+ tick: function (time) {
|
|
|
+ var boxHelper;
|
|
|
+ var boundingBox = this.boundingBox;
|
|
|
+ var centerDifferenceVec3 = this.centerDifferenceVec3;
|
|
|
+ var clearedIntersectedEls = this.clearedIntersectedEls;
|
|
|
+ var closestCenterDifference;
|
|
|
+ var newClosestEl;
|
|
|
+ var intersectedEls = this.intersectedEls;
|
|
|
+ var el = this.el;
|
|
|
+ var i;
|
|
|
+ var newIntersectedEls = this.newIntersectedEls;
|
|
|
+ var objectEls = this.objectEls;
|
|
|
+ var prevCheckTime = this.prevCheckTime;
|
|
|
+ var previousIntersectedEls = this.previousIntersectedEls;
|
|
|
+ var self = this;
|
|
|
+
|
|
|
+ if (!this.data.enabled) { return; }
|
|
|
+
|
|
|
+ // Only check for intersection if interval time has passed.
|
|
|
+ if (prevCheckTime && (time - prevCheckTime < this.data.interval)) { return; }
|
|
|
+ // Update check time.
|
|
|
+ this.prevCheckTime = time;
|
|
|
+
|
|
|
+ if (this.dirty) { this.refreshObjects(); }
|
|
|
+
|
|
|
+ // Update the bounding box to account for rotations and position changes.
|
|
|
+ boundingBox.setFromObject(el.object3D);
|
|
|
+ this.boxMin.copy(boundingBox.min);
|
|
|
+ this.boxMax.copy(boundingBox.max);
|
|
|
+ boundingBox.getCenter(this.boxCenter);
|
|
|
+
|
|
|
+ if (this.data.debug) {
|
|
|
+ this.boxHelper.setFromObject(el.object3D);
|
|
|
+ if (!this.boxHelper.parent) { el.sceneEl.object3D.add(this.boxHelper); }
|
|
|
+ }
|
|
|
+
|
|
|
+ copyArray(previousIntersectedEls, intersectedEls);
|
|
|
+
|
|
|
+ // Populate intersectedEls array.
|
|
|
+ intersectedEls.length = 0;
|
|
|
+ for (i = 0; i < objectEls.length; i++) {
|
|
|
+ if (objectEls[i] === this.el) { continue; }
|
|
|
+
|
|
|
+ // Don't collide with non-visible if flag set.
|
|
|
+ if (!this.data.collideNonVisible && !objectEls[i].getAttribute('visible')) {
|
|
|
+ // Remove box helper if debug flag set and has box helper.
|
|
|
+ if (this.data.debug) {
|
|
|
+ boxHelper = objectEls[i].object3D.boxHelper;
|
|
|
+ if (boxHelper) {
|
|
|
+ el.sceneEl.object3D.remove(boxHelper);
|
|
|
+ objectEls[i].object3D.boxHelper = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for interection.
|
|
|
+ if (this.isIntersecting(objectEls[i])) { intersectedEls.push(objectEls[i]); }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get newly intersected entities.
|
|
|
+ newIntersectedEls.length = 0;
|
|
|
+ for (i = 0; i < intersectedEls.length; i++) {
|
|
|
+ if (previousIntersectedEls.indexOf(intersectedEls[i]) === -1) {
|
|
|
+ newIntersectedEls.push(intersectedEls[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Emit cleared events on no longer intersected entities.
|
|
|
+ clearedIntersectedEls.length = 0;
|
|
|
+ for (i = 0; i < previousIntersectedEls.length; i++) {
|
|
|
+ if (intersectedEls.indexOf(previousIntersectedEls[i]) !== -1) { continue; }
|
|
|
+ if (!previousIntersectedEls[i].hasAttribute('aabb-collider')) {
|
|
|
+ previousIntersectedEls[i].emit('hitend');
|
|
|
+ }
|
|
|
+ clearedIntersectedEls.push(previousIntersectedEls[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Emit events on intersected entities. Do this after the cleared events.
|
|
|
+ for (i = 0; i < newIntersectedEls.length; i++) {
|
|
|
+ if (newIntersectedEls[i] === this.el) { continue; }
|
|
|
+ if (newIntersectedEls[i].hasAttribute('aabb-collider')) { continue; }
|
|
|
+ newIntersectedEls[i].emit('hitstart');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculate closest intersected entity based on centers.
|
|
|
+ for (i = 0; i < intersectedEls.length; i++) {
|
|
|
+ if (intersectedEls[i] === this.el) { continue; }
|
|
|
+ centerDifferenceVec3
|
|
|
+ .copy(intersectedEls[i].object3D.boundingBoxCenter)
|
|
|
+ .sub(this.boxCenter);
|
|
|
+ if (closestCenterDifference === undefined ||
|
|
|
+ centerDifferenceVec3.length() < closestCenterDifference) {
|
|
|
+ closestCenterDifference = centerDifferenceVec3.length();
|
|
|
+ newClosestEl = intersectedEls[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Emit events for the new closest entity and the old closest entity.
|
|
|
+ if (!intersectedEls.length && this.closestIntersectedEl) {
|
|
|
+ // No intersected entities, clear any closest entity.
|
|
|
+ this.hitClosestClearEventDetail.el = this.closestIntersectedEl;
|
|
|
+ this.closestIntersectedEl.emit('hitclosestclear');
|
|
|
+ this.closestIntersectedEl = null;
|
|
|
+ el.emit('hitclosestclear', this.hitClosestClearEventDetail);
|
|
|
+ } else if (newClosestEl !== this.closestIntersectedEl) {
|
|
|
+ // Clear the previous closest entity.
|
|
|
+ if (this.closestIntersectedEl) {
|
|
|
+ this.hitClosestClearEventDetail.el = this.closestIntersectedEl;
|
|
|
+ this.closestIntersectedEl.emit('hitclosestclear', this.hitClosestClearEventDetail);
|
|
|
+ }
|
|
|
+ if (newClosestEl) {
|
|
|
+ // Emit for the new closest entity.
|
|
|
+ newClosestEl.emit('hitclosest');
|
|
|
+ this.closestIntersectedEl = newClosestEl;
|
|
|
+ this.hitClosestEventDetail.el = newClosestEl;
|
|
|
+ el.emit('hitclosest', this.hitClosestEventDetail);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (clearedIntersectedEls.length) {
|
|
|
+ el.emit('hitend');
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newIntersectedEls.length) {
|
|
|
+ el.emit('hitstart', this.hitStartEventDetail);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * AABB collision detection.
|
|
|
+ * 3D version of https://www.youtube.com/watch?v=ghqD3e37R7E
|
|
|
+ */
|
|
|
+ isIntersecting: (function () {
|
|
|
+ var boundingBox = new THREE.Box3();
|
|
|
+
|
|
|
+ return function (el) {
|
|
|
+ var isIntersecting;
|
|
|
+ var boxHelper;
|
|
|
+ var boxMin;
|
|
|
+ var boxMax;
|
|
|
+
|
|
|
+ boundingBox.setFromObject(el.object3D);
|
|
|
+
|
|
|
+ if (this.data.debug) {
|
|
|
+ if (!el.object3D.boxHelper) {
|
|
|
+ el.object3D.boxHelper = new THREE.BoxHelper(
|
|
|
+ el.object3D, new THREE.Color(Math.random(), Math.random(), Math.random()));
|
|
|
+ el.sceneEl.object3D.add(el.object3D.boxHelper);
|
|
|
+ }
|
|
|
+ el.object3D.boxHelper.setFromObject(el.object3D);
|
|
|
+ }
|
|
|
+
|
|
|
+ boxMin = boundingBox.min;
|
|
|
+ boxMax = boundingBox.max;
|
|
|
+ el.object3D.boundingBoxCenter = el.object3D.boundingBoxCenter || new THREE.Vector3();
|
|
|
+ boundingBox.getCenter(el.object3D.boundingBoxCenter);
|
|
|
+ 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);
|
|
|
+ };
|
|
|
+ })(),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Mark the object list as dirty, to be refreshed before next raycast.
|
|
|
+ */
|
|
|
+ setDirty: function () {
|
|
|
+ this.dirty = true;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Update list of objects to test for intersection.
|
|
|
+ */
|
|
|
+ refreshObjects: function () {
|
|
|
+ var data = this.data;
|
|
|
+ // If objects not defined, intersect with everything.
|
|
|
+ this.objectEls = data.objects
|
|
|
+ ? this.el.sceneEl.querySelectorAll(data.objects)
|
|
|
+ : this.el.sceneEl.children;
|
|
|
+ this.dirty = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ function copyArray (dest, source) {
|
|
|
+ var i;
|
|
|
+ dest.length = 0;
|
|
|
+ for (i = 0; i < source.length; i++) { dest[i] = source[i]; }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+/***/ })
|
|
|
+/******/ ]);
|