/**
* Атмосфера области (v1 Atmosphere, вариант 'ash'): медленные хлопья пепла
* в воздухе вокруг камеры. Voxel-облако (instanced-кубы рендера): поза
* каждого хлопка — детерминированные синусы от времени цикла (гейт-стабильно),
* дрейф по ветру + лёгкое кружение; хлопья всегда в кадре — облако следует
* за целью камеры (v1: частицы в screen-space, v2 — кубы вокруг target).
*/
import type { VoxelCloud, VoxelRenderer } from '@rpg/engine';
/** Параметры пепла (масштаб мира v2: радиус в вокселях). */
const COUNT = 36; // хлопков в облаке (v1 ~40 частиц)
const RADIUS = 22; // половина ширины облака вокруг камеры
const TOP = 14; // высота верхнего слоя пепла над землёй
const SPEED = 0.35; // дрейф по ветру (вокс/сек)
/** Цвет пепла (палитра сцены: P3 — светлый пепел). */
const ASH_COLOR = '#3e3e48';
export class AshParticles {
private readonly cloud: VoxelCloud;
constructor(renderer: VoxelRenderer) {
this.cloud = renderer.addVoxelCloud([]);
}
/** Поза облака: цель камеры и время цикла (детерминизм гейтов). */
update(t: number, target: readonly [number, number, number]): void {
const voxels: { pos: [number, number, number]; color: string }[] = [];
for (let i = 0; i < COUNT; i++) {
// сид хлопка — от индекса; фазы кружения разнесены
const p = i * 2.399; // золотое сечение: равномерный разброс фаз
// базовый дрейф по ветру: хлопок циклично проходит окно вокруг камеры
const drift = (t * SPEED + i * (RADIUS * 2) / COUNT) % (RADIUS * 2);
const x = target[0] - RADIUS + drift;
const z = target[2] - RADIUS + ((i * 7919) % (RADIUS * 2));
// вертикаль: медленное оседание + лёгкое кружение по горизонтали
const fall = ((t * (0.25 + (i % 5) * 0.06) + (i * 3.7) % TOP) % TOP);
const y = TOP - fall;
voxels.push({
pos: [
x + Math.sin(t * 0.7 + p) * 1.5,
y,
z + Math.cos(t * 0.5 + p) * 1.5,
],
color: ASH_COLOR,
});
}
this.cloud.update(voxels);
}
/** Скрыть облако (область без атмосферы). */
hide(): void {
this.cloud.update([]);
}
dispose(): void {
this.cloud.dispose();
}
}