181 lines
6.5 KiB
C++
181 lines
6.5 KiB
C++
#include "gui.h"
|
|
|
|
#include "imgui.h"
|
|
#include "imgui_impl_glfw.h"
|
|
#include "imgui_impl_opengl3.h"
|
|
#include <GLFW/glfw3.h> // drags in the system GL headers we need below
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
|
|
static void glfw_error_callback(int error, const char* description) {
|
|
std::fprintf(stderr, "GLFW error %d: %s\n", error, description);
|
|
}
|
|
|
|
// Analog-style rpm tach: a 270-degree arc face (gap at the bottom),
|
|
// tick marks in thousands, a red zone from redline to max, and a needle.
|
|
// Draws in-place at the current cursor position, then advances the
|
|
// cursor past it like any other widget.
|
|
static void DrawTachGauge(float rpm, float max_rpm, float redline_rpm, float diameter) {
|
|
constexpr float kPi = 3.14159265358979323846f;
|
|
|
|
ImVec2 p0 = ImGui::GetCursorScreenPos();
|
|
ImGui::InvisibleButton("##tach_gauge", ImVec2(diameter, diameter));
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
|
|
ImVec2 center(p0.x + diameter * 0.5f, p0.y + diameter * 0.5f);
|
|
float radius = diameter * 0.5f - 4.0f;
|
|
|
|
// Angle convention here: 0 = 3 o'clock, increases clockwise.
|
|
// 135 -> 405 sweeps the long way around through the top (270),
|
|
// leaving a gap at the bottom like a real tach face.
|
|
const float a_min = 135.0f * kPi / 180.0f;
|
|
const float a_max = 405.0f * kPi / 180.0f;
|
|
|
|
const ImU32 face_color = IM_COL32(25, 25, 28, 255);
|
|
const ImU32 rim_color = IM_COL32(200, 200, 200, 255);
|
|
const ImU32 tick_color = IM_COL32(220, 220, 220, 255);
|
|
const ImU32 redline_col = IM_COL32(220, 40, 40, 255);
|
|
const ImU32 needle_color = (rpm >= redline_rpm) ? IM_COL32(255, 60, 60, 255)
|
|
: IM_COL32(255, 200, 60, 255);
|
|
|
|
// Face
|
|
draw_list->AddCircleFilled(center, radius, face_color, 64);
|
|
draw_list->AddCircle(center, radius, rim_color, 64, 2.0f);
|
|
|
|
// Tick marks + labels, one per 1000 rpm
|
|
const int major_step = 1000;
|
|
const int num_majors = static_cast<int>(max_rpm) / major_step;
|
|
for (int i = 0; i <= num_majors; ++i) {
|
|
float t = (i * major_step) / max_rpm;
|
|
float angle = a_min + t * (a_max - a_min);
|
|
ImVec2 dir(std::cos(angle), std::sin(angle));
|
|
bool in_redline = (i * major_step) >= redline_rpm;
|
|
ImU32 color = in_redline ? redline_col : tick_color;
|
|
|
|
ImVec2 p_inner(center.x + dir.x * radius * 0.80f, center.y + dir.y * radius * 0.80f);
|
|
ImVec2 p_outer(center.x + dir.x * radius * 0.95f, center.y + dir.y * radius * 0.95f);
|
|
draw_list->AddLine(p_inner, p_outer, color, 2.0f);
|
|
|
|
char buf[8];
|
|
std::snprintf(buf, sizeof(buf), "%d", i);
|
|
ImVec2 text_size = ImGui::CalcTextSize(buf);
|
|
ImVec2 p_label(center.x + dir.x * radius * 0.63f - text_size.x * 0.5f,
|
|
center.y + dir.y * radius * 0.63f - text_size.y * 0.5f);
|
|
draw_list->AddText(p_label, color, buf);
|
|
}
|
|
|
|
// Redline arc along the rim
|
|
if (redline_rpm < max_rpm) {
|
|
float t_redline = static_cast<float>(redline_rpm / max_rpm);
|
|
float redline_angle = a_min + t_redline * (a_max - a_min);
|
|
draw_list->PathArcTo(center, radius * 0.95f, redline_angle, a_max);
|
|
draw_list->PathStroke(redline_col, 0, 3.0f);
|
|
}
|
|
|
|
// Needle
|
|
float rpm_clamped = rpm < 0.0f ? 0.0f : (rpm > max_rpm ? max_rpm : rpm);
|
|
float t = rpm_clamped / max_rpm;
|
|
float needle_angle = a_min + t * (a_max - a_min);
|
|
ImVec2 needle_dir(std::cos(needle_angle), std::sin(needle_angle));
|
|
ImVec2 needle_tip(center.x + needle_dir.x * radius * 0.85f, center.y + needle_dir.y * radius * 0.85f);
|
|
draw_list->AddLine(center, needle_tip, needle_color, 3.0f);
|
|
draw_list->AddCircleFilled(center, radius * 0.06f, IM_COL32(230, 230, 230, 255), 24);
|
|
|
|
// Digital readout under the needle hub
|
|
char rpm_buf[32];
|
|
std::snprintf(rpm_buf, sizeof(rpm_buf), "%.0f RPM", rpm);
|
|
ImVec2 rpm_text_size = ImGui::CalcTextSize(rpm_buf);
|
|
draw_list->AddText(ImVec2(center.x - rpm_text_size.x * 0.5f, center.y + radius * 0.35f),
|
|
IM_COL32(255, 255, 255, 255), rpm_buf);
|
|
}
|
|
|
|
bool Gui::init() {
|
|
glfwSetErrorCallback(glfw_error_callback);
|
|
if (!glfwInit()) return false;
|
|
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
|
|
|
m_window = glfwCreateWindow(900, 600, "Engine Sim", nullptr, nullptr);
|
|
if (!m_window) {
|
|
glfwTerminate();
|
|
return false;
|
|
}
|
|
glfwMakeContextCurrent(m_window);
|
|
glfwSwapInterval(1); // vsync
|
|
|
|
IMGUI_CHECKVERSION();
|
|
ImGui::CreateContext();
|
|
ImGui::StyleColorsDark();
|
|
|
|
ImGui_ImplGlfw_InitForOpenGL(m_window, true);
|
|
ImGui_ImplOpenGL3_Init(); // nullptr glsl version -> backend picks one to match the hints above
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Gui::begin_frame() {
|
|
if (glfwWindowShouldClose(m_window)) return false;
|
|
|
|
glfwPollEvents();
|
|
ImGui_ImplOpenGL3_NewFrame();
|
|
ImGui_ImplGlfw_NewFrame();
|
|
ImGui::NewFrame();
|
|
return true;
|
|
}
|
|
|
|
void Gui::render(const EngineStats& stats) {
|
|
const ImGuiIO& io = ImGui::GetIO();
|
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
ImGui::SetNextWindowSize(io.DisplaySize);
|
|
|
|
ImGui::Begin("Engine Sim", nullptr,
|
|
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
|
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
|
|
|
ImGui::Columns(2, "main_columns");
|
|
|
|
// --- Left column: primary live readouts ---
|
|
DrawTachGauge(static_cast<float>(stats.rpm), static_cast<float>(stats.max_rpm),
|
|
static_cast<float>(stats.redline_rpm), 220.0f);
|
|
|
|
ImGui::Text("Fuel Consumption: %.2f L/h", stats.fuel_consumption_lph);
|
|
ImGui::Text("Crank Power: %.2f kW", stats.crank_power_kw);
|
|
ImGui::Text("Output Heat: %.1f J", stats.output_heat_j);
|
|
|
|
ImGui::NextColumn();
|
|
|
|
// --- Right column: extensible stats list (oil temp, coolant temp, ...) ---
|
|
ImGui::Text("Stats");
|
|
ImGui::Separator();
|
|
for (const StatEntry& s : stats.extra_stats) {
|
|
ImGui::Text("%s: %.2f %s", s.label.c_str(), s.value, s.unit.c_str());
|
|
}
|
|
|
|
ImGui::Columns(1);
|
|
ImGui::End();
|
|
}
|
|
|
|
void Gui::end_frame() {
|
|
ImGui::Render();
|
|
|
|
int display_w, display_h;
|
|
glfwGetFramebufferSize(m_window, &display_w, &display_h);
|
|
glViewport(0, 0, display_w, display_h);
|
|
glClearColor(0.1f, 0.1f, 0.12f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
|
|
|
glfwSwapBuffers(m_window);
|
|
}
|
|
|
|
void Gui::shutdown() {
|
|
ImGui_ImplOpenGL3_Shutdown();
|
|
ImGui_ImplGlfw_Shutdown();
|
|
ImGui::DestroyContext();
|
|
|
|
glfwDestroyWindow(m_window);
|
|
glfwTerminate();
|
|
}
|