From db41d43345ea73cf7c1bbb29448e52ffb822e3e0 Mon Sep 17 00:00:00 2001 From: vimene Date: Tue, 13 Jan 2026 02:04:52 +0100 Subject: added textures for the hardware renderer - removed "using" directive in .hpp - reverse order of arguments for quaternion rotation, i.e. q.rot(v) instead of v.rot(q), where q is a quaterinon and v a vector - pass the inverse of the view matrix to render_and_present_frame, to allow light calculation in shaders (we used to just pass the matrix of the quaternion of the transformation, i.e. discard scaling and translations) - added another mesh and texture (viking_room) for testing purposes - added transparent background option - added Quaternion::look_towards(), which is the equivalent of Matrix4::look_at() but only for rotations - various improvement to .obj parsing - load texture coordinates from .obj file - merged duplicate vertices in Mesh::linearize_indices() --- src/obj_parser.cpp | 149 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 53 deletions(-) (limited to 'src/obj_parser.cpp') diff --git a/src/obj_parser.cpp b/src/obj_parser.cpp index d83d75f..28df871 100644 --- a/src/obj_parser.cpp +++ b/src/obj_parser.cpp @@ -6,37 +6,44 @@ #include #include #include +#include +#include #include "math/vector.hpp" +#include "math/utils.hpp" #include "o3d/mesh.hpp" #include "o3d/vertex_data.hpp" namespace engine { -namespace { +using math::Vector2, math::Vector3; -std::vector split(const std::string& s, char sep) { - std::vector res; - std::string::size_type last_ind = 0; - for (std::string::size_type ind = 0; ind < s.length(); ind++) { +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) { - res.push_back(s.substr(last_ind, ind - last_ind)); + if (!fn(n, s.substr(last_ind, ind - last_ind))) return; + n++; last_ind = ind + 1; } } - res.push_back(s.substr(last_ind)); - return res; + fn(n, s.substr(last_ind)); } -float parse_float(const std::string& s) { +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; - // TODO: improve error checking if (ind == s.length()) - return 0.f; + return {}; int n = 0; while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9') @@ -45,9 +52,8 @@ float parse_float(const std::string& s) { if (ind == s.length()) return (positive ? 1.f : -1.f) * static_cast(n); - // TODO: improve error checking if (s[ind] != '.') - return 0.f; + return {}; ind++; @@ -58,79 +64,116 @@ float parse_float(const std::string& s) { decimal_fac *= .1f; } - // TODO: improve error checking if (ind != s.length()) - return 0.f; + return {}; return (positive ? 1.f : -1.f) * (static_cast(n) + decimal_part); } -float parse_int(const std::string& 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; - // TODO: improve error checking if (ind == s.length()) - return 0.f; + return {}; int n = 0; while (ind < s.length() && s[ind] >= '0' && s[ind] <= '9') n = n * 10 + static_cast(s[ind++]) - static_cast('0'); - // TODO: improve error checking if (ind != s.length()) - return 0.f; + 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 - std::exit(1); + std::cerr << "file `" << obj_path << "' not found" << std::endl; // TODO: improve + exit(EXIT_FAILURE); } std::string line; while (std::getline(obj_file, line)) { - if (line.length() == 0 || line[0] == '#') + std::string_view line_view { line }; + if (line_view.length() == 0 || line_view[0] == '#') continue; - if (line.rfind("o ", 0) == 0) { - // std::cout << "Object: " << line.substr(2) << std::endl; - } else if (line.rfind("v ", 0) == 0) { - auto s_coords = split(line.substr(2), ' '); - mesh.vertices.emplace_back(parse_float(s_coords[0]), parse_float(s_coords[1]), parse_float(s_coords[2])); - } else if (line.rfind("vn ", 0) == 0) { - auto s_coords = split(line.substr(3), ' '); - mesh.normals.emplace_back(parse_float(s_coords[0]), parse_float(s_coords[1]), parse_float(s_coords[2])); - } else if (line.rfind("s ", 0) == 0) { - // auto smooth = false; - // auto s_smooth = line.substr(2); - // if (s_smooth == "0" || s_smooth == "off") { - // smooth = false; - // } else if (s_smooth == "1" || s_smooth == "on") { - // smooth = true; - // } - // std::cout << "Smooth: " << std::boolalpha << smooth << std::endl; - } else if (line.rfind("f ", 0) == 0) { - std::array, 3> indices; - auto line_split = split(line.substr(2), ' '); - for (int i = 0; i < 3; i++) { - auto indices_s_group = split(line_split[i], '/'); - indices[i][0] = parse_int(indices_s_group[0]) - 1; - indices[i][1] = parse_int(indices_s_group[2]) - 1; - } - // std::cout << "Face:" - // << " 1: vertex: " << indices[0][0] << " normal: " << indices[0][1] - // << " 2: vertex: " << indices[1][0] << " normal: " << indices[1][1] - // << " 3: vertex: " << indices[2][0] << " normal: " << indices[2][1] - // << std::endl; + 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; -- cgit v1.2.3