ludic/packages/ludic.anim
2026-09-25 08:12:29 +03:00
..
tests 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 2026-09-25 08:12:29 +03:00
clip.ludic 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 2026-09-25 08:12:29 +03:00
index.ludic 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 2026-09-25 08:12:29 +03:00
mix.ludic 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 2026-09-25 08:12:29 +03:00
package.ludic 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 2026-09-25 08:12:29 +03:00
pose.ludic 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 2026-09-25 08:12:29 +03:00
read.ludic 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 2026-09-25 08:12:29 +03:00
README.md 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 2026-09-25 08:12:29 +03:00

ludic.anim

The animation clips an animator keyed into a glTF, played on a render3d Skin and cross-faded two at a time. ludic.render3d parses no animations and offers a pose api (skin_set_quat, skin_set_offset, skin_pose) rather than a player, so the sampler lives here. Uses ludic_render3d and nothing else: it reads the document gltf_load just parsed and writes a Skin's pose. It asks the game nothing, so it has no port.

import "ludic.anim"
let m = gltf_load("assets/kit/hiker2", "hiker_m.gltf", "hiker_m")
anim_read_doc("assets/kit/hiker2")                 # straight after the load, before the next
let walk = anim_find("hiker_m_walk")
anim_play(sk, walk, t, idle, t2, 0.3)              # 70% walk, 30% idle, into the joint matrices

API

Clip { name, dur, n, cnode, cpath, ckeys, coff, cvoff, times, vals }, AC_ROT, AC_POS a clip, its channels flattened: a channel's keys are times[coff..coff+ckeys], its values 4 (rotation) or 3 (translation) per key from cvoff
anim_read_doc(dir) -> int every rotation and translation channel of every animation in gltf_doc, reading the .bin beside it in dir; how many clips were new
anim_find(name) -> Clip, anim_count(), anim_add(c) -> bool, anim_clear() the table, by name; a name already held is skipped on read and refused on add
anim_play(sk, a, ta, b, tb, blend) -> bool a at ta, cross-faded toward b at tb by blend (0 all a, 1 all b; b may be null), folded into the joint matrices
anim_mix(c, sk, t, w, rot, pos, hit), anim_to_pose(sk, rot, pos, hit), anim_key_at(c, ch, t) the pieces anim_play is made of, for a caller that mixes more than two
anim_set_positions(on), anim_positions() keyed translations applied or not (off: baking keys location on every bone, and rounding down an eighteen-bone chain is a collapsed animal)
anim_first_key(c) -> float the earliest key; a read warns when it is not zero

Rules it keeps

  • Clips are named per rig. A channel addresses its FILE's node indices, two files number their nodes differently, and a name already held is skipped - so one shared name would hand the second body the first one's skeleton. Name them hiker_m_walk / hiker_f_walk, bear_walk.
  • Read straight after the load. gltf_doc is whichever file was parsed last, and gltf_load closes the .bin, which anim_read_doc reopens (a read through the closed handle is zeros).
  • Key from frame zero. A clip wraps on its duration and holds below its first key, so a clip keyed from frame 1 holds its opening pose for 1/30 s every cycle. anim_read_doc prints it.
  • The math. skin_pose takes a delta D in the model frame, 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.
  • The scratch is made once and grown: a call is per actor per frame.

Tests

ludic test packages/ludic.anim

A two-bone skin made from a glTF document with no GPU: sampling, wrapping, the cross-fade, translations off by default, and clips read out of a .bin written to the temp directory.