# Historical Radar Simulator Exhibit Project Profile **Author:** Mark Allyn **License:** MIT License --- ## 1. Project Overview & Environment This project is a museum exhibit designed to simulate vintage radar systems from the 1940s through the 1960s. It features real-time interactive simulations of the early World War II British **Chain Home Radar**, a shore-based **Marine Traffic Control Radar**, and a mobile **Shipborne/Patrol Boat Radar**. ### Hardware Specifications * **Target Machine:** Geekom A8 Max Mini PC * **Memory:** 32 GB RAM * **Processor:** AMD Ryzen 9 8945HS * **Integrated Graphics:** Radeon 780M Graphics GPU (Primary rendering target) ### Software & Compiling Stack * **Operating System:** Ubuntu 25.10 ("Questing") * **Programming Language:** C++20 * **Compiler:** g++ (Ubuntu 15.2.0-4ubuntu4) 15.2.0 * **Graphics Core:** OpenGL 4.3 Core Profile * **Window & Event Management:** GLFW * **OpenGL Function Pointer Loader:** GLAD * **Text Rendering Engine:** FreeType (libfreetype2) * **Geospatial & Spatial Ingestion:** GDAL (`libgdal-dev` for reading LIDAR/ENC chart assets) * **Database Infrastructure:** PostgreSQL * **Database Name:** `radar` * **Username:** `radar` * **Password:** `radar` * **Target Data Table:** `target_data` ### Compilation & Operating Rules * **Build Automation:** Modern CMake (`CMakeLists.txt`) * **Project Executable Target Name:** `radar_simulator` * **Execution Command:** Run from the compiled build folder using `./radar_simulator` or via the full path `{PROJECT_DIR}/build/radar_simulator`. * **Remote Workflow Environment:** Code is written and verified via SSH sessions from a Windows workstation. Compilation is allowed over the SSH connection. Execution and visual verification must be performed physically on the Geekom hardware. --- ## 2. Coding Guidelines & Architecture Standard * **Header Guard Protocol:** `#pragma once` is mandatory for all headers. * **Naming Conventions:** Use `snake_case` for variables, objects, and functions. Use `PascalCase` for classes and structs. * **Comments Pattern:** Single-line comments must utilize `//`. Multi-line blocks or logic structural boundaries must use standard `/* ... */` formatting. * **Type Assertions:** Avoid using the `auto` keyword to preserve strict type transparency across the pipeline. * **UI Mappings:** Divide the screen layout into distinct functional sub-windows using `glViewport`, `glScissor`, and `glEnable(GL_SCISSOR_TEST)`. * **Left Hand Panel (35% Width):** Displays textual descriptions of the selected scope, interactive control titles, and mapped key layouts. Regular text is rendered in white, control titles are rendered in red, and hotkeys are rendered in pink. * **Right Hand Panel (Main Area):** Houses the active circular or vector radar scope display over a pitch-black background. * **Right Hand Lower Panel (15% Height):** Serves as a diagnostic status bar displaying maximum range, active cursor metrics, and scope identification written in yellow text. --- ## 3. Target Ingestion Pipeline (`traffic_cop`) Targets enter the simulator from an external tracking network or an internal simulation table managed in PostgreSQL. All elements converge into a uniform memory footprint on a dedicated background execution thread called the `traffic_cop`. ### Data Models ```cpp enum class TargetType : int { VESSEL = 0, AIRCRAFT = 1 }; struct target_data_structure { double target_longitude; double target_latitude; std::string vessel_name; // Null if unavailable std::string registration; // Null if unavailable float length; // Meters float beam; // Meters int vessel_type; // AIS or aircraft code type uint32_t mmsi; // AIS unique ID / ICAO hex for planes float course; // Over-ground heading in degrees from True North float speed; // Over-ground speed in knots time_t timestamp; // Aging tracker for clearing dead nodes float altitude; // Meters (Fixed to 0 for surface targets) TargetType type; }; To optimize memory bandwidth on the Radeon 780M GPU, the traffic_cop packs raw telemetry down into a lightweight structure passed directly into a standard OpenGL Shader Storage Buffer Object (SSBO) with a layout(std430) alignment. C++struct target_data_to_shader_structure { float target_x; // Local Cartesian offset East (meters from radar origin) float target_y; // Local Cartesian offset North (meters from radar origin) float length; // Vessel length bounding box size (meters) float beam; // Vessel beam bounding box size (meters) float course; // Heading vector in radians from True North float altitude; // Target altitude profile (meters) long long timestamp;// Data alignment padding field }; Ingestion ProtocolThread Isolation: The traffic_cop handles parsing, scaling, and double-buffering completely isolated on its own thread to prevent blocking the frame rendering pipeline. Precision Correction: Coordinate projections (converting Lat/Long maps to local relative meters) are performed on the CPU using double-precision calculations before passing parameters down as 32-bit floating-point numbers to protect against rounding distortions. Boundary & Ceiling Filters: Targets residing beyond the selected scope's maximum range are immediately culled. Marine installations enforce an absolute <= 40-meter altitude cap; any aircraft target exceeding this height is dropped. Synchronized Dispatch: The traffic_cop writes updates into the graphics pipeline using a std::mutex critical section precisely when the PPI radar line swings past 0.0 radians (True North). 4. Operational Exhibit Scopes Users step through the available scope list in a continuous loop using the forward (1) and backward (2) keys. Scopes reset to their baseline parameters upon entry. Exhibit Introduction: Displays static informational text on the left panel. The right view panel remains blank until navigation keys are pressed. Chain Home A-Scope (1940s): Simulated 1D electrostatic vector trace. Features an inverted top-down baseline. All video signals deflect downward. Includes crystal-controlled 20-mile reference pips and a wide $150^\circ$ floodlight footprint. Target azimuth coordinates are evaluated manually via a radiogoniometer search coil nulling control. Marine A-Scope (1940s): Represents early surface-search systems like the British Type 271. Features a bottom-up baseline trace showing signal power versus range. The antenna is rotated manually using a servo motor console knob to sweep for target peak voltages. Marine Traffic Control PPI Scope (1960s): Stationary shore installation tracking Bellingham Bay. Features a high-power $25\text{ kW}$ transmitter, a highly directional 12-foot linear waveguide array ($0.7^\circ$ horizontal beamwidth), and a slow 15 RPM rotation rate. The screen top is locked to True North. Shipborne Marine PPI Scope (1960s): Simulates a moving patrol boat or Coast Guard cutter. Features a $15\text{ kW}$ tactical transmitter, a 4-to-6-foot antenna ($1.2^\circ$ horizontal beamwidth), and a fast 25 RPM sweep. The screen top is locked to the vessel's bow, and the graticule ring dynamically adjusts using gyrocompass inputs. 5. Shader Modularization Strategy The rendering engine decomposes processing tasks across distinct, optimized shader sets to isolate raw signal generation from persistent phosphor simulations. [Target SSBO Data] & [GDAL Elevation Maps] │ ▼ ┌──────────────────────────────────────────────────┐ │ PASS A: Signal Generation & Terrain Shader Pass │ │ (Computes Radar Equations, Blooming, & Clutter) │ └────────────────────────┬─────────────────────────┘ │ ▼ [High-Precision Float FBO Texture] ┌──────────────────────────────────────────────────┐ │ PASS B: P7 Phosphor Integration & Decay Shader │ │ (Applies Multistage Time-Based Exponential Fade) │ └────────────────────────┬─────────────────────────┘ │ ▼ [Ping-Pong Swap Texture] ┌──────────────────────────────────────────────────┐ │ PASS C: Orthographic 2D UI & Graticule Overlay │ │ (Applies Backlit Vector Rings and Text Overlays) │ └──────────────────────────────────────────────────┘ Pass A (Signal Generation & Terrain Pipeline): Evaluates the radar equation, target cross-sections, and landscape masks on a per-pixel basis. It writes output results directly into a high-precision floating-point Framebuffer Object (FBO) texture, preventing signal clipping and rounding artifacts. Pass B (P7 Phosphor Integration & Persistence Pass): Emulates vintage cathode-ray tube long-persistence phosphors. It blends the newly generated excitation texture from Pass A into a historical ping-pong frame buffer tracking multi-stage exponential decay. Pass C (Orthographic 2D UI & Graticule Overlay): Draws edge-lit vector rings, angular graduation marks, baseline coordinates, and text glyph textures directly onto screen coordinates.6. Settings file used for troubleshooting and tuning parameters without having to re-compile. settings.hC++/* * 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. * Author: Mark Allyn */ #pragma once #include /* ========================================================================= DEBUGGING AND SIMULATION SWITCHES ========================================================================= */ #define DEBUG_DISABLE_P7_PERSISTENCE 0 /* 1 = Bypass long persistence tracking for raw debugging */ #define DEBUG_DISABLE_TERRAIN 0 /* 1 = Hide landMASS rendering to isolate target processing */ #define GL_DEBUG_OUTPUT_ENABLED 1 /* 1 = Attach robust driver-level diagnostic callbacks */ /* ========================================================================= HISTORICAL COLOR PALETTES (HEX EMBEDDINGS) ========================================================================= */ /* A-Scope Graticule Backlights & Incandescent Pilot Bulbs */ #define HEX_COLOR_PILOT_LAMP 0xFFB347 /* #FFB347 - Warm Incandescent Glow */ /* A-Scope Mono-Phosphor Trace Colors */ #define HEX_COLOR_ASCOPE_EXCITATION 0x39FF14 /* #39FF14 - Vibrant Phosphor Flash */ #define HEX_COLOR_ASCOPE_AFTERGLOW 0x004400 /* #004400 - Darker Green Decay Base */ /* P7 Phosphor Color Pipeline States for PPI Radars */ #define HEX_COLOR_P7_EXCITATION 0xA0CFFF /* #A0CFFF - Bright Blue Active Beam Flash */ #define HEX_COLOR_P7_IMMEDIATE_BLUE 0x1010FF /* #1010FF - Post-Beam 1ms Decay Core */ #define HEX_COLOR_P7_SHORT_TERM_YG 0xE2FF80 /* #E2FF80 - Yellow Green 1s Persistent State */ #define HEX_COLOR_P7_LONG_TERM_AMBER 0xFFA040 /* #FFA040 - Amber 10s Deep Remembrance */ #define HEX_COLOR_P7_EXPIRATION 0x050700 /* #050700 - Near-Black Expiration Threshold */ /* GUI Core Color Specifications */ #define HEX_COLOR_TEXT_WHITE 0xFFFFFF #define HEX_COLOR_TEXT_RED 0xFF0000 #define HEX_COLOR_TEXT_PINK 0xFFC0CB #define HEX_COLOR_TEXT_YELLOW 0xFFFF00 /* ========================================================================= HARDWARE ENVIRONMENT MATRICES ========================================================================= */ namespace ChainHome { const float PEAK_POWER = 500000.0f; /* 500 KW */ const float WAVELENGTH = 12.0f; /* 12 Meters */ const float ANTENNA_GAIN = 3.16f; /* 5 dB expressed as linear gain */ const float PULSE_WIDTH = 0.000020f; /* 20 microseconds */ const float BEAM_WIDTH = 150.0f; /* floodlight sector in degrees */ const float PRF = 25.0f; /* Pulse Repetition Frequency (Hz) */ const float MAX_RANGE_METERS = 321868.0f; /* Fixed 200 Miles */ } namespace MarineAScope { const float PEAK_POWER = 500000.0f; /* 500 KW */ const float WAVELENGTH = 0.10f; /* 10 cm */ const float ANTENNA_GAIN = 1000.0f; /* 30 dB linear gain conversion */ const float PRF = 500.0f; /* 500 Hz pulse repetition */ const float HORIZONTAL_BEAMWIDTH = 2.5f; /* degrees */ const float SYSTEM_TEMPERATURE = 290.0f; /* Kelvins (Ts) */ const float NOISE_FIGURE = 20.0f; /* Early receiver noise figure in dB */ const float BOLTZMANN_CONSTANT = 1.38e-23f; /* (k) Joules/Kelvin */ } namespace MarineTrafficPPI { const float PEAK_POWER = 25000.0f; /* 25.0 KW */ const float FREQUENCY_HZ = 9.375e9f; /* 9.375 GHz */ const float WAVELENGTH = 0.032f; /* 0.032 meters (3.2 cm X-Band) */ const float NOISE_FIGURE = 11.0f; /* 11 dB */ const float SYSTEM_LOSSES = 8.0f; /* 8 dB */ const float HORIZONTAL_BEAMWIDTH = 0.7f; /* 0.7 degrees razor-sharp */ const float PULSE_WIDTH = 0.1e-6f; /* 0.1 microseconds */ const float ANTENNA_RPM = 15.0f; /* 15 RPM mechanical rotation rate */ const float ANTENNA_GAIN = 2511.89f; /* 34 dB linear converter multiplier */ } namespace MarineBoatPPI { const float PEAK_POWER = 15000.0f; /* 15.0 KW tactical marine */ const float FREQUENCY_HZ = 9.375e9f; /* 9.375 GHz */ const float WAVELENGTH = 0.032f; /* 0.032 meters (3.2 cm X-Band) */ const float NOISE_FIGURE = 11.0f; /* 11 dB */ const float SYSTEM_LOSSES = 8.0f; /* 8 dB */ const float HORIZONTAL_BEAMWIDTH = 1.2f; /* 1.2 degrees */ const float PULSE_WIDTH = 0.1e-6f; /* 0.1 microseconds */ const float ANTENNA_RPM = 25.0f; /* 25 RPM high-frequency sweep */ const float ANTENNA_GAIN = 1258.93f; /* 31 dB linear converter multiplier */ } main.cppC++/* * 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. * Author: Mark Allyn */ #include #include #include #include #include #include #include #include #include #include #include #include "settings.h" enum class TargetType : int { VESSEL = 0, AIRCRAFT = 1 }; enum class ScopeType { EXHIBIT_INTRO = 0, CHAIN_HOME_A_SCOPE = 1, MARINE_A_SCOPE = 2, MARINE_TRAFFIC_PPI = 3, MARINE_BOAT_PPI = 4 }; struct target_data_to_shader_structure { float target_x; float target_y; float length; float beam; float course; float altitude; long long timestamp; }; struct SharedTrafficBuffer { std::mutex data_mutex; std::vector active_targets; std::atomic current_sweep_radians{0.0f}; }; struct ScopeState { float intensity = 0.8f; float receiver_sensitivity = 0.5f; float stc_sensitivity = 0.3f; float stc_range = 0.4f; float noise_filter_rain = 0.2f; float radiogoniometer_deg = 0.0f; float max_range_setting = 12.0f; float range_cursor = 2.0f; float bearing_cursor_deg = 0.0f; float antenna_bearing_deg = 0.0f; }; struct SystemController { ScopeType current_scope = ScopeType::EXHIBIT_INTRO; std::vector available_scopes; bool navigation_enabled = true; size_t active_scope_index = 0; ScopeState states[5]; SharedTrafficBuffer thread_bridge; std::atomic run_thread_execution{true}; /* Persistent Phosphor Accumulation FBO Handlers */ GLuint phosphor_fbo[2] = {0, 0}; GLuint phosphor_texture[2] = {0, 0}; int current_fbo_index = 0; int fbo_width = 1024; int fbo_height = 1024; }; static SystemController g_SystemContext; void APIENTRY glDebugOutputCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { (void)source; (void)id; (void)length; (void)userParam; if (type == GL_DEBUG_TYPE_ERROR) { std::cerr << "[GPU RUNTIME CRITICAL ERROR]: " << message << std::endl; } } void traffic_cop_thread_loop() { while (g_SystemContext.run_thread_execution.load()) { std::this_thread::sleep_for(std::chrono::milliseconds(16)); float current_sweep = g_SystemContext.thread_bridge.current_sweep_radians.load(); /* Sync Batch Dispatch Trigger precisely when crossing True North (0.0 rad) */ if (std::abs(current_sweep) <= 0.05f) { std::lock_guard lock(g_SystemContext.thread_bridge.data_mutex); g_SystemContext.thread_bridge.active_targets.clear(); target_data_to_shader_structure mock_freighter; mock_freighter.target_x = 2200.0f; mock_freighter.target_y = 4100.0f; mock_freighter.length = 210.0f; mock_freighter.beam = 32.0f; mock_freighter.course = 0.785f; mock_freighter.altitude = 0.0f; mock_freighter.timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); /* Altitude Ceiling Filter Validation Rules */ if (mock_freighter.altitude <= 40.0f) { g_SystemContext.thread_bridge.active_targets.push_back(mock_freighter); } } } } void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { (void)window; (void)scancode; (void)mods; if (action != GLFW_PRESS && action != GLFW_REPEAT) return; size_t active_idx = static_cast(g_SystemContext.current_scope); ScopeState& state = g_SystemContext.states[active_idx]; switch (key) { case GLFW_KEY_1: if (g_SystemContext.navigation_enabled && !g_SystemContext.available_scopes.empty()) { g_SystemContext.active_scope_index = (g_SystemContext.active_scope_index + 1) % g_SystemContext.available_scopes.size(); g_SystemContext.current_scope = g_SystemContext.available_scopes[g_SystemContext.active_scope_index]; } break; case GLFW_KEY_2: if (g_SystemContext.navigation_enabled && !g_SystemContext.available_scopes.empty()) { if (g_SystemContext.active_scope_index == 0) g_SystemContext.active_scope_index = g_SystemContext.available_scopes.size() - 1; else g_SystemContext.active_scope_index--; g_SystemContext.current_scope = g_SystemContext.available_scopes[g_SystemContext.active_scope_index]; } break; case GLFW_KEY_3: state.intensity = std::max(0.0f, state.intensity - 0.05f); break; case GLFW_KEY_4: state.intensity = std::min(1.0f, state.intensity + 0.05f); break; case GLFW_KEY_5: state.receiver_sensitivity = std::max(0.0f, state.receiver_sensitivity - 0.05f); break; case GLFW_KEY_6: state.receiver_sensitivity = std::min(1.0f, state.receiver_sensitivity + 0.05f); break; case GLFW_KEY_Q: state.stc_sensitivity = std::max(0.0f, state.stc_sensitivity - 0.05f); break; case GLFW_KEY_W: state.stc_sensitivity = std::min(1.0f, state.stc_sensitivity + 0.05f); break; case GLFW_KEY_E: state.stc_range = std::max(0.0f, state.stc_range - 0.05f); break; case GLFW_KEY_R: state.stc_range = std::min(1.0f, state.stc_range + 0.05f); break; /* Master Sync for Dual Map Controls */ case GLFW_KEY_T: case GLFW_KEY_J: state.noise_filter_rain = std::max(0.0f, state.noise_filter_rain - 0.05f); break; case GLFW_KEY_Y: case GLFW_KEY_K: state.noise_filter_rain = std::min(1.0f, state.noise_filter_rain + 0.05f); break; case GLFW_KEY_U: state.radiogoniometer_deg = std::fmod(state.radiogoniometer_deg - 2.0f + 360.0f, 360.0f); break; case GLFW_KEY_I: state.radiogoniometer_deg = std::fmod(state.radiogoniometer_deg + 2.0f, 360.0f); break; case GLFW_KEY_O: state.max_range_setting = std::max(1.5f, state.max_range_setting - 1.5f); break; case GLFW_KEY_P: state.max_range_setting = std::min(48.0f, state.max_range_setting + 1.5f); break; case GLFW_KEY_A: state.range_cursor = std::max(0.0f, state.range_cursor - 0.1f); break; case GLFW_KEY_S: state.range_cursor = std::min(state.max_range_setting, state.range_cursor + 0.1f); break; case GLFW_KEY_D: state.bearing_cursor_deg = std::fmod(state.bearing_cursor_deg - 1.0f + 360.0f, 360.0f); break; case GLFW_KEY_F: state.bearing_cursor_deg = std::fmod(state.bearing_cursor_deg + 1.0f, 360.0f); break; case GLFW_KEY_G: state.antenna_bearing_deg = std::fmod(state.antenna_bearing_deg - 1.0f + 360.0f, 360.0f); break; case GLFW_KEY_H: state.antenna_bearing_deg = std::fmod(state.antenna_bearing_deg + 1.0f, 360.0f); break; case GLFW_KEY_ESCAPE: glfwSetWindowShouldClose(window, GLFW_TRUE); break; } } void allocate_phosphor_storage_buffers() { glGenFramebuffers(2, g_SystemContext.phosphor_fbo); glGenTextures(2, g_SystemContext.phosphor_texture); for (int i = 0; i < 2; ++i) { glBindFramebuffer(GL_FRAMEBUFFER, g_SystemContext.phosphor_fbo[i]); glBindTexture(GL_TEXTURE_2D, g_SystemContext.phosphor_texture[i]); /* High Precision Floating Point Textures required to eliminate quantization errors during continuous integration fade steps */ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, g_SystemContext.fbo_width, g_SystemContext.fbo_height, 0, GL_RGBA, GL_FLOAT, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, g_SystemContext.phosphor_texture[i], 0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { std::cerr << "[FBO CORE ERROR]: High precision accumulation texture link failure." << std::endl; } glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); } glBindFramebuffer(GL_FRAMEBUFFER, 0); } int main(int argc, char* argv[]) { std::vector args(argv + 1, argv + argc); if (args.empty()) { g_SystemContext.available_scopes = { ScopeType::EXHIBIT_INTRO, ScopeType::CHAIN_HOME_A_SCOPE, ScopeType::MARINE_A_SCOPE, ScopeType::MARINE_TRAFFIC_PPI, ScopeType::MARINE_BOAT_PPI }; } else if (args.size() == 1) { g_SystemContext.navigation_enabled = false; std::string name = args[0]; if (name == "chain_home") g_SystemContext.available_scopes.push_back(ScopeType::CHAIN_HOME_A_SCOPE); else if (name == "marine_ascope") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_A_SCOPE); else if (name == "marine_traffic_ppi") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_TRAFFIC_PPI); else if (name == "marine_boat_ppi") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_BOAT_PPI); } else { g_SystemContext.available_scopes.push_back(ScopeType::EXHIBIT_INTRO); for (const auto& name : args) { if (name == "chain_home") g_SystemContext.available_scopes.push_back(ScopeType::CHAIN_HOME_A_SCOPE); else if (name == "marine_ascope") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_A_SCOPE); else if (name == "marine_traffic_ppi") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_TRAFFIC_PPI); else if (name == "marine_boat_ppi") g_SystemContext.available_scopes.push_back(ScopeType::MARINE_BOAT_PPI); } } g_SystemContext.current_scope = g_SystemContext.available_scopes[0]; std::thread traffic_cop_thread(traffic_cop_thread_loop); if (!glfwInit()) return -1; glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); GLFWwindow* window = glfwCreateWindow(1920, 1080, "Historical Radar Simulator Exhibit", nullptr, nullptr); if (!window) { glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetKeyCallback(window, key_callback); gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); #if GL_DEBUG_OUTPUT_ENABLED glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(glDebugOutputCallback, nullptr); #endif allocate_phosphor_storage_buffers(); while (!glfwWindowShouldClose(window)) { int width, height; glfwGetFramebufferSize(window, &width, &height); /* Base System Reset */ glScissor(0, 0, width, height); glDisable(GL_SCISSOR_TEST); glClearColor(0.01f, 0.01f, 0.01f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); /* VIEWPORT 1: Left Control Layout Panel */ int left_w = static_cast(width * 0.35f); glViewport(0, 0, left_w, height); glScissor(0, 0, left_w, height); glClearColor(0.04f, 0.04f, 0.04f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); /* Execute FreeType UI text pass here */ /* VIEWPORT 2: Main Operational Scope Unit */ int scope_x = left_w; int scope_w = width - left_w; int status_h = static_cast(height * 0.15f); int scope_h = height - status_h; /* P7 Ping-Pong Interception Loop logic for active PPI installations */ if (g_SystemContext.current_scope == ScopeType::MARINE_TRAFFIC_PPI || g_SystemContext.current_scope == ScopeType::MARINE_BOAT_PPI) { int read_idx = g_SystemContext.current_fbo_index; int write_idx = 1 - read_idx; /* Bind write target */ glBindFramebuffer(GL_FRAMEBUFFER, g_SystemContext.phosphor_fbo[write_idx]); glViewport(0, 0, g_SystemContext.fbo_width, g_SystemContext.fbo_height); glScissor(0, 0, g_SystemContext.fbo_width, g_SystemContext.fbo_height); /* Pass B: Draw raw physical excitation sweep signals directly into the persistent layer */ /* Pass C: Bind texture[read_idx], apply mathematical exponential decay delta inside the blend shader */ glBindFramebuffer(GL_FRAMEBUFFER, 0); g_SystemContext.current_fbo_index = write_idx; // Cycle the buffers /* Draw completed phosphor frame onto presentation screen coordinates */ glViewport(scope_x, status_h, scope_w, scope_h); glScissor(scope_x, status_h, scope_w, scope_h); /* Draw raw texture bounds across simple screen-space quad geometry */ } else { /* Standard A-Scope direct rendering pass track bypassing the texture matrix entirely */ glViewport(scope_x, status_h, scope_w, scope_h); glScissor(scope_x, status_h, scope_w, scope_h); /* Direct A-Scope geometric stream lines rendering code execution loop */ } /* VIEWPORT 3: Yellow Diagnostic Status Bar */ glViewport(scope_x, 0, scope_w, status_h); glScissor(scope_x, 0, scope_w, status_h); glClearColor(0.02f, 0.02f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); /* Diagnostic status outputs rendering step */ /* Advance mechanical radar line variables smoothly */ float step = 0.012f; if (g_SystemContext.current_scope == ScopeType::MARINE_BOAT_PPI) step = 0.022f; // Fast boat scan float updated = g_SystemContext.thread_bridge.current_sweep_radians.load() + step; if (updated >= 6.2831853f) updated = 0.0f; g_SystemContext.thread_bridge.current_sweep_radians.store(updated); glfwSwapBuffers(window); glfwPollEvents(); } g_SystemContext.run_thread_execution.store(false); traffic_cop_thread.join(); glfwTerminate(); return 0; } 7. Mathematical & Logical Shader Set Specifications (GLSL Target Mapping) Set 1 & 2: Text Rendering System (FreeType Font Pipeline) OpenGL Shading Language // VERTEX SHADER #version 430 core layout(location = 0) in vec4 vertex; // [pos.x, pos.y, tex.s, tex.t] out vec2 v_TexCoords; uniform mat4 u_OrthographicProjection; void main() { v_TexCoords = vertex.zw; gl_Position = u_OrthographicProjection * vec4(vertex.xy, 0.0, 1.0); } // FRAGMENT SHADER #version 430 core in vec2 v_TexCoords; out vec4 frag_color; uniform sampler2D u_TextGlyphTexture; uniform vec3 u_TextRGBUniform; // Passed from color macros (#FFFFFF, #FF0000, etc.) void main() { // Extract mono-channel mask information from the FreeType slot float glyph_alpha = texture(u_TextGlyphTexture, v_TexCoords).r; if (glyph_alpha < 0.05) discard; frag_color = vec4(u_TextRGBUniform, glyph_alpha); } Set 3: Chain Home Radar A-Scope Targets (Inverted Vector Generator) OpenGL Shading Language // VERTEX SHADER #version 430 core /* Generates a continuous line-strip representing physical voltage deflection */ layout(location = 0) in float in_range_miles; out float v_signal_amplitude; // Sync block data passback layout(std430, binding = 0) buffer TargetBuffer { struct TargetData { float target_x; float target_y; float length; float beam; float course; float altitude; long long ts; } targets[]; }; uniform int u_target_count; uniform float u_sensitivity; uniform float u_radiogoniometer_rad; // Changed via U/I keys const float ch_wavelength = 12.0; // Pseudo-random high-frequency noise loop for 25 MHz atmospheric grass simulation float generate_atmospheric_jitter(float seed) { return fract(sin(seed * 12.9898) * 43758.5453) * 0.06; } void main() { float total_echoes = 0.0; float range_meters = in_range_miles * 1609.34; for(int i = 0; i < u_target_count; ++i) { float t_range = sqrt(targets[i].target_x * targets[i].target_x + targets[i].target_y * targets[i].target_y); // 1. Evaluate range coincidence window if (abs(range_meters - t_range) < 1500.0) { float t_bearing = atan(targets[i].target_x, targets[i].target_y); float aspect_angle = t_bearing - targets[i].course; float proj_width = abs(sin(aspect_angle)) * targets[i].length + abs(cos(aspect_angle)) * targets[i].beam; float sigma = proj_width * 2.5; // 2. Metric Resonance Multiplier Rule if (targets[i].length >= (ch_wavelength * 0.4) && targets[i].length <= (ch_wavelength * 0.6)) { sigma *= 1.5; } // 3. Radiogoniometer Search Coil Nulling calculation // Null point occurs when search coil is perpendicular to signal wavefront float angular_null_factor = abs(cos(t_bearing - u_radiogoniometer_rad)); // 4. Calculate Radar Equation Received Power Pr float P_r = (500000.0 * pow(3.16, 2.0) * pow(ch_wavelength, 2.0) * sigma * angular_null_factor) / (pow(4.0 * 3.14159, 3.0) * pow(t_range, 4.0)); total_echoes += P_r * 2e11; // Conversion factor into vertical deflection voltage } } // 5. Injected Crystal Calibration Pips (Every 20 Miles) float calibration_pip = 0.0; if (mod(in_range_miles + 0.1, 20.0) < 0.3) { calibration_pip = 0.25; // Fixed invariant height spike } // 6. Summing Amplifier Protocol Mapping Downward float noise_grass = generate_atmospheric_jitter(in_range_miles); float receiver_signal = (noise_grass + total_echoes) * u_sensitivity; // Total voltage pushes the trace down from the upper ceiling grid (y = 0.9) float final_y = 0.9 - (receiver_signal + calibration_pip); // Normalized device coordinates map range miles horizontally from left (-0.9) to right (0.9) float final_x = -0.9 + (in_range_miles / 200.0) * 1.8; gl_Position = vec4(final_x, final_y, 0.0, 1.0); v_signal_amplitude = receiver_signal + calibration_pip; } // FRAGMENT SHADER #version 430 core in float v_signal_amplitude; out vec4 frag_color; void main() { // Green mono-phosphor mixing matrix base vec3 excitation_color = vec3(0.22, 1.0, 0.08); // #39FF14 vec3 decay_base = vec3(0.0, 0.26, 0.0); // #004400 vec3 mixed_color = mix(decay_base, excitation_color, clamp(v_signal_amplitude * 4.0, 0.0, 1.0)); frag_color = vec4(mixed_color, 0.9); } Set 4: Marine Radar A-Scope Targets (Right-Side Up Centimetric Pipeline) OpenGL Shading Language // VERTEX SHADER #version 430 core layout(location = 0) in float in_range_miles; out float v_intensity; layout(std430, binding = 0) buffer TargetBuffer { struct TargetData { float target_x; float target_y; float length; float beam; float course; float altitude; long long ts; } targets[]; }; uniform int u_target_count; uniform float u_sensitivity; uniform float u_antenna_bearing_deg; // Rotated via G/H keys uniform float u_max_range_miles; const float peak_power = 500000.0; const float wavelength = 0.10; const float antenna_gain = 1000.0; const float horizontal_beamwidth = 2.5; float generate_thermal_noise(float r) { // Standard high-frequency receiver thermal grass calculations return fract(cos(r * 45.1232) * 91234.56) * 0.04; } void main() { float total_echoes = 0.0; float range_meters = in_range_miles * 1852.0; // Nautical Mile scale conversion for (int i = 0; i < u_target_count; ++i) { float t_range = sqrt(targets[i].target_x * targets[i].target_x + targets[i].target_y * targets[i].target_y); // Evaluate radar return pulse shape width envelopes if (abs(range_meters - t_range) < 60.0) { float t_bearing_rad = atan(targets[i].target_x, targets[i].target_y); float t_bearing_deg = degrees(t_bearing_rad); if (t_bearing_deg < 0.0) t_bearing_deg += 360.0; float angle_diff = abs(t_bearing_deg - u_antenna_bearing_deg); if (angle_diff > 180.0) angle_diff = 360.0 - angle_diff; // Direct servo directional beam attenuation float beam_factor = exp(-2.77 * pow(angle_diff / (horizontal_beamwidth / 2.0), 2.0)); float aspect = t_bearing_rad - targets[i].course; float rcs = (abs(sin(aspect)) * targets[i].length + abs(cos(aspect)) * targets[i].beam) * 2.5; // Standard Marine Radar Equation float Pr = (peak_power * pow(antenna_gain, 2.0) * pow(wavelength, 2.0) * rcs * beam_factor) / (pow(4.0 * 3.14159, 3.0) * pow(t_range, 4.0)); // Pulse rise/fall envelope configuration shapes the curve float offset = abs(range_meters - t_range) / 60.0; float pulse_envelope = cos(offset * 1.5707); // Smooth bell output curve instead of a raw vertical line total_echoes += Pr * pulse_envelope * 1e8; } } // Dynamic calibration ticks based on range modes float calibration_pip = 0.0; float interval = (u_max_range_miles > 6.0) ? 2.0 : (u_max_range_miles > 2.0) ? 1.0 : 0.25; if (mod(in_range_miles, interval) < 0.04) { calibration_pip = 0.15; } // Right-Side Up Addition Logic: Deflects upwards from bottom baseline floor (y = -0.8) float grass = generate_thermal_noise(in_range_miles); float receiver_signal = (grass + total_echoes) * u_sensitivity; float final_y = -0.8 + (receiver_signal + calibration_pip); float final_x = -0.9 + (in_range_miles / u_max_range_miles) * 1.8; gl_Position = vec4(final_x, final_y, 0.0, 1.0); v_intensity = receiver_signal + calibration_pip; } // FRAGMENT SHADER #version 430 core in float v_intensity; out vec4 frag_color; void main() { frag_color = vec4(vec3(0.22, 1.0, 0.08) * (v_intensity * 5.0), 1.0); } Set 5 & 6: PPI Processing Radar Return (Render-to-Texture Signal Generation Pass) OpenGL Shading Language // VERTEX SHADER #version 430 core layout(location = 0) in vec2 in_position; // Coordinate space spanning [-1, 1] texture boundaries out vec2 v_tex_coord; void main() { v_tex_coord = in_position * 0.5 + 0.5; gl_Position = vec4(in_position, 0.0, 1.0); } // FRAGMENT SHADER #version 430 core in vec2 v_tex_coord; out vec4 frag_excitation_energy; layout(std430, binding = 0) buffer TargetBuffer { struct TargetData { float target_x; float target_y; float length; float beam; float course; float altitude; long long ts; } targets[]; }; uniform int u_target_count; uniform float u_gain; // 0.0 to 1.0 manual control knob uniform float u_noise_filter_rain; // Linked to J/K or T/Y properties uniform float u_active_sweep_rad; // Dynamic angular vector uniform float u_max_range_meters; // Dynamic conversion threshold mapping scales uniform int u_is_boat_radar; // 1 = Active configuration metrics for mobile patrol boat // Constants injected dynamically derived from the structural lookup namespaces uniform float Pt; uniform float G_squared; uniform float lambda_squared; uniform float L; uniform float baseline_beamwidth; uniform float baseline_pulse_width; float generate_sea_clutter(vec2 uv, float sweep_angle) { // Computes heavy concentric sea clutter returns fading out at 1/R^3 near origin float dist = length(uv - vec2(0.5)); float clutter_noise = fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453); return (clutter_noise * exp(-dist * 8.0) * 0.3) * (1.0 - u_noise_filter_rain); } void main() { vec2 polar_offset = v_tex_coord - vec2(0.5); float frag_range = length(polar_offset) * u_max_range_meters; float frag_azimuth = atan(polar_offset.x, polar_offset.y); // Derived clockwise relative to North if (frag_azimuth < 0.0) frag_azimuth += 2.0 * 3.14159265; // 1. Evaluate sweep intersection envelope float angular_distance = abs(frag_azimuth - u_active_sweep_rad); if (angular_distance > 3.14159265) angular_distance = (2.0 * 3.14159265) - angular_distance; if (angular_distance > (baseline_beamwidth * 3.0 * 3.14159 / 180.0)) { discard; // Optimize processing load pipeline safely } float total_video_voltage = 0.0; for (int i = 0; i < u_target_count; ++i) { float t_range = sqrt(targets[i].target_x * targets[i].target_x + targets[i].target_y * targets[i].target_y); float t_azimuth = atan(targets[i].target_x, targets[i].target_y); if (t_azimuth < 0.0) t_azimuth += 2.0 * 3.14159265; // Calculate aspect area cross-section curve profiles float aspect = t_azimuth - targets[i].course; float projected_width = (abs(sin(aspect)) * targets[i].length) + (abs(cos(aspect)) * targets[i].beam); float sigma = projected_width * 2.5; // 2. Process Raw Radar Equation float R4 = pow(t_range, 4.0); if (R4 < 1.0) R4 = 1.0; float P_r = (Pt * G_squared * lambda_squared * sigma) / (pow(4.0 * 3.14159, 3.0) * R4 * L); // 3. Logarithmic Compression Block float V_echo = log(1.0 + P_r * 8000.0); float V_total = u_gain * V_echo; // 4. Mathematical Target Saturation Blooming Formulas float saturation = max(0.0, V_total - 1.0); float dynamic_range_width = baseline_pulse_width + (0.06 * saturation * u_max_range_meters); float dynamic_azimuth_width = (baseline_beamwidth * 3.14159 / 180.0) + (0.04 * pow(saturation, 2.0)); float d_range = abs(frag_range - t_range); float d_azimuth = abs(frag_azimuth - t_azimuth); if (d_azimuth > 3.14159) d_azimuth = (2.0 * 3.14159) - d_azimuth; // 5. Evaluate spatial envelope maps float radial_paint = exp(-pow(d_range / dynamic_range_width, 2.0)); float azimuth_paint = exp(-pow(d_azimuth / dynamic_azimuth_width, 2.0)); // Accumulate active arc signatures total_video_voltage += V_total * radial_paint * azimuth_paint; } // Mix raw environment components into receiver stack float clutter = generate_sea_clutter(v_tex_coord, u_active_sweep_rad); float final_excitation = (total_video_voltage + clutter) * exp(-pow(angular_distance / (baseline_beamwidth * 3.14159 / 180.0), 2.0)); frag_excitation_energy = vec4(vec3(final_excitation), 1.0); } Set 7: Range Rings for PPI Radars (Concentric Vector Circles) OpenGL Shading Language // FRAGMENT SHADER #version 430 core in vec2 v_tex_coord; // Interpolated coordinates out vec4 frag_color; uniform float u_max_range_miles; void main() { float dist = length(v_tex_coord - vec2(0.5)); // Normalized range out from center axis float range_miles = dist * u_max_range_miles; // Define interval patterns float interval = (u_max_range_miles > 10.0) ? 4.0 : (u_max_range_miles > 4.0) ? 2.0 : 0.5; float modulo_ring = mod(range_miles, interval); // High-precision ring edge generation using pixel derivatives float ring_width = 0.015 * u_max_range_miles; if (modulo_ring < ring_width && dist < 0.48) { // Pilot bulb amber glow backlight (#FFB347) frag_color = vec4(1.0, 0.701, 0.278, 0.4); } else { discard; } } Set 8: P7 Phosphor Persistence Accumulation Shader (The Decay Engine) OpenGL Shading Language // FRAGMENT SHADER #version 430 core /* Processed over a screen-space quad reading the frame history loop */ in vec2 v_tex_coord; out vec4 frag_accumulated_out; uniform sampler2D u_tex_current_excitation; // Pass B layout output texture uniform sampler2D u_tex_history_persistent; // Texture from read_idx index tracking uniform float u_time_delta_seconds; // Time scale step track uniform int u_disable_persistence_switch; // Hooked into DEBUG_DISABLE_P7_PERSISTENCE macro state void main() { vec4 current_flash = texture(u_tex_current_excitation, v_tex_coord); if (u_disable_persistence_switch == 1) { // Render raw instantaneous flash states cleanly bypassing afterglow tracking if(current_flash.r > 0.02) { frag_accumulated_out = vec4(0.627, 0.811, 1.0, 1.0); // Bright Blue excitation (#A0CFFF) } else { frag_accumulated_out = vec4(0.0, 0.0, 0.0, 1.0); } return; } vec4 history = texture(u_tex_history_persistent, v_tex_coord); // 1. Ingest new signal values inside current execution pass frames float normalized_energy = max(current_flash.r, history.r); float alpha_age = history.a; if (current_flash.r > 0.05) { alpha_age = 0.0; // Reset lifetime clock for newly excitation coordinate vectors } else { alpha_age += u_time_delta_seconds; // Chrono aging tracking step } // 2. Map color states across normalized time deltas vec3 physical_p7_rgb = vec3(0.0); if (alpha_age <= 0.001) { physical_p7_rgb = vec3(0.627, 0.811, 1.0); // State 1: #A0CFFF (Excitation Blue) } else if (alpha_age <= 0.01) { physical_p7_rgb = vec3(0.062, 0.062, 1.0); // State 2: #1010FF (Immediate Blue core) } else if (alpha_age <= 1.0) { // Interpolate gracefully down into yellow green afterglow states float factor = (alpha_age - 0.01) / 0.99; physical_p7_rgb = mix(vec3(0.062, 0.062, 1.0), vec3(0.886, 1.0, 0.501), factor); // #E2FF80 } else if (alpha_age <= 11.0) { // Deep integration curve decay down into warm amber storage float factor = (alpha_age - 1.0) / 10.0; physical_p7_rgb = mix(vec3(0.886, 1.0, 0.501), vec3(1.0, 0.627, 0.25), factor); // #FFA040 } else { // Expiration threshold boundary mapping float factor = clamp((alpha_age - 11.0) / 2.0, 0.0, 1.0); physical_p7_rgb = mix(vec3(1.0, 0.627, 0.25), vec3(0.02, 0.027, 0.0), factor); // #050700 if (alpha_age > 13.0) normalized_energy *= 0.92; // Systematically clear the tail from storage FBO } frag_accumulated_out = vec4(physical_p7_rgb, normalized_energy); } Set 9: Land and Terrain Radar Return (GDAL Matrix Ingestion) OpenGL Shading Language // FRAGMENT SHADER #version 430 core in vec2 v_tex_coord; out vec4 frag_terrain_power; uniform sampler2D u_gdal_elevation_map; // Packed height profile map parsed by CPU thread uniform float u_active_sweep_rad; uniform float u_max_range_meters; uniform int u_disable_terrain_switch; // Hooked into DEBUG_DISABLE_TERRAIN macro state void main() { if (u_disable_terrain_switch == 1) { discard; } vec2 polar_offset = v_tex_coord - vec2(0.5); float frag_azimuth = atan(polar_offset.x, polar_offset.y); if (frag_azimuth < 0.0) frag_azimuth += 2.0 * 3.14159265; // Direct aspect alignment check if (abs(frag_azimuth - u_active_sweep_rad) > 0.02) { discard; // Terrain coordinates are only rendered when intersected by the active beam sweep } // Extract elevation maps metrics natively from GDAL texture layers float elevation = texture(u_gdal_elevation_map, v_tex_coord).r; if (elevation > 0.0) { // Valid terrain coordinate target detected // Land terrain structures return immense, saturating echo signatures frag_terrain_power = vec4(vec3(1.8), 1.0); } else { discard; } } Set 10 & 11: Calibration Graticule & Base Lines (Incandescent Mechanical Faceplate Layer) OpenGL Shading Language // VERTEX SHADER #version 430 core layout(location = 0) in vec2 in_vertex_pos; uniform mat4 u_MVP_Matrix; uniform float u_boat_heading_deg; // Rotates the graticule ring for Boat Radar mode uniform int u_is_boat_mode; void main() { vec2 rotated_pos = in_vertex_pos; if (u_is_boat_mode == 1) { float rad = radians(u_boat_heading_deg); float cos_b = cos(rad); float sin_b = sin(rad); rotated_pos.x = in_vertex_pos.x * cos_b - in_vertex_pos.y * sin_b; rotated_pos.y = in_vertex_pos.x * sin_b + in_vertex_pos.y * cos_b; } gl_Position = u_MVP_Matrix * vec4(rotated_pos, 0.0, 1.0); } // FRAGMENT SHADER #version 430 core out vec4 frag_graticule_color; uniform vec3 u_incandescent_lamp_rgb; // Initialized to #FFB347 macro state value void main() { // Renders the fine ticks, text rings, and A-scope baselines with a warm, steady edge-lit glow frag_graticule_color = vec4(u_incandescent_lamp_rgb, 0.75); }