voxelcore/res/modules/animation.lua
2026-09-29 18:59:28 +03:00

156 lines
3.7 KiB
Lua

local internals = __vc_internals
local function bezier(a, b, c, d, u)
local s = 1 - u
return s * s * s * a +
3 * s * s * u * b +
3 * s * u * u * c +
u * u * u * d
end
local function bezier_derivative(a, b, c, d, u)
local s = 1 - u
return
3 * s * s * (b - a) +
6 * s * u * (c - b) +
3 * u * u * (d - c)
end
local function bezier_interpolation(k0, k1, t)
local frame = k0.frame + t * (k1.frame - k0.frame)
local u = t
for i=1,8 do
local x = bezier(
k0.frame, k0.rx + k0.frame, k1.lx + k1.frame, k1.frame, u)
local dx = bezier_derivative(
k0.frame, k0.rx + k0.frame, k1.lx + k1.frame, k1.frame, u)
if math.abs(dx) < 1e-8 then
break
end
u = u - (x - frame) / dx
if u < 0 then u = 0 end
if u > 1 then u = 1 end
end
return bezier(k0.value, k0.ry + k0.value, k1.ly + k1.value, k1.value, u)
end
local this = {
CH_TRANSLATE = 1,
CH_ROTATE = 2,
CH_SCALE = 3,
CH_ZOOM = 4,
CH_TEXTURE = 5,
INT_CONST = 1,
INT_LINEAR = 2,
INT_BEZIER = 3,
INT_CUSTOM = 4,
MAX_DURATION = 1e9,
MAX_FRAMES = 1e9,
TRACE_CODEGEN = false,
maths = {
bezier_interpolation = bezier_interpolation
}
}
local loaded_tracks = {}
local backup_tracks = {}
function internals.store_animation(name, track)
loaded_tracks[name] = track
end
function this.get_track(identifier)
return loaded_tracks[identifier]
end
local running_actions = {}
local playing_tracks = {}
function this.action(func)
table.insert(running_actions, coroutine.create(func))
end
local PlayingTrack = {
__index = {
stop = function(self)
table.remove_value(playing_tracks, self)
self.__timer = 0.0
self.__playing = false
end,
pause = function(self)
table.remove_value(playing_tracks, self)
self.__playing = false
end,
resume = function(self)
if not self.__playing then
table.insert(playing_tracks, self)
self.__playing = true
end
end
}
}
function this.play(name, target)
if target and target.reset_pose then
target:reset_pose()
end
local track = setmetatable({
name = name,
target = target,
__timer = 0.0,
__playing = true,
}, PlayingTrack)
table.insert(playing_tracks, track)
return track
end
function internals.on_animation_frame()
for i=1,#running_actions do
local co = running_actions[i]
local status, result = coroutine.resume(co)
if not status then
debug.error("error in animation action: "..result)
table.remove(running_actions, i)
elseif coroutine.status(co) == "dead" then
table.remove(running_actions, i)
end
end
local delta = time.delta()
for i=1,#playing_tracks do
local track_info = playing_tracks[i]
local track = loaded_tracks[track_info.name]
if not track then
debug.error("animation track not found: "..track_info.name)
table.remove(playing_tracks, i)
else
track_info.__timer = track_info.__timer + delta
if track_info.__timer > track.duration then
table.remove(playing_tracks, i)
else
track.func(track_info.target, track_info.__timer)
end
end
end
end
function internals.stop_animation_actions()
running_actions = {}
end
function internals.backup_and_clear_animation()
loaded_tracks, backup_tracks = backup_tracks, {}
end
function internals.restore_animation_backup()
loaded_tracks, backup_tracks = backup_tracks, {}
end
return this