#include "assets.h" #include "file_formats/wavefront.h" #include "platform.h" #include #include #include "stb_image.h" #include "stb_vorbis.h" #include #include "debug/logger.h" #include "render/render.h" #include "cgltf.h" Buffer read_file_into_buffer(const char *filename); int wf_face_index_comp(const void *_a, const void *_b) { wf_face_index *a = (wf_face_index *)_a; wf_face_index *b = (wf_face_index *)_b; if(a->vertex < b->vertex) return -1; if(a->vertex > b->vertex) return +1; if(a->texture < b->texture) return -1; if(a->texture > b->texture) return +1; if(a->normal < b->normal) return -1; if(a->normal > b->normal) return +1; return 0; } bool assets_load_object_obj(asset_manager *am, const char *filename, r_object *result) { wf_obj_file obj_file; wf_mtl_file mtl_file; bool has_mtl; char path[512]; // @Buf: Files with paths longer than 512 bytes will fail to load p_file file; u64 size; Buffer content; bool success; // Load obj file strcpy(path, "assets/"); strncat(path, filename, 512 - strlen(path)); p_file_init(&file, path); size = p_file_size(&file); content.data = (u8*)p_alloc(size); content.size = 0; p_file_read(&file, &content, size); p_file_deinit(&file); success = wf_parse_obj((char*)content.data, content.size, filename, &obj_file); p_free(content.data); if(!success) return false; has_mtl = false; // Load mtl file (optional) if(obj_file.mtllib_names_count > 0) { has_mtl = true; char *mtl_name = obj_file.mtllib_names[0]; strcpy(path, "assets/"); strncat(path, mtl_name, 512 - strlen(path)); p_file_init(&file, path); size = p_file_size(&file); content.data = (u8*)p_alloc(size); content.size = 0; p_file_read(&file, &content, size); p_file_deinit(&file); success = wf_parse_mtl((char*)content.data, content.size, mtl_name, &mtl_file); p_free(content.data); if(!success) return false; } // We take only the first object if(obj_file.objects_count == 0) { LOG(LOG_WARNING, "Asset: cannot load model. File does not contain any objects."); return false; } wf_object *object = obj_file.objects; // Setup model r_mesh *res_model = assets_new_models(am, 1); memset(res_model, 0, sizeof(r_mesh)); // Actual conversion { wf_face_index *unique_indices; u32 unique_indices_count; unique_indices_count = object->faces_count * 3; unique_indices = (wf_face_index *)p_alloc(sizeof(wf_face_index) * unique_indices_count); memcpy(unique_indices, object->faces, sizeof(wf_face_index) * unique_indices_count); qsort(unique_indices, unique_indices_count, sizeof(wf_face_index), wf_face_index_comp); u32 removed = 0; for(u32 i = 1; i < unique_indices_count; i++) { wf_face_index *prev = unique_indices + i - 1; wf_face_index *curr = unique_indices + i; if(wf_face_index_comp(prev, curr) == 0) // Remove duplicate removed++; unique_indices[i - removed] = unique_indices[i]; } unique_indices_count -= removed; res_model->vertices_count = unique_indices_count; res_model->vertices = (v3*)p_alloc(sizeof(v3) * unique_indices_count); if(object->texture_coords) res_model->uvs = (v2*)p_alloc(sizeof(v2) * unique_indices_count); else res_model->uvs = NULL; res_model->normals = (v3*)p_alloc(sizeof(v3) * unique_indices_count); for(u32 i = 0; i < unique_indices_count; i++) { wf_index vertex_i = (unique_indices[i].vertex > 0 ? unique_indices[i].vertex - 1 : 0); wf_index texture_coord_i = (unique_indices[i].texture > 0 ? unique_indices[i].texture - 1 : 0); wf_index normal_i = (unique_indices[i].normal > 0 ? unique_indices[i].normal - 1 : 0); res_model->vertices[i] = object->vertices [vertex_i].xyz / object->vertices[vertex_i].w; if(object->texture_coords) res_model->uvs[i] = object->texture_coords[texture_coord_i].uv; res_model->normals[i] = object->normals[normal_i]; } res_model->indices_count = object->faces_count * 3; res_model->indices = (u32*)p_alloc(sizeof(u32) * object->faces_count * 3); for(u32 f = 0; f < object->faces_count; f++) { wf_face *curr_face = object->faces + f; for(u32 fi = 0; fi < 3; fi++) { wf_face_index *curr_index = curr_face->indices + fi; wf_face_index *found_ptr = (wf_face_index *)bsearch(curr_index, unique_indices, unique_indices_count, sizeof(wf_face_index), wf_face_index_comp); res_model->indices[3 * f + fi] = (found_ptr - unique_indices); } } p_free(unique_indices); } // Setup material r_material *res_material = assets_new_materials(am, 1); memset(res_material, 0, sizeof(r_material)); res_material->shader = &r_render_state.shader_pbr; // Load textures if(object->material_name != NULL && has_mtl) { // @Feature: Load multiple materials wf_material *material = NULL; for(wf_size i = 0; i < mtl_file.materials_count; i++) { if(strcmp(object->material_name, mtl_file.materials[i].name) == 0) { material = mtl_file.materials + i; break; } } if(material) { // Load diffuse texture if(material->map_Kd && strcmp(material->map_Kd, "") != 0) { strcpy(path, "assets/"); strncat(path, material->map_Kd, 512 - strlen(path)); stbi_set_flip_vertically_on_load(true); v2s size; s32 channels; u8 *data = stbi_load(path, &size.x, &size.y, &channels, 4); r_texture *texture = assets_new_textures(am, 1); *texture = r_texture_create(data, size, R_TEXTURE_SRGB); res_material->albedo_texture = texture; res_material->albedo_factor = v4{1,1,1,1}; } // Load emissive texture if(material->map_Ke && strcmp(material->map_Ke, "") != 0) { strcpy(path, "assets/"); strncat(path, material->map_Ke, 512 - strlen(path)); stbi_set_flip_vertically_on_load(true); v2s size; s32 channels; u8 *data = stbi_load(path, &size.x, &size.y, &channels, 4); r_texture *texture = assets_new_textures(am, 1); *texture = r_texture_create(data, size, R_TEXTURE_SRGB); res_material->emissive_texture = texture; res_material->emissive_factor = v4{1,1,1,1}; } } } result->meshes = (r_mesh**)p_alloc(sizeof(void*)); result->mesh_material = (r_material**)p_alloc(sizeof(void*)); result->meshes[0] = res_model; result->mesh_material[0] = res_material; result->mesh_local_transform = (m4*)p_alloc(sizeof(m4)); result->mesh_local_transform[0] = m4_identity; result->count = 1; result->scale = v3{1,1,1}; result->position = v3{0,0,0}; result->rotation = v3{0,0,0}; wf_cleanup_obj(&obj_file); if(has_mtl) { wf_cleanup_mtl(&mtl_file); } return true; } struct gltf_loader_state { asset_manager *am; cgltf_data *data; r_texture *texture_buffer; u32 texture_capacity; }; void gltf_loader_reserve_texture_space(gltf_loader_state *state, u32 count) { state->texture_capacity = count; state->texture_buffer = assets_new_textures(state->am, state->texture_capacity); memset(state->texture_buffer, 0, sizeof(r_texture) * state->texture_capacity); } r_texture *gltf_lazy_load_texture(gltf_loader_state *state, u32 texture_index, bool sRGB) { assert(texture_index < state->texture_capacity); if(state->texture_buffer[texture_index].flags & R_TEXTURE_INITIALIZED) { bool is_buffered_sRGB = !!(state->texture_buffer[texture_index].flags & (sRGB ? R_TEXTURE_SRGB : 0)); assert(sRGB == is_buffered_sRGB); return &state->texture_buffer[texture_index]; } cgltf_data *data = state->data; cgltf_buffer_view *t_view = data->images[texture_index].buffer_view; LOG(LOG_DEBUG, "Loading texture %s, sRGB: %d", (t_view->name ? t_view->name : t_view->buffer->name), sRGB); u8 *t_data; s32 t_width, t_height, t_channels; stbi_set_flip_vertically_on_load(false); t_data = stbi_load_from_memory((u8*)t_view->buffer->data + t_view->offset, t_view->size, &t_width, &t_height, &t_channels, 4); u32 flags = (sRGB ? R_TEXTURE_SRGB : 0); state->texture_buffer[texture_index] = r_texture_create(t_data, v2s{t_width, t_height}, flags); return &state->texture_buffer[texture_index]; } void gltf_visit(gltf_loader_state *state, r_object *object, m4 transform, cgltf_node *node) { cgltf_data *data = state->data; // Transform if(node->has_matrix) { transform = transpose(*(m4*)node->matrix) * transform; } else { if(node->has_translation) { v3 t = {node->translation[0], node->translation[1], node->translation[2]}; transform = translation_v3(t) * transform; } if(node->has_rotation) { LOG(LOG_ERROR, "Quaternion rotation not supported yet"); } if(node->has_scale) { v3 s = {node->scale[0], node->scale[1], node->scale[2]}; transform = scale_v3(s) * transform; } } // @Feature: Skin if(node->mesh) { cgltf_mesh *mesh = node->mesh; for(cgltf_size p = 0; p < mesh->primitives_count; p++) { cgltf_primitive *primitive = &mesh->primitives[p]; if(primitive->type == cgltf_primitive_type_triangles) { // Mesh v3 *vertices = NULL; v3 *normals = NULL; v3 *tangents = NULL; v2 *uvs = NULL; u64 vertices_count = 0; u32 *indices = NULL; u64 indices_count = 0; if(primitive->indices->type != cgltf_type_scalar) { (LOG_ERROR, "glTF: Unhandled indices type %d", primitive->indices->type); continue; } cgltf_accessor *a = primitive->indices; indices = (u32*)p_alloc(a->count * sizeof(u32)); indices_count = a->count; cgltf_accessor_unpack_indices(a, indices, a->count); vertices_count = primitive->attributes[0].data->count; for(cgltf_size i = 0; i < primitive->attributes_count; i++) { if(primitive->attributes[i].type == cgltf_attribute_type_position) { cgltf_accessor *a = primitive->attributes[i].data; vertices = (v3*)p_alloc(a->count * sizeof(v3)); if(vertices_count != a->count) { LOG(LOG_ERROR, "glTF: vertex count (%d) != previously stored vertex count (%d)", a->count, vertices_count); continue; } cgltf_accessor_unpack_floats(a, (f32*)vertices, a->count * 3); } else if(primitive->attributes[i].type == cgltf_attribute_type_normal) { cgltf_accessor *a = primitive->attributes[i].data; normals = (v3*)p_alloc(a->count * sizeof(v3)); if(vertices_count != a->count) { LOG(LOG_ERROR, "glTF: normal count (%d) != vertex count (%d)", a->count, vertices_count); continue; } cgltf_accessor_unpack_floats(a, (f32*)normals, a->count * 3); } else if(primitive->attributes[i].type == cgltf_attribute_type_tangent) { cgltf_accessor *a = primitive->attributes[i].data; tangents = (v3*)p_alloc(a->count * sizeof(v3)); v4 *tangents_tmp = (v4*)p_alloc(a->count * sizeof(v4)); if(vertices_count != a->count) { LOG(LOG_ERROR, "glTF: tangent count (%d) != vertex count (%d)", a->count, vertices_count); continue; } cgltf_accessor_unpack_floats(a, (f32*)tangents_tmp, a->count * 4); for(u32 i = 0; i < a->count; i++) tangents[i] = tangents_tmp[i].xyz; p_free(tangents_tmp); } else if(primitive->attributes[i].type == cgltf_attribute_type_texcoord) { cgltf_accessor *a = primitive->attributes[i].data; uvs = (v2*)p_alloc(a->count * sizeof(v2)); if(vertices_count != a->count) { LOG(LOG_ERROR, "glTF: uv count (%d) != vertex count (%d)", a->count, vertices_count); continue; } cgltf_accessor_unpack_floats(a, (f32*)uvs, a->count * 2); } else { //LOG(LOG_WARNING, "Unmanaged attribute type: %d", primitive->attributes[i].type); } } r_mesh *mesh = (r_mesh*)p_alloc(sizeof(r_mesh)); *mesh = r_mesh_create(indices_count, indices, vertices_count, vertices, normals, tangents, uvs); // @Feature: Calculate tangents if missing // Material r_material *material = assets_new_materials(state->am, 1); memset(material, 0, sizeof(r_material)); material->shader = &r_render_state.shader_pbr; // Find image index u32 albedo_texture_index = primitive->material->pbr_metallic_roughness.base_color_texture.texture->image - data->images; if(albedo_texture_index > data->images_count) LOG(LOG_ERROR, "Albedo image index (%u) out of bounds (%u)", albedo_texture_index, data->images_count); material->albedo_texture = gltf_lazy_load_texture(state, albedo_texture_index, true); material->albedo_factor = V4(primitive->material->pbr_metallic_roughness.base_color_factor); // @Cleanup: @Performance: Merge metallic and roughness texture or split them up? u32 metallic_texture_index = primitive->material->pbr_metallic_roughness.metallic_roughness_texture.texture->image - data->images; if(metallic_texture_index > data->images_count) LOG(LOG_ERROR, "Metallic image index (%u) out of bounds (%u)", metallic_texture_index, data->images_count); material->metallic_texture = gltf_lazy_load_texture(state, metallic_texture_index, false); material->metallic_factor = primitive->material->pbr_metallic_roughness.metallic_factor; u32 roughness_texture_index = primitive->material->pbr_metallic_roughness.metallic_roughness_texture.texture->image - data->images; if(roughness_texture_index > data->images_count) LOG(LOG_ERROR, "Roughness image index (%u) out of bounds (%u)", roughness_texture_index, data->images_count); material->roughness_texture = gltf_lazy_load_texture(state, roughness_texture_index, false); material->roughness_factor = primitive->material->pbr_metallic_roughness.roughness_factor; if(primitive->material->normal_texture.texture) { u32 normal_texture_index = primitive->material->normal_texture.texture->image - data->images; if(normal_texture_index > data->images_count) LOG(LOG_ERROR, "Normal image index (%u) out of bounds (%u)", normal_texture_index, data->images_count); material->normal_texture = gltf_lazy_load_texture(state, normal_texture_index, false); } if(primitive->material->emissive_texture.texture) { u32 emissive_texture_index = primitive->material->emissive_texture.texture->image - data->images; if(emissive_texture_index > data->images_count) LOG(LOG_ERROR, "Emission image index (%u) out of bounds (%u)", emissive_texture_index, data->images_count); material->emissive_texture = gltf_lazy_load_texture(state, emissive_texture_index, true); f32 strength = primitive->material->has_emissive_strength ? primitive->material->has_emissive_strength : 1.0; material->emissive_factor = V4(V3(primitive->material->emissive_factor) * strength, 1.0); } object->meshes = (r_mesh**)p_realloc(object->meshes, (object->count + 1) * sizeof(r_mesh)); object->mesh_material = (r_material**)p_realloc(object->mesh_material, (object->count + 1) * sizeof(r_material)); object->mesh_local_transform = (m4*)p_realloc(object->mesh_local_transform, (object->count + 1) * sizeof(m4)); object->meshes [object->count] = mesh; object->mesh_material[object->count] = material; object->mesh_local_transform[object->count] = transform; object->count++; } else { LOG(LOG_ERROR, "glTF: Unhandled primitive type %d", primitive->type); } } } for(cgltf_size i = 0; i < node->children_count; i++) { gltf_visit(state, object, transform, node->children[i]); } } bool assets_load_object_gltf(asset_manager *am, const char *filename, r_object *result) { gltf_loader_state state; state.am = am; cgltf_options options = {}; cgltf_data *data = NULL; cgltf_result status = cgltf_parse_file(&options, filename, &data); if(status != cgltf_result_success) { LOG(LOG_DEBUG, "Error parsing glTF file \"%s\".", filename); return false; } status = cgltf_load_buffers(&options, data, filename); if(status != cgltf_result_success) { LOG(LOG_DEBUG, "Erorr loading glTF buffers from file \"%s\".", filename); cgltf_free(data); return false; } state.data = data; // Reserve texture space gltf_loader_reserve_texture_space(&state, data->images_count); // Prepare object r_object object; memset(&object, 0, sizeof(r_object)); object.scale = v3{1,1,1}; object.position = v3{0,0,0}; object.rotation = v3{0,0,0}; object.has_shadow = true; for(cgltf_size i = 0; i < data->scene->nodes_count; i++) { gltf_visit(&state, &object, m4_identity, data->scene->nodes[i]); } // Resize unused space *result = object; cgltf_free(data); return true; } bool assets_load_audio(asset_manager *am, const char *name, p_audio_buffer *result) { s32 music_channels; s32 music_sample_rate; s16 *music_data; s32 music_samples = stb_vorbis_decode_filename(name, &music_channels, &music_sample_rate, &music_data); result->samples = (p_audio_sample*) p_alloc(sizeof(p_audio_sample) * music_samples); for(u32 i = 0; i < music_samples; i++) { result->samples[i].left = (f32)music_data[2*i] / -SHRT_MIN; // Left result->samples[i].right = (f32)music_data[2*i + 1] / -SHRT_MIN; // Right } result->size = music_samples; free(music_data); return true; } bool assets_load_cubemap(asset_manager *am, const char *px, const char *nx, const char *py, const char *ny, const char *pz, const char *nz, r_cubemap *result) { stbi_set_flip_vertically_on_load(false); //stbi_ldr_to_hdr_scale(1.0f); //stbi_ldr_to_hdr_gamma(2.2f); float *data[6]; v2s size; s32 channels; v2s s; data[0] = stbi_loadf(px, &s.x, &s.y, &channels, 3); size = s; data[1] = stbi_loadf(nx, &s.x, &s.y, &channels, 3); assert(size == s); data[2] = stbi_loadf(py, &s.x, &s.y, &channels, 3); assert(size == s); data[3] = stbi_loadf(ny, &s.x, &s.y, &channels, 3); assert(size == s); data[4] = stbi_loadf(pz, &s.x, &s.y, &channels, 3); assert(size == s); data[5] = stbi_loadf(nz, &s.x, &s.y, &channels, 3); assert(size == s); r_cubemap cubemap = r_cubemap_create(data, size, 0); if(result) *result = cubemap; return true; } void assets_init(asset_manager *am) { am->shaders = NULL; am->shaders_count = 0; am->models = NULL; am->models_count = 0; am->textures = NULL; am->textures_count = 0; am->materials = NULL; am->materials_count = 0; am->objects = NULL; am->objects_count = 0; am->allocations = NULL; am->allocations_count = 0; } void assets_free(asset_manager *am) { } static void assets_add_allocation(asset_manager *am, void *data) { u32 old_count = am->allocations_count; am->allocations_count++; am->allocations = (void**)p_realloc(am->allocations, am->allocations_count * sizeof(void*)); am->allocations[old_count] = data; } r_shader *assets_new_shaders(asset_manager *am, u32 count) { u32 old_count = am->shaders_count; am->shaders_count = old_count + count; r_shader *data = (r_shader*) p_alloc(count * sizeof(r_shader)); assets_add_allocation(am, data); am->shaders = (r_shader**) p_realloc(am->shaders, am->shaders_count * sizeof(r_shader*)); for(u32 i = 0; i < count; i++) am->shaders[old_count + i] = data + i; return data; } r_mesh *assets_new_models(asset_manager *am, u32 count) { u32 old_count = am->models_count; am->models_count = old_count + count; r_mesh *data = (r_mesh*) p_alloc(count * sizeof(r_mesh)); assets_add_allocation(am, data); am->models = (r_mesh**) p_realloc(am->models, am->models_count * sizeof(r_mesh*)); for(u32 i = 0; i < count; i++) am->models[old_count + i] = data + i; return data; } r_texture *assets_new_textures(asset_manager *am, u32 count) { u32 old_count = am->textures_count; am->textures_count = old_count + count; r_texture *data = (r_texture*) p_alloc(count * sizeof(r_texture)); assets_add_allocation(am, data); am->textures = (r_texture**) p_realloc(am->textures, am->textures_count * sizeof(r_texture*)); for(u32 i = 0; i < count; i++) am->textures[old_count + i] = data + i; return data; } r_material *assets_new_materials(asset_manager *am, u32 count) { u32 old_count = am->materials_count; am->materials_count = old_count + count; r_material *data = (r_material*) p_alloc(count * sizeof(r_material)); assets_add_allocation(am, data); am->materials = (r_material**) p_realloc(am->materials, am->materials_count * sizeof(r_material*)); for(u32 i = 0; i < count; i++) am->materials[old_count + i] = data + i; return data; } r_object *assets_new_objects(asset_manager *am, u32 count) { u32 old_count = am->objects_count; am->objects_count = old_count + count; r_object *data = (r_object*) p_alloc(count * sizeof(r_object)); assets_add_allocation(am, data); am->objects = (r_object**) p_realloc(am->objects, am->objects_count * sizeof(r_object*)); for(u32 i = 0; i < count; i++) am->objects[old_count + i] = data + i; return data; } Buffer read_file_into_buffer(const char *filename) { Buffer empty = { .size = 0, .data = NULL }; Buffer result; p_file file; bool success; success = p_file_init(&file, filename); if(success) { result.size = p_file_size(&file); result.data = (u8*)p_alloc(result.size); success = p_file_read(&file, &result, result.size); p_file_deinit(&file); } if(!success) return empty; return result; }