1
0

physics_rapier_vehicle_controller.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js physics - rapier3d vehicle controller</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  7. <link type="text/css" rel="stylesheet" href="main.css">
  8. <style>
  9. body {
  10. color: #333;
  11. }
  12. </style>
  13. </head>
  14. <body>
  15. <div id="info">
  16. <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> physics - <a href="https://github.com/dimforge/rapier.js" target="_blank">rapier</a> vehicle controller
  17. <p>WASD or Arrow keys to move</p>
  18. <p>Space to brake</p>
  19. <p>R to reset</p>
  20. </div>
  21. <script type="importmap">
  22. {
  23. "imports": {
  24. "three": "../build/three.module.js",
  25. "three/addons/": "./jsm/"
  26. }
  27. }
  28. </script>
  29. <script type="module">
  30. import * as THREE from 'three';
  31. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  32. import { RapierPhysics } from 'three/addons/physics/RapierPhysics.js';
  33. import { RapierHelper } from 'three/addons/helpers/RapierHelper.js';
  34. import Stats from 'three/addons/libs/stats.module.js';
  35. let camera, scene, renderer, stats;
  36. let physics, physicsHelper, controls;
  37. let car, chassis, wheels, movement, vehicleController;
  38. init();
  39. async function init() {
  40. scene = new THREE.Scene();
  41. scene.background = new THREE.Color( 0xbfd1e5 );
  42. camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
  43. camera.position.set( 0, 4, 10 );
  44. const ambient = new THREE.HemisphereLight( 0x555555, 0xFFFFFF );
  45. scene.add( ambient );
  46. const light = new THREE.DirectionalLight( 0xffffff, 4 );
  47. light.position.set( 0, 12.5, 12.5 );
  48. light.castShadow = true;
  49. light.shadow.radius = 3;
  50. light.shadow.blurSamples = 8;
  51. light.shadow.mapSize.width = 2048;
  52. light.shadow.mapSize.height = 2048;
  53. const size = 40;
  54. light.shadow.camera.left = - size;
  55. light.shadow.camera.bottom = - size;
  56. light.shadow.camera.right = size;
  57. light.shadow.camera.top = size;
  58. light.shadow.camera.near = 1;
  59. light.shadow.camera.far = 50;
  60. scene.add( light );
  61. renderer = new THREE.WebGLRenderer( { antialias: true } );
  62. renderer.setPixelRatio( window.devicePixelRatio );
  63. renderer.setSize( window.innerWidth, window.innerHeight );
  64. renderer.shadowMap.enabled = true;
  65. document.body.appendChild( renderer.domElement );
  66. renderer.setAnimationLoop( animate );
  67. controls = new OrbitControls( camera, renderer.domElement );
  68. controls.target = new THREE.Vector3( 0, 2, 0 );
  69. controls.update();
  70. const geometry = new THREE.BoxGeometry( 100, 0.5, 100 );
  71. const material = new THREE.MeshStandardMaterial( { color: 0xFFFFFF } );
  72. const ground = new THREE.Mesh( geometry, material );
  73. ground.receiveShadow = true;
  74. ground.position.set( 0, - 0.25, - 20 );
  75. ground.userData.physics = { mass: 0 };
  76. scene.add( ground );
  77. new THREE.TextureLoader().load( 'textures/grid.png', function ( texture ) {
  78. texture.wrapS = THREE.RepeatWrapping;
  79. texture.wrapT = THREE.RepeatWrapping;
  80. texture.repeat.set( 80, 80 );
  81. ground.material.map = texture;
  82. ground.material.needsUpdate = true;
  83. } );
  84. stats = new Stats();
  85. document.body.appendChild( stats.dom );
  86. initPhysics();
  87. onWindowResize();
  88. // Movement input
  89. movement = {
  90. forward: 0,
  91. right: 0,
  92. brake: 0,
  93. reset: false,
  94. accelerateForce: { value: 0, min: - 30, max: 30, step: 1 },
  95. brakeForce: { value: 0, min: 0, max: 1, step: 0.05 }
  96. };
  97. window.addEventListener( 'keydown', ( event ) => {
  98. //console.log( event.key );
  99. if ( event.key === 'w' || event.key === 'ArrowUp' ) movement.forward = - 1;
  100. if ( event.key === 's' || event.key === 'ArrowDown' ) movement.forward = 1;
  101. if ( event.key === 'a' || event.key === 'ArrowLeft' ) movement.right = 1;
  102. if ( event.key === 'd' || event.key === 'ArrowRight' ) movement.right = - 1;
  103. if ( event.key === 'r' ) movement.reset = true;
  104. if ( event.key === ' ' ) movement.brake = 1;
  105. } );
  106. window.addEventListener( 'keyup', ( event ) => {
  107. if ( event.key === 'w' || event.key === 's' || event.key === 'ArrowUp' || event.key === 'ArrowDown' ) movement.forward = 0;
  108. if ( event.key === 'a' || event.key === 'd' || event.key === 'ArrowLeft' || event.key === 'ArrowRight' ) movement.right = 0;
  109. if ( event.key === 'r' ) movement.reset = false;
  110. if ( event.key === ' ' ) movement.brake = 0;
  111. } );
  112. window.addEventListener( 'resize', onWindowResize, false );
  113. }
  114. async function initPhysics() {
  115. //Initialize physics engine using the script in the jsm/physics folder
  116. physics = await RapierPhysics();
  117. //Optionally display collider outlines
  118. physicsHelper = new RapierHelper( physics.world );
  119. scene.add( physicsHelper );
  120. physics.addScene( scene );
  121. createCar();
  122. }
  123. function createCar( ) {
  124. const geometry = new THREE.BoxGeometry( 2, 1, 4 );
  125. const material = new THREE.MeshStandardMaterial( { color: 0xFF0000 } );
  126. const mesh = new THREE.Mesh( geometry, material );
  127. mesh.castShadow = true;
  128. scene.add( mesh );
  129. car = mesh;
  130. mesh.position.y = 1;
  131. physics.addMesh( mesh, 10, 0.8 ); // addMesh places the RigidBody in the mesh.userData.physics object
  132. chassis = mesh.userData.physics.body;
  133. vehicleController = physics.world.createVehicleController( chassis );
  134. wheels = [];
  135. addWheel( 0, { x: - 1, y: 0, z: - 1.5 }, mesh );
  136. addWheel( 1, { x: 1, y: 0, z: - 1.5 }, mesh );
  137. addWheel( 2, { x: - 1, y: 0, z: 1.5 }, mesh );
  138. addWheel( 3, { x: 1, y: 0, z: 1.5 }, mesh );
  139. vehicleController.setWheelSteering( 0, Math.PI / 4 );
  140. vehicleController.setWheelSteering( 1, Math.PI / 4 );
  141. }
  142. function addWheel( index, pos, carMesh ) {
  143. // Define wheel properties
  144. const wheelRadius = 0.3;
  145. const wheelWidth = 0.4;
  146. const suspensionRestLength = 0.8;
  147. const wheelPosition = pos; // Position relative to chassis
  148. const wheelDirection = { x: 0.0, y: - 1.0, z: 0.0 }; // Downward direction
  149. const wheelAxle = { x: - 1.0, y: 0.0, z: 0.0 }; // Axle direction
  150. // Add the wheel to the vehicle controller
  151. vehicleController.addWheel(
  152. wheelPosition,
  153. wheelDirection,
  154. wheelAxle,
  155. suspensionRestLength,
  156. wheelRadius
  157. );
  158. // Set suspension stiffness for wheel
  159. vehicleController.setWheelSuspensionStiffness( index, 24.0 );
  160. // Set wheel friction
  161. vehicleController.setWheelFrictionSlip( index, 1000.0 );
  162. // Enable steering for the wheel
  163. vehicleController.setWheelSteering( index, pos.z < 0 );
  164. // Create a wheel mesh
  165. const geometry = new THREE.CylinderGeometry( wheelRadius, wheelRadius, wheelWidth, 16 );
  166. geometry.rotateZ( Math.PI * 0.5 );
  167. const material = new THREE.MeshStandardMaterial( { color: 0x000000 } );
  168. const wheel = new THREE.Mesh( geometry, material );
  169. wheel.castShadow = true;
  170. wheel.position.copy( pos );
  171. wheels.push( wheel );
  172. carMesh.add( wheel );
  173. }
  174. function updateWheels() {
  175. if ( vehicleController === undefined ) return;
  176. const wheelSteeringQuat = new THREE.Quaternion();
  177. const wheelRotationQuat = new THREE.Quaternion();
  178. const up = new THREE.Vector3( 0, 1, 0 );
  179. //const chassisPosition = chassis.translation();
  180. wheels.forEach( ( wheel, index ) => {
  181. const wheelAxleCs = vehicleController.wheelAxleCs( index );
  182. const connection = vehicleController.wheelChassisConnectionPointCs( index ).y || 0;
  183. const suspension = vehicleController.wheelSuspensionLength( index ) || 0;
  184. const steering = vehicleController.wheelSteering( index ) || 0;
  185. const rotationRad = vehicleController.wheelRotation( index ) || 0;
  186. wheel.position.y = connection - suspension;
  187. wheelSteeringQuat.setFromAxisAngle( up, steering );
  188. wheelRotationQuat.setFromAxisAngle( wheelAxleCs, rotationRad );
  189. wheel.quaternion.multiplyQuaternions( wheelSteeringQuat, wheelRotationQuat );
  190. } );
  191. }
  192. function updateCarControl() {
  193. if ( movement.reset ) {
  194. chassis.setTranslation( new physics.RAPIER.Vector3( 0, 1, 0 ), true );
  195. chassis.setRotation( new physics.RAPIER.Quaternion( 0, 0, 0, 1 ), true );
  196. chassis.setLinvel( new physics.RAPIER.Vector3( 0, 0, 0 ), true );
  197. chassis.setAngvel( new physics.RAPIER.Vector3( 0, 0, 0 ), true );
  198. movement.accelerateForce.value = 0;
  199. movement.brakeForce.value = 0;
  200. return;
  201. }
  202. let accelerateForce = 0;
  203. if ( movement.forward < 0 ) {
  204. //if (movement.accelerateForce.value === 0) chassis.wakeUp();
  205. accelerateForce = movement.accelerateForce.value - movement.accelerateForce.step;
  206. if ( accelerateForce < movement.accelerateForce.min ) accelerateForce = movement.accelerateForce.min;
  207. } else if ( movement.forward > 0 ) {
  208. //if (movement.accelerateForce.value === 0) chassis.wakeUp();
  209. accelerateForce = movement.accelerateForce.value + movement.accelerateForce.step;
  210. if ( accelerateForce > movement.accelerateForce.max ) accelerateForce = movement.accelerateForce.max;
  211. } else {
  212. if ( chassis.isSleeping() ) chassis.wakeUp();
  213. }
  214. movement.accelerateForce.value = accelerateForce;
  215. //console.log(accelerateForce);
  216. let brakeForce = 0;
  217. if ( movement.brake > 0 ) {
  218. brakeForce = movement.brakeForce.value + movement.brakeForce.step;
  219. if ( brakeForce > movement.brakeForce.max ) brakeForce = movement.brakeForce.max;
  220. }
  221. movement.brakeForce.value = brakeForce;
  222. const engineForce = accelerateForce;
  223. vehicleController.setWheelEngineForce( 0, engineForce );
  224. vehicleController.setWheelEngineForce( 1, engineForce );
  225. const currentSteering = vehicleController.wheelSteering( 0 );
  226. const steerDirection = movement.right;
  227. const steerAngle = Math.PI / 4;
  228. const steering = THREE.MathUtils.lerp( currentSteering, steerAngle * steerDirection, 0.25 );
  229. vehicleController.setWheelSteering( 0, steering );
  230. vehicleController.setWheelSteering( 1, steering );
  231. const wheelBrake = movement.brake * brakeForce;
  232. vehicleController.setWheelBrake( 0, wheelBrake );
  233. vehicleController.setWheelBrake( 1, wheelBrake );
  234. vehicleController.setWheelBrake( 2, wheelBrake );
  235. vehicleController.setWheelBrake( 3, wheelBrake );
  236. }
  237. function onWindowResize( ) {
  238. camera.aspect = window.innerWidth / window.innerHeight;
  239. camera.updateProjectionMatrix();
  240. renderer.setSize( window.innerWidth, window.innerHeight );
  241. }
  242. function animate() {
  243. if ( vehicleController ) {
  244. updateCarControl();
  245. vehicleController.updateVehicle( 1 / 60 );
  246. updateWheels();
  247. }
  248. if ( controls && car ) {
  249. controls.target.copy( car.position );
  250. controls.update();
  251. }
  252. if ( physicsHelper ) physicsHelper.update();
  253. renderer.render( scene, camera );
  254. stats.update();
  255. }
  256. </script>
  257. </body>
  258. </html>
粤ICP备19079148号