From bd57f345ed9dbed1d81683e48199626de2ea9044 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 27 Jun 2025 10:18:39 -0700 Subject: Restructure project --- src/asset/model.c | 1968 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1968 insertions(+) create mode 100644 src/asset/model.c (limited to 'src/asset/model.c') diff --git a/src/asset/model.c b/src/asset/model.c new file mode 100644 index 0000000..25f2780 --- /dev/null +++ b/src/asset/model.c @@ -0,0 +1,1968 @@ +/// Loads scenes from memory and files. +/// +/// Only the GLTF scene format is current supported. +/// +/// ---------------------------------------------------------------------------- +/// glTF File Format Documentation +/// ---------------------------------------------------------------------------- +/// +/// cgltf: +/// https://github.com/jkuhlmann/cgltf +/// +/// gltf overview: +/// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0b.png +/// +/// gltf spec: +/// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md +/// +/// Sample models: +/// https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Sponza/glTF/Sponza.gltf +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AlphaBlendModeTest/glTF/AlphaBlendModeTest.gltf +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Buggy/glTF/Buggy.gltf +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AntiqueCamera/glTF/AntiqueCamera.gltf +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf +/// +/// ---------------------------------------------------------------------------- +/// Implementation Notes +/// ---------------------------------------------------------------------------- +/// +/// # glTF and the gfx library +/// +/// glTF has concepts that are similar to those in the gfx library, but there +/// isn't an exact 1-1 mapping. Concepts map as follows: +/// +/// glTF gfx +/// ---- --- +/// buffer Buffer +/// accessor + buffer view BufferView +/// mesh primitive (geom + mat) Mesh (also geom + mat) +/// mesh SceneObject +/// node SceneNode +/// +/// glTF buffers map 1-1 with gfx Buffers. glTF scenes make heavy re-use of +/// buffers across views/accessors/meshes, so it is important to make that same +/// re-use in the gfx library to use the data effectively and without +/// duplication. The Sponza scene, for example, has all of its data in one giant +/// buffer. +/// +/// glTF accessors and buffer views are combined and mapped to gfx BufferViews. +/// The glTF buffer view's offset/length/stride are combined with the accessor's +/// offset, and together with the remaining information of both data structures +/// baked into a BufferView. Internally, this information is fed into +/// glVertexAttribPointer() calls, wrapped in a VAO (index view/accessor +/// information is fed into glDrawElements()). This baking should not hurt +/// re-use, at least in the OpenGL world. +/// +/// A glTF mesh primitive contains a piece of geometry and a material. This maps +/// directly to a gfx Mesh. +/// +/// A glTF mesh is a list of mesh primitives. This maps nicely to a gfx +/// SceneObject, with the only inconvenience that terminology gets a little +/// confusing. +/// +/// Finally, glTF nodes map directly to gfx SceneNodes. Both enforce a strict +/// tree hierarchy; DAGs are not supported. +/// +/// # Materials +/// +/// glTF uses the metallic-roughness material model. However, an extension +/// allows a scene to use the specular-glossiness model as well and cgltf +/// supports it: +/// +/// https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/ +/// +/// From the docs, the specular-glossiness model can represent more materials +/// than the metallic-roughness model, but it is also more computationally +/// expensive. Furthermore, a material in glTF can specify parameters for both +/// models, leaving it up to the implementation to decide which one to use. +/// In our case, we use the specular-glosiness model if parameters for it are +/// provided, otherwise we use the metallic-roughness model. + +#include "asset/model.h" + +#include "asset/texture.h" +#include "gfx/core.h" +#include "gfx/gfx.h" +#include "gfx/scene/animation.h" +#include "gfx/scene/camera.h" +#include "gfx/scene/material.h" +#include "gfx/scene/mesh.h" +#include "gfx/scene/node.h" +#include "gfx/scene/object.h" +#include "gfx/scene/scene.h" +#include "gfx/sizes.h" +#include "gfx/util/shader.h" + +#include "gfx_assert.h" +#include "scene/model_impl.h" + +#include "cstring.h" +#include "error.h" +#include "log/log.h" +#include "math/camera.h" +#include "math/defs.h" +#include "math/mat4.h" +#include "math/quat.h" +#include "math/vec2.h" +#include "math/vec3.h" + +#include "cgltf_tangents.h" +#define CGLTF_IMPLEMENTATION +#include "cgltf.h" + +#include +#include + +// Taken from the GL header file. +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +// Uniforms names. Must match the names in shaders. +#define UNIFORM_BASE_COLOR_FACTOR "BaseColorFactor" +#define UNIFORM_METALLIC_FACTOR "MetallicFactor" +#define UNIFORM_ROUGHNESS_FACTOR "RoughnessFactor" +#define UNIFORM_EMISSIVE_FACTOR "EmissiveFactor" +#define UNIFORM_BASE_COLOR_TEXTURE "BaseColorTexture" +#define UNIFORM_METALLIC_ROUGHNESS_TEXTURE "MetallicRoughnessTexture" +#define UNIFORM_EMISSIVE_TEXTURE "EmissiveTexture" +#define UNIFORM_AMBIENT_OCCLUSION_TEXTURE "AmbientOcclusionTexture" +#define UNIFORM_NORMAL_MAP "NormalMap" + +// Shader compiler defines. Must match the names in shaders. +#define DEFINE_HAS_TEXCOORDS "HAS_TEXCOORDS" +#define DEFINE_HAS_NORMALS "HAS_NORMALS" +#define DEFINE_HAS_TANGENTS "HAS_TANGENTS" +#define DEFINE_HAS_ALBEDO_MAP "HAS_ALBEDO_MAP" +#define DEFINE_HAS_METALLIC_ROUGHNESS_MAP "HAS_METALLIC_ROUGHNESS_MAP" +#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP" +#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP" +#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP" +#define DEFINE_HAS_JOINTS "HAS_JOINTS" +#define DEFINE_MAX_JOINTS "MAX_JOINTS" + +typedef enum TextureType { + BaseColorTexture, + MetallicRoughnessTexture, + EmissiveTexture, + AmbientOcclusionTexture, + NormalMap, +} TextureType; + +/// Describes the properties of a mesh. +/// This is used to create shader permutations. +typedef struct MeshPermutation { + union { + struct { + // Vertex attributes. + bool has_texcoords : 1; + bool has_normals : 1; + bool has_tangents : 1; + bool has_joints : 1; + bool has_weights : 1; + // Textures. + bool has_albedo_map : 1; + bool has_metallic_roughness_map : 1; + bool has_normal_map : 1; + bool has_occlusion_map : 1; + bool has_emissive_map : 1; + }; + int32_t all; + }; +} MeshPermutation; + +/// Build shader compiler defines from a mesh permutation. +static size_t make_defines( + MeshPermutation perm, ShaderCompilerDefine* defines) { + static const char* str_true = "1"; + size_t next = 0; + +#define check(field, define) \ + if (perm.field) { \ + defines[next].name = sstring_make(define); \ + defines[next].value = sstring_make(str_true); \ + next++; \ + } + check(has_texcoords, DEFINE_HAS_TEXCOORDS); + check(has_normals, DEFINE_HAS_NORMALS); + check(has_tangents, DEFINE_HAS_TANGENTS); + check(has_joints, DEFINE_HAS_JOINTS); + check(has_albedo_map, DEFINE_HAS_ALBEDO_MAP); + check(has_metallic_roughness_map, DEFINE_HAS_METALLIC_ROUGHNESS_MAP); + check(has_normal_map, DEFINE_HAS_NORMAL_MAP); + check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP); + check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP); + + if (perm.has_joints) { + defines[next].name = sstring_make(DEFINE_MAX_JOINTS); + defines[next].value = sstring_itoa(GFX_MAX_NUM_JOINTS); + next++; + } + + return next; +} + +/// Compile a shader permutation. +static ShaderProgram* make_shader_permutation( + GfxCore* gfxcore, MeshPermutation perm) { + LOGD( + "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: " + "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, " + "metallic-roughness map: " + "%d, normal " + "map: %d, AO map: %d, emissive map: %d", + perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints, + perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map, + perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map); + + ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES]; + const size_t num_defines = make_defines(perm, defines); + return gfx_make_cook_torrance_shader_perm(gfxcore, defines, num_defines); +} + +/// Map a texture type to the name of the shader uniform used to access the +/// texture. +static const char* get_texture_uniform_name(TextureType type) { + switch (type) { + case BaseColorTexture: + return UNIFORM_BASE_COLOR_TEXTURE; + case MetallicRoughnessTexture: + return UNIFORM_METALLIC_ROUGHNESS_TEXTURE; + case EmissiveTexture: + return UNIFORM_EMISSIVE_TEXTURE; + case AmbientOcclusionTexture: + return UNIFORM_AMBIENT_OCCLUSION_TEXTURE; + case NormalMap: + return UNIFORM_NORMAL_MAP; + } + assert(false); + return 0; +} + +/// Map a glTF primitive type to a gfx primitive type. +static PrimitiveType from_gltf_primitive_type(cgltf_primitive_type type) { + switch (type) { + case cgltf_primitive_type_triangles: + return Triangles; + case cgltf_primitive_type_triangle_fan: + return TriangleFan; + case cgltf_primitive_type_triangle_strip: + return TriangleStrip; + // Not yet implemented. + case cgltf_primitive_type_lines: + case cgltf_primitive_type_line_loop: + case cgltf_primitive_type_line_strip: + case cgltf_primitive_type_points: + break; + } + LOGE("Unsupported primitive type: %d", type); + assert(false); + return 0; +} + +/// Map a glTF animation path type to its Gfx equivalent. +static ChannelType from_gltf_animation_path_type( + cgltf_animation_path_type type) { + switch (type) { + case cgltf_animation_path_type_translation: + return TranslationChannel; + case cgltf_animation_path_type_rotation: + return RotationChannel; + case cgltf_animation_path_type_scale: + return ScaleChannel; + case cgltf_animation_path_type_weights: + return WeightsChannel; + case cgltf_animation_path_type_invalid: + assert(false); + break; + } + assert(false); + return 0; +} + +/// Map a glTF interpolation to its Gfx equivalent. +static AnimationInterpolation from_gltf_interpolation_type( + cgltf_interpolation_type type) { + switch (type) { + case cgltf_interpolation_type_linear: + return LinearInterpolation; + case cgltf_interpolation_type_step: + return StepInterpolation; + case cgltf_interpolation_type_cubic_spline: + return CubicSplineInterpolation; + } + assert(false); + return 0; +} + +/// Return the component's size in bytes. +static cgltf_size get_component_size(cgltf_component_type type) { + switch (type) { + case cgltf_component_type_r_8: + return 1; + case cgltf_component_type_r_8u: + return 1; + case cgltf_component_type_r_16: + return 2; + case cgltf_component_type_r_16u: + return 2; + case cgltf_component_type_r_32u: + return 4; + case cgltf_component_type_r_32f: + return 4; + case cgltf_component_type_invalid: + assert(false); + break; + } + assert(false); + 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* 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*)component); + return max((float)c / 127.0, -1.0); + } + case cgltf_component_type_r_8u: { + // 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*)component); + return max((float)c / 32767.0, -1.0); + } + case cgltf_component_type_r_16u: { + // 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*)component); + return (float)c / 4294967295.0; + } + case cgltf_component_type_r_32f: { + const float c = *((float*)component); + return c; + } + case cgltf_component_type_invalid: + FAIL(); + break; + } + FAIL(); + return 0; +} + +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. +/// +/// Note that this function scans all of the scenes in the glTF data. +static size_t get_total_primitives(const cgltf_data* data) { + size_t total = 0; + for (cgltf_size i = 0; i < data->meshes_count; ++i) { + total += data->meshes[i].primitives_count; + } + return total; +} + +/// Load all buffers from the glTF scene. +/// +/// If buffer data is loaded from memory, set filepath = null. +/// +/// Return an array of Buffers such that the index of each glTF buffer in the +/// original array matches the same Buffer in the resulting array. +/// +/// TODO: There is no need to load the inverse bind matrices buffer into the +/// GPU. Might need to lazily load buffers. +static bool load_buffers( + const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers) { + assert(data); + assert(gfxcore); + assert(buffers); + + for (cgltf_size i = 0; i < data->buffers_count; ++i) { + const cgltf_buffer* buffer = &data->buffers[i]; + assert(buffer->data); + buffers[i] = gfx_make_buffer( + gfxcore, &(BufferDesc){ + .usage = BufferStatic, + .type = BufferUntyped, + .data.data = buffer->data, + .data.count = buffer->size}); + if (!buffers[i]) { + return false; + } + } + + return true; +} + +/// Load tangent buffers. +static bool load_tangent_buffers( + const cgltfTangentBuffer* cgltf_tangent_buffers, + cgltf_size num_tangent_buffers, GfxCore* gfxcore, + Buffer** tangent_buffers) { + assert(cgltf_tangent_buffers); + assert(gfxcore); + assert(tangent_buffers); + + for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { + const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i]; + assert(buffer->data); + tangent_buffers[i] = gfx_make_buffer( + gfxcore, &(BufferDesc){ + .usage = BufferStatic, + .type = BufferUntyped, + .data.data = buffer->data, + .data.count = buffer->size_bytes}); + if (!tangent_buffers[i]) { + return false; + } + } + + return true; +} + +/// Lazily load all textures from the glTF scene. +/// +/// Colour textures like albedo are in sRGB colour space. Non-colour textures +/// like normal maps are in linear space (e.g. DamagedHelmet sample). Since we +/// don't know how the texture is going to be used at this point, we can't tell +/// what colour space it should be loaded in (ideally this would be part of the +/// image file format, but not all formats specify colour space.) Therefore, we +/// load the textures lazily and don't actually commit them to GPU memory until +/// we know their colour space when loading glTF materials. +/// +/// Return an array of LoadTextureCmds such that the index of each cmd matches +/// the index of each glTF texture in the scene. +static void load_textures_lazy( + const cgltf_data* data, GfxCore* gfxcore, const char* directory, + LoadTextureCmd* load_texture_cmds) { + assert(data); + assert(gfxcore); + assert(load_texture_cmds); + + for (cgltf_size i = 0; i < data->textures_count; ++i) { + const cgltf_texture* texture = &data->textures[i]; + const cgltf_image* image = texture->image; + const cgltf_sampler* sampler = texture->sampler; + + // glTF models might not specify a sampler. In such case, the client can + // pick its own defaults. + // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#samplers + bool mipmaps = true; + TextureFiltering filtering = LinearFiltering; + TextureWrapping wrap = Repeat; + + if (sampler) { + // The gfx library does not distinguish between sampling the texture and + // combining the mipmap levels. + const cgltf_int filter = + sampler->min_filter == 0 ? sampler->mag_filter : sampler->min_filter; + + switch (filter) { + case GL_NEAREST_MIPMAP_NEAREST: + mipmaps = true; + filtering = NearestFiltering; + break; + case GL_NEAREST_MIPMAP_LINEAR: + case GL_LINEAR_MIPMAP_NEAREST: + case GL_LINEAR_MIPMAP_LINEAR: + mipmaps = true; + filtering = LinearFiltering; + break; + case GL_NEAREST: + filtering = NearestFiltering; + break; + case GL_LINEAR: + filtering = LinearFiltering; + break; + default: + break; + } + } + + // Currently only supporting loading textures from files. + assert(image->uri); + assert(directory); + mstring fullpath = + mstring_concat_path(mstring_make(directory), mstring_make(image->uri)); + + load_texture_cmds[i] = (LoadTextureCmd){ + .origin = AssetFromFile, + .type = LoadTexture, + .colour_space = sRGB, + .filtering = filtering, + .wrap = wrap, + .mipmaps = mipmaps, + .data.texture.filepath = fullpath}; + } +} + +/// Load a texture uniform. +/// +/// This determines a texture's colour space based on its intended use, loads +/// the texture, and then defines the sampler shader uniform. +static bool load_texture_and_uniform( + const cgltf_data* data, Gfx* gfx, const cgltf_texture_view* texture_view, + TextureType texture_type, const Texture** textures, + LoadTextureCmd* load_texture_cmds, int* next_uniform, MaterialDesc* desc) { + assert(data); + assert(gfx); + assert(texture_view); + assert(textures); + assert(next_uniform); + assert(desc); + assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + + const size_t texture_index = texture_view->texture - data->textures; + assert(texture_index < data->textures_count); + + // Here we are assuming that if a texture is re-used, it is re-used with the + // same texture view. This should be fine because, e.g., a normal map would + // not be used as albedo and vice versa. + if (!textures[texture_index]) { + LoadTextureCmd* cmd = &load_texture_cmds[texture_index]; + // TODO: Check for colour textures and default to LinearColourSpace instead. + if (texture_type == NormalMap) { + cmd->colour_space = LinearColourSpace; + } + + LOGD( + "Load texture: %s (mipmaps: %d, filtering: %d)", + mstring_cstr(&cmd->data.texture.filepath), cmd->mipmaps, + cmd->filtering); + + textures[texture_index] = gfx_load_texture(gfx, cmd); + if (!textures[texture_index]) { + log_error( + "Failed to load texture: %s", + mstring_cstr(&cmd->data.texture.filepath)); + return false; + } + } + + assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc->uniforms[(*next_uniform)++] = (ShaderUniform){ + .name = sstring_make(get_texture_uniform_name(texture_type)), + .type = UniformTexture, + .value.texture = textures[texture_index]}; + + return true; +} + +/// Load all materials from the glTF scene. +/// +/// Return an array of Materials such that the index of each descriptor matches +/// the index of each glTF material in the scene. Also return the number of +/// materials and the textures used by them. +static bool load_materials( + const cgltf_data* data, Gfx* gfx, LoadTextureCmd* load_texture_cmds, + const Texture** textures, Material** materials) { + assert(data); + assert(gfx); + assert(materials); + if (data->textures_count > 0) { + assert(load_texture_cmds); + assert(textures); + } + + for (cgltf_size i = 0; i < data->materials_count; ++i) { + const cgltf_material* mat = &data->materials[i]; + + int next_uniform = 0; + MaterialDesc desc = {0}; + + // TODO: specular/glossiness and other material parameters. + if (mat->has_pbr_metallic_roughness) { + const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness; + + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[next_uniform++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), + .type = UniformVec4, + .value.vec4 = vec4_from_array(pbr->base_color_factor)}; + + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[next_uniform++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_METALLIC_FACTOR), + .type = UniformFloat, + .value.scalar = pbr->metallic_factor}; + + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[next_uniform++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), + .type = UniformFloat, + .value.scalar = pbr->roughness_factor}; + + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[next_uniform++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), + .type = UniformVec3, + .value.vec3 = vec3_from_array(mat->emissive_factor)}; + + if (pbr->base_color_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &pbr->base_color_texture, BaseColorTexture, textures, + load_texture_cmds, &next_uniform, &desc)) { + return false; + } + } + + if (pbr->metallic_roughness_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &pbr->metallic_roughness_texture, + MetallicRoughnessTexture, textures, load_texture_cmds, + &next_uniform, &desc)) { + return false; + } + } + } + + if (mat->emissive_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &mat->emissive_texture, EmissiveTexture, textures, + load_texture_cmds, &next_uniform, &desc)) { + return false; + } + } + + if (mat->occlusion_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &mat->occlusion_texture, AmbientOcclusionTexture, + textures, load_texture_cmds, &next_uniform, &desc)) { + return false; + } + } + + if (mat->normal_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &mat->normal_texture, NormalMap, textures, + load_texture_cmds, &next_uniform, &desc)) { + return false; + } + } + + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.num_uniforms = next_uniform; + + materials[i] = gfx_make_material(&desc); + if (!materials[i]) { + return false; + } + } + + return true; +} + +/// Create a default material for meshes that do not have a material. +static Material* make_default_material() { + MaterialDesc desc = (MaterialDesc){0}; + + assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), + .type = UniformVec4, + .value.vec4 = vec4_make(1, 1, 1, 1)}; + + assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_METALLIC_FACTOR), + .type = UniformFloat, + .value.scalar = 0}; + + assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), + .type = UniformFloat, + .value.scalar = 1}; + + assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), + .type = UniformVec3, + .value.vec3 = vec3_make(0, 0, 0)}; + + return gfx_make_material(&desc); +} + +/// 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) { + aabb3 box = {0}; + if (accessor->has_min && accessor->has_max) { + box = aabb3_make( + vec3_from_array(accessor->min), vec3_from_array(accessor->max)); + } else { + 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; +} + +/// Load all meshes from the glTF scene. +static bool load_meshes( + const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers, + Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers, + cgltf_size num_tangent_buffers, Material** materials, + ShaderProgram* const shader, size_t primitive_count, Geometry** geometries, + Mesh** meshes, SceneObject** scene_objects) { + // Walk through the mesh primitives to create Meshes. A GLTF mesh primitive + // has a material (Mesh) and vertex data (Geometry). A GLTF mesh maps to + // a SceneObject. + // + // glTF gfx + // ---- --- + // Mesh SceneObject + // Mesh primitive Mesh / Geometry + // Accessor + buffer view BufferView + // Buffer Buffer + assert(data); + assert(gfxcore); + assert(buffers); + assert(materials); + assert(geometries); + assert(meshes); + assert(scene_objects); + if (num_tangent_buffers > 0) { + assert(tangent_buffers); + assert(cgltf_tangent_buffers); + } + + // Points to the next available Mesh and also the next available Geometry. + // There is one (Mesh, Geometry) pair per glTF mesh primitive. + size_t next_mesh = 0; + + for (cgltf_size m = 0; m < data->meshes_count; ++m) { + const cgltf_mesh* mesh = &data->meshes[m]; + + ObjectDesc object_desc = {0}; + + for (cgltf_size p = 0; p < mesh->primitives_count; ++p) { + assert(next_mesh < primitive_count); + const cgltf_primitive* prim = &mesh->primitives[p]; + const cgltf_material* mat = prim->material; + + MeshPermutation perm = {0}; + if (mat) { + perm.has_normal_map = mat->normal_texture.texture != 0; + perm.has_occlusion_map = mat->occlusion_texture.texture != 0; + perm.has_emissive_map = mat->emissive_texture.texture != 0; + + if (mat->has_pbr_metallic_roughness) { + const cgltf_pbr_metallic_roughness* pbr = + &mat->pbr_metallic_roughness; + perm.has_albedo_map = pbr->base_color_texture.texture != 0; + perm.has_metallic_roughness_map = + pbr->metallic_roughness_texture.texture != 0; + } else { + // TODO: specular/glossiness and other material parameters. + } + } + + GeometryDesc geometry_desc = { + .type = from_gltf_primitive_type(prim->type), + .buffer_usage = BufferStatic}; + + // Vertex indices. + if (prim->indices) { + const cgltf_accessor* accessor = prim->indices; + const cgltf_buffer_view* view = prim->indices->buffer_view; + const cgltf_size buffer_index = view->buffer - data->buffers; + + assert(buffer_index < data->buffers_count); + Buffer* buffer = buffers[buffer_index]; + + const cgltf_size component_size = + get_component_size(accessor->component_type); + switch (component_size) { + case 1: { + BufferViewIdx8* indices = &geometry_desc.indices8; + // TODO: discards const qualifier. + indices->buffer = buffer; + indices->offset_bytes = accessor->offset + view->offset; + indices->size_bytes = view->size; + indices->stride_bytes = view->stride; + geometry_desc.num_indices = prim->indices->count; + break; + } + case 2: { + BufferViewIdx16* indices = &geometry_desc.indices16; + indices->buffer = buffer; + indices->offset_bytes = accessor->offset + view->offset; + indices->size_bytes = view->size; + indices->stride_bytes = view->stride; + geometry_desc.num_indices = prim->indices->count; + break; + } + default: + // TODO: Handle 32-bit indices. + assert(false); + break; + } + } + + // Vertex attributes. + for (cgltf_size a = 0; a < prim->attributes_count; ++a) { + const cgltf_attribute* attrib = &prim->attributes[a]; + const cgltf_accessor* accessor = attrib->data; + const cgltf_buffer_view* view = accessor->buffer_view; + const cgltf_size offset = accessor->offset + view->offset; + const cgltf_size buffer_index = view->buffer - data->buffers; + + assert(buffer_index < data->buffers_count); + Buffer* buffer = buffers[buffer_index]; + + BufferView2d* buffer_view_2d = 0; + BufferView3d* buffer_view_3d = 0; + BufferView4d* buffer_view_4d = 0; + BufferViewFloat* buffer_view_float = 0; + BufferViewU8* buffer_view_u8 = 0; + BufferViewU16* buffer_view_u16 = 0; + + switch (attrib->type) { + case cgltf_attribute_type_position: { + switch (accessor->type) { + case cgltf_type_vec2: + assert(geometry_desc.positions3d.buffer == 0); + buffer_view_2d = &geometry_desc.positions2d; + 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); + break; + default: + FAIL( + "Unhandled accessor type %d in vertex positions", + accessor->type); + assert(false); + return false; + } + // It is assumed that meshes have positions, so there is nothing to + // do for the mesh permutation in this case. + break; + } + case cgltf_attribute_type_normal: + buffer_view_3d = &geometry_desc.normals; + perm.has_normals = true; + break; + case cgltf_attribute_type_tangent: + buffer_view_4d = &geometry_desc.tangents; + perm.has_tangents = true; + break; + case cgltf_attribute_type_texcoord: + buffer_view_2d = &geometry_desc.texcoords; + perm.has_texcoords = true; + break; + case cgltf_attribute_type_color: + // TODO: Add support for color. + break; + case cgltf_attribute_type_joints: + // Joints can be either u8 or u16. + switch (accessor->component_type) { + case cgltf_component_type_r_8u: + buffer_view_u8 = &geometry_desc.joints.u8; + break; + case cgltf_component_type_r_16u: + buffer_view_u16 = &geometry_desc.joints.u16; + break; + default: + assert(false); + return false; + } + perm.has_joints = true; + break; + case cgltf_attribute_type_weights: + // Weights can be either u8, u16, or float. + switch (accessor->component_type) { + case cgltf_component_type_r_8u: + buffer_view_u8 = &geometry_desc.weights.u8; + break; + case cgltf_component_type_r_16u: + buffer_view_u16 = &geometry_desc.weights.u16; + break; + case cgltf_component_type_r_32f: + buffer_view_float = &geometry_desc.weights.floats; + break; + default: + assert(false); + return false; + } + perm.has_weights = true; + break; + case cgltf_attribute_type_invalid: + assert(false); + break; + } + +#define CONFIGURE_BUFFER(buf) \ + if (buf) { \ + buf->buffer = buffer; \ + buf->offset_bytes = offset; \ + buf->size_bytes = view->size; \ + buf->stride_bytes = view->stride; \ + } + CONFIGURE_BUFFER(buffer_view_2d); + CONFIGURE_BUFFER(buffer_view_3d); + CONFIGURE_BUFFER(buffer_view_4d); + CONFIGURE_BUFFER(buffer_view_u8); + CONFIGURE_BUFFER(buffer_view_u16); + CONFIGURE_BUFFER(buffer_view_float); + } // Vertex attributes. + + assert( + (perm.has_joints && perm.has_weights) || + (!perm.has_joints && !perm.has_weights)); + + // If the mesh primitive has no tangents, see if they were computed + // separately. + if (!geometry_desc.tangents.buffer) { + for (cgltf_size t = 0; t < num_tangent_buffers; ++t) { + const cgltfTangentBuffer* cgltf_buffer = &cgltf_tangent_buffers[t]; + + if (cgltf_buffer->primitive == prim) { + BufferView4d* view = &geometry_desc.tangents; + view->buffer = tangent_buffers[t]; + view->offset_bytes = 0; + view->size_bytes = cgltf_buffer->size_bytes; + view->stride_bytes = 0; // Tightly packed. + break; + } + } + } + + // Set the number of vertices in the geometry. Since a geometry can have + // either 2d or 3d positions but not both, here we can perform addition + // to compute the total number of vertices. + geometry_desc.num_verts = + (geometry_desc.positions2d.size_bytes / sizeof(vec2)) + + (geometry_desc.positions3d.size_bytes / sizeof(vec3)); + +#define CHECK_COUNT(buffer_view, type, num_components) \ + if (geometry_desc.buffer_view.buffer) { \ + assert( \ + (geometry_desc.buffer_view.size_bytes / \ + (num_components * sizeof(type))) == geometry_desc.num_verts); \ + } + + // Check that the number of vertices is consistent across all vertex + // attributes. + CHECK_COUNT(normals, vec3, 1); + CHECK_COUNT(tangents, vec4, 1); + CHECK_COUNT(texcoords, vec2, 1); + CHECK_COUNT(joints.u8, uint8_t, 4); + CHECK_COUNT(joints.u16, uint16_t, 4); + CHECK_COUNT(weights.u8, uint8_t, 4); + CHECK_COUNT(weights.u16, uint16_t, 4); + CHECK_COUNT(weights.floats, float, 4); + + Material* material = 0; + if (mat) { + const cgltf_size material_index = mat - data->materials; + assert(material_index < data->materials_count); + material = materials[material_index]; + } else { + // Create a default material for meshes that do not specify one. + material = make_default_material(); + } + assert(material); + + geometries[next_mesh] = gfx_make_geometry(gfxcore, &geometry_desc); + if (!geometries[next_mesh]) { + return false; + } + + // If the user specifies a custom shader, use that instead. Otherwise + // compile a shader based on the mesh's permutation. + // + // Note that Gfx takes care of caching shaders and shader programs. + // + // Caching materials could be useful, but, provided they can share + // shaders, the renderer can check later whether uniforms have the same + // values. Also, changing uniforms is much faster than swapping shaders, + // so shader caching is the most important thing here. + ShaderProgram* mesh_shader = + shader ? shader : make_shader_permutation(gfxcore, perm); + assert(mesh_shader); + + meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){ + .geometry = geometries[next_mesh], + .material = material, + .shader = mesh_shader}); + + if (!meshes[next_mesh]) { + return false; + } + + assert(object_desc.num_meshes < GFX_MAX_NUM_MESHES); + object_desc.meshes[object_desc.num_meshes] = meshes[next_mesh]; + object_desc.num_meshes++; + + ++next_mesh; + } // glTF mesh primitive / gfx Mesh. + + scene_objects[m] = gfx_make_object(&object_desc); + if (!scene_objects[m]) { + return false; + } + } // glTF mesh / gfx SceneObject. + + 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 +/// are the joints). However, we want to map the "base joint" (the joint/node +/// with the smallest index) to 0 in the AnimaDesc's joint array. We can do this +/// by subtracting the "base node index" from every joint index or channel +/// target. +/// +/// There is an assumption in the animation library that joints are contiguous +/// anyway, so this "base joint index" works provided the joint nodes are also +/// contiguous in the glTF. The glTF does not guarantee this, but I think it's +/// a reasonable assumption that exporters write glTF files in such a way, and +/// Blender does appear to do so. +cgltf_size find_base_joint_index(const cgltf_data* data) { + assert(data); + + cgltf_size base_joint_index = (cgltf_size)-1; + + for (cgltf_size s = 0; s < data->skins_count; ++s) { + const cgltf_skin* skin = &data->skins[s]; + for (cgltf_size j = 0; j < skin->joints_count; ++j) { + // Joint is an index/pointer into the nodes array. + const cgltf_size node_index = skin->joints[j] - data->nodes; + assert(node_index < data->nodes_count); + // Min. + if (node_index < base_joint_index) { + base_joint_index = node_index; + } + } + } + + return base_joint_index; +} + +/// Load all skins (Gfx skeletons) from the glTF scene. +/// Return the total number of joints. +static size_t load_skins( + const cgltf_data* data, Buffer* const* buffers, cgltf_size base_joint_index, + AnimaDesc* anima_desc) { + assert(data); + assert(buffers); + assert(anima_desc); + assert(base_joint_index < data->nodes_count); + + // Determines whether the ith joint in the node hierarchy is a joint node. + // This is then used to determine whether a joint is a root of the joint + // hierarchy. + bool is_joint_node[GFX_MAX_NUM_JOINTS] = {false}; + + size_t num_joints = 0; + + for (cgltf_size s = 0; s < data->skins_count; ++s) { + const cgltf_skin* skin = &data->skins[s]; + const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices; + assert(matrices_accessor->count == skin->joints_count); + + num_joints += skin->joints_count; + assert(num_joints < GFX_MAX_NUM_JOINTS); + + SkeletonDesc* skeleton_desc = &anima_desc->skeletons[s]; + *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count}; + + // for (cgltf_size j = 0; j < skin->joints_count; ++j) { + 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; + assert(node_index < data->nodes_count); + + const cgltf_size parent_node_index = + skin->joints[i]->parent - data->nodes; + assert(parent_node_index < data->nodes_count); + + // Subtract the base index to pack the joints as tightly as possible in + // the AnimaDesc. + assert(node_index >= base_joint_index); + const cgltf_size joint_index = node_index - base_joint_index; + + assert(parent_node_index >= base_joint_index); + const cgltf_size parent_index = parent_node_index - base_joint_index; + + skeleton_desc->joints[i] = joint_index; + + JointDesc* joint_desc = &anima_desc->joints[joint_index]; + joint_desc->parent = parent_index; + 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. + // if (skin->skeleton) { + // // cgltf_size root_index = skin->skeleton - data->nodes; + // // assert(root_index <= data->nodes_count); + // // root_node = nodes[root_index]; + // assert(false); + //} + } + + // Animation library assumes that joints are contiguous. + for (size_t i = 0; i < num_joints; ++i) { + assert(is_joint_node[i]); + } + + // Insert the root joint. + // This is the root of all skeletons. It is, specifically, the root of all + // joints that do not have a parent; skins (skeletons) in glTF are not + // guaranteed to have a common parent, but are generally a set of disjoint + // trees. + const size_t root_index = num_joints; + assert(root_index < GFX_MAX_NUM_JOINTS); + anima_desc->joints[root_index] = (JointDesc){.parent = INDEX_NONE}; + num_joints++; + + // Make root joints point to the root joint at index N. + // The root joints are the ones that have a non-joint node in the glTF as a + // parent. + for (size_t i = 0; i < root_index; ++i) { + JointDesc* joint = &anima_desc->joints[i]; + if ((joint->parent >= root_index) || !is_joint_node[joint->parent]) { + joint->parent = root_index; + } + } + + return num_joints; +} + +/// Load all animations from the glTF scene. +static void load_animations( + const cgltf_data* data, cgltf_size base_joint_index, + AnimaDesc* anima_desc) { + assert(data); + assert(anima_desc); + assert(base_joint_index < data->nodes_count); + assert(data->animations_count <= GFX_MAX_NUM_ANIMATIONS); + + for (cgltf_size a = 0; a < data->animations_count; ++a) { + const cgltf_animation* animation = &data->animations[a]; + AnimationDesc* animation_desc = &anima_desc->animations[a]; + + *animation_desc = (AnimationDesc){ + .name = sstring_make(animation->name), + .num_channels = animation->channels_count}; + + assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS); + for (cgltf_size c = 0; c < animation->channels_count; ++c) { + const cgltf_animation_channel* channel = &animation->channels[c]; + ChannelDesc* channel_desc = &animation_desc->channels[c]; + const cgltf_animation_sampler* sampler = channel->sampler; + + const size_t target_index = channel->target_node - data->nodes; + assert(target_index < data->nodes_count); + + assert(target_index >= base_joint_index); + const size_t tight_target_index = target_index - base_joint_index; + assert(tight_target_index < anima_desc->num_joints); + + *channel_desc = (ChannelDesc){ + .target = tight_target_index, + .type = from_gltf_animation_path_type(channel->target_path), + .interpolation = from_gltf_interpolation_type(sampler->interpolation), + .num_keyframes = 0}; + + // Read time inputs. + 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: { + 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: { + 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; + } + } + } +} + +/// Load all nodes from the glTF scene. +/// +/// This function ignores the many scenes and default scene of the glTF spec +/// and instead just loads all nodes into a single gfx Scene. +static void load_nodes( + const cgltf_data* data, SceneNode* root_node, SceneObject** objects, + SceneCamera** cameras, const Anima* anima, SceneNode** nodes) { + // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a + // disjount union of strict trees: + // + // "For Version 2.0 conformance, the glTF node hierarchy is not a directed + // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. + // That is, no node may be a direct descendant of more than one node. This + // restriction is meant to simplify implementation and facilitate + // conformance." + // + // This matches the gfx library implementation, where every node can have at + // most one parent. + assert(data); + assert(root_node); + assert(objects); + assert(cameras); + assert(nodes); + + cgltf_size next_camera = 0; + + for (cgltf_size n = 0; n < data->nodes_count; ++n) { + const cgltf_node* node = &data->nodes[n]; + + // Add SceneObject, SceneCamera or Lights. + // TODO: Handle lights once they are implemented in the gfx library. + if (node->mesh) { + const cgltf_size mesh_index = node->mesh - data->meshes; + assert(mesh_index < data->meshes_count); + SceneObject* object = objects[mesh_index]; + gfx_construct_object_node(nodes[n], object); + + if (node->skin) { + assert(anima); + + const cgltf_size skin_index = node->skin - data->skins; + assert(skin_index < data->skins_count); + const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); + gfx_set_object_skeleton(object, skeleton); + } + } else if (node->camera) { + assert(next_camera < data->cameras_count); + + Camera camera; + const cgltf_camera* cam = node->camera; + + // TODO: We could define a function load_cameras() the same way we load + // every mesh and then remove this ad-hoc loading of cameras here, as well + // as remove 'next_camera'. + switch (cam->type) { + case cgltf_camera_type_orthographic: + camera = camera_orthographic( + 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag, + cam->data.orthographic.znear, cam->data.orthographic.zfar); + break; + case cgltf_camera_type_perspective: + camera = camera_perspective( + cam->data.perspective.yfov, cam->data.perspective.aspect_ratio, + cam->data.perspective.znear, cam->data.perspective.zfar); + break; + case cgltf_camera_type_invalid: + break; + } + + gfx_set_camera_camera(cameras[next_camera], &camera); + gfx_construct_camera_node(nodes[n], cameras[next_camera]); + ++next_camera; + } else { + // TODO: implementation for missing node types. + // These nodes currently default to logical nodes. + } + assert(nodes[n]); + + // Set transform. + mat4 transform; + if (node->has_matrix) { + transform = mat4_from_array(node->matrix); + } else { + transform = mat4_id(); + if (node->has_scale) { + const mat4 scale = mat4_scale(vec3_from_array(node->scale)); + transform = mat4_mul(transform, scale); + } + if (node->has_rotation) { + const quat q = quat_from_array(node->rotation); + const mat4 rotate = mat4_from_quat(q); + transform = mat4_mul(transform, rotate); + } + if (node->has_translation) { + const mat4 translate = + mat4_translate(vec3_from_array(node->translation)); + transform = mat4_mul(translate, transform); + } + } + gfx_set_node_transform(nodes[n], &transform); + + // If this is a top-level node in the glTF scene, set its parent to the + // given root node. + if (!node->parent) { + gfx_set_node_parent(nodes[n], root_node); + } else { + const cgltf_size parent_index = node->parent - data->nodes; + assert(parent_index < data->nodes_count); + SceneNode* parent = nodes[parent_index]; + assert(parent); + gfx_set_node_parent(nodes[n], parent); + } + } // SceneNode. +} + +/// Remove joint nodes from the Gfx Scene. +/// +/// Joint nodes are not needed because joints are packed into the Anima. +static void remove_joint_nodes( + const cgltf_data* data, SceneNode** scene_nodes) { + assert(data); + assert(scene_nodes); + + // This works assuming the joint nodes are contiguous. Contiguity is checked + // when loading skins. See load_skins(). + size_t min_joint_index = (size_t)-1; + size_t max_joint_index = 0; + + // First get the minimum and maximum indices of all joint nodes. + for (cgltf_size s = 0; s < data->skins_count; ++s) { + const cgltf_skin* skin = &data->skins[s]; + + for (cgltf_size j = 0; j < skin->joints_count; ++j) { + // Joint is an index/pointer into the nodes array. + const cgltf_size joint_index = skin->joints[j] - data->nodes; + assert(joint_index < data->nodes_count); + + if (joint_index < min_joint_index) { + min_joint_index = joint_index; + } + if (joint_index > max_joint_index) { + max_joint_index = joint_index; + } + } + } + + assert(min_joint_index < data->nodes_count); + assert(max_joint_index < data->nodes_count); + + // Now walk over the joint nodes. If a joint's parent is itself not a joint + // node, then that joint is a root of a joint hierarchy (skins in glTF may + // have multiple roots). In such case, delete the root joint recursively. + for (cgltf_size s = 0; s < data->skins_count; ++s) { + const cgltf_skin* skin = &data->skins[s]; + + for (cgltf_size j = 0; j < skin->joints_count; ++j) { + // Joint is an index/pointer into the nodes array. + const cgltf_size joint_index = skin->joints[j] - data->nodes; + assert(joint_index < data->nodes_count); + + const cgltf_node* joint = &data->nodes[joint_index]; + + // Parent node index. + const cgltf_size parent_index = joint->parent - data->nodes; + assert(parent_index < data->nodes_count); + + // If the parent is not a joint node, recursively delete this joint node. + if ((parent_index < min_joint_index) || + (parent_index > max_joint_index)) { + gfx_destroy_node(&scene_nodes[joint_index]); + } + } + } +} + +/// Load all scenes from the glTF file. +/// +/// If the scene is loaded from memory, set filepath = null. +/// +/// This function ignores the many scenes and default scene of the glTF spec +/// and instead just loads all scenes into a single Gfx Scene. +static Model* load_scene( + cgltf_data* data, Gfx* gfx, const mstring* filepath, ShaderProgram* shader, + const cgltfTangentBuffer* cgltf_tangent_buffers, + cgltf_size num_tangent_buffers) { + // In a GLTF scene, buffers can be shared among meshes, meshes among nodes, + // etc. Each object is referenced by its index in the relevant array. Here we + // do a button-up construction, first allocating our own graphics objects in + // the same quantities and then re-using the GLTF indices to index these + // arrays. + // + // For simplicity, this function also handles all of the cleanup. Arrays are + // allocated up front, and the helper functions construct their elements. If + // an error is encountered, the helper functions can simply return and this + // function cleans up any intermediate objects that had been created up until + // the point of failure. + // + // Loading animation data: + // - Buffers with animation sampler data need to stay on the CPU, not + // uploaded to the GPU. We could try to implement GPU animation at a later + // stage. + assert(data); + assert(gfx); + assert(filepath); + assert((num_tangent_buffers == 0) || (cgltf_tangent_buffers != 0)); + + bool success = false; + + GfxCore* gfxcore = gfx_get_core(gfx); + const size_t primitive_count = get_total_primitives(data); + + const mstring directory = mstring_dirname(*filepath); + LOGD("Filepath: %s", mstring_cstr(filepath)); + LOGD("Directory: %s", mstring_cstr(&directory)); + + Buffer** tangent_buffers = 0; + Buffer** buffers = 0; + LoadTextureCmd* load_texture_cmds = 0; + const Texture** textures = 0; // Textures are owned by asset cache. + Material** materials = 0; + Geometry** geometries = 0; + Mesh** meshes = 0; + AnimaDesc* anima_desc = 0; + SceneObject** scene_objects = 0; + SceneCamera** scene_cameras = 0; + SceneNode** scene_nodes = 0; + Anima* anima = 0; + SceneNode* root_node = 0; + Model* model = 0; + + tangent_buffers = calloc(num_tangent_buffers, sizeof(Buffer*)); + buffers = calloc(data->buffers_count, sizeof(Buffer*)); + textures = calloc(data->textures_count, sizeof(Texture*)); + materials = calloc(data->materials_count, sizeof(Material*)); + geometries = calloc(primitive_count, sizeof(Geometry*)); + meshes = calloc(primitive_count, sizeof(Mesh*)); + scene_objects = calloc(data->meshes_count, sizeof(SceneObject*)); + scene_cameras = calloc(data->cameras_count, sizeof(SceneCamera**)); + scene_nodes = calloc(data->nodes_count, sizeof(SceneNode**)); + // A glTF scene does not necessarily have textures. Materials can be given + // as constants, for example. + if (data->textures_count > 0) { + load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd)); + } + + if (!buffers || !tangent_buffers || + ((data->textures_count > 0) && !load_texture_cmds) || !textures || + !materials || !geometries || !meshes || !scene_objects || + !scene_cameras || !scene_nodes) { + goto cleanup; + } + + if ((num_tangent_buffers > 0) && + !load_tangent_buffers( + cgltf_tangent_buffers, num_tangent_buffers, gfxcore, + tangent_buffers)) { + goto cleanup; + } + + if (!load_buffers(data, gfxcore, buffers)) { + goto cleanup; + } + + if (data->textures_count > 0) { + load_textures_lazy( + data, gfxcore, mstring_cstr(&directory), load_texture_cmds); + } + + if (!load_materials(data, gfx, load_texture_cmds, textures, materials)) { + goto cleanup; + } + + if (!load_meshes( + data, gfxcore, buffers, tangent_buffers, cgltf_tangent_buffers, + num_tangent_buffers, materials, shader, primitive_count, geometries, + meshes, scene_objects)) { + goto cleanup; + } + + // Skins refer to nodes, and nodes may refer to skins. To break this circular + // dependency, glTF defines skins in terms of node indices. We could do the + // same if Gfx allowed allocating nodes contiguously in memory. For now, + // create the nodes up front and use the indices of the array to map to the + // node_idx. + for (cgltf_size i = 0; i < data->nodes_count; ++i) { + scene_nodes[i] = gfx_make_node(); + } + + // Create the scene's root node. + // This is an anima node if the scene has skins; otherwise it is a logical + // node. + root_node = gfx_make_node(); + if (data->skins_count > 0) { + anima_desc = calloc(1, sizeof(AnimaDesc)); + if (!anima_desc) { + goto cleanup; + } + + const cgltf_size base = find_base_joint_index(data); + + anima_desc->num_skeletons = data->skins_count; + anima_desc->num_animations = data->animations_count; + 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); + } + + // The root node becomes the root of all scene nodes. + load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes); + + // Clean up scene nodes that correspond to joints in the glTF. These are + // not needed anymore. + if (data->skins_count > 0) { + remove_joint_nodes(data, scene_nodes); + } + + model = gfx_make_model(root_node); + + success = true; + +cleanup: + // The arrays of resources are no longer needed. The resources themselves are + // destroyed only if this function fails. + if (tangent_buffers) { + if (!success) { + for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { + if (tangent_buffers[i]) { + gfx_destroy_buffer(gfxcore, &tangent_buffers[i]); + } + } + } + free(tangent_buffers); + } + if (buffers) { + if (!success) { + for (cgltf_size i = 0; i < data->buffers_count; ++i) { + if (buffers[i]) { + gfx_destroy_buffer(gfxcore, &buffers[i]); + } + } + } + free(buffers); + } + if (load_texture_cmds) { + free(load_texture_cmds); + } + if (textures) { + free(textures); + } + if (materials) { + if (!success) { + for (cgltf_size i = 0; i < data->materials_count; ++i) { + if (materials[i]) { + gfx_destroy_material(&materials[i]); + } + } + } + free(materials); + } + if (geometries) { + if (!success) { + for (size_t i = 0; i < primitive_count; ++i) { + if (geometries[i]) { + gfx_destroy_geometry(gfxcore, &geometries[i]); + } + } + } + free(geometries); + } + if (meshes) { + if (!success) { + for (size_t i = 0; i < primitive_count; ++i) { + if (meshes[i]) { + gfx_destroy_mesh(&meshes[i]); + } + } + } + free(meshes); + } + if (anima_desc) { + free(anima_desc); + } + if (scene_objects) { + if (!success) { + for (cgltf_size i = 0; i < data->meshes_count; ++i) { + if (scene_objects[i]) { + gfx_destroy_object(&scene_objects[i]); + } + } + } + free(scene_objects); + } + if (scene_cameras) { + if (!success) { + for (cgltf_size i = 0; i < data->cameras_count; ++i) { + if (scene_cameras[i]) { + gfx_destroy_camera(&scene_cameras[i]); + } + } + } + free(scene_cameras); + } + if (scene_nodes) { + if (!success) { + for (cgltf_size i = 0; i < data->nodes_count; ++i) { + if (scene_nodes[i]) { + gfx_destroy_node(&scene_nodes[i]); + } + } + } + free(scene_nodes); + } + if (!success) { + if (root_node) { + gfx_destroy_node(&root_node); // Node owns the anima. + } else if (anima) { + gfx_destroy_anima(&anima); + } + } + return model; +} + +Model* gfx_model_load(Gfx* gfx, const LoadModelCmd* cmd) { + assert(gfx); + assert(cmd); + + Model* model = 0; + + cgltf_options options = {0}; + cgltf_data* data = NULL; + cgltfTangentBuffer* tangent_buffers = 0; + + cgltf_result result; + switch (cmd->origin) { + case AssetFromFile: + result = cgltf_parse_file(&options, mstring_cstr(&cmd->filepath), &data); + break; + case AssetFromMemory: + result = cgltf_parse(&options, cmd->data, cmd->size_bytes, &data); + break; + } + if (result != cgltf_result_success) { + goto cleanup; + } + + if (cmd->origin == AssetFromFile) { + // Must call cgltf_load_buffers() to load buffer data. + result = cgltf_load_buffers(&options, data, mstring_cstr(&cmd->filepath)); + if (result != cgltf_result_success) { + goto cleanup; + } + } + + // Compute tangents for normal-mapped models that are missing them. + cgltf_size num_tangent_buffers = 0; + cgltf_compute_tangents( + &options, data, &tangent_buffers, &num_tangent_buffers); + + model = load_scene( + data, gfx, &cmd->filepath, cmd->shader, tangent_buffers, + num_tangent_buffers); + +cleanup: + if (data) { + cgltf_free(data); + } + if (tangent_buffers) { + free(tangent_buffers); + } + return model; +} -- cgit v1.2.3