UNPKG

aframe-physics-extras

Version:

Cannon API interface components the A-Frame Physics System.

312 lines (295 loc) 10.7 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 'use strict'; /* global AFRAME */ if (typeof AFRAME === 'undefined') { throw new Error('Component attempted to register before AFRAME was available.'); } require('./src/physics-collider.js'); require('./src/physics-collision-filter.js'); require('./src/physics-sleepy.js'); require('./src/body-merger.js'); },{"./src/body-merger.js":2,"./src/physics-collider.js":3,"./src/physics-collision-filter.js":4,"./src/physics-sleepy.js":5}],2:[function(require,module,exports){ 'use strict'; /* global AFRAME, THREE, CANNON */ AFRAME.registerComponent('body-merger', { schema: { default: 'static-body' }, init: function () { const doMerge = evt => { if (evt.target === this.el) { this.el.removeEventListener('body-loaded', doMerge); this.merge(); } }; if (this.el.body) { this.merge(); } else { this.el.addEventListener('body-loaded', doMerge); } }, merge: function () { const body = this.el.body; const tmpMat = new THREE.Matrix4(); const tmpQuat = new THREE.Quaternion(); const tmpPos = new THREE.Vector3(); const tmpScale = new THREE.Vector3(1, 1, 1); // todo: apply worldScale const offset = new CANNON.Vec3(); const orientation = new CANNON.Quaternion(); for (let child of this.el.childNodes) { if (!child.body || !child.getAttribute(this.data)) { continue; } child.object3D.updateMatrix(); while (child.body.shapes.length) { tmpPos.copy(child.body.shapeOffsets.pop()); tmpQuat.copy(child.body.shapeOrientations.pop()); tmpMat.compose(tmpPos, tmpQuat, tmpScale); tmpMat.multiply(child.object3D.matrix); tmpMat.decompose(tmpPos, tmpQuat, tmpScale); offset.copy(tmpPos); orientation.copy(tmpQuat); body.addShape(child.body.shapes.pop(), offset, orientation); } child.removeAttribute(this.data); } } }); },{}],3:[function(require,module,exports){ 'use strict'; /* global AFRAME */ AFRAME.registerComponent('physics-collider', { schema: { ignoreSleep: { default: true } }, init: function () { this.collisions = new Set(); this.currentCollisions = new Set(); this.newCollisions = []; this.clearedCollisions = []; this.collisionEventDetails = { els: this.newCollisions, clearedEls: this.clearedCollisions }; }, update: function () { if (this.el.body) { this.updateBody(); } else { this.el.addEventListener('body-loaded', this.updateBody.bind(this), { once: true }); } }, tick: function () { const uppperMask = 0xFFFF0000; const lowerMask = 0x0000FFFF; return function () { if (!this.el.body) return; const currentCollisions = this.currentCollisions; const thisBodyId = this.el.body.id; const worldCollisions = this.el.body.world.bodyOverlapKeeper.current; const worldBodyMap = this.el.body.world.idToBodyMap; const collisions = this.collisions; const newCollisions = this.newCollisions; const clearedCollisions = this.clearedCollisions; let i = 0; let upperId = (worldCollisions[i] & uppperMask) >> 16; let target; newCollisions.length = clearedCollisions.length = 0; currentCollisions.clear(); while (i < worldCollisions.length && upperId < thisBodyId) { if (worldBodyMap[upperId]) { target = worldBodyMap[upperId].el; if ((worldCollisions[i] & lowerMask) === thisBodyId) { currentCollisions.add(target); if (!collisions.has(target)) { newCollisions.push(target); } } } upperId = (worldCollisions[++i] & uppperMask) >> 16; } while (i < worldCollisions.length && upperId === thisBodyId) { if (worldBodyMap[worldCollisions[i] & lowerMask]) { target = worldBodyMap[worldCollisions[i] & lowerMask].el; currentCollisions.add(target); if (!collisions.has(target)) { newCollisions.push(target); } } upperId = (worldCollisions[++i] & uppperMask) >> 16; } for (let col of collisions) { if (!currentCollisions.has(col)) { clearedCollisions.push(col); collisions.delete(col); } } for (let col of newCollisions) { collisions.add(col); } if (newCollisions.length || clearedCollisions.length) { this.el.emit('collisions', this.collisionEventDetails); } }; }(), remove: function () { if (this.originalSleepConfig) { AFRAME.utils.extend(this.el.body, this.originalSleepConfig); } }, updateBody: function (evt) { // ignore bubbled 'body-loaded' events if (evt !== undefined && evt.target !== this.el) { return; } if (this.data.ignoreSleep) { // ensure sleep doesn't disable collision detection this.el.body.allowSleep = false; /* naiveBroadphase ignores collisions between sleeping & static bodies */ this.el.body.type = window.CANNON.Body.KINEMATIC; // Kinematics must have velocity >= their sleep limit to wake others this.el.body.sleepSpeedLimit = 0; } else if (this.originalSleepConfig === undefined) { this.originalSleepConfig = { allowSleep: this.el.body.allowSleep, sleepSpeedLimit: this.el.body.sleepSpeedLimit, type: this.el.body.type }; } else { // restore original settings AFRAME.utils.extend(this.el.body, this.originalSleepConfig); } } }); },{}],4:[function(require,module,exports){ 'use strict'; /* global AFRAME */ AFRAME.registerComponent('collision-filter', { schema: { group: { default: 'default' }, collidesWith: { default: ['default'] }, collisionForces: { default: true } }, init: function () { this.updateBodyBound = this.updateBody.bind(this); this.system.registerMe(this); this.el.addEventListener('body-loaded', this.updateBodyBound); }, update: function () { // register any new groups this.system.registerMe(this); if (this.el.body) { this.updateBody(); } }, remove: function () { this.el.removeEventListener('body-loaded', this.updateBodyBound); }, updateBody: function (evt) { // ignore bubbled 'body-loaded' events if (evt !== undefined && evt.target !== this.el) { return; } this.el.body.collisionFilterMask = this.system.getFilterCode(this.data.collidesWith); this.el.body.collisionFilterGroup = this.system.getFilterCode(this.data.group); this.el.body.collisionResponse = this.data.collisionForces; } }); AFRAME.registerSystem('collision-filter', { schema: { collisionGroups: { default: ['default'] } }, dependencies: ['physics'], init: function () { this.maxGroups = Math.log2(Number.MAX_SAFE_INTEGER); }, registerMe: function (comp) { // add any unknown groups to the master list const newGroups = [comp.data.group, ...comp.data.collidesWith].filter(group => this.data.collisionGroups.indexOf(group) === -1); this.data.collisionGroups.push(...newGroups); if (this.data.collisionGroups.length > this.maxGroups) { throw new Error('Too many collision groups'); } }, getFilterCode: function (elGroups) { let code = 0; if (!Array.isArray(elGroups)) { elGroups = [elGroups]; } // each group corresponds to a bit which is turned on when matched // floor negates any unmatched groups (2^-1 = 0.5) elGroups.forEach(group => { code += Math.floor(Math.pow(2, this.data.collisionGroups.indexOf(group))); }); return code; } }); },{}],5:[function(require,module,exports){ 'use strict'; // Make dynamic bodies idle when not grabbed /* global AFRAME */ AFRAME.registerComponent('sleepy', { schema: { allowSleep: { default: true }, speedLimit: { default: 0.25, type: 'number' }, delay: { default: 0.25, type: 'number' }, linearDamping: { default: 0.99, type: 'number' }, angularDamping: { default: 0.99, type: 'number' }, holdState: { default: 'grabbed' } }, init: function () { this.updateBodyBound = this.updateBody.bind(this); this.holdStateBound = this.holdState.bind(this); this.resumeStateBound = this.resumeState.bind(this); this.el.addEventListener('body-loaded', this.updateBodyBound); }, update: function () { if (this.el.body) { this.updateBody(); } }, remove: function () { this.el.removeEventListener('body-loaded', this.updateBodyBound); this.el.removeEventListener('stateadded', this.holdStateBound); this.el.removeEventListener('stateremoved', this.resumeStateBound); }, updateBody: function (evt) { // ignore bubbled 'body-loaded' events if (evt !== undefined && evt.target !== this.el) { return; } if (this.data.allowSleep) { // only "local" driver compatable try { this.el.body.world.allowSleep = true; } catch (err) { console.error('Unable to activate sleep in physics.' + '`sleepy` requires "local" physics driver'); } } this.el.body.allowSleep = this.data.allowSleep; this.el.body.sleepSpeedLimit = this.data.speedLimit; this.el.body.sleepTimeLimit = this.data.delay; this.el.body.linearDamping = this.data.linearDamping; this.el.body.angularDamping = this.data.angularDamping; if (this.data.allowSleep) { this.el.addEventListener('stateadded', this.holdStateBound); this.el.addEventListener('stateremoved', this.resumeStateBound); } else { this.el.removeEventListener('stateadded', this.holdStateBound); this.el.removeEventListener('stateremoved', this.resumeStateBound); } }, // disble the sleeping during interactions because sleep will break constraints holdState: function (evt) { let state = this.data.holdState; // api change in A-Frame v0.8.0 if (evt.detail === state || evt.detail.state === state) { this.el.body.allowSleep = false; } }, resumeState: function (evt) { let state = this.data.holdState; if (evt.detail === state || evt.detail.state === state) { this.el.body.allowSleep = this.data.allowSleep; } } }); },{}]},{},[1]);