Added Model GUI to load models

This commit is contained in:
OusmBlueNinja 2025-04-01 20:28:37 -05:00
parent 073288fc63
commit dfbbb65c93
12 changed files with 400 additions and 173 deletions

View File

@ -14,6 +14,7 @@
#include "stb_image.h" #include "stb_image.h"
#include <fstream> #include <fstream>
#include <yaml-cpp/yaml.h> #include <yaml-cpp/yaml.h>
#include "../Engine/Components/Component.h" // For interface base
using std::vector; using std::vector;
@ -147,6 +148,10 @@ int main() {
Entity* selectedEntity = nullptr; Entity* selectedEntity = nullptr;
int selectedIndex = -1; int selectedIndex = -1;
// Variables for the "Change Model" popup.
static bool showModelPopup = false;
static char newModelPath[256] = "";
float lastFrameTime = (float)glfwGetTime(); float lastFrameTime = (float)glfwGetTime();
while (!glfwWindowShouldClose(Engine::GetWindow())) { while (!glfwWindowShouldClose(Engine::GetWindow())) {
float currentFrameTime = (float)glfwGetTime(); float currentFrameTime = (float)glfwGetTime();
@ -173,18 +178,45 @@ int main() {
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode); ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
// Left Panel: Entity List. // Left Panel: Entity List with Add/Remove buttons.
ImGui::Begin("Entity List"); ImGui::Begin("Entity List");
for (int i = 0; i < entities.size(); i++) { for (int i = 0; i < entities.size(); i++) {
char label[32]; char label[32];
sprintf(label, "Entity %d", i); sprintf(label, "Entity %d", i);
if (ImGui::Selectable(label, selectedIndex == i)) if (ImGui::Selectable(label, selectedIndex == i)) {
selectedIndex = i;
if (ImGui::IsItemClicked()) {
selectedEntity = entities[i]; selectedEntity = entities[i];
selectedIndex = i; selectedIndex = i;
} }
} }
if (ImGui::Button("Add Cube")) {
Entity* newCube = new Entity(EntityType::CUBE);
newCube->transform.position = glm::vec3(0.0f);
newCube->modelComponent = new ModelComponent();
// Default global model properties.
newCube->modelComponent->diffuseColor = glm::vec3(1.0f);
newCube->modelComponent->specularColor = glm::vec3(1.0f);
newCube->modelComponent->shininess = 32.0f;
entities.push_back(newCube);
}
ImGui::SameLine();
if (ImGui::Button("Add Light")) {
Entity* newLight = new Entity(EntityType::LIGHT);
newLight->transform.position = glm::vec3(0.0f);
newLight->lightComponent = new LightComponent();
newLight->lightComponent->color = glm::vec3(1.0f);
newLight->lightComponent->intensity = 1.0f;
entities.push_back(newLight);
}
if (selectedEntity && ImGui::Button("Remove Selected")) {
// Remove selected entity.
auto it = std::find(entities.begin(), entities.end(), selectedEntity);
if (it != entities.end()) {
delete *it;
entities.erase(it);
selectedEntity = nullptr;
selectedIndex = -1;
}
}
ImGui::End(); ImGui::End();
// Right Panel: Entity Inspector. // Right Panel: Entity Inspector.
@ -200,6 +232,12 @@ int main() {
ImGui::ColorEdit3("Diffuse", glm::value_ptr(selectedEntity->modelComponent->diffuseColor)); ImGui::ColorEdit3("Diffuse", glm::value_ptr(selectedEntity->modelComponent->diffuseColor));
ImGui::ColorEdit3("Specular", glm::value_ptr(selectedEntity->modelComponent->specularColor)); ImGui::ColorEdit3("Specular", glm::value_ptr(selectedEntity->modelComponent->specularColor));
ImGui::DragFloat("Shininess", &selectedEntity->modelComponent->shininess, 1.0f, 1.0f, 128.0f); ImGui::DragFloat("Shininess", &selectedEntity->modelComponent->shininess, 1.0f, 1.0f, 128.0f);
// Button to open model editor popup.
if (ImGui::Button("Change Model")) {
showModelPopup = true;
// Pre-fill popup with current model path.
strncpy(newModelPath, selectedEntity->modelComponent->modelPath.c_str(), sizeof(newModelPath));
}
} }
if (selectedEntity->GetType() == EntityType::LIGHT && selectedEntity->lightComponent) { if (selectedEntity->GetType() == EntityType::LIGHT && selectedEntity->lightComponent) {
ImGui::Separator(); ImGui::Separator();
@ -212,6 +250,47 @@ int main() {
} }
ImGui::End(); ImGui::End();
// Model Editor Popup.
if (showModelPopup && selectedEntity && selectedEntity->modelComponent) {
ImGui::OpenPopup("Edit Model");
showModelPopup = false;
}
if (ImGui::BeginPopupModal("Edit Model", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::InputText("Model Path", newModelPath, sizeof(newModelPath));
if (ImGui::Button("Load Model", ImVec2(120, 0))) {
// Update the model component with the new model path.
selectedEntity->modelComponent->LoadModel(newModelPath);
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::Separator();
ImGui::Text("Submesh Textures:");
// Display a grid of textures.
if (selectedEntity->modelComponent->meshes.empty()) {
ImGui::Text("None");
} else {
int columns = 12; // Change this value for a different number of columns.
int count = 0;
for (size_t i = 0; i < selectedEntity->modelComponent->meshes.size(); i++) {
if (count % columns != 0)
ImGui::SameLine();
// Convert the diffuse texture to an ImTextureID.
ImTextureID texID = (ImTextureID)(intptr_t)(selectedEntity->modelComponent->meshes[i].diffuseTexture);
// Display the texture image with a fixed size.
ImGui::Image(texID, ImVec2(64, 64));
count++;
}
}
ImGui::EndPopup();
}
// New Panel: File Operations (Save/Load). // New Panel: File Operations (Save/Load).
ImGui::Begin("Scene File"); ImGui::Begin("Scene File");
if (ImGui::Button("Save")) { if (ImGui::Button("Save")) {

View File

@ -1,12 +1,14 @@
#include "ModelComponent.h" #include "ModelComponent.h"
#include "stb_image.h" #include "stb_image.h"
#define TINYOBJLOADER_IMPLEMENTATION #include "../Engine.h"
#include "tiny_obj_loader.h"
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <map> #include <map>
#include <algorithm> #include <algorithm>
#include "../utils/AssetManager.h"
#include <glm/gtc/type_ptr.hpp>
// Constructor: initialize default values. // Constructor: initialize default values.
ModelComponent::ModelComponent() ModelComponent::ModelComponent()
@ -20,15 +22,17 @@ ModelComponent::ModelComponent()
} }
ModelComponent::~ModelComponent() { ModelComponent::~ModelComponent() {
// Clean up each submesh. // Clean up each submesh's OpenGL buffers.
for (auto &mesh : meshes) { for (auto &mesh : meshes) {
if (mesh.VAO) glDeleteVertexArrays(1, &mesh.VAO); if (mesh.VAO) glDeleteVertexArrays(1, &mesh.VAO);
if (mesh.VBO) glDeleteBuffers(1, &mesh.VBO); if (mesh.VBO) glDeleteBuffers(1, &mesh.VBO);
if (mesh.EBO) glDeleteBuffers(1, &mesh.EBO); if (mesh.EBO) glDeleteBuffers(1, &mesh.EBO);
if(mesh.diffuseTexture) glDeleteTextures(1, &mesh.diffuseTexture); // Do not delete mesh.diffuseTexture here since it's managed by the Asset Manager.
// if(mesh.diffuseTexture) glDeleteTextures(1, &mesh.diffuseTexture);
} }
} }
void ModelComponent::SetupMesh(SubMesh &mesh) { void ModelComponent::SetupMesh(SubMesh &mesh) {
glGenVertexArrays(1, &mesh.VAO); glGenVertexArrays(1, &mesh.VAO);
glGenBuffers(1, &mesh.VBO); glGenBuffers(1, &mesh.VBO);
@ -86,122 +90,38 @@ bool ModelComponent::LoadNormalTexture(const std::string &filepath) {
// Loads a model using tiny_obj_loader and groups faces by material. // Loads a model using tiny_obj_loader and groups faces by material.
bool ModelComponent::LoadModel(const std::string &filepath) { bool ModelComponent::LoadModel(const std::string &filepath) {
// Use the AssetManager to load the raw model data.
std::cout << "[Info] Loading Model \'" << filepath << "\' " << std::endl; ModelAsset asset;
modelPath = filepath; if (!AssetManager::Get().LoadModel(filepath, asset))
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn, err;
// Get the directory of 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; return false;
}
// Save the model path.
modelPath = asset.modelPath;
// Clear any existing submeshes. // Clear any existing submeshes.
meshes.clear(); meshes.clear();
// For each shape in the OBJ file. // Copy the cached raw submesh data.
for (size_t s = 0; s < shapes.size(); s++) { // Note: We do not copy OpenGL buffers (VAO, VBO, EBO) since they are instance-specific.
// Use a map to group faces by material ID. for (const SubMesh &cachedMesh : asset.meshes) {
std::map<int, SubMesh> submeshMap; SubMesh mesh = cachedMesh;
size_t index_offset = 0; // Ensure buffer IDs are reset.
// Iterate over each face. mesh.VAO = mesh.VBO = mesh.EBO = 0;
for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { meshes.push_back(mesh);
int fv = shapes[s].mesh.num_face_vertices[f];
// Get material ID for this face.
int matID = -1;
if (shapes[s].mesh.material_ids.size() > f)
matID = shapes[s].mesh.material_ids[f];
// If submesh for this material does not exist, create it.
if (submeshMap.find(matID) == submeshMap.end())
submeshMap[matID] = SubMesh();
SubMesh &currentMesh = submeshMap[matID];
// Process each vertex in the face.
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;
} }
// For each group (submesh) in this shape, set material properties and create buffers. for (auto &mesh : meshes) {
for (auto &pair : submeshMap) { SetupMesh(mesh);
int matID = pair.first;
SubMesh &subMesh = pair.second;
// If there is a valid material, assign its properties.
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;
LoadDiffuseTexture(texturePath, subMesh.diffuseTexture);
} }
}
// Setup the OpenGL buffers for this submesh.
SetupMesh(subMesh);
// Add submesh to our model.
meshes.push_back(subMesh);
}
}
std::cout << "[Done] Loaded Model \'" << filepath << "\' " << std::endl;
return true; return true;
} }
// Draws the entire model by drawing each submesh.
void ModelComponent::Draw() { void ModelComponent::Draw() {
// Render each submesh.
for (auto &mesh : meshes) { for (auto &mesh : meshes) {
// Bind the appropriate texture if available. // Bind the diffuse texture if available.
if (mesh.diffuseTexture) { if (mesh.diffuseTexture) {
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mesh.diffuseTexture); glBindTexture(GL_TEXTURE_2D, mesh.diffuseTexture);
@ -211,3 +131,4 @@ void ModelComponent::Draw() {
glBindVertexArray(0); glBindVertexArray(0);
} }
} }

View File

@ -27,6 +27,8 @@ public:
static bool Init(); static bool Init();
// Returns the main GLFW window. // Returns the main GLFW window.
static GLFWwindow* GetWindow(); static GLFWwindow* GetWindow();
static GLuint GetShader();
// Clean up resources and shutdown GLFW. // Clean up resources and shutdown GLFW.
static void Shutdown(); static void Shutdown();

View File

@ -63,6 +63,9 @@ bool Engine::Init() {
GLFWwindow* Engine::GetWindow() { GLFWwindow* Engine::GetWindow() {
return window; return window;
} }
GLuint Engine::GetShader() {
return shaderProgram;
}
void Engine::ResizeFramebuffer(int width, int height) { void Engine::ResizeFramebuffer(int width, int height) {
// Avoid division by zero. // Avoid division by zero.
@ -157,7 +160,8 @@ GLuint Engine::CompileShader(const char* vertexSrc, const char* fragmentSrc) {
// SetupScene now creates the shader program used for all models. // SetupScene now creates the shader program used for all models.
// It no longer creates cube-specific VAOs, since ModelComponent will store mesh data. // It no longer creates cube-specific VAOs, since ModelComponent will store mesh data.
bool Engine::SetupScene() { bool Engine::SetupScene() {
// Vertex shader: passes through vertex attributes.
const char* vertexShaderSrc = R"( const char* vertexShaderSrc = R"(
#version 330 core #version 330 core
layout(location = 0) in vec3 aPos; layout(location = 0) in vec3 aPos;
@ -183,9 +187,7 @@ bool Engine::SetupScene() {
} }
)"; )";
// Fragment shader: uses a normal map and material properties.
const char* fragmentShaderSrc = R"( const char* fragmentShaderSrc = R"(
// Fragment shader:
#version 330 core #version 330 core
out vec4 FragColor; out vec4 FragColor;
@ -200,7 +202,7 @@ uniform int numLights;
uniform vec3 viewPos; uniform vec3 viewPos;
uniform sampler2D diffuseTexture; uniform sampler2D diffuseTexture;
uniform sampler2D normalMap; uniform sampler2D normalMap;
uniform bool useNormalMap; // NEW uniform to control normal mapping uniform bool useNormalMap; // Control flag for normal mapping
// Material properties. // Material properties.
uniform vec3 materialDiffuse; uniform vec3 materialDiffuse;
@ -208,31 +210,32 @@ uniform vec3 materialSpecular;
uniform float materialShininess; uniform float materialShininess;
void main() { void main() {
vec3 perturbedNormal = normalize(Normal); vec3 finalNormal;
if(useNormalMap) { if(useNormalMap) {
// Sample normal map. // Sample and transform normal map.
vec3 normMap = texture(normalMap, TexCoords).rgb; vec3 normMap = texture(normalMap, TexCoords).rgb;
normMap = normalize(normMap * 2.0 - 1.0); normMap = normalize(normMap * 2.0 - 1.0);
// Flip Z if needed.
normMap.z = -normMap.z; normMap.z = -normMap.z;
// Calculate tangent space basis.
vec3 T = normalize(Tangent); vec3 T = normalize(Tangent);
vec3 B = normalize(cross(Normal, T)); vec3 B = normalize(cross(Normal, T));
mat3 TBN = mat3(T, B, normalize(Normal)); mat3 TBN = mat3(T, B, normalize(Normal));
perturbedNormal = normalize(TBN * normMap); finalNormal = normalize(TBN * normMap);
} else {
// Compute a flat normal from screen-space derivatives.
finalNormal = normalize(cross(dFdx(FragPos), dFdy(FragPos)));
} }
vec3 diffuseTex = texture(diffuseTexture, TexCoords).rgb; vec3 diffuseTex = texture(diffuseTexture, TexCoords).rgb;
vec3 ambient = 0.1 * materialDiffuse * diffuseTex; vec3 ambient = 0.1 * materialDiffuse * diffuseTex;
vec3 lighting = ambient; vec3 lighting = ambient;
for(int i = 0; i < numLights; i++) { for(int i = 0; i < numLights; i++) {
vec3 lightDir = normalize(lightPositions[i] - FragPos); vec3 lightDir = normalize(lightPositions[i] - FragPos);
float diff = max(dot(perturbedNormal, lightDir), 0.0); float diff = max(dot(finalNormal, lightDir), 0.0);
vec3 diffuse = diff * materialDiffuse * diffuseTex * lightColors[i]; vec3 diffuse = diff * materialDiffuse * diffuseTex * lightColors[i];
vec3 viewDir = normalize(viewPos - FragPos); vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, perturbedNormal); vec3 reflectDir = reflect(-lightDir, finalNormal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), materialShininess); float spec = pow(max(dot(viewDir, reflectDir), 0.0), materialShininess);
vec3 specular = materialSpecular * spec * lightColors[i]; vec3 specular = materialSpecular * spec * lightColors[i];
@ -240,9 +243,11 @@ void main() {
} }
FragColor = vec4(lighting, 1.0); FragColor = vec4(lighting, 1.0);
} }
)"; )";
shaderProgram = CompileShader(vertexShaderSrc, fragmentShaderSrc); shaderProgram = CompileShader(vertexShaderSrc, fragmentShaderSrc);
if (shaderProgram == 0) { if (shaderProgram == 0) {
return false; return false;

View File

@ -0,0 +1,168 @@
#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();
}

View File

@ -0,0 +1,47 @@
#ifndef ASSET_MANAGER_H
#define ASSET_MANAGER_H
#include <string>
#include <unordered_map>
#include <vector>
#include <map>
#include <GL/glew.h>
#include <glm/glm.hpp>
class SubMesh;
// Structure holding raw model data (geometry and material information) without OpenGL buffers.
struct ModelAsset {
std::vector<SubMesh> meshes;
std::string modelPath;
};
class AssetManager {
public:
// Get the singleton instance.
static AssetManager& Get();
// Loads a model from file and caches the raw geometry and material data.
// Returns true if successful, populating outAsset.
bool LoadModel(const std::string &filepath, ModelAsset &outAsset);
// Loads a texture from file and caches it.
// textureID will be set to the loaded texture's OpenGL ID.
bool LoadTexture(const std::string &filepath, GLuint &textureID);
// Clears caches. (Optionally delete textures via glDeleteTextures if needed.)
void Clear();
private:
// Private constructor for singleton.
AssetManager() {}
// Disable copy constructor and assignment operator.
AssetManager(const AssetManager&) = delete;
AssetManager& operator=(const AssetManager&) = delete;
// Caches.
std::unordered_map<std::string, ModelAsset> modelCache;
std::unordered_map<std::string, GLuint> textureCache;
};
#endif // ASSET_MANAGER_H

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -42,6 +42,11 @@ Size=245,76
Collapsed=0 Collapsed=0
DockId=0x00000007,0 DockId=0x00000007,0
[Window][Edit Model]
Pos=246,-1
Size=886,794
Collapsed=0
[Docking][Data] [Docking][Data]
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,0 Size=1280,800 Split=X Selected=0xB6999AB4 DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,0 Size=1280,800 Split=X Selected=0xB6999AB4
DockNode ID=0x00000005 Parent=0x08BD597D SizeRef=245,800 Split=Y Selected=0x5A1EAB5B DockNode ID=0x00000005 Parent=0x08BD597D SizeRef=245,800 Split=Y Selected=0x5A1EAB5B