feat(packages): ludic.anim - glTF animation clips read out of the parsed document, sampled by halving, cross-faded two at a time and folded into a render3d Skin's pose; clips per rig by name, translations off unless asked, a late first key reported
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ab5584f421
commit
c446c07a4c
9 changed files with 530 additions and 0 deletions
78
packages/ludic.anim/pose.ludic
Normal file
78
packages/ludic.anim/pose.ludic
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# pose.ludic - the sampled LOCAL transforms turned into the model-frame deltas `skin_pose` wants.
|
||||
# skin_pose folds a delta D in as local = (G_p^-1 . D . G_p) . R_rest, and a channel gives the
|
||||
# absolute local L, so D = G_p . (L . R_rest^-1) . G_p^-1 (a root: D = L . R_rest^-1)
|
||||
var parts: []floats = null
|
||||
var rot_s: floats = null
|
||||
var pos_s: floats = null
|
||||
var hit_s: words = null
|
||||
var cap: int = 0
|
||||
|
||||
function part(i: int) -> floats {
|
||||
if parts == null {
|
||||
parts = new []floats
|
||||
for k in 0 .. 8 { push(parts, floats(4)) }
|
||||
}
|
||||
return parts[i]
|
||||
}
|
||||
|
||||
export function anim_to_pose(sk: Skin, rot: floats, pos: floats, hit: words) -> void {
|
||||
let l = part(0)
|
||||
let rinv = part(1)
|
||||
let gp = part(2)
|
||||
let gpi = part(3)
|
||||
let tmp = part(4)
|
||||
let d = part(5)
|
||||
let v = part(6)
|
||||
for i in 0 .. sk.n_nodes {
|
||||
let p = sk.par[i]
|
||||
if (hit[i] & 1) != 0 {
|
||||
q_load(l, rot, i)
|
||||
q_load(rinv, sk.rest_r, i)
|
||||
q_conj(rinv, rinv)
|
||||
q_mul(tmp, l, rinv)
|
||||
if p >= 0 {
|
||||
q_load(gp, sk.rest_g, p)
|
||||
q_conj(gpi, gp)
|
||||
q_mul(d, gp, tmp)
|
||||
q_mul(d, d, gpi)
|
||||
} else { q_copy(d, tmp) }
|
||||
q_normalize(d)
|
||||
skin_set_quat(sk, i, d)
|
||||
}
|
||||
if (hit[i] & 2) != 0 and positions {
|
||||
let dx = pos[i * 3] - sk.rest_t[i * 3]
|
||||
let dy = pos[i * 3 + 1] - sk.rest_t[i * 3 + 1]
|
||||
let dz = pos[i * 3 + 2] - sk.rest_t[i * 3 + 2]
|
||||
if p >= 0 {
|
||||
q_load(gp, sk.rest_g, p)
|
||||
v3_set(v, dx, dy, dz)
|
||||
q_rotate(v, gp, v)
|
||||
skin_set_offset(sk, i, v[0], v[1], v[2])
|
||||
} else { skin_set_offset(sk, i, dx, dy, dz) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# The scratch is made ONCE and grown, never allocated per call: a call is per actor per frame.
|
||||
function scratch(n: int) -> void {
|
||||
if cap >= n { return }
|
||||
rot_s = floats(n * 4)
|
||||
pos_s = floats(n * 3)
|
||||
hit_s = words(n)
|
||||
cap = n
|
||||
}
|
||||
|
||||
# Play `a` at time `ta`, cross-faded toward `b` at `tb` by `blend` (0 is all a, 1 is all b), and
|
||||
# fold it into the joint matrices. `b` may be null. False when there is no skin or no clip.
|
||||
export function anim_play(sk: Skin, a: Clip, ta: float, b: Clip, tb: float, blend: float) -> bool {
|
||||
if sk == null or a == null { return false }
|
||||
let n = sk.n_nodes
|
||||
scratch(n)
|
||||
for i in 0 .. n { hit_s[i] = 0 }
|
||||
skin_reset(sk)
|
||||
anim_mix(a, sk, ta, 1.0, rot_s, pos_s, hit_s)
|
||||
if b != null and blend > 0.001 { anim_mix(b, sk, tb, blend, rot_s, pos_s, hit_s) }
|
||||
anim_to_pose(sk, rot_s, pos_s, hit_s)
|
||||
skin_pose(sk)
|
||||
return true
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue