<!------- TRANSITION PAGES ------->

<div id="landing_page_div" class="display_page">
  <div id="landing_logo_div">
    <img id="logo_landing" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_derby_logo.svg">
    <img id="button_play" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_button_play.svg">
  </div>
</div>

<!-- animated loader page -->
<div id="loading_animation_page" class="display_page">
  <div id="loading_animation_div">
    <svg id="loading_animation_svg" viewBox="0 0 100 100">
      <line class="loading_animation_lines" id="loading_animation_line_1" x1=5% y1=45% x2=95% y2=45% />
      <line class="loading_animation_lines" id="loading_animation_line_2" x1=5% y1=50% x2=95% y2=50% />
      <line class="loading_animation_lines" id="loading_animation_line_3" x1=19% y1=55% x2=81% y2=55% />
    </svg>
    <img id="loading_animation_insignia" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_insignia.svg">
  </div>
</div>

<!-- round win announcement page -->
<div id="round_win_page_div" class="display_page announcement_page">
  <div id="round_win_card_div" class="card_div">  
    <img id="round_win_card" class="card" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_round_win_card_gretchen.svg">
    <img id="round_score_green" class="round_score green_score" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_1of3_green.svg">
    <img id="round_score_red" class="round_score red_score" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_0of3_red.svg">
    <img id="button_play_next_round" class="card_button" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_button_play_next_round.svg">
  </div>
</div>

<!-- round draw announcement page -->
<div id="round_draw_page_div" class="display_page announcement_page">
  <div id="round_draw_card_div" class="card_div">  
    <img id="round_draw_card" class="card" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_round_draw_card.svg">
    <img id="button_replay_round" class="card_button" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_button_replay_round.svg">
  </div>
</div>

<!-- game win announcement pages -->
<div id="game_win_gretchen_page_div" class="display_page announcement_page">
  <div id="game_win_gretchen_card_div" class="card_div">  
    <img id="game_win_gretchen_card" class="card" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_game_win_card_gretchen.svg">
    <img class="card_button button_new_game" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_button_new_game.svg">
  </div>
</div>

<div id="game_win_bertha_page_div" class="display_page announcement_page">
  <div id="game_win_bertha_card_div" class="card_div">  
    <img id="game_win_bertha_card" class="card" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_game_win_card_bertha.svg">
    <img class="card_button button_new_game" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_button_new_game.svg">
  </div>
</div>




<!------- UI ------->

<!-- logo -->
<div id="logo_div">
  <img id="logo" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_derby_logo.svg">
  <a href="https://matthewma.in" target="_blank"><img id="copyright" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_copyright.svg"></a>
</div>


<!-- scoreboard -->
<div id="scoreboard_div">
  <img id="scoreboard_img" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_scoreboard.svg">
  <img id="board_score_green" class="board_score green_score" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_0of3_green.svg">
  <img id="board_score_red" class="board_score red_score" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_0of3_red.svg">
</div>

<!-- buttons -->
<div id="buttons_div">
  <img id="button_restart" class="buttons_items button_restart" title="restart the game" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_button_restart.svg">
  <img class="buttons_items button_divider" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_button_divider.svg">
  <img id="button_draw" class="buttons_items button_draw" title="declare match a draw" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_button_draw.svg">
</div>

<!-- nav -->
<img class="nav left_nav" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_nav_left.svg">
<img class="nav right_nav" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_nav_right.svg">



<!------- LIBRARIES ------->

<!-- jQuery source -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- three.js source -->
<script src='https://assets.codepen.io/409445/three.min.js'></script>
<!-- GLTFLoader.js source -->
<script src="https://assets.codepen.io/409445/GLTFLoader.js"></script>
<!-- physi.js source -->
<script src="https://assets.codepen.io/409445/physi.js"></script>
<!-- full physijs_worker.js contents in a script (necessary so that worker 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 { 
  width: 100%; 
  height: 100%;
}

.display_page {
  position: fixed;
  width: 100%;
  height: 100%;
  background: #F6EDD0;
  box-shadow:  0 0 120px 5px #514014 inset;
  z-index: 1;
}


/* Loading Animation */

#loading_animation_page {
  visibility: hidden;
  z-index: 2;
}

