#include "obj_parser.hpp" #include #include #include #include #include #include #include #include #include "math/vector.hpp" #include "math/utils.hpp" #include "o3d/mesh.hpp" namespace engine { using math::Vector2, math::Vector3; template static void split(const std::string_view& s, char sep, CallbackFn fn) { size_t n = 0; std::string_view::size_type last_ind = 0; for (std::string_view::size_type ind = 0; ind < s.length(); ind++) { if (s[ind] == sep) { if (!fn(n, s.substr(last_ind, ind - last_ind))) return; n++; last_ind = ind + 1; } } fn(n, s.substr(last_ind)); } template static std::optional parse_num(const std::string_view& s); template<> std::optional parse_num(const std::string_view& s) { std::string::size_type ind = 0; bool positive = true; while (ind < s.length() && (s[ind] == '+' || s[ind] == '-')) if (s[ind++] == '-') positive = !positive; if (ind == s.length()) return {}; int n = 0; while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9') n = n * 10 + static_cast(s[ind++]) - static_cast('0'); if (ind == s.length()) return (positive ? 1.f : -1.f) * static_cast(n); if (s[ind] != '.') return {}; ind++; float decimal_part = 0.f; float decimal_fac = .1f; while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9') { decimal_part += decimal_fac * static_cast(static_cast(s[ind++]) - static_cast('0')); decimal_fac *= .1f; } if (ind != s.length()) return {}; return (positive ? 1.f : -1.f) * (static_cast(n) + decimal_part); } template<> std::optional parse_num(const std::string_view& s) { std::string::size_type ind = 0; bool positive = true; while (ind < s.length() && (s[ind] == '+' || s[ind] == '-')) if (s[ind++] == '-') positive = !positive; if (ind == s.length()) return {}; int n = 0; while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9') n = n * 10 + static_cast(s[ind++]) - static_cast('0'); if (ind != s.length()) return {}; return (positive ? 1.f : -1.f) * n; } template static constexpr std::optional split_to_vec(const std::string_view& s, char sep) { std::array coords; bool err = false; split(s, sep, [&](auto n, const auto& coord_s) { auto coord = parse_num(coord_s); if (!coord) { err = true; return false; } coords[n] = *coord; return true; }); if (err) return {}; return engine::math::utils::array_to_vec(coords); } // TODO: improve this. This is a workaround to have a unwrap-like function, but we should check more // precisely the error template [[noreturn]] static std::optional err_exit() { std::cerr << "error: failed to parse .obj file (line number " << line_num << ")" << std::endl; exit(EXIT_FAILURE); } o3d::Mesh parse_object(const std::string& obj_path) { o3d::Mesh mesh; std::ifstream obj_file(obj_path); if (!obj_file.is_open()) { std::cerr << "file `" << obj_path << "' not found" << std::endl; // TODO: improve exit(EXIT_FAILURE); } std::string line; while (std::getline(obj_file, line)) { std::string_view line_view { line }; if (line_view.length() == 0 || line_view[0] == '#') continue; auto data_type_idx = line_view.find(" "); if (data_type_idx == std::string_view::npos) { std::cerr << "error: failed to parse .obj file: no argument given to data type '" << line_view << "`" << std::endl; exit(EXIT_FAILURE); } auto data_type = line_view.substr(0, data_type_idx); auto data_arg = line_view.substr(data_type_idx + 1); if (data_type == "o") { // TODO: implement } else if (data_type == "v") { mesh.vertices.push_back(*split_to_vec(data_arg, ' ').or_else(err_exit)); } else if (data_type == "vn") { mesh.normals.push_back(*split_to_vec(data_arg, ' ').or_else(err_exit)); } else if (data_type == "vt") { auto uv = *split_to_vec(data_arg, ' ').or_else(err_exit); uv.y = 1.f - uv.y; mesh.uvs.push_back(uv); } else if (data_type == "s") { // TODO: implement } else if (data_type == "f") { std::array, 3> indices; bool err = false; split(data_arg, ' ', [&](auto m, const auto& vertex_indices_s) { split(vertex_indices_s, '/', [&](auto n, const auto& idx_s) { // indices of texture coordinates and normals are reversed in .obj relative to the // engine auto idx_opt = parse_num(idx_s); if (!idx_opt) { err = true; return false; } indices[m][(n > 0 ? 3 - n : n)] = *idx_opt - 1; return true; }); return !err; }); if (err) err_exit(); mesh.indices.push_back(indices); } else if (data_type == "mtllib") { // TODO: implement } else if (data_type == "usemtl") { // TODO: implement } else { std::cerr << "error while parsing .obj file: unknown data type: " << line_view << std::endl; exit(EXIT_FAILURE); } } return mesh; } }