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> |