From d084ce87999ece9a34288970f018f291d7a70637 Mon Sep 17 00:00:00 2001 From: Daniel Poveda Date: Mon, 13 Oct 2025 19:47:21 +0200 Subject: [PATCH] feat: load obj --- src/engine.cpp | 135 +++++++++++++++----------------------- src/engine.h | 2 +- src/mesh.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++++++++- src/mesh.h | 5 ++ 4 files changed, 229 insertions(+), 84 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 209c4a4..00e0c7e 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -111,7 +111,7 @@ void Engine::run() const double now = glfwGetTime(); const double delta_time = now - last_update_time_; - process_input(); + process_input(delta_time); update(delta_time); if (now - last_frame_time_ >= fps_limit) { @@ -156,78 +156,27 @@ void Engine::setup() world_->add_entity(camera_); Logger::info("Camera created and added to world"); - // Load textures - auto top_texture = Texture::load("data/textures/top.png"); - auto front_texture = Texture::load("data/textures/front.png"); + // Load the box_stack model + auto box_stack_mesh = Mesh::load("data/models/box_stack.obj"); + if (box_stack_mesh) { + auto box_stack_model = std::make_shared(box_stack_mesh); + box_stack_model->set_position(glm::vec3(-2.0f, 0.0f, 0.0f)); + models_.push_back(box_stack_model); + world_->add_entity(box_stack_model); + Logger::info("Box stack model loaded and added to world"); + } - // Create cube mesh with two buffers - mesh_ = std::make_shared(); - - // Buffer 1: Top and bottom faces - std::vector top_bottom_vertices = { - // Top face (y = 0.5) - {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, - {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, - // Bottom face (y = -0.5) - {{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, - {{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}}; - std::vector top_bottom_indices = {// Top face - 0, 1, 2, 0, 2, 3, - // Bottom face - 4, 6, 5, 4, 7, 6}; - - auto top_bottom_buffer = - std::make_shared(top_bottom_vertices, top_bottom_indices); - Material top_material(top_texture); - mesh_->add_buffer(top_bottom_buffer, top_material); - - // Buffer 2: Front, back, left, right faces - std::vector side_vertices = { - // Front face (z = 0.5) - {{-0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, - {{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, - // Back face (z = -0.5) - {{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, - {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, - {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, - {{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, - // Left face (x = -0.5) - {{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, - {{-0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, - {{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, - {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, - // Right face (x = 0.5) - {{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, - {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, - {{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, - {{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}}; - std::vector side_indices = {// Front face - 0, 1, 2, 0, 2, 3, - // Back face - 4, 5, 6, 4, 6, 7, - // Left face - 8, 9, 10, 8, 10, 11, - // Right face - 12, 13, 14, 12, 14, 15}; - - auto side_buffer = - std::make_shared(side_vertices, side_indices); - Material side_material(front_texture); - mesh_->add_buffer(side_buffer, side_material); - - Logger::info("Cube mesh created with two buffers"); - - // Create model at origin - auto model = std::make_shared(mesh_); - model->set_position(glm::vec3(0.0f, 0.0f, 0.0f)); - models_.push_back(model); - world_->add_entity(model); + // Load the gunslinger model + auto gunslinger_mesh = Mesh::load("data/models/gunslinger.obj"); + if (gunslinger_mesh) { + auto gunslinger_model = + std::make_shared(gunslinger_mesh); + gunslinger_model->set_position(glm::vec3(2.0f, 0.0f, 0.0f)); + gunslinger_model->set_scale(glm::vec3(0.5f, 0.5f, 0.5f)); + models_.push_back(gunslinger_model); + world_->add_entity(gunslinger_model); + Logger::info("Gunslinger model loaded and added to world"); + } Logger::info("Scene setup complete"); } @@ -238,22 +187,44 @@ void Engine::start() // Can be used for initialization that needs the scene to be ready } -void Engine::process_input() +void Engine::process_input(const double delta_time) { glfwPollEvents(); + + if (!camera_) { + return; + } + + constexpr float camera_speed = 7.5f; + + glm::vec3 forward = glm::vec3(0.0f, 0.0f, -1.0f); + glm::vec3 right = glm::vec3(1.0f, 0.0f, 0.0f); + + glm::mat4 rotation = glm::mat4(1.0f); + rotation = glm::rotate(rotation, camera_->rotation().y, + glm::vec3(0.0f, 1.0f, 0.0f)); + rotation = glm::rotate(rotation, camera_->rotation().x, + glm::vec3(1.0f, 0.0f, 0.0f)); + forward = glm::vec3(rotation * glm::vec4(forward, 0.0f)); + right = glm::vec3(rotation * glm::vec4(right, 0.0f)); + + const float movement = camera_speed * static_cast(delta_time); + if (glfwGetKey(window_, GLFW_KEY_UP) == GLFW_PRESS) { + camera_->set_position(camera_->position() + forward * movement); + } + if (glfwGetKey(window_, GLFW_KEY_DOWN) == GLFW_PRESS) { + camera_->set_position(camera_->position() - forward * movement); + } + if (glfwGetKey(window_, GLFW_KEY_LEFT) == GLFW_PRESS) { + camera_->set_position(camera_->position() - right * movement); + } + if (glfwGetKey(window_, GLFW_KEY_RIGHT) == GLFW_PRESS) { + camera_->set_position(camera_->position() + right * movement); + } } void Engine::update(const double delta_time) { - // Update angle for rotation - angle_ += 32.0 * delta_time; - - // Update rotation for all models - for (auto& model : models_) { - model->set_rotation(glm::vec3( - 0.0f, glm::radians(static_cast(angle_)), 0.0f)); - } - // Update world world_->update(static_cast(delta_time)); } diff --git a/src/engine.h b/src/engine.h index 80de442..6e082a3 100644 --- a/src/engine.h +++ b/src/engine.h @@ -25,7 +25,7 @@ private: // Lifecycle methods void setup(); void start(); - void process_input(); + void process_input(const double delta_time); void update(const double delta_time); void render(); diff --git a/src/mesh.cpp b/src/mesh.cpp index fdf0253..f130b28 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1,15 +1,184 @@ #include "mesh.h" +#include +#include +#include + +#include "../lib/tiny_obj_loader.h" + #include "buffer.h" +#include "logger.h" #include "material.h" +#include "shader.h" +#include "state.h" +#include "texture.h" +#include "vertex.h" void Mesh::add_buffer(const std::shared_ptr& buffer, - const Material& material) + const Material& material) { buffers_.push_back(buffer); materials_.push_back(material); } +std::shared_ptr Mesh::load(const char* filename, + const std::shared_ptr& shader) +{ + Logger::info(sstr("Loading mesh from: ", filename)); + + // Load the OBJ file + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn_err; + + // Get the directory of the OBJ file for loading materials + std::filesystem::path obj_path(filename); + std::string base_dir = obj_path.parent_path().string(); + if (!base_dir.empty()) { + base_dir += "/"; + } + + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn_err, + filename, base_dir.c_str())) { + Logger::error( + sstr("Failed to load OBJ file: ", filename, " - ", + warn_err)); + return nullptr; + } + + if (!warn_err.empty()) { + Logger::warn(sstr("TinyObjLoader warning: ", warn_err)); + } + + // Create the mesh + auto mesh = std::make_shared(); + + // Cache loaded textures to avoid loading the same texture multiple + // times + std::unordered_map> texture_cache; + + // Process each shape + for (const auto& shape : shapes) { + Logger::info(sstr("Processing shape: ", shape.name)); + + // Group faces by material + std::unordered_map> material_vertices; + std::unordered_map> material_indices; + + // Process all faces in this shape + size_t index_offset = 0; + for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); + f++) { + int fv = shape.mesh.num_face_vertices[f]; + int material_id = shape.mesh.material_ids[f]; + + // Process each vertex in the face + for (size_t v = 0; v < static_cast(fv); v++) { + tinyobj::index_t idx = + shape.mesh.indices[index_offset + v]; + + Vertex vertex; + + // Position + vertex.position.x = + attrib.vertices[3 * idx.vertex_index + 0]; + vertex.position.y = + attrib.vertices[3 * idx.vertex_index + 1]; + vertex.position.z = + attrib.vertices[3 * idx.vertex_index + 2]; + + // Color (default to white) + vertex.color = glm::vec3(1.0f, 1.0f, 1.0f); + + // Texture coordinates + if (idx.texcoord_index >= 0) { + vertex.tex_coord.x = + attrib.texcoords + [2 * idx.texcoord_index + 0]; + vertex.tex_coord.y = 1.0f + - attrib.texcoords + [2 * idx.texcoord_index + + 1]; // Flip Y + } + + material_vertices[material_id].push_back( + vertex); + material_indices[material_id].push_back( + static_cast( + material_vertices[material_id].size() + - 1)); + } + + index_offset += fv; + } + + // Create buffers for each material + for (const auto& [material_id, vertices] : material_vertices) { + const auto& indices = material_indices[material_id]; + + // Create buffer + auto buffer = + std::make_shared(vertices, indices); + + // Create material + std::shared_ptr texture = nullptr; + + // Load texture if material has one + if (material_id >= 0 + && material_id + < static_cast(materials.size())) { + const auto& mat = materials[material_id]; + std::string texture_name = mat.diffuse_texname; + + // Try ambient texture if diffuse is not + // available + if (texture_name.empty()) { + texture_name = mat.ambient_texname; + } + + if (!texture_name.empty()) { + // Check cache first + auto it = + texture_cache.find(texture_name); + if (it != texture_cache.end()) { + texture = it->second; + } else { + // Load texture + std::string texture_path = + base_dir + texture_name; + texture = Texture::load( + texture_path.c_str()); + if (texture) { + texture_cache + [texture_name] = + texture; + Logger::info(sstr( + "Loaded texture: ", + texture_path)); + } else { + Logger::warn(sstr( + "Failed to load texture: ", + texture_path)); + } + } + } + } + + // Use provided shader or default shader + auto material_shader = + shader ? shader : state::default_shader; + Material mat(texture, material_shader); + + mesh->add_buffer(buffer, mat); + } + } + + Logger::info(sstr("Mesh loaded successfully with ", mesh->num_buffers(), + " buffers")); + return mesh; +} + void Mesh::draw() { // Draw each buffer with its material diff --git a/src/mesh.h b/src/mesh.h index 3e529cf..aabe4d7 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -7,12 +7,17 @@ #include "material.h" class Buffer; +class Shader; class Mesh { public: Mesh() = default; ~Mesh() = default; + static std::shared_ptr load( + const char* filename, + const std::shared_ptr& shader = nullptr); + void add_buffer(const std::shared_ptr& buffer, const Material& material);