aframe-extras
Version:
Add-ons and examples for A-Frame VR.
222 lines (193 loc) • 6.79 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else {
var a = factory();
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(self, () => {
return /******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./src/misc/sphere-collider.js":
/*!*************************************!*\
!*** ./src/misc/sphere-collider.js ***!
\*************************************/
/***/ ((module) => {
/**
* Based on aframe/examples/showcase/tracked-controls.
*
* Implement bounding sphere collision detection for entities with a mesh.
* Sets the specified state on the intersected entities.
*
* @property {string} objects - Selector of the entities to test for collision.
* @property {string} state - State to set on collided entities.
*
*/
module.exports = AFRAME.registerComponent('sphere-collider', {
schema: {
enabled: {default: true},
interval: {default: 80},
objects: {default: ''},
state: {default: 'collided'},
radius: {default: 0.05},
watch: {default: true}
},
init: function () {
/** @type {MutationObserver} */
this.observer = null;
/** @type {Array<Element>} Elements to watch for collisions. */
this.els = [];
/** @type {Array<Element>} Elements currently in collision state. */
this.collisions = [];
this.prevCheckTime = undefined;
this.eventDetail = {};
this.handleHit = this.handleHit.bind(this);
this.handleHitEnd = this.handleHitEnd.bind(this);
},
play: function () {
const sceneEl = this.el.sceneEl;
if (this.data.watch) {
this.observer = new MutationObserver(this.update.bind(this, null));
this.observer.observe(sceneEl, {childList: true, subtree: true});
}
},
pause: function () {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
},
/**
* Update list of entities to test for collision.
*/
update: function () {
const data = this.data;
let objectEls;
// Push entities into list of els to intersect.
if (data.objects) {
objectEls = this.el.sceneEl.querySelectorAll(data.objects);
} else {
// If objects not defined, intersect with everything.
objectEls = this.el.sceneEl.children;
}
// Convert from NodeList to Array
this.els = Array.prototype.slice.call(objectEls);
},
tick: (function () {
const position = new THREE.Vector3(),
meshPosition = new THREE.Vector3(),
colliderScale = new THREE.Vector3(),
size = new THREE.Vector3(),
box = new THREE.Box3(),
collisions = [],
distanceMap = new Map();
return function (time) {
if (!this.data.enabled) { return; }
// Only check for intersection if interval time has passed.
const prevCheckTime = this.prevCheckTime;
if (prevCheckTime && (time - prevCheckTime < this.data.interval)) { return; }
// Update check time.
this.prevCheckTime = time;
const el = this.el,
data = this.data,
mesh = el.getObject3D('mesh');
let colliderRadius;
if (!mesh) { return; }
collisions.length = 0;
distanceMap.clear();
el.object3D.getWorldPosition(position);
el.object3D.getWorldScale(colliderScale);
colliderRadius = data.radius * scaleFactor(colliderScale);
// Update collision list.
this.els.forEach(intersect);
// Emit events and add collision states, in order of distance.
collisions
.sort((a, b) => distanceMap.get(a) > distanceMap.get(b) ? 1 : -1)
.forEach(this.handleHit);
// Remove collision state from other elements.
this.collisions
.filter((el) => !distanceMap.has(el))
.forEach(this.handleHitEnd);
// Store new collisions
copyArray(this.collisions, collisions);
// Bounding sphere collision detection
function intersect (el) {
let radius, mesh, distance, extent;
if (!el.isEntity) { return; }
mesh = el.getObject3D('mesh');
if (!mesh) { return; }
box.setFromObject(mesh).getSize(size);
extent = Math.max(size.x, size.y, size.z) / 2;
radius = Math.sqrt(2 * extent * extent);
box.getCenter(meshPosition);
if (!radius) { return; }
distance = position.distanceTo(meshPosition);
if (distance < radius + colliderRadius) {
collisions.push(el);
distanceMap.set(el, distance);
}
}
// use max of scale factors to maintain bounding sphere collision
function scaleFactor (scaleVec) {
return Math.max(scaleVec.x, scaleVec.y, scaleVec.z);
}
};
})(),
handleHit: function (targetEl) {
targetEl.emit('hit');
targetEl.addState(this.data.state);
this.eventDetail.el = targetEl;
this.el.emit('hit', this.eventDetail);
},
handleHitEnd: function (targetEl) {
targetEl.emit('hitend');
targetEl.removeState(this.data.state);
this.eventDetail.el = targetEl;
this.el.emit('hitend', this.eventDetail);
}
});
function copyArray (dest, source) {
dest.length = 0;
for (let i = 0; i < source.length; i++) { dest[i] = source[i]; }
}
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __webpack_require__("./src/misc/sphere-collider.js");
/******/
/******/ return __webpack_exports__;
/******/ })()
;
});
//# sourceMappingURL=sphere-collider.js.map