Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Simulateur de Camion - Vues Extérieures</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: 'Arial', sans-serif; | |
| } | |
| #container { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| } | |
| #ui { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| z-index: 100; | |
| } | |
| .dashboard { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: rgba(0, 0, 0, 0.7); | |
| border-radius: 15px; | |
| padding: 15px; | |
| display: flex; | |
| gap: 30px; | |
| backdrop-filter: blur(5px); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .gauge { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| color: white; | |
| } | |
| .gauge-value { | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| margin-top: 5px; | |
| color: #4ade80; | |
| } | |
| .gauge-label { | |
| font-size: 0.8rem; | |
| opacity: 0.8; | |
| } | |
| .steering-wheel { | |
| position: absolute; | |
| bottom: 120px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 120px; | |
| height: 120px; | |
| background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45" fill="none" stroke="white" stroke-width="5"/><circle cx="50" cy="50" r="15" fill="none" stroke="white" stroke-width="5"/><path d="M50,5 L50,25 M50,75 L50,95 M5,50 L25,50 M75,50 L95,50" stroke="white" stroke-width="5"/></svg>') no-repeat center; | |
| background-size: contain; | |
| pointer-events: none; | |
| } | |
| .gear-indicator { | |
| position: absolute; | |
| bottom: 100px; | |
| right: 30px; | |
| background: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| padding: 10px 15px; | |
| border-radius: 8px; | |
| font-size: 1.2rem; | |
| font-weight: bold; | |
| backdrop-filter: blur(5px); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .menu { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| background: rgba(0, 0, 0, 0.7); | |
| border-radius: 10px; | |
| padding: 10px; | |
| backdrop-filter: blur(5px); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| pointer-events: all; | |
| } | |
| .menu-button { | |
| background: rgba(255, 255, 255, 0.1); | |
| color: white; | |
| border: none; | |
| padding: 8px 15px; | |
| border-radius: 5px; | |
| margin: 5px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .menu-button:hover { | |
| background: rgba(255, 255, 255, 0.3); | |
| } | |
| .view-buttons { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| background: rgba(0, 0, 0, 0.7); | |
| border-radius: 10px; | |
| padding: 10px; | |
| backdrop-filter: blur(5px); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| pointer-events: all; | |
| } | |
| .loading-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: #111; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| z-index: 1000; | |
| } | |
| .progress-bar { | |
| width: 300px; | |
| height: 10px; | |
| background: rgba(255, 255, 255, 0.2); | |
| border-radius: 5px; | |
| margin-top: 20px; | |
| overflow: hidden; | |
| } | |
| .progress { | |
| height: 100%; | |
| background: #3b82f6; | |
| width: 0%; | |
| transition: width 0.3s; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="container"> | |
| <div id="ui"> | |
| <div class="dashboard"> | |
| <div class="gauge"> | |
| <div class="gauge-label">VITESSE</div> | |
| <div id="speed" class="gauge-value">0 km/h</div> | |
| </div> | |
| <div class="gauge"> | |
| <div class="gauge-label">RPM</div> | |
| <div id="rpm" class="gauge-value">0</div> | |
| </div> | |
| <div class="gauge"> | |
| <div class="gauge-label">RÉSERVOIR</div> | |
| <div id="fuel" class="gauge-value">100%</div> | |
| </div> | |
| <div class="gauge"> | |
| <div class="gauge-label">TEMPS</div> | |
| <div id="time" class="gauge-value">00:00</div> | |
| </div> | |
| <div class="gauge"> | |
| <div class="gauge-label">DISTANCE</div> | |
| <div id="distance" class="gauge-value">0 km</div> | |
| </div> | |
| </div> | |
| <div class="steering-wheel" id="steering-wheel"></div> | |
| <div class="gear-indicator" id="gear-indicator">N</div> | |
| <div class="menu"> | |
| <button class="menu-button" id="toggle-camera">VUE INTÉRIEURE</button> | |
| <button class="menu-button" id="toggle-lights">PHARES</button> | |
| <button class="menu-button" id="reset-truck">RÉINITIALISER</button> | |
| </div> | |
| <div class="view-buttons"> | |
| <button class="menu-button" id="view-behind">VUE ARRIÈRE</button> | |
| <button class="menu-button" id="view-side">VUE LATÉRALE</button> | |
| <button class="menu-button" id="view-top">VUE AÉRIENNE</button> | |
| <button class="menu-button" id="view-free">CAMÉRA LIBRE</button> | |
| </div> | |
| </div> | |
| <div class="loading-screen" id="loading-screen"> | |
| <h1 class="text-2xl font-bold mb-4">SIMULATEUR DE CAMION - PAYSAGES</h1> | |
| <p class="mb-2">Chargement des ressources...</p> | |
| <div class="progress-bar"> | |
| <div class="progress" id="progress-bar"></div> | |
| </div> | |
| <p class="mt-4 text-sm" id="loading-text">Initialisation du système...</p> | |
| </div> | |
| </div> | |
| <script> | |
| // Variables globales | |
| let scene, camera, renderer, truck, controls; | |
| let speed = 0, rpm = 1000, fuel = 100, time = 0, distance = 0; | |
| let steeringAngle = 0, gear = 'N'; | |
| let lightsOn = false, hornPlaying = false; | |
| let cameraMode = 'interior'; | |
| let currentView = 'free'; // free, behind, side, top | |
| let lastTime = 0; | |
| let loadingProgress = 0; | |
| let assetsLoaded = false; | |
| let clock = new THREE.Clock(); | |
| // Initialisation | |
| function init() { | |
| // Création de la scène | |
| scene = new THREE.Scene(); | |
| scene.fog = new THREE.FogExp2(0xcccccc, 0.002); | |
| // Création de la caméra | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); | |
| camera.position.set(0, 5, -10); | |
| camera.lookAt(0, 2, 10); | |
| // Création du rendu | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
| renderer.toneMapping = THREE.ACESFilmicToneMapping; | |
| renderer.toneMappingExposure = 0.8; | |
| document.getElementById('container').appendChild(renderer.domElement); | |
| // Contrôles de la caméra | |
| controls = new THREE.OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.25; | |
| controls.enableZoom = true; | |
| controls.enablePan = false; | |
| controls.maxPolarAngle = Math.PI * 0.5; | |
| controls.minDistance = 5; | |
| controls.maxDistance = 50; | |
| // Configuration des lumières | |
| setupLights(); | |
| // Création de l'environnement | |
| createEnvironment(); | |
| // Création du camion | |
| createTruck(); | |
| // Configuration des événements | |
| setupEventListeners(); | |
| // Animation | |
| animate(); | |
| } | |
| // Chargement des assets | |
| function loadAssets() { | |
| const loadingStages = [ | |
| "Initialisation du système...", | |
| "Chargement des textures...", | |
| "Création de l'environnement...", | |
| "Finalisation..." | |
| ]; | |
| let currentStage = 0; | |
| document.getElementById('loading-text').textContent = loadingStages[currentStage]; | |
| const loadingInterval = setInterval(() => { | |
| loadingProgress += Math.random() * 3; | |
| if (loadingProgress > (currentStage + 1) * 20) { | |
| currentStage++; | |
| if (currentStage < loadingStages.length) { | |
| document.getElementById('loading-text').textContent = loadingStages[currentStage]; | |
| } | |
| } | |
| if (loadingProgress > 100) loadingProgress = 100; | |
| document.getElementById('progress-bar').style.width = loadingProgress + '%'; | |
| if (loadingProgress >= 100) { | |
| clearInterval(loadingInterval); | |
| setTimeout(() => { | |
| document.getElementById('loading-screen').style.display = 'none'; | |
| assetsLoaded = true; | |
| }, 500); | |
| } | |
| }, 100); | |
| } | |
| // Configuration des lumières | |
| function setupLights() { | |
| // Lumière directionnelle principale | |
| const sunlight = new THREE.DirectionalLight(0xffffff, 1); | |
| sunlight.position.set(100, 100, 50); | |
| sunlight.castShadow = true; | |
| sunlight.shadow.mapSize.width = 2048; | |
| sunlight.shadow.mapSize.height = 2048; | |
| sunlight.shadow.camera.near = 0.5; | |
| sunlight.shadow.camera.far = 500; | |
| sunlight.shadow.camera.left = -100; | |
| sunlight.shadow.camera.right = 100; | |
| sunlight.shadow.camera.top = 100; | |
| sunlight.shadow.camera.bottom = -100; | |
| scene.add(sunlight); | |
| // Lumière ambiante | |
| const ambientLight = new THREE.AmbientLight(0x404040); | |
| scene.add(ambientLight); | |
| // Phares du camion | |
| const headlight1 = new THREE.SpotLight(0xffffff, 1, 100, Math.PI/4, 0.5); | |
| headlight1.position.set(1, 2, 3); | |
| headlight1.castShadow = true; | |
| headlight1.visible = false; | |
| scene.add(headlight1); | |
| const headlight2 = new THREE.SpotLight(0xffffff, 1, 100, Math.PI/4, 0.5); | |
| headlight2.position.set(-1, 2, 3); | |
| headlight2.castShadow = true; | |
| headlight2.visible = false; | |
| scene.add(headlight2); | |
| } | |
| // Création de l'environnement amélioré | |
| function createEnvironment() { | |
| // Sol avec texture haute résolution | |
| const groundGeometry = new THREE.PlaneGeometry(10000, 10000); | |
| const groundMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x3a5f0b, | |
| roughness: 0.8, | |
| metalness: 0.2 | |
| }); | |
| const ground = new THREE.Mesh(groundGeometry, groundMaterial); | |
| ground.rotation.x = -Math.PI / 2; | |
| ground.receiveShadow = true; | |
| scene.add(ground); | |
| // Route principale avec marquages détaillés | |
| const roadGeometry = new THREE.PlaneGeometry(20, 10000); | |
| const roadMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x333333, | |
| roughness: 0.7, | |
| metalness: 0.1 | |
| }); | |
| const road = new THREE.Mesh(roadGeometry, roadMaterial); | |
| road.rotation.x = -Math.PI / 2; | |
| road.position.y = 0.01; | |
| road.receiveShadow = true; | |
| scene.add(road); | |
| // Lignes de la route avec réflexion | |
| const lineGeometry = new THREE.PlaneGeometry(0.5, 5); | |
| const lineMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0xffff00, | |
| emissive: 0x333300, | |
| emissiveIntensity: 0.5, | |
| roughness: 0.1, | |
| metalness: 0.8 | |
| }); | |
| for (let i = -5000; i < 5000; i += 15) { | |
| const line = new THREE.Mesh(lineGeometry, lineMaterial); | |
| line.rotation.x = -Math.PI / 2; | |
| line.position.set(0, 0.02, i); | |
| scene.add(line); | |
| } | |
| // Paysage naturel détaillé | |
| createNaturalFeatures(); | |
| } | |
| // Création des éléments naturels améliorés | |
| function createNaturalFeatures() { | |
| // Arbres variés | |
| const treeTypes = [ | |
| { height: 5, trunkColor: 0x8b4513, leavesColor: 0x3a5f0b }, | |
| { height: 8, trunkColor: 0x654321, leavesColor: 0x2a4b0d }, | |
| { height: 12, trunkColor: 0x5e2605, leavesColor: 0x1e3b0a } | |
| ]; | |
| for (let i = 0; i < 500; i++) { | |
| const type = treeTypes[Math.floor(Math.random() * treeTypes.length)]; | |
| const x = (Math.random() > 0.5 ? 1 : -1) * (30 + Math.random() * 500); | |
| const z = -2000 + Math.random() * 4000; | |
| const tree = new THREE.Group(); | |
| // Tronc | |
| const trunkGeometry = new THREE.CylinderGeometry(0.3, 0.5, type.height * 0.3, 8); | |
| const trunkMaterial = new THREE.MeshStandardMaterial({ color: type.trunkColor }); | |
| const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial); | |
| trunk.position.y = type.height * 0.15; | |
| trunk.castShadow = true; | |
| tree.add(trunk); | |
| // Feuillage | |
| const leavesGeometry = new THREE.SphereGeometry(type.height * 0.4, 16, 16); | |
| const leavesMaterial = new THREE.MeshStandardMaterial({ | |
| color: type.leavesColor, | |
| transparent: true, | |
| opacity: 0.9 | |
| }); | |
| const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial); | |
| leaves.position.y = type.height * 0.3; | |
| leaves.castShadow = true; | |
| tree.add(leaves); | |
| // Position aléatoire | |
| tree.position.set(x, 0, z); | |
| tree.rotation.y = Math.random() * Math.PI; | |
| scene.add(tree); | |
| } | |
| // Montagnes réalistes | |
| const mountainGeometry = new THREE.ConeGeometry(300, 500, 64); | |
| const mountainMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x777777, | |
| roughness: 0.9, | |
| metalness: 0.1 | |
| }); | |
| for (let i = 0; i < 8; i++) { | |
| const x = (Math.random() > 0.5 ? 1 : -1) * (1000 + Math.random() * 2000); | |
| const z = -4000 + Math.random() * 8000; | |
| const mountain = new THREE.Mesh(mountainGeometry, mountainMaterial); | |
| mountain.position.set(x, -100, z); | |
| mountain.rotation.y = Math.random() * Math.PI; | |
| // Détails supplémentaires | |
| const snowGeometry = new THREE.ConeGeometry(250, 100, 64); | |
| const snowMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff }); | |
| const snow = new THREE.Mesh(snowGeometry, snowMaterial); | |
| snow.position.y = 200; | |
| mountain.add(snow); | |
| scene.add(mountain); | |
| } | |
| } | |
| // Création du camion | |
| function createTruck() { | |
| // Groupe principal du camion | |
| truck = new THREE.Group(); | |
| // Cabine | |
| const cabinGeometry = new THREE.BoxGeometry(3, 2.5, 3); | |
| const cabinMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x2a75bb, | |
| roughness: 0.5, | |
| metalness: 0.5 | |
| }); | |
| const cabin = new THREE.Mesh(cabinGeometry, cabinMaterial); | |
| cabin.position.set(0, 2, 0); | |
| cabin.castShadow = true; | |
| cabin.receiveShadow = true; | |
| truck.add(cabin); | |
| // Pare-brise | |
| const windshieldGeometry = new THREE.BoxGeometry(2.8, 1.5, 0.1); | |
| const windshieldMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x7ec0ee, | |
| transparent: true, | |
| opacity: 0.7, | |
| roughness: 0.1, | |
| metalness: 0.9 | |
| }); | |
| const windshield = new THREE.Mesh(windshieldGeometry, windshieldMaterial); | |
| windshield.position.set(0, 2.5, 1.5); | |
| truck.add(windshield); | |
| // Châssis | |
| const chassisGeometry = new THREE.BoxGeometry(4, 1, 8); | |
| const chassisMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x1a1a1a, | |
| roughness: 0.7, | |
| metalness: 0.3 | |
| }); | |
| const chassis = new THREE.Mesh(chassisGeometry, chassisMaterial); | |
| chassis.position.set(0, 1, -2); | |
| chassis.castShadow = true; | |
| chassis.receiveShadow = true; | |
| truck.add(chassis); | |
| // Roues | |
| const wheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.4, 32); | |
| const wheelMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x333333, | |
| roughness: 0.8, | |
| metalness: 0.2 | |
| }); | |
| const wheelTireGeometry = new THREE.TorusGeometry(0.5, 0.2, 16, 32); | |
| const wheelTireMaterial = new THREE.MeshStandardMaterial({ color: 0x111111 }); | |
| // Roues avant | |
| const wheelFL = new THREE.Group(); | |
| const wheelFLBase = new THREE.Mesh(wheelGeometry, wheelMaterial); | |
| wheelFLBase.rotation.z = Math.PI / 2; | |
| wheelFL.add(wheelFLBase); | |
| const wheelFLTire = new THREE.Mesh(wheelTireGeometry, wheelTireMaterial); | |
| wheelFLTire.rotation.x = Math.PI / 2; | |
| wheelFL.add(wheelFLTire); | |
| wheelFL.position.set(1.5, 1, 2); | |
| truck.add(wheelFL); | |
| const wheelFR = new THREE.Group(); | |
| const wheelFRBase = new THREE.Mesh(wheelGeometry, wheelMaterial); | |
| wheelFRBase.rotation.z = Math.PI / 2; | |
| wheelFR.add(wheelFRBase); | |
| const wheelFRTire = new THREE.Mesh(wheelTireGeometry, wheelTireMaterial); | |
| wheelFRTire.rotation.x = Math.PI / 2; | |
| wheelFR.add(wheelFRTire); | |
| wheelFR.position.set(-1.5, 1, 2); | |
| truck.add(wheelFR); | |
| // Roues arrière (double essieu) | |
| for (let i = 0; i < 2; i++) { | |
| const wheelRL = new THREE.Group(); | |
| const wheelRLBase = new THREE.Mesh(wheelGeometry, wheelMaterial); | |
| wheelRLBase.rotation.z = Math.PI / 2; | |
| wheelRL.add(wheelRLBase); | |
| const wheelRLTire = new THREE.Mesh(wheelTireGeometry, wheelTireMaterial); | |
| wheelRLTire.rotation.x = Math.PI / 2; | |
| wheelRL.add(wheelRLTire); | |
| wheelRL.position.set(1.5, 1, -4 + i * 0.8); | |
| truck.add(wheelRL); | |
| const wheelRR = new THREE.Group(); | |
| const wheelRRBase = new THREE.Mesh(wheelGeometry, wheelMaterial); | |
| wheelRRBase.rotation.z = Math.PI / 2; | |
| wheelRR.add(wheelRRBase); | |
| const wheelRRTire = new THREE.Mesh(wheelTireGeometry, wheelTireMaterial); | |
| wheelRRTire.rotation.x = Math.PI / 2; | |
| wheelRR.add(wheelRRTire); | |
| wheelRR.position.set(-1.5, 1, -4 + i * 0.8); | |
| truck.add(wheelRR); | |
| } | |
| // Ajout du camion à la scène | |
| scene.add(truck); | |
| truck.position.set(0, 2, 0); | |
| } | |
| // Configuration des événements | |
| function setupEventListeners() { | |
| // Contrôles clavier | |
| const keyStates = {}; | |
| document.addEventListener('keydown', (event) => { | |
| keyStates[event.key.toLowerCase()] = true; | |
| // Klaxon | |
| if (event.key.toLowerCase() === 'h' && !hornPlaying) { | |
| hornPlaying = true; | |
| setTimeout(() => { hornPlaying = false; }, 1000); | |
| } | |
| // Phares | |
| if (event.key.toLowerCase() === 'f') { | |
| toggleLights(); | |
| } | |
| // Changement de caméra | |
| if (event.key.toLowerCase() === 'c') { | |
| toggleCamera(); | |
| } | |
| // Changement de vitesse | |
| if (event.key >= '1' && event.key <= '6') { | |
| gear = event.key; | |
| document.getElementById('gear-indicator').textContent = gear; | |
| } | |
| }); | |
| document.addEventListener('keyup', (event) => { | |
| keyStates[event.key.toLowerCase()] = false; | |
| }); | |
| // Contrôles UI | |
| document.getElementById('toggle-camera').addEventListener('click', toggleCamera); | |
| document.getElementById('toggle-lights').addEventListener('click', toggleLights); | |
| document.getElementById('reset-truck').addEventListener('click', resetTruck); | |
| // Boutons de vue | |
| document.getElementById('view-behind').addEventListener('click', () => setView('behind')); | |
| document.getElementById('view-side').addEventListener('click', () => setView('side')); | |
| document.getElementById('view-top').addEventListener('click', () => setView('top')); | |
| document.getElementById('view-free').addEventListener('click', () => setView('free')); | |
| // Animation des contrôles | |
| function updateControls(delta) { | |
| // Accélération/freinage | |
| if (keyStates['z'] || keyStates['arrowup']) { | |
| if (speed < getMaxSpeed()) speed += 0.1 * delta * 60; | |
| if (rpm < getMaxRpm()) rpm += 50 * delta * 60; | |
| } else if (keyStates['s'] || keyStates['arrowdown']) { | |
| if (speed > -20) speed -= 0.2 * delta * 60; | |
| if (rpm > 1000) rpm -= 100 * delta * 60; | |
| } else { | |
| // Ralentissement naturel | |
| if (speed > 0) speed = Math.max(0, speed - 0.05 * delta * 60); | |
| if (speed < 0) speed = Math.min(0, speed + 0.05 * delta * 60); | |
| if (rpm > 1000) rpm = Math.max(1000, rpm - 50 * delta * 60); | |
| } | |
| // Direction | |
| if (keyStates['q'] || keyStates['arrowleft']) { | |
| steeringAngle = Math.min(0.5, steeringAngle + 0.02 * delta * 60); | |
| } else if (keyStates['d'] || keyStates['arrowright']) { | |
| steeringAngle = Math.max(-0.5, steeringAngle - 0.02 * delta * 60); | |
| } else { | |
| // Retour au centre | |
| if (steeringAngle > 0) steeringAngle = Math.max(0, steeringAngle - 0.01 * delta * 60); | |
| if (steeringAngle < 0) steeringAngle = Math.min(0, steeringAngle + 0.01 * delta * 60); | |
| } | |
| // Frein | |
| if (keyStates[' ']) { | |
| if (speed > 0) speed = Math.max(0, speed - 0.2 * delta * 60); | |
| if (speed < 0) speed = Math.min(0, speed + 0.2 * delta * 60); | |
| if (rpm > 1000) rpm = Math.max(1000, rpm - 100 * delta * 60); | |
| } | |
| // Rétrograder | |
| if (keyStates['r'] && speed < 5) { | |
| gear = gear === 'R' ? '1' : 'R'; | |
| document.getElementById('gear-indicator').textContent = gear; | |
| } | |
| // Mise à jour de l'UI | |
| document.getElementById('speed').textContent = Math.abs(Math.round(speed)) + ' km/h'; | |
| document.getElementById('rpm').textContent = rpm; | |
| document.getElementById('steering-wheel').style.transform = `translateX(-50%) rotate(${steeringAngle * 60}deg)`; | |
| // Consommation de carburant | |
| if (speed > 0) { | |
| fuel = Math.max(0, fuel - 0.001 * delta * 60); | |
| document.getElementById('fuel').textContent = Math.round(fuel) + '%'; | |
| } | |
| // Mise à jour de la distance | |
| distance += speed * delta * 0.01; | |
| document.getElementById('distance').textContent = Math.round(distance) + ' km'; | |
| // Mise à jour du temps | |
| time += delta; | |
| const minutes = Math.floor(time / 60); | |
| const seconds = Math.floor(time % 60); | |
| document.getElementById('time').textContent = | |
| (minutes < 10 ? '0' + minutes : minutes) + ':' + | |
| (seconds < 10 ? '0' + seconds : seconds); | |
| } | |
| // Vitesse maximale en fonction de la vitesse | |
| function getMaxSpeed() { | |
| switch(gear) { | |
| case '1': return 30; | |
| case '2': return 50; | |
| case '3': return 80; | |
| case '4': return 110; | |
| case '5': return 140; | |
| case '6': return 180; | |
| case 'R': return 20; | |
| default: return 30; | |
| } | |
| } | |
| // RPM maximal en fonction de la vitesse | |
| function getMaxRpm() { | |
| switch(gear) { | |
| case '1': return 4000; | |
| case '2': return 4500; | |
| case '3': return 5000; | |
| case '4': return 5500; | |
| case '5': return 6000; | |
| case '6': return 6500; | |
| case 'R': return 3000; | |
| default: return 4000; | |
| } | |
| } | |
| // Rafraîchissement des contrôles | |
| let lastUpdateTime = 0; | |
| function updateControlsLoop(timestamp) { | |
| const delta = (timestamp - lastUpdateTime) / 1000; | |
| lastUpdateTime = timestamp; | |
| if (delta < 0.1) { // Éviter les gros sauts en cas de perte de focus | |
| updateControls(delta); | |
| } | |
| requestAnimationFrame(updateControlsLoop); | |
| } | |
| requestAnimationFrame(updateControlsLoop); | |
| } | |
| // Basculer les phares | |
| function toggleLights() { | |
| lightsOn = !lightsOn; | |
| scene.children.forEach(child => { | |
| if (child.type === 'SpotLight') { | |
| child.visible = lightsOn; | |
| } | |
| }); | |
| document.getElementById('toggle-lights').textContent = lightsOn ? 'ÉTEINDRE' : 'PHARES'; | |
| } | |
| // Basculer la caméra | |
| function toggleCamera() { | |
| cameraMode = cameraMode === 'interior' ? 'exterior' : 'interior'; | |
| document.getElementById('toggle-camera').textContent = | |
| cameraMode === 'interior' ? 'VUE EXTÉRIEURE' : 'VUE INTÉRIEURE'; | |
| } | |
| // Changer la vue | |
| function setView(view) { | |
| currentView = view; | |
| // Mettre à jour l'état des boutons | |
| document.getElementById('view-behind').classList.toggle('bg-blue-500', view === 'behind'); | |
| document.getElementById('view-side').classList.toggle('bg-blue-500', view === 'side'); | |
| document.getElementById('view-top').classList.toggle('bg-blue-500', view === 'top'); | |
| document.getElementById('view-free').classList.toggle('bg-blue-500', view === 'free'); | |
| } | |
| // Réinitialiser le camion | |
| function resetTruck() { | |
| truck.position.set(0, 2, 0); | |
| truck.rotation.set(0, 0, 0); | |
| speed = 0; | |
| rpm = 1000; | |
| steeringAngle = 0; | |
| gear = 'N'; | |
| distance = 0; | |
| document.getElementById('gear-indicator').textContent = gear; | |
| } | |
| // Animation | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const delta = clock.getDelta(); | |
| if (!assetsLoaded) return; | |
| // Déplacement du camion | |
| if (truck) { | |
| truck.position.z += speed * delta; | |
| truck.rotation.y = -steeringAngle * 0.5; | |
| // Rotation des roues | |
| truck.children.forEach(child => { | |
| if (child.type === 'Group') { // Roues | |
| child.children.forEach(wheel => { | |
| if (wheel.geometry?.type === 'CylinderGeometry') { | |
| wheel.rotation.x += speed * delta; | |
| } | |
| }); | |
| // Direction des roues avant | |
| if (Math.abs(child.position.z - 2) < 0.1) { | |
| child.rotation.y = steeringAngle * 2; | |
| } | |
| } | |
| }); | |
| // Positionnement de la caméra en fonction de la vue | |
| if (cameraMode === 'interior') { | |
| // Vue intérieure | |
| camera.position.set( | |
| truck.position.x + 0.5 * Math.sin(truck.rotation.y), | |
| truck.position.y + 2, | |
| truck.position.z - 5 + 2 * Math.sin(truck.rotation.y) | |
| ); | |
| camera.lookAt( | |
| truck.position.x, | |
| truck.position.y + 1, | |
| truck.position.z + 10 | |
| ); | |
| } else { | |
| // Vue extérieure avec différentes perspectives | |
| switch(currentView) { | |
| case 'behind': | |
| // Vue arrière | |
| camera.position.set( | |
| truck.position.x + 5 * Math.sin(truck.rotation.y), | |
| truck.position.y + 5, | |
| truck.position.z - 15 + 5 * Math.sin(truck.rotation.y) | |
| ); | |
| camera.lookAt(truck.position); | |
| break; | |
| case 'side': | |
| // Vue latérale | |
| camera.position.set( | |
| truck.position.x + 15, | |
| truck.position.y + 5, | |
| truck.position.z | |
| ); | |
| camera.lookAt(truck.position); | |
| break; | |
| case 'top': | |
| // Vue aérienne | |
| camera.position.set( | |
| truck.position.x, | |
| truck.position.y + 25, | |
| truck.position.z - 10 | |
| ); | |
| camera.lookAt(truck.position); | |
| break; | |
| case 'free': | |
| default: | |
| // Caméra libre (contrôlée par OrbitControls) | |
| break; | |
| } | |
| } | |
| } | |
| // Mise à jour des contrôles | |
| controls.update(); | |
| // Rendu | |
| renderer.render(scene, camera); | |
| } | |
| // Redimensionnement | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| // Démarrer l'application | |
| init(); | |
| loadAssets(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=docto41/globe-future" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |