collada scene animation

This commit is contained in:
Zack Buhman 2026-04-14 18:43:37 -05:00
parent 8a9198c3e8
commit 779b837caa
6 changed files with 317 additions and 21 deletions

View File

@ -45,7 +45,8 @@ OBJS = \
src/vulkan_helper.o \
src/collada/scene/vulkan.o \
src/collada/scene.o \
src/collada/node_state.o
src/collada/node_state.o \
src/collada/animate.o
SCENES = \
data/scenes/shadow_test/shadow_test.o

15
include/collada/animate.h Normal file
View File

@ -0,0 +1,15 @@
#include "collada/instance_types.h"
namespace collada::animate {
static inline float fract(float f)
{
return f - floorf(f);
}
static inline float loop(float f, float n)
{
return fract(f / n) * n;
}
void animate_node(instance_types::node& node_instance, float t);
}

View File

@ -16,9 +16,9 @@ namespace collada::scene {
void load_scene(types::descriptor const * const descriptor);
void draw();
void update(XMMATRIX const & projection,
XMMATRIX const & view,
float t);
int find_node_index_by_name(const char * name);
void update(float t);
void unload_scene();
};

253
src/collada/animate.cpp Normal file
View File

@ -0,0 +1,253 @@
#include <stdio.h>
#include "directxmath/directxmath.h"
#include "collada/types.h"
#include "collada/instance_types.h"
namespace collada::animate {
struct frame_ix {
int f0;
int f1;
};
static inline frame_ix find_frame_ix(types::source const& source, float t)
{
for (int i = 0; i < source.count - 1; i++) {
if (source.float_array[i] <= t && source.float_array[i+1] > t) {
return {i, i + 1};
}
}
return {source.count - 1, 0};
}
static inline float linear_interpolate_iv(types::source const& source, frame_ix frame_ix, float t)
{
float prev = source.float_array[(frame_ix.f0) * source.stride];
float next = source.float_array[(frame_ix.f1) * source.stride];
return (t - prev) / (next - prev);
}
static inline float linear_interpolate_value(types::source const& source, frame_ix frame_ix, int parameter_ix, float iv)
{
float prev = source.float_array[(frame_ix.f0) * source.stride + parameter_ix];
float next = source.float_array[(frame_ix.f1) * source.stride + parameter_ix];
return prev + iv * (next - prev);
}
static inline XMMATRIX linear_interpolate_matrix(types::source const& source, frame_ix frame_ix, float iv)
{
XMFLOAT4X4 const * prev = (XMFLOAT4X4 const *)&source.float_array[(frame_ix.f0) * source.stride];
XMFLOAT4X4 const * next = (XMFLOAT4X4 const *)&source.float_array[(frame_ix.f1) * source.stride];
XMVECTOR prev_scale;
XMVECTOR prev_rotate;
XMVECTOR prev_translate;
bool prev_srt = XMMatrixDecompose(&prev_scale, &prev_rotate, &prev_translate, XMMatrixTranspose(XMLoadFloat4x4(prev)));
assert(prev_srt == true);
XMVECTOR next_scale;
XMVECTOR next_rotate;
XMVECTOR next_translate;
bool next_srt = XMMatrixDecompose(&next_scale, &next_rotate, &next_translate, XMMatrixTranspose(XMLoadFloat4x4(next)));
assert(next_srt == true);
XMVECTOR scale = XMVectorLerp(prev_scale, next_scale, iv);
XMVECTOR rotate = XMQuaternionSlerp(prev_rotate, next_rotate, iv);
XMVECTOR translate = XMVectorLerp(prev_translate, next_translate, iv);
return XMMatrixAffineTransformation(scale,
XMVectorZero(),
rotate,
translate);
}
static inline float pow3(float f)
{
return f * f * f;
}
static inline float pow2(float f)
{
return f * f;
}
static inline XMVECTOR bezier(XMVECTOR p0, XMVECTOR c0, XMVECTOR c1, XMVECTOR p1, float s)
{
return
p0 * pow3(1 - s)
+ 3 * c0 * s * pow2(1 - s)
+ 3 * c1 * pow2(s) * (1 - s)
+ p1 * pow3(s);
}
static inline float bezier_binary_search(XMVECTOR p0, XMVECTOR c0, XMVECTOR c1, XMVECTOR p1, float want)
{
float low = 0.0f;
float high = 1.0f;
int iterations = 0;
while (iterations < 20) {
iterations += 1;
float s = (high + low) * 0.5f;
XMVECTOR bs = bezier(p0, c0, c1, p1, s);
float t = XMVectorGetX(bs);
const float epsilon = 0.001f;
if (fabsf(t - want) < epsilon) {
return XMVectorGetY(bs);
}
if (t > want) {
high = s;
} else {
low = s;
}
}
fprintf(stderr, "%f %f\n", XMVectorGetX(p0), XMVectorGetY(p0));
fprintf(stderr, "%f %f\n", XMVectorGetX(c0), XMVectorGetY(c0));
fprintf(stderr, "%f %f\n", XMVectorGetX(c1), XMVectorGetY(c1));
fprintf(stderr, "%f %f\n", XMVectorGetX(p1), XMVectorGetY(p1));
assert(false);
}
static inline XMFLOAT2 const * tangent_index(types::source const& source, int frame_ix, int parameter_ix)
{
int ix = frame_ix * source.stride + parameter_ix * 2;
return (XMFLOAT2 const *)&source.float_array[ix];
}
static float bezier_sampler(types::sampler const * const sampler, frame_ix frame_ix, int parameter_ix, float 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])
*/
float frame0_input = sampler->input.float_array[frame_ix.f0];
float frame1_input = sampler->input.float_array[frame_ix.f1];
float frame0_output = sampler->output.float_array[(frame_ix.f0) * sampler->output.stride + parameter_ix];
float frame1_output = sampler->output.float_array[(frame_ix.f1) * sampler->output.stride + parameter_ix];
XMVECTOR p0 = XMVectorSet(frame0_input, frame0_output, 0, 0);
XMVECTOR c0 = XMLoadFloat2(tangent_index(sampler->out_tangent, frame_ix.f0, parameter_ix));
XMVECTOR c1 = XMLoadFloat2(tangent_index(sampler->in_tangent, frame_ix.f1, parameter_ix));
XMVECTOR p1 = XMVectorSet(frame1_input, frame1_output, 0, 0);
return bezier_binary_search(p0, c0, c1, p1, t);
}
static void apply_transform_target(instance_types::transform& transform,
enum types::target_attribute channel_target_attribute,
float value)
{
switch (transform.type) {
case types::transform_type::TRANSLATE: [[fallthrough]];
case types::transform_type::SCALE:
switch (channel_target_attribute) {
case types::target_attribute::X: transform.vector = XMVectorSetX(transform.vector, value); return;
case types::target_attribute::Y: transform.vector = XMVectorSetY(transform.vector, value); return;
case types::target_attribute::Z: transform.vector = XMVectorSetZ(transform.vector, value); return;
default: assert(false);
}
case types::transform_type::ROTATE:
switch (channel_target_attribute) {
case types::target_attribute::X: transform.vector = XMVectorSetX(transform.vector, value); return;
case types::target_attribute::Y: transform.vector = XMVectorSetY(transform.vector, value); return;
case types::target_attribute::Z: transform.vector = XMVectorSetZ(transform.vector, value); return;
case types::target_attribute::ANGLE: transform.vector = XMVectorSetW(transform.vector, value); return;
default: assert(false);
}
default:
assert(false);
break;
}
}
static enum types::target_attribute const rotate_target_attributes[] = {
types::target_attribute::X,
types::target_attribute::Y,
types::target_attribute::Z,
types::target_attribute::ANGLE,
};
static enum types::target_attribute const translate_scale_target_attributes[] = {
types::target_attribute::X,
types::target_attribute::Y,
types::target_attribute::Z,
};
static void animate_channel_segment(types::channel const& channel,
instance_types::transform& transform,
frame_ix frame_ix, float t)
{
enum types::target_attribute const * target_attributes = &channel.target_attribute;
int target_attributes_count = 1;
if (channel.target_attribute == types::target_attribute::ALL) {
switch (transform.type) {
case types::transform_type::TRANSLATE: [[fallthrough]];
case types::transform_type::SCALE:
target_attributes = translate_scale_target_attributes;
target_attributes_count = 3;
break;
case types::transform_type::ROTATE:
target_attributes = rotate_target_attributes;
target_attributes_count = 4;
break;
case types::transform_type::MATRIX:
break;
default:
assert(false);
break;
}
}
assert(channel.source_sampler->interpolation.stride == 1);
if (transform.type == types::transform_type::MATRIX) {
enum types::interpolation interpolation = channel.source_sampler->interpolation.interpolation_array[frame_ix.f0];
assert(interpolation == types::interpolation::LINEAR);
float iv = linear_interpolate_iv(channel.source_sampler->input, frame_ix, t);
XMMATRIX matrix = linear_interpolate_matrix(channel.source_sampler->output, frame_ix, iv);
transform.matrix = matrix;
} else {
for (int parameter_ix = 0; parameter_ix < target_attributes_count; parameter_ix++) {
enum types::interpolation interpolation = channel.source_sampler->interpolation.interpolation_array[frame_ix.f0];
float value;
if (interpolation == types::interpolation::BEZIER) {
value = bezier_sampler(channel.source_sampler, frame_ix, parameter_ix, t);
} else {
float iv = linear_interpolate_iv(channel.source_sampler->input, frame_ix, t);
value = linear_interpolate_value(channel.source_sampler->output, frame_ix, parameter_ix, iv);
}
apply_transform_target(transform, target_attributes[parameter_ix], value);
}
}
}
void animate_node(instance_types::node& node_instance, float t)
{
for (int i = 0; i < node_instance.node->channels_count; i++) {
types::channel const& channel = *node_instance.node->channels[i];
instance_types::transform& transform = node_instance.transforms[channel.target_transform_index];
frame_ix frame_ix = find_frame_ix(channel.source_sampler->input, t);
//assert(frame_ix >= 0); // animation is missing a key frame
animate_channel_segment(channel, transform, frame_ix, t);
}
}
}

