From 4bc4ca2796bd434880b77d3c4bcbb56107456777 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 2 Mar 2024 07:47:29 -0800 Subject: Compute joint bounding boxes. --- game/src/plugins/viewer.c | 66 ++--- gfx/include/gfx/renderer.h | 13 + gfx/include/gfx/scene/animation.h | 33 ++- gfx/include/gfx/scene/object.h | 4 + gfx/src/asset/asset_cache.c | 11 +- gfx/src/asset/model.c | 493 +++++++++++++++++++++++--------------- gfx/src/renderer/imm_renderer.c | 45 ++-- gfx/src/renderer/renderer.c | 6 +- gfx/src/scene/animation.c | 104 ++++++-- gfx/src/scene/animation_impl.h | 15 +- gfx/src/scene/object.c | 5 + gfx/src/scene/object_impl.h | 4 +- gfx/src/scene/types.h | 1 - 13 files changed, 520 insertions(+), 280 deletions(-) diff --git a/game/src/plugins/viewer.c b/game/src/plugins/viewer.c index 4f4ef03..5e8d7d3 100644 --- a/game/src/plugins/viewer.c +++ b/game/src/plugins/viewer.c @@ -7,6 +7,8 @@ #include #include +#include + #include // Paths to various scene files. @@ -20,6 +22,8 @@ static const char* DAMAGED_HELMET = "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf"; static const char* GIRL = "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf"; +static const char* BOXES = + "/home/jeanne/Nextcloud/assets/models/boxes/boxes.gltf"; #define DEFAULT_SCENE_FILE GIRL @@ -167,43 +171,48 @@ void update(Game* game, State* state, double t, double dt) { } /// Render the bounding boxes of all scene objects. -static void render_bounding_boxes_rec(ImmRenderer* imm, const SceneNode* node) { +static void render_bounding_boxes_rec( + ImmRenderer* imm, const Anima* anima, const mat4* parent_model_matrix, + const SceneNode* node) { assert(imm); assert(node); + const mat4 model_matrix = + mat4_mul(*parent_model_matrix, gfx_get_node_transform(node)); + const NodeType node_type = gfx_get_node_type(node); if (node_type == ModelNode) { const Model* model = gfx_get_node_model(node); const SceneNode* root = gfx_get_model_root(model); - render_bounding_boxes_rec(imm, root); + render_bounding_boxes_rec(imm, anima, &model_matrix, root); + } else if (node_type == AnimaNode) { + anima = gfx_get_node_anima(node); } else if (node_type == ObjectNode) { - // TODO: Look at the scene log. The JointNodes are detached from the - // ObjectNodes. This is why the boxes are not being transformed as expected - // here. Anima needs to animate boxes? Use OOBB in addition to AABB? - // - // TODO: Idea: when a model is loaded, compute an OOBB per joint using the - // vertices that are affected by the joint. Then transform this OOBB when - // animating the skeleton. Start with AABB for simplicity. The AABB/OOBB - // in the skeleton should be const. The transform AABB/OOBB is derived - // on demand. Stack allocator would be best for this kind of per-frame - // data. - // - // TODO: After computing joint AABB/OOBBs, check here whether the node has - // a skeleton, and if so, render the skeleton's boxes instead of the - // node's (the node's boxes are not animated, but computer from the rest - // pose). - const mat4 model = gfx_get_node_global_transform(node); - const SceneObject* obj = gfx_get_node_object(node); - const aabb3 box = gfx_get_object_aabb(obj); - gfx_imm_set_model_matrix(imm, &model); - gfx_imm_draw_aabb3(imm, box); + gfx_imm_set_model_matrix(imm, &model_matrix); + + const SceneObject* obj = gfx_get_node_object(node); + const Skeleton* skeleton = gfx_get_object_skeleton(obj); + + if (skeleton) { // Animated model. + assert(anima); + const size_t num_joints = gfx_get_skeleton_num_joints(skeleton); + for (size_t i = 0; i < num_joints; ++i) { + if (gfx_joint_has_box(anima, skeleton, i)) { + const Box box = gfx_get_joint_box(anima, skeleton, i); + gfx_imm_draw_box3(imm, box.vertices); + } + } + } else { // Static model. + const aabb3 box = gfx_get_object_aabb(obj); + gfx_imm_draw_aabb3(imm, box); + } } // Render children's boxes. const SceneNode* child = gfx_get_node_child(node); while (child) { - render_bounding_boxes_rec(imm, child); + render_bounding_boxes_rec(imm, anima, &model_matrix, child); child = gfx_get_node_sibling(child); } } @@ -218,17 +227,20 @@ static void render_bounding_boxes(const Game* game, const State* state) { assert(render_backend); assert(imm); + const mat4 id = mat4_id(); + Anima* anima = 0; + gfx_set_blending(render_backend, true); gfx_set_depth_mask(render_backend, false); - gfx_set_polygon_offset(render_backend, 0.5f, 0.5f); + gfx_set_polygon_offset(render_backend, -1.5f, -1.0f); gfx_imm_start(imm); gfx_imm_set_camera(imm, gfx_get_camera_camera(state->camera)); - gfx_imm_set_colour(imm, vec4_make(0.2, 0.2, 1.0, 0.3)); - render_bounding_boxes_rec(imm, gfx_get_scene_root(state->scene)); + gfx_imm_set_colour(imm, vec4_make(0.3, 0.3, 0.9, 0.1)); + render_bounding_boxes_rec(imm, anima, &id, gfx_get_scene_root(state->scene)); gfx_imm_end(imm); - gfx_set_polygon_offset(render_backend, 0.0f, 0.0f); + gfx_reset_polygon_offset(render_backend); gfx_set_depth_mask(render_backend, true); gfx_set_blending(render_backend, false); } diff --git a/gfx/include/gfx/renderer.h b/gfx/include/gfx/renderer.h index 78d8bbd..9236e3f 100644 --- a/gfx/include/gfx/renderer.h +++ b/gfx/include/gfx/renderer.h @@ -66,6 +66,19 @@ void gfx_imm_draw_aabb2(ImmRenderer*, aabb2); /// Draw a bounding box. void gfx_imm_draw_aabb3(ImmRenderer*, aabb3); +/// Draw a box. +/// +/// The vertices must be given in the following order: +/// +/// 7 ----- 6 +/// / /| +/// 3 ----- 2 | +/// | | | +/// | 4 ----- 5 +/// |/ |/ +/// 0 ----- 1 +void gfx_imm_draw_box3(ImmRenderer* renderer, const vec3 vertices[8]); + /// Set the camera. void gfx_imm_set_camera(ImmRenderer*, const Camera*); diff --git a/gfx/include/gfx/scene/animation.h b/gfx/include/gfx/scene/animation.h index 42c0c43..d95b895 100644 --- a/gfx/include/gfx/scene/animation.h +++ b/gfx/include/gfx/scene/animation.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -22,21 +23,26 @@ typedef struct Joint Joint; typedef struct Skeleton Skeleton; /// Index type used to store relative indices into arrays. -typedef uint16_t rel_idx; +typedef uint16_t joint_idx; /// Index value denoting no index. -static const rel_idx INDEX_NONE = (rel_idx)-1; +static const joint_idx INDEX_NONE = (joint_idx)-1; + +typedef struct Box { + vec3 vertices[8]; +} Box; /// Joint descriptor. typedef struct JointDesc { - rel_idx parent; /// Parent Joint; index into Anima's joints. - mat4 inv_bind_matrix; /// Transforms the mesh into the joint's local space. + joint_idx parent; /// Parent Joint; index into Anima's joints. + mat4 inv_bind_matrix; /// Transforms the mesh into the joint's local space. + aabb3 box; /// Bounding box. } JointDesc; /// Skeleton descriptor. typedef struct SkeletonDesc { - size_t num_joints; - rel_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array. + size_t num_joints; + joint_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array. } SkeletonDesc; /// Animation interpolation mode. @@ -67,7 +73,7 @@ typedef struct KeyframeDesc { /// Animation channel descriptor. typedef struct ChannelDesc { - rel_idx target; /// Index into Anima's joints array. + joint_idx target; /// Index into Anima's joints array. ChannelType type; AnimationInterpolation interpolation; size_t num_keyframes; @@ -121,3 +127,16 @@ void gfx_stop_animation(Anima*); /// Return the anima's ith skeleton. const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i); + +/// Return the number of joints in the skeleton. +size_t gfx_get_skeleton_num_joints(const Skeleton*); + +/// Return true if the skeleton's ith joint has a bounding box. +/// +/// IK joints that do not directly transform vertices have no bounding box. +bool gfx_joint_has_box(const Anima*, const Skeleton*, size_t joint); + +/// Return the bounding box of the skeleton's ith joint. +/// +/// IK joints that do not directly transform vertices have no box. +Box gfx_get_joint_box(const Anima*, const Skeleton*, size_t joint); diff --git a/gfx/include/gfx/scene/object.h b/gfx/include/gfx/scene/object.h index 891c3cd..7579d29 100644 --- a/gfx/include/gfx/scene/object.h +++ b/gfx/include/gfx/scene/object.h @@ -29,6 +29,10 @@ void gfx_destroy_object(SceneObject**); /// Set the object's skeleton. void gfx_set_object_skeleton(SceneObject*, const Skeleton*); +/// Get the object's skeleton. +/// Return null if the object has no skeleton. +const Skeleton* gfx_get_object_skeleton(const SceneObject*); + /// Gets the object's bounding box. /// /// The object's bounding box is the bounding box of its mesh geometries. diff --git a/gfx/src/asset/asset_cache.c b/gfx/src/asset/asset_cache.c index 1d64d66..d077421 100644 --- a/gfx/src/asset/asset_cache.c +++ b/gfx/src/asset/asset_cache.c @@ -152,10 +152,13 @@ static void log_model_loaded(const LoadModelCmd* cmd) { static Model* clone_model(const Model* model) { assert(model); - // Only the Anima needs to be (shallow) cloned since everything else in the - // model is static. Also note that only the Anima's joints and animation state - // need to be cloned; all other members can be shared. So a shallow clone of - // the anima is sufficient. + // Only the Anima needs to be cloned since everything else in the model is + // static. + // + // The Anima can be partially shallow-cloned. Skeletons and animations are + // static and can be shared with the original Anima. Other members are + // deep-cloned. Skeletons in particular point back to their Anima, so need to + // be deep-cloned. const SceneNode* root = mem_get_node(model->root); if (gfx_get_node_type(root) == AnimaNode) { const Anima* anima = gfx_get_node_anima(root); diff --git a/gfx/src/asset/model.c b/gfx/src/asset/model.c index 37f129e..2053dc4 100644 --- a/gfx/src/asset/model.c +++ b/gfx/src/asset/model.c @@ -94,6 +94,7 @@ #include "gfx/sizes.h" #include "gfx/util/shader.h" +#include "gfx_assert.h" #include "scene/model_impl.h" #include "cstring.h" @@ -110,7 +111,6 @@ #define CGLTF_IMPLEMENTATION #include "cgltf.h" -#include #include #include @@ -323,204 +323,231 @@ static cgltf_size get_component_size(cgltf_component_type type) { return 0; } +/// Return the number dimensionality of the given data type. +int get_num_dimensions(cgltf_type type) { + switch (type) { + case cgltf_type_scalar: + return 1; + case cgltf_type_vec2: + return 2; + case cgltf_type_vec3: + return 3; + case cgltf_type_vec4: + return 4; + case cgltf_type_mat2: + return 4; // 2x2 + case cgltf_type_mat3: + return 9; // 3x3 + case cgltf_type_mat4: + return 16; // 4x4 + case cgltf_type_invalid: + FAIL(""); + break; + } + FAIL(""); + return 0; +} + +/// Read an int64 from the given data pointer and accessor. +/// The largest integer in glTF is u32, so we can fit all integers in an int64. +static int64_t read_int(const void* component, const cgltf_accessor* accessor) { + assert(component); + assert(accessor); + + switch (accessor->component_type) { + case cgltf_component_type_r_8: { + const int8_t c = *((int8_t*)component); + return c; + } + case cgltf_component_type_r_8u: { + const uint8_t c = *((uint8_t*)component); + return c; + } + case cgltf_component_type_r_16: { + const int16_t c = *((int16_t*)component); + return c; + } + case cgltf_component_type_r_16u: { + const uint16_t c = *((uint16_t*)component); + return c; + } + case cgltf_component_type_r_32u: { + const uint32_t c = *((uint32_t*)component); + return c; + } + case cgltf_component_type_r_32f: { + const float c = *((float*)component); + return (int64_t)c; + } + case cgltf_component_type_invalid: + FAIL(""); + break; + } + FAIL(""); + return 0; +} + /// Read a float from the given data pointer and accessor. /// /// This function uses the normalization equations from the spec. See the /// animation section: /// /// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations -static float read_float(const void* data, const cgltf_accessor* accessor) { - assert(data); +static float read_float(const void* component, const cgltf_accessor* accessor) { + assert(component); assert(accessor); switch (accessor->component_type) { case cgltf_component_type_r_8: { - assert(accessor->normalized); - const int8_t c = *((int8_t*)data); + // assert(accessor->normalized); + const int8_t c = *((int8_t*)component); return max((float)c / 127.0, -1.0); } case cgltf_component_type_r_8u: { - assert(accessor->normalized); - const uint8_t c = *((uint8_t*)data); + // assert(accessor->normalized); + const uint8_t c = *((uint8_t*)component); return (float)c / 255.0; } case cgltf_component_type_r_16: { - assert(accessor->normalized); - const int16_t c = *((int16_t*)data); + // assert(accessor->normalized); + const int16_t c = *((int16_t*)component); return max((float)c / 32767.0, -1.0); } case cgltf_component_type_r_16u: { - assert(accessor->normalized); - const uint16_t c = *((uint16_t*)data); + // assert(accessor->normalized); + const uint16_t c = *((uint16_t*)component); return (float)c / 65535.0; } case cgltf_component_type_r_32u: { - assert(accessor->normalized); - const uint32_t c = *((uint32_t*)data); + // assert(accessor->normalized); + const uint32_t c = *((uint32_t*)component); return (float)c / 4294967295.0; } case cgltf_component_type_r_32f: { - const float c = *((float*)data); + const float c = *((float*)component); return c; } case cgltf_component_type_invalid: - assert(false); + FAIL(""); break; } - assert(false); + FAIL(""); return 0; } -/// Iterate over the vectors in an accessor. -#define ACCESSOR_FOREACH_VEC(dimensions, accessor, body) \ - { \ - assert((1 <= dimensions) && (dimensions <= 4)); \ - assert( \ - ((dimensions == 1) && (accessor->type == cgltf_type_scalar)) || \ - ((dimensions == 2) && (accessor->type == cgltf_type_vec2)) || \ - ((dimensions == 3) && (accessor->type == cgltf_type_vec3)) || \ - ((dimensions == 4) && (accessor->type == cgltf_type_vec4))); \ - const cgltf_buffer_view* view = accessor->buffer_view; \ - const cgltf_buffer* buffer = view->buffer; \ - const cgltf_size offset = accessor->offset + view->offset; \ - const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ - /* Component size in bytes. */ \ - const cgltf_size comp_size = get_component_size(accessor->component_type); \ - /* Element size in bytes. */ \ - const cgltf_size elem_size = dimensions * comp_size; \ - /* Stride in bytes. If the view stride is 0, then the elements are tightly \ - * packed. */ \ - const cgltf_size stride = view->stride != 0 ? view->stride : elem_size; \ - /* There isn't an accessor stride in the spec, but cgltf still specifies \ - * one. */ \ - assert(accessor->stride == elem_size); \ - /* Accessor data must fit inside the buffer. */ \ - assert( \ - (offset + (accessor->count * elem_size) + \ - ((accessor->count - 1) * view->stride)) <= buffer->size); \ - /* Accessor data must fit inside the view. */ \ - assert(accessor->count * accessor->stride <= view->size); \ - cgltf_float x = 0, y = 0, z = 0, w = 0; \ - /* Silence unused variable warnings. */ \ - (void)y; \ - (void)z; \ - (void)w; \ - /* The {component type} X {dimensions} combinations are a pain to handle. \ - For floats, we switch on type first and then lay out a loop for each \ - dimension to get a tight loop with a possibly inlined body. For other \ - types, we take the performance hit and perform checks and conversions \ - inside the loop for simplicity. */ \ - if (accessor->component_type == cgltf_component_type_r_32f) { \ - switch (dimensions) { \ - case 1: \ - assert(accessor->type == cgltf_type_scalar); \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - x = *floats; \ - body; \ - } \ - break; \ - case 2: \ - assert(accessor->type == cgltf_type_vec2); \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - x = *floats++; \ - y = *floats; \ - body; \ - } \ - break; \ - case 3: \ - assert(accessor->type == cgltf_type_vec3); \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - x = *floats++; \ - y = *floats++; \ - z = *floats; \ - body; \ - } \ - break; \ - case 4: \ - assert(accessor->type == cgltf_type_vec4); \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - x = *floats++; \ - y = *floats++; \ - z = *floats++; \ - w = *floats; \ - body; \ - } \ - break; \ - } \ - } else { \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const uint8_t* component = bytes; \ - \ - x = read_float(component, accessor); \ - component += comp_size; \ - if (dimensions > 1) { \ - y = read_float(component, accessor); \ - component += comp_size; \ - } \ - if (dimensions > 2) { \ - z = read_float(component, accessor); \ - component += comp_size; \ - } \ - if (dimensions > 3) { \ - w = read_float(component, accessor); \ - component += comp_size; \ - } \ - body; \ - } \ - } \ - } - -/// Iterate over the matrices in an accessor. -#define ACCESSOR_FOREACH_MAT(dimensions, accessor, body) \ - { \ - assert((2 <= dimensions) && (dimensions <= 4)); \ - assert(!(dimensions == 2) || (accessor->type == cgltf_type_mat2)); \ - assert(!(dimensions == 3) || (accessor->type == cgltf_type_mat3)); \ - assert(!(dimensions == 4) || (accessor->type == cgltf_type_mat4)); \ - const cgltf_buffer_view* view = accessor->buffer_view; \ - const cgltf_buffer* buffer = view->buffer; \ - const cgltf_size offset = accessor->offset + view->offset; \ - const cgltf_size comp_size = get_component_size(accessor->component_type); \ - const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ - assert( \ - (offset + accessor->count * dimensions * comp_size) < buffer->size); \ - /* From the spec: */ \ - /* "Buffer views with other types of data MUST NOT not define */ \ - /* byteStride (unless such layout is explicitly enabled by an */ \ - /* extension)."*/ \ - assert(view->stride == 0); \ - assert(accessor->stride == dimensions * dimensions * comp_size); \ - assert(accessor->component_type == cgltf_component_type_r_32f); \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - switch (dimensions) { \ - case 2: \ - assert(accessor->type == cgltf_type_mat2); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - body; \ - floats += 4; \ - } \ - break; \ - case 3: \ - assert(accessor->type == cgltf_type_mat3); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - body; \ - floats += 9; \ - } \ - break; \ - case 4: \ - assert(accessor->type == cgltf_type_mat4); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - body; \ - floats += 16; \ - } \ - break; \ - } \ +typedef struct AccessorIter { + const cgltf_accessor* accessor; + const uint8_t* next_element; + cgltf_size comp_size; // Component size in bytes. + cgltf_size stride; // ELement stride in bytes. + cgltf_size index; // Index of the next element. + bool is_matrix; +} AccessorIter; + +typedef struct AccessorData { + union { + struct { + float x, y, z, w; // Possibly normalized. + int64_t xi, yi, zi, wi; // Always unnormalized. + }; + const float* floats; + }; +} AccessorData; + +bool accessor_iter_next(AccessorIter* iter, AccessorData* data) { + assert(iter); + assert(data); + + if (iter->index < iter->accessor->count) { + const int dimensions = get_num_dimensions(iter->accessor->type); + const uint8_t* component = iter->next_element; + + // So that the caller can access the element's components as an array. + data->floats = (const float*)component; + + if (!iter->is_matrix) { // Scalar or vector. + // x + data->x = read_float(component, iter->accessor); + data->xi = read_int(component, iter->accessor); + component += iter->comp_size; + // y + if (dimensions > 1) { + data->y = read_float(component, iter->accessor); + data->yi = read_int(component, iter->accessor); + component += iter->comp_size; + } + // z + if (dimensions > 2) { + data->z = read_float(component, iter->accessor); + data->zi = read_int(component, iter->accessor); + component += iter->comp_size; + } + // w + if (dimensions > 3) { + data->w = read_float(component, iter->accessor); + data->wi = read_int(component, iter->accessor); + component += iter->comp_size; + } + } + + iter->next_element += iter->stride; + iter->index++; + return true; } + return false; +} + +AccessorIter make_accessor_iter(const cgltf_accessor* accessor) { + assert(accessor); + + const bool is_matrix = (accessor->type == cgltf_type_mat2) || + (accessor->type == cgltf_type_mat3) || + (accessor->type == cgltf_type_mat4); + + const int dimensions = get_num_dimensions(accessor->type); + assert( + ((dimensions == 1) && (accessor->type == cgltf_type_scalar)) || + ((dimensions == 2) && (accessor->type == cgltf_type_vec2)) || + ((dimensions == 3) && (accessor->type == cgltf_type_vec3)) || + ((dimensions == 4) && (accessor->type == cgltf_type_vec4)) || + ((dimensions == 4) && (accessor->type == cgltf_type_mat2)) || + ((dimensions == 9) && (accessor->type == cgltf_type_mat3)) || + ((dimensions == 16) && (accessor->type == cgltf_type_mat4))); + + const cgltf_buffer_view* view = accessor->buffer_view; + const cgltf_buffer* buffer = view->buffer; + const cgltf_size offset = accessor->offset + view->offset; + const uint8_t* bytes = (const uint8_t*)buffer->data + offset; + // Component size in bytes. + const cgltf_size comp_size = get_component_size(accessor->component_type); + // Element size in bytes. + const cgltf_size elem_size = dimensions * comp_size; + // Stride in bytes. If the view stride is 0, then the elements are tightly + // packed. + const cgltf_size stride = view->stride != 0 ? view->stride : elem_size; + + // There isn't an accessor stride in the spec, but cgltf still specifies one. + assert(accessor->stride == elem_size); + + // Accessor data must fit inside the view. + assert(accessor->offset + (accessor->count * accessor->stride) <= view->size); + + // Accessor data must fit inside the buffer. + assert( + (offset + (accessor->count * elem_size) + + ((accessor->count - 1) * view->stride)) <= buffer->size); + + return (AccessorIter){ + .accessor = accessor, + .next_element = bytes, + .comp_size = comp_size, + .stride = stride, + .index = 0, + .is_matrix = is_matrix, + }; +} + /// Return the total number of primitives in the scene. Each mesh may contain /// multiple primitives. /// @@ -703,7 +730,7 @@ static bool load_texture_and_uniform( textures[texture_index] = gfx_load_texture(gfx, cmd); if (!textures[texture_index]) { - prepend_error( + log_error( "Failed to load texture: %s", mstring_cstr(&cmd->data.texture.filepath)); return false; @@ -856,20 +883,25 @@ static Material* make_default_material() { /// Compute the bounding box of the vertices pointed to by the accessor. /// 'dim' is the dimension of the vertices (2D or 3D). -aabb3 compute_aabb(const cgltf_accessor* accessor, int dim) { +aabb3 compute_aabb(const cgltf_accessor* accessor) { aabb3 box = {0}; if (accessor->has_min && accessor->has_max) { box = aabb3_make( vec3_from_array(accessor->min), vec3_from_array(accessor->max)); } else { - ACCESSOR_FOREACH_VEC(dim, accessor, { - const vec3 p = vec3_make(x, y, z); + AccessorIter iter = make_accessor_iter(accessor); + AccessorData vertex = {0}; + cgltf_size i = 0; + + while (accessor_iter_next(&iter, &vertex)) { + const vec3 p = vec3_make(vertex.x, vertex.y, vertex.z); if (i == 0) { box = aabb3_make(p, p); } else { box = aabb3_add(box, p); } - }); + ++i; + } } return box; } @@ -1000,15 +1032,15 @@ static bool load_meshes( case cgltf_type_vec2: assert(geometry_desc.positions3d.buffer == 0); buffer_view_2d = &geometry_desc.positions2d; - geometry_desc.aabb = compute_aabb(accessor, 2); + geometry_desc.aabb = compute_aabb(accessor); break; case cgltf_type_vec3: assert(geometry_desc.positions2d.buffer == 0); buffer_view_3d = &geometry_desc.positions3d; - geometry_desc.aabb = compute_aabb(accessor, 3); + geometry_desc.aabb = compute_aabb(accessor); break; default: - LOGE( + FAIL( "Unhandled accessor type %d in vertex positions", accessor->type); assert(false); @@ -1186,6 +1218,77 @@ static bool load_meshes( return true; } +/// Compute bounding boxes for the joints in the model. +static void compute_joint_bounding_boxes( + const cgltf_data* data, size_t num_joints, JointDesc* joint_descs) { + assert(data); + assert(joint_descs); + assert(num_joints <= GFX_MAX_NUM_JOINTS); + + // Initialize bounding boxes so that we can compute unions below. + for (size_t i = 0; i < num_joints; ++i) { + joint_descs[i].box = aabb3_make_empty(); + } + + // Iterate over the meshes -> primitives -> vertices -> joint indices, and add + // the vertex to the joint's bounding box. + for (cgltf_size n = 0; n < data->nodes_count; ++n) { + const cgltf_node* node = &data->nodes[n]; + + if (node->skin) { + if (node->mesh) { + const cgltf_mesh* mesh = node->mesh; + + for (cgltf_size pr = 0; pr < mesh->primitives_count; ++pr) { + const cgltf_primitive* prim = &mesh->primitives[pr]; + + // Find the indices of the positions and joints arrays in the + // primitive's attributes. + int positions_index = -1; + int joints_index = -1; + for (int a = 0; a < (int)prim->attributes_count; ++a) { + const cgltf_attribute* attrib = &prim->attributes[a]; + + if (attrib->type == cgltf_attribute_type_position) { + positions_index = a; + } else if (attrib->type == cgltf_attribute_type_joints) { + joints_index = a; + } + } + + if ((positions_index != -1) && (joints_index != -1)) { + const cgltf_accessor* positions = + prim->attributes[positions_index].data; + const cgltf_accessor* joints = prim->attributes[joints_index].data; + + assert(positions->count == joints->count); + + AccessorIter positions_iter = make_accessor_iter(positions); + AccessorIter joints_iter = make_accessor_iter(joints); + AccessorData position = {0}, joint = {0}; + + while (accessor_iter_next(&positions_iter, &position)) { + const bool advance = accessor_iter_next(&joints_iter, &joint); + assert(advance); // Counts should match. + + const vec3 p = vec3_make(position.x, position.y, position.z); + const int64_t j[4] = {joint.xi, joint.yi, joint.wi, joint.zi}; + + for (int i = 0; i < 4; ++i) { + const size_t joint_index = j[i]; + assert((size_t)joint_index < num_joints); + + joint_descs[joint_index].box = + aabb3_add(joint_descs[joint_index].box, p); + } + } + } + } + } + } + } +} + /// Find the joint node with the smallest index across all skeletons. /// /// The channels in glTF may target arbitrary nodes in the scene (those nodes @@ -1249,8 +1352,10 @@ static size_t load_skins( *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count}; // 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); + AccessorIter iter = make_accessor_iter(matrices_accessor); + AccessorData matrix = {0}; + for (cgltf_size i = 0; accessor_iter_next(&iter, &matrix); ++i) { + const mat4 inv_bind_matrix = mat4_from_array(matrix.floats); // Joint is an index/pointer into the nodes array. const cgltf_size node_index = skin->joints[i] - data->nodes; @@ -1275,7 +1380,7 @@ static size_t load_skins( 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 skin's // (skeleton's) node hierarchy. @@ -1352,23 +1457,32 @@ static void load_animations( .num_keyframes = 0}; // Read time inputs. - ACCESSOR_FOREACH_VEC(1, sampler->input, { - channel_desc->keyframes[i].time = x; + AccessorIter iter = make_accessor_iter(sampler->input); + AccessorData input = {0}; + for (cgltf_size i = 0; accessor_iter_next(&iter, &input); ++i) { + channel_desc->keyframes[i].time = input.x; channel_desc->num_keyframes++; - }); + } // Read transform outputs. + AccessorData output = {0}; switch (channel->target_path) { - case cgltf_animation_path_type_translation: - ACCESSOR_FOREACH_VEC(3, sampler->output, { - channel_desc->keyframes[i].translation = vec3_make(x, y, z); - }); + case cgltf_animation_path_type_translation: { + iter = make_accessor_iter(sampler->output); + for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) { + channel_desc->keyframes[i].translation = + vec3_make(output.x, output.y, output.z); + } break; - case cgltf_animation_path_type_rotation: - ACCESSOR_FOREACH_VEC(4, sampler->output, { - channel_desc->keyframes[i].rotation = qmake(x, y, z, w); - }); + } + case cgltf_animation_path_type_rotation: { + iter = make_accessor_iter(sampler->output); + for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) { + channel_desc->keyframes[i].rotation = + qmake(output.x, output.y, output.z, output.w); + } break; + } default: // TODO: Handle other channel transformations. break; @@ -1421,10 +1535,6 @@ static void load_nodes( assert(skin_index < data->skins_count); const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); gfx_set_object_skeleton(object, skeleton); - - // TODO: Compute AABBs/OOBBs for the skeleton's joints here. Iterate - // over the mesh's primitives, its vertices, their joint indices, and - // add the vertex to the AABB/OOBB. } } else if (node->camera) { assert(next_camera < data->cameras_count); @@ -1686,6 +1796,9 @@ static Model* load_scene( anima_desc->num_joints = load_skins(data, buffers, base, anima_desc); load_animations(data, base, anima_desc); + compute_joint_bounding_boxes( + data, anima_desc->num_joints, anima_desc->joints); + anima = gfx_make_anima(anima_desc); gfx_construct_anima_node(root_node, anima); } diff --git a/gfx/src/renderer/imm_renderer.c b/gfx/src/renderer/imm_renderer.c index e8c0410..dd5e2cb 100644 --- a/gfx/src/renderer/imm_renderer.c +++ b/gfx/src/renderer/imm_renderer.c @@ -135,32 +135,45 @@ void gfx_imm_draw_aabb3(ImmRenderer* renderer, aabb3 box) { assert(renderer); // clang-format off - const vec3 verts[8] = { - box.min, // 2 ----- 6 - vec3_make(box.min.x, box.min.y, box.max.z), // / /| - vec3_make(box.min.x, box.max.y, box.min.z), // 3 ----- 7 | + const vec3 vertices[8] = { + vec3_make(box.min.x, box.min.y, box.max.z), // 7 ----- 6 + vec3_make(box.max.x, box.min.y, box.max.z), // / /| + vec3_make(box.max.x, box.max.y, box.max.z), // 3 ----- 2 | vec3_make(box.min.x, box.max.y, box.max.z), // | | | - vec3_make(box.max.x, box.min.y, box.min.z), // | 0 ----- 4 - vec3_make(box.max.x, box.min.y, box.max.z), // |/ |/ - vec3_make(box.max.x, box.max.y, box.min.z), // 1 ----- 5 - box.max}; + vec3_make(box.min.x, box.min.y, box.min.z), // | 4 ----- 5 + vec3_make(box.max.x, box.min.y, box.min.z), // |/ |/ + vec3_make(box.max.x, box.max.y, box.min.z), // 0 ----- 1 + vec3_make(box.min.x, box.max.y, box.min.z)}; // clang-format on -#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2] - // TODO: Use vertex indices in Geometry. + gfx_imm_draw_box3(renderer, vertices); +} + +void gfx_imm_draw_box3(ImmRenderer* renderer, const vec3 vertices[8]) { + assert(renderer); + assert(vertices); + + // 7 ----- 6 + // / /| + // 3 ----- 2 | + // | | | + // | 4 ----- 5 + // |/ |/ + // 0 ----- 1 + +#define tri(i0, i1, i2) vertices[i0], vertices[i1], vertices[i2] const vec3 tris[36] = {// Front. - tri(1, 5, 7), tri(1, 7, 3), + tri(0, 1, 2), tri(0, 2, 3), // Right. - tri(5, 4, 6), tri(5, 6, 7), + tri(1, 5, 6), tri(1, 6, 2), // Back. - tri(4, 0, 2), tri(4, 2, 6), + tri(5, 4, 7), tri(5, 7, 6), // Left. - tri(0, 1, 3), tri(0, 3, 2), + tri(4, 0, 03), tri(4, 3, 7), // Top. - tri(3, 7, 6), tri(3, 6, 2), + tri(3, 2, 6), tri(3, 6, 7), // Bottom. tri(0, 4, 5), tri(0, 5, 1)}; -#undef tri gfx_imm_draw_triangles(renderer, tris, 12); } diff --git a/gfx/src/renderer/renderer.c b/gfx/src/renderer/renderer.c index 161eed2..2244733 100644 --- a/gfx/src/renderer/renderer.c +++ b/gfx/src/renderer/renderer.c @@ -197,9 +197,9 @@ static void load_skeleton(RenderState* state, skeleton_idx skeleton_index) { state->num_joints = skeleton->num_joints; for (size_t i = 0; i < skeleton->num_joints; ++i) { - const rel_idx joint_index = skeleton->joints[i]; - const Joint* joint = &state->anima->joints[joint_index]; - state->joint_matrices[i] = joint->joint_matrix; + const joint_idx joint_index = skeleton->joints[i]; + const Joint* joint = &state->anima->joints[joint_index]; + state->joint_matrices[i] = joint->joint_matrix; } } diff --git a/gfx/src/scene/animation.c b/gfx/src/scene/animation.c index 18e2a99..08d02ce 100644 --- a/gfx/src/scene/animation.c +++ b/gfx/src/scene/animation.c @@ -9,7 +9,7 @@ static const R PLAYBACK_UNINITIALIZED = -1; -static rel_idx get_anima_root_joint_index(Anima* anima) { +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); @@ -21,7 +21,7 @@ static Joint* get_anima_root_joint(Anima* anima) { return &anima->joints[get_anima_root_joint_index(anima)]; } -static Joint* get_anima_joint(Anima* anima, rel_idx index) { +static const Joint* get_anima_joint(const Anima* anima, joint_idx index) { assert(anima); assert(index < GFX_MAX_NUM_JOINTS); assert(index != INDEX_NONE); @@ -29,22 +29,33 @@ static Joint* get_anima_joint(Anima* anima, rel_idx index) { 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, rel_idx joint_index, rel_idx parent_index) { + 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(anima, parent_index); + 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(anima, parent->child); + Joint* child = get_anima_joint_mut(anima, parent->child); while (child->next != INDEX_NONE) { - child = get_anima_joint(anima, child->next); + child = get_anima_joint_mut(anima, child->next); } // Wire up this joint as the last child's sibling. child->next = joint_index; @@ -64,6 +75,7 @@ static void make_joint(Anima* anima, const JointDesc* desc, Joint* joint) { 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) { @@ -88,6 +100,7 @@ static Animation* make_animation(const AnimationDesc* desc) { 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]; @@ -110,6 +123,7 @@ static Animation* make_animation(const AnimationDesc* desc) { 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); @@ -122,7 +136,7 @@ Anima* gfx_make_anima(const AnimaDesc* desc) { 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; + const joint_idx parent = desc->joints[i].parent; assert(parent != INDEX_NONE); assert(parent < desc->num_joints); } @@ -134,10 +148,7 @@ Anima* gfx_make_anima(const AnimaDesc* desc) { // 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]); - // TODO: Here and everywhere else, I think it would simplify the code - // greatly to make mem_alloc_xyz() fail if the allocation fails. At that - // point the user should just bump their memory limits. + 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; @@ -166,7 +177,7 @@ Anima* gfx_make_anima(const AnimaDesc* desc) { // 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); + 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. @@ -339,7 +350,7 @@ static void animate_channel(Anima* anima, const Channel* channel, R t) { // work. t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t; - Joint* target = get_anima_joint(anima, channel->target); + Joint* target = get_anima_joint_mut(anima, channel->target); switch (channel->type) { case RotationChannel: { @@ -380,7 +391,7 @@ static void compute_joint_matrices_rec( // Recursively compute the joint matrices for this joint's siblings. if (joint->next != INDEX_NONE) { - Joint* sibling = get_anima_joint(anima, joint->next); + Joint* sibling = get_anima_joint_mut(anima, joint->next); compute_joint_matrices_rec( anima, sibling, parent_global_joint_transform, @@ -389,7 +400,7 @@ static void compute_joint_matrices_rec( // Recursively compute the joint matrices for this joint's children. if (joint->child != INDEX_NONE) { - Joint* child = get_anima_joint(anima, joint->child); + Joint* child = get_anima_joint_mut(anima, joint->child); compute_joint_matrices_rec( anima, child, &global_joint_transform, root_inv_global_transform); @@ -431,16 +442,14 @@ void gfx_update_animation(Anima* anima, R t) { // Compute joint matrices after having transformed the skeletons. // - // Skeletons are not guaranteed to have a common parent, but are generally a - // set of disjoint trees (glTF). The anima's parent node, however, is - // guaranteed to be the common ancestor of all 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 therefore begins by descending from the anima's - // node. This node's immediate children may not be joints, however, so we need - // to gracefully handle them and proceed recursively. + // Joint matrix calculation begins by descending from the anima's root joint, + // which we have constructed to be the common root of all skeletons. // - // Lack of a common parent aside, the procedure touches every joint exactly - // once (and potentially other non-joint intermediate nodes). + // This procedure touches every joint exactly once. 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); @@ -464,3 +473,52 @@ const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i) { 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/gfx/src/scene/animation_impl.h b/gfx/src/scene/animation_impl.h index 7265858..4408158 100644 --- a/gfx/src/scene/animation_impl.h +++ b/gfx/src/scene/animation_impl.h @@ -24,18 +24,19 @@ typedef struct Buffer Buffer; /// Joints are mutable and store the transform and joint matrices that result /// from animation, aside from the inverse bind matrix. typedef struct Joint { - 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_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; - rel_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array. + joint_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array. } Skeleton; /// A keyframe of animation. @@ -49,7 +50,7 @@ typedef struct Keyframe { /// Animation channel. typedef struct Channel { - rel_idx target; /// Index into Anima's joints array. + joint_idx target; /// Index into Anima's joints array. ChannelType type; AnimationInterpolation interpolation; size_t num_keyframes; diff --git a/gfx/src/scene/object.c b/gfx/src/scene/object.c index 9291feb..406c81f 100644 --- a/gfx/src/scene/object.c +++ b/gfx/src/scene/object.c @@ -72,6 +72,11 @@ void gfx_set_object_skeleton(SceneObject* object, const Skeleton* skeleton) { object->skeleton = mem_get_skeleton_index(skeleton); } +const Skeleton* gfx_get_object_skeleton(const SceneObject* object) { + assert(object); + return (object->skeleton.val == 0) ? 0 : mem_get_skeleton(object->skeleton); +} + aabb3 gfx_get_object_aabb(const SceneObject* object) { assert(object); return object->box; diff --git a/gfx/src/scene/object_impl.h b/gfx/src/scene/object_impl.h index 91e1427..88f8e31 100644 --- a/gfx/src/scene/object_impl.h +++ b/gfx/src/scene/object_impl.h @@ -20,7 +20,7 @@ typedef struct MeshLink { /// of Meshes, and the Meshes are re-used. typedef struct SceneObject { mesh_link_idx mesh_link; /// First MeshLink in the list. - skeleton_idx skeleton; - node_idx parent; /// Parent SceneNode. + skeleton_idx skeleton; /// 0 for static objects. + node_idx parent; /// Parent SceneNode. aabb3 box; } SceneObject; diff --git a/gfx/src/scene/types.h b/gfx/src/scene/types.h index 9752bcf..d0ffc41 100644 --- a/gfx/src/scene/types.h +++ b/gfx/src/scene/types.h @@ -13,7 +13,6 @@ typedef uint16_t gfx_idx; DEF_STRONG_INDEX(anima, gfx_idx) DEF_STRONG_INDEX(animation, gfx_idx) DEF_STRONG_INDEX(camera, gfx_idx) -DEF_STRONG_INDEX(joint, gfx_idx) DEF_STRONG_INDEX(light, gfx_idx) DEF_STRONG_INDEX(material, gfx_idx) DEF_STRONG_INDEX(mesh, gfx_idx) -- cgit v1.2.3