Merge pull request #165 from orange-cpp/feature/hud

Feature/hud
This commit is contained in:
2026-03-13 22:11:26 +03:00
committed by GitHub
13 changed files with 523 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ add_subdirectory(example_barycentric)
add_subdirectory(example_glfw3) add_subdirectory(example_glfw3)
add_subdirectory(example_proj_mat_builder) add_subdirectory(example_proj_mat_builder)
add_subdirectory(example_signature_scan) add_subdirectory(example_signature_scan)
add_subdirectory(example_hud)
if(OMATH_ENABLE_VALGRIND) if(OMATH_ENABLE_VALGRIND)
omath_setup_valgrind(example_projection_matrix_builder) omath_setup_valgrind(example_projection_matrix_builder)

View File

@@ -0,0 +1,16 @@
project(example_hud)
add_executable(${PROJECT_NAME} main.cpp gui/main_window.cpp gui/main_window.hpp)
set_target_properties(
${PROJECT_NAME}
PROPERTIES CXX_STANDARD 23
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}")
find_package(OpenGL)
find_package(GLEW REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE glfw imgui::imgui omath::omath OpenGL::GL)

View File

@@ -0,0 +1,110 @@
//
// 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>
#include <omath/hud/entity_overlay.hpp>
namespace imgui_desktop::gui
{
bool MainWindow::m_canMoveWindow = false;
MainWindow::MainWindow(const std::string_view& caption, int width, int height)
{
if (!glfwInit())
std::exit(EXIT_FAILURE);
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, true);
m_window = glfwCreateWindow(width, height, caption.data(), nullptr, nullptr);
glfwMakeContextCurrent(m_window);
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = {0.f, 0.f, 0.f, 0.f};
ImGui::GetStyle().AntiAliasedLines = false;
ImGui::GetStyle().AntiAliasedFill = false;
ImGui_ImplGlfw_InitForOpenGL(m_window, true);
ImGui_ImplOpenGL3_Init("#version 130");
}
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<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);
}
glfwDestroyWindow(m_window);
}
} // namespace imgui_desktop::gui
// imgui_desktop

View File

@@ -0,0 +1,28 @@
//
// Created by Vlad on 6/17/2025.
//
//
// Created by Orange on 11/11/2024.
//
#pragma once
#include <string_view>
struct GLFWwindow;
namespace imgui_desktop::gui
{
class MainWindow
{
public:
MainWindow(const std::string_view &caption, int width, int height);
void Run();
private:
GLFWwindow* m_window;
static bool m_canMoveWindow;
bool m_opened = true;
};
} // gui
// imgui_desktop

View File

@@ -0,0 +1,8 @@
//
// Created by orange on 13.03.2026.
//
#include "gui/main_window.hpp"
int main()
{
imgui_desktop::gui::MainWindow("omath::hud", 800, 600).Run();
}

View File

@@ -0,0 +1,23 @@
//
// Created by orange on 13.03.2026.
//
#pragma once
#include "omath/linear_algebra/vector2.hpp"
#include <array>
namespace omath::hud
{
class CanvasBox final
{
public:
CanvasBox(Vector2<float> top, Vector2<float> bottom, float ratio = 4.f);
[[nodiscard]]
std::array<Vector2<float>, 4> as_array() const;
Vector2<float> top_left_corner;
Vector2<float> top_right_corner;
Vector2<float> bottom_left_corner;
Vector2<float> bottom_right_corner;
};
} // namespace omath::hud

View File

@@ -0,0 +1,66 @@
//
// Created by orange on 13.03.2026.
//
#pragma once
#include "canvas_box.hpp"
#include "hud_renderer_interface.hpp"
#include "omath/linear_algebra/vector2.hpp"
#include "omath/utility/color.hpp"
#include <memory>
#include <string_view>
namespace omath::hud
{
class EntityOverlay final
{
public:
EntityOverlay(const Vector2<float>& top, const Vector2<float>& bottom,
const std::shared_ptr<HudRendererInterface>& renderer);
void add_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f},
float thickness = 1.f) const;
void add_cornered_2d_box(const Color& box_color, const Color& fill_color = Color{0.f, 0.f, 0.f, 0.f},
float corner_ratio_len = 0.2f, float thickness = 1.f) const;
void add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color, float width,
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;
template<typename... Args>
void add_right_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_right_label(color, offset, outlined, std::string_view{label});
}
void add_right_label(const Color& color, float offset, bool outlined, const std::string_view& text);
template<typename... Args>
void add_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_top_label(color, offset, outlined, std::string_view{label});
}
void add_top_label(const Color& color, float offset, bool outlined, std::string_view text);
void add_top_bar(const Color& color, const Color& outline_color, const Color& bg_color, float height,
float ratio, float offset = 5.f);
void add_snap_line(const Vector2<float>& start_pos, const Color& color, float width);
private:
void draw_outlined_text(const Vector2<float>& position, const Color& color,
const std::string_view& text);
CanvasBox m_canvas;
Vector2<float> m_text_cursor_right;
Vector2<float> m_text_cursor_top;
std::shared_ptr<HudRendererInterface> m_renderer;
};
} // namespace omath::hud

