#include "model_format_glb.h" #include #include #include #include #include #include #include #include #include #include /* * Ensure the system is little-endian. * * GLB is a little-endian format and this module assumes the host * system matches this in multiple places. */ #if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) static_assert( __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, "This module requires a little-endian system"); #else #warning "Could not determine endianness: assuming little endian" #endif #define MAGIC 0x46546c67 // "glTF" #define VERSION 2 #define MAX_STRING_SIZE 128 #define MAX_BONE_COUNT 32 enum { CHUNK_TYPE_JSON = 0x4e4f534a, // "JSON" CHUNK_TYPE_BIN = 0x004e4942, // "BIN\0" }; enum { RENDERING_MODE_TRIANGLES = 4, }; typedef enum { COMPONENT_TYPE_S8 = 5120, // Byte COMPONENT_TYPE_U8 = 5121, // Unsigned byte COMPONENT_TYPE_S16 = 5122, // Short COMPONENT_TYPE_U16 = 5123, // Unsigned short COMPONENT_TYPE_U32 = 5125, // Unsigned int COMPONENT_TYPE_F32 = 5126, // Float } component_type_t; typedef enum { ACCESSOR_TYPE_SCALAR, ACCESSOR_TYPE_VEC2, ACCESSOR_TYPE_VEC3, ACCESSOR_TYPE_VEC4, ACCESSOR_TYPE_MAT2, ACCESSOR_TYPE_MAT3, ACCESSOR_TYPE_MAT4, } accessor_type_t; typedef struct { uint32_t magic; uint32_t version; uint32_t len; } file_hdr_t; typedef struct { uint32_t len; uint32_t type; } chunk_hdr_t; typedef struct { uint32_t normals; uint32_t positions; uint32_t bone_indices; uint32_t weights; uint32_t indices; } mesh_t; typedef struct { uint32_t inverse_bind_matrices; 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; 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; 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 { uint32_t offset; uint32_t len; uint32_t stride; } view_t; typedef struct { view_t normals; view_t positions; view_t bone_indices; 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 { uint32_t offset; uint32_t len; } gltf_info_member_t; typedef struct { bone_t *bones; uint32_t *bone_parents; 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; 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; static void *map_file(const char *path) { const int fd = open(path, O_RDONLY); assert(fd != -1); struct stat stat; const int res = fstat(fd, &stat); assert(res == 0); void *addr = mmap(nullptr, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); assert(addr != nullptr); close(fd); return addr; } static void read_string(const uint8_t **p, char buffer[MAX_STRING_SIZE]) { assert(**p == '"'); ++(*p); size_t len = 0; while (**p != '"') { assert(len < MAX_STRING_SIZE - 1); buffer[len++] = *(*p)++; } ++(*p); buffer[len] = '\0'; } static bool ends_value(uint8_t b) { switch (b) { case '}': case ']': case ',': return true; default: return false; } } static void skip_value(const uint8_t **p) { switch (**p) { int nesting; case '{': ++(*p); nesting = 0; while (**p != '}' || nesting > 0) { if (**p == '{') ++nesting; if (**p == '}') --nesting; ++(*p); } ++(*p); break; case '[': ++(*p); nesting = 0; while (**p != ']' || nesting > 0) { if (**p == '[') ++nesting; if (**p == ']') --nesting; ++(*p); } ++(*p); break; case '"': ++(*p); while (**p != '"') { if (**p == '\\') ++(*p); ++(*p); } ++(*p); break; default: ++(*p); while (!ends_value(**p)) ++(*p); break; } } static uint32_t read_uint(const uint8_t **p) { uint32_t value = 0; while (!ends_value(**p)) { assert(isdigit(**p)); value = 10 * value + *(*p)++ - '0'; } return value; } static float read_float(const uint8_t **p) { enum { INTEGRAL, DECIMAL, EXPONENT, } state = INTEGRAL; bool neg_val = false, neg_exp = false; float val = 0, exp = 0; unsigned div = 10; while (!ends_value(**p)) { const uint8_t c = *((*p)++); switch (state) { case INTEGRAL: if (isdigit(c)) val = 10 * val + (c - '0'); else if (c == '-') neg_val = true; else if (c == '.') state = DECIMAL; else assert(false); break; case DECIMAL: if (isdigit(c)) val += (float)(c - '0') / div; else if (c == 'e') state = EXPONENT; else assert(false); div *= 10; break; case EXPONENT: if (isdigit(c)) exp = 10 * exp + (c - '0'); else if (c == '-') neg_exp = true; else assert(false); break; default: assert(false); } } if (neg_val) val *= -1; if (neg_exp) exp *= -1; return val * powf(10, exp); } static vec3_t read_vec3(const uint8_t **p) { vec3_t v; assert(**p == '['); ++(*p); v.x = read_float(p); assert(**p == ','); ++(*p); v.y = read_float(p); assert(**p == ','); ++(*p); v.z = read_float(p); assert(**p == ']'); ++(*p); return v; } static vec4_t read_vec4(const uint8_t **p) { vec4_t v; assert(**p == '['); ++(*p); v.x = read_float(p); assert(**p == ','); ++(*p); v.y = read_float(p); assert(**p == ','); ++(*p); v.z = read_float(p); assert(**p == ','); ++(*p); v.w = read_float(p); assert(**p == ']'); ++(*p); return v; } static mesh_t read_mesh_primitives(const uint8_t **p) { char buffer[MAX_STRING_SIZE]; mesh_t mesh = {}; assert(**p == '['); ++(*p); assert(**p == '{'); ++(*p); while (**p != '}') { read_string(p, buffer); assert(**p == ':'); ++(*p); if (strcmp("attributes", buffer) == 0) { assert(**p == '{'); ++(*p); while (**p != '}') { read_string(p, buffer); assert(**p == ':'); ++(*p); if (strcmp("NORMAL", buffer) == 0) { 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); } if (**p == ',') ++(*p); } ++(*p); } else if (strcmp("indices", buffer) == 0) { mesh.indices = read_uint(p); } else if (strcmp("mode", buffer) == 0) { assert(read_uint(p) == RENDERING_MODE_TRIANGLES); } else { skip_value(p); } if (**p == ',') ++(*p); } ++(*p); assert(**p == ']'); ++(*p); return mesh; } static mesh_t read_meshes(const uint8_t *p) { char buffer[MAX_STRING_SIZE]; assert(*p == '['); ++p; assert(*p == '{'); ++p; while (*p != '}') { read_string(&p, buffer); assert(*p == ':'); ++p; if (strcmp("primitives", buffer) == 0) return read_mesh_primitives(&p); else skip_value(&p); if (*p == ',') ++p; } // Primitives key missing from mesh object assert(false); } static skin_t read_skins(const uint8_t *p) { skin_t skin = {}; char buffer[MAX_STRING_SIZE]; assert(*p == '['); ++p; assert(*p == '{'); ++p; while (*p != '}') { read_string(&p, buffer); assert(*p == ':'); ++p; if (strcmp("inverseBindMatrices", buffer) == 0) { skin.inverse_bind_matrices = read_uint(&p); } else if (strcmp("joints", buffer) == 0) { assert(*p == '['); ++p; for (skin.joint_count = 0; *p != ']'; ++skin.joint_count) { assert(skin.joint_count < MAX_BONE_COUNT); skin.joints[skin.joint_count] = read_uint(&p); if (*p == ',') ++p; } ++p; } else { skip_value(&p); } if (*p == ',') ++p; } ++p; assert(*p == ']'); return skin; } static void read_joint_children( const uint8_t **p, skin_t skin, uint32_t bone_parents[MAX_BONE_COUNT], uint32_t parent) { assert(**p == '['); ++(*p); while (**p != ']') { const uint32_t i = read_uint(p); uint32_t j; for (j = 0; j < skin.joint_count; ++j) { if (skin.joints[j] == i) break; } assert(j < skin.joint_count); assert(bone_parents[j] == UINT32_MAX); assert(parent < j); bone_parents[j] = parent; if (**p == ',') ++(*p); } ++(*p); } static void read_joints( const uint8_t *p, skin_t skin, bone_t *bones, uint32_t *bone_parents) { char buffer[MAX_STRING_SIZE]; for (uint32_t i = 0; i < skin.joint_count; ++i) bone_parents[i] = UINT32_MAX; assert(*p == '['); ++p; for (uint32_t i = 0; *p != ']'; ++i) { uint32_t j; for (j = 0; j < skin.joint_count; ++j) { if (skin.joints[j] == i) break; } if (j >= skin.joint_count) { skip_value(&p); if (*p == ',') ++p; continue; } assert(*p == '{'); ++p; while (*p != '}') { read_string(&p, buffer); assert(*p == ':'); ++p; if (strcmp("children", buffer) == 0) { read_joint_children(&p, skin, bone_parents, j); } else if (strcmp("translation", buffer) == 0) { bones[j].translation = read_vec3(&p); } else if (strcmp("rotation", buffer) == 0) { bones[j].rotation = read_vec4(&p); } else if (strcmp("scale", buffer) == 0) { const vec3_t scale = read_vec3(&p); assert(fabsf(scale.x - 1.0f) < 1e-4); assert(fabsf(scale.y - 1.0f) < 1e-4); assert(fabsf(scale.z - 1.0f) < 1e-4); } else { skip_value(&p); } if (*p == ',') ++p; } ++p; if (*p == ',') ++p; } } 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); 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]; 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 assert(false); } static accessor_t read_accessor(const uint8_t **p) { char buffer[MAX_STRING_SIZE]; accessor_t accessor = {}; assert(**p == '{'); ++(*p); while (**p != '}') { read_string(p, buffer); assert(**p == ':'); ++(*p); if (strcmp("bufferView", buffer) == 0) accessor.view = read_uint(p); else if (strcmp("byteOffset", buffer) == 0) accessor.offset = read_uint(p); else if (strcmp("count", buffer) == 0) accessor.count = read_uint(p); else if (strcmp("componentType", buffer) == 0) accessor.component = read_component_type(p); else if (strcmp("type", buffer) == 0) accessor.type = read_accessor_type(p); else if (strcmp("sparse", buffer) == 0) assert(false); // Sparse accessors are not supported else skip_value(p); if (**p == ',') ++(*p); } ++(*p); return accessor; } 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) { 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; } } } return accessors; } static view_t read_view(const uint8_t **p) { char buffer[MAX_STRING_SIZE]; view_t view = {}; assert(**p == '{'); ++(*p); while (**p != '}') { read_string(p, buffer); assert(**p == ':'); ++(*p); if (strcmp("buffer", buffer) == 0) assert(read_uint(p) == 0); else if (strcmp("byteOffset", buffer) == 0) view.offset = read_uint(p); else if (strcmp("byteLength", buffer) == 0) view.len = read_uint(p); else if (strcmp("byteStride", buffer) == 0) view.stride = read_uint(p); else skip_value(p); if (**p == ',') ++(*p); } ++(*p); return view; } 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) { 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; } } 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]; assert(*json == '{'); ++json; /* * We want to avoid having to read every single accessor, buffer * view etc, so we must identify which ones we need *before* * processing these sections. However, the sections may appear in * any order, so we must first do a pass through the JSON to * identify the section locations, so that we can then go through * them in the necessary order. */ 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 != '}') { read_string(&json, buffer); assert(*json == ':'); ++json; if (strcmp("meshes", buffer) == 0) meshes_json = json; else if (strcmp("skins", buffer) == 0) 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) views_json = json; skip_value(&json); if (*json == ',') ++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); uint32_t *bone_parents = calloc(skin.joint_count, sizeof(uint32_t)); assert(bone_parents != nullptr); read_joints(nodes_json, skin, bones, bone_parents); 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); 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( accessors.positions.count == accessors.normals.count && accessors.positions.count == accessors.bone_indices.count && accessors.positions.count == accessors.weights.count); // Ensure the joint count is consistent assert(accessors.inverse_bind_matrices.count == skin.joint_count); // 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, .bone_parents = bone_parents, .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, .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( vertex_t *restrict dest, const vec3_t *restrict positions, 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]; } } static void load_keyframes( bone_t *restrict keyframes, const uint8_t *restrict bin, const gltf_info_member_t *translations_info, const gltf_info_member_t *rotations_info, size_t keyframe_count, size_t bone_count) { for (unsigned b = 0; b < bone_count; ++b) { const uint8_t *translations = bin + translations_info[b].offset; const uint8_t *rotations = bin + rotations_info[b].offset; for (unsigned f = 0; f < keyframe_count; ++f) { bone_t *dest = keyframes + bone_count * f + b; memcpy( &dest->translation, translations + f * sizeof(vec3_t), sizeof(vec3_t)); memcpy( &dest->rotation, rotations + f * sizeof(vec4_t), sizeof(vec4_t)); } } } model_t model_format_glb_read(const char *path) { uint8_t *file = map_file(path); const file_hdr_t *file_hdr = (const file_hdr_t *)file; assert(file_hdr->magic == MAGIC); assert(file_hdr->version == VERSION); const size_t len = file_hdr->len; // Read JSON chunk size_t offset = sizeof(file_hdr_t); const chunk_hdr_t *json_hdr = (const chunk_hdr_t *)(file + offset); assert(json_hdr->type == CHUNK_TYPE_JSON); offset += sizeof(chunk_hdr_t); const gltf_info_t info = read_json(file + offset); // Read binary chunk header offset += json_hdr->len; assert(offset != len); const chunk_hdr_t *bin_hdr = (const chunk_hdr_t *)(file + offset); assert(bin_hdr->type == CHUNK_TYPE_BIN); offset += sizeof(chunk_hdr_t); const uint8_t *bin_data = file + offset; offset += bin_hdr->len; assert(offset == len); // Load data from binary chunk into model model_t model = model_init(info.vertex_count, info.index_count); model.bone_count = info.bone_count; model.bones = info.bones; model.bone_parents = info.bone_parents; 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); model.inverse_bind_matrices = malloc(info.bone_count * sizeof(mat4_t)); assert(model.inverse_bind_matrices != nullptr); memcpy( model.inverse_bind_matrices, bin_data + info.inverse_bind_matrices.offset, info.inverse_bind_matrices.len); // Load animation data model.animation.keyframe_count = info.animation.keyframe_count; model.animation.times_us = malloc(model.animation.keyframe_count * sizeof(uint32_t)); assert(model.animation.times_us != nullptr); const float *times = (const float *)(bin_data + info.animation.times[0].offset); for (unsigned i = 0; i < model.animation.keyframe_count; ++i) model.animation.times_us[i] = (size_t)roundf(1'000'000 * times[i]); model.animation.length_us = model.animation.times_us[model.animation.keyframe_count - 1]; for (unsigned i = 1; i < info.bone_count; ++i) { // Verify that all time series data matches if (info.animation.times[i].offset != info.animation.times[0].offset) { assert( memcmp( bin_data + info.animation.times[0].offset, bin_data + info.animation.times[i].offset, model.animation.keyframe_count * sizeof(float)) == 0); } } model.animation.keyframes = malloc( model.animation.keyframe_count * model.bone_count * sizeof(bone_t)); assert(model.animation.keyframes != nullptr); load_keyframes( model.animation.keyframes, bin_data, info.animation.translations, info.animation.rotations, model.animation.keyframe_count, model.bone_count); munmap(file, len); return model; }