/* * 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 #include /* ------------------------------------------------------------------ */ /* Global instances */ /* ------------------------------------------------------------------ */ ChainHomeRadar g_chain_home; MarineAScopeRadar g_marine_ascope; MarineTrafficRadar g_marine_traffic; PoliceboatRadar g_police_boat; /* ------------------------------------------------------------------ */ /* Colour constants (RGB, 0.0–1.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(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(SCOPE_SQUARE); const float scope_h = static_cast(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(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(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(STATUS_BAR_HEIGHT) - MARGIN - LH; float max_w = static_cast(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(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(STATUS_BAR_HEIGHT) - MARGIN - LH; float max_w = static_cast(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(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(STATUS_BAR_HEIGHT) - MARGIN - LH; float max_w = static_cast(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(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(STATUS_BAR_HEIGHT) - MARGIN - LH; float max_w = static_cast(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]); }