diff --git a/src/RouteManager.js b/src/RouteManager.js index 4c71ce9..bb840b9 100644 --- a/src/RouteManager.js +++ b/src/RouteManager.js @@ -19,6 +19,13 @@ export class RouteManager { this.onRouteChanged = null; this.gameManager = null; + + this.vehicleSystem = null; + this.latestPathPoints = []; + } + + setVehicleSystem(vs) { + this.vehicleSystem = vs; } setGameManager(gm) { @@ -127,6 +134,11 @@ export class RouteManager { // 2. Pay this.gameManager.deductFunds(cost); + // Spawn bus + if (this.vehicleSystem && this.latestPathPoints.length > 0) { + this.vehicleSystem.addBusToRoute(this.latestPathPoints); + } + // 3. Freeze & Save this.currentPathMesh.material.color.setHex(0x10B981); @@ -222,6 +234,8 @@ export class RouteManager { }); } + this.latestPathPoints = fullPathPoints; + // Rebuild Mesh if (this.currentPathMesh) { this.scene.remove(this.currentPathMesh); diff --git a/src/VehicleSystem.js b/src/VehicleSystem.js new file mode 100644 index 0000000..ddebee3 --- /dev/null +++ b/src/VehicleSystem.js @@ -0,0 +1,89 @@ +import * as THREE from 'three'; + +export class VehicleSystem { + constructor(scene) { + this.scene = scene; + this.buses = []; // { mesh, points, dists, totalLen, currentDist, speed, direction } + + this.busGeom = new THREE.BoxGeometry(3.5, 4.0, 10.0); + this.busGeom.translate(0, 3.5, 0); + + this.busMat = new THREE.MeshStandardMaterial({ + color: 0xF59E0B, // Amber body + emissive: 0xB45309, // Slight orange glow so they don't get lost in shadow + emissiveIntensity: 0.4, + roughness: 0.2 + }); + } + + addBusToRoute(routePathPoints) { + if (!routePathPoints || routePathPoints.length < 2) return; + + // Clone points to ensure they aren't affected by outside changes + const points = routePathPoints.map(p => p.clone()); + + const mesh = new THREE.Mesh(this.busGeom, this.busMat); + mesh.position.copy(points[0]); + mesh.castShadow = true; + this.scene.add(mesh); + + // Pre-calculate cumulative distances for smooth interpolation + let totalLen = 0; + const dists = [0]; + for (let i = 0; i < points.length - 1; i++) { + const d = points[i].distanceTo(points[i + 1]); + totalLen += d; + dists.push(totalLen); + } + + this.buses.push({ + mesh: mesh, + points: points, + dists: dists, + totalLen: totalLen, + currentDist: 0, + speed: 40, // Speed in units/sec + direction: 1 // 1 = Forward, -1 = Backward + }); + } + + update(deltaTime) { + this.buses.forEach(bus => { + // 1. Move + bus.currentDist += bus.speed * deltaTime * bus.direction; + + // 2. Check Bounds & Reversal + if (bus.currentDist >= bus.totalLen) { + bus.currentDist = bus.totalLen; + bus.direction = -1; + } else if (bus.currentDist <= 0) { + bus.currentDist = 0; + bus.direction = 1; + } + + // 3. Find current segment index + let i = 0; + // Simple linear search (efficient enough for small N points) + while (i < bus.dists.length - 2 && bus.currentDist > bus.dists[i + 1]) { + i++; + } + + // 4. Interpolate Position + const startDist = bus.dists[i]; + const endDist = bus.dists[i + 1]; + const segmentLen = endDist - startDist; + + // Avoid divide by zero if 2 points are identical + const alpha = segmentLen > 0.0001 ? (bus.currentDist - startDist) / segmentLen : 0; + + const pStart = bus.points[i]; + const pEnd = bus.points[i + 1]; + + bus.mesh.position.lerpVectors(pStart, pEnd, alpha); + + // 5. Rotation (Look at target) + const lookTarget = bus.direction === 1 ? pEnd : pStart; + bus.mesh.lookAt(lookTarget); + }); + } +} diff --git a/src/main.js b/src/main.js index bf81a02..4d60935 100644 --- a/src/main.js +++ b/src/main.js @@ -6,7 +6,7 @@ import { InputManager } from './InputManager.js'; import { RouteManager } from './RouteManager.js'; import { UIManager } from './UIManager.js'; import { GameManager } from './GameManager.js'; - +import { VehicleSystem } from './VehicleSystem.js'; // ========================================== // 1. Configuration @@ -32,7 +32,9 @@ const SETTINGS = { }; let scene, camera, renderer, controls; -let inputManager, routeManager, uiManager, gameManager; +let inputManager, routeManager, uiManager, gameManager, vehicleSystem; + +const clock = new THREE.Clock(); // Added Clock function init() { setupScene(); @@ -45,6 +47,11 @@ function init() { gameManager = new GameManager(routeManager, uiManager); routeManager.setGameManager(gameManager); // Dependency Injection + // Vehicle System + vehicleSystem = new VehicleSystem(scene); + routeManager.setVehicleSystem(vehicleSystem); // Inject into RouteManager + + gameManager.start(); // Start the loop // 3. Input @@ -268,7 +275,13 @@ function createBuildingLayer(buildings) { function animate() { requestAnimationFrame(animate); + const delta = clock.getDelta(); // Get time since last frame + controls.update(); + if (vehicleSystem) { + vehicleSystem.update(delta); + } + renderer.render(scene, camera); }