Compare commits

..

2 Commits

Author SHA1 Message Date
2f8a3706c5 Change font size and use outlines 2026-06-18 10:23:58 -07:00
1e6c10d156 fixed fonts 2026-06-18 10:02:43 -07:00
7 changed files with 308 additions and 110 deletions

View File

@@ -27,19 +27,21 @@ Examples of data and functions for this class and it subordinates:
of the left hand panel. Please note that in the future, this left hand panel may be moved of the left hand panel. Please note that in the future, this left hand panel may be moved
to a second physical monitor. to a second physical monitor.
3. Text status panel to be located below the radar scope panel. This will display the current 3. Text status panel to be located below the left-hand text panel (bottom of the left
control settings for the controls and the range and bearing and description of side of the screen, NOT below the scope). The scope takes the full right-side height
targets that are on or near the bearing and range cursor. The text status panel shall manage with no panel beneath it. The status panel will display the current control settings
the hit-test. The hit test shall determine which (if any) targets are either directly the same and the range and bearing and description of targets that are on or near the bearing
rand and the same bearing as the cursor or close to it. The closeness shall be determined and range cursor. The text status panel shall manage the hit-test. The hit test shall
by a configuration variable. Since the cursor information as well as the target information determine which (if any) targets are either directly the same range and the same
is already in thread 1, this operatoin shall be performed by the CPU as the CPU will have bearing as the cursor or close to it. The closeness shall be determined by a
all information on the target, including the name and description. configuration variable. Since the cursor information as well as the target information
is already in thread 1, this operation shall be performed by the CPU as the CPU will
have all information on the target, including the name and description.
Please note that any text (such as the vessel name and registration will not be part Please note that any text (such as the vessel name and registration) will not be part
of the SSBO target information. It will need to be formed in text blocks that can of the SSBO target information. It will need to be formed in text blocks that can
be submitted as textures to be inserted into the status text panel below the scope be submitted as textures to be inserted into the status text panel at the bottom of
panel. the left side of the screen.
4. Target data. This will handle the target data that is presented by both the simulator 4. Target data. This will handle the target data that is presented by both the simulator
and the raspberry pi data. All that data will be in the format of the and the raspberry pi data. All that data will be in the format of the

View File

@@ -148,23 +148,29 @@ of {PROJECT_DIR}/build/radar_simulator.
In the CMakeLists.txt, plese use the name radar_simulator for the add_executable call. In the CMakeLists.txt, plese use the name radar_simulator for the add_executable call.
There will be three main areas of the screen. On the right hand side will be the radar There will be three screen areas (viewports):
scope.
On the left hand side of the screen will be a text description of the scope as well 1. Left text panel (upper-left): displays the description of the current scope and the
as the controls of the scope and keyboard keys for each control. This text will be keyboard controls for that scope. Text is white; control labels are red; keystrokes
white while the control labels will be red and the keystrokes will be in pink. are pink. This panel occupies the upper portion of the left side of the window.
At some point, pending a decision with the museum, we may purchase components to mount the controls on a panel. Until that is done, the controls will be on the keyboard. At some point, pending a decision with the museum, we may purchase components to
mount the controls on a panel. Until that is done, the controls will be on the keyboard.
On the right hand side of the screen, below the scope, would be a text status window, showing current 2. Status bar (lower-left): sits directly below the left text panel. It shows the current
maximum range, range, and bearing as appropriate to which scope we are using as well as the identification control values — maximum range, range cursor, bearing cursor, and scope identification
of the scope that we are using. as appropriate to whichever scope is active. This text will be yellow.
Suggest that we use glViewport to define the left-hand text area, the main scope, 3. Scope (right side, full height): the radar scope takes the entire right side of the
and the yellow status bar. This allows you to render the scope with its own coordinate window from top to bottom. There is no status bar below the scope.
system while keeping the text fixed. Along with glViewport, use glScissor and glEnable(GL_SCISSOR_TEST)
Below the scope will be a text status window. This text will be yellow Use glViewport, glScissor, and glEnable(GL_SCISSOR_TEST) to define each of the three
areas so that each can render with its own coordinate system.
The left side dimensions are defined in settings.h:
LEFT_PANEL_WIDTH — horizontal width of the left side
STATUS_BAR_HEIGHT — height of the status bar (bottom of left side)
LEFT_TEXT_HEIGHT — height of the text panel (top of left side; = WINDOW_HEIGHT - STATUS_BAR_HEIGHT)
The scope fills the remaining right side at full window height (SCOPE_WIDTH x WINDOW_HEIGHT).
Scopes in the right panel Scopes in the right panel

View File

@@ -92,5 +92,8 @@ private:
GLuint shader_program_; GLuint shader_program_;
}; };
/* Global instance — shared by all panels and radar stubs. */ /* Global instances — shared by all panels and radar stubs.
* g_text_renderer : FONT_SIZE_NORMAL (body text, controls, status bar)
* g_text_renderer_large: FONT_SIZE_TITLE (panel titles, scope headings) */
extern TextRenderer g_text_renderer; extern TextRenderer g_text_renderer;
extern TextRenderer g_text_renderer_large;

