From 3ed80d0c9507b034715885a5fa32de00d266900c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 9 Sep 2026 19:26:17 +0300 Subject: [PATCH] feat: custom curves --- res/modules/animation.lua | 73 ++++++++++++++++++++-------- res/modules/internal/formats/vca.lua | 14 ++++-- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/res/modules/animation.lua b/res/modules/animation.lua index a54a090b1..5b768a70a 100644 --- a/res/modules/animation.lua +++ b/res/modules/animation.lua @@ -9,6 +9,7 @@ local this = { INT_CONST = 1, INT_LINEAR = 2, INT_BEZIER = 3, + INT_CUSTOM = 4, TRACE_CODEGEN = false, } @@ -53,10 +54,7 @@ local function bezier_interpolation(k0, k1, t) return bezier(k0.value, k0.ry, k1.ly, k1.value, u) end -local patterns = { - {name="sint", pattern="sin(t)"}, - {name="sint2", pattern="sin(t * 2)"}, -} +local patterns = {} local exclude_patters = { "end", (string.pattern_safe("'")), @@ -127,9 +125,24 @@ local env = { local t = (frame - left.frame) / (right.frame - left.frame) if interp == INT_BEZIER then return bezier_interpolation(left, right, t) + elseif type(interp) == "function" then + return interp(left.value, right.value, t) end return left.value * (1.0 - t) + right.value * t end, + value_at_custom = function(keys, frame, func) + local left, right = key_neighbors(keys, frame) + if left == right then + return keys[left].value + end + left = keys[left] + right = keys[right] + if left == nil then + return right.value + end + local t = (frame - left.frame) / (right.frame - left.frame) + return func(left, right, t) + end, set_matrix = function(target, matrix) local info = mat4.decompose(matrix) if info then @@ -176,9 +189,18 @@ local function codegen_track(raw_track, lineset, memoised, keysets, use_tsf) keysets[lineset.target_name] = target_keysets end target_keysets[i] = line.keys - code = code .. string.format( - "\n local l%d = value_at(keysets['%s'][%d], t * %s, %s)", - i, lineset.target_name, i, raw_track.fps, line.interp) + + if line.curve_func then + local valueat = string.format("curves[%s]", string.escape(line.curve_func)) + + code = code .. string.format( + "\n local l%d = value_at_custom(keysets['%s'][%d], t * %s, %s)", + i, lineset.target_name, i, raw_track.fps, valueat) + else + code = code .. string.format( + "\n local l%d = value_at(keysets['%s'][%d], t * %s, %s)", + i, lineset.target_name, i, raw_track.fps, line.interp) + end end if line.channel == this.CH_TRANSLATE then @@ -229,7 +251,7 @@ local function codegen_track(raw_track, lineset, memoised, keysets, use_tsf) return code end -local function codegen_rig_target(raw_track, memoised, keysets) +local function codegen_rig_target(raw_track, context) local code = "\n if target.set_matrix and target.index then\n" code = code .. " local dst = DST\n" for bone, lineset in pairs(raw_track.linesets) do @@ -237,7 +259,7 @@ local function codegen_rig_target(raw_track, memoised, keysets) goto continue end local lineset_code = codegen_track( - raw_track, lineset, memoised, keysets, true) + raw_track, lineset, context.memoised, context.keysets, true) code = code .. "\n do" .. lineset_code .. "\n end\n" .. " target:set_matrix(target:index(" .. string.escape(bone) .. "), dst)\n" @@ -246,7 +268,7 @@ local function codegen_rig_target(raw_track, memoised, keysets) return code .. " end" end -local function codegen_object_target(raw_track, memoised, keysets) +local function codegen_object_target(raw_track, context) local code = "\n if target.set_pos then\n" code = code .. " local dst = DST\n" local lineset = raw_track.linesets[""] @@ -254,13 +276,13 @@ local function codegen_object_target(raw_track, memoised, keysets) return "" end local lineset_code = codegen_track( - raw_track, lineset, memoised, keysets, true) + raw_track, lineset, context.memoised, context.keysets, true) code = code .. "\n do" .. lineset_code .. "\n end\n" .. " set_matrix(target, dst)\n" return code .. " end" end -local function codegen_camera_target(raw_track, memoised, keysets) +local function codegen_camera_target(raw_track, context) local code = "\n if target.set_zoom then\n" code = code .. " local zoom = 1.0\n" local lineset = raw_track.linesets[""] @@ -268,7 +290,7 @@ local function codegen_camera_target(raw_track, memoised, keysets) return "" end local lineset_code = codegen_track( - raw_track, lineset, memoised, keysets, false) + raw_track, lineset, context.memoised, context.keysets, false) code = code .. "\n do" .. lineset_code .. "\n end\n" .. " target:set_zoom(zoom)\n" return code .. " end" @@ -276,15 +298,24 @@ end function this.compile_track(raw_track, track_name) local code = "" - local memoised = {} - local keysets = {} + local context = { + memoised = {}, + keysets = {}, + curves = {}, + } + for name, curve in pairs(raw_track.curves) do + context.curves[name] = load(string.format( + "return function(kl, kr, t) return %s end", + process_expression(curve.func, context.memoised) + ), "", "t", env)() + end - code = code .. codegen_rig_target(raw_track, memoised, keysets) - code = code .. codegen_object_target(raw_track, memoised, keysets) - code = code .. codegen_camera_target(raw_track, memoised, keysets) + code = code .. codegen_rig_target(raw_track, context) + code = code .. codegen_object_target(raw_track, context) + code = code .. codegen_camera_target(raw_track, context) local memoised_code = "" - for name, expression in pairs(memoised) do + for name, expression in pairs(context.memoised) do memoised_code = memoised_code .. "\n local " .. name .. " = " .. expression end @@ -302,7 +333,9 @@ function this.compile_track(raw_track, track_name) end local generator, err = load( - src, "", "bt", table.extend({keysets = keysets}, env)) + src, "", "bt", table.extend({ + keysets = context.keysets, curves = context.curves + }, env)) if not generator then error(err) end diff --git a/res/modules/internal/formats/vca.lua b/res/modules/internal/formats/vca.lua index 0faa89404..8146ffff6 100644 --- a/res/modules/internal/formats/vca.lua +++ b/res/modules/internal/formats/vca.lua @@ -1,7 +1,6 @@ local internals = __vc_internals local DEFAULT_FPS = 60 -local INT_BEZIER = animation.INT_BEZIER local action_to_channel = { move = animation.CH_TRANSLATE, @@ -29,14 +28,19 @@ local function parse_configure(raw_track, node) end local function parse_curve(line, node) - line.interp = curve_to_interp[node.curve] + if node.curve:starts_with(".") then + line.interp = animation.INT_CUSTOM + line.curve_func = node.curve:sub(2) + else + line.interp = curve_to_interp[node.curve] + end line.keys = {} for j, key_node in ipairs(node) do local keyframe = { frame = tonumber(key_node.frame), value = tonumber(key_node.value), } - if line.interp == INT_BEZIER then + if line.interp == animation.INT_BEZIER then keyframe.lx = tonumber(key_node.lx) keyframe.ly = tonumber(key_node.ly) keyframe.rx = tonumber(key_node.rx) @@ -51,6 +55,7 @@ local function parse_track(root) duration = math.huge, fps = DEFAULT_FPS, linesets = {}, + curves = {}, } local linesets = raw_track.linesets for i, node in ipairs(root) do @@ -61,6 +66,9 @@ local function parse_track(root) if tag == "configure" then parse_configure(raw_track, node) goto continue + elseif tag == "curve" then + raw_track.curves[node.name] = node + goto continue end local target_type = nil