UNPKG

aframe-extras

Version:

Add-ons and examples for A-Frame VR.

585 lines (492 loc) 18.7 kB
(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/checkpoint.js": /*!********************************!*\ !*** ./src/misc/checkpoint.js ***! \********************************/ /***/ (() => { AFRAME.registerComponent('checkpoint', { schema: { offset: {default: {x: 0, y: 0, z: 0}, type: 'vec3'} }, init: function () { this.active = false; this.targetEl = null; this.fire = this.fire.bind(this); this.offset = new THREE.Vector3(); }, update: function () { this.offset.copy(this.data.offset); }, play: function () { this.el.addEventListener('click', this.fire); }, pause: function () { this.el.removeEventListener('click', this.fire); }, remove: function () { this.pause(); }, fire: function () { const targetEl = this.el.sceneEl.querySelector('[checkpoint-controls]'); if (!targetEl) { throw new Error('No `checkpoint-controls` component found.'); } targetEl.components['checkpoint-controls'].setCheckpoint(this.el); }, getOffset: function () { return this.offset.copy(this.data.offset); } }); /***/ }), /***/ "./src/misc/cube-env-map.js": /*!**********************************!*\ !*** ./src/misc/cube-env-map.js ***! \**********************************/ /***/ (() => { /** * @param {Array<THREE.Material>|THREE.Material} material * @return {Array<THREE.Material>} */ function ensureMaterialArray (material) { if (!material) { return []; } else if (Array.isArray(material)) { return material; } else if (material.materials) { return material.materials; } else { return [material]; } } /** * @param {THREE.Object3D} mesh * @param {Array<string>} materialNames * @param {THREE.Texture} envMap * @param {number} reflectivity [description] */ function applyEnvMap (mesh, materialNames, envMap, reflectivity) { if (!mesh) return; materialNames = materialNames || []; mesh.traverse((node) => { if (!node.isMesh) return; const meshMaterials = ensureMaterialArray(node.material); meshMaterials.forEach((material) => { if (material && !('envMap' in material)) return; if (materialNames.length && materialNames.indexOf(material.name) === -1) return; material.envMap = envMap; material.reflectivity = reflectivity; material.needsUpdate = true; }); }); } /** * Specifies an envMap on an entity, without replacing any existing material * properties. */ AFRAME.registerComponent('cube-env-map', { multiple: true, schema: { path: {default: ''}, extension: {default: 'jpg', oneOf: ['jpg', 'png']}, enableBackground: {default: false}, reflectivity: {default: 1, min: 0, max: 1}, materials: {default: []} }, init: function () { const data = this.data; this.texture = new THREE.CubeTextureLoader().load([ data.path + 'posx.' + data.extension, data.path + 'negx.' + data.extension, data.path + 'posy.' + data.extension, data.path + 'negy.' + data.extension, data.path + 'posz.' + data.extension, data.path + 'negz.' + data.extension ]); this.texture.format = THREE.RGBAFormat; this.object3dsetHandler = () => { const mesh = this.el.getObject3D('mesh'); const data = this.data; applyEnvMap(mesh, data.materials, this.texture, data.reflectivity); }; this.object3dsetHandler(); this.el.addEventListener('object3dset', this.object3dsetHandler); }, update: function (oldData) { const data = this.data; const mesh = this.el.getObject3D('mesh'); let addedMaterialNames = []; let removedMaterialNames = []; if (data.materials.length) { if (oldData.materials) { addedMaterialNames = data.materials.filter((name) => !oldData.materials.includes(name)); removedMaterialNames = oldData.materials.filter((name) => !data.materials.includes(name)); } else { addedMaterialNames = data.materials; } } if (addedMaterialNames.length) { applyEnvMap(mesh, addedMaterialNames, this.texture, data.reflectivity); } if (removedMaterialNames.length) { applyEnvMap(mesh, removedMaterialNames, null, 1); } if (oldData.materials && data.reflectivity !== oldData.reflectivity) { const maintainedMaterialNames = data.materials .filter((name) => oldData.materials.includes(name)); if (maintainedMaterialNames.length) { applyEnvMap(mesh, maintainedMaterialNames, this.texture, data.reflectivity); } } if (this.data.enableBackground && !oldData.enableBackground) { this.setBackground(this.texture); } else if (!this.data.enableBackground && oldData.enableBackground) { this.setBackground(null); } }, remove: function () { this.el.removeEventListener('object3dset', this.object3dsetHandler); const mesh = this.el.getObject3D('mesh'); const data = this.data; applyEnvMap(mesh, data.materials, null, 1); if (data.enableBackground) this.setBackground(null); }, setBackground: function (texture) { this.el.sceneEl.object3D.background = texture; } }); /***/ }), /***/ "./src/misc/grab.js": /*!**************************!*\ !*** ./src/misc/grab.js ***! \**************************/ /***/ (() => { /* global CANNON */ /** * Based on aframe/examples/showcase/tracked-controls. * * Handles events coming from the hand-controls. * Determines if the entity is grabbed or released. * Updates its position to move along the controller. */ AFRAME.registerComponent('grab', { init: function () { this.system = this.el.sceneEl.systems.physics; this.GRABBED_STATE = 'grabbed'; this.grabbing = false; this.hitEl = /** @type {AFRAME.Element} */ null; this.physics = /** @type {AFRAME.System} */ this.el.sceneEl.systems.physics; this.constraint = /** @type {CANNON.Constraint} */ null; // Bind event handlers this.onHit = this.onHit.bind(this); this.onGripOpen = this.onGripOpen.bind(this); this.onGripClose = this.onGripClose.bind(this); }, play: function () { const el = this.el; el.addEventListener('hit', this.onHit); el.addEventListener('gripdown', this.onGripClose); el.addEventListener('gripup', this.onGripOpen); el.addEventListener('trackpaddown', this.onGripClose); el.addEventListener('trackpadup', this.onGripOpen); el.addEventListener('triggerdown', this.onGripClose); el.addEventListener('triggerup', this.onGripOpen); }, pause: function () { const el = this.el; el.removeEventListener('hit', this.onHit); el.removeEventListener('gripdown', this.onGripClose); el.removeEventListener('gripup', this.onGripOpen); el.removeEventListener('trackpaddown', this.onGripClose); el.removeEventListener('trackpadup', this.onGripOpen); el.removeEventListener('triggerdown', this.onGripClose); el.removeEventListener('triggerup', this.onGripOpen); }, onGripClose: function () { this.grabbing = true; }, onGripOpen: function () { const hitEl = this.hitEl; this.grabbing = false; if (!hitEl) { return; } hitEl.removeState(this.GRABBED_STATE); this.hitEl = undefined; this.system.removeConstraint(this.constraint); this.constraint = null; }, onHit: function (evt) { const hitEl = evt.detail.el; // If the element is already grabbed (it could be grabbed by another controller). // If the hand is not grabbing the element does not stick. // If we're already grabbing something you can't grab again. if (hitEl.is(this.GRABBED_STATE) || !this.grabbing || this.hitEl) { return; } hitEl.addState(this.GRABBED_STATE); this.hitEl = hitEl; this.constraint = new CANNON.LockConstraint(this.el.body, hitEl.body); this.system.addConstraint(this.constraint); } }); /***/ }), /***/ "./src/misc/normal-material.js": /*!*************************************!*\ !*** ./src/misc/normal-material.js ***! \*************************************/ /***/ (() => { /** * Recursively applies a MeshNormalMaterial to the entity, such that * face colors are determined by their orientation. Helpful for * debugging geometry */ AFRAME.registerComponent('normal-material', { init: function () { this.material = new THREE.MeshNormalMaterial({flatShading: true}); this.applyMaterial = this.applyMaterial.bind(this); this.el.addEventListener('object3dset', this.applyMaterial); this.applyMaterial(); }, remove: function () { this.el.removeEventListener('object3dset', this.applyMaterial); }, applyMaterial: function () { this.el.object3D.traverse((node) => { if (node.isMesh) node.material = this.material; }); } }); /***/ }), /***/ "./src/misc/sphere-collider.js": /*!*************************************!*\ !*** ./src/misc/sphere-collider.js ***! \*************************************/ /***/ (() => { /** * 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. * */ 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; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ var getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /******/ /* webpack/runtime/make namespace object */ /******/ (() => { /******/ // define __esModule on exports /******/ __webpack_require__.r = (exports) => { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // This entry needs to be wrapped in an IIFE because it needs to be in strict mode. (() => { "use strict"; /*!***************************!*\ !*** ./src/misc/index.js ***! \***************************/ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _checkpoint_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./checkpoint.js */ "./src/misc/checkpoint.js"); /* harmony import */ var _checkpoint_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_checkpoint_js__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _cube_env_map_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./cube-env-map.js */ "./src/misc/cube-env-map.js"); /* harmony import */ var _cube_env_map_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_cube_env_map_js__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var _grab_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./grab.js */ "./src/misc/grab.js"); /* harmony import */ var _grab_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_grab_js__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var _normal_material_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./normal-material.js */ "./src/misc/normal-material.js"); /* harmony import */ var _normal_material_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_normal_material_js__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _sphere_collider_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./sphere-collider.js */ "./src/misc/sphere-collider.js"); /* harmony import */ var _sphere_collider_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_sphere_collider_js__WEBPACK_IMPORTED_MODULE_4__); })(); /******/ return __webpack_exports__; /******/ })() ; }); //# sourceMappingURL=aframe-extras.misc.js.map