collada: node animation

This commit is contained in:
Zack Buhman 2026-03-17 14:46:00 -05:00
parent 0489f33243
commit 71744c4344
16 changed files with 2508 additions and 74 deletions

View File

@ -40,8 +40,10 @@ OBJS = \
src/collada/scene.o \ src/collada/scene.o \
src/collada/effect.o \ src/collada/effect.o \
src/collada/node_state.o \ src/collada/node_state.o \
src/collada/animate.o \
data/scenes/ship20/ship20.o \ data/scenes/ship20/ship20.o \
data/scenes/noodle/noodle.o data/scenes/noodle/noodle.o \
data/scenes/shadow_test/shadow_test.o
all: test.so all: test.so

View File

@ -9,3 +9,15 @@ PYTHONPATH=~/d3d10 python -m collada.main \
PYTHONPATH=~/d3d10 python -m collada.main \ PYTHONPATH=~/d3d10 python -m collada.main \
include/data/scenes/noodle.h include/data/scenes/noodle.h
# shadow_test
PYTHONPATH=~/d3d10 python -m collada.main \
~/love-demo/scene/shadow_test/shadow_test.DAE \
data/scenes/shadow_test/shadow_test.cpp \
data/scenes/shadow_test/shadow_test.vtx \
data/scenes/shadow_test/shadow_test.vjw \
data/scenes/shadow_test/shadow_test.idx
PYTHONPATH=~/d3d10 python -m collada.main \
include/data/scenes/shadow_test.h

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

Binary file not shown.

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

@ -21,8 +21,12 @@ namespace collada::instance_types {
types::transform_type type; types::transform_type type;
}; };
struct node_instance { struct node {
transform * transforms = NULL; // immutable state
types::node const * node;
// mutable state
transform * transforms;
XMMATRIX world; XMMATRIX world;
}; };
} }

View File

