Back to Pursuit PIT
SOURCE / PINNED RELEASE

Made of little things.

Pursuit PIT

Release
565f4981bdc3…
Author-recorded commit
197afdbed44b…
License
LICENSE
Author’s source reference
nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-951414e7fd7

Archive hash verified: 3b0485589942fd5c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/game/bend.ts
// Bends the flat, straight road-space scene into the winding, rolling highway on the GPU.
// Everything is modelled as before (x across, y up, z = -(s - originS)); the vertex shader
// then looks up the road frame at that s and moves the vertex onto it. Cars, scenery, ramps
// and effects all follow bends and hills without knowing about them.
import * as THREE from 'three';
import type { RoadFrames } from './road.js';

const GLSL = /* glsl */ `
uniform sampler2D roadFrames;
uniform vec3 roadInfo; // first sample's s relative to the origin, step, count
vec3 bendRoad(vec3 p) {
  float u = (-p.z - roadInfo.x) / roadInfo.y;
  float uc = clamp(u, 0.0, roadInfo.z - 1.001);
  float i = floor(uc);
  vec4 a = texelFetch(roadFrames, ivec2(int(i), 0), 0);
  vec4 b = texelFetch(roadFrames, ivec2(int(i) + 1, 0), 0);
  vec4 fr = mix(a, b, uc - i);
  float ext = (u - uc) * roadInfo.y;
  float s = sin(fr.z), c = cos(fr.z);
  return vec3(fr.x + s * ext + c * p.x, fr.w + p.y, fr.y - c * ext + s * p.x);
}
`;

const PROJECT = /* glsl */ `
vec4 mvPosition = vec4(transformed, 1.0);
#ifdef USE_INSTANCING
  mvPosition = instanceMatrix * mvPosition;
#endif
mvPosition = viewMatrix * vec4(bendRoad((modelMatrix * mvPosition).xyz), 1.0);
gl_Position = projectionMatrix * mvPosition;
`;

export class Bender {
  private texData: Float32Array;
  private tex: THREE.DataTexture;
  private uniforms: { roadFrames: { value: THREE.DataTexture }; roadInfo: { value: THREE.Vector3 } };

  constructor(private frames: RoadFrames) {
    this.texData = new Float32Array(frames.count * 4);
    this.tex = new THREE.DataTexture(this.texData, frames.count, 1, THREE.RGBAFormat, THREE.FloatType);
    this.tex.minFilter = this.tex.magFilter = THREE.NearestFilter;
    this.uniforms = {
      roadFrames: { value: this.tex },
      roadInfo: { value: new THREE.Vector3(frames.from - frames.origin, frames.step, frames.count) },
    };
  }

  /** Upload the frames after RoadFrames.update. */
  sync(): void {
    this.texData.set(this.frames.data);
    this.tex.needsUpdate = true;
    this.uniforms.roadInfo.value.set(this.frames.from - this.frames.origin, this.frames.step, this.frames.count);
  }

  /**
   * Make every material in the scene bend, and stop three culling by unbent bounds.
   * Cheap enough per frame; it also catches meshes added later (cars, effects).
   */
  patch(scene: THREE.Scene): void {
    scene.traverse((o) => {
      const mesh = o as THREE.Mesh;
      if (!mesh.isMesh || o.userData.flat) return;
      o.frustumCulled = false;
      const m = mesh.material as THREE.Material;
      if (m.userData.bent) return;
      m.userData.bent = true;
      const uniforms = this.uniforms;
      m.onBeforeCompile = (shader) => {
        Object.assign(shader.uniforms, uniforms);
        shader.vertexShader = shader.vertexShader
          .replace('#include <common>', '#include <common>\n' + GLSL)
          .replace('#include <project_vertex>', PROJECT);
      };
      m.customProgramCacheKey = () => 'bend';
      m.needsUpdate = true;
    });
  }
}