/**
* Чистая математика покадровых клипов (без Pixi): время в «шагах»,
* режимы loop/once/pingpong, события смены шага и завершения.
* Тикается от фиксированного шага движка; dt больше периода осознанно
* «проматывает» промежуточные шаги (сообщается только финальный) —
* по аналогии с пересечением границы delay в TweenManager.
*/
/** Режим клипа: loop — цикл; once — один раз и стоп на последнем кадре;
* pingpong — цикл туда-обратно (крайние кадры без дубля). */
export type ClipLoop = 'loop' | 'once' | 'pingpong';
export interface ClipDef {
/** Кадров в секунду. */
fps: number;
loop?: ClipLoop;
/** Множитель скорости клипа (перемножается со speedScale аниматора). */
speedScale?: number;
}
export interface ClipTick {
/** Индекс шага после продвижения (для once не превышает len-1). */
step: number;
/** Индекс шага до продвижения. */
prevStep: number;
/** Клип дошёл до конца (once) — ровно один раз при пересечении конца. */
finished: boolean;
}
/**
* Продвинуть время клипа (сек) на dt и посчитать новый шаг.
* Время накапливается в шагах: time += dt * fps * speedScale.
*/
export function stepClip(
len: number,
def: ClipDef,
prevTime: number,
dt: number
): { time: number; tick: ClipTick } {
const scale = def.speedScale ?? 1;
const time = prevTime + dt * def.fps * scale;
const prevStep = stepAt(len, def.loop ?? 'loop', prevTime);
const step = stepAt(len, def.loop ?? 'loop', time);
// finished — только для once и только в тик, когда конец был пересечён.
const finished = def.loop !== 'loop' && time >= len - 1 && prevTime < len - 1;
return { time, tick: { step, prevStep, finished } };
}
/** Индекс шага в момент time (в шагах) для заданного режима. */
function stepAt(len: number, loop: ClipLoop, time: number): number {
if (loop === 'once') return Math.min(Math.max(0, Math.floor(time)), len - 1);
if (loop === 'pingpong') {
// Период 2*(len-1): туда 0..len-1, обратно len-1..0; крайние кадры без дубля.
const period = Math.max(1, 2 * (len - 1));
const m = ((Math.floor(time) % period) + period) % period;
return m < len ? m : period - m;
}
const m = ((Math.floor(time) % len) + len) % len;
return m;
}