diff --git a/model_format_glb.c b/model_format_glb.c index 2bf01a0..8c820f7 100644 --- a/model_format_glb.c +++ b/model_format_glb.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ static_assert( #define VERSION 2 #define MAX_KEY_SIZE 128 +#define MAX_JOINT_COUNT 32 enum { CHUNK_TYPE_JSON = 0x4e4f534a, // "JSON" @@ -54,6 +56,19 @@ typedef struct { uint32_t indices; } mesh_t; +typedef struct { + uint32_t inverse_bind_matrices; + uint32_t joints[MAX_JOINT_COUNT]; + 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; @@ -185,6 +200,100 @@ static uint32_t read_uint(const uint8_t **p) 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_KEY_SIZE]; @@ -269,6 +378,127 @@ static mesh_t read_meshes(const uint8_t *p) assert(false); } +static skin_t read_skins(const uint8_t *p) +{ + skin_t skin = {}; + char buffer[MAX_KEY_SIZE]; + size_t len; + + assert(*p == '['); + ++p; + assert(*p == '{'); + ++p; + + while (*p != '}') { + len = read_key(&p, buffer); + assert(*p == ':'); + ++p; + + if (memcmp("inverseBindMatrices", buffer, len) == 0) { + skin.inverse_bind_matrices = read_uint(&p); + } else if (memcmp("joints", buffer, len) == 0) { + assert(*p == '['); + ++p; + for (skin.joint_count = 0; *p != ']'; ++skin.joint_count) { + assert(skin.joint_count < MAX_JOINT_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, joint_t joints[MAX_JOINT_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(joints[j].parent == UINT32_MAX); + joints[j].parent = parent; + + if (**p == ',') + ++(*p); + } + ++(*p); +} + +static void +read_joints(const uint8_t *p, skin_t skin, joint_t joints[MAX_JOINT_COUNT]) +{ + char buffer[MAX_KEY_SIZE]; + size_t len; + + for (uint32_t i = 0; i < skin.joint_count; ++i) + joints[i].parent = 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 != '}') { + len = read_key(&p, buffer); + assert(*p == ':'); + ++p; + + if (memcmp("children", buffer, len) == 0) { + read_joint_children(&p, skin, joints, j); + } else if (memcmp("translation", buffer, len) == 0) { + joints[j].translation = read_vec3(&p); + } else if (memcmp("rotation", buffer, len) == 0) { + joints[j].rotation = read_vec4(&p); + } else if (memcmp("scale", buffer, len) == 0) { + joints[j].scale = read_vec3(&p); + } else { + skip_value(&p); + } + + if (*p == ',') + ++p; + } + ++p; + + if (*p == ',') + ++p; + } +} + static accessor_t read_accessor(const uint8_t **p) { char buffer[MAX_KEY_SIZE]; @@ -374,6 +604,9 @@ static views_t read_views(const uint8_t *p, accessors_t accessors) if (*p == ',') ++p; } + + if (accessors.normals.view == accessors.positions.view) + views.positions = views.normals; return views; } @@ -394,6 +627,8 @@ static gltf_info_t read_json(const uint8_t *json) * 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 *accessors_json = nullptr; const uint8_t *views_json = nullptr; while (*json != '}') { @@ -402,6 +637,10 @@ static gltf_info_t read_json(const uint8_t *json) ++json; if (memcmp("meshes", buffer, len) == 0) meshes_json = json; + else if (memcmp("skins", buffer, len) == 0) + skins_json = json; + else if (memcmp("nodes", buffer, len) == 0) + nodes_json = json; else if (memcmp("accessors", buffer, len) == 0) accessors_json = json; else if (memcmp("bufferViews", buffer, len) == 0) @@ -411,36 +650,39 @@ static gltf_info_t read_json(const uint8_t *json) ++json; } assert(meshes_json != nullptr); + assert(skins_json != nullptr); + assert(nodes_json != nullptr); assert(accessors_json != nullptr); assert(views_json != nullptr); - mesh_t mesh = read_meshes(meshes_json); - accessors_t accessors = read_accessors(accessors_json, mesh); - views_t views = read_views(views_json, accessors); + 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); + + const accessors_t accessors = read_accessors(accessors_json, mesh); + const views_t views = read_views(views_json, accessors); - size_t normals_len, positions_len; if (accessors.positions.view == accessors.normals.view) { - views.positions = views.normals; assert(views.normals.stride == sizeof(vec3_t)); - assert(views.positions.stride == sizeof(vec3_t)); - normals_len = accessors.normals.count * sizeof(vec3_t); - positions_len = accessors.positions.count * sizeof(vec3_t); } else { - normals_len = views.normals.len; - positions_len = views.positions.len; + const size_t normal_size + = views.normals.len / accessors.normals.count; + assert(normal_size == sizeof(vec3_t)); + const size_t position_size + = views.positions.len / accessors.positions.count; + assert(position_size == sizeof(vec3_t)); } - - assert(views.indices.stride == 0); assert(views.indices.len / accessors.indices.count == sizeof(index_t)); return (gltf_info_t) { .normals = { .offset = views.normals.offset + accessors.normals.offset, - .len = normals_len, + .len = accessors.normals.count * sizeof(vec3_t), }, .positions = { .offset = views.positions.offset + accessors.positions.offset, - .len = positions_len, + .len = accessors.positions.count * sizeof(vec3_t), }, .indices = { .offset = views.indices.offset + accessors.indices.offset,