diff --git a/include/blt/parse/obj_loader.h b/include/blt/parse/obj_loader.h new file mode 100644 index 0000000..5d2c72d --- /dev/null +++ b/include/blt/parse/obj_loader.h @@ -0,0 +1,174 @@ +/* + * + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_WITH_GRAPHICS_OBJ_LOADER_H +#define BLT_WITH_GRAPHICS_OBJ_LOADER_H + +#include "blt/math/vectors.h" +#include "blt/std/hashmap.h" +#include +#include +#include + +namespace blt::gfx +{ + + typedef blt::vec3f vertex_t; + typedef blt::vec2f uv_t; + typedef blt::vec3f normal_t; + typedef blt::vec3f color_t; + + class model_data + { + public: + private: + std::vector vertices; + std::vector uvs; + std::vector normals; + }; + + struct face_t + { + std::int32_t vertex, uv, normal; + }; + + static inline bool operator==(const face_t& f1, const face_t& f2) + { + return f1.vertex == f2.vertex && f1.uv == f2.uv && f1.normal == f2.normal; + } + + struct face_hash + { + size_t operator()(const face_t& face) const + { + std::hash hasher; + return hasher(face.vertex) ^ hasher(face.uv) ^ hasher(face.normal); + } + }; + + struct face_eq + { + bool operator()(const face_t& f1, const face_t& f2) const + { + return f1 == f2; + } + }; + + struct constructed_vertex_t + { + vertex_t vertex; + uv_t uv; + normal_t normal; + }; + + struct triangle_t + { + std::int32_t v[3]; + }; + + struct quad_t + { + std::int32_t v[4]; + }; + + struct material_t + { + std::string material_name; + color_t ambient; + color_t diffuse; + color_t specular; + float specular_exponent = 0.0f; + float transparency = 1.0f; + color_t transmission_filter_color{1, 1, 1}; + std::string texture_ambient; + std::string texture_diffuse; + std::string map_spec_color; + std::string map_spec_highlight; + std::string map_bump; + std::string map_displacement; + }; + + struct object_data + { + std::vector object_names; + std::string material; + std::vector indices; + }; + + class obj_model_t + { + private: + std::vector vertex_data_; + std::vector objects_; + HASHMAP materials_; + public: + obj_model_t(std::vector&& vertex_data, std::vector&& objects, HASHMAP&& mats): + vertex_data_(vertex_data), objects_(objects), materials_(mats) + {} + + inline const auto& vertex_data() + { + return vertex_data_; + }; + + inline const auto& objects() + { + return objects_; + }; + + inline const auto& materials() + { + return materials_; + } + }; + + class char_tokenizer; + + class obj_loader + { + private: + std::vector vertices; + std::vector uvs; + std::vector normals; + + // maps between face (constructed vertex) -> vertex indices + HASHMAP vertex_map; + std::vector vertex_data; + object_data current_object; + std::vector data; + HASHMAP materials; + + size_t current_line = 0; + private: + bool handle_vertex_and_normals(float x, float y, float z, char type); + + void parse_vertex_line(char_tokenizer& tokenizer); + + void parse_face(char_tokenizer& tokenizer); + + void handle_face_vertex(const std::vector& face_list, std::int32_t* arr); + + public: + obj_model_t parseFile(std::string_view file); + }; + + obj_model_t quick_load(std::string_view file); + +} + +#endif //BLT_WITH_GRAPHICS_OBJ_LOADER_H diff --git a/include/blt/std/allocator.h b/include/blt/std/allocator.h index a60bfa8..3b5bfa5 100644 --- a/include/blt/std/allocator.h +++ b/include/blt/std/allocator.h @@ -165,7 +165,7 @@ namespace blt * * ALLOCATORS RETURN UNINIT STORAGE!! THIS HAS BEEN DISABLED. */ - inline void allocate_in_block(pointer begin, size_t n) + inline void allocate_in_block(pointer, size_t) { // if constexpr (std::is_default_constructible_v && !std::is_trivially_default_constructible_v) // { diff --git a/include/blt/std/string.h b/include/blt/std/string.h index 4f3e44f..d4b82b2 100755 --- a/include/blt/std/string.h +++ b/include/blt/std/string.h @@ -292,6 +292,29 @@ namespace blt::string return s; } + static inline BLT_CPP20_CONSTEXPR std::string_view ltrim(std::string_view s) + { + size_t start_pos = 0; + for (auto c = s.begin(); c != s.end() && std::isblank(*c); ++c, start_pos++); + return s.substr(start_pos); + } + + static inline BLT_CPP20_CONSTEXPR std::string_view rtrim(std::string_view s) + { + size_t end_pos = 0; + for (auto c = s.rbegin(); c != s.rend() && std::isblank(*c); ++c, end_pos++); + return s.substr(0, s.size() - end_pos); + } + + static inline BLT_CPP20_CONSTEXPR std::string_view trim(std::string_view s) + { + size_t start_pos = 0; + for (auto c = s.begin(); c != s.end() && std::isblank(*c); ++c, start_pos++); + size_t end_pos = s.size(); + for (auto c = s.rbegin(); c != s.rend() && std::isblank(*c); ++c, end_pos--); + return s.substr(start_pos, end_pos - start_pos); + } + // trim from both ends (in place) static inline BLT_CPP20_CONSTEXPR std::string& trim(std::string& s) { diff --git a/include/blt/std/types.h b/include/blt/std/types.h new file mode 100644 index 0000000..34dfa99 --- /dev/null +++ b/include/blt/std/types.h @@ -0,0 +1,43 @@ +/* + * + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_TYPES_H +#define BLT_TYPES_H + +#include + +#ifndef NO_BLT_NAMESPACE_ON_TYPES +namespace blt +{ +#endif + using i8 = std::int8_t; + using i16 = std::int16_t; + using i32 = std::int32_t; + using i64 = std::int64_t; + + using u8 = std::uint8_t; + using u16 = std::uint16_t; + using u32 = std::uint32_t; + using u64 = std::uint64_t; + + using size_t = std::size_t; +#ifndef NO_BLT_NAMESPACE_ON_TYPES +} +#endif + +#endif //BLT_TYPES_H diff --git a/src/blt/parse/obj_loader.cpp b/src/blt/parse/obj_loader.cpp new file mode 100644 index 0000000..215dde3 --- /dev/null +++ b/src/blt/parse/obj_loader.cpp @@ -0,0 +1,243 @@ +/* + * + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#define BLT_DISABLE_TRACE +#define BLT_DISABLE_DEBUG + +#include +#include +#include +#include +#include +#include "blt/std/assert.h" +#include "blt/std/utility.h" +#include + + +namespace blt::gfx +{ + class char_tokenizer + { + private: + std::string_view string; + std::size_t current_pos = 0; + public: + explicit char_tokenizer(std::string_view view): string(view) + {} + + inline char advance() + { + return string[current_pos++]; + } + + inline bool has_next(size_t offset = 0) + { + return current_pos + offset < string.size(); + } + + inline std::string_view read_fully() + { + return blt::string::trim(string.substr(current_pos)); + } + }; + + template + T get(std::string_view str) + { + T x; + const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), x); + // probably not needed. + if (ec != std::errc()) + { +// int i; +// const auto [ptr2, ec2] = std::from_chars(str.data(), str.data() + str.size(), i); +// if (ec2 == std::errc()) +// { +// x = static_cast(i); +// } else +// { + BLT_WARN("Unable to parse string '%s' into number!", std::string(str).c_str()); + x = 0; +// } + } + return x; + } + + void obj_loader::parse_vertex_line(char_tokenizer& tokenizer) + { + char type = tokenizer.advance(); + + if (type == 'p') + { + BLT_WARN("Unexpected type '%c' (not supported)", type); + return; + } + + auto elements = blt::string::split(std::string(tokenizer.read_fully()), " "); + BLT_ASSERT(elements.size() >= 2 && "Current line doesn't have enough arguments to process!"); + float x = get(elements[0]), y = get(elements[1]); + BLT_DEBUG_STREAM << "Loaded value of (" << x << ", " << y << ")"; + if (elements.size() < 3) + { + if (type == 't') + uvs.push_back(uv_t{x, y}); + else + BLT_ERROR("Unable to parse line '%s' type '%c' not recognized for arg count", std::string(tokenizer.read_fully()).c_str(), type); + } else + { + float z = get(elements[2]); + BLT_DEBUG_STREAM << " with z: " << z; + if (!handle_vertex_and_normals(x, y, z, type)) + BLT_ERROR("Unable to parse line '%s' type '%c' not recognized", std::string(tokenizer.read_fully()).c_str(), type); + } + BLT_DEBUG_STREAM << "\n"; + } + + bool obj_loader::handle_vertex_and_normals(float x, float y, float z, char type) + { + if (std::isspace(type)) + { + vertices.push_back(vertex_t{x, y, z}); + } else if (type == 'n') + { + normals.push_back(normal_t{x, y, z}); + } else + return false; + return true; + } + + obj_model_t quick_load(std::string_view file) + { + return obj_loader().parseFile(file); + } + + obj_model_t obj_loader::parseFile(std::string_view file) + { + auto lines = blt::fs::getLinesFromFile(std::string(file)); + for (auto line_e : blt::enumerate(lines)) + { + auto& line = line_e.second; + current_line = line_e.first; + char_tokenizer token(line); + if (!token.has_next() || token.read_fully().empty()) + continue; + switch (token.advance()) + { + case '#': + continue; + case 'f': + parse_face(token); + break; + case 'v': + parse_vertex_line(token); + break; + case 'o': + { + current_object.object_names.emplace_back(token.read_fully()); + BLT_TRACE("Setting object '%s'", std::string(current_object.object_name).c_str()); + break; + } + case 'm': + { + while (token.has_next() && token.advance() != ' ') + {} + BLT_WARN("Material '%s' needs to be loaded!", std::string(token.read_fully()).c_str()); + break; + } + case 'u': + { + if (!current_object.indices.empty()) + data.push_back(current_object); + current_object = {}; + while (token.has_next() && token.advance() != ' ') + {} + current_object.material = token.read_fully(); + //BLT_WARN("Using material '%s'", std::string(token.read_fully()).c_str()); + break; + } + case 's': + //BLT_WARN("Using shading: %s", std::string(token.read_fully()).c_str()); + break; + } + } + data.push_back(current_object); + return {std::move(vertex_data), std::move(data), std::move(materials)}; + } + + void obj_loader::parse_face(char_tokenizer& tokenizer) + { + auto faces = blt::string::split(std::string(tokenizer.read_fully()), ' '); + if (faces.size() == 3) + { + triangle_t triangle{}; + handle_face_vertex(faces, triangle.v); + current_object.indices.push_back(triangle); + } else if (faces.size() == 4) + { + quad_t quad{}; + handle_face_vertex(faces, quad.v); + triangle_t t1{}; + triangle_t t2{}; + + for (int i = 0; i < 3; i++) + t1.v[i] = quad.v[i]; + t2.v[0] = quad.v[0]; + t2.v[1] = quad.v[2]; + t2.v[2] = quad.v[3]; + + current_object.indices.push_back(t1); + current_object.indices.push_back(t2); + } else + BLT_WARN("Unsupported face vertex count of %d on line %d!", faces.size(), current_line); + } + + void obj_loader::handle_face_vertex(const std::vector& face_list, int32_t* arr) + { + for (const auto& pair : blt::enumerate(face_list)) + { + auto indices = blt::string::split(pair.second, '/'); + BLT_ASSERT(indices.size() == 3 && "Must have vertex, uv, and normal indices!!"); + + auto vi = get(indices[0]) - 1; + auto ui = get(indices[1]) - 1; + auto ni = get(indices[2]) - 1; + + BLT_DEBUG("Found vertex: %d, UV: %d, and normal: %d", vi, ui, ni); + + face_t face{vi, ui, ni}; + + auto loc = vertex_map.find(face); + if (loc == vertex_map.end()) + { + BLT_DEBUG("DID NOT FIND FACE!"); + auto index = static_cast(vertex_data.size()); + vertex_data.push_back({vertices[vi], uvs[ui], normals[ni]}); + BLT_DEBUG("Vertex: (%f, %f, %f), UV: (%f, %f), Normal: (%f, %f, %f)", vertices[vi].x(), vertices[vi].y(), vertices[vi].z(), + uvs[ui].x(), uvs[ui].y(), normals[ni].x(), normals[ni].y(), normals[ni].z()); + vertex_map.insert({face, index}); + arr[pair.first] = index; + } else + { + BLT_TRACE("Using cached data; %d; map size: %d", loc->second, vertex_data.size()); + const auto& d = vertex_data[loc->second]; + BLT_TRACE("Vertex: (%f, %f, %f), UV: (%f, %f), Normal: (%f, %f, %f)", d.vertex.x(), d.vertex.y(), d.vertex.z(), + d.uv.x(), d.uv.y(), d.normal.x(), d.normal.y(), d.normal.z()); + arr[pair.first] = loc->second; + } + } + } +} \ No newline at end of file