/**
* Шиммер воды (полировка прудов): редкие светлые блики на поверхности.
* Отдельный слой частиц движка — плоские квадраты НА воде (не воксели,
* не world.set — без ремешинга мира): блики вспыхивают и гаснут фазами
* от времени цикла, детерминированно (гейты кадр-равенств выключают слой
* вместе с пеплом мостом setAsh).
*/
import type { ParticleLayer, VoxelRenderer } from '@rpg/engine';
/** Клетка поверхности воды: центр клетки (воксель + 0.5). */
export interface WaterCell {
x: number; // центр по x
z: number; // центр по z
}
const STEP = 128; // каждый N-й воксель поверхности — потенциальный блик (редкие)
const GLOW = 0.84; // порог фазы: выше — блик виден (вспышка короткая)
/** Высота поверхности воды (вода — слой y=2, верхняя грань y=3) + сдвиг
* против z-fighting с кубом воды. */
const Y = 3.03;
/** Блик: приглушённая рябь и яркая вспышка (поверх W2 #568590). */
const COLORS = ['#8fb8bf', '#cfe4e6'] as const;
const GLOW_ALPHA = 0.9;
export class WaterShimmer {
private readonly layer: ParticleLayer;
/** Снапшот для моста: бликов в последней позе. */
private lastCount = 0;
constructor(renderer: VoxelRenderer, private readonly cells: readonly WaterCell[]) {
this.layer = renderer.addFlatParticles();
}
/** Вокселей воды в области и бликов сейчас (мост shimmerInfo). */
get info(): { cells: number; visible: number } {
return { cells: this.cells.length, visible: this.lastCount };
}
/** Поза бликов: время цикла (детерминизм гейтов). */
update(t: number): void {
const points: { pos: [number, number, number]; color: string; alpha: number }[] = [];
for (let i = 0; i < this.cells.length; i += STEP) {
const c = this.cells[i]!;
const phase = Math.sin(t * 0.9 + i * 2.399); // золотое сечение фаз
if (phase <= GLOW) continue;
points.push({
pos: [c.x, Y, c.z],
color: phase > 0.93 ? COLORS[1]! : COLORS[0]!, // яркая вспышка редка
alpha: GLOW_ALPHA,
});
}
this.lastCount = points.length;
this.layer.update(points);
}
/** Скрыть блики (не водная область / мост setAsh). */
hide(): void {
this.layer.update([]);
}
dispose(): void {
this.layer.dispose();
}
}