Merge pull request #166 from orange-cpp/feature/hud_improvement

Feature/hud improvement
This commit is contained in:
2026-03-15 14:01:33 +03:00
committed by GitHub
7 changed files with 444 additions and 85 deletions

View File

@@ -1,11 +1,9 @@
//
// Created by Orange on 11/11/2024.
//
#include "main_window.hpp"
#include "omath/hud/renderer_realizations/imgui_renderer.hpp"
#include <GLFW/glfw3.h>
#include <algorithm>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
@@ -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));
const auto* vp = ImGui::GetMainViewport();
ImGui::GetBackgroundDrawList()->AddRectFilled({}, vp->Size, ImColor(30, 30, 30, 220));
ImGui::Begin("OHUD Showcase", &m_opened,
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDecoration);
{
ImGui::SetWindowPos({});
ImGui::SetWindowSize(ImGui::GetMainViewport()->Size);
draw_controls();
draw_overlay();
ImGui::ColorEdit4("Box", reinterpret_cast<float*>(&box_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Box fill", reinterpret_cast<float*>(&box_fill), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Bar", reinterpret_cast<float*>(&bar_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Bar Background", reinterpret_cast<float*>(&bar_bg_color),
ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Bar Outline", reinterpret_cast<float*>(&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<omath::hud::ImguiHudRenderer>());
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<float>{400, 600}, omath::Color::from_rgba(255, 0, 0, 255), 2.f);
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(m_window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(0.f, 0.f, 0.f, 0.f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(m_window);
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);
}
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<float*>(&m_box_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Fill##box", reinterpret_cast<float*>(&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<float*>(&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);
}
if (ImGui::CollapsingHeader("Bars", ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::ColorEdit4("Color##bar", reinterpret_cast<float*>(&m_bar_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("BG##bar", reinterpret_cast<float*>(&m_bar_bg_color), ImGuiColorEditFlags_NoInputs);
ImGui::ColorEdit4("Outline##bar", reinterpret_cast<float*>(&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);
}
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);
}
if (ImGui::CollapsingHeader("Skeleton"))
{
ImGui::Checkbox("Show##skel", &m_show_skeleton);
ImGui::ColorEdit4("Color##skel", reinterpret_cast<float*>(&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<float*>(&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<omath::hud::ImguiHudRenderer>());
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

View File

@@ -1,11 +1,9 @@
//
// Created by Vlad on 6/17/2025.
//
//
// Created by Orange on 11/11/2024.
//
#pragma once
#include <omath/hud/entity_overlay.hpp>
#include <omath/utility/color.hpp>
#include <string_view>
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
} // namespace imgui_desktop::gui

View File

@@ -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<typename... Args>
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<float>& 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<typename... Args>
void add_bottom_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> 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<typename... Args>
void add_left_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> 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<typename... Args>
void add_centered_bottom_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> 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<typename... Args>
void add_centered_top_label(const Color& color, const float offset, const bool outlined,
std::format_string<Args...> 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<float>& position, const Color& color,
const std::string_view& text);
void draw_dashed_line(const Vector2<float>& from, const Vector2<float>& to, const Color& color,
float dash_len, float gap_len, float thickness) const;
CanvasBox m_canvas;
Vector2<float> m_text_cursor_right;
Vector2<float> m_text_cursor_top;
Vector2<float> m_text_cursor_bottom;
Vector2<float> m_text_cursor_left;
std::shared_ptr<HudRendererInterface> m_renderer;
};
} // namespace omath::hud

View File

@@ -18,8 +18,7 @@ namespace omath::hud
virtual void add_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color,
float thickness) = 0;
virtual void add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color,
float thickness) = 0;
virtual void add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color) = 0;
virtual void add_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) = 0;

View File

@@ -14,7 +14,7 @@ namespace omath::hud
void add_line(const Vector2<float>& line_start, const Vector2<float>& line_end, const Color& color,
float thickness) override;
void add_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color, float thickness) override;
void add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color, float thickness) override;
void add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color) override;
void add_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) override;
void add_filled_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) override;
void add_text(const Vector2<float>& position, const Color& color, const std::string_view& text) override;

View File

@@ -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<float>(width, -max_bar_height * ratio), color);
m_renderer->add_rectangle(bar_start - Vector2<float>(1.f, 0.f),
bar_start + Vector2<float>(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<float>{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<float>
{
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<float,float>, std::pair<float,float>>;
static constexpr std::array<B, 15> 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<float>& from, const Vector2<float>& 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<float>& from, const Vector2<float>& 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<float>& 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<float>{0.f, offset};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width, height), bg_color);
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(max_bar_width * ratio, height), color);
m_renderer->add_rectangle(bar_start, bar_start + Vector2<float>(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<float>{0.f, offset}, color, text);
else
m_renderer->add_text(m_text_cursor_bottom + Vector2<float>{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<float>{-(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<float>{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<float>{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<float>& top, const Vector2<float>& bottom,
const std::shared_ptr<HudRendererInterface>& 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

View File

@@ -22,15 +22,13 @@ namespace omath::hud
{
ImGui::GetBackgroundDrawList()->AddPolyline(reinterpret_cast<const ImVec2*>(vertexes.data()),
static_cast<int>(vertexes.size()), color.to_im_color(),
ImDrawFlags_None, thickness);
ImDrawFlags_Closed, thickness);
}
void ImguiHudRenderer::add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color,
const float thickness)
void ImguiHudRenderer::add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color)
{
ImGui::GetBackgroundDrawList()->AddPolyline(reinterpret_cast<const ImVec2*>(vertexes.data()),
static_cast<int>(vertexes.size()), color.to_im_color(),
ImDrawFlags_Closed, thickness);
ImGui::GetBackgroundDrawList()->AddConvexPolyFilled(reinterpret_cast<const ImVec2*>(vertexes.data()),
static_cast<int>(vertexes.size()), color.to_im_color());
}
void ImguiHudRenderer::add_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color)
@@ -50,7 +48,7 @@ namespace omath::hud
text.data() + text.size());
}
[[nodiscard]]
Vector2<float> calc_text_size(const std::string_view& text)
Vector2<float> ImguiHudRenderer::calc_text_size(const std::string_view& text)
{
return Vector2<float>::from_im_vec2(ImGui::CalcTextSize(text.data()));
}