#loading_animation_div {
  width: 25%;
  height: 25%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#loading_animation_svg {
   width: 100%;
   height: 100%;
}

.loading_animation_lines { 
   stroke: #272727;
   stroke-width: 1%;
   stroke-linecap: round;
   animation-name: fade_out_in;
   animation-iteration-count: infinite;
   animation-duration: 900ms;
   animation-timing-function: linear;
}
#loading_animation_line_1 { animation-delay: 0ms; }
#loading_animation_line_2 { animation-delay: 175ms; }
#loading_animation_line_3 { animation-delay: 350ms; }

@keyframes fade_out_in {
   0% { opacity: 1 }
   20% { opacity: 0 }
   100% { opacity: 1 }
}

#loading_animation_insignia {
  position: absolute;
  width: 22%;
  height: 22%;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}



/* Landing Page */

#landing_page_div {
  background: #F9F9F9;
}

#landing_logo_div {
  position: absolute;
  width: 50%;
  top: 37%;
  left: 50%;
  transform: translate(-50%, -50%);
}

#logo_landing {
  width: 100%;
}

#button_play {
  position: absolute;
  width: 20%;
  top: 140%;
  left: 50%;
  transform: translate(-50%, -50%);
  opacity: .9;
}
#button_play:hover { 
  opacity: 1;
  cursor: pointer;
}



/* Announcement Pages */

.announcement_page {  
  visibility: hidden;
}

.card_div {
  position: absolute;
  width: 60%;
  top: 48%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.card {
  width: 100%;
}

.card_button {
  position: absolute;
  width: 30%;
  top: 95.5%;
  left: 50%;
  transform: translate(-50%, -50%);
  opacity: .9;
  cursor: pointer;
}
.card_button:hover { opacity: 1 }

.round_score {
  position: absolute;
  height: 8%;
  top: 66%;
  transform: translate(-50%, -50%);
}
#round_score_green { left: 38%; }
#round_score_red { left: 80%; }



/* UI */

#logo_div {
  position: fixed;
  width: 23%;
  min-width: 115px;
  max-width: 220px;
  top: 6%;
  left: 4%;
  opacity: .8;
}
#logo {
  width: 100%;
}
#copyright {
  position: absolute;
  bottom: -5%;
  left: 50%;
  transform: translate(-50%);
  width: 22%;
}

#scoreboard_div {
  position: fixed;
  width: 16%;
  max-width: 160px;
  min-width: 90px;
  top: 6%;
  right: 4%;
  opacity: .8;
}
#scoreboard_img {
  height: 100%;
  width: 100%;
}
.board_score {
  position: absolute;
  height: 35%;
  left: 76%;
}
#board_score_green { top: -1.5%; }
#board_score_red { top: 60%; }

#buttons_div {
  position: fixed;
  width: 35px;
  height: 80px;
  top: 47%;
  transform: translate(0, -50%);
  right: 4%;
}
.buttons_items {
  position: absolute;
  width: 100%;
  opacity: .7;
}
.button_restart {
  height: 33.333%;
  top: 0;
}
.button_divider {
  height: 33.333%;
  width: 70%;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  opacity: .5;
}
.button_draw {
  height: 33.333%;
  bottom: 0;
}
.button_restart:hover { opacity: 1; }
.button_draw:hover { opacity: 1; }

.nav {
  position: fixed;
  width: 12%;
  max-width: 125px;
  min-width: 75px;
  bottom: 5%;
  opacity: .85;
}
.left_nav { left: 4%; }
.right_nav { right: 4%; }

//////////////////////////////////////////
///////////     BUS DERBY      ///////////
//////////////////////////////////////////



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

Physijs.scripts.ammo = "https://assets.codepen.io/409445/ammo.js";

//physijs_worker.js called from HTML script (needed so worker can be used in codepen)
// Physijs.scripts.worker = "https://chandlerprall.github.io/Physijs/physijs_worker.js";
var blob = new Blob( [document.querySelector('#physijs_worker').textContent] );
Physijs.scripts.worker = window.URL.createObjectURL(blob);