View File

@@ -0,0 +1,28 @@
//
// Created by orange on 13.03.2026.
//
#pragma once
#include "omath/linear_algebra/vector2.hpp"
#include "omath/utility/color.hpp"
#include <span>
namespace omath::hud
{
class HudRendererInterface
{
public:
virtual ~HudRendererInterface() = default;
virtual void add_line(const Vector2<float>& line_start, const Vector2<float>& line_end, const Color& color,
float thickness) = 0;
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_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) = 0;
virtual void add_filled_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color) = 0;
virtual void add_text(const Vector2<float>& position, const Color& color, const std::string_view& text) = 0;
};
} // namespace omath::hud

View File

@@ -0,0 +1,20 @@
//
// Created by orange on 13.03.2026.
//
#pragma once
#include <omath/hud/hud_renderer_interface.hpp>
namespace omath::hud
{
class ImguiHudRenderer final : public HudRendererInterface
{
public:
~ImguiHudRenderer() override;
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_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;
};
}

27
source/hud/canvas_box.cpp Normal file
View File

@@ -0,0 +1,27 @@
//
// Created by orange on 13.03.2026.
//
//
// Created by Vlad on 6/17/2025.
//
#include "omath/hud/canvas_box.hpp"
namespace omath::hud
{
CanvasBox::CanvasBox(const Vector2<float> top, Vector2<float> bottom, const float ratio)
{
bottom.x = top.x;
const auto height = std::abs(top.y - bottom.y);
top_left_corner = top - Vector2<float>{height / ratio, 0};
top_right_corner = top + Vector2<float>{height / ratio, 0};
bottom_left_corner = bottom - Vector2<float>{height / ratio, 0};
bottom_right_corner = bottom + Vector2<float>{height / ratio, 0};
}
std::array<Vector2<float>, 4> CanvasBox::as_array() const
{
return {top_left_corner, top_right_corner, bottom_right_corner, bottom_left_corner};
}
} // namespace ohud

View File

