diff options
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | Assets/Shaders/Sphere.glsl | 53 | ||||
| m--------- | Vendor/ImGuizmo | 0 | ||||
| -rw-r--r-- | premake5.lua | 48 | ||||
| -rw-r--r-- | src/Core/Application.cpp | 21 | ||||
| -rw-r--r-- | src/Core/SettingsManager.cpp | 2 | ||||
| -rw-r--r-- | src/States/ConfigState.cpp | 38 | ||||
| -rw-r--r-- | src/States/WorldBuilderState.cpp | 678 | ||||
| -rw-r--r-- | src/States/WorldBuilderState.h | 65 |
9 files changed, 886 insertions, 22 deletions
diff --git a/.gitmodules b/.gitmodules index 5ef952d..09f0a03 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "Vendor/toml11"] path = Vendor/toml11 url = https://github.com/ToruNiina/toml11 +[submodule "Vendor/ImGuizmo"] + path = Vendor/ImGuizmo + url = https://github.com/CedricGuillemet/ImGuizmo.git diff --git a/Assets/Shaders/Sphere.glsl b/Assets/Shaders/Sphere.glsl new file mode 100644 index 0000000..37757b5 --- /dev/null +++ b/Assets/Shaders/Sphere.glsl @@ -0,0 +1,53 @@ +#type vertex + +#version 330 core + +layout(location = 0) in vec3 a_Position; +layout(location = 1) in vec3 a_Normal; + +uniform mat4 u_ViewProjection; +uniform mat4 u_Transform; + +out vec3 v_Normal; +out vec3 v_WorldPos; + +void main() +{ + v_WorldPos = vec3(u_Transform * vec4(a_Position, 1.0)); + v_Normal = mat3(transpose(inverse(u_Transform))) * a_Normal; + gl_Position = u_ViewProjection * vec4(v_WorldPos, 1.0); +} + +#type fragment + +#version 330 core + +in vec3 v_Normal; +in vec3 v_WorldPos; + +uniform vec3 u_Color; +uniform float u_Specular; +uniform float u_Emission; +uniform vec3 u_LightPos; +uniform vec3 u_CameraPos; + +out vec4 o_FragColor; + +void main() +{ + vec3 normal = normalize(v_Normal); + vec3 lightDir = normalize(u_LightPos - v_WorldPos); + vec3 viewDir = normalize(u_CameraPos - v_WorldPos); + vec3 reflectDir = reflect(-lightDir, normal); + + float diff = max(dot(normal, lightDir), 0.0); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); + + vec3 ambient = u_Color * 0.1; + vec3 diffuse = u_Color * diff; + vec3 specular = vec3(u_Specular) * spec; + vec3 emission = u_Color * u_Emission; + + vec3 result = ambient + diffuse + specular + emission; + o_FragColor = vec4(result, 1.0); +} diff --git a/Vendor/ImGuizmo b/Vendor/ImGuizmo new file mode 160000 +Subproject 3e725628fa64986c71ebc7c764621a8e7423fef diff --git a/premake5.lua b/premake5.lua index 576d49b..adeb0c0 100644 --- a/premake5.lua +++ b/premake5.lua @@ -29,6 +29,7 @@ IncludeDir["glfw"] = "Vendor/glfw/include" IncludeDir["glad"] = "Vendor/glad/include" IncludeDir["imgui"] = "Vendor/imgui" IncludeDir["imgui_backends"] = "Vendor/imgui/backends" +IncludeDir["imguizmo"] = "Vendor/ImGuizmo" IncludeDir["toml11"] = "Vendor/toml11/include" group "Dependencies" @@ -244,6 +245,49 @@ project "ImGui" runtime "Release" optimize "on" symbols "off" + +project "ImGuizmo" + kind "StaticLib" + language "C++" + staticruntime "on" + + targetdir ("bin/" .. outputdir) + objdir ("bin-int/" .. outputdir) + + files + { + "Vendor/ImGuizmo/ImGuizmo.cpp", + "Vendor/ImGuizmo/ImGuizmo.h" + } + + includedirs + { + "%{IncludeDir.imgui}", + "%{IncludeDir.imguizmo}" + } + + filter "system:windows" + systemversion "latest" + cppdialect "C++17" + + filter "system:linux" + pic "On" + systemversion "latest" + cppdialect "C++17" + + filter "configurations:Debug" + runtime "Debug" + symbols "on" + + filter "configurations:Release" + runtime "Release" + optimize "on" + + filter "configurations:Dist" + runtime "Release" + optimize "on" + symbols "off" + group "" project "Donut" @@ -285,6 +329,7 @@ project "Donut" "%{IncludeDir.glad}", "%{IncludeDir.imgui}", "%{IncludeDir.imgui_backends}", + "%{IncludeDir.imguizmo}", "%{IncludeDir.toml11}", } @@ -292,7 +337,8 @@ project "Donut" { "GLFW", "GLAD", - "ImGui" + "ImGui", + "ImGuizmo" } filter "system:windows" diff --git a/src/Core/Application.cpp b/src/Core/Application.cpp index b879535..62f1faa 100644 --- a/src/Core/Application.cpp +++ b/src/Core/Application.cpp @@ -5,9 +5,10 @@ #include "States/SimulationState.h" #include "States/ConfigState.h" +#include "States/WorldBuilderState.h" #include <GLFW/glfw3.h> - +#include <ImGuizmo.h> namespace Donut { @@ -88,6 +89,12 @@ namespace Donut event.Handled = true; return true; } + else if (e.GetKeyCode() == GLFW_KEY_3) + { + m_StateManager->SwitchToState("WorldBuilder"); + event.Handled = true; + return true; + } return true; }); @@ -99,10 +106,8 @@ namespace Donut { Logger::Init(); - // Initialize settings manager first SettingsManager::Initialize(); - // Apply loaded settings const auto& settings = SettingsManager::GetSettingsConst(); RendererAPI::API api = (settings.graphics.renderAPI == "Vulkan") ? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL; RendererAPI::SetAPI(api); @@ -114,8 +119,9 @@ namespace Donut RenderCommand::SetFaceCulling(false); m_StateManager = CreateScope<StateManager>(); - m_StateManager->RegisterState("Config", CreateScope<ConfigState>()); - m_StateManager->RegisterState("Simulation", CreateScope<SimulationState>()); + m_StateManager->RegisterState("Config", CreateScope<ConfigState>()); + m_StateManager->RegisterState("Simulation", CreateScope<SimulationState>()); + m_StateManager->RegisterState("WorldBuilder", CreateScope<WorldBuilderState>()); m_StateManager->SwitchToState("Config"); } @@ -141,8 +147,11 @@ namespace Donut void Application::OnRender() { m_StateManager->Render(); - m_Window->BeginImGuiFrame(); + + ImGuizmo::BeginFrame(); + ImGuizmo::SetOrthographic(false); + SetupDockingLayout(); m_StateManager->OnImUIRender(); m_Window->EndImGuiFrame(); diff --git a/src/Core/SettingsManager.cpp b/src/Core/SettingsManager.cpp index eed2a3f..af163e7 100644 --- a/src/Core/SettingsManager.cpp +++ b/src/Core/SettingsManager.cpp @@ -138,7 +138,9 @@ namespace Donut DONUT_INFO("Settings saved to {}", filePath); } catch (const std::exception& e) + { DONUT_ERROR("Failed to save settings: {}", e.what()); + } } void SettingsManager::SetSimulationSettings(const SimulationSettings& settings) diff --git a/src/States/ConfigState.cpp b/src/States/ConfigState.cpp index 1470c6d..69bb2f1 100644 --- a/src/States/ConfigState.cpp +++ b/src/States/ConfigState.cpp @@ -16,20 +16,20 @@ namespace Donut const auto& settings = SettingsManager::GetSettingsConst(); - m_SelectedAPI = (settings.graphics.renderAPI == "Vulkan") ? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL; - m_SelectedTheme = (settings.graphics.selectedTheme == "Light") ? 1 : - (settings.graphics.selectedTheme == "Blue") ? 2 : 0; - m_TargetFPS = settings.simulation.targetFPS; - m_ComputeHeight = settings.simulation.computeHeight; - m_MaxStepsMoving = settings.simulation.maxStepsMoving; - m_MaxStepsStatic = settings.simulation.maxStepsStatic; - m_EarlyExitDistance = settings.simulation.earlyExitDistance; - m_GravityEnabled = settings.simulation.gravityEnabled; - m_VSyncEnabled = settings.graphics.vSyncEnabled; - m_ShowFPS = settings.graphics.showFPS; + m_SelectedAPI = (settings.graphics.renderAPI == "Vulkan") ? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL; + m_SelectedTheme = (settings.graphics.selectedTheme == "Light") ? 1 : + (settings.graphics.selectedTheme == "Blue") ? 2 : 0; + m_TargetFPS = settings.simulation.targetFPS; + m_ComputeHeight = settings.simulation.computeHeight; + m_MaxStepsMoving = settings.simulation.maxStepsMoving; + m_MaxStepsStatic = settings.simulation.maxStepsStatic; + m_EarlyExitDistance = settings.simulation.earlyExitDistance; + m_GravityEnabled = settings.simulation.gravityEnabled; + m_VSyncEnabled = settings.graphics.vSyncEnabled; + m_ShowFPS = settings.graphics.showFPS; m_ShowPerformanceMetrics = settings.graphics.showPerformanceMetrics; - m_ShowDebugInfo = settings.graphics.showDebugInfo; - m_EnableAntiAliasing = settings.graphics.enableAntiAliasing; + m_ShowDebugInfo = settings.graphics.showDebugInfo; + m_EnableAntiAliasing = settings.graphics.enableAntiAliasing; } void ConfigState::OnExit() @@ -52,7 +52,7 @@ namespace Donut void ConfigState::OnRender() { - Renderer::SetClearColor({ 0.05f, 0.05f, 0.1f, 1.0f }); + Renderer::SetClearColor({ 0.1f, 0.1f, 0.1f, 1.0f }); Renderer::Clear(); } @@ -104,6 +104,7 @@ namespace Donut if (isSelected) ImGui::SetItemDefaultFocus(); } + ImGui::EndCombo(); } @@ -215,7 +216,7 @@ namespace Donut ImGui::Separator(); ImGui::Spacing(); - float buttonWidth = (ImGui::GetWindowWidth() - 100) / 5.0f; + float buttonWidth = (ImGui::GetWindowWidth() - 120) / 6.0f; if (ImGui::Button("Start Simulation", ImVec2(buttonWidth, 35))) { @@ -224,6 +225,13 @@ namespace Donut } ImGui::SameLine(); + if (ImGui::Button("World Builder", ImVec2(buttonWidth, 35))) + { + ApplySettings(); + Application::Get().GetStateManager().SwitchToState("WorldBuilder"); + } + + ImGui::SameLine(); if (ImGui::Button("Reset to Defaults", ImVec2(buttonWidth, 35))) ResetToDefaults(); diff --git a/src/States/WorldBuilderState.cpp b/src/States/WorldBuilderState.cpp new file mode 100644 index 0000000..807038d --- /dev/null +++ b/src/States/WorldBuilderState.cpp @@ -0,0 +1,678 @@ +#include "WorldBuilderState.h" + +#include "Core/Application.h" +#include "Core/Window.h" + +#include "Rendering/Renderer.h" +#include "Rendering/Shader.h" +#include "Rendering/VertexArray.h" +#include "Rendering/VertexBuffer.h" +#include "Rendering/IndexBuffer.h" +#include "Rendering/Texture.h" + +#include <imgui.h> +#include <ImGuizmo.h> +#include <GLFW/glfw3.h> + +#include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/type_ptr.hpp> + +#include <fstream> +#include <sstream> +#include <limits> + +#define _USE_MATH_DEFINES +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +// TODO(Hachem): Remove constant shader and object creation in main loop +// TODO(Hachem): Change selection appearance +// TODO(Hachem): JSON Scene saving and loading + +namespace Donut +{ + void WorldBuilderState::OnEnter() + { + DONUT_INFO("Entering World Builder State"); + + ImGuizmo::Enable(true); + + m_Camera.SetCameraMode(CameraMode::Orbital); + m_Camera.SetOrbitalTarget(glm::vec3(0.0f, 0.0f, 0.0f)); + m_Camera.SetOrbitalRadius(15.0f); + m_Camera.SetOrbitalLimits(2.0f, 200.0f); + m_Camera.SetOrbitalSpeed(0.01f); + m_Camera.SetZoomSpeed(2.0f); + m_Camera.SetAzimuth(0.0f); + m_Camera.SetElevation(static_cast<float>(M_PI) / 3.0f); + m_Camera.UpdateOrbital(); + + Material defaultMaterial(glm::vec3(0.8f, 0.2f, 0.2f), 0.5f, 0.0f); + Object sphere1(glm::vec3(-3.0f, 0.0f, 0.0f), 1.0f, defaultMaterial); + m_Scene.objs.push_back(sphere1); + + Material material2(glm::vec3(0.2f, 0.8f, 0.2f), 0.3f, 0.0f); + Object sphere2(glm::vec3(3.0f, 0.0f, 0.0f), 1.5f, material2); + m_Scene.objs.push_back(sphere2); + + Material material3(glm::vec3(0.2f, 0.2f, 0.8f), 0.7f, 0.0f); + Object sphere3(glm::vec3(0.0f, 2.0f, 0.0f), 0.8f, material3); + m_Scene.objs.push_back(sphere3); + + m_Initialized = true; + } + + void WorldBuilderState::OnExit() + { + DONUT_INFO("Exiting World Builder State"); + } + + void WorldBuilderState::OnUpdate(float deltaTime) + { + if (m_CameraDragging && + !ImGuizmo::IsUsing()) + { + GLFWwindow* window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow()); + double xpos, ypos; + glfwGetCursorPos(window, &xpos, &ypos); + + glm::vec2 currentMousePos(xpos, ypos); + glm::vec2 delta = currentMousePos - m_LastMousePos; + + float sensitivity = 0.005f; + float azimuthDelta = delta.x * sensitivity; + float elevationDelta = -delta.y * sensitivity; + + float newAzimuth = m_Camera.GetAzimuth() + azimuthDelta; + float newElevation = m_Camera.GetElevation() + elevationDelta; + + newElevation = glm::clamp(newElevation, 0.01f, static_cast<float>(M_PI) - 0.01f); + + m_Camera.SetAzimuth(newAzimuth); + m_Camera.SetElevation(newElevation); + m_Camera.UpdateOrbital(); + + m_LastMousePos = currentMousePos; + } + } + + void WorldBuilderState::OnRender() + { + RenderCommand::SetClearColor(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f)); + RenderCommand::Clear(); + RenderScene(); + } + + void WorldBuilderState::OnEvent(Event& event) + { + EventDispatcher dispatcher(event); + + dispatcher.Dispatch<MouseButtonPressedEvent>([this](MouseButtonPressedEvent& e) + { + if (e.GetMouseButton() == GLFW_MOUSE_BUTTON_LEFT) + { + if (ImGuizmo::IsUsing() || + ImGuizmo::IsOver()) + return false; + + GLFWwindow* window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow()); + double xpos, ypos; + glfwGetCursorPos(window, &xpos, &ypos); + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + float ndcX = (2.0f * xpos) / width - 1.0f; + float ndcY = 1.0f - (2.0f * ypos) / height; + + glm::vec4 rayStart_NDC(ndcX, ndcY, -1.0f, 1.0f); + glm::vec4 rayEnd_NDC(ndcX, ndcY, 0.0f, 1.0f); + + glm::mat4 invVP = glm::inverse(m_Camera.GetProjectionMatrix() * m_Camera.GetViewMatrix()); + glm::vec4 rayStart_World = invVP * rayStart_NDC; + glm::vec4 rayEnd_World = invVP * rayEnd_NDC; + + rayStart_World /= rayStart_World.w; + rayEnd_World /= rayEnd_World.w; + + glm::vec3 rayDir = glm::normalize(glm::vec3(rayEnd_World - rayStart_World)); + glm::vec3 rayOrigin = glm::vec3(rayStart_World); + + float closestDistance = std::numeric_limits<float>::max(); + int closestObjectIndex = -1; + + for (size_t i = 0; i < m_Scene.objs.size(); ++i) + { + const Object& obj = m_Scene.objs[i]; + + glm::vec3 oc = rayOrigin - obj.m_Centre; + float a = glm::dot(rayDir, rayDir); + float b = 2.0f * glm::dot(oc, rayDir); + float c = glm::dot(oc, oc) - obj.m_Radius * obj.m_Radius; + float discriminant = b * b - 4 * a * c; + + if (discriminant > 0) + { + float t1 = (-b - sqrt(discriminant)) / (2.0f * a); + float t2 = (-b + sqrt(discriminant)) / (2.0f * a); + + if (t1 > 0 && t1 < closestDistance) + { + closestDistance = t1; + closestObjectIndex = static_cast<int>(i); + } + else if (t2 > 0 && t2 < closestDistance) + { + closestDistance = t2; + closestObjectIndex = static_cast<int>(i); + } + } + } + + if (closestObjectIndex >= 0) + { + m_SelectedObjectIndex = closestObjectIndex; + DONUT_INFO("Selected object {}", closestObjectIndex); + return true; + } + else + { + m_CameraDragging = true; + m_LastMousePos = glm::vec2(xpos, ypos); + } + + return true; + } + return false; + }); + + dispatcher.Dispatch<MouseButtonReleasedEvent>([this](MouseButtonReleasedEvent& e) + { + if (e.GetMouseButton() == GLFW_MOUSE_BUTTON_LEFT) + { + if (!ImGuizmo::IsUsing()) + m_CameraDragging = false; + return true; + } + + return false; + }); + + dispatcher.Dispatch<WindowResizeEvent>([this](WindowResizeEvent& e) + { + int newWidth = e.GetWidth(); + int newHeight = e.GetHeight(); + if (newWidth > 0 && newHeight > 0) + { + float aspect = static_cast<float>(newWidth) / static_cast<float>(newHeight); + m_Camera.SetProjection(45.0f, aspect, 0.1f, 1000.0f); + } + return false; + }); + + dispatcher.Dispatch<MouseScrolledEvent>([this](MouseScrolledEvent& e) + { + float zoomSpeed = 0.1f; + float zoomDelta = e.GetYOffset() * zoomSpeed; + + double currentRadius = m_Camera.GetOrbitalRadius(); + double newRadius = currentRadius - zoomDelta * currentRadius * 0.1f; + + double minRadius = 2.0f; + double maxRadius = 200.0f; + newRadius = glm::clamp(newRadius, minRadius, maxRadius); + + m_Camera.SetOrbitalRadius(newRadius); + m_Camera.UpdateOrbital(); + + return true; + }); + + dispatcher.Dispatch<KeyPressedEvent>([this](KeyPressedEvent& e) + { + if (m_SelectedObjectIndex >= 0 && + m_SelectedObjectIndex < static_cast<int>(m_Scene.objs.size())) + { + switch (e.GetKeyCode()) + { + case GLFW_KEY_W: + m_GizmoOperation = ImGuizmo::TRANSLATE; + return true; + case GLFW_KEY_E: + m_GizmoOperation = ImGuizmo::ROTATE; + return true; + case GLFW_KEY_R: + m_GizmoOperation = ImGuizmo::SCALE; + return true; + } + } + return false; + }); + } + + void WorldBuilderState::OnImUIRender() + { + GLFWwindow* window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow()); + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + ImGuiViewport* vp = ImGui::GetMainViewport(); + ImGuizmo::SetRect(vp->Pos.x, vp->Pos.y, vp->Size.x, vp->Size.y); + + ImGui::SetNextWindowSize(ImVec2(400, 600), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver); + + ImGui::Begin("World Builder", nullptr, ImGuiWindowFlags_NoCollapse); + + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); + ImGui::TextColored(ImVec4(0.8f, 0.8f, 1.0f, 1.0f), "World Builder"); + ImGui::PopFont(); + ImGui::Separator(); + + if (ImGui::CollapsingHeader("Scene Info", &m_ShowSceneInfo)) + { + ImGui::Text("Objects in scene: %zu", m_Scene.objs.size()); + + ImGui::Separator(); + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Camera Info:"); + + glm::vec3 cameraPos = m_Camera.GetOrbitalPosition(); + glm::vec3 target = m_Camera.GetOrbitalTarget(); + + ImGui::Text("Position: (%.2f, %.2f, %.2f)", cameraPos.x, cameraPos.y, cameraPos.z); + ImGui::Text("Target: (%.2f, %.2f, %.2f)", target.x, target.y, target.z); + ImGui::Text("Distance: %.2f", m_Camera.GetOrbitalRadius()); + ImGui::Text("Azimuth: %.1f°", glm::degrees(m_Camera.GetAzimuth())); + ImGui::Text("Elevation: %.1f°", glm::degrees(m_Camera.GetElevation())); + + ImGui::Separator(); + + if (ImGui::Button("Reset Camera")) + { + m_Camera.SetOrbitalRadius(15.0f); + m_Camera.SetAzimuth(0.0f); + m_Camera.SetElevation(static_cast<float>(M_PI) / 3.0f); + m_Camera.SetOrbitalTarget(glm::vec3(0.0f, 0.0f, 0.0f)); + m_Camera.UpdateOrbital(); + } + + ImGui::SameLine(); + if (ImGui::Button("Clear Scene")) + ClearScene(); + + ImGui::SameLine(); + if (ImGui::Button("Focus on Objects")) + { + if (!m_Scene.objs.empty()) + { + glm::vec3 center(0.0f); + for (const auto& obj : m_Scene.objs) + center += obj.m_Centre; + center /= static_cast<float>(m_Scene.objs.size()); + m_Camera.SetOrbitalTarget(center); + m_Camera.UpdateOrbital(); + } + } + } + + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("Create Object", &m_ShowObjectCreator)) + { + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "New Sphere"); + ImGui::Separator(); + + ImGui::Text("Position:"); + ImGui::DragFloat3("##Position", &m_NewObjectPosition.x, 0.1f); + + ImGui::Text("Radius:"); + ImGui::DragFloat("##Radius", &m_NewObjectRadius, 0.1f, 0.1f, 10.0f); + + ImGui::Text("Color:"); + ImGui::ColorEdit3("##Color", &m_NewObjectColor.x); + + ImGui::Text("Specular:"); + ImGui::SliderFloat("##Specular", &m_NewObjectSpecular, 0.0f, 1.0f); + + ImGui::Text("Emission:"); + ImGui::SliderFloat("##Emission", &m_NewObjectEmission, 0.0f, 1.0f); + + ImGui::Spacing(); + + if (ImGui::Button("Add Sphere", ImVec2(ImGui::GetWindowWidth() - 20, 30))) + AddSphere(); + } + + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("Scene Objects", &m_ShowObjectList)) + { + if (m_Scene.objs.empty()) + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "No objects in scene"); + else + { + for (size_t i = 0; i < m_Scene.objs.size(); ++i) + { + Object& obj = m_Scene.objs[i]; + + ImGui::PushID(static_cast<int>(i)); + + bool isSelected = (m_SelectedObjectIndex == static_cast<int>(i)); + if (ImGui::Selectable(("Sphere " + std::to_string(i)).c_str(), isSelected)) + m_SelectedObjectIndex = static_cast<int>(i); + + if (isSelected) + { + ImGui::SameLine(); + if (ImGui::Button("Remove")) + { + RemoveSelectedObject(); + } + + ImGui::Text("Position: (%.2f, %.2f, %.2f)", + obj.m_Centre.x, obj.m_Centre.y, obj.m_Centre.z); + ImGui::Text("Radius: %.2f", obj.m_Radius); + ImGui::Text("Color: (%.2f, %.2f, %.2f)", + obj.m_Material.m_Color.x, obj.m_Material.m_Color.y, obj.m_Material.m_Color.z); + } + + ImGui::PopID(); + } + } + } + + if (ImGui::CollapsingHeader("Gizmo Controls", &m_ShowGizmoControls)) + { + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Gizmo Operation:"); + + if (ImGui::RadioButton("Translate", m_GizmoOperation == ImGuizmo::TRANSLATE)) + m_GizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", m_GizmoOperation == ImGuizmo::ROTATE)) + m_GizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", m_GizmoOperation == ImGuizmo::SCALE)) + m_GizmoOperation = ImGuizmo::SCALE; + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Hotkeys:"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "W = Translate, E = Rotate, R = Scale"); + } + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + float buttonWidth = (ImGui::GetWindowWidth() - 30) / 2.0f; + + if (ImGui::Button("Save Scene", ImVec2(buttonWidth, 30))) + SaveScene(); + + ImGui::SameLine(); + if (ImGui::Button("Load Scene", ImVec2(buttonWidth, 30))) + LoadScene(); + + ImGui::Spacing(); + + if (ImGui::Button("Back to Config", ImVec2(ImGui::GetWindowWidth() - 20, 30))) + Application::Get().GetStateManager().SwitchToState("Config"); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Camera: Left mouse = rotate, Scroll = zoom"); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Gizmo: W=Translate, E=Rotate, R=Scale"); + + ImGui::End(); + + if (m_SelectedObjectIndex >= 0 && + m_SelectedObjectIndex < static_cast<int>(m_Scene.objs.size())) + { + glm::mat4 view = m_Camera.GetViewMatrix(); + glm::mat4 projection = m_Camera.GetProjectionMatrix(); + + Object& selectedObj = m_Scene.objs[m_SelectedObjectIndex]; + + glm::mat4 objectTransform = glm::mat4(1.0f); + objectTransform = glm::translate(objectTransform, selectedObj.m_Centre); + objectTransform = glm::scale(objectTransform, glm::vec3(selectedObj.m_Radius)); + + DONUT_INFO("Object position: ({}, {}, {})", selectedObj.m_Centre.x, selectedObj.m_Centre.y, selectedObj.m_Centre.z); + glm::vec3 cameraPos = m_Camera.GetOrbitalPosition(); + DONUT_INFO("Camera position: ({}, {}, {})", cameraPos.x, cameraPos.y, cameraPos.z); + + glm::vec3 viewTranslation = glm::vec3(view[3][0], view[3][1], view[3][2]); + DONUT_INFO("View translation: ({}, {}, {})", viewTranslation.x, viewTranslation.y, viewTranslation.z); + + ImGuizmo::SetDrawlist(ImGui::GetForegroundDrawList()); + if (ImGuizmo::Manipulate(glm::value_ptr(view), glm::value_ptr(projection), + m_GizmoOperation, m_GizmoMode, + glm::value_ptr(objectTransform), + nullptr, nullptr)) + { + glm::vec3 newPosition = glm::vec3(objectTransform[3][0], objectTransform[3][1], objectTransform[3][2]); + selectedObj.m_Centre = newPosition; + + glm::vec3 scale = glm::vec3( + glm::length(glm::vec3(objectTransform[0])), + glm::length(glm::vec3(objectTransform[1])), + glm::length(glm::vec3(objectTransform[2])) + ); + selectedObj.m_Radius = (scale.x + scale.y + scale.z) / 3.0f; // Average scale + + DONUT_INFO("Matrix [3]: ({}, {}, {}, {})", objectTransform[3][0], objectTransform[3][1], objectTransform[3][2], objectTransform[3][3]); + DONUT_INFO("New position: ({}, {}, {})", newPosition.x, newPosition.y, newPosition.z); + } + } + } + + void WorldBuilderState::AddSphere() + { + Material material(m_NewObjectColor, m_NewObjectSpecular, m_NewObjectEmission); + Object sphere(m_NewObjectPosition, m_NewObjectRadius, material); + m_Scene.objs.push_back(sphere); + + m_NewObjectPosition = glm::vec3(0.0f, 0.0f, 0.0f); + m_NewObjectRadius = 1.0f; + m_NewObjectColor = glm::vec3(1.0f, 1.0f, 1.0f); + m_NewObjectSpecular = 0.5f; + m_NewObjectEmission = 0.0f; + + DONUT_INFO("Added sphere to scene"); + } + + void WorldBuilderState::RemoveSelectedObject() + { + if (m_SelectedObjectIndex >= 0 && m_SelectedObjectIndex < static_cast<int>(m_Scene.objs.size())) + { + m_Scene.objs.erase(m_Scene.objs.begin() + m_SelectedObjectIndex); + m_SelectedObjectIndex = -1; + DONUT_INFO("Removed object from scene"); + } + } + + void WorldBuilderState::ClearScene() + { + m_Scene.objs.clear(); + m_SelectedObjectIndex = -1; + DONUT_INFO("Cleared scene"); + } + + void WorldBuilderState::SaveScene() + { + std::ofstream file("scene.txt"); + + if (file.is_open()) + { + file << m_Scene.objs.size() << std::endl; + for (const auto& obj : m_Scene.objs) + { + file << obj.m_Centre.x << " " << obj.m_Centre.y << " " << obj.m_Centre.z << " "; + file << obj.m_Radius << " "; + file << obj.m_Material.m_Color.x << " " << obj.m_Material.m_Color.y << " " << obj.m_Material.m_Color.z << " "; + file << obj.m_Material.m_Specular << " " << obj.m_Material.m_Emission << std::endl; + } + file.close(); + DONUT_INFO("Scene saved to scene.txt"); + } + else + DONUT_ERROR("Failed to save scene"); + } + + void WorldBuilderState::LoadScene() + { + std::ifstream file("scene.txt"); + if (file.is_open()) + { + m_Scene.objs.clear(); + m_SelectedObjectIndex = -1; + + size_t objectCount; + file >> objectCount; + + for (size_t i = 0; i < objectCount; ++i) + { + glm::vec3 position; + float radius; + glm::vec3 color; + float specular, emission; + + file >> position.x >> position.y >> position.z; + file >> radius; + file >> color.x >> color.y >> color.z; + file >> specular >> emission; + + Material material(color, specular, emission); + Object sphere(position, radius, material); + m_Scene.objs.push_back(sphere); + } + + file.close(); + DONUT_INFO("Scene loaded from scene.txt"); + } + else + DONUT_ERROR("Failed to load scene"); + } + + void WorldBuilderState::RenderScene() + { + GLFWwindow* window = static_cast<GLFWwindow*>(Application::Get().GetWindow().GetNativeWindow()); + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + RenderCommand::SetViewport(0, 0, width, height); + RenderCommand::EnableDepthTest(); + + static Ref<VertexArray> sphereVAO = nullptr; + static Ref<Shader> sphereShader = Ref<Shader>(Shader::Create("Assets/Shaders/Sphere.glsl")); + + if (!sphereVAO) + { + std::vector<float> vertices; + std::vector<uint32_t> indices; + + const int segments = 32; + const int rings = 16; + + for (int ring = 0; ring <= rings; ++ring) + { + float phi = static_cast<float>(M_PI) * ring / rings; + float sinPhi = sin(phi); + float cosPhi = cos(phi); + + for (int segment = 0; segment <= segments; ++segment) + { + float theta = 2.0f * static_cast<float>(M_PI) * segment / segments; + float sinTheta = sin(theta); + float cosTheta = cos(theta); + + float x = cosTheta * sinPhi; + float y = cosPhi; + float z = sinTheta * sinPhi; + + float nx = x; + float ny = y; + float nz = z; + + vertices.push_back(x); + vertices.push_back(y); + vertices.push_back(z); + vertices.push_back(nx); + vertices.push_back(ny); + vertices.push_back(nz); + } + } + + for (int ring = 0; ring < rings; ++ring) + { + for (int segment = 0; segment < segments; ++segment) + { + uint32_t first = ring * (segments + 1) + segment; + uint32_t second = first + segments + 1; + + indices.push_back(first); + indices.push_back(second); + indices.push_back(first + 1); + + indices.push_back(second); + indices.push_back(second + 1); + indices.push_back(first + 1); + } + } + + auto vertexBuffer = Ref<VertexBuffer>(VertexBuffer::Create(vertices.data(), static_cast<uint32_t>(vertices.size() * sizeof(float)))); + VertexBufferLayout layout; + layout.Push<float>(3); + layout.Push<float>(3); + vertexBuffer->SetLayout(layout); + + auto indexBuffer = Ref<IndexBuffer>(IndexBuffer::Create(indices.data(), static_cast<uint32_t>(indices.size()))); + + sphereVAO = Ref<VertexArray>(VertexArray::Create()); + sphereVAO->AddVertexBuffer(vertexBuffer); + sphereVAO->SetIndexBuffer(indexBuffer); + } + + glm::mat4 view = m_Camera.GetViewMatrix(); + glm::mat4 projection = m_Camera.GetProjectionMatrix(); + glm::mat4 viewProjection = projection * view; + + glm::vec3 lightPos = m_Scene.m_LightPos; + glm::vec3 cameraPos = m_Camera.GetOrbitalPosition(); + + sphereShader->Bind(); + sphereShader->SetMat4("u_ViewProjection", viewProjection); + sphereShader->SetFloat3("u_LightPos", lightPos); + sphereShader->SetFloat3("u_CameraPos", cameraPos); + + for (size_t i = 0; i < m_Scene.objs.size(); ++i) + { + const auto& obj = m_Scene.objs[i]; + + glm::mat4 transform = glm::translate(glm::mat4(1.0f), obj.m_Centre); + transform = glm::scale(transform, glm::vec3(obj.m_Radius)); + + sphereShader->SetMat4("u_Transform", transform); + + if (m_SelectedObjectIndex == static_cast<int>(i)) + { + glm::vec3 highlightColor = obj.m_Material.m_Color * 1.5f; + highlightColor = glm::clamp(highlightColor, 0.0f, 1.0f); + sphereShader->SetFloat3("u_Color", highlightColor); + sphereShader->SetFloat("u_Emission", 0.2f); + } + else + { + sphereShader->SetFloat3("u_Color", obj.m_Material.m_Color); + sphereShader->SetFloat("u_Emission", obj.m_Material.m_Emission); + } + + sphereShader->SetFloat("u_Specular", obj.m_Material.m_Specular); + + sphereVAO->Bind(); + RenderCommand::DrawIndexed(sphereVAO); + } + + RenderCommand::DisableDepthTest(); + } +}; diff --git a/src/States/WorldBuilderState.h b/src/States/WorldBuilderState.h new file mode 100644 index 0000000..c9b8d00 --- /dev/null +++ b/src/States/WorldBuilderState.h @@ -0,0 +1,65 @@ +#pragma once + +#include "Core/Camera.h" +#include "Core/State.h" +#include "Core/Event.h" +#include "Core/Log.h" + +#include "Engine/Scene.h" +#include "Engine/Object.h" + +#include "Rendering/Renderer.h" +#include "Rendering/Shader.h" +#include "Rendering/VertexArray.h" + +#include <imgui.h> +#include <ImGuizmo.h> + +#include <vector> +#include <memory> + +namespace Donut +{ + class WorldBuilderState + : public State + { + public: + ~WorldBuilderState() = default; + + void OnEnter() override; + void OnExit() override; + void OnUpdate(float deltaTime) override; + void OnRender() override; + void OnImUIRender() override; + void OnEvent(Event& event) override; + private: + void AddSphere(); + void RemoveSelectedObject(); + void ClearScene(); + void SaveScene(); + void LoadScene(); + void RenderScene(); + private: + Scene m_Scene; + Camera m_Camera; + bool m_Initialized = false; + + glm::vec3 m_NewObjectPosition = glm::vec3(0.0f, 0.0f, 0.0f); + float m_NewObjectRadius = 1.0f; + glm::vec3 m_NewObjectColor = glm::vec3(1.0f, 1.0f, 1.0f); + float m_NewObjectSpecular = 0.5f; + float m_NewObjectEmission = 0.0f; + + int m_SelectedObjectIndex = -1; + bool m_CameraDragging = false; + glm::vec2 m_LastMousePos = glm::vec2(0.0f, 0.0f); + + ImGuizmo::OPERATION m_GizmoOperation = ImGuizmo::TRANSLATE; + ImGuizmo::MODE m_GizmoMode = ImGuizmo::LOCAL; + + bool m_ShowObjectList = true; + bool m_ShowObjectCreator = true; + bool m_ShowSceneInfo = true; + bool m_ShowGizmoControls = true; + }; +}; |
