diff options
Diffstat (limited to 'src/resources')
-rw-r--r-- | src/resources/gltf.c | 765 | ||||
-rw-r--r-- | src/resources/loaders.h | 4 | ||||
-rw-r--r-- | src/resources/obj.c | 390 |
3 files changed, 1155 insertions, 4 deletions
diff --git a/src/resources/gltf.c b/src/resources/gltf.c index b646f58..81992d1 100644 --- a/src/resources/gltf.c +++ b/src/resources/gltf.c @@ -1 +1,764 @@ -// TODO: Port code from old repo
\ No newline at end of file +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include "animation.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 "path.h" +#include "render.h" +#include "render_backend.h" +#include "render_types.h" +#include "str.h" + +#define CGLTF_IMPLEMENTATION +#include <cgltf.h> + +struct face { + cgltf_uint indices[3]; +}; +typedef struct face face; + +KITC_DECL_TYPED_ARRAY(vec3) +KITC_DECL_TYPED_ARRAY(vec2) +KITC_DECL_TYPED_ARRAY(u32) +KITC_DECL_TYPED_ARRAY(vec4u) +KITC_DECL_TYPED_ARRAY(vec4) +KITC_DECL_TYPED_ARRAY(face) +// KITC_DECL_TYPED_ARRAY(joint) + +bool model_load_gltf_str(const char *file_string, const char *filepath, str8 relative_path, + model *out_model, bool invert_textures_y); + +model_handle model_load_gltf(struct core *core, const char *path, bool invert_texture_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); + + model model = { 0 }; + model.name = str8_cstr_view(path); + model.meshes = mesh_darray_new(1); + model.materials = material_darray_new(1); + + bool success = + model_load_gltf_str(file_string, path, relative_path.path, &model, invert_texture_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"); + } + + u32 index = model_darray_len(core->models); + model_darray_push(core->models, model); + + arena_free_all(&scratch); + arena_free_storage(&scratch); + return (model_handle){ .raw = index }; +} + +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 + +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); + vec4u_darray *tmp_joint_indices = vec4u_darray_new(1000); + vec4_darray *tmp_weights = vec4_darray_new(1000); + joint_darray *tmp_joints = joint_darray_new(256); + vertex_bone_data_darray *tmp_vertex_bone_data = vertex_bone_data_darray_new(1000); + + 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; + 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); + + cgltf_accessor *gltf_inverse_bind_matrices = gltf_skin->inverse_bind_matrices; + + // for each one we'll spit out a joint + for (size_t i = 0; i < num_joints; i++) { + cgltf_node *joint_node = gltf_skin->joints[i]; + + joint joint_i = { .name = "testjoint" }; + 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; + } 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)); + } + // TODO: support scaling as vec instead of float + } + joint_i.local_transform = transform_to_mat(&joint_i.transform_components); + cgltf_accessor_read_float(gltf_inverse_bind_matrices, i, &joint_i.inverse_bind_matrix.data[0], + 16); + joint_darray_push(tmp_joints, joint_i); + } + } + + // --- Materials + TRACE("Num materials %d", data->materials_count); + size_t num_materials = data->materials_count; + for (size_t m = 0; m < num_materials; m++) { + cgltf_material gltf_material = data->materials[m]; + material our_material = DEFAULT_MATERIAL; + + strcpy(our_material.name, gltf_material.name); + + cgltf_pbr_metallic_roughness pbr = gltf_material.pbr_metallic_roughness; + if (gltf_material.has_pbr_metallic_roughness) { + // we will use base color texture like blinn phong + cgltf_texture_view diff_tex_view = pbr.base_color_texture; + + char diffuse_map_path[1024]; + snprintf(diffuse_map_path, sizeof(diffuse_map_path), "%s/%s", relative_path.buf, + diff_tex_view.texture->image->uri); + + strcpy(our_material.diffuse_tex_path, diffuse_map_path); + texture diffuse_texture = texture_data_load(our_material.diffuse_tex_path, false); + texture_data_upload(&diffuse_texture); + our_material.diffuse_texture = diffuse_texture; + + cgltf_texture_view specular_tex_view = pbr.metallic_roughness_texture; + + char specular_map_path[1024]; + snprintf(specular_map_path, sizeof(specular_map_path), "%s/%s", relative_path.buf, + specular_tex_view.texture->image->uri); + + strcpy(our_material.specular_tex_path, specular_map_path); + texture specular_texture = texture_data_load(our_material.specular_tex_path, false); + texture_data_upload(&specular_texture); + our_material.specular_texture = specular_texture; + } + + material_darray_push(out_model->materials, our_material); + } + + // --- Meshes + TRACE("Num meshes %d", data->meshes_count); + size_t num_meshes = data->meshes_count; + for (size_t m = 0; m < num_meshes; m++) { + cgltf_primitive primitive = data->meshes[m].primitives[0]; + DEBUG("Found %d attributes", primitive.attributes_count); + // DEBUG("Number of this primitive %d", primitive.) + + for (int a = 0; a < data->meshes[m].primitives[0].attributes_count; a++) { + cgltf_attribute attribute = data->meshes[m].primitives[0].attributes[a]; + if (attribute.type == cgltf_attribute_type_position) { + TRACE("Load positions from accessor"); + + cgltf_accessor *accessor = attribute.data; + assert(accessor->component_type == cgltf_component_type_r_32f); + // CASSERT_MSG(accessor->type == cgltf_type_vec3, "Vertex positions should be a vec3"); + + TRACE("Loading %d vec3 components", accessor->count); + + for (cgltf_size v = 0; v < accessor->count; ++v) { + vec3 pos; + cgltf_accessor_read_float(accessor, v, &pos.x, 3); + vec3_darray_push(tmp_positions, pos); + } + + } else if (attribute.type == cgltf_attribute_type_normal) { + TRACE("Load normals from accessor"); + + cgltf_accessor *accessor = attribute.data; + assert(accessor->component_type == cgltf_component_type_r_32f); + // CASSERT_MSG(accessor->type == cgltf_type_vec3, "Normal vectors 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(tmp_normals, pos); + } + + } else if (attribute.type == cgltf_attribute_type_texcoord) { + TRACE("Load texture coordinates from accessor"); + cgltf_accessor *accessor = attribute.data; + assert(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(tmp_uvs, tex); + } + } else if (attribute.type == cgltf_attribute_type_joints) { + TRACE("Load joint indices from accessor"); + cgltf_accessor *accessor = attribute.data; + assert(accessor->component_type == cgltf_component_type_r_16u); + assert(accessor->type == cgltf_type_vec4); + vec4u joint_indices; + vec4 joints_as_floats; + for (cgltf_size v = 0; v < accessor->count; ++v) { + cgltf_accessor_read_float(accessor, v, &joints_as_floats.x, 4); + joint_indices.x = (u32)joints_as_floats.x; + joint_indices.y = (u32)joints_as_floats.y; + joint_indices.z = (u32)joints_as_floats.z; + joint_indices.w = (u32)joints_as_floats.w; + printf("Joints affecting vertex %d : %d %d %d %d\n", v, joint_indices.x, joint_indices.y, + joint_indices.z, joint_indices.w); + vec4u_darray_push(tmp_joint_indices, joint_indices); + } + + } else if (attribute.type == cgltf_attribute_type_weights) { + TRACE("Load joint weights from accessor"); + cgltf_accessor *accessor = attribute.data; + assert(accessor->component_type == cgltf_component_type_r_32f); + assert(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 mesh = { 0 }; + mesh.vertices = vertex_darray_new(10); + mesh.vertex_bone_data = vertex_bone_data_darray_new(1); + + if (primitive.material != NULL) { + for (int i = 0; i < material_darray_len(out_model->materials); i++) { + printf("%s vs %s \n", primitive.material->name, out_model->materials->data[i].name); + if (strcmp(primitive.material->name, out_model->materials->data[i].name) == 0) { + TRACE("Found material"); + mesh.material_index = i; + break; + } + } + } + + if (is_skinned) { + mesh.is_skinned = true; + // mesh.vertex_bone_data = vertex_bone_data_darray_new(tmp_joint_indices->len); + mesh.bones = joint_darray_new(tmp_joints->len); + for (int i = 0; i < tmp_joint_indices->len; i++) { + vertex_bone_data data; + data.joints = tmp_joint_indices->data[i]; + data.weights = tmp_weights->data[i]; + vertex_bone_data_darray_push(tmp_vertex_bone_data, + data); // Push the temp data that aligns with raw vertices + } + for (int i = 0; i < tmp_joints->len; i++) { + joint data = tmp_joints->data[i]; + joint_darray_push(mesh.bones, data); + } + } + + cgltf_accessor *indices = primitive.indices; + if (primitive.indices > 0) { + WARN("indices!"); + mesh.has_indices = true; + + mesh.indices = malloc(indices->count * sizeof(u32)); + mesh.indices_len = indices->count; + + // store indices + for (cgltf_size i = 0; i < indices->count; ++i) { + cgltf_uint ei; + cgltf_accessor_read_uint(indices, i, &ei, 1); + mesh.indices[i] = ei; + } + + // fetch and store vertices for each index + for (cgltf_size i = 0; i < indices->count; ++i) { + vertex vert; + cgltf_uint index = mesh.indices[i]; + vert.position = tmp_positions->data[index]; + vert.normal = tmp_normals->data[index]; + vert.uv = tmp_uvs->data[index]; + vertex_darray_push(mesh.vertices, vert); + + if (is_skinned) { + vertex_bone_data vbd = tmp_vertex_bone_data->data[index]; // create a copy + vertex_bone_data_darray_push(mesh.vertex_bone_data, vbd); + } + // for each vertex do the bone data + } + } else { + mesh.has_indices = false; + return false; // TODO + } + + mesh_darray_push(out_model->meshes, mesh); + + // clear data for each mesh + vec3_darray_clear(tmp_positions); + vec3_darray_clear(tmp_normals); + vec2_darray_free(tmp_uvs); + vec4u_darray_clear(tmp_joint_indices); + vec4_darray_clear(tmp_weights); + joint_darray_clear(tmp_joints); + } + + for (int i = 0; i < out_model->meshes->len; i++) { + u32 mat_idx = out_model->meshes->data[i].material_index; + printf("Mesh %d Mat index %d Mat name %s\n", i, mat_idx, + out_model->materials->data[mat_idx].name); + } + + // Animations + TRACE("Num animations %d", data->animations_count); + size_t num_animations = data->animations_count; + if (num_animations > 0) { +// Create an arena for all animation related data +#define ANIMATION_STORAGE_ARENA_SIZE (1024 * 1024 * 1024) + char *animation_backing_storage = malloc(ANIMATION_STORAGE_ARENA_SIZE); + // We'll store data on this arena so we can easily free it all at once later + out_model->animation_data_arena = + arena_create(animation_backing_storage, ANIMATION_STORAGE_ARENA_SIZE); + arena *arena = &out_model->animation_data_arena; + + if (!out_model->animations) { + out_model->animations = animation_clip_darray_new(num_animations); + } + + for (int anim_idx = 0; anim_idx < data->animations_count; anim_idx++) { + cgltf_animation animation = data->animations[anim_idx]; + animation_clip clip = { 0 }; + + for (size_t c = 0; c < animation.channels_count; c++) { + cgltf_animation_channel channel = animation.channels[c]; + + animation_sampler *sampler = arena_alloc(arena, sizeof(animation_sampler)); + + animation_sampler **target_property; + keyframe_kind data_type; + + switch (channel.target_path) { + case cgltf_animation_path_type_rotation: + target_property = &clip.rotation; + data_type = KEYFRAME_ROTATION; + break; + case cgltf_animation_path_type_translation: + target_property = &clip.translation; + data_type = KEYFRAME_TRANSLATION; + break; + case cgltf_animation_path_type_scale: + target_property = &clip.scale; + data_type = KEYFRAME_SCALE; + break; + case cgltf_animation_path_type_weights: + target_property = &clip.weights; + data_type = KEYFRAME_WEIGHTS; + WARN("Morph target weights arent supported yet"); + return false; + default: + WARN("unsupported animation type"); + return false; + } + *target_property = sampler; + + sampler->current_index = 0; + printf("1 %d index\n", sampler->current_index); + sampler->animation.interpolation = INTERPOLATION_LINEAR; + + // keyframe times + size_t n_frames = channel.sampler->input->count; + assert(channel.sampler->input->component_type == cgltf_component_type_r_32f); + // FIXME: CASSERT_MSG function "Expected animation sampler input component to be type f32 + // (keyframe times)"); + 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); + + assert_path_type_matches_component_type(channel.target_path, channel.sampler->output); + + // keyframe values + size_t n_values = channel.sampler->output->count; + assert(n_frames == n_values); + + keyframes keyframes = { 0 }; + keyframes.kind = KEYFRAME_ROTATION; + 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 + break; + } + } + } + sampler->animation.values = keyframes; + + sampler->min = channel.sampler->input->min[0]; + sampler->max = channel.sampler->input->max[0]; + + // clip.rotation = sampler; + // printf("%d timestamps\n", sampler->animation.n_timestamps); + // printf("%d index\n", sampler->current_index); + } + + WARN("stuff %ld", clip.rotation->animation.n_timestamps); + animation_clip_darray_push(out_model->animations, clip); + } + } + + return true; +} + +/* +bool model_load_gltf(const char *path, model *out_model) { + TRACE("Load GLTF %s", path); + + // Setup temp arrays + kitc_darray *tmp_positions = kitc_darray_new(sizeof(vec3), 1000); + kitc_darray *tmp_normals = kitc_darray_new(sizeof(vec3), 1000); + kitc_darray *tmp_uvs = kitc_darray_new(sizeof(vec2), 1000); + + // may as well just init with max capacity as we're just gonna free at end of this function anyway + bh_material_darray *materials = bh_material_darray_new(MAX_MATERIALS); + CASSERT(materials->len == 0); + + cgltf_options options = {0}; + cgltf_data *data = NULL; + cgltf_result result = cgltf_parse_file(&options, path, &data); + if (result == cgltf_result_success) { + DEBUG("gltf loaded succesfully"); + + cgltf_load_buffers(&options, data, path); + DEBUG("loaded buffers"); + + // -- Load materials. + // Each mesh will be handed a material + TRACE("Num materials %d", data->materials_count); + out_model->num_materials = data->materials_count; + + for (int m = 0; m < data->materials_count; m++) { + cgltf_material gltf_material = data->materials[m]; + bh_material our_material = {0}; + + str8 name = str8_copy(gltf_material.name); + printf("Material name %s\n", name.buf); + our_material.name = name; + + cgltf_pbr_metallic_roughness pbr = gltf_material.pbr_metallic_roughness; + if (gltf_material.has_pbr_metallic_roughness) { + // we will use base color texture like blinn phong + cgltf_texture_view diff_tex = pbr.base_color_texture; + strcpy(our_material.diffuse_tex_path, diff_tex.texture->image->uri); + } + + bh_material_darray_push(materials, our_material); + } + + // -- Load animations. + TRACE("Num animations %d", data->animations_count); + out_model->num_animations = data->animations_count; + for (int anim_idx = 0; anim_idx < data->animations_count; anim_idx++) { + cgltf_animation animation = data->animations[anim_idx]; + animation_clip our_animation = {0}; + + // loop through each channel (track) + for (int c = 0; c < animation.channels_count; c++) { + // each channel (track) has a target and a sampler + // for the time being we assume the target is the model itself + cgltf_animation_channel channel = animation.channels[c]; + animation_track our_track = {0}; + our_track.interpolation = interpolation_fn_from_gltf(channel.sampler->interpolation); + our_track.property = anim_prop_from_gltf(channel.target_path); + + // get the actual data out via the "accessor" + // input will be the times + + // Keyframe times + size_t n_frames = channel.sampler->input->count; + our_track.num_keyframes = n_frames; + f32 *times = malloc(sizeof(f32) * n_frames); + our_track.keyframe_times = times; + CASSERT_MSG(channel.sampler->input->component_type == cgltf_component_type_r_32f, + "Expected animation sampler input component to be type f32 (keyframe times)"); + cgltf_accessor_unpack_floats(channel.sampler->input, times, channel.sampler->input->count); + + // printf("keyframe times[\n"); + // for (int i = 0; i < n_frames; i++) { + // printf(" %f\n", times[i]); + // } + // printf("]\n"); + + // Data! + if (channel.target_path == cgltf_animation_path_type_rotation) { + CASSERT(channel.sampler->output->component_type == cgltf_component_type_r_32f); + CASSERT(channel.sampler->output->type == cgltf_type_vec4); + } + + our_track.keyframes = malloc(sizeof(keyframe_data) * n_frames); + for (cgltf_size v = 0; v < channel.sampler->output->count; ++v) { + quat rot; + cgltf_accessor_read_float(channel.sampler->output, v, &rot.x, 4); + // vectors[v] = rot; + // printf("Quat %f %f %f %f\n", rot.x, rot.y, rot.z, rot.w); + our_track.keyframes[v].rotation = rot; + } + + our_track.min_time = channel.sampler->input->min[0]; + our_track.max_time = channel.sampler->input->max[0]; + + // printf("min time: %f max time %f\n", our_track.min_time, our_track.max_time); + + animation_track_darray_push(&our_animation.tracks, our_track); + } + + out_model->animations[anim_idx] = our_animation; + } + + // Load meshes + TRACE("Num meshes %d", data->meshes_count); + out_model->num_meshes = data->meshes_count; + + for (int m = 0; m < data->meshes_count; m++) { + // at the moment we only handle one primitives per mesh + // CASSERT(data->meshes[m].primitives_count == 1); + + // Load vertex data from FIRST primitive only + cgltf_primitive primitive = data->meshes[m].primitives[0]; + DEBUG("Found %d attributes", primitive.attributes_count); + for (int a = 0; a < data->meshes[m].primitives[0].attributes_count; a++) { + cgltf_attribute attribute = data->meshes[m].primitives[0].attributes[a]; + if (attribute.type == cgltf_attribute_type_position) { + TRACE("Load positions from accessor"); + + cgltf_accessor *accessor = attribute.data; + CASSERT(accessor->component_type == cgltf_component_type_r_32f); + 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); + kitc_darray_push(tmp_positions, &pos); + } + + } else if (attribute.type == cgltf_attribute_type_normal) { + TRACE("Load normals from accessor"); + + cgltf_accessor *accessor = attribute.data; + CASSERT(accessor->component_type == cgltf_component_type_r_32f); + CASSERT_MSG(accessor->type == cgltf_type_vec3, "Normal vectors should be a vec3"); + + for (cgltf_size v = 0; v < accessor->count; ++v) { + vec3 pos; + cgltf_accessor_read_float(accessor, v, &pos.x, 3); + kitc_darray_push(tmp_normals, &pos); + } + + } else if (attribute.type == cgltf_attribute_type_texcoord) { + TRACE("Load texture coordinates from accessor"); + cgltf_accessor *accessor = attribute.data; + 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"); + } + kitc_darray_push(tmp_uvs, &tex); + } + } else if (attribute.type == cgltf_attribute_type_joints) { + // handle joints + + } else { + WARN("Unhandled cgltf_attribute_type: %s. skipping..", attribute.name); + } + } + + // Create mesh + mesh mesh; + mesh.vertices = + kitc_darray_new(sizeof(mesh_vertex), data->meshes[m].primitives[0].attributes_count); + + // Flatten faces from indices if present otherwise push vertices verbatim + cgltf_accessor *indices = primitive.indices; + if (primitive.indices > 0) { + mesh.has_indices = true; + + kitc_darray *element_indexes = kitc_darray_new(sizeof(cgltf_uint), indices->count); + TRACE("Indices count %ld\n", indices->count); + for (cgltf_size i = 0; i < indices->count; ++i) { + cgltf_uint ei; + cgltf_accessor_read_uint(indices, i, &ei, 1); + kitc_darray_push(element_indexes, &ei); + } + + kitc_darray_iter indices_iter = kitc_darray_iter_new(element_indexes); + cgltf_uint *cur; + while ((cur = kitc_darray_iter_next(&indices_iter))) { + mesh_vertex vert; + memcpy(&vert.position, &((vec3 *)tmp_positions->data)[*cur], sizeof(vec3)); + memcpy(&vert.normal, &((vec3 *)tmp_normals->data)[*cur], sizeof(vec3)); + memcpy(&vert.tex_coord, &((vec2 *)tmp_uvs->data)[*cur], sizeof(vec2)); + kitc_darray_push(mesh.vertices, &vert); + // mesh_vertex_debug_print(vert); + } + // printf("indices: %ld, positions: %ld\n", kitc_darray_len(element_indexes), + kitc_darray_free(element_indexes); + } else { + mesh.has_indices = false; + + bool calc_normals = false; + if (kitc_darray_len(tmp_normals) == 0) { + TRACE("No normals data is present. Normals will be calculated for you."); + calc_normals = true; + } + for (int v = 0; v < kitc_darray_len(tmp_positions); v++) { + mesh_vertex vert; + memcpy(&vert.position, &((vec3 *)tmp_positions->data)[v], sizeof(vec3)); + if (!calc_normals) { + memcpy(&vert.normal, &((vec3 *)tmp_normals->data)[v], sizeof(vec3)); + } + memcpy(&vert.tex_coord, &((vec2 *)tmp_uvs->data)[v], sizeof(vec2)); + kitc_darray_push(mesh.vertices, &vert); + } + + if (calc_normals) { + if (mesh.has_indices) { + // generate_normals_nonindexed(mesh.vertices); + } else { + generate_normals_nonindexed(mesh.vertices); + } + } + } + + // Material + if (primitive.material != NULL) { + for (int i = 0; i < bh_material_darray_len(materials); i++) { + if (strcmp(primitive.material->name, cstr(materials->data->name))) { + TRACE("Found material"); + mesh.material_index = i; + break; + } + } + } + + // mesh.material_index = 0; // TODO: make sure DEFAULT_MATERIAL is added at material index 0 + // TODO: material handling + mesh.material_index = bh_material_darray_len(materials) - 1; + + calc_mesh_bounding_box(&mesh); + // out_model->meshes.data[m] = mesh; + mesh_darray_push(&out_model->meshes, mesh); + + kitc_darray_clear(tmp_positions); + kitc_darray_clear(tmp_normals); + kitc_darray_clear(tmp_uvs); + } + // End Load meshes + + // Load animations + DEBUG("Num animations %d", data->animations_count); + out_model->num_animations = data->animations_count; + + // End Load animations + + cgltf_free(data); + } else { + ERROR("Load failed"); + kitc_darray_free(tmp_positions); + kitc_darray_free(tmp_normals); + kitc_darray_free(tmp_uvs); + return false; + } + + for (int i = 0; i < materials->len; i++) { + out_model->materials[i] = materials->data[i]; + } + + calc_model_bounding_box(out_model); + + DEBUG("Num meshes %d", out_model->num_meshes); + DEBUG("Num materials %d", out_model->num_materials); + DEBUG("Num animations %d", out_model->num_animations); + + CASSERT(out_model->num_materials == 1); + + kitc_darray_free(tmp_positions); + kitc_darray_free(tmp_normals); + kitc_darray_free(tmp_uvs); + bh_material_darray_free(materials); + + TRACE("Finished loading GLTF"); + return true; +} +*/
\ No newline at end of file diff --git a/src/resources/loaders.h b/src/resources/loaders.h index ba38ec4..858e4d1 100644 --- a/src/resources/loaders.h +++ b/src/resources/loaders.h @@ -1,9 +1,9 @@ #pragma once #include "defines.h" +#include "render_types.h" struct core; -typedef u32 model_handle; model_handle model_load_obj(struct core *core, const char *path, bool invert_texture_y); -model_handle model_load_gltf(struct core *core, const char *path, bool invert_texture_y);
\ No newline at end of file +model_handle model_load_gltf(struct core *core, const char *path, bool invert_texture_y); diff --git a/src/resources/obj.c b/src/resources/obj.c index b646f58..ea73ffa 100644 --- a/src/resources/obj.c +++ b/src/resources/obj.c @@ -1 +1,389 @@ -// TODO: Port code from old repo
\ No newline at end of file +/** + * @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 "path.h" +#include "render.h" +#include "render_backend.h" +#include "render_types.h" +#include "str.h" + +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); + +model_handle 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); + + model model = { 0 }; + model.name = str8_cstr_view(path); + model.meshes = mesh_darray_new(1); + model.materials = material_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"); + } + + u32 index = model_darray_len(core->models); + model_darray_push(core->models, model); + + arena_free_all(&scratch); + arena_free_storage(&scratch); + return (model_handle){ .raw = index }; +} + +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, + 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, + 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, + 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 + + 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; +} |