<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ride</title>
<style>
body { margin: 0; overflow: hidden; background-color: #fff; }
canvas { display: block; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- CONFIGURATION ---
const MODELS_FOLDER = './glb/';
const MODEL_FILES = ['dirty.glb', 'truck.glb'];
const TARGET_WIDTH = 3;
const WORLD_SPEED = 0.6;
const LANE_WIDTH = 8;
// --- SCENE SETUP ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(15, 10, 25);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// STUDIO LIGHTING
scene.add(new THREE.AmbientLight(0xffffff, 1.0));
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.5);
hemiLight.position.set(0, 20, 0);
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(10, 20, 10);
scene.add(dirLight);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
const clock = new THREE.Clock();
// --- THE INFINITE ROAD ---
const roadGeo = new THREE.PlaneGeometry(40, 1000);
const roadMat = new THREE.MeshStandardMaterial({ color: 0xdddddd });
const road = new THREE.Mesh(roadGeo, roadMat);
road.rotation.x = -Math.PI / 2;
scene.add(road);
// FLAT ROAD MARKERS (No Height)
const markers = [];
for(let i = 0; i < 40; i++) {
// Use PlaneGeometry for zero height (like paint)
const markerGeo = new THREE.PlaneGeometry(0.4, 2);
const markerMat = new THREE.MeshStandardMaterial({
color: 0xbbbbbb,
side: THREE.DoubleSide
});
const marker = new THREE.Mesh(markerGeo, markerMat);
// Rotate to lay flat on road
marker.rotation.x = -Math.PI / 2;
// Position slightly above 0 (0.01) to prevent flickering (Z-fighting)
marker.position.set(0, 0.01, (i * 10) - 200);
scene.add(marker);
markers.push(marker);
}
// LOADER SETUP
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
loader.setDRACOLoader(dracoLoader);
const allActiveCars = [];
const mixers = [];
function normalizeModelSize(model) {
const box = new THREE.Box3().setFromObject(model);
const size = new THREE.Vector3();
box.getSize(size);
const scaleFactor = TARGET_WIDTH / size.x;
model.scale.set(scaleFactor, scaleFactor, scaleFactor);
const center = new THREE.Vector3();
box.getCenter(center);
model.position.x -= center.x * scaleFactor;
model.position.y -= center.y * scaleFactor;
model.position.z -= center.z * scaleFactor;
}
async function loadAndStream() {
try {
for (let i = 0; i < MODEL_FILES.length; i++) {
const gltf = await loader.loadAsync(MODELS_FOLDER + MODEL_FILES[i]);
const model = gltf.scene;
normalizeModelSize(model);
const xPos = (i * LANE_WIDTH) - ((MODEL_FILES.length - 1) * LANE_WIDTH / 2);
model.position.set(xPos, 0, 0);
model.rotation.y = 0;
scene.add(model);
allActiveCars.push(model);
if (gltf.animations?.length > 0) {
const mixer = new THREE.AnimationMixer(model);
mixer.clipAction(gltf.animations[0]).play();
mixers.push(mixer);
}
}
} catch (err) { console.error("Load Error:", err); }
}
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixers.forEach(m => m.update(delta));
// MOVE ROAD MARKERS (Creates the illusion of driving)
markers.forEach(marker => {
marker.position.z -= WORLD_SPEED;
if (marker.position.z < -100) {
marker.position.z = 100;
}
});
controls.update();
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
loadAndStream();
animate();
</script>
</body>
</html>Expand
glb – https://sketchfab.com/search?features=downloadable+animated&q=car&type=models