diff --git a/examples/example_hud/gui/main_window.cpp b/examples/example_hud/gui/main_window.cpp index ff6c8a5b..0cafd8ea 100644 --- a/examples/example_hud/gui/main_window.cpp +++ b/examples/example_hud/gui/main_window.cpp @@ -1,11 +1,9 @@ // // Created by Orange on 11/11/2024. // - #include "main_window.hpp" #include "omath/hud/renderer_realizations/imgui_renderer.hpp" #include -#include #include #include #include @@ -20,6 +18,10 @@ namespace imgui_desktop::gui if (!glfwInit()) std::exit(EXIT_FAILURE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, true); m_window = glfwCreateWindow(width, height, caption.data(), nullptr, nullptr); @@ -27,84 +29,184 @@ namespace imgui_desktop::gui ImGui::CreateContext(); ImGui::StyleColorsDark(); - - ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = {0.f, 0.f, 0.f, 0.f}; - + ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = {0.05f, 0.05f, 0.05f, 0.92f}; ImGui::GetStyle().AntiAliasedLines = false; ImGui::GetStyle().AntiAliasedFill = false; - ImGui_ImplGlfw_InitForOpenGL(m_window, true); - ImGui_ImplOpenGL3_Init("#version 130"); + ImGui_ImplOpenGL3_Init("#version 150"); } void MainWindow::Run() { - omath::Color box_color = {0.f, 0.f, 0.f, 1.f}; - omath::Color box_fill = {0.f, 0.f, 0.f, 0.f}; - omath::Color bar_color = {0.f, 1.f, 0.f, 1.f}; - omath::Color bar_bg_color = {0.f, 0.f, 0.f, 0.0f}; - omath::Color bar_outline_color = {0.f, 0.f, 0.f, 1.f}; - float bar_width = 3.f; - float bar_value = 1.f; - while (!glfwWindowShouldClose(m_window) && m_opened) { glfwPollEvents(); - ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - ImGui::GetBackgroundDrawList()->AddRectFilled({}, ImGui::GetMainViewport()->Size, ImColor(40, 40, 40, 200)); - - ImGui::Begin("OHUD Showcase", &m_opened, - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDecoration); - { - ImGui::SetWindowPos({}); - ImGui::SetWindowSize(ImGui::GetMainViewport()->Size); - - ImGui::ColorEdit4("Box", reinterpret_cast(&box_color), ImGuiColorEditFlags_NoInputs); - ImGui::ColorEdit4("Box fill", reinterpret_cast(&box_fill), ImGuiColorEditFlags_NoInputs); - ImGui::ColorEdit4("Bar", reinterpret_cast(&bar_color), ImGuiColorEditFlags_NoInputs); - ImGui::ColorEdit4("Bar Background", reinterpret_cast(&bar_bg_color), - ImGuiColorEditFlags_NoInputs); - ImGui::ColorEdit4("Bar Outline", reinterpret_cast(&bar_outline_color), - ImGuiColorEditFlags_NoInputs); - - ImGui::PushItemWidth(100.f); - ImGui::SliderFloat("Bar Width", &bar_width, 1.f, 20.f); - ImGui::SliderFloat("Bar Value", &bar_value, 0.f, 1.f); - ImGui::PopItemWidth(); - ImGui::End(); - } - - omath::hud::EntityOverlay ent({400.f, 100.f}, {400.f, 400.f}, std::make_shared()); - - ent.add_2d_box(box_color, box_fill, 1.f); - ent.add_cornered_2d_box(omath::Color::from_rgba(255, 0, 255, 255), box_fill); - ent.add_right_bar(bar_color, bar_outline_color, bar_bg_color, bar_width, bar_value); - ent.add_left_bar(bar_color, bar_outline_color, bar_bg_color, bar_width, bar_value); - ent.add_top_bar(bar_color, bar_outline_color, bar_bg_color, bar_width, bar_value); - ent.add_right_label({0.f, 1.f, 0.f, 1.f}, 3, true, "Health: {}/100", 100); - ent.add_right_label({1.f, 0.f, 0.f, 1.f}, 3, true, "Shield: {}/125", 125); - ent.add_right_label({1.f, 0.f, 1.f, 1.f}, 3, true, "*LOCKED*"); - - ent.add_top_label(omath::Color::from_rgba(255, 255, 0, 255), 3, true, "*SCOPED*"); - ent.add_top_label(omath::Color::from_rgba(255, 0, 0, 255), 3, true, "*BLEEDING*"); - ent.add_snap_line(omath::Vector2{400, 600}, omath::Color::from_rgba(255, 0, 0, 255), 2.f); + const auto* vp = ImGui::GetMainViewport(); + ImGui::GetBackgroundDrawList()->AddRectFilled({}, vp->Size, ImColor(30, 30, 30, 220)); + + draw_controls(); + draw_overlay(); + ImGui::Render(); + present(); + } + glfwDestroyWindow(m_window); + } + + void MainWindow::draw_controls() + { + const auto* vp = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos({0.f, 0.f}); + ImGui::SetNextWindowSize({280.f, vp->Size.y}); + ImGui::Begin("Controls", &m_opened, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGui::PushItemWidth(160.f); + + if (ImGui::CollapsingHeader("Entity", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::SliderFloat("X##ent", &m_entity_x, 100.f, vp->Size.x - 100.f); + ImGui::SliderFloat("Top Y", &m_entity_top_y, 20.f, m_entity_bottom_y - 20.f); + ImGui::SliderFloat("Bottom Y", &m_entity_bottom_y, m_entity_top_y + 20.f, vp->Size.y - 20.f); + } - int display_w, display_h; + if (ImGui::CollapsingHeader("Box", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Box", &m_show_box); ImGui::SameLine(); + ImGui::Checkbox("Cornered", &m_show_cornered_box); ImGui::SameLine(); + ImGui::Checkbox("Dashed", &m_show_dashed_box); + ImGui::ColorEdit4("Color##box", reinterpret_cast(&m_box_color), ImGuiColorEditFlags_NoInputs); + ImGui::ColorEdit4("Fill##box", reinterpret_cast(&m_box_fill), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Thickness", &m_box_thickness, 0.5f, 5.f); + ImGui::SliderFloat("Corner ratio", &m_corner_ratio, 0.05f, 0.5f); + ImGui::Separator(); + ImGui::ColorEdit4("Dash color", reinterpret_cast(&m_dash_color), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Dash length", &m_dash_len, 2.f, 30.f); + ImGui::SliderFloat("Dash gap", &m_dash_gap, 1.f, 20.f); + ImGui::SliderFloat("Dash thick", &m_dash_thickness, 0.5f, 5.f); + } - glfwGetFramebufferSize(m_window, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); + if (ImGui::CollapsingHeader("Bars", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::ColorEdit4("Color##bar", reinterpret_cast(&m_bar_color), ImGuiColorEditFlags_NoInputs); + ImGui::ColorEdit4("BG##bar", reinterpret_cast(&m_bar_bg_color), ImGuiColorEditFlags_NoInputs); + ImGui::ColorEdit4("Outline##bar", reinterpret_cast(&m_bar_outline_color), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Width##bar", &m_bar_width, 1.f, 20.f); + ImGui::SliderFloat("Value##bar", &m_bar_value, 0.f, 1.f); + ImGui::SliderFloat("Offset##bar", &m_bar_offset, 1.f, 20.f); + ImGui::Checkbox("Right##bar", &m_show_right_bar); ImGui::SameLine(); + ImGui::Checkbox("Left##bar", &m_show_left_bar); + ImGui::Checkbox("Top##bar", &m_show_top_bar); ImGui::SameLine(); + ImGui::Checkbox("Bottom##bar", &m_show_bottom_bar); + } - glClearColor(0.f, 0.f, 0.f, 0.f); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwSwapBuffers(m_window); + if (ImGui::CollapsingHeader("Labels", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Outlined", &m_outlined); + ImGui::SliderFloat("Offset##lbl", &m_label_offset, 0.f, 15.f); + ImGui::Checkbox("Right##lbl", &m_show_right_labels); ImGui::SameLine(); + ImGui::Checkbox("Left##lbl", &m_show_left_labels); + ImGui::Checkbox("Top##lbl", &m_show_top_labels); ImGui::SameLine(); + ImGui::Checkbox("Bottom##lbl", &m_show_bottom_labels); + ImGui::Checkbox("Ctr top##lbl", &m_show_centered_top); ImGui::SameLine(); + ImGui::Checkbox("Ctr bot##lbl", &m_show_centered_bottom); } - glfwDestroyWindow(m_window); + + if (ImGui::CollapsingHeader("Skeleton")) + { + ImGui::Checkbox("Show##skel", &m_show_skeleton); + ImGui::ColorEdit4("Color##skel", reinterpret_cast(&m_skel_color), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Thick##skel", &m_skel_thickness, 0.5f, 5.f); + } + + if (ImGui::CollapsingHeader("Snap Line")) + { + ImGui::Checkbox("Show##snap", &m_show_snap); + ImGui::ColorEdit4("Color##snap", reinterpret_cast(&m_snap_color), ImGuiColorEditFlags_NoInputs); + ImGui::SliderFloat("Width##snap", &m_snap_width, 0.5f, 5.f); + } + + ImGui::PopItemWidth(); + ImGui::End(); + } + + void MainWindow::draw_overlay() + { + const auto* vp = ImGui::GetMainViewport(); + + omath::hud::EntityOverlay ent( + {m_entity_x, m_entity_top_y}, {m_entity_x, m_entity_bottom_y}, + std::make_shared()); + + draw_boxes(ent); + draw_bars(ent); + draw_labels(ent); + + if (m_show_skeleton) + ent.add_skeleton(m_skel_color, m_skel_thickness); + if (m_show_snap) + ent.add_snap_line({vp->Size.x / 2.f, vp->Size.y}, m_snap_color, m_snap_width); + } + + void MainWindow::draw_boxes(omath::hud::EntityOverlay& ent) const + { + if (m_show_box) + ent.add_2d_box(m_box_color, m_box_fill, m_box_thickness); + if (m_show_cornered_box) + ent.add_cornered_2d_box(omath::Color::from_rgba(255, 0, 255, 255), m_box_fill, m_corner_ratio, m_box_thickness); + if (m_show_dashed_box) + ent.add_dashed_box(m_dash_color, m_dash_len, m_dash_gap, m_dash_thickness); + } + + void MainWindow::draw_bars(omath::hud::EntityOverlay& ent) const + { + if (m_show_right_bar) + ent.add_right_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); + if (m_show_left_bar) + ent.add_left_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); + if (m_show_top_bar) + ent.add_top_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); + if (m_show_bottom_bar) + ent.add_bottom_bar(m_bar_color, m_bar_outline_color, m_bar_bg_color, m_bar_width, m_bar_value, m_bar_offset); + } + + void MainWindow::draw_labels(omath::hud::EntityOverlay& ent) const + { + if (m_show_right_labels) + { + ent.add_right_label({0.f, 1.f, 0.f, 1.f}, m_label_offset, m_outlined, "Health: {}/100", 100); + ent.add_right_label({1.f, 0.f, 0.f, 1.f}, m_label_offset, m_outlined, "Shield: {}/125", 125); + ent.add_right_label({1.f, 0.f, 1.f, 1.f}, m_label_offset, m_outlined, "*LOCKED*"); + } + if (m_show_left_labels) + { + ent.add_left_label(omath::Color::from_rgba(255, 128, 0, 255), m_label_offset, m_outlined, "Armor: 75"); + ent.add_left_label(omath::Color::from_rgba(0, 200, 255, 255), m_label_offset, m_outlined, "Level: 42"); + } + if (m_show_top_labels) + { + ent.add_top_label(omath::Color::from_rgba(255, 255, 0, 255), m_label_offset, m_outlined, "*SCOPED*"); + ent.add_top_label(omath::Color::from_rgba(255, 0, 0, 255), m_label_offset, m_outlined, "*BLEEDING*"); + } + if (m_show_centered_top) + ent.add_centered_top_label(omath::Color::from_rgba(0, 255, 255, 255), m_label_offset, m_outlined, "*VISIBLE*"); + if (m_show_centered_bottom) + ent.add_centered_bottom_label(omath::Color::from_rgba(255, 255, 255, 255), m_label_offset, m_outlined, "PlayerName"); + if (m_show_bottom_labels) + ent.add_bottom_label(omath::Color::from_rgba(200, 200, 0, 255), m_label_offset, m_outlined, "42m"); + } + + void MainWindow::present() + { + int w, h; + glfwGetFramebufferSize(m_window, &w, &h); + glViewport(0, 0, w, h); + glClearColor(0.f, 0.f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + glfwSwapBuffers(m_window); } } // namespace imgui_desktop::gui -// imgui_desktop \ No newline at end of file diff --git a/examples/example_hud/gui/main_window.hpp b/examples/example_hud/gui/main_window.hpp index f8eb072e..dc4c8194 100644 --- a/examples/example_hud/gui/main_window.hpp +++ b/examples/example_hud/gui/main_window.hpp @@ -1,11 +1,9 @@ -// -// Created by Vlad on 6/17/2025. -// - // // Created by Orange on 11/11/2024. // #pragma once +#include +#include #include struct GLFWwindow; @@ -15,14 +13,57 @@ namespace imgui_desktop::gui class MainWindow { public: - MainWindow(const std::string_view &caption, int width, int height); - + MainWindow(const std::string_view& caption, int width, int height); void Run(); private: - GLFWwindow* m_window; + void draw_controls(); + void draw_overlay(); + void draw_boxes(omath::hud::EntityOverlay& ent) const; + void draw_bars(omath::hud::EntityOverlay& ent) const; + void draw_labels(omath::hud::EntityOverlay& ent) const; + void present(); + + GLFWwindow* m_window = nullptr; static bool m_canMoveWindow; bool m_opened = true; + + // Entity + float m_entity_x = 550.f, m_entity_top_y = 150.f, m_entity_bottom_y = 450.f; + + // Box + omath::Color m_box_color{1.f, 1.f, 1.f, 1.f}; + omath::Color m_box_fill{0.f, 0.f, 0.f, 0.f}; + float m_box_thickness = 1.f, m_corner_ratio = 0.2f; + bool m_show_box = true, m_show_cornered_box = true, m_show_dashed_box = false; + + // Dashed box + omath::Color m_dash_color = omath::Color::from_rgba(255, 200, 0, 255); + float m_dash_len = 8.f, m_dash_gap = 5.f, m_dash_thickness = 1.f; + + // Bars + omath::Color m_bar_color{0.f, 1.f, 0.f, 1.f}; + omath::Color m_bar_bg_color{0.f, 0.f, 0.f, 0.5f}; + omath::Color m_bar_outline_color{0.f, 0.f, 0.f, 1.f}; + float m_bar_width = 4.f, m_bar_value = 0.75f, m_bar_offset = 5.f; + bool m_show_right_bar = true, m_show_left_bar = true; + bool m_show_top_bar = true, m_show_bottom_bar = true; + + // Labels + float m_label_offset = 3.f; + bool m_outlined = true; + bool m_show_right_labels = true, m_show_left_labels = true; + bool m_show_top_labels = true, m_show_bottom_labels = true; + bool m_show_centered_top = true, m_show_centered_bottom = true; + + // Skeleton + omath::Color m_skel_color = omath::Color::from_rgba(255, 255, 255, 200); + float m_skel_thickness = 1.f; + bool m_show_skeleton = false; + + // Snap line + omath::Color m_snap_color = omath::Color::from_rgba(255, 50, 50, 255); + float m_snap_width = 1.5f; + bool m_show_snap = true; }; -} // gui -// imgui_desktop \ No newline at end of file +} // namespace imgui_desktop::gui diff --git a/include/omath/hud/entity_overlay.hpp b/include/omath/hud/entity_overlay.hpp index 026d2b55..9b138187 100644 --- a/include/omath/hud/entity_overlay.hpp +++ b/include/omath/hud/entity_overlay.hpp @@ -26,7 +26,7 @@ namespace omath::hud float ratio, float offset = 5.f); void add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width, - float ratio, float offset = 5.f) const; + float ratio, float offset = 5.f); template void add_right_label(const Color& color, const float offset, const bool outlined, @@ -55,12 +55,64 @@ namespace omath::hud void add_snap_line(const Vector2& start_pos, const Color& color, float width); + void add_dashed_box(const Color& color, float dash_len = 8.f, float gap_len = 5.f, + float thickness = 1.f) const; + + void add_skeleton(const Color& color, float thickness = 1.f) const; + + void add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height, + float ratio, float offset = 5.f); + + template + void add_bottom_label(const Color& color, const float offset, const bool outlined, + std::format_string fmt, Args&&... args) + { + const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); + add_bottom_label(color, offset, outlined, std::string_view{label}); + } + + void add_bottom_label(const Color& color, float offset, bool outlined, std::string_view text); + + template + void add_left_label(const Color& color, const float offset, const bool outlined, + std::format_string fmt, Args&&... args) + { + const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); + add_left_label(color, offset, outlined, std::string_view{label}); + } + + void add_left_label(const Color& color, float offset, bool outlined, const std::string_view& text); + + template + void add_centered_bottom_label(const Color& color, const float offset, const bool outlined, + std::format_string fmt, Args&&... args) + { + const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); + add_centered_bottom_label(color, offset, outlined, std::string_view{label}); + } + + void add_centered_bottom_label(const Color& color, float offset, bool outlined, const std::string_view& text); + + template + void add_centered_top_label(const Color& color, const float offset, const bool outlined, + std::format_string fmt, Args&&... args) + { + const std::string label = std::vformat(fmt.get(), std::make_format_args(args...)); + add_centered_top_label(color, offset, outlined, std::string_view{label}); + } + + void add_centered_top_label(const Color& color, float offset, bool outlined, const std::string_view& text); + private: void draw_outlined_text(const Vector2& position, const Color& color, const std::string_view& text); + void draw_dashed_line(const Vector2& from, const Vector2& to, const Color& color, + float dash_len, float gap_len, float thickness) const; CanvasBox m_canvas; Vector2 m_text_cursor_right; Vector2 m_text_cursor_top; + Vector2 m_text_cursor_bottom; + Vector2 m_text_cursor_left; std::shared_ptr m_renderer; }; } // namespace omath::hud \ No newline at end of file diff --git a/include/omath/hud/hud_renderer_interface.hpp b/include/omath/hud/hud_renderer_interface.hpp index 117cb7d6..8d625324 100644 --- a/include/omath/hud/hud_renderer_interface.hpp +++ b/include/omath/hud/hud_renderer_interface.hpp @@ -18,8 +18,7 @@ namespace omath::hud virtual void add_polyline(const std::span>& vertexes, const Color& color, float thickness) = 0; - virtual void add_filled_polyline(const std::span>& vertexes, const Color& color, - float thickness) = 0; + virtual void add_filled_polyline(const std::span>& vertexes, const Color& color) = 0; virtual void add_rectangle(const Vector2& min, const Vector2& max, const Color& color) = 0; diff --git a/include/omath/hud/renderer_realizations/imgui_renderer.hpp b/include/omath/hud/renderer_realizations/imgui_renderer.hpp index cd4dbbe8..5645de2e 100644 --- a/include/omath/hud/renderer_realizations/imgui_renderer.hpp +++ b/include/omath/hud/renderer_realizations/imgui_renderer.hpp @@ -14,7 +14,7 @@ namespace omath::hud void add_line(const Vector2& line_start, const Vector2& line_end, const Color& color, float thickness) override; void add_polyline(const std::span>& vertexes, const Color& color, float thickness) override; - void add_filled_polyline(const std::span>& vertexes, const Color& color, float thickness) override; + void add_filled_polyline(const std::span>& vertexes, const Color& color) override; void add_rectangle(const Vector2& min, const Vector2& max, const Color& color) override; void add_filled_rectangle(const Vector2& min, const Vector2& max, const Color& color) override; void add_text(const Vector2& position, const Color& color, const std::string_view& text) override; diff --git a/source/hud/entity_overlay.cpp b/source/hud/entity_overlay.cpp index c560195f..de50f20b 100644 --- a/source/hud/entity_overlay.cpp +++ b/source/hud/entity_overlay.cpp @@ -10,7 +10,9 @@ namespace omath::hud const auto points = m_canvas.as_array(); m_renderer->add_polyline({points.data(), points.size()}, box_color, thickness); - m_renderer->add_filled_polyline({points.data(), points.size()}, fill_color, thickness); + + if (fill_color.value().w > 0.f) + m_renderer->add_filled_polyline({points.data(), points.size()}, fill_color); } void EntityOverlay::add_cornered_2d_box(const Color& box_color, const Color& fill_color, const float corner_ratio_len, const float thickness) const @@ -65,7 +67,7 @@ namespace omath::hud m_text_cursor_right.x += offset + width; } void EntityOverlay::add_left_bar(const Color& color, const Color& outline_color, const Color& bg_color, - const float width, float ratio, const float offset) const + const float width, float ratio, const float offset) { ratio = std::clamp(ratio, 0.f, 1.f); const auto max_bar_height = std::abs(m_canvas.top_left_corner.y - m_canvas.bottom_right_corner.y); @@ -76,6 +78,8 @@ namespace omath::hud m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2(width, -max_bar_height * ratio), color); m_renderer->add_rectangle(bar_start - Vector2(1.f, 0.f), bar_start + Vector2(width, -max_bar_height), outline_color); + + m_text_cursor_left.x -= offset + width; } void EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined, const std::string_view& text) @@ -118,6 +122,95 @@ namespace omath::hud + Vector2{m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x, 0.f} / 2; m_renderer->add_line(start_pos, line_end, color, width); } + void EntityOverlay::add_skeleton(const Color& color, const float thickness) const + { + // Maps normalized (rx in [0,1], ry in [0,1]) to canvas screen position + const auto joint = [&](const float rx, const float ry) -> Vector2 + { + const auto top = m_canvas.top_left_corner + + (m_canvas.top_right_corner - m_canvas.top_left_corner) * rx; + const auto bot = m_canvas.bottom_left_corner + + (m_canvas.bottom_right_corner - m_canvas.bottom_left_corner) * rx; + return top + (bot - top) * ry; + }; + + using B = std::pair, std::pair>; + static constexpr std::array k_bones{{ + // Spine + {{0.50f, 0.13f}, {0.50f, 0.22f}}, // head → neck + {{0.50f, 0.22f}, {0.50f, 0.38f}}, // neck → chest + {{0.50f, 0.38f}, {0.50f, 0.55f}}, // chest → pelvis + // Left arm + {{0.50f, 0.22f}, {0.25f, 0.25f}}, // neck → L shoulder + {{0.25f, 0.25f}, {0.13f, 0.42f}}, // L shoulder → L elbow + {{0.13f, 0.42f}, {0.08f, 0.56f}}, // L elbow → L hand + // Right arm + {{0.50f, 0.22f}, {0.75f, 0.25f}}, // neck → R shoulder + {{0.75f, 0.25f}, {0.87f, 0.42f}}, // R shoulder → R elbow + {{0.87f, 0.42f}, {0.92f, 0.56f}}, // R elbow → R hand + // Left leg + {{0.50f, 0.55f}, {0.36f, 0.58f}}, // pelvis → L hip + {{0.36f, 0.58f}, {0.32f, 0.77f}}, // L hip → L knee + {{0.32f, 0.77f}, {0.27f, 0.97f}}, // L knee → L foot + // Right leg + {{0.50f, 0.55f}, {0.64f, 0.58f}}, // pelvis → R hip + {{0.64f, 0.58f}, {0.68f, 0.77f}}, // R hip → R knee + {{0.68f, 0.77f}, {0.73f, 0.97f}}, // R knee → R foot + }}; + + for (const auto& [a, b] : k_bones) + m_renderer->add_line(joint(a.first, a.second), joint(b.first, b.second), color, thickness); + } + + void EntityOverlay::draw_dashed_line(const Vector2& from, const Vector2& to, const Color& color, + const float dash_len, const float gap_len, const float thickness) const + { + const auto total = (to - from).length(); + if (total <= 0.f) + return; + + const auto dir = (to - from).normalized(); + const float step = dash_len + gap_len; + + const float n_dashes = std::floor((total + gap_len) / step); + if (n_dashes < 1.f) + return; + + const float used = n_dashes * dash_len + (n_dashes - 1.f) * gap_len; + const float offset = (total - used) / 2.f; + + for (float i = 0.f; i < n_dashes; ++i) + { + const float pos = offset + i * step; + const auto dash_start = from + dir * pos; + const auto dash_end = from + dir * std::min(pos + dash_len, total); + m_renderer->add_line(dash_start, dash_end, color, thickness); + } + } + + void EntityOverlay::add_dashed_box(const Color& color, const float dash_len, const float gap_len, + const float thickness) const + { + const float min_edge = std::min( + (m_canvas.top_right_corner - m_canvas.top_left_corner).length(), + (m_canvas.bottom_right_corner - m_canvas.top_right_corner).length()); + const float corner_len = std::min(dash_len, min_edge / 2.f); + + const auto draw_edge = [&](const Vector2& from, const Vector2& to) + { + const auto dir = (to - from).normalized(); + + m_renderer->add_line(from, from + dir * corner_len, color, thickness); + draw_dashed_line(from + dir * corner_len, to - dir * corner_len, color, dash_len, gap_len, thickness); + m_renderer->add_line(to - dir * corner_len, to, color, thickness); + }; + + draw_edge(m_canvas.top_left_corner, m_canvas.top_right_corner); + draw_edge(m_canvas.top_right_corner, m_canvas.bottom_right_corner); + draw_edge(m_canvas.bottom_right_corner,m_canvas.bottom_left_corner); + draw_edge(m_canvas.bottom_left_corner, m_canvas.top_left_corner); + } + void EntityOverlay::draw_outlined_text(const Vector2& position, const Color& color, const std::string_view& text) { @@ -129,10 +222,84 @@ namespace omath::hud m_renderer->add_text(position + outline_offset, Color{0.f, 0.f, 0.f, 1.f}, text.data()); m_renderer->add_text(position, color, text.data()); } + void EntityOverlay::add_bottom_bar(const Color& color, const Color& outline_color, const Color& bg_color, + const float height, float ratio, const float offset) + { + ratio = std::clamp(ratio, 0.f, 1.f); + const auto max_bar_width = std::abs(m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x); + + const auto bar_start = m_canvas.bottom_left_corner + Vector2{0.f, offset}; + m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2(max_bar_width, height), bg_color); + m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2(max_bar_width * ratio, height), color); + m_renderer->add_rectangle(bar_start, bar_start + Vector2(max_bar_width, height), outline_color); + + m_text_cursor_bottom.y += offset + height; + } + + void EntityOverlay::add_bottom_label(const Color& color, const float offset, const bool outlined, + const std::string_view text) + { + const auto text_size = m_renderer->calc_text_size(text); + + if (outlined) + draw_outlined_text(m_text_cursor_bottom + Vector2{0.f, offset}, color, text); + else + m_renderer->add_text(m_text_cursor_bottom + Vector2{0.f, offset}, color, text); + + m_text_cursor_bottom.y += text_size.y; + } + + void EntityOverlay::add_left_label(const Color& color, const float offset, const bool outlined, + const std::string_view& text) + { + const auto text_size = m_renderer->calc_text_size(text); + const auto pos = m_text_cursor_left + Vector2{-(offset + text_size.x), 0.f}; + + if (outlined) + draw_outlined_text(pos, color, text); + else + m_renderer->add_text(pos, color, text); + + m_text_cursor_left.y += text_size.y; + } + + void EntityOverlay::add_centered_bottom_label(const Color& color, const float offset, const bool outlined, + const std::string_view& text) + { + const auto text_size = m_renderer->calc_text_size(text); + const auto box_center_x = + m_canvas.bottom_left_corner.x + (m_canvas.bottom_right_corner.x - m_canvas.bottom_left_corner.x) / 2.f; + const auto pos = Vector2{box_center_x - text_size.x / 2.f, m_text_cursor_bottom.y + offset}; + + if (outlined) + draw_outlined_text(pos, color, text); + else + m_renderer->add_text(pos, color, text); + + m_text_cursor_bottom.y += text_size.y; + } + + void EntityOverlay::add_centered_top_label(const Color& color, const float offset, const bool outlined, + const std::string_view& text) + { + const auto text_size = m_renderer->calc_text_size(text); + const auto box_center_x = + m_canvas.top_left_corner.x + (m_canvas.top_right_corner.x - m_canvas.top_left_corner.x) / 2.f; + + m_text_cursor_top.y -= text_size.y; + const auto pos = Vector2{box_center_x - text_size.x / 2.f, m_text_cursor_top.y - offset}; + + if (outlined) + draw_outlined_text(pos, color, text); + else + m_renderer->add_text(pos, color, text); + } + EntityOverlay::EntityOverlay(const Vector2& top, const Vector2& bottom, const std::shared_ptr& renderer) : m_canvas(top, bottom), m_text_cursor_right(m_canvas.top_right_corner), - m_text_cursor_top(m_canvas.top_left_corner), m_renderer(renderer) + m_text_cursor_top(m_canvas.top_left_corner), m_text_cursor_bottom(m_canvas.bottom_left_corner), + m_text_cursor_left(m_canvas.top_left_corner), m_renderer(renderer) { } } // namespace omath::hud \ No newline at end of file diff --git a/source/hud/renderer_realizations/imgui_renderer.cpp b/source/hud/renderer_realizations/imgui_renderer.cpp index 8c664916..5d397d4d 100644 --- a/source/hud/renderer_realizations/imgui_renderer.cpp +++ b/source/hud/renderer_realizations/imgui_renderer.cpp @@ -22,15 +22,13 @@ namespace omath::hud { ImGui::GetBackgroundDrawList()->AddPolyline(reinterpret_cast(vertexes.data()), static_cast(vertexes.size()), color.to_im_color(), - ImDrawFlags_None, thickness); + ImDrawFlags_Closed, thickness); } - void ImguiHudRenderer::add_filled_polyline(const std::span>& vertexes, const Color& color, - const float thickness) + void ImguiHudRenderer::add_filled_polyline(const std::span>& vertexes, const Color& color) { - ImGui::GetBackgroundDrawList()->AddPolyline(reinterpret_cast(vertexes.data()), - static_cast(vertexes.size()), color.to_im_color(), - ImDrawFlags_Closed, thickness); + ImGui::GetBackgroundDrawList()->AddConvexPolyFilled(reinterpret_cast(vertexes.data()), + static_cast(vertexes.size()), color.to_im_color()); } void ImguiHudRenderer::add_rectangle(const Vector2& min, const Vector2& max, const Color& color) @@ -50,7 +48,7 @@ namespace omath::hud text.data() + text.size()); } [[nodiscard]] - Vector2 calc_text_size(const std::string_view& text) + Vector2 ImguiHudRenderer::calc_text_size(const std::string_view& text) { return Vector2::from_im_vec2(ImGui::CalcTextSize(text.data())); }