<div class="scrollTarget">
</div>

<div id="loading_image_page">
  <div id="loading_image_div">
    <img id="loading_image" src="">
  </div>
</div> 

<!-- full physijs_worker.js contents in a scrip (necessary in order to use workers 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%;
}

canvas { 
  width: 100%; 
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
}

#loading_image_page {
  position: fixed;
  width: 100%;
  height: 100%;
  background: #1A1B1F;
  z-index: 1;
}

#loading_image_div {
  width: 40%;
  height: 40%;
  position: absolute;
  top: 60%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#loading_image {
  width: 100%;
}

.scrollTarget {
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  z-index: 5;
  background: transparent;
  height: 300vh;
}
//wow, thanks Jackº
var _gsScope = (typeof module !== "undefined" && module.exports && typeof global !== "undefined") ? global : this || window;
(_gsScope._gsQueue || (_gsScope._gsQueue = [])).push(function () {
    "use strict";
    var _xyzContexts = "position,scale,rotation".split(","),
        _contexts = {x:"position", y:"position", z:"position"},
        _DEG2RAD = Math.PI / 180,
        _degreesToRadians = function(value) {
            return (typeof(value) === "string" && value.charAt(1) === "=") ? value.substr(0, 2) + (parseFloat(value.substr(2)) * _DEG2RAD) : value * _DEG2RAD;
        }, i, p;
    for (i = 0; i < _xyzContexts.length; i++) {
        p = _xyzContexts[i];
        _contexts[p + "X"] = p;
        _contexts[p + "Y"] = p;
        _contexts[p + "Z"] = p;
    }
    var ThreePlugin = _gsScope._gsDefine.plugin({
        propName: "three",
        priority: 0,
        API: 2,
        version: "0.0.1",
        init: function (target, values, tween, index) {
            var context, axis, value, p, i, m;
            for (p in values) {
                context = _contexts[p];
                value = values[p];
                if (typeof(value) === "function") {
                    value = value(index || 0, target);
                }
                if (context) {
                    i = p.charAt(p.length-1).toLowerCase();
                    axis = (i.indexOf("x") !== -1) ? "x" : (i.indexOf("z") !== -1) ? "z" : "y";
                    this._addTween(target[context], axis, target[context][axis], (p.indexOf("rotation") !== -1) ? _degreesToRadians(value) : value, p);
                } else if (p === "scale") {
                    this._addTween(target[p], "x", target[p].x, value, p + "X");
                    this._addTween(target[p], "y", target[p].y, value, p + "Y");
                    this._addTween(target[p], "z", target[p].z, value, p + "Z");
                } else if (p === "opacity") {
                    m = (target.material.length) ? target.material : [target.material];
                    i = m.length;
                    while (--i > -1) {
                        m[i].transparent = true;
                        this._addTween(m[i], p, m[i][p], value, p);
                    }
                } else {
                    this._addTween(target, p, target[p], value, p);
                }
                this._overwriteProps.push(p);
            }
            return true;
        }
    });

}); if (_gsScope._gsDefine) { _gsScope._gsQueue.pop()(); }
///////////////////////////////////////////////////////////////////
///////////   DOMINOS WITH POSITIONAL COLLISION AUDIO   ///////////
///////////////////////////////////////////////////////////////////


///---Sources---///

//imports physijs_worker.js contents from HTML (necessary in order to use workers in codepen)
var blob = new Blob( [document.querySelector('#physijs_worker').textContent] );
Physijs.scripts.worker = window.URL.createObjectURL(blob);
//imports ammo.js, which physi.js runs on top of
Physijs.scripts.ammo = 'https://chandlerprall.github.io/Physijs/examples/js/ammo.js';


///---Initiation---///

//sets the renderer in the HTML
var loadingImagePage = document.getElementById("loading_image_page"); 
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMapEnabled = true;
// to antialias the shadow
renderer.shadowMapType = THREE.PCFSoftShadowMap;
document.body.appendChild( renderer.domElement );

//physi.js scene
var scene = new Physijs.Scene;
scene.background = new THREE.Color( 0xe6e6e6 );
scene.setGravity(new THREE.Vector3(0, -50, 0));

//background
renderer.setClearColor (0x111111, 1);

//camera
var camera = new THREE.PerspectiveCamera(35, window.innerWidth/window.innerHeight, 1, 10000 );
camera.position.set( 0, 15, 100 );
camera.lookAt( scene.position );
scene.add( camera );

