From 2e1a604d7b8148d9d7224a3dac456a2fbf0b33fc Mon Sep 17 00:00:00 2001 From: Camden Dixie O'Brien Date: Wed, 1 Oct 2025 13:58:00 +0100 Subject: [PATCH] Load bones, bone indices and weights into model --- linear_alg.h | 6 ++ model.c | 20 +++-- model.h | 11 +++ model_format_glb.c | 180 +++++++++++++++++++++++++++----------------- model_format_utah.c | 2 +- 5 files changed, 145 insertions(+), 74 deletions(-) diff --git a/linear_alg.h b/linear_alg.h index 6d1679f..09fcaa6 100644 --- a/linear_alg.h +++ b/linear_alg.h @@ -1,6 +1,8 @@ #ifndef LINEAR_ALG_H #define LINEAR_ALG_H +#include + #define PI 3.14159265358979323846264 typedef struct { @@ -15,6 +17,10 @@ typedef struct { vec4_t x, y, z, w; } mat4_t; +typedef struct { + uint16_t x, y, z, w; +} u16_vec4_t; + // clang-format off #define MAT4_ID \ (mat4_t) { \ diff --git a/model.c b/model.c index cd55d7b..45b64e7 100644 --- a/model.c +++ b/model.c @@ -16,9 +16,19 @@ model_t model_init(size_t vertex_count, size_t index_count) void model_cleanup(model_t *m) { - free(m->vertices); - free(m->indices); - m->vertices = nullptr; - m->indices = nullptr; - m->vertex_count = m->index_count = 0; + if (m->vertices != nullptr) { + free(m->vertices); + m->vertices = nullptr; + m->vertex_count = 0; + } + if (m->indices != nullptr) { + free(m->indices); + m->indices = nullptr; + m->index_count = 0; + } + if (m->bones != nullptr) { + free(m->bones); + m->bones = nullptr; + m->bone_count = 0; + } } diff --git a/model.h b/model.h index 03061ea..50ba14b 100644 --- a/model.h +++ b/model.h @@ -11,13 +11,24 @@ typedef uint16_t index_t; typedef struct { vec3_t position; vec3_t normal; + u16_vec4_t bones; + vec4_t weights; } vertex_t; +typedef struct { + uint32_t parent; + vec3_t translation; + vec4_t rotation; + vec3_t scale; +} bone_t; + typedef struct { vertex_t *vertices; index_t *indices; + bone_t *bones; size_t vertex_count; size_t index_count; + size_t bone_count; } model_t; model_t model_init(size_t vertex_count, size_t index_count); diff --git a/model_format_glb.c b/model_format_glb.c index 3e35700..cd80d15 100644 --- a/model_format_glb.c +++ b/model_format_glb.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -72,7 +73,7 @@ typedef struct { typedef struct { uint32_t normals; uint32_t positions; - uint32_t bones; + uint32_t bone_indices; uint32_t weights; uint32_t indices; } mesh_t; @@ -83,22 +84,19 @@ typedef struct { uint32_t joint_count; } skin_t; -typedef struct { - uint32_t parent; - vec3_t translation; - vec4_t rotation; - vec3_t scale; -} joint_t; - typedef struct { uint32_t view; uint32_t offset; uint32_t count; + component_type_t component; + accessor_type_t type; } accessor_t; typedef struct { accessor_t normals; accessor_t positions; + accessor_t bone_indices; + accessor_t weights; accessor_t indices; } accessors_t; @@ -111,6 +109,8 @@ typedef struct { typedef struct { view_t normals; view_t positions; + view_t bone_indices; + view_t weights; view_t indices; } views_t; @@ -120,9 +120,13 @@ typedef struct { } gltf_info_member_t; typedef struct { + bone_t *bones; gltf_info_member_t normals; gltf_info_member_t positions; + gltf_info_member_t bone_indices; + gltf_info_member_t weights; gltf_info_member_t indices; + size_t vertex_count, index_count, bone_count; } gltf_info_t; static void *map_file(const char *path) @@ -343,6 +347,10 @@ static mesh_t read_mesh_primitives(const uint8_t **p) mesh.normals = read_uint(p); } else if (strcmp("POSITION", buffer) == 0) { mesh.positions = read_uint(p); + } else if (strcmp("JOINTS_0", buffer) == 0) { + mesh.bone_indices = read_uint(p); + } else if (strcmp("WEIGHTS_0", buffer) == 0) { + mesh.weights = read_uint(p); } else { skip_value(p); } @@ -436,7 +444,7 @@ static skin_t read_skins(const uint8_t *p) } static void read_joint_children( - const uint8_t **p, skin_t skin, joint_t joints[MAX_JOINT_COUNT], + const uint8_t **p, skin_t skin, bone_t bones[MAX_JOINT_COUNT], uint32_t parent) { assert(**p == '['); @@ -450,8 +458,8 @@ static void read_joint_children( break; } assert(j < skin.joint_count); - assert(joints[j].parent == UINT32_MAX); - joints[j].parent = parent; + assert(bones[j].parent == UINT32_MAX); + bones[j].parent = parent; if (**p == ',') ++(*p); @@ -460,12 +468,12 @@ static void read_joint_children( } static void -read_joints(const uint8_t *p, skin_t skin, joint_t joints[MAX_JOINT_COUNT]) +read_joints(const uint8_t *p, skin_t skin, bone_t bones[MAX_JOINT_COUNT]) { char buffer[MAX_STRING_SIZE]; for (uint32_t i = 0; i < skin.joint_count; ++i) - joints[i].parent = UINT32_MAX; + bones[i].parent = UINT32_MAX; assert(*p == '['); ++p; @@ -492,13 +500,13 @@ read_joints(const uint8_t *p, skin_t skin, joint_t joints[MAX_JOINT_COUNT]) ++p; if (strcmp("children", buffer) == 0) - read_joint_children(&p, skin, joints, j); + read_joint_children(&p, skin, bones, j); else if (strcmp("translation", buffer) == 0) - joints[j].translation = read_vec3(&p); + bones[j].translation = read_vec3(&p); else if (strcmp("rotation", buffer) == 0) - joints[j].rotation = read_vec4(&p); + bones[j].rotation = read_vec4(&p); else if (strcmp("scale", buffer) == 0) - joints[j].scale = read_vec3(&p); + bones[j].scale = read_vec3(&p); else skip_value(&p); @@ -512,6 +520,14 @@ read_joints(const uint8_t *p, skin_t skin, joint_t joints[MAX_JOINT_COUNT]) } } +static component_type_t read_component_type(const uint8_t **p) +{ + const uint32_t component = read_uint(p); + assert(component >= COMPONENT_TYPE_S8); + assert(component <= COMPONENT_TYPE_F32); + return (component_type_t)component; +} + static accessor_type_t read_accessor_type(const uint8_t **p) { char buffer[MAX_STRING_SIZE]; @@ -528,8 +544,7 @@ static accessor_type_t read_accessor_type(const uint8_t **p) assert(false); } -static accessor_t read_accessor( - const uint8_t **p, component_type_t component_type, accessor_type_t type) +static accessor_t read_accessor(const uint8_t **p) { char buffer[MAX_STRING_SIZE]; accessor_t accessor = {}; @@ -549,9 +564,9 @@ static accessor_t read_accessor( else if (strcmp("count", buffer) == 0) accessor.count = read_uint(p); else if (strcmp("componentType", buffer) == 0) - assert(read_uint(p) == component_type); + accessor.component = read_component_type(p); else if (strcmp("type", buffer) == 0) - assert(read_accessor_type(p) == type); + accessor.type = read_accessor_type(p); else if (strcmp("sparse", buffer) == 0) assert(false); // Sparse accessors are not supported else @@ -571,19 +586,18 @@ static accessors_t read_accessors(const uint8_t *p, mesh_t mesh) assert(*p == '['); ++p; for (uint32_t i = 0; *p != ']'; ++i) { - if (i == mesh.normals) { - accessors.normals - = read_accessor(&p, COMPONENT_TYPE_F32, ACCESSOR_TYPE_VEC3); - } else if (i == mesh.positions) { - accessors.positions - = read_accessor(&p, COMPONENT_TYPE_F32, ACCESSOR_TYPE_VEC3); - } else if (i == mesh.indices) { - accessors.indices = read_accessor( - &p, COMPONENT_TYPE_U16, ACCESSOR_TYPE_SCALAR); - } else { + if (i == mesh.normals) + accessors.normals = read_accessor(&p); + else if (i == mesh.positions) + accessors.positions = read_accessor(&p); + else if (i == mesh.bone_indices) + accessors.bone_indices = read_accessor(&p); + else if (i == mesh.weights) + accessors.weights = read_accessor(&p); + else if (i == mesh.indices) + accessors.indices = read_accessor(&p); + else skip_value(&p); - } - if (*p == ',') ++p; } @@ -632,6 +646,10 @@ static views_t read_views(const uint8_t *p, accessors_t accessors) views.normals = read_view(&p); else if (i == accessors.positions.view) views.positions = read_view(&p); + else if (i == accessors.bone_indices.view) + views.bone_indices = read_view(&p); + else if (i == accessors.weights.view) + views.weights = read_view(&p); else if (i == accessors.indices.view) views.indices = read_view(&p); else @@ -643,9 +661,23 @@ static views_t read_views(const uint8_t *p, accessors_t accessors) if (accessors.normals.view == accessors.positions.view) views.positions = views.normals; + + assert(views.normals.len != 0); + assert(views.positions.len != 0); + assert(views.bone_indices.len != 0); + assert(views.weights.len != 0); + assert(views.indices.len != 0); return views; } +static gltf_info_member_t info_member(accessor_t accessor, view_t view) +{ + return (gltf_info_member_t) { + .len = view.stride == 0 ? view.len : accessor.count * view.stride, + .offset = view.offset + accessor.offset, + }; +} + static gltf_info_t read_json(const uint8_t *json) { char buffer[MAX_STRING_SIZE]; @@ -692,38 +724,60 @@ static gltf_info_t read_json(const uint8_t *json) const mesh_t mesh = read_meshes(meshes_json); const skin_t skin = read_skins(skins_json); - joint_t joints[MAX_JOINT_COUNT] = {}; - read_joints(nodes_json, skin, joints); + bone_t *bones = calloc(skin.joint_count, sizeof(bone_t)); + assert(bones != nullptr); + read_joints(nodes_json, skin, bones); const accessors_t accessors = read_accessors(accessors_json, mesh); const views_t views = read_views(views_json, accessors); - // Assume indices are in their own buffer view - assert(views.indices.stride == 0); + // Verify data types + assert( + accessors.normals.component == COMPONENT_TYPE_F32 + && accessors.normals.type == ACCESSOR_TYPE_VEC3); + assert( + accessors.positions.component == COMPONENT_TYPE_F32 + && accessors.positions.type == ACCESSOR_TYPE_VEC3); + assert( + accessors.bone_indices.component == COMPONENT_TYPE_U16 + && accessors.bone_indices.type == ACCESSOR_TYPE_VEC4); + assert( + accessors.weights.component == COMPONENT_TYPE_F32 + && accessors.weights.type == ACCESSOR_TYPE_VEC4); + assert( + accessors.indices.component == COMPONENT_TYPE_U16 + && accessors.indices.type == ACCESSOR_TYPE_SCALAR); + + // Ensure the vertex count is consistent + assert( + accessors.positions.count == accessors.normals.count + && accessors.positions.count == accessors.bone_indices.count + && accessors.positions.count == accessors.weights.count); return (gltf_info_t) { - .normals = { - .offset = views.normals.offset + accessors.normals.offset, - .len = accessors.normals.count * sizeof(vec3_t), - }, - .positions = { - .offset = views.positions.offset + accessors.positions.offset, - .len = accessors.positions.count * sizeof(vec3_t), - }, - .indices = { - .offset = views.indices.offset + accessors.indices.offset, - .len = views.indices.len, - }, + .bones = bones, + .positions = info_member(accessors.positions, views.positions), + .normals = info_member(accessors.normals, views.normals), + .bone_indices + = info_member(accessors.bone_indices, views.bone_indices), + .weights = info_member(accessors.weights, views.weights), + .indices = info_member(accessors.indices, views.indices), + .vertex_count = accessors.positions.count, + .index_count = accessors.indices.count, + .bone_count = skin.joint_count, }; } static void load_vertices( vertex_t *restrict dest, const vec3_t *restrict positions, - const vec3_t *restrict normals, size_t count) + const vec3_t *restrict normals, const u16_vec4_t *restrict bones, + const vec4_t *restrict weights, size_t count) { for (unsigned i = 0; i < count; ++i) { dest[i].position = positions[i]; dest[i].normal = normals[i]; + dest[i].bones = bones[i]; + dest[i].weights = weights[i]; } } @@ -743,17 +797,6 @@ model_t model_format_glb_read(const char *path) offset += sizeof(chunk_hdr_t); const gltf_info_t info = read_json(file + offset); - // Check glTF info for validity - assert(info.positions.len == info.normals.len); - assert(info.positions.offset != info.normals.offset); - if (info.positions.offset < info.normals.offset) { - const uint32_t pos_end = info.positions.offset + info.positions.len; - assert(pos_end <= info.normals.offset); - } else { - const uint32_t norm_end = info.normals.offset + info.normals.len; - assert(norm_end <= info.positions.offset); - } - // Read binary chunk header offset += json_hdr->len; assert(offset != len); @@ -765,13 +808,14 @@ model_t model_format_glb_read(const char *path) assert(offset == len); // Load data from binary chunk into model - const size_t vertex_count = info.positions.len / sizeof(vec3_t); - const size_t index_count = info.indices.len / sizeof(index_t); - model_t model = model_init(vertex_count, index_count); - const vec3_t *positions - = (const vec3_t *)(bin_data + info.positions.offset); - const vec3_t *normals = (const vec3_t *)(bin_data + info.normals.offset); - load_vertices(model.vertices, positions, normals, vertex_count); + model_t model = model_init(info.vertex_count, info.index_count); + model.bone_count = info.bone_count; + model.bones = info.bones; + load_vertices( + model.vertices, (const vec3_t *)(bin_data + info.positions.offset), + (const vec3_t *)(bin_data + info.normals.offset), + (const u16_vec4_t *)(bin_data + info.bone_indices.offset), + (const vec4_t *)(bin_data + info.weights.offset), info.vertex_count); memcpy(model.indices, bin_data + info.indices.offset, info.indices.len); munmap(file, len); diff --git a/model_format_utah.c b/model_format_utah.c index 4d7808b..371acd3 100644 --- a/model_format_utah.c +++ b/model_format_utah.c @@ -184,7 +184,7 @@ static vertex_t read_vertex(read_ctx_t *ctx) { const vec3_t position = read_vec3(ctx); const vec3_t normal = read_vec3(ctx); - return (vertex_t) { position, normal }; + return (vertex_t) { position, normal, {}, {} }; } model_t model_format_utah_read(const char *path)