View File

@@ -136,13 +136,28 @@
#define WINDOW_HEIGHT 1080 #define WINDOW_HEIGHT 1080
#define WINDOW_TITLE "RAF / Marine Radar Exhibit" #define WINDOW_TITLE "RAF / Marine Radar Exhibit"
// Left text panel occupies left side; scope and status bar share the right side /* Left side is split vertically: text panel on top, status bar on bottom.
* Right side is the scope viewport — it takes the full window height. */
#define LEFT_PANEL_WIDTH 500 #define LEFT_PANEL_WIDTH 500
#define STATUS_BAR_HEIGHT 250 #define STATUS_BAR_HEIGHT 250
// Derived dimensions — scope area fills remaining right-side space // Derived dimensions
#define SCOPE_WIDTH (WINDOW_WIDTH - LEFT_PANEL_WIDTH) #define SCOPE_WIDTH (WINDOW_WIDTH - LEFT_PANEL_WIDTH)
#define SCOPE_HEIGHT (WINDOW_HEIGHT - STATUS_BAR_HEIGHT) #define SCOPE_HEIGHT WINDOW_HEIGHT // scope takes full right-side height
#define LEFT_TEXT_HEIGHT (WINDOW_HEIGHT - STATUS_BAR_HEIGHT) // text portion of left panel
// Pixel margin on every side of the scope area (keeps edges visible on the monitor)
#define SCOPE_MARGIN 10
// Available scope area after removing the margin
#define SCOPE_VW (SCOPE_WIDTH - 2 * SCOPE_MARGIN)
#define SCOPE_VH (SCOPE_HEIGHT - 2 * SCOPE_MARGIN)
// Square scope: side length is the smaller of SCOPE_VW and SCOPE_VH (height wins here)
#define SCOPE_SQUARE SCOPE_VH
// Horizontal offset to centre the square inside the SCOPE_VW-wide available area
#define SCOPE_SQUARE_XOFF ((SCOPE_VW - SCOPE_SQUARE) / 2)
// 1 = draw a white debug border box around each screen panel (temporary layout aid)
#define DEBUG_DRAW_WINDOW_BORDERS 1
/* ============================================================ /* ============================================================
* TEXT RENDERING * TEXT RENDERING
@@ -151,10 +166,12 @@
#define FONT_PATH "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" #define FONT_PATH "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
#define FONT_SIZE_NORMAL 18u #define FONT_SIZE_NORMAL 18u
#define FONT_SIZE_TITLE 24u #define FONT_SIZE_TITLE 26u
// Pixels between text baselines at scale 1.0 with FONT_SIZE_NORMAL // Pixels between text baselines at scale 1.0 with FONT_SIZE_NORMAL
#define LINE_HEIGHT 24.0f #define LINE_HEIGHT 24.0f
// Pixels between text baselines at scale 1.0 with FONT_SIZE_TITLE
#define LINE_HEIGHT_LARGE 34.0f
// Pixel margin from panel edge for text placement // Pixel margin from panel edge for text placement
#define TEXT_MARGIN 10.0f #define TEXT_MARGIN 10.0f

View File

@@ -66,13 +66,13 @@ static const float COL_GREEN_LO[3] = { 0.00f, 0.55f, 0.18f };
/* Layout helpers */ /* Layout helpers */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
static const float SCALE_TITLE = 1.25f;
static const float SCALE_NORMAL = 1.00f; static const float SCALE_NORMAL = 1.00f;
static const float SCALE_SMALL = 0.85f; static const float SCALE_SMALL = 0.85f;
static const float MARGIN = static_cast<float>(TEXT_MARGIN); static const float MARGIN = static_cast<float>(TEXT_MARGIN);
static const float LH = LINE_HEIGHT; /* line height at scale 1.0 */ static const float LH = LINE_HEIGHT;
static const float LH_LARGE = LINE_HEIGHT_LARGE;
/* Render one line of text and advance y downward by one line. */ /* Render one body-text line and advance y downward. */
static void text_line(const std::string& text, float x, float& y, static void text_line(const std::string& text, float x, float& y,
float scale, float scale,
float cr, float cg, float cb) { float cr, float cg, float cb) {
@@ -80,6 +80,13 @@ static void text_line(const std::string& text, float x, float& y,
y -= LH * scale; y -= LH * scale;
} }
/* Render one title-size line using the large renderer at scale 1.0. */
static void text_line_large(const std::string& text, float x, float& y,
float cr, float cg, float cb) {
g_text_renderer_large.render_text(text, x, y, 1.0f, cr, cg, cb);
y -= LH_LARGE;
}
/* Render a blank spacer line. */ /* Render a blank spacer line. */
static void blank_line(float& y, float scale) { static void blank_line(float& y, float scale) {
y -= LH * scale * 0.6f; y -= LH * scale * 0.6f;
@@ -120,19 +127,19 @@ static void control_line(const std::string& label,
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
static void render_scope_name(const std::string& radar_name) { static void render_scope_name(const std::string& radar_name) {
const float scope_w = static_cast<float>(SCOPE_WIDTH); const float scope_w = static_cast<float>(SCOPE_SQUARE);
const float scope_h = static_cast<float>(SCOPE_HEIGHT); const float scope_h = static_cast<float>(SCOPE_SQUARE);
float name_w = g_text_renderer.get_text_width(radar_name, 2.0f); float name_w = g_text_renderer_large.get_text_width(radar_name, 1.0f);
float nx = (scope_w - name_w) / 2.0f; float nx = (scope_w - name_w) / 2.0f;
float ny = scope_h / 2.0f + 20.0f; float ny = scope_h / 2.0f + 20.0f;
g_text_renderer.render_text(radar_name, nx, ny, 2.0f, g_text_renderer_large.render_text(radar_name, nx, ny, 1.0f,
COL_GREEN_DIM[0], COL_GREEN_DIM[1], COL_GREEN_DIM[2]); COL_GREEN_DIM[0], COL_GREEN_DIM[1], COL_GREEN_DIM[2]);
std::string sub = "[ stub - initialization test only ]"; std::string sub = "[ stub - initialization test only ]";
float sub_w = g_text_renderer.get_text_width(sub, SCALE_NORMAL); float sub_w = g_text_renderer.get_text_width(sub, SCALE_NORMAL);
float sx = (scope_w - sub_w) / 2.0f; float sx = (scope_w - sub_w) / 2.0f;
float sy = ny - LH * 2.0f; float sy = ny - LH_LARGE;
g_text_renderer.render_text(sub, sx, sy, SCALE_NORMAL, g_text_renderer.render_text(sub, sx, sy, SCALE_NORMAL,
COL_GREEN_LO[0], COL_GREEN_LO[1], COL_GREEN_LO[2]); COL_GREEN_LO[0], COL_GREEN_LO[1], COL_GREEN_LO[2]);
} }
@@ -157,10 +164,10 @@ static void render_common_status(float x, float& y, float scale) {
/* ================================================================== */ /* ================================================================== */
void ChainHomeRadar::render_left_panel() { void ChainHomeRadar::render_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH * SCALE_TITLE; float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN; float x = MARGIN;
text_line("CHAIN HOME RADAR", x, y, SCALE_TITLE, text_line_large("CHAIN HOME RADAR", x, y,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]); COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL); blank_line(y, SCALE_NORMAL);
@@ -231,10 +238,10 @@ void ChainHomeRadar::render_status_bar() {
/* ================================================================== */ /* ================================================================== */
void MarineAScopeRadar::render_left_panel() { void MarineAScopeRadar::render_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH * SCALE_TITLE; float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN; float x = MARGIN;
text_line("MARINE A-SCOPE RADAR", x, y, SCALE_TITLE, text_line_large("MARINE A-SCOPE RADAR", x, y,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]); COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL); blank_line(y, SCALE_NORMAL);
@@ -303,10 +310,10 @@ void MarineAScopeRadar::render_status_bar() {
/* ================================================================== */ /* ================================================================== */
void MarineTrafficRadar::render_left_panel() { void MarineTrafficRadar::render_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH * SCALE_TITLE; float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN; float x = MARGIN;
text_line("MARINE TRAFFIC CONTROL RADAR", x, y, SCALE_TITLE, text_line_large("MARINE TRAFFIC CONTROL RADAR", x, y,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]); COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL); blank_line(y, SCALE_NORMAL);
@@ -379,10 +386,10 @@ void MarineTrafficRadar::render_status_bar() {
/* ================================================================== */ /* ================================================================== */
void PoliceboatRadar::render_left_panel() { void PoliceboatRadar::render_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH * SCALE_TITLE; float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN; float x = MARGIN;
text_line("POLICE BOAT RADAR", x, y, SCALE_TITLE, text_line_large("POLICE BOAT RADAR", x, y,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]); COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL); blank_line(y, SCALE_NORMAL);

View File

@@ -42,6 +42,7 @@
/* Global singleton used by all components. */ /* Global singleton used by all components. */
TextRenderer g_text_renderer; TextRenderer g_text_renderer;
TextRenderer g_text_renderer_large;
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Internal helpers */ /* Internal helpers */

View File

@@ -57,6 +57,126 @@
#include <chrono> #include <chrono>
#include <thread> #include <thread>
/* ------------------------------------------------------------------ */
/* Debug border-box line renderer (DEBUG_DRAW_WINDOW_BORDERS) */
/* ------------------------------------------------------------------ */
#if DEBUG_DRAW_WINDOW_BORDERS
static const char* LINE_VERT_SRC =
"#version 430 core\n"
"layout(location = 0) in vec2 pos;\n"
"uniform mat4 projection;\n"
"void main() {\n"
" gl_Position = projection * vec4(pos, 0.0, 1.0);\n"
"}\n";
static const char* LINE_FRAG_SRC =
"#version 430 core\n"
"uniform vec3 line_color;\n"
"out vec4 frag_color;\n"
"void main() {\n"
" frag_color = vec4(line_color, 1.0);\n"
"}\n";
static GLuint g_line_vao = 0;
static GLuint g_line_vbo = 0;
static GLuint g_line_prog = 0;
static bool init_line_renderer() {
GLint ok = 0;
char log_buf[512];
GLuint vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &LINE_VERT_SRC, nullptr);
glCompileShader(vert);
glGetShaderiv(vert, GL_COMPILE_STATUS, &ok);
if (!ok) {
glGetShaderInfoLog(vert, 512, nullptr, log_buf);
fprintf(stderr, "[main] border vert shader error:\n%s\n", log_buf);
glDeleteShader(vert);
return false;
}
GLuint frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &LINE_FRAG_SRC, nullptr);
glCompileShader(frag);
glGetShaderiv(frag, GL_COMPILE_STATUS, &ok);
if (!ok) {
glGetShaderInfoLog(frag, 512, nullptr, log_buf);
fprintf(stderr, "[main] border frag shader error:\n%s\n", log_buf);
glDeleteShader(vert);
glDeleteShader(frag);
return false;
}
g_line_prog = glCreateProgram();
glAttachShader(g_line_prog, vert);
glAttachShader(g_line_prog, frag);
glLinkProgram(g_line_prog);
glGetProgramiv(g_line_prog, GL_LINK_STATUS, &ok);
if (!ok) {
glGetProgramInfoLog(g_line_prog, 512, nullptr, log_buf);
fprintf(stderr, "[main] border shader link error:\n%s\n", log_buf);
glDeleteShader(vert);
glDeleteShader(frag);
return false;
}
glDeleteShader(vert);
glDeleteShader(frag);
glGenVertexArrays(1, &g_line_vao);
glGenBuffers(1, &g_line_vbo);
glBindVertexArray(g_line_vao);
glBindBuffer(GL_ARRAY_BUFFER, g_line_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 8, nullptr, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
fprintf(stdout, "[main] debug border renderer initialised\n");
return true;
}
/* Draw a white line-loop rectangle inset 3 px from the edges of the
* current viewport (w x h). Call after setting glViewport/glScissor. */
static void draw_border_box(int w, int h) {
const float fw = static_cast<float>(w);
const float fh = static_cast<float>(h);
const float pad = 3.0f;
float verts[8] = {
pad, pad,
fw - pad, pad,
fw - pad, fh - pad,
pad, fh - pad
};
/* Same orthographic pixel-space projection as TextRenderer::set_projection. */
float proj[16] = {
2.0f / fw, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / fh, 0.0f, 0.0f,
0.0f, 0.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 1.0f
};
glUseProgram(g_line_prog);
glUniformMatrix4fv(glGetUniformLocation(g_line_prog, "projection"),
1, GL_FALSE, proj);
glUniform3f(glGetUniformLocation(g_line_prog, "line_color"),
1.0f, 1.0f, 1.0f);
glBindVertexArray(g_line_vao);
glBindBuffer(GL_ARRAY_BUFFER, g_line_vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_LINE_LOOP, 0, 4);
glBindVertexArray(0);
glUseProgram(0);
}
#endif /* DEBUG_DRAW_WINDOW_BORDERS */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Application state */ /* Application state */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -93,6 +213,7 @@ static const float COL_PINK[3] = { 1.00f, 0.60f, 0.80f };
static const float COL_RED[3] = { 1.00f, 0.25f, 0.25f }; static const float COL_RED[3] = { 1.00f, 0.25f, 0.25f };
static const float LH_MAIN = LINE_HEIGHT; static const float LH_MAIN = LINE_HEIGHT;
static const float LH_LARGE = LINE_HEIGHT_LARGE;
static const float MARGIN = static_cast<float>(TEXT_MARGIN); static const float MARGIN = static_cast<float>(TEXT_MARGIN);
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -372,12 +493,12 @@ static void parse_args(int argc, char* argv[]) {
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
static void render_intro_left_panel() { static void render_intro_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH_MAIN * 1.25f; float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN; float x = MARGIN;
g_text_renderer.render_text("RAF / MARINE RADAR EXHIBIT", x, y, 1.25f, g_text_renderer_large.render_text("RAF / MARINE RADAR EXHIBIT", x, y, 1.0f,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]); COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
y -= LH_MAIN * 1.25f; y -= LH_LARGE;
y -= LH_MAIN * 0.5f; y -= LH_MAIN * 0.5f;
@@ -409,16 +530,16 @@ static void render_intro_left_panel() {
} }
static void render_radar_list() { static void render_radar_list() {
float scope_w = static_cast<float>(SCOPE_WIDTH); float scope_w = static_cast<float>(SCOPE_SQUARE);
float scope_h = static_cast<float>(SCOPE_HEIGHT); float scope_h = static_cast<float>(SCOPE_SQUARE);
/* Title */ /* Title */
std::string title = "SELECT A RADAR"; std::string title = "SELECT A RADAR";
float tw = g_text_renderer.get_text_width(title, 1.5f); float tw = g_text_renderer_large.get_text_width(title, 1.0f);
g_text_renderer.render_text(title, g_text_renderer_large.render_text(title,
(scope_w - tw) / 2.0f, (scope_w - tw) / 2.0f,
scope_h - 60.0f, scope_h - 60.0f,
1.5f, 1.0f,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]); COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
if (g_selectable.empty()) { if (g_selectable.empty()) {
@@ -440,18 +561,17 @@ static void render_radar_list() {
std::string entry = g_selectable[i].display_name; std::string entry = g_selectable[i].display_name;
bool highlighted = (i == g_selection_cursor); bool highlighted = (i == g_selection_cursor);
float scale = highlighted ? 1.3f : 1.0f;
const float* col = highlighted ? COL_GREEN_HI : COL_GREEN_DIM; const float* col = highlighted ? COL_GREEN_HI : COL_GREEN_DIM;
std::string prefix = highlighted ? "> " : " "; std::string prefix = highlighted ? "> " : " ";
std::string line = prefix + entry; std::string line = prefix + entry;
float lw = g_text_renderer.get_text_width(line, scale); TextRenderer& tr = highlighted ? g_text_renderer_large : g_text_renderer;
float lw = tr.get_text_width(line, 1.0f);
float lx = (scope_w - lw) / 2.0f; float lx = (scope_w - lw) / 2.0f;
float ly = list_y_start - i * LH_MAIN * 1.4f; float ly = list_y_start - i * LH_MAIN * 1.4f;
g_text_renderer.render_text(line, lx, ly, scale, tr.render_text(line, lx, ly, 1.0f, col[0], col[1], col[2]);
col[0], col[1], col[2]);
} }
} }
@@ -502,69 +622,89 @@ static void render_selection_status_bar() {
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
static void render_selection_screen() { static void render_selection_screen() {
int scope_x = LEFT_PANEL_WIDTH;
int scope_y = STATUS_BAR_HEIGHT;
int scope_w = SCOPE_WIDTH;
int scope_h = SCOPE_HEIGHT;
/* Left panel */
glViewport(0, 0, LEFT_PANEL_WIDTH, WINDOW_HEIGHT);
glScissor(0, 0, LEFT_PANEL_WIDTH, WINDOW_HEIGHT);
glEnable(GL_SCISSOR_TEST); glEnable(GL_SCISSOR_TEST);
/* Left text panel — upper portion of the left side */
glViewport(0, STATUS_BAR_HEIGHT, LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
glScissor(0, STATUS_BAR_HEIGHT, LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
glClearColor(0.04f, 0.04f, 0.04f, 1.0f); glClearColor(0.04f, 0.04f, 0.04f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(LEFT_PANEL_WIDTH, WINDOW_HEIGHT); g_text_renderer.set_projection(LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
g_text_renderer_large.set_projection(LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
render_intro_left_panel(); render_intro_left_panel();
#if DEBUG_DRAW_WINDOW_BORDERS
draw_border_box(LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
#endif
/* Scope area — radar list */ /* Left status bar — lower portion of the left side */
glViewport(scope_x, scope_y, scope_w, scope_h); glViewport(0, 0, LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
glScissor(scope_x, scope_y, scope_w, scope_h); glScissor(0, 0, LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
glClearColor(0.04f, 0.04f, 0.04f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
g_text_renderer_large.set_projection(LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
render_selection_status_bar();
#if DEBUG_DRAW_WINDOW_BORDERS
draw_border_box(LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
#endif
/* Scope — square, centred in the right-side area */
glViewport(LEFT_PANEL_WIDTH + SCOPE_MARGIN + SCOPE_SQUARE_XOFF, SCOPE_MARGIN,
SCOPE_SQUARE, SCOPE_SQUARE);
glScissor(LEFT_PANEL_WIDTH + SCOPE_MARGIN + SCOPE_SQUARE_XOFF, SCOPE_MARGIN,
SCOPE_SQUARE, SCOPE_SQUARE);
glClearColor(0.0f, 0.05f, 0.01f, 1.0f); glClearColor(0.0f, 0.05f, 0.01f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(scope_w, scope_h); g_text_renderer.set_projection(SCOPE_SQUARE, SCOPE_SQUARE);
g_text_renderer_large.set_projection(SCOPE_SQUARE, SCOPE_SQUARE);
render_radar_list(); render_radar_list();
#if DEBUG_DRAW_WINDOW_BORDERS
/* Status bar */ draw_border_box(SCOPE_SQUARE, SCOPE_SQUARE);
glViewport(scope_x, 0, scope_w, STATUS_BAR_HEIGHT); #endif
glScissor(scope_x, 0, scope_w, STATUS_BAR_HEIGHT);
glClearColor(0.04f, 0.04f, 0.04f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(scope_w, STATUS_BAR_HEIGHT);
render_selection_status_bar();
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
} }
static void render_operating_screen() { static void render_operating_screen() {
int scope_x = LEFT_PANEL_WIDTH;
int scope_y = STATUS_BAR_HEIGHT;
int scope_w = SCOPE_WIDTH;
int scope_h = SCOPE_HEIGHT;
/* Left panel — radar description and controls */
glViewport(0, 0, LEFT_PANEL_WIDTH, WINDOW_HEIGHT);
glScissor(0, 0, LEFT_PANEL_WIDTH, WINDOW_HEIGHT);
glEnable(GL_SCISSOR_TEST); glEnable(GL_SCISSOR_TEST);
/* Left text panel — upper portion of the left side */
glViewport(0, STATUS_BAR_HEIGHT, LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
glScissor(0, STATUS_BAR_HEIGHT, LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
glClearColor(0.04f, 0.04f, 0.04f, 1.0f); glClearColor(0.04f, 0.04f, 0.04f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(LEFT_PANEL_WIDTH, WINDOW_HEIGHT); g_text_renderer.set_projection(LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
g_text_renderer_large.set_projection(LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
g_active_radar->render_left_panel(); g_active_radar->render_left_panel();
#if DEBUG_DRAW_WINDOW_BORDERS
draw_border_box(LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
#endif
/* Scope — radar name (stub) */ /* Left status bar — lower portion of the left side */
glViewport(scope_x, scope_y, scope_w, scope_h); glViewport(0, 0, LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
glScissor(scope_x, scope_y, scope_w, scope_h); glScissor(0, 0, LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
glClearColor(0.04f, 0.04f, 0.04f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
g_text_renderer_large.set_projection(LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
g_active_radar->render_status_bar();
#if DEBUG_DRAW_WINDOW_BORDERS
draw_border_box(LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
#endif
/* Scope — square, centred in the right-side area */
glViewport(LEFT_PANEL_WIDTH + SCOPE_MARGIN + SCOPE_SQUARE_XOFF, SCOPE_MARGIN,
SCOPE_SQUARE, SCOPE_SQUARE);
glScissor(LEFT_PANEL_WIDTH + SCOPE_MARGIN + SCOPE_SQUARE_XOFF, SCOPE_MARGIN,
SCOPE_SQUARE, SCOPE_SQUARE);
glClearColor(0.0f, 0.05f, 0.01f, 1.0f); glClearColor(0.0f, 0.05f, 0.01f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(scope_w, scope_h); g_text_renderer.set_projection(SCOPE_SQUARE, SCOPE_SQUARE);
g_text_renderer_large.set_projection(SCOPE_SQUARE, SCOPE_SQUARE);
g_active_radar->render_scope(); g_active_radar->render_scope();
#if DEBUG_DRAW_WINDOW_BORDERS
/* Status bar — current control values */ draw_border_box(SCOPE_SQUARE, SCOPE_SQUARE);
glViewport(scope_x, 0, scope_w, STATUS_BAR_HEIGHT); #endif
glScissor(scope_x, 0, scope_w, STATUS_BAR_HEIGHT);
glClearColor(0.04f, 0.04f, 0.04f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(scope_w, STATUS_BAR_HEIGHT);
g_active_radar->render_status_bar();
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
} }
@@ -627,7 +767,14 @@ int main(int argc, char* argv[]) {
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
/* 6. Load text renderer shaders and font. */ /* 6a. Initialise debug border renderer (guarded by DEBUG_DRAW_WINDOW_BORDERS). */
#if DEBUG_DRAW_WINDOW_BORDERS
if (!init_line_renderer()) {
fprintf(stderr, "[main] debug border renderer init failed (non-fatal)\n");
}
#endif
/* 6. Load text renderer shaders and fonts. */
if (!g_text_renderer.initialize_shaders()) { if (!g_text_renderer.initialize_shaders()) {
fprintf(stderr, "[main] text renderer shader init failed\n"); fprintf(stderr, "[main] text renderer shader init failed\n");
glfwTerminate(); glfwTerminate();
@@ -638,6 +785,16 @@ int main(int argc, char* argv[]) {
glfwTerminate(); glfwTerminate();
return 1; return 1;
} }
if (!g_text_renderer_large.initialize_shaders()) {
fprintf(stderr, "[main] large text renderer shader init failed\n");
glfwTerminate();
return 1;
}
if (!g_text_renderer_large.initialize_font(FONT_PATH, FONT_SIZE_TITLE)) {
fprintf(stderr, "[main] large font load failed: " FONT_PATH "\n");
glfwTerminate();
return 1;
}
/* 7. Register keyboard callback. */ /* 7. Register keyboard callback. */
glfwSetKeyCallback(window, key_callback); glfwSetKeyCallback(window, key_callback);
@@ -704,6 +861,11 @@ int main(int argc, char* argv[]) {
/* 10. Cleanup. */ /* 10. Cleanup. */
g_text_renderer.cleanup(); g_text_renderer.cleanup();
#if DEBUG_DRAW_WINDOW_BORDERS
if (g_line_vao) { glDeleteVertexArrays(1, &g_line_vao); g_line_vao = 0; }
if (g_line_vbo) { glDeleteBuffers(1, &g_line_vbo); g_line_vbo = 0; }
if (g_line_prog) { glDeleteProgram(g_line_prog); g_line_prog = 0; }
#endif
glfwDestroyWindow(window); glfwDestroyWindow(window);
glfwTerminate(); glfwTerminate();