Added Model GUI to load models
This commit is contained in:
parent
073288fc63
commit
dfbbb65c93
@ -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")) {
|
||||||
|
@ -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 ¤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<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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
168
Engine/utils/AssetManager.cpp
Normal file
168
Engine/utils/AssetManager.cpp
Normal 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 ¤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<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();
|
||||||
|
}
|
47
Engine/utils/AssetManager.h
Normal file
47
Engine/utils/AssetManager.h
Normal 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
|
BIN
Three-Labs.exe
BIN
Three-Labs.exe
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/Engine/utils/AssetManager.o
Normal file
BIN
build/Engine/utils/AssetManager.o
Normal file
Binary file not shown.
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user