/**
* Атмосфера области (v1 Atmosphere, вариант 'ash'): медленные хлопья пепла
* в воздухе. Отдельный слой частиц движка (спрайты-билборды, не воксели):
* позы — детерминированные синусы от времени цикла (гейт-стабильно).
* Хлопки живут в МИРОВЫХ координатах (не «приклеены» к камере): каждый
* дрейфует по ветру от своего якоря, а окно видимости камеры их заворачивает.
* Окно покрывает весь кадр с перекрытием (маржа за экраном), и в марже
* альфа гаснет — ушедший хлопок появляется с противоположной стороны
* уже за экраном, без «появления из воздуха».
*/
import type { ParticleLayer, VoxelRenderer } from '@rpg/engine';
import { edgeAlpha, particleWindow, wrap } from './particlesWindow';
/** Параметры пепла (окно — след кадра + маржа, в мировых юнитах). */
const COUNT = 128; // хлопков в облаке (окно выросло до всего кадра)
const TOP = 44; // высота верхнего слоя пепла над землёй
const SPEED = 1.2; // дрейф по ветру (юнит/сек; v1: 2..7 px/s при 32 px/юнит)
const MARGIN = 16; // перекрытие за краем кадра
/** Цвет пепла (палитра сцены: P3 — светлый пепел). */
const ASH_COLOR = '#3e3e48';
export class AshParticles {
private readonly layer: ParticleLayer;
constructor(private readonly renderer: VoxelRenderer) {
this.layer = renderer.addParticles();
}
/** Поза облака: время цикла (окно — из цели камеры рендерера). */
update(t: number): void {
const win = particleWindow(this.renderer, 0, TOP, MARGIN);
const points: { pos: [number, number, number]; color: string; alpha: number }[] = [];
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,
win.minX, win.maxX - win.minX,
);
const z = wrap(
i * 47.3 + Math.cos(t * 0.5 + p) * 1.5,
win.minZ, win.maxZ - win.minZ,
);
// вертикаль: оседание 0.5..1.1 юнит/с (v1: 1..4 px/s)
const fall = ((t * (0.5 + (i % 5) * 0.15) + (i * 3.7) % TOP) % TOP);
const y = TOP - fall;
points.push({ pos: [x, y, z], color: ASH_COLOR, alpha: edgeAlpha(win, MARGIN, x, z) });
}
this.layer.update(points);
}
/** Скрыть облако (область без атмосферы). */
hide(): void {
this.layer.update([]);
}
dispose(): void {
this.layer.dispose();
}
}