diff options
Diffstat (limited to 'archive/src/resources')
-rw-r--r-- | archive/src/resources/gltf.c | 596 | ||||
-rw-r--r-- | archive/src/resources/loaders.h | 17 | ||||
-rw-r--r-- | archive/src/resources/obj.c | 398 |
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", ¤t_material.ambient_colour.x, +// // ¤t_material.ambient_colour.y, ¤t_material.ambient_colour.z); +// // } else if (strcmp(line_header, "Kd") == 0) { +// // // diffuse +// // sscanf(pch + offset, "%f %f %f", ¤t_material.diffuse.x, +// ¤t_material.diffuse.y, +// // ¤t_material.diffuse.z); +// // } else if (strcmp(line_header, "Ks") == 0) { +// // // specular +// // sscanf(pch + offset, "%f %f %f", ¤t_material.specular.x, +// // ¤t_material.specular.y, +// // ¤t_material.specular.z); +// // } else if (strcmp(line_header, "Ns") == 0) { +// // // specular exponent +// // sscanf(pch + offset, "%f", ¤t_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(¤t_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(¤t_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; +// } |