Files
healer-man/src/game/spectatorCameraMath.ts
T
2026-09-16 20:00:13 -04:00

52 lines
1.8 KiB
TypeScript

import type { Vector3Tuple } from "./dungeonTypes";
export interface SpectatorCameraAxes {
readonly forward: number;
readonly right: number;
readonly vertical: number;
}
const MAX_CAMERA_FRAME_SECONDS = 0.1;
function held(codes: ReadonlySet<string>, ...candidates: readonly string[]): number {
return Number(candidates.some((code) => codes.has(code)));
}
export function spectatorKeyboardAxes(codes: ReadonlySet<string>): SpectatorCameraAxes {
return {
forward: held(codes, "KeyW") - held(codes, "KeyS"),
right: held(codes, "KeyD") - held(codes, "KeyA"),
vertical: held(codes, "KeyE", "Space") - held(codes, "KeyQ", "ControlLeft", "ControlRight"),
};
}
export function spectatorCameraBoosted(codes: ReadonlySet<string>): boolean {
return codes.has("ShiftLeft") || codes.has("ShiftRight");
}
/** Returns a bounded, normalized free-flight translation for one render frame. */
export function spectatorCameraTranslation(
yaw: number,
pitch: number,
axes: SpectatorCameraAxes,
deltaSeconds: number,
boosted: boolean,
): Vector3Tuple {
if (![yaw, pitch, deltaSeconds, axes.forward, axes.right, axes.vertical].every(Number.isFinite)) return [0, 0, 0];
const forwardX = Math.sin(yaw) * Math.cos(pitch);
const forwardY = Math.sin(pitch);
const forwardZ = Math.cos(yaw) * Math.cos(pitch);
const rightX = Math.cos(yaw);
const rightZ = -Math.sin(yaw);
let x = forwardX * axes.forward + rightX * axes.right;
let y = forwardY * axes.forward + axes.vertical;
let z = forwardZ * axes.forward + rightZ * axes.right;
const length = Math.hypot(x, y, z);
if (length <= 1e-6) return [0, 0, 0];
const scale = (boosted ? 28 : 10) * Math.max(0, Math.min(MAX_CAMERA_FRAME_SECONDS, deltaSeconds)) / Math.max(1, length);
x *= scale;
y *= scale;
z *= scale;
return [x, y, z];
}