A high resolution coin toss app. Forces three.js to double the canvas element size before compression to create photorealistic pennies at standard browser zoom levels. Physics handled with physi.js.
A Pen by Matthew Main on CodePen.
A high resolution coin toss app. Forces three.js to double the canvas element size before compression to create photorealistic pennies at standard browser zoom levels. Physics handled with physi.js.
A Pen by Matthew Main on CodePen.
| <div id="canvas_div"></div> | |
| <div id="toss_icon_div"> | |
| <img id="toss_icon_svg" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/coin_toss_icon.svg"> | |
| </div> | |
| <!-- jQuery source --> | |
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> | |
| <!-- three.js source --> | |
| <script src='https://threejs.org/build/three.min.js'></script> | |
| <!-- GLTFLoader.js source --> | |
| <script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/loaders/GLTFLoader.js"></script> | |
| <!-- physi.js source --> | |
| <script src='https://chandlerprall.github.io/Physijs/physi.js'></script> | |
| <!-- full physijs_worker.js contents in a scrip (needed so workers can be used in codepen) --> | |
| <script id="physijs_worker" type="javascript/worker"> | |
| var transferableMessage = self.webkitPostMessage || self.postMessage, | |
| // enum | |
| MESSAGE_TYPES = { | |
| WORLDREPORT: 0, | |
| COLLISIONREPORT: 1, | |
| VEHICLEREPORT: 2, | |
| CONSTRAINTREPORT: 3 | |
| }, | |
| // temp variables | |
| _object, | |
| _vector, | |
| _transform, | |
| // functions | |
| public_functions = {}, | |
| getShapeFromCache, | |
| setShapeCache, | |
| createShape, | |
| reportWorld, | |
| reportVehicles, | |
| reportCollisions, | |
| reportConstraints, | |
| // world variables | |
| fixedTimeStep, // used when calling stepSimulation | |
| rateLimit, // sets whether or not to sync the simulation rate with fixedTimeStep | |
| last_simulation_time, | |
| last_simulation_duration = 0, | |
| world, | |
| transform, | |
| _vec3_1, | |
| _vec3_2, | |
| _vec3_3, | |
| _quat, | |
| // private cache | |
| _objects = {}, | |
| _vehicles = {}, | |
| _constraints = {}, | |
| _materials = {}, | |
| _objects_ammo = {}, | |
| _num_objects = 0, | |
| _num_wheels = 0, | |
| _num_constraints = 0, | |
| _object_shapes = {}, | |
| // The following objects are to track objects that ammo.js doesn't clean | |
| // up. All are cleaned up when they're corresponding body is destroyed. | |
| // Unfortunately, it's very difficult to get at these objects from the | |
| // body, so we have to track them ourselves. | |
| _motion_states = {}, | |
| // Don't need to worry about it for cached shapes. | |
| _noncached_shapes = {}, | |
| // A body with a compound shape always has a regular shape as well, so we | |
| // have track them separately. | |
| _compound_shapes = {}, | |
| // object reporting | |
| REPORT_CHUNKSIZE, // report array is increased in increments of this chunk size | |
| WORLDREPORT_ITEMSIZE = 14, // how many float values each reported item needs | |
| worldreport, | |
| COLLISIONREPORT_ITEMSIZE = 5, // one float for each object id, and a Vec3 contact normal | |
| collisionreport, | |
| VEHICLEREPORT_ITEMSIZE = 9, // vehicle id, wheel index, 3 for position, 4 for rotation | |
| vehiclereport, | |
| CONSTRAINTREPORT_ITEMSIZE = 6, // constraint id, offset object, offset, applied impulse | |
| constraintreport; | |
| var ab = new ArrayBuffer( 1 ); | |
| transferableMessage( ab, [ab] ); | |
| var SUPPORT_TRANSFERABLE = ( ab.byteLength === 0 ); | |
| getShapeFromCache = function ( cache_key ) { | |
| if ( _object_shapes[ cache_key ] !== undefined ) { | |
| return _object_shapes[ cache_key ]; | |
| } | |
| return null; | |
| }; | |
| setShapeCache = function ( cache_key, shape ) { | |
| _object_shapes[ cache_key ] = shape; | |
| } | |
| createShape = function( description ) { | |
| var cache_key, shape; | |
| _transform.setIdentity(); | |
| switch ( description.type ) { | |
| case 'plane': | |
| cache_key = 'plane_' + description.normal.x + '_' + description.normal.y + '_' + description.normal.z; | |
| if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { | |
| _vec3_1.setX(description.normal.x); | |
| _vec3_1.setY(description.normal.y); | |
| _vec3_1.setZ(description.normal.z); | |
| shape = new Ammo.btStaticPlaneShape(_vec3_1, 0 ); | |
| setShapeCache( cache_key, shape ); | |
| } | |
| break; | |
| case 'box': | |
| cache_key = 'box_' + description.width + '_' + description.height + '_' + description.depth; | |
| if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { | |
| _vec3_1.setX(description.width / 2); | |
| _vec3_1.setY(description.height / 2); | |
| _vec3_1.setZ(description.depth / 2); | |
| shape = new Ammo.btBoxShape(_vec3_1); | |
| setShapeCache( cache_key, shape ); | |
| } | |
| break; | |
| case 'sphere': | |
| cache_key = 'sphere_' + description.radius; | |
| if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { | |
| shape = new Ammo.btSphereShape( description.radius ); | |
| setShapeCache( cache_key, shape ); | |
| } | |
| break; | |
| case 'cylinder': | |
| cache_key = 'cylinder_' + description.width + '_' + description.height + '_' + description.depth; | |
| if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { | |
| _vec3_1.setX(description.width / 2); | |
| _vec3_1.setY(description.height / 2); | |
| _vec3_1.setZ(description.depth / 2); | |
| shape = new Ammo.btCylinderShape(_vec3_1); | |
| setShapeCache( cache_key, shape ); | |
| } | |
| break; | |
| case 'capsule': | |
| cache_key = 'capsule_' + description.radius + '_' + description.height; | |
| if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { | |
| // In Bullet, capsule height excludes the end spheres | |
| shape = new Ammo.btCapsuleShape( description.radius, description.height - 2 * description.radius ); | |
| setShapeCache( cache_key, shape ); | |
| } | |
| break; | |
| case 'cone': | |
| cache_key = 'cone_' + description.radius + '_' + description.height; | |
| if ( ( shape = getShapeFromCache( cache_key ) ) === null ) { | |
| shape = new Ammo.btConeShape( description.radius, description.height ); | |
| setShapeCache( cache_key, shape ); | |
| } | |
| break; | |
| case 'concave': | |
| var i, triangle, triangle_mesh = new Ammo.btTriangleMesh; | |
| if (!description.triangles.length) return false | |
| for ( i = 0; i < description.triangles.length; i++ ) { | |
| triangle = description.triangles[i]; | |
| _vec3_1.setX(triangle[0].x); | |
| _vec3_1.setY(triangle[0].y); | |
| _vec3_1.setZ(triangle[0].z); | |
| _vec3_2.setX(triangle[1].x); | |
| _vec3_2.setY(triangle[1].y); | |
| _vec3_2.setZ(triangle[1].z); | |
| _vec3_3.setX(triangle[2].x); | |
| _vec3_3.setY(triangle[2].y); | |
| _vec3_3.setZ(triangle[2].z); | |
| triangle_mesh.addTriangle( | |
| _vec3_1, | |
| _vec3_2, | |
| _vec3_3, | |
| true | |
| ); | |
| } | |
| shape = new Ammo.btBvhTriangleMeshShape( | |
| triangle_mesh, | |
| true, | |
| true | |
| ); | |
| _noncached_shapes[description.id] = shape; | |
| break; | |
| case 'convex': | |
| var i, point, shape = new Ammo.btConvexHullShape; | |
| for ( i = 0; i < description.points.length; i++ ) { | |
| point = description.points[i]; | |
| _vec3_1.setX(point.x); | |
| _vec3_1.setY(point.y); | |
| _vec3_1.setZ(point.z); | |
| shape.addPoint(_vec3_1); | |
| } | |
| _noncached_shapes[description.id] = shape; | |
| break; | |
| case 'heightfield': | |
| var ptr = Ammo.allocate(4 * description.xpts * description.ypts, "float", Ammo.ALLOC_NORMAL); | |
| for (var f = 0; f < description.points.length; f++) { | |
| Ammo.setValue(ptr + f, description.points[f] , 'float'); | |
| } | |
| shape = new Ammo.btHeightfieldTerrainShape( | |
| description.xpts, | |
| description.ypts, | |
| ptr, | |
| 1, | |
| -description.absMaxHeight, | |
| description.absMaxHeight, | |
| 2, | |
| 0, | |
| false | |
| ); | |
| _vec3_1.setX(description.xsize/(description.xpts - 1)); | |
| _vec3_1.setY(description.ysize/(description.ypts - 1)); | |
| _vec3_1.setZ(1); | |
| shape.setLocalScaling(_vec3_1); | |
| _noncached_shapes[description.id] = shape; | |
| break; | |
| default: | |
| // Not recognized | |
| return; | |
| break; | |
| } | |
| return shape; | |
| }; | |
| public_functions.init = function( params ) { | |
| importScripts( params.ammo ); | |
| _transform = new Ammo.btTransform; | |
| _vec3_1 = new Ammo.btVector3(0,0,0); | |
| _vec3_2 = new Ammo.btVector3(0,0,0); | |
| _vec3_3 = new Ammo.btVector3(0,0,0); | |
| _quat = new Ammo.btQuaternion(0,0,0,0); | |
| REPORT_CHUNKSIZE = params.reportsize || 50; | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| // Transferable messages are supported, take advantage of them with TypedArrays | |
| worldreport = new Float32Array(2 + REPORT_CHUNKSIZE * WORLDREPORT_ITEMSIZE); // message id + # of objects to report + chunk size * # of values per object | |
| collisionreport = new Float32Array(2 + REPORT_CHUNKSIZE * COLLISIONREPORT_ITEMSIZE); // message id + # of collisions to report + chunk size * # of values per object | |
| vehiclereport = new Float32Array(2 + REPORT_CHUNKSIZE * VEHICLEREPORT_ITEMSIZE); // message id + # of vehicles to report + chunk size * # of values per object | |
| constraintreport = new Float32Array(2 + REPORT_CHUNKSIZE * CONSTRAINTREPORT_ITEMSIZE); // message id + # of constraints to report + chunk size * # of values per object | |
| } else { | |
| // Transferable messages are not supported, send data as normal arrays | |
| worldreport = []; | |
| collisionreport = []; | |
| vehiclereport = []; | |
| constraintreport = []; | |
| } | |
| worldreport[0] = MESSAGE_TYPES.WORLDREPORT; | |
| collisionreport[0] = MESSAGE_TYPES.COLLISIONREPORT; | |
| vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; | |
| constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; | |
| var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration, | |
| dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration ), | |
| solver = new Ammo.btSequentialImpulseConstraintSolver, | |
| broadphase; | |
| if ( !params.broadphase ) params.broadphase = { type: 'dynamic' }; | |
| switch ( params.broadphase.type ) { | |
| case 'sweepprune': | |
| _vec3_1.setX(params.broadphase.aabbmin.x); | |
| _vec3_1.setY(params.broadphase.aabbmin.y); | |
| _vec3_1.setZ(params.broadphase.aabbmin.z); | |
| _vec3_2.setX(params.broadphase.aabbmax.x); | |
| _vec3_2.setY(params.broadphase.aabbmax.y); | |
| _vec3_2.setZ(params.broadphase.aabbmax.z); | |
| broadphase = new Ammo.btAxisSweep3( | |
| _vec3_1, | |
| _vec3_2 | |
| ); | |
| break; | |
| case 'dynamic': | |
| default: | |
| broadphase = new Ammo.btDbvtBroadphase; | |
| break; | |
| } | |
| world = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration ); | |
| fixedTimeStep = params.fixedTimeStep; | |
| rateLimit = params.rateLimit; | |
| transferableMessage({ cmd: 'worldReady' }); | |
| }; | |
| public_functions.registerMaterial = function( description ) { | |
| _materials[ description.id ] = description; | |
| }; | |
| public_functions.unRegisterMaterial = function( description ) { | |
| delete _materials[ description.id ]; | |
| }; | |
| public_functions.setFixedTimeStep = function( description ) { | |
| fixedTimeStep = description; | |
| }; | |
| public_functions.setGravity = function( description ) { | |
| _vec3_1.setX(description.x); | |
| _vec3_1.setY(description.y); | |
| _vec3_1.setZ(description.z); | |
| world.setGravity(_vec3_1); | |
| }; | |
| public_functions.addObject = function( description ) { | |
| var i, | |
| localInertia, shape, motionState, rbInfo, body; | |
| shape = createShape( description ); | |
| if (!shape) return | |
| // If there are children then this is a compound shape | |
| if ( description.children ) { | |
| var compound_shape = new Ammo.btCompoundShape, _child; | |
| compound_shape.addChildShape( _transform, shape ); | |
| for ( i = 0; i < description.children.length; i++ ) { | |
| _child = description.children[i]; | |
| var trans = new Ammo.btTransform; | |
| trans.setIdentity(); | |
| _vec3_1.setX(_child.position_offset.x); | |
| _vec3_1.setY(_child.position_offset.y); | |
| _vec3_1.setZ(_child.position_offset.z); | |
| trans.setOrigin(_vec3_1); | |
| _quat.setX(_child.rotation.x); | |
| _quat.setY(_child.rotation.y); | |
| _quat.setZ(_child.rotation.z); | |
| _quat.setW(_child.rotation.w); | |
| trans.setRotation(_quat); | |
| shape = createShape( description.children[i] ); | |
| compound_shape.addChildShape( trans, shape ); | |
| Ammo.destroy(trans); | |
| } | |
| shape = compound_shape; | |
| _compound_shapes[ description.id ] = shape; | |
| } | |
| _vec3_1.setX(0); | |
| _vec3_1.setY(0); | |
| _vec3_1.setZ(0); | |
| shape.calculateLocalInertia( description.mass, _vec3_1 ); | |
| _transform.setIdentity(); | |
| _vec3_2.setX(description.position.x); | |
| _vec3_2.setY(description.position.y); | |
| _vec3_2.setZ(description.position.z); | |
| _transform.setOrigin(_vec3_2); | |
| _quat.setX(description.rotation.x); | |
| _quat.setY(description.rotation.y); | |
| _quat.setZ(description.rotation.z); | |
| _quat.setW(description.rotation.w); | |
| _transform.setRotation(_quat); | |
| motionState = new Ammo.btDefaultMotionState( _transform ); // #TODO: btDefaultMotionState supports center of mass offset as second argument - implement | |
| rbInfo = new Ammo.btRigidBodyConstructionInfo( description.mass, motionState, shape, _vec3_1 ); | |
| if ( description.materialId !== undefined ) { | |
| rbInfo.set_m_friction( _materials[ description.materialId ].friction ); | |
| rbInfo.set_m_restitution( _materials[ description.materialId ].restitution ); | |
| } | |
| body = new Ammo.btRigidBody( rbInfo ); | |
| Ammo.destroy(rbInfo); | |
| if ( typeof description.collision_flags !== 'undefined' ) { | |
| body.setCollisionFlags( description.collision_flags ); | |
| } | |
| world.addRigidBody( body ); | |
| body.id = description.id; | |
| _objects[ body.id ] = body; | |
| _motion_states[ body.id ] = motionState; | |
| var ptr = body.a != undefined ? body.a : body.ptr; | |
| _objects_ammo[ptr] = body.id; | |
| _num_objects++; | |
| transferableMessage({ cmd: 'objectReady', params: body.id }); | |
| }; | |
| public_functions.addVehicle = function( description ) { | |
| var vehicle_tuning = new Ammo.btVehicleTuning(), | |
| vehicle; | |
| vehicle_tuning.set_m_suspensionStiffness( description.suspension_stiffness ); | |
| vehicle_tuning.set_m_suspensionCompression( description.suspension_compression ); | |
| vehicle_tuning.set_m_suspensionDamping( description.suspension_damping ); | |
| vehicle_tuning.set_m_maxSuspensionTravelCm( description.max_suspension_travel ); | |
| vehicle_tuning.set_m_maxSuspensionForce( description.max_suspension_force ); | |
| vehicle = new Ammo.btRaycastVehicle( vehicle_tuning, _objects[ description.rigidBody ], new Ammo.btDefaultVehicleRaycaster( world ) ); | |
| vehicle.tuning = vehicle_tuning; | |
| _objects[ description.rigidBody ].setActivationState( 4 ); | |
| vehicle.setCoordinateSystem( 0, 1, 2 ); | |
| world.addVehicle( vehicle ); | |
| _vehicles[ description.id ] = vehicle; | |
| }; | |
| public_functions.removeVehicle = function( description ) { | |
| delete _vehicles[ description.id ]; | |
| }; | |
| public_functions.addWheel = function( description ) { | |
| if ( _vehicles[description.id] !== undefined ) { | |
| var tuning = _vehicles[description.id].tuning; | |
| if ( description.tuning !== undefined ) { | |
| tuning = new Ammo.btVehicleTuning(); | |
| tuning.set_m_suspensionStiffness( description.tuning.suspension_stiffness ); | |
| tuning.set_m_suspensionCompression( description.tuning.suspension_compression ); | |
| tuning.set_m_suspensionDamping( description.tuning.suspension_damping ); | |
| tuning.set_m_maxSuspensionTravelCm( description.tuning.max_suspension_travel ); | |
| tuning.set_m_maxSuspensionForce( description.tuning.max_suspension_force ); | |
| } | |
| _vec3_1.setX(description.connection_point.x); | |
| _vec3_1.setY(description.connection_point.y); | |
| _vec3_1.setZ(description.connection_point.z); | |
| _vec3_2.setX(description.wheel_direction.x); | |
| _vec3_2.setY(description.wheel_direction.y); | |
| _vec3_2.setZ(description.wheel_direction.z); | |
| _vec3_3.setX(description.wheel_axle.x); | |
| _vec3_3.setY(description.wheel_axle.y); | |
| _vec3_3.setZ(description.wheel_axle.z); | |
| _vehicles[description.id].addWheel( | |
| _vec3_1, | |
| _vec3_2, | |
| _vec3_3, | |
| description.suspension_rest_length, | |
| description.wheel_radius, | |
| tuning, | |
| description.is_front_wheel | |
| ); | |
| } | |
| _num_wheels++; | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| vehiclereport = new Float32Array(1 + _num_wheels * VEHICLEREPORT_ITEMSIZE); // message id & ( # of objects to report * # of values per object ) | |
| vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; | |
| } else { | |
| vehiclereport = [ MESSAGE_TYPES.VEHICLEREPORT ]; | |
| } | |
| }; | |
| public_functions.setSteering = function( details ) { | |
| if ( _vehicles[details.id] !== undefined ) { | |
| _vehicles[details.id].setSteeringValue( details.steering, details.wheel ); | |
| } | |
| }; | |
| public_functions.setBrake = function( details ) { | |
| if ( _vehicles[details.id] !== undefined ) { | |
| _vehicles[details.id].setBrake( details.brake, details.wheel ); | |
| } | |
| }; | |
| public_functions.applyEngineForce = function( details ) { | |
| if ( _vehicles[details.id] !== undefined ) { | |
| _vehicles[details.id].applyEngineForce( details.force, details.wheel ); | |
| } | |
| }; | |
| public_functions.removeObject = function( details ) { | |
| world.removeRigidBody( _objects[details.id] ); | |
| Ammo.destroy(_objects[details.id]); | |
| Ammo.destroy(_motion_states[details.id]); | |
| if (_compound_shapes[details.id]) Ammo.destroy(_compound_shapes[details.id]); | |
| if (_noncached_shapes[details.id]) Ammo.destroy(_noncached_shapes[details.id]); | |
| var ptr = _objects[details.id].a != undefined ? _objects[details.id].a : _objects[details.id].ptr; | |
| delete _objects_ammo[ptr]; | |
| delete _objects[details.id]; | |
| delete _motion_states[details.id]; | |
| if (_compound_shapes[details.id]) delete _compound_shapes[details.id]; | |
| if (_noncached_shapes[details.id]) delete _noncached_shapes[details.id]; | |
| _num_objects--; | |
| }; | |
| public_functions.updateTransform = function( details ) { | |
| _object = _objects[details.id]; | |
| _object.getMotionState().getWorldTransform( _transform ); | |
| if ( details.pos ) { | |
| _vec3_1.setX(details.pos.x); | |
| _vec3_1.setY(details.pos.y); | |
| _vec3_1.setZ(details.pos.z); | |
| _transform.setOrigin(_vec3_1); | |
| } | |
| if ( details.quat ) { | |
| _quat.setX(details.quat.x); | |
| _quat.setY(details.quat.y); | |
| _quat.setZ(details.quat.z); | |
| _quat.setW(details.quat.w); | |
| _transform.setRotation(_quat); | |
| } | |
| _object.setWorldTransform( _transform ); | |
| _object.activate(); | |
| }; | |
| public_functions.updateMass = function( details ) { | |
| // #TODO: changing a static object into dynamic is buggy | |
| _object = _objects[details.id]; | |
| // Per http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?p=&f=9&t=3663#p13816 | |
| world.removeRigidBody( _object ); | |
| _vec3_1.setX(0); | |
| _vec3_1.setY(0); | |
| _vec3_1.setZ(0); | |
| _object.setMassProps( details.mass, _vec3_1 ); | |
| world.addRigidBody( _object ); | |
| _object.activate(); | |
| }; | |
| public_functions.applyCentralImpulse = function ( details ) { | |
| _vec3_1.setX(details.x); | |
| _vec3_1.setY(details.y); | |
| _vec3_1.setZ(details.z); | |
| _objects[details.id].applyCentralImpulse(_vec3_1); | |
| _objects[details.id].activate(); | |
| }; | |
| public_functions.applyImpulse = function ( details ) { | |
| _vec3_1.setX(details.impulse_x); | |
| _vec3_1.setY(details.impulse_y); | |
| _vec3_1.setZ(details.impulse_z); | |
| _vec3_2.setX(details.x); | |
| _vec3_2.setY(details.y); | |
| _vec3_2.setZ(details.z); | |
| _objects[details.id].applyImpulse( | |
| _vec3_1, | |
| _vec3_2 | |
| ); | |
| _objects[details.id].activate(); | |
| }; | |
| public_functions.applyCentralForce = function ( details ) { | |
| _vec3_1.setX(details.x); | |
| _vec3_1.setY(details.y); | |
| _vec3_1.setZ(details.z); | |
| _objects[details.id].applyCentralForce(_vec3_1); | |
| _objects[details.id].activate(); | |
| }; | |
| public_functions.applyForce = function ( details ) { | |
| _vec3_1.setX(details.impulse_x); | |
| _vec3_1.setY(details.impulse_y); | |
| _vec3_1.setZ(details.impulse_z); | |
| _vec3_2.setX(details.x); | |
| _vec3_2.setY(details.y); | |
| _vec3_2.setZ(details.z); | |
| _objects[details.id].applyForce( | |
| _vec3_1, | |
| _vec3_2 | |
| ); | |
| _objects[details.id].activate(); | |
| }; | |
| public_functions.setAngularVelocity = function ( details ) { | |
| _vec3_1.setX(details.x); | |
| _vec3_1.setY(details.y); | |
| _vec3_1.setZ(details.z); | |
| _objects[details.id].setAngularVelocity( | |
| _vec3_1 | |
| ); | |
| _objects[details.id].activate(); | |
| }; | |
| public_functions.setLinearVelocity = function ( details ) { | |
| _vec3_1.setX(details.x); | |
| _vec3_1.setY(details.y); | |
| _vec3_1.setZ(details.z); | |
| _objects[details.id].setLinearVelocity( | |
| _vec3_1 | |
| ); | |
| _objects[details.id].activate(); | |
| }; | |
| public_functions.setAngularFactor = function ( details ) { | |
| _vec3_1.setX(details.x); | |
| _vec3_1.setY(details.y); | |
| _vec3_1.setZ(details.z); | |
| _objects[details.id].setAngularFactor( | |
| _vec3_1 | |
| ); | |
| }; | |
| public_functions.setLinearFactor = function ( details ) { | |
| _vec3_1.setX(details.x); | |
| _vec3_1.setY(details.y); | |
| _vec3_1.setZ(details.z); | |
| _objects[details.id].setLinearFactor( | |
| _vec3_1 | |
| ); | |
| }; | |
| public_functions.setDamping = function ( details ) { | |
| _objects[details.id].setDamping( details.linear, details.angular ); | |
| }; | |
| public_functions.setCcdMotionThreshold = function ( details ) { | |
| _objects[details.id].setCcdMotionThreshold( details.threshold ); | |
| }; | |
| public_functions.setCcdSweptSphereRadius = function ( details ) { | |
| _objects[details.id].setCcdSweptSphereRadius( details.radius ); | |
| }; | |
| public_functions.addConstraint = function ( details ) { | |
| var constraint; | |
| switch ( details.type ) { | |
| case 'point': | |
| if ( details.objectb === undefined ) { | |
| _vec3_1.setX(details.positiona.x); | |
| _vec3_1.setY(details.positiona.y); | |
| _vec3_1.setZ(details.positiona.z); | |
| constraint = new Ammo.btPoint2PointConstraint( | |
| _objects[ details.objecta ], | |
| _vec3_1 | |
| ); | |
| } else { | |
| _vec3_1.setX(details.positiona.x); | |
| _vec3_1.setY(details.positiona.y); | |
| _vec3_1.setZ(details.positiona.z); | |
| _vec3_2.setX(details.positionb.x); | |
| _vec3_2.setY(details.positionb.y); | |
| _vec3_2.setZ(details.positionb.z); | |
| constraint = new Ammo.btPoint2PointConstraint( | |
| _objects[ details.objecta ], | |
| _objects[ details.objectb ], | |
| _vec3_1, | |
| _vec3_2 | |
| ); | |
| } | |
| break; | |
| case 'hinge': | |
| if ( details.objectb === undefined ) { | |
| _vec3_1.setX(details.positiona.x); | |
| _vec3_1.setY(details.positiona.y); | |
| _vec3_1.setZ(details.positiona.z); | |
| _vec3_2.setX(details.axis.x); | |
| _vec3_2.setY(details.axis.y); | |
| _vec3_2.setZ(details.axis.z); | |
| constraint = new Ammo.btHingeConstraint( | |
| _objects[ details.objecta ], | |
| _vec3_1, | |
| _vec3_2 | |
| ); | |
| } else { | |
| _vec3_1.setX(details.positiona.x); | |
| _vec3_1.setY(details.positiona.y); | |
| _vec3_1.setZ(details.positiona.z); | |
| _vec3_2.setX(details.positionb.x); | |
| _vec3_2.setY(details.positionb.y); | |
| _vec3_2.setZ(details.positionb.z); | |
| _vec3_3.setX(details.axis.x); | |
| _vec3_3.setY(details.axis.y); | |
| _vec3_3.setZ(details.axis.z); | |
| constraint = new Ammo.btHingeConstraint( | |
| _objects[ details.objecta ], | |
| _objects[ details.objectb ], | |
| _vec3_1, | |
| _vec3_2, | |
| _vec3_3, | |
| _vec3_3 | |
| ); | |
| } | |
| break; | |
| case 'slider': | |
| var transforma, transformb, rotation; | |
| transforma = new Ammo.btTransform(); | |
| _vec3_1.setX(details.positiona.x); | |
| _vec3_1.setY(details.positiona.y); | |
| _vec3_1.setZ(details.positiona.z); | |
| transforma.setOrigin(_vec3_1); | |
| var rotation = transforma.getRotation(); | |
| rotation.setEuler( details.axis.x, details.axis.y, details.axis.z ); | |
| transforma.setRotation( rotation ); | |
| if ( details.objectb ) { | |
| transformb = new Ammo.btTransform(); | |
| _vec3_2.setX(details.positionb.x); | |
| _vec3_2.setY(details.positionb.y); | |
| _vec3_2.setZ(details.positionb.z); | |
| transformb.setOrigin(_vec3_2); | |
| rotation = transformb.getRotation(); | |
| rotation.setEuler( details.axis.x, details.axis.y, details.axis.z ); | |
| transformb.setRotation( rotation ); | |
| constraint = new Ammo.btSliderConstraint( | |
| _objects[ details.objecta ], | |
| _objects[ details.objectb ], | |
| transforma, | |
| transformb, | |
| true | |
| ); | |
| } else { | |
| constraint = new Ammo.btSliderConstraint( | |
| _objects[ details.objecta ], | |
| transforma, | |
| true | |
| ); | |
| } | |
| Ammo.destroy(transforma); | |
| if (transformb != undefined) { | |
| Ammo.destroy(transformb); | |
| } | |
| break; | |
| case 'conetwist': | |
| var transforma, transformb; | |
| transforma = new Ammo.btTransform(); | |
| transforma.setIdentity(); | |
| transformb = new Ammo.btTransform(); | |
| transformb.setIdentity(); | |
| _vec3_1.setX(details.positiona.x); | |
| _vec3_1.setY(details.positiona.y); | |
| _vec3_1.setZ(details.positiona.z); | |
| _vec3_2.setX(details.positionb.x); | |
| _vec3_2.setY(details.positionb.y); | |
| _vec3_2.setZ(details.positionb.z); | |
| transforma.setOrigin(_vec3_1); | |
| transformb.setOrigin(_vec3_2); | |
| var rotation = transforma.getRotation(); | |
| rotation.setEulerZYX( -details.axisa.z, -details.axisa.y, -details.axisa.x ); | |
| transforma.setRotation( rotation ); | |
| rotation = transformb.getRotation(); | |
| rotation.setEulerZYX( -details.axisb.z, -details.axisb.y, -details.axisb.x ); | |
| transformb.setRotation( rotation ); | |
| constraint = new Ammo.btConeTwistConstraint( | |
| _objects[ details.objecta ], | |
| _objects[ details.objectb ], | |
| transforma, | |
| transformb | |
| ); | |
| constraint.setLimit( Math.PI, 0, Math.PI ); | |
| Ammo.destroy(transforma); | |
| Ammo.destroy(transformb); | |
| break; | |
| case 'dof': | |
| var transforma, transformb, rotation; | |
| transforma = new Ammo.btTransform(); | |
| transforma.setIdentity(); | |
| _vec3_1.setX(details.positiona.x); | |
| _vec3_1.setY(details.positiona.y); | |
| _vec3_1.setZ(details.positiona.z); | |
| transforma.setOrigin(_vec3_1 ); | |
| rotation = transforma.getRotation(); | |
| rotation.setEulerZYX( -details.axisa.z, -details.axisa.y, -details.axisa.x ); | |
| transforma.setRotation( rotation ); | |
| if ( details.objectb ) { | |
| transformb = new Ammo.btTransform(); | |
| transformb.setIdentity(); | |
| _vec3_2.setX(details.positionb.x); | |
| _vec3_2.setY(details.positionb.y); | |
| _vec3_2.setZ(details.positionb.z); | |
| transformb.setOrigin(_vec3_2); | |
| rotation = transformb.getRotation(); | |
| rotation.setEulerZYX( -details.axisb.z, -details.axisb.y, -details.axisb.x ); | |
| transformb.setRotation( rotation ); | |
| constraint = new Ammo.btGeneric6DofConstraint( | |
| _objects[ details.objecta ], | |
| _objects[ details.objectb ], | |
| transforma, | |
| transformb | |
| ); | |
| } else { | |
| constraint = new Ammo.btGeneric6DofConstraint( | |
| _objects[ details.objecta ], | |
| transforma | |
| ); | |
| } | |
| Ammo.destroy(transforma); | |
| if (transformb != undefined) { | |
| Ammo.destroy(transformb); | |
| } | |
| break; | |
| default: | |
| return; | |
| }; | |
| world.addConstraint( constraint ); | |
| constraint.enableFeedback(); | |
| _constraints[ details.id ] = constraint; | |
| _num_constraints++; | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| constraintreport = new Float32Array(1 + _num_constraints * CONSTRAINTREPORT_ITEMSIZE); // message id & ( # of objects to report * # of values per object ) | |
| constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; | |
| } else { | |
| constraintreport = [ MESSAGE_TYPES.CONSTRAINTREPORT ]; | |
| } | |
| }; | |
| public_functions.removeConstraint = function( details ) { | |
| var constraint = _constraints[ details.id ]; | |
| if ( constraint !== undefined ) { | |
| world.removeConstraint( constraint ); | |
| delete _constraints[ details.id ]; | |
| _num_constraints--; | |
| } | |
| }; | |
| public_functions.constraint_setBreakingImpulseThreshold = function( details ) { | |
| var constraint = _constraints[ details.id ]; | |
| if ( constraint !== undefind ) { | |
| constraint.setBreakingImpulseThreshold( details.threshold ); | |
| } | |
| }; | |
| public_functions.simulate = function simulate( params ) { | |
| if ( world ) { | |
| params = params || {}; | |
| if ( !params.timeStep ) { | |
| if ( last_simulation_time ) { | |
| params.timeStep = 0; | |
| while ( params.timeStep + last_simulation_duration <= fixedTimeStep ) { | |
| params.timeStep = ( Date.now() - last_simulation_time ) / 1000; // time since last simulation | |
| } | |
| } else { | |
| params.timeStep = fixedTimeStep; // handle first frame | |
| } | |
| } else { | |
| if ( params.timeStep < fixedTimeStep ) { | |
| params.timeStep = fixedTimeStep; | |
| } | |
| } | |
| params.maxSubSteps = params.maxSubSteps || Math.ceil( params.timeStep / fixedTimeStep ); // If maxSubSteps is not defined, keep the simulation fully up to date | |
| last_simulation_duration = Date.now(); | |
| world.stepSimulation( params.timeStep, params.maxSubSteps, fixedTimeStep ); | |
| reportVehicles(); | |
| reportCollisions(); | |
| reportConstraints(); | |
| reportWorld(); | |
| last_simulation_duration = ( Date.now() - last_simulation_duration ) / 1000; | |
| last_simulation_time = Date.now(); | |
| } | |
| }; | |
| // Constraint functions | |
| public_functions.hinge_setLimits = function( params ) { | |
| _constraints[ params.constraint ].setLimit( params.low, params.high, 0, params.bias_factor, params.relaxation_factor ); | |
| }; | |
| public_functions.hinge_enableAngularMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.enableAngularMotor( true, params.velocity, params.acceleration ); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.hinge_disableMotor = function( params ) { | |
| _constraints[ params.constraint ].enableMotor( false ); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.slider_setLimits = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.setLowerLinLimit( params.lin_lower || 0 ); | |
| constraint.setUpperLinLimit( params.lin_upper || 0 ); | |
| constraint.setLowerAngLimit( params.ang_lower || 0 ); | |
| constraint.setUpperAngLimit( params.ang_upper || 0 ); | |
| }; | |
| public_functions.slider_setRestitution = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.setSoftnessLimLin( params.linear || 0 ); | |
| constraint.setSoftnessLimAng( params.angular || 0 ); | |
| }; | |
| public_functions.slider_enableLinearMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.setTargetLinMotorVelocity( params.velocity ); | |
| constraint.setMaxLinMotorForce( params.acceleration ); | |
| constraint.setPoweredLinMotor( true ); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.slider_disableLinearMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.setPoweredLinMotor( false ); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.slider_enableAngularMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.setTargetAngMotorVelocity( params.velocity ); | |
| constraint.setMaxAngMotorForce( params.acceleration ); | |
| constraint.setPoweredAngMotor( true ); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.slider_disableAngularMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.setPoweredAngMotor( false ); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.conetwist_setLimit = function( params ) { | |
| _constraints[ params.constraint ].setLimit( params.z, params.y, params.x ); // ZYX order | |
| }; | |
| public_functions.conetwist_enableMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.enableMotor( true ); | |
| constraint.getRigidBodyA().activate(); | |
| constraint.getRigidBodyB().activate(); | |
| }; | |
| public_functions.conetwist_setMaxMotorImpulse = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.setMaxMotorImpulse( params.max_impulse ); | |
| constraint.getRigidBodyA().activate(); | |
| constraint.getRigidBodyB().activate(); | |
| }; | |
| public_functions.conetwist_setMotorTarget = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| _quat.setX(params.x); | |
| _quat.setY(params.y); | |
| _quat.setZ(params.z); | |
| _quat.setW(params.w); | |
| constraint.setMotorTarget(_quat); | |
| constraint.getRigidBodyA().activate(); | |
| constraint.getRigidBodyB().activate(); | |
| }; | |
| public_functions.conetwist_disableMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| constraint.enableMotor( false ); | |
| constraint.getRigidBodyA().activate(); | |
| constraint.getRigidBodyB().activate(); | |
| }; | |
| public_functions.dof_setLinearLowerLimit = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| _vec3_1.setX(params.x); | |
| _vec3_1.setY(params.y); | |
| _vec3_1.setZ(params.z); | |
| constraint.setLinearLowerLimit(_vec3_1); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.dof_setLinearUpperLimit = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| _vec3_1.setX(params.x); | |
| _vec3_1.setY(params.y); | |
| _vec3_1.setZ(params.z); | |
| constraint.setLinearUpperLimit(_vec3_1); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.dof_setAngularLowerLimit = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| _vec3_1.setX(params.x); | |
| _vec3_1.setY(params.y); | |
| _vec3_1.setZ(params.z); | |
| constraint.setAngularLowerLimit(_vec3_1); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.dof_setAngularUpperLimit = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| _vec3_1.setX(params.x); | |
| _vec3_1.setY(params.y); | |
| _vec3_1.setZ(params.z); | |
| constraint.setAngularUpperLimit(_vec3_1); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.dof_enableAngularMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| var motor = constraint.getRotationalLimitMotor( params.which ); | |
| motor.set_m_enableMotor( true ); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.dof_configureAngularMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| var motor = constraint.getRotationalLimitMotor( params.which ); | |
| motor.set_m_loLimit( params.low_angle ); | |
| motor.set_m_hiLimit( params.high_angle ); | |
| motor.set_m_targetVelocity( params.velocity ); | |
| motor.set_m_maxMotorForce( params.max_force ); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| public_functions.dof_disableAngularMotor = function( params ) { | |
| var constraint = _constraints[ params.constraint ]; | |
| var motor = constraint.getRotationalLimitMotor( params.which ); | |
| motor.set_m_enableMotor( false ); | |
| constraint.getRigidBodyA().activate(); | |
| if ( constraint.getRigidBodyB() ) { | |
| constraint.getRigidBodyB().activate(); | |
| } | |
| }; | |
| reportWorld = function() { | |
| var index, object, | |
| transform, origin, rotation, | |
| offset = 0, | |
| i = 0; | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| if ( worldreport.length < 2 + _num_objects * WORLDREPORT_ITEMSIZE ) { | |
| worldreport = new Float32Array( | |
| 2 + // message id & # objects in report | |
| ( Math.ceil( _num_objects / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * WORLDREPORT_ITEMSIZE // # of values needed * item size | |
| ); | |
| worldreport[0] = MESSAGE_TYPES.WORLDREPORT; | |
| } | |
| } | |
| worldreport[1] = _num_objects; // record how many objects we're reporting on | |
| //for ( i = 0; i < worldreport[1]; i++ ) { | |
| for ( index in _objects ) { | |
| if ( _objects.hasOwnProperty( index ) ) { | |
| object = _objects[index]; | |
| // #TODO: we can't use center of mass transform when center of mass can change, | |
| // but getMotionState().getWorldTransform() screws up on objects that have been moved | |
| //object.getMotionState().getWorldTransform( transform ); | |
| transform = object.getCenterOfMassTransform(); | |
| origin = transform.getOrigin(); | |
| rotation = transform.getRotation(); | |
| // add values to report | |
| offset = 2 + (i++) * WORLDREPORT_ITEMSIZE; | |
| worldreport[ offset ] = object.id; | |
| worldreport[ offset + 1 ] = origin.x(); | |
| worldreport[ offset + 2 ] = origin.y(); | |
| worldreport[ offset + 3 ] = origin.z(); | |
| worldreport[ offset + 4 ] = rotation.x(); | |
| worldreport[ offset + 5 ] = rotation.y(); | |
| worldreport[ offset + 6 ] = rotation.z(); | |
| worldreport[ offset + 7 ] = rotation.w(); | |
| _vector = object.getLinearVelocity(); | |
| worldreport[ offset + 8 ] = _vector.x(); | |
| worldreport[ offset + 9 ] = _vector.y(); | |
| worldreport[ offset + 10 ] = _vector.z(); | |
| _vector = object.getAngularVelocity(); | |
| worldreport[ offset + 11 ] = _vector.x(); | |
| worldreport[ offset + 12 ] = _vector.y(); | |
| worldreport[ offset + 13 ] = _vector.z(); | |
| } | |
| } | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| transferableMessage( worldreport.buffer, [worldreport.buffer] ); | |
| } else { | |
| transferableMessage( worldreport ); | |
| } | |
| }; | |
| reportCollisions = function() { | |
| var i, offset, | |
| dp = world.getDispatcher(), | |
| num = dp.getNumManifolds(), | |
| manifold, num_contacts, j, pt, | |
| _collided = false; | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| if ( collisionreport.length < 2 + num * COLLISIONREPORT_ITEMSIZE ) { | |
| collisionreport = new Float32Array( | |
| 2 + // message id & # objects in report | |
| ( Math.ceil( _num_objects / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * COLLISIONREPORT_ITEMSIZE // # of values needed * item size | |
| ); | |
| collisionreport[0] = MESSAGE_TYPES.COLLISIONREPORT; | |
| } | |
| } | |
| collisionreport[1] = 0; // how many collisions we're reporting on | |
| for ( i = 0; i < num; i++ ) { | |
| manifold = dp.getManifoldByIndexInternal( i ); | |
| num_contacts = manifold.getNumContacts(); | |
| if ( num_contacts === 0 ) { | |
| continue; | |
| } | |
| for ( j = 0; j < num_contacts; j++ ) { | |
| pt = manifold.getContactPoint( j ); | |
| //if ( pt.getDistance() < 0 ) { | |
| offset = 2 + (collisionreport[1]++) * COLLISIONREPORT_ITEMSIZE; | |
| collisionreport[ offset ] = _objects_ammo[ manifold.getBody0() ]; | |
| collisionreport[ offset + 1 ] = _objects_ammo[ manifold.getBody1() ]; | |
| _vector = pt.get_m_normalWorldOnB(); | |
| collisionreport[ offset + 2 ] = _vector.x(); | |
| collisionreport[ offset + 3 ] = _vector.y(); | |
| collisionreport[ offset + 4 ] = _vector.z(); | |
| break; | |
| //} | |
| transferableMessage( _objects_ammo ); | |
| } | |
| } | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| transferableMessage( collisionreport.buffer, [collisionreport.buffer] ); | |
| } else { | |
| transferableMessage( collisionreport ); | |
| } | |
| }; | |
| reportVehicles = function() { | |
| var index, vehicle, | |
| transform, origin, rotation, | |
| offset = 0, | |
| i = 0, j = 0; | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| if ( vehiclereport.length < 2 + _num_wheels * VEHICLEREPORT_ITEMSIZE ) { | |
| vehiclereport = new Float32Array( | |
| 2 + // message id & # objects in report | |
| ( Math.ceil( _num_wheels / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * VEHICLEREPORT_ITEMSIZE // # of values needed * item size | |
| ); | |
| vehiclereport[0] = MESSAGE_TYPES.VEHICLEREPORT; | |
| } | |
| } | |
| for ( index in _vehicles ) { | |
| if ( _vehicles.hasOwnProperty( index ) ) { | |
| vehicle = _vehicles[index]; | |
| for ( j = 0; j < vehicle.getNumWheels(); j++ ) { | |
| //vehicle.updateWheelTransform( j, true ); | |
| //transform = vehicle.getWheelTransformWS( j ); | |
| transform = vehicle.getWheelInfo( j ).get_m_worldTransform(); | |
| origin = transform.getOrigin(); | |
| rotation = transform.getRotation(); | |
| // add values to report | |
| offset = 1 + (i++) * VEHICLEREPORT_ITEMSIZE; | |
| vehiclereport[ offset ] = index; | |
| vehiclereport[ offset + 1 ] = j; | |
| vehiclereport[ offset + 2 ] = origin.x(); | |
| vehiclereport[ offset + 3 ] = origin.y(); | |
| vehiclereport[ offset + 4 ] = origin.z(); | |
| vehiclereport[ offset + 5 ] = rotation.x(); | |
| vehiclereport[ offset + 6 ] = rotation.y(); | |
| vehiclereport[ offset + 7 ] = rotation.z(); | |
| vehiclereport[ offset + 8 ] = rotation.w(); | |
| } | |
| } | |
| } | |
| if ( j !== 0 ) { | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| transferableMessage( vehiclereport.buffer, [vehiclereport.buffer] ); | |
| } else { | |
| transferableMessage( vehiclereport ); | |
| } | |
| } | |
| }; | |
| reportConstraints = function() { | |
| var index, constraint, | |
| offset_body, | |
| transform, origin, | |
| offset = 0, | |
| i = 0; | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| if ( constraintreport.length < 2 + _num_constraints * CONSTRAINTREPORT_ITEMSIZE ) { | |
| constraintreport = new Float32Array( | |
| 2 + // message id & # objects in report | |
| ( Math.ceil( _num_constraints / REPORT_CHUNKSIZE ) * REPORT_CHUNKSIZE ) * CONSTRAINTREPORT_ITEMSIZE // # of values needed * item size | |
| ); | |
| constraintreport[0] = MESSAGE_TYPES.CONSTRAINTREPORT; | |
| } | |
| } | |
| for ( index in _constraints ) { | |
| if ( _constraints.hasOwnProperty( index ) ) { | |
| constraint = _constraints[index]; | |
| offset_body = constraint.getRigidBodyA(); | |
| transform = constraint.getFrameOffsetA(); | |
| origin = transform.getOrigin(); | |
| // add values to report | |
| offset = 1 + (i++) * CONSTRAINTREPORT_ITEMSIZE; | |
| constraintreport[ offset ] = index; | |
| constraintreport[ offset + 1 ] = offset_body.id; | |
| constraintreport[ offset + 2 ] = origin.getX(); | |
| constraintreport[ offset + 3 ] = origin.getY(); | |
| constraintreport[ offset + 4 ] = origin.getZ(); | |
| constraintreport[ offset + 5 ] = constraint.getAppliedImpulse(); | |
| } | |
| } | |
| if ( i !== 0 ) { | |
| if ( SUPPORT_TRANSFERABLE ) { | |
| transferableMessage( constraintreport.buffer, [constraintreport.buffer] ); | |
| } else { | |
| transferableMessage( constraintreport ); | |
| } | |
| } | |
| }; | |
| self.onmessage = function( event ) { | |
| if ( event.data instanceof Float32Array ) { | |
| // transferable object | |
| switch ( event.data[0] ) { | |
| case MESSAGE_TYPES.WORLDREPORT: | |
| worldreport = new Float32Array( event.data ); | |
| break; | |
| case MESSAGE_TYPES.COLLISIONREPORT: | |
| collisionreport = new Float32Array( event.data ); | |
| break; | |
| case MESSAGE_TYPES.VEHICLEREPORT: | |
| vehiclereport = new Float32Array( event.data ); | |
| break; | |
| case MESSAGE_TYPES.CONSTRAINTREPORT: | |
| constraintreport = new Float32Array( event.data ); | |
| break; | |
| } | |
| return; | |
| } | |
| if ( event.data.cmd && public_functions[event.data.cmd] ) { | |
| //if ( event.data.params.id !== undefined && _objects[event.data.params.id] === undefined && event.data.cmd !== 'addObject' && event.data.cmd !== 'registerMaterial' ) return; | |
| public_functions[event.data.cmd]( event.data.params ); | |
| } | |
| }; | |
| </script> |
| ///////////////////////////////////////////// | |
| ////////////// COIN TOSS //////////////// | |
| ///////////////////////////////////////////// | |
| /////---Sources---///// | |
| var blob = new Blob( [document.querySelector('#physijs_worker').textContent] ); | |
| Physijs.scripts.worker = window.URL.createObjectURL(blob); | |
| Physijs.scripts.ammo = 'https://chandlerprall.github.io/Physijs/examples/js/ammo.js'; | |
| /////---Settings---///// | |
| var pennyDiameter = 40; | |
| var pennyDropHeight = 250; | |
| var gravity = -350; | |
| var fr = 100; // friction | |
| var re = 100; // restitution | |
| /////---Initiation---///// | |
| var pennyArray = []; | |
| ///HTML | |
| //declares a renderer object | |
| var renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| //sets the canvas at double-size for higher resolution | |
| renderer.setSize( window.innerWidth*2, window.innerHeight*2 ); | |
| //places the renderer canvas element inside the canvas_div element | |
| var canvasDiv = document.getElementById("canvas_div"); | |
| canvasDiv.appendChild( renderer.domElement ); | |
| //gets the renderer canvas element | |
| var canvas = document.getElementsByTagName('canvas')[0]; | |
| //shrinks the canvas to the window's size while keeping high resolution | |
| canvas.style.width = window.innerWidth + "px"; | |
| canvas.style.height = window.innerHeight + "px"; | |
| ///physi.js scene | |
| var scene = new Physijs.Scene; | |
| scene.setGravity(new THREE.Vector3(0, gravity, 0)); | |
| scene.addEventListener('update', physicsUpdate); // keeps physics engine in sync with renderer | |
| ///background | |
| renderer.setClearColor (0xF2F2F2, 1); | |
| ///camera | |
| var camera = new THREE.PerspectiveCamera(35, window.innerWidth/window.innerHeight, 1, 10000 ); | |
| camera.position.set( 0, 350, 350 ); | |
| scene.add( camera ); | |
| ///lighting & shadows | |
| var lightA1 = new THREE.AmbientLight(0xFFFFFF, 0.8); | |
| scene.add(lightA1); | |
| var lightD1 = new THREE.DirectionalLight( 0xFFFFFF, 0.3 ); | |
| lightD1.position.set( -250, 300, 150 ); | |
| lightD1.castShadow = true; | |
| lightD1.shadow.camera.left = -500; | |
| lightD1.shadow.camera.top = -500; | |
| lightD1.shadow.camera.right = 500; | |
| lightD1.shadow.camera.bottom = 500; | |
| lightD1.shadow.camera.near = 1; | |
| lightD1.shadow.camera.far = 700; | |
| lightD1.shadow.mapSize.height = lightD1.shadow.mapSize.width = 1500; | |
| scene.add( lightD1 ); | |
| renderer.shadowMap.enabled = true; | |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
| // var helper = new THREE.CameraHelper( lightD1.shadow.camera ); | |
| // camera.position.set( 0, 1500, 1500 ); | |
| // scene.add( helper ); | |
| ///---Platform---/// | |
| var platformGeometry = new THREE.PlaneGeometry( 1500, 1500 ); | |
| var platformMaterial = Physijs.createMaterial( | |
| new THREE.MeshLambertMaterial({ color: 0xEEEEEE }), fr, re | |
| ); | |
| var platform = new Physijs.PlaneMesh(platformGeometry, platformMaterial ); | |
| platform.name = "platform"; | |
| platform.rotateX( - Math.PI / 2); | |
| platform.receiveShadow = true; | |
| scene.add(platform); | |
| ///---Penny---/// | |
| function Penny() { //settings | |
| var penny; | |
| var radiusTop = pennyDiameter*.5; | |
| var radiusBottom = pennyDiameter*.5; | |
| var height = pennyDiameter*.1; | |
| var segments = 50; | |
| //array to store materials | |
| var materialsArray = []; | |
| //component images | |
| var headsImage = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/penny_heads.png" | |
| var tailsImage = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/penny_tails.png"; | |
| //declares a new penny pennyGeometry | |
| var pennyGeometry = new THREE.CylinderGeometry( radiusTop, radiusBottom, height, segments ); | |
| //declares a new three.js material for the side of the penny, then from it creates a new physi.js material that gives the material friction and restitution | |
| var pennyMaterialSide = Physijs.createMaterial( new THREE.MeshLambertMaterial({ color: 0x4F3E37 }), fr, re ); | |
| materialsArray.push( pennyMaterialSide ); //(materialindex = 0) | |
| //declares a new loader for the heads image, assigns it to a material, and pushes to the materials array | |
| var headsLoader = new THREE.TextureLoader(); | |
| headsLoader.load( headsImage, function ( texture ) { | |
| var pennyMaterialTop = Physijs.createMaterial( new THREE.MeshBasicMaterial({ map: texture }), fr, re ); | |
| materialsArray.push(pennyMaterialTop); //(materialindex = 1) | |
| }); | |
| //declares a new loader for the tails image, assigns it to a material, and pushes to the materials array | |
| var tailsLoader = new THREE.TextureLoader(); | |
| tailsLoader.load( tailsImage, function ( texture ) { | |
| var pennyMaterialBottom = Physijs.createMaterial( new THREE.MeshBasicMaterial({ map: texture }), fr, re ); | |
| materialsArray.push(pennyMaterialBottom); //(materialindex = 2) | |
| }); | |
| //assigns each of the penny's faces to a material index | |
| var faceCount = pennyGeometry.faces.length; | |
| for ( i=0; i<faceCount; i++ ) { | |
| //first set of faces makes up the penny's side | |
| if ( i < segments*2 ) { | |
| pennyGeometry.faces[i].materialIndex = 0; | |
| //second set of faces makes up the penny's top | |
| } else if ( i < segments*3 ) { | |
| pennyGeometry.faces[i].materialIndex = 1; | |
| //third set of faces makes up the penny's bottom | |
| } else { | |
| pennyGeometry.faces[i].materialIndex = 2; | |
| } | |
| } | |
| // assigns the penny as a physi.js object and adds it to the scene | |
| penny = new Physijs.CylinderMesh( pennyGeometry, materialsArray, 5 ); //(last parameter is mass) | |
| penny.rotation.set( 1.57, 1.57, 0 ); | |
| penny.castShadow = true; | |
| scene.add( penny ); | |
| // lifts the penny and give it some random initial spin | |
| penny.__dirtyPosition = true; | |
| penny.__dirtyRotation = true; | |
| penny.position.y = pennyDropHeight; | |
| penny.setAngularVelocity(new THREE.Vector3( | |
| Math.random()*50-25, | |
| Math.random()*30-15, | |
| Math.random()*30-15 | |
| )); | |
| } | |
| ///---Supplimentary Functions---/// | |
| function physicsUpdate(){ | |
| scene.simulate( undefined, 2 ); | |
| } | |
| function onWindowResize() { | |
| sceneHeight = window.innerHeight; | |
| sceneWidth = window.innerWidth; | |
| canvas.style.width = window.innerWidth + "px"; | |
| canvas.style.height = window.innerHeight + "px"; | |
| camera.aspect = sceneWidth/sceneHeight; | |
| camera.updateProjectionMatrix(); | |
| } | |
| function addPenny() { | |
| pennyArray.push(new Penny); | |
| } | |
| ///---Interaction---/// | |
| window.addEventListener('resize', onWindowResize, false); | |
| $("#toss_icon_svg").click(function(){ addPenny(); }); | |
| ///---Rendering---/// | |
| addPenny(); | |
| function render() { | |
| scene.simulate(); | |
| camera.lookAt( platform.position.x , platform.position.y+65, platform.position.z ); | |
| //camera.lookAt( penny.position.x , penny.position.y, penny.position.z ); | |
| renderer.render( scene, camera); | |
| requestAnimationFrame( render ); | |
| }; | |
| render(); |
| body { | |
| margin: 0; | |
| width: 100%; | |
| height: 100%; | |
| overflow: hidden; | |
| } | |
| #canvas_div { | |
| width: 100%; | |
| height: 100%; | |
| } | |
| #toss_icon_div { | |
| position: fixed; | |
| width: 60px; | |
| height: 60px; | |
| top: 40px; | |
| right: 50px; | |
| } | |
| #toss_icon_svg { | |
| position: relative; | |
| width: 100%; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%,-50%); | |
| transition: width 150ms; | |
| } | |
| #toss_icon_svg:hover { | |
| width: 110%; | |
| cursor: pointer; | |
| } |