import * as THREE from 'three'; import type { Base } from '../sim/bases'; /** * Bases and the active objective, as things you can see from a distance. * The player navigates by looking, so both need to be visible over scenery. */ const BEACON_HEIGHT = 34; /** * A slim pillar plus a ring on the ground. * * The first version was a wide translucent cylinder, which was legible from a * distance and a solid green wall from inside — and since the car parks in the * middle of one, that is exactly where you spend your time. Narrow enough to see * past, tall enough to see over buildings. */ function beacon(colour: number, radius: number): THREE.Group { const group = new THREE.Group(); const material = new THREE.MeshBasicMaterial({ color: colour, transparent: true, opacity: 0.3, side: THREE.DoubleSide, depthWrite: false, // Additive keeps it reading as light rather than as a pane of glass. blending: THREE.AdditiveBlending, }); const pillar = new THREE.Mesh( new THREE.CylinderGeometry(0.9, 0.9, BEACON_HEIGHT, 8, 1, true), material, ); pillar.position.y = BEACON_HEIGHT / 2; group.add(pillar); // The ring is what tells you where to actually stop. const ring = new THREE.Mesh( new THREE.RingGeometry(radius - 0.7, radius, 32).rotateX(-Math.PI / 2), material, ); ring.position.y = 0.08; group.add(ring); return group; } export function createMarkers(scene: THREE.Scene, bases: Base[]) { for (const base of bases) { const hut = new THREE.Mesh( new THREE.BoxGeometry(7, 3.4, 7), new THREE.MeshStandardMaterial({ color: 0x3f5a46, roughness: 0.85 }), ); // The building sits beside the junction, not on it — on it, it blocks the // road, and at the starting base it sits directly over the car. hut.position.set(base.hutX, 1.7, base.hutZ); hut.castShadow = true; hut.receiveShadow = true; scene.add(hut); // The beacon stays over the junction, since that is where you park. const light = beacon(0x5fd08a, 9); light.position.set(base.x, 0, base.z); scene.add(light); } const objective = beacon(0xffc247, 11); objective.visible = false; scene.add(objective); return { /** Point the objective beacon at a target, or hide it when idle. */ setObjective(target: { x: number; z: number } | null) { objective.visible = target !== null; if (target) objective.position.set(target.x, 0, target.z); }, /** Slow spin, so a beacon reads as a marker rather than scenery. */ update(elapsed: number) { objective.rotation.y = elapsed * 0.6; }, }; }