/////---Settings---/////

var bwf = 3.5;  //bus wheel friction 
var bwr = 0;  //bus wheel restitution
var pf = 4.2;  //platform friction
var pr = 0;  //platform restitution
var gravity = -50;



/////---Initiation---/////
 
var scene, environment, camera;
var backgroundColor = 0xCDD3D6;
var busArray = [];
var Player1 = { name: "gretchen", score: 0 };
var Player2 = { name: "bertha", score: 0 };
var gameHasBegun = false;
var roundActive = false;
var gameHasEnded = false;
var loadingAnimation = document.getElementById("loading_animation_page");
var loadingAnimationVisible = false;

///Renderer
var renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );



/////---Objects---/////


///--Environment---///
function Environment() {
  
  ///physi.js scene
  scene = new Physijs.Scene;
  scene.setGravity(new THREE.Vector3(0, gravity, 0));

  ///background
  renderer.setClearColor (backgroundColor, 1);

  ///camera
  camera = new THREE.PerspectiveCamera(35, window.innerWidth/window.innerHeight, 1, 10000 );
  camera.position.set( 0, 300, 600 );
  camera.zoom = 3;
  scene.add( camera );

  ///lighting & shadows
  var lightA1 = new THREE.AmbientLight(0xFFFFFF, 0.85);
  scene.add(lightA1);
  var lightD1 = new THREE.DirectionalLight( 0xFFFFFF, 0.3 );
  lightD1.position.set( -20, 100, 20 );
  lightD1.castShadow = true;
  lightD1.shadow.camera.left = -100;
  lightD1.shadow.camera.top = -100;
  lightD1.shadow.camera.right = 100;
  lightD1.shadow.camera.bottom = 100;
  lightD1.shadow.camera.near = 1;
  lightD1.shadow.camera.far = 130;
  lightD1.shadow.mapSize.height = lightD1.shadow.mapSize.width = 1000;
  scene.add( lightD1 );
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;

  ///fog
  scene.fog = new THREE.Fog( 
    backgroundColor, 
    camera.position.z + 35, 
    camera.position.z + 250 
  );

  ///platform
  var platform;
  var platformDiameter = 170;
  var platformRadiusTop = platformDiameter * 0.5;  
  var platformRadiusBottom = platformDiameter * 0.5 + 0.2;
  var platformHeight = 1;
  var platformSegments = 85;
  
  var platformGeometry = new THREE.CylinderGeometry( 
    platformRadiusTop, 
    platformRadiusBottom, 
    platformHeight, 
    platformSegments 
  );
  
  //physi.js platform (invisible; provides structure) (separating three.js & physi.js improves peformance)
  var physiPlatformMaterial = Physijs.createMaterial(
    new THREE.MeshLambertMaterial(), pf, pr  
  );
  var physiPlatform = new Physijs.CylinderMesh(platformGeometry, physiPlatformMaterial, 0 );
  physiPlatform.name = "physicalPlatform";
  physiPlatform.position.set(0, -0.5, 0);
  physiPlatform.visible = false;
  scene.add( physiPlatform );

  //three.js platform (visible; provides image) (separating three.js & physi.js improves peformance)
  var platformMaterialsArray = [];
  var platformMaterialColor = new THREE.MeshLambertMaterial( { color: 0x606060 } );
  platformMaterialsArray.push( platformMaterialColor );  //(materialindex = 0)
  var platformImage = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/asphalt_texture_2.jpg";
  var platformTextureLoader = new THREE.TextureLoader();
  ptr = 4.5;  //platform texture repeat
  platformTextureLoader.load(platformImage, function (texture) {
    //shrinks & repeats the image for the designate number of times
    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
    texture.repeat.set( ptr, ptr );
    //sets textue
    var platformMaterialImage = new THREE.MeshLambertMaterial( { map: texture } );
    platformMaterialsArray.push( platformMaterialImage );  //(materials index = 1)
  });
  var faceCount = platformGeometry.faces.length;
  for ( i=0; i<faceCount; i++ ) {
    if ( i < platformSegments*2 ) {  //(cylinder side)
      platformGeometry.faces[i].materialIndex = 0;
    } else if ( i < platformSegments*3 ) {  //(cylinder top)
      platformGeometry.faces[i].materialIndex = 1;
    } else {  //(cylinder bottom)
      platformGeometry.faces[i].materialIndex = 0;
    }
  }
  var visiblePlatform = new THREE.Mesh( platformGeometry, platformMaterialsArray );
  visiblePlatform.name = "visiblePlatform"
  visiblePlatform.position.set(0, -.5, 0);
  visiblePlatform.rotation.y = .4;
  visiblePlatform.receiveShadow = true;
  scene.add( visiblePlatform );

}


