/**
* Атмосфера области (v1 Atmosphere, вариант 'ash'): медленные хлопья пепла
* в воздухе. Voxel-облако (instanced-кубы рендера): поза каждого хлопка —
* детерминированные синусы от времени цикла (гейт-стабильно). Хлопки живут
* в МИРОВЫХ координатах (не «приклеены» к камере): каждый дрейфует по ветру
* от своего якоря, а окно видимости вокруг цели камеры их заворачивает —
* ушедший из окна хлопок появляется с противоположной стороны.
*/
import type { VoxelCloud, VoxelRenderer } from '@rpg/engine';
/** Параметры пепла (масштаб мира v2: радиус в вокселях). */
const COUNT = 48; // хлопков в облаке (v1 ~40 частиц)
const RADIUS = 60; // половина окна вокруг камеры (кадр 80×45 вокс)
const TOP = 44; // высота верхнего слоя пепла над землёй
const SPEED = 0.35; // дрейф по ветру (вокс/сек)
/** Цвет пепла (палитра сцены: P3 — светлый пепел). */
const ASH_COLOR = '#3e3e48';
/** Заворачивание значения в окно [min, min+size) — респавн с другой стороны. */
const wrap = (v: number, min: number, size: number): number => {
let r = (v - min) % size;
if (r < 0) r += size;
return min + r;
};
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 x = wrap(
i * 61.7 + t * SPEED + Math.sin(t * 0.7 + p) * 1.5,
target[0] - RADIUS, RADIUS * 2,
);
const z = wrap(
i * 47.3 + Math.cos(t * 0.5 + p) * 1.5,
target[2] - RADIUS, RADIUS * 2,
);
// вертикаль: медленное оседание
const fall = ((t * (0.25 + (i % 5) * 0.06) + (i * 3.7) % TOP) % TOP);
voxels.push({ pos: [x, TOP - fall, z], color: ASH_COLOR });
}
this.cloud.update(voxels);
}
/** Скрыть облако (область без атмосферы). */
hide(): void {
this.cloud.update([]);
}
dispose(): void {
this.cloud.dispose();
}
}