/**
* Чистая математика перспективной камеры (тесты без WebGL).
* Конвенция yaw — угол «target → камера» в XZ (как у орбиты ModelViewer
* и screenMoveToWorld): камера стоит в yaw-направлении от цели и смотрит
* в противоположную горизонталь. pitch > 0 — камера смотрит вниз,
* pitch < 0 — вверх. Знак Y согласован с граблёй «камера НАД сценой»:
* отрицательный forward.y при pitch > 0 — взгляд к земле.
*/
/** Вертикальный FOV перспективной камеры (рад, ~50°). */
export const PERSP_FOV = 50 * (Math.PI / 180);
/** Режим камеры: изометрия (ортокамера), третье и первое лицо (перспектива). */
export type CameraMode = 'iso' | 'third' | 'first';
/** Клампы pitch орбиты (рад): third person — камера не ныряет под землю
* и не съезжает на горизонт; first person — свободнее, можно смотреть вверх. */
export const PERSP_PITCH_THIRD: readonly [number, number] = [0.08, 1.35];
export const PERSP_PITCH_FIRST: readonly [number, number] = [-1.2, 1.2];
/** Клампы дистанции орбиты в долях viewSize: ближе — упираемся в героя,
* дальше — теряем кадр. */
export const PERSP_DIST_K: readonly [number, number] = [0.25, 3];
/** Cap дальней границы окна частиц в долях глубины цели: без него окно
* первого лица при взгляде вдоль горизонта уходит в бесконечность —
* плотность размазывается, wrap гоняет частицы по километрам. */
export const PERSP_WINDOW_FAR_K = 3;
/** Направление взгляда (unit): горизонталь противоположна орбит-направлению
* «target → камера». Чистая функция. */
export function camForward(yaw: number, pitch: number): [number, number, number] {
const cp = Math.cos(pitch);
return [-Math.cos(yaw) * cp, -Math.sin(pitch), -Math.sin(yaw) * cp];
}
/** Позиция камеры на орбите вокруг target: yaw — угол «target → камера»
* в XZ, pitch > 0 — камера выше цели. Чистая функция (вьювер модели
* переэкспортирует её — прежний дом). */
export function orbitPosition(
yaw: number, pitch: number, dist: number, target: readonly [number, number, number],
): [number, number, number] {
const cp = Math.cos(pitch);
return [
target[0] + dist * cp * Math.cos(yaw),
target[1] + dist * Math.sin(pitch),
target[2] + dist * cp * Math.sin(yaw),
];
}