From 12c36c61c3f8d19c44cb2e5ffdf3ed812a0390d2 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 29 Feb 2020 03:05:36 +0100 Subject: Implement .a3d model loader (amalgine specific format) --- src/ModelLoader.cpp | 230 ++++++++++++++++++++++++++++++++++++ src/main.cpp | 9 +- src/model_loader/ObjModelLoader.cpp | 5 +- 3 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 src/ModelLoader.cpp (limited to 'src') diff --git a/src/ModelLoader.cpp b/src/ModelLoader.cpp new file mode 100644 index 0000000..60626f1 --- /dev/null +++ b/src/ModelLoader.cpp @@ -0,0 +1,230 @@ +#include "../include/ModelLoader.hpp" +#include "../include/File.hpp" +#include "../include/Image.hpp" +#include +#include +#include +#include +#include +#include + +namespace amalgine { + class ModelLoaderInternal { + public: + ModelLoaderInternal(const char *data, size_t size) : data(data), size(size), offset(0) {} + + template + bool extract(T *result) { + if(size - offset >= sizeof(T)) { + // This is fine, because all data in .a3d files are aligned correctly. + // However this could be incorrect if the file is not an .a3d file, in which case this would crash on ARM processors for example.. + // TODO: Use memcpy on ARM (and other platforms that dont support unaligned memory access) + *result = *(T*)(data + offset); + offset += sizeof(T); + return true; + } else { + return false; + } + } + + template + bool extract_list(T *result, size_t num_items) { + const size_t bytes_to_extract = sizeof(T) * num_items; + if(size - offset >= bytes_to_extract) { + memcpy(result, data + offset, bytes_to_extract); + offset += bytes_to_extract; + return true; + } else { + return false; + } + } + + std::string extract_null_terminated_string() { + std::string result; + const char *p = data + offset; + while(true) { + char c = *p++; + if(c == '\0') + break; + else + result += c; + } + offset += p - (data + offset); + return result; + } + + bool skip(size_t num_bytes) { + if(size - offset >= num_bytes) { + offset += num_bytes; + return true; + } else { + return false; + } + } + + bool empty() const { return offset == size; } + private: + const char *data; + const size_t size; + size_t offset; + }; + + struct Face { + unsigned int index[3]; + }; + + // TODO: Check bounds of indices and size of data before allocating + static bool model_loader_version_1(ModelLoaderInternal &model_loader, std::vector &triangles, std::vector &texture_coords, Image **image, const std::string &dir_path) { + // TODO: Remove these temporary variables when amalgine has been modified to use indices instead of + // data directly in shaders + std::vector temp_vertices; + std::vector temp_texcoords; + std::vector indices; + while(!model_loader.empty()) { + // TODO: Verify num_vertices isn't too large? + unsigned int num_vertices; + if(!model_loader.extract(&num_vertices) || num_vertices == 0) { + fprintf(stderr, "Error: failed to extract num_vertices\n"); + return false; + } + + unsigned int num_triangles = num_vertices / 3; + temp_vertices.resize(num_triangles); + if(!model_loader.extract_list(&temp_vertices[0], num_triangles)) { + fprintf(stderr, "Error: failed to extract vertices\n"); + return false; + } + + if(!model_loader.skip(num_vertices * sizeof(vec3f))) { + fprintf(stderr, "Error: failed to extract normals\n"); + return false; + } + + unsigned int has_texcoords; + if(!model_loader.extract(&has_texcoords) || has_texcoords > 1) { + fprintf(stderr, "Error: failed to extract has_texcoords\n"); + return false; + } + + if(has_texcoords) { + temp_texcoords.resize(num_vertices); + if(!model_loader.extract_list(&temp_texcoords[0], num_vertices)) { + fprintf(stderr, "Error: failed to extract texture coordinates\n"); + return false; + } + } + + unsigned int num_faces; + if(!model_loader.extract(&num_faces)) { + fprintf(stderr, "Error: failed to extract num_faces\n"); + return false; + } + + indices.resize(num_faces); + if(!model_loader.extract_list(&indices[0], num_faces)) { + fprintf(stderr, "Error: failed to extract face indices\n"); + return false; + } + + unsigned int triangles_offset = triangles.size(); + triangles.resize(triangles.size() + indices.size()); + const Vertex3D *temp_vertices_begin = &temp_vertices[0].p1; + unsigned int max_vertex_index = (unsigned int)(temp_vertices.size() - 1) * 3; + for(unsigned int i = 0; i < indices.size(); ++i) { + triangles[triangles_offset + i].p1 = temp_vertices_begin[std::min(indices[i].index[0], max_vertex_index)]; + triangles[triangles_offset + i].p2 = temp_vertices_begin[std::min(indices[i].index[1], max_vertex_index)]; + triangles[triangles_offset + i].p3 = temp_vertices_begin[std::min(indices[i].index[2], max_vertex_index)]; + } + + unsigned int texcoords_offset = texture_coords.size(); + texture_coords.resize(texture_coords.size() + indices.size() * 3); + if(has_texcoords) { + unsigned int max_texcoord_index = (unsigned int)temp_texcoords.size() - 1; + for(unsigned int i = 0; i < indices.size(); ++i) { + texture_coords[texcoords_offset + 0] = temp_texcoords[std::min(indices[i].index[0], max_texcoord_index)]; + texture_coords[texcoords_offset + 1] = temp_texcoords[std::min(indices[i].index[1], max_texcoord_index)]; + texture_coords[texcoords_offset + 2] = temp_texcoords[std::min(indices[i].index[2], max_texcoord_index)]; + texcoords_offset += 3; + } + } else { + for(unsigned int i = 0; i < indices.size(); ++i) { + // TODO: is this needed even with the resize above? or use memset? + texture_coords[texcoords_offset + i + 0] = { 0.0f, 0.0f }; + texture_coords[texcoords_offset + i + 1] = { 0.0f, 0.0f }; + texture_coords[texcoords_offset + i + 2] = { 0.0f, 0.0f }; + } + } + + unsigned int has_materials; + if(!model_loader.extract(&has_materials) || has_materials > 1) { + fprintf(stderr, "Error: failed to extract has_materials\n"); + return false; + } + + if(has_materials) { + for(unsigned int i = 0; i < 3; ++i) { + unsigned int num_textures; + if(!model_loader.extract(&num_textures)) { + fprintf(stderr, "Error: failed to extract num_textures\n"); + return false; + } + + for(unsigned int j = 0; j < num_textures; ++j) { + std::string texture_name = model_loader.extract_null_terminated_string(); + fprintf(stderr, "texture name: %s\n", texture_name.c_str()); + std::string texture_path = dir_path + "/" + texture_name; + assert(!*image); // TODO: Support multiple images + Result image_result = Image::loadFromFile(texture_path.c_str()); + if(!image_result) { + fprintf(stderr, "%s\n", image_result.getErrorMsg().c_str()); + return false; + } + *image = image_result.unwrap(); + } + } + } + } + return true; + } + + // TODO: Instead of reading all file data to a buffer and then copying it, maybe read from file + // directly into data structures? + bool ModelLoader::load_from_file(const char *filepath, std::vector &triangles, std::vector &texture_coords, Image **image) { + triangles.clear(); + texture_coords.clear(); + *image = nullptr; + bool result = true; + + size_t file_size; + char *file_data = file_get_content(filepath, &file_size); + if(!file_data) + return false; + + std::string dir_path = filepath; + dirname(&dir_path[0]); + const int dir_path_size = strlen(dir_path.c_str()); + dir_path.resize(dir_path_size); + + ModelLoaderInternal model_loader(file_data, file_size); + + unsigned int magic_number; + unsigned int version = 0; + + if(!model_loader.extract(&magic_number) || magic_number != 0x036144AF) { + fprintf(stderr, "Error: file %s is not an .a3d file (magic number not found)\n", filepath); + result = false; + goto cleanup; + } + + if(model_loader.extract(&version) && version == 1) { + result = model_loader_version_1(model_loader, triangles, texture_coords, image, dir_path); + } else { + fprintf(stderr, "Error: version not supported: %u\n", version); + result = false; + } + + cleanup: + free(file_data); + return result; + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f2b2c3f..7cefb17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "../include/ThirdPersonCamera.hpp" #include "../include/model_loader/ObjModelLoader.hpp" +#include "../include/ModelLoader.hpp" #include #include @@ -96,7 +97,7 @@ int main() { std::unique_ptr vertex_shader = load_shader_from_file("shaders/vertex.vert", Shader::Type::VERTEX); std::unique_ptr pixel_shader = load_shader_from_file("shaders/fragment.frag", Shader::Type::PIXEL); std::unique_ptr shader_program = build_shader_program_from_shaders({ vertex_shader.get(), pixel_shader.get() }); - Model buddha_model = load_model("/home/dec05eba/Downloads/ELI4OS.obj", shader_program.get()); + Model buddha_model = load_model("/home/dec05eba/Downloads/ELI4OS.a3d", shader_program.get()); buddha_model.frame.get_uniform_by_name("proj")->set(proj); Result view_uniform = buddha_model.frame.get_uniform_by_name("view"); Result model_uniform = buddha_model.frame.get_uniform_by_name("model"); @@ -104,7 +105,7 @@ int main() { std::unique_ptr vertex_no_texture_shader = load_shader_from_file("shaders/vertex_no_texture.vert", Shader::Type::VERTEX); std::unique_ptr pixel_no_texture_shader = load_shader_from_file("shaders/fragment_no_texture.frag", Shader::Type::PIXEL); std::unique_ptr no_texture_shader_program = build_shader_program_from_shaders({ vertex_no_texture_shader.get(), pixel_no_texture_shader.get() }); - Model character_model = load_model("/home/dec05eba/Downloads/FinalBaseMesh.obj", no_texture_shader_program.get()); + Model character_model = load_model("/home/dec05eba/Downloads/FinalBaseMesh.a3d", no_texture_shader_program.get()); character_model.frame.get_uniform_by_name("proj")->set(proj); Result char_view_uniform = character_model.frame.get_uniform_by_name("view"); Result char_model_uniform = character_model.frame.get_uniform_by_name("model"); @@ -260,7 +261,9 @@ Model load_model(const char *filepath, ShaderProgram *shader_program) { std::vector triangles; std::vector texture_coords; Image *image; - ObjModelLoader::load_from_file(filepath, triangles, texture_coords, &image); + if(!ModelLoader::load_from_file(filepath, triangles, texture_coords, &image)) { + fprintf(stderr, "Error: failed to load model from file: %s\n", filepath); + } // TODO: This needs to be done to prevent crash in glDrawArrays, but this should not be used when shader doesn't handle any texture //if(texture_coords.size() < triangles.size()) // texture_coords.resize(triangles.size()); diff --git a/src/model_loader/ObjModelLoader.cpp b/src/model_loader/ObjModelLoader.cpp index 7976bc9..7c4ee5c 100644 --- a/src/model_loader/ObjModelLoader.cpp +++ b/src/model_loader/ObjModelLoader.cpp @@ -101,7 +101,7 @@ namespace amalgine { static Result load_material_from_file(const char *filepath); - void ObjModelLoader::load_from_file(const char *filepath, std::vector &triangles, std::vector &texture_coords, Image **image) { + bool ObjModelLoader::load_from_file(const char *filepath, std::vector &triangles, std::vector &texture_coords, Image **image) { triangles.clear(); texture_coords.clear(); *image = nullptr; @@ -109,7 +109,7 @@ namespace amalgine { size_t file_size; char *file_data = file_get_content(filepath, &file_size); if(!file_data) - return; + return false; std::string dir_path = filepath; dirname(&dir_path[0]); @@ -238,6 +238,7 @@ namespace amalgine { cleanup: free(file_data); + return true; } Result load_material_from_file(const char *filepath) { -- cgit v1.2.3