/**
* Тряска камеры: несколько одновременных толчков, затухание линейно по времени.
* Чистая логика (без Pixi) — смещение детерминировано seed'ом, тестируется в Vitest.
*/
import { createRng, type Rng } from '../math/rng';
interface ShakeEntry {
strength: number;
duration: number;
elapsed: number;
}
export class Shake {
private entries: ShakeEntry[] = [];
private rng: Rng;
private offsetX = 0;
private offsetY = 0;
constructor(seed = 0x5eed1234) {
this.rng = createRng(seed);
}
/** Добавить толчок: амплитуда в пикселях, длительность в секундах. */
add(strength: number, duration: number): void {
if (strength <= 0 || duration <= 0) return;
this.entries.push({ strength, duration, elapsed: 0 });
}
update(dt: number): void {
this.offsetX = 0;
this.offsetY = 0;
for (const e of this.entries) {
e.elapsed += dt;
const k = Math.max(0, 1 - e.elapsed / e.duration); // линейное затухание
this.offsetX += (this.rng.next() * 2 - 1) * e.strength * k;
this.offsetY += (this.rng.next() * 2 - 1) * e.strength * k;
}
this.entries = this.entries.filter((e) => e.elapsed < e.duration);
}
/** Текущее смещение в пикселях (0 вне активной тряски). */
get offset(): { x: number; y: number } {
return { x: Math.round(this.offsetX), y: Math.round(this.offsetY) };
}
get active(): boolean {
return this.entries.length > 0;
}
clear(): void {
this.entries = [];
this.offsetX = 0;
this.offsetY = 0;
}
}