diff --git a/AmmoDriver.md b/AmmoDriver.md
index b88fac2..d7227bf 100644
--- a/AmmoDriver.md
+++ b/AmmoDriver.md
@@ -45,7 +45,7 @@ or
Then, add `aframe-physics-system` itself, also with a script tag:
```html
-
+
```
#### NPM and Webpack
diff --git a/CannonDriver.md b/CannonDriver.md
index b6a03da..197fe45 100644
--- a/CannonDriver.md
+++ b/CannonDriver.md
@@ -11,13 +11,13 @@ This page describes how to use aframe-physics-system with the Cannon Driver
In the [dist/](https://github.com/c-frame/aframe-physics-system/tree/master/dist) folder, download the full or minified build. Include the script on your page, and all components are automatically registered for you:
```html
-
+
```
CDN builds for aframe-physics-system@v$npm_package_version:
-- [aframe-physics-system.js](https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.3/dist/aframe-physics-system.js) *(development)*
-- [aframe-physics-system.min.js](https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.3/dist/aframe-physics-system.min.js) *(production)*
+- [aframe-physics-system.js](https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.4/dist/aframe-physics-system.js) *(development)*
+- [aframe-physics-system.min.js](https://cdn.jsdelivr.net/gh/c-frame/aframe-physics-system@v4.2.4/dist/aframe-physics-system.min.js) *(production)*
### npm
diff --git a/dist/aframe-physics-system.js b/dist/aframe-physics-system.js
index b381346..821d467 100644
--- a/dist/aframe-physics-system.js
+++ b/dist/aframe-physics-system.js
@@ -1,197 +1,197 @@
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i {
+ if (!object.visible) return false;
+ if (object.parent) {
+ return isObjectVisible(object.parent);
+ }
+ return true;
+};
const iterateGeometries = exports.iterateGeometries = function () {
const inverse = new THREE.Matrix4();
return function (root, options, cb) {
inverse.copy(root.matrixWorld).invert();
root.traverse(mesh => {
const transform = new THREE.Matrix4();
- if (mesh.isMesh && mesh.name !== "Sky" && (options.includeInvisible || mesh.el && mesh.el.object3D.visible || mesh.visible)) {
+ if (mesh.isMesh && mesh.name !== "Sky" && (options.includeInvisible || isObjectVisible(mesh))) {
if (mesh === root) {
transform.identity();
} else {
@@ -13670,3408 +13677,3408 @@ module.exports = function (fn, options) {
};
},{}],9:[function(require,module,exports){
-/* global Ammo */
-const CONSTRAINT = require("../constants").CONSTRAINT;
-
-module.exports = AFRAME.registerComponent("ammo-constraint", {
- multiple: true,
-
- schema: {
- // Type of constraint.
- type: {
- default: CONSTRAINT.LOCK,
- oneOf: [
- CONSTRAINT.LOCK,
- CONSTRAINT.FIXED,
- CONSTRAINT.SPRING,
- CONSTRAINT.SLIDER,
- CONSTRAINT.HINGE,
- CONSTRAINT.CONE_TWIST,
- CONSTRAINT.POINT_TO_POINT
- ]
- },
-
- // Target (other) body for the constraint.
- target: { type: "selector" },
-
- // Offset of the hinge or point-to-point constraint, defined locally in the body. Used for hinge, coneTwist pointToPoint constraints.
- pivot: { type: "vec3" },
- targetPivot: { type: "vec3" },
-
- // An axis that each body can rotate around, defined locally to that body. Used for hinge constraints.
- axis: { type: "vec3", default: { x: 0, y: 0, z: 1 } },
- targetAxis: { type: "vec3", default: { x: 0, y: 0, z: 1 } },
-
- // damping & stuffness - used for spring contraints only
- damping: { type: "number", default: 1 },
- stiffness: { type: "number", default: 100 },
- },
-
- init: function() {
- this.system = this.el.sceneEl.systems.physics;
- this.constraint = null;
- },
-
- remove: function() {
- if (!this.constraint) return;
-
- this.system.removeConstraint(this.constraint);
- this.constraint = null;
- },
-
- update: function() {
- const el = this.el,
- data = this.data;
-
- this.remove();
-
- if (!el.body || !data.target.body) {
- (el.body ? data.target : el).addEventListener("body-loaded", this.update.bind(this, {}), { once: true });
- return;
- }
-
- this.constraint = this.createConstraint();
- this.system.addConstraint(this.constraint);
- },
-
- /**
- * @return {Ammo.btTypedConstraint}
- */
- createConstraint: function() {
- let constraint;
- const data = this.data,
- body = this.el.body,
- targetBody = data.target.body;
-
- const bodyTransform = body
- .getCenterOfMassTransform()
- .inverse()
- .op_mul(targetBody.getWorldTransform());
- const targetTransform = new Ammo.btTransform();
- targetTransform.setIdentity();
-
- switch (data.type) {
- case CONSTRAINT.LOCK: {
- constraint = new Ammo.btGeneric6DofConstraint(body, targetBody, bodyTransform, targetTransform, true);
- const zero = new Ammo.btVector3(0, 0, 0);
- //TODO: allow these to be configurable
- constraint.setLinearLowerLimit(zero);
- constraint.setLinearUpperLimit(zero);
- constraint.setAngularLowerLimit(zero);
- constraint.setAngularUpperLimit(zero);
- Ammo.destroy(zero);
- break;
- }
- //TODO: test and verify all other constraint types
- case CONSTRAINT.FIXED: {
- //btFixedConstraint does not seem to debug render
- bodyTransform.setRotation(body.getWorldTransform().getRotation());
- targetTransform.setRotation(targetBody.getWorldTransform().getRotation());
- constraint = new Ammo.btFixedConstraint(body, targetBody, bodyTransform, targetTransform);
- break;
- }
- case CONSTRAINT.SPRING: {
- constraint = new Ammo.btGeneric6DofSpringConstraint(body, targetBody, bodyTransform, targetTransform, true);
-
- // Very limited initial implementation of spring constraint.
- // See: https://github.com/n5ro/aframe-physics-system/issues/171
- for (var i in [0,1,2,3,4,5]) {
- constraint.enableSpring(1, true)
- constraint.setStiffness(1, this.data.stiffness)
- constraint.setDamping(1, this.data.damping)
- }
- const upper = new Ammo.btVector3(-1, -1, -1);
- const lower = new Ammo.btVector3(1, 1, 1);
- constraint.setLinearUpperLimit(upper);
- constraint.setLinearLowerLimit(lower)
- Ammo.destroy(upper);
- Ammo.destroy(lower);
- break;
- }
- case CONSTRAINT.SLIDER: {
- //TODO: support setting linear and angular limits
- constraint = new Ammo.btSliderConstraint(body, targetBody, bodyTransform, targetTransform, true);
- constraint.setLowerLinLimit(-1);
- constraint.setUpperLinLimit(1);
- // constraint.setLowerAngLimit();
- // constraint.setUpperAngLimit();
- break;
- }
- case CONSTRAINT.HINGE: {
- const pivot = new Ammo.btVector3(data.pivot.x, data.pivot.y, data.pivot.z);
- const targetPivot = new Ammo.btVector3(data.targetPivot.x, data.targetPivot.y, data.targetPivot.z);
-
- const axis = new Ammo.btVector3(data.axis.x, data.axis.y, data.axis.z);
- const targetAxis = new Ammo.btVector3(data.targetAxis.x, data.targetAxis.y, data.targetAxis.z);
-
- constraint = new Ammo.btHingeConstraint(body, targetBody, pivot, targetPivot, axis, targetAxis, true);
-
- Ammo.destroy(pivot);
- Ammo.destroy(targetPivot);
- Ammo.destroy(axis);
- Ammo.destroy(targetAxis);
- break;
- }
- case CONSTRAINT.CONE_TWIST: {
- const pivotTransform = new Ammo.btTransform();
- pivotTransform.setIdentity();
- pivotTransform.getOrigin().setValue(data.pivot.x, data.pivot.y, data.pivot.z);
- const targetPivotTransform = new Ammo.btTransform();
- targetPivotTransform.setIdentity();
- targetPivotTransform.getOrigin().setValue(data.targetPivot.x, data.targetPivot.y, data.targetPivot.z);
- constraint = new Ammo.btConeTwistConstraint(body, targetBody, pivotTransform, targetPivotTransform);
- Ammo.destroy(pivotTransform);
- Ammo.destroy(targetPivotTransform);
- break;
- }
- case CONSTRAINT.POINT_TO_POINT: {
- const pivot = new Ammo.btVector3(data.pivot.x, data.pivot.y, data.pivot.z);
- const targetPivot = new Ammo.btVector3(data.targetPivot.x, data.targetPivot.y, data.targetPivot.z);
-
- constraint = new Ammo.btPoint2PointConstraint(body, targetBody, pivot, targetPivot);
-
- Ammo.destroy(pivot);
- Ammo.destroy(targetPivot);
- break;
- }
- default:
- throw new Error("[constraint] Unexpected type: " + data.type);
- }
-
- Ammo.destroy(targetTransform);
-
- return constraint;
- }
-});
+/* global Ammo */
+const CONSTRAINT = require("../constants").CONSTRAINT;
+
+module.exports = AFRAME.registerComponent("ammo-constraint", {
+ multiple: true,
+
+ schema: {
+ // Type of constraint.
+ type: {
+ default: CONSTRAINT.LOCK,
+ oneOf: [
+ CONSTRAINT.LOCK,
+ CONSTRAINT.FIXED,
+ CONSTRAINT.SPRING,
+ CONSTRAINT.SLIDER,
+ CONSTRAINT.HINGE,
+ CONSTRAINT.CONE_TWIST,
+ CONSTRAINT.POINT_TO_POINT
+ ]
+ },
+
+ // Target (other) body for the constraint.
+ target: { type: "selector" },
+
+ // Offset of the hinge or point-to-point constraint, defined locally in the body. Used for hinge, coneTwist pointToPoint constraints.
+ pivot: { type: "vec3" },
+ targetPivot: { type: "vec3" },
+
+ // An axis that each body can rotate around, defined locally to that body. Used for hinge constraints.
+ axis: { type: "vec3", default: { x: 0, y: 0, z: 1 } },
+ targetAxis: { type: "vec3", default: { x: 0, y: 0, z: 1 } },
+
+ // damping & stuffness - used for spring contraints only
+ damping: { type: "number", default: 1 },
+ stiffness: { type: "number", default: 100 },
+ },
+
+ init: function() {
+ this.system = this.el.sceneEl.systems.physics;
+ this.constraint = null;
+ },
+
+ remove: function() {
+ if (!this.constraint) return;
+
+ this.system.removeConstraint(this.constraint);
+ this.constraint = null;
+ },
+
+ update: function() {
+ const el = this.el,
+ data = this.data;
+
+ this.remove();
+
+ if (!el.body || !data.target.body) {
+ (el.body ? data.target : el).addEventListener("body-loaded", this.update.bind(this, {}), { once: true });
+ return;
+ }
+
+ this.constraint = this.createConstraint();
+ this.system.addConstraint(this.constraint);
+ },
+
+ /**
+ * @return {Ammo.btTypedConstraint}
+ */
+ createConstraint: function() {
+ let constraint;
+ const data = this.data,
+ body = this.el.body,
+ targetBody = data.target.body;
+
+ const bodyTransform = body
+ .getCenterOfMassTransform()
+ .inverse()
+ .op_mul(targetBody.getWorldTransform());
+ const targetTransform = new Ammo.btTransform();
+ targetTransform.setIdentity();
+
+ switch (data.type) {
+ case CONSTRAINT.LOCK: {
+ constraint = new Ammo.btGeneric6DofConstraint(body, targetBody, bodyTransform, targetTransform, true);
+ const zero = new Ammo.btVector3(0, 0, 0);
+ //TODO: allow these to be configurable
+ constraint.setLinearLowerLimit(zero);
+ constraint.setLinearUpperLimit(zero);
+ constraint.setAngularLowerLimit(zero);
+ constraint.setAngularUpperLimit(zero);
+ Ammo.destroy(zero);
+ break;
+ }
+ //TODO: test and verify all other constraint types
+ case CONSTRAINT.FIXED: {
+ //btFixedConstraint does not seem to debug render
+ bodyTransform.setRotation(body.getWorldTransform().getRotation());
+ targetTransform.setRotation(targetBody.getWorldTransform().getRotation());
+ constraint = new Ammo.btFixedConstraint(body, targetBody, bodyTransform, targetTransform);
+ break;
+ }
+ case CONSTRAINT.SPRING: {
+ constraint = new Ammo.btGeneric6DofSpringConstraint(body, targetBody, bodyTransform, targetTransform, true);
+
+ // Very limited initial implementation of spring constraint.
+ // See: https://github.com/n5ro/aframe-physics-system/issues/171
+ for (var i in [0,1,2,3,4,5]) {
+ constraint.enableSpring(1, true)
+ constraint.setStiffness(1, this.data.stiffness)
+ constraint.setDamping(1, this.data.damping)
+ }
+ const upper = new Ammo.btVector3(-1, -1, -1);
+ const lower = new Ammo.btVector3(1, 1, 1);
+ constraint.setLinearUpperLimit(upper);
+ constraint.setLinearLowerLimit(lower)
+ Ammo.destroy(upper);
+ Ammo.destroy(lower);
+ break;
+ }
+ case CONSTRAINT.SLIDER: {
+ //TODO: support setting linear and angular limits
+ constraint = new Ammo.btSliderConstraint(body, targetBody, bodyTransform, targetTransform, true);
+ constraint.setLowerLinLimit(-1);
+ constraint.setUpperLinLimit(1);
+ // constraint.setLowerAngLimit();
+ // constraint.setUpperAngLimit();
+ break;
+ }
+ case CONSTRAINT.HINGE: {
+ const pivot = new Ammo.btVector3(data.pivot.x, data.pivot.y, data.pivot.z);
+ const targetPivot = new Ammo.btVector3(data.targetPivot.x, data.targetPivot.y, data.targetPivot.z);
+
+ const axis = new Ammo.btVector3(data.axis.x, data.axis.y, data.axis.z);
+ const targetAxis = new Ammo.btVector3(data.targetAxis.x, data.targetAxis.y, data.targetAxis.z);
+
+ constraint = new Ammo.btHingeConstraint(body, targetBody, pivot, targetPivot, axis, targetAxis, true);
+
+ Ammo.destroy(pivot);
+ Ammo.destroy(targetPivot);
+ Ammo.destroy(axis);
+ Ammo.destroy(targetAxis);
+ break;
+ }
+ case CONSTRAINT.CONE_TWIST: {
+ const pivotTransform = new Ammo.btTransform();
+ pivotTransform.setIdentity();
+ pivotTransform.getOrigin().setValue(data.pivot.x, data.pivot.y, data.pivot.z);
+ const targetPivotTransform = new Ammo.btTransform();
+ targetPivotTransform.setIdentity();
+ targetPivotTransform.getOrigin().setValue(data.targetPivot.x, data.targetPivot.y, data.targetPivot.z);
+ constraint = new Ammo.btConeTwistConstraint(body, targetBody, pivotTransform, targetPivotTransform);
+ Ammo.destroy(pivotTransform);
+ Ammo.destroy(targetPivotTransform);
+ break;
+ }
+ case CONSTRAINT.POINT_TO_POINT: {
+ const pivot = new Ammo.btVector3(data.pivot.x, data.pivot.y, data.pivot.z);
+ const targetPivot = new Ammo.btVector3(data.targetPivot.x, data.targetPivot.y, data.targetPivot.z);
+
+ constraint = new Ammo.btPoint2PointConstraint(body, targetBody, pivot, targetPivot);
+
+ Ammo.destroy(pivot);
+ Ammo.destroy(targetPivot);
+ break;
+ }
+ default:
+ throw new Error("[constraint] Unexpected type: " + data.type);
+ }
+
+ Ammo.destroy(targetTransform);
+
+ return constraint;
+ }
+});
},{"../constants":20}],10:[function(require,module,exports){
-/* global Ammo,THREE */
-const AmmoDebugDrawer = require("ammo-debug-drawer");
-const threeToAmmo = require("three-to-ammo");
-const CONSTANTS = require("../../constants"),
- ACTIVATION_STATE = CONSTANTS.ACTIVATION_STATE,
- COLLISION_FLAG = CONSTANTS.COLLISION_FLAG,
- SHAPE = CONSTANTS.SHAPE,
- TYPE = CONSTANTS.TYPE,
- FIT = CONSTANTS.FIT;
-
-const ACTIVATION_STATES = [
- ACTIVATION_STATE.ACTIVE_TAG,
- ACTIVATION_STATE.ISLAND_SLEEPING,
- ACTIVATION_STATE.WANTS_DEACTIVATION,
- ACTIVATION_STATE.DISABLE_DEACTIVATION,
- ACTIVATION_STATE.DISABLE_SIMULATION
-];
-
-const RIGID_BODY_FLAGS = {
- NONE: 0,
- DISABLE_WORLD_GRAVITY: 1
-};
-
-function almostEqualsVector3(epsilon, u, v) {
- return Math.abs(u.x - v.x) < epsilon && Math.abs(u.y - v.y) < epsilon && Math.abs(u.z - v.z) < epsilon;
-}
-
-function almostEqualsBtVector3(epsilon, u, v) {
- return Math.abs(u.x() - v.x()) < epsilon && Math.abs(u.y() - v.y()) < epsilon && Math.abs(u.z() - v.z()) < epsilon;
-}
-
-function almostEqualsQuaternion(epsilon, u, v) {
- return (
- (Math.abs(u.x - v.x) < epsilon &&
- Math.abs(u.y - v.y) < epsilon &&
- Math.abs(u.z - v.z) < epsilon &&
- Math.abs(u.w - v.w) < epsilon) ||
- (Math.abs(u.x + v.x) < epsilon &&
- Math.abs(u.y + v.y) < epsilon &&
- Math.abs(u.z + v.z) < epsilon &&
- Math.abs(u.w + v.w) < epsilon)
- );
-}
-
-let AmmoBody = {
- schema: {
- loadedEvent: { default: "" },
- mass: { default: 1 },
- gravity: { type: "vec3", default: null },
- linearDamping: { default: 0.01 },
- angularDamping: { default: 0.01 },
- linearSleepingThreshold: { default: 1.6 },
- angularSleepingThreshold: { default: 2.5 },
- angularFactor: { type: "vec3", default: { x: 1, y: 1, z: 1 } },
- activationState: {
- default: ACTIVATION_STATE.ACTIVE_TAG,
- oneOf: ACTIVATION_STATES
- },
- type: { default: "dynamic", oneOf: [TYPE.STATIC, TYPE.DYNAMIC, TYPE.KINEMATIC] },
- emitCollisionEvents: { default: false },
- disableCollision: { default: false },
- collisionFilterGroup: { default: 1 }, //32-bit mask,
- collisionFilterMask: { default: 1 }, //32-bit mask
- scaleAutoUpdate: { default: true },
- restitution: {default: 0} // does not support updates
- },
-
- /**
- * Initializes a body component, assigning it to the physics system and binding listeners for
- * parsing the elements geometry.
- */
- init: function() {
- this.system = this.el.sceneEl.systems.physics;
- this.shapeComponents = [];
-
- if (this.data.loadedEvent === "") {
- this.loadedEventFired = true;
- } else {
- this.el.addEventListener(
- this.data.loadedEvent,
- () => {
- this.loadedEventFired = true;
- },
- { once: true }
- );
- }
-
- if (this.system.initialized && this.loadedEventFired) {
- this.initBody();
- }
- },
-
- /**
- * Parses an element's geometry and component metadata to create an Ammo body instance for the
- * component.
- */
- initBody: (function() {
- const pos = new THREE.Vector3();
- const quat = new THREE.Quaternion();
- const boundingBox = new THREE.Box3();
-
- return function() {
- const el = this.el,
- data = this.data;
- const clamp = (num, min, max) => Math.min(Math.max(num, min), max)
-
- this.localScaling = new Ammo.btVector3();
-
- const obj = this.el.object3D;
- obj.getWorldPosition(pos);
- obj.getWorldQuaternion(quat);
-
- this.prevScale = new THREE.Vector3(1, 1, 1);
- this.prevNumChildShapes = 0;
-
- this.msTransform = new Ammo.btTransform();
- this.msTransform.setIdentity();
- this.rotation = new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w);
-
- this.msTransform.getOrigin().setValue(pos.x, pos.y, pos.z);
- this.msTransform.setRotation(this.rotation);
-
- this.motionState = new Ammo.btDefaultMotionState(this.msTransform);
-
- this.localInertia = new Ammo.btVector3(0, 0, 0);
-
- this.compoundShape = new Ammo.btCompoundShape(true);
-
- this.rbInfo = new Ammo.btRigidBodyConstructionInfo(
- data.mass,
- this.motionState,
- this.compoundShape,
- this.localInertia
- );
- this.rbInfo.m_restitution = clamp(this.data.restitution, 0, 1);
- this.body = new Ammo.btRigidBody(this.rbInfo);
- this.body.setActivationState(ACTIVATION_STATES.indexOf(data.activationState) + 1);
- this.body.setSleepingThresholds(data.linearSleepingThreshold, data.angularSleepingThreshold);
-
- this.body.setDamping(data.linearDamping, data.angularDamping);
-
- const angularFactor = new Ammo.btVector3(data.angularFactor.x, data.angularFactor.y, data.angularFactor.z);
- this.body.setAngularFactor(angularFactor);
- Ammo.destroy(angularFactor);
-
- this._updateBodyGravity(data.gravity)
-
- this.updateCollisionFlags();
-
- this.el.body = this.body;
- this.body.el = el;
-
- this.isLoaded = true;
-
- this.el.emit("body-loaded", { body: this.el.body });
-
- this._addToSystem();
- };
- })(),
-
- tick: function() {
- if (this.system.initialized && !this.isLoaded && this.loadedEventFired) {
- this.initBody();
- }
- },
-
- _updateBodyGravity(gravity) {
-
- if (gravity.x !== undefined &&
- gravity.y !== undefined &&
- gravity.z !== undefined) {
- const gravityBtVec = new Ammo.btVector3(gravity.x, gravity.y, gravity.z);
- if (!almostEqualsBtVector3(0.001, gravityBtVec, this.system.driver.physicsWorld.getGravity())) {
- this.body.setFlags(RIGID_BODY_FLAGS.DISABLE_WORLD_GRAVITY);
- } else {
- this.body.setFlags(RIGID_BODY_FLAGS.NONE);
- }
- this.body.setGravity(gravityBtVec);
- Ammo.destroy(gravityBtVec);
- }
- else {
- // no per-body gravity specified - just use world gravity
- this.body.setFlags(RIGID_BODY_FLAGS.NONE);
- }
- },
-
- _updateShapes: (function() {
- const needsPolyhedralInitialization = [SHAPE.HULL, SHAPE.HACD, SHAPE.VHACD];
- return function() {
- let updated = false;
-
- const obj = this.el.object3D;
- if (this.data.scaleAutoUpdate && this.prevScale && !almostEqualsVector3(0.001, obj.scale, this.prevScale)) {
- this.prevScale.copy(obj.scale);
- updated = true;
-
- this.localScaling.setValue(this.prevScale.x, this.prevScale.y, this.prevScale.z);
- this.compoundShape.setLocalScaling(this.localScaling);
- }
-
- if (this.shapeComponentsChanged) {
- this.shapeComponentsChanged = false;
- updated = true;
- for (let i = 0; i < this.shapeComponents.length; i++) {
- const shapeComponent = this.shapeComponents[i];
- if (shapeComponent.getShapes().length === 0) {
- this._createCollisionShape(shapeComponent);
- }
- const collisionShapes = shapeComponent.getShapes();
- for (let j = 0; j < collisionShapes.length; j++) {
- const collisionShape = collisionShapes[j];
- if (!collisionShape.added) {
- this.compoundShape.addChildShape(collisionShape.localTransform, collisionShape);
- collisionShape.added = true;
- }
- }
- }
-
- if (this.data.type === TYPE.DYNAMIC) {
- this.updateMass();
- }
-
- this.system.driver.updateBody(this.body);
- }
-
- //call initializePolyhedralFeatures for hull shapes if debug is turned on and/or scale changes
- if (this.system.debug && (updated || !this.polyHedralFeaturesInitialized)) {
- for (let i = 0; i < this.shapeComponents.length; i++) {
- const collisionShapes = this.shapeComponents[i].getShapes();
- for (let j = 0; j < collisionShapes.length; j++) {
- const collisionShape = collisionShapes[j];
- if (needsPolyhedralInitialization.indexOf(collisionShape.type) !== -1) {
- collisionShape.initializePolyhedralFeatures(0);
- }
- }
- }
- this.polyHedralFeaturesInitialized = true;
- }
- };
- })(),
-
- _createCollisionShape: function(shapeComponent) {
- const data = shapeComponent.data;
- const vertices = [];
- const matrices = [];
- const indexes = [];
-
- const root = shapeComponent.el.object3D;
- const matrixWorld = root.matrixWorld;
-
- threeToAmmo.iterateGeometries(root, data, (vertexArray, matrixArray, indexArray) => {
- vertices.push(vertexArray);
- matrices.push(matrixArray);
- indexes.push(indexArray);
- });
-
- const collisionShapes = threeToAmmo.createCollisionShapes(vertices, matrices, indexes, matrixWorld.elements, data);
- shapeComponent.addShapes(collisionShapes);
- return;
- },
-
- /**
- * Registers the component with the physics system.
- */
- play: function() {
- if (this.isLoaded) {
- this._addToSystem();
- }
- },
-
- _addToSystem: function() {
- if (!this.addedToSystem) {
- this.system.addBody(this.body, this.data.collisionFilterGroup, this.data.collisionFilterMask);
-
- if (this.data.emitCollisionEvents) {
- this.system.driver.addEventListener(this.body);
- }
-
- this.system.addComponent(this);
- this.addedToSystem = true;
- }
- },
-
- /**
- * Unregisters the component with the physics system.
- */
- pause: function() {
- if (this.addedToSystem) {
- this.system.removeComponent(this);
- this.system.removeBody(this.body);
- this.addedToSystem = false;
- }
- },
-
- /**
- * Updates the rigid body instance, where possible.
- */
- update: function(prevData) {
- if (this.isLoaded) {
- if (!this.hasUpdated) {
- //skip the first update
- this.hasUpdated = true;
- return;
- }
-
- const data = this.data;
-
- if (prevData.type !== data.type || prevData.disableCollision !== data.disableCollision) {
- this.updateCollisionFlags();
- }
-
- if (prevData.activationState !== data.activationState) {
- this.body.forceActivationState(ACTIVATION_STATES.indexOf(data.activationState) + 1);
- if (data.activationState === ACTIVATION_STATE.ACTIVE_TAG) {
- this.body.activate(true);
- }
- }
-
- if (
- prevData.collisionFilterGroup !== data.collisionFilterGroup ||
- prevData.collisionFilterMask !== data.collisionFilterMask
- ) {
- const broadphaseProxy = this.body.getBroadphaseProxy();
- broadphaseProxy.set_m_collisionFilterGroup(data.collisionFilterGroup);
- broadphaseProxy.set_m_collisionFilterMask(data.collisionFilterMask);
- this.system.driver.broadphase
- .getOverlappingPairCache()
- .removeOverlappingPairsContainingProxy(broadphaseProxy, this.system.driver.dispatcher);
- }
-
- if (prevData.linearDamping != data.linearDamping || prevData.angularDamping != data.angularDamping) {
- this.body.setDamping(data.linearDamping, data.angularDamping);
- }
-
- if (!almostEqualsVector3(0.001, prevData.gravity, data.gravity)) {
- this._updateBodyGravity(data.gravity)
- }
-
- if (
- prevData.linearSleepingThreshold != data.linearSleepingThreshold ||
- prevData.angularSleepingThreshold != data.angularSleepingThreshold
- ) {
- this.body.setSleepingThresholds(data.linearSleepingThreshold, data.angularSleepingThreshold);
- }
-
- if (!almostEqualsVector3(0.001, prevData.angularFactor, data.angularFactor)) {
- const angularFactor = new Ammo.btVector3(data.angularFactor.x, data.angularFactor.y, data.angularFactor.z);
- this.body.setAngularFactor(angularFactor);
- Ammo.destroy(angularFactor);
- }
-
- if (prevData.restitution != data.restitution ) {
- console.warn("ammo-body restitution cannot be updated from its initial value.")
- }
-
- //TODO: support dynamic update for other properties
- }
- },
-
- /**
- * Removes the component and all physics and scene side effects.
- */
- remove: function() {
- if (this.triMesh) Ammo.destroy(this.triMesh);
- if (this.localScaling) Ammo.destroy(this.localScaling);
- if (this.compoundShape) Ammo.destroy(this.compoundShape);
- if (this.body) {
- Ammo.destroy(this.body);
- delete this.body;
- }
- Ammo.destroy(this.rbInfo);
- Ammo.destroy(this.msTransform);
- Ammo.destroy(this.motionState);
- Ammo.destroy(this.localInertia);
- Ammo.destroy(this.rotation);
- },
-
- beforeStep: function() {
- this._updateShapes();
- // Note that since static objects don't move,
- // we don't sync them to physics on a routine basis.
- if (this.data.type === TYPE.KINEMATIC) {
- this.syncToPhysics();
- }
- },
-
- step: function() {
- if (this.data.type === TYPE.DYNAMIC) {
- this.syncFromPhysics();
- }
- },
-
- /**
- * Updates the rigid body's position, velocity, and rotation, based on the scene.
- */
- syncToPhysics: (function() {
- const q = new THREE.Quaternion();
- const v = new THREE.Vector3();
- const q2 = new THREE.Vector3();
- const v2 = new THREE.Vector3();
- return function() {
- const el = this.el,
- parentEl = el.parentEl,
- body = this.body;
-
- if (!body) return;
-
- this.motionState.getWorldTransform(this.msTransform);
-
- if (parentEl.isScene) {
- v.copy(el.object3D.position);
- q.copy(el.object3D.quaternion);
- } else {
- el.object3D.getWorldPosition(v);
- el.object3D.getWorldQuaternion(q);
- }
-
- const position = this.msTransform.getOrigin();
- v2.set(position.x(), position.y(), position.z());
-
- const quaternion = this.msTransform.getRotation();
- q2.set(quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w());
-
- if (!almostEqualsVector3(0.001, v, v2) || !almostEqualsQuaternion(0.001, q, q2)) {
- if (!this.body.isActive()) {
- this.body.activate(true);
- }
- this.msTransform.getOrigin().setValue(v.x, v.y, v.z);
- this.rotation.setValue(q.x, q.y, q.z, q.w);
- this.msTransform.setRotation(this.rotation);
- this.motionState.setWorldTransform(this.msTransform);
-
- if (this.data.type !== TYPE.KINEMATIC) {
- this.body.setCenterOfMassTransform(this.msTransform);
- }
- }
- };
- })(),
-
- /**
- * Updates the scene object's position and rotation, based on the physics simulation.
- */
- syncFromPhysics: (function() {
- const v = new THREE.Vector3(),
- q1 = new THREE.Quaternion(),
- q2 = new THREE.Quaternion();
- return function() {
- this.motionState.getWorldTransform(this.msTransform);
- const position = this.msTransform.getOrigin();
- const quaternion = this.msTransform.getRotation();
-
- const el = this.el,
- body = this.body;
-
- // For the parent, prefer to use the THHREE.js scene graph parent (if it can be determined)
- // and only use the HTML scene graph parent as a fallback.
- // Usually these are the same, but there are various cases where it's useful to modify the THREE.js
- // scene graph so that it deviates from the HTML.
- // In these cases the THREE.js scene graph should be considered the definitive reference in terms
- // of object positioning etc.
- // For specific examples, and more discussion, see:
- // https://github.com/c-frame/aframe-physics-system/pull/1#issuecomment-1264686433
- const parentEl = el.object3D.parent.el ? el.object3D.parent.el : el.parentEl;
-
- if (!body) return;
- if (!parentEl) return;
-
- if (parentEl.isScene) {
- el.object3D.position.set(position.x(), position.y(), position.z());
- el.object3D.quaternion.set(quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w());
- } else {
- q1.set(quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w());
- parentEl.object3D.getWorldQuaternion(q2);
- q1.multiply(q2.invert());
- el.object3D.quaternion.copy(q1);
-
- v.set(position.x(), position.y(), position.z());
- parentEl.object3D.worldToLocal(v);
- el.object3D.position.copy(v);
- }
- };
- })(),
-
- addShapeComponent: function(shapeComponent) {
- if (shapeComponent.data.type === SHAPE.MESH && this.data.type !== TYPE.STATIC) {
- console.warn("non-static mesh colliders not supported");
- return;
- }
-
- this.shapeComponents.push(shapeComponent);
- this.shapeComponentsChanged = true;
- },
-
- removeShapeComponent: function(shapeComponent) {
- const index = this.shapeComponents.indexOf(shapeComponent);
- if (this.compoundShape && index !== -1 && this.body) {
- const shapes = shapeComponent.getShapes();
- for (var i = 0; i < shapes.length; i++) {
- this.compoundShape.removeChildShape(shapes[i]);
- }
- this.shapeComponentsChanged = true;
- this.shapeComponents.splice(index, 1);
- }
- },
-
- updateMass: function() {
- const shape = this.body.getCollisionShape();
- const mass = this.data.type === TYPE.DYNAMIC ? this.data.mass : 0;
- shape.calculateLocalInertia(mass, this.localInertia);
- this.body.setMassProps(mass, this.localInertia);
- this.body.updateInertiaTensor();
- },
-
- updateCollisionFlags: function() {
- let flags = this.data.disableCollision ? 4 : 0;
- switch (this.data.type) {
- case TYPE.STATIC:
- flags |= COLLISION_FLAG.STATIC_OBJECT;
- break;
- case TYPE.KINEMATIC:
- flags |= COLLISION_FLAG.KINEMATIC_OBJECT;
- break;
- default:
- this.body.applyGravity();
- break;
- }
- this.body.setCollisionFlags(flags);
-
- this.updateMass();
-
- // TODO: enable CCD if dynamic?
- // this.body.setCcdMotionThreshold(0.001);
- // this.body.setCcdSweptSphereRadius(0.001);
-
- this.system.driver.updateBody(this.body);
- },
-
- getVelocity: function() {
- return this.body.getLinearVelocity();
- }
-};
-
-module.exports.definition = AmmoBody;
-module.exports.Component = AFRAME.registerComponent("ammo-body", AmmoBody);
+/* global Ammo,THREE */
+const AmmoDebugDrawer = require("ammo-debug-drawer");
+const threeToAmmo = require("three-to-ammo");
+const CONSTANTS = require("../../constants"),
+ ACTIVATION_STATE = CONSTANTS.ACTIVATION_STATE,
+ COLLISION_FLAG = CONSTANTS.COLLISION_FLAG,
+ SHAPE = CONSTANTS.SHAPE,
+ TYPE = CONSTANTS.TYPE,
+ FIT = CONSTANTS.FIT;
+
+const ACTIVATION_STATES = [
+ ACTIVATION_STATE.ACTIVE_TAG,
+ ACTIVATION_STATE.ISLAND_SLEEPING,
+ ACTIVATION_STATE.WANTS_DEACTIVATION,
+ ACTIVATION_STATE.DISABLE_DEACTIVATION,
+ ACTIVATION_STATE.DISABLE_SIMULATION
+];
+
+const RIGID_BODY_FLAGS = {
+ NONE: 0,
+ DISABLE_WORLD_GRAVITY: 1
+};
+
+function almostEqualsVector3(epsilon, u, v) {
+ return Math.abs(u.x - v.x) < epsilon && Math.abs(u.y - v.y) < epsilon && Math.abs(u.z - v.z) < epsilon;
+}
+
+function almostEqualsBtVector3(epsilon, u, v) {
+ return Math.abs(u.x() - v.x()) < epsilon && Math.abs(u.y() - v.y()) < epsilon && Math.abs(u.z() - v.z()) < epsilon;
+}
+
+function almostEqualsQuaternion(epsilon, u, v) {
+ return (
+ (Math.abs(u.x - v.x) < epsilon &&
+ Math.abs(u.y - v.y) < epsilon &&
+ Math.abs(u.z - v.z) < epsilon &&
+ Math.abs(u.w - v.w) < epsilon) ||
+ (Math.abs(u.x + v.x) < epsilon &&
+ Math.abs(u.y + v.y) < epsilon &&
+ Math.abs(u.z + v.z) < epsilon &&
+ Math.abs(u.w + v.w) < epsilon)
+ );
+}
+
+let AmmoBody = {
+ schema: {
+ loadedEvent: { default: "" },
+ mass: { default: 1 },
+ gravity: { type: "vec3", default: null },
+ linearDamping: { default: 0.01 },
+ angularDamping: { default: 0.01 },
+ linearSleepingThreshold: { default: 1.6 },
+ angularSleepingThreshold: { default: 2.5 },
+ angularFactor: { type: "vec3", default: { x: 1, y: 1, z: 1 } },
+ activationState: {
+ default: ACTIVATION_STATE.ACTIVE_TAG,
+ oneOf: ACTIVATION_STATES
+ },
+ type: { default: "dynamic", oneOf: [TYPE.STATIC, TYPE.DYNAMIC, TYPE.KINEMATIC] },
+ emitCollisionEvents: { default: false },
+ disableCollision: { default: false },
+ collisionFilterGroup: { default: 1 }, //32-bit mask,
+ collisionFilterMask: { default: 1 }, //32-bit mask
+ scaleAutoUpdate: { default: true },
+ restitution: {default: 0} // does not support updates
+ },
+
+ /**
+ * Initializes a body component, assigning it to the physics system and binding listeners for
+ * parsing the elements geometry.
+ */
+ init: function() {
+ this.system = this.el.sceneEl.systems.physics;
+ this.shapeComponents = [];
+
+ if (this.data.loadedEvent === "") {
+ this.loadedEventFired = true;
+ } else {
+ this.el.addEventListener(
+ this.data.loadedEvent,
+ () => {
+ this.loadedEventFired = true;
+ },
+ { once: true }
+ );
+ }
+
+ if (this.system.initialized && this.loadedEventFired) {
+ this.initBody();
+ }
+ },
+
+ /**
+ * Parses an element's geometry and component metadata to create an Ammo body instance for the
+ * component.
+ */
+ initBody: (function() {
+ const pos = new THREE.Vector3();
+ const quat = new THREE.Quaternion();
+ const boundingBox = new THREE.Box3();
+
+ return function() {
+ const el = this.el,
+ data = this.data;
+ const clamp = (num, min, max) => Math.min(Math.max(num, min), max)
+
+ this.localScaling = new Ammo.btVector3();
+
+ const obj = this.el.object3D;
+ obj.getWorldPosition(pos);
+ obj.getWorldQuaternion(quat);
+
+ this.prevScale = new THREE.Vector3(1, 1, 1);
+ this.prevNumChildShapes = 0;
+
+ this.msTransform = new Ammo.btTransform();
+ this.msTransform.setIdentity();
+ this.rotation = new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w);
+
+ this.msTransform.getOrigin().setValue(pos.x, pos.y, pos.z);
+ this.msTransform.setRotation(this.rotation);
+
+ this.motionState = new Ammo.btDefaultMotionState(this.msTransform);
+
+ this.localInertia = new Ammo.btVector3(0, 0, 0);
+
+ this.compoundShape = new Ammo.btCompoundShape(true);
+
+ this.rbInfo = new Ammo.btRigidBodyConstructionInfo(
+ data.mass,
+ this.motionState,
+ this.compoundShape,
+ this.localInertia
+ );
+ this.rbInfo.m_restitution = clamp(this.data.restitution, 0, 1);
+ this.body = new Ammo.btRigidBody(this.rbInfo);
+ this.body.setActivationState(ACTIVATION_STATES.indexOf(data.activationState) + 1);
+ this.body.setSleepingThresholds(data.linearSleepingThreshold, data.angularSleepingThreshold);
+
+ this.body.setDamping(data.linearDamping, data.angularDamping);
+
+ const angularFactor = new Ammo.btVector3(data.angularFactor.x, data.angularFactor.y, data.angularFactor.z);
+ this.body.setAngularFactor(angularFactor);
+ Ammo.destroy(angularFactor);
+
+ this._updateBodyGravity(data.gravity)
+
+ this.updateCollisionFlags();
+
+ this.el.body = this.body;
+ this.body.el = el;
+
+ this.isLoaded = true;
+
+ this.el.emit("body-loaded", { body: this.el.body });
+
+ this._addToSystem();
+ };
+ })(),
+
+ tick: function() {
+ if (this.system.initialized && !this.isLoaded && this.loadedEventFired) {
+ this.initBody();
+ }
+ },
+
+ _updateBodyGravity(gravity) {
+
+ if (gravity.x !== undefined &&
+ gravity.y !== undefined &&
+ gravity.z !== undefined) {
+ const gravityBtVec = new Ammo.btVector3(gravity.x, gravity.y, gravity.z);
+ if (!almostEqualsBtVector3(0.001, gravityBtVec, this.system.driver.physicsWorld.getGravity())) {
+ this.body.setFlags(RIGID_BODY_FLAGS.DISABLE_WORLD_GRAVITY);
+ } else {
+ this.body.setFlags(RIGID_BODY_FLAGS.NONE);
+ }
+ this.body.setGravity(gravityBtVec);
+ Ammo.destroy(gravityBtVec);
+ }
+ else {
+ // no per-body gravity specified - just use world gravity
+ this.body.setFlags(RIGID_BODY_FLAGS.NONE);
+ }
+ },
+
+ _updateShapes: (function() {
+ const needsPolyhedralInitialization = [SHAPE.HULL, SHAPE.HACD, SHAPE.VHACD];
+ return function() {
+ let updated = false;
+
+ const obj = this.el.object3D;
+ if (this.data.scaleAutoUpdate && this.prevScale && !almostEqualsVector3(0.001, obj.scale, this.prevScale)) {
+ this.prevScale.copy(obj.scale);
+ updated = true;
+
+ this.localScaling.setValue(this.prevScale.x, this.prevScale.y, this.prevScale.z);
+ this.compoundShape.setLocalScaling(this.localScaling);
+ }
+
+ if (this.shapeComponentsChanged) {
+ this.shapeComponentsChanged = false;
+ updated = true;
+ for (let i = 0; i < this.shapeComponents.length; i++) {
+ const shapeComponent = this.shapeComponents[i];
+ if (shapeComponent.getShapes().length === 0) {
+ this._createCollisionShape(shapeComponent);
+ }
+ const collisionShapes = shapeComponent.getShapes();
+ for (let j = 0; j < collisionShapes.length; j++) {
+ const collisionShape = collisionShapes[j];
+ if (!collisionShape.added) {
+ this.compoundShape.addChildShape(collisionShape.localTransform, collisionShape);
+ collisionShape.added = true;
+ }
+ }
+ }
+
+ if (this.data.type === TYPE.DYNAMIC) {
+ this.updateMass();
+ }
+
+ this.system.driver.updateBody(this.body);
+ }
+
+ //call initializePolyhedralFeatures for hull shapes if debug is turned on and/or scale changes
+ if (this.system.debug && (updated || !this.polyHedralFeaturesInitialized)) {
+ for (let i = 0; i < this.shapeComponents.length; i++) {
+ const collisionShapes = this.shapeComponents[i].getShapes();
+ for (let j = 0; j < collisionShapes.length; j++) {
+ const collisionShape = collisionShapes[j];
+ if (needsPolyhedralInitialization.indexOf(collisionShape.type) !== -1) {
+ collisionShape.initializePolyhedralFeatures(0);
+ }
+ }
+ }
+ this.polyHedralFeaturesInitialized = true;
+ }
+ };
+ })(),
+
+ _createCollisionShape: function(shapeComponent) {
+ const data = shapeComponent.data;
+ const vertices = [];
+ const matrices = [];
+ const indexes = [];
+
+ const root = shapeComponent.el.object3D;
+ const matrixWorld = root.matrixWorld;
+
+ threeToAmmo.iterateGeometries(root, data, (vertexArray, matrixArray, indexArray) => {
+ vertices.push(vertexArray);
+ matrices.push(matrixArray);
+ indexes.push(indexArray);
+ });
+
+ const collisionShapes = threeToAmmo.createCollisionShapes(vertices, matrices, indexes, matrixWorld.elements, data);
+ shapeComponent.addShapes(collisionShapes);
+ return;
+ },
+
+ /**
+ * Registers the component with the physics system.
+ */
+ play: function() {
+ if (this.isLoaded) {
+ this._addToSystem();
+ }
+ },
+
+ _addToSystem: function() {
+ if (!this.addedToSystem) {
+ this.system.addBody(this.body, this.data.collisionFilterGroup, this.data.collisionFilterMask);
+
+ if (this.data.emitCollisionEvents) {
+ this.system.driver.addEventListener(this.body);
+ }
+
+ this.system.addComponent(this);
+ this.addedToSystem = true;
+ }
+ },
+
+ /**
+ * Unregisters the component with the physics system.
+ */
+ pause: function() {
+ if (this.addedToSystem) {
+ this.system.removeComponent(this);
+ this.system.removeBody(this.body);
+ this.addedToSystem = false;
+ }
+ },
+
+ /**
+ * Updates the rigid body instance, where possible.
+ */
+ update: function(prevData) {
+ if (this.isLoaded) {
+ if (!this.hasUpdated) {
+ //skip the first update
+ this.hasUpdated = true;
+ return;
+ }
+
+ const data = this.data;
+
+ if (prevData.type !== data.type || prevData.disableCollision !== data.disableCollision) {
+ this.updateCollisionFlags();
+ }
+
+ if (prevData.activationState !== data.activationState) {
+ this.body.forceActivationState(ACTIVATION_STATES.indexOf(data.activationState) + 1);
+ if (data.activationState === ACTIVATION_STATE.ACTIVE_TAG) {
+ this.body.activate(true);
+ }
+ }
+
+ if (
+ prevData.collisionFilterGroup !== data.collisionFilterGroup ||
+ prevData.collisionFilterMask !== data.collisionFilterMask
+ ) {
+ const broadphaseProxy = this.body.getBroadphaseProxy();
+ broadphaseProxy.set_m_collisionFilterGroup(data.collisionFilterGroup);
+ broadphaseProxy.set_m_collisionFilterMask(data.collisionFilterMask);
+ this.system.driver.broadphase
+ .getOverlappingPairCache()
+ .removeOverlappingPairsContainingProxy(broadphaseProxy, this.system.driver.dispatcher);
+ }
+
+ if (prevData.linearDamping != data.linearDamping || prevData.angularDamping != data.angularDamping) {
+ this.body.setDamping(data.linearDamping, data.angularDamping);
+ }
+
+ if (!almostEqualsVector3(0.001, prevData.gravity, data.gravity)) {
+ this._updateBodyGravity(data.gravity)
+ }
+
+ if (
+ prevData.linearSleepingThreshold != data.linearSleepingThreshold ||
+ prevData.angularSleepingThreshold != data.angularSleepingThreshold
+ ) {
+ this.body.setSleepingThresholds(data.linearSleepingThreshold, data.angularSleepingThreshold);
+ }
+
+ if (!almostEqualsVector3(0.001, prevData.angularFactor, data.angularFactor)) {
+ const angularFactor = new Ammo.btVector3(data.angularFactor.x, data.angularFactor.y, data.angularFactor.z);
+ this.body.setAngularFactor(angularFactor);
+ Ammo.destroy(angularFactor);
+ }
+
+ if (prevData.restitution != data.restitution ) {
+ console.warn("ammo-body restitution cannot be updated from its initial value.")
+ }
+
+ //TODO: support dynamic update for other properties
+ }
+ },
+
+ /**
+ * Removes the component and all physics and scene side effects.
+ */
+ remove: function() {
+ if (this.triMesh) Ammo.destroy(this.triMesh);
+ if (this.localScaling) Ammo.destroy(this.localScaling);
+ if (this.compoundShape) Ammo.destroy(this.compoundShape);
+ if (this.body) {
+ Ammo.destroy(this.body);
+ delete this.body;
+ }
+ Ammo.destroy(this.rbInfo);
+ Ammo.destroy(this.msTransform);
+ Ammo.destroy(this.motionState);
+ Ammo.destroy(this.localInertia);
+ Ammo.destroy(this.rotation);
+ },
+
+ beforeStep: function() {
+ this._updateShapes();
+ // Note that since static objects don't move,
+ // we don't sync them to physics on a routine basis.
+ if (this.data.type === TYPE.KINEMATIC) {
+ this.syncToPhysics();
+ }
+ },
+
+ step: function() {
+ if (this.data.type === TYPE.DYNAMIC) {
+ this.syncFromPhysics();
+ }
+ },
+
+ /**
+ * Updates the rigid body's position, velocity, and rotation, based on the scene.
+ */
+ syncToPhysics: (function() {
+ const q = new THREE.Quaternion();
+ const v = new THREE.Vector3();
+ const q2 = new THREE.Vector3();
+ const v2 = new THREE.Vector3();
+ return function() {
+ const el = this.el,
+ parentEl = el.parentEl,
+ body = this.body;
+
+ if (!body) return;
+
+ this.motionState.getWorldTransform(this.msTransform);
+
+ if (parentEl.isScene) {
+ v.copy(el.object3D.position);
+ q.copy(el.object3D.quaternion);
+ } else {
+ el.object3D.getWorldPosition(v);
+ el.object3D.getWorldQuaternion(q);
+ }
+
+ const position = this.msTransform.getOrigin();
+ v2.set(position.x(), position.y(), position.z());
+
+ const quaternion = this.msTransform.getRotation();
+ q2.set(quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w());
+
+ if (!almostEqualsVector3(0.001, v, v2) || !almostEqualsQuaternion(0.001, q, q2)) {
+ if (!this.body.isActive()) {
+ this.body.activate(true);
+ }
+ this.msTransform.getOrigin().setValue(v.x, v.y, v.z);
+ this.rotation.setValue(q.x, q.y, q.z, q.w);
+ this.msTransform.setRotation(this.rotation);
+ this.motionState.setWorldTransform(this.msTransform);
+
+ if (this.data.type !== TYPE.KINEMATIC) {
+ this.body.setCenterOfMassTransform(this.msTransform);
+ }
+ }
+ };
+ })(),
+
+ /**
+ * Updates the scene object's position and rotation, based on the physics simulation.
+ */
+ syncFromPhysics: (function() {
+ const v = new THREE.Vector3(),
+ q1 = new THREE.Quaternion(),
+ q2 = new THREE.Quaternion();
+ return function() {
+ this.motionState.getWorldTransform(this.msTransform);
+ const position = this.msTransform.getOrigin();
+ const quaternion = this.msTransform.getRotation();
+
+ const el = this.el,
+ body = this.body;
+
+ // For the parent, prefer to use the THHREE.js scene graph parent (if it can be determined)
+ // and only use the HTML scene graph parent as a fallback.
+ // Usually these are the same, but there are various cases where it's useful to modify the THREE.js
+ // scene graph so that it deviates from the HTML.
+ // In these cases the THREE.js scene graph should be considered the definitive reference in terms
+ // of object positioning etc.
+ // For specific examples, and more discussion, see:
+ // https://github.com/c-frame/aframe-physics-system/pull/1#issuecomment-1264686433
+ const parentEl = el.object3D.parent.el ? el.object3D.parent.el : el.parentEl;
+
+ if (!body) return;
+ if (!parentEl) return;
+
+ if (parentEl.isScene) {
+ el.object3D.position.set(position.x(), position.y(), position.z());
+ el.object3D.quaternion.set(quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w());
+ } else {
+ q1.set(quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w());
+ parentEl.object3D.getWorldQuaternion(q2);
+ q1.multiply(q2.invert());
+ el.object3D.quaternion.copy(q1);
+
+ v.set(position.x(), position.y(), position.z());
+ parentEl.object3D.worldToLocal(v);
+ el.object3D.position.copy(v);
+ }
+ };
+ })(),
+
+ addShapeComponent: function(shapeComponent) {
+ if (shapeComponent.data.type === SHAPE.MESH && this.data.type !== TYPE.STATIC) {
+ console.warn("non-static mesh colliders not supported");
+ return;
+ }
+
+ this.shapeComponents.push(shapeComponent);
+ this.shapeComponentsChanged = true;
+ },
+
+ removeShapeComponent: function(shapeComponent) {
+ const index = this.shapeComponents.indexOf(shapeComponent);
+ if (this.compoundShape && index !== -1 && this.body) {
+ const shapes = shapeComponent.getShapes();
+ for (var i = 0; i < shapes.length; i++) {
+ this.compoundShape.removeChildShape(shapes[i]);
+ }
+ this.shapeComponentsChanged = true;
+ this.shapeComponents.splice(index, 1);
+ }
+ },
+
+ updateMass: function() {
+ const shape = this.body.getCollisionShape();
+ const mass = this.data.type === TYPE.DYNAMIC ? this.data.mass : 0;
+ shape.calculateLocalInertia(mass, this.localInertia);
+ this.body.setMassProps(mass, this.localInertia);
+ this.body.updateInertiaTensor();
+ },
+
+ updateCollisionFlags: function() {
+ let flags = this.data.disableCollision ? 4 : 0;
+ switch (this.data.type) {
+ case TYPE.STATIC:
+ flags |= COLLISION_FLAG.STATIC_OBJECT;
+ break;
+ case TYPE.KINEMATIC:
+ flags |= COLLISION_FLAG.KINEMATIC_OBJECT;
+ break;
+ default:
+ this.body.applyGravity();
+ break;
+ }
+ this.body.setCollisionFlags(flags);
+
+ this.updateMass();
+
+ // TODO: enable CCD if dynamic?
+ // this.body.setCcdMotionThreshold(0.001);
+ // this.body.setCcdSweptSphereRadius(0.001);
+
+ this.system.driver.updateBody(this.body);
+ },
+
+ getVelocity: function() {
+ return this.body.getLinearVelocity();
+ }
+};
+
+module.exports.definition = AmmoBody;
+module.exports.Component = AFRAME.registerComponent("ammo-body", AmmoBody);
+
+},{"../../constants":20,"ammo-debug-drawer":4,"three-to-ammo":6}],11:[function(require,module,exports){
+var CANNON = require('cannon-es');
+const { threeToCannon, ShapeType } = require('three-to-cannon');
+const identityQuaternion = new THREE.Quaternion()
+
+function mesh2shape (object, options) {
+
+ const result = threeToCannon(object, options);
+ return result;
+}
+
+require('../../../lib/CANNON-shape2mesh');
+
+var Body = {
+ dependencies: ['velocity'],
+
+ schema: {
+ mass: {default: 5, if: {type: 'dynamic'}},
+ linearDamping: { default: 0.01, if: {type: 'dynamic'}},
+ angularDamping: { default: 0.01, if: {type: 'dynamic'}},
+ shape: {default: 'auto', oneOf: ['auto', 'box', 'cylinder', 'sphere', 'hull', 'mesh', 'none']},
+ cylinderAxis: {default: 'y', oneOf: ['x', 'y', 'z']},
+ sphereRadius: {default: NaN},
+ type: {default: 'dynamic', oneOf: ['static', 'dynamic']}
+ },
+
+ /**
+ * Initializes a body component, assigning it to the physics system and binding listeners for
+ * parsing the elements geometry.
+ */
+ init: function () {
+ this.system = this.el.sceneEl.systems.physics;
+
+ if (this.el.sceneEl.hasLoaded) {
+ this.initBody();
+ } else {
+ this.el.sceneEl.addEventListener('loaded', this.initBody.bind(this));
+ }
+ },
+
+ /**
+ * Parses an element's geometry and component metadata to create a CANNON.Body instance for the
+ * component.
+ */
+ initBody: function () {
+ var el = this.el,
+ data = this.data;
+
+ var obj = this.el.object3D;
+ var pos = obj.position;
+ var quat = obj.quaternion;
+
+ this.body = new CANNON.Body({
+ mass: data.type === 'static' ? 0 : data.mass || 0,
+ material: this.system.getMaterial('defaultMaterial'),
+ position: new CANNON.Vec3(pos.x, pos.y, pos.z),
+ quaternion: new CANNON.Quaternion(quat.x, quat.y, quat.z, quat.w),
+ linearDamping: data.linearDamping,
+ angularDamping: data.angularDamping,
+ type: data.type === 'dynamic' ? CANNON.Body.DYNAMIC : CANNON.Body.STATIC,
+ });
+
+ // Matrix World must be updated at root level, if scale is to be applied – updateMatrixWorld()
+ // only checks an object's parent, not the rest of the ancestors. Hence, a wrapping entity with
+ // scale="0.5 0.5 0.5" will be ignored.
+ // Reference: https://github.com/mrdoob/three.js/blob/master/src/core/Object3D.js#L511-L541
+ // Potential fix: https://github.com/mrdoob/three.js/pull/7019
+ this.el.object3D.updateMatrixWorld(true);
+
+ if(data.shape !== 'none') {
+ var options = data.shape === 'auto' ? undefined : AFRAME.utils.extend({}, this.data, {
+ type: ShapeType[data.shape.toUpperCase()]
+ });
+
+ const shapeInfo = mesh2shape(this.el.object3D, options);
+ let shape, offset, orientation;
+ if (shapeInfo) {
+ ({ shape, offset, orientation } = shapeInfo);
+ }
+
+ if (!shape) {
+ el.addEventListener('object3dset', this.initBody.bind(this));
+ return;
+ }
+
+ this.body.addShape(shape, offset, orientation);
+
+ // Show wireframe
+ if (this.system.debug) {
+ this.shouldUpdateWireframe = true;
+ }
+
+ this.hasShape = true;
+ }
+
+ this.el.body = this.body;
+ this.body.el = el;
+
+ // If component wasn't initialized when play() was called, finish up.
+ if (this.isPlaying) {
+ this._play();
+ }
+
+ if (this.hasShape) {
+ this.el.emit('body-loaded', {body: this.el.body});
+ }
+ },
+
+ addShape: function(shape, offset, orientation) {
+ if (this.data.shape !== 'none') {
+ console.warn('shape can only be added if shape property is none');
+ return;
+ }
+
+ if (!shape) {
+ console.warn('shape cannot be null');
+ return;
+ }
+
+ if (!this.body) {
+ console.warn('shape cannot be added before body is loaded');
+ return;
+ }
+ this.body.addShape(shape, offset, orientation);
+
+ if (this.system.debug) {
+ this.shouldUpdateWireframe = true;
+ }
+
+ this.shouldUpdateBody = true;
+ },
+
+ tick: function () {
+ if (this.shouldUpdateBody) {
+
+ // Calling play will result in the object being re-added to the
+ // physics system with the updated body / shape data.
+ // But we mustn't add it twice, so any previously loaded body should be paused first.
+ this._pause();
+ this.hasShape = true;
+ this._play()
+
+ this.el.emit('body-loaded', {body: this.el.body});
+ this.shouldUpdateBody = false;
+ }
+
+ if (this.shouldUpdateWireframe) {
+ this.createWireframe(this.body);
+ this.shouldUpdateWireframe = false;
+ }
+ },
+
+ /**
+ * Registers the component with the physics system, if ready.
+ */
+ play: function () {
+ this._play();
+ },
+
+ /**
+ * Internal helper to register component with physics system.
+ */
+ _play: function () {
+
+ if (!this.hasShape) return;
+
+ this.syncToPhysics();
+ this.system.addComponent(this);
+ this.system.addBody(this.body);
+ if (this.wireframe) this.el.sceneEl.object3D.add(this.wireframe);
+ },
+
+ /**
+ * Unregisters the component with the physics system.
+ */
+ pause: function () {
+ this._pause();
+ },
+
+ _pause: function () {
+
+ if (!this.hasShape) return;
+
+ this.system.removeComponent(this);
+ if (this.body) this.system.removeBody(this.body);
+ if (this.wireframe) this.el.sceneEl.object3D.remove(this.wireframe);
+ },
+
+ /**
+ * Updates the CANNON.Body instance, where possible.
+ */
+ update: function (prevData) {
+ if (!this.body) return;
+
+ var data = this.data;
+
+ if (prevData.type != undefined && data.type != prevData.type) {
+ this.body.type = data.type === 'dynamic' ? CANNON.Body.DYNAMIC : CANNON.Body.STATIC;
+ }
+
+ this.body.mass = data.mass || 0;
+ if (data.type === 'dynamic') {
+ this.body.linearDamping = data.linearDamping;
+ this.body.angularDamping = data.angularDamping;
+ }
+ if (data.mass !== prevData.mass) {
+ this.body.updateMassProperties();
+ }
+ if (this.body.updateProperties) this.body.updateProperties();
+ },
+
+ /**
+ * Removes the component and all physics and scene side effects.
+ */
+ remove: function () {
+ if (this.body) {
+ delete this.body.el;
+ delete this.body;
+ }
+ delete this.el.body;
+ delete this.wireframe;
+ },
+
+ beforeStep: function () {
+ if (this.body.mass === 0) {
+ this.syncToPhysics();
+ }
+ },
+
+ step: function () {
+ if (this.body.mass !== 0) {
+ this.syncFromPhysics();
+ }
+ },
+
+ /**
+ * Creates a wireframe for the body, for debugging.
+ * TODO(donmccurdy) – Refactor this into a standalone utility or component.
+ * @param {CANNON.Body} body
+ * @param {CANNON.Shape} shape
+ */
+ createWireframe: function (body) {
+ if (this.wireframe) {
+ this.el.sceneEl.object3D.remove(this.wireframe);
+ delete this.wireframe;
+ }
+ this.wireframe = new THREE.Object3D();
+ this.el.sceneEl.object3D.add(this.wireframe);
+
+ var offset, mesh;
+ var orientation = new THREE.Quaternion();
+ for (var i = 0; i < this.body.shapes.length; i++)
+ {
+ offset = this.body.shapeOffsets[i],
+ orientation.copy(this.body.shapeOrientations[i]),
+ mesh = CANNON.shape2mesh(this.body).children[i];
+
+ var wireframe = new THREE.LineSegments(
+ new THREE.EdgesGeometry(mesh.geometry),
+ new THREE.LineBasicMaterial({color: 0xff0000})
+ );
+
+ if (offset) {
+ wireframe.position.copy(offset);
+ }
+
+ if (orientation) {
+ wireframe.quaternion.copy(orientation);
+ }
+
+ this.wireframe.add(wireframe);
+ }
+
+ this.syncWireframe();
+ },
+
+ /**
+ * Updates the debugging wireframe's position and rotation.
+ */
+ syncWireframe: function () {
+ var offset,
+ wireframe = this.wireframe;
+
+ if (!this.wireframe) return;
+
+ // Apply rotation. If the shape required custom orientation, also apply
+ // that on the wireframe.
+ wireframe.quaternion.copy(this.body.quaternion);
+ if (wireframe.orientation) {
+ wireframe.quaternion.multiply(wireframe.orientation);
+ }
+
+ // Apply position. If the shape required custom offset, also apply that on
+ // the wireframe.
+ wireframe.position.copy(this.body.position);
+ if (wireframe.offset) {
+ offset = wireframe.offset.clone().applyQuaternion(wireframe.quaternion);
+ wireframe.position.add(offset);
+ }
+
+ wireframe.updateMatrix();
+ },
+
+ /**
+ * Updates the CANNON.Body instance's position, velocity, and rotation, based on the scene.
+ */
+ syncToPhysics: (function () {
+ var q = new THREE.Quaternion(),
+ v = new THREE.Vector3();
+ return function () {
+ var el = this.el,
+ parentEl = el.parentEl,
+ body = this.body;
+
+ if (!body) return;
+
+ if (el.components.velocity) body.velocity.copy(el.getAttribute('velocity'));
+
+ if (parentEl.isScene) {
+ body.quaternion.copy(el.object3D.quaternion);
+ body.position.copy(el.object3D.position);
+ } else {
+ el.object3D.getWorldQuaternion(q);
+ body.quaternion.copy(q);
+ el.object3D.getWorldPosition(v);
+ body.position.copy(v);
+ }
+
+ if (this.body.updateProperties) this.body.updateProperties();
+ if (this.wireframe) this.syncWireframe();
+ };
+ }()),
+
+ /**
+ * Updates the scene object's position and rotation, based on the physics simulation.
+ */
+ syncFromPhysics: (function () {
+ var v = new THREE.Vector3(),
+ q1 = new THREE.Quaternion(),
+ q2 = new THREE.Quaternion();
+ return function () {
+ var el = this.el,
+ parentEl = el.parentEl,
+ body = this.body;
+
+ if (!body) return;
+ if (!parentEl) return;
+
+ if (parentEl.isScene) {
+ el.object3D.quaternion.copy(body.quaternion);
+ el.object3D.position.copy(body.position);
+ } else {
+ q1.copy(body.quaternion);
+ parentEl.object3D.getWorldQuaternion(q2);
+ q1.premultiply(q2.invert());
+ el.object3D.quaternion.copy(q1);
+
+ v.copy(body.position);
+ parentEl.object3D.worldToLocal(v);
+ el.object3D.position.copy(v);
+ }
+
+ if (this.wireframe) this.syncWireframe();
+ };
+ }())
+};
+
+module.exports.definition = Body;
+module.exports.Component = AFRAME.registerComponent('body', Body);
+
+},{"../../../lib/CANNON-shape2mesh":2,"cannon-es":5,"three-to-cannon":7}],12:[function(require,module,exports){
+var Body = require('./body');
+
+/**
+ * Dynamic body.
+ *
+ * Moves according to physics simulation, and may collide with other objects.
+ */
+var DynamicBody = AFRAME.utils.extend({}, Body.definition);
+
+module.exports = AFRAME.registerComponent('dynamic-body', DynamicBody);
+
+},{"./body":11}],13:[function(require,module,exports){
+var Body = require('./body');
+
+/**
+ * Static body.
+ *
+ * Solid body with a fixed position. Unaffected by gravity and collisions, but
+ * other objects may collide with it.
+ */
+var StaticBody = AFRAME.utils.extend({}, Body.definition);
+
+StaticBody.schema = AFRAME.utils.extend({}, Body.definition.schema, {
+ type: {default: 'static', oneOf: ['static', 'dynamic']},
+ mass: {default: 0}
+});
+
+module.exports = AFRAME.registerComponent('static-body', StaticBody);
+
+},{"./body":11}],14:[function(require,module,exports){
+var CANNON = require("cannon-es");
+
+module.exports = AFRAME.registerComponent("constraint", {
+ multiple: true,
+
+ schema: {
+ // Type of constraint.
+ type: { default: "lock", oneOf: ["coneTwist", "distance", "hinge", "lock", "pointToPoint"] },
+
+ // Target (other) body for the constraint.
+ target: { type: "selector" },
+
+ // Maximum force that should be applied to constraint the bodies.
+ maxForce: { default: 1e6, min: 0 },
+
+ // If true, bodies can collide when they are connected.
+ collideConnected: { default: true },
+
+ // Wake up bodies when connected.
+ wakeUpBodies: { default: true },
+
+ // The distance to be kept between the bodies. If 0, will be set to current distance.
+ distance: { default: 0, min: 0 },
+
+ // Offset of the hinge or point-to-point constraint, defined locally in the body.
+ pivot: { type: "vec3" },
+ targetPivot: { type: "vec3" },
+
+ // An axis that each body can rotate around, defined locally to that body.
+ axis: { type: "vec3", default: { x: 0, y: 0, z: 1 } },
+ targetAxis: { type: "vec3", default: { x: 0, y: 0, z: 1 } }
+ },
+
+ init: function() {
+ this.system = this.el.sceneEl.systems.physics;
+ this.constraint = /* {CANNON.Constraint} */ null;
+ },
+
+ remove: function() {
+ if (!this.constraint) return;
+
+ this.system.removeConstraint(this.constraint);
+ this.constraint = null;
+ },
+
+ update: function() {
+ var el = this.el,
+ data = this.data;
+
+ this.remove();
+
+ if (!el.body || !data.target.body) {
+ (el.body ? data.target : el).addEventListener("body-loaded", this.update.bind(this, {}));
+ return;
+ }
+
+ this.constraint = this.createConstraint();
+ this.system.addConstraint(this.constraint);
+ },
+
+ /**
+ * Creates a new constraint, given current component data. The CANNON.js constructors are a bit
+ * different for each constraint type. A `.type` property is added to each constraint, because
+ * `instanceof` checks are not reliable for some types. These types are needed for serialization.
+ * @return {CANNON.Constraint}
+ */
+ createConstraint: function() {
+ var constraint,
+ data = this.data,
+ pivot = new CANNON.Vec3(data.pivot.x, data.pivot.y, data.pivot.z),
+ targetPivot = new CANNON.Vec3(data.targetPivot.x, data.targetPivot.y, data.targetPivot.z),
+ axis = new CANNON.Vec3(data.axis.x, data.axis.y, data.axis.z),
+ targetAxis = new CANNON.Vec3(data.targetAxis.x, data.targetAxis.y, data.targetAxis.z);
+
+ var constraint;
+
+ switch (data.type) {
+ case "lock":
+ constraint = new CANNON.LockConstraint(this.el.body, data.target.body, { maxForce: data.maxForce });
+ constraint.type = "LockConstraint";
+ break;
+
+ case "distance":
+ constraint = new CANNON.DistanceConstraint(this.el.body, data.target.body, data.distance, data.maxForce);
+ constraint.type = "DistanceConstraint";
+ break;
+
+ case "hinge":
+ constraint = new CANNON.HingeConstraint(this.el.body, data.target.body, {
+ pivotA: pivot,
+ pivotB: targetPivot,
+ axisA: axis,
+ axisB: targetAxis,
+ maxForce: data.maxForce
+ });
+ constraint.type = "HingeConstraint";
+ break;
+
+ case "coneTwist":
+ constraint = new CANNON.ConeTwistConstraint(this.el.body, data.target.body, {
+ pivotA: pivot,
+ pivotB: targetPivot,
+ axisA: axis,
+ axisB: targetAxis,
+ maxForce: data.maxForce
+ });
+ constraint.type = "ConeTwistConstraint";
+ break;
+
+ case "pointToPoint":
+ constraint = new CANNON.PointToPointConstraint(
+ this.el.body,
+ pivot,
+ data.target.body,
+ targetPivot,
+ data.maxForce
+ );
+ constraint.type = "PointToPointConstraint";
+ break;
+
+ default:
+ throw new Error("[constraint] Unexpected type: " + data.type);
+ }
+
+ constraint.collideConnected = data.collideConnected;
+ return constraint;
+ }
+});
+
+},{"cannon-es":5}],15:[function(require,module,exports){
+module.exports = {
+ 'velocity': require('./velocity'),
+
+ registerAll: function (AFRAME) {
+ if (this._registered) return;
+
+ AFRAME = AFRAME || window.AFRAME;
+
+ if (!AFRAME.components['velocity']) AFRAME.registerComponent('velocity', this.velocity);
+
+ this._registered = true;
+ }
+};
+
+},{"./velocity":16}],16:[function(require,module,exports){
+/**
+ * Velocity, in m/s.
+ */
+module.exports = AFRAME.registerComponent('velocity', {
+ schema: {type: 'vec3'},
+
+ init: function () {
+ this.system = this.el.sceneEl.systems.physics;
+
+ if (this.system) {
+ this.system.addComponent(this);
+ }
+ },
+
+ remove: function () {
+ if (this.system) {
+ this.system.removeComponent(this);
+ }
+ },
+
+ tick: function (t, dt) {
+ if (!dt) return;
+ if (this.system) return;
+ this.afterStep(t, dt);
+ },
+
+ afterStep: function (t, dt) {
+ if (!dt) return;
+
+ var physics = this.el.sceneEl.systems.physics || {data: {maxInterval: 1 / 60}},
+
+ // TODO - There's definitely a bug with getComputedAttribute and el.data.
+ velocity = this.el.getAttribute('velocity') || {x: 0, y: 0, z: 0},
+ position = this.el.object3D.position || {x: 0, y: 0, z: 0};
+
+ dt = Math.min(dt, physics.data.maxInterval * 1000);
+
+ this.el.object3D.position.set(
+ position.x + velocity.x * dt / 1000,
+ position.y + velocity.y * dt / 1000,
+ position.z + velocity.z * dt / 1000
+ );
+ }
+});
+
+},{}],17:[function(require,module,exports){
+/* global Ammo,THREE */
+const threeToAmmo = require("three-to-ammo");
+const CONSTANTS = require("../../constants"),
+ SHAPE = CONSTANTS.SHAPE,
+ FIT = CONSTANTS.FIT;
+
+var AmmoShape = {
+ schema: {
+ type: {
+ default: SHAPE.HULL,
+ oneOf: [
+ SHAPE.BOX,
+ SHAPE.CYLINDER,
+ SHAPE.SPHERE,
+ SHAPE.CAPSULE,
+ SHAPE.CONE,
+ SHAPE.HULL,
+ SHAPE.HACD,
+ SHAPE.VHACD,
+ SHAPE.MESH,
+ SHAPE.HEIGHTFIELD
+ ]
+ },
+ fit: { default: FIT.ALL, oneOf: [FIT.ALL, FIT.MANUAL] },
+ halfExtents: { type: "vec3", default: { x: 1, y: 1, z: 1 } },
+ minHalfExtent: { default: 0 },
+ maxHalfExtent: { default: Number.POSITIVE_INFINITY },
+ sphereRadius: { default: NaN },
+ cylinderAxis: { default: "y", oneOf: ["x", "y", "z"] },
+ margin: { default: 0.01 },
+ offset: { type: "vec3", default: { x: 0, y: 0, z: 0 } },
+ orientation: { type: "vec4", default: { x: 0, y: 0, z: 0, w: 1 } },
+ heightfieldData: { default: [] },
+ heightfieldDistance: { default: 1 },
+ includeInvisible: { default: false }
+ },
+
+ multiple: true,
+
+ init: function() {
+ if (this.data.fit !== FIT.MANUAL) {
+ if (this.el.object3DMap.mesh) {
+ this.mesh = this.el.object3DMap.mesh;
+ } else {
+ const self = this;
+ this.el.addEventListener("object3dset", function (e) {
+ if (e.detail.type === "mesh") {
+ self.init();
+ }
+ });
+ console.log("Cannot use FIT.ALL without object3DMap.mesh. Waiting for it to be set.");
+ return;
+ }
+ }
+
+ this.system = this.el.sceneEl.systems.physics;
+ this.collisionShapes = [];
+
+ let bodyEl = this.el;
+ this.body = bodyEl.components["ammo-body"] || null;
+ while (!this.body && bodyEl.parentNode != this.el.sceneEl) {
+ bodyEl = bodyEl.parentNode;
+ if (bodyEl.components["ammo-body"]) {
+ this.body = bodyEl.components["ammo-body"];
+ }
+ }
+ if (!this.body) {
+ console.warn("body not found");
+ return;
+ }
+ this.body.addShapeComponent(this);
+ },
+
+ getMesh: function() {
+ return this.mesh || null;
+ },
+
+ addShapes: function(collisionShapes) {
+ this.collisionShapes = collisionShapes;
+ },
+
+ getShapes: function() {
+ return this.collisionShapes;
+ },
+
+ remove: function() {
+ if (!this.body) {
+ return;
+ }
+
+ this.body.removeShapeComponent(this);
+
+ while (this.collisionShapes.length > 0) {
+ const collisionShape = this.collisionShapes.pop();
+ collisionShape.destroy();
+ Ammo.destroy(collisionShape.localTransform);
+ }
+ }
+};
+
+module.exports.definition = AmmoShape;
+module.exports.Component = AFRAME.registerComponent("ammo-shape", AmmoShape);
+
+},{"../../constants":20,"three-to-ammo":6}],18:[function(require,module,exports){
+var CANNON = require('cannon-es');
+
+var Shape = {
+ schema: {
+ shape: {default: 'box', oneOf: ['box', 'sphere', 'cylinder']},
+ offset: {type: 'vec3', default: {x: 0, y: 0, z: 0}},
+ orientation: {type: 'vec4', default: {x: 0, y: 0, z: 0, w: 1}},
+
+ // sphere
+ radius: {type: 'number', default: 1, if: {shape: ['sphere']}},
+
+ // box
+ halfExtents: {type: 'vec3', default: {x: 0.5, y: 0.5, z: 0.5}, if: {shape: ['box']}},
+
+ // cylinder
+ radiusTop: {type: 'number', default: 1, if: {shape: ['cylinder']}},
+ radiusBottom: {type: 'number', default: 1, if: {shape: ['cylinder']}},
+ height: {type: 'number', default: 1, if: {shape: ['cylinder']}},
+ numSegments: {type: 'int', default: 8, if: {shape: ['cylinder']}}
+ },
+
+ multiple: true,
+
+ init: function() {
+ if (this.el.sceneEl.hasLoaded) {
+ this.initShape();
+ } else {
+ this.el.sceneEl.addEventListener('loaded', this.initShape.bind(this));
+ }
+ },
+
+ initShape: function() {
+ this.bodyEl = this.el;
+ var bodyType = this._findType(this.bodyEl);
+ var data = this.data;
+
+ while (!bodyType && this.bodyEl.parentNode != this.el.sceneEl) {
+ this.bodyEl = this.bodyEl.parentNode;
+ bodyType = this._findType(this.bodyEl);
+ }
+
+ if (!bodyType) {
+ console.warn('body not found');
+ return;
+ }
+
+ var scale = new THREE.Vector3();
+ this.bodyEl.object3D.getWorldScale(scale);
+ var shape, offset, orientation;
+
+ if (data.hasOwnProperty('offset')) {
+ offset = new CANNON.Vec3(
+ data.offset.x * scale.x,
+ data.offset.y * scale.y,
+ data.offset.z * scale.z
+ );
+ }
+
+ if (data.hasOwnProperty('orientation')) {
+ orientation = new CANNON.Quaternion();
+ orientation.copy(data.orientation);
+ }
+
+ switch(data.shape) {
+ case 'sphere':
+ shape = new CANNON.Sphere(data.radius * scale.x);
+ break;
+ case 'box':
+ var halfExtents = new CANNON.Vec3(
+ data.halfExtents.x * scale.x,
+ data.halfExtents.y * scale.y,
+ data.halfExtents.z * scale.z
+ );
+ shape = new CANNON.Box(halfExtents);
+ break;
+ case 'cylinder':
+ shape = new CANNON.Cylinder(
+ data.radiusTop * scale.x,
+ data.radiusBottom * scale.x,
+ data.height * scale.y,
+ data.numSegments
+ );
+
+ //rotate by 90 degrees similar to mesh2shape:createCylinderShape
+ var quat = new CANNON.Quaternion();
+ quat.setFromEuler(90 * THREE.MathUtils.DEG2RAD, 0, 0, 'XYZ').normalize();
+ orientation.mult(quat, orientation);
+ break;
+ default:
+ console.warn(data.shape + ' shape not supported');
+ return;
+ }
+
+ if (this.bodyEl.body) {
+ this.bodyEl.components[bodyType].addShape(shape, offset, orientation);
+ } else {
+ this.bodyEl.addEventListener('body-loaded', function() {
+ this.bodyEl.components[bodyType].addShape(shape, offset, orientation);
+ }, {once: true});
+ }
+ },
+
+ _findType: function(el) {
+ if (el.hasAttribute('body')) {
+ return 'body';
+ } else if (el.hasAttribute('dynamic-body')) {
+ return 'dynamic-body';
+ } else if (el.hasAttribute('static-body')) {
+ return'static-body';
+ }
+ return null;
+ },
+
+ remove: function() {
+ if (this.bodyEl.parentNode) {
+ console.warn('removing shape component not currently supported');
+ }
+ }
+};
+
+module.exports.definition = Shape;
+module.exports.Component = AFRAME.registerComponent('shape', Shape);
+
+},{"cannon-es":5}],19:[function(require,module,exports){
+var CANNON = require('cannon-es');
+
+module.exports = AFRAME.registerComponent('spring', {
+
+ multiple: true,
+
+ schema: {
+ // Target (other) body for the constraint.
+ target: {type: 'selector'},
+
+ // Length of the spring, when no force acts upon it.
+ restLength: {default: 1, min: 0},
+
+ // How much will the spring suppress the force.
+ stiffness: {default: 100, min: 0},
+
+ // Stretch factor of the spring.
+ damping: {default: 1, min: 0},
+
+ // Offsets.
+ localAnchorA: {type: 'vec3', default: {x: 0, y: 0, z: 0}},
+ localAnchorB: {type: 'vec3', default: {x: 0, y: 0, z: 0}},
+ },
+
+ init: function() {
+ this.system = this.el.sceneEl.systems.physics;
+ this.system.addComponent(this);
+ this.isActive = true;
+ this.spring = /* {CANNON.Spring} */ null;
+ },
+
+ update: function(oldData) {
+ var el = this.el;
+ var data = this.data;
+
+ if (!data.target) {
+ console.warn('Spring: invalid target specified.');
+ return;
+ }
+
+ // wait until the CANNON bodies is created and attached
+ if (!el.body || !data.target.body) {
+ (el.body ? data.target : el).addEventListener('body-loaded', this.update.bind(this, {}));
+ return;
+ }
+
+ // create the spring if necessary
+ this.createSpring();
+ // apply new data to the spring
+ this.updateSpring(oldData);
+ },
+
+ updateSpring: function(oldData) {
+ if (!this.spring) {
+ console.warn('Spring: Component attempted to change spring before its created. No changes made.');
+ return;
+ }
+ var data = this.data;
+ var spring = this.spring;
+
+ // Cycle through the schema and check if an attribute has changed.
+ // if so, apply it to the spring
+ Object.keys(data).forEach(function(attr) {
+ if (data[attr] !== oldData[attr]) {
+ if (attr === 'target') {
+ // special case for the target selector
+ spring.bodyB = data.target.body;
+ return;
+ }
+ spring[attr] = data[attr];
+ }
+ })
+ },
+
+ createSpring: function() {
+ if (this.spring) return; // no need to create a new spring
+ this.spring = new CANNON.Spring(this.el.body);
+ },
+
+ // If the spring is valid, update the force each tick the physics are calculated
+ step: function(t, dt) {
+ return this.spring && this.isActive ? this.spring.applyForce() : void 0;
+ },
+
+ // resume updating the force when component upon calling play()
+ play: function() {
+ this.isActive = true;
+ },
+
+ // stop updating the force when component upon calling stop()
+ pause: function() {
+ this.isActive = false;
+ },
+
+ //remove the event listener + delete the spring
+ remove: function() {
+ if (this.spring)
+ delete this.spring;
+ this.spring = null;
+ }
+})
+
+},{"cannon-es":5}],20:[function(require,module,exports){
+module.exports = {
+ GRAVITY: -9.8,
+ MAX_INTERVAL: 4 / 60,
+ ITERATIONS: 10,
+ CONTACT_MATERIAL: {
+ friction: 0.01,
+ restitution: 0.3,
+ contactEquationStiffness: 1e8,
+ contactEquationRelaxation: 3,
+ frictionEquationStiffness: 1e8,
+ frictionEquationRegularization: 3
+ },
+ ACTIVATION_STATE: {
+ ACTIVE_TAG: "active",
+ ISLAND_SLEEPING: "islandSleeping",
+ WANTS_DEACTIVATION: "wantsDeactivation",
+ DISABLE_DEACTIVATION: "disableDeactivation",
+ DISABLE_SIMULATION: "disableSimulation"
+ },
+ COLLISION_FLAG: {
+ STATIC_OBJECT: 1,
+ KINEMATIC_OBJECT: 2,
+ NO_CONTACT_RESPONSE: 4,
+ CUSTOM_MATERIAL_CALLBACK: 8, //this allows per-triangle material (friction/restitution)
+ CHARACTER_OBJECT: 16,
+ DISABLE_VISUALIZE_OBJECT: 32, //disable debug drawing
+ DISABLE_SPU_COLLISION_PROCESSING: 64 //disable parallel/SPU processing
+ },
+ TYPE: {
+ STATIC: "static",
+ DYNAMIC: "dynamic",
+ KINEMATIC: "kinematic"
+ },
+ SHAPE: {
+ BOX: "box",
+ CYLINDER: "cylinder",
+ SPHERE: "sphere",
+ CAPSULE: "capsule",
+ CONE: "cone",
+ HULL: "hull",
+ HACD: "hacd",
+ VHACD: "vhacd",
+ MESH: "mesh",
+ HEIGHTFIELD: "heightfield"
+ },
+ FIT: {
+ ALL: "all",
+ MANUAL: "manual"
+ },
+ CONSTRAINT: {
+ LOCK: "lock",
+ FIXED: "fixed",
+ SPRING: "spring",
+ SLIDER: "slider",
+ HINGE: "hinge",
+ CONE_TWIST: "coneTwist",
+ POINT_TO_POINT: "pointToPoint"
+ }
+};
+
+},{}],21:[function(require,module,exports){
+/* global THREE */
+const Driver = require("./driver");
+
+if (typeof window !== 'undefined') {
+ window.AmmoModule = window.Ammo;
+ window.Ammo = null;
+}
+
+const EPS = 10e-6;
+
+function AmmoDriver() {
+ this.collisionConfiguration = null;
+ this.dispatcher = null;
+ this.broadphase = null;
+ this.solver = null;
+ this.physicsWorld = null;
+ this.debugDrawer = null;
+
+ this.els = new Map();
+ this.eventListeners = [];
+ this.collisions = new Map();
+ this.collisionKeys = [];
+ this.currentCollisions = new Map();
+}
+
+AmmoDriver.prototype = new Driver();
+AmmoDriver.prototype.constructor = AmmoDriver;
+
+module.exports = AmmoDriver;
+
+/* @param {object} worldConfig */
+AmmoDriver.prototype.init = function(worldConfig) {
+ //Emscripten doesn't use real promises, just a .then() callback, so it necessary to wrap in a real promise.
+ return new Promise(resolve => {
+ AmmoModule().then(result => {
+ Ammo = result;
+ this.epsilon = worldConfig.epsilon || EPS;
+ this.debugDrawMode = worldConfig.debugDrawMode || THREE.AmmoDebugConstants.NoDebug;
+ this.maxSubSteps = worldConfig.maxSubSteps || 4;
+ this.fixedTimeStep = worldConfig.fixedTimeStep || 1 / 60;
+ this.collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
+ this.dispatcher = new Ammo.btCollisionDispatcher(this.collisionConfiguration);
+ this.broadphase = new Ammo.btDbvtBroadphase();
+ this.solver = new Ammo.btSequentialImpulseConstraintSolver();
+ this.physicsWorld = new Ammo.btDiscreteDynamicsWorld(
+ this.dispatcher,
+ this.broadphase,
+ this.solver,
+ this.collisionConfiguration
+ );
+ this.physicsWorld.setForceUpdateAllAabbs(false);
+ this.physicsWorld.setGravity(
+ new Ammo.btVector3(0, worldConfig.hasOwnProperty("gravity") ? worldConfig.gravity : -9.8, 0)
+ );
+ this.physicsWorld.getSolverInfo().set_m_numIterations(worldConfig.solverIterations);
+ resolve();
+ });
+ });
+};
+
+/* @param {Ammo.btCollisionObject} body */
+AmmoDriver.prototype.addBody = function(body, group, mask) {
+ this.physicsWorld.addRigidBody(body, group, mask);
+ const bodyptr = Ammo.getPointer(body);
+ this.els.set(bodyptr, body.el);
+ this.collisions.set(bodyptr, []);
+ this.collisionKeys.push(bodyptr);
+ this.currentCollisions.set(bodyptr, new Set());
+};
+
+/* @param {Ammo.btCollisionObject} body */
+AmmoDriver.prototype.removeBody = function(body) {
+ this.physicsWorld.removeRigidBody(body);
+ this.removeEventListener(body);
+ const bodyptr = Ammo.getPointer(body);
+ this.els.delete(bodyptr);
+ this.collisions.delete(bodyptr);
+ this.collisionKeys.splice(this.collisionKeys.indexOf(bodyptr), 1);
+ this.currentCollisions.delete(bodyptr);
+};
+
+AmmoDriver.prototype.updateBody = function(body) {
+ if (this.els.has(Ammo.getPointer(body))) {
+ this.physicsWorld.updateSingleAabb(body);
+ }
+};
+
+/* @param {number} deltaTime */
+AmmoDriver.prototype.step = function(deltaTime) {
+ this.physicsWorld.stepSimulation(deltaTime, this.maxSubSteps, this.fixedTimeStep);
+
+ const numManifolds = this.dispatcher.getNumManifolds();
+ for (let i = 0; i < numManifolds; i++) {
+ const persistentManifold = this.dispatcher.getManifoldByIndexInternal(i);
+ const numContacts = persistentManifold.getNumContacts();
+ const body0ptr = Ammo.getPointer(persistentManifold.getBody0());
+ const body1ptr = Ammo.getPointer(persistentManifold.getBody1());
+ let collided = false;
+
+ for (let j = 0; j < numContacts; j++) {
+ const manifoldPoint = persistentManifold.getContactPoint(j);
+ const distance = manifoldPoint.getDistance();
+ if (distance <= this.epsilon) {
+ collided = true;
+ break;
+ }
+ }
+
+ if (collided) {
+ if (this.collisions.get(body0ptr).indexOf(body1ptr) === -1) {
+ this.collisions.get(body0ptr).push(body1ptr);
+ if (this.eventListeners.indexOf(body0ptr) !== -1) {
+ this.els.get(body0ptr).emit("collidestart", { targetEl: this.els.get(body1ptr) });
+ }
+ if (this.eventListeners.indexOf(body1ptr) !== -1) {
+ this.els.get(body1ptr).emit("collidestart", { targetEl: this.els.get(body0ptr) });
+ }
+ }
+ this.currentCollisions.get(body0ptr).add(body1ptr);
+ }
+ }
+
+ for (let i = 0; i < this.collisionKeys.length; i++) {
+ const body0ptr = this.collisionKeys[i];
+ const body1ptrs = this.collisions.get(body0ptr);
+ for (let j = body1ptrs.length - 1; j >= 0; j--) {
+ const body1ptr = body1ptrs[j];
+ if (this.currentCollisions.get(body0ptr).has(body1ptr)) {
+ continue;
+ }
+ if (this.eventListeners.indexOf(body0ptr) !== -1) {
+ this.els.get(body0ptr).emit("collideend", { targetEl: this.els.get(body1ptr) });
+ }
+ if (this.eventListeners.indexOf(body1ptr) !== -1) {
+ this.els.get(body1ptr).emit("collideend", { targetEl: this.els.get(body0ptr) });
+ }
+ body1ptrs.splice(j, 1);
+ }
+ this.currentCollisions.get(body0ptr).clear();
+ }
+
+ if (this.debugDrawer) {
+ this.debugDrawer.update();
+ }
+};
+
+/* @param {?} constraint */
+AmmoDriver.prototype.addConstraint = function(constraint) {
+ this.physicsWorld.addConstraint(constraint, false);
+};
+
+/* @param {?} constraint */
+AmmoDriver.prototype.removeConstraint = function(constraint) {
+ this.physicsWorld.removeConstraint(constraint);
+};
+
+/* @param {Ammo.btCollisionObject} body */
+AmmoDriver.prototype.addEventListener = function(body) {
+ this.eventListeners.push(Ammo.getPointer(body));
+};
+
+/* @param {Ammo.btCollisionObject} body */
+AmmoDriver.prototype.removeEventListener = function(body) {
+ const ptr = Ammo.getPointer(body);
+ if (this.eventListeners.indexOf(ptr) !== -1) {
+ this.eventListeners.splice(this.eventListeners.indexOf(ptr), 1);
+ }
+};
+
+AmmoDriver.prototype.destroy = function() {
+ Ammo.destroy(this.collisionConfiguration);
+ Ammo.destroy(this.dispatcher);
+ Ammo.destroy(this.broadphase);
+ Ammo.destroy(this.solver);
+ Ammo.destroy(this.physicsWorld);
+ Ammo.destroy(this.debugDrawer);
+};
+
+/**
+ * @param {THREE.Scene} scene
+ * @param {object} options
+ */
+AmmoDriver.prototype.getDebugDrawer = function(scene, options) {
+ if (!this.debugDrawer) {
+ options = options || {};
+ options.debugDrawMode = options.debugDrawMode || this.debugDrawMode;
+ this.debugDrawer = new THREE.AmmoDebugDrawer(scene, this.physicsWorld, options);
+ }
+ return this.debugDrawer;
+};
+
+},{"./driver":22}],22:[function(require,module,exports){
+/**
+ * Driver - defines limited API to local and remote physics controllers.
+ */
+
+function Driver () {}
+
+module.exports = Driver;
+
+/******************************************************************************
+ * Lifecycle
+ */
+
+/* @param {object} worldConfig */
+Driver.prototype.init = abstractMethod;
+
+/* @param {number} deltaMS */
+Driver.prototype.step = abstractMethod;
+
+Driver.prototype.destroy = abstractMethod;
+
+/******************************************************************************
+ * Bodies
+ */
+
+/* @param {CANNON.Body} body */
+Driver.prototype.addBody = abstractMethod;
+
+/* @param {CANNON.Body} body */
+Driver.prototype.removeBody = abstractMethod;
+
+/**
+ * @param {CANNON.Body} body
+ * @param {string} methodName
+ * @param {Array} args
+ */
+Driver.prototype.applyBodyMethod = abstractMethod;
+
+/** @param {CANNON.Body} body */
+Driver.prototype.updateBodyProperties = abstractMethod;
+
+/******************************************************************************
+ * Materials
+ */
+
+/** @param {object} materialConfig */
+Driver.prototype.addMaterial = abstractMethod;
+
+/**
+ * @param {string} materialName1
+ * @param {string} materialName2
+ * @param {object} contactMaterialConfig
+ */
+Driver.prototype.addContactMaterial = abstractMethod;
+
+/******************************************************************************
+ * Constraints
+ */
+
+/* @param {CANNON.Constraint} constraint */
+Driver.prototype.addConstraint = abstractMethod;
+
+/* @param {CANNON.Constraint} constraint */
+Driver.prototype.removeConstraint = abstractMethod;
+
+/******************************************************************************
+ * Contacts
+ */
+
+/** @return {Array