diff --git a/Editor/editor.cpp b/Editor/editor.cpp index 7fa5d03..4e381c8 100644 --- a/Editor/editor.cpp +++ b/Editor/editor.cpp @@ -14,6 +14,7 @@ #include "stb_image.h" #include #include +#include "../Engine/Components/Component.h" // For interface base using std::vector; @@ -147,6 +148,10 @@ int main() { Entity* selectedEntity = nullptr; int selectedIndex = -1; + // Variables for the "Change Model" popup. + static bool showModelPopup = false; + static char newModelPath[256] = ""; + float lastFrameTime = (float)glfwGetTime(); while (!glfwWindowShouldClose(Engine::GetWindow())) { float currentFrameTime = (float)glfwGetTime(); @@ -173,18 +178,45 @@ int main() { ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode); - // Left Panel: Entity List. + // Left Panel: Entity List with Add/Remove buttons. ImGui::Begin("Entity List"); for (int i = 0; i < entities.size(); i++) { char label[32]; sprintf(label, "Entity %d", i); - if (ImGui::Selectable(label, selectedIndex == i)) - selectedIndex = i; - if (ImGui::IsItemClicked()) { + if (ImGui::Selectable(label, selectedIndex == i)) { selectedEntity = entities[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(); // Right Panel: Entity Inspector. @@ -200,6 +232,12 @@ int main() { ImGui::ColorEdit3("Diffuse", glm::value_ptr(selectedEntity->modelComponent->diffuseColor)); ImGui::ColorEdit3("Specular", glm::value_ptr(selectedEntity->modelComponent->specularColor)); 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) { ImGui::Separator(); @@ -212,6 +250,47 @@ int main() { } 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). ImGui::Begin("Scene File"); if (ImGui::Button("Save")) { diff --git a/Engine/Components/ModelComponent.cpp b/Engine/Components/ModelComponent.cpp index d4739e4..8eda6ee 100644 --- a/Engine/Components/ModelComponent.cpp +++ b/Engine/Components/ModelComponent.cpp @@ -1,12 +1,14 @@ #include "ModelComponent.h" #include "stb_image.h" -#define TINYOBJLOADER_IMPLEMENTATION -#include "tiny_obj_loader.h" +#include "../Engine.h" + #include #include #include #include +#include "../utils/AssetManager.h" +#include // Constructor: initialize default values. ModelComponent::ModelComponent() @@ -20,15 +22,17 @@ ModelComponent::ModelComponent() } ModelComponent::~ModelComponent() { - // Clean up each submesh. + // Clean up each submesh's OpenGL buffers. for (auto &mesh : meshes) { - if(mesh.VAO) glDeleteVertexArrays(1, &mesh.VAO); - if(mesh.VBO) glDeleteBuffers(1, &mesh.VBO); - if(mesh.EBO) glDeleteBuffers(1, &mesh.EBO); - if(mesh.diffuseTexture) glDeleteTextures(1, &mesh.diffuseTexture); + if (mesh.VAO) glDeleteVertexArrays(1, &mesh.VAO); + if (mesh.VBO) glDeleteBuffers(1, &mesh.VBO); + if (mesh.EBO) glDeleteBuffers(1, &mesh.EBO); + // 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) { glGenVertexArrays(1, &mesh.VAO); glGenBuffers(1, &mesh.VBO); @@ -86,123 +90,39 @@ bool ModelComponent::LoadNormalTexture(const std::string &filepath) { // Loads a model using tiny_obj_loader and groups faces by material. bool ModelComponent::LoadModel(const std::string &filepath) { - - std::cout << "[Info] Loading Model \'" << filepath << "\' " << std::endl; - modelPath = filepath; - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector 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; + // Use the AssetManager to load the raw model data. + ModelAsset asset; + if (!AssetManager::Get().LoadModel(filepath, asset)) return false; - } + + // Save the model path. + modelPath = asset.modelPath; // Clear any existing submeshes. meshes.clear(); - // For each shape in the OBJ file. - 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; - // Iterate over each face. - for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { - 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 ¤tMesh = 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(currentMesh.vertices.size() - 1)); - } - index_offset += fv; - } - - // For each group (submesh) in this shape, set material properties and create buffers. - for (auto &pair : submeshMap) { - int matID = pair.first; - SubMesh &subMesh = pair.second; - // If there is a valid material, assign its properties. - 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; - LoadDiffuseTexture(texturePath, subMesh.diffuseTexture); - } - } - // Setup the OpenGL buffers for this submesh. - SetupMesh(subMesh); - // Add submesh to our model. - meshes.push_back(subMesh); - } + // Copy the cached raw submesh data. + // Note: We do not copy OpenGL buffers (VAO, VBO, EBO) since they are instance-specific. + for (const SubMesh &cachedMesh : asset.meshes) { + SubMesh mesh = cachedMesh; + // Ensure buffer IDs are reset. + mesh.VAO = mesh.VBO = mesh.EBO = 0; + meshes.push_back(mesh); + } + + for (auto &mesh : meshes) { + SetupMesh(mesh); } - std::cout << "[Done] Loaded Model \'" << filepath << "\' " << std::endl; return true; } -// Draws the entire model by drawing each submesh. + void ModelComponent::Draw() { + // Render each submesh. for (auto &mesh : meshes) { - // Bind the appropriate texture if available. - if(mesh.diffuseTexture) { + // Bind the diffuse texture if available. + if (mesh.diffuseTexture) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, mesh.diffuseTexture); } @@ -211,3 +131,4 @@ void ModelComponent::Draw() { glBindVertexArray(0); } } + diff --git a/Engine/Engine.h b/Engine/Engine.h index 9b73c37..a4e7328 100644 --- a/Engine/Engine.h +++ b/Engine/Engine.h @@ -27,6 +27,8 @@ public: static bool Init(); // Returns the main GLFW window. static GLFWwindow* GetWindow(); + static GLuint GetShader(); + // Clean up resources and shutdown GLFW. static void Shutdown(); diff --git a/Engine/engine.cpp b/Engine/engine.cpp index edcbaa7..66972d7 100644 --- a/Engine/engine.cpp +++ b/Engine/engine.cpp @@ -63,6 +63,9 @@ bool Engine::Init() { GLFWwindow* Engine::GetWindow() { return window; } +GLuint Engine::GetShader() { + return shaderProgram; +} void Engine::ResizeFramebuffer(int width, int height) { // 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. // It no longer creates cube-specific VAOs, since ModelComponent will store mesh data. bool Engine::SetupScene() { - // Vertex shader: passes through vertex attributes. + + const char* vertexShaderSrc = R"( #version 330 core layout(location = 0) in vec3 aPos; @@ -183,65 +187,66 @@ bool Engine::SetupScene() { } )"; - // Fragment shader: uses a normal map and material properties. const char* fragmentShaderSrc = R"( - // Fragment shader: -#version 330 core -out vec4 FragColor; - -in vec3 FragPos; -in vec3 Normal; -in vec2 TexCoords; -in vec3 Tangent; - -uniform vec3 lightPositions[2]; -uniform vec3 lightColors[2]; -uniform int numLights; -uniform vec3 viewPos; -uniform sampler2D diffuseTexture; -uniform sampler2D normalMap; -uniform bool useNormalMap; // NEW uniform to control normal mapping - -// Material properties. -uniform vec3 materialDiffuse; -uniform vec3 materialSpecular; -uniform float materialShininess; - -void main() { - vec3 perturbedNormal = normalize(Normal); - if(useNormalMap) { - // Sample normal map. - vec3 normMap = texture(normalMap, TexCoords).rgb; - normMap = normalize(normMap * 2.0 - 1.0); - // Flip Z if needed. - normMap.z = -normMap.z; - // Calculate tangent space basis. - vec3 T = normalize(Tangent); - vec3 B = normalize(cross(Normal, T)); - mat3 TBN = mat3(T, B, normalize(Normal)); - perturbedNormal = normalize(TBN * normMap); - } + #version 330 core + out vec4 FragColor; - vec3 diffuseTex = texture(diffuseTexture, TexCoords).rgb; - - vec3 ambient = 0.1 * materialDiffuse * diffuseTex; - vec3 lighting = ambient; - for(int i = 0; i < numLights; i++) { - vec3 lightDir = normalize(lightPositions[i] - FragPos); - float diff = max(dot(perturbedNormal, lightDir), 0.0); - vec3 diffuse = diff * materialDiffuse * diffuseTex * lightColors[i]; - - vec3 viewDir = normalize(viewPos - FragPos); - vec3 reflectDir = reflect(-lightDir, perturbedNormal); - float spec = pow(max(dot(viewDir, reflectDir), 0.0), materialShininess); - vec3 specular = materialSpecular * spec * lightColors[i]; - - lighting += diffuse + specular; - } - FragColor = vec4(lighting, 1.0); -} - + in vec3 FragPos; + in vec3 Normal; + in vec2 TexCoords; + in vec3 Tangent; + + uniform vec3 lightPositions[2]; + uniform vec3 lightColors[2]; + uniform int numLights; + uniform vec3 viewPos; + uniform sampler2D diffuseTexture; + uniform sampler2D normalMap; + uniform bool useNormalMap; // Control flag for normal mapping + + // Material properties. + uniform vec3 materialDiffuse; + uniform vec3 materialSpecular; + uniform float materialShininess; + + void main() { + vec3 finalNormal; + if(useNormalMap) { + // Sample and transform normal map. + vec3 normMap = texture(normalMap, TexCoords).rgb; + normMap = normalize(normMap * 2.0 - 1.0); + normMap.z = -normMap.z; + vec3 T = normalize(Tangent); + vec3 B = normalize(cross(Normal, T)); + mat3 TBN = mat3(T, B, normalize(Normal)); + 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 ambient = 0.1 * materialDiffuse * diffuseTex; + vec3 lighting = ambient; + + for(int i = 0; i < numLights; i++) { + vec3 lightDir = normalize(lightPositions[i] - FragPos); + float diff = max(dot(finalNormal, lightDir), 0.0); + vec3 diffuse = diff * materialDiffuse * diffuseTex * lightColors[i]; + + vec3 viewDir = normalize(viewPos - FragPos); + vec3 reflectDir = reflect(-lightDir, finalNormal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), materialShininess); + vec3 specular = materialSpecular * spec * lightColors[i]; + + lighting += diffuse + specular; + } + FragColor = vec4(lighting, 1.0); + } )"; + + + shaderProgram = CompileShader(vertexShaderSrc, fragmentShaderSrc); if (shaderProgram == 0) { diff --git a/Engine/utils/AssetManager.cpp b/Engine/utils/AssetManager.cpp new file mode 100644 index 0000000..5fb406d --- /dev/null +++ b/Engine/utils/AssetManager.cpp @@ -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 +#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(); +} diff --git a/Engine/utils/AssetManager.h b/Engine/utils/AssetManager.h new file mode 100644 index 0000000..cef1abb --- /dev/null +++ b/Engine/utils/AssetManager.h @@ -0,0 +1,47 @@ +#ifndef ASSET_MANAGER_H +#define ASSET_MANAGER_H + +#include +#include +#include +#include +#include +#include +class SubMesh; + +// Structure holding raw model data (geometry and material information) without OpenGL buffers. +struct ModelAsset { + std::vector 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 modelCache; + std::unordered_map textureCache; +}; + +#endif // ASSET_MANAGER_H diff --git a/Three-Labs.exe b/Three-Labs.exe index 22e15bc..340a6bb 100644 Binary files a/Three-Labs.exe and b/Three-Labs.exe differ diff --git a/build/Editor/editor.o b/build/Editor/editor.o index 5365a25..936a721 100644 Binary files a/build/Editor/editor.o and b/build/Editor/editor.o differ diff --git a/build/Engine/Components/ModelComponent.o b/build/Engine/Components/ModelComponent.o index 9319fa5..ee72c17 100644 Binary files a/build/Engine/Components/ModelComponent.o and b/build/Engine/Components/ModelComponent.o differ diff --git a/build/Engine/engine.o b/build/Engine/engine.o index 0c643e9..c03ad28 100644 Binary files a/build/Engine/engine.o and b/build/Engine/engine.o differ diff --git a/build/Engine/utils/AssetManager.o b/build/Engine/utils/AssetManager.o new file mode 100644 index 0000000..a65ed98 Binary files /dev/null and b/build/Engine/utils/AssetManager.o differ diff --git a/imgui.ini b/imgui.ini index adfc07f..fa3c9fe 100644 --- a/imgui.ini +++ b/imgui.ini @@ -42,6 +42,11 @@ Size=245,76 Collapsed=0 DockId=0x00000007,0 +[Window][Edit Model] +Pos=246,-1 +Size=886,794 +Collapsed=0 + [Docking][Data] 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