/** Тесты окна частиц перспективной камеры (пирамиды) — без WebGL. */
import { describe, expect, it } from 'vitest';
import { particleWindowPersp } from '../polyWindow';

/** Пирамида «прототипа»: камера в (0, 20, 30), смотрит в target (0, 0, 0)
 *  сверху-сзади, вертикальный fov 60° (tanHalf 0.577), аспект 16/9. */
function proto(dy0 = -2, dy1 = 40, far = 200) {
    const target: [number, number, number] = [0, 0, 0];
    const camPos: [number, number, number] = [0, 20, 30];
    const f = [0, -0.555, -0.832] as const; // ≈ взгляд в target (нормирован)
    const r = [1, 0, 0] as const; // экранная горизонталь
    const u = [0, 0.832, -0.555] as const; // экранная вертикаль
    return { r, u, f, camPos, target, tanHalf: 0.577, aspect: 16 / 9, near: 0.5, far, dy0, dy1 };
}

const inside = (b: { minX: number; maxX: number; minZ: number; maxZ: number }, x: number, z: number) =>
    x >= b.minX && x <= b.maxX && z >= b.minZ && z <= b.maxZ;

describe('particleWindowPersp (окно пирамиды)', () => {
    it('точка земли перед камерой внутри окна, за камерой — снаружи', () => {
        const b = particleWindowPersp(proto());
        expect(inside(b, 0, -10)).toBe(true); // перед камерой, на земле
        expect(inside(b, 0, 60)).toBe(false); // позади камеры
        expect(inside(b, 0, 35)).toBe(false); // под самой камерой — ниже нижней грани кадра
    });
    it('окно расширяется от камеры: дальняя земля шире ближней', () => {
        const b = particleWindowPersp(proto());
        // точка дальше по взгляду на том же луче — всё ещё внутри
        expect(inside(b, 30, -40)).toBe(true);
        expect(b.maxX).toBeGreaterThan(10); // полуширина растёт с глубиной
    });
    it('вырожденные углы не дают NaN (окно конечное или пустое)', () => {
        const p = proto(-2, 40, 200);
        const b = particleWindowPersp({ ...p, tanHalf: 1e-6, far: 1e-6 });
        expect(Number.isFinite(b.minX + b.maxX + b.minZ + b.maxZ)).toBe(true);
    });
    it('фрустум без пересечения со слоем (взгляд в небо) — конечное вырожденное окно', () => {
        // far позади near (вызов с cap'ом D=0 при взгляде вверх): полиэдр пуст,
        // но контракт — конечный бокс, а не ±Infinity (wrapV делит на размер)
        const b = particleWindowPersp(proto(-2, 40, 0.1));
        expect(Number.isFinite(b.minX + b.maxX + b.minZ + b.maxZ)).toBe(true);
        expect(b.maxX - b.minX).toBeLessThan(1); // вырожденный бокс в точке цели
    });
    it('far cap режет окно: меньший cap — уже окно', () => {
        const wide = particleWindowPersp(proto(-2, 40, 200));
        const narrow = particleWindowPersp(proto(-2, 40, 40));
        expect(narrow.maxX - narrow.minX).toBeLessThan(wide.maxX - wide.minX);
        expect(narrow.maxZ - narrow.minZ).toBeLessThan(wide.maxZ - wide.minZ);
    });
});