Newer
Older
rpg / v1 / packages / engine / src / render / postfx / postSim.ts
/**
 * Чистая математика пост-эффектов: переменный блюр (радиальный спад силы),
 * сэмпл-ядро блюра, хроматическая аберрация, марево. Без Pixi — тестируется
 * в Vitest headless; PostFx.ts только переносит сэмплы в шейдер.
 */
export { pulseEnvelope, pulseTotal, type PulseSpec } from '../lightSim';

/** Гладкая ступень 0..1 (с нулевой производной на краях — без «шва» спада). */
function smoothstep(t: number): number {
    const x = Math.max(0, Math.min(1, t));
    return x * x * (3 - 2 * x);
}

/**
 * Сила блюра на расстоянии distPx от центра, px выборки: пик в центре,
 * гладкий спад до нуля на радиусе. radiusPx <= 0 — глобальный режим
 * (сила постоянна по всему кадру).
 */
export function blurStrengthAt(distPx: number, radiusPx: number, strengthPx: number): number {
    if (radiusPx <= 0) return strengthPx;
    return strengthPx * smoothstep(1 - distPx / radiusPx);
}

/**
 * Ядро блюра: taps смещений равномерного диска (спираль золотого сечения) —
 * покрывает круг без кластерных артефактов сетки; норма 1 px выборки.
 */
export function blurKernel(taps = 8): ReadonlyArray<readonly [number, number]> {
    const golden = Math.PI * (3 - Math.sqrt(5)) * 2;
    const out: Array<readonly [number, number]> = [];
    for (let i = 0; i < taps; i++) {
        const r = Math.sqrt((i + 0.5) / taps);
        const a = i * golden;
        out.push([Math.cos(a) * r, Math.sin(a) * r]);
    }
    return out;
}

/**
 * Сдвиг каналов хроматической аберрации на расстоянии distPx от центра, px:
 * R/B разводятся по радиалии; спад как у блюра — импульсы «от точки».
 */
export function aberrationShiftAt(distPx: number, radiusPx: number, strengthPx: number): number {
    if (radiusPx <= 0) return strengthPx;
    return strengthPx * smoothstep(1 - distPx / radiusPx);
}

/** Параметры марева: амплитуда px, длина волны px, скорость фаз/с. */
export interface ShimmerParams {
    amp: number;
    wavelength: number;
    speed: number;
}

/** Смещение по Y (px) синусоидой по X и времени — «горячий воздух». */
export function shimmerOffsetY(xPx: number, timeSec: number, p: ShimmerParams): number {
    return p.amp * Math.sin((xPx / p.wavelength + timeSec * p.speed) * Math.PI * 2);
}