Car.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. /**
  2. * @author alteredq / http://alteredqualia.com/
  3. */
  4. THREE.Car = function () {
  5. var scope = this;
  6. // car geometry manual parameters
  7. this.modelScale = 1;
  8. this.backWheelOffset = 2;
  9. this.autoWheelGeometry = true;
  10. // car geometry parameters automatically set from wheel mesh
  11. // - assumes wheel mesh is front left wheel in proper global
  12. // position with respect to body mesh
  13. // - other wheels are mirrored against car root
  14. // - if necessary back wheels can be offset manually
  15. this.wheelOffset = new THREE.Vector3();
  16. this.wheelDiameter = 1;
  17. // car "feel" parameters
  18. this.MAX_SPEED = 2200;
  19. this.MAX_REVERSE_SPEED = -1500;
  20. this.MAX_WHEEL_ROTATION = 0.6;
  21. this.FRONT_ACCELERATION = 1250;
  22. this.BACK_ACCELERATION = 1500;
  23. this.WHEEL_ANGULAR_ACCELERATION = 1.5;
  24. this.FRONT_DECCELERATION = 750;
  25. this.WHEEL_ANGULAR_DECCELERATION = 1.0;
  26. this.STEERING_RADIUS_RATIO = 0.0023;
  27. this.MAX_TILT_SIDES = 0.05;
  28. this.MAX_TILT_FRONTBACK = 0.015;
  29. // internal control variables
  30. this.speed = 0;
  31. this.acceleration = 0;
  32. this.wheelOrientation = 0;
  33. this.carOrientation = 0;
  34. // car rigging
  35. this.root = new THREE.Object3D();
  36. this.frontLeftWheelRoot = new THREE.Object3D();
  37. this.frontRightWheelRoot = new THREE.Object3D();
  38. this.bodyMesh = null;
  39. this.frontLeftWheelMesh = null;
  40. this.frontRightWheelMesh = null;
  41. this.backLeftWheelMesh = null;
  42. this.backRightWheelMesh = null;
  43. this.bodyGeometry = null;
  44. this.wheelGeometry = null;
  45. this.bodyMaterials = null;
  46. this.wheelMaterials = null;
  47. // internal helper variables
  48. this.loaded = false;
  49. this.meshes = [];
  50. // API
  51. this.enableShadows = function ( enable ) {
  52. for ( var i = 0; i < this.meshes.length; i ++ ) {
  53. this.meshes[ i ].castShadow = enable;
  54. this.meshes[ i ].receiveShadow = enable;
  55. }
  56. };
  57. this.setVisible = function ( enable ) {
  58. for ( var i = 0; i < this.meshes.length; i ++ ) {
  59. this.meshes[ i ].visible = enable;
  60. this.meshes[ i ].visible = enable;
  61. }
  62. };
  63. this.loadPartsJSON = function ( bodyURL, wheelURL ) {
  64. var loader = new THREE.JSONLoader();
  65. loader.load( bodyURL, function( geometry, materials ) { createBody( geometry, materials ) } );
  66. loader.load( wheelURL, function( geometry, materials ) { createWheels( geometry, materials ) } );
  67. };
  68. this.loadPartsBinary = function ( bodyURL, wheelURL ) {
  69. var loader = new THREE.BinaryLoader();
  70. loader.load( bodyURL, function( geometry, materials ) { createBody( geometry, materials ) } );
  71. loader.load( wheelURL, function( geometry, materials ) { createWheels( geometry, materials ) } );
  72. };
  73. this.updateCarModel = function ( delta, controls ) {
  74. // speed and wheels based on controls
  75. if ( controls.moveForward ) {
  76. this.speed = THREE.Math.clamp( this.speed + delta * this.FRONT_ACCELERATION, this.MAX_REVERSE_SPEED, this.MAX_SPEED );
  77. this.acceleration = THREE.Math.clamp( this.acceleration + delta, -1, 1 );
  78. }
  79. if ( controls.moveBackward ) {
  80. this.speed = THREE.Math.clamp( this.speed - delta * this.BACK_ACCELERATION, this.MAX_REVERSE_SPEED, this.MAX_SPEED );
  81. this.acceleration = THREE.Math.clamp( this.acceleration - delta, -1, 1 );
  82. }
  83. if ( controls.moveLeft ) {
  84. this.wheelOrientation = THREE.Math.clamp( this.wheelOrientation + delta * this.WHEEL_ANGULAR_ACCELERATION, - this.MAX_WHEEL_ROTATION, this.MAX_WHEEL_ROTATION );
  85. }
  86. if ( controls.moveRight ) {
  87. this.wheelOrientation = THREE.Math.clamp( this.wheelOrientation - delta * this.WHEEL_ANGULAR_ACCELERATION, - this.MAX_WHEEL_ROTATION, this.MAX_WHEEL_ROTATION );
  88. }
  89. // speed decay
  90. if ( ! ( controls.moveForward || controls.moveBackward ) ) {
  91. if ( this.speed > 0 ) {
  92. var k = exponentialEaseOut( this.speed / this.MAX_SPEED );
  93. this.speed = THREE.Math.clamp( this.speed - k * delta * this.FRONT_DECCELERATION, 0, this.MAX_SPEED );
  94. this.acceleration = THREE.Math.clamp( this.acceleration - k * delta, 0, 1 );
  95. } else {
  96. var k = exponentialEaseOut( this.speed / this.MAX_REVERSE_SPEED );
  97. this.speed = THREE.Math.clamp( this.speed + k * delta * this.BACK_ACCELERATION, this.MAX_REVERSE_SPEED, 0 );
  98. this.acceleration = THREE.Math.clamp( this.acceleration + k * delta, -1, 0 );
  99. }
  100. }
  101. // steering decay
  102. if ( ! ( controls.moveLeft || controls.moveRight ) ) {
  103. if ( this.wheelOrientation > 0 ) {
  104. this.wheelOrientation = THREE.Math.clamp( this.wheelOrientation - delta * this.WHEEL_ANGULAR_DECCELERATION, 0, this.MAX_WHEEL_ROTATION );
  105. } else {
  106. this.wheelOrientation = THREE.Math.clamp( this.wheelOrientation + delta * this.WHEEL_ANGULAR_DECCELERATION, - this.MAX_WHEEL_ROTATION, 0 );
  107. }
  108. }
  109. // car update
  110. var forwardDelta = this.speed * delta;
  111. this.carOrientation += ( forwardDelta * this.STEERING_RADIUS_RATIO )* this.wheelOrientation;
  112. // displacement
  113. this.root.position.x += Math.sin( this.carOrientation ) * forwardDelta;
  114. this.root.position.z += Math.cos( this.carOrientation ) * forwardDelta;
  115. // steering
  116. this.root.rotation.y = this.carOrientation;
  117. // tilt
  118. if ( this.loaded ) {
  119. this.bodyMesh.rotation.z = this.MAX_TILT_SIDES * this.wheelOrientation * ( this.speed / this.MAX_SPEED );
  120. this.bodyMesh.rotation.x = - this.MAX_TILT_FRONTBACK * this.acceleration;
  121. }
  122. // wheels rolling
  123. var angularSpeedRatio = 1 / ( this.modelScale * ( this.wheelDiameter / 2 ) );
  124. var wheelDelta = forwardDelta * angularSpeedRatio;
  125. if ( this.loaded ) {
  126. this.frontLeftWheelMesh.rotation.x += wheelDelta;
  127. this.frontRightWheelMesh.rotation.x += wheelDelta;
  128. this.backLeftWheelMesh.rotation.x += wheelDelta;
  129. this.backRightWheelMesh.rotation.x += wheelDelta;
  130. }
  131. // front wheels steering
  132. this.frontLeftWheelRoot.rotation.y = this.wheelOrientation;
  133. this.frontRightWheelRoot.rotation.y = this.wheelOrientation;
  134. };
  135. // internal helper methods
  136. function createBody ( geometry, materials ) {
  137. scope.bodyGeometry = geometry;
  138. scope.bodyMaterials = materials;
  139. createCar();
  140. };
  141. function createWheels ( geometry, materials ) {
  142. scope.wheelGeometry = geometry;
  143. scope.wheelMaterials = materials;
  144. createCar();
  145. };
  146. function createCar () {
  147. if ( scope.bodyGeometry && scope.wheelGeometry ) {
  148. // compute wheel geometry parameters
  149. if ( scope.autoWheelGeometry ) {
  150. scope.wheelGeometry.computeBoundingBox();
  151. var bb = scope.wheelGeometry.boundingBox;
  152. scope.wheelOffset.addVectors( bb.min, bb.max );
  153. scope.wheelOffset.multiplyScalar( 0.5 );
  154. scope.wheelDiameter = bb.max.y - bb.min.y;
  155. scope.wheelGeometry.center();
  156. }
  157. // rig the car
  158. var s = scope.modelScale,
  159. delta = new THREE.Vector3();
  160. var bodyFaceMaterial = new THREE.MeshFaceMaterial( scope.bodyMaterials );
  161. var wheelFaceMaterial = new THREE.MeshFaceMaterial( scope.wheelMaterials );
  162. // body
  163. scope.bodyMesh = new THREE.Mesh( scope.bodyGeometry, bodyFaceMaterial );
  164. scope.bodyMesh.scale.set( s, s, s );
  165. scope.root.add( scope.bodyMesh );
  166. // front left wheel
  167. delta.multiplyVectors( scope.wheelOffset, new THREE.Vector3( s, s, s ) );
  168. scope.frontLeftWheelRoot.position.add( delta );
  169. scope.frontLeftWheelMesh = new THREE.Mesh( scope.wheelGeometry, wheelFaceMaterial );
  170. scope.frontLeftWheelMesh.scale.set( s, s, s );
  171. scope.frontLeftWheelRoot.add( scope.frontLeftWheelMesh );
  172. scope.root.add( scope.frontLeftWheelRoot );
  173. // front right wheel
  174. delta.multiplyVectors( scope.wheelOffset, new THREE.Vector3( -s, s, s ) );
  175. scope.frontRightWheelRoot.position.add( delta );
  176. scope.frontRightWheelMesh = new THREE.Mesh( scope.wheelGeometry, wheelFaceMaterial );
  177. scope.frontRightWheelMesh.scale.set( s, s, s );
  178. scope.frontRightWheelMesh.rotation.z = Math.PI;
  179. scope.frontRightWheelRoot.add( scope.frontRightWheelMesh );
  180. scope.root.add( scope.frontRightWheelRoot );
  181. // back left wheel
  182. delta.multiplyVectors( scope.wheelOffset, new THREE.Vector3( s, s, -s ) );
  183. delta.z -= scope.backWheelOffset;
  184. scope.backLeftWheelMesh = new THREE.Mesh( scope.wheelGeometry, wheelFaceMaterial );
  185. scope.backLeftWheelMesh.position.add( delta );
  186. scope.backLeftWheelMesh.scale.set( s, s, s );
  187. scope.root.add( scope.backLeftWheelMesh );
  188. // back right wheel
  189. delta.multiplyVectors( scope.wheelOffset, new THREE.Vector3( -s, s, -s ) );
  190. delta.z -= scope.backWheelOffset;
  191. scope.backRightWheelMesh = new THREE.Mesh( scope.wheelGeometry, wheelFaceMaterial );
  192. scope.backRightWheelMesh.position.add( delta );
  193. scope.backRightWheelMesh.scale.set( s, s, s );
  194. scope.backRightWheelMesh.rotation.z = Math.PI;
  195. scope.root.add( scope.backRightWheelMesh );
  196. // cache meshes
  197. scope.meshes = [ scope.bodyMesh, scope.frontLeftWheelMesh, scope.frontRightWheelMesh, scope.backLeftWheelMesh, scope.backRightWheelMesh ];
  198. // callback
  199. scope.loaded = true;
  200. if ( scope.callback ) {
  201. scope.callback( scope );
  202. }
  203. }
  204. };
  205. function quadraticEaseOut( k ) { return - k * ( k - 2 ); }
  206. function cubicEaseOut( k ) { return --k * k * k + 1; }
  207. function circularEaseOut( k ) { return Math.sqrt( 1 - --k * k ); }
  208. function sinusoidalEaseOut( k ) { return Math.sin( k * Math.PI / 2 ); }
  209. function exponentialEaseOut( k ) { return k === 1 ? 1 : - Math.pow( 2, - 10 * k ) + 1; }
  210. };