Newer
Older
rpg / v2 / apps / game / src / waterShimmer.ts
/**
 * Шиммер воды (полировка прудов): редкие светлые блики на поверхности.
 * Отдельное voxel-облако НАД водой (не world.set — без ремешинга мира):
 * блики вспыхивают и гаснут фазами от времени цикла, детерминированно
 * (гейты кадр-равенств выключают слой вместе с пеплом мостом setAsh).
 */
import type { VoxelCloud, VoxelRenderer } from '@rpg/engine';

/** Воксель поверхности воды: центр куба слоя (блик выступает на 0.05). */
export interface WaterCell {
    x: number; // центр куба по x (воксель + 0.5)
    z: number; // центр куба по z
}

const STEP = 19; // каждый N-й воксель поверхности — потенциальный блик (редкие)
const GLOW = 0.84; // порог фазы: выше — блик виден (вспышка короткая)
/** Сдвиг блика из плоскости куба воды: куб блика иначе объёмно перекрывается
 *  с кубом воды (копланарные вертикальные грани) — на кромках пруда z-fighting,
 *  порядок отрисовки облаков меняется от пересоздания → мерцающие пиксели. */
const EPS = 0.03;
/** Блик: приглушённая рябь и яркая вспышка (поверх W2 #568590). */
const COLORS = ['#8fb8bf', '#cfe4e6'] as const;

export class WaterShimmer {
    private readonly cloud: VoxelCloud;
    /** Снапшот для моста: бликов в последней позе. */
    private lastCount = 0;

    constructor(renderer: VoxelRenderer, private readonly cells: readonly WaterCell[]) {
        this.cloud = renderer.addVoxelCloud([]);
        this.cloud.mesh.castShadow = false; // блик — свет, не объект: без тени
    }

    /** Вокселей воды в области и бликов сейчас (мост shimmerInfo). */
    get info(): { cells: number; visible: number } {
        return { cells: this.cells.length, visible: this.lastCount };
    }

    /** Поза бликов: время цикла (детерминизм гейтов). */
    update(t: number): void {
        const voxels: { pos: [number, number, number]; color: string }[] = [];
        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;
            voxels.push({
                pos: [c.x + EPS, 2.55, c.z + EPS],
                color: phase > 0.93 ? COLORS[1] : COLORS[0], // яркая вспышка редка
            });
        }
        this.lastCount = voxels.length;
        this.cloud.update(voxels);
        this.cloud.mesh.castShadow = false; // пересоздание облака возвращает тень
    }

    /** Скрыть блики (не водная область / мост setAsh). */
    hide(): void {
        this.cloud.update([]);
    }

    dispose(): void {
        this.cloud.dispose();
    }
}