#define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" #include "../Components/ModelComponent.h" // For definitions of SubMesh and Vertex #include "AssetManager.h" #include #include #include #include #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 shapes; std::vector 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 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 ¤tMesh = 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(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(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(); }