View File

@ -1,7 +1,10 @@
#include "collada/scene.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "collada/scene.h"
#include "collada/animate.h"
namespace collada::scene {
void state::load_scene(types::descriptor const * const descriptor)
@ -35,21 +38,25 @@ namespace collada::scene {
}
}
void state::update(XMMATRIX const & projection,
XMMATRIX const & view,
float t)
int state::find_node_index_by_name(const char * name)
{
//t = animate::loop(t / 1.0f, 1.0f);
for (int i = 0; i < descriptor->nodes_count; i++) {
if (strcmp(descriptor->nodes[i]->name, name) == 0) {
return i;
}
}
fprintf(stderr, "node `%s` not found in scene\n", name);
exit(EXIT_FAILURE);
}
void state::update(float t)
{
t = animate::loop(t, 3.3f);
for (int i = 0; i < descriptor->nodes_count; i++) {
//animate::animate_node(node_state.node_instances[i], t);
animate::animate_node(node_state.node_instances[i], t);
node_state.update_node_world_transform(node_state.node_instances[i]);
}
vulkan.transfer_transforms(projection,
view,
descriptor->nodes_count,
node_state.node_instances);
}
void state::unload_scene()

View File

@ -112,9 +112,10 @@ XMMATRIX currentProjection()
return projection;
}
XMMATRIX currentView()
XMMATRIX currentView(collada::instance_types::node const & camera_node)
{
XMVECTOR eye = XMVectorSet(-57, 159, 269, 0);
XMVECTOR eye = XMVector3Transform(XMVectorZero(), camera_node.world);
XMVECTOR at = XMVectorSet(0, 0, 0, 0);
XMVECTOR up = XMVectorSet(0, 0, 1, 0);
XMMATRIX view = XMMatrixLookAtLH(eye, at, up);
@ -276,6 +277,14 @@ inline static int positive_modulo(int i, unsigned int n) {
return (i % n + n) % n;
}
inline static double getTime(int64_t start_time)
{
int64_t current_time;
SDL_GetCurrentTime(&current_time);
int64_t time = current_time - start_time;
return (double)(time / 1000) * 0.000001;
}
int main()
{
SDL_CHECK(SDL_Init(SDL_INIT_VIDEO));
@ -1191,6 +1200,11 @@ int main()
uint32_t imageIndex{ 0 };
bool quit{ false };
int32_t samplerIndex{ 0 };
int64_t start_time;
SDL_GetCurrentTime(&start_time);
int cameraIndex = collada_state.find_node_index_by_name("Camera001");
while (quit == false) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
@ -1364,10 +1378,16 @@ int main()
collada_state.vulkan.change_frame(commandBuffer, frameIndex);
XMMATRIX projection = currentProjection();
XMMATRIX view = currentView();
collada_state.update(projection, view, 0);
double time = getTime(start_time);
collada_state.update(time / 3.0f);
XMMATRIX projection = currentProjection();
XMMATRIX view = currentView(collada_state.node_state.node_instances[cameraIndex]);
collada_state.vulkan.transfer_transforms(projection,
view,
collada_state.descriptor->nodes_count,
collada_state.node_state.node_instances);
collada_state.draw();
vkCmdEndRendering(commandBuffer);