|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>3D Car Simulator</title> |
|
<style> |
|
body { margin: 0; overflow: hidden; } |
|
canvas { display: block; } |
|
</style> |
|
</head> |
|
<body> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script> |
|
<script> |
|
|
|
const scene = new THREE.Scene(); |
|
scene.background = new THREE.Color(0x87CEEB); |
|
|
|
|
|
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
camera.position.set(0, 10, 20); |
|
|
|
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true }); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
renderer.shadowMap.enabled = true; |
|
document.body.appendChild(renderer.domElement); |
|
|
|
|
|
const controls = new THREE.OrbitControls(camera, renderer.domElement); |
|
controls.enableDamping = true; |
|
controls.dampingFactor = 0.05; |
|
|
|
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); |
|
scene.add(ambientLight); |
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); |
|
directionalLight.position.set(50, 100, 50); |
|
directionalLight.castShadow = true; |
|
directionalLight.shadow.mapSize.width = 2048; |
|
directionalLight.shadow.mapSize.height = 2048; |
|
scene.add(directionalLight); |
|
|
|
|
|
const groundGeometry = new THREE.PlaneGeometry(500, 500); |
|
const groundMaterial = new THREE.MeshStandardMaterial({ |
|
color: 0x4CAF50, |
|
roughness: 0.8 |
|
}); |
|
const ground = new THREE.Mesh(groundGeometry, groundMaterial); |
|
ground.rotation.x = -Math.PI / 2; |
|
ground.receiveShadow = true; |
|
scene.add(ground); |
|
|
|
|
|
const roadGeometry = new THREE.PlaneGeometry(15, 500); |
|
const roadMaterial = new THREE.MeshStandardMaterial({ |
|
color: 0x333333, |
|
roughness: 0.9 |
|
}); |
|
const road = new THREE.Mesh(roadGeometry, roadMaterial); |
|
road.rotation.x = -Math.PI / 2; |
|
road.position.y = 0.01; |
|
road.receiveShadow = true; |
|
scene.add(road); |
|
|
|
|
|
const carGroup = new THREE.Group(); |
|
scene.add(carGroup); |
|
|
|
const carBodyGeometry = new THREE.BoxGeometry(5, 1, 2); |
|
const carBodyMaterial = new THREE.MeshStandardMaterial({ color: 0xFF5722 }); |
|
const carBody = new THREE.Mesh(carBodyGeometry, carBodyMaterial); |
|
carBody.position.y = 1; |
|
carBody.castShadow = true; |
|
carGroup.add(carBody); |
|
|
|
const carTopGeometry = new THREE.BoxGeometry(3, 1, 2); |
|
const carTopMaterial = new THREE.MeshStandardMaterial({ color: 0xE91E63 }); |
|
const carTop = new THREE.Mesh(carTopGeometry, carTopMaterial); |
|
carTop.position.y = 2; |
|
carTop.castShadow = true; |
|
carGroup.add(carTop); |
|
|
|
function addWheel(x, z) { |
|
const wheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.4, 32); |
|
const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x212121 }); |
|
const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial); |
|
wheel.rotation.z = Math.PI / 2; |
|
wheel.position.set(x, 0.5, z); |
|
wheel.castShadow = true; |
|
carGroup.add(wheel); |
|
} |
|
|
|
addWheel(-2, -1); |
|
addWheel(-2, 1); |
|
addWheel(2, -1); |
|
addWheel(2, 1); |
|
|
|
|
|
function createMountain() { |
|
const mountainGroup = new THREE.Group(); |
|
const height = 10 + Math.random() * 20; |
|
const width = 20 + Math.random() * 30; |
|
const depth = 20 + Math.random() * 30; |
|
|
|
const mountainGeometry = new THREE.ConeGeometry(width, height, 32); |
|
const mountainMaterial = new THREE.MeshStandardMaterial({ color: 0x795548 }); |
|
const mountain = new THREE.Mesh(mountainGeometry, mountainMaterial); |
|
mountain.rotation.x = -Math.PI / 2; |
|
mountain.position.y = height / 2; |
|
mountain.castShadow = true; |
|
mountainGroup.add(mountain); |
|
|
|
const positionX = -250 + Math.random() * 500; |
|
const positionZ = -250 + Math.random() * 500; |
|
mountainGroup.position.set(positionX, 0, positionZ); |
|
|
|
return mountainGroup; |
|
} |
|
|
|
for (let i = 0; i < 15; i++) { |
|
scene.add(createMountain()); |
|
} |
|
|
|
|
|
function createTree() { |
|
const treeGroup = new THREE.Group(); |
|
|
|
const trunkGeometry = new THREE.CylinderGeometry(0.3, 0.5, 5); |
|
const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x5D4037 }); |
|
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial); |
|
trunk.position.y = 2.5; |
|
trunk.castShadow = true; |
|
treeGroup.add(trunk); |
|
|
|
const leavesGeometry = new THREE.ConeGeometry(2, 4, 8); |
|
const leavesMaterial = new THREE.MeshStandardMaterial({ color: 0x2E7D32 }); |
|
const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial); |
|
leaves.position.y = 6; |
|
leaves.castShadow = true; |
|
treeGroup.add(leaves); |
|
|
|
const positionX = -200 + Math.random() * 400; |
|
const positionZ = -200 + Math.random() * 400; |
|
treeGroup.position.set(positionX, 0, positionZ); |
|
|
|
return treeGroup; |
|
} |
|
|
|
for (let i = 0; i < 30; i++) { |
|
scene.add(createTree()); |
|
} |
|
|
|
|
|
function createCloud() { |
|
const cloudGroup = new THREE.Group(); |
|
|
|
for (let i = 0; i < 3; i++) { |
|
const cloudGeometry = new THREE.SphereGeometry(2.5 + Math.random() * 2, 16, 16); |
|
const cloudMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff }); |
|
const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial); |
|
cloud.position.set( |
|
-3 + Math.random() * 6, |
|
-2 + Math.random() * 4, |
|
-3 + Math.random() * 6 |
|
); |
|
cloud.castShadow = true; |
|
cloudGroup.add(cloud); |
|
} |
|
|
|
const positionX = -200 + Math.random() * 400; |
|
const positionZ = -200 + Math.random() * 400; |
|
const positionY = 20 + Math.random() * 30; |
|
cloudGroup.position.set(positionX, positionY, positionZ); |
|
|
|
return cloudGroup; |
|
} |
|
|
|
for (let i = 0; i < 10; i++) { |
|
scene.add(createCloud()); |
|
} |
|
|
|
|
|
const trainGroup = new THREE.Group(); |
|
scene.add(trainGroup); |
|
|
|
function createTrainCar() { |
|
const carGroup = new THREE.Group(); |
|
|
|
const carGeometry = new THREE.BoxGeometry(5, 3, 8); |
|
const carMaterial = new THREE.MeshStandardMaterial({ color: 0x2196F3 }); |
|
const car = new THREE.Mesh(carGeometry, carMaterial); |
|
car.position.y = 1.5; |
|
car.castShadow = true; |
|
carGroup.add(car); |
|
|
|
const wheelGeometry = new THREE.CylinderGeometry(0.8, 0.8, 1, 32); |
|
const wheelMaterial = new THREE.MeshStandardMaterial({ color: 0x212121 }); |
|
|
|
const wheelFL = new THREE.Mesh(wheelGeometry, wheelMaterial); |
|
wheelFL.rotation.z = Math.PI / 2; |
|
wheelFL.position.set(-2.5, 0.8, 3); |
|
wheelFL.castShadow = true; |
|
carGroup.add(wheelFL); |
|
|
|
const wheelFR = new THREE.Mesh(wheelGeometry, wheelMaterial); |
|
wheelFR.rotation.z = Math.PI / 2; |
|
wheelFR.position.set(2.5, 0.8, 3); |
|
wheelFR.castShadow = true; |
|
carGroup.add(wheelFR); |
|
|
|
const wheelBL = new THREE.Mesh(wheelGeometry, wheelMaterial); |
|
wheelBL.rotation.z = Math.PI / 2; |
|
wheelBL.position.set(-2.5, 0.8, -3); |
|
wheelBL.castShadow = true; |
|
carGroup.add(wheelBL); |
|
|
|
const wheelBR = new THREE.Mesh(wheelGeometry, wheelMaterial); |
|
wheelBR.rotation.z = Math.PI / 2; |
|
wheelBR.position.set(2.5, 0.8, -3); |
|
wheelBR.castShadow = true; |
|
carGroup.add(wheelBR); |
|
|
|
return carGroup; |
|
} |
|
|
|
|
|
const trackRadius = 100; |
|
for (let i = 0; i < 64; i++) { |
|
const angle = (i / 64) * Math.PI * 2; |
|
const x = Math.cos(angle) * trackRadius; |
|
const z = Math.sin(angle) * trackRadius; |
|
|
|
const trackGeometry = new THREE.BoxGeometry(5, 0.2, 1); |
|
const trackMaterial = new THREE.MeshStandardMaterial({ color: 0x212121 }); |
|
const track = new THREE.Mesh(trackGeometry, trackMaterial); |
|
track.position.set(x, 0.02, z); |
|
track.rotation.y = angle; |
|
track.receiveShadow = true; |
|
scene.add(track); |
|
|
|
if (i % 8 === 0) { |
|
const sleeperGeometry = new THREE.BoxGeometry(0.5, 0.5, 3); |
|
const sleeperMaterial = new THREE.MeshStandardMaterial({ color: 0x795548 }); |
|
const sleeper = new THREE.Mesh(sleeperGeometry, sleeperMaterial); |
|
sleeper.position.set(x, 0.02, z); |
|
sleeper.receiveShadow = true; |
|
scene.add(sleeper); |
|
} |
|
} |
|
|
|
|
|
for (let i = 0; i < 4; i++) { |
|
const trainCar = createTrainCar(); |
|
trainCar.position.z = -i * 8; |
|
trainGroup.add(trainCar); |
|
} |
|
|
|
|
|
const keys = {}; |
|
document.addEventListener('keydown', (e) => { keys[e.code] = true; }); |
|
document.addEventListener('keyup', (e) => { keys[e.code] = false; }); |
|
|
|
|
|
let trainAngle = 0; |
|
|
|
function animate() { |
|
requestAnimationFrame(animate); |
|
|
|
|
|
if (keys['ArrowUp']) { |
|
carGroup.position.z -= 0.2; |
|
} |
|
if (keys['ArrowDown']) { |
|
carGroup.position.z += 0.2; |
|
} |
|
if (keys['ArrowLeft']) { |
|
carGroup.rotation.y += 0.05; |
|
} |
|
if (keys['ArrowRight']) { |
|
carGroup.rotation.y -= 0.05; |
|
} |
|
|
|
|
|
trainAngle += 0.005; |
|
trainGroup.position.x = Math.cos(trainAngle) * trackRadius; |
|
trainGroup.position.z = Math.sin(trainAngle) * trackRadius; |
|
trainGroup.rotation.y = -trainAngle + Math.PI / 2; |
|
|
|
|
|
const carDirection = new THREE.Vector3(); |
|
carGroup.getWorldDirection(carDirection); |
|
carDirection.multiplyScalar(-10); |
|
|
|
const carPosition = carGroup.position.clone(); |
|
carPosition.y += 5; |
|
|
|
camera.position.lerp(carPosition.clone().add(carDirection), 0.1); |
|
camera.lookAt(carPosition); |
|
|
|
controls.update(); |
|
renderer.render(scene, camera); |
|
} |
|
|
|
|
|
window.addEventListener('resize', () => { |
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
camera.updateProjectionMatrix(); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
}); |
|
|
|
animate(); |
|
</script> |
|
</body> |
|
</html> |