Compare commits
2 Commits
5355a9eded
...
2f8a3706c5
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f8a3706c5 | |||
| 1e6c10d156 |
@@ -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
|
||||||
|
|||||||
32
CLAUDE.md
32
CLAUDE.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,11 +164,11 @@ 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);
|
||||||
|
|
||||||
text_line("Location: Ventnor, Isle of Wight", x, y, SCALE_SMALL,
|
text_line("Location: Ventnor, Isle of Wight", x, y, SCALE_SMALL,
|
||||||
@@ -231,11 +238,11 @@ 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);
|
||||||
|
|
||||||
text_line("Location: Community Boating Center,", x, y, SCALE_SMALL,
|
text_line("Location: Community Boating Center,", x, y, SCALE_SMALL,
|
||||||
@@ -303,11 +310,11 @@ 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);
|
||||||
|
|
||||||
text_line("Location: Center of Bellingham Bay,", x, y, SCALE_SMALL,
|
text_line("Location: Center of Bellingham Bay,", x, y, SCALE_SMALL,
|
||||||
@@ -379,11 +386,11 @@ 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);
|
||||||
|
|
||||||
text_line("Starting location: Taylor Dock /", x, y, SCALE_SMALL,
|
text_line("Starting location: Taylor Dock /", x, y, SCALE_SMALL,
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
280
src/main.cpp
280
src/main.cpp
@@ -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,17 +530,17 @@ 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()) {
|
||||||
std::string msg = "No radars available. Restart with radar names as arguments.";
|
std::string msg = "No radars available. Restart with radar names as arguments.";
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user