///---Buses---///
function Bus(platformSide) {  //platformSide should be "platformLeft" or "platformRight"
  
  var bus = this;
  bus.platformSide = platformSide;
  bus.score = 0;

  ///frame
  var bfp = ( bus.platformSide == "platformLeft" ? { x:-40, y:3, z:0 } : { x:40, y:3, z:0 } );  //bus frame position
  var busFrameGeometry = new THREE.BoxGeometry( 33, 4, 5 );
  var busFrameMesh = new THREE.MeshStandardMaterial({ color: 0x333333 });
  var busFrameMaterial = Physijs.createMaterial( busFrameMesh, 0.9, 0.9 );
  bus.frame = new Physijs.BoxMesh(busFrameGeometry, busFrameMaterial, 100 );
  bus.frame.name = "frame";
  bus.frame.componentOf = "bus";
  bus.frame.position.set( bfp.x, bfp.y, bfp.z );
  bus.frame.castShadow = true;

  ///interior (provides mass to body for collisions)
  var busInteriorGeometry = new THREE.BoxGeometry( 33, 7, 11 );
  var busInteriorMesh = new THREE.MeshStandardMaterial({ color: 0x777777 });
  var busInteriorMaterial = Physijs.createMaterial( busInteriorMesh, 50, 50 );
  bus.interior = new Physijs.BoxMesh(busInteriorGeometry, busInteriorMaterial, 5000 );
  bus.interior.name = "interior";
  bus.interior.visible = false;  //(if visible, edges stick out from rounded frame)
  bus.interior.componentOf = "bus"; 
  bus.interior.position.set( 0, 5.5, 0 );
  bus.frame.add(bus.interior);

  ///body
  var color = ( bus.platformSide == "platformLeft" ? "green" : "red" );
  var loader = new THREE.GLTFLoader();
  loader.load(
    `https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_body_${color}.glb`,
    function ( gltf ) {
      var scale = 5.6;  
      bus.body = gltf.scene.children[0]; 
      bus.body.name = "body";
      bus.body.componentOf = "bus"; 
      bus.body.rotation.set ( 0, -1.5708, 0 );
      bus.body.scale.set (scale,scale,scale);
      bus.body.position.set ( 0, 3.6, 0 );
      bus.body.castShadow = true;
      bus.frame.add(bus.body);
    },
  );
  
  //rotates platformLeft bus 180 degress so facing right bus
  if ( bus.platformSide === "platformLeft" ) { bus.frame.rotation.y = Math.PI }
  
  //adds all static bus parts to the scene as a single physical object
  scene.add( bus.frame );
  
  ///wheels
  var fr = 2;  //wheel front radius
  var br = 2;  //wheel back radius
  var wi = 1;  //wheel width
  var segments = 50;  //wheel cylinder segments (pie slices)
  var busWheelMaterialsArray = [];
  var busWheelImage = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_wheel_front_uv_fill.png";
  var busWheelGeometry = new THREE.CylinderGeometry( fr, br, wi, segments );

  //wheel side & back material (color only, no image)
  var busWheelColorBaseMaterial = new THREE.MeshLambertMaterial({ color: 0x000000 });
  var busWheelColorMaterial = Physijs.createMaterial( busWheelColorBaseMaterial, bwf, bwr );
  busWheelMaterialsArray.push( busWheelColorMaterial );  //(.materialindex = 0)

  //wheel front material (wheel image)
  var busWheelImageLoader = new THREE.TextureLoader();
  busWheelImageLoader.load( busWheelImage, function ( texture ) { 
    var busWheelImageMaterial = Physijs.createMaterial( 
      new THREE.MeshBasicMaterial({ map: texture }), bwf, bwr 
    ); 
    busWheelMaterialsArray.push( busWheelImageMaterial );  //(.materialindex = 1)
  }); 

  //assigns each of the wheel's faces to a .materialindex
  var busWheelFaceCount = busWheelGeometry.faces.length;
  for ( i=0; i<busWheelFaceCount; i++ ) {
    //first set of faces makes up the wheel's tread
    if ( i < segments*2 ) {
      busWheelGeometry.faces[i].materialIndex = 0; //assigns color material index
    //second set of faces makes up the wheel's outside
    } else if ( i < segments*3 ) {
      busWheelGeometry.faces[i].materialIndex = 1; //assigns image material index
    //third set of faces makes up the wheel's inside
    } else {
      busWheelGeometry.faces[i].materialIndex = 0; //assigns color material index
    }
  }

  //wheel creation & configuration as four physi.js objects
  bus.wheel_fl = new Physijs.CylinderMesh( busWheelGeometry, busWheelMaterialsArray, 300 );
  bus.wheel_fr = new Physijs.CylinderMesh( busWheelGeometry, busWheelMaterialsArray, 300 );
  bus.wheel_bl = new Physijs.CylinderMesh( busWheelGeometry, busWheelMaterialsArray, 300 );
  bus.wheel_br = new Physijs.CylinderMesh( busWheelGeometry, busWheelMaterialsArray, 300 );

  if ( bus.platformSide === "platformRight" ) {
    var frontX = bfp.x - 9.5; var backX = bfp.x + 9.5 
  } else { 
    var frontX = bfp.x + 9.5; var backX = bfp.x - 9.5 
  }
  configureWheel( bus.wheel_fl, { x: frontX, y: 2, z: bfp.z + 5 }, "port" );
  configureWheel( bus.wheel_fr, { x: frontX, y: 2, z: bfp.z - 5 }, "starboard" );
  configureWheel( bus.wheel_bl, { x: backX, y: 2, z: bfp.z + 5 }, "port" );
  configureWheel( bus.wheel_br, { x: backX, y: 2, z: bfp.z - 5 }, "starboard" );   
  
  ///wheel constraints
  var wheel_fl_constraint = new Physijs.DOFConstraint( bus.wheel_fl, bus.frame, bus.wheel_fl.position );
  var wheel_fr_constraint = new Physijs.DOFConstraint( bus.wheel_fr, bus.frame, bus.wheel_fr.position );
  var wheel_bl_constraint = new Physijs.DOFConstraint( bus.wheel_bl, bus.frame, bus.wheel_bl.position );
  var wheel_br_constraint = new Physijs.DOFConstraint( bus.wheel_br, bus.frame, bus.wheel_br.position );

  bus.wheel_fl_constraint = configureWheelConstraints( wheel_fl_constraint );
  bus.wheel_fr_constraint = configureWheelConstraints( wheel_fr_constraint );
  bus.wheel_bl_constraint = configureWheelConstraints( wheel_bl_constraint );
  bus.wheel_br_constraint = configureWheelConstraints( wheel_br_constraint );

}


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

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

