/**
* Тесты света частиц: блок юниформ (дефолты), упаковка снапшота источников
* (цвет × яркость, кап), фонарик (нормировка направления, конус), сброс.
*/
import { describe, expect, it } from 'vitest';
import { makeParticleLightUniforms, packParticleLights, PARTICLE_LIGHT_MAX } from '../particles';
import type { ParticleLight, ParticleSpot } from '../particles';
describe('свет частиц (packParticleLights)', () => {
it('блок юниформ: по умолчанию источников нет, фонарик погашен', () => {
const u = makeParticleLightUniforms();
expect(u.uLightCount!.value).toBe(0);
expect(u.uSpotOn!.value).toBe(0);
expect((u.uLightPos!.value as Float32Array).length).toBe(PARTICLE_LIGHT_MAX * 3);
});
it('упаковка: цвет × яркость, радиус и позиция по местам', () => {
const u = makeParticleLightUniforms();
const points: ParticleLight[] = [
{ pos: [10, 2, 20], color: 0xff8040, intensity: 0.5, radius: 30 },
{ pos: [0, 2, 0], color: 0x202020, intensity: 2, radius: 5 },
];
packParticleLights(u, points, null);
expect(u.uLightCount!.value).toBe(2);
const col = u.uLightColor!.value as Float32Array;
// 0xff8040 × 0.5 → (0.5, 0.25, 0.125)
expect(col[0]).toBeCloseTo(0.5);
expect(col[1]).toBeCloseTo(0.251, 2);
expect(col[2]).toBeCloseTo(0.125, 2);
// второй источник: 0x20 × 2 → 0.25
expect(col[5]).toBeCloseTo(0.25);
const pos = u.uLightPos!.value as Float32Array;
expect([pos[0], pos[1], pos[2]]).toEqual([10, 2, 20]);
expect((u.uLightRadius!.value as Float32Array)[1]).toBe(5);
expect(u.uSpotOn!.value).toBe(0);
});
it('кап: лишние источники обрезаются до PARTICLE_LIGHT_MAX', () => {
const u = makeParticleLightUniforms();
const many: ParticleLight[] = Array.from({ length: 40 }, (_, i) => ({
pos: [i, 0, 0], color: 0xffffff, intensity: 1, radius: 1,
}));
packParticleLights(u, many, null);
expect(u.uLightCount!.value).toBe(PARTICLE_LIGHT_MAX);
});
it('фонарик: направление нормируется, конус от угла и penumbra', () => {
const u = makeParticleLightUniforms();
const spot: ParticleSpot = {
pos: [5, 7, 5], dir: [0, -3, 0], color: 0xfff2c0, intensity: 0.8,
distance: 60, angle: Math.PI / 4, penumbra: 0.4,
};
packParticleLights(u, [], spot);
expect(u.uSpotOn!.value).toBe(1);
const dir = u.uSpotDir!.value as Float32Array;
expect([dir[0], dir[1], dir[2]]).toEqual([0, -1, 0]); // нормировка
expect(u.uSpotCos!.value).toBeCloseTo(Math.cos(Math.PI / 8));
// внутренний край — уже угол, косинус больше внешнего
expect(u.uSpotSoft!.value).toBeGreaterThan(u.uSpotCos!.value);
const c = u.uSpotColor!.value as Float32Array;
expect(c[0]).toBeCloseTo(0.8);
expect(c[1]).toBeCloseTo((0xf2 / 255) * 0.8);
});
it('сброс: погасший фонарик гасит uSpotOn, счётчик обнуляется', () => {
const u = makeParticleLightUniforms();
packParticleLights(u, [], {
pos: [0, 0, 0], dir: [1, 0, 0], color: 0xffffff, intensity: 1,
distance: 10, angle: 1, penumbra: 0.5,
});
expect(u.uSpotOn!.value).toBe(1);
packParticleLights(u, [], null);
expect(u.uSpotOn!.value).toBe(0);
packParticleLights(u, [{ pos: [1, 1, 1], color: 0xffffff, intensity: 1, radius: 2 }], null);
expect(u.uLightCount!.value).toBe(1);
packParticleLights(u, [], null);
expect(u.uLightCount!.value).toBe(0);
});
});