Play animation's blend is given in seconds, not in frames of the new animation (#106)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-17 11:14:15 +02:00
1 parent 04dc254b3d
commit 8a377d9239
4 files changed
+39 -26

No files matched your search

@@ -22,8 +22,8 @@ export const PlayAnimation: NodeDef = {
'Plays an animation on an object: its rows reach the object and what is placed inside it, so every ' +
'instance plays on its own. A paused one runs on from where it stands; one already running keeps its ' +
'frame but wins again over the others on the object. Blend fades numbers and colours over that many ' +
'of its frames from what they held when it took over; everything else switches at once.',
inputs: [exec(), obj('object'), animationPort(), withDefault(num('blend', 'blend (frames)'), 0)],
'seconds from what they held when it took over; everything else switches at once.',
inputs: [exec(), obj('object'), animationPort(), withDefault(num('blend', 'blend (s)'), 0)],
outputs: [exec()],
params: [],
run: (_ctx, inputs) => {
+12 -11
View File
@@ -392,20 +392,21 @@ export interface AnimationPlayer {
}
/**
* A crossfade into a player that just took over (#106): over `frames` of its own, a row that blends
* mixes from what the property held into the player's value. `from` is taken on the first tick of the
* blend; a property an earlier player writes in the same tick is mixed from that value instead, live.
* A crossfade into a player that just took over (#106): over `seconds`, whatever the animation's frame
* rate, a row that blends mixes from what the property held into the player's value. `from` is taken
* on the first tick of the blend; a property an earlier player writes in the same tick is mixed from
* that value instead, live.
*/
export interface AnimationBlend {
frames: number;
seconds: number;
elapsed: number;
from?: Record<string, PropertyValue>;
}
/** How far a blend has come, 0…1; an animation without a frame rate has nothing to blend over. */
export function blendShare(blend: AnimationBlend, fps: number): number {
if (!(blend.frames > 0) || !(fps > 0)) return 1;
return Math.min(1, (blend.elapsed * fps) / blend.frames);
/** How far a blend has come, 0…1. */
export function blendShare(blend: AnimationBlend): number {
if (!(blend.seconds > 0)) return 1;
return Math.min(1, blend.elapsed / blend.seconds);
}
interface PlayerHolder {
@@ -418,13 +419,13 @@ let startCount = 0;
* Starts the animation on the object, or lets a paused one run on. One already running keeps its
* frame, so a graph may ask for it every frame without holding it at its first frame – but it counts
* as started last again: turning back to a walk that still runs has to show that walk.
* `blendFrames` crossfades into it – but only when it takes over: asked for again while it already
* `blendSeconds` crossfades into it – but only when it takes over: asked for again while it already
* leads on the object, it keeps the blend it has, so a graph calling every frame does not restart it.
*/
export function playAnimation(holder: PlayerHolder, animationId: string, blendFrames = 0): void {
export function playAnimation(holder: PlayerHolder, animationId: string, blendSeconds = 0): void {
const players = (holder.animations ??= []);
const known = players.find((p) => p.animationId === animationId);
const blend = blendFrames > 0 ? { frames: blendFrames, elapsed: 0 } : undefined;
const blend = blendSeconds > 0 ? { seconds: blendSeconds, elapsed: 0 } : undefined;
if (known) {
const leads = players.every((p) => p.order <= known.order);
known.playing = true;
+1 -2
View File
@@ -7,7 +7,6 @@ import {
blendShare,
blends,
blendValue,
fpsOf,
isPositionRow,
playbackOf,
trackWriteAt,
@@ -73,7 +72,7 @@ export class AnimationSystem implements System {
if (blend) blend.elapsed += dt;
}
const head = animationPlayheadAt(asset, player.time);
const share = blend ? blendShare(blend, fpsOf(asset)) : 1;
const share = blend ? blendShare(blend) : 1;
for (const b of bindings) {
let value = trackWriteAt(b.track, b.kind, head.frame, player.bases[b.base], head.reverse);
if (value === undefined) continue;