function onWindowResize() {
  sceneHeight = window.innerHeight;
  sceneWidth = window.innerWidth;
  renderer.setSize(sceneWidth, sceneHeight);
  camera.aspect = sceneWidth/sceneHeight;
  camera.updateProjectionMatrix();
}

function configureWheel( wheel, position, BusSide ) {
  wheel.name = "wheel";
  wheel.componentOf = "bus";
  BusSide === "port" ? wheel.rotation.x = Math.PI / 2 : wheel.rotation.x = -Math.PI / 2;
  wheel.position.set( position.x, position.y, position.z );
  wheel.setDamping( 0.5, 0.5 );
  wheel.castShadow = true;
  scene.add( wheel );
}

function configureWheelConstraints( constraint ) {
  scene.addConstraint( constraint );
  constraint.setAngularLowerLimit({ x: 0, y: 0, z: 1 });
  constraint.setAngularUpperLimit({ x: 0, y: 0, z: 0 });
  return constraint;
}

function restartGame() {
  window.location.reload();
}

function initializeMatch() {
  environment = new Environment();
  busArray = [];
  busArray.push( new Bus("platformLeft") );
  busArray.push( new Bus("platformRight") );
  roundActive = true;
}

function displayLoadingAnimation(milliseconds) {
  loadingAnimation.style.visibility = "visible";
  loadingAnimationVisible = true;
  setTimeout(function(){ 
    loadingAnimation.style.visibility = "hidden"; 
    loadingAnimationVisible = false;
  }, milliseconds); 
}

