Ping-pong turns on the last key, and stepped rows hold the key they last passed (#112)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f68941fa3a
commit
cc14988338
7 files changed
+72
-22
No files matched your search
@@ -27,6 +27,8 @@ export interface PlayheadAt {
|
||||
frame: number;
|
||||
/** Only the two `once` modes ever finish. */
|
||||
finished: boolean;
|
||||
/** On ping-pong's way back: a value that jumps holds the key the playhead passed last – the one above. */
|
||||
reverse: boolean;
|
||||
}
|
||||
|
||||
/** Slack for `time * fps`: six ticks of 1/60 s sum to 0.09999999999999999, which at 10 fps is frame 1. */
|
||||
@@ -67,18 +69,26 @@ export function animationDuration(asset: AnimationAsset): number {
|
||||
|
||||
/** Where the playhead stands `time` seconds after the start, for a pass of `count` frames. */
|
||||
export function playheadAt(count: number, fps: number, playback: Playback, time: number): PlayheadAt {
|
||||
if (count <= 0) return { frame: 0, finished: playback === 'once' || playback === 'once-reset' };
|
||||
if (!Number.isFinite(fps) || fps <= 0 || !(time > 0)) return { frame: 0, finished: false };
|
||||
if (count <= 0)
|
||||
return { frame: 0, finished: playback === 'once' || playback === 'once-reset', reverse: false };
|
||||
if (!Number.isFinite(fps) || fps <= 0 || !(time > 0)) return { frame: 0, finished: false, reverse: false };
|
||||
const at = time * fps + STEP_EPSILON;
|
||||
switch (playback) {
|
||||
case 'loop':
|
||||
return { frame: at % count, finished: false };
|
||||
return { frame: at % count, finished: false, reverse: false };
|
||||
case 'once':
|
||||
case 'once-reset':
|
||||
return at >= count ? { frame: count, finished: true } : { frame: at, finished: false };
|
||||
return at >= count
|
||||
? { frame: count, finished: true, reverse: false }
|
||||
: { frame: at, finished: false, reverse: false };
|
||||
case 'pingpong': {
|
||||
const phase = at % (2 * count);
|
||||
return { frame: phase < count ? phase : 2 * count - phase, finished: false };
|
||||
// Turns on the last key, so neither end is shown twice: keys on 0…9 play 0…9…1 in 18 frames (#112).
|
||||
const last = count - 1;
|
||||
if (last <= 0) return { frame: 0, finished: false, reverse: false };
|
||||
const phase = at % (2 * last);
|
||||
return phase < last
|
||||
? { frame: phase, finished: false, reverse: false }
|
||||
: { frame: 2 * last - phase, finished: false, reverse: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,12 +124,14 @@ export function blendValue(kind: string, a: PropertyValue, b: PropertyValue, t:
|
||||
|
||||
/**
|
||||
* A row's value at a (fractional) frame: the first value before the first key, the last after the
|
||||
* last one. `undefined` only for a row without keys.
|
||||
* last one. A value that jumps holds until the next key in the direction of play: played in
|
||||
* `reverse`, that is the key above. `undefined` only for a row without keys.
|
||||
*/
|
||||
export function trackValueAt(
|
||||
keys: readonly AnimationKey[],
|
||||
kind: string,
|
||||
frame: number,
|
||||
reverse = false,
|
||||
): PropertyValue | undefined {
|
||||
const first = keys[0];
|
||||
if (!first) return undefined;
|
||||
@@ -128,7 +140,7 @@ export function trackValueAt(
|
||||
const next = keys[i]!;
|
||||
if (frame >= next.frame) continue;
|
||||
const prev = keys[i - 1]!;
|
||||
if (!blends(kind)) return prev.value;
|
||||
if (!blends(kind)) return reverse && frame > prev.frame ? next.value : prev.value;
|
||||
return blendValue(kind, prev.value, next.value, (frame - prev.frame) / (next.frame - prev.frame));
|
||||
}
|
||||
return keys[keys.length - 1]!.value;
|
||||
@@ -144,8 +156,9 @@ export function trackWriteAt(
|
||||
kind: string,
|
||||
frame: number,
|
||||
base: PropertyValue | undefined,
|
||||
reverse = false,
|
||||
): PropertyValue | undefined {
|
||||
const value = trackValueAt(track.keys, kind, frame);
|
||||
const value = trackValueAt(track.keys, kind, frame, reverse);
|
||||
if (value === undefined) return undefined;
|
||||
const offset = track.relative && kind === 'number' && typeof base === 'number';
|
||||
return offset ? base + Number(value) : value;
|
||||
|
||||
Reference in new issue
Block a user