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/camera.ts
// Port of "ParentScript 7 - camera": a chase camera that eases towards a
// point 50 units behind the car and is pulled in front of occluders.
import { Vector3 } from 'three';
import type { RayWorld } from '../physics/rayWorld.js';
import type { Car } from './car.js';

export class ChaseCamera {
  worldPosition = new Vector3(-2000, -584, 250);
  readonly target = new Vector3();
  readonly up = new Vector3(0, 0.01, 1);
  fieldOfView = 70;
  private pWannaBe = this.worldPosition.clone();
  // The Lingo sets "pFolllowing = 1" (typo), so pFollowing starts VOID (0).
  private pFollowing = 0;
  private pOrbit = 0;
  /** The black overlay added in new() until fadeIn(). */
  blackedOut = true;

  constructor(
    private readonly car: Car,
    private readonly rays: RayWorld,
    private readonly milliseconds: () => number,
  ) {}

  fadeIn(): void {
    this.blackedOut = false;
  }

  carStoppedDriving(): void {
    this.pOrbit = 1;
  }

  update(): void {
    const rb = this.car.rb;
    const carPos = rb.position;
    if (this.pOrbit === 0) {
      const tDontFollow = rb.linearVelocity.length() < 10 ? 1 : 0;
      if (this.pFollowing && tDontFollow === 0) {
        const tBehindDir = rb.linearVelocity.negate();
        tBehindDir.z = 0;
        tBehindDir.normalize();
        this.pWannaBe = carPos.clone().addScaledVector(tBehindDir, 50);
      } else {
        const yAxis = new Vector3().setFromMatrixColumn(this.car.modelTransform(), 1);
        this.pWannaBe = carPos.clone().addScaledVector(yAxis.negate(), 50);
        if (carPos.distanceTo(this.worldPosition) > 300) this.pFollowing = 1;
      }
      this.pWannaBe.z = carPos.z + 10;
    } else {
      const tTime = this.milliseconds() / 1000;
      this.pWannaBe = carPos.clone().add(new Vector3(Math.sin(tTime) * 150, Math.cos(tTime) * 150, 50));
    }
    this.worldPosition.add(this.pWannaBe.clone().sub(this.worldPosition).multiplyScalar(0.1));
    this.target.copy(carPos).add(new Vector3(0, 0, 10));
    const hit = this.rays.modelsUnderRay(this.target, this.worldPosition.clone().sub(this.target));
    if (hit && hit.isectPosition.distanceTo(this.target) < this.worldPosition.distanceTo(this.target)) {
      this.worldPosition.copy(hit.isectPosition);
    }
  }
}