diff --git a/Editor/editor.cpp b/Editor/editor.cpp index b4348ad..7fa5d03 100644 --- a/Editor/editor.cpp +++ b/Editor/editor.cpp @@ -12,6 +12,8 @@ #include #include #include "stb_image.h" +#include +#include using std::vector; @@ -46,7 +48,6 @@ bool LoadIconTexture(const char* filepath, GLuint &texOut, ImTextureID &imTexOut return true; } - // Editor camera. struct EditorCamera { glm::vec3 position = glm::vec3(0.0f, 0.0f, 8.0f); @@ -166,8 +167,6 @@ int main() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - - ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); @@ -213,6 +212,59 @@ int main() { } ImGui::End(); + // New Panel: File Operations (Save/Load). + ImGui::Begin("Scene File"); + if (ImGui::Button("Save")) { + YAML::Emitter out; + out << YAML::BeginMap; + // Save an array of entities. + out << YAML::Key << "entities" << YAML::Value << YAML::BeginSeq; + for (auto e : entities) { + out << YAML::BeginMap; + // Save the type. + out << YAML::Key << "type" << YAML::Value << (e->GetType() == EntityType::CUBE ? "cube" : "light"); + // Save transform. + out << YAML::Key << "transform" << YAML::Value << e->transform.Save(); + // Save model component if cube. + if (e->GetType() == EntityType::CUBE && e->modelComponent) { + out << YAML::Key << "model" << YAML::Value << e->modelComponent->Save(); + } + // Save light component if light. + if (e->GetType() == EntityType::LIGHT && e->lightComponent) { + out << YAML::Key << "light" << YAML::Value << e->lightComponent->Save(); + } + out << YAML::EndMap; + } + out << YAML::EndSeq; + out << YAML::EndMap; + + std::ofstream fout("default.yaml"); + fout << out.c_str(); + fout.close(); + } + if (ImGui::Button("Load")) { + YAML::Node node = YAML::LoadFile("default.yaml"); + if (node["entities"]) { + YAML::Node entitiesNode = node["entities"]; + int idx = 0; + for (auto entityNode : entitiesNode) { + if (idx < entities.size()) { + // Load transform. + if (entityNode["transform"]) + entities[idx]->transform.Load(entityNode["transform"]); + // Load model for cubes. + if (entities[idx]->GetType() == EntityType::CUBE && entityNode["model"]) + entities[idx]->modelComponent->Load(entityNode["model"]); + // Load light for lights. + if (entities[idx]->GetType() == EntityType::LIGHT && entityNode["light"]) + entities[idx]->lightComponent->Load(entityNode["light"]); + } + idx++; + } + } + } + ImGui::End(); + // Bottom Panel: Rendered Output. ImGui::Begin("Rendered Output"); ImVec2 viewportSize = ImGui::GetContentRegionAvail(); diff --git a/Engine/Components/Camera.h b/Engine/Components/Camera.h index 251fbff..90bcf95 100644 --- a/Engine/Components/Camera.h +++ b/Engine/Components/Camera.h @@ -4,6 +4,7 @@ #include "Component.h" #include #include +#include // A simple Camera component that inherits from Component. class Camera : public Component { @@ -32,6 +33,46 @@ public: float GetYaw() const; float GetPitch() const; + // Save the camera state to a YAML node. + virtual YAML::Node Save() const override { + YAML::Node node; + node["position"] = YAML::Node(); + node["position"].push_back(position.x); + node["position"].push_back(position.y); + node["position"].push_back(position.z); + + node["yaw"] = yaw; + node["pitch"] = pitch; + node["fov"] = fov; + node["aspect"] = aspect; + node["nearPlane"] = nearPlane; + node["farPlane"] = farPlane; + return node; + } + + // Load the camera state from a YAML node. + virtual void Load(const YAML::Node &node) override { + if (node["position"]) { + position.x = node["position"][0].as(); + position.y = node["position"][1].as(); + position.z = node["position"][2].as(); + } + if (node["yaw"]) + yaw = node["yaw"].as(); + if (node["pitch"]) + pitch = node["pitch"].as(); + if (node["fov"]) + fov = node["fov"].as(); + if (node["aspect"]) + aspect = node["aspect"].as(); + if (node["nearPlane"]) + nearPlane = node["nearPlane"].as(); + if (node["farPlane"]) + farPlane = node["farPlane"].as(); + } + + // (Other Camera methods remain unchanged.) + private: glm::vec3 position; float yaw; diff --git a/Engine/Components/Component.h b/Engine/Components/Component.h index 6077fcf..6f678db 100644 --- a/Engine/Components/Component.h +++ b/Engine/Components/Component.h @@ -1,12 +1,19 @@ #ifndef COMPONENT_H #define COMPONENT_H -// Base Component class for all components. +#include + class Component { public: virtual ~Component() {} + // Update the component each frame. virtual void Update(float deltaTime) = 0; + + // Save the component's state to a YAML node. + virtual YAML::Node Save() const = 0; + // Load the component's state from a YAML node. + virtual void Load(const YAML::Node &node) = 0; }; #endif // COMPONENT_H diff --git a/Engine/Components/LightComponent.h b/Engine/Components/LightComponent.h index 61ea366..21fe470 100644 --- a/Engine/Components/LightComponent.h +++ b/Engine/Components/LightComponent.h @@ -2,6 +2,7 @@ #define LIGHT_COMPONENT_H #include +#include class LightComponent { public: @@ -11,6 +12,28 @@ public: // Light properties. glm::vec3 color; float intensity; + + // Save this light to a YAML node. + YAML::Node Save() const { + YAML::Node node; + node["color"] = YAML::Node(); + node["color"].push_back(color.x); + node["color"].push_back(color.y); + node["color"].push_back(color.z); + node["intensity"] = intensity; + return node; + } + + // Load this light from a YAML node. + void Load(const YAML::Node &node) { + if (node["color"]) { + color.x = node["color"][0].as(); + color.y = node["color"][1].as(); + color.z = node["color"][2].as(); + } + if (node["intensity"]) + intensity = node["intensity"].as(); + } }; #endif // LIGHT_COMPONENT_H diff --git a/Engine/Components/ModelComponent.cpp b/Engine/Components/ModelComponent.cpp index 672e9bc..d4739e4 100644 --- a/Engine/Components/ModelComponent.cpp +++ b/Engine/Components/ModelComponent.cpp @@ -86,6 +86,8 @@ 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; @@ -121,7 +123,6 @@ bool ModelComponent::LoadModel(const std::string &filepath) { size_t index_offset = 0; // Iterate over each face. for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { - std::cout << "[Info] AssetManager: Loading Mesh: "<< i << std::endl; int fv = shapes[s].mesh.num_face_vertices[f]; // Get material ID for this face. int matID = -1; @@ -192,6 +193,7 @@ bool ModelComponent::LoadModel(const std::string &filepath) { meshes.push_back(subMesh); } } + std::cout << "[Done] Loaded Model \'" << filepath << "\' " << std::endl; return true; } diff --git a/Engine/Components/ModelComponent.h b/Engine/Components/ModelComponent.h index 57c5f9d..da6f35f 100644 --- a/Engine/Components/ModelComponent.h +++ b/Engine/Components/ModelComponent.h @@ -1,10 +1,11 @@ #ifndef MODEL_COMPONENT_H #define MODEL_COMPONENT_H -#include #include #include #include +#include +#include // Structure representing a single vertex. struct Vertex { @@ -22,6 +23,8 @@ struct SubMesh { glm::vec3 specularColor; float shininess; GLuint diffuseTexture; + // (Optional) If you add a normal texture per submesh. + // GLuint normalTexture; GLuint VAO, VBO, EBO; SubMesh() @@ -38,11 +41,16 @@ public: ModelComponent(); ~ModelComponent(); - // Base model path. + // Base model file path. std::string modelPath; - // Submeshes + // Submeshes. std::vector meshes; + // Global material overrides (if any). + glm::vec3 diffuseColor; + glm::vec3 specularColor; + float shininess; + // Functions. bool LoadModel(const std::string &filepath); void Draw(); @@ -53,11 +61,49 @@ public: // Setup mesh for a submesh. void SetupMesh(SubMesh &mesh); - - // (Optional) Global material properties if needed. - glm::vec3 diffuseColor; - glm::vec3 specularColor; - float shininess; + + // --- YAML Serialization Functions (Globals Only) --- + // Save the global ModelComponent state to a YAML node. + YAML::Node Save() const { + YAML::Node node; + node["modelPath"] = modelPath; + + node["globalDiffuseColor"] = YAML::Node(); + node["globalDiffuseColor"].push_back(diffuseColor.x); + node["globalDiffuseColor"].push_back(diffuseColor.y); + node["globalDiffuseColor"].push_back(diffuseColor.z); + + node["globalSpecularColor"] = YAML::Node(); + node["globalSpecularColor"].push_back(specularColor.x); + node["globalSpecularColor"].push_back(specularColor.y); + node["globalSpecularColor"].push_back(specularColor.z); + + node["globalShininess"] = shininess; + + return node; + } + + // Load the global ModelComponent state from a YAML node. + void Load(const YAML::Node &node) { + if (node["modelPath"]) { + modelPath = node["modelPath"].as(); + } + if (node["globalDiffuseColor"]) { + diffuseColor.x = node["globalDiffuseColor"][0].as(); + diffuseColor.y = node["globalDiffuseColor"][1].as(); + diffuseColor.z = node["globalDiffuseColor"][2].as(); + } + if (node["globalSpecularColor"]) { + specularColor.x = node["globalSpecularColor"][0].as(); + specularColor.y = node["globalSpecularColor"][1].as(); + specularColor.z = node["globalSpecularColor"][2].as(); + } + if (node["globalShininess"]) { + shininess = node["globalShininess"].as(); + } + // Reload the model (populates meshes based on the modelPath). + LoadModel(modelPath); + } }; #endif // MODEL_COMPONENT_H diff --git a/Engine/Components/TransformComponent.h b/Engine/Components/TransformComponent.h index ce01316..220d39a 100644 --- a/Engine/Components/TransformComponent.h +++ b/Engine/Components/TransformComponent.h @@ -3,6 +3,7 @@ #include #include +#include class TransformComponent { public: @@ -15,6 +16,46 @@ public: // Returns the world transform matrix. glm::mat4 GetMatrix() const; + + // Save this transform to a YAML node. + YAML::Node Save() const { + YAML::Node node; + node["position"] = YAML::Node(); + node["position"].push_back(position.x); + node["position"].push_back(position.y); + node["position"].push_back(position.z); + + node["rotation"] = YAML::Node(); + node["rotation"].push_back(rotation.x); + node["rotation"].push_back(rotation.y); + node["rotation"].push_back(rotation.z); + + node["scale"] = YAML::Node(); + node["scale"].push_back(scale.x); + node["scale"].push_back(scale.y); + node["scale"].push_back(scale.z); + + return node; + } + + // Load this transform from a YAML node. + void Load(const YAML::Node &node) { + if (node["position"]) { + position.x = node["position"][0].as(); + position.y = node["position"][1].as(); + position.z = node["position"][2].as(); + } + if (node["rotation"]) { + rotation.x = node["rotation"][0].as(); + rotation.y = node["rotation"][1].as(); + rotation.z = node["rotation"][2].as(); + } + if (node["scale"]) { + scale.x = node["scale"][0].as(); + scale.y = node["scale"][1].as(); + scale.z = node["scale"][2].as(); + } + } }; #endif // TRANSFORM_COMPONENT_H diff --git a/Engine/engine.cpp b/Engine/engine.cpp index c806617..edcbaa7 100644 --- a/Engine/engine.cpp +++ b/Engine/engine.cpp @@ -185,55 +185,62 @@ bool Engine::SetupScene() { // Fragment shader: uses a normal map and material properties. const char* fragmentShaderSrc = R"( - #version 330 core - out vec4 FragColor; + // 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); + } - 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; - - // Material properties. - uniform vec3 materialDiffuse; - uniform vec3 materialSpecular; - uniform float materialShininess; - - void main() { - // Sample normal map. - vec3 normMap = texture(normalMap, TexCoords).rgb; - normMap = normalize(normMap * 2.0 - 1.0); - normMap.z = -normMap.z; - vec3 N = normalize(Normal); - vec3 T = normalize(Tangent); - vec3 B = normalize(cross(N, T)); - mat3 TBN = mat3(T, B, N); - vec3 perturbedNormal = normalize(TBN * normMap); - - 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); - } + 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); +} + )"; shaderProgram = CompileShader(vertexShaderSrc, fragmentShaderSrc); @@ -278,26 +285,36 @@ ImTextureID Engine::RenderScene(const glm::mat4 &view, const glm::mat4 &projecti } // Render each cube entity using its ModelComponent. - for (auto e : entities) { - if (e->GetType() == EntityType::CUBE && e->modelComponent) { - glm::mat4 modelMatrix = e->transform.GetMatrix(); - glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix)); - glUniform3fv(glGetUniformLocation(shaderProgram, "materialDiffuse"), 1, glm::value_ptr(e->modelComponent->diffuseColor)); - glUniform3fv(glGetUniformLocation(shaderProgram, "materialSpecular"), 1, glm::value_ptr(e->modelComponent->specularColor)); - glUniform1f(glGetUniformLocation(shaderProgram, "materialShininess"), e->modelComponent->shininess); - // Bind the diffuse texture to texture unit 0. +for (auto e : entities) { + if (e->GetType() == EntityType::CUBE && e->modelComponent) { + glm::mat4 modelMatrix = e->transform.GetMatrix(); + glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix)); + + // Loop through all submeshes in the model component. + for (const auto &mesh : e->modelComponent->meshes) { + // Set material properties for the current submesh. + glUniform3fv(glGetUniformLocation(shaderProgram, "materialDiffuse"), 1, glm::value_ptr(mesh.diffuseColor)); + glUniform3fv(glGetUniformLocation(shaderProgram, "materialSpecular"), 1, glm::value_ptr(mesh.specularColor)); + glUniform1f(glGetUniformLocation(shaderProgram, "materialShininess"), mesh.shininess); + + // Bind the diffuse texture. glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, e->modelComponent->diffuseTexture); + glBindTexture(GL_TEXTURE_2D, mesh.diffuseTexture); glUniform1i(glGetUniformLocation(shaderProgram, "diffuseTexture"), 0); - // Bind the normal texture to texture unit 1. - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, e->modelComponent->normalTexture); - glUniform1i(glGetUniformLocation(shaderProgram, "normalMap"), 1); - - // Draw the model. - e->modelComponent->Draw(); + + // If you have a normal texture, bind it similarly (adjust as needed). + // glActiveTexture(GL_TEXTURE1); + // glBindTexture(GL_TEXTURE_2D, mesh.normalTexture); + // glUniform1i(glGetUniformLocation(shaderProgram, "normalMap"), 1); + + // Bind the submesh's VAO and draw its elements. + glBindVertexArray(mesh.VAO); + glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_INT, 0); + glBindVertexArray(0); } } +} + glBindFramebuffer(GL_FRAMEBUFFER, 0); return (ImTextureID)(intptr_t)colorTexture; diff --git a/Makefile b/Makefile index db83e87..9b1ce7f 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CXX := g++ CXXFLAGS := -std=c++20 -Wall -Wextra -O2 -I/c/msys64/mingw64/include -Ivendor/imgui-docking -Ivendor/stb -Ivendor/tini_obj # Use this to link against the correct import lib -LDFLAGS := -Llib -lglfw3 -lopengl32 -lglew32 -lglu32 +LDFLAGS := -Llib -lglfw3 -lopengl32 -lglew32 -lglu32 -lyaml-cpp # Source and build directories (including vendor folder) SRC_DIRS := . Editor Engine vendor/imgui-docking Engine/Components Engine/Entity Engine/utils diff --git a/Three-Labs.exe b/Three-Labs.exe index eefdc55..22e15bc 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 edcb451..5365a25 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 f38c037..9319fa5 100644 Binary files a/build/Engine/Components/ModelComponent.o and b/build/Engine/Components/ModelComponent.o differ diff --git a/build/Engine/Entity/Entity.o b/build/Engine/Entity/Entity.o index 367471c..bd831e6 100644 Binary files a/build/Engine/Entity/Entity.o and b/build/Engine/Entity/Entity.o differ diff --git a/build/Engine/engine.o b/build/Engine/engine.o index 52770de..0c643e9 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 deleted file mode 100644 index d84313d..0000000 Binary files a/build/Engine/utils/AssetManager.o and /dev/null differ diff --git a/default.yaml b/default.yaml new file mode 100644 index 0000000..312b721 --- /dev/null +++ b/default.yaml @@ -0,0 +1,91 @@ +entities: + - type: cube + transform: + position: + - -2 + - 0 + - 0 + rotation: + - 0 + - 0 + - 0 + scale: + - 1 + - 1 + - 1 + model: + modelPath: ./assets/models/sponza.obj + globalDiffuseColor: + - 0.800000012 + - 0.200000003 + - 0.200000003 + globalSpecularColor: + - 1 + - 1 + - 1 + globalShininess: 32 + - type: cube + transform: + position: + - 2 + - 0 + - 0 + rotation: + - 0 + - 0 + - 0 + scale: + - 0.00999999978 + - 0.00999999978 + - 0.00999999978 + model: + modelPath: ./assets/models/sponza.obj + globalDiffuseColor: + - 0 + - 1 + - 0 + globalSpecularColor: + - 1 + - 1 + - 1 + globalShininess: 16 + - type: light + transform: + position: + - 0 + - 10 + - 0 + rotation: + - 0 + - 0 + - 0 + scale: + - 1 + - 1 + - 1 + light: + color: + - 1 + - 1 + - 1 + intensity: 1.5 + - type: light + transform: + position: + - 0 + - -10 + - 0 + rotation: + - 0 + - 0 + - 0 + scale: + - 1 + - 1 + - 1 + light: + color: + - 0.200000003 + - 0.200000003 + - 1 + intensity: 1 \ No newline at end of file diff --git a/imgui.ini b/imgui.ini index d0e8ff7..adfc07f 100644 --- a/imgui.ini +++ b/imgui.ini @@ -31,14 +31,22 @@ Collapsed=0 DockId=0x00000004,0 [Window][Entity List] -Pos=0,0 -Size=245,800 +Pos=0,78 +Size=245,722 Collapsed=0 -DockId=0x00000005,0 +DockId=0x00000008,0 + +[Window][Scene File] +Pos=0,0 +Size=245,76 +Collapsed=0 +DockId=0x00000007,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 Selected=0x5A1EAB5B + DockNode ID=0x00000005 Parent=0x08BD597D SizeRef=245,800 Split=Y Selected=0x5A1EAB5B + DockNode ID=0x00000007 Parent=0x00000005 SizeRef=245,76 Selected=0xE1A4FD08 + DockNode ID=0x00000008 Parent=0x00000005 SizeRef=245,722 Selected=0x5A1EAB5B DockNode ID=0x00000006 Parent=0x08BD597D SizeRef=1033,800 Split=X DockNode ID=0x00000003 Parent=0x00000006 SizeRef=772,800 Split=X DockNode ID=0x00000001 Parent=0x00000003 SizeRef=272,800 Selected=0x5098C5B2