Files
Server_Monitor/code/assets.cpp
2023-09-26 19:40:16 +02:00

706 lines
21 KiB
C++

#include "assets.h"
#include "file_formats/wavefront.h"
#include "platform.h"
#include <stdlib.h>
#include <string.h>
#include "stb_image.h"
#include "stb_vorbis.h"
#include <limits.h>
#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;
}