feat: add sample_animation with linear interpolation
Samples an animation at time t. Wraps t for loops. Finds the keyframe segment containing t and linearly interpolates per-bone angles. Returns frame with angles in degrees (intuitive for tests and callers; internal storage uses radians). Edge cases handled: - t = 0 returns first-keyframe values - t = duration returns last-keyframe values (no overshoot) - t > duration with loop=true wraps via modulo - Keyframes with disjoint bone-sets: each bone interpolated where available, snapped where only one side has it.
This commit is contained in:
54
init.lua
54
init.lua
@@ -178,4 +178,58 @@ function M.build_animation(anim_table, rig)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ====================================================================
|
||||||
|
-- Animation sampling
|
||||||
|
-- ====================================================================
|
||||||
|
function M.sample_animation(anim, t)
|
||||||
|
-- Wrap t for loops.
|
||||||
|
if anim.loop and t > anim.duration then
|
||||||
|
t = t % anim.duration
|
||||||
|
end
|
||||||
|
if t < 0 then t = 0 end
|
||||||
|
if t > anim.duration then t = anim.duration end
|
||||||
|
|
||||||
|
local kfs = anim.keyframes
|
||||||
|
-- Find segment [kf_i, kf_{i+1}] containing t.
|
||||||
|
local prev_kf, next_kf = kfs[1], kfs[1]
|
||||||
|
for i = 1, #kfs - 1 do
|
||||||
|
if t >= kfs[i].t and t <= kfs[i + 1].t then
|
||||||
|
prev_kf = kfs[i]
|
||||||
|
next_kf = kfs[i + 1]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If t equals last keyframe's t (or beyond and not looping), snap to last.
|
||||||
|
if t >= kfs[#kfs].t then
|
||||||
|
prev_kf = kfs[#kfs]
|
||||||
|
next_kf = kfs[#kfs]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Linear interpolation per bone present in either keyframe.
|
||||||
|
local frame = {}
|
||||||
|
local span = next_kf.t - prev_kf.t
|
||||||
|
local alpha = (span > 0) and ((t - prev_kf.t) / span) or 0
|
||||||
|
|
||||||
|
local all_bones = {}
|
||||||
|
for bid, _ in pairs(prev_kf.bones) do all_bones[bid] = true end
|
||||||
|
for bid, _ in pairs(next_kf.bones) do all_bones[bid] = true end
|
||||||
|
|
||||||
|
for bid, _ in pairs(all_bones) do
|
||||||
|
local prev_v = prev_kf.bones[bid]
|
||||||
|
local next_v = next_kf.bones[bid]
|
||||||
|
if prev_v and next_v then
|
||||||
|
frame[bid] = {
|
||||||
|
angle = math.deg(prev_v.angle * (1 - alpha) + next_v.angle * alpha),
|
||||||
|
}
|
||||||
|
elseif prev_v then
|
||||||
|
frame[bid] = { angle = math.deg(prev_v.angle) }
|
||||||
|
elseif next_v then
|
||||||
|
frame[bid] = { angle = math.deg(next_v.angle) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return frame
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
Reference in New Issue
Block a user