//lighting
var lightP1 = new THREE.PointLight(0xFFFFFF, 0.2);
lightP1.position.set(-50,100,50);
lightP1.castShadow = true;
scene.add(lightP1);

var lightA1 = new THREE.AmbientLight(0xFFFFFF, .85);
scene.add(lightA1);


///---Tray---///

//declares a three.js geometry
var trayGeometry = new THREE.BoxGeometry(200, 1, 200);
//declares a physi.js material (threejs_material, friction, restitution)
var trayMaterial = Physijs.createMaterial(new THREE.MeshLambertMaterial({ color: 0xFFFFFF }),0.9,0.3);
var rimMaterial = Physijs.createMaterial(new THREE.MeshLambertMaterial({ color: 0xDDDDDD }),0.9,0.3);
//declares a physi.js box mesh (threejs_geometry, physijs_material, weight)
//(a weight of 0 will cause the mesh to remain fixed and not subject to gravity)
var tray = new Physijs.BoxMesh(trayGeometry, trayMaterial, 0);
tray.name = "tray";
tray.castShadow = true;
tray.receiveShadow = true;

//adds the whole tray to the scene as a single 3D object
scene.add(tray);
var tileGeometry = new THREE.BoxGeometry( 2, 15, 7 );
var ballGeometry = new THREE.SphereGeometry(5, 32, 32);
var ballMaterial = new THREE.MeshLambertMaterial({ color: 0x4149CF });
var ball = new Physijs.SphereMesh(ballGeometry, ballMaterial, undefined, 2);
ball.rotation.z = 90;
ball.__dirtyRotation = true;
ball.castShadow = true;
ball.receiveShadow = true;
ball.position.set(-25,18,3);
scene.add(ball);

///---Tiles---///

//creates an array of tiles positoned across the tray
var tiles = [];
for ( i=0; i<6 ; i++ ) {
  var tileGeometry = new THREE.BoxGeometry( 2, 15, 7 );
  var tileMaterial = Physijs.createMaterial( new THREE.MeshPhongMaterial({ color: 0xffffff}) );
  var tile = new Physijs.BoxMesh(tileGeometry, tileMaterial, 1);
  tile.castShadow = true;
  tile.receiveShadow = true;
  tile.position.set((i * 8) - 10,8,0);
  tile.hitList = ["tray"];
  tile.hasFallenFlat = false;
  tile.name = `tile${i}`;
  tiles.push(tile);
  scene.add(tile);
}








///---Functions---///

function displayLoadingImageIfDocumentNotReady() {
  loadingImagePage.style.visibility = "visible";
  document.onreadystatechange = () => {
    if (document.readyState === "complete") { 
      loadingImagePage.style.visibility = "hidden"; 
    }
  }
}

function removeFromArray(array,value) {  
  for ( i=0; i<array.length; i++ ) { 
    if ( array[i] === value ) {
     array.splice(i, 1); 
    } 
  }
}


///---Rendering---///

function render() {

  //tiles[0].position.x += .25
  //tiles[0].__dirtyPosition = true;
  
  //runs the physi.js physics engine
  scene.simulate();
  //renders the scene (scene, camera)
  renderer.render( scene, camera);
  //updates the scene on each screen repaint
  requestAnimationFrame( render );
};

displayLoadingImageIfDocumentNotReady(); 
render();

var ballPos = {
  x: ball.position.x
};

gsap.registerPlugin(ScrollTrigger);

var tl = gsap.timeline({
  scrollTrigger: {
    trigger: ".scrollTarget",
    start: "top top",
    end: "bottom 100%",
    scrub: true,
    markers: {color: "black"}
  }
})

tl.to(ballPos, {
   x: 50,
   ease: Linear.easeNone,
   duration: 7,
   onUpdate: function() {
     ball.position.x = ballPos.x;
    ball.__dirtyPosition = true;
   }
});




window.addEventListener( 'resize', function () {
  
  var width = window.innerWidth;
  var height = window.innerHeight;
  
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  
  renderer.setSize( width, height );
  
  
}, false );

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/93/three.min.js
  2. https://chandlerprall.github.io/Physijs/physi.js
  3. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js
  4. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/ScrollTrigger.min.js
  5. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/gsap-latest-beta.min.js
  6. https://codepen.io/motionharvest/pen/wBvaRbB/2e0a33310abc78fed3c00970d23e6f5c.js