From 13c7adb42168a566c97f36db76080d80e02a6aae Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 17 Feb 2024 11:17:30 -0800 Subject: Pack joints into array to simplify animation data structure and make it easier to clone. --- gfx/include/gfx/scene/animation.h | 48 +++++----- gfx/include/gfx/scene/node.h | 13 --- gfx/src/asset/scene.c | 163 +++++++++++++++++++++++++-------- gfx/src/renderer/renderer.c | 21 ++--- gfx/src/scene/animation.c | 185 ++++++++++++++++++++++++++------------ gfx/src/scene/animation_impl.h | 42 ++++----- gfx/src/scene/node.c | 32 +------ gfx/src/scene/node_impl.h | 1 - gfx/src/scene/scene_memory.c | 6 -- gfx/src/scene/scene_memory.h | 1 - 10 files changed, 309 insertions(+), 203 deletions(-) diff --git a/gfx/include/gfx/scene/animation.h b/gfx/include/gfx/scene/animation.h index eeb21fd..42c0c43 100644 --- a/gfx/include/gfx/scene/animation.h +++ b/gfx/include/gfx/scene/animation.h @@ -17,25 +17,26 @@ typedef struct Buffer Buffer; typedef struct SceneNode SceneNode; -typedef struct Anima Anima; -typedef struct Joint Joint; -// TODO: Remove this when removing gfx_get_anima_skeleton(). +typedef struct Anima Anima; +typedef struct Joint Joint; typedef struct Skeleton Skeleton; +/// Index type used to store relative indices into arrays. +typedef uint16_t rel_idx; + +/// Index value denoting no index. +static const rel_idx INDEX_NONE = (rel_idx)-1; + /// Joint descriptor. typedef struct JointDesc { - mat4 inv_bind_matrix; + rel_idx parent; /// Parent Joint; index into Anima's joints. + mat4 inv_bind_matrix; /// Transforms the mesh into the joint's local space. } JointDesc; /// Skeleton descriptor. -/// -/// The last element of the joints array is the root of the hierarchy. For -/// flexibility (glTF), the root need not be the immediate parent of the -/// top-level joints of the skeleton, but rather, intermediate non-joint nodes -/// are allowed between the root and the skeleton. typedef struct SkeletonDesc { - size_t num_joints; // Number of joints and matrices. - const SceneNode* joints[GFX_MAX_NUM_JOINTS]; + size_t num_joints; + rel_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array. } SkeletonDesc; /// Animation interpolation mode. @@ -66,7 +67,7 @@ typedef struct KeyframeDesc { /// Animation channel descriptor. typedef struct ChannelDesc { - SceneNode* target; + rel_idx target; /// Index into Anima's joints array. ChannelType type; AnimationInterpolation interpolation; size_t num_keyframes; @@ -75,19 +76,25 @@ typedef struct ChannelDesc { /// Animation descriptor. typedef struct AnimationDesc { - // TODO: Replace the name with a hash for a smaller footprint and faster - // comparisons. + // TODO: Store a name hash for faster comparisons. sstring name; // Animation name. Required for playback. size_t num_channels; // Number of channels. ChannelDesc channels[GFX_MAX_NUM_CHANNELS]; } AnimationDesc; /// Anima object descriptor. +/// +/// The last joint of the joints array at index 'num_joints - 1' must be 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 AnimaDesc { size_t num_skeletons; size_t num_animations; + size_t num_joints; SkeletonDesc skeletons[GFX_MAX_NUM_SKELETONS]; AnimationDesc animations[GFX_MAX_NUM_ANIMATIONS]; + JointDesc joints[GFX_MAX_NUM_JOINTS]; } AnimaDesc; /// Animation play settings. @@ -97,15 +104,7 @@ typedef struct AnimationPlaySettings { // TODO: Add animation speed. } AnimationPlaySettings; -/// Create a joint. -Joint* gfx_make_joint(const JointDesc*); - -/// Destroy the joint. -void gfx_destroy_joint(Joint**); - /// Create an anima object. -/// -/// The anima owns its skeletons and animations. Anima* gfx_make_anima(const AnimaDesc*); /// Destroy the anima. @@ -120,8 +119,5 @@ void gfx_update_animation(Anima*, R t); /// Stop the current animation. void gfx_stop_animation(Anima*); -// TODO: Remove this, it's ugly. Things would be much simpler if scene nodes -// were allocated in arrays. Then our descs can take indices instead of -// pointers, and locating a node is simply a matter of indexing the array. The -// same is true for skeletons here and other objects. +/// Return the anima's ith skeleton. const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i); diff --git a/gfx/include/gfx/scene/node.h b/gfx/include/gfx/scene/node.h index aedac92..7976ae8 100644 --- a/gfx/include/gfx/scene/node.h +++ b/gfx/include/gfx/scene/node.h @@ -8,7 +8,6 @@ #include typedef struct Anima Anima; -typedef struct Joint Joint; typedef struct Light Light; typedef struct SceneCamera SceneCamera; typedef struct SceneObject SceneObject; @@ -21,7 +20,6 @@ typedef enum NodeType { LogicalNode, AnimaNode, CameraNode, - JointNode, LightNode, ObjectNode } NodeType; @@ -48,9 +46,6 @@ SceneNode* gfx_make_anima_node(Anima*); /// Create a new camera node. SceneNode* gfx_make_camera_node(SceneCamera*); -/// Create a joint node. -SceneNode* gfx_make_joint_node(Joint*); - /// Create a new light node. SceneNode* gfx_make_light_node(Light*); @@ -63,9 +58,6 @@ void gfx_construct_anima_node(SceneNode*, Anima*); /// Make the node a camera node. void gfx_construct_camera_node(SceneNode*, SceneCamera*); -/// Make the node a joint node. -void gfx_construct_joint_node(SceneNode*, Joint*); - /// Make the node a light node. void gfx_construct_light_node(SceneNode*, Light*); @@ -96,11 +88,6 @@ Anima* gfx_get_node_anima(const SceneNode*); /// The node must be of type CameraNode. SceneCamera* gfx_get_node_camera(const SceneNode* node); -/// Get the node's joint. -/// -/// The node must be of type JointNode. -Joint* gfx_get_node_joint(const SceneNode*); - /// Get the node's light. /// /// The node must be of type LightNode. diff --git a/gfx/src/asset/scene.c b/gfx/src/asset/scene.c index 156e7b6..ef2328c 100644 --- a/gfx/src/asset/scene.c +++ b/gfx/src/asset/scene.c @@ -1177,63 +1177,147 @@ static bool load_meshes( return true; } +/// Find the joint node with the smallest index across all skeletons. +/// +/// The channels in glTF may target arbitrary nodes in the scene (those nodes +/// are the joints). However, we want to map the "base joint" (the joint/node +/// with the smallest index) to 0 in the AnimaDesc's joint array. We can do this +/// by subtracting the "base node index" from every joint index or channel +/// target. +/// +/// There is an assumption in the animation library that joints are contiguous +/// anyway, so this "base joint index" works provided the joint nodes are also +/// contiguous in the glTF. The glTF does not guarantee this, but I think it's +/// a reasonable assumption that exporters write glTF files in such a way, and +/// Blender does appear to do so. +cgltf_size find_base_joint_index(const cgltf_data* data) { + assert(data); + + cgltf_size base_joint_index = (cgltf_size)-1; + + for (cgltf_size s = 0; s < data->skins_count; ++s) { + const cgltf_skin* skin = &data->skins[s]; + for (cgltf_size j = 0; j < skin->joints_count; ++j) { + // Joint is an index/pointer into the nodes array. + const cgltf_size node_index = skin->joints[j] - data->nodes; + assert(node_index < data->nodes_count); + // Min. + if (node_index < base_joint_index) { + base_joint_index = node_index; + } + } + } + + return base_joint_index; +} + /// Load all skins (Gfx skeletons) from the glTF scene. -static void load_skins( - const cgltf_data* data, Buffer* const* buffers, SceneNode** nodes, - SkeletonDesc* descs) { +/// Return the total number of joints. +static size_t load_skins( + const cgltf_data* data, Buffer* const* buffers, cgltf_size base_joint_index, + AnimaDesc* anima_desc) { assert(data); assert(buffers); - assert(nodes); - assert(descs); + assert(anima_desc); + assert(base_joint_index < data->nodes_count); + + // Determines whether the ith joint in the node hierarchy is a joint node. + // This is then used to determine whether a joint is a root of the joint + // hierarchy. + bool is_joint_node[GFX_MAX_NUM_JOINTS] = {false}; + + size_t num_joints = 0; for (cgltf_size s = 0; s < data->skins_count; ++s) { const cgltf_skin* skin = &data->skins[s]; const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices; assert(matrices_accessor->count == skin->joints_count); - SkeletonDesc* desc = &descs[s]; - *desc = (SkeletonDesc){.num_joints = skin->joints_count}; + num_joints += skin->joints_count; + assert(num_joints < GFX_MAX_NUM_JOINTS); + + SkeletonDesc* skeleton_desc = &anima_desc->skeletons[s]; + *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count}; - assert(skin->joints_count < GFX_MAX_NUM_JOINTS); // for (cgltf_size j = 0; j < skin->joints_count; ++j) { ACCESSOR_FOREACH_MAT(4, matrices_accessor, { const mat4 inv_bind_matrix = mat4_from_array(floats); + // Joint is an index/pointer into the nodes array. const cgltf_size node_index = skin->joints[i] - data->nodes; assert(node_index < data->nodes_count); - SceneNode* node = nodes[node_index]; - // Transform the node into a joint node. - const JointDesc joint_desc = - (JointDesc){.inv_bind_matrix = inv_bind_matrix}; - Joint* joint = gfx_make_joint(&joint_desc); - gfx_construct_joint_node(node, joint); + const cgltf_size parent_node_index = + skin->joints[i]->parent - data->nodes; + assert(parent_node_index < data->nodes_count); + + // Subtract the base index to pack the joints as tightly as possible in + // the AnimaDesc. + assert(node_index >= base_joint_index); + const cgltf_size joint_index = node_index - base_joint_index; - desc->joints[i] = node; + assert(parent_node_index >= base_joint_index); + const cgltf_size parent_index = parent_node_index - base_joint_index; + + skeleton_desc->joints[i] = joint_index; + + JointDesc* joint_desc = &anima_desc->joints[joint_index]; + joint_desc->parent = parent_index; + joint_desc->inv_bind_matrix = inv_bind_matrix; + + is_joint_node[joint_index] = true; }); - // glTF may specify a "skeleton", which is the root of the skeleton's node - // hierarchy. TODO: We could use this root node to optimize the descend in - // joint matrix computation. The root node should be passed with the - // AnimaDesc. + // glTF may specify a "skeleton", which is the root of the skin's + // (skeleton's) node hierarchy. // if (skin->skeleton) { - // cgltf_size root_index = skin->skeleton - data->nodes; - // assert(root_index <= data->nodes_count); - // root_node = nodes[root_index]; - // } + // // cgltf_size root_index = skin->skeleton - data->nodes; + // // assert(root_index <= data->nodes_count); + // // root_node = nodes[root_index]; + // assert(false); + //} + } + + // Animation library assumes that joints are contiguous. + for (size_t i = 0; i < num_joints; ++i) { + assert(is_joint_node[i]); } + + // Insert the root joint. + // This is the root of all skeletons. It is, specifically, the root of all + // joints that do not have a parent; skins (skeletons) in glTF are not + // guaranteed to have a common parent, but are generally a set of disjoint + // trees. + const size_t root_index = num_joints; + assert(root_index < GFX_MAX_NUM_JOINTS); + anima_desc->joints[root_index] = (JointDesc){.parent = INDEX_NONE}; + num_joints++; + + // Make root joints point to the root joint at index N. + // The root joints are the ones that have a non-joint node in the glTF as a + // parent. + for (size_t i = 0; i < root_index; ++i) { + JointDesc* joint = &anima_desc->joints[i]; + if ((joint->parent >= root_index) || !is_joint_node[joint->parent]) { + joint->parent = root_index; + } + } + + return num_joints; } /// Load all animations from the glTF scene. static void load_animations( - const cgltf_data* data, SceneNode** nodes, AnimationDesc* descs) { + const cgltf_data* data, cgltf_size base_joint_index, + AnimaDesc* anima_desc) { assert(data); - assert(nodes); - assert(descs); + assert(anima_desc); + assert(base_joint_index < data->nodes_count); + assert(data->animations_count <= GFX_MAX_NUM_ANIMATIONS); for (cgltf_size a = 0; a < data->animations_count; ++a) { const cgltf_animation* animation = &data->animations[a]; - AnimationDesc* animation_desc = &descs[a]; + AnimationDesc* animation_desc = &anima_desc->animations[a]; *animation_desc = (AnimationDesc){ .name = sstring_make(animation->name), @@ -1244,13 +1328,16 @@ static void load_animations( const cgltf_animation_channel* channel = &animation->channels[c]; ChannelDesc* channel_desc = &animation_desc->channels[c]; const cgltf_animation_sampler* sampler = channel->sampler; - const size_t node_index = channel->target_node - data->nodes; - assert(node_index < data->nodes_count); - SceneNode* target = nodes[node_index]; - assert(target); + + const size_t target_index = channel->target_node - data->nodes; + assert(target_index < data->nodes_count); + + assert(target_index >= base_joint_index); + const size_t tight_target_index = target_index - base_joint_index; + assert(tight_target_index < anima_desc->num_joints); *channel_desc = (ChannelDesc){ - .target = target, + .target = tight_target_index, .type = from_gltf_animation_path_type(channel->target_path), .interpolation = from_gltf_interpolation_type(sampler->interpolation), .num_keyframes = 0}; @@ -1316,8 +1403,8 @@ static void load_nodes( const cgltf_size mesh_index = node->mesh - data->meshes; assert(mesh_index < data->meshes_count); SceneObject* object = objects[mesh_index]; - gfx_construct_object_node(nodes[n], object); + if (node->skin) { assert(anima); @@ -1522,12 +1609,14 @@ static SceneNode* load_scene( goto cleanup; } - load_skins(data, buffers, scene_nodes, anima_desc->skeletons); - load_animations(data, scene_nodes, anima_desc->animations); + const cgltf_size base = find_base_joint_index(data); anima_desc->num_skeletons = data->skins_count; anima_desc->num_animations = data->animations_count; - anima = gfx_make_anima(anima_desc); + anima_desc->num_joints = load_skins(data, buffers, base, anima_desc); + load_animations(data, base, anima_desc); + + anima = gfx_make_anima(anima_desc); gfx_construct_anima_node(root_node, anima); } gfx_set_node_parent(root_node, parent_node); @@ -1535,6 +1624,8 @@ static SceneNode* load_scene( // The root node becomes the root of all scene nodes. load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes); + // TODO: Clean up scene nodes that correspond to joints in the glTF. + success = true; cleanup: diff --git a/gfx/src/renderer/renderer.c b/gfx/src/renderer/renderer.c index ac6a45f..5cc06d6 100644 --- a/gfx/src/renderer/renderer.c +++ b/gfx/src/renderer/renderer.c @@ -175,9 +175,10 @@ typedef struct RenderState { const mat4* camera_rotation; // From camera to world space, rotation only. const mat4* view_matrix; const mat4* projection; - Light* environment_light; const float fovy; const float aspect; + Light* environment_light; + Anima* anima; size_t num_joints; mat4 joint_matrices[GFX_MAX_NUM_JOINTS]; } RenderState; @@ -191,15 +192,13 @@ static void load_skeleton(RenderState* state, skeleton_idx skeleton_index) { assert(skeleton); assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS); + state->num_joints = skeleton->num_joints; + for (size_t i = 0; i < skeleton->num_joints; ++i) { - const SceneNode* node = mem_get_node(skeleton->joints[i]); - assert(node); - assert(node->type == JointNode); - const Joint* joint = mem_get_joint(node->joint); - assert(joint); - state->joint_matrices[i] = joint->joint_matrix; + const rel_idx joint_index = skeleton->joints[i]; + const Joint* joint = &state->anima->joints[joint_index]; + state->joint_matrices[i] = joint->joint_matrix; } - state->num_joints = skeleton->num_joints; } /// Draw the scene recursively. @@ -209,7 +208,9 @@ static void draw_recursively( const mat4 node_transform = mat4_mul(parent_transform, node->transform); // Activate light. - if (node->type == LightNode) { + if (node->type == AnimaNode) { + state->anima = gfx_get_node_anima(node); + } else if (node->type == LightNode) { Light* light = mem_get_light(node->light); assert(light); @@ -349,6 +350,6 @@ void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) { // Assuming a perspective matrix. .fovy = atan(1.0 / (mat4_at(projection, 1, 1))) * 2, .aspect = aspect}; - + draw_recursively(&state, mat4_id(), scene->root); } diff --git a/gfx/src/scene/animation.c b/gfx/src/scene/animation.c index 459e864..18e2a99 100644 --- a/gfx/src/scene/animation.c +++ b/gfx/src/scene/animation.c @@ -3,42 +3,78 @@ #include "node_impl.h" #include "scene_memory.h" +#include + // #include // Debugging. static const R PLAYBACK_UNINITIALIZED = -1; -Joint* gfx_make_joint(const JointDesc* desc) { +static rel_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 Joint* get_anima_joint(Anima* anima, rel_idx index) { + assert(anima); + assert(index < GFX_MAX_NUM_JOINTS); + assert(index != INDEX_NONE); + assert(index < anima->num_joints); + return &anima->joints[index]; +} + +static void set_joint_parent( + Anima* anima, rel_idx joint_index, rel_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(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(anima, parent->child); + while (child->next != INDEX_NONE) { + child = get_anima_joint(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* joint = mem_alloc_joint(); + 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(); - return joint; -} - -void gfx_destroy_joint(Joint** joint) { - assert(joint); - - if (*joint) { - if ((*joint)->parent.val) { - gfx_del_node((*joint)->parent); - } - mem_free_joint(joint); - } } static Skeleton* make_skeleton(const SkeletonDesc* desc) { assert(desc); - assert(desc->num_joints < GFX_MAX_NUM_JOINTS); + assert(desc->num_joints <= GFX_MAX_NUM_JOINTS); Skeleton* skeleton = mem_alloc_skeleton(); skeleton->num_joints = desc->num_joints; - for (size_t i = 0; i < desc->num_joints; ++i) { - skeleton->joints[i] = mem_get_node_index(desc->joints[i]); - } + memcpy( + skeleton->joints, desc->joints, + desc->num_joints * sizeof(skeleton->joints[0])); return skeleton; } @@ -56,7 +92,7 @@ static Animation* make_animation(const AnimationDesc* desc) { const ChannelDesc* channel_desc = &desc->channels[c]; Channel* channel = &animation->channels[c]; - channel->target = mem_get_node_index(channel_desc->target); + channel->target = channel_desc->target; channel->type = channel_desc->type; channel->interpolation = channel_desc->interpolation; channel->num_keyframes = channel_desc->num_keyframes; @@ -82,6 +118,16 @@ static Animation* make_animation(const AnimationDesc* desc) { 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 rel_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(); @@ -114,6 +160,20 @@ Anima* gfx_make_anima(const AnimaDesc* desc) { 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(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; } @@ -177,6 +237,16 @@ bool gfx_play_animation(Anima* anima, const AnimationPlaySettings* settings) { 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); @@ -255,9 +325,10 @@ static vec3 interpolate_translation( } } -static void animate_channel(const Channel* channel, R t) { +static void animate_channel(Anima* anima, const Channel* channel, R t) { + assert(anima); assert(channel); - assert(channel->target.val != 0); + assert(channel->target < anima->num_joints); int prev, next; find_keyframes(channel, t, &prev, &next); @@ -268,17 +339,17 @@ static void animate_channel(const Channel* channel, R t) { // work. t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t; - SceneNode* target = mem_get_node(channel->target); + Joint* target = get_anima_joint(anima, channel->target); switch (channel->type) { case RotationChannel: { const quat rotation = interpolate_rotation(channel, prev, next, t); - gfx_set_node_rotation(target, &rotation); + gfx_set_joint_rotation(target, rotation); break; } case TranslationChannel: { const vec3 translation = interpolate_translation(channel, prev, next, t); - gfx_set_node_position(target, &translation); + gfx_set_joint_position(target, translation); break; } // Not yet supported. @@ -292,35 +363,36 @@ static void animate_channel(const Channel* channel, R t) { } static void compute_joint_matrices_rec( - node_idx node_index, mat4 parent_global_joint_transform, + Anima* anima, Joint* joint, const mat4* parent_global_joint_transform, const mat4* root_inv_global_transform) { - if (node_index.val == 0) { - return; - } + assert(anima); + assert(joint); + assert(parent_global_joint_transform); + assert(root_inv_global_transform); - const SceneNode* node = mem_get_node(node_index); - const mat4 global_joint_transform = - mat4_mul(parent_global_joint_transform, node->transform); + const mat4 global_joint_transform = + mat4_mul(*parent_global_joint_transform, joint->transform); - // For flexibility (glTF), we allow non-joint nodes between the root of the - // skeleton and its joint nodes. We check the node type as opposed to assert - // it. - if (node->type == JointNode) { - // Compute this node's joint matrix. - Joint* joint = mem_get_joint(node->joint); + // Compute this joint's matrix. + joint->joint_matrix = mat4_mul( + *root_inv_global_transform, + mat4_mul(global_joint_transform, joint->inv_bind_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(anima, joint->next); + + compute_joint_matrices_rec( + anima, sibling, parent_global_joint_transform, + root_inv_global_transform); } - // Recursively compute the joint matrices for this node's children. - node_idx child = node->child; - while (child.val != 0) { + // Recursively compute the joint matrices for this joint's children. + if (joint->child != INDEX_NONE) { + Joint* child = get_anima_joint(anima, joint->child); + compute_joint_matrices_rec( - child, global_joint_transform, root_inv_global_transform); - node = mem_get_node(child); - child = node->next; // Next sibling. + anima, child, &global_joint_transform, root_inv_global_transform); } } @@ -354,7 +426,7 @@ void gfx_update_animation(Anima* anima, R t) { // 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(channel, animation_time); + animate_channel(anima, channel, animation_time); } // Compute joint matrices after having transformed the skeletons. @@ -369,19 +441,14 @@ void gfx_update_animation(Anima* anima, R t) { // // Lack of a common parent aside, the procedure touches every joint exactly // once (and potentially other non-joint intermediate nodes). - node_idx root_index = anima->parent; - SceneNode* root = mem_get_node(root_index); - // LOGD("Root: %u, child: %u", root_index.val, root->child.val); - const mat4 root_global_transform = gfx_get_node_global_transform(root); + SceneNode* root_node = mem_get_node(anima->parent); + // LOGD("Root: %u, child: %u", anima->parent.val, root->child.val); + const mat4 root_global_transform = gfx_get_node_global_transform(root_node); const mat4 root_inv_global_transform = mat4_inverse(root_global_transform); - // Step over root's children (siblings of the first child). - node_idx child = root->child; - while (child.val != 0) { - compute_joint_matrices_rec( - child, root_global_transform, &root_inv_global_transform); - SceneNode* node = mem_get_node(child); - child = node->next; // Next sibling. - } + + 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) { diff --git a/gfx/src/scene/animation_impl.h b/gfx/src/scene/animation_impl.h index ceebbbf..7265858 100644 --- a/gfx/src/scene/animation_impl.h +++ b/gfx/src/scene/animation_impl.h @@ -20,32 +20,27 @@ typedef struct Buffer Buffer; // // 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 { - node_idx parent; // Parent SceneNode. - mat4 inv_bind_matrix; - mat4 joint_matrix; + rel_idx child; /// First child Joint; index into Anima's joints. + rel_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). } Joint; /// Animation skeleton. -/// -/// The joints array maps joint indices to scene nodes. -/// -/// The last element of the joints array is the root of the hierarchy. Storing -/// the root of the hierarchy allows us to compute joint matrices only for scene -/// nodes that are actually part of a skeleton, since the root identifies the -/// skeleton's node hierarchy. -/// -/// Some model formats (glTF) may not actually specify a root. In that case, the -/// client must create one to form a strict tree hierarchy. typedef struct Skeleton { skeleton_idx next; size_t num_joints; - node_idx joints[GFX_MAX_NUM_JOINTS]; // Last = root. + rel_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] + R time; /// Start time in [0, end animation time] union { vec3 translation; quat rotation; @@ -54,7 +49,7 @@ typedef struct Keyframe { /// Animation channel. typedef struct Channel { - node_idx target; + rel_idx target; /// Index into Anima's joints array. ChannelType type; AnimationInterpolation interpolation; size_t num_keyframes; @@ -87,9 +82,16 @@ typedef struct AnimationState { /// /// 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 { - AnimationState state; - skeleton_idx skeleton; - animation_idx animation; // Index of first animation. - node_idx parent; // Parent SceneNode. + node_idx parent; /// Parent SceneNode. + 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/gfx/src/scene/node.c b/gfx/src/scene/node.c index 333dc28..1a68216 100644 --- a/gfx/src/scene/node.c +++ b/gfx/src/scene/node.c @@ -41,15 +41,6 @@ SceneNode* gfx_make_camera_node(SceneCamera* camera) { return node; } -SceneNode* gfx_make_joint_node(Joint* joint) { - assert(joint); - SceneNode* node = gfx_make_node(); - node->type = JointNode; - node->joint = mem_get_joint_index(joint); - joint->parent = mem_get_node_index(node); - return node; -} - SceneNode* gfx_make_light_node(Light* light) { assert(light); SceneNode* node = gfx_make_node(); @@ -87,12 +78,6 @@ static void free_node_resource(SceneNode* node) { gfx_destroy_camera(&camera); break; } - case JointNode: { - Joint* joint = mem_get_joint(node->joint); - joint->parent.val = 0; - gfx_destroy_joint(&joint); - break; - } case LightNode: { Light* light = mem_get_light(node->light); light->parent.val = 0; @@ -129,16 +114,7 @@ void gfx_construct_camera_node(SceneNode* node, SceneCamera* camera) { } // TODO: Add a common helper function between each gfx_make_xyz_node() and -// gfx_construct_xyz_node() pair. -void gfx_construct_joint_node(SceneNode* node, Joint* joint) { - assert(node); - assert(joint); - free_node_resource(node); - node->type = JointNode; - node->joint = mem_get_joint_index(joint); - joint->parent = mem_get_node_index(node); -} - +// gfx_construct_xyz_node() pair. void gfx_construct_light_node(SceneNode* node, Light* light) { assert(node); assert(light); @@ -217,10 +193,6 @@ SceneCamera* gfx_get_node_camera(const SceneNode* node) { NODE_GET(node, camera, CameraNode); } -Joint* gfx_get_node_joint(const SceneNode* node) { - NODE_GET(node, joint, JointNode); -} - Light* gfx_get_node_light(const SceneNode* node) { NODE_GET(node, light, LightNode); } @@ -309,8 +281,6 @@ static const char* get_node_type_str(NodeType type) { return "AnimaNode"; case CameraNode: return "CameraNode"; - case JointNode: - return "JointNode"; case LightNode: return "LightNode"; case ObjectNode: diff --git a/gfx/src/scene/node_impl.h b/gfx/src/scene/node_impl.h index e8d546f..4592fce 100644 --- a/gfx/src/scene/node_impl.h +++ b/gfx/src/scene/node_impl.h @@ -16,7 +16,6 @@ typedef struct SceneNode { union { anima_idx anima; camera_idx camera; - joint_idx joint; light_idx light; object_idx object; }; diff --git a/gfx/src/scene/scene_memory.c b/gfx/src/scene/scene_memory.c index cace654..2b900b9 100644 --- a/gfx/src/scene/scene_memory.c +++ b/gfx/src/scene/scene_memory.c @@ -16,7 +16,6 @@ DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS) DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS) DEF_MEMPOOL(camera_pool, SceneCamera, GFX_MAX_NUM_CAMERAS) -DEF_MEMPOOL(joint_pool, Joint, GFX_MAX_NUM_JOINTS) DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS) DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS) DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES) @@ -33,7 +32,6 @@ typedef struct SceneMemory { anima_pool animas; animation_pool animations; camera_pool cameras; - joint_pool joints; light_pool lights; material_pool materials; mesh_pool meshs; // Purposeful typo to make the PLURAL() macro work. @@ -59,7 +57,6 @@ void scene_mem_init() { mempool_make(&mem.animas); mempool_make(&mem.animations); mempool_make(&mem.cameras); - mempool_make(&mem.joints); mempool_make(&mem.lights); mempool_make(&mem.materials); mempool_make(&mem.meshs); @@ -74,7 +71,6 @@ void scene_mem_init() { ALLOC_DUMMY(&mem.animas); ALLOC_DUMMY(&mem.animations); ALLOC_DUMMY(&mem.cameras); - ALLOC_DUMMY(&mem.joints); ALLOC_DUMMY(&mem.lights); ALLOC_DUMMY(&mem.materials); ALLOC_DUMMY(&mem.meshs); @@ -107,7 +103,6 @@ void scene_mem_destroy() { DESTROY(anima); // Animations are owned by animas and do not have a destructor. DESTROY(camera); - DESTROY(joint); DESTROY(light); DESTROY(material); DESTROY(mesh); @@ -136,7 +131,6 @@ void scene_mem_destroy() { DEF_MEMORY(anima, Anima) DEF_MEMORY(animation, Animation) DEF_MEMORY(camera, SceneCamera) -DEF_MEMORY(joint, Joint) DEF_MEMORY(light, Light) DEF_MEMORY(material, Material) DEF_MEMORY(mesh, Mesh) diff --git a/gfx/src/scene/scene_memory.h b/gfx/src/scene/scene_memory.h index 9486ee6..04dbd20 100644 --- a/gfx/src/scene/scene_memory.h +++ b/gfx/src/scene/scene_memory.h @@ -29,7 +29,6 @@ void scene_mem_destroy(); DECL_MEMORY(anima, Anima) DECL_MEMORY(animation, Animation) DECL_MEMORY(camera, SceneCamera) -DECL_MEMORY(joint, Joint) DECL_MEMORY(light, Light) DECL_MEMORY(material, Material) DECL_MEMORY(mesh, Mesh) -- cgit v1.2.3