| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <title>three.js physics - rapier3d vehicle controller</title>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
- <link type="text/css" rel="stylesheet" href="main.css">
- <style>
- body {
- color: #333;
- }
- </style>
- </head>
- <body>
- <div id="info">
- <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
- <p>WASD or Arrow keys to move</p>
- <p>Space to brake</p>
- <p>R to reset</p>
- </div>
- <script type="importmap">
- {
- "imports": {
- "three": "../build/three.module.js",
- "three/addons/": "./jsm/"
- }
- }
- </script>
- <script type="module">
- import * as THREE from 'three';
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
- import { RapierPhysics } from 'three/addons/physics/RapierPhysics.js';
- import { RapierHelper } from 'three/addons/helpers/RapierHelper.js';
- import Stats from 'three/addons/libs/stats.module.js';
- let camera, scene, renderer, stats;
- let physics, physicsHelper, controls;
- let car, chassis, wheels, movement, vehicleController;
- init();
- async function init() {
- scene = new THREE.Scene();
- scene.background = new THREE.Color( 0xbfd1e5 );
- camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
- camera.position.set( 0, 4, 10 );
- const ambient = new THREE.HemisphereLight( 0x555555, 0xFFFFFF );
- scene.add( ambient );
- const light = new THREE.DirectionalLight( 0xffffff, 4 );
- light.position.set( 0, 12.5, 12.5 );
- light.castShadow = true;
- light.shadow.radius = 3;
- light.shadow.blurSamples = 8;
- light.shadow.mapSize.width = 2048;
- light.shadow.mapSize.height = 2048;
- const size = 40;
- light.shadow.camera.left = - size;
- light.shadow.camera.bottom = - size;
- light.shadow.camera.right = size;
- light.shadow.camera.top = size;
- light.shadow.camera.near = 1;
- light.shadow.camera.far = 50;
- scene.add( light );
-
- renderer = new THREE.WebGLRenderer( { antialias: true } );
- renderer.setPixelRatio( window.devicePixelRatio );
- renderer.setSize( window.innerWidth, window.innerHeight );
- renderer.shadowMap.enabled = true;
- document.body.appendChild( renderer.domElement );
- renderer.setAnimationLoop( animate );
- controls = new OrbitControls( camera, renderer.domElement );
- controls.target = new THREE.Vector3( 0, 2, 0 );
- controls.update();
- const geometry = new THREE.BoxGeometry( 100, 0.5, 100 );
- const material = new THREE.MeshStandardMaterial( { color: 0xFFFFFF } );
-
- const ground = new THREE.Mesh( geometry, material );
- ground.receiveShadow = true;
- ground.position.set( 0, - 0.25, - 20 );
- ground.userData.physics = { mass: 0 };
- scene.add( ground );
- new THREE.TextureLoader().load( 'textures/grid.png', function ( texture ) {
- texture.wrapS = THREE.RepeatWrapping;
- texture.wrapT = THREE.RepeatWrapping;
- texture.repeat.set( 80, 80 );
- ground.material.map = texture;
- ground.material.needsUpdate = true;
- } );
- stats = new Stats();
- document.body.appendChild( stats.dom );
-
- initPhysics();
-
- onWindowResize();
- // Movement input
- movement = {
- forward: 0,
- right: 0,
- brake: 0,
- reset: false,
- accelerateForce: { value: 0, min: - 30, max: 30, step: 1 },
- brakeForce: { value: 0, min: 0, max: 1, step: 0.05 }
- };
- window.addEventListener( 'keydown', ( event ) => {
- //console.log( event.key );
- if ( event.key === 'w' || event.key === 'ArrowUp' ) movement.forward = - 1;
- if ( event.key === 's' || event.key === 'ArrowDown' ) movement.forward = 1;
- if ( event.key === 'a' || event.key === 'ArrowLeft' ) movement.right = 1;
- if ( event.key === 'd' || event.key === 'ArrowRight' ) movement.right = - 1;
- if ( event.key === 'r' ) movement.reset = true;
- if ( event.key === ' ' ) movement.brake = 1;
-
- } );
- window.addEventListener( 'keyup', ( event ) => {
- if ( event.key === 'w' || event.key === 's' || event.key === 'ArrowUp' || event.key === 'ArrowDown' ) movement.forward = 0;
- if ( event.key === 'a' || event.key === 'd' || event.key === 'ArrowLeft' || event.key === 'ArrowRight' ) movement.right = 0;
- if ( event.key === 'r' ) movement.reset = false;
- if ( event.key === ' ' ) movement.brake = 0;
-
- } );
- window.addEventListener( 'resize', onWindowResize, false );
- }
- async function initPhysics() {
- //Initialize physics engine using the script in the jsm/physics folder
- physics = await RapierPhysics();
- //Optionally display collider outlines
- physicsHelper = new RapierHelper( physics.world );
- scene.add( physicsHelper );
- physics.addScene( scene );
- createCar();
- }
- function createCar( ) {
- const geometry = new THREE.BoxGeometry( 2, 1, 4 );
- const material = new THREE.MeshStandardMaterial( { color: 0xFF0000 } );
- const mesh = new THREE.Mesh( geometry, material );
- mesh.castShadow = true;
- scene.add( mesh );
- car = mesh;
- mesh.position.y = 1;
- physics.addMesh( mesh, 10, 0.8 ); // addMesh places the RigidBody in the mesh.userData.physics object
- chassis = mesh.userData.physics.body;
- vehicleController = physics.world.createVehicleController( chassis );
- wheels = [];
- addWheel( 0, { x: - 1, y: 0, z: - 1.5 }, mesh );
- addWheel( 1, { x: 1, y: 0, z: - 1.5 }, mesh );
- addWheel( 2, { x: - 1, y: 0, z: 1.5 }, mesh );
- addWheel( 3, { x: 1, y: 0, z: 1.5 }, mesh );
- vehicleController.setWheelSteering( 0, Math.PI / 4 );
- vehicleController.setWheelSteering( 1, Math.PI / 4 );
- }
- function addWheel( index, pos, carMesh ) {
- // Define wheel properties
- const wheelRadius = 0.3;
- const wheelWidth = 0.4;
- const suspensionRestLength = 0.8;
- const wheelPosition = pos; // Position relative to chassis
- const wheelDirection = { x: 0.0, y: - 1.0, z: 0.0 }; // Downward direction
- const wheelAxle = { x: - 1.0, y: 0.0, z: 0.0 }; // Axle direction
- // Add the wheel to the vehicle controller
- vehicleController.addWheel(
- wheelPosition,
- wheelDirection,
- wheelAxle,
- suspensionRestLength,
- wheelRadius
- );
- // Set suspension stiffness for wheel
- vehicleController.setWheelSuspensionStiffness( index, 24.0 );
- // Set wheel friction
- vehicleController.setWheelFrictionSlip( index, 1000.0 );
- // Enable steering for the wheel
- vehicleController.setWheelSteering( index, pos.z < 0 );
- // Create a wheel mesh
- const geometry = new THREE.CylinderGeometry( wheelRadius, wheelRadius, wheelWidth, 16 );
- geometry.rotateZ( Math.PI * 0.5 );
- const material = new THREE.MeshStandardMaterial( { color: 0x000000 } );
- const wheel = new THREE.Mesh( geometry, material );
- wheel.castShadow = true;
- wheel.position.copy( pos );
- wheels.push( wheel );
- carMesh.add( wheel );
- }
- function updateWheels() {
- if ( vehicleController === undefined ) return;
- const wheelSteeringQuat = new THREE.Quaternion();
- const wheelRotationQuat = new THREE.Quaternion();
- const up = new THREE.Vector3( 0, 1, 0 );
- //const chassisPosition = chassis.translation();
- wheels.forEach( ( wheel, index ) => {
- const wheelAxleCs = vehicleController.wheelAxleCs( index );
- const connection = vehicleController.wheelChassisConnectionPointCs( index ).y || 0;
- const suspension = vehicleController.wheelSuspensionLength( index ) || 0;
- const steering = vehicleController.wheelSteering( index ) || 0;
- const rotationRad = vehicleController.wheelRotation( index ) || 0;
- wheel.position.y = connection - suspension;
- wheelSteeringQuat.setFromAxisAngle( up, steering );
- wheelRotationQuat.setFromAxisAngle( wheelAxleCs, rotationRad );
- wheel.quaternion.multiplyQuaternions( wheelSteeringQuat, wheelRotationQuat );
-
- } );
- }
- function updateCarControl() {
- if ( movement.reset ) {
- chassis.setTranslation( new physics.RAPIER.Vector3( 0, 1, 0 ), true );
- chassis.setRotation( new physics.RAPIER.Quaternion( 0, 0, 0, 1 ), true );
- chassis.setLinvel( new physics.RAPIER.Vector3( 0, 0, 0 ), true );
- chassis.setAngvel( new physics.RAPIER.Vector3( 0, 0, 0 ), true );
- movement.accelerateForce.value = 0;
- movement.brakeForce.value = 0;
- return;
- }
- let accelerateForce = 0;
- if ( movement.forward < 0 ) {
- //if (movement.accelerateForce.value === 0) chassis.wakeUp();
- accelerateForce = movement.accelerateForce.value - movement.accelerateForce.step;
- if ( accelerateForce < movement.accelerateForce.min ) accelerateForce = movement.accelerateForce.min;
-
- } else if ( movement.forward > 0 ) {
- //if (movement.accelerateForce.value === 0) chassis.wakeUp();
- accelerateForce = movement.accelerateForce.value + movement.accelerateForce.step;
-
- if ( accelerateForce > movement.accelerateForce.max ) accelerateForce = movement.accelerateForce.max;
-
- } else {
- if ( chassis.isSleeping() ) chassis.wakeUp();
- }
- movement.accelerateForce.value = accelerateForce;
- //console.log(accelerateForce);
- let brakeForce = 0;
- if ( movement.brake > 0 ) {
-
- brakeForce = movement.brakeForce.value + movement.brakeForce.step;
- if ( brakeForce > movement.brakeForce.max ) brakeForce = movement.brakeForce.max;
- }
- movement.brakeForce.value = brakeForce;
- const engineForce = accelerateForce;
- vehicleController.setWheelEngineForce( 0, engineForce );
- vehicleController.setWheelEngineForce( 1, engineForce );
- const currentSteering = vehicleController.wheelSteering( 0 );
- const steerDirection = movement.right;
- const steerAngle = Math.PI / 4;
- const steering = THREE.MathUtils.lerp( currentSteering, steerAngle * steerDirection, 0.25 );
- vehicleController.setWheelSteering( 0, steering );
- vehicleController.setWheelSteering( 1, steering );
- const wheelBrake = movement.brake * brakeForce;
- vehicleController.setWheelBrake( 0, wheelBrake );
- vehicleController.setWheelBrake( 1, wheelBrake );
- vehicleController.setWheelBrake( 2, wheelBrake );
- vehicleController.setWheelBrake( 3, wheelBrake );
- }
- function onWindowResize( ) {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize( window.innerWidth, window.innerHeight );
- }
- function animate() {
- if ( vehicleController ) {
- updateCarControl();
- vehicleController.updateVehicle( 1 / 60 );
- updateWheels();
- }
- if ( controls && car ) {
- controls.target.copy( car.position );
- controls.update();
- }
- if ( physicsHelper ) physicsHelper.update();
- renderer.render( scene, camera );
- stats.update();
- }
- </script>
- </body>
- </html>
|