@@ -0,0 +1,138 @@
//
// Created by orange on 13.03.2026.
//
#include "omath/hud/entity_overlay.hpp"
namespace omath::hud
{
void EntityOverlay::add_2d_box(const Color& box_color, const Color& fill_color, const float thickness) const
{
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);
}
void EntityOverlay::add_cornered_2d_box(const Color& box_color, const Color& fill_color,
const float corner_ratio_len, const float thickness) const
{
const auto corner_line_length =
std::abs((m_canvas.top_left_corner - m_canvas.top_right_corner).x * corner_ratio_len);
if (fill_color.value().w > 0.f)
add_2d_box(fill_color, fill_color);
// Left Side
m_renderer->add_line(m_canvas.top_left_corner,
m_canvas.top_left_corner + Vector2<float>{corner_line_length, 0.f}, box_color, thickness);
m_renderer->add_line(m_canvas.top_left_corner,
m_canvas.top_left_corner + Vector2<float>{0.f, corner_line_length}, box_color, thickness);
m_renderer->add_line(m_canvas.bottom_left_corner,
m_canvas.bottom_left_corner - Vector2<float>{0.f, corner_line_length}, box_color,
thickness);
m_renderer->add_line(m_canvas.bottom_left_corner,
m_canvas.bottom_left_corner + Vector2<float>{corner_line_length, 0.f}, box_color,
thickness);
// Right Side
m_renderer->add_line(m_canvas.top_right_corner,
m_canvas.top_right_corner - Vector2<float>{corner_line_length, 0.f}, box_color, thickness);
m_renderer->add_line(m_canvas.top_right_corner,
m_canvas.top_right_corner + Vector2<float>{0.f, corner_line_length}, box_color, thickness);
m_renderer->add_line(m_canvas.bottom_right_corner,
m_canvas.bottom_right_corner - Vector2<float>{0.f, corner_line_length}, box_color,
thickness);
m_renderer->add_line(m_canvas.bottom_right_corner,
m_canvas.bottom_right_corner - Vector2<float>{corner_line_length, 0.f}, box_color,
thickness);
}
void EntityOverlay::add_right_bar(const Color& color, const Color& outline_color, const Color& bg_color,
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_right_corner.y - m_canvas.bottom_right_corner.y);
const auto bar_start = m_canvas.bottom_right_corner + Vector2<float>{offset, 0.f};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(width, -max_bar_height), bg_color);
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_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
{
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);
const auto bar_start = m_canvas.bottom_left_corner + Vector2<float>{-(offset + width), 0.f};
m_renderer->add_filled_rectangle(bar_start, bar_start + Vector2<float>(width, -max_bar_height), bg_color);
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);
}
void EntityOverlay::add_right_label(const Color& color, const float offset, const bool outlined,
const std::string_view& text)
{
if (outlined)
draw_outlined_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text);
else
m_renderer->add_text(m_text_cursor_right + Vector2<float>{offset, 0.f}, color, text.data());
m_text_cursor_right.y += ImGui::CalcTextSize(text.data()).y;
}
void EntityOverlay::add_top_label(const Color& color, const float offset, const bool outlined,
const std::string_view text)
{
m_text_cursor_top.y -= ImGui::CalcTextSize(text.data()).y;
if (outlined)
draw_outlined_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text);
else
m_renderer->add_text(m_text_cursor_top + Vector2<float>{0.f, -offset}, color, text.data());
}
void EntityOverlay::add_top_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.top_left_corner.x - m_canvas.bottom_right_corner.x);
const auto bar_start = m_canvas.top_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_top.y -= offset + height;
}
void EntityOverlay::add_snap_line(const Vector2<float>& start_pos, const Color& color, const float width)
{
const Vector2<float> line_end =
m_canvas.bottom_left_corner
+ 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::draw_outlined_text(const Vector2<float>& position, const Color& color,
const std::string_view& text)
{
static constexpr std::array outline_offsets = {
Vector2<float>{-1, -1}, Vector2<float>{-1, 0}, Vector2<float>{-1, 1}, Vector2<float>{0, -1},
Vector2<float>{0, 1}, Vector2<float>{1, -1}, Vector2<float>{1, 0}, Vector2<float>{1, 1}};
for (const auto& outline_offset : outline_offsets)
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());
}
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)
{
}
} // namespace omath::hud

View File

@@ -0,0 +1,53 @@
//
// Created by orange on 13.03.2026.
//
#include "omath/hud/renderer_realizations/imgui_renderer.hpp"
#include <imgui.h>
namespace omath::hud
{
ImguiHudRenderer::~ImguiHudRenderer() = default;
void ImguiHudRenderer::add_line(const Vector2<float>& line_start, const Vector2<float>& line_end,
const Color& color, const float thickness)
{
ImGui::GetBackgroundDrawList()->AddLine(line_start.to_im_vec2(), line_end.to_im_vec2(),
color.to_im_color(), thickness);
}
void ImguiHudRenderer::add_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color,
const float thickness)
{
ImGui::GetBackgroundDrawList()->AddPolyline(
reinterpret_cast<const ImVec2*>(vertexes.data()),
static_cast<int>(vertexes.size()),
color.to_im_color(), ImDrawFlags_None, thickness);
}
void ImguiHudRenderer::add_filled_polyline(const std::span<const Vector2<float>>& vertexes, const Color& color,
const float thickness)
{
ImGui::GetBackgroundDrawList()->AddPolyline(
reinterpret_cast<const ImVec2*>(vertexes.data()),
static_cast<int>(vertexes.size()),
color.to_im_color(), ImDrawFlags_Closed, thickness);
}
void ImguiHudRenderer::add_rectangle(const Vector2<float>& min, const Vector2<float>& max, const Color& color)
{
ImGui::GetBackgroundDrawList()->AddRect(min.to_im_vec2(), max.to_im_vec2(), color.to_im_color());
}
void ImguiHudRenderer::add_filled_rectangle(const Vector2<float>& min, const Vector2<float>& max,
const Color& color)
{
ImGui::GetBackgroundDrawList()->AddRectFilled(min.to_im_vec2(), max.to_im_vec2(), color.to_im_color());
}
void ImguiHudRenderer::add_text(const Vector2<float>& position, const Color& color,
const std::string_view& text)
{
ImGui::GetBackgroundDrawList()->AddText(position.to_im_vec2(), color.to_im_color(), text.data(),
text.data() + text.size());
}
} // namespace omath::hud

View File

@@ -31,7 +31,11 @@
"dependencies": [ "dependencies": [
"glfw3", "glfw3",
"glew", "glew",
"opengl" "opengl",
{
"name": "imgui",
"features": ["glfw-binding", "opengl3-binding"]
}
] ]
}, },
"imgui": { "imgui": {