diff --git a/src/Application.cpp b/src/Application.cpp index 4fcdb49..bf3cb95 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -2,9 +2,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -122,6 +124,40 @@ auto linux_key_to_imgui(uint32_t keycode) -> std::optional return ImGuiKey_F11; case KEY_F12: return ImGuiKey_F12; + case KEY_KP0: + return ImGuiKey_Keypad0; + case KEY_KP1: + return ImGuiKey_Keypad1; + case KEY_KP2: + return ImGuiKey_Keypad2; + case KEY_KP3: + return ImGuiKey_Keypad3; + case KEY_KP4: + return ImGuiKey_Keypad4; + case KEY_KP5: + return ImGuiKey_Keypad5; + case KEY_KP6: + return ImGuiKey_Keypad6; + case KEY_KP7: + return ImGuiKey_Keypad7; + case KEY_KP8: + return ImGuiKey_Keypad8; + case KEY_KP9: + return ImGuiKey_Keypad9; + case KEY_KPDOT: + return ImGuiKey_KeypadDecimal; + case KEY_KPENTER: + return ImGuiKey_KeypadEnter; + case KEY_KPPLUS: + return ImGuiKey_KeypadAdd; + case KEY_KPMINUS: + return ImGuiKey_KeypadSubtract; + case KEY_KPASTERISK: + return ImGuiKey_KeypadMultiply; + case KEY_KPSLASH: + return ImGuiKey_KeypadDivide; + case KEY_KPEQUAL: + return ImGuiKey_KeypadEqual; case KEY_0: return ImGuiKey_0; case KEY_1: @@ -199,6 +235,142 @@ auto linux_key_to_imgui(uint32_t keycode) -> std::optional } } +auto linux_key_to_char(uint32_t keycode, bool shift) -> std::optional +{ + switch (keycode) { + case KEY_0: + return shift ? U')' : U'0'; + case KEY_1: + return shift ? U'!' : U'1'; + case KEY_2: + return shift ? U'@' : U'2'; + case KEY_3: + return shift ? U'#' : U'3'; + case KEY_4: + return shift ? U'$' : U'4'; + case KEY_5: + return shift ? U'%' : U'5'; + case KEY_6: + return shift ? U'^' : U'6'; + case KEY_7: + return shift ? U'&' : U'7'; + case KEY_8: + return shift ? U'*' : U'8'; + case KEY_9: + return shift ? U'(' : U'9'; + case KEY_KP0: + return U'0'; + case KEY_KP1: + return U'1'; + case KEY_KP2: + return U'2'; + case KEY_KP3: + return U'3'; + case KEY_KP4: + return U'4'; + case KEY_KP5: + return U'5'; + case KEY_KP6: + return U'6'; + case KEY_KP7: + return U'7'; + case KEY_KP8: + return U'8'; + case KEY_KP9: + return U'9'; + case KEY_Q: + return shift ? U'Q' : U'q'; + case KEY_W: + return shift ? U'W' : U'w'; + case KEY_E: + return shift ? U'E' : U'e'; + case KEY_R: + return shift ? U'R' : U'r'; + case KEY_T: + return shift ? U'T' : U't'; + case KEY_Y: + return shift ? U'Y' : U'y'; + case KEY_U: + return shift ? U'U' : U'u'; + case KEY_I: + return shift ? U'I' : U'i'; + case KEY_O: + return shift ? U'O' : U'o'; + case KEY_P: + return shift ? U'P' : U'p'; + case KEY_A: + return shift ? U'A' : U'a'; + case KEY_S: + return shift ? U'S' : U's'; + case KEY_D: + return shift ? U'D' : U'd'; + case KEY_F: + return shift ? U'F' : U'f'; + case KEY_G: + return shift ? U'G' : U'g'; + case KEY_H: + return shift ? U'H' : U'h'; + case KEY_J: + return shift ? U'J' : U'j'; + case KEY_K: + return shift ? U'K' : U'k'; + case KEY_L: + return shift ? U'L' : U'l'; + case KEY_Z: + return shift ? U'Z' : U'z'; + case KEY_X: + return shift ? U'X' : U'x'; + case KEY_C: + return shift ? U'C' : U'c'; + case KEY_V: + return shift ? U'V' : U'v'; + case KEY_B: + return shift ? U'B' : U'b'; + case KEY_N: + return shift ? U'N' : U'n'; + case KEY_M: + return shift ? U'M' : U'm'; + case KEY_SPACE: + return U' '; + case KEY_MINUS: + return shift ? U'_' : U'-'; + case KEY_EQUAL: + return shift ? U'+' : U'='; + case KEY_LEFTBRACE: + return shift ? U'{' : U'['; + case KEY_RIGHTBRACE: + return shift ? U'}' : U']'; + case KEY_BACKSLASH: + return shift ? U'|' : U'\\'; + case KEY_SEMICOLON: + return shift ? U':' : U';'; + case KEY_APOSTROPHE: + return shift ? U'"' : U'\''; + case KEY_GRAVE: + return shift ? U'~' : U'`'; + case KEY_COMMA: + return shift ? U'<' : U','; + case KEY_DOT: + return shift ? U'>' : U'.'; + case KEY_SLASH: + return shift ? U'?' : U'/'; + case KEY_KPDOT: + return U'.'; + case KEY_KPPLUS: + return U'+'; + case KEY_KPMINUS: + return U'-'; + case KEY_KPASTERISK: + return U'*'; + case KEY_KPSLASH: + return U'/'; + case KEY_KPEQUAL: + return U'='; + default: + return std::nullopt; + } +} + } // namespace namespace Lunar { @@ -226,6 +398,8 @@ Application::Application() m_logger.info("App init done!"); m_renderer->set_antialiasing(VulkanRenderer::AntiAliasingKind::MSAA_4X); + + m_cursor = PolarCoordinate::from_vec3(m_camera.target - m_camera.position); } Application::~Application() @@ -252,6 +426,7 @@ auto Application::run() -> void while (m_running) { uint64_t now { SDL_GetTicks() }; uint64_t dt { now - last }; + float dt_seconds { static_cast(dt) / 1000.0f }; last = now; if (dt > 0) @@ -273,6 +448,8 @@ auto Application::run() -> void } else if (e.type == SDL_EVENT_MOUSE_MOTION) { m_mouse_x = e.motion.x; m_mouse_y = e.motion.y; + m_mouse_dx = e.motion.xrel; + m_mouse_dy = e.motion.yrel; forward_to_imgui = true; } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN || e.type == SDL_EVENT_MOUSE_BUTTON_UP) { @@ -289,26 +466,119 @@ auto Application::run() -> void ImGui_ImplSDL3_ProcessEvent(&e); } + auto const target_offset { m_camera.target - m_camera.position }; + auto const target_distance { target_offset.magnitude() }; + auto const target_polar { PolarCoordinate::from_vec3(target_offset) }; + // Keep cursor angles in sync with externally-updated targets so view + // aligns to the target direction. Preserve radius when the target sits + // on top of the camera to avoid collapsing it. + if (target_distance > 0.0f) { + m_cursor.r = target_distance; + m_cursor.theta = target_polar.theta; + m_cursor.phi = target_polar.phi; + } + + bool rotated_this_frame { false }; + if (mouse_captured()) { + constexpr float mouse_sensitivity { 0.0005f }; + constexpr float phi_epsilon { 0.002f }; + + m_cursor.theta + -= static_cast(m_mouse_dx) * mouse_sensitivity; + m_cursor.phi -= static_cast(m_mouse_dy) * mouse_sensitivity; + m_cursor.phi = std::clamp(m_cursor.phi, phi_epsilon, + std::numbers::pi_v - phi_epsilon); + rotated_this_frame = (m_mouse_dx != 0.0 || m_mouse_dy != 0.0); + } + + auto look_dir { m_cursor.to_vec3().normalized_safe() }; + if (!rotated_this_frame && target_distance > 0.0f) { + look_dir = target_offset.normalized_safe(); + } + if (look_dir.magnitude() == 0.0f) + look_dir = smath::Vec3 { 0.0f, 0.0f, -1.0f }; + smath::Vec3 const world_up { 0.0f, 1.0f, 0.0f }; + auto up_basis = world_up; + if (std::abs(look_dir.dot(up_basis)) > 0.99f) + up_basis = smath::Vec3 { 0.0f, 0.0f, 1.0f }; + auto right { look_dir.cross(up_basis).normalized_safe() }; + if (right.magnitude() == 0.0f) + right = smath::Vec3 { 1.0f, 0.0f, 0.0f }; + auto camera_up { right.cross(look_dir).normalized_safe() }; + if (camera_up.magnitude() == 0.0f) + camera_up = world_up; + + smath::Vec3 move_dir {}; + if (is_key_pressed(KEY_W)) + move_dir += look_dir; + if (is_key_pressed(KEY_S)) + move_dir -= look_dir; + if (is_key_pressed(KEY_D)) + move_dir += right; + if (is_key_pressed(KEY_A)) + move_dir -= right; + if (is_key_pressed(KEY_SPACE)) + move_dir += world_up; + if (is_key_pressed(KEY_LEFTSHIFT)) + move_dir -= world_up; + + if (move_dir.magnitude() > 0.0f) { + constexpr float move_speed { 10.0f }; + move_dir = move_dir.normalized_safe(); + m_camera.position += move_dir * (move_speed * dt_seconds); + } + + if (!m_show_imgui) { + m_camera.up = camera_up; + if (rotated_this_frame) { + auto const distance = target_distance > 0.0f + ? target_distance + : std::max(1.0f, m_cursor.r); + m_camera.target = m_camera.position + look_dir * distance; + } + } + + m_mouse_dx = 0.0; + m_mouse_dy = 0.0; + ImGui_ImplSDL3_NewFrame(); ImGui_ImplVulkan_NewFrame(); ImGui::NewFrame(); + ImGui::SetNextWindowSize({ 300, 100 }); + ImGui::SetNextWindowPos({ 0, 0 }); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4 { 0, 0, 0, 0.5f }); + if (ImGui::Begin("Debug Info", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) { + defer(ImGui::End()); + + ImGui::Text("%s", std::format("FPS: {:.2f}", fps).c_str()); + ImGui::Text("%s", + std::format("Cam pos: ({:.2f}, {:.2f}, {:.2f})", + m_camera.position.x(), m_camera.position.y(), + m_camera.position.z()) + .c_str()); + ImGui::Text("%s", + std::format("Cam tgt: ({:.2f}, {:.2f}, {:.2f})", + m_camera.target.x(), m_camera.target.y(), + m_camera.target.z()) + .c_str()); + ImGui::Text("%s", + std::format("Cam up: ({:.2f}, {:.2f}, {:.2f})", + m_camera.up.x(), m_camera.up.y(), m_camera.up.z()) + .c_str()); + ImGui::Text("%s", + std::format("Cursor r/theta/phi: {:.2f}, {:.2f}, {:.2f}", + m_cursor.r, m_cursor.theta, m_cursor.phi) + .c_str()); + } + ImGui::PopStyleColor(); + if (m_show_imgui) { ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once); ImGui::ShowDemoWindow(); - ImGui::SetNextWindowSize({ 100, 50 }); - ImGui::SetNextWindowPos({ 0, 0 }); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4 { 0, 0, 0, 0.5f }); - if (ImGui::Begin("Debug Info", nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize)) { - defer(ImGui::End()); - - ImGui::Text("%s", std::format("FPS: {:.2f}", fps).c_str()); - } - ImGui::PopStyleColor(); - ImGui::SetNextWindowSize({ 300, -1 }, ImGuiCond_Once); if (ImGui::Begin("Fun menu")) { defer(ImGui::End()); @@ -328,36 +598,47 @@ auto Application::run() -> void static_cast( selected_item)); } + + if (ImGui::CollapsingHeader("Camera", + ImGuiTreeNodeFlags_Framed + | ImGuiTreeNodeFlags_SpanAvailWidth + | ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::DragFloat4("Pos", m_camera.position.data()); + ImGui::DragFloat4("Target", m_camera.target.data()); + ImGui::DragFloat4("Up", m_camera.up.data()); + } } } ImGui::Render(); m_renderer->render([&](VulkanRenderer::GL &gl) { - auto view { smath::matrix_look_at(smath::Vec3 { 0.0f, 0.0f, 3.0f }, - smath::Vec3 { 0.0f, 0.0f, 0.0f }, - smath::Vec3 { 0.0f, 1.0f, 0.0f }, false) }; + auto view { smath::matrix_look_at( + m_camera.position, m_camera.target, m_camera.up) }; auto const draw_extent = m_renderer->draw_extent(); auto const aspect = draw_extent.height == 0 ? 1.0f : static_cast(draw_extent.width) / static_cast(draw_extent.height); auto projection { smath::matrix_perspective( - smath::deg(70.0f), aspect, 0.1f, 10000.0f) }; + m_camera.fovy, aspect, 0.1f, 10000.0f) }; projection[1][1] *= -1; auto view_projection { projection * view }; - auto rect_model { smath::scale( - smath::translate(smath::Vec3 { 0.0f, 0.0f, -5.0f }), - smath::Vec3 { 5.0f, 5.0f, 1.0f }) }; + // auto rect_model { smath::scale( + // smath::translate(smath::Vec3 { 0.0f, 0.0f, -5.0f }), + // smath::Vec3 { 5.0f, 5.0f, 1.0f }) }; - gl.set_transform(view_projection * rect_model); + // gl.set_transform(view_projection * rect_model); + gl.set_transform(view_projection); gl.set_texture(); auto const &meshes { m_renderer->test_meshes() }; if (meshes.size() > 2 && !meshes[2]->surfaces.empty()) { auto const &surface = meshes[2]->surfaces[0]; - gl.draw_mesh(meshes[2]->mesh_buffers, view_projection, + gl.draw_mesh(meshes[2]->mesh_buffers, + view_projection + * smath::translate(smath::Vec3 { 0.0f, 0.0f, -10.0f }), surface.count, surface.start_index); } @@ -386,6 +667,8 @@ auto Application::run() -> void gl.draw_rectangle({ -0.5f, 0.5f }, { 0.5f, 0.5f }); gl.draw_rectangle( { 0, 0.5f }, { 0.5f, 0.5f }, { Colors::TEAL, 1.0f }); + + gl.draw_sphere(m_camera.target, 0.01f); }); } } @@ -474,13 +757,28 @@ auto Application::handle_keyboard_event(libinput_event_keyboard *event) -> void } if (pressed && key == KEY_F11 && m_ctrl_pressed_count > 0) { - mouse_captured(!mouse_captured()); - m_show_imgui = !mouse_captured(); + bool const new_show_imgui { !m_show_imgui }; + m_show_imgui = new_show_imgui; + mouse_captured(!new_show_imgui); } if (auto imgui_key { linux_key_to_imgui(key) }) { - ImGui::GetIO().AddKeyEvent(*imgui_key, pressed); + if (m_show_imgui) + ImGui::GetIO().AddKeyEvent(*imgui_key, pressed); } + + if (m_show_imgui && pressed) { + bool const shift_pressed { is_key_pressed(KEY_LEFTSHIFT) + || is_key_pressed(KEY_RIGHTSHIFT) + || (key == KEY_LEFTSHIFT && pressed) + || (key == KEY_RIGHTSHIFT && pressed) }; + + if (auto ch { linux_key_to_char(key, shift_pressed) }) + ImGui::GetIO().AddInputCharacter(*ch); + } + + if (key < m_key_state.size()) + m_key_state[key] = pressed; } auto Application::clamp_mouse_to_window(int width, int height) -> void @@ -502,7 +800,14 @@ auto Application::mouse_captured(bool new_state) -> void return; } - m_mouse_captured = new_state; + m_mouse_captured = new_state && !m_show_imgui; +} + +auto Application::is_key_pressed(uint32_t key) const -> bool +{ + if (key >= m_key_state.size()) + return false; + return m_key_state[key]; } } // namespace Lunar diff --git a/src/Application.h b/src/Application.h index 22b9e45..2fd9cba 100644 --- a/src/Application.h +++ b/src/Application.h @@ -1,12 +1,14 @@ #pragma once +#include #include #include +#include +#include #include "Logger.h" - -#include +#include "Types.h" struct libinput; struct libinput_event_keyboard; @@ -26,6 +28,7 @@ struct Application { auto mouse_captured(bool new_state) -> void; auto mouse_captured() const -> bool { return m_mouse_captured; } auto toggle_mouse_captured() -> void { mouse_captured(!m_mouse_captured); } + auto is_key_pressed(uint32_t key) const -> bool; private: auto init_input() -> void; @@ -48,6 +51,13 @@ private: double m_mouse_x { 0.0 }; double m_mouse_y { 0.0 }; + double m_mouse_dx { 0.0 }; + double m_mouse_dy { 0.0 }; + + std::array m_key_state {}; + + Camera m_camera; + PolarCoordinate m_cursor; }; } // namespace Lunar diff --git a/src/Types.h b/src/Types.h index 74c1b7e..fa129e3 100644 --- a/src/Types.h +++ b/src/Types.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -55,4 +57,39 @@ struct GPUSceneData { smath::Vec4 sunlight_color; }; +struct Camera { + smath::Vec3 position {}; + smath::Vec3 target { 0, 0, -1 }; + smath::Vec3 up { 0, 1, 0 }; + float fovy { smath::deg(70.0f) }; +}; + +struct PolarCoordinate { + float r, theta, phi; + + static PolarCoordinate from_vec3(smath::Vec3 const &v) + { + PolarCoordinate p; + p.r = std::sqrt(v.x() * v.x() + v.y() * v.y() + v.z() * v.z()); + + if (p.r == 0.0f) { + p.theta = 0.0f; + p.phi = 0.0f; + return p; + } + + p.theta = std::atan2(v.z(), v.x()); + p.phi = std::acos(v.y() / p.r); + return p; + } + + smath::Vec3 to_vec3() const + { + float sin_phi = std::sin(phi); + + return smath::Vec3 { r * sin_phi * std::cos(theta), r * std::cos(phi), + r * sin_phi * std::sin(theta) }; + } +}; + } // namespace Lunar diff --git a/src/VulkanRenderer.cpp b/src/VulkanRenderer.cpp index 908893c..10f6e2b 100644 --- a/src/VulkanRenderer.cpp +++ b/src/VulkanRenderer.cpp @@ -341,6 +341,73 @@ auto VulkanRenderer::GL::draw_rectangle(smath::Vec2 pos, smath::Vec2 size, end(); } +auto VulkanRenderer::GL::draw_sphere(smath::Vec3 center, float radius, + int rings, int segments, std::optional sphere_color) -> void +{ + assert(m_drawing && "begin_drawing must be called first"); + + if (radius <= 0.0f) + return; + + if (rings < 2) + rings = 2; + if (segments < 3) + segments = 3; + + float const pi = 3.14159265358979323846f; + + // Use caller color if provided, otherwise keep current GL color state. + if (sphere_color.has_value()) + color(*sphere_color); + + // Build as latitude strips + for (int y = 0; y < rings; y++) { + float const v0 = static_cast(y) / static_cast(rings); + float const v1 = static_cast(y + 1) / static_cast(rings); + + float const theta0 = v0 * pi; + float const theta1 = v1 * pi; + + float const sin0 = std::sin(theta0); + float const cos0 = std::cos(theta0); + float const sin1 = std::sin(theta1); + float const cos1 = std::cos(theta1); + + begin(GeometryKind::TriangleStrip); + + for (int x = 0; x <= segments; x++) { + float const u + = static_cast(x) / static_cast(segments); + float const phi = u * (2.0f * pi); + + float const sp = std::sin(phi); + float const cp = std::cos(phi); + + // Vertex on ring y+1 + { + smath::Vec3 n { sin1 * cp, cos1, sin1 * sp }; + normal(n); + uv(smath::Vec2 { u, 1.0f - v1 }); + + smath::Vec3 p = center + n * radius; + vert(p); + } + + // Vertex on ring y + { + smath::Vec3 n { sin0 * cp, cos0, sin0 * sp }; + normal(n); + uv(smath::Vec2 { u, 1.0f - v0 }); + + smath::Vec3 p = center + n * radius; + vert(p); + } + } + + end(); + } +} + auto VulkanRenderer::GL::draw_mesh(GPUMeshBuffers const &mesh, smath::Mat4 const &transform, uint32_t index_count, uint32_t first_index, int32_t vertex_offset) -> void @@ -530,7 +597,7 @@ auto VulkanRenderer::resize(uint32_t width, uint32_t height) -> void auto VulkanRenderer::set_antialiasing(AntiAliasingKind kind) -> void { enqueue_render_command(RenderCommand { - RenderCommand::SetAntiAliasing { kind }, + RenderCommand::SetAntiAliasing { kind }, }); } diff --git a/src/VulkanRenderer.h b/src/VulkanRenderer.h index 42481ca..1bc5e7b 100644 --- a/src/VulkanRenderer.h +++ b/src/VulkanRenderer.h @@ -76,6 +76,9 @@ struct VulkanRenderer { auto draw_rectangle(smath::Vec2 pos, smath::Vec2 size, smath::Vec4 color = smath::Vec4 { Colors::WHITE, 1.0f }, float rotation = 0.0f) -> void; + auto draw_sphere(smath::Vec3 center, float radius, int rings = 16, + int segments = 32, std::optional sphere_color = {}) + -> void; auto end() -> void; auto flush() -> void; diff --git a/thirdparty/smath b/thirdparty/smath index 1a42238..830c64b 160000 --- a/thirdparty/smath +++ b/thirdparty/smath @@ -1 +1 @@ -Subproject commit 1a42238a41f84373a567c777baaf7ad18c860162 +Subproject commit 830c64b25aadeb303654f0d03b7c66d62e1fcc43