aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2025-08-17 22:38:29 +0200
committerhachem <im@hachem.wtf>2025-08-17 22:38:29 +0200
commit1ecaea469e86ef3f5d18122c8b2cc187286e27f7 (patch)
treec56d2320bd69ea3a1c2cd9c78c288546214a8667 /src
parentd9a8a37a61bff05a06068e203d6548ca931857de (diff)
Add World builder state
Diffstat (limited to 'src')
-rw-r--r--src/Core/Application.cpp21
-rw-r--r--src/Core/SettingsManager.cpp2
-rw-r--r--src/States/ConfigState.cpp38
-rw-r--r--src/States/WorldBuilderState.cpp678
-rw-r--r--src/States/WorldBuilderState.h65
5 files changed, 783 insertions, 21 deletions
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;
+ };
+};