Files
modular-radar/src/01_classes.cpp

477 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* MIT License
*
* Copyright (c) 2026 Mark Allyn
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* 01_classes.cpp
* Author: Mark Allyn
* Component: 01_classes
*
* Stub implementations for all four radar subclasses.
* Each renders description text in the left panel, the radar name in the
* scope panel, and current control values in the status bar.
* No real radar simulation occurs here; this exists to test the
* initialization and radar-management/selection flow.
*/
#include "01_classes.h"
#include "02_text_description.h"
#include "data_structure_and_constants.h"
#include "settings.h"
#include <cstdio>
#include <string>
/* ------------------------------------------------------------------ */
/* Global instances */
/* ------------------------------------------------------------------ */
ChainHomeRadar g_chain_home;
MarineAScopeRadar g_marine_ascope;
MarineTrafficRadar g_marine_traffic;
PoliceboatRadar g_police_boat;
/* ------------------------------------------------------------------ */
/* Colour constants (RGB, 0.01.0) */
/* ------------------------------------------------------------------ */
static const float COL_WHITE[3] = { 1.00f, 1.00f, 1.00f };
static const float COL_RED[3] = { 1.00f, 0.25f, 0.25f };
static const float COL_PINK[3] = { 1.00f, 0.60f, 0.80f };
static const float COL_YELLOW[3] = { 1.00f, 1.00f, 0.00f };
static const float COL_GREEN_DIM[3] = { 0.00f, 0.85f, 0.30f };
static const float COL_GREEN_LO[3] = { 0.00f, 0.55f, 0.18f };
/* ------------------------------------------------------------------ */
/* Layout helpers */
/* ------------------------------------------------------------------ */
static const float SCALE_NORMAL = 1.00f;
static const float SCALE_SMALL = 0.85f;
static const float MARGIN = static_cast<float>(TEXT_MARGIN);
static const float LH = LINE_HEIGHT;
static const float LH_LARGE = LINE_HEIGHT_LARGE;
/* Render one body-text line and advance y downward. */
static void text_line(const std::string& text, float x, float& y,
float scale,
float cr, float cg, float cb) {
g_text_renderer.render_text(text, x, y, scale, cr, cg, cb);
y -= LH * scale;
}
/* Render a body-text line with word wrapping; y advances by one line per
* wrapped output line. max_width is the available horizontal pixel span. */
static void text_line_wrapped(const std::string& text, float x, float& y,
float scale, float max_width,
float cr, float cg, float cb) {
g_text_renderer.render_text_wrapped(text, x, y, scale, max_width, LH, cr, cg, cb);
}
/* 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. */
static void blank_line(float& y, float scale) {
y -= LH * scale * 0.6f;
}
/* Render one control entry: label in red, keys in pink, on the same line.
* Format: " label_text key_lo / key_hi"
* Returns the new y after advancing one line. */
static void control_line(const std::string& label,
const std::string& key_lo, const std::string& key_hi,
float x, float& y, float scale) {
float xp = x + 6.0f;
/* Label in red */
g_text_renderer.render_text(label, xp, y, scale,
COL_RED[0], COL_RED[1], COL_RED[2]);
xp += g_text_renderer.get_text_width(label, scale);
/* Lower key in pink */
g_text_renderer.render_text(key_lo, xp, y, scale,
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
xp += g_text_renderer.get_text_width(key_lo, scale);
/* Separator in red */
g_text_renderer.render_text(" / ", xp, y, scale,
COL_RED[0], COL_RED[1], COL_RED[2]);
xp += g_text_renderer.get_text_width(" / ", scale);
/* Upper key in pink */
g_text_renderer.render_text(key_hi, xp, y, scale,
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
y -= LH * scale;
}
/* ------------------------------------------------------------------ */
/* Scope helper: render centred name + stub subtitle */
/* ------------------------------------------------------------------ */
static void render_scope_name(const std::string& radar_name) {
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;
float ny = scope_h / 2.0f + 20.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]);
std::string sub = "[ stub - initialization test only ]";
float sub_w = g_text_renderer.get_text_width(sub, SCALE_NORMAL);
float sx = (scope_w - sub_w) / 2.0f;
float sy = ny - LH_LARGE;
g_text_renderer.render_text(sub, sx, sy, SCALE_NORMAL,
COL_GREEN_LO[0], COL_GREEN_LO[1], COL_GREEN_LO[2]);
}
/* ------------------------------------------------------------------ */
/* Status bar helper: common controls (intensity, sensitivity, STC) */
/* ------------------------------------------------------------------ */
static void render_common_status(float x, float& y, float scale) {
char buf[320];
snprintf(buf, sizeof(buf),
"Intensity: %.2f | Sensitivity: %.2f | STC Sens: %.2f | STC Range: %.1f nm",
uniform_intensity, uniform_sensitivity,
uniform_stc_sensitivity, uniform_stc_range * uniform_max_range);
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
text_line_wrapped(buf, x, y, scale, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}
/* ================================================================== */
/* ChainHomeRadar */
/* ================================================================== */
void ChainHomeRadar::render_left_panel() {
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
text_line_large("CHAIN HOME RADAR", x, y,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("Location: Ventnor, Isle of Wight", x, y, SCALE_SMALL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("The UK developed Chain Home in the 1940s", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("to detect German aircraft attacking across", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("the English Channel. It was one of the", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("first operational radar systems ever built.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("The scope shows range on the horizontal", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("axis and echo amplitude on the vertical.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("Bearing is found with the radiogoniometer:", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("rotate the search coil until the echo", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("amplitude reaches a minimum (null point).", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
/* Controls header */
text_line("--- CONTROLS ---", x, y, SCALE_NORMAL,
COL_RED[0], COL_RED[1], COL_RED[2]);
control_line("Intensity: ", "3", "4", x, y, SCALE_NORMAL);
control_line("Sensitivity: ", "5", "6", x, y, SCALE_NORMAL);
control_line("STC Sensitivity: ", "Q", "W", x, y, SCALE_NORMAL);
control_line("STC Range: ", "E", "R", x, y, SCALE_NORMAL);
control_line("Radiogoniometer: ", "U", "I", x, y, SCALE_NORMAL);
blank_line(y, SCALE_NORMAL);
text_line("1 - Return to Radar Selection", x, y, SCALE_NORMAL,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}
void ChainHomeRadar::render_scope() {
render_scope_name("CHAIN HOME RADAR");
}
void ChainHomeRadar::render_status_bar() {
float x = MARGIN;
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
text_line_wrapped("CHAIN HOME RADAR (Ventnor, Isle of Wight) | Fixed range: 200 nm",
x, y, SCALE_NORMAL, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
render_common_status(x, y, SCALE_NORMAL);
char buf[200];
snprintf(buf, sizeof(buf), "Radiogoniometer: %.1f deg", uniform_radiogoniometer);
text_line_wrapped(buf, x, y, SCALE_NORMAL, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}
/* ================================================================== */
/* MarineAScopeRadar */
/* ================================================================== */
void MarineAScopeRadar::render_left_panel() {
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
text_line_large("MARINE A-SCOPE RADAR", x, y,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("Location: Community Boating Center,", x, y, SCALE_SMALL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("Bellingham Bay, Washington", x, y, SCALE_SMALL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("Microwave radar was developed after", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("Chain Home. Its shorter wavelengths", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("could detect small targets such as a", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("submarine periscope or conning tower.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("A steerable dish is connected to an", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("oscilloscope. Bearing is determined by", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("rotating the dish with the servo control.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("--- CONTROLS ---", x, y, SCALE_NORMAL,
COL_RED[0], COL_RED[1], COL_RED[2]);
control_line("Intensity: ", "3", "4", x, y, SCALE_NORMAL);
control_line("Sensitivity: ", "5", "6", x, y, SCALE_NORMAL);
control_line("STC Sensitivity: ", "Q", "W", x, y, SCALE_NORMAL);
control_line("STC Range: ", "E", "R", x, y, SCALE_NORMAL);
control_line("Max Range: ", "O", "P", x, y, SCALE_NORMAL);
control_line("Antenna Rotation: ", "G", "H", x, y, SCALE_NORMAL);
blank_line(y, SCALE_NORMAL);
text_line("1 - Return to Radar Selection", x, y, SCALE_NORMAL,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}
void MarineAScopeRadar::render_scope() {
render_scope_name("MARINE A-SCOPE RADAR");
}
void MarineAScopeRadar::render_status_bar() {
float x = MARGIN;
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
text_line_wrapped("MARINE A-SCOPE RADAR (Community Boating Center, Bellingham Bay)",
x, y, SCALE_NORMAL, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
render_common_status(x, y, SCALE_NORMAL);
char buf[200];
snprintf(buf, sizeof(buf),
"Max Range: %.1f nm | Antenna Bearing: %.1f deg",
uniform_max_range, uniform_marine_a_bearing);
text_line_wrapped(buf, x, y, SCALE_NORMAL, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}
/* ================================================================== */
/* MarineTrafficRadar */
/* ================================================================== */
void MarineTrafficRadar::render_left_panel() {
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
text_line_large("MARINE TRAFFIC CONTROL RADAR", x, y,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("Location: Center of Bellingham Bay,", x, y, SCALE_SMALL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("Washington", x, y, SCALE_SMALL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("The Plan Position Indicator (PPI) radar", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("sweeps a beam clockwise from north.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("Range is the distance of a blip from", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("centre; bearing is the beam direction.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("All modern marine radars use this type", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("of display. Coastal guard stations use", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("them to manage harbour traffic.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("--- CONTROLS ---", x, y, SCALE_NORMAL,
COL_RED[0], COL_RED[1], COL_RED[2]);
control_line("Intensity: ", "3", "4", x, y, SCALE_NORMAL);
control_line("Sensitivity: ", "5", "6", x, y, SCALE_NORMAL);
control_line("STC Sensitivity: ", "Q", "W", x, y, SCALE_NORMAL);
control_line("STC Range: ", "E", "R", x, y, SCALE_NORMAL);
control_line("Rain Noise Filter: ", "T", "Y", x, y, SCALE_NORMAL);
control_line("Max Range: ", "O", "P", x, y, SCALE_NORMAL);
control_line("Range Cursor: ", "A", "S", x, y, SCALE_NORMAL);
control_line("Bearing Cursor: ", "D", "F", x, y, SCALE_NORMAL);
blank_line(y, SCALE_NORMAL);
text_line("1 - Return to Radar Selection", x, y, SCALE_NORMAL,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}
void MarineTrafficRadar::render_scope() {
render_scope_name("MARINE TRAFFIC CONTROL RADAR");
}
void MarineTrafficRadar::render_status_bar() {
float x = MARGIN;
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
text_line_wrapped("MARINE TRAFFIC CONTROL RADAR (Bellingham Bay)",
x, y, SCALE_NORMAL, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
render_common_status(x, y, SCALE_NORMAL);
char buf[280];
snprintf(buf, sizeof(buf),
"Noise Filter: %.2f | Max Range: %.1f nm | "
"Range Cursor: %.2f nm | Bearing Cursor: %.1f deg",
uniform_noise_filter, uniform_max_range,
uniform_range_cursor * uniform_max_range, uniform_bearing_cursor);
text_line_wrapped(buf, x, y, SCALE_NORMAL, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}
/* ================================================================== */
/* PoliceboatRadar */
/* ================================================================== */
void PoliceboatRadar::render_left_panel() {
float y = static_cast<float>(LEFT_TEXT_HEIGHT) - MARGIN - LH_LARGE;
float x = MARGIN;
text_line_large("POLICE BOAT RADAR", x, y,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("Starting location: Taylor Dock /", x, y, SCALE_SMALL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("Boulevard Park, Bellingham Bay", x, y, SCALE_SMALL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("This PPI radar is mounted on a moving", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("police boat patrolling Bellingham Bay.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("The sweep rotates clockwise relative to", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("the boat's heading, not true north. A", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("small indicator shows where north is.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("Watch how the display changes as the", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
text_line("boat moves and turns.", x, y, SCALE_NORMAL,
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
blank_line(y, SCALE_NORMAL);
text_line("--- CONTROLS ---", x, y, SCALE_NORMAL,
COL_RED[0], COL_RED[1], COL_RED[2]);
control_line("Intensity: ", "3", "4", x, y, SCALE_NORMAL);
control_line("Sensitivity: ", "5", "6", x, y, SCALE_NORMAL);
control_line("STC Sensitivity: ", "Q", "W", x, y, SCALE_NORMAL);
control_line("STC Range: ", "E", "R", x, y, SCALE_NORMAL);
control_line("Rain Noise Filter: ", "T", "Y", x, y, SCALE_NORMAL);
control_line("Max Range: ", "O", "P", x, y, SCALE_NORMAL);
control_line("Range Cursor: ", "A", "S", x, y, SCALE_NORMAL);
control_line("Bearing Cursor: ", "D", "F", x, y, SCALE_NORMAL);
blank_line(y, SCALE_NORMAL);
text_line("1 - Return to Radar Selection", x, y, SCALE_NORMAL,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}
void PoliceboatRadar::render_scope() {
render_scope_name("POLICE BOAT RADAR");
}
void PoliceboatRadar::render_status_bar() {
float x = MARGIN;
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
text_line_wrapped("POLICE BOAT RADAR (Bellingham Bay - moving)",
x, y, SCALE_NORMAL, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
render_common_status(x, y, SCALE_NORMAL);
char buf[280];
snprintf(buf, sizeof(buf),
"Noise Filter: %.2f | Max Range: %.1f nm | "
"Range Cursor: %.2f nm | Bearing Cursor: %.1f deg",
uniform_noise_filter, uniform_max_range,
uniform_range_cursor * uniform_max_range, uniform_bearing_cursor);
text_line_wrapped(buf, x, y, SCALE_NORMAL, max_w,
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
}