/** Тесты чистой математики камеры: направление взгляда и орбита — без WebGL. */
import { describe, expect, it } from 'vitest';
import { ISO_YAW } from '../voxelRenderer';
import { camForward, orbitPosition, perspVisibleRadius, PERSP_FOV } from '../cameraMath';
describe('camForward (направление взгляда от yaw/pitch)', () => {
it('pitch 0 — горизонтальный взгляд против орбит-направления', () => {
const f = camForward(ISO_YAW, 0);
expect(f[1]).toBeCloseTo(0); // горизонталь (−0 тоже горизонталь)
// орбита «target → камера» при yaw π/4 — (+x,+z); взгляд — в минус
expect(f[0]).toBeCloseTo(-Math.cos(ISO_YAW));
expect(f[2]).toBeCloseTo(-Math.sin(ISO_YAW));
});
it('pitch > 0 — камера смотрит вниз (Y отрицателен, грабля «из-под земли»)', () => {
const f = camForward(0.5, 0.5);
expect(f[1]).toBeLessThan(0);
});
it('pitch < 0 — взгляд вверх; длина всегда 1', () => {
const up = camForward(1.0, -0.3);
expect(up[1]).toBeGreaterThan(0);
const len = Math.hypot(...camForward(2.7, -0.8));
expect(len).toBeCloseTo(1);
});
});
describe('orbitPosition (переезд из modelViewer: формула та же)', () => {
it('yaw π/4 — камера в (+x,+z) от цели, pitch поднимает по Y', () => {
const pos = orbitPosition(Math.PI / 4, 0, 10, [1, 2, 3]);
expect(pos[0]).toBeCloseTo(1 + 10 * Math.cos(Math.PI / 4));
expect(pos[1]).toBeCloseTo(2);
expect(pos[2]).toBeCloseTo(3 + 10 * Math.sin(Math.PI / 4));
const above = orbitPosition(Math.PI / 4, 0.5, 10, [0, 0, 0]);
expect(above[1]).toBeCloseTo(10 * Math.sin(0.5));
});
});
describe('perspVisibleRadius (полуширина для теневых фрустумов)', () => {
it('сечение пирамиды на глубине D растёт с D', () => {
const tanHalf = Math.tan(PERSP_FOV / 2);
expect(perspVisibleRadius(240, tanHalf, 16 / 9, 80))
.toBeCloseTo(240 * tanHalf * (16 / 9));
expect(perspVisibleRadius(480, tanHalf, 16 / 9, 80))
.toBeGreaterThan(perspVisibleRadius(240, tanHalf, 16 / 9, 80));
});
it('кламп снизу четвертью изо-полуширины: вырожденный D не роняет фрустум', () => {
expect(perspVisibleRadius(0, Math.tan(PERSP_FOV / 2), 16 / 9, 80))
.toBeCloseTo(80 * (16 / 9) * 0.25);
});
});