1750 lines
97 KiB
Markdown
1750 lines
97 KiB
Markdown
Introduction:
|
||
|
||
This is a project for a museum to demonstrate a simulation of a 1950's to 1960's
|
||
vintage marine radar and air traffic control radar
|
||
|
||
The project will be implemented on a Geekom A8 Max
|
||
with AMD AI chip R9-8945HS with 32 GB ram
|
||
|
||
# Project: C++ OpenGL Radar Simulation
|
||
**Environment:** Ubuntu Linux (Remote SSH from Windows)
|
||
**Tech Stack:** C++20, OpenGL 3.3 Core, GLFW, GLAD, FreeType, GDAL (libgdal-dev)
|
||
|
||
|
||
The operating system is Linux (Ubuntu)
|
||
Details:
|
||
|
||
Distributor ID: Ubuntu
|
||
Description: Ubuntu 25.10
|
||
Release: 25.10
|
||
Codename: questing
|
||
|
||
The compiler is cpp (Ubuntu 15.2.0-4ubuntu4) 15.2.0
|
||
|
||
We plan to use the cmake for building.
|
||
|
||
Please add MIT license header to each file
|
||
Please add Author: Mark Allyn to each file
|
||
|
||
Here is the directory structure with files already installed:
|
||
All directories are in the new-radar top level directory. The
|
||
entire directory list is /home/maallyn/new-radar on the Geekom.
|
||
|
||
./shaders
|
||
./shaders/CLAUDE.md
|
||
./glad
|
||
./glad/src
|
||
./glad/src/glad.c
|
||
./include
|
||
./include/glad
|
||
./include/glad/glad.h
|
||
./include/CLAUDE.md
|
||
./include/KHR
|
||
./include/KHR/khrplatform.h
|
||
./new-claude
|
||
./README.md
|
||
./CMakeLists.txt
|
||
./build
|
||
./build/CLAUDE.md
|
||
./CLAUDE.md
|
||
./.new-claude.swp
|
||
./LICENSE
|
||
./src
|
||
./src/CLAUDE.md
|
||
./map
|
||
./map/lidar_processed
|
||
./map/charts_enc
|
||
./map/charts_enc/US5WA45M.000
|
||
./map/charts_enc/n48_w123_1arc_v3.tif
|
||
./map/lidar_raw
|
||
./map/lidar_raw/wa2022_nooksack_dem_J1364940.zip
|
||
./map/lidar_raw/wa2016_west_dem_J1364939.zip
|
||
|
||
|
||
==================================================================
|
||
|
||
GENERAL STUFF
|
||
|
||
==================================================================
|
||
|
||
Please note that all on-screen text shall be white and fully
|
||
illuminated and is not subject to phosphor persistence or decay.
|
||
|
||
Exceptions:
|
||
|
||
Graticule text: should be incandescent for the bearing marks.
|
||
Graticule text for all a-scope should be incandescent, not white
|
||
and not phosphor as they are dependent on glass graticules with etched
|
||
lines and text.
|
||
|
||
PPI Scope Range Ring Markers Text on the PPI scope range rings shall be blue
|
||
fade to yellow green as on p7 phosphor. Which is the same for ppi targets.
|
||
|
||
Please note that direction as stated here are True directions. 000 is True North
|
||
|
||
Maximum Range is 6 miles for marine type radar
|
||
Maximum Range is 20 miles for air traffic control radar.
|
||
Maximum Range is 100 miles for chain home
|
||
Maximum Range 10 miles for precision approach radar; graticule is
|
||
incandescent showing the azimuth path and elevation path as described
|
||
below in PAR description. Those graticules are etched glass for minimal
|
||
parallax
|
||
|
||
The proposed location of the marine radar antenna is in the middle of Bellingham
|
||
Bay on a 100 foot platform. (This should be mentioned as fictitious in the description)
|
||
Location is 48.74361448950435 latitude, -122.56466911663048 longitude
|
||
|
||
The proposed location of the air traffic control radar is the Bellingham
|
||
airport (BLI) control tower. Latitude: 48° 47' 33.7" N ; Longitude: 122° 32' 15.1" W
|
||
|
||
The proposed location for the chain home would be at the original location on
|
||
the UK coast facing the European Continent
|
||
|
||
The following types of scope will be used; (note that these are not all
|
||
going to show at once. They will be selectable using a push button (a letter on
|
||
the keyboard until I get physical buttons that are connected to a gpio pin.
|
||
The selection key should be s (short for scope)
|
||
|
||
Please note that all keyboard based controls need to be described in each
|
||
scope's left hand text panel. These are different for each scope. Note that
|
||
the s for selecting a scope should be in each scope's description and what
|
||
would the next scope be.
|
||
|
||
Also note that when the radar exhibit starts, the very first option will
|
||
be on the screen. Then the screen will advance through the scopes by two means;
|
||
the pressing of the s key by the user, or automatically at every 120 seconds. You will
|
||
need to emphasize in the first description that you can advance without waiting
|
||
for the automatic advancing by pressing the s key. You can reverse by hitting the S key
|
||
(upper case s) This should be articulated for the
|
||
description window for each scope. When the main exhibit descriptor screen comes up, it's
|
||
important to highlight the feature that the user can press the s key or the S key any time to 'hurry up'
|
||
the scope advancement.
|
||
|
||
Also ensure that the timeout clock will reset when the user changes to a new scope, or presses
|
||
any key or operates any control on the panel. This
|
||
should be articulated in the descriptive text
|
||
|
||
|
||
1. Exhibit introduction - a text block describing the exhibit and the
|
||
basics on how to use it and what you are seeing. This should be text only. Top would
|
||
be in all caps, "WELCOME TO MUSEUM VINTAGE RADAR EXHIBIT"
|
||
|
||
2. Marine A-Scope - (horizontal axis is range; vertical axis is amplitude of
|
||
return pulse; bearing will be set via a bearing control; current implementation
|
||
would be two keys on the keyboard; one key to go clockwise on bearing and another
|
||
key would be to go counterclockwise. The A scope phosphor is P1, which is green.
|
||
The c key for clockwise on a scope and v for counterclockwise.
|
||
The step rate for this control, before the knob is implemented would be one or two
|
||
degrees per key press, but if the key is held down, it would increase slowly due to
|
||
how long the key is depressed
|
||
|
||
The A scope graticule is manually swapped out at each maximum range value
|
||
by the operator during the period. Here we will have to fake it out. And that
|
||
graticule needs to have an incandescent color. That graticule will have three horizontal
|
||
graticule lines for estimating return pulse strength. The range lines (vertical lines)
|
||
must match the interim and final ranges as selected by the max range selection
|
||
To change maximum range, use key u for up and d for down. Possible settings are 2,4,6
|
||
miles; this must be noted clearly on the description text.
|
||
|
||
Max is 2; one interim range at 1
|
||
Max is 4; one interim range at 2
|
||
Max is 6; one interim range at 4
|
||
|
||
In addition to the blips for targets, there would be a floor of noise (signal received by
|
||
rain and waves. This needs to be shown.
|
||
|
||
Graticule swap simulation: In the period, changing maximum range required the operator
|
||
to physically slide the glass graticule panel upward and out from in front of the CRT,
|
||
then slide the replacement graticule (calibrated for the new range) downward into position.
|
||
This must be simulated when the operator presses u or d to change range.
|
||
|
||
The graticule swap animation uses four states:
|
||
NORMAL - graticule in place, scope operating normally
|
||
SLIDING_OUT - old graticule translates upward off screen (~0.5 seconds)
|
||
BARE_CRT - no graticule visible; CRT trace and noise floor still running
|
||
SLIDING_IN - new graticule (correct lines for new range) slides down into position (~0.5 seconds)
|
||
After SLIDING_IN completes, state returns to NORMAL with the new range active.
|
||
The u and d keys are ignored during the swap animation (operator's hands are busy).
|
||
The graticule remains incandescent color throughout — it is edge-lit glass.
|
||
|
||
3. Chain Home A Scope There is a second use of the a-scope.
|
||
That is for the early world war 2 chain home
|
||
radar. This operated very differently. You have a large array of high power transmitters
|
||
'floodlighting' the target area (in World War 2, that would be the English Channel.
|
||
Since we don't care about land reflections with the original chain home setup was
|
||
facing the English Channel, we can tell visitors that this radar is set at the
|
||
English Channel (do this explanation on the explanation side panel for this
|
||
radar mode. And for simulating operator using this radar, there would be two controls,
|
||
one for the 'nulling the signal at the correct direction; simulating the behavior
|
||
of the goniometer and the other for using the goniometer for elevation. For museum
|
||
accuracy, we need to simulate the sharp 'null' when the goniometer is at the direction
|
||
of the signal. This concept needs to be covered in the description text thoroughly
|
||
as this is a bit advanced. I need your advice on how to do this for children and those
|
||
who never heard of chain home.
|
||
|
||
The goniometer vertical and horizontal switch could be key [ for toggling. The goniometer tuning
|
||
would be 9 and 0 to avoid using the shift key. The tuning keys should have one unit
|
||
for single press, but a slow build of of speed if key is held down. This has to stay
|
||
slow due to the sudden appearance of the null.
|
||
|
||
Targets for Chain Home would all have to be simulated as there will be no ais
|
||
nor ads-b. Simulations would show several aircraft approaching the radar in many
|
||
different directions and ranges. The museum visitor for exercise could try to sort
|
||
out the targets by range and bearing and elevation by the nulling procedure noted
|
||
above as well as the distance of the pulse from the origin.
|
||
|
||
The graticule is etched glass (side lit with incandescent lights) with 10 mile
|
||
markers for range (horizontal axis). There are no vertical markers; the signal
|
||
strength value is not important. The only vertical value that is important is the
|
||
nulling of the signal based on bearing and elevation from the manipulation of the
|
||
goniometer.
|
||
|
||
The refresh rates for chain home were slow in order to avoid aliasing with targets
|
||
far away, the pulse repetition frequency (PRF) is about 25 times per second. This
|
||
rate is 1/2 of the standard 50 Hz for British power.
|
||
|
||
The operator did have a switch to switch from the 25 pulses per second PRF to 12.5
|
||
pulses per second PRF so that they could help eliminate the range ambiguity
|
||
problem, where a target far away could appear to be right on site since that echo
|
||
would return at the precise time for the next pulse to go out at 25 PRF. This needs
|
||
to be explained in the explainer window for the chain home. Mention that mountains or planes
|
||
in the continent could have that kind of range. Furthermore, the operator can reduce
|
||
the PRF in order to reduce confusion caused by other radio transmissions such as press-to-talk
|
||
communications transmissions.
|
||
|
||
Let's assign key . for toggling between 25 and 12.5 PRF. There is no range selection.
|
||
Note on description; this is to reduce use of the shift key.
|
||
|
||
Because of the slow repetition rate, the phosphor used was an early implementation
|
||
of the p7 phosphor so that the targets will still glow between the sweeps and not cause
|
||
flickering.
|
||
|
||
Another unique feature would be a response to the drifting problem in early electronics.
|
||
The scope electronics would use a crystal calibrator that puts tiny pips or spikes at
|
||
known intervals (10 miles). The operator would use a knob, or control, to stretch or
|
||
shrink the electronic trace so that the 10 mile pips align perfectly with the 10 mile
|
||
marks on the edge lit glass graticule.
|
||
|
||
Let's assign key n for shrink and m for stretch. (may be ambiguous, but I am running
|
||
out of keys. Note in the descriptor.
|
||
|
||
|
||
4. Marine PPI Scope -
|
||
marine scopes have the following items in common:
|
||
Targets, range rings, and range ring text levels shall be treated the same for
|
||
presentation. All are P7 phosphor. Immediate strike by the electron beam is blue.
|
||
persistence is green/yellow. Targets, range rings, and range ring labels shall all
|
||
persist and fade out together. They should be faded out by the time the sweep
|
||
to that location.
|
||
|
||
The maximum range settings are 6 miles for the marine radar scope
|
||
Rings should be 2,4, and 6 miles for marine.
|
||
The max range settings for marine ppi will be u for up and d for
|
||
down. If you are in the marine ppi, you change only the max range for the marine
|
||
ppi. The possible max range values for
|
||
the marine radar are 2,4,6
|
||
miles.
|
||
|
||
Marine:
|
||
Max is 2; one interim range at 1, final ring at 2
|
||
Max is 4; one interim range at 2, final ring at 4
|
||
Max is 6; one interim range at 4, final ring at 6
|
||
|
||
Note on range. If cursor range is beyone max, clamp it to the max.
|
||
|
||
Bear in mind that the max range setting is independent for both radars.
|
||
|
||
The bearing graticule (lit incandescent) There should
|
||
be an inner circle with tickmarks for each degree, starting at 0 (north) and going
|
||
clockwise to the last tick, which is 359. Outside the inner ring shall be text
|
||
labels for every 15 degrees. Outside the text labels, there will be
|
||
an outer ring. Both inner and outer rings, along with ticks, and the bearing
|
||
labels are to be incandescent color.
|
||
|
||
The sweep time shall be 4 seconds for the marine scope
|
||
|
||
The sweep direction is clockwise, which means that the antenna
|
||
dish rotates clockwise.
|
||
|
||
The scope has a cursor for range and bearing. The cursor consists of a
|
||
section of a ring ( 10 degrees) and a cross line for bearing.
|
||
The cursor should be yellow (it
|
||
a plastic overlay in the period time. Two controls control the cursor; range and
|
||
bearing. Both were physical crank controls. For now, both we need to use key pairs
|
||
on the keyboard. A white text indication of range and bearing should be put under
|
||
the scope. In the real day, it was a machanical readout. The key sequence would be
|
||
r for bearing to the right and l for bearing for the left; and t for higher range
|
||
and y for smaller range. These controls should have slow movement for single stroke; but
|
||
gradual for for holding key down.
|
||
|
||
5. Police Patrol Boat PPI
|
||
|
||
This scope shows the radar display aboard a simulated Bellingham Police
|
||
Department patrol vessel making its routine waterfront patrol. Unlike all
|
||
other scopes in this exhibit the radar origin is not fixed — it moves with
|
||
the boat, making this a genuinely different operational experience.
|
||
|
||
THE PATROL VESSEL AND ITS RADAR
|
||
The vessel carries a professional-grade open-array radar: 6 kW peak power,
|
||
24-inch open array, ~1.9° horizontal beamwidth, mounted 3–4 m above the
|
||
waterline on a flybridge or radar arch. This is a working law-enforcement
|
||
vessel, not a recreational boat; the equipment reflects that. The narrower
|
||
beamwidth (vs a recreational radome's 4–6°) gives better azimuth resolution
|
||
and less sea clutter per range cell — important when searching for small
|
||
targets such as kayakers and stand-up paddleboards near the ferry lane.
|
||
|
||
MAX RANGE: 2 miles. Range steps: 0.5 / 1 / 2 miles.
|
||
The patrol mission is close-in situational awareness. The tight range steps
|
||
let the officer zoom into the marina entrance, Whatcom Waterway, or the
|
||
ferry lane without switching to a different scope.
|
||
|
||
RADAR HORIZON: ~4.3 nm at 3.5 m antenna height. Range is mission-limited
|
||
(2 miles) rather than horizon-limited.
|
||
|
||
PATROL ROUTE (v1 — open water only)
|
||
The simulated vessel follows a continuous back-and-forth patrol of the
|
||
working waterfront at variable speed. Entry into Squalicum Marina and
|
||
Whatcom Waterway is deferred to a future version; v1 stays in open water
|
||
where the radar geometry is straightforward.
|
||
|
||
Speed by zone:
|
||
Open waterfront / ferry lane: 10 knots
|
||
Near docks and breakwater: 4 knots
|
||
Route (loaded from data/patrol_route.json at startup):
|
||
Whatcom Waterway entrance → ferry terminal → Boulevard Park →
|
||
Taylor Dock → Community Boating Center → reverse and repeat
|
||
|
||
SMALL TARGET SCENARIO — FERRY LANE
|
||
The Bellingham terminal serves Alaska Marine Highway ferries up to 400 ft.
|
||
The simulator places a scripted stand-up paddleboarder on a slow drift
|
||
across the ferry departure lane, plus random kayakers near the harbor mouth.
|
||
These are marginal radar targets: low RCS, no metal, minimal freeboard.
|
||
At 10 kt approach in light chop the paddleboarder may show as a faint
|
||
intermittent blip or vanish into sea clutter entirely. Visitors can try the
|
||
wave clutter filter (keys 5/6) and observe how suppressing clutter can also
|
||
suppress the very target they are looking for. This is the central teaching
|
||
moment of this scope: radar does not see everything.
|
||
|
||
DISPLAY ORIENTATION
|
||
Default is North-up (000° at top). The k/j keys rotate the display offset.
|
||
Matching the offset to the boat's heading puts the bow at the top — Head-up
|
||
mode. The left panel labels the current mode and shows the heading marker,
|
||
a white dashed line from scope center toward the bow, drawn after all
|
||
phosphor content so it never decays.
|
||
|
||
TERRAIN AND BREAKWATER CLUTTER
|
||
The concrete Squalicum Harbor outer breakwater is a strong radar return and
|
||
a significant shadow-caster. Everything behind the breakwater from the
|
||
patrol boat's point of view is shadowed — no return. The marina interior is
|
||
not visible from open water. This is realistic and visible on the scope.
|
||
Coastline, piers, and the ferry terminal structure also appear as clutter.
|
||
Shadow masks are pre-computed for patrol route waypoints by
|
||
terrain_preprocess and selected at runtime by nearest-waypoint lookup.
|
||
|
||
Left panel status (below description text):
|
||
Zone: [plain text, e.g. "Ferry lane — open waterfront"]
|
||
Boat pos: XX.XXXX°N XXX.XXXX°W
|
||
Boat heading: XXX°T
|
||
Boat speed: X.X kt
|
||
Display mode: North-up (or Head-up)
|
||
Cursor range: X.X nm
|
||
Cursor brg: XXX°T
|
||
Max range: X.X mi
|
||
|
||
6. Air Traffic PPI Scope -
|
||
Targets, range rings, and range ring text levels
|
||
All are P7 phosphor. Immediate strike by the electron beam is blue.
|
||
persistence is green/yellow. Targets, range rings, and range ring labels shall all
|
||
persist and fade out together. They should be faded out by the time the sweep
|
||
to that location.
|
||
|
||
Rings should be 5,10,15,20 for the air traffic control radar.
|
||
The max range settings for air ppi will be u for up and d for
|
||
down. Use of these controls affect only the scope you are in. No other scopes are
|
||
affected.
|
||
The ranges for air traffic control radar are 5,10,15,20
|
||
miles.
|
||
|
||
Air Traffic Control:
|
||
Max is 5; one interim range; two total; rings at 2.5; final ring at 5
|
||
Max is 10; four interim ranges, five total; 2,4,6,8; final ring at 10
|
||
Max is 15; three interim ranges four total; 4,8,12; final ring at 15
|
||
Max is 20, three interim ranges four total; 5,10,15; final ring at 20
|
||
|
||
Note on range. If cursor range is beyone max, clamp it to the max.
|
||
|
||
Bear in mind that the max range setting is independent for both radars.
|
||
|
||
The bearing graticule (lit incandescent) for the scopes are the same. There should
|
||
be an inner circle with tickmarks for each degree, starting at 0 (north) and going
|
||
clockwise to the last tick, which is 359. Outside the inner ring shall be text
|
||
labels for every 15 degrees. Outside the text labels, there will be
|
||
an outer ring. Both inner and outer rings, along with ticks, and the bearing
|
||
labels are to be incandescent color.
|
||
|
||
The sweep time shall be 5 seconds for the
|
||
air traffic scope.
|
||
|
||
The sweep direction on the scope is clockwise, which means that the antenna
|
||
dish rotates clockwise.
|
||
|
||
The scope has cursor for range and bearing. The cursor consists of a
|
||
section of a ring ( 10 degrees) and a cross line for bearing.
|
||
The cursor should be yellow (it
|
||
a plastic overlay in the period time. Two controls control the cursor; range and
|
||
bearing. Both were physical crank controls. For now, both we need to use key pairs
|
||
on the keyboard. A white text indication of range and bearing should be put under
|
||
the scope. In the real day, it was a machanical readout. The key sequence would be
|
||
r for bearing to the right and l for bearing for the left; and t for higher range
|
||
and y for smaller range.
|
||
These controls should have slow movement for single stroke; but
|
||
gradual for for holding key down.
|
||
|
||
7. Precision approach (PAR for short)
|
||
PAR was developed in WWII and matured in the 1950s. With a fixed 10 mile range, it was
|
||
controller who talked the pilot down verbally over radio, which means that the pilot
|
||
does not have to rely on any equipment on the plane itself to help with landing.
|
||
The display shows the full 10-mile approach path, but the controller's active guidance window
|
||
is roughly the last 5 miles, intensifying from about 2 miles out to touchdown.
|
||
This needs to be carefully explained on the explainer screen.
|
||
Let's locate this at the south end of Runway 16/34 landing at BLI and let's have the
|
||
active runway 34 (northbound landing)
|
||
|
||
Locate at the end of Runway 16/34
|
||
at Bellingham Airport (BLI). Two vertically stacked scopes share the
|
||
right panel. Top scope: azimuth (lateral deviation vs. range from
|
||
touchdown). Bottom scope: elevation (vertical deviation vs. range).
|
||
Have the azimuth scope to about 1/3 larger than the elevation scope
|
||
Both use P7 phosphor; graticules are incandescent etched glass.
|
||
Range: 10 miles maximum, fixed (no range change control).
|
||
Non-linear scale: inner 5 miles occupies 70% of horizontal width.
|
||
All targets are simulated. No cursor or bearing controls; PAR
|
||
has no bearing selection — it always points toward the runway.
|
||
Sweep rate: approximately 30 Hz alternating between azimuth and
|
||
elevation planes so that each will scan 1/15 th of a second.
|
||
|
||
THREADS
|
||
|
||
These are the threads of processes:
|
||
|
||
1. Display initiation and operation (anything that 'touches' the display and the shaders) Thread 1
|
||
2. Software that receives data for targets. Thread 2 (this is the traffic cop that polls the
|
||
raspberry pis. and the Simulator. This needs mutex access to shared data with thread 1. It will
|
||
also need mutex access to shared data with thread 4 (the simulator)
|
||
3. Knob panel - thread 3 - uses a mutex to write shared state variables that thread 1 reads
|
||
to send to the shaders.
|
||
4. Simulator, thread 4. It is polled by the traffic cop
|
||
|
||
Threads 2,3, need mutex access to shared data that is read by thread 1.
|
||
Thread 2 needs mutex access for shared data with thread 4, the simulator
|
||
|
||
SUMMARY OF Controls:
|
||
● ┌─────┬─────────────────────────────────────┬───────┬──────────┬──────────────┬────────────┬──────────┬─────────┬─────┐
|
||
│ Key │ Function │ Intro │ Marine A │ Chain Home A │ Marine PPI │ Boat PPI │ ATC PPI │ PAR │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ s │ Advance to next scope │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ S │ back to previous scope │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ c │ Bearing clockwise │ │ ✓ │ │ │ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ v │ Bearing counterclockwise │ │ ✓ │ │ │ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ u │ Max range up │ │ ✓ │ │ ✓ │ ✓ │ ✓ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ d │ Max range down │ │ ✓ │ │ ✓ │ ✓ │ ✓ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ r │ Cursor bearing right │ │ │ │ ✓ │ ✓ │ ✓ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ l │ Cursor bearing left │ │ │ │ ✓ │ ✓ │ ✓ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ t │ Cursor range increase │ │ │ │ ✓ │ ✓ │ ✓ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ y │ Cursor range decrease │ │ │ │ ✓ │ ✓ │ ✓ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ k │ Display offset right (boat heading) │ │ │ │ ✓ │ ✓ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ j │ Display offset left (boat heading) │ │ │ │ ✓ │ ✓ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ [ │ Goniometer H/V switch │ │ │ ✓ │ │ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ 9 │ Goniometer tune left │ │ │ ✓ │ │ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ 0 │ Goniometer tune right │ │ │ ✓ │ │ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ . │ Toggle PRF (25/12.5 Hz) │ │ │ ✓ │ │ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ n │ Calibrator shrink │ │ │ ✓ │ │ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ m │ Calibrator stretch │ │ │ ✓ │ │ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ 1 │ Gain increase │ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ 2 │ Gain decrease │ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ 3 │ Rain clutter filter increase │ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ 4 │ Rain clutter filter decrease │ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │ ✓ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ 5 │ Wave clutter filter increase │ │ ✓ │ ✓ │ ✓ │ ✓ │ │ │
|
||
├─────┼─────────────────────────────────────┼───────┼──────────┼──────────────┼────────────┼──────────┼─────────┼─────┤
|
||
│ 6 │ Wave clutter filter decrease │ │ ✓ │ ✓ │ ✓ │ ✓ │ │ │
|
||
└─────┴─────────────────────────────────────┴───────┴──────────┴──────────────┴────────────┴──────────┴─────────┴─────┘
|
||
|
||
Note: k/j (display bearing offset) are for marine PPI scopes only. The fixed ATC tower
|
||
at BLI has no heading offset need. On the fixed Marine PPI, k/j demonstrate North-up vs.
|
||
Head-up orientation as a teaching aid. On the Boat PPI, k/j are operationally meaningful:
|
||
zero offset = North-up (chart-style); offset matching boat heading = Head-up (bow at top).
|
||
|
||
Table for general controls not yet implemented on the keyboard in the table above:
|
||
|
||
1. Intensity
|
||
2. Focus
|
||
3. Astigmatism
|
||
4. Graticule light intensity
|
||
|
||
Note: Gain (keys 1/2), rain clutter (keys 3/4), and wave clutter (keys 5/6) are now
|
||
in the keyboard control table above. They remain physical encoder controls on the
|
||
operator panel when that hardware is installed; the keyboard keys are the temporary
|
||
stand-in. All three have defaults in settings.h.
|
||
|
||
SUMMARY of target handling:
|
||
|
||
The traffic cop handles anything that is coming from the simulator as well as the raspberry pi's
|
||
It will use polling to find if anything is available from the raspberry pis and the simulator. It will
|
||
poll each source once per beam update
|
||
|
||
The raspberry pi receiver pulls the data from each raspberry pi. If nothing, it does nothing else
|
||
for this sweep. If there is data, it will provide data to the traffic cop upon poll.
|
||
|
||
Each raspberry pi, after boot-up, will respond to polls from the raspberry pi receiver (thread 2)
|
||
|
||
The Simulator will run all fake targets. It will provide data to the traffic cop upon traffic cop poll.
|
||
It can run as a separate thread. It will not write data to anywhere except when polled by the traffic cop.
|
||
|
||
|
||
CONTROLS
|
||
|
||
Every control listed above — both keyboard controls and the 7 general operator controls —
|
||
shall have a corresponding default constant in settings.h. This allows any startup value
|
||
to be changed at compile time without touching scope or rendering code.
|
||
|
||
The 7 general controls (Intensity, Focus, Astigmatism, Gain, Rain Clutter, Wave Clutter,
|
||
Graticule Intensity) are physical encoder controls not yet purchased. Their placeholder
|
||
default constants are defined in settings.h. KnobPanel (Thread 3) will compile and run
|
||
from the start but will idle without ever acquiring Mutex A until real hardware is wired
|
||
in. SharedRenderState holds the default values unchanged; Thread 1 reads and applies them
|
||
every frame. No feature flags or conditional compilation are needed — the code path is
|
||
complete end-to-end, always at the compile-time default.
|
||
|
||
Three of the 7 general controls — Gain, Rain Clutter, and Wave Clutter — have temporary
|
||
keyboard implementations (keys 1/2, 3/4, and 5/6 respectively) that write to the same
|
||
SharedRenderState fields the hardware encoders will eventually write to. When physical
|
||
encoders are installed, the keyboard keys can be removed or left as redundant overrides.
|
||
|
||
Things to note about the keyboard type controls.
|
||
The letter on the keyboard are temporary. When I get around to making
|
||
the operators panel, this all will go away.
|
||
|
||
Implementation of controls:
|
||
|
||
1. For keyboard controls. Those are run as thread one where The keyboard callback
|
||
belongs to GLFW (glfwSetKeyCallback)
|
||
They will manipulate the shaders only.
|
||
|
||
2. The control desk controls will have to mutex to access the state variables that
|
||
thread 1 sends to the shaders.
|
||
|
||
3. If the control does not yet exist, we still want stubs for receiving control
|
||
data for that control. It's just that nothing will call it.
|
||
|
||
Scope and left window arrangement.
|
||
|
||
For each scope, put the scope itself on the right hand of the window.
|
||
On the left hand of the window will be a text description of that scope.
|
||
|
||
Underneath each scope's description will be cursor range and bearing from the radar
|
||
location; and the setting of maximum range; and the bearing offset; for 0 would
|
||
be to have 0 degrees pointing to true north (this is needed if I decide to
|
||
implement a radar on a boat. If implemented, use k for bearing to right; and
|
||
j for bearing to left. Make note in description that this is only used if this
|
||
is a radar on a boat. (perhaps later on, I could add a PPI on a boat scenario)
|
||
|
||
Please note that some keys may be the same from scope to scope. This is okay. Each
|
||
scope's controls are for that scope that you are connected do.They will not effect
|
||
settings on another scope.
|
||
|
||
Please note that the maximum range setting on a scope specific to that scope
|
||
and will be in that scope's definition. and the bearing selection
|
||
is scope specific. The manually operated radar dish for the a scope is not the same
|
||
as the PPI radar dishes. They are from different eras. In addition, all range and
|
||
bearing data for marine is separate than for air traffic control. They are completely
|
||
different radars. Range and bearing for the precision approach radar will be different
|
||
than any other radar as that radar is located at the end of the runway and scan both
|
||
horizontal and vertical.
|
||
|
||
Please analyze and comment. Please do not generate any code file nor shader files.
|
||
|
||
==================================================================
|
||
|
||
CLASS DESIGN AND FILE LAYOUT
|
||
|
||
==================================================================
|
||
|
||
Class Hierarchy:
|
||
|
||
Scope (abstract base)
|
||
├── ExhibitIntro
|
||
├── AScope (abstract)
|
||
│ ├── MarineAScope
|
||
│ └── ChainHomeAScope
|
||
├── PPIScope (abstract)
|
||
│ ├── MarinePPIScope
|
||
│ ├── BoatPPIScope
|
||
│ └── ATCPPIScope
|
||
└── PARScope
|
||
|
||
Scope (abstract base) — everything all scopes share:
|
||
- Left panel text rendering
|
||
- s / S key handling (scope advance / reverse)
|
||
- Auto-advance timer reset on any key or control input
|
||
- Pure virtual: render(), handleKey(), getDescription()
|
||
|
||
ExhibitIntro : public Scope
|
||
- Text-only rendering, no radar display
|
||
- Header: "WELCOME TO MUSEUM VINTAGE RADAR EXHIBIT" (all caps)
|
||
|
||
AScope : public Scope (abstract) — shared A-scope behavior:
|
||
- Horizontal range axis, vertical amplitude axis
|
||
- Noise floor rendering (rain/wave clutter)
|
||
- Incandescent graticule (three horizontal amplitude lines + vertical range lines)
|
||
- Bearing control with key-hold acceleration
|
||
- Phosphor type as parameter (P1 or P7)
|
||
|
||
MarineAScope : public AScope
|
||
- P1 phosphor (green)
|
||
- Range settings: 2, 4, 6 miles
|
||
- Graticule swap animation state machine (NORMAL/SLIDING_OUT/BARE_CRT/SLIDING_IN)
|
||
when operator changes max range — see Marine A-Scope section above for full detail
|
||
- Keys: c (bearing CW), v (bearing CCW), u (range up), d (range down)
|
||
u and d ignored during graticule swap animation
|
||
|
||
ChainHomeAScope : public AScope
|
||
- P7 phosphor (early implementation)
|
||
- Goniometer state: H/V mode toggle, azimuth angle, elevation angle
|
||
- PRF toggle: 25 Hz / 12.5 Hz
|
||
- Calibrator stretch/shrink scale factor
|
||
- Fixed 100-mile range
|
||
- Keys: [ (goniometer H/V toggle), 9/0 (tune), . (PRF), n/m (calibrator)
|
||
|
||
PPIScope : public Scope (abstract) — shared PPI behavior:
|
||
- Clockwise sweep with P7 phosphor persistence (blue strike, green/yellow decay)
|
||
- Incandescent bearing graticule (1-degree ticks, 15-degree labels, inner/outer rings)
|
||
- Yellow cursor: 10-degree arc + bearing crossline
|
||
- Cursor range/bearing readout under scope (white text)
|
||
- Bearing offset for boat mode (k/j)
|
||
- Cursor range clamped to max range
|
||
|
||
MarinePPIScope : public PPIScope
|
||
- Sweep time: 4 seconds
|
||
- Max range: 2, 4, 6 miles with correct ring sets
|
||
- Keys: u (range up), d (range down) — this scope only
|
||
|
||
BoatPPIScope : public PPIScope
|
||
- Direct subclass of PPIScope (not of MarinePPIScope)
|
||
- Range steps: 0.5 / 1 / 2 miles; sweep time 4 s; phosphor P7
|
||
- Radar parameters: 6 kW, 1.9° open array (distinct from 30 kW / 0.5° coastal)
|
||
- Radar origin = boat lat/lon from SharedRenderState, updated every sweep
|
||
- Variable patrol speed per route segment (loaded from data/patrol_route.json)
|
||
- Heading marker: white dashed line from scope center toward boat heading;
|
||
drawn after all phosphor content so it always appears fully bright
|
||
- Nearest pre-computed shadow mask selected each sweep via
|
||
TerrainMap::selectNearestBoatMask() — no runtime ray-marching
|
||
- Display mode indicator: "North-up" / "Head-up" based on offset vs heading
|
||
- Left panel status: zone text, lat/lon, boat heading, speed, display mode
|
||
- Keys: u/d (range 0.5/1/2), r/l (cursor bearing), t/y (cursor range),
|
||
k/j (display offset), 1/2 (gain), 3/4 (rain filter), 5/6 (wave filter)
|
||
|
||
ATCPPIScope : public PPIScope
|
||
- Sweep time: 5 seconds
|
||
- Max range: 5, 10, 15, 20 miles with correct ring sets
|
||
- Keys: u (range up), d (range down) — this scope only
|
||
|
||
PARScope : public Scope
|
||
- Two stacked sub-scopes: azimuth (top, ~1/3 larger) and elevation (bottom)
|
||
- 30 Hz alternating scan between planes (~15 Hz each)
|
||
- Fixed 10-mile range, non-linear scale (inner 5 miles = 70% width)
|
||
- P7 phosphor; incandescent etched glass graticules
|
||
- All targets simulated; no cursor or bearing controls
|
||
|
||
Supporting classes:
|
||
ScopeManager Thread 1 — owns scope list, s/S switching, 120s auto-advance timer
|
||
PhosphorRenderer Thread 1 — P1 and P7 decay/persistence; shared dependency
|
||
Graticule Thread 1 — incandescent graticule lines/text; parameterized per scope
|
||
LeftPanel Thread 1 — scope description text panel (left side of window)
|
||
SharedRenderState Threads 1,2,3 — Mutex A; state vars Thread 1 reads each frame for shader uniforms
|
||
TargetBuffer Threads 2,4 — Mutex B; target data handoff between TrafficCop and Simulator
|
||
TrafficCop Thread 2 — polls Simulator and RPi receivers; writes to SharedRenderState
|
||
Simulator Thread 4 — runs fake targets; returns data to TrafficCop when polled
|
||
KnobPanel Thread 3 — future hardware stub; writes to SharedRenderState under Mutex A
|
||
RPiReceiver Thread 2 — stub; one per Raspberry Pi; called by TrafficCop
|
||
|
||
File layout:
|
||
|
||
src/
|
||
main.cpp
|
||
scope_manager.h / scope_manager.cpp
|
||
scope.h / scope.cpp — abstract Scope base
|
||
scope_intro.h / scope_intro.cpp
|
||
scope_ascope.h / scope_ascope.cpp — abstract AScope
|
||
scope_marine_a.h / scope_marine_a.cpp
|
||
scope_chain_home.h / scope_chain_home.cpp
|
||
scope_ppi.h / scope_ppi.cpp — abstract PPIScope
|
||
scope_marine_ppi.h / scope_marine_ppi.cpp
|
||
scope_boat_ppi.h / scope_boat_ppi.cpp — BoatPPIScope; police patrol boat;
|
||
moving radar origin; variable speed;
|
||
heading marker; display-mode tracking;
|
||
nearest-mask selection from TerrainMap
|
||
scope_atc_ppi.h / scope_atc_ppi.cpp
|
||
scope_par.h / scope_par.cpp
|
||
phosphor.h / phosphor.cpp
|
||
graticule.h / graticule.cpp
|
||
left_panel.h / left_panel.cpp
|
||
shared_render_state.h / shared_render_state.cpp
|
||
target_buffer.h / target_buffer.cpp
|
||
traffic_cop.h / traffic_cop.cpp
|
||
simulator.h / simulator.cpp
|
||
knob_panel.h / knob_panel.cpp
|
||
rpi_receiver.h / rpi_receiver.cpp
|
||
|
||
settings.h — all tunable constants; no .cpp needed
|
||
|
||
data/
|
||
patrol_route.json — boat waypoints with lat/lon and speed per
|
||
segment; loaded by Simulator at startup;
|
||
not compiled in — edit without rebuild
|
||
|
||
shaders/
|
||
phosphor.vert / phosphor.frag — parameterized for P1 and P7 via uniforms
|
||
graticule.vert / graticule.frag
|
||
text.vert / text.frag
|
||
sweep.vert / sweep.frag
|
||
|
||
settings.h — tunable constants:
|
||
All magic numbers live here. Every source file that needs a tunable value
|
||
includes settings.h. No values are hardcoded elsewhere.
|
||
Categories planned:
|
||
- Phosphor P1 color
|
||
- Phosphor P7 strike color, persistence color, decay times (PPI and Chain Home)
|
||
- Sweep line width, brightness, fade trail, periods per scope
|
||
- PAR scan rate; Chain Home PRF high and low
|
||
- Graticule incandescent color, line widths
|
||
- PPI bearing ring tick lengths, label interval, font size
|
||
- PPI range ring line width, label size, label color
|
||
- Cursor color, line width, arc span
|
||
- Noise floor amplitude and variation (Marine A-Scope)
|
||
- Graticule swap animation durations (slide out, bare CRT, slide in)
|
||
- Key-hold acceleration (initial step, rate, max) — separate for goniometer
|
||
- Auto-advance timer interval (120 seconds)
|
||
- Window size and panel layout fractions
|
||
- PAR azimuth/elevation height fractions
|
||
- UI text color and size; cursor readout text size
|
||
- Graticule label color (incandescent)
|
||
- Gain: default (0.5), minimum (0.0), maximum (1.0), keyboard step size
|
||
- Rain clutter filter: default (0.0 = off), minimum (0.0), maximum (1.0), keyboard step size
|
||
- Wave clutter filter: default (0.0 = off), minimum (0.0), maximum (1.0), keyboard step size
|
||
- Key-hold acceleration for gain, rain clutter, and wave clutter keys
|
||
- Terrain bounding box (lat/lon min/max) and processed cell size
|
||
- Terrain material σ° values (soil, rock, concrete, water calm/rough)
|
||
- Terrain material speckle/grain amplitudes (soil, rock, concrete)
|
||
- Terrain classification thresholds (rock elevation, rock slope)
|
||
- Terrain polar grid dimensions (range bins, bearing bins)
|
||
- Terrain clutter brightness scale for marine PPI
|
||
- Terrain boat recompute threshold (degrees bearing offset change)
|
||
- ATC terrain clutter suppressed flag (bool, default true)
|
||
- ATC terrain shadow enabled flag (bool, default true)
|
||
- LiDAR structure height threshold for man-made classification
|
||
- BOAT_PATROL_ROUTE_JSON: path to patrol route file (default "data/patrol_route.json")
|
||
Waypoints and per-segment speeds live in the JSON, not in settings.h, so
|
||
the route can be adjusted without recompiling
|
||
- BOAT_WAYPOINT_ARRIVAL_M: radius within which a waypoint is considered
|
||
reached, advancing to the next (default 50.0 m)
|
||
- BOAT_HEADING_TURN_RATE_DEG_S: maximum turn rate deg/s for heading
|
||
interpolation — realistic, not instantaneous (default 3.0)
|
||
- BOAT_HEADUP_TOLERANCE_DEG: offset within ±this value of boat heading
|
||
triggers "Head-up" label in left panel (default 5.0)
|
||
- BOAT_HEADING_MARKER_COLOR: RGB color of heading marker line (default white)
|
||
- BOAT_HEADING_MARKER_FRACTION: marker length as fraction of scope radius
|
||
(default 0.35)
|
||
- BOAT_HEADING_MARKER_DASH_PX: dash length in pixels (default 8)
|
||
- BOAT_HEADING_MARKER_GAP_PX: gap length in pixels (default 4)
|
||
- BOAT_CLUTTER_MASK_COUNT: number of pre-computed boat shadow masks (default 6)
|
||
- BOAT_MASK_SWITCH_THRESHOLD_M: minimum boat displacement from last selected
|
||
mask waypoint before a new mask is selected (default 500.0 m)
|
||
- METERS_PER_DEGREE: flat-earth scale factor for lat/lon → meters conversion
|
||
(111320.0, valid for 2 nm max range)
|
||
- Patrol boat radar parameters: BOAT_PEAK_POWER_W (6000), BOAT_FREQ_HZ,
|
||
BOAT_HORIZ_BEAMWIDTH_DEG (1.9), BOAT_VERT_BEAMWIDTH_DEG (20.0),
|
||
BOAT_ANTENNA_HEIGHT_M (3.5)
|
||
- Boat PPI range steps: BOAT_RANGE_STEPS[] = {0.5, 1.0, 2.0} miles,
|
||
BOAT_RANGE_STEP_COUNT = 3
|
||
|
||
==================================================================
|
||
|
||
LEFT PANEL TEXT — ONE ENTRY PER SCOPE
|
||
|
||
==================================================================
|
||
|
||
Note: Each left panel renders its description text followed immediately by its
|
||
control table. Every panel also states the next scope name so the visitor knows
|
||
what pressing s will show. The s / S advance note appears in every panel.
|
||
|
||
------------------------------------------------------------------
|
||
PANEL 1 — EXHIBIT INTRODUCTION
|
||
------------------------------------------------------------------
|
||
|
||
Header (all caps, large):
|
||
WELCOME TO THE MUSEUM VINTAGE RADAR EXHIBIT
|
||
|
||
Body:
|
||
Welcome! This exhibit lets you experience how radar worked from the 1940s
|
||
through the 1960s — technology that changed the course of World War 2 and
|
||
shaped modern aviation and maritime safety.
|
||
|
||
Radar works by sending out short bursts of radio energy and listening for
|
||
the echo that bounces back from ships, aircraft, and terrain. By measuring
|
||
the time it takes for the echo to return, the radar calculates how far away
|
||
the object is. Rotating the antenna builds a map of everything around it.
|
||
|
||
This exhibit features seven radar displays. Explore each one at your own pace.
|
||
|
||
Press s at any time to jump to the next display — do not wait for the
|
||
automatic 120-second advance. Press S (shift+s) to go back.
|
||
Pressing any key or control resets the 120-second timer.
|
||
|
||
Next: Marine A-Scope →
|
||
|
||
┌─────┬───────────────────────┐
|
||
│ KEY │ FUNCTION │
|
||
├─────┼───────────────────────┤
|
||
│ s │ Next display │
|
||
│ S │ Previous display │
|
||
└─────┴───────────────────────┘
|
||
|
||
------------------------------------------------------------------
|
||
PANEL 2 — MARINE A-SCOPE
|
||
------------------------------------------------------------------
|
||
|
||
The A-Scope was one of the earliest radar displays, used aboard ships and
|
||
in coastal stations in the 1950s. Unlike the circular display you may have
|
||
seen in movies, the A-Scope sweeps left to right: distance (range) runs
|
||
along the bottom axis, and the height of each spike shows how strong the
|
||
echo is from that direction.
|
||
|
||
To look in a different direction, the operator physically rotates the
|
||
antenna by hand. Use c and v to rotate the antenna on this display.
|
||
|
||
The green glow is the P1 phosphor coating on the inside of the cathode ray
|
||
tube. In a real radar room this was often the only light in the space.
|
||
|
||
The glass panel in front of the screen is the graticule — an etched,
|
||
back-lit calibration scale for measuring range. When you change the maximum
|
||
range setting, watch the operator swap the graticule panel by hand — just
|
||
as it was done in the period.
|
||
|
||
Location: Bellingham Bay, WA — a fictional 100-foot mid-bay platform.
|
||
|
||
Press s to advance. Press S to go back. Auto-advance in 120 seconds.
|
||
Any key press resets the timer. Next: Chain Home A-Scope →
|
||
|
||
┌──────┬──────────────────────────────┐
|
||
│ KEY │ FUNCTION │
|
||
├──────┼──────────────────────────────┤
|
||
│ s/S │ Next / previous scope │
|
||
│ c/v │ Bearing clockwise / CCW │
|
||
│ u/d │ Max range up / down (2,4,6) │
|
||
│ 1/2 │ Gain increase / decrease │
|
||
│ 3/4 │ Rain filter increase / dec │
|
||
│ 5/6 │ Wave filter increase / dec │
|
||
└──────┴──────────────────────────────┘
|
||
|
||
------------------------------------------------------------------
|
||
PANEL 3 — CHAIN HOME A-SCOPE
|
||
------------------------------------------------------------------
|
||
|
||
Chain Home was Britain's early warning radar network, built in the late
|
||
1930s and critical during the Battle of Britain (1940). Instead of a
|
||
rotating dish, it used a fixed array of tall transmit towers that flooded
|
||
the sky over the English Channel with radio energy — a technique called
|
||
floodlighting. Aircraft crossing the Channel reflected this energy back
|
||
to separate receive antennas, sometimes at ranges up to 100 miles.
|
||
|
||
FINDING DIRECTION — THE GONIOMETER
|
||
Because there was no rotating dish, finding the bearing and altitude of a
|
||
target required a technique called nulling. A device called a goniometer
|
||
electronically combined signals from several receive antennas. The operator
|
||
slowly turned the goniometer dial while watching the screen. In most
|
||
positions the signal was strong. But at one precise setting the signal
|
||
suddenly disappeared — this was the null. When nulled, the goniometer was
|
||
pointing directly at the aircraft. Think of it like tuning a radio: you
|
||
search for the silent spot between two stations. Press [ to switch the
|
||
goniometer between bearing (horizontal) and elevation (vertical) modes.
|
||
Use keys 9 and 0 to tune — turn slowly, the null appears suddenly.
|
||
|
||
CALIBRATOR PIPS
|
||
Early electronics drifted, stretching or compressing the range scale.
|
||
The crystal calibrator injects tiny spikes at exact 10-mile intervals.
|
||
Use n to shrink and m to stretch the trace until the pips line up with
|
||
the 10-mile marks on the glass graticule.
|
||
|
||
This display is set at the Chain Home station at Poling, East Sussex,
|
||
facing the English Channel. All targets are simulated German aircraft.
|
||
|
||
Press s to advance. Press S to go back. Auto-advance in 120 seconds.
|
||
Any key press resets the timer. Next: Marine PPI →
|
||
|
||
┌──────┬─────────────────────────────────┐
|
||
│ KEY │ FUNCTION │
|
||
├──────┼─────────────────────────────────┤
|
||
│ s/S │ Next / previous scope │
|
||
│ [ │ Goniometer mode: bearing / elev │
|
||
│ 9/0 │ Goniometer tune left / right │
|
||
│ . │ Toggle PRF: 25 Hz / 12.5 Hz │
|
||
│ n/m │ Calibrator shrink / stretch │
|
||
│ 1/2 │ Gain increase / decrease │
|
||
│ 3/4 │ Rain filter increase / dec │
|
||
│ 5/6 │ Wave filter increase / dec │
|
||
└──────┴─────────────────────────────────┘
|
||
|
||
------------------------------------------------------------------
|
||
PANEL 4 — MARINE PPI SCOPE
|
||
------------------------------------------------------------------
|
||
|
||
The PPI (Plan Position Indicator) became the standard radar display for
|
||
ships from the late 1950s onward. The antenna rotates clockwise and the
|
||
sweep line rotates with it, painting a map of everything within range.
|
||
Targets glow bright blue the instant the sweep passes over them, then
|
||
fade through green to yellow before the sweep returns — this is the P7
|
||
phosphor persistence that keeps the picture visible between sweeps.
|
||
|
||
The dotted range rings give distance reference. The incandescent bearing
|
||
scale shows True direction (0 = North, clockwise to 359).
|
||
|
||
The yellow overlay is a mechanical cursor — a plastic ring and crosshair
|
||
mounted in front of the screen. Use the cursor keys to position it over a
|
||
target; range and bearing read out below the scope.
|
||
|
||
Location: Bellingham Bay, WA — a fictional 100-foot mid-bay platform.
|
||
Targets: AIS-equipped vessels and simulated traffic.
|
||
|
||
Press s to advance. Press S to go back. Auto-advance in 120 seconds.
|
||
Any key press resets the timer. Next: Marine PPI — Boat Scenario →
|
||
|
||
┌──────┬────────────────────────────────────────────────┐
|
||
│ KEY │ FUNCTION │
|
||
├──────┼────────────────────────────────────────────────┤
|
||
│ s/S │ Next / previous scope │
|
||
│ u/d │ Max range up / down (2, 4, 6 mi) │
|
||
│ r/l │ Cursor bearing right / left │
|
||
│ t/y │ Cursor range increase / decrease │
|
||
│ k/j │ Display offset right / left │
|
||
│ │ (zero = North-up; rotate to match heading │
|
||
│ │ = Head-up — a teaching aid on fixed radar) │
|
||
│ 1/2 │ Gain increase / decrease │
|
||
│ 3/4 │ Rain filter increase / decrease │
|
||
│ 5/6 │ Wave filter increase / decrease │
|
||
└──────┴────────────────────────────────────────────────┘
|
||
|
||
------------------------------------------------------------------
|
||
PANEL 5 — POLICE PATROL BOAT PPI
|
||
------------------------------------------------------------------
|
||
|
||
This display is from the radar aboard a Bellingham Police Department
|
||
patrol vessel making its routine waterfront patrol. The scope center
|
||
is the boat — it moves with the patrol route, not fixed in the bay.
|
||
|
||
THE RADAR IS DIFFERENT HERE
|
||
The patrol boat carries a professional 6-kilowatt open-array radar
|
||
with a 1.9-degree beam — narrower than a cheap boat radar, but wider
|
||
than the big 30-kilowatt coastal radar you saw two displays back. The
|
||
blips look noticeably fatter than on the coastal radar. The maximum
|
||
range here is 2 miles: the patrol mission is close-in harbor work,
|
||
not long-range scanning.
|
||
|
||
THE FERRY LANE PROBLEM
|
||
The Bellingham terminal serves Alaska state ferries up to 400 feet
|
||
long. When a ferry departs, anyone in the departure lane — a kayaker,
|
||
a paddleboarder — must get clear. Watch the scope carefully: there
|
||
is a stand-up paddleboarder drifting slowly near the ferry lane.
|
||
Can you spot it?
|
||
|
||
Stand-up paddleboards are very hard to see on radar. No metal, almost
|
||
no freeboard, small size — the radar echo is barely above the noise.
|
||
Try adjusting the wave clutter filter (keys 5 and 6). Turning it up
|
||
reduces the sea clutter that is hiding the target — but it may also
|
||
suppress the target itself. This trade-off is real. Radar does not
|
||
see everything.
|
||
|
||
NORTH-UP AND HEAD-UP
|
||
By default the scope shows North-up: True North at the top, like a
|
||
chart. Press k or j to rotate the display. When the heading marker
|
||
(white dashed line) points straight up, you are in Head-up mode —
|
||
the bow is at the top. This is how most early marine radars worked.
|
||
|
||
Location: Bellingham waterfront patrol, Bellingham Bay, WA.
|
||
Targets: AIS vessels, paddleboarder, random kayakers.
|
||
|
||
Press s to advance. Press S to go back. Auto-advance in 120 seconds.
|
||
Any key press resets the timer. Next: ATC PPI →
|
||
|
||
┌──────┬────────────────────────────────────────────────┐
|
||
│ KEY │ FUNCTION │
|
||
├──────┼────────────────────────────────────────────────┤
|
||
│ s/S │ Next / previous scope │
|
||
│ u/d │ Max range up / down (0.5, 1, 2 mi) │
|
||
│ r/l │ Cursor bearing right / left │
|
||
│ t/y │ Cursor range increase / decrease │
|
||
│ k/j │ Display offset right / left │
|
||
│ │ (0° = North-up; match heading = Head-up) │
|
||
│ 1/2 │ Gain increase / decrease │
|
||
│ 3/4 │ Rain filter increase / decrease │
|
||
│ 5/6 │ Wave filter increase / decrease │
|
||
└──────┴────────────────────────────────────────────────┘
|
||
|
||
------------------------------------------------------------------
|
||
PANEL 6 — AIR TRAFFIC CONTROL PPI SCOPE
|
||
------------------------------------------------------------------
|
||
|
||
This is the Airport Surveillance Radar (ASR) display used by air traffic
|
||
controllers at regional airports in the 1960s. It works on the same
|
||
principle as the marine PPI but covers a larger area (up to 20 miles) and
|
||
is optimised for tracking aircraft rather than ships. The S-Band frequency
|
||
(3 GHz) gives a good balance of range, resolution, and resistance to
|
||
weather clutter for airspace surveillance.
|
||
|
||
Controllers used this display to sequence arriving and departing aircraft,
|
||
identify potential conflicts, and provide navigation guidance to pilots
|
||
flying in low visibility conditions.
|
||
|
||
Location: Bellingham International Airport (BLI), dedicated radar tower.
|
||
Targets: ADS-B equipped aircraft and simulated traffic.
|
||
|
||
Press s to advance. Press S to go back. Auto-advance in 120 seconds.
|
||
Any key press resets the timer. Next: Precision Approach Radar →
|
||
|
||
┌──────┬──────────────────────────────────────────┐
|
||
│ KEY │ FUNCTION │
|
||
├──────┼──────────────────────────────────────────┤
|
||
│ s/S │ Next / previous scope │
|
||
│ u/d │ Max range up / down (5, 10, 15, 20 mi) │
|
||
│ r/l │ Cursor bearing right / left │
|
||
│ t/y │ Cursor range increase / decrease │
|
||
│ 1/2 │ Gain increase / decrease │
|
||
│ 3/4 │ Rain filter increase / decrease │
|
||
└──────┴──────────────────────────────────────────┘
|
||
|
||
------------------------------------------------------------------
|
||
PANEL 7 — PRECISION APPROACH RADAR (PAR)
|
||
------------------------------------------------------------------
|
||
|
||
The Precision Approach Radar was developed during World War 2 and refined
|
||
through the 1950s. It gives a controller a precise picture of exactly where
|
||
an aircraft is on its final approach to the runway — both its left-right
|
||
position and its altitude.
|
||
|
||
Unlike instrument landing systems that require special equipment on the
|
||
aircraft, PAR needed nothing from the pilot's plane. The controller watched
|
||
the display and talked the pilot down over the radio: "You are slightly
|
||
right of centerline, begin correcting left. You are above the glide path,
|
||
begin a slightly steeper descent." This made PAR invaluable when a plane's
|
||
own instruments failed or when visibility dropped to near zero in heavy fog.
|
||
|
||
TOP DISPLAY — AZIMUTH (left-right)
|
||
Shows whether the aircraft is left or right of the runway centerline.
|
||
The centerline runs horizontally through the middle of the display.
|
||
|
||
BOTTOM DISPLAY — ELEVATION (up-down)
|
||
Shows whether the aircraft is above or below the correct glide slope.
|
||
The ideal descent path runs through the center of the display.
|
||
|
||
Both displays expand the inner 5 miles to 70% of the screen width —
|
||
giving maximum precision during the critical final approach phase.
|
||
|
||
Location: South end of Runway 16/34, Bellingham Airport (BLI).
|
||
Active runway: 34 (aircraft landing northbound). All traffic is simulated.
|
||
|
||
Press s to advance. Press S to go back. Auto-advance in 120 seconds.
|
||
Any key press resets the timer. Next: Exhibit Introduction →
|
||
|
||
┌──────┬──────────────────────────────┐
|
||
│ KEY │ FUNCTION │
|
||
├──────┼──────────────────────────────┤
|
||
│ s/S │ Next / previous scope │
|
||
│ 1/2 │ Gain increase / decrease │
|
||
│ 3/4 │ Rain filter increase / dec │
|
||
└──────┴──────────────────────────────┘
|
||
|
||
==================================================================
|
||
|
||
UNITS
|
||
|
||
==================================================================
|
||
|
||
All dimensions are stored and computed in METERS throughout the
|
||
entire system — in shared state, in the database, in shaders, and
|
||
in all target data structures.
|
||
|
||
Any incoming value that arrives in feet (e.g. ADS-B altitude,
|
||
antenna heights from configuration) MUST be converted to meters
|
||
at the data boundary — in RPiReceiver::parseFrame() or
|
||
Simulator::poll() — before the value enters any shared data
|
||
structure. No feet values may appear anywhere inside the system
|
||
after the conversion point.
|
||
|
||
Conversion constant in settings.h:
|
||
FEET_TO_METERS = 0.3048 (exact)
|
||
|
||
==================================================================
|
||
|
||
DEFAULT TARGET DIMENSIONS
|
||
|
||
==================================================================
|
||
|
||
When a target is first seen with no database record, the system
|
||
inserts a row using the defaults below. All values in meters.
|
||
need_update is set TRUE so the operator knows to fill in real data.
|
||
|
||
┌──────────────────┬───────────┬──────────────────┬────────────┐
|
||
│ Category │ Length (m)│ Width/Beam (m) │ Material │
|
||
├──────────────────┼───────────┼──────────────────┼────────────┤
|
||
│ GA aircraft │ 4.0 │ 1.0 (fuselage) │ aluminum │
|
||
│ Commercial a/c │ 30.0 │ 5.0 (fuselage) │ aluminum │
|
||
│ AIS vessel │ 20.0 │ 5.0 (beam) │ steel │
|
||
│ Simulator boat │ 6.0 │ 2.0 (beam) │ fiberglass │
|
||
└──────────────────┴───────────┴──────────────────┴────────────┘
|
||
|
||
Notes:
|
||
- AIS vessels are legally required commercial traffic, so steel is
|
||
the correct default material.
|
||
- Simulator boats are small pleasure craft, so fiberglass is correct.
|
||
- The system defaults all new aircraft to GA. The operator must
|
||
update commercial entries via the --database panel.
|
||
- Height above water/ground defaults to 0 for vessels and 0 for
|
||
aircraft (updated by live data).
|
||
|
||
==================================================================
|
||
|
||
POSTGRESQL DATABASE
|
||
|
||
==================================================================
|
||
|
||
PostgreSQL is installed. Database: radar. User: radar. Password: radar.
|
||
User has full privileges on database radar.
|
||
|
||
Schema (all dimensions in METERS):
|
||
|
||
1. target_type ENUM('AIS', 'ADSB', 'LOCAL')
|
||
2. target_id BIGINT — MMSI for marine; ICAO 24-bit address for
|
||
aircraft; simulator may reuse same space
|
||
3. length_m REAL — length in meters
|
||
4. width_m REAL — beam (vessels) or fuselage width (aircraft)
|
||
in meters
|
||
5. height_m REAL — height above water/ground in meters
|
||
6. material ENUM('fiberglass', 'wood', 'aluminum', 'steel')
|
||
7. need_update BOOLEAN DEFAULT TRUE
|
||
8. last_seen TIMESTAMPTZ — ISO 8601, updated each time target
|
||
is received from any source
|
||
9. last_updated TIMESTAMPTZ — ISO 8601, updated when operator saves
|
||
changes via DB panel
|
||
10. track history — deferred to a future version; not in v1 schema
|
||
|
||
The fields length_m, width_m, height_m, and material are used by the
|
||
CPU to compute RCS before passing to the shader. They are static
|
||
per-target data uploaded to GPU via SSBO or uniform array each frame.
|
||
|
||
Per-frame dynamic fields (location, heading, altitude) are NOT stored
|
||
in the database. They live in TargetBuffer (Threads 2/4) and are
|
||
updated from live Raspberry Pi or simulator data each sweep.
|
||
|
||
==================================================================
|
||
|
||
DATABASE MANAGEMENT PANEL
|
||
|
||
==================================================================
|
||
|
||
Activated by command-line flag: --database
|
||
In this mode NO radar display is shown. main.cpp skips all scope
|
||
and shader initialization and starts the Dear ImGui loop instead.
|
||
|
||
Toolkit: Dear ImGui, integrated with GLFW/OpenGL 3.3.
|
||
Source lives in src/imgui/ and is compiled directly into the project
|
||
(no separate install step required).
|
||
|
||
The panel provides:
|
||
- Scrollable table of all targets with need_update highlighted
|
||
- Inline edit fields for length, width, height, material
|
||
- Dropdown for target_type
|
||
- Save button that writes to PostgreSQL via libpq
|
||
- Quit button to exit the DB panel (then start main exhibit separately)
|
||
|
||
==================================================================
|
||
|
||
RADAR EQUATION AND BLOOM
|
||
|
||
==================================================================
|
||
|
||
The radar equation is evaluated CPU-side to compute received power
|
||
(P_r) for each target. The result drives target brightness and bloom.
|
||
Computation happens in TrafficCop after each poll, outside any mutex
|
||
lock (read-only access to target physical data at that point).
|
||
|
||
The computed brightness value is uploaded per-target to the GPU via
|
||
SSBO or uniform array. The phosphor shader reads it to set amplitude.
|
||
|
||
BLOOM POST-PROCESSING (bloom.vert / bloom.frag):
|
||
Separate shader pair. Pipeline:
|
||
1. Render targets to offscreen FBO at full computed brightness
|
||
2. Apply two-pass Gaussian blur to pixels above luminance threshold
|
||
3. Additively blend blurred result back onto main framebuffer
|
||
Constants in settings.h: BLOOM_LUMINANCE_THRESHOLD,
|
||
BLOOM_BLUR_RADIUS_PX, BLOOM_BLEND_STRENGTH
|
||
|
||
RADAR PARAMETERS — MARINE (X Band):
|
||
Frequency: 9225 MHz
|
||
Peak power: 30 kW
|
||
Horizontal beamwidth: 0.5 degrees
|
||
Vertical beamwidth: 20 degrees
|
||
Antenna length: 15 feet (4.572 m)
|
||
Antenna height: 50 feet (15.24 m) above surface
|
||
|
||
RADAR PARAMETERS — ATC (S Band):
|
||
Frequency: 3000 MHz (3 GHz)
|
||
Peak power: 25 kW (exhibit value; real ASR is higher)
|
||
Horizontal beamwidth: 1.4 degrees
|
||
Vertical beamwidth: 5 degrees
|
||
Antenna width: 20 feet (6.096 m)
|
||
Antenna height: 50 feet (15.24 m) above runway
|
||
|
||
RADAR PARAMETERS — CHAIN HOME (AMES Type 1):
|
||
Frequency: 30 MHz
|
||
Peak power: 500 kW
|
||
Pulse width: 20 microseconds
|
||
PRF: 25 Hz or 12.5 Hz (operator selectable)
|
||
Transmit gain (Gt): ~7.5 linear (~8.7 dBi) — floodlight, not a beam
|
||
formula: G = 30000 / (az_bw_deg × el_bw_deg)
|
||
with az ~100 deg, el ~40 deg
|
||
Receive gain (Gr): 4 to 10 linear (6–10 dBi)
|
||
System loss: ~8 dB (transmission line)
|
||
Bistatic: yes — separate transmit and receive antennas
|
||
RCS resonance: at 30 MHz (lambda ~10 m) aircraft are in the
|
||
Mie/resonant scattering region; RCS is multiplied
|
||
by CHAIN_HOME_RCS_RESONANCE_FACTOR (default 3.0)
|
||
before radar equation computation. Applied to
|
||
Chain Home targets only.
|
||
|
||
RADAR PARAMETERS — PAR (X Band):
|
||
Peak power: 100 kW
|
||
Frequency: ~10 GHz (lambda ~3 cm)
|
||
Antenna size: ~5 meters
|
||
Beamwidth: ~0.34 degrees (lambda/D)
|
||
Horizontal scan: 20 degrees total
|
||
Vertical scan: 10 degrees total
|
||
Pulse width: short (high range resolution)
|
||
PRF: high (~30 Hz alternating az/el)
|
||
|
||
RADAR PARAMETERS — PATROL BOAT (X Band):
|
||
Frequency: 9300–9500 MHz (X-band; treat same as marine for exhibit)
|
||
Peak power: 6 kW
|
||
Antenna type: Open array, 24-inch (610 mm)
|
||
Horizontal beamwidth: 1.9 degrees
|
||
Vertical beamwidth: 20 degrees
|
||
Antenna height: 3.5 m above waterline (flybridge or radar arch)
|
||
Radar horizon: ~4.3 nm to sea-level target
|
||
Operational max range: 2 miles (mission-limited, not horizon-limited)
|
||
|
||
NOTE — beamwidth comparison:
|
||
Fixed coastal marine: 0.5° — sharp blips, high azimuth resolution
|
||
Police patrol boat: 1.9° — noticeably fatter blips; good exhibit contrast
|
||
Consumer radome: 4–6° — poorest resolution (not used in this exhibit)
|
||
|
||
NOTE — small target detection in sea clutter:
|
||
The narrower 1.9° beam illuminates ~1/3 the sea surface area per range
|
||
cell compared to a 5° radome, improving signal-to-clutter ratio by ~5 dB.
|
||
Even so, a stand-up paddleboard (RCS ~0.1–0.5 m²) is marginal in any chop.
|
||
Detection is realistic only in near-calm conditions at ≤1 mile.
|
||
|
||
All radar parameters shall have corresponding constexpr constants
|
||
in settings.h so they can be tuned without touching equation code.
|
||
|
||
==================================================================
|
||
|
||
TERRAIN AND LAND CLUTTER
|
||
|
||
==================================================================
|
||
|
||
DATA SOURCES
|
||
map/charts_enc/US5WA45M.000
|
||
S-57 Electronic Navigational Chart; coastline polygon,
|
||
pier/breakwater geometry, named landmarks. Parsed with GDAL/OGR.
|
||
|
||
map/charts_enc/n48_w123_1arc_v3.tif
|
||
USGS 1 arc-second (~30 m) DEM GeoTIFF. Terrain elevation for the
|
||
Bellingham area — Cascades, Chuckanut Mountain, coastal lowlands.
|
||
|
||
map/lidar_raw/wa2016_west_dem_J1364939.zip
|
||
2016 western WA LiDAR DEM. Resolves waterfront structures,
|
||
breakwaters, piers, Boulevard Park boardwalk, and other man-made
|
||
features at ~1 m resolution.
|
||
|
||
map/lidar_raw/wa2022_nooksack_dem_J1364940.zip
|
||
2022 Nooksack basin LiDAR DEM. Covers the delta and lowlands
|
||
northeast of Bellingham Bay; used for terrain shadowing from the
|
||
northeast quadrant.
|
||
|
||
All three sources are processed offline by terrain_preprocess into
|
||
binary grids in map/lidar_processed/. The raw files are never opened
|
||
at exhibit runtime. See TERRAIN PREPROCESSING section below.
|
||
|
||
GDAL (libgdal-dev) is a required build dependency for
|
||
terrain_preprocess. It is NOT linked into the main radar binary.
|
||
|
||
CLASSES
|
||
|
||
TerrainMap (Thread 1, read-only after init)
|
||
Loaded once at startup from map/lidar_processed/. Reads the four
|
||
binary grids and the metadata JSON. Provides:
|
||
- Elevation query by lat/lon
|
||
- Material query by lat/lon
|
||
- Pre-computed polar clutter grid (range × bearing bins) per
|
||
fixed radar location
|
||
- Line-of-sight shadow mask per radar location
|
||
Thread 1 only after init; no mutex required.
|
||
|
||
LandClutter (Thread 1)
|
||
Queries TerrainMap to generate clutter returns for each scope.
|
||
Called once per full sweep rotation, not once per frame.
|
||
- Marine A-Scope: produces amplitude samples along the current
|
||
antenna bearing for injection into the range trace.
|
||
- Marine PPI / ATC PPI: produces a polar texture (range × bearing)
|
||
uploaded to the GPU once per sweep period.
|
||
|
||
TERRAIN MATERIALS AND RCS
|
||
|
||
Each terrain cell is classified as one of four materials. Normalized
|
||
backscatter coefficient σ° (linear, m²/m²) is defined in settings.h:
|
||
|
||
TERRAIN_SIGMA0_SOIL ~0.010 (−20 dB) — moist vegetated soil
|
||
TERRAIN_SIGMA0_ROCK ~0.032 (−15 dB) — exposed rock, rough face
|
||
TERRAIN_SIGMA0_CONCRETE ~0.100 (−10 dB) — smooth hard surface;
|
||
structures produce
|
||
corner-reflector effect
|
||
TERRAIN_SIGMA0_WATER_CALM ~0.0003 (−35 dB) — open water, calm;
|
||
mostly specular, low
|
||
backscatter
|
||
TERRAIN_SIGMA0_WATER_ROUGH ~0.010 (−20 dB) — choppy sea, sea clutter
|
||
|
||
Classification rules applied by terrain_preprocess at build time:
|
||
- Below sea level or inside S-57 coastline polygon → water
|
||
- Inside S-57 pier/breakwater/wharf feature → concrete
|
||
- Elevation > TERRAIN_ROCK_THRESHOLD_M AND slope
|
||
> TERRAIN_ROCK_SLOPE_THRESHOLD_DEG → rock
|
||
- All remaining land cells → soil
|
||
|
||
RADAR EQUATION FOR TERRAIN (area-extensive / clutter form)
|
||
|
||
P_r = (P_t × G² × λ² × σ° × A_cell) / ((4π)³ × R⁴)
|
||
|
||
A_cell is the terrain cell area projected along the beam.
|
||
Evaluated CPU-side in LandClutter for each illuminated cell.
|
||
Result drives per-cell brightness in the clutter texture.
|
||
Same radar parameters (P_t, G, λ) used for point targets.
|
||
|
||
SHADOW / LINE-OF-SIGHT MASKING
|
||
|
||
Computed by terrain_preprocess for each fixed radar location and
|
||
stored in the processed data as uint8 shadow masks. Algorithm: march
|
||
outward from the radar along each azimuth radial, tracking the maximum
|
||
elevation angle seen. Any cell whose surface angle falls below that
|
||
maximum is shadowed — clutter amplitude = 0 and target returns through
|
||
that cell are attenuated proportionally.
|
||
|
||
Shadow masks stored per radar location:
|
||
map/lidar_processed/shadow_marine.u8 — marine bay platform
|
||
map/lidar_processed/shadow_atc.u8 — BLI ATC tower
|
||
|
||
Bearing offset (k/j keys on PPI scopes):
|
||
The k/j keys change display heading only — which direction appears
|
||
at the top of the scope. They do NOT move the radar geographically.
|
||
The shadow mask is unchanged. The terrain clutter shader receives
|
||
the offset as a rotation uniform and samples the polar texture at
|
||
the offset angle. Zero CPU overhead; no shadow recomputation.
|
||
|
||
Boat PPI scope scenario:
|
||
When BoatPPIScope is active, SharedRenderState.boatModeActive is set
|
||
TRUE. LandClutter uses boatLatDeg / boatLonDeg as the polar grid origin
|
||
instead of the fixed marine platform position.
|
||
|
||
Shadow mask selection: terrain_preprocess pre-computes BOAT_CLUTTER_MASK_COUNT
|
||
shadow masks for waypoints evenly spaced around the simulated route.
|
||
These are written as:
|
||
map/lidar_processed/shadow_boat_NNN.u8 — one file per boat mask waypoint
|
||
NNN = zero-padded index
|
||
At runtime, TerrainMap::selectNearestBoatMask() scans the BOAT_CLUTTER_MASK_COUNT
|
||
waypoints and returns the index whose position is nearest to the current boat
|
||
lat/lon (straight-line distance). The selected mask changes at most once per
|
||
4-second sweep when the boat has moved more than BOAT_MASK_SWITCH_THRESHOLD_M
|
||
(default 500 m) from the last selected waypoint position.
|
||
|
||
Since Bellingham Bay is open water, the major terrain shadowing features
|
||
(Chuckanut Mountain, Eliza Island, Lummi Island) are visible from most bay
|
||
positions. The nearest-mask approximation introduces negligible error within
|
||
the 500 m switching threshold.
|
||
|
||
The terrain clutter shader receives the boat position offset as a translation
|
||
uniform (u_radarOffsetM, a vec2) in addition to the bearing offset rotation.
|
||
The shader adds this offset when sampling the polar texture so coastline
|
||
features appear at their correct positions relative to the moving radar.
|
||
|
||
PER-SCOPE TERRAIN BEHAVIOR
|
||
|
||
Marine A-Scope:
|
||
Land returns appear as stable blips at fixed ranges on the current
|
||
antenna bearing. Concrete structures give strong returns; soil/hills
|
||
give moderate returns; shadowed areas return nothing. Period
|
||
operators were trained to discard stable blips when searching for
|
||
moving targets.
|
||
|
||
Marine PPI (fixed platform and boat heading offset):
|
||
Land clutter is fully visible. Coastline, hills, piers, and
|
||
breakwaters paint exactly as on a real period marine radar. The
|
||
clutter texture updates once per 4-second sweep rotation.
|
||
|
||
ATC PPI:
|
||
Moving Target Indicator (MTI) cancellation suppresses land clutter.
|
||
Controlled by ATC_TERRAIN_CLUTTER_SUPPRESSED (default true) in
|
||
settings.h. Terrain shadowing of aircraft IS applied — controlled
|
||
by ATC_TERRAIN_SHADOW_ENABLED (default true). Aircraft approaching
|
||
behind a ridge appear at reduced amplitude or disappear until they
|
||
clear the ridge, as on period ASR equipment.
|
||
|
||
Chain Home A-Scope:
|
||
No terrain data applied. The exhibit scenario faces the English
|
||
Channel; the transmitter floodlights the sea. Land returns are
|
||
not simulated for this scope.
|
||
|
||
PAR:
|
||
No terrain clutter. PAR points at a single fixed approach path;
|
||
the narrow beam and short range make land returns negligible.
|
||
|
||
TERRAIN CLUTTER SHADER
|
||
|
||
terrain_clutter.vert / terrain_clutter.frag
|
||
Renders the polar clutter texture as a quad overlay on the PPI
|
||
scope. Converts screen coordinates to polar, samples the clutter
|
||
texture, and outputs P7-compatible phosphor color and alpha so
|
||
terrain returns decay on the same timescale as target echoes.
|
||
Uniforms:
|
||
u_bearingOffsetDeg — display heading correction (default 0.0)
|
||
u_radarOffsetM — vec2 (dx_m, dy_m) from fixed marine platform origin
|
||
to current radar position; (0,0) for fixed scopes,
|
||
non-zero for Boat PPI as boat moves around the bay
|
||
u_clutterSuppressed — bool; 1 = suppress (ATC mode)
|
||
u_maxRangeM — current scope max range in meters
|
||
u_clutterBrightness — TERRAIN_MARINE_CLUTTER_BRIGHTNESS scale
|
||
Used by MarinePPIScope, BoatPPIScope, and ATCPPIScope.
|
||
|
||
==================================================================
|
||
|
||
TERRAIN PREPROCESSING
|
||
|
||
==================================================================
|
||
|
||
The raw LiDAR zip files cannot be used at exhibit runtime. The offline
|
||
tool terrain_preprocess processes them once and writes ready-to-use
|
||
binary grids to map/lidar_processed/. Must be run before first launch
|
||
and re-run whenever TERRAIN_BBOX_* or TERRAIN_PROCESSED_CELL_DEG
|
||
constants in settings.h change.
|
||
|
||
TOOL
|
||
Build target: terrain_preprocess (separate CMake executable)
|
||
Source: src/terrain_preprocess.cpp
|
||
Links: GDAL only; NOT linked into the main radar binary
|
||
Run: ./terrain_preprocess (from build directory)
|
||
|
||
PIPELINE (runs in order)
|
||
1. Unzip both LiDAR archives to a temp directory.
|
||
2. Inventory tiles — enumerate .tif / .img files, check CRS and
|
||
native resolution of each.
|
||
3. Merge tiles within each survey into a GDAL VRT mosaic.
|
||
4. Warp both surveys to WGS84 (EPSG:4326) at
|
||
TERRAIN_PROCESSED_CELL_DEG resolution.
|
||
5. Crop to bounding box: TERRAIN_BBOX_LAT_MIN/MAX,
|
||
TERRAIN_BBOX_LON_MIN/MAX.
|
||
6. Merge the two surveys — where they overlap, the 2022 Nooksack
|
||
data wins over the 2016 western data (higher vintage / resolution).
|
||
7. Material classification:
|
||
Load S-57 ENC (US5WA45M.000) via GDAL/OGR.
|
||
Apply classification rules described in TERRAIN section above.
|
||
8. Compute shadow masks using radial elevation-angle march along each
|
||
azimuth bearing for each of the following radar positions:
|
||
- Fixed marine platform (lat 48.7436, lon -122.5647)
|
||
- ATC tower at BLI
|
||
- BOAT_CLUTTER_MASK_COUNT boat waypoints from BOAT_SIM_WAYPOINTS[]
|
||
All shadow masks use the same algorithm; only the origin differs.
|
||
9. Write to map/lidar_processed/:
|
||
elevation.f32 float32 row-major grid, meters, WGS84
|
||
material.u8 uint8 per cell (0=water 1=soil 2=rock 3=concrete)
|
||
shadow_marine.u8 uint8 visibility mask for marine radar
|
||
shadow_atc.u8 uint8 visibility mask for ATC radar
|
||
shadow_boat_000.u8 …
|
||
shadow_boat_NNN.u8 uint8 visibility masks for boat waypoints;
|
||
NNN = zero-padded waypoint index
|
||
terrain_meta.json grid dimensions, lat/lon origin, cell size,
|
||
source file checksums, processing date,
|
||
boat mask waypoint lat/lon list
|
||
|
||
RUNTIME VALIDATION
|
||
TerrainMap reads terrain_meta.json at startup and compares the stored
|
||
bounding box and cell-size values against current settings.h constants.
|
||
If they differ it prints a warning and continues with stale data:
|
||
WARNING: terrain data was built with different bounding box or
|
||
cell size — re-run terrain_preprocess before exhibit launch.
|
||
The exhibit does not crash; it runs with the old grid.
|
||
|
||
OUTPUT FILES (map/lidar_processed/)
|
||
elevation.f32 — float32 elevation grid
|
||
material.u8 — uint8 material classification grid
|
||
shadow_marine.u8 — uint8 line-of-sight mask, marine radar
|
||
shadow_atc.u8 — uint8 line-of-sight mask, ATC radar
|
||
shadow_boat_000.u8 … — uint8 line-of-sight masks for boat waypoints
|
||
shadow_boat_NNN.u8 (BOAT_CLUTTER_MASK_COUNT files total)
|
||
terrain_meta.json — metadata, provenance record, and boat mask
|
||
waypoint lat/lon list used for nearest-mask lookup
|
||
|
||
These files are the only terrain inputs at runtime.
|
||
The raw zip archives in map/lidar_raw/ are never opened by
|
||
the exhibit binary.
|
||
|
||
==================================================================
|
||
|
||
BOAT SCENARIO
|
||
|
||
==================================================================
|
||
|
||
The boat scenario (scope 5 — Police Patrol Boat PPI) simulates a Bellingham
|
||
Police Department patrol vessel making its waterfront patrol. The radar is a
|
||
6 kW professional open-array unit (1.9° beamwidth), not the same hardware as
|
||
the fixed coastal marine radar. The radar origin moves with the boat every sweep.
|
||
|
||
PATROL ROUTE FILE — data/patrol_route.json
|
||
|
||
Loaded by the Simulator at startup. Not compiled in — the route can be
|
||
refined without a rebuild. Format (approximate):
|
||
|
||
{
|
||
"waypoints": [
|
||
{ "lat": 48.7530, "lon": -122.5150, "speed_kt": 10.0,
|
||
"zone": "Ferry lane — open waterfront" },
|
||
{ "lat": 48.7480, "lon": -122.5050, "speed_kt": 4.0,
|
||
"zone": "Near Squalicum breakwater" },
|
||
{ "lat": 48.7460, "lon": -122.5120, "speed_kt": 10.0,
|
||
"zone": "Open waterfront west" },
|
||
{ "lat": 48.7380, "lon": -122.5200, "speed_kt": 10.0,
|
||
"zone": "Boulevard Park approach" },
|
||
{ "lat": 48.7340, "lon": -122.5150, "speed_kt": 4.0,
|
||
"zone": "Taylor Dock area" },
|
||
{ "lat": 48.7320, "lon": -122.5050, "speed_kt": 4.0,
|
||
"zone": "Community Boating Center" }
|
||
],
|
||
"loop": "reverse"
|
||
}
|
||
|
||
"loop": "reverse" means the boat reaches the last waypoint then reverses
|
||
direction back through the list — a back-and-forth patrol, not a closed loop.
|
||
All coordinates are open water; marina and Whatcom Waterway entry deferred.
|
||
Adjust lat/lon values to keep the vessel in navigable water once the ENC
|
||
coastline is loaded. Values above are starting approximations.
|
||
|
||
BOAT NAVIGATION SIMULATION (Simulator, Thread 4)
|
||
|
||
The Simulator maintains a BoatNavigator sub-object that loads the JSON route
|
||
at startup and advances the vessel each time TrafficCop polls. Data returned
|
||
to TrafficCop alongside the regular target list:
|
||
boat_lat_deg — current latitude (degrees WGS84)
|
||
boat_lon_deg — current longitude (degrees WGS84)
|
||
boat_heading_deg — current true heading (degrees, 0 = north)
|
||
boat_speed_kts — current speed from active waypoint segment
|
||
boat_zone_str — zone label string for left panel display
|
||
|
||
TrafficCop writes these to SharedRenderState under Mutex A.
|
||
Thread 1 reads them every frame when BoatPPIScope is active.
|
||
|
||
Navigation algorithm (runs in Simulator::poll(), Thread 4):
|
||
1. Compute great-circle bearing from current position to next waypoint.
|
||
2. Rotate boatHeadingDeg toward that bearing at up to
|
||
BOAT_HEADING_TURN_RATE_DEG_S per elapsed second (clamped).
|
||
3. Advance position along current heading at the current segment speed_kt.
|
||
4. If distance to next waypoint < BOAT_WAYPOINT_ARRIVAL_M, advance index.
|
||
On reverse-loop: flip traversal direction at each end.
|
||
5. Store updated state in BoatNavigator; return to TrafficCop on poll.
|
||
|
||
SIMULATED SMALL TARGETS
|
||
|
||
The Simulator generates two categories of small targets for the patrol scope:
|
||
|
||
Scripted paddleboarder:
|
||
A single stand-up paddleboarder drifts slowly across the ferry departure
|
||
lane on a fixed looping path (~0.5 kt, random drift added). RCS set to
|
||
BOAT_SUP_RCS_M2 (default 0.2 m²). This target also appears on the fixed
|
||
Marine PPI scope (same Bellingham Bay coverage area, same target pipeline).
|
||
|
||
Random kayakers:
|
||
BOAT_RANDOM_KAYAK_COUNT (default 2) kayaks wander within a defined zone
|
||
near the ferry terminal and harbor mouth. RCS set to BOAT_KAYAK_RCS_M2
|
||
(default 0.4 m² — slightly larger than SUP due to hull and occupant).
|
||
Random targets also appear on the fixed Marine PPI.
|
||
|
||
These use the same radar equation path as all other targets; the low RCS
|
||
values naturally produce faint, intermittent blips in any sea state, which
|
||
is the exhibit's intended behavior. No special-casing required.
|
||
|
||
Settings.h additions for small targets:
|
||
BOAT_SUP_RCS_M2 0.2 — stand-up paddleboard + paddler RCS (m²)
|
||
BOAT_KAYAK_RCS_M2 0.4 — kayak + occupant RCS (m²)
|
||
BOAT_RANDOM_KAYAK_COUNT 2 — number of random kayak targets
|
||
BOAT_KAYAK_ZONE_LAT/LON — bounding box for random kayak positions
|
||
|
||
SHARED STATE ADDITIONS
|
||
|
||
SharedRenderState new fields (all under Mutex A):
|
||
float boatLatDeg = 0.0f (set from JSON WP0 at startup)
|
||
float boatLonDeg = 0.0f
|
||
float boatHeadingDeg = 0.0f
|
||
float boatSpeedKts = 0.0f
|
||
char boatZone[64] = "" — zone label, copied from JSON waypoint
|
||
bool boatModeActive = false — set TRUE by ScopeManager when BoatPPIScope
|
||
active, FALSE for all other scopes
|
||
|
||
TARGET PROJECTION FOR MOVING RADAR ORIGIN
|
||
|
||
For fixed scopes, target positions are projected from a known constant origin.
|
||
For the Boat PPI, TrafficCop recalculates each target's polar coordinates
|
||
relative to the boat's current position after every poll.
|
||
|
||
Flat-earth projection (adequate for 2 nm max range):
|
||
dx_m = (target_lon − boat_lon) × cos(boat_lat × π/180) × METERS_PER_DEGREE
|
||
dy_m = (target_lat − boat_lat) × METERS_PER_DEGREE
|
||
range_m = sqrt(dx_m² + dy_m²)
|
||
bearing_deg = atan2(dx_m, dy_m) × 180/π (adjusted to 0–360, CW from north)
|
||
|
||
METERS_PER_DEGREE = 111320.0 (constexpr in settings.h).
|
||
Result (range_m, bearing_deg, brightness) stored in TargetBuffer under Mutex B.
|
||
|
||
HEADING MARKER RENDERING
|
||
|
||
BoatPPIScope::renderHeadingMarker() runs inside render() after all phosphor
|
||
content, using the graticule shader so the line never decays.
|
||
|
||
Geometry: dashed line from scope center to
|
||
center + BOAT_HEADING_MARKER_FRACTION × scope_radius
|
||
in the direction (boatHeadingDeg + bearingOffsetDeg).
|
||
Color: BOAT_HEADING_MARKER_COLOR (default white).
|
||
Dash/gap: BOAT_HEADING_MARKER_DASH_PX / BOAT_HEADING_MARKER_GAP_PX.
|
||
|
||
DISPLAY MODE LOGIC
|
||
|
||
Every frame, BoatPPIScope computes:
|
||
float diff = fabs(fmod(bearingOffsetDeg − boatHeadingDeg + 540.0f, 360.0f) − 180.0f)
|
||
mode = (diff <= BOAT_HEADUP_TOLERANCE_DEG) ? "Head-up" : "North-up"
|
||
Rendered as white text in the left-panel status area.
|
||
|
||
TERRAIN CLUTTER AND BREAKWATER SHADOWS
|
||
|
||
See SHADOW / LINE-OF-SIGHT MASKING → Boat PPI scope scenario for the
|
||
mask-selection algorithm and shadow_boat_NNN.u8 file set.
|
||
|
||
The Squalicum Harbor outer breakwater is a significant shadow-caster.
|
||
From any open-water patrol position the interior of the marina basin is
|
||
shadowed — nothing behind the breakwater is visible. This is realistic
|
||
and is visible on the scope as a sharp shadow arc on the far side of the
|
||
breakwater return.
|
||
|
||
At the start of each 4-second sweep, BoatPPIScope::updateLandClutter() calls:
|
||
1. TerrainMap::selectNearestBoatMask(boatLatDeg, boatLonDeg)
|
||
2. LandClutter::generateForBoat(boatLatDeg, boatLonDeg, maskIndex)
|
||
3. Upload new polar clutter texture to GPU.
|
||
If the mask index is unchanged from the previous sweep, steps 2–3 are skipped.
|
||
|
||
Terrain clutter shader receives:
|
||
u_radarOffsetM = vec2(
|
||
(boatLon − MARINE_PLATFORM_LON) × cos(boatLat × π/180) × METERS_PER_DEGREE,
|
||
(boatLat − MARINE_PLATFORM_LAT) × METERS_PER_DEGREE)
|
||
|
||
V1 GEOMETRY SCOPE (open water only — marina deferred)
|
||
|
||
Vector features needed from NOAA ENC 18424 for the v1 patrol route:
|
||
- Outer shoreline of Bellingham Bay
|
||
- Squalicum Harbor outer breakwater (solid, strong return, shadow-caster)
|
||
- Ferry terminal structure (Bellingham Cruise Terminal area)
|
||
- Taylor Dock pier outline (weak return — wood, but pilings visible)
|
||
- Boulevard Park shoreline
|
||
Internal marina dock fingers, Whatcom Waterway channel walls, and Georgia
|
||
Pacific / Waterfront District structures are deferred until the patrol
|
||
route is extended into those areas in a future version.
|
||
|
||
==================================================================
|
||
|
||
FILE LAYOUT (COMPLETE — including additions)
|
||
|
||
==================================================================
|
||
|
||
src/
|
||
main.cpp
|
||
scope_manager.h / scope_manager.cpp
|
||
scope.h / scope.cpp
|
||
scope_intro.h / scope_intro.cpp
|
||
scope_ascope.h / scope_ascope.cpp
|
||
scope_marine_a.h / scope_marine_a.cpp
|
||
scope_chain_home.h / scope_chain_home.cpp
|
||
scope_ppi.h / scope_ppi.cpp
|
||
scope_marine_ppi.h / scope_marine_ppi.cpp
|
||
scope_boat_ppi.h / scope_boat_ppi.cpp
|
||
scope_atc_ppi.h / scope_atc_ppi.cpp
|
||
scope_par.h / scope_par.cpp
|
||
phosphor.h / phosphor.cpp
|
||
graticule.h / graticule.cpp
|
||
left_panel.h / left_panel.cpp
|
||
shared_render_state.h / shared_render_state.cpp
|
||
target_buffer.h / target_buffer.cpp
|
||
traffic_cop.h / traffic_cop.cpp
|
||
simulator.h / simulator.cpp
|
||
knob_panel.h / knob_panel.cpp
|
||
rpi_receiver.h / rpi_receiver.cpp
|
||
db_panel.h / db_panel.cpp — Dear ImGui DB management panel
|
||
(--database mode only)
|
||
terrain_map.h / terrain_map.cpp — DEM load, shadow mask, polar clutter
|
||
grid; read-only after init, Thread 1
|
||
land_clutter.h / land_clutter.cpp — per-sweep clutter arrays for A-scope
|
||
range trace and PPI clutter texture
|
||
terrain_preprocess.cpp — standalone offline preprocessing tool;
|
||
separate CMake target; links GDAL only;
|
||
NOT part of main radar binary
|
||
settings.h — all constexpr constants; no .cpp
|
||
|
||
imgui/ — Dear ImGui source, compiled in
|
||
imgui.h / imgui.cpp
|
||
imgui_impl_glfw.h / imgui_impl_glfw.cpp
|
||
imgui_impl_opengl3.h / imgui_impl_opengl3.cpp
|
||
imgui_draw.cpp / imgui_tables.cpp / imgui_widgets.cpp
|
||
|
||
shaders/
|
||
phosphor.vert / phosphor.frag — P1 and P7 via uniforms
|
||
graticule.vert / graticule.frag
|
||
text.vert / text.frag
|
||
sweep.vert / sweep.frag
|
||
bloom.vert / bloom.frag — FBO bloom post-processing
|
||
terrain_clutter.vert / terrain_clutter.frag — polar clutter texture overlay
|
||
on PPI; P7-compatible decay;
|
||
bearing offset rotation uniform;
|
||
u_radarOffsetM vec2 for boat origin
|