hachem.wtf
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
commitd65233be9433a01c54e959aea72e87ff2e385937 (patch)
tree70cea1cf2b1f62b09f94d5b0dcbc7c193cec2fe4 /src
parentdf479e3f8b803f8675fedb009f7ea78aa107d194 (diff)
feat: add world builder state
Diffstat (limited to 'src')
-rw-r--r--src/core/application.cpp19
-rw-r--r--src/core/settings_manager.cpp2
-rw-r--r--src/states/config_state.cpp38
-rw-r--r--src/states/world_builder_state.cpp674
-rw-r--r--src/states/world_builder_state.h65
5 files changed, 779 insertions, 19 deletions
diff --git a/src/core/application.cpp b/src/core/application.cpp
index 9d2278e..85c7645 100644
--- a/src/core/application.cpp
+++ b/src/core/application.cpp
@@ -5,9 +5,10 @@
#include "states/simulation_state.h"
#include "states/config_state.h"
+#include "states/world_builder_state.h"
#include <GLFW/glfw3.h>
-
+#include <ImGuizmo.h>
namespace Donut
{
@@ -88,6 +89,12 @@ namespace Donut
event.handled = true;
return true;
}
+ else if (e.get_key_code() == GLFW_KEY_3)
+ {
+ m_state_manager->switch_to_state("WorldBuilder");
+ event.handled = true;
+ return true;
+ }
return true;
});
@@ -112,8 +119,9 @@ namespace Donut
RenderCommand::set_face_culling(false);
m_state_manager = create_scope<StateManager>();
- m_state_manager->register_state("Config", create_scope<ConfigState>());
- m_state_manager->register_state("Simulation", create_scope<SimulationState>());
+ m_state_manager->register_state("Config", create_scope<ConfigState>());
+ m_state_manager->register_state("Simulation", create_scope<SimulationState>());
+ m_state_manager->register_state("WorldBuilder", create_scope<WorldBuilderState>());
m_state_manager->switch_to_state("Config");
}
@@ -139,8 +147,11 @@ namespace Donut
void Application::on_render()
{
m_state_manager->render();
-
m_window->begin_im_gui_frame();
+
+ ImGuizmo::BeginFrame();
+ ImGuizmo::SetOrthographic(false);
+
setup_docking_layout();
m_state_manager->on_im_ui_render();
m_window->end_im_gui_frame();
diff --git a/src/core/settings_manager.cpp b/src/core/settings_manager.cpp
index eb68794..289285c 100644
--- a/src/core/settings_manager.cpp
+++ b/src/core/settings_manager.cpp
@@ -138,7 +138,9 @@ namespace Donut
DONUT_INFO("Settings saved to {}", file_path);
}
catch (const std::exception& e)
+ {
DONUT_ERROR("Failed to save settings: {}", e.what());
+ }
}
void SettingsManager::set_simulation_settings(const SimulationSettings& settings)
diff --git a/src/states/config_state.cpp b/src/states/config_state.cpp
index 1f1ef16..41fbaad 100644
--- a/src/states/config_state.cpp
+++ b/src/states/config_state.cpp
@@ -16,20 +16,20 @@ namespace Donut
const auto& settings = SettingsManager::get_settings_const();
- m_selected_api = (settings.graphics.render_api == "Vulkan") ? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL;
- m_selected_theme = (settings.graphics.selected_theme == "Light") ? 1 :
- (settings.graphics.selected_theme == "Blue") ? 2 : 0;
- m_target_fps = settings.simulation.target_fps;
- m_compute_height = settings.simulation.compute_height;
- m_max_steps_moving = settings.simulation.max_steps_moving;
- m_max_steps_static = settings.simulation.max_steps_static;
- m_early_exit_distance = settings.simulation.early_exit_distance;
- m_gravity_enabled = settings.simulation.gravity_enabled;
- m_v_sync_enabled = settings.graphics.v_sync_enabled;
- m_show_fps = settings.graphics.show_fps;
+ m_selected_api = (settings.graphics.render_api == "Vulkan") ? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL;
+ m_selected_theme = (settings.graphics.selected_theme == "Light") ? 1 :
+ (settings.graphics.selected_theme == "Blue") ? 2 : 0;
+ m_target_fps = settings.simulation.target_fps;
+ m_compute_height = settings.simulation.compute_height;
+ m_max_steps_moving = settings.simulation.max_steps_moving;
+ m_max_steps_static = settings.simulation.max_steps_static;
+ m_early_exit_distance = settings.simulation.early_exit_distance;
+ m_gravity_enabled = settings.simulation.gravity_enabled;
+ m_v_sync_enabled = settings.graphics.v_sync_enabled;
+ m_show_fps = settings.graphics.show_fps;
m_show_performance_metrics = settings.graphics.show_performance_metrics;
- m_show_debug_info = settings.graphics.show_debug_info;
- m_enable_anti_aliasing = settings.graphics.enable_anti_aliasing;
+ m_show_debug_info = settings.graphics.show_debug_info;
+ m_enable_anti_aliasing = settings.graphics.enable_anti_aliasing;
}
void ConfigState::on_exit()
@@ -52,7 +52,7 @@ namespace Donut
void ConfigState::on_render()
{
- Renderer::set_clear_color({ 0.05f, 0.05f, 0.1f, 1.0f });
+ Renderer::set_clear_color({ 0.1f, 0.1f, 0.1f, 1.0f });
Renderer::clear();
}
@@ -104,6 +104,7 @@ namespace Donut
if (is_selected)
ImGui::SetItemDefaultFocus();
}
+
ImGui::EndCombo();
}
@@ -215,7 +216,7 @@ namespace Donut
ImGui::Separator();
ImGui::Spacing();
- float button_width = (ImGui::GetWindowWidth() - 100) / 5.0f;
+ float button_width = (ImGui::GetWindowWidth() - 120) / 6.0f;
if (ImGui::Button("Start Simulation", ImVec2(button_width, 35)))
{
@@ -224,6 +225,13 @@ namespace Donut
}
ImGui::SameLine();
+ if (ImGui::Button("World Builder", ImVec2(button_width, 35)))
+ {
+ apply_settings();
+ Application::get().get_state_manager().switch_to_state("WorldBuilder");
+ }
+
+ ImGui::SameLine();
if (ImGui::Button("Reset to Defaults", ImVec2(button_width, 35)))
reset_to_defaults();
diff --git a/src/states/world_builder_state.cpp b/src/states/world_builder_state.cpp
new file mode 100644
index 0000000..16b7866
--- /dev/null
+++ b/src/states/world_builder_state.cpp
@@ -0,0 +1,674 @@
+#include "world_builder_state.h"
+
+#include "core/application.h"
+#include "core/window.h"
+
+#include "rendering/renderer.h"
+#include "rendering/shader.h"
+#include "rendering/vertex_array.h"
+#include "rendering/vertex_buffer.h"
+#include "rendering/index_buffer.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
+
+namespace Donut
+{
+ void WorldBuilderState::on_enter()
+ {
+ DONUT_INFO("Entering World Builder State");
+
+ ImGuizmo::Enable(true);
+
+ m_camera.set_camera_mode(CameraMode::Orbital);
+ m_camera.set_orbital_target(glm::vec3(0.0f, 0.0f, 0.0f));
+ m_camera.set_orbital_radius(15.0f);
+ m_camera.set_orbital_limits(2.0f, 200.0f);
+ m_camera.set_orbital_speed(0.01f);
+ m_camera.set_zoom_speed(2.0f);
+ m_camera.set_azimuth(0.0f);
+ m_camera.set_elevation(static_cast<float>(M_PI) / 3.0f);
+ m_camera.update_orbital();
+
+ Material default_material(glm::vec3(0.8f, 0.2f, 0.2f), 0.5f, 0.0f);
+ Object sphere1(glm::vec3(-3.0f, 0.0f, 0.0f), 1.0f, default_material);
+ 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::on_exit()
+ {
+ DONUT_INFO("Exiting World Builder State");
+ }
+
+ void WorldBuilderState::on_update(float delta_time)
+ {
+ if (m_camera_dragging &&
+ !ImGuizmo::IsUsing())
+ {
+ GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window());
+ double xpos, ypos;
+ glfwGetCursorPos(window, &xpos, &ypos);
+
+ glm::vec2 current_mouse_pos(xpos, ypos);
+ glm::vec2 delta = current_mouse_pos - m_last_mouse_pos;
+
+ float sensitivity = 0.005f;
+ float azimuth_delta = delta.x * sensitivity;
+ float elevation_delta = -delta.y * sensitivity;
+
+ float new_azimuth = m_camera.get_azimuth() + azimuth_delta;
+ float new_elevation = m_camera.get_elevation() + elevation_delta;
+
+ new_elevation = glm::clamp(new_elevation, 0.01f, static_cast<float>(M_PI) - 0.01f);
+
+ m_camera.set_azimuth(new_azimuth);
+ m_camera.set_elevation(new_elevation);
+ m_camera.update_orbital();
+
+ m_last_mouse_pos = current_mouse_pos;
+ }
+ }
+
+ void WorldBuilderState::on_render()
+ {
+ RenderCommand::set_clear_color(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f));
+ RenderCommand::clear();
+ render_scene();
+ }
+
+ void WorldBuilderState::on_event(Event& event)
+ {
+ EventDispatcher dispatcher(event);
+
+ dispatcher.dispatch<MouseButtonPressedEvent>([this](MouseButtonPressedEvent& e)
+ {
+ if (e.get_mouse_button() == GLFW_MOUSE_BUTTON_LEFT)
+ {
+ if (ImGuizmo::IsUsing() ||
+ ImGuizmo::IsOver())
+ return false;
+
+ GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window());
+ double xpos, ypos;
+ glfwGetCursorPos(window, &xpos, &ypos);
+
+ int width, height;
+ glfwGetFramebufferSize(window, &width, &height);
+
+ float ndc_x = (2.0f * xpos) / width - 1.0f;
+ float ndc_y = 1.0f - (2.0f * ypos) / height;
+
+ glm::vec4 ray_start_ndc(ndc_x, ndc_y, -1.0f, 1.0f);
+ glm::vec4 ray_end_ndc(ndc_x, ndc_y, 0.0f, 1.0f);
+
+ glm::mat4 inv_vp = glm::inverse(m_camera.get_projection_matrix() * m_camera.get_view_matrix());
+ glm::vec4 ray_start_world = inv_vp * ray_start_ndc;
+ glm::vec4 ray_end_world = inv_vp * ray_end_ndc;
+
+ ray_start_world /= ray_start_world.w;
+ ray_end_world /= ray_end_world.w;
+
+ glm::vec3 ray_dir = glm::normalize(glm::vec3(ray_end_world - ray_start_world));
+ glm::vec3 ray_origin = glm::vec3(ray_start_world);
+
+ float closest_distance = std::numeric_limits<float>::max();
+ int closest_object_index = -1;
+
+ for (size_t i = 0; i < m_scene.objs.size(); ++i)
+ {
+ const Object& obj = m_scene.objs[i];
+
+ glm::vec3 oc = ray_origin - obj.m_centre;
+ float a = glm::dot(ray_dir, ray_dir);
+ float b = 2.0f * glm::dot(oc, ray_dir);
+ 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 < closest_distance)
+ {
+ closest_distance = t1;
+ closest_object_index = static_cast<int>(i);
+ }
+ else if (t2 > 0 && t2 < closest_distance)
+ {
+ closest_distance = t2;
+ closest_object_index = static_cast<int>(i);
+ }
+ }
+ }
+
+ if (closest_object_index >= 0)
+ {
+ m_selected_object_index = closest_object_index;
+ DONUT_INFO("Selected object {}", closest_object_index);
+ return true;
+ }
+ else
+ {
+ m_camera_dragging = true;
+ m_last_mouse_pos = glm::vec2(xpos, ypos);
+ }
+
+ return true;
+ }
+ return false;
+ });
+
+ dispatcher.dispatch<MouseButtonReleasedEvent>([this](MouseButtonReleasedEvent& e)
+ {
+ if (e.get_mouse_button() == GLFW_MOUSE_BUTTON_LEFT)
+ {
+ if (!ImGuizmo::IsUsing())
+ m_camera_dragging = false;
+ return true;
+ }
+
+ return false;
+ });
+
+ dispatcher.dispatch<WindowResizeEvent>([this](WindowResizeEvent& e)
+ {
+ int new_width = e.get_width();
+ int new_height = e.get_height();
+ if (new_width > 0 && new_height > 0)
+ {
+ float aspect = static_cast<float>(new_width) / static_cast<float>(new_height);
+ m_camera.set_projection(45.0f, aspect, 0.1f, 1000.0f);
+ }
+ return false;
+ });
+
+ dispatcher.dispatch<MouseScrolledEvent>([this](MouseScrolledEvent& e)
+ {
+ float zoom_speed = 0.1f;
+ float zoom_delta = e.get_y_offset() * zoom_speed;
+
+ double current_radius = m_camera.get_orbital_radius();
+ double new_radius = current_radius - zoom_delta * current_radius * 0.1f;
+
+ double min_radius = 2.0f;
+ double max_radius = 200.0f;
+ new_radius = glm::clamp(new_radius, min_radius, max_radius);
+
+ m_camera.set_orbital_radius(new_radius);
+ m_camera.update_orbital();
+
+ return true;
+ });
+
+ dispatcher.dispatch<KeyPressedEvent>([this](KeyPressedEvent& e)
+ {
+ if (m_selected_object_index >= 0 &&
+ m_selected_object_index < static_cast<int>(m_scene.objs.size()))
+ {
+ switch (e.get_key_code())
+ {
+ case GLFW_KEY_W:
+ m_gizmo_operation = ImGuizmo::TRANSLATE;
+ return true;
+ case GLFW_KEY_E:
+ m_gizmo_operation = ImGuizmo::ROTATE;
+ return true;
+ case GLFW_KEY_R:
+ m_gizmo_operation = ImGuizmo::SCALE;
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+
+ void WorldBuilderState::on_im_ui_render()
+ {
+ GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window());
+ 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_show_scene_info))
+ {
+ 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 camera_pos = m_camera.get_orbital_position();
+ glm::vec3 target = m_camera.get_orbital_target();
+
+ ImGui::Text("Position: (%.2f, %.2f, %.2f)", camera_pos.x, camera_pos.y, camera_pos.z);
+ ImGui::Text("Target: (%.2f, %.2f, %.2f)", target.x, target.y, target.z);
+ ImGui::Text("Distance: %.2f", m_camera.get_orbital_radius());
+ ImGui::Text("Azimuth: %.1f°", glm::degrees(m_camera.get_azimuth()));
+ ImGui::Text("Elevation: %.1f°", glm::degrees(m_camera.get_elevation()));
+
+ ImGui::Separator();
+
+ if (ImGui::Button("Reset Camera"))
+ {
+ m_camera.set_orbital_radius(15.0f);
+ m_camera.set_azimuth(0.0f);
+ m_camera.set_elevation(static_cast<float>(M_PI) / 3.0f);
+ m_camera.set_orbital_target(glm::vec3(0.0f, 0.0f, 0.0f));
+ m_camera.update_orbital();
+ }
+
+ ImGui::SameLine();
+ if (ImGui::Button("Clear Scene"))
+ clear_scene();
+
+ 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.set_orbital_target(center);
+ m_camera.update_orbital();
+ }
+ }
+ }
+
+ ImGui::Spacing();
+
+ if (ImGui::CollapsingHeader("Create Object", &m_show_object_creator))
+ {
+ ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "New Sphere");
+ ImGui::Separator();
+
+ ImGui::Text("Position:");
+ ImGui::DragFloat3("##Position", &m_new_object_position.x, 0.1f);
+
+ ImGui::Text("Radius:");
+ ImGui::DragFloat("##Radius", &m_new_object_radius, 0.1f, 0.1f, 10.0f);
+
+ ImGui::Text("Color:");
+ ImGui::ColorEdit3("##Color", &m_new_object_color.x);
+
+ ImGui::Text("Specular:");
+ ImGui::SliderFloat("##Specular", &m_new_object_specular, 0.0f, 1.0f);
+
+ ImGui::Text("Emission:");
+ ImGui::SliderFloat("##Emission", &m_new_object_emission, 0.0f, 1.0f);
+
+ ImGui::Spacing();
+
+ if (ImGui::Button("Add Sphere", ImVec2(ImGui::GetWindowWidth() - 20, 30)))
+ add_sphere();
+ }
+
+ ImGui::Spacing();
+
+ if (ImGui::CollapsingHeader("Scene Objects", &m_show_object_list))
+ {
+ 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 is_selected = (m_selected_object_index == static_cast<int>(i));
+ if (ImGui::Selectable(("Sphere " + std::to_string(i)).c_str(), is_selected))
+ m_selected_object_index = static_cast<int>(i);
+
+ if (is_selected)
+ {
+ ImGui::SameLine();
+ if (ImGui::Button("Remove"))
+ {
+ remove_selected_object();
+ }
+
+ 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_show_gizmo_controls))
+ {
+ ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Gizmo Operation:");
+
+ if (ImGui::RadioButton("Translate", m_gizmo_operation == ImGuizmo::TRANSLATE))
+ m_gizmo_operation = ImGuizmo::TRANSLATE;
+ ImGui::SameLine();
+ if (ImGui::RadioButton("Rotate", m_gizmo_operation == ImGuizmo::ROTATE))
+ m_gizmo_operation = ImGuizmo::ROTATE;
+ ImGui::SameLine();
+ if (ImGui::RadioButton("Scale", m_gizmo_operation == ImGuizmo::SCALE))
+ m_gizmo_operation = 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 button_width = (ImGui::GetWindowWidth() - 30) / 2.0f;
+
+ if (ImGui::Button("Save Scene", ImVec2(button_width, 30)))
+ save_scene();
+
+ ImGui::SameLine();
+ if (ImGui::Button("Load Scene", ImVec2(button_width, 30)))
+ load_scene();
+
+ ImGui::Spacing();
+
+ if (ImGui::Button("Back to Config", ImVec2(ImGui::GetWindowWidth() - 20, 30)))
+ Application::get().get_state_manager().switch_to_state("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_selected_object_index >= 0 &&
+ m_selected_object_index < static_cast<int>(m_scene.objs.size()))
+ {
+ glm::mat4 view = m_camera.get_view_matrix();
+ glm::mat4 projection = m_camera.get_projection_matrix();
+
+ Object& selected_obj = m_scene.objs[m_selected_object_index];
+
+ glm::mat4 object_transform = glm::mat4(1.0f);
+ object_transform = glm::translate(object_transform, selected_obj.m_centre);
+ object_transform = glm::scale(object_transform, glm::vec3(selected_obj.m_radius));
+
+ DONUT_INFO("Object position: ({}, {}, {})", selected_obj.m_centre.x, selected_obj.m_centre.y, selected_obj.m_centre.z);
+ glm::vec3 camera_pos = m_camera.get_orbital_position();
+ DONUT_INFO("Camera position: ({}, {}, {})", camera_pos.x, camera_pos.y, camera_pos.z);
+
+ glm::vec3 view_translation = glm::vec3(view[3][0], view[3][1], view[3][2]);
+ DONUT_INFO("View translation: ({}, {}, {})", view_translation.x, view_translation.y, view_translation.z);
+
+ ImGuizmo::SetDrawlist(ImGui::GetForegroundDrawList());
+ if (ImGuizmo::Manipulate(glm::value_ptr(view), glm::value_ptr(projection),
+ m_gizmo_operation, m_gizmo_mode,
+ glm::value_ptr(object_transform),
+ nullptr, nullptr))
+ {
+ glm::vec3 new_position = glm::vec3(object_transform[3][0], object_transform[3][1], object_transform[3][2]);
+ selected_obj.m_centre = new_position;
+
+ glm::vec3 scale = glm::vec3(
+ glm::length(glm::vec3(object_transform[0])),
+ glm::length(glm::vec3(object_transform[1])),
+ glm::length(glm::vec3(object_transform[2]))
+ );
+ selected_obj.m_radius = (scale.x + scale.y + scale.z) / 3.0f;
+
+ DONUT_INFO("Matrix [3]: ({}, {}, {}, {})", object_transform[3][0], object_transform[3][1], object_transform[3][2], object_transform[3][3]);
+ DONUT_INFO("New position: ({}, {}, {})", new_position.x, new_position.y, new_position.z);
+ }
+ }
+ }
+
+ void WorldBuilderState::add_sphere()
+ {
+ Material material(m_new_object_color, m_new_object_specular, m_new_object_emission);
+ Object sphere(m_new_object_position, m_new_object_radius, material);
+ m_scene.objs.push_back(sphere);
+
+ m_new_object_position = glm::vec3(0.0f, 0.0f, 0.0f);
+ m_new_object_radius = 1.0f;
+ m_new_object_color = glm::vec3(1.0f, 1.0f, 1.0f);
+ m_new_object_specular = 0.5f;
+ m_new_object_emission = 0.0f;
+
+ DONUT_INFO("Added sphere to scene");
+ }
+
+ void WorldBuilderState::remove_selected_object()
+ {
+ if (m_selected_object_index >= 0 && m_selected_object_index < static_cast<int>(m_scene.objs.size()))
+ {
+ m_scene.objs.erase(m_scene.objs.begin() + m_selected_object_index);
+ m_selected_object_index = -1;
+ DONUT_INFO("Removed object from scene");
+ }
+ }
+
+ void WorldBuilderState::clear_scene()
+ {
+ m_scene.objs.clear();
+ m_selected_object_index = -1;
+ DONUT_INFO("Cleared scene");
+ }
+
+ void WorldBuilderState::save_scene()
+ {
+ 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::load_scene()
+ {
+ std::ifstream file("scene.txt");
+ if (file.is_open())
+ {
+ m_scene.objs.clear();
+ m_selected_object_index = -1;
+
+ size_t object_count;
+ file >> object_count;
+
+ for (size_t i = 0; i < object_count; ++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::render_scene()
+ {
+ GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window());
+ int width, height;
+ glfwGetFramebufferSize(window, &width, &height);
+
+ RenderCommand::set_viewport(0, 0, width, height);
+ RenderCommand::enable_depth_test();
+
+ static Ref<VertexArray> sphere_vao = nullptr;
+ static Ref<Shader> sphere_shader = Ref<Shader>(Shader::create("assets/shaders/sphere.glsl"));
+
+ if (!sphere_vao)
+ {
+ 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 sin_phi = sin(phi);
+ float cos_phi = cos(phi);
+
+ for (int segment = 0; segment <= segments; ++segment)
+ {
+ float theta = 2.0f * static_cast<float>(M_PI) * segment / segments;
+ float sin_theta = sin(theta);
+ float cos_theta = cos(theta);
+
+ float x = cos_theta * sin_phi;
+ float y = cos_phi;
+ float z = sin_theta * sin_phi;
+
+ 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 vertex_buffer = Ref<VertexBuffer>(VertexBuffer::create(vertices.data(), static_cast<uint32_t>(vertices.size() * sizeof(float))));
+ VertexBufferLayout layout;
+ layout.push<float>(3);
+ layout.push<float>(3);
+ vertex_buffer->set_layout(layout);
+
+ auto index_buffer = Ref<IndexBuffer>(IndexBuffer::create(indices.data(), static_cast<uint32_t>(indices.size())));
+
+ sphere_vao = Ref<VertexArray>(VertexArray::create());
+ sphere_vao->add_vertex_buffer(vertex_buffer);
+ sphere_vao->set_index_buffer(index_buffer);
+ }
+
+ glm::mat4 view = m_camera.get_view_matrix();
+ glm::mat4 projection = m_camera.get_projection_matrix();
+ glm::mat4 view_projection = projection * view;
+
+ glm::vec3 light_pos = m_scene.m_light_pos;
+ glm::vec3 camera_pos = m_camera.get_orbital_position();
+
+ sphere_shader->bind();
+ sphere_shader->set_mat4("u_ViewProjection", view_projection);
+ sphere_shader->set_float3("u_LightPos", light_pos);
+ sphere_shader->set_float3("u_CameraPos", camera_pos);
+
+ 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));
+
+ sphere_shader->set_mat4("u_Transform", transform);
+
+ if (m_selected_object_index == static_cast<int>(i))
+ {
+ glm::vec3 highlight_color = obj.m_material.m_color * 1.5f;
+ highlight_color = glm::clamp(highlight_color, 0.0f, 1.0f);
+ sphere_shader->set_float3("u_Color", highlight_color);
+ sphere_shader->set_float("u_Emission", 0.2f);
+ }
+ else
+ {
+ sphere_shader->set_float3("u_Color", obj.m_material.m_color);
+ sphere_shader->set_float("u_Emission", obj.m_material.m_emission);
+ }
+
+ sphere_shader->set_float("u_Specular", obj.m_material.m_specular);
+
+ sphere_vao->bind();
+ RenderCommand::draw_indexed(sphere_vao);
+ }
+
+ RenderCommand::disable_depth_test();
+ }
+};
diff --git a/src/states/world_builder_state.h b/src/states/world_builder_state.h
new file mode 100644
index 0000000..c587fbe
--- /dev/null
+++ b/src/states/world_builder_state.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/vertex_array.h"
+
+#include <imgui.h>
+#include <ImGuizmo.h>
+
+#include <vector>
+#include <memory>
+
+namespace Donut
+{
+ class WorldBuilderState
+ : public State
+ {
+ public:
+ ~WorldBuilderState() = default;
+
+ void on_enter() override;
+ void on_exit() override;
+ void on_update(float delta_time) override;
+ void on_render() override;
+ void on_im_ui_render() override;
+ void on_event(Event& event) override;
+ private:
+ void add_sphere();
+ void remove_selected_object();
+ void clear_scene();
+ void save_scene();
+ void load_scene();
+ void render_scene();
+ private:
+ Scene m_scene;
+ Camera m_camera;
+ bool m_initialized = false;
+
+ glm::vec3 m_new_object_position = glm::vec3(0.0f, 0.0f, 0.0f);
+ float m_new_object_radius = 1.0f;
+ glm::vec3 m_new_object_color = glm::vec3(1.0f, 1.0f, 1.0f);
+ float m_new_object_specular = 0.5f;
+ float m_new_object_emission = 0.0f;
+
+ int m_selected_object_index = -1;
+ bool m_camera_dragging = false;
+ glm::vec2 m_last_mouse_pos = glm::vec2(0.0f, 0.0f);
+
+ ImGuizmo::OPERATION m_gizmo_operation = ImGuizmo::TRANSLATE;
+ ImGuizmo::MODE m_gizmo_mode = ImGuizmo::LOCAL;
+
+ bool m_show_object_list = true;
+ bool m_show_object_creator = true;
+ bool m_show_scene_info = true;
+ bool m_show_gizmo_controls = true;
+ };
+};