export type PlanarVector = readonly [x: number, z: number]; export const DEFAULT_CAMERA_PITCH = 0.32; export function clamp(value: number, minimum: number, maximum: number): number { return Math.min(maximum, Math.max(minimum, value)); } export function applyRadialDeadzone( x: number, y: number, deadzone = 0.18, ): PlanarVector { if (!Number.isFinite(x) || !Number.isFinite(y)) return [0, 0]; const magnitude = Math.hypot(x, y); if (magnitude <= deadzone || magnitude === 0) return [0, 0]; const remappedMagnitude = clamp((magnitude - deadzone) / (1 - deadzone), 0, 1); const scale = remappedMagnitude / magnitude; return [x * scale, y * scale]; } export function normalizePlanar(x: number, z: number): PlanarVector { if (!Number.isFinite(x) || !Number.isFinite(z)) return [0, 0]; const magnitude = Math.hypot(x, z); if (magnitude <= 1) return [x, z]; return [x / magnitude, z / magnitude]; } /** Converts input into world movement for a Three.js +Z-forward player. */ export function cameraRelativeMovement( moveX: number, moveForward: number, yaw: number, ): PlanarVector { const [x, forward] = normalizePlanar(moveX, moveForward); const safeYaw = Number.isFinite(yaw) ? yaw : 0; const sinYaw = Math.sin(safeYaw); const cosYaw = Math.cos(safeYaw); return [ -x * cosYaw + forward * sinYaw, x * sinYaw + forward * cosYaw, ]; } /** Applies device-right look using the screen-right basis of the +Z camera rig. */ export function cameraYawAfterHorizontalLook(yaw: number, horizontalLook: number): number { const safeYaw = Number.isFinite(yaw) ? yaw : 0; const safeLook = Number.isFinite(horizontalLook) ? horizontalLook : 0; return wrapAngle(safeYaw - safeLook); } export function clampCameraPitch(pitch: number): number { return Number.isFinite(pitch) ? clamp(pitch, -0.18, 0.88) : DEFAULT_CAMERA_PITCH; } export function wrapAngle(angle: number): number { return Number.isFinite(angle) ? Math.atan2(Math.sin(angle), Math.cos(angle)) : 0; } export function planarDistance( from: readonly [number, number, number], to: readonly [number, number, number], ): number { return Math.hypot(to[0] - from[0], to[2] - from[2]); }