From e5148ac4b87d88d4ac31a40fe6f09a0b2b9671f4 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Tue, 24 Feb 2026 23:42:06 +0000 Subject: [PATCH] collada_scene/animate: implement bezier interpolation --- _math.lua | 69 ++++++++++++++++-- collada_scene/animate.lua | 79 +++++++++++++++++++- scene/test/test.lua | 150 +++++++++++++++++++------------------- 3 files changed, 215 insertions(+), 83 deletions(-) diff --git a/_math.lua b/_math.lua index 214b659..a090f66 100644 --- a/_math.lua +++ b/_math.lua @@ -552,6 +552,61 @@ mat4 = { setmetatable(mat4, mat4) +vec2 = { + __call = function(_t, x, y, z) + -- newByteData is zero-initialized + local data = love.data.newByteData(2 * 4) + local f = ffi.cast('float*', data:getFFIPointer()) + local value = { + data = data, + f = f, + } + f[0] = x + f[1] = y + setmetatable(value, vec2) + return value + end, + + load_table = function(t) + assert(#t == 2) + assert(t[1] ~= nil) + assert(t[2] ~= nil) + return vec2(t[1], t[2]) + end, + + set_x = function(v, value) + return vec2(value, v.f[1]) + end, + + set_y = function(v, value) + return vec2(v.f[0], value) + end, + + get_x = function(v) + return v.f[0] + end, + + get_y = function(v) + return v.f[1] + end, + + __mul = function(v, s) + return vec2(v.f[0] * s, + v.f[1] * s) + end, + + __add = function(v1, v2) + return vec2(v1.f[0] + v2.f[0], + v1.f[1] + v2.f[1]) + end, + + print = function(v) + print(tostring(v.f[0]) .. " " .. tostring(v.f[1])) + end, +} + +setmetatable(vec2, vec2) + vec3 = { __call = function(_t, x, y, z) -- newByteData is zero-initialized @@ -561,9 +616,9 @@ vec3 = { data = data, f = f, } - f[0] = x or 0 - f[1] = y or 0 - f[2] = z or 0 + f[0] = x + f[1] = y + f[2] = z setmetatable(value, vec3) return value end, @@ -732,10 +787,10 @@ vec4 = { data = data, f = f, } - f[0] = x or 0 - f[1] = y or 0 - f[2] = z or 0 - f[3] = w or 0 + f[0] = x + f[1] = y + f[2] = z + f[3] = w setmetatable(value, vec4) return value end, diff --git a/collada_scene/animate.lua b/collada_scene/animate.lua index 06d1834..93098e7 100644 --- a/collada_scene/animate.lua +++ b/collada_scene/animate.lua @@ -35,6 +35,82 @@ local linear_interpolate_value = function(source, frame_ix, parameter_ix, iv) return prev + iv * (next - prev) end + +local pow3 = function(f) + return f * f * f +end + +local pow2 = function(f) + return f * f +end + +local bezier = function(p0, c0, c1, p1, s) + return + (p0 * pow3(1 - s)) + + (c0 * 3 * s * pow2(1 - s)) + + (c1 * 3 * pow2(s) * (1 - s)) + + (p1 * pow3(s)) +end + +local bezier_binary_search = function(p0, c0, c1, p1, want) + local low = 0.0 + local high = 1.0 + + local iterations = 0 + while iterations < 20 do + iterations = iterations + 1 + + local s = (high + low) * 0.5 + local bs = bezier(p0, c0, c1, p1, s) + local t = vec2.get_x(bs) + + local epsilon = 0.001 + if (math.abs(t - want) < epsilon) then + return vec2.get_y(bs) + end + + if t > want then + high = s + else + low = s + end + end + + print(vec2.get_x(p0), vec2.get_y(p0)) + print(vec2.get_x(c0), vec2.get_y(c0)) + print(vec2.get_x(c1), vec2.get_y(c1)) + print(vec2.get_x(p1), vec2.get_y(p1)) + assert(false) +end + +local tangent_index = function(source, frame_ix, parameter_ix) + local ix = frame_ix * source.stride + parameter_ix * 2 + x = source.float_array[ix + 0 + 1] + y = source.float_array[ix + 1 + 1] + return {x, y} +end + +local bezier_sampler = function(sampler, frame_ix, parameter_ix, t) + -- P0 is (INPUT[i] , OUTPUT[i]) + -- C0 (or T0) is (OUT_TANGENT[i][0] , OUT_TANGENT[i][1]) + -- C1 (or T1) is (IN_TANGENT[i+1][0], IN_TANGENT[i+1][1]) + -- P1 is (INPUT[i+1], OUTPUT[i+1]) + + local frame0_input = sampler.input.float_array[frame_ix + 0 + 1] + local frame1_input = sampler.input.float_array[frame_ix + 1 + 1] + + local frame0_output = sampler.output.float_array[(frame_ix + 0) * sampler.output.stride + parameter_ix + 1] + local frame1_output = sampler.output.float_array[(frame_ix + 1) * sampler.output.stride + parameter_ix + 1] + + local p0 = vec2(frame0_input, frame0_output) + local c0 = vec2.load_table(tangent_index(sampler.out_tangent, frame_ix + 0, parameter_ix)) + local c1 = vec2.load_table(tangent_index(sampler.in_tangent, frame_ix + 1, parameter_ix)) + local p1 = vec2(frame1_input, frame1_output) + + return bezier_binary_search(p0, c0, c1, p1, t) +end + + local apply_transform_target = function(transform, channel_target_attribute, value) if transform.type == collada_types.transform_type.TRANSLATE or transform.type == collada_types.transform_type.SCALE then if channel_target_attribute == collada_types.target_attribute.X then @@ -81,7 +157,8 @@ local animate_channel_segment = function(channel, transform, frame_ix, t) for parameter_ix = 0, target_attributes_count-1 do local interpolation = channel.source_sampler.interpolation.interpolation_array[frame_ix] local value - if false then + if interpolation == collada_types.interpolation.BEZIER then + value = bezier_sampler(channel.source_sampler, frame_ix, parameter_ix, t) else local iv = linear_interpolate_iv(channel.source_sampler.input, frame_ix, t) value = linear_interpolate_value(channel.source_sampler.output, frame_ix, parameter_ix, iv) diff --git a/scene/test/test.lua b/scene/test/test.lua index 227186d..605c863 100644 --- a/scene/test/test.lua +++ b/scene/test/test.lua @@ -30,18 +30,18 @@ local array_node_torus_knot23_translation_x_output_array = { -21.94384, } local array_node_torus_knot23_translation_x_intangent_array = { - {-0.3329306, -21.94384}, - {0.667, 7.158293}, - {1.667, 40.64392}, - {2.667, 15.03987}, - {3.222333, -21.94384}, + -0.3329306, -21.94384, + 0.667, 7.158293, + 1.667, 40.64392, + 2.667, 15.03987, + 3.222333, -21.94384, } local array_node_torus_knot23_translation_x_outtangent_array = { - {0.333, -21.94384}, - {1.333, 28.00002}, - {2.333, 40.64392}, - {3.111, -5.801854}, - {3.666264, -21.94384}, + 0.333, -21.94384, + 1.333, 28.00002, + 2.333, 40.64392, + 3.111, -5.801854, + 3.666264, -21.94384, } local array_node_torus_knot23_translation_x_interpolation_array = { collada_types.interpolation.BEZIER, @@ -97,18 +97,18 @@ local array_node_torus_knot23_translation_y_output_array = { -1.68812e-14, } local array_node_torus_knot23_translation_y_intangent_array = { - {-0.3329306, -1.68812e-14}, - {0.667, 24.27013}, - {1.667, -12.4935}, - {2.667, 9.835234}, - {3.222333, -1.68812e-14}, + -0.3329306, -1.68812e-14, + 0.667, 24.27013, + 1.667, -12.4935, + 2.667, 9.835234, + 3.222333, -1.68812e-14, } local array_node_torus_knot23_translation_y_outtangent_array = { - {0.333, -1.68812e-14}, - {1.333, 24.27013}, - {2.333, -12.4935}, - {3.111, 9.835234}, - {3.666264, -1.68812e-14}, + 0.333, -1.68812e-14, + 1.333, 24.27013, + 2.333, -12.4935, + 3.111, 9.835234, + 3.666264, -1.68812e-14, } local array_node_torus_knot23_translation_y_interpolation_array = { collada_types.interpolation.BEZIER, @@ -164,18 +164,18 @@ local array_node_torus_knot23_translation_z_output_array = { 45.45129, } local array_node_torus_knot23_translation_z_intangent_array = { - {-0.3329306, 45.45129}, - {0.667, 45.45129}, - {1.667, 45.45129}, - {2.667, 45.45129}, - {3.222333, 45.45129}, + -0.3329306, 45.45129, + 0.667, 45.45129, + 1.667, 45.45129, + 2.667, 45.45129, + 3.222333, 45.45129, } local array_node_torus_knot23_translation_z_outtangent_array = { - {0.333, 45.45129}, - {1.333, 45.45129}, - {2.333, 45.45129}, - {3.111, 45.45129}, - {3.666264, 45.45129}, + 0.333, 45.45129, + 1.333, 45.45129, + 2.333, 45.45129, + 3.111, 45.45129, + 3.666264, 45.45129, } local array_node_torus_knot23_translation_z_interpolation_array = { collada_types.interpolation.BEZIER, @@ -235,22 +235,22 @@ local array_node_torusknot25_rotationz_angle_output_array = { -2.386905, } local array_node_torusknot25_rotationz_angle_intangent_array = { - {-0.3329306, -2.386905}, - {0.3335, -38.2941}, - {0.8335, -59.67817}, - {1.3335, -40.14935}, - {1.8335, -99.69791}, - {2.3335, -163.7238}, - {3.055833, -2.386905}, + -0.3329306, -2.386905, + 0.3335, -38.2941, + 0.8335, -59.67817, + 1.3335, -40.14935, + 1.8335, -99.69791, + 2.3335, -163.7238, + 3.055833, -2.386905, } local array_node_torusknot25_rotationz_angle_outtangent_array = { - {0.1665, -2.386905}, - {0.6665, -57.37209}, - {1.1665, -59.67817}, - {1.6665, -40.14935}, - {2.1665, -140.8482}, - {2.7775, -163.7238}, - {3.666264, -2.386905}, + 0.1665, -2.386905, + 0.6665, -57.37209, + 1.1665, -59.67817, + 1.6665, -40.14935, + 2.1665, -140.8482, + 2.7775, -163.7238, + 3.666264, -2.386905, } local array_node_torusknot25_rotationz_angle_interpolation_array = { collada_types.interpolation.BEZIER, @@ -312,22 +312,22 @@ local array_node_torusknot25_rotationy_angle_output_array = { -49.62293, } local array_node_torusknot25_rotationy_angle_intangent_array = { - {-0.3329306, -49.62293}, - {0.3335, -29.32237}, - {0.8335, 34.11597}, - {1.3335, -56.85069}, - {1.8335, -56.85069}, - {2.3335, -170.1778}, - {3.055833, -49.62293}, + -0.3329306, -49.62293, + 0.3335, -29.32237, + 0.8335, 34.11597, + 1.3335, -56.85069, + 1.8335, -56.85069, + 2.3335, -170.1778, + 3.055833, -49.62293, } local array_node_torusknot25_rotationy_angle_outtangent_array = { - {0.1665, -49.62293}, - {0.6665, -1.437308}, - {1.1665, 34.11597}, - {1.6665, -56.85069}, - {2.1665, -56.85069}, - {2.7775, -170.1778}, - {3.666264, -49.62293}, + 0.1665, -49.62293, + 0.6665, -1.437308, + 1.1665, 34.11597, + 1.6665, -56.85069, + 2.1665, -56.85069, + 2.7775, -170.1778, + 3.666264, -49.62293, } local array_node_torusknot25_rotationy_angle_interpolation_array = { collada_types.interpolation.BEZIER, @@ -389,22 +389,22 @@ local array_node_torusknot25_rotationx_angle_output_array = { 183.132, } local array_node_torusknot25_rotationx_angle_intangent_array = { - {-0.3329306, 183.132}, - {0.3335, 256.4932}, - {0.8335, 216.862}, - {1.3335, 133.4248}, - {1.8335, 133.4248}, - {2.3335, 146.1407}, - {3.055833, 183.132}, + -0.3329306, 183.132, + 0.3335, 256.4932, + 0.8335, 216.862, + 1.3335, 133.4248, + 1.8335, 133.4248, + 2.3335, 146.1407, + 3.055833, 183.132, } local array_node_torusknot25_rotationx_angle_outtangent_array = { - {0.1665, 183.132}, - {0.6665, 256.4932}, - {1.1665, 175.8802}, - {1.6665, 133.4248}, - {2.1665, 133.4248}, - {2.7775, 162.6932}, - {3.666264, 183.132}, + 0.1665, 183.132, + 0.6665, 256.4932, + 1.1665, 175.8802, + 1.6665, 133.4248, + 2.1665, 133.4248, + 2.7775, 162.6932, + 3.666264, 183.132, } local array_node_torusknot25_rotationx_angle_interpolation_array = { collada_types.interpolation.BEZIER, @@ -825,9 +825,9 @@ local instance_controllers_node_torus_knot23 = { local instance_lights_node_torus_knot23 = { } local node_channels_node_torus_knot23 = { - node_channel_node_torus_knot23_translation_y, - node_channel_node_torus_knot23_translation_x, node_channel_node_torus_knot23_translation_z, + node_channel_node_torus_knot23_translation_x, + node_channel_node_torus_knot23_translation_y, } local node_node_torus_knot23 = { parent_index = -1, @@ -903,9 +903,9 @@ local instance_controllers_node_torusknot25 = { local instance_lights_node_torusknot25 = { } local node_channels_node_torusknot25 = { + node_channel_node_torusknot25_rotationy_angle, node_channel_node_torusknot25_rotationz_angle, node_channel_node_torusknot25_rotationx_angle, - node_channel_node_torusknot25_rotationy_angle, } local node_node_torusknot25 = { parent_index = -1,