init commit
This commit is contained in:
+180
@@ -0,0 +1,180 @@
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct GLFWwindow;
|
||||
|
||||
// One row for the extensible right-hand stats list (oil temp, coolant
|
||||
// temp, etc.) — push more entries onto EngineStats::extra_stats as the
|
||||
// sim grows, no Gui changes needed.
|
||||
struct StatEntry {
|
||||
std::string label;
|
||||
double value;
|
||||
std::string unit;
|
||||
};
|
||||
|
||||
// Plain data the GUI reads each frame. The GUI knows nothing about
|
||||
// Engine/Transmission/Vehicle — main.cpp fills this in from Simulation.
|
||||
struct EngineStats {
|
||||
double rpm = 0.0;
|
||||
double redline_rpm = 6500.0; // tach gauge: start of red zone
|
||||
double max_rpm = 8000.0; // tach gauge: full scale
|
||||
|
||||
double fuel_consumption_lph = 0.0;
|
||||
double crank_power_kw = 0.0;
|
||||
double output_heat_j = 0.0;
|
||||
|
||||
std::vector<StatEntry> extra_stats;
|
||||
};
|
||||
|
||||
class Gui {
|
||||
public:
|
||||
bool init(); // create window + ImGui context
|
||||
bool begin_frame(); // returns false when the window should close
|
||||
void render(const EngineStats& stats);
|
||||
void end_frame(); // swap buffers
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
GLFWwindow* m_window = nullptr;
|
||||
};
|
||||
Reference in New Issue
Block a user