diff options
Diffstat (limited to 'src/states')
| -rw-r--r-- | src/states/config_state.cpp | 307 | ||||
| -rw-r--r-- | src/states/config_state.h | 47 | ||||
| -rw-r--r-- | src/states/simulation_state.cpp | 386 | ||||
| -rw-r--r-- | src/states/simulation_state.h | 27 | ||||
| -rw-r--r-- | src/states/world_builder_state.cpp | 1113 | ||||
| -rw-r--r-- | src/states/world_builder_state.h | 96 |
6 files changed, 1976 insertions, 0 deletions
diff --git a/src/states/config_state.cpp b/src/states/config_state.cpp new file mode 100644 index 0000000..d5fca87 --- /dev/null +++ b/src/states/config_state.cpp @@ -0,0 +1,307 @@ +#include "config_state.h" +#include "rendering/renderer.h" +#include "core/application.h" +#include "core/theme_manager.h" +#include "core/settings_manager.h" + +#include <imgui.h> +#include <imgui_internal.h> +#include <glad/glad.h> + +namespace Donut +{ + auto ConfigState::on_enter() -> void + { + DONUT_INFO("Entering Config State"); + + 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_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; + } + + auto ConfigState::on_exit() -> void + { + DONUT_INFO("Exiting Config State"); + } + + auto ConfigState::on_update(float delta_time) -> void + { + if (m_show_restart_message) + { + m_restart_message_timer += delta_time; + if (m_restart_message_timer > 3.0f) + { + m_show_restart_message = false; + m_restart_message_timer = 0.0f; + } + } + } + + auto ConfigState::on_render() -> void + { + Renderer::set_clear_color({ 0.1f, 0.1f, 0.1f, 1.0f }); + Renderer::clear(); + } + + auto ConfigState::on_event(Event& event) -> void + { + } + + auto ConfigState::on_im_ui_render() -> void + { + ImGui::SetNextWindowSize(ImVec2(900, 700), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f), + ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); + + ImGui::Begin("Donut Configuration", nullptr, ImGuiWindowFlags_NoCollapse); + + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); + ImGui::TextColored(ImVec4(0.8f, 0.8f, 1.0f, 1.0f), "Donut Configuration"); + ImGui::PopFont(); + ImGui::Separator(); + + ImGui::Columns(2, "ConfigColumns", true); + + float available_height = ImGui::GetWindowHeight() - 140; + + ImGui::BeginChild("GraphicsSettings", ImVec2(0, available_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Graphics Settings"); + ImGui::Separator(); + + ImGui::Text("Render API:"); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "(requires restart)"); + + const char* api_names[] = { "OpenGL", "Vulkan" }; + static int current_api = (int)m_selected_api - 1; + + if (ImGui::BeginCombo("##RenderAPI", api_names[current_api])) + { + for (int i = 0; i < IM_ARRAYSIZE(api_names); i++) + { + const bool is_selected = (current_api == i); + if (ImGui::Selectable(api_names[i], is_selected)) + { + current_api = i; + m_selected_api = (RendererAPI::API)(i + 1); + m_show_restart_message = true; + m_restart_message_timer = 0.0f; + } + + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + + ImGui::Text("Current API: "); + ImGui::SameLine(); + const char* current_api_name = (Renderer::get_api() == RendererAPI::API::OpenGL) ? "OpenGL" : "Vulkan"; + ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), current_api_name); + + ImGui::Spacing(); + + ImGui::Checkbox("Enable VSync", &m_v_sync_enabled); + ImGui::SameLine(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "(recommended)"); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Display"); + ImGui::Separator(); + + ImGui::Text("Window Size: 1280x720"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Fullscreen: Not implemented yet"); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "System Info"); + ImGui::Separator(); + + ImGui::Text("OpenGL Version: %s", glGetString(GL_VERSION)); + ImGui::Text("GPU: %s", glGetString(GL_RENDERER)); + ImGui::Text("Vendor: %s", glGetString(GL_VENDOR)); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Performance"); + ImGui::Separator(); + + ImGui::SliderInt("Target FPS", &m_target_fps, 30, 120, "%d FPS"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Target frame rate for the simulation"); + + ImGui::Checkbox("Show FPS Counter", &m_show_fps); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Display current FPS in the simulation"); + + ImGui::Checkbox("Show Performance Metrics", &m_show_performance_metrics); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Show detailed performance information"); + + ImGui::Checkbox("Show Debug Info", &m_show_debug_info); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Display debug information and statistics"); + + ImGui::Checkbox("Enable Anti-Aliasing", &m_enable_anti_aliasing); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Enable anti-aliasing for smoother rendering"); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Theme"); + ImGui::Separator(); + + const char* theme_names[] = { "Dark", "Light", "Blue" }; + if (ImGui::Combo("UI Theme", &m_selected_theme, theme_names, IM_ARRAYSIZE(theme_names))) + ThemeManager::set_theme(static_cast<Theme>(m_selected_theme)); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Choose the application theme"); + + ImGui::EndChild(); + + ImGui::NextColumn(); + + ImGui::BeginChild("SimulationSettings", ImVec2(0, available_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Simulation Settings"); + ImGui::Separator(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Quality"); + ImGui::Separator(); + + ImGui::SliderInt("Compute Height", &m_compute_height, 64, 2048, "%d px"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Resolution of the compute shader. Higher values give better quality but lower performance."); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Higher = better quality, lower performance"); + + ImGui::SliderInt("Max Steps (Moving)", &m_max_steps_moving, 1000, 60000, "%d"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Maximum ray marching steps when camera is moving"); + + ImGui::SliderInt("Max Steps (Static)", &m_max_steps_static, 1000, 30000, "%d"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Maximum ray marching steps when camera is stationary"); + + ImGui::SliderFloat("Early Exit Distance", &m_early_exit_distance, 1e11f, 1e13f, "%.2e"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Distance at which ray marching stops to improve performance"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Distance at which ray marching stops"); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Physics"); + ImGui::Separator(); + + ImGui::Checkbox("Enable Gravity", &m_gravity_enabled); + + ImGui::EndChild(); + + ImGui::Columns(1); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + float button_width = (ImGui::GetWindowWidth() - 120) / 5.0f; + + 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(); + + ImGui::SameLine(); + if (ImGui::Button("Apply Settings", ImVec2(button_width, 35))) + apply_settings(); + + ImGui::SameLine(); + if (ImGui::Button("Save Settings", ImVec2(button_width, 35))) + { + apply_settings(); + DONUT_INFO("Settings saved manually"); + } + + ImGui::SameLine(); + if (ImGui::Button("Exit", ImVec2(button_width, 35))) + Application::get().close(); + + if (m_show_restart_message) + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.8f, 0.2f, 1.0f)); + ImGui::TextWrapped("Warning: Render API changed! Please restart the application for changes to take effect."); + ImGui::PopStyleColor(); + } + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Donut Engine v1.0.0 | Black Hole Simulation"); + + ImGui::End(); + } + + auto ConfigState::apply_settings() -> void + { + SimulationSettings sim_settings; + sim_settings.target_fps = m_target_fps; + sim_settings.compute_height = m_compute_height; + sim_settings.max_steps_moving = m_max_steps_moving; + sim_settings.max_steps_static = m_max_steps_static; + sim_settings.early_exit_distance = m_early_exit_distance; + sim_settings.gravity_enabled = m_gravity_enabled; + + GraphicsSettings gfx_settings; + gfx_settings.render_api = (m_selected_api == RendererAPI::API::Vulkan) ? "Vulkan" : "OpenGL"; + gfx_settings.v_sync_enabled = m_v_sync_enabled; + gfx_settings.show_fps = m_show_fps; + gfx_settings.show_performance_metrics = m_show_performance_metrics; + gfx_settings.show_debug_info = m_show_debug_info; + gfx_settings.enable_anti_aliasing = m_enable_anti_aliasing; + gfx_settings.selected_theme = (m_selected_theme == 1) ? "Light" : + (m_selected_theme == 2) ? "Blue" : "Dark"; + + SettingsManager::set_simulation_settings(sim_settings); + SettingsManager::set_graphics_settings(gfx_settings); + + RendererAPI::set_api(m_selected_api); + DONUT_INFO("Settings applied and saved"); + } + + auto ConfigState::reset_to_defaults() -> void + { + m_selected_api = RendererAPI::API::OpenGL; + m_target_fps = 60; + m_compute_height = 512; + m_max_steps_moving = 30000; + m_max_steps_static = 15000; + m_early_exit_distance = 5e12f; + m_gravity_enabled = true; + m_v_sync_enabled = true; + m_show_fps = true; + m_show_performance_metrics = true; + m_show_debug_info = false; + m_enable_anti_aliasing = true; + m_selected_theme = 0; + + apply_settings(); + DONUT_INFO("Settings reset to defaults and saved"); + } +}; diff --git a/src/states/config_state.h b/src/states/config_state.h new file mode 100644 index 0000000..e64889b --- /dev/null +++ b/src/states/config_state.h @@ -0,0 +1,47 @@ +#pragma once + +#include "core/state.h" +#include "core/event.h" +#include "core/log.h" +#include "rendering/renderer.h" + +namespace Donut +{ + class ConfigState + : public State + { + public: + ~ConfigState() = default; + + auto on_enter() -> void override; + auto on_exit() -> void override; + auto on_update(float delta_time) -> void override; + auto on_render() -> void override; + auto on_im_ui_render() -> void override; + auto on_event(Event& event) -> void override; + + private: + auto apply_settings() -> void; + auto reset_to_defaults() -> void; + + private: + RendererAPI::API m_selected_api = RendererAPI::API::OpenGL; + bool m_show_restart_message = false; + float m_restart_message_timer = 0.0f; + + int m_target_fps = 60; + int m_compute_height = 512; + int m_max_steps_moving = 30000; + int m_max_steps_static = 15000; + float m_early_exit_distance = 5e12f; + bool m_gravity_enabled = true; + + bool m_v_sync_enabled = true; + bool m_show_fps = true; + bool m_show_performance_metrics = true; + bool m_show_debug_info = false; + bool m_enable_anti_aliasing = true; + + int m_selected_theme = 0; // 0=Dark, 1=Light, 2=Blue + }; +};
\ No newline at end of file diff --git a/src/states/simulation_state.cpp b/src/states/simulation_state.cpp new file mode 100644 index 0000000..06955ff --- /dev/null +++ b/src/states/simulation_state.cpp @@ -0,0 +1,386 @@ +#include "simulation_state.h" +#include "rendering/renderer.h" +#include "core/application.h" +#include "core/hdri_manager.h" +#include "core/window.h" +#include "core/event.h" +#include "core/settings_manager.h" + +#include <imgui.h> +#include <GLFW/glfw3.h> +#include <chrono> +#include <iomanip> +#include <sstream> +#include <vector> + +namespace Donut +{ + auto SimulationState::on_enter() -> void + { + DONUT_INFO("Entering Simulation State"); + + const auto& settings = SettingsManager::get_settings_const(); + auto& engine = Application::get().get_engine(); + + engine.set_target_fps(settings.simulation.target_fps); + engine.set_compute_height(settings.simulation.compute_height); + engine.set_max_steps_moving(settings.simulation.max_steps_moving); + engine.set_max_steps_static(settings.simulation.max_steps_static); + engine.set_early_exit_distance(settings.simulation.early_exit_distance); + engine.get_gravity() = settings.simulation.gravity_enabled; + + engine.set_disk_thickness(settings.simulation.disk_thickness); + engine.set_disk_density(settings.simulation.disk_density); + engine.set_rotation_speed(settings.simulation.rotation_speed); + engine.set_blur_strength(settings.simulation.blur_strength); + engine.set_glow_intensity(settings.simulation.glow_intensity); + + engine.update_compute_dimensions(); + m_initialized = true; + } + + auto SimulationState::on_exit() -> void + { + DONUT_INFO("Exiting Simulation State"); + } + + auto SimulationState::on_update(float delta_time) -> void + { + auto& engine = Application::get().get_engine(); + engine.update_performance(delta_time); + engine.update_window_dimensions(); + engine.update_physics(delta_time); + + if (engine.get_camera().is_dragging()) + { + GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window()); + double xpos, ypos; + glfwGetCursorPos(window, &xpos, &ypos); + engine.get_camera().process_orbital_mouse_move(xpos, ypos); + } + } + + auto SimulationState::on_render() -> void + { + auto& engine = Application::get().get_engine(); + RenderCommand::set_clear_color(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + RenderCommand::clear(); + RenderCommand::set_viewport(0, 0, static_cast<uint32_t>(engine.get_width()), static_cast<uint32_t>(engine.get_height())); + + engine.dispatch_compute(engine.get_camera()); + engine.draw_blur_pass(); + } + + auto SimulationState::on_event(Event& event) -> void + { + auto& engine = Application::get().get_engine(); + + if (event.get_event_type() == EventType::MouseButtonPressed) + { + MouseButtonPressedEvent& e = (MouseButtonPressedEvent&)event; + int button = e.get_mouse_button(); + + GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window()); + Camera& camera = engine.get_camera(); + double last_x = camera.get_last_x(); + double last_y = camera.get_last_y(); + glfwGetCursorPos(window, &last_x, &last_y); + + camera.process_orbital_mouse_button(button, GLFW_PRESS, 0); + } + else if (event.get_event_type() == EventType::MouseButtonReleased) + { + MouseButtonReleasedEvent& e = (MouseButtonReleasedEvent&)event; + int button = e.get_mouse_button(); + engine.get_camera().process_orbital_mouse_button(button, GLFW_RELEASE, 0); + } + + if (event.get_event_type() == EventType::MouseScrolled) + { + MouseScrolledEvent& e = (MouseScrolledEvent&)event; + engine.get_camera().process_orbital_scroll(e.get_x_offset(), e.get_y_offset()); + } + + if (event.get_event_type() == EventType::KeyPressed) + { + KeyPressedEvent& e = (KeyPressedEvent&)event; + if (e.get_key_code() == GLFW_KEY_G) + { + engine.get_gravity() = !engine.get_gravity(); + DONUT_INFO("Gravity turned {}", engine.get_gravity() ? "ON" : "OFF"); + } + } + } + + auto SimulationState::on_im_ui_render() -> void + { + auto& engine = Application::get().get_engine(); + + ImGui::SetNextWindowSize(ImVec2(400, 600), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - 420, 20), ImGuiCond_FirstUseEver); + + ImGui::Begin("Simulation Controls", nullptr, ImGuiWindowFlags_NoCollapse); + + ImGui::TextColored(ImVec4(0.8f, 0.8f, 1.0f, 1.0f), "Black Hole Simulation"); + ImGui::SameLine(); + if (ImGui::Button("Back to Config")) + Application::get().get_state_manager().switch_to_state("Config"); + ImGui::SameLine(); + if (ImGui::Button("Save Settings")) + { + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.target_fps = engine.get_target_fps(); + settings.compute_height = engine.get_compute_height(); + settings.max_steps_moving = engine.get_max_steps_moving(); + settings.max_steps_static = engine.get_max_steps_static(); + settings.early_exit_distance = engine.get_early_exit_distance(); + settings.gravity_enabled = engine.get_gravity(); + SettingsManager::set_simulation_settings(settings); + DONUT_INFO("Simulation settings saved manually"); + } + + ImGui::Separator(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Performance"); + ImGui::Separator(); + + ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); + ImGui::Text("Frame Time: %.3f ms", 1000.0f / ImGui::GetIO().Framerate); + ImGui::Text("Engine FPS: %.1f", engine.get_current_fps()); + + int target_fps = engine.get_target_fps(); + if (ImGui::SliderInt("Target FPS", &target_fps, 30, 120)) + { + engine.set_target_fps(target_fps); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.target_fps = target_fps; + SettingsManager::set_simulation_settings(settings); + } + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Simulation Info"); + ImGui::Separator(); + + ImGui::Text("Resolution: %dx%d", engine.get_width(), engine.get_height()); + ImGui::Text("Compute Resolution: %dx%d", engine.get_compute_width(), engine.get_compute_height()); + ImGui::Text("Objects: %zu", engine.get_objects().size()); + + if (ImGui::Button("Print Object Info")) + engine.print_object_info(); + + int compute_height = engine.get_compute_height(); + if (ImGui::SliderInt("Compute Height", &compute_height, 64, 2048)) + { + engine.set_compute_height(compute_height); + engine.update_compute_dimensions(); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.compute_height = compute_height; + SettingsManager::set_simulation_settings(settings); + } + + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Compute Width: %d (auto-calculated)", engine.get_compute_width()); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Quality Settings"); + ImGui::Separator(); + + int max_steps_moving = engine.get_max_steps_moving(); + if (ImGui::SliderInt("Max Steps (Moving)", &max_steps_moving, 1000, 60000)) + { + engine.set_max_steps_moving(max_steps_moving); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.max_steps_moving = max_steps_moving; + SettingsManager::set_simulation_settings(settings); + } + int max_steps_static = engine.get_max_steps_static(); + if (ImGui::SliderInt("Max Steps (Static)", &max_steps_static, 1000, 30000)) + { + engine.set_max_steps_static(max_steps_static); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.max_steps_static = max_steps_static; + SettingsManager::set_simulation_settings(settings); + } + float early_exit_distance = engine.get_early_exit_distance(); + if (ImGui::SliderFloat("Early Exit Distance", &early_exit_distance, 1e11f, 1e13f, "%.2e")) + { + engine.set_early_exit_distance(early_exit_distance); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.early_exit_distance = early_exit_distance; + SettingsManager::set_simulation_settings(settings); + } + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Physics"); + ImGui::Separator(); + + bool& gravity = engine.get_gravity(); + if (ImGui::Checkbox("Gravity Enabled", &gravity)) + { + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.gravity_enabled = gravity; + SettingsManager::set_simulation_settings(settings); + } + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Accretion Disk"); + ImGui::Separator(); + + float disk_thickness = engine.get_disk_thickness(); + if (ImGui::SliderFloat("Cloud Thickness", &disk_thickness, 0.1f, 2.0f, "%.2f")) + { + engine.set_disk_thickness(disk_thickness); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.disk_thickness = disk_thickness; + SettingsManager::set_simulation_settings(settings); + } + ImGui::TextDisabled("Thickness relative to Schwarzschild radius"); + + float disk_density = engine.get_disk_density(); + if (ImGui::SliderFloat("Cloud Density", &disk_density, 0.1f, 3.0f, "%.2f")) + { + engine.set_disk_density(disk_density); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.disk_density = disk_density; + SettingsManager::set_simulation_settings(settings); + } + ImGui::TextDisabled("Overall density multiplier"); + + float rotation_speed = engine.get_rotation_speed(); + if (ImGui::SliderFloat("Rotation Speed", &rotation_speed, 0.0f, 3.0f, "%.2f")) + { + engine.set_rotation_speed(rotation_speed); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.rotation_speed = rotation_speed; + SettingsManager::set_simulation_settings(settings); + } + ImGui::TextDisabled("Rotation speed multiplier (0 = no rotation)"); + + float blur_strength = engine.get_blur_strength(); + if (ImGui::SliderFloat("Blur Strength", &blur_strength, 0.5f, 5.0f, "%.2f")) + { + engine.set_blur_strength(blur_strength); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.blur_strength = blur_strength; + SettingsManager::set_simulation_settings(settings); + } + ImGui::TextDisabled("Blur radius for glow effect"); + + float glow_intensity = engine.get_glow_intensity(); + if (ImGui::SliderFloat("Glow Intensity", &glow_intensity, 0.1f, 3.0f, "%.2f")) + { + engine.set_glow_intensity(glow_intensity); + SimulationSettings settings = SettingsManager::get_settings_const().simulation; + settings.glow_intensity = glow_intensity; + SettingsManager::set_simulation_settings(settings); + } + ImGui::TextDisabled("Intensity of the glow effect"); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "HDRI Environment"); + ImGui::Separator(); + + static int selected_hdri = 0; + ImGui::PushID("SimulationHDRI"); + auto& hdri_manager = HDRIManager::get(); + const auto& available_hdri = hdri_manager.get_available_hdri(); + + static std::vector<std::string> hdri_option_names; + static std::vector<const char*> hdri_options; + + if (hdri_option_names.size() != available_hdri.size()) + { + hdri_option_names.clear(); + hdri_options.clear(); + + for (const auto& path : available_hdri) + { + hdri_option_names.push_back(hdri_manager.get_hdri_name(path)); + hdri_options.push_back(hdri_option_names.back().c_str()); + } + } + + if (ImGui::Combo("HDRI Environment", &selected_hdri, hdri_options.data(), static_cast<int>(hdri_options.size()))) + { + auto& hdri_manager = HDRIManager::get(); + hdri_manager.set_current_hdri(available_hdri[selected_hdri]); + engine.set_hdri_environment(hdri_manager.get_current_hdri()); + } + ImGui::TextDisabled("HDRI provides background and lighting for the simulation"); + ImGui::PopID(); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Camera"); + ImGui::Separator(); + + ImGui::Text("Position: (%.2e, %.2e, %.2e)", + engine.get_camera().get_orbital_position().x, + engine.get_camera().get_orbital_position().y, + engine.get_camera().get_orbital_position().z); + ImGui::Text("Radius: %.2e", engine.get_camera().get_orbital_radius()); + ImGui::Text("Azimuth: %.2f", engine.get_camera().get_azimuth()); + ImGui::Text("Elevation: %.2f", engine.get_camera().get_elevation()); + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Export"); + ImGui::Separator(); + + if (ImGui::Button("Export Frame (1080p)", ImVec2(-1, 30))) + { + auto& engine = Application::get().get_engine(); + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "frame_1080p_" << std::put_time(std::localtime(&time_t), "%Y-%m-%d_%H-%M-%S") << ".png"; + engine.export_high_res_frame(ss.str(), 1920, 1080); + } + + if (ImGui::Button("Export High-Res Frame (4K)", ImVec2(-1, 30))) + { + auto& engine = Application::get().get_engine(); + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "high_res_frame_4k_" << std::put_time(std::localtime(&time_t), "%Y-%m-%d_%H-%M-%S") << ".png"; + engine.export_high_res_frame(ss.str(), 4096, 3072); + } + + if (ImGui::Button("Export High-Res Frame (8K)", ImVec2(-1, 30))) + { + auto& engine = Application::get().get_engine(); + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "high_res_frame_8k_" << std::put_time(std::localtime(&time_t), "%Y-%m-%d_%H-%M-%S") << ".png"; + engine.export_high_res_frame(ss.str(), 8192, 6144); + } + + if (ImGui::Button("Export Ultra High-Res Frame (16K)", ImVec2(-1, 30))) + { + auto& engine = Application::get().get_engine(); + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << "high_res_frame_16k_" << std::put_time(std::localtime(&time_t), "%Y%m%d_%H%M%S") << ".png"; + engine.export_high_res_frame(ss.str(), 16384, 12288); + } + + ImGui::Spacing(); + + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Controls"); + ImGui::Separator(); + + ImGui::Text("Left Mouse: Orbit camera"); + ImGui::Text("Scroll: Zoom in/out"); + ImGui::Text("G: Toggle gravity"); + ImGui::Text("Right Mouse: Enable gravity (hold)"); + + ImGui::End(); + } +}; diff --git a/src/states/simulation_state.h b/src/states/simulation_state.h new file mode 100644 index 0000000..625d19e --- /dev/null +++ b/src/states/simulation_state.h @@ -0,0 +1,27 @@ +#pragma once + +#include "core/state.h" +#include "core/event.h" +#include "core/log.h" +#include "engine/engine.h" +#include <vector> + +namespace Donut +{ + class SimulationState + : public State + { + public: + ~SimulationState() = default; + + auto on_enter() -> void override; + auto on_exit() -> void override; + auto on_update(float delta_time) -> void override; + auto on_render() -> void override; + auto on_im_ui_render() -> void override; + auto on_event(Event& event) -> void override; + + private: + bool m_initialized = false; + }; +}; diff --git a/src/states/world_builder_state.cpp b/src/states/world_builder_state.cpp new file mode 100644 index 0000000..09283c3 --- /dev/null +++ b/src/states/world_builder_state.cpp @@ -0,0 +1,1113 @@ +#include "world_builder_state.h" + +#include "core/application.h" +#include "core/window.h" +#include "core/hdri_manager.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 <nlohmann/json.hpp> + +#include <numbers> +#include <fstream> +#include <sstream> +#include <limits> +#include <vector> + +namespace Donut +{ + auto WorldBuilderState::on_enter() -> void + { + 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>(std::numbers::pi) / 3.0f); + m_camera.update_orbital(); + + m_sphere_shader = Ref<Shader>(Shader::create("assets/shaders/Sphere.glsl")); + m_skybox_shader = Ref<Shader>(Shader::create("assets/shaders/Skybox.glsl")); + m_grid_shader = Ref<Shader>(Shader::create("assets/shaders/Grid.glsl")); + + if (!m_sphere_shader) + DONUT_ERROR("Failed to create sphere shader"); + if (!m_skybox_shader) + DONUT_ERROR("Failed to create skybox shader"); + if (!m_grid_shader) + DONUT_ERROR("Failed to create grid shader"); + + initialize_sphere_geometry(); + initialize_skybox_geometry(); + initialize_grid_geometry(); + + auto& hdri_manager = HDRIManager::get(); + m_hdri_environment = hdri_manager.get_current_hdri(); + if (!m_hdri_environment) + { + hdri_manager.set_current_hdri("assets/hdri/HDR_blue_nebulae-1.hdr"); + m_hdri_environment = hdri_manager.get_current_hdri(); + if (!m_hdri_environment) + DONUT_WARN("Failed to load default HDRI for WorldBuilder, using fallback"); + } + + Material black_hole_material(glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, 0.0f); + m_black_hole = Object(glm::vec3(0.0f, 0.0f, 0.0f), 2.0f, black_hole_material); + m_black_hole_initialized = true; + + // Set default grid size + m_grid_size = 10.0f; + + m_initialized = true; + } + + auto WorldBuilderState::on_exit() -> void + { + DONUT_INFO("Exiting World Builder State"); + } + + auto WorldBuilderState::on_update(float delta_time) -> void + { + 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>(std::numbers::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; + } + } + + auto WorldBuilderState::on_render() -> void + { + auto& hdri_manager = HDRIManager::get(); + m_hdri_environment = hdri_manager.get_current_hdri(); + + RenderCommand::set_clear_color(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f)); + RenderCommand::clear(); + + if (m_hdri_environment) + render_skybox(); + + if (m_show_grid) + render_grid(); + + render_scene(); + } + + auto WorldBuilderState::on_event(Event& event) -> void + { + 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 * static_cast<float>(xpos)) / static_cast<float>(width) - 1.0f; + float ndc_y = 1.0f - (2.0f * static_cast<float>(ypos)) / static_cast<float>(height); + + glm::vec4 rayStart_NDC(ndc_x, ndc_y, -1.0f, 1.0f); + glm::vec4 rayEnd_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 rayStart_World = inv_vp * rayStart_NDC; + glm::vec4 rayEnd_World = inv_vp * rayEnd_NDC; + + rayStart_World /= rayStart_World.w; + rayEnd_World /= rayEnd_World.w; + + glm::vec3 ray_dir = glm::normalize(glm::vec3(rayEnd_World - rayStart_World)); + glm::vec3 ray_origin = glm::vec3(rayStart_World); + + float closest_distance = std::numeric_limits<float>::max(); + int closest_object_index = -1; + + if (m_black_hole_initialized) + { + glm::vec3 oc = ray_origin - m_black_hole.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) - m_black_hole.m_radius * m_black_hole.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 = -2; + } + else if (t2 > 0 && t2 < closest_distance) + { + closest_distance = t2; + closest_object_index = -2; + } + } + } + + 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 if (closest_object_index == -2) + { + m_selected_object_index = -1; + DONUT_INFO("Black hole clicked (not selectable)"); + return true; + } + else + { + m_selected_object_index = -1; + 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_M: + m_gizmo_operation = ImGuizmo::TRANSLATE; + return true; + case GLFW_KEY_R: + m_gizmo_operation = ImGuizmo::ROTATE; + return true; + case GLFW_KEY_S: + m_gizmo_operation = ImGuizmo::SCALE; + return true; + } + } + return false; + }); + } + + auto WorldBuilderState::on_im_ui_render() -> void + { + 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")) + { + ImGui::Text("Objects in scene: %zu/16 (+ 1 black hole)", 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>(std::numbers::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")) + { + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "New Sphere"); + ImGui::Separator(); + + ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), "Objects: %zu/16", m_scene.objs.size()); + + 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 (m_scene.objs.size() >= 16) + { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f); + ImGui::Button("add Sphere (Limit Reached)", ImVec2(ImGui::GetWindowWidth() - 20, 30)); + ImGui::PopStyleVar(); + } + else + { + if (ImGui::Button("add Sphere", ImVec2(ImGui::GetWindowWidth() - 20, 30))) + add_sphere(); + } + } + + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("Scene Objects")) + { + if (m_black_hole_initialized) + { + ImGui::PushID(-1); + + ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "Black Hole (Center)"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Position: (0.00, 0.00, 0.00)"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Radius: %.2f", m_black_hole.m_radius); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Color: Black (with white outline)"); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Cannot be removed or modified"); + + ImGui::PopID(); + ImGui::Separator(); + } + + if (m_scene.objs.empty()) + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "No additional 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(); + } + } + } + + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("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), "M = Move, R = Rotate, S = Scale"); + } + + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("Selection Outline")) + { + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Outline Settings:"); + + ImGui::Text("Outline Color:"); + ImGui::ColorEdit3("##OutlineColor", &m_outline_color.x); + + ImGui::Text("Outline Width:"); + ImGui::SliderFloat("##OutlineWidth", &m_outline_width, 0.01f, 0.9f, "%.2f"); + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "White rim around sphere silhouette"); + } + + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("Grid Settings")) + { + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Grid Settings:"); + + ImGui::Text("Show Grid:"); + ImGui::SameLine(); + ImGui::Checkbox("##ShowGrid", &m_show_grid); + + ImGui::Text("Grid Color:"); + ImGui::ColorEdit3("##GridColor", &m_grid_color.x); + + ImGui::Text("Grid Alpha:"); + ImGui::SliderFloat("##GridAlpha", &m_grid_alpha, 0.0f, 1.0f, "%.2f"); + + ImGui::Text("Grid Size:"); + ImGui::SliderFloat("##GridSize", &m_grid_size, 1.0f, 500.0f, "%.1f"); + + ImGui::Spacing(); + if (ImGui::Button("Regenerate Grid", ImVec2(ImGui::GetWindowWidth() - 20, 25))) + initialize_grid_geometry(); + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Reference grid for spatial orientation"); + } + + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("HDRI Environment", nullptr)) + { + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "HDRI Settings:"); + + static int selected_hdri = 0; + ImGui::PushID("WorldBuilderHDRI"); + auto& hdri_manager = HDRIManager::get(); + const auto& available_hdri = hdri_manager.get_available_hdri(); + + static std::vector<std::string> hdri_option_names; + static std::vector<const char*> hdri_options; + + if (hdri_option_names.size() != available_hdri.size()) + { + hdri_option_names.clear(); + hdri_options.clear(); + + for (const auto& path : available_hdri) + { + hdri_option_names.push_back(hdri_manager.get_hdri_name(path)); + hdri_options.push_back(hdri_option_names.back().c_str()); + } + } + + if (ImGui::Combo("HDRI Environment", &selected_hdri, hdri_options.data(), static_cast<int>(hdri_options.size()))) + { + auto& hdri_manager = HDRIManager::get(); + hdri_manager.set_current_hdri(available_hdri[selected_hdri]); + m_hdri_environment = hdri_manager.get_current_hdri(); + } + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "HDRI provides background skybox and lighting for the scene"); + ImGui::PopID(); + } + + 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("Start Simulation", ImVec2(ImGui::GetWindowWidth() - 20, 30))) + { + auto& engine = Application::get().get_engine(); + engine.load_objects_from_scene(m_scene.objs); + Application::get().get_state_manager().switch_to_state("Simulation"); + } + + 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: M=Move, R=Rotate, S=Scale"); + + ImGui::End(); + + if (m_selected_object_index >= 0 && + m_selected_object_index < static_cast<int>(m_scene.objs.size()) && + m_selected_object_index != -2) + { + 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); + } + } + } + + auto WorldBuilderState::add_sphere() -> void + { + if (m_scene.objs.size() >= 16) + { + DONUT_WARN("Cannot add more objects. Maximum of 16 objects reached."); + return; + } + + 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_selected_object_index = static_cast<int>(m_scene.objs.size() - 1); + + 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 and selected it"); + } + + auto WorldBuilderState::remove_selected_object() -> void + { + 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"); + } + } + + auto WorldBuilderState::clear_scene() -> void + { + m_scene.objs.clear(); + m_selected_object_index = -1; + DONUT_INFO("Cleared scene (black hole remains at center)"); + } + + auto WorldBuilderState::save_scene() -> void + { + nlohmann::json scene_data; + scene_data["objects"] = nlohmann::json::array(); + + for (const auto& obj : m_scene.objs) + { + nlohmann::json sphere_data; + sphere_data["position"] = + { + obj.m_centre.x, + obj.m_centre.y, + obj.m_centre.z + }; + + sphere_data["color"] = + { + obj.m_material.m_color.x, + obj.m_material.m_color.y, + obj.m_material.m_color.z + }; + + sphere_data["radius"] = obj.m_radius; + sphere_data["specular"] = obj.m_material.m_specular; + sphere_data["emission"] = obj.m_material.m_emission; + scene_data["objects"].push_back(sphere_data); + } + + std::ofstream file("Scene.json"); + if (file.is_open()) + { + file << scene_data.dump(4); + file.close(); + DONUT_INFO("Scene saved to Scene.json (black hole always present at center)"); + } + else + DONUT_ERROR("Failed to save scene"); + } + + auto WorldBuilderState::load_scene() -> void + { + std::ifstream file("Scene.json"); + if (file.is_open()) + { + m_scene.objs.clear(); + m_selected_object_index = -1; + + nlohmann::json scene_data; + file >> scene_data; + + if (scene_data.contains("objects")) + { + for (const auto& sphere_data : scene_data["objects"]) + { + glm::vec3 position; + glm::vec3 color; + float radius; + float specular; + float emission; + + auto pos_array = sphere_data["position"]; + position.x = pos_array[0].get<float>(); + position.y = pos_array[1].get<float>(); + position.z = pos_array[2].get<float>(); + + auto color_array = sphere_data["color"]; + color.x = color_array[0].get<float>(); + color.y = color_array[1].get<float>(); + color.z = color_array[2].get<float>(); + + radius = sphere_data["radius"].get<float>(); + specular = sphere_data["specular"].get<float>(); + emission = sphere_data["emission"].get<float>(); + + Material material(color, specular, emission); + Object sphere(position, radius, material); + m_scene.objs.push_back(sphere); + } + } + + file.close(); + DONUT_INFO("Scene loaded from Scene.json (black hole remains at center)"); + } + else + DONUT_ERROR("Failed to load scene"); + } + + auto WorldBuilderState::initialize_sphere_geometry() -> void + { + 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>(std::numbers::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>(std::numbers::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()))); + + m_sphere_vao = Ref<VertexArray>(VertexArray::create()); + m_sphere_vao->add_vertex_buffer(vertex_buffer); + m_sphere_vao->set_index_buffer(index_buffer); + } + + auto WorldBuilderState::render_scene() -> void + { + 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(); + + 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(); + + m_sphere_shader->bind(); + m_sphere_shader->set_mat4("u_ViewProjection", view_projection); + m_sphere_shader->set_float3("u_LightPos", light_pos); + m_sphere_shader->set_float3("u_CameraPos", camera_pos); + + m_sphere_shader->set_float3("u_OutlineColor", m_outline_color); + m_sphere_shader->set_float("u_OutlineWidth", m_outline_width); + + if (m_hdri_environment) + { + m_hdri_environment->bind(1); + m_sphere_shader->set_int("u_HDRIEnvironment", 1); + } + + if (m_black_hole_initialized) + { + glm::mat4 black_hole_transform = glm::translate(glm::mat4(1.0f), m_black_hole.m_centre); + black_hole_transform = glm::scale(black_hole_transform, glm::vec3(m_black_hole.m_radius)); + + m_sphere_shader->set_mat4("u_Transform", black_hole_transform); + m_sphere_shader->set_int("u_IsSelected", 1); + m_sphere_shader->set_float3("u_Color", m_black_hole.m_material.m_color); + m_sphere_shader->set_float("u_Emission", m_black_hole.m_material.m_emission); + m_sphere_shader->set_float("u_Specular", m_black_hole.m_material.m_specular); + m_sphere_shader->set_float("u_OutlineWidth", 0.3f); + + m_sphere_vao->bind(); + RenderCommand::draw_indexed(m_sphere_vao); + + m_sphere_shader->set_float("u_OutlineWidth", m_outline_width); + } + + 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)); + + m_sphere_shader->set_mat4("u_Transform", transform); + + bool is_selected = (m_selected_object_index == static_cast<int>(i)); + m_sphere_shader->set_int("u_IsSelected", is_selected ? 1 : 0); + if (is_selected) + { + glm::vec3 highlight_color = obj.m_material.m_color * 1.5f; + highlight_color = glm::clamp(highlight_color, 0.0f, 1.0f); + m_sphere_shader->set_float3("u_Color", highlight_color); + m_sphere_shader->set_float("u_Emission", 0.2f); + } + else + { + m_sphere_shader->set_float3("u_Color", obj.m_material.m_color); + m_sphere_shader->set_float("u_Emission", obj.m_material.m_emission); + } + + m_sphere_shader->set_float("u_Specular", obj.m_material.m_specular); + + m_sphere_vao->bind(); + RenderCommand::draw_indexed(m_sphere_vao); + } + + RenderCommand::disable_depth_test(); + } + + + + auto WorldBuilderState::initialize_skybox_geometry() -> void + { + float skybox_vertices[] = + { + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + -1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f + }; + + auto vertex_buffer = Ref<VertexBuffer>(VertexBuffer::create(skybox_vertices, sizeof(skybox_vertices))); + VertexBufferLayout layout; + layout.push<float>(3); + vertex_buffer->set_layout(layout); + + m_skybox_vao = Ref<VertexArray>(VertexArray::create()); + m_skybox_vao->add_vertex_buffer(vertex_buffer); + } + + auto WorldBuilderState::render_skybox() -> void + { + if (!m_skybox_shader || !m_skybox_vao || !m_hdri_environment) + return; + + 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::disable_depth_test(); + + glm::mat4 view = m_camera.get_view_matrix(); + glm::mat4 projection = m_camera.get_projection_matrix(); + + view = glm::mat4(glm::mat3(view)); + + m_skybox_shader->bind(); + m_skybox_shader->set_mat4("u_View", view); + m_skybox_shader->set_mat4("u_Projection", projection); + + m_hdri_environment->bind(0); + m_skybox_shader->set_int("u_Skybox", 0); + + m_skybox_vao->bind(); + RenderCommand::draw_arrays(36); + + RenderCommand::enable_depth_test(); + } + + auto WorldBuilderState::initialize_grid_geometry() -> void + { + if (m_grid_vao) + { + m_grid_vao->unbind(); + m_grid_vao.reset(); + } + + const float grid_size = 50.0f; + const int grid_lines = 101; + const float half_size = grid_size * 0.5f; + const float step = grid_size / (grid_lines - 1); + + std::vector<float> vertices; + std::vector<uint32_t> indices; + + for (int i = 0; i < grid_lines; ++i) + { + float z = -half_size + i * step; + + vertices.push_back(-half_size); + vertices.push_back(0.0f); + vertices.push_back(z); + + vertices.push_back(half_size); + vertices.push_back(0.0f); + vertices.push_back(z); + + uint32_t base_index = static_cast<uint32_t>(vertices.size() / 3) - 2; + indices.push_back(base_index); + indices.push_back(base_index + 1); + } + + for (int i = 0; i < grid_lines; ++i) + { + float x = -half_size + i * step; + + vertices.push_back(x); + vertices.push_back(0.0f); + vertices.push_back(-half_size); + + vertices.push_back(x); + vertices.push_back(0.0f); + vertices.push_back(half_size); + + uint32_t base_index = static_cast<uint32_t>(vertices.size() / 3) - 2; + indices.push_back(base_index); + indices.push_back(base_index + 1); + } + + auto vertex_buffer = Ref<VertexBuffer>(VertexBuffer::create(vertices.data(), static_cast<uint32_t>(vertices.size() * sizeof(float)))); + VertexBufferLayout layout; + layout.push<float>(3); + vertex_buffer->set_layout(layout); + + auto index_buffer = Ref<IndexBuffer>(IndexBuffer::create(indices.data(), static_cast<uint32_t>(indices.size()))); + + m_grid_vao = Ref<VertexArray>(VertexArray::create()); + m_grid_vao->add_vertex_buffer(vertex_buffer); + m_grid_vao->set_index_buffer(index_buffer); + + m_grid_vao->unbind(); + } + + auto WorldBuilderState::render_grid() -> void + { + if (!m_grid_shader || !m_grid_vao) + return; + + 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(); + RenderCommand::enable_blending(); + + glm::mat4 view = m_camera.get_view_matrix(); + glm::mat4 projection = m_camera.get_projection_matrix(); + glm::mat4 view_projection = projection * view; + glm::mat4 transform = glm::mat4(1.0f); + + glm::vec3 camera_pos = m_camera.get_orbital_position(); + + m_grid_shader->bind(); + m_grid_shader->set_mat4("u_ViewProjection", view_projection); + m_grid_shader->set_mat4("u_Transform", transform); + m_grid_shader->set_float3("u_GridColor", m_grid_color); + m_grid_shader->set_float("u_GridAlpha", m_grid_alpha); + m_grid_shader->set_float("u_GridSize", m_grid_size); + m_grid_shader->set_float3("u_CameraPos", camera_pos); + + m_grid_vao->bind(); + RenderCommand::draw_lines(m_grid_vao); + + // Clean up state to prevent conflicts with scene rendering + m_grid_vao->unbind(); + m_grid_shader->unbind(); + RenderCommand::disable_blending(); + + // Ensure depth test is still enabled for scene rendering + RenderCommand::enable_depth_test(); + } +}; diff --git a/src/states/world_builder_state.h b/src/states/world_builder_state.h new file mode 100644 index 0000000..8b4678a --- /dev/null +++ b/src/states/world_builder_state.h @@ -0,0 +1,96 @@ +#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; + + auto on_enter() -> void override; + auto on_exit() -> void override; + auto on_update(float delta_time) -> void override; + auto on_render() -> void override; + auto on_im_ui_render() -> void override; + auto on_event(Event& event) -> void override; + private: + auto add_sphere() -> void; + auto remove_selected_object() -> void; + auto clear_scene() -> void; + auto save_scene() -> void; + auto load_scene() -> void; + auto render_scene() -> void; + auto initialize_sphere_geometry() -> void; + auto initialize_grid_geometry() -> void; + auto render_grid() -> void; + private: + Scene m_scene; + Camera m_camera; + bool m_initialized = false; + + Ref<Shader> m_sphere_shader; + Ref<VertexArray> m_sphere_vao; + Ref<CubemapTexture> m_hdri_environment; + + Ref<Shader> m_skybox_shader; + Ref<VertexArray> m_skybox_vao; + + Ref<Shader> m_grid_shader; + Ref<VertexArray> m_grid_vao; + + 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; + bool m_show_outline_controls = true; + bool m_show_grid = true; + + glm::vec3 m_outline_color = glm::vec3(1.0f, 1.0f, 1.0f); + float m_outline_width = 0.25f; + + glm::vec3 m_grid_color = glm::vec3(0.5f, 0.5f, 0.5f); + float m_grid_alpha = 0.5f; + float m_grid_size = 50.0f; + + Object m_black_hole; + bool m_black_hole_initialized = false; + + auto set_hdri_environment(Ref<CubemapTexture> hdri) -> void { m_hdri_environment = hdri; } + auto get_hdri_environment() const -> Ref<CubemapTexture> { return m_hdri_environment; } + + auto initialize_skybox_geometry() -> void; + auto render_skybox() -> void; + }; +}; |
