summaryrefslogtreecommitdiff
path: root/archive/src/resources
diff options
context:
space:
mode:
Diffstat (limited to 'archive/src/resources')
-rw-r--r--archive/src/resources/gltf.c596
-rw-r--r--archive/src/resources/loaders.h17
-rw-r--r--archive/src/resources/obj.c398
3 files changed, 1011 insertions, 0 deletions
diff --git a/archive/src/resources/gltf.c b/archive/src/resources/gltf.c
new file mode 100644
index 0000000..66ae1b6
--- /dev/null
+++ b/archive/src/resources/gltf.c
@@ -0,0 +1,596 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "animation.h"
+#include "colours.h"
+#include "core.h"
+#include "defines.h"
+#include "file.h"
+#include "loaders.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "mem.h"
+#include "pbr.h"
+#include "platform.h"
+#include "ral_types.h"
+#include "render.h"
+#include "render_types.h"
+#include "str.h"
+
+#define CGLTF_IMPLEMENTATION
+#include <cgltf.h>
+
+extern Core g_core;
+
+/* GLTF Loading Pipeline
+ ===================== */
+
+struct face {
+ cgltf_uint indices[3];
+};
+typedef struct face face;
+
+KITC_DECL_TYPED_ARRAY(Vec3)
+KITC_DECL_TYPED_ARRAY(Vec2)
+KITC_DECL_TYPED_ARRAY(Vec4u)
+KITC_DECL_TYPED_ARRAY(Vec4i)
+KITC_DECL_TYPED_ARRAY(Vec4)
+KITC_DECL_TYPED_ARRAY(face)
+KITC_DECL_TYPED_ARRAY(i32)
+
+size_t GLTF_LoadMaterials(cgltf_data* data, Str8 relative_path, Material_darray* out_materials);
+
+ModelHandle ModelLoad_gltf(const char* path, bool invert_texture_y) {
+ size_t arena_size = MB(1);
+ arena scratch = arena_create(malloc(arena_size), arena_size);
+
+ TRACE("Loading model at Path %s\n", path);
+ path_opt relative_path = path_parent(&scratch, path);
+ if (!relative_path.has_value) {
+ WARN("Couldnt get a relative path for the path to use for loading materials & textures later");
+ }
+ const char* file_string = string_from_file(path);
+
+ ModelHandle handle;
+ Model* model = Model_pool_alloc(&g_core.models, &handle);
+ model->name = Str8_cstr_view(path);
+
+ bool success =
+ model_load_gltf_str(file_string, path, relative_path.path, model, invert_texture_y);
+
+ if (!success) {
+ FATAL("Couldnt load GLTF file at path %s", path);
+ ERROR_EXIT("Load fails are considered crash-worthy right now. This will change later.\n");
+ }
+
+ arena_free_all(&scratch);
+ arena_free_storage(&scratch);
+ return handle;
+}
+
+void assert_path_type_matches_component_type(cgltf_animation_path_type target_path,
+ cgltf_accessor* output) {
+ if (target_path == cgltf_animation_path_type_rotation) {
+ assert(output->component_type == cgltf_component_type_r_32f);
+ assert(output->type == cgltf_type_vec4);
+ }
+}
+
+// TODO: Brainstorm how I can make this simpler and break it up into more testable pieces
+
+void load_position_components(Vec3_darray* positions, cgltf_accessor* accessor) {
+ TRACE("Loading %d vec3 position components", accessor->count);
+ CASSERT_MSG(accessor->component_type == cgltf_component_type_r_32f,
+ "Positions components are floats");
+ CASSERT_MSG(accessor->type == cgltf_type_vec3, "Vertex positions should be a vec3");
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ Vec3 pos;
+ cgltf_accessor_read_float(accessor, v, &pos.x, 3);
+ Vec3_darray_push(positions, pos);
+ }
+}
+
+void load_normal_components(Vec3_darray* normals, cgltf_accessor* accessor) {
+ TRACE("Loading %d vec3 normal components", accessor->count);
+ CASSERT_MSG(accessor->component_type == cgltf_component_type_r_32f,
+ "Normal vector components are floats");
+ CASSERT_MSG(accessor->type == cgltf_type_vec3, "Vertex normals should be a vec3");
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ Vec3 pos;
+ cgltf_accessor_read_float(accessor, v, &pos.x, 3);
+ Vec3_darray_push(normals, pos);
+ }
+}
+
+void load_texcoord_components(Vec2_darray* texcoords, cgltf_accessor* accessor) {
+ TRACE("Load texture coordinates from accessor");
+ CASSERT(accessor->component_type == cgltf_component_type_r_32f);
+ CASSERT_MSG(accessor->type == cgltf_type_vec2, "Texture coordinates should be a vec2");
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ Vec2 tex;
+ bool success = cgltf_accessor_read_float(accessor, v, &tex.x, 2);
+ if (!success) {
+ ERROR("Error loading tex coord");
+ }
+ Vec2_darray_push(texcoords, tex);
+ }
+}
+
+void load_joint_index_components(Vec4i_darray* joint_indices, cgltf_accessor* accessor) {
+ TRACE("Load joint indices from accessor");
+ CASSERT(accessor->component_type == cgltf_component_type_r_16u);
+ CASSERT_MSG(accessor->type == cgltf_type_vec4, "Joint indices should be a vec4");
+ Vec4i tmp_joint_index;
+ Vec4 joints_as_floats;
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ cgltf_accessor_read_float(accessor, v, &joints_as_floats.x, 4);
+ tmp_joint_index.x = (u32)joints_as_floats.x;
+ tmp_joint_index.y = (u32)joints_as_floats.y;
+ tmp_joint_index.z = (u32)joints_as_floats.z;
+ tmp_joint_index.w = (u32)joints_as_floats.w;
+ printf("Joints affecting vertex %d : %d %d %d %d\n", v, tmp_joint_index.x, tmp_joint_index.y,
+ tmp_joint_index.z, tmp_joint_index.w);
+ Vec4i_darray_push(joint_indices, tmp_joint_index);
+ }
+}
+
+bool model_load_gltf_str(const char* file_string, const char* filepath, Str8 relative_path,
+ Model* out_model, bool invert_textures_y) {
+ TRACE("Load GLTF from string");
+
+ // Setup temps
+ Vec3_darray* tmp_positions = Vec3_darray_new(1000);
+ Vec3_darray* tmp_normals = Vec3_darray_new(1000);
+ Vec2_darray* tmp_uvs = Vec2_darray_new(1000);
+ Vec4i_darray* tmp_joint_indices = Vec4i_darray_new(1000);
+ Vec4_darray* tmp_weights = Vec4_darray_new(1000);
+ Material_darray* tmp_materials = Material_darray_new(1);
+ Mesh_darray* tmp_meshes = Mesh_darray_new(1);
+ i32_darray* tmp_material_indexes = i32_darray_new(1);
+
+ Joint_darray* joints = Joint_darray_new(256);
+
+ cgltf_options options = { 0 };
+ cgltf_data* data = NULL;
+ cgltf_result result = cgltf_parse_file(&options, filepath, &data);
+ if (result != cgltf_result_success) {
+ WARN("gltf load failed");
+ // TODO: cleanup arrays(allocate all from arena ?)
+ return false;
+ }
+
+ cgltf_load_buffers(&options, data, filepath);
+ DEBUG("loaded buffers");
+
+ // --- Skin
+ size_t num_skins = data->skins_count;
+ bool is_skinned = false;
+ Armature main_skeleton = { 0 };
+ if (num_skins == 1) {
+ is_skinned = true;
+ } else if (num_skins > 1) {
+ WARN("GLTF files with more than 1 skin are not supported");
+ return false;
+ }
+
+ if (is_skinned) {
+ cgltf_skin* gltf_skin = data->skins;
+ DEBUG("loading skin %s", gltf_skin->name);
+ size_t num_joints = gltf_skin->joints_count;
+ DEBUG("# Joints %d", num_joints);
+
+ // Create our data that will be placed onto the model
+ Armature armature = { .label = "test_skin" };
+ printf("Skin %s\n", gltf_skin->name);
+ // armature.label = Clone_cstr(&armature.arena, gltf_skin->name);
+ armature.joints = joints; // ! Make sure not to free this
+
+ cgltf_accessor* gltf_inverse_bind_matrices = gltf_skin->inverse_bind_matrices;
+
+ // --- Joints
+ // for each one we'll spit out a joint
+ for (size_t i = 0; i < num_joints; i++) {
+ // Get the joint and assign its node index for later referencing
+ cgltf_node* joint_node = gltf_skin->joints[i];
+ TRACE("Joint %d (node index %d)", i, cgltf_node_index(data, joint_node));
+ Joint joint_i = { .debug_label = "test_joint",
+ .node_idx = cgltf_node_index(data, joint_node),
+ .inverse_bind_matrix = mat4_ident() };
+
+ if (joint_node->children_count > 0 && !joint_node->has_translation &&
+ !joint_node->has_rotation) {
+ WARN("Joint node with index %d is the root node", i);
+ joint_i.transform_components = TRANSFORM_DEFAULT;
+ joint_i.parent = -1;
+ for (u32 c_i = 0; c_i < joint_node->children_count; c_i++) {
+ joint_i.children[c_i] = cgltf_node_index(data, joint_node->children[c_i]);
+ joint_i.children_count++;
+ }
+ } else {
+ TRACE("Storing joint transform");
+ joint_i.transform_components = TRANSFORM_DEFAULT;
+ if (joint_node->has_translation) {
+ memcpy(&joint_i.transform_components.position, &joint_node->translation, 3 * sizeof(f32));
+ }
+ if (joint_node->has_rotation) {
+ memcpy(&joint_i.transform_components.rotation, &joint_node->rotation, 4 * sizeof(f32));
+ }
+ if (joint_node->has_scale) {
+ memcpy(&joint_i.transform_components.scale, &joint_node->scale, 3 * sizeof(f32));
+ }
+ joint_i.parent = cgltf_node_index(data, joint_node->parent);
+ }
+ // Calculate and store the starting transform of the joint
+ joint_i.local_transform = transform_to_mat(&joint_i.transform_components);
+ // Read in the inverse bind matrix
+ cgltf_accessor_read_float(gltf_inverse_bind_matrices, i, &joint_i.inverse_bind_matrix.data[0],
+ 16);
+ Joint_darray_push(armature.joints, joint_i);
+ }
+ main_skeleton = armature;
+ // out_model->armature = armature;
+ // out_model->has_joints = true;
+ }
+
+ // --- Materials
+ size_t num_materials = GLTF_LoadMaterials(data, relative_path, tmp_materials);
+
+ // --- Meshes
+ size_t num_meshes = data->meshes_count;
+ TRACE("Num meshes %d", num_meshes);
+ for (size_t m = 0; m < num_meshes; m++) {
+ printf("Primitive count %d\n", data->meshes[m].primitives_count);
+ for (size_t prim_i = 0; prim_i < data->meshes[m].primitives_count; prim_i++) {
+ DEBUG("Primitive %d\n", prim_i);
+
+ cgltf_primitive primitive = data->meshes[m].primitives[prim_i];
+ DEBUG("Found %d attributes", primitive.attributes_count);
+
+ for (cgltf_size a = 0; a < primitive.attributes_count; a++) {
+ cgltf_attribute attribute = primitive.attributes[a];
+ if (attribute.type == cgltf_attribute_type_position) {
+ cgltf_accessor* accessor = attribute.data;
+ load_position_components(tmp_positions, accessor);
+ } else if (attribute.type == cgltf_attribute_type_normal) {
+ cgltf_accessor* accessor = attribute.data;
+ load_normal_components(tmp_normals, accessor);
+ } else if (attribute.type == cgltf_attribute_type_texcoord) {
+ cgltf_accessor* accessor = attribute.data;
+ load_texcoord_components(tmp_uvs, accessor);
+ } else if (attribute.type == cgltf_attribute_type_joints) {
+ TRACE("Load joint indices from accessor");
+ cgltf_accessor* accessor = attribute.data;
+ load_joint_index_components(tmp_joint_indices, accessor);
+ } else if (attribute.type == cgltf_attribute_type_weights) {
+ TRACE("Load joint weights from accessor");
+ cgltf_accessor* accessor = attribute.data;
+ CASSERT(accessor->component_type == cgltf_component_type_r_32f);
+ CASSERT(accessor->type == cgltf_type_vec4);
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ Vec4 weights;
+ cgltf_accessor_read_float(accessor, v, &weights.x, 4);
+ printf("Weights affecting vertex %d : %f %f %f %f\n", v, weights.x, weights.y,
+ weights.z, weights.w);
+ Vec4_darray_push(tmp_weights, weights);
+ }
+ } else {
+ WARN("Unhandled cgltf_attribute_type: %s. skipping..", attribute.name);
+ }
+ }
+ // mesh.vertex_bone_data = vertex_bone_data_darray_new(1);
+ i32 mat_idx = -1;
+ if (primitive.material != NULL) {
+ DEBUG("Primitive Material %s", primitive.material->name);
+ // FIXME!
+ for (u32 i = 0; i < Material_darray_len(tmp_materials); i++) {
+ printf("%s vs %s \n", primitive.material->name, tmp_materials->data[i].name);
+ if (strcmp(primitive.material->name, tmp_materials->data[i].name) == 0) {
+ INFO("Found material");
+ mat_idx = i;
+ i32_darray_push(tmp_material_indexes, mat_idx);
+ break;
+ }
+ }
+ } else {
+ i32_darray_push(tmp_material_indexes, -1);
+ }
+
+ TRACE("Vertex data has been loaded");
+
+ Vertex_darray* geo_vertices = Vertex_darray_new(3);
+ u32_darray* geo_indices = u32_darray_new(0);
+
+ // Store vertices
+ printf("Positions %d Normals %d UVs %d\n", tmp_positions->len, tmp_normals->len,
+ tmp_uvs->len);
+ // assert(tmp_positions->len == tmp_normals->len);
+ // assert(tmp_normals->len == tmp_uvs->len);
+ bool has_normals = tmp_normals->len > 0;
+ bool has_uvs = tmp_uvs->len > 0;
+ for (u32 v_i = 0; v_i < tmp_positions->len; v_i++) {
+ Vertex v = { 0 };
+ if (is_skinned) {
+ v.skinned_3d.position = tmp_positions->data[v_i];
+ v.skinned_3d.normal = has_normals ? tmp_normals->data[v_i] : VEC3_ZERO,
+ v.skinned_3d.tex_coords = has_uvs ? tmp_uvs->data[v_i] : vec2_create(0., 0.);
+ v.skinned_3d.bone_ids = tmp_joint_indices->data[v_i];
+ v.skinned_3d.bone_weights = tmp_weights->data[v_i];
+ } else {
+ v.static_3d.position = tmp_positions->data[v_i];
+ v.static_3d.normal = has_normals ? tmp_normals->data[v_i] : VEC3_ZERO,
+ v.static_3d.tex_coords = has_uvs ? tmp_uvs->data[v_i] : vec2_create(0., 0.);
+ }
+ Vertex_darray_push(geo_vertices, v);
+ };
+
+ // Store indices
+ cgltf_accessor* indices = primitive.indices;
+ if (primitive.indices > 0) {
+ WARN("indices! %d", indices->count);
+
+ // store indices
+ for (cgltf_size i = 0; i < indices->count; ++i) {
+ cgltf_uint ei;
+ cgltf_accessor_read_uint(indices, i, &ei, 1);
+ u32_darray_push(geo_indices, ei);
+ }
+
+ Geometry* geometry = malloc(sizeof(Geometry));
+ geometry->format = is_skinned ? VERTEX_SKINNED : VERTEX_STATIC_3D;
+ geometry->has_indices = true;
+ geometry->vertices = geo_vertices;
+ geometry->indices = geo_indices;
+ geometry->index_count = geo_indices->len;
+
+ Mesh m = Mesh_Create(geometry, false);
+ if (is_skinned) {
+ m.is_skinned = true;
+ m.armature = main_skeleton;
+ }
+ Mesh_darray_push(tmp_meshes, m);
+
+ Vec3_darray_clear(tmp_positions);
+ Vec3_darray_clear(tmp_normals);
+ Vec2_darray_clear(tmp_uvs);
+ Vec4i_darray_clear(tmp_joint_indices);
+ Vec4_darray_clear(tmp_weights);
+ } else {
+ WARN("No indices found. Ignoring mesh...");
+ }
+ }
+
+ // --- Animations
+ size_t num_animations = data->animations_count;
+ TRACE("Num animations %d", num_animations);
+
+ if (num_animations > 0) {
+ if (!out_model->animations) {
+ out_model->animations = AnimationClip_darray_new(num_animations);
+ }
+ out_model->anim_arena = arena_create(malloc(MB(1)), MB(1));
+ arena* arena = &out_model->anim_arena;
+
+ // Iterate over each animation in the GLTF
+ for (int anim_idx = 0; anim_idx < data->animations_count; anim_idx++) {
+ cgltf_animation animation = data->animations[anim_idx];
+ AnimationClip clip = { 0 };
+ clip.clip_name = "test anim clip";
+ clip.channels = AnimationSampler_darray_new(1);
+
+ // for each animation, loop through all the channels
+ for (size_t c = 0; c < animation.channels_count; c++) {
+ cgltf_animation_channel channel = animation.channels[c];
+
+ AnimationSampler sampler = { 0 };
+
+ KeyframeKind data_type;
+
+ switch (channel.target_path) {
+ case cgltf_animation_path_type_rotation:
+ data_type = KEYFRAME_ROTATION;
+ break;
+ case cgltf_animation_path_type_translation:
+ data_type = KEYFRAME_TRANSLATION;
+ break;
+ case cgltf_animation_path_type_scale:
+ data_type = KEYFRAME_SCALE;
+ break;
+ case cgltf_animation_path_type_weights:
+ data_type = KEYFRAME_WEIGHTS;
+ WARN("Morph target weights arent supported yet");
+ return false;
+ default:
+ WARN("unsupported animation type");
+ return false;
+ }
+
+ sampler.current_index = 0;
+ sampler.animation.interpolation = INTERPOLATION_LINEAR; // NOTE: hardcoded for now
+
+ // Keyframe times
+ size_t n_frames = channel.sampler->input->count;
+ CASSERT_MSG(channel.sampler->input->component_type == cgltf_component_type_r_32f,
+ "Expected animation sampler input component to be type f32");
+ f32* times = arena_alloc(arena, n_frames * sizeof(f32));
+ sampler.animation.n_timestamps = n_frames;
+ sampler.animation.timestamps = times;
+ cgltf_accessor_unpack_floats(channel.sampler->input, times, n_frames);
+
+ // Keyframe values
+ size_t n_values = channel.sampler->output->count;
+ CASSERT_MSG(n_frames == n_values, "keyframe times = keyframe values");
+
+ Keyframes keyframes = { 0 };
+ keyframes.kind = data_type;
+ keyframes.count = n_values;
+ keyframes.values = arena_alloc(arena, n_values * sizeof(Keyframe));
+ for (cgltf_size v = 0; v < channel.sampler->output->count; ++v) {
+ switch (data_type) {
+ case KEYFRAME_ROTATION: {
+ Quat rot;
+ cgltf_accessor_read_float(channel.sampler->output, v, &rot.x, 4);
+ // printf("Quat %f %f %f %f\n", rot.x, rot.y, rot.z, rot.w);
+ keyframes.values[v].rotation = rot;
+ break;
+ }
+ case KEYFRAME_TRANSLATION: {
+ Vec3 trans;
+ cgltf_accessor_read_float(channel.sampler->output, v, &trans.x, 3);
+ keyframes.values[v].translation = trans;
+ break;
+ }
+ case KEYFRAME_SCALE: {
+ Vec3 scale;
+ cgltf_accessor_read_float(channel.sampler->output, v, &scale.x, 3);
+ keyframes.values[v].scale = scale;
+ break;
+ }
+ case KEYFRAME_WEIGHTS: {
+ // TODO: morph weights
+ break;
+ }
+ }
+ }
+ sampler.animation.values = keyframes;
+ sampler.min = channel.sampler->input->min[0];
+ sampler.max = channel.sampler->input->max[0];
+
+ // *target_property = sampler;
+ printf("%d timestamps between %f and %f\n", sampler.animation.n_timestamps, sampler.min,
+ sampler.max);
+
+ // TODO: get target
+ size_t target_index = cgltf_node_index(data, channel.target_node);
+ size_t joint_index = 0;
+ bool found = false;
+ for (u32 ji = 0; ji < main_skeleton.joints->len; ji++) {
+ if (main_skeleton.joints->data[ji].node_idx == target_index) {
+ joint_index = ji;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ WARN("Coulndnt find joint index");
+ }
+ sampler.target_joint_idx =
+ joint_index; // NOTE: this assuming the target is a joint at the moment
+ AnimationSampler_darray_push(clip.channels, sampler);
+ }
+
+ AnimationClip_darray_push(out_model->animations, clip);
+ }
+ }
+
+ // exit(0);
+ }
+
+ num_meshes = tmp_meshes->len;
+
+ // we now have an array of meshes, materials, and the material which each mesh should get
+ out_model->meshes = malloc(num_meshes * sizeof(MeshHandle));
+ out_model->mesh_count = num_meshes;
+ out_model->materials = malloc(num_materials * sizeof(MaterialHandle));
+ out_model->material_count = num_materials;
+
+ MaterialHandle* mat_handles = calloc(num_materials, sizeof(MaterialHandle));
+ for (u32 mat_i = 0; mat_i < num_materials; mat_i++) {
+ mat_handles[mat_i] =
+ Material_pool_insert(Render_GetMaterialPool(), &tmp_materials->data[mat_i]);
+ }
+ memcpy(out_model->materials, mat_handles, num_materials * sizeof(MaterialHandle));
+
+ for (u32 mesh_i = 0; mesh_i < num_meshes; mesh_i++) {
+ i32 mat_idx = tmp_material_indexes->data[mesh_i];
+ if (mat_idx > 0) {
+ tmp_meshes->data[mesh_i].material = mat_handles[mat_idx];
+
+ } else {
+ Material default_mat = PBRMaterialDefault();
+ tmp_meshes->data[mesh_i].material =
+ Material_pool_insert(Render_GetMaterialPool(), &default_mat);
+ }
+ MeshHandle mesh = Mesh_pool_insert(Render_GetMeshPool(), &tmp_meshes->data[mesh_i]);
+ out_model->meshes[mesh_i] = mesh;
+ }
+
+ free(mat_handles);
+
+ return true;
+}
+
+const char* bool_yes_no(bool pred) { return pred ? "Yes" : "No"; }
+
+// Loads all materials
+size_t GLTF_LoadMaterials(cgltf_data* data, Str8 relative_path, Material_darray* out_materials) {
+ size_t num_materials = data->materials_count;
+ TRACE("Num materials %d", num_materials);
+ for (size_t m = 0; m < num_materials; m++) {
+ cgltf_material gltf_material = data->materials[m];
+ TRACE("Loading material '%s'", gltf_material.name);
+ cgltf_pbr_metallic_roughness pbr = gltf_material.pbr_metallic_roughness;
+
+ Material our_material = PBRMaterialDefault(); // focusing on PBR materials for now
+
+ our_material.base_colour =
+ vec3(pbr.base_color_factor[0], pbr.base_color_factor[1], pbr.base_color_factor[2]);
+ our_material.metallic = pbr.metallic_factor;
+ our_material.roughness = pbr.roughness_factor;
+
+ // -- albedo / base colour
+ cgltf_texture_view albedo_tex_view = pbr.base_color_texture;
+ bool has_albedo_texture = albedo_tex_view.texture != NULL;
+ TRACE("Has PBR base colour texture? %s", bool_yes_no(has_albedo_texture));
+ printf("Base colour factor: %f %f %f\n", pbr.base_color_factor[0], pbr.base_color_factor[1],
+ pbr.base_color_factor[2]);
+ if (has_albedo_texture) {
+ char albedo_map_path[1024];
+ snprintf(albedo_map_path, sizeof(albedo_map_path), "%s/%s", relative_path.buf,
+ albedo_tex_view.texture->image->uri);
+ our_material.albedo_map = TextureLoadFromFile(albedo_map_path);
+ } else {
+ our_material.albedo_map = Render_GetWhiteTexture();
+ WARN("GLTF model has no albedo map");
+ our_material.base_colour =
+ vec3_create(pbr.base_color_factor[0], pbr.base_color_factor[1], pbr.base_color_factor[2]);
+ }
+
+ // -- metallic
+ cgltf_texture_view metal_rough_tex_view = pbr.metallic_roughness_texture;
+ printf("Metal factor: %f\n", pbr.metallic_factor);
+ printf("Roughness factor: %f\n", pbr.roughness_factor);
+ if (metal_rough_tex_view.texture != NULL) {
+ char metal_rough_map_path[1024];
+ snprintf(metal_rough_map_path, sizeof(metal_rough_map_path), "%s/%s", relative_path.buf,
+ metal_rough_tex_view.texture->image->uri);
+ our_material.metallic_roughness_map = TextureLoadFromFile(metal_rough_map_path);
+ } else {
+ WARN("GLTF model has no metal/roughness map");
+ our_material.metallic = pbr.metallic_factor;
+ our_material.roughness = pbr.roughness_factor;
+ }
+
+ cgltf_texture_view normal_tex_view = gltf_material.normal_texture;
+ if (normal_tex_view.texture != NULL) {
+ char normal_map_path[1024];
+ snprintf(normal_map_path, sizeof(normal_map_path), "%s/%s", relative_path.buf,
+ normal_tex_view.texture->image->uri);
+ our_material.normal_map = TextureLoadFromFile(normal_map_path);
+ } else {
+ WARN("GLTF model has no normal map");
+ }
+
+ u32 string_length = strlen(gltf_material.name) + 1;
+ assert(string_length < 64);
+ strcpy(our_material.name, gltf_material.name);
+
+ Material_darray_push(out_materials, our_material);
+ }
+
+ return out_materials->len;
+}
diff --git a/archive/src/resources/loaders.h b/archive/src/resources/loaders.h
new file mode 100644
index 0000000..ea1f9a2
--- /dev/null
+++ b/archive/src/resources/loaders.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "defines.h"
+#include "render_types.h"
+#include "str.h"
+
+// --- Public API
+PUB ModelHandle ModelLoad_obj(const char* path, bool invert_texture_y);
+PUB ModelHandle ModelLoad_gltf(const char* path, bool invert_texture_y);
+
+typedef struct GLTF_LoadStats {
+ u32 mesh_count, material_count, vertex_count, index_count, animation_count, joint_count;
+} GLTF_LoadStats;
+
+// --- Internal
+bool model_load_gltf_str(const char* file_string, const char* filepath, Str8 relative_path,
+ Model* out_model, bool invert_textures_y);
diff --git a/archive/src/resources/obj.c b/archive/src/resources/obj.c
new file mode 100644
index 0000000..a5e9b18
--- /dev/null
+++ b/archive/src/resources/obj.c
@@ -0,0 +1,398 @@
+/**
+ * @file obj.c
+ * @brief Wavefront OBJ loader.
+ * @copyright Copyright (c) 2024
+ */
+#include <ctype.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "core.h"
+#include "darray.h"
+#include "file.h"
+#include "log.h"
+#include "maths.h"
+#include "mem.h"
+#include "platform.h"
+#include "render.h"
+#include "render_types.h"
+#include "str.h"
+
+extern Core g_core;
+
+struct face {
+ u32 vertex_indices[3];
+ u32 normal_indices[3];
+ u32 uv_indices[3];
+};
+typedef struct face face;
+
+KITC_DECL_TYPED_ARRAY(Vec3)
+KITC_DECL_TYPED_ARRAY(Vec2)
+KITC_DECL_TYPED_ARRAY(face)
+
+// Forward declarations
+// void create_submesh(mesh_darray *meshes, Vec3_darray *tmp_positions, Vec3_darray *tmp_normals,
+// Vec2_darray *tmp_uvs, face_darray *tmp_faces, material_darray *materials,
+// bool material_loaded, char current_material_name[256]);
+// bool load_material_lib(const char *path, str8 relative_path, material_darray *materials);
+// bool model_load_obj_str(const char *file_string, str8 relative_path, Model *out_model,
+// bool invert_textures_y);
+
+ModelHandle model_load_obj(Core* core, const char* path, bool invert_textures_y) {
+ size_t arena_size = 1024;
+ arena scratch = arena_create(malloc(arena_size), arena_size);
+
+ TRACE("Loading model at Path %s\n", path);
+ path_opt relative_path = path_parent(&scratch, path);
+ if (!relative_path.has_value) {
+ WARN("Couldnt get a relative path for the path to use for loading materials & textures later");
+ }
+ const char* file_string = string_from_file(path);
+
+ ModelHandle handle;
+ // model *model = model_pool_alloc(&g_core.models, &handle);
+ // model->name = str8_cstr_view(path);
+ // model->meshes = mesh_darray_new(1);
+
+ // bool success = model_load_obj_str(file_string, relative_path.path, &model, invert_textures_y);
+
+ // if (!success) {
+ // FATAL("Couldnt load OBJ file at path %s", path);
+ // ERROR_EXIT("Load fails are considered crash-worthy right now. This will change later.\n");
+ // }
+
+ // arena_free_all(&scratch);
+ // arena_free_storage(&scratch);
+ return handle;
+}
+
+bool model_load_obj_str(const char* file_string, Str8 relative_path, Model* out_model,
+ bool invert_textures_y) {
+ TRACE("Load OBJ from string");
+
+ // // Setup temps
+ // vec3_darray *tmp_positions = vec3_darray_new(1000);
+ // vec3_darray *tmp_normals = vec3_darray_new(1000);
+ // vec2_darray *tmp_uvs = vec2_darray_new(1000);
+ // face_darray *tmp_faces = face_darray_new(1000);
+ // // TODO: In the future I'd like these temporary arrays to be allocated from an arena provided
+ // // by the function one level up, model_load_obj. That way we can just `return false;` anywhere
+ // in
+ // // this code to indicate an error, and be sure that all that memory will be cleaned up without
+ // // having to call vec3_darray_free in every single error case before returning.
+
+ // // Other state
+ // bool object_set = false;
+ // bool material_loaded = false;
+ // char current_material_name[64];
+
+ // char *pch;
+ // char *rest = file_string;
+ // pch = strtok_r((char *)file_string, "\n", &rest);
+
+ // int line_num = 0;
+ // char last_char_type = 'a';
+
+ // while (pch != NULL) {
+ // line_num++;
+ // char line_header[128];
+ // int offset = 0;
+
+ // // skip whitespace
+ // char *p = pch;
+
+ // skip_space(pch);
+
+ // if (*p == '\0') {
+ // /* the string is empty */
+ // } else {
+ // // read the first word of the line
+ // int res = sscanf(pch, "%s %n", line_header, &offset);
+ // /* printf("header: %s, offset : %d res: %d\n",line_header, offset, res); */
+ // if (res != 1) {
+ // break;
+ // }
+
+ // if (strcmp(line_header, "o") == 0 || strcmp(line_header, "g") == 0) {
+ // // if we're currently parsing one
+ // if (!object_set) {
+ // object_set = true;
+ // } else {
+ // create_submesh(out_model->meshes, tmp_positions, tmp_normals, tmp_uvs, tmp_faces,
+ // NULL, // out_model->materials,
+ // material_loaded, current_material_name);
+ // object_set = false;
+ // }
+ // } else if (strcmp(line_header, "v") == 0) {
+ // // special logic: if we went from faces back to vertices trigger a mesh output.
+ // // PS: I hate OBJ
+ // if (last_char_type == 'f') {
+ // create_submesh(out_model->meshes, tmp_positions, tmp_normals, tmp_uvs, tmp_faces,
+ // NULL, // FIXME: out_model->materials,
+ // material_loaded, current_material_name);
+ // object_set = false;
+ // }
+
+ // last_char_type = 'v';
+ // vec3 vertex;
+ // sscanf(pch + offset, "%f %f %f", &vertex.x, &vertex.y, &vertex.z);
+
+ // vec3_darray_push(tmp_positions, vertex);
+ // } else if (strcmp(line_header, "vt") == 0) {
+ // last_char_type = 't';
+ // vec2 uv;
+ // char copy[1024];
+ // memcpy(copy, pch + offset, strlen(pch + offset) + 1);
+ // char *p = pch + offset;
+ // while (isspace((unsigned char)*p)) ++p;
+
+ // // I can't remember what is going on here
+ // memset(copy, 0, 1024);
+ // memcpy(copy, pch + offset, strlen(pch + offset) + 1);
+ // int res = sscanf(copy, "%f %f", &uv.x, &uv.y);
+ // memset(copy, 0, 1024);
+ // memcpy(copy, pch + offset, strlen(pch + offset) + 1);
+ // if (res != 1) {
+ // // da frick? some .obj files have 3 uvs instead of 2
+ // f32 dummy;
+ // int res2 = sscanf(copy, "%f %f %f", &uv.x, &uv.y, &dummy);
+ // }
+
+ // if (invert_textures_y) {
+ // uv.y = -uv.y; // flip Y axis to be consistent with how other PNGs are being handled
+ // // `texture_load` will flip it again
+ // }
+ // vec2_darray_push(tmp_uvs, uv);
+ // } else if (strcmp(line_header, "vn") == 0) {
+ // last_char_type = 'n';
+ // vec3 normal;
+ // sscanf(pch + offset, "%f %f %f", &normal.x, &normal.y, &normal.z);
+ // vec3_darray_push(tmp_normals, normal);
+ // } else if (strcmp(line_header, "f") == 0) {
+ // last_char_type = 'f';
+ // struct face f;
+ // sscanf(pch + offset, "%d/%d/%d %d/%d/%d %d/%d/%d", &f.vertex_indices[0],
+ // &f.uv_indices[0],
+ // &f.normal_indices[0], &f.vertex_indices[1], &f.uv_indices[1],
+ // &f.normal_indices[1], &f.vertex_indices[2], &f.uv_indices[2],
+ // &f.normal_indices[2]);
+ // // printf("f %d/%d/%d %d/%d/%d %d/%d/%d\n", f.vertex_indices[0], f.uv_indices[0],
+ // // f.normal_indices[0],
+ // // f.vertex_indices[1], f.uv_indices[1], f.normal_indices[1],
+ // // f.vertex_indices[2], f.uv_indices[2], f.normal_indices[2]);
+ // face_darray_push(tmp_faces, f);
+ // } else if (strcmp(line_header, "mtllib") == 0) {
+ // char filename[1024];
+ // sscanf(pch + offset, "%s", filename);
+ // char mtllib_path[1024];
+ // snprintf(mtllib_path, sizeof(mtllib_path), "%s/%s", relative_path.buf, filename);
+ // if (!load_material_lib(mtllib_path, relative_path, out_model->materials)) {
+ // ERROR("couldnt load material lib");
+ // return false;
+ // }
+ // } else if (strcmp(line_header, "usemtl") == 0) {
+ // material_loaded = true;
+ // sscanf(pch + offset, "%s", current_material_name);
+ // }
+ // }
+
+ // pch = strtok_r(NULL, "\n", &rest);
+ // }
+
+ // // last mesh or if one wasnt created with 'o' directive
+ // if (face_darray_len(tmp_faces) > 0) {
+ // TRACE("Last leftover mesh");
+ // create_submesh(out_model->meshes, tmp_positions, tmp_normals, tmp_uvs, tmp_faces,
+ // NULL, // TODO: out_model->materials,
+ // material_loaded, current_material_name);
+ // }
+
+ // // Free data
+ // free((char *)file_string);
+ // vec3_darray_free(tmp_positions);
+ // vec3_darray_free(tmp_normals);
+ // vec2_darray_free(tmp_uvs);
+ // face_darray_free(tmp_faces);
+ // TRACE("Freed temporary OBJ loading data");
+
+ // if (mesh_darray_len(out_model->meshes) > 256) {
+ // printf("num meshes: %ld\n", mesh_darray_len(out_model->meshes));
+ // }
+
+ // // TODO: bounding box calculation for each mesh
+ // // TODO: bounding box calculation for model
+
+ // TODO: copy from mesh_darray to malloc'd mesh* array
+
+ return true;
+}
+
+// /**
+// * @brief Takes the current positions, normals, uvs arrays and constructs the vertex array
+// * from those indices.
+// */
+// void create_submesh(mesh_darray *meshes, vec3_darray *tmp_positions, vec3_darray *tmp_normals,
+// vec2_darray *tmp_uvs, face_darray *tmp_faces, material_darray *materials,
+// bool material_loaded, char current_material_name[256]) {
+// // size_t num_verts = face_darray_len(tmp_faces) * 3;
+// // vertex_darray *out_vertices = vertex_darray_new(num_verts);
+
+// // face_darray_iter face_iter = face_darray_iter_new(tmp_faces);
+// // struct face *f;
+
+// // while ((f = face_darray_iter_next(&face_iter))) {
+// // for (int j = 0; j < 3; j++) {
+// // vertex vert = { 0 };
+// // vert.position = tmp_positions->data[f->vertex_indices[j] - 1];
+// // if (vec3_darray_len(tmp_normals) == 0) {
+// // vert.normal = vec3_create(0.0, 0.0, 0.0);
+// // } else {
+// // vert.normal = tmp_normals->data[f->normal_indices[j] - 1];
+// // }
+// // vert.uv = tmp_uvs->data[f->uv_indices[j] - 1];
+// // vertex_darray_push(out_vertices, vert);
+// // }
+// // }
+
+// // DEBUG("Loaded submesh\n vertices: %zu\n uvs: %zu\n normals: %zu\n faces: %zu",
+// // vec3_darray_len(tmp_positions), vec2_darray_len(tmp_uvs),
+// vec3_darray_len(tmp_normals),
+// // face_darray_len(tmp_faces));
+
+// // // Clear current object faces
+// // face_darray_clear(tmp_faces);
+
+// // mesh m = { .vertices = out_vertices };
+// // if (material_loaded) {
+// // // linear scan to find material
+// // bool found = false;
+// // DEBUG("Num of materials : %ld", material_darray_len(materials));
+// // material_darray_iter mat_iter = material_darray_iter_new(materials);
+// // blinn_phong_material *cur_material;
+// // while ((cur_material = material_darray_iter_next(&mat_iter))) {
+// // if (strcmp(cur_material->name, current_material_name) == 0) {
+// // DEBUG("Found match");
+// // m.material_index = mat_iter.current_idx - 1;
+// // found = true;
+// // break;
+// // }
+// // }
+
+// // if (!found) {
+// // // TODO: default material
+// // m.material_index = 0;
+// // DEBUG("Set default material");
+// // }
+// // }
+// // mesh_darray_push(meshes, m);
+// }
+
+// bool load_material_lib(const char *path, str8 relative_path, material_darray *materials) {
+// TRACE("BEGIN load material lib at %s", path);
+
+// // const char *file_string = string_from_file(path);
+// // if (file_string == NULL) {
+// // ERROR("couldnt load %s", path);
+// // return false;
+// // }
+
+// // char *pch;
+// // char *saveptr;
+// // pch = strtok_r((char *)file_string, "\n", &saveptr);
+
+// // material current_material = DEFAULT_MATERIAL;
+
+// // bool material_set = false;
+
+// // while (pch != NULL) {
+// // char line_header[128];
+// // int offset = 0;
+// // // read the first word of the line
+// // int res = sscanf(pch, "%s %n", line_header, &offset);
+// // if (res != 1) {
+// // break;
+// // }
+
+// // // When we see "newmtl", start a new material, or flush the previous one
+// // if (strcmp(line_header, "newmtl") == 0) {
+// // if (material_set) {
+// // // a material was being parsed, so flush that one and start a new one
+// // material_darray_push(materials, current_material);
+// // DEBUG("pushed material with name %s", current_material.name);
+// // WARN("Reset current material");
+// // current_material = DEFAULT_MATERIAL;
+// // } else {
+// // material_set = true;
+// // }
+// // // scan the new material name
+// // char material_name[64];
+// // sscanf(pch + offset, "%s", current_material.name);
+// // DEBUG("material name %s\n", current_material.name);
+// // // current_material.name = material_name;
+// // } else if (strcmp(line_header, "Ka") == 0) {
+// // // ambient
+// // sscanf(pch + offset, "%f %f %f", &current_material.ambient_colour.x,
+// // &current_material.ambient_colour.y, &current_material.ambient_colour.z);
+// // } else if (strcmp(line_header, "Kd") == 0) {
+// // // diffuse
+// // sscanf(pch + offset, "%f %f %f", &current_material.diffuse.x,
+// &current_material.diffuse.y,
+// // &current_material.diffuse.z);
+// // } else if (strcmp(line_header, "Ks") == 0) {
+// // // specular
+// // sscanf(pch + offset, "%f %f %f", &current_material.specular.x,
+// // &current_material.specular.y,
+// // &current_material.specular.z);
+// // } else if (strcmp(line_header, "Ns") == 0) {
+// // // specular exponent
+// // sscanf(pch + offset, "%f", &current_material.spec_exponent);
+// // } else if (strcmp(line_header, "map_Kd") == 0) {
+// // char diffuse_map_filename[1024];
+// // sscanf(pch + offset, "%s", diffuse_map_filename);
+// // char diffuse_map_path[1024];
+// // snprintf(diffuse_map_path, sizeof(diffuse_map_path), "%s/%s", relative_path.buf,
+// // diffuse_map_filename);
+// // printf("load from %s\n", diffuse_map_path);
+
+// // // --------------
+// // texture diffuse_texture = texture_data_load(diffuse_map_path, true);
+// // current_material.diffuse_texture = diffuse_texture;
+// // strcpy(current_material.diffuse_tex_path, diffuse_map_path);
+// // texture_data_upload(&current_material.diffuse_texture);
+// // // --------------
+// // } else if (strcmp(line_header, "map_Ks") == 0) {
+// // // char specular_map_path[1024] = "assets/";
+// // // sscanf(pch + offset, "%s", specular_map_path + 7);
+// // char specular_map_filename[1024];
+// // sscanf(pch + offset, "%s", specular_map_filename);
+// // char specular_map_path[1024];
+// // snprintf(specular_map_path, sizeof(specular_map_path), "%s/%s", relative_path.buf,
+// // specular_map_filename);
+// // printf("load from %s\n", specular_map_path);
+// // // --------------
+// // texture specular_texture = texture_data_load(specular_map_path, true);
+// // current_material.specular_texture = specular_texture;
+// // strcpy(current_material.specular_tex_path, specular_map_path);
+// // texture_data_upload(&current_material.specular_texture);
+// // // --------------
+// // } else if (strcmp(line_header, "map_Bump") == 0) {
+// // // TODO
+// // }
+
+// // pch = strtok_r(NULL, "\n", &saveptr);
+// // }
+
+// // TRACE("end load material lib");
+
+// // // last mesh or if one wasnt created with 'o' directive
+// // // TRACE("Last leftover material");
+// // material_darray_push(materials, current_material);
+
+// // INFO("Loaded %ld materials", material_darray_len(materials));
+// TRACE("END load material lib");
+// return true;
+// }