Files
healer-man/src/game/arenaCamera.ts
T
2026-09-11 14:26:15 -04:00

57 lines
2.4 KiB
TypeScript

import { Raycaster, Vector3, type Object3D } from "three";
import { clampCameraPitch, DEFAULT_CAMERA_PITCH } from "./inputMath";
import type { ArenaStageManifest, Vec3Tuple } from "./arenaTypes";
export const ARENA_CAMERA_DISTANCE = 5.8;
export const ARENA_CAMERA_FOCUS_HEIGHT = 1.35;
export interface ArenaCameraPose {
readonly position: [number, number, number];
readonly target: [number, number, number];
}
export function arenaSpawnYaw(stage: ArenaStageManifest, team: 0 | 1): number {
const from = stage.teamSpawns[team];
const to = stage.preparationGates[team];
return Math.atan2(to[0] - from[0], to[2] - from[2]);
}
/** Follow the controlled character at their elevation, including underground arenas. */
export function arenaCameraPose(
footPosition: Vec3Tuple,
yaw: number,
pitch = DEFAULT_CAMERA_PITCH,
distance = ARENA_CAMERA_DISTANCE,
): ArenaCameraPose {
const safeYaw = Number.isFinite(yaw) ? yaw : 0;
const safePitch = clampCameraPitch(pitch);
const radius = Number.isFinite(distance) ? Math.max(0.25, Math.min(12, distance)) : ARENA_CAMERA_DISTANCE;
const target: [number, number, number] = [footPosition[0], footPosition[1] + ARENA_CAMERA_FOCUS_HEIGHT, footPosition[2]];
return {
target,
position: [
target[0] - Math.sin(safeYaw) * Math.cos(safePitch) * radius,
target[1] + Math.sin(safePitch) * radius,
target[2] - Math.cos(safeYaw) * Math.cos(safePitch) * radius,
],
};
}
/** Probe both directions so one-sided imported walls also obstruct the camera. */
export function arenaCameraClearDistance(target: Vector3, desired: Vector3, environment: Object3D, ray: Raycaster): number {
const length = target.distanceTo(desired);
if (length < 0.001) return 0;
const direction = desired.clone().sub(target).normalize();
let distance = length;
for (const offset of [[0, 0, 0], [0.18, 0, 0], [-0.18, 0, 0], [0, 0.18, 0], [0, -0.18, 0]]) {
const origin = target.clone().add(new Vector3(...offset));
ray.near = 0.03;
ray.far = length;
ray.set(origin, direction);
for (const hit of ray.intersectObject(environment, true)) distance = Math.min(distance, hit.distance - 0.2);
ray.set(origin.addScaledVector(direction, length), direction.clone().negate());
for (const hit of ray.intersectObject(environment, true)) distance = Math.min(distance, length - hit.distance - 0.2);
}
return Math.max(0.15, distance);
}