57 lines
2.4 KiB
TypeScript
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);
|
|
}
|