ThreeLab/Engine/utils/AssetManager.cpp
2025-04-01 20:28:37 -05:00

169 lines
6.4 KiB
C++

#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"
#include "../Components/ModelComponent.h" // For definitions of SubMesh and Vertex
#include "AssetManager.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <glm/glm.hpp>
#include "stb_image.h"
// Get the singleton instance.
AssetManager& AssetManager::Get() {
static AssetManager instance;
return instance;
}
bool AssetManager::LoadModel(const std::string &filepath, ModelAsset &outAsset) {
// Check if the model is already cached.
auto it = modelCache.find(filepath);
if (it != modelCache.end()) {
outAsset = it->second;
return true;
}
std::cout << "[Info] AssetManager: Loading Model '" << filepath << "'" << std::endl;
ModelAsset asset;
asset.modelPath = filepath;
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn, err;
// Get the base directory for the OBJ file.
std::string baseDir = "";
size_t lastSlash = filepath.find_last_of("/\\");
if (lastSlash != std::string::npos) {
baseDir = filepath.substr(0, lastSlash + 1);
}
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filepath.c_str(), baseDir.c_str());
if (!warn.empty()) {
std::cout << "[Warning] " << warn << std::endl;
}
if (!err.empty()) {
std::cerr << "[Error] " << err << std::endl;
}
if (!ret) {
std::cerr << "[Error] Failed to load/parse OBJ file: " << filepath << std::endl;
return false;
}
// Process each shape.
for (size_t s = 0; s < shapes.size(); s++) {
// Use a map to group faces by material ID.
std::map<int, SubMesh> submeshMap;
size_t index_offset = 0;
for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
int fv = shapes[s].mesh.num_face_vertices[f];
int matID = -1;
if (shapes[s].mesh.material_ids.size() > f)
matID = shapes[s].mesh.material_ids[f];
if (submeshMap.find(matID) == submeshMap.end())
submeshMap[matID] = SubMesh();
SubMesh &currentMesh = submeshMap[matID];
for (size_t v = 0; v < fv; v++) {
tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
Vertex vertex;
// Position.
vertex.Position = glm::vec3(
attrib.vertices[3 * idx.vertex_index + 0],
attrib.vertices[3 * idx.vertex_index + 1],
attrib.vertices[3 * idx.vertex_index + 2]
);
// Normal.
if (idx.normal_index >= 0 && 3 * idx.normal_index + 2 < attrib.normals.size()) {
vertex.Normal = glm::vec3(
attrib.normals[3 * idx.normal_index + 0],
attrib.normals[3 * idx.normal_index + 1],
attrib.normals[3 * idx.normal_index + 2]
);
} else {
vertex.Normal = glm::vec3(0.0f);
}
// Texture Coordinates.
if (idx.texcoord_index >= 0 && 2 * idx.texcoord_index + 1 < attrib.texcoords.size()) {
vertex.TexCoords = glm::vec2(
attrib.texcoords[2 * idx.texcoord_index + 0],
attrib.texcoords[2 * idx.texcoord_index + 1]
);
} else {
vertex.TexCoords = glm::vec2(0.0f);
}
// Tangent (not provided).
vertex.Tangent = glm::vec3(0.0f);
// Add vertex and index.
currentMesh.vertices.push_back(vertex);
currentMesh.indices.push_back(static_cast<GLuint>(currentMesh.vertices.size() - 1));
}
index_offset += fv;
}
// Process each submesh for this shape.
for (auto &pair : submeshMap) {
int matID = pair.first;
SubMesh &subMesh = pair.second;
if (matID >= 0 && matID < static_cast<int>(materials.size())) {
tinyobj::material_t mat = materials[matID];
subMesh.diffuseColor = glm::vec3(mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]);
subMesh.specularColor = glm::vec3(mat.specular[0], mat.specular[1], mat.specular[2]);
subMesh.shininess = mat.shininess;
if (!mat.diffuse_texname.empty()) {
std::string texturePath = baseDir + mat.diffuse_texname;
// Load and cache the texture.
LoadTexture(texturePath, subMesh.diffuseTexture);
}
}
// Note: OpenGL buffers (VAO, VBO, EBO) are not created here.
asset.meshes.push_back(subMesh);
}
}
std::cout << "[Done] AssetManager: Loaded Model '" << filepath << "'" << std::endl;
modelCache[filepath] = asset;
outAsset = asset;
return true;
}
bool AssetManager::LoadTexture(const std::string &filepath, GLuint &textureID) {
// Check if texture is already cached.
auto it = textureCache.find(filepath);
if (it != textureCache.end()) {
textureID = it->second;
return true;
}
int w, h, channels;
unsigned char* data = stbi_load(filepath.c_str(), &w, &h, &channels, 0);
if (!data) {
std::cerr << "[Error] AssetManager: Failed to load texture: " << filepath << std::endl;
return false;
}
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
GLenum format = (channels == 3) ? GL_RGB : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_image_free(data);
textureCache[filepath] = textureID;
return true;
}
void AssetManager::Clear() {
modelCache.clear();
// Optionally delete textures from OpenGL context.
for (auto &pair : textureCache) {
glDeleteTextures(1, &pair.second);
}
textureCache.clear();
}