<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>
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;
}


/////////////////////////////////////////////
//////////////   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));

///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*.085;
  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 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();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.