Back to Supersonic RC Revive
SOURCE / PINNED RELEASE

Made of little things.

Supersonic RC Revive

Release
1ba42f1ca1d6…
Author-recorded commit
baecad10b1cd…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.napplet.soy%2F/n-143146b0d6f

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

src/game/lingo/drivingManager.ts
// Port of "ParentScript 2 - drivingManager": owns the physics world, the car
// and the camera, and advances them with the original's frame chunking.
import { Matrix4, Quaternion, Vector3 } from 'three';
import type { Input } from '../input.js';
import { findNode, type LevelData, nodeMatrix, START_NODE, staticBodies } from '../level/data.js';
import { RayWorld } from '../physics/rayWorld.js';
import type { HavokOptions } from '../physics/havok.js';
import { ChaseCamera } from './camera.js';
import { Car, type CarStart } from './car.js';
import { HavokManager } from './havokManager.js';

/** The original movie's tempo (DRCF frameRate = 40). */
export const ORIGINAL_FRAME_MS = 1000 / 40;

export interface HavokSubscriber {
  havokUpdate(tDif: number): void;
}

/** The level's start marker (custom levels); the original's level has none. */
export function carStart(level: LevelData): CarStart | undefined {
  const node = findNode(level.markers.level, START_NODE);
  if (!node) return undefined;
  const position = new Vector3();
  const quaternion = new Quaternion();
  nodeMatrix(node.matrix).decompose(position, quaternion, new Vector3());
  return { position, quaternion };
}

export class DrivingManager {
  readonly rays: RayWorld;
  readonly havokManager: HavokManager;
  readonly players: Array<{ car: Car; camera: ChaseCamera }> = [];
  get car(): Car { return this.players[0].car; }
  get camera(): ChaseCamera { return this.players[0].camera; }
  private readonly subscribers: HavokSubscriber[] = [];
  pRunning = 1;
  /** Milliseconds of game time, the stand-in for "the milliSeconds". */
  milliseconds = 0;

  constructor(private readonly level: LevelData, input: Input, havokOverrides: Partial<HavokOptions> = {}, initialPlayer = true) {
    const { rayMesh, markers, collision } = level;
    this.rays = new RayWorld(rayMesh.positions, rayMesh.indices, rayMesh.owner, markers.raymesh_models);
    const g = collision.gravity;
    this.havokManager = new HavokManager(staticBodies(collision), new Vector3(g[0], g[1], g[2]), collision.scale, havokOverrides);
    if (initialPlayer) this.addPlayer(input);
  }

  addPlayer(input: Input, start = carStart(this.level), name = 'car'): { car: Car; camera: ChaseCamera } {
    const markers = this.level.markers;
    const hull = new Float32Array(markers.car_hull.positions.flat());
    const hullNode: Matrix4 = nodeMatrix(markers.car_hull.node_matrix);
    const car = new Car(this.havokManager, this.rays, input, hull, hullNode, start, name);
    const camera = new ChaseCamera(car, this.rays, () => this.milliseconds);
    const player = { car, camera };
    this.players.push(player);
    return player;
  }

  removePlayer(player: { car: Car; camera: ChaseCamera }): void {
    const i = this.players.indexOf(player);
    if (i < 0) return;
    this.players.splice(i, 1);
    this.havokManager.removeBody(player.car.rb);
  }

  registerForUpdate(subscriber: HavokSubscriber): void {
    this.subscribers.push(subscriber);
  }

  unregisterForUpdate(subscriber: HavokSubscriber): void {
    const i = this.subscribers.indexOf(subscriber);
    if (i >= 0) this.subscribers.splice(i, 1);
  }

  /** stepFrame: called once per Director frame with the elapsed milliseconds. */
  stepFrame(elapsedMs: number): void {
    this.milliseconds += elapsedMs;
    if (this.pRunning === 0) return;
    this.update(Math.min(elapsedMs, 100));
  }

  update(tDif: number): void {
    if (tDif > 20) {
      this.update(tDif - 20);
      tDif = 20;
    }
    if (this.pRunning === 0) return;
    this.havokManager.update(tDif);
    for (const player of this.players) {
      player.camera.update();
      player.car.update();
    }
    for (const s of [...this.subscribers]) s.havokUpdate(tDif);
  }

  stopDriving(): void {
    for (const player of this.players) {
      player.camera.carStoppedDriving();
      player.car.stopDriving();
    }
  }

  destroy(): void {
    this.pRunning = 0;
    this.havokManager.destroy();
  }
}