function pause(milliseconds) {
  var then = Date.now(); 
  var now;
  do { now = Date.now() } while ( now - then < milliseconds );
}

function freezeBuses() {
  for ( i=0; i<busArray.count; i++ ) {
    busArray[i].frame.mass = 0;
    busArray[i].interior.mass = 0;
    busArray[i].wheel_fl.mass = busArray[i].wheel_fr.mass = 0;
    busArray[i].wheel_bl.mass = busArray[i].wheel_br.mass = 0;
  } 
}

function restartGame() {
  Player1.score = 0;
  Player2.score = 0;
  var greenScoreEls = document.getElementsByClassName("green_score");  
  var redScoreEls = document.getElementsByClassName("red_score");
  for ( i=0; i<2; i++) {
    greenScoreEls[i].src = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_0of3_green.svg";
    redScoreEls[i].src = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_0of3_red.svg";
  };
  $("#game_win_gretchen_page_div").css("visibility", "hidden");
  $("#game_win_bertha_page_div").css("visibility", "hidden");
  gameHasEnded = false;
  initializeMatch();
}

function aBusHasFallen() {
  return ( busArray[0].frame.position.y < -50 || busArray[1].frame.position.y < -50 )
}

function bothBusesHaveFallen() {
  return ( busArray[0].frame.position.y < 0 && busArray[1].frame.position.y < 0 )
}

function declareMatchDraw() {
  roundActive = false;
  $("#round_draw_page_div").css("visibility", "visible");
}

function declareRoundWin(winner) {
  loadingAnimation.style.visibility = "visible";
  var cardEl = document.getElementById("round_win_card"); 
  cardEl.src = `https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_round_win_card_${winner.name}.svg`;
  var greenScoreEls = document.getElementsByClassName("green_score");  
  var redScoreEls = document.getElementsByClassName("red_score");
  for ( i=0; i<2; i++) {
    greenScoreEls[i].src = `https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_${Player1.score}of3_green.svg`;
    redScoreEls[i].src = `https://s3-us-west-2.amazonaws.com/s.cdpn.io/409445/bus_${Player2.score}of3_red.svg`;
  };
  $("#round_win_page_div").css("visibility", "visible");
  displayLoadingAnimation(1500);
}

function declareGameWin(winner) {
  gameHasEnded = true;
  loadingAnimation.style.visibility = "visible";
  if (winner.name === "gretchen") {
    $("#game_win_gretchen_page_div").css("visibility", "visible");
  } else {
    $("#game_win_bertha_page_div").css("visibility", "visible");
  }
  displayLoadingAnimation(1500);
}

function checkForMatchCompletion() {
  if ( aBusHasFallen() ) {
    freezeBuses(); 
    pause(1000);
    roundActive = false;
    if ( bothBusesHaveFallen() ) { 
      declareMatchDraw();
    } else {
      var winner = busArray[0].frame.position.y > 0 ? Player1 : Player2;
      winner.score += 1;
      if (winner.score < 3) {
        declareRoundWin(winner);
      } else {
        declareGameWin(winner);
      }
    }
  }
}

