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.
// 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();
}
}
