wip(0.S3): the packages migrated by ludic migrate state packages - every package test green
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1e8b5b0523
commit
07505e7ef2
294 changed files with 14996 additions and 14675 deletions
|
|
@ -16,37 +16,47 @@ export property Clip {
|
|||
vals: floats
|
||||
}
|
||||
|
||||
var clips: []Clip = null
|
||||
export state AnimState {
|
||||
clips: []Clip = null
|
||||
positions: bool = false
|
||||
q_a: floats = null
|
||||
q_b: floats = null
|
||||
q_cur: floats = null
|
||||
parts: []floats = null
|
||||
rot_s: floats = null
|
||||
pos_s: floats = null
|
||||
hit_s: words = null
|
||||
cap: int = 0
|
||||
}
|
||||
# Keyed translations are off unless asked for: baking keys location on every bone, and a skeleton
|
||||
# does not translate its joints, so applying them turns rounding into a pose.
|
||||
var positions: bool = false
|
||||
|
||||
function clips_init() -> void {
|
||||
if clips == null { clips = new []Clip }
|
||||
function clips_init(anim_st: mut AnimState) -> void {
|
||||
if anim_st.clips == null { anim_st.clips = new []Clip }
|
||||
}
|
||||
|
||||
# the clip of that name, or null; names are per file (per body, per species), never shared
|
||||
export function anim_find(name: string) -> Clip {
|
||||
clips_init()
|
||||
for i in 0 .. len(clips) { if clips[i].name == name { return clips[i] } }
|
||||
export function anim_find(anim_st: mut AnimState, name: string) -> Clip {
|
||||
clips_init(anim_st)
|
||||
for i in 0 .. len(anim_st.clips) { if anim_st.clips[i].name == name { return anim_st.clips[i] } }
|
||||
return null
|
||||
}
|
||||
|
||||
export function anim_count() -> int {
|
||||
clips_init()
|
||||
return len(clips)
|
||||
export function anim_count(anim_st: mut AnimState) -> int {
|
||||
clips_init(anim_st)
|
||||
return len(anim_st.clips)
|
||||
}
|
||||
|
||||
# a clip made outside a glTF (a test, a generated clip); a name already held is refused
|
||||
export function anim_add(c: Clip) -> bool {
|
||||
if c == null or anim_find(c.name) != null { return false }
|
||||
push(clips, c)
|
||||
export function anim_add(anim_st: mut AnimState, c: Clip) -> bool {
|
||||
if c == null or anim_find(anim_st, c.name) != null { return false }
|
||||
push(anim_st.clips, c)
|
||||
return true
|
||||
}
|
||||
|
||||
export function anim_clear() -> void { clips = new []Clip }
|
||||
export function anim_set_positions(on: bool) -> void { positions = on }
|
||||
export function anim_positions() -> bool { return positions }
|
||||
export function anim_clear(anim_st: mut AnimState) -> void { anim_st.clips = new []Clip }
|
||||
export function anim_set_positions(anim_st: mut AnimState, on: bool) -> void { anim_st.positions = on }
|
||||
export function anim_positions(anim_st: AnimState) -> bool { return anim_st.positions }
|
||||
|
||||
# Which key pair straddles time t, by halving rather than walking: a grazing clip is three
|
||||
# hundred keys long and a valley can have dozens of animals posed in a frame.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
# 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
|
||||
var q_a: floats = null
|
||||
var q_b: floats = null
|
||||
var q_cur: floats = null
|
||||
|
||||
export function anim_mix(c: Clip, sk: Skin, t: float, w: float, rot: floats, pos: floats, hit: words) -> void {
|
||||
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 q_a == null {
|
||||
q_a = floats(4)
|
||||
q_b = floats(4)
|
||||
q_cur = floats(4)
|
||||
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 }
|
||||
|
|
@ -27,27 +24,27 @@ export function anim_mix(c: Clip, sk: Skin, t: float, w: float, rot: floats, pos
|
|||
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(c, ch, node, i0, i1, f, w, rot, hit) } else { mix_pos(c, ch, node, i0, i1, f, w, pos, hit) }
|
||||
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(c: Clip, ch: int, node: int, i0: int, i1: int, f: float, w: float, rot: floats, hit: words) -> void {
|
||||
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 {
|
||||
q_a[e] = c.vals[vo + i0 * 4 + e]
|
||||
q_b[e] = c.vals[vo + i1 * 4 + e]
|
||||
anim_st.q_a[e] = c.vals[vo + i0 * 4 + e]
|
||||
anim_st.q_b[e] = c.vals[vo + i1 * 4 + e]
|
||||
}
|
||||
q_nlerp(q_a, q_a, q_b, f)
|
||||
q_nlerp(anim_st.q_a, anim_st.q_a, anim_st.q_b, f)
|
||||
if (hit[node] & 1) == 0 {
|
||||
q_store(rot, node, q_a)
|
||||
q_store(rot, node, anim_st.q_a)
|
||||
hit[node] = hit[node] | 1
|
||||
} else {
|
||||
q_load(q_cur, rot, node)
|
||||
q_nlerp(q_cur, q_cur, q_a, w)
|
||||
q_store(rot, node, q_cur)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,23 @@
|
|||
# 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)) }
|
||||
function part(anim_st: mut AnimState, i: int) -> floats {
|
||||
if anim_st.parts == null {
|
||||
anim_st.parts = new []floats
|
||||
for k in 0 .. 8 { push(anim_st.parts, floats(4)) }
|
||||
}
|
||||
return parts[i]
|
||||
return anim_st.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)
|
||||
export function anim_to_pose(anim_st: mut AnimState, sk: Skin, rot: floats, pos: floats, hit: words) -> void {
|
||||
let l = part(anim_st, 0)
|
||||
let rinv = part(anim_st, 1)
|
||||
let gp = part(anim_st, 2)
|
||||
let gpi = part(anim_st, 3)
|
||||
let tmp = part(anim_st, 4)
|
||||
let d = part(anim_st, 5)
|
||||
let v = part(anim_st, 6)
|
||||
for i in 0 .. sk.n_nodes {
|
||||
let p = sk.par[i]
|
||||
if (hit[i] & 1) != 0 {
|
||||
|
|
@ -39,7 +34,7 @@ export function anim_to_pose(sk: Skin, rot: floats, pos: floats, hit: words) ->
|
|||
q_normalize(d)
|
||||
skin_set_quat(sk, i, d)
|
||||
}
|
||||
if (hit[i] & 2) != 0 and positions {
|
||||
if (hit[i] & 2) != 0 and anim_st.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]
|
||||
|
|
@ -54,25 +49,25 @@ export function anim_to_pose(sk: Skin, rot: floats, pos: floats, hit: words) ->
|
|||
}
|
||||
|
||||
# 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
|
||||
function scratch(anim_st: mut AnimState, n: int) -> void {
|
||||
if anim_st.cap >= n { return }
|
||||
anim_st.rot_s = floats(n * 4)
|
||||
anim_st.pos_s = floats(n * 3)
|
||||
anim_st.hit_s = words(n)
|
||||
anim_st.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 {
|
||||
export function anim_play(anim_st: mut AnimState, 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 }
|
||||
scratch(anim_st, n)
|
||||
for i in 0 .. n { anim_st.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)
|
||||
anim_mix(anim_st, a, sk, ta, 1.0, anim_st.rot_s, anim_st.pos_s, anim_st.hit_s)
|
||||
if b != null and blend > 0.001 { anim_mix(anim_st, b, sk, tb, blend, anim_st.rot_s, anim_st.pos_s, anim_st.hit_s) }
|
||||
anim_to_pose(anim_st, sk, anim_st.rot_s, anim_st.pos_s, anim_st.hit_s)
|
||||
skin_pose(sk)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,31 +3,31 @@
|
|||
|
||||
# Call it straight after a load and before the next one: `gltf_doc` and `gltf_bin` point at
|
||||
# whichever file was read last, so a clip that is not taken now cannot be taken later.
|
||||
export function anim_read_doc(dir: string) -> int {
|
||||
clips_init()
|
||||
if gltf_doc == null { return 0 }
|
||||
if value_has(gltf_doc, "animations") == 0 { return 0 }
|
||||
export function anim_read_doc(anim_st: mut AnimState, render3d_st: mut Render3dState, dir: string) -> int {
|
||||
clips_init(anim_st)
|
||||
if render3d_st.gltf_doc == null { return 0 }
|
||||
if value_has(render3d_st.gltf_doc, "animations") == 0 { return 0 }
|
||||
# REOPEN THE .bin. `gltf_load` closes it before it returns, and an accessor read through a closed
|
||||
# handle returns a buffer of the right size full of zeros: right names, right counts, no motion.
|
||||
let buffers = value_get(gltf_doc, "buffers")
|
||||
let buffers = value_get(render3d_st.gltf_doc, "buffers")
|
||||
if value_count(buffers) == 0 { return 0 }
|
||||
let bin_uri = value_as_str(value_get(value_at(buffers, 0), "uri"))
|
||||
gltf_bin = file_open(dir + "/" + bin_uri, "rb")
|
||||
if gltf_bin == null {
|
||||
render3d_st.gltf_bin = file_open(dir + "/" + bin_uri, "rb")
|
||||
if render3d_st.gltf_bin == null {
|
||||
print(`anim: cannot reopen {dir}/{bin_uri}`)
|
||||
return 0
|
||||
}
|
||||
let arr = value_get(gltf_doc, "animations")
|
||||
let arr = value_get(render3d_st.gltf_doc, "animations")
|
||||
var got = 0
|
||||
for ai in 0 .. value_count(arr) {
|
||||
let c = read_one(value_at(arr, ai))
|
||||
let c = read_one(anim_st, render3d_st, value_at(arr, ai))
|
||||
if c != null {
|
||||
push(clips, c)
|
||||
push(anim_st.clips, c)
|
||||
got += 1
|
||||
}
|
||||
}
|
||||
file_close(gltf_bin)
|
||||
gltf_bin = null
|
||||
file_close(render3d_st.gltf_bin)
|
||||
render3d_st.gltf_bin = null
|
||||
return got
|
||||
}
|
||||
|
||||
|
|
@ -39,10 +39,10 @@ function kept_path(ch: Val) -> string {
|
|||
return path
|
||||
}
|
||||
|
||||
function read_one(a: Val) -> Clip {
|
||||
function read_one(anim_st: mut AnimState, render3d_st: mut Render3dState, a: Val) -> Clip {
|
||||
var nm = ""
|
||||
if value_has(a, "name") != 0 { nm = value_as_str(value_get(a, "name")) }
|
||||
if anim_find(nm) != null { return null } # a second rig's file, same clip
|
||||
if anim_find(anim_st, nm) != null { return null } # a second rig's file, same clip
|
||||
let chans = value_get(a, "channels")
|
||||
let samps = value_get(a, "samplers")
|
||||
let nc = value_count(chans)
|
||||
|
|
@ -55,7 +55,7 @@ function read_one(a: Val) -> Clip {
|
|||
let path = kept_path(ch)
|
||||
if path == "" { continue }
|
||||
let sp = value_at(samps, value_as_int(value_get(ch, "sampler")))
|
||||
let cnt = gltf_accessor_count(value_as_int(value_get(sp, "input")))
|
||||
let cnt = gltf_accessor_count(render3d_st, value_as_int(value_get(sp, "input")))
|
||||
n_keep += 1
|
||||
n_t += cnt
|
||||
if path == "rotation" { n_v += cnt * 4 } else { n_v += cnt * 3 }
|
||||
|
|
@ -81,14 +81,14 @@ function read_one(a: Val) -> Clip {
|
|||
let sp = value_at(samps, value_as_int(value_get(ch, "sampler")))
|
||||
c.cnode[k] = value_as_int(value_get(value_get(ch, "target"), "node"))
|
||||
if path == "rotation" { c.cpath[k] = AC_ROT } else { c.cpath[k] = AC_POS }
|
||||
let tb = gltf_accessor_floats(value_as_int(value_get(sp, "input")))
|
||||
let nk = gltf_count
|
||||
let tb = gltf_accessor_floats(render3d_st, value_as_int(value_get(sp, "input")))
|
||||
let nk = render3d_st.gltf_count
|
||||
c.ckeys[k] = nk
|
||||
c.coff[k] = at
|
||||
for i in 0 .. nk { c.times[at + i] = tb[i] }
|
||||
if nk > 0 and c.times[at + nk - 1] > c.dur { c.dur = c.times[at + nk - 1] }
|
||||
at += nk
|
||||
let vb = gltf_accessor_floats(value_as_int(value_get(sp, "output")))
|
||||
let vb = gltf_accessor_floats(render3d_st, value_as_int(value_get(sp, "output")))
|
||||
var comps = 3
|
||||
if c.cpath[k] == AC_ROT { comps = 4 }
|
||||
c.cvoff[k] = av
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ program AnimTest {
|
|||
function links_runtime() -> bool { return Input.key_pressed(0) }
|
||||
|
||||
# a root and one child 1 m up, no inverse bind matrices, one skin of both
|
||||
function skin() -> Skin {
|
||||
gltf_doc = Json.parse("{\"nodes\": [{\"name\": \"root\", \"children\": [1]}, {\"name\": \"bone\", \"translation\": [0, 1, 0]}], \"skins\": [{\"joints\": [0, 1]}]}")
|
||||
return skin_load(0)
|
||||
function skin(render3d_st: mut Render3dState) -> Skin {
|
||||
render3d_st.gltf_doc = Json.parse("{\"nodes\": [{\"name\": \"root\", \"children\": [1]}, {\"name\": \"bone\", \"translation\": [0, 1, 0]}], \"skins\": [{\"joints\": [0, 1]}]}")
|
||||
return skin_load(render3d_st, 0)
|
||||
}
|
||||
|
||||
# one channel on node 1: identity at 0, a quarter turn about Z at 1 s
|
||||
|
|
@ -44,14 +44,14 @@ program AnimTest {
|
|||
return q[2]
|
||||
}
|
||||
|
||||
test "a clip is found by name, once" {
|
||||
anim_clear()
|
||||
expect(anim_add(turn("hiker_m_walk")))
|
||||
expect(not anim_add(turn("hiker_m_walk")))
|
||||
expect(anim_add(turn("hiker_f_walk")))
|
||||
expect_eq(anim_count(), 2)
|
||||
expect(anim_find("hiker_f_walk") != null)
|
||||
expect(anim_find("bear_walk") == null)
|
||||
test "a clip is found by name, once" (anim_st: mut AnimState) {
|
||||
anim_clear(anim_st)
|
||||
expect(anim_add(anim_st, turn("hiker_m_walk")))
|
||||
expect(not anim_add(anim_st, turn("hiker_m_walk")))
|
||||
expect(anim_add(anim_st, turn("hiker_f_walk")))
|
||||
expect_eq(anim_count(anim_st), 2)
|
||||
expect(anim_find(anim_st, "hiker_f_walk") != null)
|
||||
expect(anim_find(anim_st, "bear_walk") == null)
|
||||
}
|
||||
|
||||
test "a key pair is found by halving" {
|
||||
|
|
@ -61,56 +61,56 @@ program AnimTest {
|
|||
expect_eq(anim_key_at(c, 0, -1.0), 0)
|
||||
}
|
||||
|
||||
test "playing samples between keys, and wraps on the duration" {
|
||||
let sk = skin()
|
||||
test "playing samples between keys, and wraps on the duration" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
let sk = skin(render3d_st)
|
||||
let c = turn("t")
|
||||
expect(anim_play(sk, c, 0.0, null, 0.0, 0.0))
|
||||
expect(anim_play(anim_st, sk, c, 0.0, null, 0.0, 0.0))
|
||||
expect_near(pose_z(sk), 0.0, 0.001)
|
||||
anim_play(sk, c, 1.0 - 0.0001, null, 0.0, 0.0)
|
||||
anim_play(anim_st, sk, c, 1.0 - 0.0001, null, 0.0, 0.0)
|
||||
expect_near(pose_z(sk), HALF, 0.01)
|
||||
anim_play(sk, c, 0.5, null, 0.0, 0.0)
|
||||
anim_play(anim_st, sk, c, 0.5, null, 0.0, 0.0)
|
||||
let mid = pose_z(sk)
|
||||
anim_play(sk, c, 1.5, null, 0.0, 0.0)
|
||||
anim_play(anim_st, sk, c, 1.5, null, 0.0, 0.0)
|
||||
expect_near(pose_z(sk), mid, 0.001)
|
||||
expect(mid > 0.3 and mid < 0.45)
|
||||
}
|
||||
|
||||
test "a cross-fade lands between the two clips" {
|
||||
let sk = skin()
|
||||
test "a cross-fade lands between the two clips" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
let sk = skin(render3d_st)
|
||||
let a = turn("a")
|
||||
let b = turn("b")
|
||||
anim_play(sk, a, 0.0, b, 0.999, 0.5)
|
||||
anim_play(anim_st, sk, a, 0.0, b, 0.999, 0.5)
|
||||
let z = pose_z(sk)
|
||||
expect(z > 0.3 and z < 0.45)
|
||||
anim_play(sk, a, 0.0, b, 0.999, 0.0)
|
||||
anim_play(anim_st, sk, a, 0.0, b, 0.999, 0.0)
|
||||
expect_near(pose_z(sk), 0.0, 0.001)
|
||||
}
|
||||
|
||||
test "a bone is found in the skin's own joints, not the file's first of that name" {
|
||||
gltf_doc = Json.parse("{\"nodes\": [{\"name\": \"bone\"}, {\"name\": \"root\", \"children\": [2]}, {\"name\": \"bone\"}], \"skins\": [{\"joints\": [1, 2]}]}")
|
||||
let sk = skin_load(0)
|
||||
test "a bone is found in the skin's own joints, not the file's first of that name" (render3d_st: mut Render3dState) {
|
||||
render3d_st.gltf_doc = Json.parse("{\"nodes\": [{\"name\": \"bone\"}, {\"name\": \"root\", \"children\": [2]}, {\"name\": \"bone\"}], \"skins\": [{\"joints\": [1, 2]}]}")
|
||||
let sk = skin_load(render3d_st, 0)
|
||||
expect_eq(skin_joint(sk, "bone"), 2)
|
||||
expect_eq(skin_joint_quiet(sk, "wing_l"), -1)
|
||||
expect_eq(skin_joint_quiet(null, "bone"), -1)
|
||||
}
|
||||
|
||||
test "no skin or no clip plays nothing" {
|
||||
expect(not anim_play(null, turn("t"), 0.0, null, 0.0, 0.0))
|
||||
expect(not anim_play(skin(), null, 0.0, null, 0.0, 0.0))
|
||||
test "no skin or no clip plays nothing" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
expect(not anim_play(anim_st, null, turn("t"), 0.0, null, 0.0, 0.0))
|
||||
expect(not anim_play(anim_st, skin(render3d_st), null, 0.0, null, 0.0, 0.0))
|
||||
}
|
||||
|
||||
test "keyed translations are ignored until asked for" {
|
||||
let sk = skin()
|
||||
test "keyed translations are ignored until asked for" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
let sk = skin(render3d_st)
|
||||
let c = turn("t")
|
||||
c.cpath[0] = AC_POS
|
||||
c.vals[0] = 0.0
|
||||
c.vals[1] = 3.0
|
||||
c.vals[3] = 0.0
|
||||
c.vals[4] = 3.0
|
||||
anim_play(sk, c, 0.0, null, 0.0, 0.0)
|
||||
anim_play(anim_st, sk, c, 0.0, null, 0.0, 0.0)
|
||||
expect_near(sk.pose_t[4], 0.0, 0.0001)
|
||||
anim_set_positions(true)
|
||||
anim_play(sk, c, 0.0, null, 0.0, 0.0)
|
||||
anim_set_positions(anim_st, true)
|
||||
anim_play(anim_st, sk, c, 0.0, null, 0.0, 0.0)
|
||||
expect_near(sk.pose_t[4], 2.0, 0.0001)
|
||||
}
|
||||
|
||||
|
|
@ -122,8 +122,8 @@ program AnimTest {
|
|||
b[at + 3] = (v >> 24) & 255
|
||||
}
|
||||
|
||||
test "clips are read out of a glTF's .bin, and a late first key is measured" {
|
||||
anim_clear()
|
||||
test "clips are read out of a glTF's .bin, and a late first key is measured" (anim_st: mut AnimState, render3d_st: mut Render3dState) {
|
||||
anim_clear(anim_st)
|
||||
let dir = Os.temp_dir() + "/ludic-anim-test"
|
||||
Fs.mkdir(dir)
|
||||
let b = buffer(40)
|
||||
|
|
@ -133,10 +133,10 @@ program AnimTest {
|
|||
put_f(b, 8 + 24, HALF)
|
||||
put_f(b, 8 + 28, HALF)
|
||||
expect(Fs.write_bytes(dir + "/t.bin", b, 40))
|
||||
gltf_doc = Json.parse("{\"buffers\": [{\"uri\": \"t.bin\"}], \"bufferViews\": [{\"byteOffset\": 0}, {\"byteOffset\": 8}], \"accessors\": [{\"bufferView\": 0, \"count\": 2, \"type\": \"SCALAR\"}, {\"bufferView\": 1, \"count\": 2, \"type\": \"VEC4\"}], \"animations\": [{\"name\": \"bear_walk\", \"samplers\": [{\"input\": 0, \"output\": 1}], \"channels\": [{\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"rotation\"}}, {\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"scale\"}}]}]}")
|
||||
expect_eq(anim_read_doc(dir), 1)
|
||||
expect_eq(anim_read_doc(dir), 0)
|
||||
let c = anim_find("bear_walk")
|
||||
render3d_st.gltf_doc = Json.parse("{\"buffers\": [{\"uri\": \"t.bin\"}], \"bufferViews\": [{\"byteOffset\": 0}, {\"byteOffset\": 8}], \"accessors\": [{\"bufferView\": 0, \"count\": 2, \"type\": \"SCALAR\"}, {\"bufferView\": 1, \"count\": 2, \"type\": \"VEC4\"}], \"animations\": [{\"name\": \"bear_walk\", \"samplers\": [{\"input\": 0, \"output\": 1}], \"channels\": [{\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"rotation\"}}, {\"sampler\": 0, \"target\": {\"node\": 1, \"path\": \"scale\"}}]}]}")
|
||||
expect_eq(anim_read_doc(anim_st, render3d_st, dir), 1)
|
||||
expect_eq(anim_read_doc(anim_st, render3d_st, dir), 0)
|
||||
let c = anim_find(anim_st, "bear_walk")
|
||||
expect(c != null)
|
||||
expect_eq(c.n, 1)
|
||||
expect_near(c.dur, 1.0, 0.0001)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue