Back to Supersonic RC Revive
SOURCE / PINNED RELEASE

Made of little things.

Supersonic RC Revive

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

Archive hash verified: 76e4eb30c67a6f45…. 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. */
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 car: Car;
  readonly camera: ChaseCamera;
  private readonly subscribers: HavokSubscriber[] = [];
  pRunning = 1;
  /** Milliseconds of game time, the stand-in for "the milliSeconds". */
  milliseconds = 0;

  constructor(level: LevelData, input: Input, havokOverrides: Partial<HavokOptions> = {}) {
    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);
    const hull = new Float32Array(markers.car_hull.positions.flat());
    const hullNode: Matrix4 = nodeMatrix(markers.car_hull.node_matrix);
    this.car = new Car(this.havokManager, this.rays, input, hull, hullNode, carStart(level));
    this.camera = new ChaseCamera(this.car, this.rays, () => this.milliseconds);
  }

  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);
    this.camera.update();
    this.car.update();
    for (const s of [...this.subscribers]) s.havokUpdate(tDif);
  }

  stopDriving(): void {
    this.camera.carStoppedDriving();
    this.car.stopDriving();
  }

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