window.Physijs = (function() { 'use strict'; var SUPPORT_TRANSFERABLE, _is_simulating = false, _Physijs = Physijs, // used for noConflict method Physijs = {}, // object assigned to window.Physijs Eventable, // class to provide simple event methods getObjectId, // returns a unique ID for a Physijs mesh object getEulerXYZFromQuaternion, getQuatertionFromEuler, convertWorldPositionToObject, // Converts a world-space position to object-space addObjectChildren, _temp1, _temp2, _temp_vector3_1 = new THREE.Vector3, _temp_vector3_2 = new THREE.Vector3, _temp_matrix4_1 = new THREE.Matrix4, _quaternion_1 = new THREE.Quaternion, // constants MESSAGE_TYPES = { WORLDREPORT: 0, COLLISIONREPORT: 1, VEHICLEREPORT: 2, CONSTRAINTREPORT: 3 }, REPORT_ITEMSIZE = 14, COLLISIONREPORT_ITEMSIZE = 5, VEHICLEREPORT_ITEMSIZE = 9, CONSTRAINTREPORT_ITEMSIZE = 6; Physijs.scripts = {}; Eventable = function() { this._eventListeners = {}; }; Eventable.prototype.addEventListener = function( event_name, callback ) { if ( !this._eventListeners.hasOwnProperty( event_name ) ) { this._eventListeners[event_name] = []; } this._eventListeners[event_name].push( callback ); }; Eventable.prototype.removeEventListener = function( event_name, callback ) { var index; if ( !this._eventListeners.hasOwnProperty( event_name ) ) return false; if ( (index = this._eventListeners[event_name].indexOf( callback )) >= 0 ) { this._eventListeners[event_name].splice( index, 1 ); return true; } return false; }; Eventable.prototype.dispatchEvent = function( event_name ) { var i, parameters = Array.prototype.splice.call( arguments, 1 ); if ( this._eventListeners.hasOwnProperty( event_name ) ) { for ( i = 0; i < this._eventListeners[event_name].length; i++ ) { this._eventListeners[event_name][i].apply( this, parameters ); } } }; Eventable.make = function( obj ) { obj.prototype.addEventListener = Eventable.prototype.addEventListener; obj.prototype.removeEventListener = Eventable.prototype.removeEventListener; obj.prototype.dispatchEvent = Eventable.prototype.dispatchEvent; }; getObjectId = (function() { var _id = 1; return function() { return _id++; }; })(); getEulerXYZFromQuaternion = function ( x, y, z, w ) { return new THREE.Vector3( Math.atan2( 2 * ( x * w - y * z ), ( w * w - x * x - y * y + z * z ) ), Math.asin( 2 * ( x * z + y * w ) ), Math.atan2( 2 * ( z * w - x * y ), ( w * w + x * x - y * y - z * z ) ) ); }; getQuatertionFromEuler = function( x, y, z ) { var c1, s1, c2, s2, c3, s3, c1c2, s1s2; c1 = Math.cos( y ); s1 = Math.sin( y ); c2 = Math.cos( -z ); s2 = Math.sin( -z ); c3 = Math.cos( x ); s3 = Math.sin( x ); c1c2 = c1 * c2; s1s2 = s1 * s2; return { w: c1c2 * c3 - s1s2 * s3, x: c1c2 * s3 + s1s2 * c3, y: s1 * c2 * c3 + c1 * s2 * s3, z: c1 * s2 * c3 - s1 * c2 * s3 }; }; convertWorldPositionToObject = function( position, object ) { _temp_matrix4_1.identity(); // reset temp matrix // Set the temp matrix's rotation to the object's rotation _temp_matrix4_1.identity().makeRotationFromQuaternion( object.quaternion ); // Invert rotation matrix in order to "unrotate" a point back to object space _temp_matrix4_1.getInverse( _temp_matrix4_1 ); // Yay! Temp vars! _temp_vector3_1.copy( position ); _temp_vector3_2.copy( object.position ); // Apply the rotation return _temp_vector3_1.sub( _temp_vector3_2 ).applyMatrix4( _temp_matrix4_1 ); }; // Physijs.noConflict Physijs.noConflict = function() { window.Physijs = _Physijs; return Physijs; }; // Physijs.createMaterial Physijs.createMaterial = function( material, friction, restitution ) { var physijs_material = function(){}; physijs_material.prototype = material; physijs_material = new physijs_material; physijs_material._physijs = { id: material.id, friction: friction === undefined ? .8 : friction, restitution: restitution === undefined ? .2 : restitution }; return physijs_material; }; // Constraints Physijs.PointConstraint = function( objecta, objectb, position ) { if ( position === undefined ) { position = objectb; objectb = undefined; } this.type = 'point'; this.appliedImpulse = 0; this.id = getObjectId(); this.objecta = objecta._physijs.id; this.positiona = convertWorldPositionToObject( position, objecta ).clone(); if ( objectb ) { this.objectb = objectb._physijs.id; this.positionb = convertWorldPositionToObject( position, objectb ).clone(); } }; Physijs.PointConstraint.prototype.getDefinition = function() { return { type: this.type, id: this.id, objecta: this.objecta, objectb: this.objectb, positiona: this.positiona, positionb: this.positionb }; }; Physijs.HingeConstraint = function( objecta, objectb, position, axis ) { if ( axis === undefined ) { axis = position; position = objectb; objectb = undefined; } this.type = 'hinge'; this.appliedImpulse = 0; this.id = getObjectId(); this.scene = objecta.parent; this.objecta = objecta._physijs.id; this.positiona = convertWorldPositionToObject( position, objecta ).clone(); this.position = position.clone(); this.axis = axis; if ( objectb ) { this.objectb = objectb._physijs.id; this.positionb = convertWorldPositionToObject( position, objectb ).clone(); } }; Physijs.HingeConstraint.prototype.getDefinition = function() { return { type: this.type, id: this.id, objecta: this.objecta, objectb: this.objectb, positiona: this.positiona, positionb: this.positionb, axis: this.axis }; }; /* * low = minimum angle in radians * high = maximum angle in radians * bias_factor = applied as a factor to constraint error * relaxation_factor = controls bounce (0.0 == no bounce) */ Physijs.HingeConstraint.prototype.setLimits = function( low, high, bias_factor, relaxation_factor ) { this.scene.execute( 'hinge_setLimits', { constraint: this.id, low: low, high: high, bias_factor: bias_factor, relaxation_factor: relaxation_factor } ); }; Physijs.HingeConstraint.prototype.enableAngularMotor = function( velocity, acceleration ) { this.scene.execute( 'hinge_enableAngularMotor', { constraint: this.id, velocity: velocity, acceleration: acceleration } ); }; Physijs.HingeConstraint.prototype.disableMotor = function( velocity, acceleration ) { this.scene.execute( 'hinge_disableMotor', { constraint: this.id } ); }; Physijs.SliderConstraint = function( objecta, objectb, position, axis ) { if ( axis === undefined ) { axis = position; position = objectb; objectb = undefined; } this.type = 'slider'; this.appliedImpulse = 0; this.id = getObjectId(); this.scene = objecta.parent; this.objecta = objecta._physijs.id; this.positiona = convertWorldPositionToObject( position, objecta ).clone(); this.axis = axis; if ( objectb ) { this.objectb = objectb._physijs.id; this.positionb = convertWorldPositionToObject( position, objectb ).clone(); } }; Physijs.SliderConstraint.prototype.getDefinition = function() { return { type: this.type, id: this.id, objecta: this.objecta, objectb: this.objectb, positiona: this.positiona, positionb: this.positionb, axis: this.axis }; }; Physijs.SliderConstraint.prototype.setLimits = function( lin_lower, lin_upper, ang_lower, ang_upper ) { this.scene.execute( 'slider_setLimits', { constraint: this.id, lin_lower: lin_lower, lin_upper: lin_upper, ang_lower: ang_lower, ang_upper: ang_upper } ); }; Physijs.SliderConstraint.prototype.setRestitution = function( linear, angular ) { this.scene.execute( 'slider_setRestitution', { constraint: this.id, linear: linear, angular: angular } ); }; Physijs.SliderConstraint.prototype.enableLinearMotor = function( velocity, acceleration) { this.scene.execute( 'slider_enableLinearMotor', { constraint: this.id, velocity: velocity, acceleration: acceleration } ); }; Physijs.SliderConstraint.prototype.disableLinearMotor = function() { this.scene.execute( 'slider_disableLinearMotor', { constraint: this.id } ); }; Physijs.SliderConstraint.prototype.enableAngularMotor = function( velocity, acceleration ) { this.scene.execute( 'slider_enableAngularMotor', { constraint: this.id, velocity: velocity, acceleration: acceleration } ); }; Physijs.SliderConstraint.prototype.disableAngularMotor = function() { this.scene.execute( 'slider_disableAngularMotor', { constraint: this.id } ); }; Physijs.ConeTwistConstraint = function( objecta, objectb, position ) { if ( position === undefined ) { throw 'Both objects must be defined in a ConeTwistConstraint.'; } this.type = 'conetwist'; this.appliedImpulse = 0; this.id = getObjectId(); this.scene = objecta.parent; this.objecta = objecta._physijs.id; this.positiona = convertWorldPositionToObject( position, objecta ).clone(); this.objectb = objectb._physijs.id; this.positionb = convertWorldPositionToObject( position, objectb ).clone(); this.axisa = { x: objecta.rotation.x, y: objecta.rotation.y, z: objecta.rotation.z }; this.axisb = { x: objectb.rotation.x, y: objectb.rotation.y, z: objectb.rotation.z }; }; Physijs.ConeTwistConstraint.prototype.getDefinition = function() { return { type: this.type, id: this.id, objecta: this.objecta, objectb: this.objectb, positiona: this.positiona, positionb: this.positionb, axisa: this.axisa, axisb: this.axisb }; }; Physijs.ConeTwistConstraint.prototype.setLimit = function( x, y, z ) { this.scene.execute( 'conetwist_setLimit', { constraint: this.id, x: x, y: y, z: z } ); }; Physijs.ConeTwistConstraint.prototype.enableMotor = function() { this.scene.execute( 'conetwist_enableMotor', { constraint: this.id } ); }; Physijs.ConeTwistConstraint.prototype.setMaxMotorImpulse = function( max_impulse ) { this.scene.execute( 'conetwist_setMaxMotorImpulse', { constraint: this.id, max_impulse: max_impulse } ); }; Physijs.ConeTwistConstraint.prototype.setMotorTarget = function( target ) { if ( target instanceof THREE.Vector3 ) { target = new THREE.Quaternion().setFromEuler( new THREE.Euler( target.x, target.y, target.z ) ); } else if ( target instanceof THREE.Euler ) { target = new THREE.Quaternion().setFromEuler( target ); } else if ( target instanceof THREE.Matrix4 ) { target = new THREE.Quaternion().setFromRotationMatrix( target ); } this.scene.execute( 'conetwist_setMotorTarget', { constraint: this.id, x: target.x, y: target.y, z: target.z, w: target.w } ); }; Physijs.ConeTwistConstraint.prototype.disableMotor = function() { this.scene.execute( 'conetwist_disableMotor', { constraint: this.id } ); }; Physijs.DOFConstraint = function( objecta, objectb, position ) { if ( position === undefined ) { position = objectb; objectb = undefined; } this.type = 'dof'; this.appliedImpulse = 0; this.id = getObjectId(); this.scene = objecta.parent; this.objecta = objecta._physijs.id; this.positiona = convertWorldPositionToObject( position, objecta ).clone(); this.axisa = { x: objecta.rotation.x, y: objecta.rotation.y, z: objecta.rotation.z }; if ( objectb ) { this.objectb = objectb._physijs.id; this.positionb = convertWorldPositionToObject( position, objectb ).clone(); this.axisb = { x: objectb.rotation.x, y: objectb.rotation.y, z: objectb.rotation.z }; } }; Physijs.DOFConstraint.prototype.getDefinition = function() { return { type: this.type, id: this.id, objecta: this.objecta, objectb: this.objectb, positiona: this.positiona, positionb: this.positionb, axisa: this.axisa, axisb: this.axisb }; }; Physijs.DOFConstraint.prototype.setLinearLowerLimit = function( limit ) { this.scene.execute( 'dof_setLinearLowerLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); }; Physijs.DOFConstraint.prototype.setLinearUpperLimit = function( limit ) { this.scene.execute( 'dof_setLinearUpperLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); }; Physijs.DOFConstraint.prototype.setAngularLowerLimit = function( limit ) { this.scene.execute( 'dof_setAngularLowerLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); }; Physijs.DOFConstraint.prototype.setAngularUpperLimit = function( limit ) { this.scene.execute( 'dof_setAngularUpperLimit', { constraint: this.id, x: limit.x, y: limit.y, z: limit.z } ); }; Physijs.DOFConstraint.prototype.enableAngularMotor = function( which ) { this.scene.execute( 'dof_enableAngularMotor', { constraint: this.id, which: which } ); }; Physijs.DOFConstraint.prototype.configureAngularMotor = function( which, low_angle, high_angle, velocity, max_force ) { this.scene.execute( 'dof_configureAngularMotor', { constraint: this.id, which: which, low_angle: low_angle, high_angle: high_angle, velocity: velocity, max_force: max_force } ); }; Physijs.DOFConstraint.prototype.disableAngularMotor = function( which ) { this.scene.execute( 'dof_disableAngularMotor', { constraint: this.id, which: which } ); }; // Physijs.Scene Physijs.Scene = function( params ) { var self = this; Eventable.call( this ); THREE.Scene.call( this ); this._worker = new Worker( Physijs.scripts.worker || 'physijs_worker.js' ); this._worker.transferableMessage = this._worker.webkitPostMessage || this._worker.postMessage; this._materials_ref_counts = {}; this._objects = {}; this._vehicles = {}; this._constraints = {}; var ab = new ArrayBuffer( 1 ); this._worker.transferableMessage( ab, [ab] ); SUPPORT_TRANSFERABLE = ( ab.byteLength === 0 ); this._worker.onmessage = function ( event ) { var _temp, data = event.data; if ( data instanceof ArrayBuffer && data.byteLength !== 1 ) { // byteLength === 1 is the worker making a SUPPORT_TRANSFERABLE test data = new Float32Array( data ); } if ( data instanceof Float32Array ) { // transferable object switch ( data[0] ) { case MESSAGE_TYPES.WORLDREPORT: self._updateScene( data ); break; case MESSAGE_TYPES.COLLISIONREPORT: self._updateCollisions( data ); break; case MESSAGE_TYPES.VEHICLEREPORT: self._updateVehicles( data ); break; case MESSAGE_TYPES.CONSTRAINTREPORT: self._updateConstraints( data ); break; } } else { if ( data.cmd ) { // non-transferable object switch ( data.cmd ) { case 'objectReady': _temp = data.params; if ( self._objects[ _temp ] ) { self._objects[ _temp ].dispatchEvent( 'ready' ); } break; case 'worldReady': self.dispatchEvent( 'ready' ); break; case 'vehicle': window.test = data; break; default: // Do nothing, just show the message console.debug('Received: ' + data.cmd); console.dir(data.params); break; } } else { switch ( data[0] ) { case MESSAGE_TYPES.WORLDREPORT: self._updateScene( data ); break; case MESSAGE_TYPES.COLLISIONREPORT: self._updateCollisions( data ); break; case MESSAGE_TYPES.VEHICLEREPORT: self._updateVehicles( data ); break; case MESSAGE_TYPES.CONSTRAINTREPORT: self._updateConstraints( data ); break; } } } }; params = params || {}; params.ammo = Physijs.scripts.ammo || 'ammo.js'; params.fixedTimeStep = params.fixedTimeStep || 1 / 60; params.rateLimit = params.rateLimit || true; this.execute( 'init', params ); }; Physijs.Scene.prototype = new THREE.Scene; Physijs.Scene.prototype.constructor = Physijs.Scene; Eventable.make( Physijs.Scene ); Physijs.Scene.prototype._updateScene = function( data ) { var num_objects = data[1], object, i, offset; for ( i = 0; i < num_objects; i++ ) { offset = 2 + i * REPORT_ITEMSIZE; object = this._objects[ data[ offset ] ]; if ( object === undefined ) { continue; } if ( object.__dirtyPosition === false ) { object.position.set( data[ offset + 1 ], data[ offset + 2 ], data[ offset + 3 ] ); } if ( object.__dirtyRotation === false ) { object.quaternion.set( data[ offset + 4 ], data[ offset + 5 ], data[ offset + 6 ], data[ offset + 7 ] ); } object._physijs.linearVelocity.set( data[ offset + 8 ], data[ offset + 9 ], data[ offset + 10 ] ); object._physijs.angularVelocity.set( data[ offset + 11 ], data[ offset + 12 ], data[ offset + 13 ] ); } if ( SUPPORT_TRANSFERABLE ) { // Give the typed array back to the worker this._worker.transferableMessage( data.buffer, [data.buffer] ); } _is_simulating = false; this.dispatchEvent( 'update' ); }; Physijs.Scene.prototype._updateVehicles = function( data ) { var vehicle, wheel, i, offset; for ( i = 0; i < ( data.length - 1 ) / VEHICLEREPORT_ITEMSIZE; i++ ) { offset = 1 + i * VEHICLEREPORT_ITEMSIZE; vehicle = this._vehicles[ data[ offset ] ]; if ( vehicle === undefined ) { continue; } wheel = vehicle.wheels[ data[ offset + 1 ] ]; wheel.position.set( data[ offset + 2 ], data[ offset + 3 ], data[ offset + 4 ] ); wheel.quaternion.set( data[ offset + 5 ], data[ offset + 6 ], data[ offset + 7 ], data[ offset + 8 ] ); } if ( SUPPORT_TRANSFERABLE ) { // Give the typed array back to the worker this._worker.transferableMessage( data.buffer, [data.buffer] ); } }; Physijs.Scene.prototype._updateConstraints = function( data ) { var constraint, object, i, offset; for ( i = 0; i < ( data.length - 1 ) / CONSTRAINTREPORT_ITEMSIZE; i++ ) { offset = 1 + i * CONSTRAINTREPORT_ITEMSIZE; constraint = this._constraints[ data[ offset ] ]; object = this._objects[ data[ offset + 1 ] ]; if ( constraint === undefined || object === undefined ) { continue; } _temp_vector3_1.set( data[ offset + 2 ], data[ offset + 3 ], data[ offset + 4 ] ); _temp_matrix4_1.extractRotation( object.matrix ); _temp_vector3_1.applyMatrix4( _temp_matrix4_1 ); constraint.positiona.addVectors( object.position, _temp_vector3_1 ); constraint.appliedImpulse = data[ offset + 5 ] ; } if ( SUPPORT_TRANSFERABLE ) { // Give the typed array back to the worker this._worker.transferableMessage( data.buffer, [data.buffer] ); } }; Physijs.Scene.prototype._updateCollisions = function( data ) { /** * #TODO * This is probably the worst way ever to handle collisions. The inherent evilness is a residual * effect from the previous version's evilness which mutated when switching to transferable objects. * * If you feel inclined to make this better, please do so. */ var i, j, offset, object, object2, id1, id2, collisions = {}, normal_offsets = {}; // Build collision manifest for ( i = 0; i < data[1]; i++ ) { offset = 2 + i * COLLISIONREPORT_ITEMSIZE; object = data[ offset ]; object2 = data[ offset + 1 ]; normal_offsets[ object + '-' + object2 ] = offset + 2; normal_offsets[ object2 + '-' + object ] = -1 * ( offset + 2 ); // Register collisions for both the object colliding and the object being collided with if ( !collisions[ object ] ) collisions[ object ] = []; collisions[ object ].push( object2 ); if ( !collisions[ object2 ] ) collisions[ object2 ] = []; collisions[ object2 ].push( object ); } // Deal with collisions for ( id1 in this._objects ) { if ( !this._objects.hasOwnProperty( id1 ) ) continue; object = this._objects[ id1 ]; // If object touches anything, ... if ( collisions[ id1 ] ) { // Clean up touches array for ( j = 0; j < object._physijs.touches.length; j++ ) { if ( collisions[ id1 ].indexOf( object._physijs.touches[j] ) === -1 ) { object._physijs.touches.splice( j--, 1 ); } } // Handle each colliding object for ( j = 0; j < collisions[ id1 ].length; j++ ) { id2 = collisions[ id1 ][ j ]; object2 = this._objects[ id2 ]; if ( object2 ) { // If object was not already touching object2, notify object if ( object._physijs.touches.indexOf( id2 ) === -1 ) { object._physijs.touches.push( id2 ); _temp_vector3_1.subVectors( object.getLinearVelocity(), object2.getLinearVelocity() ); _temp1 = _temp_vector3_1.clone(); _temp_vector3_1.subVectors( object.getAngularVelocity(), object2.getAngularVelocity() ); _temp2 = _temp_vector3_1.clone(); var normal_offset = normal_offsets[ object._physijs.id + '-' + object2._physijs.id ]; if ( normal_offset > 0 ) { _temp_vector3_1.set( -data[ normal_offset ], -data[ normal_offset + 1 ], -data[ normal_offset + 2 ] ); } else { normal_offset *= -1; _temp_vector3_1.set( data[ normal_offset ], data[ normal_offset + 1 ], data[ normal_offset + 2 ] ); } object.dispatchEvent( 'collision', object2, _temp1, _temp2, _temp_vector3_1 ); } } } } else { // not touching other objects object._physijs.touches.length = 0; } } this.collisions = collisions; if ( SUPPORT_TRANSFERABLE ) { // Give the typed array back to the worker this._worker.transferableMessage( data.buffer, [data.buffer] ); } }; Physijs.Scene.prototype.addConstraint = function ( constraint, show_marker ) { this._constraints[ constraint.id ] = constraint; this.execute( 'addConstraint', constraint.getDefinition() ); if ( show_marker ) { var marker; switch ( constraint.type ) { case 'point': marker = new THREE.Mesh( new THREE.SphereGeometry( 1.5 ), new THREE.MeshNormalMaterial ); marker.position.copy( constraint.positiona ); this._objects[ constraint.objecta ].add( marker ); break; case 'hinge': marker = new THREE.Mesh( new THREE.SphereGeometry( 1.5 ), new THREE.MeshNormalMaterial ); marker.position.copy( constraint.positiona ); this._objects[ constraint.objecta ].add( marker ); break; case 'slider': marker = new THREE.Mesh( new THREE.BoxGeometry( 10, 1, 1 ), new THREE.MeshNormalMaterial ); marker.position.copy( constraint.positiona ); // This rotation isn't right if all three axis are non-0 values // TODO: change marker's rotation order to ZYX marker.rotation.set( constraint.axis.y, // yes, y and constraint.axis.x, // x axis are swapped constraint.axis.z ); this._objects[ constraint.objecta ].add( marker ); break; case 'conetwist': marker = new THREE.Mesh( new THREE.SphereGeometry( 1.5 ), new THREE.MeshNormalMaterial ); marker.position.copy( constraint.positiona ); this._objects[ constraint.objecta ].add( marker ); break; case 'dof': marker = new THREE.Mesh( new THREE.SphereGeometry( 1.5 ), new THREE.MeshNormalMaterial ); marker.position.copy( constraint.positiona ); this._objects[ constraint.objecta ].add( marker ); break; } } return constraint; }; Physijs.Scene.prototype.onSimulationResume = function() { this.execute( 'onSimulationResume', { } ); }; Physijs.Scene.prototype.removeConstraint = function( constraint ) { if ( this._constraints[constraint.id ] !== undefined ) { this.execute( 'removeConstraint', { id: constraint.id } ); delete this._constraints[ constraint.id ]; } }; Physijs.Scene.prototype.execute = function( cmd, params ) { this._worker.postMessage({ cmd: cmd, params: params }); }; addObjectChildren = function( parent, object ) { var i; for ( i = 0; i < object.children.length; i++ ) { if ( object.children[i]._physijs ) { object.children[i].updateMatrix(); object.children[i].updateMatrixWorld(); _temp_vector3_1.setFromMatrixPosition( object.children[i].matrixWorld ); _quaternion_1.setFromRotationMatrix( object.children[i].matrixWorld ); object.children[i]._physijs.position_offset = { x: _temp_vector3_1.x, y: _temp_vector3_1.y, z: _temp_vector3_1.z }; object.children[i]._physijs.rotation = { x: _quaternion_1.x, y: _quaternion_1.y, z: _quaternion_1.z, w: _quaternion_1.w }; parent._physijs.children.push( object.children[i]._physijs ); } addObjectChildren( parent, object.children[i] ); } }; Physijs.Scene.prototype.add = function( object ) { THREE.Mesh.prototype.add.call( this, object ); if ( object._physijs ) { object.world = this; if ( object instanceof Physijs.Vehicle ) { this.add( object.mesh ); this._vehicles[ object._physijs.id ] = object; this.execute( 'addVehicle', object._physijs ); } else { object.__dirtyPosition = false; object.__dirtyRotation = false; this._objects[object._physijs.id] = object; if ( object.children.length ) { object._physijs.children = []; addObjectChildren( object, object ); } if ( object.material._physijs ) { if ( !this._materials_ref_counts.hasOwnProperty( object.material._physijs.id ) ) { this.execute( 'registerMaterial', object.material._physijs ); object._physijs.materialId = object.material._physijs.id; this._materials_ref_counts[object.material._physijs.id] = 1; } else { this._materials_ref_counts[object.material._physijs.id]++; } } // Object starting position + rotation object._physijs.position = { x: object.position.x, y: object.position.y, z: object.position.z }; object._physijs.rotation = { x: object.quaternion.x, y: object.quaternion.y, z: object.quaternion.z, w: object.quaternion.w }; // Check for scaling var mass_scaling = new THREE.Vector3( 1, 1, 1 ); if ( object._physijs.width ) { object._physijs.width *= object.scale.x; } if ( object._physijs.height ) { object._physijs.height *= object.scale.y; } if ( object._physijs.depth ) { object._physijs.depth *= object.scale.z; } this.execute( 'addObject', object._physijs ); } } }; Physijs.Scene.prototype.remove = function( object ) { if ( object instanceof Physijs.Vehicle ) { this.execute( 'removeVehicle', { id: object._physijs.id } ); while( object.wheels.length ) { this.remove( object.wheels.pop() ); } this.remove( object.mesh ); delete this._vehicles[ object._physijs.id ]; } else { THREE.Mesh.prototype.remove.call( this, object ); if ( object._physijs ) { delete this._objects[object._physijs.id]; this.execute( 'removeObject', { id: object._physijs.id } ); } } if ( object.material && object.material._physijs && this._materials_ref_counts.hasOwnProperty( object.material._physijs.id ) ) { this._materials_ref_counts[object.material._physijs.id]--; if(this._materials_ref_counts[object.material._physijs.id] == 0) { this.execute( 'unRegisterMaterial', object.material._physijs ); delete this._materials_ref_counts[object.material._physijs.id]; } } }; Physijs.Scene.prototype.setFixedTimeStep = function( fixedTimeStep ) { if ( fixedTimeStep ) { this.execute( 'setFixedTimeStep', fixedTimeStep ); } }; Physijs.Scene.prototype.setGravity = function( gravity ) { if ( gravity ) { this.execute( 'setGravity', gravity ); } }; Physijs.Scene.prototype.simulate = function( timeStep, maxSubSteps ) { var object_id, object, update; if ( _is_simulating ) { return false; } _is_simulating = true; for ( object_id in this._objects ) { if ( !this._objects.hasOwnProperty( object_id ) ) continue; object = this._objects[object_id]; if ( object.__dirtyPosition || object.__dirtyRotation ) { update = { id: object._physijs.id }; if ( object.__dirtyPosition ) { update.pos = { x: object.position.x, y: object.position.y, z: object.position.z }; object.__dirtyPosition = false; } if ( object.__dirtyRotation ) { update.quat = { x: object.quaternion.x, y: object.quaternion.y, z: object.quaternion.z, w: object.quaternion.w }; object.__dirtyRotation = false; } this.execute( 'updateTransform', update ); } } this.execute( 'simulate', { timeStep: timeStep, maxSubSteps: maxSubSteps } ); return true; }; // Phsijs.Mesh Physijs.Mesh = function ( geometry, material, mass ) { var index; if ( !geometry ) { return; } Eventable.call( this ); THREE.Mesh.call( this, geometry, material ); if ( !geometry.boundingBox ) { geometry.computeBoundingBox(); } this._physijs = { type: null, id: getObjectId(), mass: mass || 0, touches: [], linearVelocity: new THREE.Vector3, angularVelocity: new THREE.Vector3 }; }; Physijs.Mesh.prototype = new THREE.Mesh; Physijs.Mesh.prototype.constructor = Physijs.Mesh; Eventable.make( Physijs.Mesh ); // Physijs.Mesh.mass Physijs.Mesh.prototype.__defineGetter__('mass', function() { return this._physijs.mass; }); Physijs.Mesh.prototype.__defineSetter__('mass', function( mass ) { this._physijs.mass = mass; if ( this.world ) { this.world.execute( 'updateMass', { id: this._physijs.id, mass: mass } ); } }); // Physijs.Mesh.applyCentralImpulse Physijs.Mesh.prototype.applyCentralImpulse = function ( force ) { if ( this.world ) { this.world.execute( 'applyCentralImpulse', { id: this._physijs.id, x: force.x, y: force.y, z: force.z } ); } }; // Physijs.Mesh.applyImpulse Physijs.Mesh.prototype.applyImpulse = function ( force, offset ) { if ( this.world ) { this.world.execute( 'applyImpulse', { id: this._physijs.id, impulse_x: force.x, impulse_y: force.y, impulse_z: force.z, x: offset.x, y: offset.y, z: offset.z } ); } }; // Physijs.Mesh.applyTorque Physijs.Mesh.prototype.applyTorque = function ( force ) { if ( this.world ) { this.world.execute( 'applyTorque', { id: this._physijs.id, torque_x: force.x, torque_y: force.y, torque_z: force.z } ); } }; // Physijs.Mesh.applyCentralForce Physijs.Mesh.prototype.applyCentralForce = function ( force ) { if ( this.world ) { this.world.execute( 'applyCentralForce', { id: this._physijs.id, x: force.x, y: force.y, z: force.z } ); } }; // Physijs.Mesh.applyForce Physijs.Mesh.prototype.applyForce = function ( force, offset ) { if ( this.world ) { this.world.execute( 'applyForce', { id: this._physijs.id, force_x: force.x, force_y : force.y, force_z : force.z, x: offset.x, y: offset.y, z: offset.z } ); } }; // Physijs.Mesh.getAngularVelocity Physijs.Mesh.prototype.getAngularVelocity = function () { return this._physijs.angularVelocity; }; // Physijs.Mesh.setAngularVelocity Physijs.Mesh.prototype.setAngularVelocity = function ( velocity ) { if ( this.world ) { this.world.execute( 'setAngularVelocity', { id: this._physijs.id, x: velocity.x, y: velocity.y, z: velocity.z } ); } }; // Physijs.Mesh.getLinearVelocity Physijs.Mesh.prototype.getLinearVelocity = function () { return this._physijs.linearVelocity; }; // Physijs.Mesh.setLinearVelocity Physijs.Mesh.prototype.setLinearVelocity = function ( velocity ) { if ( this.world ) { this.world.execute( 'setLinearVelocity', { id: this._physijs.id, x: velocity.x, y: velocity.y, z: velocity.z } ); } }; // Physijs.Mesh.setAngularFactor Physijs.Mesh.prototype.setAngularFactor = function ( factor ) { if ( this.world ) { this.world.execute( 'setAngularFactor', { id: this._physijs.id, x: factor.x, y: factor.y, z: factor.z } ); } }; // Physijs.Mesh.setLinearFactor Physijs.Mesh.prototype.setLinearFactor = function ( factor ) { if ( this.world ) { this.world.execute( 'setLinearFactor', { id: this._physijs.id, x: factor.x, y: factor.y, z: factor.z } ); } }; // Physijs.Mesh.setDamping Physijs.Mesh.prototype.setDamping = function ( linear, angular ) { if ( this.world ) { this.world.execute( 'setDamping', { id: this._physijs.id, linear: linear, angular: angular } ); } }; // Physijs.Mesh.setCcdMotionThreshold Physijs.Mesh.prototype.setCcdMotionThreshold = function ( threshold ) { if ( this.world ) { this.world.execute( 'setCcdMotionThreshold', { id: this._physijs.id, threshold: threshold } ); } }; // Physijs.Mesh.setCcdSweptSphereRadius Physijs.Mesh.prototype.setCcdSweptSphereRadius = function ( radius ) { if ( this.world ) { this.world.execute( 'setCcdSweptSphereRadius', { id: this._physijs.id, radius: radius } ); } }; // Physijs.PlaneMesh Physijs.PlaneMesh = function ( geometry, material, mass ) { var width, height; Physijs.Mesh.call( this, geometry, material, mass ); if ( !geometry.boundingBox ) { geometry.computeBoundingBox(); } width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; this._physijs.type = 'plane'; this._physijs.normal = geometry.faces[0].normal.clone(); this._physijs.mass = (typeof mass === 'undefined') ? width * height : mass; }; Physijs.PlaneMesh.prototype = new Physijs.Mesh; Physijs.PlaneMesh.prototype.constructor = Physijs.PlaneMesh; // Physijs.HeightfieldMesh Physijs.HeightfieldMesh = function ( geometry, material, mass, xdiv, ydiv) { Physijs.Mesh.call( this, geometry, material, mass ); this._physijs.type = 'heightfield'; this._physijs.xsize = geometry.boundingBox.max.x - geometry.boundingBox.min.x; this._physijs.ysize = geometry.boundingBox.max.y - geometry.boundingBox.min.y; this._physijs.xpts = (typeof xdiv === 'undefined') ? Math.sqrt(geometry.vertices.length) : xdiv + 1; this._physijs.ypts = (typeof ydiv === 'undefined') ? Math.sqrt(geometry.vertices.length) : ydiv + 1; // note - this assumes our plane geometry is square, unless we pass in specific xdiv and ydiv this._physijs.absMaxHeight = Math.max(geometry.boundingBox.max.z,Math.abs(geometry.boundingBox.min.z)); var points = []; var a, b; for ( var i = 0; i < geometry.vertices.length; i++ ) { a = i % this._physijs.xpts; b = Math.round( ( i / this._physijs.xpts ) - ( (i % this._physijs.xpts) / this._physijs.xpts ) ); points[i] = geometry.vertices[ a + ( ( this._physijs.ypts - b - 1 ) * this._physijs.ypts ) ].z; //points[i] = geometry.vertices[i]; } this._physijs.points = points; }; Physijs.HeightfieldMesh.prototype = new Physijs.Mesh; Physijs.HeightfieldMesh.prototype.constructor = Physijs.HeightfieldMesh; // Physijs.BoxMesh Physijs.BoxMesh = function( geometry, material, mass ) { var width, height, depth; Physijs.Mesh.call( this, geometry, material, mass ); if ( !geometry.boundingBox ) { geometry.computeBoundingBox(); } width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; this._physijs.type = 'box'; this._physijs.width = width; this._physijs.height = height; this._physijs.depth = depth; this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; }; Physijs.BoxMesh.prototype = new Physijs.Mesh; Physijs.BoxMesh.prototype.constructor = Physijs.BoxMesh; // Physijs.SphereMesh Physijs.SphereMesh = function( geometry, material, mass ) { Physijs.Mesh.call( this, geometry, material, mass ); if ( !geometry.boundingSphere ) { geometry.computeBoundingSphere(); } this._physijs.type = 'sphere'; this._physijs.radius = geometry.boundingSphere.radius; this._physijs.mass = (typeof mass === 'undefined') ? (4/3) * Math.PI * Math.pow(this._physijs.radius, 3) : mass; }; Physijs.SphereMesh.prototype = new Physijs.Mesh; Physijs.SphereMesh.prototype.constructor = Physijs.SphereMesh; // Physijs.CylinderMesh Physijs.CylinderMesh = function( geometry, material, mass ) { var width, height, depth; Physijs.Mesh.call( this, geometry, material, mass ); if ( !geometry.boundingBox ) { geometry.computeBoundingBox(); } width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; this._physijs.type = 'cylinder'; this._physijs.width = width; this._physijs.height = height; this._physijs.depth = depth; this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; }; Physijs.CylinderMesh.prototype = new Physijs.Mesh; Physijs.CylinderMesh.prototype.constructor = Physijs.CylinderMesh; // Physijs.CapsuleMesh Physijs.CapsuleMesh = function( geometry, material, mass ) { var width, height, depth; Physijs.Mesh.call( this, geometry, material, mass ); if ( !geometry.boundingBox ) { geometry.computeBoundingBox(); } width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; this._physijs.type = 'capsule'; this._physijs.radius = Math.max(width / 2, depth / 2); this._physijs.height = height; this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; }; Physijs.CapsuleMesh.prototype = new Physijs.Mesh; Physijs.CapsuleMesh.prototype.constructor = Physijs.CapsuleMesh; // Physijs.ConeMesh Physijs.ConeMesh = function( geometry, material, mass ) { var width, height, depth; Physijs.Mesh.call( this, geometry, material, mass ); if ( !geometry.boundingBox ) { geometry.computeBoundingBox(); } width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; this._physijs.type = 'cone'; this._physijs.radius = width / 2; this._physijs.height = height; this._physijs.mass = (typeof mass === 'undefined') ? width * height : mass; }; Physijs.ConeMesh.prototype = new Physijs.Mesh; Physijs.ConeMesh.prototype.constructor = Physijs.ConeMesh; // Physijs.ConcaveMesh Physijs.ConcaveMesh = function( geometry, material, mass ) { var i, width, height, depth, vertices, face, triangles = []; Physijs.Mesh.call( this, geometry, material, mass ); if ( !geometry.boundingBox ) { geometry.computeBoundingBox(); } vertices = geometry.vertices; for ( i = 0; i < geometry.faces.length; i++ ) { face = geometry.faces[i]; if ( face instanceof THREE.Face3) { triangles.push([ { x: vertices[face.a].x, y: vertices[face.a].y, z: vertices[face.a].z }, { x: vertices[face.b].x, y: vertices[face.b].y, z: vertices[face.b].z }, { x: vertices[face.c].x, y: vertices[face.c].y, z: vertices[face.c].z } ]); } else if ( face instanceof THREE.Face4 ) { triangles.push([ { x: vertices[face.a].x, y: vertices[face.a].y, z: vertices[face.a].z }, { x: vertices[face.b].x, y: vertices[face.b].y, z: vertices[face.b].z }, { x: vertices[face.d].x, y: vertices[face.d].y, z: vertices[face.d].z } ]); triangles.push([ { x: vertices[face.b].x, y: vertices[face.b].y, z: vertices[face.b].z }, { x: vertices[face.c].x, y: vertices[face.c].y, z: vertices[face.c].z }, { x: vertices[face.d].x, y: vertices[face.d].y, z: vertices[face.d].z } ]); } } width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; this._physijs.type = 'concave'; this._physijs.triangles = triangles; this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; }; Physijs.ConcaveMesh.prototype = new Physijs.Mesh; Physijs.ConcaveMesh.prototype.constructor = Physijs.ConcaveMesh; // Physijs.ConvexMesh Physijs.ConvexMesh = function( geometry, material, mass ) { var i, width, height, depth, points = []; Physijs.Mesh.call( this, geometry, material, mass ); if ( !geometry.boundingBox ) { geometry.computeBoundingBox(); } for ( i = 0; i < geometry.vertices.length; i++ ) { points.push({ x: geometry.vertices[i].x, y: geometry.vertices[i].y, z: geometry.vertices[i].z }); } width = geometry.boundingBox.max.x - geometry.boundingBox.min.x; height = geometry.boundingBox.max.y - geometry.boundingBox.min.y; depth = geometry.boundingBox.max.z - geometry.boundingBox.min.z; this._physijs.type = 'convex'; this._physijs.points = points; this._physijs.mass = (typeof mass === 'undefined') ? width * height * depth : mass; }; Physijs.ConvexMesh.prototype = new Physijs.Mesh; Physijs.ConvexMesh.prototype.constructor = Physijs.ConvexMesh; // Physijs.Vehicle Physijs.Vehicle = function( mesh, tuning ) { tuning = tuning || new Physijs.VehicleTuning; this.mesh = mesh; this.wheels = []; this._physijs = { id: getObjectId(), rigidBody: mesh._physijs.id, suspension_stiffness: tuning.suspension_stiffness, suspension_compression: tuning.suspension_compression, suspension_damping: tuning.suspension_damping, max_suspension_travel: tuning.max_suspension_travel, friction_slip: tuning.friction_slip, max_suspension_force: tuning.max_suspension_force }; }; Physijs.Vehicle.prototype.addWheel = function( wheel_geometry, wheel_material, connection_point, wheel_direction, wheel_axle, suspension_rest_length, wheel_radius, is_front_wheel, tuning ) { var wheel = new THREE.Mesh( wheel_geometry, wheel_material ); wheel.castShadow = wheel.receiveShadow = true; wheel.position.copy( wheel_direction ).multiplyScalar( suspension_rest_length / 100 ).add( connection_point ); this.world.add( wheel ); this.wheels.push( wheel ); this.world.execute( 'addWheel', { id: this._physijs.id, connection_point: { x: connection_point.x, y: connection_point.y, z: connection_point.z }, wheel_direction: { x: wheel_direction.x, y: wheel_direction.y, z: wheel_direction.z }, wheel_axle: { x: wheel_axle.x, y: wheel_axle.y, z: wheel_axle.z }, suspension_rest_length: suspension_rest_length, wheel_radius: wheel_radius, is_front_wheel: is_front_wheel, tuning: tuning }); }; Physijs.Vehicle.prototype.setSteering = function( amount, wheel ) { if ( wheel !== undefined && this.wheels[ wheel ] !== undefined ) { this.world.execute( 'setSteering', { id: this._physijs.id, wheel: wheel, steering: amount } ); } else if ( this.wheels.length > 0 ) { for ( var i = 0; i < this.wheels.length; i++ ) { this.world.execute( 'setSteering', { id: this._physijs.id, wheel: i, steering: amount } ); } } }; Physijs.Vehicle.prototype.setBrake = function( amount, wheel ) { if ( wheel !== undefined && this.wheels[ wheel ] !== undefined ) { this.world.execute( 'setBrake', { id: this._physijs.id, wheel: wheel, brake: amount } ); } else if ( this.wheels.length > 0 ) { for ( var i = 0; i < this.wheels.length; i++ ) { this.world.execute( 'setBrake', { id: this._physijs.id, wheel: i, brake: amount } ); } } }; Physijs.Vehicle.prototype.applyEngineForce = function( amount, wheel ) { if ( wheel !== undefined && this.wheels[ wheel ] !== undefined ) { this.world.execute( 'applyEngineForce', { id: this._physijs.id, wheel: wheel, force: amount } ); } else if ( this.wheels.length > 0 ) { for ( var i = 0; i < this.wheels.length; i++ ) { this.world.execute( 'applyEngineForce', { id: this._physijs.id, wheel: i, force: amount } ); } } }; // Physijs.VehicleTuning Physijs.VehicleTuning = function( suspension_stiffness, suspension_compression, suspension_damping, max_suspension_travel, friction_slip, max_suspension_force ) { this.suspension_stiffness = suspension_stiffness !== undefined ? suspension_stiffness : 5.88; this.suspension_compression = suspension_compression !== undefined ? suspension_compression : 0.83; this.suspension_damping = suspension_damping !== undefined ? suspension_damping : 0.88; this.max_suspension_travel = max_suspension_travel !== undefined ? max_suspension_travel : 500; this.friction_slip = friction_slip !== undefined ? friction_slip : 10.5; this.max_suspension_force = max_suspension_force !== undefined ? max_suspension_force : 6000; }; return Physijs; })();