function handleKeyDown ( keyEvent ) {
  // sets wheel motors; configureAngularMotor params are:
  //   1) which_motor (as numbers matched to axes: 0 = x, 1 = y, 2 = z)
  //   2) low_limit (lower limit of the motor)
  //   3) high_limit (upper limit of the motor)
  //   4) velocity (target velocity)
  //   5) max_force (maximum force the motor can apply)
  switch ( keyEvent.keyCode ) {
    // BUS 1
    // pivots wheels for steering
    case 65: case 37:  // "a" key or left arrow key (turn left)
      busArray[0].wheel_fr_constraint.configureAngularMotor( 1, -Math.PI / 4, Math.PI / 4, 10, 200 );
      busArray[0].wheel_fr_constraint.enableAngularMotor( 1 );
      busArray[0].wheel_fl_constraint.configureAngularMotor( 1, -Math.PI / 4, Math.PI / 4, 10, 200 );
      busArray[0].wheel_fl_constraint.enableAngularMotor( 1 );
    break;
    case 68: case 39:  // "d" key  or right arrow key (turn right)
      busArray[0].wheel_fr_constraint.configureAngularMotor( 1, -Math.PI / 4, Math.PI / 4, -10, 200 );
      busArray[0].wheel_fr_constraint.enableAngularMotor( 1 );
      busArray[0].wheel_fl_constraint.configureAngularMotor( 1, -Math.PI / 4, Math.PI / 4, -10, 200 );
      busArray[0].wheel_fl_constraint.enableAngularMotor( 1 );
    break;
    // rotates wheels for propulsion
    case 87: case 38: // "w" key or up arrow key (forward)
      busArray[0].wheel_bl_constraint.configureAngularMotor( 2, 1, 0, -30, 50000 );
      busArray[0].wheel_bl_constraint.enableAngularMotor( 2 );
      busArray[0].wheel_br_constraint.configureAngularMotor( 2, 1, 0, -30, 50000 );
      busArray[0].wheel_br_constraint.enableAngularMotor( 2 );
    break;
    case 83: case 40:  // "s" key or down arrow key (backward)
      busArray[0].wheel_bl_constraint.configureAngularMotor( 2, 1, 0, 20, 3500 );
      busArray[0].wheel_bl_constraint.enableAngularMotor( 2 );
      busArray[0].wheel_br_constraint.configureAngularMotor( 2, 1, 0, 20, 3500 );
      busArray[0].wheel_br_constraint.enableAngularMotor( 2 );
    break;
    // BUS 2
    // pivots wheels for steering
    case 76:  // "l" key (turn left)
      busArray[1].wheel_fr_constraint.configureAngularMotor( 1, -Math.PI / 4, Math.PI / 4, 10, 200 );
      busArray[1].wheel_fr_constraint.enableAngularMotor( 1 );
      busArray[1].wheel_fl_constraint.configureAngularMotor( 1, -Math.PI / 4, Math.PI / 4, 10, 200 );
      busArray[1].wheel_fl_constraint.enableAngularMotor( 1 );
    break;
    case 222:  // "'" key (turn right)
      busArray[1].wheel_fr_constraint.configureAngularMotor( 1, -Math.PI / 4, Math.PI / 4, -10, 200 );
      busArray[1].wheel_fr_constraint.enableAngularMotor( 1 );
      busArray[1].wheel_fl_constraint.configureAngularMotor( 1, -Math.PI / 4, Math.PI / 4, -10, 200 );
      busArray[1].wheel_fl_constraint.enableAngularMotor( 1 );
    break;
    // rotates wheels for propulsion
    case 80:  // "p" key (forward)
      busArray[1].wheel_bl_constraint.configureAngularMotor( 2, 1, 0, 30, 50000 );
      busArray[1].wheel_bl_constraint.enableAngularMotor( 2 );
      busArray[1].wheel_br_constraint.configureAngularMotor( 2, 1, 0, 30, 50000 );
      busArray[1].wheel_br_constraint.enableAngularMotor( 2 );
    break;
    case 186:  // ";" key (backward)
      busArray[1].wheel_bl_constraint.configureAngularMotor( 2, 1, 0, -20, 3500 );
      busArray[1].wheel_bl_constraint.enableAngularMotor( 2 );
      busArray[1].wheel_br_constraint.configureAngularMotor( 2, 1, 0, -20, 3500 );
      busArray[1].wheel_br_constraint.enableAngularMotor( 2 );
    break;
  }
}

