# mix.ludic - sample one clip at a time into per-node buffers, mixed in at a weight. `rot` is 4 per # node, `pos` 3 per node, and `hit` says which nodes have received a value (bit 0 a rotation, bit 1 # a translation), so a node the clips never mention stays at rest export function anim_mix(anim_st: mut AnimState, c: Clip, sk: Skin, t: float, w: float, rot: floats, pos: floats, hit: words) -> void { if c == null or sk == null { return } if anim_st.q_a == null { anim_st.q_a = floats(4) anim_st.q_b = floats(4) anim_st.q_cur = floats(4) } var tt = t if c.dur > 0.0 { tt = t % c.dur } for ch in 0 .. c.n { let node = c.cnode[ch] if node < 0 or node >= sk.n_nodes { continue } let n = c.ckeys[ch] let o = c.coff[ch] let i0 = anim_key_at(c, ch, tt) var i1 = i0 + 1 if i1 > n - 1 { i1 = n - 1 } var f = 0.0 if i1 > i0 { let span = c.times[o + i1] - c.times[o + i0] if span > 0.000001 { f = Math.clamp((tt - c.times[o + i0]) / span, 0.0, 1.0) } } if c.cpath[ch] == AC_ROT { mix_rot(anim_st, c, ch, node, i0, i1, f, w, rot, hit) } else { mix_pos(c, ch, node, i0, i1, f, w, pos, hit) } } } # Across clips the first contributor lands and the rest are nlerped onto it by their share, which # is what makes a cross-fade rather than a jump. Test the ROTATION bit, not the whole word: glTF # writes a node's translation before its rotation, and `hit == 0` then skipped every rotation. function mix_rot(anim_st: mut AnimState, c: Clip, ch: int, node: int, i0: int, i1: int, f: float, w: float, rot: floats, hit: words) -> void { let vo = c.cvoff[ch] for e in 0 .. 4 { anim_st.q_a[e] = c.vals[vo + i0 * 4 + e] anim_st.q_b[e] = c.vals[vo + i1 * 4 + e] } q_nlerp(anim_st.q_a, anim_st.q_a, anim_st.q_b, f) if (hit[node] & 1) == 0 { q_store(rot, node, anim_st.q_a) hit[node] = hit[node] | 1 } else { q_load(anim_st.q_cur, rot, node) q_nlerp(anim_st.q_cur, anim_st.q_cur, anim_st.q_a, w) q_store(rot, node, anim_st.q_cur) } } function mix_pos(c: Clip, ch: int, node: int, i0: int, i1: int, f: float, w: float, pos: floats, hit: words) -> void { let vo = c.cvoff[ch] let ax = Math.lerp(c.vals[vo + i0 * 3], c.vals[vo + i1 * 3], f) let ay = Math.lerp(c.vals[vo + i0 * 3 + 1], c.vals[vo + i1 * 3 + 1], f) let az = Math.lerp(c.vals[vo + i0 * 3 + 2], c.vals[vo + i1 * 3 + 2], f) if (hit[node] & 2) == 0 { pos[node * 3] = ax pos[node * 3 + 1] = ay pos[node * 3 + 2] = az hit[node] = hit[node] | 2 } else { pos[node * 3] = Math.lerp(pos[node * 3], ax, w) pos[node * 3 + 1] = Math.lerp(pos[node * 3 + 1], ay, w) pos[node * 3 + 2] = Math.lerp(pos[node * 3 + 2], az, w) } }