Compare commits
4 Commits
5355a9eded
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2017732c1c | |||
| 200778af26 | |||
| 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
|
||||||
@@ -165,3 +167,41 @@ struct target_data_to_thread_1_structure {
|
|||||||
uint32_t mmsi; // AIS unique identifier; ICAO hex address for aircraft
|
uint32_t mmsi; // AIS unique identifier; ICAO hex address for aircraft
|
||||||
};
|
};
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
CURRENT IMPLEMENTATION STATUS (stub phase)
|
||||||
|
|
||||||
|
Files: include/01_classes.h, src/01_classes.cpp
|
||||||
|
|
||||||
|
What is built:
|
||||||
|
|
||||||
|
RadarBase — abstract base class with three pure virtual methods:
|
||||||
|
render_left_panel() — renders radar description and keyboard control list
|
||||||
|
render_scope() — renders scope content (stub: radar name + "stub" label)
|
||||||
|
render_status_bar() — renders current control uniform values
|
||||||
|
|
||||||
|
Four stub subclasses, each overriding all three methods:
|
||||||
|
ChainHomeRadar (Ventnor, Isle of Wight)
|
||||||
|
MarineAScopeRadar (Community Boating Center, Bellingham Bay)
|
||||||
|
MarineTrafficRadar (center of Bellingham Bay)
|
||||||
|
PoliceboatRadar (Taylor Dock / Boulevard Park, Bellingham Bay)
|
||||||
|
|
||||||
|
One global instance of each subclass is declared in 01_classes.h and defined
|
||||||
|
in 01_classes.cpp: g_chain_home, g_marine_ascope, g_marine_traffic, g_police_boat.
|
||||||
|
|
||||||
|
What is NOT yet built (future components):
|
||||||
|
- Real scope rendering (radar sweep, target blips, graticule, range rings/ticks,
|
||||||
|
north indicator, P7 persistence layer, land/terrain background)
|
||||||
|
- Target data ingestion from Traffic Cop
|
||||||
|
- The Keyboard controls class (keyboard handling currently lives in the
|
||||||
|
static key_callback() function in src/main.cpp)
|
||||||
|
- The Traffic Cop class (Thread 2) — not yet started
|
||||||
|
- The Simulator class (Thread 3) — not yet started
|
||||||
|
- Per-frame update() method — the 30 Hz render loop in main.cpp calls
|
||||||
|
render_left_panel(), render_scope(), and render_status_bar() directly
|
||||||
|
|
||||||
|
Both target data structures (target_data_structure and
|
||||||
|
target_data_to_thread_1_structure) are defined in
|
||||||
|
include/data_structure_and_constants.h and will be used when the
|
||||||
|
Traffic Cop and Simulator components are built.
|
||||||
|
|
||||||
|
|||||||
@@ -69,3 +69,57 @@ field:
|
|||||||
instructs the user to use the 1 and 2 keyboard keys to select the radar. The 1 key
|
instructs the user to use the 1 and 2 keyboard keys to select the radar. The 1 key
|
||||||
steps forward in a circle and the 2 key steps backward and the enter key makes the
|
steps forward in a circle and the 2 key steps backward and the enter key makes the
|
||||||
selection
|
selection
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
CURRENT IMPLEMENTATION STATUS
|
||||||
|
|
||||||
|
This spec covers two related but distinct pieces of the 02_text_description component:
|
||||||
|
|
||||||
|
A. Text renderer engine
|
||||||
|
Files: include/02_text_description.h, src/02_text_description.cpp
|
||||||
|
Status: COMPLETE
|
||||||
|
|
||||||
|
Implements the TextRenderer class using FreeType. Loads printable ASCII glyphs
|
||||||
|
(codes 32-126) from a TTF font into individual GL_RED textures and renders them
|
||||||
|
as textured quads into whichever viewport is currently active.
|
||||||
|
|
||||||
|
Public methods:
|
||||||
|
initialize_shaders() — compiles/links shaders/02_text_description_{vert,frag}.glsl
|
||||||
|
initialize_font(path, size) — loads a TTF font at a given pixel height
|
||||||
|
render_text() — renders a string at a viewport-local pixel coordinate
|
||||||
|
render_text_wrapped() — renders with automatic word-wrapping at a max pixel width
|
||||||
|
get_text_width() — returns the pixel width of a string at a given scale
|
||||||
|
set_projection(w, h) — sets the orthographic projection for a viewport
|
||||||
|
cleanup() — releases all GL resources and glyph textures
|
||||||
|
|
||||||
|
Two global instances are defined in src/02_text_description.cpp and declared
|
||||||
|
extern in the header:
|
||||||
|
g_text_renderer — loaded at FONT_SIZE_NORMAL (body text, controls, status bar)
|
||||||
|
g_text_renderer_large — loaded at FONT_SIZE_TITLE (panel titles, scope headings)
|
||||||
|
|
||||||
|
Font path and sizes are defined in include/settings.h:
|
||||||
|
FONT_PATH "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
|
||||||
|
FONT_SIZE_NORMAL 18 px
|
||||||
|
FONT_SIZE_TITLE 26 px
|
||||||
|
|
||||||
|
B. Left-panel text content for each radar
|
||||||
|
File: src/01_classes.cpp (render_left_panel() methods in each radar subclass)
|
||||||
|
Status: STUB — text content implemented; actual scope rendering is not yet done.
|
||||||
|
|
||||||
|
Each radar subclass in 01_classes renders:
|
||||||
|
- Radar title (large renderer, white)
|
||||||
|
- Location sub-header (small scale, white)
|
||||||
|
- 3-4 lines of description text (white)
|
||||||
|
- "--- CONTROLS ---" header (red) followed by per-control entries
|
||||||
|
(label in red, keystroke(s) in pink, on the same line)
|
||||||
|
- "1 - Return to Radar Selection" (yellow)
|
||||||
|
|
||||||
|
The intro scene (item 5 above) is rendered by render_intro_left_panel() in
|
||||||
|
src/main.cpp, which shows a welcome paragraph on the left panel and the
|
||||||
|
radar selection list centred in the scope viewport.
|
||||||
|
|
||||||
|
Status bar content for each radar is rendered by render_status_bar() in
|
||||||
|
src/01_classes.cpp, displaying current uniform values (intensity, sensitivity,
|
||||||
|
STC sensitivity/range, and radar-specific values such as max range, cursor
|
||||||
|
positions, radiogoniometer, or antenna bearing) in yellow.
|
||||||
|
|||||||
@@ -33,3 +33,65 @@ The following is suggested.
|
|||||||
5. Run in a loop where every 30th of a second, update the selected radar.
|
5. Run in a loop where every 30th of a second, update the selected radar.
|
||||||
|
|
||||||
======================================================================
|
======================================================================
|
||||||
|
|
||||||
|
CURRENT IMPLEMENTATION STATUS
|
||||||
|
|
||||||
|
File: src/main.cpp
|
||||||
|
Status: COMPLETE (stub phase)
|
||||||
|
|
||||||
|
The five-step flow described above is fully implemented in main():
|
||||||
|
|
||||||
|
Step 1 — parse_args()
|
||||||
|
Parses argv[] for radar names (chain_home, marine_ascope, marine_traffic,
|
||||||
|
police_boat). Sets radar_management[].available for each named radar.
|
||||||
|
With no arguments, all radars are made available.
|
||||||
|
|
||||||
|
STUB NOTE: The current build unconditionally sets operatable=1 for all four
|
||||||
|
radars before checking availability. When real radar components are built,
|
||||||
|
only the ones that are fully implemented should have operatable set to 1.
|
||||||
|
The check "available only if operatable" already works correctly in the code;
|
||||||
|
only the stub's operatable override needs to be updated.
|
||||||
|
|
||||||
|
Step 2 — GLFW / GLAD / OpenGL initialization
|
||||||
|
Creates a 1920x1080 window (WINDOW_WIDTH x WINDOW_HEIGHT from settings.h),
|
||||||
|
initialises GLAD, enables GL_DEBUG_OUTPUT (controlled by ENABLE_GL_DEBUG_OUTPUT
|
||||||
|
in settings.h), and sets global GL state (blending, no depth test).
|
||||||
|
|
||||||
|
Step 3 — Intro / selection screen
|
||||||
|
Renders the welcome text on the left panel (render_intro_left_panel()) and
|
||||||
|
the radar selection list centred in the scope viewport (render_radar_list()).
|
||||||
|
The status bar shows keyboard instructions (render_selection_status_bar()).
|
||||||
|
|
||||||
|
Step 4 — Selection loop
|
||||||
|
The GLFW key callback (key_callback) handles:
|
||||||
|
- Key 1: step selection forward
|
||||||
|
- Key 2: step selection backward
|
||||||
|
- ENTER: activate the highlighted radar; call reset_radar_controls()
|
||||||
|
- ESC: close the window
|
||||||
|
If exactly one radar is available, the application auto-activates it after
|
||||||
|
5 seconds (shows the selection screen during the countdown; breaks early
|
||||||
|
if ENTER is pressed).
|
||||||
|
|
||||||
|
Step 5 — Main render loop (~30 Hz)
|
||||||
|
Uses std::chrono::steady_clock to target a 1/30 s frame period.
|
||||||
|
Each frame:
|
||||||
|
- Polls GLFW events
|
||||||
|
- If SELECTION state: calls render_selection_screen()
|
||||||
|
- If OPERATING state: calls render_operating_screen(), which delegates
|
||||||
|
to g_active_radar->render_left_panel(), render_status_bar(),
|
||||||
|
render_scope() through the RadarBase virtual interface.
|
||||||
|
|
||||||
|
Additional features implemented:
|
||||||
|
- GL debug callback (gl_debug_callback) enabled via ENABLE_GL_DEBUG_OUTPUT
|
||||||
|
- Debug border-box renderer (draw_border_box) enabled via DEBUG_DRAW_WINDOW_BORDERS
|
||||||
|
in settings.h — draws white outlines around each panel for layout verification
|
||||||
|
- reset_radar_controls(): resets uniform_max_range, uniform_range_cursor,
|
||||||
|
uniform_bearing_cursor to defaults for the selected radar when it is activated
|
||||||
|
- Keyboard controls for all scope uniforms (intensity, sensitivity, STC, noise
|
||||||
|
filter, radiogoniometer, max range, range cursor, bearing cursor, antenna
|
||||||
|
rotation) are implemented in key_callback(); the controls are appropriately
|
||||||
|
restricted by radar type (e.g. noise filter only for PPI scopes)
|
||||||
|
|
||||||
|
Application state is tracked by AppState enum (SELECTION, OPERATING) in main.cpp.
|
||||||
|
The active radar object is pointed to by g_active_radar (a RadarBase*).
|
||||||
|
current_radar (global in global_data.cpp) holds the RADAR_* index of the active radar.
|
||||||
|
|||||||
40
CLAUDE.md
40
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
|
||||||
|
|
||||||
@@ -221,9 +227,11 @@ These are knobs on the panel once that is built. Keyboard keys until then
|
|||||||
i for right turn. - uniform is float radiogoniometer;
|
i for right turn. - uniform is float radiogoniometer;
|
||||||
8. Maximum Range for all scopes except for chain home, which is fixed. This control
|
8. Maximum Range for all scopes except for chain home, which is fixed. This control
|
||||||
will not operate for Chain Home. These maximum
|
will not operate for Chain Home. These maximum
|
||||||
range selections will be defined for each scope; If you try to go beyond the stated
|
This is a stop control. Each step for for each maximum range for the selected radar.
|
||||||
maximum ranges, the control will have no effect; note that the default maximum range would
|
those maximum ranges are in a table in settings.h. If you try to step beyond the top maximum
|
||||||
be the highest for that scope; keyboard o for lower; p for higher uniform is float max_range;
|
range for that radar, the step higher will do noting. If yoi try to step beyond the lowest
|
||||||
|
maximum range, the the lower step key will be ignored.
|
||||||
|
keyboard o for stepping lower; p for stepping higher. uniform is float max_range;
|
||||||
9. Range Cursor (for ppi scopes, not for a scopes)
|
9. Range Cursor (for ppi scopes, not for a scopes)
|
||||||
keyboard a for lower; s for higher - uniform is float range_cursor;
|
keyboard a for lower; s for higher - uniform is float range_cursor;
|
||||||
10. Bearing Cursor (for all ppi scopes; not for a scopes)
|
10. Bearing Cursor (for all ppi scopes; not for a scopes)
|
||||||
|
|||||||
101
README.md
101
README.md
@@ -1,3 +1,100 @@
|
|||||||
# radar-simulation
|
# radar-simulator
|
||||||
|
|
||||||
Source code for possible radar exhibit
|
Museum exhibit software simulating 1940s–1960s vintage radar systems.
|
||||||
|
|
||||||
|
## Project overview
|
||||||
|
|
||||||
|
This application runs as a full-screen interactive exhibit on a Geekom A8 Max
|
||||||
|
(AMD Ryzen 9 8945HS, Radeon 780M GPU, Ubuntu 25.10). Visitors can explore four
|
||||||
|
historical radar types by cycling through a selection screen and interacting with
|
||||||
|
keyboard controls.
|
||||||
|
|
||||||
|
**Four radar displays:**
|
||||||
|
|
||||||
|
| # | Name | Type | Location |
|
||||||
|
|---|------|------|----------|
|
||||||
|
| 1 | Chain Home | A-scope (WWII, 1940s) | RAF Ventnor, Isle of Wight |
|
||||||
|
| 2 | Marine A-Scope | A-scope (pre-PPI, manually steered dish) | Community Boating Center, Bellingham Bay WA |
|
||||||
|
| 3 | Marine Traffic Control | PPI scope | Center of Bellingham Bay WA |
|
||||||
|
| 4 | Police Boat | PPI scope (moving platform) | Taylor Dock / Boulevard Park, Bellingham Bay WA |
|
||||||
|
|
||||||
|
## Tech stack
|
||||||
|
|
||||||
|
- **Language:** C++20
|
||||||
|
- **Graphics:** OpenGL 4.3 Core, GLFW, GLAD
|
||||||
|
- **Text:** FreeType 2
|
||||||
|
- **Maps/LIDAR:** GDAL (libgdal-dev)
|
||||||
|
- **Database:** PostgreSQL (database: radar, user: radar)
|
||||||
|
- **Build:** CMake
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p build && cd build
|
||||||
|
cmake ..
|
||||||
|
make -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
The executable is `build/radar_simulator`.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# All four radars available
|
||||||
|
./radar_simulator
|
||||||
|
|
||||||
|
# Specific radars only
|
||||||
|
./radar_simulator chain_home marine_ascope
|
||||||
|
./radar_simulator marine_traffic police_boat
|
||||||
|
```
|
||||||
|
|
||||||
|
If only one radar is specified, it auto-activates after 5 seconds.
|
||||||
|
|
||||||
|
## Screen layout
|
||||||
|
|
||||||
|
The window is 1920×1080 and is divided into three areas:
|
||||||
|
|
||||||
|
- **Left text panel** (upper-left, 500 px wide): radar description and keyboard controls
|
||||||
|
- **Status bar** (lower-left, 500×250 px): current control values in yellow
|
||||||
|
- **Scope** (right side, square, full height): radar display
|
||||||
|
|
||||||
|
## Keyboard controls
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
|-----|--------|
|
||||||
|
| **1** | Step forward in radar selection (or return to selection when operating) |
|
||||||
|
| **2** | Step backward in radar selection |
|
||||||
|
| **ENTER** | Activate selected radar |
|
||||||
|
| **ESC** | Exit |
|
||||||
|
| **3 / 4** | Intensity lower / higher |
|
||||||
|
| **5 / 6** | Receiver sensitivity lower / higher |
|
||||||
|
| **Q / W** | STC sensitivity lower / higher |
|
||||||
|
| **E / R** | STC range lower / higher |
|
||||||
|
| **T / Y** | Rain noise filter lower / higher (PPI scopes only) |
|
||||||
|
| **U / I** | Radiogoniometer left / right (Chain Home only) |
|
||||||
|
| **O / P** | Max range step down / up (all except Chain Home) |
|
||||||
|
| **A / S** | Range cursor lower / higher (PPI scopes only) |
|
||||||
|
| **D / F** | Bearing cursor CCW / CW (PPI scopes only) |
|
||||||
|
| **G / H** | Antenna rotation CCW / CW (Marine A-Scope only) |
|
||||||
|
|
||||||
|
## Component .md files
|
||||||
|
|
||||||
|
Each component has a specification and status file:
|
||||||
|
|
||||||
|
- `01_classes.md` — class hierarchy (RadarBase + 4 subclasses; stubs built)
|
||||||
|
- `02_text_description.md` — text renderer and left-panel text content (complete)
|
||||||
|
- `03_setup_and_radar_selection.md` — startup, selection loop, main render loop (complete)
|
||||||
|
|
||||||
|
## Current build state
|
||||||
|
|
||||||
|
The initialization and radar-management infrastructure is complete:
|
||||||
|
|
||||||
|
- Four stub radar subclasses render description text, keyboard control lists,
|
||||||
|
and current control values in the status bar.
|
||||||
|
- The scope area shows the radar name with a `[stub]` label — no actual radar
|
||||||
|
simulation is drawn yet.
|
||||||
|
- All keyboard controls update CPU globals; uniforms will be pushed to shaders
|
||||||
|
as each scope component is built.
|
||||||
|
|
||||||
|
Real radar scope rendering (sweep, targets, graticule, range rings/ticks,
|
||||||
|
terrain, P7 phosphor persistence) will be added one radar at a time.
|
||||||
|
|||||||
@@ -1,4 +1,33 @@
|
|||||||
1. build a stub for each radar that displays it's information in the left panel; then show the settngs of its controls on the status panel. In the main scope, just put the name in that panel. We do not have
|
As you know, you have enabled the controls on each radar enough to show that they are working, although
|
||||||
any workings. This is only for testing the operation of the initialization and management.
|
there are no uniform variables yet in the shaders.
|
||||||
|
|
||||||
2. Set the radar management structure to have all radars fully built and available to enable the operator to go through the radar selection and operating the controls.
|
This next step is to correct a mistake I made, especially with the maximum range settings for the radars
|
||||||
|
as well as the cursors.
|
||||||
|
|
||||||
|
First of all remove the control for the maximum range for the chain home radar. The only range option
|
||||||
|
for that is 200 nm. This means removing the control as well as setting the global variable for the
|
||||||
|
maximum range for chain home to 200nm. Later on when development starts with chain home, we need to
|
||||||
|
have a uniform variable for maximum range.
|
||||||
|
|
||||||
|
Here are the changes I need for the controls.
|
||||||
|
|
||||||
|
Impliment the step changes for the maximum range and indicate the maximum range in the text
|
||||||
|
status. I changed the maximum range setting to the step setting.
|
||||||
|
|
||||||
|
The control for the bearing shouid show the following:
|
||||||
|
|
||||||
|
1. Degrees from true north, going clockwise for advance or counterclockwise for keyboard for going
|
||||||
|
down. This is for chain home, ascope marine, and marine traffic control.
|
||||||
|
|
||||||
|
2. Degrees from front of boat for the police boat radar, going clockwise for advance or
|
||||||
|
counterclockwise for keyboard for going
|
||||||
|
|
||||||
|
3. Note that clockwise is the direction for bearing going positive.
|
||||||
|
|
||||||
|
4. If the operator goes beyond the meximum of 360 clockwise, we reset to 0
|
||||||
|
and if the operator goes below 0 counterclockwise, we reset to 360.
|
||||||
|
|
||||||
|
|
||||||
|
The control for the range cursor; keep normalized from 0 to 1. However, please take that
|
||||||
|
normalized value and convert to actual range using the current max range setting and display
|
||||||
|
the actual range in the text status window.
|
||||||
|
|||||||
@@ -122,10 +122,15 @@ float uniform_noise_filter = 0.0f;
|
|||||||
// Components: Chain Home A-scope
|
// Components: Chain Home A-scope
|
||||||
float uniform_radiogoniometer = 0.0f;
|
float uniform_radiogoniometer = 0.0f;
|
||||||
|
|
||||||
// Maximum radar range (scope-dependent units) — keyboard o (lower) / p (higher)
|
/*
|
||||||
// GLSL uniform: float max_range
|
* Maximum radar range in nautical miles (NOT normalized).
|
||||||
// Components: Marine A-scope, PPI scopes (not Chain Home; that is fixed)
|
* Chain Home: fixed at CHAIN_HOME_MAX_RANGE_NM; o/p keys ignored.
|
||||||
float uniform_max_range = 1.0f;
|
* Others: set from the radar's step table when a radar is activated; updated by o/p.
|
||||||
|
* 0.0 is the placeholder before any radar is selected.
|
||||||
|
* GLSL uniform: float max_range
|
||||||
|
* Components: all scope renderers
|
||||||
|
*/
|
||||||
|
float uniform_max_range = 0.0f;
|
||||||
|
|
||||||
// Range cursor position (normalized 0-1) — keyboard a (lower) / s (higher)
|
// Range cursor position (normalized 0-1) — keyboard a (lower) / s (higher)
|
||||||
// GLSL uniform: float range_cursor
|
// GLSL uniform: float range_cursor
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ public:
|
|||||||
/* Return the rendered pixel width of text at the given scale. */
|
/* Return the rendered pixel width of text at the given scale. */
|
||||||
float get_text_width(const std::string& text, float scale) const;
|
float get_text_width(const std::string& text, float scale) const;
|
||||||
|
|
||||||
|
/* Render text at (x, y), wrapping on word boundaries when a line would
|
||||||
|
* exceed max_width pixels. y is decremented by line_height * scale after
|
||||||
|
* each rendered line, including the last. Never breaks within a word. */
|
||||||
|
void render_text_wrapped(const std::string& text,
|
||||||
|
float x, float& y, float scale,
|
||||||
|
float max_width, float line_height,
|
||||||
|
float color_r, float color_g, float color_b);
|
||||||
|
|
||||||
/* Set the orthographic projection for a viewport of w × h pixels.
|
/* Set the orthographic projection for a viewport of w × h pixels.
|
||||||
* Call this each time glViewport changes before rendering text. */
|
* Call this each time glViewport changes before rendering text. */
|
||||||
void set_projection(int w, int h);
|
void set_projection(int w, int h);
|
||||||
@@ -92,5 +100,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;
|
||||||
|
|||||||
@@ -194,9 +194,14 @@ extern float uniform_noise_filter;
|
|||||||
// Components: Chain Home A-scope
|
// Components: Chain Home A-scope
|
||||||
extern float uniform_radiogoniometer;
|
extern float uniform_radiogoniometer;
|
||||||
|
|
||||||
// Maximum radar range, all scopes except Chain Home — keyboard o (lower) / p (higher)
|
/*
|
||||||
// GLSL uniform: float max_range
|
* Maximum radar range in nautical miles (NOT normalized).
|
||||||
// Components: Marine A-scope, PPI scopes
|
* Chain Home is fixed at CHAIN_HOME_MAX_RANGE_NM; the o/p keys have no effect for it.
|
||||||
|
* For other radars the value is taken from the radar's step table and updated by o/p.
|
||||||
|
* Set via reset_radar_controls() when a radar is activated.
|
||||||
|
* GLSL uniform: float max_range
|
||||||
|
* Components: all scope renderers
|
||||||
|
*/
|
||||||
extern float uniform_max_range;
|
extern float uniform_max_range;
|
||||||
|
|
||||||
// Range cursor, PPI scopes only — keyboard a (lower) / s (higher)
|
// Range cursor, PPI scopes only — keyboard a (lower) / s (higher)
|
||||||
|
|||||||
@@ -86,21 +86,27 @@
|
|||||||
* Component: Chain Home scope (1940s, World War 2)
|
* Component: Chain Home scope (1940s, World War 2)
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
// Fixed maximum range in km; the max_range control has no effect for this scope
|
// Fixed maximum range in nautical miles; the o/p range control has no effect for this scope
|
||||||
#define CHAIN_HOME_MAX_RANGE_KM 200.0f
|
#define CHAIN_HOME_MAX_RANGE_NM 200.0f
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* MARINE A-SCOPE
|
* MARINE A-SCOPE
|
||||||
* Component: 1940s marine A-scope (pre-PPI)
|
* Component: 1940s marine A-scope (pre-PPI)
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
// Selectable maximum range steps in nautical miles (o/p keys cycle through these)
|
// Selectable maximum range steps in nautical miles (o/p keys step through these)
|
||||||
#define MARINE_A_SCOPE_NUM_RANGES 4
|
#define MARINE_A_SCOPE_NUM_RANGES 4
|
||||||
#define MARINE_A_SCOPE_RANGE_STEP_1 3.0f
|
#define MARINE_A_SCOPE_RANGE_STEP_1 3.0f
|
||||||
#define MARINE_A_SCOPE_RANGE_STEP_2 6.0f
|
#define MARINE_A_SCOPE_RANGE_STEP_2 6.0f
|
||||||
#define MARINE_A_SCOPE_RANGE_STEP_3 12.0f
|
#define MARINE_A_SCOPE_RANGE_STEP_3 12.0f
|
||||||
#define MARINE_A_SCOPE_RANGE_STEP_4 24.0f
|
#define MARINE_A_SCOPE_RANGE_STEP_4 24.0f
|
||||||
|
|
||||||
|
// Pip spacing on the A-scope sweep (nm between range pips), one value per range step
|
||||||
|
#define MARINE_A_SCOPE_PIP_STEP_1 0.5f /* 3 nm range — pip every 0.5 nm */
|
||||||
|
#define MARINE_A_SCOPE_PIP_STEP_2 1.0f /* 6 nm range — pip every 1.0 nm */
|
||||||
|
#define MARINE_A_SCOPE_PIP_STEP_3 2.0f /* 12 nm range — pip every 2.0 nm */
|
||||||
|
#define MARINE_A_SCOPE_PIP_STEP_4 4.0f /* 24 nm range — pip every 4.0 nm */
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* PPI SCOPE - MARINE TRAFFIC CONTROL
|
* PPI SCOPE - MARINE TRAFFIC CONTROL
|
||||||
* Component: PPI scope, marine traffic control station
|
* Component: PPI scope, marine traffic control station
|
||||||
@@ -114,6 +120,22 @@
|
|||||||
#define PPI_MTC_RANGE_STEP_4 24.0f
|
#define PPI_MTC_RANGE_STEP_4 24.0f
|
||||||
#define PPI_MTC_RANGE_STEP_5 48.0f
|
#define PPI_MTC_RANGE_STEP_5 48.0f
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Range ring positions (nm from centre) per range step.
|
||||||
|
* Two rings per step, placed at 1/3 and 2/3 of the maximum range.
|
||||||
|
* No ring at the maximum range itself — that is the scope edge / graticule boundary.
|
||||||
|
*/
|
||||||
|
#define PPI_MTC_RING1_STEP_1 1.0f /* 3 nm — inner ring */
|
||||||
|
#define PPI_MTC_RING2_STEP_1 2.0f /* 3 nm — outer ring */
|
||||||
|
#define PPI_MTC_RING1_STEP_2 2.0f /* 6 nm */
|
||||||
|
#define PPI_MTC_RING2_STEP_2 4.0f
|
||||||
|
#define PPI_MTC_RING1_STEP_3 4.0f /* 12 nm */
|
||||||
|
#define PPI_MTC_RING2_STEP_3 8.0f
|
||||||
|
#define PPI_MTC_RING1_STEP_4 8.0f /* 24 nm */
|
||||||
|
#define PPI_MTC_RING2_STEP_4 16.0f
|
||||||
|
#define PPI_MTC_RING1_STEP_5 16.0f /* 48 nm */
|
||||||
|
#define PPI_MTC_RING2_STEP_5 32.0f
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* PPI SCOPE - ON-BOARD BOAT
|
* PPI SCOPE - ON-BOARD BOAT
|
||||||
* Component: PPI scope, on-board boat view
|
* Component: PPI scope, on-board boat view
|
||||||
@@ -127,6 +149,22 @@
|
|||||||
#define PPI_BOAT_RANGE_STEP_4 12.0f
|
#define PPI_BOAT_RANGE_STEP_4 12.0f
|
||||||
#define PPI_BOAT_RANGE_STEP_5 24.0f
|
#define PPI_BOAT_RANGE_STEP_5 24.0f
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Range ring positions (nm from centre) per range step.
|
||||||
|
* Two rings per step, placed at 1/3 and 2/3 of the maximum range.
|
||||||
|
* No ring at the maximum range itself — that is the scope edge / graticule boundary.
|
||||||
|
*/
|
||||||
|
#define PPI_BOAT_RING1_STEP_1 0.5f /* 1.5 nm — inner ring */
|
||||||
|
#define PPI_BOAT_RING2_STEP_1 1.0f /* 1.5 nm — outer ring */
|
||||||
|
#define PPI_BOAT_RING1_STEP_2 1.0f /* 3 nm */
|
||||||
|
#define PPI_BOAT_RING2_STEP_2 2.0f
|
||||||
|
#define PPI_BOAT_RING1_STEP_3 2.0f /* 6 nm */
|
||||||
|
#define PPI_BOAT_RING2_STEP_3 4.0f
|
||||||
|
#define PPI_BOAT_RING1_STEP_4 4.0f /* 12 nm */
|
||||||
|
#define PPI_BOAT_RING2_STEP_4 8.0f
|
||||||
|
#define PPI_BOAT_RING1_STEP_5 8.0f /* 24 nm */
|
||||||
|
#define PPI_BOAT_RING2_STEP_5 16.0f
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* WINDOW AND PANEL LAYOUT
|
* WINDOW AND PANEL LAYOUT
|
||||||
* Component: Thread 1 (main.cpp), 01_classes, 02_text_description
|
* Component: Thread 1 (main.cpp), 01_classes, 02_text_description
|
||||||
@@ -136,13 +174,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 +204,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
|
||||||
@@ -181,7 +236,7 @@
|
|||||||
#define BEARING_CURSOR_STEP 1.0f
|
#define BEARING_CURSOR_STEP 1.0f
|
||||||
#define RANGE_CURSOR_STEP 0.02f
|
#define RANGE_CURSOR_STEP 0.02f
|
||||||
#define MARINE_A_BEARING_STEP 1.0f
|
#define MARINE_A_BEARING_STEP 1.0f
|
||||||
#define MAX_RANGE_STEP 0.1f
|
// Max range is now step-indexed (see MARINE_A_SCOPE_*, PPI_MTC_*, PPI_BOAT_* tables above)
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* KEYBOARD CONTROL LIMITS
|
* KEYBOARD CONTROL LIMITS
|
||||||
@@ -200,5 +255,4 @@
|
|||||||
#define NOISE_FILTER_MAX 1.0f
|
#define NOISE_FILTER_MAX 1.0f
|
||||||
#define RANGE_CURSOR_MIN 0.0f
|
#define RANGE_CURSOR_MIN 0.0f
|
||||||
#define RANGE_CURSOR_MAX 1.0f
|
#define RANGE_CURSOR_MAX 1.0f
|
||||||
#define MAX_RANGE_MIN 0.1f
|
// Max range limits removed — range is now step-indexed, clamped by table size
|
||||||
#define MAX_RANGE_MAX 1.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,21 @@ static void text_line(const std::string& text, float x, float& y,
|
|||||||
y -= LH * scale;
|
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. */
|
/* 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 +135,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]);
|
||||||
}
|
}
|
||||||
@@ -144,12 +159,12 @@ static void render_scope_name(const std::string& radar_name) {
|
|||||||
static void render_common_status(float x, float& y, float scale) {
|
static void render_common_status(float x, float& y, float scale) {
|
||||||
char buf[320];
|
char buf[320];
|
||||||
snprintf(buf, sizeof(buf),
|
snprintf(buf, sizeof(buf),
|
||||||
"Intensity: %.2f | Sensitivity: %.2f | STC Sens: %.2f | STC Range: %.2f",
|
"Intensity: %.2f | Sensitivity: %.2f | STC Sens: %.2f | STC Range: %.1f nm",
|
||||||
uniform_intensity, uniform_sensitivity,
|
uniform_intensity, uniform_sensitivity,
|
||||||
uniform_stc_sensitivity, uniform_stc_range);
|
uniform_stc_sensitivity, uniform_stc_range * uniform_max_range);
|
||||||
g_text_renderer.render_text(buf, x, y, scale,
|
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]);
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
y -= LH * scale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================================================================== */
|
/* ================================================================== */
|
||||||
@@ -157,10 +172,10 @@ 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);
|
||||||
|
|
||||||
@@ -214,15 +229,17 @@ void ChainHomeRadar::render_scope() {
|
|||||||
void ChainHomeRadar::render_status_bar() {
|
void ChainHomeRadar::render_status_bar() {
|
||||||
float x = MARGIN;
|
float x = MARGIN;
|
||||||
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
|
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
|
||||||
|
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
|
||||||
|
|
||||||
text_line("CHAIN HOME RADAR (Ventnor, Isle of Wight) | Fixed range: 200 km",
|
text_line_wrapped("CHAIN HOME RADAR (Ventnor, Isle of Wight) | Fixed range: 200 nm",
|
||||||
x, y, SCALE_NORMAL, COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
x, y, SCALE_NORMAL, max_w,
|
||||||
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
|
|
||||||
render_common_status(x, y, SCALE_NORMAL);
|
render_common_status(x, y, SCALE_NORMAL);
|
||||||
|
|
||||||
char buf[200];
|
char buf[200];
|
||||||
snprintf(buf, sizeof(buf), "Radiogoniometer: %.1f deg", uniform_radiogoniometer);
|
snprintf(buf, sizeof(buf), "Radiogoniometer: %.1f deg", uniform_radiogoniometer);
|
||||||
g_text_renderer.render_text(buf, x, y, SCALE_NORMAL,
|
text_line_wrapped(buf, x, y, SCALE_NORMAL, max_w,
|
||||||
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,10 +248,10 @@ 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);
|
||||||
|
|
||||||
@@ -284,17 +301,19 @@ void MarineAScopeRadar::render_scope() {
|
|||||||
void MarineAScopeRadar::render_status_bar() {
|
void MarineAScopeRadar::render_status_bar() {
|
||||||
float x = MARGIN;
|
float x = MARGIN;
|
||||||
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
|
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
|
||||||
|
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
|
||||||
|
|
||||||
text_line("MARINE A-SCOPE RADAR (Community Boating Center, Bellingham Bay)",
|
text_line_wrapped("MARINE A-SCOPE RADAR (Community Boating Center, Bellingham Bay)",
|
||||||
x, y, SCALE_NORMAL, COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
x, y, SCALE_NORMAL, max_w,
|
||||||
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
|
|
||||||
render_common_status(x, y, SCALE_NORMAL);
|
render_common_status(x, y, SCALE_NORMAL);
|
||||||
|
|
||||||
char buf[200];
|
char buf[200];
|
||||||
snprintf(buf, sizeof(buf),
|
snprintf(buf, sizeof(buf),
|
||||||
"Max Range (normalized): %.2f | Antenna Bearing: %.1f deg",
|
"Max Range: %.1f nm | Antenna Bearing: %.1f deg",
|
||||||
uniform_max_range, uniform_marine_a_bearing);
|
uniform_max_range, uniform_marine_a_bearing);
|
||||||
g_text_renderer.render_text(buf, x, y, SCALE_NORMAL,
|
text_line_wrapped(buf, x, y, SCALE_NORMAL, max_w,
|
||||||
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,10 +322,10 @@ 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);
|
||||||
|
|
||||||
@@ -358,19 +377,21 @@ void MarineTrafficRadar::render_scope() {
|
|||||||
void MarineTrafficRadar::render_status_bar() {
|
void MarineTrafficRadar::render_status_bar() {
|
||||||
float x = MARGIN;
|
float x = MARGIN;
|
||||||
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
|
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
|
||||||
|
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
|
||||||
|
|
||||||
text_line("MARINE TRAFFIC CONTROL RADAR (Bellingham Bay)",
|
text_line_wrapped("MARINE TRAFFIC CONTROL RADAR (Bellingham Bay)",
|
||||||
x, y, SCALE_NORMAL, COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
x, y, SCALE_NORMAL, max_w,
|
||||||
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
|
|
||||||
render_common_status(x, y, SCALE_NORMAL);
|
render_common_status(x, y, SCALE_NORMAL);
|
||||||
|
|
||||||
char buf[280];
|
char buf[280];
|
||||||
snprintf(buf, sizeof(buf),
|
snprintf(buf, sizeof(buf),
|
||||||
"Noise Filter: %.2f | Max Range (norm): %.2f | "
|
"Noise Filter: %.2f | Max Range: %.1f nm | "
|
||||||
"Range Cursor: %.2f | Bearing Cursor: %.1f deg",
|
"Range Cursor: %.2f nm | Bearing Cursor: %.1f deg",
|
||||||
uniform_noise_filter, uniform_max_range,
|
uniform_noise_filter, uniform_max_range,
|
||||||
uniform_range_cursor, uniform_bearing_cursor);
|
uniform_range_cursor * uniform_max_range, uniform_bearing_cursor);
|
||||||
g_text_renderer.render_text(buf, x, y, SCALE_NORMAL,
|
text_line_wrapped(buf, x, y, SCALE_NORMAL, max_w,
|
||||||
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,10 +400,10 @@ 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);
|
||||||
|
|
||||||
@@ -436,18 +457,20 @@ void PoliceboatRadar::render_scope() {
|
|||||||
void PoliceboatRadar::render_status_bar() {
|
void PoliceboatRadar::render_status_bar() {
|
||||||
float x = MARGIN;
|
float x = MARGIN;
|
||||||
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
|
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH;
|
||||||
|
float max_w = static_cast<float>(LEFT_PANEL_WIDTH) - 2.0f * MARGIN;
|
||||||
|
|
||||||
text_line("POLICE BOAT RADAR (Bellingham Bay - moving)",
|
text_line_wrapped("POLICE BOAT RADAR (Bellingham Bay - moving)",
|
||||||
x, y, SCALE_NORMAL, COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
x, y, SCALE_NORMAL, max_w,
|
||||||
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
|
|
||||||
render_common_status(x, y, SCALE_NORMAL);
|
render_common_status(x, y, SCALE_NORMAL);
|
||||||
|
|
||||||
char buf[280];
|
char buf[280];
|
||||||
snprintf(buf, sizeof(buf),
|
snprintf(buf, sizeof(buf),
|
||||||
"Noise Filter: %.2f | Max Range (norm): %.2f | "
|
"Noise Filter: %.2f | Max Range: %.1f nm | "
|
||||||
"Range Cursor: %.2f | Bearing Cursor: %.1f deg",
|
"Range Cursor: %.2f nm | Bearing Cursor: %.1f deg",
|
||||||
uniform_noise_filter, uniform_max_range,
|
uniform_noise_filter, uniform_max_range,
|
||||||
uniform_range_cursor, uniform_bearing_cursor);
|
uniform_range_cursor * uniform_max_range, uniform_bearing_cursor);
|
||||||
g_text_renderer.render_text(buf, x, y, SCALE_NORMAL,
|
text_line_wrapped(buf, x, y, SCALE_NORMAL, max_w,
|
||||||
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
COL_YELLOW[0], COL_YELLOW[1], COL_YELLOW[2]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 */
|
||||||
@@ -246,6 +247,38 @@ float TextRenderer::get_text_width(const std::string& text, float scale) const {
|
|||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextRenderer::render_text_wrapped(const std::string& text,
|
||||||
|
float x, float& y, float scale,
|
||||||
|
float max_width, float line_height,
|
||||||
|
float color_r, float color_g, float color_b) {
|
||||||
|
std::string line;
|
||||||
|
std::string::size_type pos = 0;
|
||||||
|
std::string::size_type slen = text.length();
|
||||||
|
|
||||||
|
while (pos <= slen) {
|
||||||
|
std::string::size_type nxt = text.find(' ', pos);
|
||||||
|
if (nxt == std::string::npos) nxt = slen;
|
||||||
|
|
||||||
|
std::string word = text.substr(pos, nxt - pos);
|
||||||
|
if (!word.empty()) {
|
||||||
|
std::string candidate = line.empty() ? word : (line + " " + word);
|
||||||
|
if (!line.empty() && get_text_width(candidate, scale) > max_width) {
|
||||||
|
render_text(line, x, y, scale, color_r, color_g, color_b);
|
||||||
|
y -= line_height * scale;
|
||||||
|
line = word;
|
||||||
|
} else {
|
||||||
|
line = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = nxt + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line.empty()) {
|
||||||
|
render_text(line, x, y, scale, color_r, color_g, color_b);
|
||||||
|
y -= line_height * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TextRenderer::set_projection(int w, int h) {
|
void TextRenderer::set_projection(int w, int h) {
|
||||||
/* Column-major orthographic matrix mapping pixel space [0,w]x[0,h]
|
/* Column-major orthographic matrix mapping pixel space [0,w]x[0,h]
|
||||||
* to NDC. Near=-1, far=1 (depth unused for 2D text). */
|
* to NDC. Near=-1, far=1 (depth unused for 2D text). */
|
||||||
|
|||||||
383
src/main.cpp
383
src/main.cpp
@@ -57,6 +57,147 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Range step lookup tables */
|
||||||
|
/* Mirror the settings.h defines as arrays so the keyboard handler */
|
||||||
|
/* can index them at run time. */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static const float ASCOPE_RANGES[MARINE_A_SCOPE_NUM_RANGES] = {
|
||||||
|
MARINE_A_SCOPE_RANGE_STEP_1, MARINE_A_SCOPE_RANGE_STEP_2,
|
||||||
|
MARINE_A_SCOPE_RANGE_STEP_3, MARINE_A_SCOPE_RANGE_STEP_4
|
||||||
|
};
|
||||||
|
|
||||||
|
static const float MTC_RANGES[PPI_MTC_NUM_RANGES] = {
|
||||||
|
PPI_MTC_RANGE_STEP_1, PPI_MTC_RANGE_STEP_2, PPI_MTC_RANGE_STEP_3,
|
||||||
|
PPI_MTC_RANGE_STEP_4, PPI_MTC_RANGE_STEP_5
|
||||||
|
};
|
||||||
|
|
||||||
|
static const float BOAT_RANGES[PPI_BOAT_NUM_RANGES] = {
|
||||||
|
PPI_BOAT_RANGE_STEP_1, PPI_BOAT_RANGE_STEP_2, PPI_BOAT_RANGE_STEP_3,
|
||||||
|
PPI_BOAT_RANGE_STEP_4, PPI_BOAT_RANGE_STEP_5
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* 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 +234,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);
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -121,6 +263,36 @@ static void GLAPIENTRY gl_debug_callback(GLenum /*source*/,
|
|||||||
fprintf(stderr, "[GL %s] %s\n", type_str, message);
|
fprintf(stderr, "[GL %s] %s\n", type_str, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* reset_radar_controls: called when a radar is activated */
|
||||||
|
/* Resets range to the widest step and zeroes the cursors. */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static void reset_radar_controls(int radar_idx) {
|
||||||
|
switch (radar_idx) {
|
||||||
|
case RADAR_CHAIN_HOME:
|
||||||
|
uniform_max_range = CHAIN_HOME_MAX_RANGE_NM;
|
||||||
|
break;
|
||||||
|
case RADAR_MARINE_ASCOPE:
|
||||||
|
ascope_range_setting = MARINE_A_SCOPE_NUM_RANGES - 1;
|
||||||
|
uniform_max_range = ASCOPE_RANGES[ascope_range_setting];
|
||||||
|
break;
|
||||||
|
case RADAR_MARINE_TRAFFIC:
|
||||||
|
traffic_control_range_setting = PPI_MTC_NUM_RANGES - 1;
|
||||||
|
uniform_max_range = MTC_RANGES[traffic_control_range_setting];
|
||||||
|
break;
|
||||||
|
case RADAR_POLICE_BOAT:
|
||||||
|
police_boat_range_setting = PPI_BOAT_NUM_RANGES - 1;
|
||||||
|
uniform_max_range = BOAT_RANGES[police_boat_range_setting];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
uniform_max_range = 0.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uniform_range_cursor = 0.0f;
|
||||||
|
uniform_bearing_cursor = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* Keyboard callback (Thread 1) */
|
/* Keyboard callback (Thread 1) */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -146,13 +318,14 @@ static void key_callback(GLFWwindow* window, int key, int /*scancode*/,
|
|||||||
(g_selection_cursor - 1 + static_cast<int>(g_selectable.size()))
|
(g_selection_cursor - 1 + static_cast<int>(g_selectable.size()))
|
||||||
% static_cast<int>(g_selectable.size());
|
% static_cast<int>(g_selectable.size());
|
||||||
} else if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER) {
|
} else if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER) {
|
||||||
/* Activate selected radar. */
|
/* Activate selected radar; reset all controls to defaults for that radar. */
|
||||||
int mi = g_selectable[g_selection_cursor].management_index;
|
int mi = g_selectable[g_selection_cursor].management_index;
|
||||||
for (int i = 0; i < RADAR_COUNT; i++) radar_management[i].selected = 0;
|
for (int i = 0; i < RADAR_COUNT; i++) radar_management[i].selected = 0;
|
||||||
radar_management[mi].selected = 1;
|
radar_management[mi].selected = 1;
|
||||||
current_radar = mi;
|
current_radar = mi;
|
||||||
g_active_radar = g_selectable[g_selection_cursor].instance;
|
g_active_radar = g_selectable[g_selection_cursor].instance;
|
||||||
g_app_state = AppState::OPERATING;
|
g_app_state = AppState::OPERATING;
|
||||||
|
reset_radar_controls(mi);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -242,17 +415,43 @@ static void key_callback(GLFWwindow* window, int key, int /*scancode*/,
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Max range (all scopes except Chain Home) */
|
/* Max range — step lower (shorter range / zoom in); Chain Home: no effect */
|
||||||
case GLFW_KEY_O:
|
case GLFW_KEY_O:
|
||||||
if (current_radar != RADAR_CHAIN_HOME) {
|
if (current_radar == RADAR_MARINE_ASCOPE) {
|
||||||
uniform_max_range = std::max(MAX_RANGE_MIN,
|
if (ascope_range_setting > 0) {
|
||||||
uniform_max_range - MAX_RANGE_STEP);
|
ascope_range_setting--;
|
||||||
|
uniform_max_range = ASCOPE_RANGES[ascope_range_setting];
|
||||||
|
}
|
||||||
|
} else if (current_radar == RADAR_MARINE_TRAFFIC) {
|
||||||
|
if (traffic_control_range_setting > 0) {
|
||||||
|
traffic_control_range_setting--;
|
||||||
|
uniform_max_range = MTC_RANGES[traffic_control_range_setting];
|
||||||
|
}
|
||||||
|
} else if (current_radar == RADAR_POLICE_BOAT) {
|
||||||
|
if (police_boat_range_setting > 0) {
|
||||||
|
police_boat_range_setting--;
|
||||||
|
uniform_max_range = BOAT_RANGES[police_boat_range_setting];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/* Max range — step higher (longer range / zoom out); Chain Home: no effect */
|
||||||
case GLFW_KEY_P:
|
case GLFW_KEY_P:
|
||||||
if (current_radar != RADAR_CHAIN_HOME) {
|
if (current_radar == RADAR_MARINE_ASCOPE) {
|
||||||
uniform_max_range = std::min(MAX_RANGE_MAX,
|
if (ascope_range_setting < MARINE_A_SCOPE_NUM_RANGES - 1) {
|
||||||
uniform_max_range + MAX_RANGE_STEP);
|
ascope_range_setting++;
|
||||||
|
uniform_max_range = ASCOPE_RANGES[ascope_range_setting];
|
||||||
|
}
|
||||||
|
} else if (current_radar == RADAR_MARINE_TRAFFIC) {
|
||||||
|
if (traffic_control_range_setting < PPI_MTC_NUM_RANGES - 1) {
|
||||||
|
traffic_control_range_setting++;
|
||||||
|
uniform_max_range = MTC_RANGES[traffic_control_range_setting];
|
||||||
|
}
|
||||||
|
} else if (current_radar == RADAR_POLICE_BOAT) {
|
||||||
|
if (police_boat_range_setting < PPI_BOAT_NUM_RANGES - 1) {
|
||||||
|
police_boat_range_setting++;
|
||||||
|
uniform_max_range = BOAT_RANGES[police_boat_range_setting];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -372,12 +571,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,16 +608,16 @@ 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()) {
|
||||||
@@ -440,28 +639,29 @@ 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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_selection_status_bar() {
|
static void render_selection_status_bar() {
|
||||||
float x = MARGIN;
|
float x = MARGIN;
|
||||||
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH_MAIN;
|
float y = static_cast<float>(STATUS_BAR_HEIGHT) - MARGIN - LH_MAIN;
|
||||||
|
float xp;
|
||||||
|
|
||||||
|
/* Line 1: "Use 1 / 2 to change selection." */
|
||||||
g_text_renderer.render_text("Use ", x, y, 1.0f,
|
g_text_renderer.render_text("Use ", x, y, 1.0f,
|
||||||
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
||||||
float xp = x + g_text_renderer.get_text_width("Use ", 1.0f);
|
xp = x + g_text_renderer.get_text_width("Use ", 1.0f);
|
||||||
|
|
||||||
g_text_renderer.render_text("1", xp, y, 1.0f,
|
g_text_renderer.render_text("1", xp, y, 1.0f,
|
||||||
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
|
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
|
||||||
@@ -475,9 +675,14 @@ static void render_selection_status_bar() {
|
|||||||
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
|
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
|
||||||
xp += g_text_renderer.get_text_width("2", 1.0f);
|
xp += g_text_renderer.get_text_width("2", 1.0f);
|
||||||
|
|
||||||
g_text_renderer.render_text(" to move the selection. Press ", xp, y, 1.0f,
|
g_text_renderer.render_text(" to change selection.", xp, y, 1.0f,
|
||||||
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
||||||
xp += g_text_renderer.get_text_width(" to move the selection. Press ", 1.0f);
|
|
||||||
|
/* Line 2: "Press ENTER to activate." */
|
||||||
|
y -= LH_MAIN;
|
||||||
|
g_text_renderer.render_text("Press ", x, y, 1.0f,
|
||||||
|
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
||||||
|
xp = x + g_text_renderer.get_text_width("Press ", 1.0f);
|
||||||
|
|
||||||
g_text_renderer.render_text("ENTER", xp, y, 1.0f,
|
g_text_renderer.render_text("ENTER", xp, y, 1.0f,
|
||||||
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
|
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
|
||||||
@@ -486,13 +691,16 @@ static void render_selection_status_bar() {
|
|||||||
g_text_renderer.render_text(" to activate.", xp, y, 1.0f,
|
g_text_renderer.render_text(" to activate.", xp, y, 1.0f,
|
||||||
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
||||||
|
|
||||||
|
/* Line 3: "Press ESC to exit the exhibit." */
|
||||||
y -= LH_MAIN;
|
y -= LH_MAIN;
|
||||||
g_text_renderer.render_text("Press ", x, y, 1.0f,
|
g_text_renderer.render_text("Press ", x, y, 1.0f,
|
||||||
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
||||||
xp = x + g_text_renderer.get_text_width("Press ", 1.0f);
|
xp = x + g_text_renderer.get_text_width("Press ", 1.0f);
|
||||||
|
|
||||||
g_text_renderer.render_text("ESC", xp, y, 1.0f,
|
g_text_renderer.render_text("ESC", xp, y, 1.0f,
|
||||||
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
|
COL_PINK[0], COL_PINK[1], COL_PINK[2]);
|
||||||
xp += g_text_renderer.get_text_width("ESC", 1.0f);
|
xp += g_text_renderer.get_text_width("ESC", 1.0f);
|
||||||
|
|
||||||
g_text_renderer.render_text(" to exit the exhibit.", xp, y, 1.0f,
|
g_text_renderer.render_text(" to exit the exhibit.", xp, y, 1.0f,
|
||||||
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
COL_WHITE[0], COL_WHITE[1], COL_WHITE[2]);
|
||||||
}
|
}
|
||||||
@@ -502,69 +710,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 +855,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 +873,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);
|
||||||
@@ -670,6 +915,7 @@ int main(int argc, char* argv[]) {
|
|||||||
current_radar = g_selectable[0].management_index;
|
current_radar = g_selectable[0].management_index;
|
||||||
g_active_radar = g_selectable[0].instance;
|
g_active_radar = g_selectable[0].instance;
|
||||||
g_app_state = AppState::OPERATING;
|
g_app_state = AppState::OPERATING;
|
||||||
|
reset_radar_controls(current_radar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,6 +950,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