Change font size and use outlines

This commit is contained in:
2026-06-18 10:23:58 -07:00
parent 1e6c10d156
commit 2f8a3706c5
5 changed files with 254 additions and 85 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
to a second physical monitor.
3. Text status panel to be located below the radar scope panel. This will display the current
control settings for the controls and the range and bearing and description of
targets that are on or near the bearing and range cursor. The text status panel shall manage
the hit-test. The hit test shall determine which (if any) targets are either directly the same
rand and the same bearing as the cursor or close to it. The closeness shall be determined
by a configuration variable. Since the cursor information as well as the target information
is already in thread 1, this operatoin shall be performed by the CPU as the CPU will have
all information on the target, including the name and description.
3. Text status panel to be located below the left-hand text panel (bottom of the left
side of the screen, NOT below the scope). The scope takes the full right-side height
with no panel beneath it. The status panel will display the current control settings
and the range and bearing and description of targets that are on or near the bearing
and range cursor. The text status panel shall manage the hit-test. The hit test shall
determine which (if any) targets are either directly the same range and the same
bearing as the cursor or close to it. The closeness shall be determined by a
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
be submitted as textures to be inserted into the status text panel below the scope
panel.
be submitted as textures to be inserted into the status text panel at the bottom of
the left side of the screen.
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

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.
There will be three main areas of the screen. On the right hand side will be the radar
scope.
There will be three screen areas (viewports):
On the left hand side of the screen will be a text description of the scope as well
as the controls of the scope and keyboard keys for each control. This text will be
white while the control labels will be red and the keystrokes will be in pink.
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.
1. Left text panel (upper-left): displays the description of the current scope and the
keyboard controls for that scope. Text is white; control labels are red; keystrokes
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.
On the right hand side of the screen, below the scope, would be a text status window, showing current
maximum range, range, and bearing as appropriate to which scope we are using as well as the identification
of the scope that we are using.
2. Status bar (lower-left): sits directly below the left text panel. It shows the current
control values — maximum range, range cursor, bearing cursor, and scope identification
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,
and the yellow status bar. This allows you to render the scope with its own coordinate
system while keeping the text fixed. Along with glViewport, use glScissor and glEnable(GL_SCISSOR_TEST)
3. Scope (right side, full height): the radar scope takes the entire right side of the
window from top to bottom. There is no status bar below the scope.
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

View File

@@ -136,13 +136,28 @@
#define WINDOW_HEIGHT 1080
#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 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_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

View File

@@ -127,8 +127,8 @@ static void control_line(const std::string& label,
/* ------------------------------------------------------------------ */
static void render_scope_name(const std::string& radar_name) {
const float scope_w = static_cast<float>(SCOPE_WIDTH);
const float scope_h = static_cast<float>(SCOPE_HEIGHT);
const float scope_w = static_cast<float>(SCOPE_SQUARE);
const float scope_h = static_cast<float>(SCOPE_SQUARE);
float name_w = g_text_renderer_large.get_text_width(radar_name, 1.0f);
float nx = (scope_w - name_w) / 2.0f;
@@ -164,7 +164,7 @@ static void render_common_status(float x, float& y, float scale) {
/* ================================================================== */
void ChainHomeRadar::render_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH_LARGE;
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
text_line_large("CHAIN HOME RADAR", x, y,
@@ -238,7 +238,7 @@ void ChainHomeRadar::render_status_bar() {
/* ================================================================== */
void MarineAScopeRadar::render_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH_LARGE;
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
text_line_large("MARINE A-SCOPE RADAR", x, y,
@@ -310,7 +310,7 @@ void MarineAScopeRadar::render_status_bar() {
/* ================================================================== */
void MarineTrafficRadar::render_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH_LARGE;
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
text_line_large("MARINE TRAFFIC CONTROL RADAR", x, y,
@@ -386,7 +386,7 @@ void MarineTrafficRadar::render_status_bar() {
/* ================================================================== */
void PoliceboatRadar::render_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH_LARGE;
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
text_line_large("POLICE BOAT RADAR", x, y,

View File

@@ -57,6 +57,126 @@
#include <chrono>
#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 */
/* ------------------------------------------------------------------ */
@@ -373,7 +493,7 @@ static void parse_args(int argc, char* argv[]) {
/* ------------------------------------------------------------------ */
static void render_intro_left_panel() {
float y = static_cast<float>(WINDOW_HEIGHT) - MARGIN - LH_LARGE;
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
g_text_renderer_large.render_text("RAF / MARINE RADAR EXHIBIT", x, y, 1.0f,
@@ -410,8 +530,8 @@ static void render_intro_left_panel() {
}
static void render_radar_list() {
float scope_w = static_cast<float>(SCOPE_WIDTH);
float scope_h = static_cast<float>(SCOPE_HEIGHT);
float scope_w = static_cast<float>(SCOPE_SQUARE);
float scope_h = static_cast<float>(SCOPE_SQUARE);
/* Title */
std::string title = "SELECT A RADAR";
@@ -502,75 +622,89 @@ static void render_selection_status_bar() {
/* ------------------------------------------------------------------ */
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);
/* 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);
glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(LEFT_PANEL_WIDTH, WINDOW_HEIGHT);
g_text_renderer_large.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();
#if DEBUG_DRAW_WINDOW_BORDERS
draw_border_box(LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
#endif
/* Scope area — radar list */
glViewport(scope_x, scope_y, scope_w, scope_h);
glScissor(scope_x, scope_y, scope_w, scope_h);
/* Left status bar — lower portion of the left side */
glViewport(0, 0, LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
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);
glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(scope_w, scope_h);
g_text_renderer_large.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();
/* Status bar */
glViewport(scope_x, 0, scope_w, STATUS_BAR_HEIGHT);
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_text_renderer_large.set_projection(scope_w, STATUS_BAR_HEIGHT);
render_selection_status_bar();
#if DEBUG_DRAW_WINDOW_BORDERS
draw_border_box(SCOPE_SQUARE, SCOPE_SQUARE);
#endif
glDisable(GL_SCISSOR_TEST);
}
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);
/* 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);
glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(LEFT_PANEL_WIDTH, WINDOW_HEIGHT);
g_text_renderer_large.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();
#if DEBUG_DRAW_WINDOW_BORDERS
draw_border_box(LEFT_PANEL_WIDTH, LEFT_TEXT_HEIGHT);
#endif
/* Scope — radar name (stub) */
glViewport(scope_x, scope_y, scope_w, scope_h);
glScissor(scope_x, scope_y, scope_w, scope_h);
/* Left status bar — lower portion of the left side */
glViewport(0, 0, LEFT_PANEL_WIDTH, STATUS_BAR_HEIGHT);
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);
glClear(GL_COLOR_BUFFER_BIT);
g_text_renderer.set_projection(scope_w, scope_h);
g_text_renderer_large.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();
/* Status bar — current control values */
glViewport(scope_x, 0, scope_w, STATUS_BAR_HEIGHT);
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_text_renderer_large.set_projection(scope_w, STATUS_BAR_HEIGHT);
g_active_radar->render_status_bar();
#if DEBUG_DRAW_WINDOW_BORDERS
draw_border_box(SCOPE_SQUARE, SCOPE_SQUARE);
#endif
glDisable(GL_SCISSOR_TEST);
}
@@ -633,6 +767,13 @@ int main(int argc, char* argv[]) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
/* 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()) {
fprintf(stderr, "[main] text renderer shader init failed\n");
@@ -720,6 +861,11 @@ int main(int argc, char* argv[]) {
/* 10. 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);
glfwTerminate();