function handleKeyUp(keyEvent){
   switch( keyEvent.keyCode ) {
    // BUS 1
    //sets front wheels straight again
     case 65: case 68: case 37: case 39:
      busArray[0].wheel_fr_constraint.configureAngularMotor( 1, 0, 0, 10, 200 );
      busArray[0].wheel_fr_constraint.enableAngularMotor( 1 );
      busArray[0].wheel_fl_constraint.configureAngularMotor( 1, 0, 0, 10, 200 );
      busArray[0].wheel_fl_constraint.enableAngularMotor( 1 );
    break;
    //stops back wheel rotation
     case 87: case 83: case 38: case 40: 
      busArray[0].wheel_bl_constraint.configureAngularMotor( 2, 0, 0, 0, 2000 );
      busArray[0].wheel_bl_constraint.enableAngularMotor( 2 );
      busArray[0].wheel_br_constraint.configureAngularMotor( 2, 0, 0, 0, 2000 );
      busArray[0].wheel_br_constraint.enableAngularMotor( 2 );
    break;
    // BUS 2
    //sets front wheels straight again
    case 76: case 222:
      busArray[1].wheel_fr_constraint.configureAngularMotor( 1, 0, 0, 10, 200 );
      busArray[1].wheel_fr_constraint.enableAngularMotor( 1 );
      busArray[1].wheel_fl_constraint.configureAngularMotor( 1, 0, 0, 10, 200 );
      busArray[1].wheel_fl_constraint.enableAngularMotor( 1 );
    break;
    //stops back wheel rotation
     case 80: case 186:
      busArray[1].wheel_bl_constraint.configureAngularMotor( 2, 0, 0, 0, 2000 );
      busArray[1].wheel_bl_constraint.enableAngularMotor( 2 );
      busArray[1].wheel_br_constraint.configureAngularMotor( 2, 0, 0, 0, 2000 );
      busArray[1].wheel_br_constraint.enableAngularMotor( 2 );
    break;
  }
}



/////---Interaction---/////

window.addEventListener("resize", onWindowResize, false);

document.onkeydown = handleKeyDown;
document.onkeyup = handleKeyUp;


$("#button_play").click(function(){ 
  gameHasBegun = true;
  displayLoadingAnimation(1500); 
  $("#landing_page_div").hide();  
  initializeMatch(); 
});

$("#button_restart").click(function(){ 
  displayLoadingAnimation(1500); 
  restartGame() 
});

$("#button_draw").click(function(){ 
  declareMatchDraw(); 
});

$("#button_play_next_round").click(function(){ 
  displayLoadingAnimation(1500); 
  $("#round_win_page_div").css("visibility", "hidden"); 
  initializeMatch(); 
});

$("#button_replay_round").click(function(){ 
  displayLoadingAnimation(1500); 
  $("#round_draw_page_div").css("visibility", "hidden"); 
  initializeMatch(); 
});

$(".button_new_game").click(function(){ 
  displayLoadingAnimation(1500); 
  restartGame(); 
});

$(document).on("keypress",function(e) {
  if ( e.key === "Enter" && !loadingAnimationVisible ) {
    if ( !gameHasBegun ) {
      $("#button_play").click();
    } else if ( gameHasEnded ) {
      $(".button_new_game").click();
    } else if ( !roundActive ) {
      $("#button_play_next_round").click();
      $("#button_replay_round").click();
    }
    e.stopPropagation();
  }
});



/////---Display---/////

initializeMatch();
//playLoadingAnimationIfDocumentNotReady();

function render() {
  if ( roundActive === true ) { checkForMatchCompletion(); }
  scene.simulate();
  camera.lookAt( 0, 1, 0 );
  //camera.lookAt( busArray[1].frame.position );
  camera.updateProjectionMatrix();
  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.