#include "../../include/model_loader/ObjModelLoader.hpp" #include "../../include/File.hpp" #include "../../include/DataView.hpp" #include "../../include/Image.hpp" #include #include #include #include #include #include namespace amalgine { static StringView get_line(char *str, size_t size) { char *found_ptr = (char*)memchr(str, '\n', size); if(!found_ptr) found_ptr = str + size; return { str, static_cast(found_ptr - str) }; } static bool is_whitespace(char c) { switch(c) { case ' ': case '\t': case '\n': case '\r': case '\v': return true; default: return false; } } static bool contains_non_whitespace(const char *str, size_t size) { for(size_t i = 0; i < size; ++i) { if(!is_whitespace(str[i])) return true; } return false; } // Return the number of columns, limited by @output_size static int split_columns(const StringView &str, char split_char, StringView *output, int output_size) { char *data = str.data; ssize_t size = str.size; int column = 0; while(size > 0) { char *found_ptr = (char*)memchr(data, split_char, size); if(!found_ptr) found_ptr = data + size; size_t column_size = static_cast(found_ptr - data); if(column_size > 0 && contains_non_whitespace(data, column_size)) { output[column++] = { data, column_size }; if(column == output_size) break; } data = found_ptr + 1; size -= (column_size + 1); } return column; } static size_t find_or_end(const StringView &str, char c) { char *found_ptr = (char*)memchr(str.data, c, str.size); if(!found_ptr) found_ptr = str.data + str.size; return static_cast(found_ptr - str.data); } struct Material { Image *image; }; 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) { triangles.clear(); texture_coords.clear(); *image = nullptr; size_t file_size; char *file_data = file_get_content(filepath, &file_size); if(!file_data) return; 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); const int max_columns = 8; StringView columns[max_columns]; std::vector vertices; std::vector temp_texture_coords; char *data = file_data; ssize_t size = file_size; while(size > 0) { StringView line_data = get_line(data, size); if(line_data.size > 2 && memcmp(line_data.data, "v ", 2) == 0) { //printf("line: |%.*s|\n", line_data.size, line_data.data); StringView column_data = { line_data.data + 2, line_data.size - 2 }; int num_columns = split_columns(column_data, ' ', columns, max_columns); if(num_columns >= 3) { columns[0].data[columns[0].size] = '\0'; columns[1].data[columns[1].size] = '\0'; columns[2].data[columns[2].size] = '\0'; vertices.push_back({ atof(columns[0].data), atof(columns[1].data), atof(columns[2].data) }); } } else if(line_data.size > 2 && memcmp(line_data.data, "f ", 2) == 0) { //printf("line: |%.*s|\n", line_data.size, line_data.data); StringView column_data = { line_data.data + 2, line_data.size - 2 }; int num_columns = split_columns(column_data, ' ', columns, max_columns); if(num_columns == 3 || num_columns == 4) { bool valid = true; int vertex_indices[4]; int texture_coord_indices[4]; for(int i = 0; i < num_columns; ++i) { const int max_attributes = 3; StringView attributes[max_attributes]; int num_attributes = split_columns(columns[i], '/', attributes, max_attributes); assert(num_attributes == 3); attributes[0].data[attributes[0].size] = '\0'; attributes[1].data[attributes[1].size] = '\0'; attributes[2].data[attributes[2].size] = '\0'; vertex_indices[i] = atoi(attributes[0].data); texture_coord_indices[i] = atoi(attributes[1].data); if(vertex_indices[i] < 1 || vertex_indices[i] > vertices.size()) { valid = false; abort(); break; } if(texture_coord_indices[i] < 1 || texture_coord_indices[i] > temp_texture_coords.size()) { valid = false; abort(); break; } --vertex_indices[i]; --texture_coord_indices[i]; } if(valid) { if(num_columns == 3) { // Triangle triangles.push_back({ vertices[vertex_indices[0]], vertices[vertex_indices[1]], vertices[vertex_indices[2]] }); texture_coords.push_back(temp_texture_coords[texture_coord_indices[0]]); texture_coords.push_back(temp_texture_coords[texture_coord_indices[1]]); texture_coords.push_back(temp_texture_coords[texture_coord_indices[2]]); } else if(num_columns == 4) { // Quad, convert to triangle. TODO: Support rendering quads instead of converting to triangles triangles.push_back({ vertices[vertex_indices[0]], vertices[vertex_indices[1]], vertices[vertex_indices[2]] }); triangles.push_back({ vertices[vertex_indices[2]], vertices[vertex_indices[3]], vertices[vertex_indices[0]] }); texture_coords.push_back(temp_texture_coords[texture_coord_indices[0]]); texture_coords.push_back(temp_texture_coords[texture_coord_indices[1]]); texture_coords.push_back(temp_texture_coords[texture_coord_indices[2]]); texture_coords.push_back(temp_texture_coords[texture_coord_indices[2]]); texture_coords.push_back(temp_texture_coords[texture_coord_indices[3]]); texture_coords.push_back(temp_texture_coords[texture_coord_indices[0]]); } } } } else if(line_data.size > 3 && memcmp(line_data.data, "vt ", 3) == 0) { StringView column_data = { line_data.data + 3, line_data.size - 3 }; int num_columns = split_columns(column_data, ' ', columns, max_columns); if(num_columns == 2) { columns[0].data[columns[0].size] = '\0'; columns[1].data[columns[1].size] = '\0'; temp_texture_coords.push_back({ atof(columns[0].data), 1.0f - atof(columns[1].data) }); } } else if(line_data.size > 7 && memcmp(line_data.data, "mtllib ", 7) == 0) { StringView column_data = { line_data.data + 7, line_data.size - 7 }; int num_columns = split_columns(column_data, ' ', columns, max_columns); if(num_columns == 1) { std::string material_path; material_path.resize(dir_path_size + 1 + columns[0].size); memcpy(&material_path[0], dir_path.data(), dir_path_size); material_path[dir_path_size] = '/'; memcpy(&material_path[dir_path_size + 1], columns[0].data, columns[0].size); printf("mtl file: %.*s\n", material_path.size(), material_path.data()); assert(!*image); Result material = load_material_from_file(material_path.c_str()); if(!material) { fprintf(stderr, "Error: %s\n", material.getErrorMsg().c_str()); abort(); goto cleanup; } *image = material->image; } } data += (line_data.size + 1); size -= (line_data.size + 1); } printf("num tex coords: %d\n", texture_coords.size()); cleanup: free(file_data); } Result load_material_from_file(const char *filepath) { size_t file_size; char *file_data = file_get_content(filepath, &file_size); if(!file_data) return Result::Err("Failed to load material: " + std::string(filepath)); 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); Image *image = nullptr; char *data = file_data; ssize_t size = file_size; while(size > 0) { StringView line_data = get_line(data, size); if(line_data.size > 7 && memcmp(line_data.data, "map_Kd ", 7) == 0) { StringView texture_file = { line_data.data + 7, line_data.size - 7 }; line_data.data[line_data.size] = '\0'; std::string texture_path; texture_path.resize(dir_path_size + 1 + texture_file.size); memcpy(&texture_path[0], dir_path.data(), dir_path_size); texture_path[dir_path_size] = '/'; memcpy(&texture_path[dir_path_size + 1], texture_file.data, texture_file.size); printf("texture file: |%.*s|\n", texture_path.size(), texture_path.data()); Result image_result = Image::loadFromFile(texture_path.c_str()); if(!image_result) return Result::Err(image_result.getErrorMsg(), image_result.getErrorCode()); image = image_result.unwrap(); break; } data += (line_data.size + 1); size -= (line_data.size + 1); } free(file_data); return Result::Ok({ image }); } }