From 6fde17649f7404e79a17d4d8f96662d03011aca1 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Thu, 30 Oct 2025 20:08:53 -0700 Subject: Move animation module outside of scene/ --- src/animation.c | 517 ++++++++++++++++++++++++++++++++++++++++++++ src/animation_impl.h | 96 +++++++++ src/asset/asset_cache.c | 2 +- src/asset/model.c | 2 +- src/memory.c | 2 +- src/memory.h | 2 +- src/render/llr.c | 2 +- src/render/llr_impl.h | 2 +- src/render/renderer.c | 2 +- src/scene/animation.c | 518 --------------------------------------------- src/scene/animation_impl.h | 96 --------- src/scene/node_impl.h | 2 +- src/scene/object_impl.h | 2 +- src/scene/scene_impl.h | 2 +- src/scene/types.h | 24 --- src/types.h | 24 +++ 16 files changed, 647 insertions(+), 648 deletions(-) create mode 100644 src/animation.c create mode 100644 src/animation_impl.h delete mode 100644 src/scene/animation.c delete mode 100644 src/scene/animation_impl.h delete mode 100644 src/scene/types.h create mode 100644 src/types.h (limited to 'src') diff --git a/src/animation.c b/src/animation.c new file mode 100644 index 0000000..c52df73 --- /dev/null +++ b/src/animation.c @@ -0,0 +1,517 @@ +#include "animation_impl.h" + +#include "memory.h" + +#include + +// #include // Debugging. + +static const R PLAYBACK_UNINITIALIZED = -1; + +static joint_idx get_anima_root_joint_index(Anima* anima) { + assert(anima); + assert(anima->num_joints > 0); + assert(anima->num_joints < GFX_MAX_NUM_JOINTS); + return anima->num_joints - 1; +} + +static Joint* get_anima_root_joint(Anima* anima) { + assert(anima); + return &anima->joints[get_anima_root_joint_index(anima)]; +} + +static const Joint* get_anima_joint(const Anima* anima, joint_idx index) { + assert(anima); + assert(index < GFX_MAX_NUM_JOINTS); + assert(index != INDEX_NONE); + assert(index < anima->num_joints); + return &anima->joints[index]; +} + +static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) { + return (Joint*)get_anima_joint(anima, index); +} + +static const Joint* get_skeleton_joint( + const Anima* anima, const Skeleton* skeleton, joint_idx index) { + assert(anima); + assert(skeleton); + return get_anima_joint(anima, skeleton->joints[index]); +} + +static void set_joint_parent( + Anima* anima, joint_idx joint_index, joint_idx parent_index) { + assert(anima); + assert(joint_index != INDEX_NONE); + assert(joint_index != get_anima_root_joint_index(anima)); + assert(parent_index != INDEX_NONE); + + Joint* parent = get_anima_joint_mut(anima, parent_index); + + if (parent->child == INDEX_NONE) { + parent->child = joint_index; + } else { + // Find the last child in the chain of children. + Joint* child = get_anima_joint_mut(anima, parent->child); + while (child->next != INDEX_NONE) { + child = get_anima_joint_mut(anima, child->next); + } + // Wire up this joint as the last child's sibling. + child->next = joint_index; + } +} + +static void make_joint(Anima* anima, const JointDesc* desc, Joint* joint) { + assert(anima); + assert(desc); + assert(joint); + + // The joint matrix needs to be initialized so that meshes look right even if + // no animation is played. Initializing joint matrices to the identity makes + // meshes appear in their bind pose. + joint->child = INDEX_NONE; + joint->next = INDEX_NONE; + joint->transform = mat4_id(); + joint->inv_bind_matrix = desc->inv_bind_matrix; + joint->joint_matrix = mat4_id(); + joint->box = desc->box; +} + +static Skeleton* make_skeleton(const SkeletonDesc* desc) { + assert(desc); + assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); + + Skeleton* skeleton = mem_alloc_skeleton(); + skeleton->num_joints = desc->num_joints; + memcpy( + skeleton->joints, desc->joints, + desc->num_joints * sizeof(skeleton->joints[0])); + return skeleton; +} + +static Animation* make_animation(const AnimationDesc* desc) { + assert(desc); + assert(desc->num_channels < GFX_MAX_NUM_CHANNELS); + + Animation* animation = mem_alloc_animation(); + animation->name = desc->name; + animation->duration = 0; + animation->num_channels = desc->num_channels; + R start_time = 0; + R end_time = 0; + + for (size_t c = 0; c < desc->num_channels; ++c) { + const ChannelDesc* channel_desc = &desc->channels[c]; + Channel* channel = &animation->channels[c]; + + channel->target = channel_desc->target; + channel->type = channel_desc->type; + channel->interpolation = channel_desc->interpolation; + channel->num_keyframes = channel_desc->num_keyframes; + assert(channel_desc->num_keyframes < GFX_MAX_NUM_KEYFRAMES); + + for (size_t k = 0; k < channel_desc->num_keyframes; ++k) { + const KeyframeDesc* keyframe_desc = &channel_desc->keyframes[k]; + Keyframe* keyframe = &channel->keyframes[k]; + + keyframe->time = keyframe_desc->time; + keyframe->translation = keyframe_desc->translation; + keyframe->rotation = keyframe_desc->rotation; + + start_time = keyframe->time < start_time ? keyframe->time : start_time; + end_time = keyframe->time > end_time ? keyframe->time : end_time; + } + } + + // LOGD("Animation start/end: %f / %f", start_time, end_time); + animation->duration = end_time - start_time; + assert(animation->duration >= 0); + return animation; +} + +Anima* gfx_make_anima(const AnimaDesc* desc) { + assert(desc); + assert(desc->num_joints > 0); + assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); + // All joints should have a parent except for the root. + for (size_t i = 0; i < desc->num_joints - 1; ++i) { + const joint_idx parent = desc->joints[i].parent; + assert(parent != INDEX_NONE); + assert(parent < desc->num_joints); + } + // The root should have no parent. + assert(desc->joints[desc->num_joints - 1].parent == INDEX_NONE); + + Anima* anima = mem_alloc_anima(); + + // Wire the skeletons in the same order they are given in the descriptor. + Skeleton* last_skeleton = 0; + for (size_t i = 0; i < desc->num_skeletons; ++i) { + Skeleton* skeleton = make_skeleton(&desc->skeletons[i]); + const skeleton_idx skeleton_index = mem_get_skeleton_index(skeleton); + if (last_skeleton == 0) { + anima->skeleton = skeleton_index; + } else { + last_skeleton->next = skeleton_index; + } + last_skeleton = skeleton; + } + + // Wire the animations in the same order they are given in the descriptor. + Animation* last_animation = 0; + for (size_t i = 0; i < desc->num_animations; ++i) { + Animation* animation = make_animation(&desc->animations[i]); + const animation_idx animation_index = mem_get_animation_index(animation); + if (last_animation == 0) { + anima->animation = animation_index; + } else { + last_animation->next = animation_index; + } + last_animation = animation; + } + + // Create joints. + anima->num_joints = desc->num_joints; + // Initialize all joints. + // Child and sibling pointers must be initialized before wiring up the + // hierarchy. + for (size_t i = 0; i < desc->num_joints; ++i) { + Joint* joint = get_anima_joint_mut(anima, i); + make_joint(anima, &desc->joints[i], joint); + } + // Wire up joints to their parents. -1 to skip the root. + for (size_t i = 0; i < desc->num_joints - 1; ++i) { + set_joint_parent(anima, i, desc->joints[i].parent); + } + + return anima; +} + +void gfx_destroy_anima(Anima** anima) { + assert(anima); + + if (*anima) { + for (skeleton_idx i = (*anima)->skeleton; i.val != 0;) { + Skeleton* skeleton = mem_get_skeleton(i); + i = skeleton->next; + mem_free_skeleton(&skeleton); + } + + for (animation_idx i = (*anima)->animation; i.val != 0;) { + Animation* animation = mem_get_animation(i); + i = animation->next; + mem_free_animation(&animation); + } + + mem_free_anima(anima); + } +} + +static Animation* find_animation(animation_idx index, const char* name) { + assert(name); + + while (index.val != 0) { + Animation* animation = mem_get_animation(index); + if (sstring_eq_cstr(animation->name, name)) { + // LOGD( + // "Found animation at index %u, %s - %s", index.val, + // sstring_cstr(&animation->name), name); + // LOGD("Animation has duration %f", animation->duration); + return animation; + } + index = animation->next; + } + + return 0; +} + +bool gfx_play_animation(Anima* anima, const AnimationPlaySettings* settings) { + assert(anima); + assert(settings); + + // TODO: Should we animate at t=0 here to kickstart the animation? Otherwise + // the client is forced to call gfx_update_animation() to do this. + Animation* animation = find_animation(anima->animation, settings->name); + if (!animation) { + return false; + } + // Playback initialized on first call to update(). + AnimationState* state = &anima->state; + state->start_time = PLAYBACK_UNINITIALIZED; + state->animation = mem_get_animation_index(animation); + state->loop = settings->loop; + return true; +} + +static void gfx_set_joint_position(Joint* joint, vec3 position) { + assert(joint); + mat4_set_v3(&joint->transform, position); +} + +static void gfx_set_joint_rotation(Joint* joint, quat rotation) { + assert(joint); + mat4_set_3x3(&joint->transform, mat4_from_quat(rotation)); +} + +static void find_keyframes(const Channel* channel, R t, int* prev, int* next) { + assert(channel); + assert(prev); + assert(next); + + *prev = -1; + *next = 0; + while (((*next + 1) < (int)channel->num_keyframes) && + (t >= channel->keyframes[*next + 1].time)) { + (*prev)++; + (*next)++; + } +} + +static R normalize_time(R a, R b, R t) { + assert(a <= t); + assert(t <= b); + return (t - a) / (b - a); +} + +static quat interpolate_rotation( + const Channel* channel, int prev, int next, R t) { + assert(channel); + + if (next == 0) { + // Animation has not started at this point in time yet. + return channel->keyframes[next].rotation; + } else { + switch (channel->interpolation) { + case StepInterpolation: + return channel->keyframes[prev].rotation; + case LinearInterpolation: { + const R normalized_t = normalize_time( + channel->keyframes[prev].time, channel->keyframes[next].time, t); + return qnormalize(qslerp( + channel->keyframes[prev].rotation, channel->keyframes[next].rotation, + normalized_t)); + break; + } + case CubicSplineInterpolation: + assert(false); // TODO + return qmake(0, 0, 0, 0); + default: + assert(false); + return qmake(0, 0, 0, 0); + } + } +} + +static vec3 interpolate_translation( + const Channel* channel, int prev, int next, R t) { + assert(channel); + + if (next == 0) { + // Animation has not started at this point in time yet. + return channel->keyframes[next].translation; + } else { + switch (channel->interpolation) { + case StepInterpolation: + return channel->keyframes[prev].translation; + case LinearInterpolation: { + const R normalized_t = normalize_time( + channel->keyframes[prev].time, channel->keyframes[next].time, t); + return vec3_lerp( + channel->keyframes[prev].translation, + channel->keyframes[next].translation, normalized_t); + break; + } + case CubicSplineInterpolation: + assert(false); // TODO + return vec3_make(0, 0, 0); + default: + assert(false); + return vec3_make(0, 0, 0); + } + } +} + +static void animate_channel(Anima* anima, const Channel* channel, R t) { + assert(anima); + assert(channel); + assert(channel->target < anima->num_joints); + + int prev, next; + find_keyframes(channel, t, &prev, &next); + + // Note that not all channels extend to the duration of an animation; some + // channels may stop animating their targets earlier. Clamp the animation time + // to the channel's end keyframe to make the rest of the math (normalize_time) + // work. + t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t; + + Joint* target = get_anima_joint_mut(anima, channel->target); + + switch (channel->type) { + case RotationChannel: { + const quat rotation = interpolate_rotation(channel, prev, next, t); + gfx_set_joint_rotation(target, rotation); + break; + } + case TranslationChannel: { + const vec3 translation = interpolate_translation(channel, prev, next, t); + gfx_set_joint_position(target, translation); + break; + } + // Not yet supported. + case ScaleChannel: + case WeightsChannel: + default: + // TODO: Add back the assertion or add support for scaling. + // assert(false); + break; + } +} + +static void compute_joint_matrices_rec( + Anima* anima, Joint* joint, const mat4* parent_global_joint_transform, + const mat4* root_inv_global_transform) { + assert(anima); + assert(joint); + assert(parent_global_joint_transform); + assert(root_inv_global_transform); + + const mat4 global_joint_transform = + mat4_mul(*parent_global_joint_transform, joint->transform); + + // Compute this joint's matrix. + joint->joint_matrix = mat4_mul( + *root_inv_global_transform, + mat4_mul(global_joint_transform, joint->inv_bind_matrix)); + + // Recursively compute the joint matrices for this joint's siblings. + if (joint->next != INDEX_NONE) { + Joint* sibling = get_anima_joint_mut(anima, joint->next); + + compute_joint_matrices_rec( + anima, sibling, parent_global_joint_transform, + root_inv_global_transform); + } + + // Recursively compute the joint matrices for this joint's children. + if (joint->child != INDEX_NONE) { + Joint* child = get_anima_joint_mut(anima, joint->child); + + compute_joint_matrices_rec( + anima, child, &global_joint_transform, root_inv_global_transform); + } +} + +void gfx_update_animation(Anima* anima, R t) { + assert(anima); + + AnimationState* state = &anima->state; + if (state->animation.val == 0) { + return; // No active animation. + } + const Animation* animation = mem_get_animation(state->animation); + assert(animation); + + // On a call to play(), the start time is set to -1 to signal that the + // animation playback has not yet been initialized. + if (state->start_time == PLAYBACK_UNINITIALIZED) { + state->start_time = t; + } + // Locate the current time point inside the animation's timeline. + assert(t >= state->start_time); + assert(animation->duration >= 0.0); + const R local_time = t - state->start_time; + const R animation_time = state->loop + ? rmod(local_time, animation->duration) + : clamp(local_time, 0.0, animation->duration); + + // LOGD( + // "animation_time = %f, animation duration: %f", animation_time, + // animation->duration); + + // Play through the animation to transform skeleton nodes. + for (size_t i = 0; i < animation->num_channels; ++i) { + const Channel* channel = &animation->channels[i]; + animate_channel(anima, channel, animation_time); + } + + // Compute joint matrices after having transformed the skeletons. + // + // The anima's parent node is the common ancestor of all skeletons, and its + // transform maps the skeletons from object space to world space. This is the + // transform used as the "global transform" in the joint matrix equations. + // + // Joint matrix calculation begins by descending from the anima's root joint, + // which we have constructed to be the common root of all skeletons. + // + // This procedure touches every joint exactly once. + const mat4 root_global_transform = mat4_id(); + const mat4 root_inv_global_transform = mat4_id(); + + Joint* root_joint = get_anima_root_joint(anima); + compute_joint_matrices_rec( + anima, root_joint, &root_global_transform, &root_inv_global_transform); +} + +const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i) { + assert(anima); + + skeleton_idx skeleton_index = anima->skeleton; + const Skeleton* skeleton = mem_get_skeleton(skeleton_index); + + for (size_t j = 1; j < i; ++j) { + skeleton_index = skeleton->next; + mem_get_skeleton(skeleton_index); + } + + return skeleton; +} + +size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) { + assert(skeleton); + return skeleton->num_joints; +} + +bool gfx_joint_has_box( + const Anima* anima, const Skeleton* skeleton, size_t joint_index) { + assert(anima); + assert(skeleton); + assert(joint_index < skeleton->num_joints); + + const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); + return !aabb3_is_empty(joint->box); +} + +Box gfx_get_joint_box( + const Anima* anima, const Skeleton* skeleton, size_t joint_index) { + assert(anima); + assert(skeleton); + + const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); + + // Transform the box to anima space. + // Note that joint matrices do not usually have a translation since joints + // mostly just rotate with respect to their parent. + const vec3 pmin = joint->box.min; + const vec3 pmax = joint->box.max; + return (Box){ + .vertices = { + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1), + mat4_mul_vec3( + joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1), + } + }; +} diff --git a/src/animation_impl.h b/src/animation_impl.h new file mode 100644 index 0000000..4929b97 --- /dev/null +++ b/src/animation_impl.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include + +#include "types.h" + +#include +#include +#include +#include +#include + +#include + +typedef struct Buffer Buffer; + +// Currently ignoring scale in skinning and animation. +// +// TODO: Simultaneous animation of disjoint animations. + +/// Skeleton joint. +/// Joints are mutable and store the transform and joint matrices that result +/// from animation, aside from the inverse bind matrix. +typedef struct Joint { + joint_idx child; // First child Joint; index into Anima's joints. + joint_idx next; // Next sibling Joint; index into Anima's joints. + mat4 transform; // Local transform relative to parent. + mat4 inv_bind_matrix; // Transforms the mesh into the joint's local space. + mat4 joint_matrix; // inv(global) * global joint transform * inv(bind). + aabb3 box; // Bounding box of vertices affected by joint. +} Joint; + +/// Animation skeleton. +typedef struct Skeleton { + skeleton_idx next; + size_t num_joints; + joint_idx joints[GFX_MAX_NUM_JOINTS]; // Indices into Anima's joints array. +} Skeleton; + +/// A keyframe of animation. +typedef struct Keyframe { + R time; // Start time in [0, end animation time] + union { + vec3 translation; + quat rotation; + }; +} Keyframe; + +/// Animation channel. +typedef struct Channel { + joint_idx target; // Index into Anima's joints array. + ChannelType type; + AnimationInterpolation interpolation; + size_t num_keyframes; + Keyframe keyframes[GFX_MAX_NUM_KEYFRAMES]; +} Channel; + +/// A skeletal animation. +typedef struct Animation { + animation_idx next; + sstring name; + R duration; + size_t num_channels; + Channel channels[GFX_MAX_NUM_CHANNELS]; +} Animation; + +/// Animation state. +/// +/// This represents the current state of an animation. +typedef struct AnimationState { + R start_time; // Time when the current animation started playing. -1 means the + // animation playback has not yet been initialized. + animation_idx animation; // Current animation. 0 = no animation. + bool loop; +} AnimationState; + +/// Animation object. +/// +/// This is the top-level animation object that encapsulates everything +/// necessary for animation. +/// +/// For lack of a better name, this is called an Anima. It is short and the +/// Latin root of animation. +/// +/// The last joint of the joints array at index 'num_joints - 1' is the root of +/// all skeletons; specifically, the root of all joints that otherwise would +/// have no parent (a skeleton need not have its own root and can be a set of +/// disjoint node hierarchies). +typedef struct Anima { + skeleton_idx skeleton; // Index of first skeleton. + animation_idx animation; // Index of first animation. + AnimationState state; // Current animation state. + size_t num_joints; // Number of actual joints in the array. + Joint joints[GFX_MAX_NUM_JOINTS]; // Shared by all skeletons. +} Anima; diff --git a/src/asset/asset_cache.c b/src/asset/asset_cache.c index 191a5fd..2df0242 100644 --- a/src/asset/asset_cache.c +++ b/src/asset/asset_cache.c @@ -1,8 +1,8 @@ #include "asset_cache.h" +#include "../animation_impl.h" #include "memory.h" #include "model.h" -#include "scene/animation_impl.h" #include "scene/model_impl.h" #include "scene/node_impl.h" #include "texture.h" diff --git a/src/asset/model.c b/src/asset/model.c index df116fb..f5131d2 100644 --- a/src/asset/model.c +++ b/src/asset/model.c @@ -85,10 +85,10 @@ #include "gfx_assert.h" #include "scene/model_impl.h" +#include #include #include #include -#include #include #include #include diff --git a/src/memory.c b/src/memory.c index 3481791..a4d6920 100644 --- a/src/memory.c +++ b/src/memory.c @@ -3,8 +3,8 @@ #include #include +#include "animation_impl.h" #include "render/llr_impl.h" -#include "scene/animation_impl.h" #include "scene/model_impl.h" #include "scene/node_impl.h" #include "scene/object_impl.h" diff --git a/src/memory.h b/src/memory.h index 624216d..bfbee66 100644 --- a/src/memory.h +++ b/src/memory.h @@ -1,7 +1,7 @@ /// Memory management of scene objects. #pragma once -#include "scene/types.h" +#include "types.h" typedef struct Camera Camera; diff --git a/src/render/llr.c b/src/render/llr.c index 189041a..76935f9 100644 --- a/src/render/llr.c +++ b/src/render/llr.c @@ -1,7 +1,7 @@ #include "llr_impl.h" +#include "animation_impl.h" #include "memory.h" -#include "scene/animation_impl.h" #include "scene/node_impl.h" #include diff --git a/src/render/llr_impl.h b/src/render/llr_impl.h index 1c08b03..c85ad15 100644 --- a/src/render/llr_impl.h +++ b/src/render/llr_impl.h @@ -3,7 +3,7 @@ #include #include -#include "scene/types.h" +#include "../types.h" #include #include diff --git a/src/render/renderer.c b/src/render/renderer.c index 385a8c1..fadf521 100644 --- a/src/render/renderer.c +++ b/src/render/renderer.c @@ -1,8 +1,8 @@ #include "renderer_impl.h" +#include "animation_impl.h" #include "llr_impl.h" #include "memory.h" -#include "scene/animation_impl.h" #include "scene/model_impl.h" #include "scene/node_impl.h" #include "scene/object_impl.h" diff --git a/src/scene/animation.c b/src/scene/animation.c deleted file mode 100644 index a498961..0000000 --- a/src/scene/animation.c +++ /dev/null @@ -1,518 +0,0 @@ -#include "animation_impl.h" - -#include "memory.h" -#include "node_impl.h" - -#include - -// #include // Debugging. - -static const R PLAYBACK_UNINITIALIZED = -1; - -static joint_idx get_anima_root_joint_index(Anima* anima) { - assert(anima); - assert(anima->num_joints > 0); - assert(anima->num_joints < GFX_MAX_NUM_JOINTS); - return anima->num_joints - 1; -} - -static Joint* get_anima_root_joint(Anima* anima) { - assert(anima); - return &anima->joints[get_anima_root_joint_index(anima)]; -} - -static const Joint* get_anima_joint(const Anima* anima, joint_idx index) { - assert(anima); - assert(index < GFX_MAX_NUM_JOINTS); - assert(index != INDEX_NONE); - assert(index < anima->num_joints); - return &anima->joints[index]; -} - -static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) { - return (Joint*)get_anima_joint(anima, index); -} - -static const Joint* get_skeleton_joint( - const Anima* anima, const Skeleton* skeleton, joint_idx index) { - assert(anima); - assert(skeleton); - return get_anima_joint(anima, skeleton->joints[index]); -} - -static void set_joint_parent( - Anima* anima, joint_idx joint_index, joint_idx parent_index) { - assert(anima); - assert(joint_index != INDEX_NONE); - assert(joint_index != get_anima_root_joint_index(anima)); - assert(parent_index != INDEX_NONE); - - Joint* parent = get_anima_joint_mut(anima, parent_index); - - if (parent->child == INDEX_NONE) { - parent->child = joint_index; - } else { - // Find the last child in the chain of children. - Joint* child = get_anima_joint_mut(anima, parent->child); - while (child->next != INDEX_NONE) { - child = get_anima_joint_mut(anima, child->next); - } - // Wire up this joint as the last child's sibling. - child->next = joint_index; - } -} - -static void make_joint(Anima* anima, const JointDesc* desc, Joint* joint) { - assert(anima); - assert(desc); - assert(joint); - - // The joint matrix needs to be initialized so that meshes look right even if - // no animation is played. Initializing joint matrices to the identity makes - // meshes appear in their bind pose. - joint->child = INDEX_NONE; - joint->next = INDEX_NONE; - joint->transform = mat4_id(); - joint->inv_bind_matrix = desc->inv_bind_matrix; - joint->joint_matrix = mat4_id(); - joint->box = desc->box; -} - -static Skeleton* make_skeleton(const SkeletonDesc* desc) { - assert(desc); - assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); - - Skeleton* skeleton = mem_alloc_skeleton(); - skeleton->num_joints = desc->num_joints; - memcpy( - skeleton->joints, desc->joints, - desc->num_joints * sizeof(skeleton->joints[0])); - return skeleton; -} - -static Animation* make_animation(const AnimationDesc* desc) { - assert(desc); - assert(desc->num_channels < GFX_MAX_NUM_CHANNELS); - - Animation* animation = mem_alloc_animation(); - animation->name = desc->name; - animation->duration = 0; - animation->num_channels = desc->num_channels; - R start_time = 0; - R end_time = 0; - - for (size_t c = 0; c < desc->num_channels; ++c) { - const ChannelDesc* channel_desc = &desc->channels[c]; - Channel* channel = &animation->channels[c]; - - channel->target = channel_desc->target; - channel->type = channel_desc->type; - channel->interpolation = channel_desc->interpolation; - channel->num_keyframes = channel_desc->num_keyframes; - assert(channel_desc->num_keyframes < GFX_MAX_NUM_KEYFRAMES); - - for (size_t k = 0; k < channel_desc->num_keyframes; ++k) { - const KeyframeDesc* keyframe_desc = &channel_desc->keyframes[k]; - Keyframe* keyframe = &channel->keyframes[k]; - - keyframe->time = keyframe_desc->time; - keyframe->translation = keyframe_desc->translation; - keyframe->rotation = keyframe_desc->rotation; - - start_time = keyframe->time < start_time ? keyframe->time : start_time; - end_time = keyframe->time > end_time ? keyframe->time : end_time; - } - } - - // LOGD("Animation start/end: %f / %f", start_time, end_time); - animation->duration = end_time - start_time; - assert(animation->duration >= 0); - return animation; -} - -Anima* gfx_make_anima(const AnimaDesc* desc) { - assert(desc); - assert(desc->num_joints > 0); - assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); - // All joints should have a parent except for the root. - for (size_t i = 0; i < desc->num_joints - 1; ++i) { - const joint_idx parent = desc->joints[i].parent; - assert(parent != INDEX_NONE); - assert(parent < desc->num_joints); - } - // The root should have no parent. - assert(desc->joints[desc->num_joints - 1].parent == INDEX_NONE); - - Anima* anima = mem_alloc_anima(); - - // Wire the skeletons in the same order they are given in the descriptor. - Skeleton* last_skeleton = 0; - for (size_t i = 0; i < desc->num_skeletons; ++i) { - Skeleton* skeleton = make_skeleton(&desc->skeletons[i]); - const skeleton_idx skeleton_index = mem_get_skeleton_index(skeleton); - if (last_skeleton == 0) { - anima->skeleton = skeleton_index; - } else { - last_skeleton->next = skeleton_index; - } - last_skeleton = skeleton; - } - - // Wire the animations in the same order they are given in the descriptor. - Animation* last_animation = 0; - for (size_t i = 0; i < desc->num_animations; ++i) { - Animation* animation = make_animation(&desc->animations[i]); - const animation_idx animation_index = mem_get_animation_index(animation); - if (last_animation == 0) { - anima->animation = animation_index; - } else { - last_animation->next = animation_index; - } - last_animation = animation; - } - - // Create joints. - anima->num_joints = desc->num_joints; - // Initialize all joints. - // Child and sibling pointers must be initialized before wiring up the - // hierarchy. - for (size_t i = 0; i < desc->num_joints; ++i) { - Joint* joint = get_anima_joint_mut(anima, i); - make_joint(anima, &desc->joints[i], joint); - } - // Wire up joints to their parents. -1 to skip the root. - for (size_t i = 0; i < desc->num_joints - 1; ++i) { - set_joint_parent(anima, i, desc->joints[i].parent); - } - - return anima; -} - -void gfx_destroy_anima(Anima** anima) { - assert(anima); - - if (*anima) { - for (skeleton_idx i = (*anima)->skeleton; i.val != 0;) { - Skeleton* skeleton = mem_get_skeleton(i); - i = skeleton->next; - mem_free_skeleton(&skeleton); - } - - for (animation_idx i = (*anima)->animation; i.val != 0;) { - Animation* animation = mem_get_animation(i); - i = animation->next; - mem_free_animation(&animation); - } - - mem_free_anima(anima); - } -} - -static Animation* find_animation(animation_idx index, const char* name) { - assert(name); - - while (index.val != 0) { - Animation* animation = mem_get_animation(index); - if (sstring_eq_cstr(animation->name, name)) { - // LOGD( - // "Found animation at index %u, %s - %s", index.val, - // sstring_cstr(&animation->name), name); - // LOGD("Animation has duration %f", animation->duration); - return animation; - } - index = animation->next; - } - - return 0; -} - -bool gfx_play_animation(Anima* anima, const AnimationPlaySettings* settings) { - assert(anima); - assert(settings); - - // TODO: Should we animate at t=0 here to kickstart the animation? Otherwise - // the client is forced to call gfx_update_animation() to do this. - Animation* animation = find_animation(anima->animation, settings->name); - if (!animation) { - return false; - } - // Playback initialized on first call to update(). - AnimationState* state = &anima->state; - state->start_time = PLAYBACK_UNINITIALIZED; - state->animation = mem_get_animation_index(animation); - state->loop = settings->loop; - return true; -} - -static void gfx_set_joint_position(Joint* joint, vec3 position) { - assert(joint); - mat4_set_v3(&joint->transform, position); -} - -static void gfx_set_joint_rotation(Joint* joint, quat rotation) { - assert(joint); - mat4_set_3x3(&joint->transform, mat4_from_quat(rotation)); -} - -static void find_keyframes(const Channel* channel, R t, int* prev, int* next) { - assert(channel); - assert(prev); - assert(next); - - *prev = -1; - *next = 0; - while (((*next + 1) < (int)channel->num_keyframes) && - (t >= channel->keyframes[*next + 1].time)) { - (*prev)++; - (*next)++; - } -} - -static R normalize_time(R a, R b, R t) { - assert(a <= t); - assert(t <= b); - return (t - a) / (b - a); -} - -static quat interpolate_rotation( - const Channel* channel, int prev, int next, R t) { - assert(channel); - - if (next == 0) { - // Animation has not started at this point in time yet. - return channel->keyframes[next].rotation; - } else { - switch (channel->interpolation) { - case StepInterpolation: - return channel->keyframes[prev].rotation; - case LinearInterpolation: { - const R normalized_t = normalize_time( - channel->keyframes[prev].time, channel->keyframes[next].time, t); - return qnormalize(qslerp( - channel->keyframes[prev].rotation, channel->keyframes[next].rotation, - normalized_t)); - break; - } - case CubicSplineInterpolation: - assert(false); // TODO - return qmake(0, 0, 0, 0); - default: - assert(false); - return qmake(0, 0, 0, 0); - } - } -} - -static vec3 interpolate_translation( - const Channel* channel, int prev, int next, R t) { - assert(channel); - - if (next == 0) { - // Animation has not started at this point in time yet. - return channel->keyframes[next].translation; - } else { - switch (channel->interpolation) { - case StepInterpolation: - return channel->keyframes[prev].translation; - case LinearInterpolation: { - const R normalized_t = normalize_time( - channel->keyframes[prev].time, channel->keyframes[next].time, t); - return vec3_lerp( - channel->keyframes[prev].translation, - channel->keyframes[next].translation, normalized_t); - break; - } - case CubicSplineInterpolation: - assert(false); // TODO - return vec3_make(0, 0, 0); - default: - assert(false); - return vec3_make(0, 0, 0); - } - } -} - -static void animate_channel(Anima* anima, const Channel* channel, R t) { - assert(anima); - assert(channel); - assert(channel->target < anima->num_joints); - - int prev, next; - find_keyframes(channel, t, &prev, &next); - - // Note that not all channels extend to the duration of an animation; some - // channels may stop animating their targets earlier. Clamp the animation time - // to the channel's end keyframe to make the rest of the math (normalize_time) - // work. - t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t; - - Joint* target = get_anima_joint_mut(anima, channel->target); - - switch (channel->type) { - case RotationChannel: { - const quat rotation = interpolate_rotation(channel, prev, next, t); - gfx_set_joint_rotation(target, rotation); - break; - } - case TranslationChannel: { - const vec3 translation = interpolate_translation(channel, prev, next, t); - gfx_set_joint_position(target, translation); - break; - } - // Not yet supported. - case ScaleChannel: - case WeightsChannel: - default: - // TODO: Add back the assertion or add support for scaling. - // assert(false); - break; - } -} - -static void compute_joint_matrices_rec( - Anima* anima, Joint* joint, const mat4* parent_global_joint_transform, - const mat4* root_inv_global_transform) { - assert(anima); - assert(joint); - assert(parent_global_joint_transform); - assert(root_inv_global_transform); - - const mat4 global_joint_transform = - mat4_mul(*parent_global_joint_transform, joint->transform); - - // Compute this joint's matrix. - joint->joint_matrix = mat4_mul( - *root_inv_global_transform, - mat4_mul(global_joint_transform, joint->inv_bind_matrix)); - - // Recursively compute the joint matrices for this joint's siblings. - if (joint->next != INDEX_NONE) { - Joint* sibling = get_anima_joint_mut(anima, joint->next); - - compute_joint_matrices_rec( - anima, sibling, parent_global_joint_transform, - root_inv_global_transform); - } - - // Recursively compute the joint matrices for this joint's children. - if (joint->child != INDEX_NONE) { - Joint* child = get_anima_joint_mut(anima, joint->child); - - compute_joint_matrices_rec( - anima, child, &global_joint_transform, root_inv_global_transform); - } -} - -void gfx_update_animation(Anima* anima, R t) { - assert(anima); - - AnimationState* state = &anima->state; - if (state->animation.val == 0) { - return; // No active animation. - } - const Animation* animation = mem_get_animation(state->animation); - assert(animation); - - // On a call to play(), the start time is set to -1 to signal that the - // animation playback has not yet been initialized. - if (state->start_time == PLAYBACK_UNINITIALIZED) { - state->start_time = t; - } - // Locate the current time point inside the animation's timeline. - assert(t >= state->start_time); - assert(animation->duration >= 0.0); - const R local_time = t - state->start_time; - const R animation_time = state->loop - ? rmod(local_time, animation->duration) - : clamp(local_time, 0.0, animation->duration); - - // LOGD( - // "animation_time = %f, animation duration: %f", animation_time, - // animation->duration); - - // Play through the animation to transform skeleton nodes. - for (size_t i = 0; i < animation->num_channels; ++i) { - const Channel* channel = &animation->channels[i]; - animate_channel(anima, channel, animation_time); - } - - // Compute joint matrices after having transformed the skeletons. - // - // The anima's parent node is the common ancestor of all skeletons, and its - // transform maps the skeletons from object space to world space. This is the - // transform used as the "global transform" in the joint matrix equations. - // - // Joint matrix calculation begins by descending from the anima's root joint, - // which we have constructed to be the common root of all skeletons. - // - // This procedure touches every joint exactly once. - const mat4 root_global_transform = mat4_id(); - const mat4 root_inv_global_transform = mat4_id(); - - Joint* root_joint = get_anima_root_joint(anima); - compute_joint_matrices_rec( - anima, root_joint, &root_global_transform, &root_inv_global_transform); -} - -const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i) { - assert(anima); - - skeleton_idx skeleton_index = anima->skeleton; - const Skeleton* skeleton = mem_get_skeleton(skeleton_index); - - for (size_t j = 1; j < i; ++j) { - skeleton_index = skeleton->next; - mem_get_skeleton(skeleton_index); - } - - return skeleton; -} - -size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) { - assert(skeleton); - return skeleton->num_joints; -} - -bool gfx_joint_has_box( - const Anima* anima, const Skeleton* skeleton, size_t joint_index) { - assert(anima); - assert(skeleton); - assert(joint_index < skeleton->num_joints); - - const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); - return !aabb3_is_empty(joint->box); -} - -Box gfx_get_joint_box( - const Anima* anima, const Skeleton* skeleton, size_t joint_index) { - assert(anima); - assert(skeleton); - - const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index); - - // Transform the box to anima space. - // Note that joint matrices do not usually have a translation since joints - // mostly just rotate with respect to their parent. - const vec3 pmin = joint->box.min; - const vec3 pmax = joint->box.max; - return (Box){ - .vertices = { - mat4_mul_vec3( - joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1), - mat4_mul_vec3( - joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1), - mat4_mul_vec3( - joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1), - mat4_mul_vec3( - joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1), - mat4_mul_vec3( - joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1), - mat4_mul_vec3( - joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1), - mat4_mul_vec3( - joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1), - mat4_mul_vec3( - joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1), - } - }; -} diff --git a/src/scene/animation_impl.h b/src/scene/animation_impl.h deleted file mode 100644 index ef1492c..0000000 --- a/src/scene/animation_impl.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include -#include - -#include "types.h" - -#include -#include -#include -#include -#include - -#include - -typedef struct Buffer Buffer; - -// Currently ignoring scale in skinning and animation. -// -// TODO: Simultaneous animation of disjoint animations. - -/// Skeleton joint. -/// Joints are mutable and store the transform and joint matrices that result -/// from animation, aside from the inverse bind matrix. -typedef struct Joint { - joint_idx child; // First child Joint; index into Anima's joints. - joint_idx next; // Next sibling Joint; index into Anima's joints. - mat4 transform; // Local transform relative to parent. - mat4 inv_bind_matrix; // Transforms the mesh into the joint's local space. - mat4 joint_matrix; // inv(global) * global joint transform * inv(bind). - aabb3 box; // Bounding box of vertices affected by joint. -} Joint; - -/// Animation skeleton. -typedef struct Skeleton { - skeleton_idx next; - size_t num_joints; - joint_idx joints[GFX_MAX_NUM_JOINTS]; // Indices into Anima's joints array. -} Skeleton; - -/// A keyframe of animation. -typedef struct Keyframe { - R time; // Start time in [0, end animation time] - union { - vec3 translation; - quat rotation; - }; -} Keyframe; - -/// Animation channel. -typedef struct Channel { - joint_idx target; // Index into Anima's joints array. - ChannelType type; - AnimationInterpolation interpolation; - size_t num_keyframes; - Keyframe keyframes[GFX_MAX_NUM_KEYFRAMES]; -} Channel; - -/// A skeletal animation. -typedef struct Animation { - animation_idx next; - sstring name; - R duration; - size_t num_channels; - Channel channels[GFX_MAX_NUM_CHANNELS]; -} Animation; - -/// Animation state. -/// -/// This represents the current state of an animation. -typedef struct AnimationState { - R start_time; // Time when the current animation started playing. -1 means the - // animation playback has not yet been initialized. - animation_idx animation; // Current animation. 0 = no animation. - bool loop; -} AnimationState; - -/// Animation object. -/// -/// This is the top-level animation object that encapsulates everything -/// necessary for animation. -/// -/// For lack of a better name, this is called an Anima. It is short and the -/// Latin root of animation. -/// -/// The last joint of the joints array at index 'num_joints - 1' is the root of -/// all skeletons; specifically, the root of all joints that otherwise would -/// have no parent (a skeleton need not have its own root and can be a set of -/// disjoint node hierarchies). -typedef struct Anima { - skeleton_idx skeleton; // Index of first skeleton. - animation_idx animation; // Index of first animation. - AnimationState state; // Current animation state. - size_t num_joints; // Number of actual joints in the array. - Joint joints[GFX_MAX_NUM_JOINTS]; // Shared by all skeletons. -} Anima; diff --git a/src/scene/node_impl.h b/src/scene/node_impl.h index 15ee232..aba8c41 100644 --- a/src/scene/node_impl.h +++ b/src/scene/node_impl.h @@ -2,7 +2,7 @@ #include -#include "types.h" +#include "../types.h" #include #include diff --git a/src/scene/object_impl.h b/src/scene/object_impl.h index b7a0752..1b14cb5 100644 --- a/src/scene/object_impl.h +++ b/src/scene/object_impl.h @@ -2,7 +2,7 @@ #include -#include "types.h" +#include "../types.h" typedef struct MeshLink { mesh_idx mesh; diff --git a/src/scene/scene_impl.h b/src/scene/scene_impl.h index 992f620..06a69f0 100644 --- a/src/scene/scene_impl.h +++ b/src/scene/scene_impl.h @@ -2,7 +2,7 @@ #include -#include "types.h" +#include "../types.h" typedef struct SceneNode SceneNode; diff --git a/src/scene/types.h b/src/scene/types.h deleted file mode 100644 index d0ffc41..0000000 --- a/src/scene/types.h +++ /dev/null @@ -1,24 +0,0 @@ -/// Strongly-typed indices for every kind of scene node resource. -#pragma once - -#include - -typedef uint16_t gfx_idx; - -#define DEF_STRONG_INDEX(TYPE_NAME, IDX_TYPE) \ - typedef struct TYPE_NAME##_idx { \ - IDX_TYPE val; \ - } TYPE_NAME##_idx; - -DEF_STRONG_INDEX(anima, gfx_idx) -DEF_STRONG_INDEX(animation, gfx_idx) -DEF_STRONG_INDEX(camera, gfx_idx) -DEF_STRONG_INDEX(light, gfx_idx) -DEF_STRONG_INDEX(material, gfx_idx) -DEF_STRONG_INDEX(mesh, gfx_idx) -DEF_STRONG_INDEX(mesh_link, gfx_idx) -DEF_STRONG_INDEX(model, gfx_idx) -DEF_STRONG_INDEX(node, gfx_idx) -DEF_STRONG_INDEX(object, gfx_idx) -DEF_STRONG_INDEX(scene, gfx_idx) -DEF_STRONG_INDEX(skeleton, gfx_idx) diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..d0ffc41 --- /dev/null +++ b/src/types.h @@ -0,0 +1,24 @@ +/// Strongly-typed indices for every kind of scene node resource. +#pragma once + +#include + +typedef uint16_t gfx_idx; + +#define DEF_STRONG_INDEX(TYPE_NAME, IDX_TYPE) \ + typedef struct TYPE_NAME##_idx { \ + IDX_TYPE val; \ + } TYPE_NAME##_idx; + +DEF_STRONG_INDEX(anima, gfx_idx) +DEF_STRONG_INDEX(animation, gfx_idx) +DEF_STRONG_INDEX(camera, gfx_idx) +DEF_STRONG_INDEX(light, gfx_idx) +DEF_STRONG_INDEX(material, gfx_idx) +DEF_STRONG_INDEX(mesh, gfx_idx) +DEF_STRONG_INDEX(mesh_link, gfx_idx) +DEF_STRONG_INDEX(model, gfx_idx) +DEF_STRONG_INDEX(node, gfx_idx) +DEF_STRONG_INDEX(object, gfx_idx) +DEF_STRONG_INDEX(scene, gfx_idx) +DEF_STRONG_INDEX(skeleton, gfx_idx) -- cgit v1.2.3