diff --git a/index.js b/index.js index 706ed6d..14bc035 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ require('./src/components/math'); require('./src/components/body/body'); require('./src/components/body/dynamic-body'); require('./src/components/body/static-body'); +require('./src/components/shape/shape') require('./src/components/constraint'); require('./src/system'); diff --git a/src/components/body/body.js b/src/components/body/body.js index ea6282f..2a7cea7 100644 --- a/src/components/body/body.js +++ b/src/components/body/body.js @@ -35,9 +35,8 @@ var Body = { * component. */ initBody: function () { - var shape, - el = this.el, - data = this.data; + var el = this.el, + data = this.data; var obj = this.el.object3D; var pos = obj.position; @@ -71,25 +70,63 @@ var Body = { el.addEventListener('object3dset', this.initBody.bind(this)); return; } - this.body.addShape(shape, shape.offset, shape.orientation); // Show wireframe if (this.system.debug) { - this.createWireframe(this.body, shape); + this.shouldUpdateWireframe = true; } + + this.isLoaded = true; } this.el.body = this.body; this.body.el = el; - this.isLoaded = true; // If component wasn't initialized when play() was called, finish up. if (this.isPlaying) { this._play(); } - this.el.emit('body-loaded', {body: this.el.body}); + if (this.isLoaded) { + this.el.emit('body-loaded', {body: this.el.body}); + } + }, + + addShape: function(shape, offset = null, orientation = null) { + 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; + } + + this.body.addShape(shape, offset, orientation); + + if (this.system.debug) { + this.shouldUpdateWireframe = true; + } + + this.shouldUpdateBody = true; + }, + + tick: function () { + if (this.shouldUpdateBody) { + this.isLoaded = 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; + } }, /** @@ -173,30 +210,39 @@ var Body = { * @param {CANNON.Body} body * @param {CANNON.Shape} shape */ - createWireframe: function (body, shape) { - var offset = shape.offset, - orientation = shape.orientation, - mesh = CANNON.shape2mesh(body).children[0]; - - this.wireframe = new THREE.LineSegments( - new THREE.EdgesGeometry(mesh.geometry), - new THREE.LineBasicMaterial({color: 0xff0000}) - ); - - if (offset) { - this.wireframe.offset = offset.clone(); + createWireframe: function (body) { + if (this.wireframe) { + this.el.sceneEl.object3D.remove(this.wireframe); + delete this.wireframe; } - - if (orientation) { - orientation.inverse(orientation); - this.wireframe.orientation = new THREE.Quaternion( - orientation.x, - orientation.y, - orientation.z, - orientation.w + 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) { + orientation.inverse(orientation); + wireframe.quaternion.copy(orientation); + } + + this.wireframe.add(wireframe); + } + this.syncWireframe(); }, diff --git a/src/components/shape/shape.js b/src/components/shape/shape.js new file mode 100644 index 0000000..4548208 --- /dev/null +++ b/src/components/shape/shape.js @@ -0,0 +1,105 @@ +var CANNON = require('cannon'); + +var Shape = { + schema: { + shape: {default: 'box', oneOf: ['box', 'sphere', 'cylinder']}, + radius: {type: 'number', default: 1, if: {shape: ['sphere']}}, + offset: {type: 'vec3', default: {x: 0, y: 0, z: 0}}, + halfExtents: {type: 'vec3', default: {x: 1, y: 1, z: 1}, if: {shape: ['box']}}, + orientation: {type: 'vec4', default: {x: 0, y: 0, z: 0, w: 1}, 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.bodyEl = this.bodyEl.parentNode; + bodyType = this._findType(this.bodyEl); + } + + 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.Math.DEG2RAD, 0, 0, 'XYZ').normalize(); + orientation.mult(quat, orientation); + break; + default: + console.warn(data.shape + ' shape not supported'); + return; + } + + this.bodyEl.components[bodyType].addShape(shape, offset, orientation); + }, + + _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);