#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 #include #include #include #include #include #include #include #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(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(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(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([this](MouseButtonPressedEvent& e) { if (e.get_mouse_button() == GLFW_MOUSE_BUTTON_LEFT) { if (ImGuizmo::IsUsing() || ImGuizmo::IsOver()) return false; GLFWwindow* window = static_cast(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::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(i); } else if (t2 > 0 && t2 < closest_distance) { closest_distance = t2; closest_object_index = static_cast(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([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([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(new_width) / static_cast(new_height); m_camera.set_projection(45.0f, aspect, 0.1f, 1000.0f); } return false; }); dispatcher.dispatch([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([this](KeyPressedEvent& e) { if (m_selected_object_index >= 0 && m_selected_object_index < static_cast(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(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(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(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(i)); bool is_selected = (m_selected_object_index == static_cast(i)); if (ImGui::Selectable(("Sphere " + std::to_string(i)).c_str(), is_selected)) m_selected_object_index = static_cast(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(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(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(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 sphere_vao = nullptr; static Ref sphere_shader = Ref(Shader::create("assets/shaders/sphere.glsl")); if (!sphere_vao) { std::vector vertices; std::vector indices; const int segments = 32; const int rings = 16; for (int ring = 0; ring <= rings; ++ring) { float phi = static_cast(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(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::create(vertices.data(), static_cast(vertices.size() * sizeof(float)))); VertexBufferLayout layout; layout.push(3); layout.push(3); vertex_buffer->set_layout(layout); auto index_buffer = Ref(IndexBuffer::create(indices.data(), static_cast(indices.size()))); sphere_vao = Ref(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(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(); } };