#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(); if(texture_name.empty()) continue; fprintf(stderr, "texture name: %s\n", texture_name.c_str()); std::string texture_path = dir_path + "/" + texture_name; //assert(!*image); // TODO: Support multiple images if(*image) continue; Result image_result = Image::loadFromFile(texture_path.c_str()); if(!image_result) { fprintf(stderr, "Warning: %s\n", image_result.getErrorMsg().c_str()); continue; } *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; } }