Overview
Second slice of #260. Live blend two skeletal animations on the active entity with an adjustable weight, then bake the blended result into a new clip.
Slice A (#356) shipped speed scaling + per-entity loop region; this slice builds on that foundation.
Scope
- New singleton AnimationBlender (or extension of
AnimationControlController) that holds:
animA, animB — names of the two animations on the active entity
weight — 0..1
mode — mix (default), additive, override
- live state controlled per-frame in
MainWindow::frameRenderingQueued
- Mix:
stateA->setWeight(1 - w), stateB->setWeight(w), both enabled.
- Additive:
Skeleton::setBlendMode(ANIMBLEND_CUMULATIVE) on the active entity for the duration of the preview.
- Override: simple A/B selection — only one state enabled, no blend curve (useful for quick swap previews).
- Bake: sample the blended state at a fixed dt (default 30 fps), capture each bone's derived position/orientation/scale after
_notifyDirty()+update(), write a new Animation track to the entity's skeleton named Blended_<animA>_<animB> (or user-supplied). Reuse AnimationMerger write helpers where possible.
QML
Inline in the Animations section of PropertiesPanel.qml (next to Play/Speed):
- Two animation pickers (filtered to the active entity's clips)
- Weight slider 0..1
- Mode dropdown (Mix / Additive / Override)
- "Bake to new clip" button + optional clip-name input
Tests
Pure-data fixture (no Ogre):
- weight=0 ⇒ pure A
- weight=1 ⇒ pure B
- weight=0.5 ⇒ midpoint of a synthesized position track
Ogre fixture (Linux CI):
- After bake, the new animation exists on the skeleton with the expected length and keyframe count
- Sampling the new animation at midpoint matches the live blend output
Out of scope
- Blend trees / state machines (multi-state graphs).
- Cross-entity blending — restricted to the active entity's own skeleton.
- More than two animations at once. (Plan defers N-way.)
Acceptance criteria
Overview
Second slice of #260. Live blend two skeletal animations on the active entity with an adjustable weight, then bake the blended result into a new clip.
Slice A (#356) shipped speed scaling + per-entity loop region; this slice builds on that foundation.
Scope
AnimationControlController) that holds:animA,animB— names of the two animations on the active entityweight— 0..1mode—mix(default),additive,overrideMainWindow::frameRenderingQueuedstateA->setWeight(1 - w),stateB->setWeight(w), both enabled.Skeleton::setBlendMode(ANIMBLEND_CUMULATIVE)on the active entity for the duration of the preview._notifyDirty()+update(), write a newAnimationtrack to the entity's skeleton namedBlended_<animA>_<animB>(or user-supplied). ReuseAnimationMergerwrite helpers where possible.QML
Inline in the Animations section of
PropertiesPanel.qml(next to Play/Speed):Tests
Pure-data fixture (no Ogre):
Ogre fixture (Linux CI):
Out of scope
Acceptance criteria