aboutsummaryrefslogtreecommitdiff
path: root/src/obj_parser.cpp
diff options
context:
space:
mode:
authorvimene <vincent.menegaux@gmail.com>2026-01-13 02:04:52 +0100
committervimene <vincent.menegaux@gmail.com>2026-01-13 02:04:52 +0100
commitdb41d43345ea73cf7c1bbb29448e52ffb822e3e0 (patch)
tree4635d654e301b3f31f8d2626f3bc2c6f2a6e50a8 /src/obj_parser.cpp
parent7f08187a46e30925e4563585fab2c6f92400330a (diff)
downloadengine-db41d43345ea73cf7c1bbb29448e52ffb822e3e0.tar.gz
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()
Diffstat (limited to 'src/obj_parser.cpp')
-rw-r--r--src/obj_parser.cpp149
1 files changed, 96 insertions, 53 deletions
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 <vector>
#include <cstddef>
#include <array>
+#include <string_view>
+#include <optional>
#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<std::string> split(const std::string& s, char sep) {
- std::vector<std::string> res;
- std::string::size_type last_ind = 0;
- for (std::string::size_type ind = 0; ind < s.length(); ind++) {
+template<typename CallbackFn>
+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<typename NumType>
+static std::optional<NumType> parse_num(const std::string_view& s);
+
+template<>
+std::optional<float> parse_num<float>(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<float>(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<float>(n) + decimal_part);
}
-float parse_int(const std::string& s) {
+template<>
+std::optional<int> parse_num<int>(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<int>(s[ind++]) - static_cast<int>('0');
- // TODO: improve error checking
if (ind != s.length())
- return 0.f;
+ return {};
return (positive ? 1.f : -1.f) * n;
}
+template<typename VectorType>
+static constexpr std::optional<VectorType> split_to_vec(const std::string_view& s, char sep) {
+ std::array<float, VectorType::size> coords;
+ bool err = false;
+ split(s, sep, [&](auto n, const auto& coord_s) {
+ auto coord = parse_num<float>(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<typename Ret, size_t line_num>
+[[noreturn]]
+static std::optional<Ret> 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<std::array<std::size_t, 2>, 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<Vector3>(data_arg, ' ').or_else(err_exit<Vector3, __LINE__>));
+ } else if (data_type == "vn") {
+ mesh.normals.push_back(*split_to_vec<Vector3>(data_arg, ' ').or_else(err_exit<Vector3, __LINE__>));
+ } else if (data_type == "vt") {
+ auto uv = *split_to_vec<Vector2>(data_arg, ' ').or_else(err_exit<Vector2, __LINE__>);
+ 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<std::array<std::size_t, 3>, 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<int>(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<int, __LINE__>();
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;