Newer
Older
rpg / packages / engine / src / render / __tests__ / camera.test.ts
import { describe, it, expect } from 'vitest';
import { Container } from 'pixi.js';
import { Camera } from '../Camera';
import { screenToWorld, worldToScreen } from '../../math/iso';

/** Смещение цели от центра камеры в экранных пикселях: мировая точка. */
function targetAtScreenOffset(cam: Camera, dxPx: number, dyPx: number): { x: number; y: number } {
    const c = worldToScreen(cam.x, cam.y);
    return screenToWorld(c.x + dxPx, c.y + dyPx);
}

describe('Camera dead zone', () => {
    it('без dead zone — камера центрируется на цели', () => {
        const cam = new Camera(480, 270);
        cam.follow(3, 5);
        expect(cam.x).toBe(3);
        expect(cam.y).toBe(5);
    });

    it('цель внутри окна — камера стоит', () => {
        const cam = new Camera(480, 270);
        cam.snap(3, 5);
        cam.deadZonePx = { width: 160, height: 100 };
        // Цель на 60 px правее и 30 px ниже центра экрана — внутри окна.
        const t = targetAtScreenOffset(cam, 60, 30);
        cam.follow(t.x, t.y);
        expect(cam.x).toBe(3);
        expect(cam.y).toBe(5);
    });

    it('цель толкает камеру ровно на величину выхода', () => {
        const cam = new Camera(480, 270);
        cam.snap(3, 5);
        cam.deadZonePx = { width: 160, height: 100 };
        // Выход за окно: 20 px вправо и 20 px вверх.
        const t = targetAtScreenOffset(cam, 100, -70);
        cam.follow(t.x, t.y);
        // Центр проекции сместился ровно на (20, −20) экранных пикселей.
        const c0 = worldToScreen(3, 5);
        const c = worldToScreen(cam.x, cam.y);
        expect(c.x - c0.x).toBeCloseTo(20, 6);
        expect(c.y - c0.y).toBeCloseTo(-20, 6);
    });

    it('snap ставит камеру на точку, минуя окно', () => {
        const cam = new Camera(480, 270);
        cam.deadZonePx = { width: 160, height: 100 };
        cam.snap(5, 2);
        expect(cam.x).toBe(5);
        expect(cam.y).toBe(2);
        // Окно сохранено: цель чуть правее центра — камера не сдвигается.
        const t = targetAtScreenOffset(cam, 50, 0);
        cam.follow(t.x, t.y);
        expect(cam.x).toBe(5);
    });
});

describe('Camera bounds (мировые юниты)', () => {
    it('границы мира держат камеру прижатой', () => {
        const cam = new Camera(480, 270);
        cam.bounds = { x: 0, y: 0, width: 64, height: 64 };
        cam.snap(-1000, 500); // за границей по обеим осям
        // bbox карты: x = (0 - 64)*16, y = 0; клэмп по проекции центра.
        const c = worldToScreen(cam.x, cam.y);
        expect(c.x).toBeCloseTo(-1024 + 240, 6);
        expect(c.y).toBeCloseTo(135, 6);
    });

    it('apply ставит worldRoot по округлённой проекции', () => {
        const cam = new Camera(480, 270);
        cam.snap(3, 5);
        const view = new Container();
        cam.apply(view);
        const applied = worldToScreen(cam.x, cam.y);
        expect(view.x).toBe(240 - Math.round(applied.x));
        expect(view.y).toBe(135 - Math.round(applied.y));
    });
});