fix(scene): keep traffic lamps out of the hero car's near field

This commit is contained in:
2026-10-10 22:02:39 +03:00
parent 48a3413ebc
commit 298da54871
3 changed files with 194 additions and 10 deletions

View File

@@ -9,9 +9,36 @@ import type { RenderQualitySettings } from '@/scene/render-quality';
* resting lets the faster lanes pull away. Instances wrap through a hidden band
* behind the camera, so nothing pops in view.
*/
const NEAR_Z = 46;
/**
* The hero car renders at 2.78x scale: its body spans |x| < 2.6 and its nose
* reaches z = -5.5. Traffic never enters that envelope or the near field
* around it. Lamps fade out over TRAFFIC_FADE_LENGTH before TRAFFIC_NEAR_Z and
* wrap inside the hidden band, so no light ever slides past the car (round 5:
* bodiless lamp pairs passing the flank read as the car's own taillights
* coming loose).
*/
export const HERO_CAR_HALF_WIDTH = 2.6;
export const TRAFFIC_NEAR_Z = -24;
export const TRAFFIC_FADE_LENGTH = 22;
const FAR_Z = -660;
const SPAN = NEAR_Z - FAR_Z;
const SPAN = TRAFFIC_NEAR_Z - FAR_Z;
/** Lane centres. Fast lanes on the left, slower trucks on the right. */
export const TRAFFIC_LANES = [-5.7, -5.7, -4.3, 5.4, 5.7] as const;
export const TRAFFIC_LAMP_HALF_SPACING = { fast: 0.9, slow: 1.0 } as const;
/** Visibility of a lamp, trail or pool at world z (1 = fully lit). */
export function trafficNearFade(z: number): number {
const t = Math.min(1, Math.max(0, (TRAFFIC_NEAR_Z - z) / TRAFFIC_FADE_LENGTH));
return t * t * (3 - 2 * t);
}
/** Keeps a vehicle inside [FAR_Z, TRAFFIC_NEAR_Z], wrapping through the
* hidden band at either end. */
export function wrapTrafficZ(z: number): number {
if (z > TRAFFIC_NEAR_Z) return z - SPAN * Math.ceil((z - TRAFFIC_NEAR_Z) / SPAN);
if (z < FAR_Z) return z + SPAN * Math.ceil((FAR_Z - z) / SPAN);
return z;
}
interface Vehicle {
x: number;
@@ -29,7 +56,7 @@ export function createTraffic(
const group = new THREE.Group();
group.name = 'expressway-traffic';
const count = quality.postEffects ? (quality.postScale >= 1 ? 14 : 10) : 7;
const lanes = [-5.1, -5.1, -2.6, 4.9, 5.2];
const lanes = TRAFFIC_LANES;
const vehicles: Vehicle[] = [];
let seed = 7;
const random = () => {
@@ -42,9 +69,9 @@ export function createTraffic(
const fast = lane < 0;
vehicles.push({
x: lane + (random() - 0.5) * 0.5,
z: FAR_Z + random() * (SPAN - 120) + 20,
z: FAR_Z + 20 + random() * (SPAN - 40),
speed: fast ? 26 + random() * 22 : 9 + random() * 6,
width: fast ? 0.78 + random() * 0.12 : 1.0,
width: fast ? TRAFFIC_LAMP_HALF_SPACING.fast : TRAFFIC_LAMP_HALF_SPACING.slow,
height: fast ? 0.95 : 1.55 + random() * 0.4,
tint: random(),
});
@@ -56,10 +83,14 @@ export function createTraffic(
varying vec2 vUv;
varying float vDepth;
varying float vLevel;
varying float vNear;
void main() {
vUv = uv;
vLevel = aLevel;
vec4 view = modelViewMatrix * instanceMatrix * vec4(position, 1.0);
vec4 world = modelMatrix * instanceMatrix * vec4(position, 1.0);
// Matches trafficNearFade(): dark before the hero car's near field.
vNear = smoothstep(0.0, 1.0, clamp((${TRAFFIC_NEAR_Z.toFixed(1)} - world.z) / ${TRAFFIC_FADE_LENGTH.toFixed(1)}, 0.0, 1.0));
vec4 view = viewMatrix * world;
vDepth = -view.z;
gl_Position = projectionMatrix * view;
}`;
@@ -67,9 +98,10 @@ export function createTraffic(
varying vec2 vUv;
varying float vDepth;
varying float vLevel;
varying float vNear;
void main() {
float far = 1.0 - smoothstep(420.0, 660.0, vDepth);
float near = smoothstep(1.5, 7.0, vDepth);
float near = vNear;
${shape}
vec3 color = mix(vec3(1.0, 0.03, 0.06), vec3(1.0, 0.22, 0.05), vLevel);
gl_FragColor = vec4(color * glow * far * near, 1.0);
@@ -135,9 +167,7 @@ export function createTraffic(
for (let i = 0; i < count; i++) {
const v = vehicles[i]!;
const relative = travelSpeed - v.speed; // > 0: we are overtaking
v.z += relative * dt;
if (v.z > NEAR_Z) v.z -= SPAN;
else if (v.z < FAR_Z) v.z += SPAN;
v.z = wrapTrafficZ(v.z + relative * dt);
const trailLength = THREE.MathUtils.clamp(Math.abs(relative) * 0.16, 0.3, 14);
// Trails stream away from the direction of relative travel.
const trailDirection = relative >= 0 ? -1 : 1;