@ -0,0 +1,13 @@
#pragma once
#include "collada/types.h"
#include "collada/instance_types.h"
namespace collada::node_state {
struct state {
instance_types::node * node_instances;
void allocate_node_instances(types::node const * const * const nodes, int nodes_count);
void update_node_world_transform(instance_types::node & node_instance);
};
};

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "collada/types.h" #include "collada/types.h"
#include "collada/instance_types.h"
#include "collada/node_state.h"
namespace collada::scene { namespace collada::scene {
struct static_skinned { struct static_skinned {
@ -10,6 +12,7 @@ namespace collada::scene {
struct state { struct state {
types::descriptor const * descriptor; types::descriptor const * descriptor;
node_state::state node_state;
unsigned int vertex_buffer_pnt; unsigned int vertex_buffer_pnt;
unsigned int vertex_buffer_jw; unsigned int vertex_buffer_jw;
@ -20,6 +23,7 @@ namespace collada::scene {
unsigned int * textures; unsigned int * textures;
// drawing
void load_layouts(); void load_layouts();
void load_images(); void load_images();
void load_scene(types::descriptor const * const descriptor); void load_scene(types::descriptor const * const descriptor);
@ -40,7 +44,13 @@ namespace collada::scene {
void draw_instance_controllers(types::instance_controller const * const instance_controllers, void draw_instance_controllers(types::instance_controller const * const instance_controllers,
int const instance_controllers_count); int const instance_controllers_count);
void draw_node(types::node const & node); void draw_node(types::node const & node, instance_types::node const & node_instance);
void draw(); void draw();
// state updates
void update(float t);
// query
instance_types::node * find_node_by_name(char const * name);
}; };
} }

View File

@ -362,6 +362,8 @@ namespace collada::types {
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
struct node { struct node {
char const * name;
int const parent_index; int const parent_index;
node_type const type; node_type const type;

View File

@ -0,0 +1,3 @@
namespace shadow_test {
extern collada::types::descriptor const descriptor;
}

View File

@ -1,14 +1,13 @@
static inline float fract(float f) #include <stdio.h>
{
return f - floorf(f);
}
static inline float loop(float f, float n) #include "directxmath/directxmath.h"
{
return fract(f / n) * n;
}
static inline int find_frame_ix(source const& source, float t) #include "collada/types.h"
#include "collada/instance_types.h"
namespace collada::animate {
static inline int find_frame_ix(types::source const& source, float t)
{ {
for (int i = 0; i < source.count - 1; i++) { for (int i = 0; i < source.count - 1; i++) {
if (source.float_array[i] <= t && source.float_array[i+1] > t) { if (source.float_array[i] <= t && source.float_array[i+1] > t) {
@ -18,14 +17,14 @@
return -1; return -1;
} }
static inline float linear_interpolate_iv(source const& source, int frame_ix, float t) static inline float linear_interpolate_iv(types::source const& source, int frame_ix, float t)
{ {
float prev = source.float_array[(frame_ix+0) * source.stride]; float prev = source.float_array[(frame_ix+0) * source.stride];
float next = source.float_array[(frame_ix+1) * source.stride]; float next = source.float_array[(frame_ix+1) * source.stride];
return (t - prev) / (next - prev); return (t - prev) / (next - prev);
} }
static inline float linear_interpolate_value(source const& source, int frame_ix, int parameter_ix, float iv) static inline float linear_interpolate_value(types::source const& source, int frame_ix, int parameter_ix, float iv)
{ {
float prev = source.float_array[(frame_ix+0) * source.stride + parameter_ix]; float prev = source.float_array[(frame_ix+0) * source.stride + parameter_ix];
float next = source.float_array[(frame_ix+1) * source.stride + parameter_ix]; float next = source.float_array[(frame_ix+1) * source.stride + parameter_ix];
@ -76,20 +75,20 @@
} }
} }
print("%f %f\n", XMVectorGetX(p0), XMVectorGetY(p0)); fprintf(stderr, "%f %f\n", XMVectorGetX(p0), XMVectorGetY(p0));
print("%f %f\n", XMVectorGetX(c0), XMVectorGetY(c0)); fprintf(stderr, "%f %f\n", XMVectorGetX(c0), XMVectorGetY(c0));
print("%f %f\n", XMVectorGetX(c1), XMVectorGetY(c1)); fprintf(stderr, "%f %f\n", XMVectorGetX(c1), XMVectorGetY(c1));
print("%f %f\n", XMVectorGetX(p1), XMVectorGetY(p1)); fprintf(stderr, "%f %f\n", XMVectorGetX(p1), XMVectorGetY(p1));
assert(false); assert(false);
} }
static inline XMFLOAT2 const * tangent_index(source const& source, int frame_ix, int parameter_ix) 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; int ix = frame_ix * source.stride + parameter_ix * 2;
return (XMFLOAT2 const *)&source.float_array[ix]; return (XMFLOAT2 const *)&source.float_array[ix];
} }
static float bezier_sampler(sampler const * const sampler, int frame_ix, int parameter_ix, float t) static float bezier_sampler(types::sampler const * const sampler, int frame_ix, int parameter_ix, float t)
{ {
/* /*
P0 is (INPUT[i] , OUTPUT[i]) P0 is (INPUT[i] , OUTPUT[i])
@ -112,25 +111,25 @@
return bezier_binary_search(p0, c0, c1, p1, t); return bezier_binary_search(p0, c0, c1, p1, t);
} }
static void apply_transform_target(transform& transform, static void apply_transform_target(instance_types::transform& transform,
enum target_attribute channel_target_attribute, enum types::target_attribute channel_target_attribute,
float value) float value)
{ {
switch (transform.type) { switch (transform.type) {
case transform_type::TRANSLATE: __attribute__((fallthrough)); case types::transform_type::TRANSLATE: __attribute__((fallthrough));
case transform_type::SCALE: case types::transform_type::SCALE:
switch (channel_target_attribute) { switch (channel_target_attribute) {
case target_attribute::X: transform.vector = XMVectorSetX(transform.vector, value); return; case types::target_attribute::X: transform.vector = XMVectorSetX(transform.vector, value); return;
case target_attribute::Y: transform.vector = XMVectorSetY(transform.vector, value); return; case types::target_attribute::Y: transform.vector = XMVectorSetY(transform.vector, value); return;
case target_attribute::Z: transform.vector = XMVectorSetZ(transform.vector, value); return; case types::target_attribute::Z: transform.vector = XMVectorSetZ(transform.vector, value); return;
default: assert(false); default: assert(false);
} }
case transform_type::ROTATE: case types::transform_type::ROTATE:
switch (channel_target_attribute) { switch (channel_target_attribute) {
case target_attribute::X: transform.vector = XMVectorSetX(transform.vector, value); return; case types::target_attribute::X: transform.vector = XMVectorSetX(transform.vector, value); return;
case target_attribute::Y: transform.vector = XMVectorSetY(transform.vector, value); return; case types::target_attribute::Y: transform.vector = XMVectorSetY(transform.vector, value); return;
case target_attribute::Z: transform.vector = XMVectorSetZ(transform.vector, value); return; case types::target_attribute::Z: transform.vector = XMVectorSetZ(transform.vector, value); return;
case target_attribute::ANGLE: transform.vector = XMVectorSetW(transform.vector, value); return; case types::target_attribute::ANGLE: transform.vector = XMVectorSetW(transform.vector, value); return;
default: assert(false); default: assert(false);
} }
default: default:
@ -139,33 +138,33 @@
} }
} }
static enum target_attribute const rotate_target_attributes[] = { static enum types::target_attribute const rotate_target_attributes[] = {
target_attribute::X, types::target_attribute::X,
target_attribute::Y, types::target_attribute::Y,
target_attribute::Z, types::target_attribute::Z,
target_attribute::ANGLE, types::target_attribute::ANGLE,
}; };
static enum target_attribute const translate_scale_target_attributes[] = { static enum types::target_attribute const translate_scale_target_attributes[] = {
target_attribute::X, types::target_attribute::X,
target_attribute::Y, types::target_attribute::Y,
target_attribute::Z, types::target_attribute::Z,
}; };
static void animate_channel_segment(channel const& channel, static void animate_channel_segment(types::channel const& channel,
transform& transform, instance_types::transform& transform,
int frame_ix, float t) int frame_ix, float t)
{ {
enum target_attribute const * target_attributes = &channel.target_attribute; enum types::target_attribute const * target_attributes = &channel.target_attribute;
int target_attributes_count = 1; int target_attributes_count = 1;
if (channel.target_attribute == target_attribute::ALL) { if (channel.target_attribute == types::target_attribute::ALL) {
switch (transform.type) { switch (transform.type) {
case transform_type::TRANSLATE: __attribute__((fallthrough)); case types::transform_type::TRANSLATE: __attribute__((fallthrough));
case transform_type::SCALE: case types::transform_type::SCALE:
target_attributes = translate_scale_target_attributes; target_attributes = translate_scale_target_attributes;
target_attributes_count = 3; target_attributes_count = 3;
break; break;
case transform_type::ROTATE: case types::transform_type::ROTATE:
target_attributes = rotate_target_attributes; target_attributes = rotate_target_attributes;
target_attributes_count = 4; target_attributes_count = 4;
break; break;
@ -177,10 +176,10 @@
for (int parameter_ix = 0; parameter_ix < target_attributes_count; parameter_ix++) { for (int parameter_ix = 0; parameter_ix < target_attributes_count; parameter_ix++) {
enum collada::interpolation interpolation = channel.source_sampler->interpolation.interpolation_array[frame_ix]; enum types::interpolation interpolation = channel.source_sampler->interpolation.interpolation_array[frame_ix];
float value; float value;
if (interpolation == interpolation::BEZIER) { if (interpolation == types::interpolation::BEZIER) {
value = bezier_sampler(channel.source_sampler, frame_ix, parameter_ix, t); value = bezier_sampler(channel.source_sampler, frame_ix, parameter_ix, t);
} else { } else {
float iv = linear_interpolate_iv(channel.source_sampler->input, frame_ix, t); float iv = linear_interpolate_iv(channel.source_sampler->input, frame_ix, t);
@ -191,11 +190,11 @@
} }
} }
static void animate_node(node const& node, node_instance& node_instance, float t) void animate_node(instance_types::node& node_instance, float t)
{ {
for (int i = 0; i < node.channels_count; i++) { for (int i = 0; i < node_instance.node->channels_count; i++) {
channel const& channel = *node.channels[i]; types::channel const& channel = *node_instance.node->channels[i];
transform& transform = node_instance.transforms[channel.target_transform_index]; instance_types::transform& transform = node_instance.transforms[channel.target_transform_index];
int frame_ix = find_frame_ix(channel.source_sampler->input, t); int frame_ix = find_frame_ix(channel.source_sampler->input, t);
assert(frame_ix >= 0); // animation is missing a key frame assert(frame_ix >= 0); // animation is missing a key frame
@ -203,3 +202,4 @@
animate_channel_segment(channel, transform, frame_ix, t); animate_channel_segment(channel, transform, frame_ix, t);
} }
} }
}

View File

@ -1,11 +1,19 @@
#include <stdio.h>
#include "directxmath/directxmath.h" #include "directxmath/directxmath.h"
#include "collada/types.h" #include "new.h"
#include "collada/instance_types.h"
#include "collada/node_state.h"
namespace collada::node_state { namespace collada::node_state {
inline static void load_transform(instance_types::transform * instance_transform,
types::transform const & transform) //////////////////////////////////////////////////////////////////////
// transforms
//////////////////////////////////////////////////////////////////////
inline static void load_transform(types::transform const & transform,
instance_types::transform * instance_transform)
{ {
switch (transform.type) { switch (transform.type) {
case types::transform_type::LOOKAT: case types::transform_type::LOOKAT:
@ -28,17 +36,37 @@ namespace collada::node_state {
default: default:
assert(false); assert(false);
} }
instance_transform->type = transform.type;
} }
void initialize_node_transforms(types::node const * const node, inline static void initialize_node_transforms(instance_types::node & node_instance)
instance_types::node_instance * const node_instance)
{ {
for (int i = 0; i < node->transforms_count; i++) { for (int i = 0; i < node_instance.node->transforms_count; i++) {
load_transform(&node_instance->transforms[i], load_transform(node_instance.node->transforms[i], &node_instance.transforms[i]);
node->transforms[i]);
} }
} }
inline static void allocate_node_instance(instance_types::node & node_instance,
types::node const * const node)
{
node_instance.node = node;
node_instance.transforms = New<instance_types::transform>(node->transforms_count);
initialize_node_transforms(node_instance);
}
void state::allocate_node_instances(types::node const * const * const nodes, int nodes_count)
{
node_instances = New<instance_types::node>(nodes_count);
for (int i = 0; i < nodes_count; i++) {
allocate_node_instance(node_instances[i], nodes[i]);
}
}
//////////////////////////////////////////////////////////////////////
// world matrix
//////////////////////////////////////////////////////////////////////
inline static bool vector_equal(XMVECTOR V1, XMVECTOR V2) inline static bool vector_equal(XMVECTOR V1, XMVECTOR V2)
{ {
uint32_t CR; uint32_t CR;
@ -60,8 +88,25 @@ namespace collada::node_state {
case types::transform_type::MATRIX: case types::transform_type::MATRIX:
return transform.matrix; return transform.matrix;
default: default:
fprintf(stderr, "unknown transform type %d\n", (int)transform.type);
assert(false); assert(false);
break; break;
} }
} }
void state::update_node_world_transform(instance_types::node & node_instance)
{
XMMATRIX world;
if (node_instance.node->parent_index >= 0)
world = node_instances[node_instance.node->parent_index].world;
else
world = XMMatrixIdentity();
for (int i = 0; i < node_instance.node->transforms_count; i++) {
world = transform_matrix(node_instance.transforms[i]) * world;
}
node_instance.world = world;
}
} }

View File

@ -14,6 +14,7 @@
#include "collada/instance_types.h" #include "collada/instance_types.h"
#include "collada/scene.h" #include "collada/scene.h"
#include "collada/effect.h" #include "collada/effect.h"
#include "collada/animate.h"
namespace collada::scene { namespace collada::scene {
@ -275,6 +276,8 @@ namespace collada::scene {
index_buffer = load_index_buffer(descriptor->index_buffer); index_buffer = load_index_buffer(descriptor->index_buffer);
load_images(); load_images();
node_state.allocate_node_instances(descriptor->nodes, descriptor->nodes_count);
} }
void state::set_color_or_texture(types::color_or_texture const& color_or_texture, void state::set_color_or_texture(types::color_or_texture const& color_or_texture,
@ -337,8 +340,6 @@ namespace collada::scene {
types::instance_material const * const instance_materials, types::instance_material const * const instance_materials,
int const instance_materials_count) int const instance_materials_count)
{ {
glUseProgram(collada::effect::program_static);
types::mesh const& mesh = geometry.mesh; types::mesh const& mesh = geometry.mesh;
for (int j = 0; j < instance_materials_count; j++) { for (int j = 0; j < instance_materials_count; j++) {
@ -437,10 +438,23 @@ namespace collada::scene {
} }
} }
void state::draw_node(types::node const & node) void state::draw_node(types::node const & node, instance_types::node const & node_instance)
{ {
draw_instance_geometries(node.instance_geometries, node.instance_geometries_count); XMMATRIX transform = node_instance.world * view::state.transform;
draw_instance_controllers(node.instance_controllers, node.instance_controllers_count); XMFLOAT4X4 float_transform;
XMStoreFloat4x4(&float_transform, transform);
if (node.instance_geometries_count) {
glUseProgram(collada::effect::program_static);
glUniformMatrix4fv(layout.uniform.transform, 1, false, (float *)&float_transform);
draw_instance_geometries(node.instance_geometries, node.instance_geometries_count);
}
if (node.instance_controllers_count) {
glUseProgram(collada::effect::program_static);
glUniformMatrix4fv(layout.uniform.transform, 1, false, (float *)&float_transform);
draw_instance_controllers(node.instance_controllers, node.instance_controllers_count);
}
} }
void state::draw() void state::draw()
@ -448,7 +462,6 @@ namespace collada::scene {
unsigned int effects[] = {collada::effect::program_static, collada::effect::program_skinned}; unsigned int effects[] = {collada::effect::program_static, collada::effect::program_skinned};
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
glUseProgram(effects[i]); glUseProgram(effects[i]);
glUniformMatrix4fv(layout.uniform.transform, 1, false, (float *)&view::state.float_transform);
glUniform1i(layout.uniform.emission_sampler, 0); glUniform1i(layout.uniform.emission_sampler, 0);
glUniform1i(layout.uniform.ambient_sampler, 1); glUniform1i(layout.uniform.ambient_sampler, 1);
glUniform1i(layout.uniform.diffuse_sampler, 2); glUniform1i(layout.uniform.diffuse_sampler, 2);
@ -468,7 +481,27 @@ namespace collada::scene {
if (node.type != types::node_type::NODE) if (node.type != types::node_type::NODE)
continue; continue;
draw_node(node); draw_node(node, node_state.node_instances[i]);
} }
} }
void state::update(float t)
{
t = animate::loop(t / 4.0f, 3.333333f);
for (int i = 0; i < descriptor->nodes_count; i++) {
animate::animate_node(node_state.node_instances[i], t);
node_state.update_node_world_transform(node_state.node_instances[i]);
}
}
instance_types::node * state::find_node_by_name(char const * name)
{
for (int i = 0; i < descriptor->nodes_count; i++) {
if (strcmp(node_state.node_instances[i].node->name, name) == 0) {
return &node_state.node_instances[i];
}
}
return nullptr;
}
} }

View File

@ -24,9 +24,11 @@
#include "collada/effect.h" #include "collada/effect.h"
#include "collada/scene.h" #include "collada/scene.h"
#include "collada/types.h" #include "collada/types.h"
#include "collada/instance_types.h"
#include "data/scenes/ship20.h" #include "data/scenes/ship20.h"
#include "data/scenes/noodle.h" #include "data/scenes/noodle.h"
#include "data/scenes/shadow_test.h"
struct line_location { struct line_location {
struct { struct {
@ -73,6 +75,9 @@ static target_type const geometry_buffer_pnc_types[3] = {
[target_name::COLOR] = { GL_RGBA8, GL_COLOR_ATTACHMENT2 }, [target_name::COLOR] = { GL_RGBA8, GL_COLOR_ATTACHMENT2 },
}; };
collada::instance_types::node * node_eye;
collada::instance_types::node * node_at;
void load_quad_index_buffer() void load_quad_index_buffer()
{ {
uint8_t const data[] = { uint8_t const data[] = {
@ -304,7 +309,11 @@ void load(const char * source_path)
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
collada::effect::load_effects(); collada::effect::load_effects();
scene_state.load_scene(&noodle::descriptor); scene_state.load_scene(&shadow_test::descriptor);
node_eye = scene_state.find_node_by_name("Camera001");
assert(node_eye != nullptr);
node_at = scene_state.find_node_by_name("Camera001.Target");
assert(node_at != nullptr);
} }
void update_keyboard(int up, int down, int left, int right, void update_keyboard(int up, int down, int left, int right,
@ -416,8 +425,11 @@ void update_joystick(int joystick_index,
} }
*/ */
view::state.at = view::state.at + direction; view::state.eye = view::state.eye + direction;
view::state.eye = view::state.at - view::state.direction * view::at_distance; //view::state.at = view::state.at - view::state.direction * view::at_distance;
//view::state.at = view::state.at + direction;
//view::state.eye = view::state.at - view::state.direction * view::at_distance;
/* /*
lighting.quadratic += 0.01 * a + -0.01 * b; lighting.quadratic += 0.01 * a + -0.01 * b;
@ -440,6 +452,10 @@ void update(float time)
{ {
current_time = time; current_time = time;
scene_state.update(time);
view::state.eye = XMVector3Transform(XMVectorZero(), node_eye->world);
view::state.at = XMVector3Transform(XMVectorZero(), node_at->world);
view::update_transforms(); view::update_transforms();
} }