Keys carry an easing for the span to the next key: linear, step, ease in, ease out, ease in-out (#101)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c92a9478f
commit
b278605c53
7 files changed
+331
-27
No files matched your search
@@ -101,10 +101,13 @@ export interface TextureAsset extends AssetBase {
|
||||
frames: TextureFrame[];
|
||||
}
|
||||
|
||||
/** How a key blends into the next one; absent is `linear`, the only one there is so far (#97). */
|
||||
export type KeyInterpolation = 'linear';
|
||||
/** How a key blends into the next one (#101); absent is `linear`. `step` holds until the next key. */
|
||||
export type KeyInterpolation = 'linear' | 'step' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
|
||||
/** A value on a whole frame. A value that cannot blend (a texture, a switch) holds until the next key. */
|
||||
/**
|
||||
* A value on a whole frame. Its interpolation rules the way to the next key. A value that cannot
|
||||
* blend (a texture, a switch) holds until the next key whatever it says.
|
||||
*/
|
||||
export interface AnimationKey {
|
||||
frame: number;
|
||||
value: PropertyValue;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isKindWith } from '../properties/kinds';
|
||||
import { type PropertyDef, schemaFor } from '../properties/PropertySchema';
|
||||
import type { AnimationAsset, AnimationKey, AnimationTrack, Asset } from './Asset';
|
||||
import type { AnimationAsset, AnimationKey, AnimationTrack, Asset, KeyInterpolation } from './Asset';
|
||||
import type { AssetType } from './AssetTypes';
|
||||
import { treeOrder } from './hierarchy';
|
||||
import { instanceChildId, resolveObjects } from './instance';
|
||||
@@ -111,6 +111,42 @@ function lerpColor(a: number, b: number, t: number): number {
|
||||
return channel(16) | channel(8) | channel(0);
|
||||
}
|
||||
|
||||
/** In the order the inspector offers them; `linear` first, since it is what an absent field means. */
|
||||
export const KEY_INTERPOLATIONS: readonly { value: KeyInterpolation; label: string }[] = [
|
||||
{ value: 'linear', label: 'Linear' },
|
||||
{ value: 'step', label: 'Step' },
|
||||
{ value: 'ease-in', label: 'Ease in' },
|
||||
{ value: 'ease-out', label: 'Ease out' },
|
||||
{ value: 'ease-in-out', label: 'Ease in-out' },
|
||||
];
|
||||
|
||||
export function isKeyInterpolation(value: unknown): value is KeyInterpolation {
|
||||
return KEY_INTERPOLATIONS.some((i) => i.value === value);
|
||||
}
|
||||
|
||||
export function interpolationOf(key: AnimationKey): KeyInterpolation {
|
||||
return key.interpolation ?? 'linear';
|
||||
}
|
||||
|
||||
/**
|
||||
* How far along the way to the next key a value is, `t` the share of the time passed. The eases are
|
||||
* quadratic: ease-in reaches 0.25 at half the time, ease-out 0.75, ease-in-out 0.5 but 0.125 at a quarter.
|
||||
*/
|
||||
export function easedShare(interpolation: KeyInterpolation, t: number): number {
|
||||
switch (interpolation) {
|
||||
case 'linear':
|
||||
return t;
|
||||
case 'step':
|
||||
return 0;
|
||||
case 'ease-in':
|
||||
return t * t;
|
||||
case 'ease-out':
|
||||
return 1 - (1 - t) * (1 - t);
|
||||
case 'ease-in-out':
|
||||
return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
|
||||
}
|
||||
}
|
||||
|
||||
/** The value between two keys, `t` from 0 (at `a`) to 1 (at `b`). */
|
||||
export function blendValue(kind: string, a: PropertyValue, b: PropertyValue, t: number): PropertyValue {
|
||||
if (kind === 'number') {
|
||||
@@ -124,8 +160,9 @@ 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. 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.
|
||||
* last one. Between two keys the earlier one's interpolation decides the way. A value that jumps –
|
||||
* one that cannot blend, or a `step` key – 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[],
|
||||
@@ -140,8 +177,11 @@ export function trackValueAt(
|
||||
const next = keys[i]!;
|
||||
if (frame >= next.frame) continue;
|
||||
const prev = keys[i - 1]!;
|
||||
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));
|
||||
const interpolation = interpolationOf(prev);
|
||||
if (!blends(kind) || interpolation === 'step')
|
||||
return reverse && frame > prev.frame ? next.value : prev.value;
|
||||
const t = (frame - prev.frame) / (next.frame - prev.frame);
|
||||
return blendValue(kind, prev.value, next.value, easedShare(interpolation, t));
|
||||
}
|
||||
return keys[keys.length - 1]!.value;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user