Files
utad-3d/src/mesh.cpp
2025-10-13 21:22:35 +02:00

195 lines
5.0 KiB
C++

#include "mesh.h"
#include <filesystem>
#include <string>
#include <unordered_map>
#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"
// Helper structures and functions for OBJ loading
namespace {
struct LoadContext {
std::string base_dir;
std::unordered_map<std::string, std::shared_ptr<Texture>> texture_cache;
const std::vector<tinyobj::material_t>* materials;
const std::shared_ptr<Shader>& shader;
};
Vertex create_vertex(const tinyobj::attrib_t& attrib,
const tinyobj::index_t& idx)
{
Vertex vertex;
// Position
if (idx.vertex_index >= 0) {
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
vertex.tex_coord = glm::vec2(0.0f, 0.0f);
if (idx.texcoord_index >= 0) {
vertex.tex_coord.x = attrib.texcoords[2 * idx.texcoord_index + 0];
vertex.tex_coord.y = attrib.texcoords[2 * idx.texcoord_index + 1];
}
return vertex;
}
void process_shape(const tinyobj::shape_t& shape,
const tinyobj::attrib_t& attrib,
std::vector<Vertex>& out_vertices,
std::vector<uint16_t>& out_indices)
{
// Process all indices - create one vertex per index
for (size_t i = 0; i < shape.mesh.indices.size(); ++i) {
const tinyobj::index_t& idx = shape.mesh.indices[i];
Vertex vertex = create_vertex(attrib, idx);
out_vertices.push_back(vertex);
out_indices.push_back(
static_cast<uint16_t>(out_vertices.size() - 1));
}
}
std::shared_ptr<Texture> load_material_texture(int material_id,
LoadContext& context)
{
if (material_id < 0
|| material_id >= static_cast<int>(context.materials->size())) {
return nullptr;
}
const auto& mat = (*context.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()) {
return nullptr;
}
// Check cache first
auto it = context.texture_cache.find(texture_name);
if (it != context.texture_cache.end()) {
return it->second;
}
// Load texture
std::string texture_path = context.base_dir + texture_name;
auto texture = Texture::load(texture_path.c_str());
if (texture) {
context.texture_cache[texture_name] = texture;
Logger::info(sstr("Loaded texture: ", texture_path));
} else {
Logger::warn(sstr("Failed to load texture: ", texture_path));
}
return texture;
}
void create_buffer_for_shape(const tinyobj::shape_t& shape,
const std::vector<Vertex>& vertices,
const std::vector<uint16_t>& indices,
LoadContext& context,
std::shared_ptr<Mesh>& mesh)
{
auto buffer = std::make_shared<Buffer>(vertices, indices);
// Use first material ID from the shape
int material_id = -1;
if (!shape.mesh.material_ids.empty() && shape.mesh.material_ids[0] >= 0) {
material_id = shape.mesh.material_ids[0];
}
auto texture = load_material_texture(material_id, context);
auto material_shader =
context.shader ? context.shader : state::default_shader;
Material mat(texture, material_shader);
mesh->add_buffer(buffer, mat);
}
} // namespace
void Mesh::add_buffer(const std::shared_ptr<Buffer>& buffer,
const Material& material)
{
buffers_.push_back(buffer);
materials_.push_back(material);
}
std::shared_ptr<Mesh> Mesh::load(const char* filename,
const std::shared_ptr<Shader>& shader)
{
Logger::info(sstr("Loading mesh from: ", filename));
// Load OBJ file
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string err;
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, &err, filename,
base_dir.c_str())) {
Logger::error(
sstr("Failed to load OBJ file: ", filename, " - ", err));
return nullptr;
}
if (!err.empty()) {
Logger::warn(sstr("TinyObjLoader warning: ", err));
}
// Setup load context
LoadContext context{base_dir, {}, &materials, shader};
// Process each shape
auto mesh = std::make_shared<Mesh>();
for (const auto& shape : shapes) {
Logger::info(sstr("Processing shape: ", shape.name));
std::vector<Vertex> vertices;
std::vector<uint16_t> indices;
process_shape(shape, attrib, vertices, indices);
create_buffer_for_shape(shape, vertices, indices, context, mesh);
}
Logger::info(sstr("Mesh loaded successfully with ", mesh->num_buffers(),
" buffers"));
return mesh;
}
void Mesh::draw()
{
// Draw each buffer with its material
for (size_t i = 0; i < buffers_.size(); ++i) {
materials_[i].prepare();
buffers_[i]->draw(*materials_[i].shader());
}
}