#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) { for(size_t i = 0; i < size; ++i) { char c = str[i]; if(c == '\n' || c == '\r') return { str, i }; } return { str, size }; } 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 FaceData { int vertex_index; int texcoord_index; int normal_index; }; // TODO: Optimize? dont use sscanf 3 times static bool split_faces(StringView *input, FaceData *face_data) { input->data[input->size] = '\0'; face_data->vertex_index = 0; face_data->texcoord_index = 0; face_data->normal_index = 0; if(sscanf(input->data, "%d/%d/%d", &face_data->vertex_index, &face_data->texcoord_index, &face_data->normal_index) == 3) return true; face_data->texcoord_index = 0; if(sscanf(input->data, "%d//%d", &face_data->vertex_index, &face_data->normal_index) == 2) return true; return sscanf(input->data, "%d/%d", &face_data->vertex_index, &face_data->texcoord_index) == 2; } struct Material { Image *image; }; static Result load_material_from_file(const char *filepath); bool 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 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); 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; // TODO: Handle these when they are not set in the below loop int vertex_indices[4]; int texture_coord_indices[4]; int normal_indices[4]; // TODO: Use this for(int i = 0; i < num_columns; ++i) { FaceData face_data; if(!split_faces(&columns[i], &face_data)) { valid = false; abort(); break; } vertex_indices[i] = face_data.vertex_index; texture_coord_indices[i] = face_data.texcoord_index; normal_indices[i] = face_data.normal_index; if(vertex_indices[i] < 1 || vertex_indices[i] > vertices.size()) { valid = false; abort(); break; } if(texture_coord_indices[i] != 0 && (texture_coord_indices[i] < 1 || texture_coord_indices[i] > temp_texture_coords.size())) { valid = false; abort(); break; } --vertex_indices[i]; --texture_coord_indices[i]; --normal_indices[i]; } if(valid) { if(num_columns == 3) { // Triangle triangles.push_back({ vertices[vertex_indices[0]], vertices[vertex_indices[1]], vertices[vertex_indices[2]] }); if(texture_coord_indices[0] >= 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]]); } } 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]] }); if(texture_coord_indices[0] >= 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 = dir_path + '/'; material_path.append(columns[0].data, columns[0].size); printf("mtl file: %s\n", material_path.c_str()); assert(!*image); Result material = load_material_from_file(material_path.c_str()); if(!material) { fprintf(stderr, "Warning: %s\n", material.getErrorMsg().c_str()); } else { *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); return true; } 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 }); } }