diff --git a/model_format_glb.c b/model_format_glb.c index 62a5bc7..88690ce 100644 --- a/model_format_glb.c +++ b/model_format_glb.c @@ -29,7 +29,7 @@ static_assert( #define VERSION 2 #define MAX_STRING_SIZE 128 -#define MAX_JOINT_COUNT 32 +#define MAX_BONE_COUNT 32 enum { CHUNK_TYPE_JSON = 0x4e4f534a, // "JSON" @@ -80,10 +80,34 @@ typedef struct { typedef struct { uint32_t inverse_bind_matrices; - uint32_t joints[MAX_JOINT_COUNT]; + uint32_t joints[MAX_BONE_COUNT]; uint32_t joint_count; } skin_t; +typedef enum { + ANIMATION_PROPERTY_TRANSLATION, + ANIMATION_PROPERTY_ROTATION, + ANIMATION_PROPERTY_SCALE, +} animation_property_t; + +typedef struct { + uint32_t sampler; + uint32_t node; + animation_property_t property; +} animation_channel_t; + +typedef struct { + uint32_t times; + uint32_t values; +} animation_sampler_t; + +typedef struct { + uint32_t times; + uint32_t translations; + uint32_t rotations; + uint32_t scales; +} animation_component_t; + typedef struct { uint32_t view; uint32_t offset; @@ -99,6 +123,12 @@ typedef struct { accessor_t weights; accessor_t indices; accessor_t inverse_bind_matrices; + struct { + accessor_t times[MAX_BONE_COUNT]; + accessor_t translations[MAX_BONE_COUNT]; + accessor_t rotations[MAX_BONE_COUNT]; + accessor_t scales[MAX_BONE_COUNT]; + } animation; } accessors_t; typedef struct { @@ -114,6 +144,12 @@ typedef struct { view_t weights; view_t indices; view_t inverse_bind_matrices; + struct { + view_t times[MAX_BONE_COUNT]; + view_t translations[MAX_BONE_COUNT]; + view_t rotations[MAX_BONE_COUNT]; + view_t scales[MAX_BONE_COUNT]; + } animation; } views_t; typedef struct { @@ -129,6 +165,13 @@ typedef struct { gltf_info_member_t weights; gltf_info_member_t indices; gltf_info_member_t inverse_bind_matrices; + struct { + gltf_info_member_t times[MAX_BONE_COUNT]; + gltf_info_member_t translations[MAX_BONE_COUNT]; + gltf_info_member_t rotations[MAX_BONE_COUNT]; + gltf_info_member_t scales[MAX_BONE_COUNT]; + size_t keyframe_count; + } animation; size_t vertex_count, index_count, bone_count; } gltf_info_t; @@ -426,7 +469,7 @@ static skin_t read_skins(const uint8_t *p) assert(*p == '['); ++p; for (skin.joint_count = 0; *p != ']'; ++skin.joint_count) { - assert(skin.joint_count < MAX_JOINT_COUNT); + assert(skin.joint_count < MAX_BONE_COUNT); skin.joints[skin.joint_count] = read_uint(&p); if (*p == ',') @@ -447,7 +490,7 @@ static skin_t read_skins(const uint8_t *p) } static void read_joint_children( - const uint8_t **p, skin_t skin, bone_t bones[MAX_JOINT_COUNT], + const uint8_t **p, skin_t skin, bone_t bones[MAX_BONE_COUNT], uint32_t parent) { assert(**p == '['); @@ -473,7 +516,7 @@ static void read_joint_children( } static void -read_joints(const uint8_t *p, skin_t skin, bone_t bones[MAX_JOINT_COUNT]) +read_joints(const uint8_t *p, skin_t skin, bone_t bones[MAX_BONE_COUNT]) { char buffer[MAX_STRING_SIZE]; @@ -525,6 +568,209 @@ read_joints(const uint8_t *p, skin_t skin, bone_t bones[MAX_JOINT_COUNT]) } } +static animation_property_t read_animation_property(const uint8_t **p) +{ + char buffer[MAX_STRING_SIZE]; + read_string(p, buffer); + if (strcmp("translation", buffer) == 0) + return ANIMATION_PROPERTY_TRANSLATION; + else if (strcmp("rotation", buffer) == 0) + return ANIMATION_PROPERTY_ROTATION; + else if (strcmp("scale", buffer) == 0) + return ANIMATION_PROPERTY_SCALE; + else + assert(false); +} + +static size_t read_animation_channels( + const uint8_t *p, animation_channel_t channels[3 * MAX_BONE_COUNT]) +{ + char buffer[MAX_STRING_SIZE]; + + assert(*p == '['); + ++p; + + size_t i; + for (i = 0; *p != ']'; ++i) { + assert(i < 3 * MAX_BONE_COUNT); + + bool sampler = false; + bool node = false; + bool property = false; + + assert(*p == '{'); + ++p; + while (*p != '}') { + read_string(&p, buffer); + assert(*p == ':'); + ++p; + + if (strcmp("sampler", buffer) == 0) { + channels[i].sampler = read_uint(&p); + sampler = true; + } else if (strcmp("target", buffer) == 0) { + assert(*p == '{'); + ++p; + while (*p != '}') { + read_string(&p, buffer); + assert(*p == ':'); + ++p; + if (strcmp("node", buffer) == 0) { + channels[i].node = read_uint(&p); + node = true; + } else if (strcmp("path", buffer) == 0) { + channels[i].property = read_animation_property(&p); + property = true; + } else { + skip_value(&p); + } + if (*p == ',') + ++p; + } + ++p; + } else { + skip_value(&p); + } + + if (*p == ',') + ++p; + } + ++p; + + assert(sampler); + assert(node); + assert(property); + + if (*p == ',') + ++p; + } + + return i; +} + +static size_t read_animation_samplers( + const uint8_t *p, animation_sampler_t samplers[3 * MAX_BONE_COUNT]) +{ + char buffer[MAX_STRING_SIZE]; + + assert(*p == '['); + ++p; + + size_t i; + for (i = 0; *p != ']'; ++i) { + assert(i < 3 * MAX_BONE_COUNT); + + bool times = false; + bool values = false; + + assert(*p == '{'); + ++p; + while (*p != '}') { + read_string(&p, buffer); + assert(*p == ':'); + ++p; + + if (strcmp("input", buffer) == 0) { + samplers[i].times = read_uint(&p); + times = true; + } else if (strcmp("output", buffer) == 0) { + samplers[i].values = read_uint(&p); + values = true; + } else if (strcmp("interpolation", buffer) == 0) { + read_string(&p, buffer); + assert(strcmp("LINEAR", buffer) == 0); + } else { + skip_value(&p); + } + + if (*p == ',') + ++p; + } + ++p; + + assert(times); + assert(values); + + if (*p == ',') + ++p; + } + + return i; +} + +static void read_animations( + const uint8_t *p, skin_t skin, animation_component_t *animation) +{ + char buffer[MAX_STRING_SIZE]; + animation_channel_t channels[3 * MAX_BONE_COUNT]; + animation_sampler_t samplers[3 * MAX_BONE_COUNT]; + + assert(*p == '['); + ++p; + + assert(*p == '{'); + ++p; + + const uint8_t *channels_json = nullptr; + const uint8_t *samplers_json = nullptr; + while (*p != '}') { + read_string(&p, buffer); + assert(*p == ':'); + ++p; + if (strcmp("channels", buffer) == 0) + channels_json = p; + else if (strcmp("samplers", buffer) == 0) + samplers_json = p; + skip_value(&p); + if (*p == ',') + ++p; + } + ++p; + assert(*p == ']'); // Only support one animation for now + + assert(channels_json != nullptr); + assert(samplers_json != nullptr); + const size_t channel_count + = read_animation_channels(channels_json, channels); + const size_t sampler_count + = read_animation_samplers(samplers_json, samplers); + + for (uint32_t i = 0; i < skin.joint_count; ++i) { + // Initialise animation component with sentinel values + animation[i].times = UINT32_MAX; + animation[i].translations = UINT32_MAX; + animation[i].rotations = UINT32_MAX; + animation[i].scales = UINT32_MAX; + + for (uint32_t j = 0; j < channel_count; ++j) { + if (channels[j].node != skin.joints[i]) + continue; + + assert(channels[j].sampler < sampler_count); + const animation_sampler_t *sampler + = samplers + channels[j].sampler; + if (animation[i].times == UINT32_MAX) + animation[i].times = sampler->times; + else + assert(animation[i].times == sampler->times); + + switch (channels[j].property) { + case ANIMATION_PROPERTY_TRANSLATION: + animation[i].translations = sampler->values; + break; + case ANIMATION_PROPERTY_ROTATION: + animation[i].rotations = sampler->values; + break; + case ANIMATION_PROPERTY_SCALE: + animation[i].scales = sampler->values; + break; + default: + assert(false); + } + } + } +} + static component_type_t read_component_type(const uint8_t **p) { const uint32_t component = read_uint(p); @@ -539,10 +785,14 @@ static accessor_type_t read_accessor_type(const uint8_t **p) read_string(p, buffer); if (strcmp("SCALAR", buffer) == 0) return ACCESSOR_TYPE_SCALAR; + else if (strcmp("VEC2", buffer) == 0) + return ACCESSOR_TYPE_VEC2; else if (strcmp("VEC3", buffer) == 0) return ACCESSOR_TYPE_VEC3; else if (strcmp("VEC4", buffer) == 0) return ACCESSOR_TYPE_VEC4; + else if (strcmp("MAT3", buffer) == 0) + return ACCESSOR_TYPE_MAT3; else if (strcmp("MAT4", buffer) == 0) return ACCESSOR_TYPE_MAT4; else @@ -585,36 +835,44 @@ static accessor_t read_accessor(const uint8_t **p) return accessor; } -static accessors_t read_accessors(const uint8_t *p, mesh_t mesh, skin_t skin) +static accessors_t read_accessors( + const uint8_t *p, mesh_t mesh, skin_t skin, + const animation_component_t *animation) { accessors_t accessors = {}; assert(*p == '['); ++p; for (uint32_t i = 0; *p != ']'; ++i) { - 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 if (i == skin.inverse_bind_matrices) - accessors.inverse_bind_matrices = read_accessor(&p); - else - skip_value(&p); + const accessor_t accessor = read_accessor(&p); if (*p == ',') ++p; + + if (i == mesh.normals) { + accessors.normals = accessor; + } else if (i == mesh.positions) { + accessors.positions = accessor; + } else if (i == mesh.bone_indices) { + accessors.bone_indices = accessor; + } else if (i == mesh.weights) { + accessors.weights = accessor; + } else if (i == mesh.indices) { + accessors.indices = accessor; + } else if (i == skin.inverse_bind_matrices) { + accessors.inverse_bind_matrices = accessor; + } else { + for (uint32_t j = 0; j < skin.joint_count; ++j) { + if (i == animation[j].times) + accessors.animation.times[j] = accessor; + else if (i == animation[j].translations) + accessors.animation.translations[j] = accessor; + else if (i == animation[j].rotations) + accessors.animation.rotations[j] = accessor; + else if (i == animation[j].scales) + accessors.animation.scales[j] = accessor; + } + } } - assert(accessors.normals.count != 0); - assert(accessors.positions.count != 0); - assert(accessors.bone_indices.count != 0); - assert(accessors.weights.count != 0); - assert(accessors.indices.count != 0); - assert(accessors.inverse_bind_matrices.count != 0); return accessors; } @@ -650,40 +908,44 @@ static view_t read_view(const uint8_t **p) return view; } -static views_t read_views(const uint8_t *p, accessors_t accessors) +static views_t +read_views(const uint8_t *p, accessors_t accessors, size_t bone_count) { views_t views = {}; + assert(*p == '['); ++p; - for (uint32_t i = 0; *p != ']'; ++i) { - if (i == accessors.normals.view) - 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 if (i == accessors.inverse_bind_matrices.view) - views.inverse_bind_matrices = read_view(&p); - else - skip_value(&p); + for (uint32_t i = 0; *p != ']'; ++i) { + const view_t view = read_view(&p); if (*p == ',') ++p; + + if (i == accessors.normals.view) + views.normals = view; + if (i == accessors.positions.view) + views.positions = view; + if (i == accessors.bone_indices.view) + views.bone_indices = view; + if (i == accessors.weights.view) + views.weights = view; + if (i == accessors.indices.view) + views.indices = view; + if (i == accessors.inverse_bind_matrices.view) + views.inverse_bind_matrices = view; + + for (uint32_t j = 0; j < bone_count; ++j) { + if (i == accessors.animation.times[j].view) + views.animation.times[j] = view; + else if (i == accessors.animation.translations[j].view) + views.animation.translations[j] = view; + else if (i == accessors.animation.rotations[j].view) + views.animation.rotations[j] = view; + else if (i == accessors.animation.scales[j].view) + views.animation.scales[j] = view; + } } - 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); - assert(views.inverse_bind_matrices.len != 0); return views; } @@ -713,6 +975,7 @@ static gltf_info_t read_json(const uint8_t *json) const uint8_t *meshes_json = nullptr; const uint8_t *skins_json = nullptr; const uint8_t *nodes_json = nullptr; + const uint8_t *animations_json = nullptr; const uint8_t *accessors_json = nullptr; const uint8_t *views_json = nullptr; while (*json != '}') { @@ -725,6 +988,8 @@ static gltf_info_t read_json(const uint8_t *json) skins_json = json; else if (strcmp("nodes", buffer) == 0) nodes_json = json; + else if (strcmp("animations", buffer) == 0) + animations_json = json; else if (strcmp("accessors", buffer) == 0) accessors_json = json; else if (strcmp("bufferViews", buffer) == 0) @@ -736,37 +1001,54 @@ static gltf_info_t read_json(const uint8_t *json) assert(meshes_json != nullptr); assert(skins_json != nullptr); assert(nodes_json != nullptr); + assert(animations_json != nullptr); assert(accessors_json != nullptr); assert(views_json != nullptr); const mesh_t mesh = read_meshes(meshes_json); const skin_t skin = read_skins(skins_json); + 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, skin); - const views_t views = read_views(views_json, accessors); + animation_component_t animation[MAX_BONE_COUNT]; + read_animations(animations_json, skin, animation); + + const accessors_t accessors + = read_accessors(accessors_json, mesh, skin, animation); + const views_t views + = read_views(views_json, accessors, skin.joint_count); // 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); - assert( - accessors.inverse_bind_matrices.component == COMPONENT_TYPE_F32 - && accessors.inverse_bind_matrices.type == ACCESSOR_TYPE_MAT4); + assert(accessors.normals.component == COMPONENT_TYPE_F32); + assert(accessors.normals.type == ACCESSOR_TYPE_VEC3); + assert(accessors.positions.component == COMPONENT_TYPE_F32); + assert(accessors.positions.type == ACCESSOR_TYPE_VEC3); + assert(accessors.bone_indices.component == COMPONENT_TYPE_U16); + assert(accessors.bone_indices.type == ACCESSOR_TYPE_VEC4); + assert(accessors.weights.component == COMPONENT_TYPE_F32); + assert(accessors.weights.type == ACCESSOR_TYPE_VEC4); + assert(accessors.indices.component == COMPONENT_TYPE_U16); + assert(accessors.indices.type == ACCESSOR_TYPE_SCALAR); + assert(accessors.inverse_bind_matrices.component == COMPONENT_TYPE_F32); + assert(accessors.inverse_bind_matrices.type == ACCESSOR_TYPE_MAT4); + for (unsigned i = 0; i < skin.joint_count; ++i) { + assert(accessors.animation.times[i].component == COMPONENT_TYPE_F32); + assert(accessors.animation.times[i].type == ACCESSOR_TYPE_SCALAR); + assert( + accessors.animation.translations[i].component + == COMPONENT_TYPE_F32); + assert( + accessors.animation.translations[i].type == ACCESSOR_TYPE_VEC3); + assert( + accessors.animation.rotations[i].component + == COMPONENT_TYPE_F32); + assert(accessors.animation.rotations[i].type == ACCESSOR_TYPE_VEC4); + assert( + accessors.animation.scales[i].component == COMPONENT_TYPE_F32); + assert(accessors.animation.scales[i].type == ACCESSOR_TYPE_VEC3); + } // Ensure the vertex count is consistent assert( @@ -777,7 +1059,16 @@ static gltf_info_t read_json(const uint8_t *json) // Ensure the joint count is consistent assert(accessors.inverse_bind_matrices.count == skin.joint_count); - return (gltf_info_t) { + // Ensure keyframe count is consistent + const size_t keyframe_count = accessors.animation.times[0].count; + for (unsigned i = 0; i < skin.joint_count; ++i) { + assert(accessors.animation.times[i].count == keyframe_count); + assert(accessors.animation.translations[i].count == keyframe_count); + assert(accessors.animation.rotations[i].count == keyframe_count); + assert(accessors.animation.scales[i].count == keyframe_count); + } + + gltf_info_t info = { .bones = bones, .positions = info_member(accessors.positions, views.positions), .normals = info_member(accessors.normals, views.normals), @@ -790,7 +1081,20 @@ static gltf_info_t read_json(const uint8_t *json) .inverse_bind_matrices = info_member( accessors.inverse_bind_matrices, views.inverse_bind_matrices), .bone_count = skin.joint_count, + .animation = { .keyframe_count = keyframe_count }, }; + for (unsigned i = 0; i < info.bone_count; ++i) { + info.animation.times[i] = info_member( + accessors.animation.times[i], views.animation.times[i]); + info.animation.translations[i] = info_member( + accessors.animation.translations[i], + views.animation.translations[i]); + info.animation.rotations[i] = info_member( + accessors.animation.rotations[i], views.animation.rotations[i]); + info.animation.scales[i] = info_member( + accessors.animation.scales[i], views.animation.scales[i]); + } + return info; } static void load_vertices(