Add feature for cursor and range rings

This commit is contained in:
2026-04-05 18:25:42 -07:00
parent 6cf9091c05
commit 337d423639
18 changed files with 779 additions and 284 deletions

View File

@@ -22,6 +22,8 @@ The proposed location of the radar antenna is at the dock of the Community
boating center in Bellingham, Washington. boating center in Bellingham, Washington.
Location is 48.72° N Latitude and -122.51° W Longitude Location is 48.72° N Latitude and -122.51° W Longitude
Zero degrees on Radar Scope is True North.
The proposed maximum range is 15 miles. The proposed maximum range is 15 miles.
Selectable ranges should be 2, 5, 10, and 15 miles Selectable ranges should be 2, 5, 10, and 15 miles
@@ -119,7 +121,7 @@ Display colors:
1. A Scope is P1 (same as oscilloscope) 1. A Scope is P1 (same as oscilloscope)
2. A Scope graticule is incandescent color 2. A Scope graticule is incandescent color
3. PPI scope active targets including scatters, graticule range rings, shoreline, 3. PPI scope active targets including scatters, graticule range rings, shoreline,
all p7 phosphor (active white blue) all p7 phosphor (active blue)
4. All persistence (also p7 greenish yellow persistence) for PPI scope active targets including 4. All persistence (also p7 greenish yellow persistence) for PPI scope active targets including
scatters, graticule range rings, shoreline scatters, graticule range rings, shoreline
5. PPI scope bearing ring and ticks is incandescent color. 5. PPI scope bearing ring and ticks is incandescent color.
@@ -135,12 +137,16 @@ is 15 miles from the center.
Signal strength: Signal strength:
Need to have following fixed signal strength: Need to have following fixed signal strength:
1. large ship would be bright and blooming Size is part of data for some targets (based on AIS and ADS-B
2. yachts would be bright but not blooming data. Use size of boat (usually in feet) expressed in length
3. sailboats would be medium bright and not blooming and width. Use those values in relation of the heading if known.
4. kayaks and rowboats would be small and dim Unknown heading shall assume size is between length and width.
1. large ship (over 100 feet) would be bright and blooming slightly
2. yachts ( 50 to 100 feet) feet would be bright but not blooming
3. sailboats ( 10 to 50 feet) would be medium bright and not blooming
4. kayaks and rowboats would be dim
5. May consider fading small boats like kayaks 5. May consider fading small boats like kayaks
and sailboats above 3 miles and sailboats above 10 miles
Details of each feature: Details of each feature:
@@ -159,6 +165,7 @@ A scope:
for each range and a horizontal line at the bottom. Left of the screen you for each range and a horizontal line at the bottom. Left of the screen you
have the words "SIGNAL STRENGTH" and bottom of the screen you have the words "SIGNAL STRENGTH" and bottom of the screen you
have the words "RANGE" have the words "RANGE"
Movement of graticles would be a bit slow and erratic (maybe about 10 seconds)
Note on screen update vs pulse repetition frequency. We need to be careful Note on screen update vs pulse repetition frequency. We need to be careful
since we have no control of the display update frequency and need since we have no control of the display update frequency and need
@@ -166,10 +173,10 @@ to do whatever is needed to reduce aliasing or flickering
PPI Scope active targets PPI Scope active targets
1. Active boats/planes; brightness determined by size as noted above 1. Active boats/planes; brightness determined by size as noted above
2. Blue white color 2. Blue
PPI Scope range rings PPI Scope range rings
1. blue white (dim) 1. blue (dim)
2. These change if operator changes max range 2. These change if operator changes max range
PPI Scope cursor (In the day, this was a moveable plastic PPI Scope cursor (In the day, this was a moveable plastic
@@ -184,6 +191,7 @@ PPI Scope Bearing ticks and ring
3. Degrees count clockwise. 3. Degrees count clockwise.
4. Use text for every 10 degrees, but text on outside of ring. 4. Use text for every 10 degrees, but text on outside of ring.
5. Have ring around tick marks 5. Have ring around tick marks
6. 2nd ring around the text marks
Controls: Controls:
@@ -283,12 +291,17 @@ Order of testing features.
4. PPI scope range rings; both active display and persistence display - test 4. PPI scope range rings; both active display and persistence display - test
for each range settings; hold for 5 seconds each for each range settings; hold for 5 seconds each
5. PPI scope active target operation, as well as persistance. Create 5. PPI scope active target operation, as well as persistance. Create
four fake targets, one small, one large and two very large with four fake targets: one small, one large and two very large with
blooming. Do random range and bearing with one in earch quadrant. 1. target 5 miles north of radar, 100 feet long heding 1 knot south
5. PPI scope cursor - test by slowly changing range and bearing head on, width of target is 20 feet.
6. PPI scope weather noise - test by changing noise level slowly 2. target 5 miles south of radar, 20 feet long, 5 feet wide
7. PPI scope waves noise - test by changing noise level slowly headng away at 20 knots
8. PPI scope handling of shoreline - test by running for a few seconds 3. 6 mile east 30 feet long, heading north about 30 knots, full side view to radar
4. 6 mile west 100 feet long, heading south 5 knots, full side view
6. PPI scope cursor - test by slowly changing range and bearing
7. PPI scope weather noise - test by changing noise level slowly
8. PPI scope waves noise - test by changing noise level slowly
9. PPI scope handling of shoreline - test by running for a few seconds
======================================================== ========================================================

View File

@@ -27,12 +27,21 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o
/usr/include/x86_64-linux-gnu/bits/long-double.h /usr/include/x86_64-linux-gnu/bits/long-double.h
/usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h /usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h
/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h
/usr/include/x86_64-linux-gnu/bits/select-decl.h
/usr/include/x86_64-linux-gnu/bits/select.h /usr/include/x86_64-linux-gnu/bits/select.h
/usr/include/x86_64-linux-gnu/bits/select2.h
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h /usr/include/x86_64-linux-gnu/bits/stdint-intn.h
/usr/include/x86_64-linux-gnu/bits/stdint-least.h /usr/include/x86_64-linux-gnu/bits/stdint-least.h
/usr/include/x86_64-linux-gnu/bits/stdint-uintn.h /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h
/usr/include/x86_64-linux-gnu/bits/stdio.h
/usr/include/x86_64-linux-gnu/bits/stdio2-decl.h
/usr/include/x86_64-linux-gnu/bits/stdio2.h
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h /usr/include/x86_64-linux-gnu/bits/stdio_lim.h
/usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h /usr/include/x86_64-linux-gnu/bits/stdlib-float.h
/usr/include/x86_64-linux-gnu/bits/stdlib.h
/usr/include/x86_64-linux-gnu/bits/string_fortified.h
/usr/include/x86_64-linux-gnu/bits/strings_fortified.h
/usr/include/x86_64-linux-gnu/bits/struct_mutex.h /usr/include/x86_64-linux-gnu/bits/struct_mutex.h
/usr/include/x86_64-linux-gnu/bits/struct_rwlock.h /usr/include/x86_64-linux-gnu/bits/struct_rwlock.h
/usr/include/x86_64-linux-gnu/bits/thread-shared-types.h /usr/include/x86_64-linux-gnu/bits/thread-shared-types.h
@@ -283,13 +292,23 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o
/usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h /usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h
/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h
/usr/include/x86_64-linux-gnu/bits/sched.h /usr/include/x86_64-linux-gnu/bits/sched.h
/usr/include/x86_64-linux-gnu/bits/select-decl.h
/usr/include/x86_64-linux-gnu/bits/select.h /usr/include/x86_64-linux-gnu/bits/select.h
/usr/include/x86_64-linux-gnu/bits/select2.h
/usr/include/x86_64-linux-gnu/bits/setjmp.h /usr/include/x86_64-linux-gnu/bits/setjmp.h
/usr/include/x86_64-linux-gnu/bits/setjmp2.h
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h /usr/include/x86_64-linux-gnu/bits/stdint-intn.h
/usr/include/x86_64-linux-gnu/bits/stdint-least.h /usr/include/x86_64-linux-gnu/bits/stdint-least.h
/usr/include/x86_64-linux-gnu/bits/stdint-uintn.h /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h
/usr/include/x86_64-linux-gnu/bits/stdio.h
/usr/include/x86_64-linux-gnu/bits/stdio2-decl.h
/usr/include/x86_64-linux-gnu/bits/stdio2.h
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h /usr/include/x86_64-linux-gnu/bits/stdio_lim.h
/usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h /usr/include/x86_64-linux-gnu/bits/stdlib-float.h
/usr/include/x86_64-linux-gnu/bits/stdlib.h
/usr/include/x86_64-linux-gnu/bits/string_fortified.h
/usr/include/x86_64-linux-gnu/bits/strings_fortified.h
/usr/include/x86_64-linux-gnu/bits/struct_mutex.h /usr/include/x86_64-linux-gnu/bits/struct_mutex.h
/usr/include/x86_64-linux-gnu/bits/struct_rwlock.h /usr/include/x86_64-linux-gnu/bits/struct_rwlock.h
/usr/include/x86_64-linux-gnu/bits/thread-shared-types.h /usr/include/x86_64-linux-gnu/bits/thread-shared-types.h
@@ -328,6 +347,8 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o
/usr/include/x86_64-linux-gnu/bits/waitflags.h /usr/include/x86_64-linux-gnu/bits/waitflags.h
/usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/x86_64-linux-gnu/bits/waitstatus.h
/usr/include/x86_64-linux-gnu/bits/wchar.h /usr/include/x86_64-linux-gnu/bits/wchar.h
/usr/include/x86_64-linux-gnu/bits/wchar2-decl.h
/usr/include/x86_64-linux-gnu/bits/wchar2.h
/usr/include/x86_64-linux-gnu/bits/wctype-wchar.h /usr/include/x86_64-linux-gnu/bits/wctype-wchar.h
/usr/include/x86_64-linux-gnu/bits/wordsize.h /usr/include/x86_64-linux-gnu/bits/wordsize.h
/usr/include/x86_64-linux-gnu/bits/xopen_lim.h /usr/include/x86_64-linux-gnu/bits/xopen_lim.h

View File

@@ -26,12 +26,21 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o: /home/maallyn/radar-simulatio
/usr/include/x86_64-linux-gnu/bits/long-double.h \ /usr/include/x86_64-linux-gnu/bits/long-double.h \
/usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h \ /usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h \
/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \ /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \
/usr/include/x86_64-linux-gnu/bits/select-decl.h \
/usr/include/x86_64-linux-gnu/bits/select.h \ /usr/include/x86_64-linux-gnu/bits/select.h \
/usr/include/x86_64-linux-gnu/bits/select2.h \
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h \ /usr/include/x86_64-linux-gnu/bits/stdint-intn.h \
/usr/include/x86_64-linux-gnu/bits/stdint-least.h \ /usr/include/x86_64-linux-gnu/bits/stdint-least.h \
/usr/include/x86_64-linux-gnu/bits/stdint-uintn.h \ /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h \
/usr/include/x86_64-linux-gnu/bits/stdio.h \
/usr/include/x86_64-linux-gnu/bits/stdio2-decl.h \
/usr/include/x86_64-linux-gnu/bits/stdio2.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h \
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h \ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h \
/usr/include/x86_64-linux-gnu/bits/stdlib.h \
/usr/include/x86_64-linux-gnu/bits/string_fortified.h \
/usr/include/x86_64-linux-gnu/bits/strings_fortified.h \
/usr/include/x86_64-linux-gnu/bits/struct_mutex.h \ /usr/include/x86_64-linux-gnu/bits/struct_mutex.h \
/usr/include/x86_64-linux-gnu/bits/struct_rwlock.h \ /usr/include/x86_64-linux-gnu/bits/struct_rwlock.h \
/usr/include/x86_64-linux-gnu/bits/thread-shared-types.h \ /usr/include/x86_64-linux-gnu/bits/thread-shared-types.h \
@@ -281,13 +290,23 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o: /home/maallyn/radar-simulation/s
/usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h \ /usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h \
/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \ /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \
/usr/include/x86_64-linux-gnu/bits/sched.h \ /usr/include/x86_64-linux-gnu/bits/sched.h \
/usr/include/x86_64-linux-gnu/bits/select-decl.h \
/usr/include/x86_64-linux-gnu/bits/select.h \ /usr/include/x86_64-linux-gnu/bits/select.h \
/usr/include/x86_64-linux-gnu/bits/select2.h \
/usr/include/x86_64-linux-gnu/bits/setjmp.h \ /usr/include/x86_64-linux-gnu/bits/setjmp.h \
/usr/include/x86_64-linux-gnu/bits/setjmp2.h \
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h \ /usr/include/x86_64-linux-gnu/bits/stdint-intn.h \
/usr/include/x86_64-linux-gnu/bits/stdint-least.h \ /usr/include/x86_64-linux-gnu/bits/stdint-least.h \
/usr/include/x86_64-linux-gnu/bits/stdint-uintn.h \ /usr/include/x86_64-linux-gnu/bits/stdint-uintn.h \
/usr/include/x86_64-linux-gnu/bits/stdio.h \
/usr/include/x86_64-linux-gnu/bits/stdio2-decl.h \
/usr/include/x86_64-linux-gnu/bits/stdio2.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h \
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h \ /usr/include/x86_64-linux-gnu/bits/stdlib-float.h \
/usr/include/x86_64-linux-gnu/bits/stdlib.h \
/usr/include/x86_64-linux-gnu/bits/string_fortified.h \
/usr/include/x86_64-linux-gnu/bits/strings_fortified.h \
/usr/include/x86_64-linux-gnu/bits/struct_mutex.h \ /usr/include/x86_64-linux-gnu/bits/struct_mutex.h \
/usr/include/x86_64-linux-gnu/bits/struct_rwlock.h \ /usr/include/x86_64-linux-gnu/bits/struct_rwlock.h \
/usr/include/x86_64-linux-gnu/bits/thread-shared-types.h \ /usr/include/x86_64-linux-gnu/bits/thread-shared-types.h \
@@ -326,6 +345,8 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o: /home/maallyn/radar-simulation/s
/usr/include/x86_64-linux-gnu/bits/waitflags.h \ /usr/include/x86_64-linux-gnu/bits/waitflags.h \
/usr/include/x86_64-linux-gnu/bits/waitstatus.h \ /usr/include/x86_64-linux-gnu/bits/waitstatus.h \
/usr/include/x86_64-linux-gnu/bits/wchar.h \ /usr/include/x86_64-linux-gnu/bits/wchar.h \
/usr/include/x86_64-linux-gnu/bits/wchar2-decl.h \
/usr/include/x86_64-linux-gnu/bits/wchar2.h \
/usr/include/x86_64-linux-gnu/bits/wctype-wchar.h \ /usr/include/x86_64-linux-gnu/bits/wctype-wchar.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \ /usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/bits/xopen_lim.h \ /usr/include/x86_64-linux-gnu/bits/xopen_lim.h \
@@ -403,8 +424,6 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/lib64/ld-linux-x86-64.so.2: /lib64/ld-linux-x86-64.so.2:
/lib/x86_64-linux-gnu/libpng16.so.16:
/lib/x86_64-linux-gnu/libc.so.6: /lib/x86_64-linux-gnu/libc.so.6:
/lib/x86_64-linux-gnu/libbz2.so.1.0: /lib/x86_64-linux-gnu/libbz2.so.1.0:
@@ -445,24 +464,24 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/x86_64-linux-gnu/bits/types/mbstate_t.h: /usr/include/x86_64-linux-gnu/bits/types/mbstate_t.h:
/usr/include/c++/15/bits/stl_algobase.h: /usr/include/x86_64-linux-gnu/bits/setjmp2.h:
/usr/include/c++/15/clocale: /usr/include/x86_64-linux-gnu/bits/sched.h:
/usr/include/c++/15/bits/sstream.tcc: /usr/include/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h:
/usr/include/c++/15/bits/locale_facets.h: /usr/include/x86_64-linux-gnu/bits/posix2_lim.h:
/usr/include/x86_64-linux-gnu/bits/atomic_wide_counter.h: /usr/include/x86_64-linux-gnu/bits/posix1_lim.h:
/usr/include/c++/15/cstdlib: /usr/include/x86_64-linux-gnu/bits/mathcalls.h:
/usr/include/freetype2/freetype/ftmoderr.h: /usr/include/x86_64-linux-gnu/bits/mathcalls-narrow.h:
/usr/include/x86_64-linux-gnu/bits/mathcalls-macros.h:
/usr/include/c++/15/bits/predefined_ops.h: /usr/include/c++/15/bits/predefined_ops.h:
/usr/include/c++/15/bits/postypes.h:
/usr/include/x86_64-linux-gnu/asm/posix_types.h: /usr/include/x86_64-linux-gnu/asm/posix_types.h:
/usr/include/c++/15/bits/ostream.tcc: /usr/include/c++/15/bits/ostream.tcc:
@@ -489,8 +508,6 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/c++/15/ext/type_traits.h: /usr/include/c++/15/ext/type_traits.h:
/usr/include/c++/15/bits/stl_algo.h:
/usr/include/c++/15/bits/cxxabi_forced.h: /usr/include/c++/15/bits/cxxabi_forced.h:
/usr/include/x86_64-linux-gnu/sys/types.h: /usr/include/x86_64-linux-gnu/sys/types.h:
@@ -503,12 +520,16 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/linux/limits.h: /usr/include/linux/limits.h:
/usr/include/x86_64-linux-gnu/bits/posix2_lim.h:
/usr/include/c++/15/bits/ios_base.h: /usr/include/c++/15/bits/ios_base.h:
/usr/include/c++/15/bits/alloc_traits.h: /usr/include/c++/15/bits/alloc_traits.h:
/usr/include/x86_64-linux-gnu/bits/math-vector.h:
/usr/include/c++/15/bits/hash_bytes.h:
/usr/include/c++/15/algorithm:
/usr/lib/gcc/x86_64-linux-gnu/15/crtbeginS.o: /usr/lib/gcc/x86_64-linux-gnu/15/crtbeginS.o:
/usr/include/c++/15/bits/range_access.h: /usr/include/c++/15/bits/range_access.h:
@@ -543,39 +564,55 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/setjmp.h: /usr/include/setjmp.h:
/usr/include/c++/15/bits/std_abs.h:
/usr/include/c++/15/typeinfo:
/usr/include/x86_64-linux-gnu/bits/posix1_lim.h:
/usr/include/x86_64-linux-gnu/gnu/stubs.h: /usr/include/x86_64-linux-gnu/gnu/stubs.h:
/usr/include/x86_64-linux-gnu/bits/endian.h: /usr/include/x86_64-linux-gnu/bits/endian.h:
/usr/lib/gcc/x86_64-linux-gnu/15/crtendS.o:
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h:
/usr/include/c++/15/ext/string_conversions.h:
/usr/include/x86_64-linux-gnu/bits/wordsize.h: /usr/include/x86_64-linux-gnu/bits/wordsize.h:
/usr/include/c++/15/pstl/execution_defs.h: /usr/include/c++/15/pstl/execution_defs.h:
/usr/include/x86_64-linux-gnu/bits/stdint-least.h: /usr/include/x86_64-linux-gnu/bits/wchar.h:
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h: /usr/include/x86_64-linux-gnu/bits/types/cookie_io_functions_t.h:
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h: /usr/include/c++/15/bits/ostream_insert.h:
/usr/include/x86_64-linux-gnu/bits/errno.h:
/usr/lib/x86_64-linux-gnu/crtn.o:
/usr/include/x86_64-linux-gnu/bits/waitflags.h:
/usr/include/x86_64-linux-gnu/bits/uintn-identity.h: /usr/include/x86_64-linux-gnu/bits/uintn-identity.h:
/usr/include/x86_64-linux-gnu/bits/stdlib-float.h: /usr/include/x86_64-linux-gnu/bits/stdlib-float.h:
/usr/include/x86_64-linux-gnu/bits/wchar.h: /usr/include/stdc-predef.h:
/usr/include/x86_64-linux-gnu/bits/types/cookie_io_functions_t.h: /usr/include/x86_64-linux-gnu/bits/typesizes.h:
/usr/include/c++/15/system_error:
/usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h:
/usr/include/x86_64-linux-gnu/bits/stdlib.h:
/usr/include/c++/15/bits/stl_uninitialized.h:
/usr/include/x86_64-linux-gnu/asm/errno.h:
/usr/include/x86_64-linux-gnu/bits/stdint-intn.h:
/lib/x86_64-linux-gnu/libpng16.so.16:
/usr/include/x86_64-linux-gnu/bits/select-decl.h:
/usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h:
/usr/include/freetype2/freetype/fttypes.h:
/usr/include/locale.h:
/usr/include/c++/15/bits/algorithmfwd.h: /usr/include/c++/15/bits/algorithmfwd.h:
@@ -589,6 +626,14 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/x86_64-linux-gnu/bits/endianness.h: /usr/include/x86_64-linux-gnu/bits/endianness.h:
/usr/include/x86_64-linux-gnu/bits/types/timer_t.h:
/usr/include/stdint.h:
/usr/include/endian.h:
/usr/include/c++/15/cmath:
/usr/include/c++/15/bits/cpp_type_traits.h: /usr/include/c++/15/bits/cpp_type_traits.h:
/usr/include/c++/15/bits/basic_string.h: /usr/include/c++/15/bits/basic_string.h:
@@ -597,16 +642,10 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/c++/15/bits/concept_check.h: /usr/include/c++/15/bits/concept_check.h:
/usr/include/x86_64-linux-gnu/bits/wchar2-decl.h:
/usr/include/c++/15/bits/version.h: /usr/include/c++/15/bits/version.h:
/usr/include/stdc-predef.h:
/usr/include/x86_64-linux-gnu/bits/typesizes.h:
/usr/include/c++/15/backward/binders.h:
/usr/include/c++/15/bits/specfun.h:
/usr/include/x86_64-linux-gnu/bits/floatn.h: /usr/include/x86_64-linux-gnu/bits/floatn.h:
/usr/include/c++/15/bits/istream.tcc: /usr/include/c++/15/bits/istream.tcc:
@@ -643,19 +682,15 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/c++/15/bits/uses_allocator.h: /usr/include/c++/15/bits/uses_allocator.h:
/home/maallyn/radar-simulation/glad/src/glad.c:
/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h: /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h:
/usr/include/string.h: /home/maallyn/radar-simulation/glad/src/glad.c:
/usr/include/c++/15/bits/stl_function.h: /usr/include/x86_64-linux-gnu/bits/select2.h:
/usr/include/c++/15/cwchar: /usr/include/c++/15/ext/alloc_traits.h:
/usr/include/c++/15/bits/requires_hosted.h: /usr/include/linux/types.h:
/usr/include/dlfcn.h:
/lib/x86_64-linux-gnu/libmvec.so.1: /lib/x86_64-linux-gnu/libmvec.so.1:
@@ -665,22 +700,12 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/freetype2/freetype/config/mac-support.h: /usr/include/freetype2/freetype/config/mac-support.h:
/usr/include/c++/15/tr1/gamma.tcc:
/usr/lib/gcc/x86_64-linux-gnu/15/include/stdarg.h:
/usr/include/c++/15/bits/locale_facets.tcc: /usr/include/c++/15/bits/locale_facets.tcc:
/usr/include/ctype.h: /usr/include/ctype.h:
/usr/include/features.h: /usr/include/features.h:
/usr/include/x86_64-linux-gnu/bits/types/FILE.h:
/usr/include/x86_64-linux-gnu/bits/timex.h:
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h:
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h: /usr/include/x86_64-linux-gnu/bits/libc-header-start.h:
/usr/include/c++/15/bits/exception_ptr.h: /usr/include/c++/15/bits/exception_ptr.h:
@@ -695,10 +720,6 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o:
/usr/include/pthread.h: /usr/include/pthread.h:
/usr/include/x86_64-linux-gnu/bits/types/struct___jmp_buf_tag.h:
/usr/include/x86_64-linux-gnu/bits/types/clockid_t.h:
/usr/include/x86_64-linux-gnu/bits/time64.h: /usr/include/x86_64-linux-gnu/bits/time64.h:
/usr/include/freetype2/freetype/config/ftconfig.h: /usr/include/freetype2/freetype/config/ftconfig.h:
@@ -713,8 +734,6 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/c++/15/bits/invoke.h: /usr/include/c++/15/bits/invoke.h:
/usr/include/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h:
/usr/include/c++/15/bits/memoryfwd.h: /usr/include/c++/15/bits/memoryfwd.h:
/usr/include/freetype2/freetype/ftsystem.h: /usr/include/freetype2/freetype/ftsystem.h:
@@ -727,10 +746,6 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/c++/15/bits/stringfwd.h: /usr/include/c++/15/bits/stringfwd.h:
/usr/include/c++/15/bits/ostream_insert.h:
/usr/include/x86_64-linux-gnu/bits/errno.h:
/usr/include/features-time64.h: /usr/include/features-time64.h:
/usr/include/c++/15/bits/uniform_int_dist.h: /usr/include/c++/15/bits/uniform_int_dist.h:
@@ -743,29 +758,45 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/c++/15/bits/codecvt.h: /usr/include/c++/15/bits/codecvt.h:
/usr/include/x86_64-linux-gnu/bits/mathcalls-macros.h: /usr/include/string.h:
/usr/include/c++/15/bits/refwrap.h: /usr/include/c++/15/bits/postypes.h:
/usr/include/c++/15/type_traits: /usr/include/x86_64-linux-gnu/bits/stdio.h:
/usr/include/stdint.h: /usr/include/c++/15/bits/stl_function.h:
/usr/include/endian.h: /usr/include/c++/15/cwchar:
/usr/include/c++/15/cmath: /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h:
/usr/include/x86_64-linux-gnu/bits/types/timer_t.h: /usr/include/x86_64-linux-gnu/bits/stdint-least.h:
/usr/include/c++/15/bits/basic_ios.tcc: /usr/include/x86_64-linux-gnu/bits/strings_fortified.h:
/usr/include/c++/15/system_error: /usr/include/x86_64-linux-gnu/bits/types/FILE.h:
/usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h: /usr/include/x86_64-linux-gnu/bits/timex.h:
/usr/include/c++/15/bits/stl_uninitialized.h: /usr/include/x86_64-linux-gnu/bits/stdio_lim.h:
/usr/include/x86_64-linux-gnu/asm/errno.h: /usr/include/c++/15/tr1/gamma.tcc:
/usr/lib/gcc/x86_64-linux-gnu/15/include/stdarg.h:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:
/usr/include/c++/15/bits/locale_facets.h:
/usr/include/x86_64-linux-gnu/bits/atomic_wide_counter.h:
/usr/include/dlfcn.h:
/usr/include/c++/15/bits/requires_hosted.h:
/usr/include/c++/15/cstdlib:
/usr/include/freetype2/freetype/ftmoderr.h:
/usr/include/stdio.h: /usr/include/stdio.h:
@@ -775,27 +806,53 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/c++/15/tr1/exp_integral.tcc: /usr/include/c++/15/tr1/exp_integral.tcc:
/usr/include/x86_64-linux-gnu/bits/types/struct___jmp_buf_tag.h:
/usr/include/x86_64-linux-gnu/bits/types/clockid_t.h:
/usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h:
/usr/lib/gcc/x86_64-linux-gnu/15/crtendS.o:
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h:
/usr/include/c++/15/ext/string_conversions.h:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:
/usr/include/c++/15/bits/basic_ios.tcc:
/usr/include/c++/15/backward/binders.h:
/usr/include/c++/15/bits/specfun.h:
/usr/include/x86_64-linux-gnu/bits/types/clock_t.h:
/usr/include/x86_64-linux-gnu/bits/byteswap.h: /usr/include/x86_64-linux-gnu/bits/byteswap.h:
/usr/include/c++/15/concepts:
/usr/include/freetype2/freetype/fterrors.h: /usr/include/freetype2/freetype/fterrors.h:
/usr/include/c++/15/concepts:
/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h: /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h:
/usr/include/c++/15/istream: /usr/include/c++/15/istream:
/usr/include/x86_64-linux-gnu/bits/types/clock_t.h: /usr/include/c++/15/bits/refwrap.h:
/usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h: /usr/include/c++/15/type_traits:
/usr/include/freetype2/freetype/fttypes.h: /usr/include/c++/15/bits/sstream.tcc:
/usr/include/locale.h: /usr/include/c++/15/bits/std_abs.h:
/usr/lib/x86_64-linux-gnu/crtn.o: /usr/include/c++/15/typeinfo:
/usr/include/x86_64-linux-gnu/bits/waitflags.h: /usr/include/c++/15/bits/stl_algo.h:
/usr/include/c++/15/bits/stl_algobase.h:
/usr/include/c++/15/clocale:
/usr/include/c++/15/bits/stl_iterator.h: /usr/include/c++/15/bits/stl_iterator.h:
@@ -829,6 +886,10 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/c++/15/bits/stl_vector.h: /usr/include/c++/15/bits/stl_vector.h:
/usr/include/x86_64-linux-gnu/asm/types.h:
/usr/include/c++/15/cwctype:
/usr/include/c++/15/bits/basic_string.tcc: /usr/include/c++/15/bits/basic_string.tcc:
/usr/include/c++/15/bits/streambuf.tcc: /usr/include/c++/15/bits/streambuf.tcc:
@@ -859,10 +920,6 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/c++/15/exception: /usr/include/c++/15/exception:
/usr/include/c++/15/ext/alloc_traits.h:
/usr/include/linux/types.h:
/usr/include/c++/15/ext/numeric_traits.h: /usr/include/c++/15/ext/numeric_traits.h:
/usr/include/c++/15/ios: /usr/include/c++/15/ios:
@@ -913,10 +970,10 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/c++/15/tr1/ell_integral.tcc: /usr/include/c++/15/tr1/ell_integral.tcc:
/usr/include/c++/15/tr1/legendre_function.tcc:
/usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h: /usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h:
/usr/include/c++/15/tr1/legendre_function.tcc:
/usr/include/c++/15/bits/charconv.h: /usr/include/c++/15/bits/charconv.h:
/usr/include/c++/15/pstl/glue_algorithm_defs.h: /usr/include/c++/15/pstl/glue_algorithm_defs.h:
@@ -949,6 +1006,8 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/freetype2/freetype/fterrdef.h: /usr/include/freetype2/freetype/fterrdef.h:
/usr/include/x86_64-linux-gnu/bits/stdio2-decl.h:
/usr/include/linux/posix_types.h: /usr/include/linux/posix_types.h:
/usr/include/GLFW/glfw3.h: /usr/include/GLFW/glfw3.h:
@@ -971,10 +1030,6 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/time.h: /usr/include/time.h:
/usr/include/c++/15/cwctype:
/usr/include/x86_64-linux-gnu/asm/types.h:
/usr/include/x86_64-linux-gnu/asm/posix_types_64.h: /usr/include/x86_64-linux-gnu/asm/posix_types_64.h:
/usr/include/x86_64-linux-gnu/bits/cpu-set.h: /usr/include/x86_64-linux-gnu/bits/cpu-set.h:
@@ -985,6 +1040,8 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/x86_64-linux-gnu/bits/fp-fast.h: /usr/include/x86_64-linux-gnu/bits/fp-fast.h:
/usr/include/x86_64-linux-gnu/bits/wchar2.h:
/usr/include/x86_64-linux-gnu/bits/fp-logb.h: /usr/include/x86_64-linux-gnu/bits/fp-logb.h:
/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h: /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h:
@@ -996,15 +1053,3 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o:
/usr/include/c++/15/bits/basic_ios.h: /usr/include/c++/15/bits/basic_ios.h:
/usr/include/x86_64-linux-gnu/bits/locale.h: /usr/include/x86_64-linux-gnu/bits/locale.h:
/usr/include/c++/15/bits/hash_bytes.h:
/usr/include/c++/15/algorithm:
/usr/include/x86_64-linux-gnu/bits/math-vector.h:
/usr/include/x86_64-linux-gnu/bits/mathcalls-narrow.h:
/usr/include/x86_64-linux-gnu/bits/mathcalls.h:
/usr/include/x86_64-linux-gnu/bits/sched.h:

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#version 330 core
uniform vec3 uColor;
out vec4 FragColor;
void main() {
FragColor = vec4(uColor, 1.0);
}

View File

@@ -0,0 +1,5 @@
#version 330 core
layout(location = 0) in vec2 aPos;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
}

View File

@@ -0,0 +1,6 @@
#version 330 core
uniform vec3 uColor;
out vec4 FragColor;
void main() {
FragColor = vec4(uColor, 1.0);
}

View File

@@ -0,0 +1,6 @@
#version 330 core
layout(location = 0) in vec2 aPos;
uniform float uYOffset;
void main() {
gl_Position = vec4(aPos.x, aPos.y + uYOffset, 0.0, 1.0);
}

View File

@@ -0,0 +1,9 @@
#version 330 core
in vec2 vUV;
uniform sampler2D uTexture;
uniform vec3 uColor;
out vec4 FragColor;
void main() {
float alpha = texture(uTexture, vUV).r;
FragColor = vec4(uColor, alpha);
}

View File

@@ -0,0 +1,9 @@
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aUV;
uniform float uYOffset;
out vec2 vUV;
void main() {
gl_Position = vec4(aPos.x, aPos.y + uYOffset, 0.0, 1.0);
vUV = aUV;
}

6
shaders/ppi_cursor.frag Normal file
View File

@@ -0,0 +1,6 @@
#version 330 core
uniform vec3 uColor;
out vec4 FragColor;
void main() {
FragColor = vec4(uColor, 1.0);
}

5
shaders/ppi_cursor.vert Normal file
View File

@@ -0,0 +1,5 @@
#version 330 core
layout(location = 0) in vec2 aPos;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
}

View File

@@ -0,0 +1,6 @@
#version 330 core
in vec3 vColor;
out vec4 fragColor;
void main() {
fragColor = vec4(vColor, 1.0);
}

View File

@@ -0,0 +1,8 @@
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec3 aColor;
out vec3 vColor;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
vColor = aColor;
}

11
shaders/ppi_targets.frag Normal file
View File

@@ -0,0 +1,11 @@
#version 330 core
in vec2 vUV; // [-1,1] range — distance from blob centre
out vec4 fragColor;
uniform vec3 uColor;
uniform float uFalloff; // gaussian width: larger = tighter core
void main() {
float d = length(vUV);
if (d > 1.0) discard;
float intensity = exp(-uFalloff * d * d);
fragColor = vec4(uColor * intensity, intensity);
}

8
shaders/ppi_targets.vert Normal file
View File

@@ -0,0 +1,8 @@
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aUV;
out vec2 vUV;
void main() {
gl_Position = vec4(aPos, 0.0, 1.0);
vUV = aUV;
}

View File

@@ -1,8 +1,12 @@
// Radar Simulation — Feature Test: 1, 2 & 3 // Radar Simulation — Feature Test: 16
// Feature 1: Initialize display, draw scope boundaries (PPI circle, A scope box) // Feature 1: Initialize display, draw scope boundaries (PPI circle, A scope box)
// Feature 2: PPI bearing ring with tick marks and degree labels // Feature 2: PPI bearing ring with tick marks, degree labels, and outer label ring
// Feature 3: Replaceable A scope graticule — cycles through 2/5/10/15 mi ranges, // Feature 3: Replaceable A scope graticule — cycles through 2/5/10/15 mi ranges,
// 5 s hold per range, 0.5 s slide animation between ranges. // 5 s hold per range, 0.5 s slide animation between ranges
// Feature 4: PPI range rings with rotating sweep and P7 persistence
// Feature 5: Four fake targets with size-based brightness
// Feature 6: PPI cursor — range ring + bearing line + intersection box (incandescent),
// auto-animates slowly (range oscillates, bearing rotates) for test
// Press ESC to exit. // Press ESC to exit.
#include <glad/glad.h> #include <glad/glad.h>
@@ -30,30 +34,40 @@ static constexpr float INCAN_R = 1.00f;
static constexpr float INCAN_G = 0.78f; static constexpr float INCAN_G = 0.78f;
static constexpr float INCAN_B = 0.35f; static constexpr float INCAN_B = 0.35f;
// P1 phosphor (green) — A scope boundary // P1 phosphor (green) — A scope
static constexpr float P1_R = 0.00f; static constexpr float P1_R = 0.00f;
static constexpr float P1_G = 0.90f; static constexpr float P1_G = 0.90f;
static constexpr float P1_B = 0.20f; static constexpr float P1_B = 0.20f;
// P7 phosphor active (blue) — PPI scope active display
static constexpr float P7A_R = 0.10f;
static constexpr float P7A_G = 0.45f;
static constexpr float P7A_B = 1.00f;
// P7 phosphor persistence (greenish yellow) — PPI scope trail/fade
static constexpr float P7P_R = 0.35f;
static constexpr float P7P_G = 0.88f;
static constexpr float P7P_B = 0.18f;
// Digits rendered into the font atlas: '0''9' // Digits rendered into the font atlas: '0''9'
static constexpr int GLYPH_FIRST = '0'; static constexpr int GLYPH_FIRST = '0';
static constexpr int GLYPH_COUNT = 10; static constexpr int GLYPH_COUNT = 10;
// Feature 3 timing // Feature 3 timing
static constexpr float HOLD_SEC = 5.0f; static constexpr float HOLD_SEC = 20.0f; // seconds per range setting
static constexpr float SLIDE_SEC = 0.5f; static constexpr float SLIDE_OUT_SEC = 5.0f; // slide current graticule out (up)
static constexpr float WAIT_SEC = 2.0f; // blank pause between graticules
static constexpr float SLIDE_IN_SEC = 5.0f; // slide new graticule in (from top)
// P7 phosphor — active (blueish white) and persistence (greenish yellow) // Feature 4 — sweep
static constexpr float P7A_R = 0.85f, P7A_G = 0.92f, P7A_B = 1.00f;
static constexpr float P7P_R = 0.35f, P7P_G = 0.88f, P7P_B = 0.18f;
// Sweep / persistence
static constexpr float SWEEP_DEG_PS = 20.0f * 6.0f; // 20 RPM → 120 °/s static constexpr float SWEEP_DEG_PS = 20.0f * 6.0f; // 20 RPM → 120 °/s
static constexpr float TRAIL_DEG = 50.0f; // lit arc behind sweep (°) static constexpr float TRAIL_DEG = 50.0f; // lit arc behind sweep head (°)
static constexpr float TARG_PERSIST = 5.0f; // target glow lifetime (s)
static constexpr int RING_SEGS = 300; static constexpr int RING_SEGS = 300;
static constexpr int TRAIL_SEGS = 50; static constexpr int TRAIL_SEGS = 50;
// Feature 5 — target glow lifetime (s) after sweep passes
static constexpr float TARG_PERSIST = 5.0f;
// ─── NDC helpers ───────────────────────────────────────────────────────────── // ─── NDC helpers ─────────────────────────────────────────────────────────────
static inline float ndcX(float px, float W) { return px / W * 2.0f - 1.0f; } static inline float ndcX(float px, float W) { return px / W * 2.0f - 1.0f; }
@@ -286,6 +300,14 @@ static Layout computeLayout(float W, float H, float marginPx)
return L; return L;
} }
// ─── Polar coordinate helpers (bearing ° CW from N, range fraction) ──────────
static inline float polarPx(const Layout& L, float bearDeg, float frac)
{ return L.ppiCX + frac * L.ppiR * std::sin(bearDeg * (PI/180.0f)); }
static inline float polarPy(const Layout& L, float bearDeg, float frac)
{ return L.ppiCY - frac * L.ppiR * std::cos(bearDeg * (PI/180.0f)); }
// ─── Feature 1: Scope boundaries ───────────────────────────────────────────── // ─── Feature 1: Scope boundaries ─────────────────────────────────────────────
struct ScopeBounds { struct ScopeBounds {
@@ -299,6 +321,7 @@ static ScopeBounds buildScopeBounds(const Layout& L, float W, float H)
ScopeBounds sb{}; ScopeBounds sb{};
std::vector<float> v; std::vector<float> v;
// PPI scope circle
sb.ppiStart = 0; sb.ppiStart = 0;
sb.ppiCount = CIRCLE_SEGS + 1; sb.ppiCount = CIRCLE_SEGS + 1;
for (int i = 0; i <= CIRCLE_SEGS; ++i) { for (int i = 0; i <= CIRCLE_SEGS; ++i) {
@@ -307,6 +330,7 @@ static ScopeBounds buildScopeBounds(const Layout& L, float W, float H)
v.push_back(ndcY(L.ppiCY + L.ppiR * std::sin(a), H)); v.push_back(ndcY(L.ppiCY + L.ppiR * std::sin(a), H));
} }
// A scope bounding box
sb.asStart = sb.ppiStart + sb.ppiCount; sb.asStart = sb.ppiStart + sb.ppiCount;
sb.asCount = 8; sb.asCount = 8;
auto ln = [&](float x1, float y1, float x2, float y2) { auto ln = [&](float x1, float y1, float x2, float y2) {
@@ -329,8 +353,9 @@ struct BearingGraticule {
GLuint lineProg = 0, textProg = 0; GLuint lineProg = 0, textProg = 0;
GLuint lineVAO = 0, lineVBO = 0; GLuint lineVAO = 0, lineVBO = 0;
GLuint textVAO = 0, textVBO = 0; GLuint textVAO = 0, textVBO = 0;
int ringStart = 0, ringCount = 0; int ring1Start = 0, ring1Count = 0; // inner ring (at scope edge)
int tickStart = 0, tickCount = 0; int tickStart = 0, tickCount = 0; // degree tick marks (outward)
int ring2Start = 0, ring2Count = 0; // outer ring (encircles labels)
int textVerts = 0; int textVerts = 0;
GLuint fontTex = 0; GLuint fontTex = 0;
}; };
@@ -343,33 +368,46 @@ static BearingGraticule buildBearingGraticule(const Layout& L, const FontAtlas&
std::vector<float> lineV; std::vector<float> lineV;
bg.ringStart = 0; // Inner ring (scope edge)
bg.ringCount = CIRCLE_SEGS + 1; bg.ring1Start = 0;
bg.ring1Count = CIRCLE_SEGS + 1;
for (int i = 0; i <= CIRCLE_SEGS; ++i) { for (int i = 0; i <= CIRCLE_SEGS; ++i) {
float a = 2.0f * PI * i / CIRCLE_SEGS; float a = 2.0f * PI * i / CIRCLE_SEGS;
lineV.push_back(ndcX(cx + R * std::cos(a), W)); lineV.push_back(ndcX(cx + R * std::cos(a), W));
lineV.push_back(ndcY(cy + R * std::sin(a), H)); lineV.push_back(ndcY(cy + R * std::sin(a), H));
} }
// Tick marks pointing outward from the scope edge
const float majorLen = 0.055f * R; const float majorLen = 0.055f * R;
const float minorLen = 0.025f * R; const float minorLen = 0.025f * R;
bg.tickStart = bg.ringStart + bg.ringCount; bg.tickStart = bg.ring1Start + bg.ring1Count;
bg.tickCount = 360 * 2; bg.tickCount = 360 * 2;
for (int b = 0; b < 360; ++b) { for (int b = 0; b < 360; ++b) {
float brad = b * PI / 180.0f; float brad = b * PI / 180.0f;
float sb = std::sin(brad); float sb_ = std::sin(brad);
float cb = std::cos(brad); float cb_ = std::cos(brad);
float len = (b % 10 == 0) ? majorLen : minorLen; float len = (b % 10 == 0) ? majorLen : minorLen;
lineV.push_back(ndcX(cx + R * sb, W)); lineV.push_back(ndcX(cx + R * sb_, W));
lineV.push_back(ndcY(cy - R * cb, H)); lineV.push_back(ndcY(cy - R * cb_, H));
lineV.push_back(ndcX(cx + (R-len) * sb, W)); lineV.push_back(ndcX(cx + (R + len) * sb_, W));
lineV.push_back(ndcY(cy - (R-len) * cb, H)); lineV.push_back(ndcY(cy - (R + len) * cb_, H));
}
// Outer ring (encircles the degree labels)
const float outerR = R * 1.18f;
bg.ring2Start = bg.tickStart + bg.tickCount;
bg.ring2Count = CIRCLE_SEGS + 1;
for (int i = 0; i <= CIRCLE_SEGS; ++i) {
float a = 2.0f * PI * i / CIRCLE_SEGS;
lineV.push_back(ndcX(cx + outerR * std::cos(a), W));
lineV.push_back(ndcY(cy + outerR * std::sin(a), H));
} }
makeLineVAO(bg.lineVAO, bg.lineVBO, lineV); makeLineVAO(bg.lineVAO, bg.lineVBO, lineV);
// Degree labels (every 10°) between the tick tips and the outer ring
std::vector<float> textV; std::vector<float> textV;
const float textR = R * 1.07f; const float textR = R * 1.09f;
for (int b = 0; b < 360; b += 10) { for (int b = 0; b < 360; b += 10) {
float brad = b * PI / 180.0f; float brad = b * PI / 180.0f;
float tx = cx + textR * std::sin(brad); float tx = cx + textR * std::sin(brad);
@@ -386,11 +424,8 @@ static BearingGraticule buildBearingGraticule(const Layout& L, const FontAtlas&
return bg; return bg;
} }
// ─── Feature 3: A scope replaceable graticule ──────────────────────────────── // ─── Range configs (shared by Features 3, 4, 5) ──────────────────────────────
// One entry per selectable range. The graticule has numMajor labeled ticks
// (full-height vertical lines) and numMinorPerMajor minor ticks between each
// pair of major ticks. Label values are computed as (m * maxMiles / numMajor).
struct RangeConfig { struct RangeConfig {
float maxMiles; float maxMiles;
int numMajor; int numMajor;
@@ -405,6 +440,8 @@ static const RangeConfig RANGE_CONFIGS[4] = {
}; };
static constexpr int RANGE_COUNT = 4; static constexpr int RANGE_COUNT = 4;
// ─── Feature 3: A scope replaceable graticule ────────────────────────────────
struct AScopeGraticule { struct AScopeGraticule {
GLuint lineVAO = 0, lineVBO = 0; GLuint lineVAO = 0, lineVBO = 0;
GLuint textVAO = 0, textVBO = 0; GLuint textVAO = 0, textVBO = 0;
@@ -412,7 +449,6 @@ struct AScopeGraticule {
int textVerts = 0; int textVerts = 0;
}; };
// Shared shader programs for all A scope graticules (uYOffset drives animation)
struct AScopeGratProg { struct AScopeGratProg {
GLuint line = 0; GLuint line = 0;
GLuint text = 0; GLuint text = 0;
@@ -436,10 +472,10 @@ static AScopeGraticule buildAScopeGraticule(
const float asH = L.asBot - L.asTop; const float asH = L.asBot - L.asTop;
const float pad = 4.0f; const float pad = 4.0f;
const float gx0 = L.asLeft + pad; // signal area left const float gx0 = L.asLeft + pad;
const float gx1 = L.asRight - pad; // signal area right const float gx1 = L.asRight - pad;
const float gy0 = L.asTop + pad; // top of signal area const float gy0 = L.asTop + pad; // top of signal area
const float gy1 = L.asTop + asH * 0.80f; // baseline (signal y = 0) const float gy1 = L.asTop + asH * 0.80f; // baseline
const float sigW = gx1 - gx0; const float sigW = gx1 - gx0;
const float sigH = gy1 - gy0; const float sigH = gy1 - gy0;
@@ -449,7 +485,7 @@ static AScopeGraticule buildAScopeGraticule(
lineV.push_back(ndcX(x2,W)); lineV.push_back(ndcY(y2,H)); lineV.push_back(ndcX(x2,W)); lineV.push_back(ndcY(y2,H));
}; };
// Outer frame (the physical glass plate border) // Outer frame
ln(L.asLeft, L.asTop, L.asRight, L.asTop); ln(L.asLeft, L.asTop, L.asRight, L.asTop);
ln(L.asRight, L.asTop, L.asRight, L.asBot); ln(L.asRight, L.asTop, L.asRight, L.asBot);
ln(L.asRight, L.asBot, L.asLeft, L.asBot); ln(L.asRight, L.asBot, L.asLeft, L.asBot);
@@ -459,7 +495,7 @@ static AScopeGraticule buildAScopeGraticule(
ln(gx0, gy1, gx1, gy1); ln(gx0, gy1, gx1, gy1);
ln(gx0, gy0, gx1, gy0); ln(gx0, gy0, gx1, gy0);
// Horizontal amplitude guide lines at 25 %, 50 %, 75 % // Horizontal amplitude guides at 25 %, 50 %, 75 %
for (int i = 1; i <= 3; ++i) for (int i = 1; i <= 3; ++i)
ln(gx0, gy0 + sigH * i * 0.25f, gx1, gy0 + sigH * i * 0.25f); ln(gx0, gy0 + sigH * i * 0.25f, gx1, gy0 + sigH * i * 0.25f);
@@ -469,11 +505,9 @@ static AScopeGraticule buildAScopeGraticule(
const float minorTickH = sigH * 0.35f; const float minorTickH = sigH * 0.35f;
for (int m = 0; m < rc.numMajor; ++m) { for (int m = 0; m < rc.numMajor; ++m) {
// Major tick (full signal height) at right edge of each interval
float xMaj = gx0 + (m + 1) * majorSpan; float xMaj = gx0 + (m + 1) * majorSpan;
ln(xMaj, gy1, xMaj, gy0); ln(xMaj, gy1, xMaj, gy0);
// Minor ticks between this major and the next
float xBase = gx0 + m * majorSpan; float xBase = gx0 + m * majorSpan;
for (int n = 1; n <= rc.numMinorPerMajor; ++n) { for (int n = 1; n <= rc.numMinorPerMajor; ++n) {
float xMin = xBase + n * minorSpan; float xMin = xBase + n * minorSpan;
@@ -484,7 +518,7 @@ static AScopeGraticule buildAScopeGraticule(
ag.lineCount = (int)lineV.size() / 2; ag.lineCount = (int)lineV.size() / 2;
makeLineVAO(ag.lineVAO, ag.lineVBO, lineV); makeLineVAO(ag.lineVAO, ag.lineVBO, lineV);
// Range labels at each major tick (whole-number miles, centred in label area) // Range labels at each major tick
std::vector<float> textV; std::vector<float> textV;
const float labelY = L.asTop + asH * 0.90f; const float labelY = L.asTop + asH * 0.90f;
const float milesPerMajor = rc.maxMiles / rc.numMajor; const float milesPerMajor = rc.maxMiles / rc.numMajor;
@@ -499,15 +533,11 @@ static AScopeGraticule buildAScopeGraticule(
return ag; return ag;
} }
// Draw one graticule with a vertical NDC offset (positive = up on screen).
// glScissor clips to the A scope box so the slide animation looks like the
// graticule is being pulled out / pushed in through a slot at the top.
static void drawAScopeGraticule( static void drawAScopeGraticule(
const AScopeGratProg& prog, const AScopeGraticule& ag, const AScopeGratProg& prog, const AScopeGraticule& ag,
float yOffNDC, const FontAtlas& fa, float W, float H, const Layout& L) float yOffNDC, const FontAtlas& fa, float W, float H, const Layout& L)
{ {
glEnable(GL_SCISSOR_TEST); glEnable(GL_SCISSOR_TEST);
// OpenGL scissor uses window coords (y=0 at bottom of window)
glScissor((GLint) L.asLeft, glScissor((GLint) L.asLeft,
(GLint)(H - L.asBot), (GLint)(H - L.asBot),
(GLint)(L.asRight - L.asLeft), (GLint)(L.asRight - L.asLeft),
@@ -535,20 +565,9 @@ static void drawAScopeGraticule(
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
} }
// ─── Polar coordinate helpers (bearing ° CW from N, range fraction) ─────────
static inline float polarPx(const Layout& L, float bearDeg, float frac)
{ return L.ppiCX + frac * L.ppiR * std::sin(bearDeg * (PI/180.0f)); }
static inline float polarPy(const Layout& L, float bearDeg, float frac)
{ return L.ppiCY - frac * L.ppiR * std::cos(bearDeg * (PI/180.0f)); }
// ─── Feature 4: PPI range rings ────────────────────────────────────────────── // ─── Feature 4: PPI range rings ──────────────────────────────────────────────
// Build vertex data (x,y,r,g,b = 5 floats/vertex): // 5 floats/vertex: NDC x, y, r, g, b
// 1. Full persistence rings (dim P7 green-yellow)
// 2. Active sweep-trail arc per ring (active → persistence colour gradient)
// 3. Sweep line from centre to edge
static void buildRingVerts(std::vector<float>& v, static void buildRingVerts(std::vector<float>& v,
const Layout& L, int rangeIdx, const Layout& L, int rangeIdx,
float sweepAngle, float W, float H) float sweepAngle, float W, float H)
@@ -561,7 +580,7 @@ static void buildRingVerts(std::vector<float>& v,
v.push_back(r); v.push_back(g); v.push_back(b); v.push_back(r); v.push_back(g); v.push_back(b);
}; };
// Full persistence rings (dim) // Full persistence rings (dim greenish yellow)
for (int ri = 1; ri <= nr; ++ri) { for (int ri = 1; ri <= nr; ++ri) {
float frac = (float)ri / nr; float frac = (float)ri / nr;
for (int i = 0; i <= RING_SEGS; ++i) { for (int i = 0; i <= RING_SEGS; ++i) {
@@ -572,7 +591,7 @@ static void buildRingVerts(std::vector<float>& v,
} }
} }
// Sweep trail arcs (active at head persistence at tail) // Sweep trail arcs: gradient from active blue at head to dim persistence at tail
for (int ri = 1; ri <= nr; ++ri) { for (int ri = 1; ri <= nr; ++ri) {
float frac = (float)ri / nr; float frac = (float)ri / nr;
for (int i = 0; i <= TRAIL_SEGS; ++i) { for (int i = 0; i <= TRAIL_SEGS; ++i) {
@@ -585,17 +604,27 @@ static void buildRingVerts(std::vector<float>& v,
} }
} }
// Sweep line (centre → edge) // Sweep line (centre → edge) in active blue
push(L.ppiCX, L.ppiCY, P7A_R, P7A_G, P7A_B); push(L.ppiCX, L.ppiCY, P7A_R, P7A_G, P7A_B);
push(polarPx(L,sweepAngle,1.0f), polarPy(L,sweepAngle,1.0f), push(polarPx(L,sweepAngle,1.0f), polarPy(L,sweepAngle,1.0f),
P7A_R, P7A_G, P7A_B); P7A_R, P7A_G, P7A_B);
} }
// Unsigned angular separation between two headings (°)
static float angDiff(float a, float b)
{
float d = std::fmod(std::fabs(a - b), 360.0f);
return d > 180.0f ? 360.0f - d : d;
}
struct RingLayer { struct RingLayer {
GLuint prog = 0, vao = 0, vbo = 0; GLuint prog = 0, vao = 0, vbo = 0;
GLuint textProg = 0, textVAO = 0, textVBO = 0;
GLuint fontTex = 0;
float labelLastActT = -999.0f; // glfwGetTime() when sweep last passed 180°
}; };
static RingLayer buildRingLayer() static RingLayer buildRingLayer(const FontAtlas& fa)
{ {
RingLayer rl{}; RingLayer rl{};
glGenVertexArrays(1, &rl.vao); glGenBuffers(1, &rl.vbo); glGenVertexArrays(1, &rl.vao); glGenBuffers(1, &rl.vbo);
@@ -609,11 +638,25 @@ static RingLayer buildRingLayer()
glBindVertexArray(0); glBindVertexArray(0);
rl.prog = makeProgram("shaders/ppi_range_rings.vert", rl.prog = makeProgram("shaders/ppi_range_rings.vert",
"shaders/ppi_range_rings.frag"); "shaders/ppi_range_rings.frag");
// Text layer for range labels
glGenVertexArrays(1, &rl.textVAO); glGenBuffers(1, &rl.textVBO);
glBindVertexArray(rl.textVAO);
glBindBuffer(GL_ARRAY_BUFFER, rl.textVBO);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), nullptr);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float),
reinterpret_cast<void*>(2*sizeof(float)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
rl.textProg = makeProgram("shaders/text.vert", "shaders/text.frag");
rl.fontTex = fa.texture;
return rl; return rl;
} }
static void renderRingLayer(RingLayer& rl, const Layout& L, static void renderRingLayer(RingLayer& rl, const Layout& L,
int rangeIdx, float sweepAngle, float W, float H) int rangeIdx, float sweepAngle, float curTime,
float W, float H, const FontAtlas& fa)
{ {
std::vector<float> v; std::vector<float> v;
buildRingVerts(v, L, rangeIdx, sweepAngle, W, H); buildRingVerts(v, L, rangeIdx, sweepAngle, W, H);
@@ -637,25 +680,192 @@ static void renderRingLayer(RingLayer& rl, const Layout& L,
} }
glDrawArrays(GL_LINES, off, 2); glDrawArrays(GL_LINES, off, 2);
glBindVertexArray(0); glBindVertexArray(0);
// Range labels — placed just below (south of) each ring.
// Sweep activates blue at 180°; color fades to P7P persistence at same
// rate as targets (TARG_PERSIST seconds).
static constexpr float LABEL_THRESH = 3.5f;
static constexpr float LABEL_OFFSET_PX = 10.0f;
if (angDiff(sweepAngle, 180.0f) < LABEL_THRESH)
rl.labelLastActT = curTime;
float ldt = curTime - rl.labelLastActT;
float lfade = (rl.labelLastActT < 0.0f) ? 0.0f
: std::max(0.0f, 1.0f - ldt / TARG_PERSIST);
bool lact = angDiff(sweepAngle, 180.0f) < LABEL_THRESH;
float lr, lg, lb;
if (lact) { lr=P7A_R; lg=P7A_G; lb=P7A_B; }
else { lr=P7P_R * lfade; lg=P7P_G * lfade; lb=P7P_B * lfade; }
if (lr + lg + lb > 0.01f) {
std::vector<float> tv;
for (int ri = 1; ri <= nr; ++ri) {
float frac = (float)ri / nr;
float px = L.ppiCX;
float py = L.ppiCY + frac * L.ppiR + LABEL_OFFSET_PX;
int lmi = (int)std::round(RANGE_CONFIGS[rangeIdx].maxMiles * frac);
appendTextQuads(tv, fa, std::to_string(lmi), px, py, W, H);
}
if (!tv.empty()) {
glBindBuffer(GL_ARRAY_BUFFER, rl.textVBO);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(tv.size()*sizeof(float)),
tv.data(), GL_DYNAMIC_DRAW);
glUseProgram(rl.textProg);
glBindVertexArray(rl.textVAO);
glUniform3f(glGetUniformLocation(rl.textProg, "uColor"), lr, lg, lb);
glUniform1i(glGetUniformLocation(rl.textProg, "uTexture"), 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, rl.fontTex);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)(tv.size() / 4));
glBindTexture(GL_TEXTURE_2D, 0);
glBindVertexArray(0);
}
}
} }
// ─── Feature 5: Fake targets ────────────────────────────────────────────────── // ─── Feature 5: Fake targets ──────────────────────────────────────────────────
//
// Apparent radar cross-section is computed from vessel dimensions and
// aspect angle: apparent = length*|sin(aspect)| + beam*|cos(aspect)|
// where aspect = bearing_from_radar_to_target - target_heading_true
//
// Signal strength tiers (apparent size in feet):
// >= 100 ft : full bright (large ship)
// 50-100 ft : full bright (yacht)
// 10-50 ft : bright (sailboat)
// < 10 ft : medium (kayak/rowboat)
struct FakeTarget { struct FakeTarget {
float bearingDeg; float bearingDeg; // bearing from radar to target (° true, CW from N)
float rangeMiles; float rangeMiles; // range from radar
float coreRadPx; // core blob radius (pixels) float lengthFt; // vessel length
float bloomRadPx; // bloom glow radius (pixels); 0 = no bloom float beamFt; // vessel beam (width)
float headingDeg; // vessel heading (° true)
mutable float lastActT; // glfwGetTime() when sweep last lit this target mutable float lastActT; // glfwGetTime() when sweep last lit this target
}; };
static float angDiff(float a, float b) // unsigned angular separation (°) // Radar cross-section: effective width presented toward the radar
static float apparentFt(const FakeTarget& t)
{ {
float d = std::fmod(std::fabs(a - b), 360.0f); float aspect = t.bearingDeg - t.headingDeg;
return d > 180.0f ? 360.0f - d : d; float rad = aspect * (PI / 180.0f);
return t.lengthFt * std::fabs(std::sin(rad))
+ t.beamFt * std::fabs(std::cos(rad));
} }
// Build one textured quad (x,y,u,v = 4 floats/vertex) centred at screen pixel (px,py). // Blob radius and bloom radius (in pixels) from apparent size
static void sizeFromApparent(float apparent, float ppiR,
float& coreRad, float& bloomRad, float& bright)
{
if (apparent >= 100.0f) {
coreRad = ppiR * 0.028f;
bloomRad = 0.0f;
bright = 1.00f;
} else if (apparent >= 50.0f) {
coreRad = ppiR * 0.022f;
bloomRad = 0.0f;
bright = 1.00f;
} else if (apparent >= 10.0f) {
coreRad = ppiR * 0.015f;
bloomRad = 0.0f;
bright = 0.90f;
} else {
coreRad = ppiR * 0.010f;
bloomRad = 0.0f;
bright = 0.75f;
}
}
// ─── A scope signal trace ─────────────────────────────────────────────────────
// Draws P1-green target spikes on the A scope at the current bearing.
// Activated when the PPI sweep passes the A scope bearing; fades like PPI targets.
struct AScopeTrace {
GLuint vao = 0, vbo = 0, prog = 0;
float lastActT[4];
};
static AScopeTrace buildAScopeTrace()
{
AScopeTrace at{};
for (int i = 0; i < 4; ++i) at.lastActT[i] = -999.0f;
glGenVertexArrays(1, &at.vao); glGenBuffers(1, &at.vbo);
glBindVertexArray(at.vao);
glBindBuffer(GL_ARRAY_BUFFER, at.vbo);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), nullptr);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
at.prog = makeProgram("shaders/scope_bounds.vert", "shaders/scope_bounds.frag");
return at;
}
// Renders target return spikes on the A scope clipped to the A scope box.
static void renderAScopeTrace(AScopeTrace& at, const Layout& L,
FakeTarget* tgts, int nTgts,
float maxRangeMi, float ascopeBearingDeg,
float sweepAngle, float curTime, float W, float H)
{
static const float THRESH = 3.5f;
static const float PAD = 4.0f;
static const float HALF_W = 3.0f; // spike half-width in pixels
const float asH = L.asBot - L.asTop;
const float gx0 = L.asLeft + PAD;
const float gx1 = L.asRight - PAD;
const float gy1 = L.asTop + asH * 0.80f; // baseline
const float sigW = gx1 - gx0;
const float sigH = (L.asTop + PAD) - gy1; // negative: upward from baseline
const bool sweepHere = angDiff(sweepAngle, ascopeBearingDeg) < THRESH;
glEnable(GL_SCISSOR_TEST);
glScissor((GLint)L.asLeft, (GLint)(H - L.asBot),
(GLint)(L.asRight - L.asLeft), (GLint)(L.asBot - L.asTop));
glUseProgram(at.prog);
glBindVertexArray(at.vao);
const GLint locCol = glGetUniformLocation(at.prog, "uColor");
for (int i = 0; i < nTgts; ++i) {
FakeTarget& t = tgts[i];
if (sweepHere && angDiff(ascopeBearingDeg, t.bearingDeg) < THRESH)
at.lastActT[i] = curTime;
float rangeFrac = t.rangeMiles / maxRangeMi;
if (rangeFrac > 1.0f) continue;
float dt = curTime - at.lastActT[i];
float fade = (at.lastActT[i] < 0.0f) ? 0.0f
: std::max(0.0f, 1.0f - dt / TARG_PERSIST);
if (fade < 0.02f) continue;
float coreRad, bloomRad, baseBright;
sizeFromApparent(apparentFt(t), L.ppiR, coreRad, bloomRad, baseBright);
float bright = baseBright * fade;
float height = sigH * bright; // upward (sigH is negative → spike goes up)
float sx = gx0 + rangeFrac * sigW;
float sy0 = gy1; // baseline
float sy1 = gy1 + height; // peak (upward because sigH < 0)
float verts[6] = {
ndcX(sx - HALF_W, W), ndcY(sy0, H),
ndcX(sx, W), ndcY(sy1, H),
ndcX(sx + HALF_W, W), ndcY(sy0, H)
};
glBindBuffer(GL_ARRAY_BUFFER, at.vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW);
glUniform3f(locCol, P1_R * bright, P1_G * bright, P1_B * bright);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
glBindVertexArray(0);
glDisable(GL_SCISSOR_TEST);
}
// Build one textured quad (x,y,u,v = 4 floats/vertex) centred at (px,py)
static void genBlob(std::vector<float>& v, static void genBlob(std::vector<float>& v,
float px, float py, float radiusPx, float W, float H) float px, float py, float radiusPx, float W, float H)
{ {
@@ -691,7 +901,7 @@ static TargetLayer buildTargetLayer()
static void renderTargets(TargetLayer& tl, static void renderTargets(TargetLayer& tl,
FakeTarget* tgts, int nTgts, FakeTarget* tgts, int nTgts,
const Layout& L, float maxRangeMi, const Layout& L, float maxRangeMi, float ppiR,
float sweepAngle, float curTime, float W, float H) float sweepAngle, float curTime, float W, float H)
{ {
static const float THRESH = 3.5f; // sweep activation threshold (°) static const float THRESH = 3.5f; // sweep activation threshold (°)
@@ -709,7 +919,7 @@ static void renderTargets(TargetLayer& tl,
t.lastActT = curTime; t.lastActT = curTime;
float rangeFrac = t.rangeMiles / maxRangeMi; float rangeFrac = t.rangeMiles / maxRangeMi;
if (rangeFrac > 1.0f) continue; if (rangeFrac > 1.0f) continue; // outside current range setting
float px = polarPx(L, t.bearingDeg, rangeFrac); float px = polarPx(L, t.bearingDeg, rangeFrac);
float py = polarPy(L, t.bearingDeg, rangeFrac); float py = polarPy(L, t.bearingDeg, rangeFrac);
@@ -717,9 +927,12 @@ static void renderTargets(TargetLayer& tl,
float dt = curTime - t.lastActT; float dt = curTime - t.lastActT;
float fade = (dt < 0.0f) ? 0.0f : std::max(0.0f, 1.0f - dt/TARG_PERSIST); float fade = (dt < 0.0f) ? 0.0f : std::max(0.0f, 1.0f - dt/TARG_PERSIST);
bool active = angDiff(sweepAngle, t.bearingDeg) < THRESH; bool active = angDiff(sweepAngle, t.bearingDeg) < THRESH;
float bright = active ? 1.0f : fade;
if (bright < 0.01f) continue; float coreRad, bloomRad, baseBright;
sizeFromApparent(apparentFt(t), ppiR, coreRad, bloomRad, baseBright);
float bright = active ? baseBright : baseBright * fade;
if (bright < 0.02f) continue;
float cr, cg, cb; float cr, cg, cb;
if (active) { cr=P7A_R; cg=P7A_G; cb=P7A_B; } if (active) { cr=P7A_R; cg=P7A_G; cb=P7A_B; }
@@ -728,7 +941,7 @@ static void renderTargets(TargetLayer& tl,
// Core blob // Core blob
{ {
std::vector<float> bv; std::vector<float> bv;
genBlob(bv, px, py, t.coreRadPx, W, H); genBlob(bv, px, py, coreRad, W, H);
glBindBuffer(GL_ARRAY_BUFFER, tl.vbo); glBindBuffer(GL_ARRAY_BUFFER, tl.vbo);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(bv.size()*sizeof(float)), glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(bv.size()*sizeof(float)),
bv.data(), GL_DYNAMIC_DRAW); bv.data(), GL_DYNAMIC_DRAW);
@@ -736,22 +949,83 @@ static void renderTargets(TargetLayer& tl,
glUniform1f(locFall, 4.5f); glUniform1f(locFall, 4.5f);
glDrawArrays(GL_TRIANGLES, 0, 6); glDrawArrays(GL_TRIANGLES, 0, 6);
} }
// Bloom glow (large targets only)
if (t.bloomRadPx > 0.0f && bright > 0.05f) {
std::vector<float> bv;
genBlob(bv, px, py, t.bloomRadPx, W, H);
glBindBuffer(GL_ARRAY_BUFFER, tl.vbo);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(bv.size()*sizeof(float)),
bv.data(), GL_DYNAMIC_DRAW);
glUniform3f(locCol, cr*bright*0.35f, cg*bright*0.35f, cb*bright*0.35f);
glUniform1f(locFall, 0.8f);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
} }
glBindVertexArray(0); glBindVertexArray(0);
} }
// ─── Key callback ──────────────────────────────────────────────────────────── // ─── Feature 6: PPI cursor ───────────────────────────────────────────────────
//
// Cursor has three graphical elements (all incandescent):
// • Range ring — full circle at the cursor range fraction
// • Bearing line — radial line from centre to scope edge at cursor bearing
// • Intersection box — small square centred where the two cross
struct CursorLayer {
GLuint prog = 0, vao = 0, vbo = 0;
};
static CursorLayer buildCursorLayer()
{
CursorLayer cl{};
glGenVertexArrays(1, &cl.vao); glGenBuffers(1, &cl.vbo);
glBindVertexArray(cl.vao);
glBindBuffer(GL_ARRAY_BUFFER, cl.vbo);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), nullptr);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
cl.prog = makeProgram("shaders/ppi_cursor.vert", "shaders/ppi_cursor.frag");
return cl;
}
static void renderCursor(CursorLayer& cl, const Layout& L,
float rangeFrac, float bearingDeg, float W, float H)
{
std::vector<float> v;
// ── Range ring (circle at cursor range) ────────────────────────────────
const float rPx = rangeFrac * L.ppiR;
const int ringStart = 0;
for (int i = 0; i <= CIRCLE_SEGS; ++i) {
float a = 2.0f * PI * i / CIRCLE_SEGS;
v.push_back(ndcX(L.ppiCX + rPx * std::cos(a), W));
v.push_back(ndcY(L.ppiCY + rPx * std::sin(a), H));
}
// ── Bearing line (centre → scope edge) ────────────────────────────────
const int lineStart = ringStart + CIRCLE_SEGS + 1;
v.push_back(ndcX(L.ppiCX, W));
v.push_back(ndcY(L.ppiCY, H));
v.push_back(ndcX(polarPx(L, bearingDeg, 1.0f), W));
v.push_back(ndcY(polarPy(L, bearingDeg, 1.0f), H));
// ── Intersection box ───────────────────────────────────────────────────
const float ix = polarPx(L, bearingDeg, rangeFrac);
const float iy = polarPy(L, bearingDeg, rangeFrac);
const float half = L.ppiR * 0.035f;
const int boxStart = lineStart + 2;
// 5 vertices to close the square loop
v.push_back(ndcX(ix - half, W)); v.push_back(ndcY(iy - half, H));
v.push_back(ndcX(ix + half, W)); v.push_back(ndcY(iy - half, H));
v.push_back(ndcX(ix + half, W)); v.push_back(ndcY(iy + half, H));
v.push_back(ndcX(ix - half, W)); v.push_back(ndcY(iy + half, H));
v.push_back(ndcX(ix - half, W)); v.push_back(ndcY(iy - half, H));
glBindBuffer(GL_ARRAY_BUFFER, cl.vbo);
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(v.size() * sizeof(float)),
v.data(), GL_DYNAMIC_DRAW);
glUseProgram(cl.prog);
glUniform3f(glGetUniformLocation(cl.prog, "uColor"), INCAN_R, INCAN_G, INCAN_B);
glBindVertexArray(cl.vao);
glDrawArrays(GL_LINE_STRIP, ringStart, CIRCLE_SEGS + 1); // range ring
glDrawArrays(GL_LINES, lineStart, 2); // bearing line
glDrawArrays(GL_LINE_STRIP, boxStart, 5); // intersection box
glBindVertexArray(0);
}
// ─── Key callback ─────────────────────────────────────────────────────────────
static void onKey(GLFWwindow* win, int key, int /*scan*/, int action, int /*mods*/) static void onKey(GLFWwindow* win, int key, int /*scan*/, int action, int /*mods*/)
{ {
@@ -759,7 +1033,7 @@ static void onKey(GLFWwindow* win, int key, int /*scan*/, int action, int /*mods
glfwSetWindowShouldClose(win, GLFW_TRUE); glfwSetWindowShouldClose(win, GLFW_TRUE);
} }
// ─── main ──────────────────────────────────────────────────────────────────── // ─── main ────────────────────────────────────────────────────────────────────
int main() int main()
{ {
@@ -774,7 +1048,7 @@ int main()
GLFWwindow* win = glfwCreateWindow( GLFWwindow* win = glfwCreateWindow(
mode->width, mode->height, mode->width, mode->height,
"Radar Test — Features 15", "Radar Test — Features 1-6",
nullptr, nullptr); nullptr, nullptr);
if (!win) { std::cerr << "Window create failed\n"; glfwTerminate(); return 1; } if (!win) { std::cerr << "Window create failed\n"; glfwTerminate(); return 1; }
@@ -794,7 +1068,7 @@ int main()
int mmW, mmH; int mmW, mmH;
glfwGetMonitorPhysicalSize(mon, &mmW, &mmH); glfwGetMonitorPhysicalSize(mon, &mmW, &mmH);
const float dpiX = static_cast<float>(mode->width) / (static_cast<float>(mmW) / 25.4f); const float dpiX = static_cast<float>(mode->width) / (static_cast<float>(mmW) / 25.4f);
const float margin = 0.5f * dpiX; const float margin = 1.0f * dpiX;
const Layout layout = computeLayout(W, H, margin); const Layout layout = computeLayout(W, H, margin);
@@ -820,29 +1094,43 @@ int main()
graticules[i] = buildAScopeGraticule(layout, fa, RANGE_CONFIGS[i], W, H); graticules[i] = buildAScopeGraticule(layout, fa, RANGE_CONFIGS[i], W, H);
// Feature 4 — PPI range rings // Feature 4 — PPI range rings
RingLayer rl = buildRingLayer(); RingLayer rl = buildRingLayer(fa);
// Feature 5 — Fake targets (one per quadrant: NE small, SE large, SW/NW very large) // Feature 5 — Four fake targets per CLAUDE.md spec:
const float pR = layout.ppiR; // 1. 10 mi north, 100 ft long / 20 ft beam, heading south (head-on) → apparent ~20 ft
// 2. 5 mi south, 20 ft long / 5 ft beam, heading south (stern-on) → apparent ~5 ft
// 3. 4 mi east, 30 ft long / 10 ft beam, heading north (full side) → apparent ~30 ft
// 4. 1 mi west, 100 ft long / 25 ft beam, heading south (full side) → apparent ~100 ft
FakeTarget targets[4] = { FakeTarget targets[4] = {
{ 55.0f, 1.3f, pR*0.010f, 0.0f, -999.0f }, // NE small (kayak) { 0.0f, 10.0f, 100.0f, 20.0f, 180.0f, -999.0f }, // N head-on, ~20 ft apparent
{ 135.0f, 3.8f, pR*0.022f, pR*0.048f, -999.0f }, // SE large, blooming { 180.0f, 5.0f, 20.0f, 5.0f, 180.0f, -999.0f }, // S stern-on, ~5 ft apparent
{ 215.0f, 2.2f, pR*0.032f, pR*0.075f, -999.0f }, // SW very large { 90.0f, 4.0f, 30.0f, 10.0f, 0.0f, -999.0f }, // E full side, ~30 ft apparent
{ 310.0f, 9.0f, pR*0.032f, pR*0.075f, -999.0f }, // NW very large { 270.0f, 1.0f, 100.0f, 25.0f, 180.0f, -999.0f }, // W full side, ~100 ft apparent
}; };
TargetLayer tl = buildTargetLayer(); TargetLayer tl = buildTargetLayer();
float sweepAngle = 0.0f; // degrees, 0 = north, clockwise // A scope trace
AScopeTrace at = buildAScopeTrace();
// NDC height of the A scope box — used to compute slide distance // Feature 6 — PPI cursor
CursorLayer cl = buildCursorLayer();
const float ppiR = layout.ppiR;
// NDC height of A scope box — used for slide animation
const float scopeNDCH = (layout.asBot - layout.asTop) * 2.0f / H; const float scopeNDCH = (layout.asBot - layout.asTop) * 2.0f / H;
// Animation state // Feature 3 animation state — 4 phases per range cycle
enum class GratPhase { HOLD, SLIDE_OUT, WAIT, SLIDE_IN };
int curRange = 0; int curRange = 0;
int nextRange = 1; int nextRange = 1;
bool sliding = false; GratPhase gratPhase = GratPhase::HOLD;
float holdTimer = 0.0f; float phaseTimer = 0.0f;
float slideTimer = 0.0f;
// A scope bearing (° true, CW from N)
const float ascopeBearingDeg = 90.0f;
float sweepAngle = 0.0f;
float prevTime = static_cast<float>(glfwGetTime()); float prevTime = static_cast<float>(glfwGetTime());
glEnable(GL_BLEND); glEnable(GL_BLEND);
@@ -857,20 +1145,34 @@ int main()
sweepAngle = std::fmod(sweepAngle + SWEEP_DEG_PS * dt, 360.0f); sweepAngle = std::fmod(sweepAngle + SWEEP_DEG_PS * dt, 360.0f);
// ── Advance Feature 3 animation ─────────────────────────────────────── // ── Advance Feature 3 animation ───────────────────────────────────────
if (!sliding) { phaseTimer += dt;
holdTimer += dt; switch (gratPhase) {
if (holdTimer >= HOLD_SEC) { case GratPhase::HOLD:
holdTimer = 0.0f; if (phaseTimer >= HOLD_SEC) {
slideTimer = 0.0f;
nextRange = (curRange + 1) % RANGE_COUNT; nextRange = (curRange + 1) % RANGE_COUNT;
sliding = true; gratPhase = GratPhase::SLIDE_OUT;
phaseTimer = 0.0f;
} }
} else { break;
slideTimer += dt; case GratPhase::SLIDE_OUT:
if (slideTimer >= SLIDE_SEC) { if (phaseTimer >= SLIDE_OUT_SEC) {
gratPhase = GratPhase::WAIT;
phaseTimer = 0.0f;
}
break;
case GratPhase::WAIT:
if (phaseTimer >= WAIT_SEC) {
curRange = nextRange; curRange = nextRange;
sliding = false; gratPhase = GratPhase::SLIDE_IN;
phaseTimer = 0.0f;
} }
break;
case GratPhase::SLIDE_IN:
if (phaseTimer >= SLIDE_IN_SEC) {
gratPhase = GratPhase::HOLD;
phaseTimer = 0.0f;
}
break;
} }
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
@@ -881,7 +1183,7 @@ int main()
const GLint sbCol = glGetUniformLocation(sb.prog, "uColor"); const GLint sbCol = glGetUniformLocation(sb.prog, "uColor");
glBindVertexArray(sb.vao); glBindVertexArray(sb.vao);
glUniform3f(sbCol, 0.25f, 0.35f, 0.55f); glUniform3f(sbCol, P7A_R * 0.4f, P7A_G * 0.4f, P7A_B * 0.4f);
glDrawArrays(GL_LINE_STRIP, sb.ppiStart, sb.ppiCount); glDrawArrays(GL_LINE_STRIP, sb.ppiStart, sb.ppiCount);
glUniform3f(sbCol, P1_R, P1_G, P1_B); glUniform3f(sbCol, P1_R, P1_G, P1_B);
@@ -889,13 +1191,14 @@ int main()
glBindVertexArray(0); glBindVertexArray(0);
// ── Feature 2: PPI bearing ring + ticks ───────────────────────────── // ── Feature 2: PPI bearing rings + ticks ─────────────────────────────
glUseProgram(bg.lineProg); glUseProgram(bg.lineProg);
glUniform3f(glGetUniformLocation(bg.lineProg, "uColor"), glUniform3f(glGetUniformLocation(bg.lineProg, "uColor"),
INCAN_R, INCAN_G, INCAN_B); INCAN_R, INCAN_G, INCAN_B);
glBindVertexArray(bg.lineVAO); glBindVertexArray(bg.lineVAO);
glDrawArrays(GL_LINE_STRIP, bg.ringStart, bg.ringCount); glDrawArrays(GL_LINE_STRIP, bg.ring1Start, bg.ring1Count);
glDrawArrays(GL_LINES, bg.tickStart, bg.tickCount); glDrawArrays(GL_LINES, bg.tickStart, bg.tickCount);
glDrawArrays(GL_LINE_STRIP, bg.ring2Start, bg.ring2Count);
glBindVertexArray(0); glBindVertexArray(0);
glUseProgram(bg.textProg); glUseProgram(bg.textProg);
@@ -909,28 +1212,50 @@ int main()
glBindVertexArray(0); glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
// ── Feature 3: A scope graticule (with slide animation) ─────────────── // ── Feature 3: A scope graticule (4-phase slide animation) ──────────
if (!sliding) { switch (gratPhase) {
case GratPhase::HOLD:
drawAScopeGraticule(agProg, graticules[curRange], drawAScopeGraticule(agProg, graticules[curRange],
0.0f, fa, W, H, layout); 0.0f, fa, W, H, layout);
} else { break;
float t = std::min(slideTimer / SLIDE_SEC, 1.0f); case GratPhase::SLIDE_OUT: {
// Old graticule slides UP (+NDC Y = up) and out through the top slot float t = std::min(phaseTimer / SLIDE_OUT_SEC, 1.0f);
drawAScopeGraticule(agProg, graticules[curRange], drawAScopeGraticule(agProg, graticules[curRange],
t * scopeNDCH, fa, W, H, layout); t * scopeNDCH, fa, W, H, layout);
// New graticule descends from above into position break;
drawAScopeGraticule(agProg, graticules[nextRange], }
case GratPhase::WAIT:
// blank — no graticule drawn
break;
case GratPhase::SLIDE_IN: {
float t = std::min(phaseTimer / SLIDE_IN_SEC, 1.0f);
// new graticule slides down from above (starts at +scopeNDCH → 0)
drawAScopeGraticule(agProg, graticules[curRange],
(1.0f - t) * scopeNDCH, fa, W, H, layout); (1.0f - t) * scopeNDCH, fa, W, H, layout);
break;
}
} }
// ── A scope signal trace ──────────────────────────────────────────────
renderAScopeTrace(at, layout, targets, 4,
RANGE_CONFIGS[curRange].maxMiles, ascopeBearingDeg,
sweepAngle, now, W, H);
// ── Feature 4: PPI range rings ──────────────────────────────────────── // ── Feature 4: PPI range rings ────────────────────────────────────────
renderRingLayer(rl, layout, curRange, sweepAngle, W, H); renderRingLayer(rl, layout, curRange, sweepAngle, now, W, H, fa);
// ── Feature 5: Active targets + persistence ─────────────────────────── // ── Feature 5: Active targets + persistence ───────────────────────────
renderTargets(tl, targets, 4, layout, renderTargets(tl, targets, 4, layout,
RANGE_CONFIGS[curRange].maxMiles, RANGE_CONFIGS[curRange].maxMiles, ppiR,
sweepAngle, now, W, H); sweepAngle, now, W, H);
// ── Feature 6: PPI cursor (slow auto-animation for test) ──────────────
// Range oscillates between ~15 % and ~85 % over 12 s period.
// Bearing rotates slowly at 18 °/s (one full revolution per 20 s).
float cursorRangeFrac = 0.15f + 0.70f * (std::sin(now * (2.0f*PI/12.0f)) * 0.5f + 0.5f);
float cursorBearingDeg = std::fmod(now * 18.0f, 360.0f);
renderCursor(cl, layout, cursorRangeFrac, cursorBearingDeg, W, H);
glfwSwapBuffers(win); glfwSwapBuffers(win);
glfwPollEvents(); glfwPollEvents();
} }
@@ -953,9 +1278,15 @@ int main()
glDeleteProgram(agProg.line); glDeleteProgram(agProg.line);
glDeleteProgram(agProg.text); glDeleteProgram(agProg.text);
glDeleteVertexArrays(1, &rl.vao); glDeleteBuffers(1, &rl.vbo); glDeleteVertexArrays(1, &rl.vao); glDeleteBuffers(1, &rl.vbo);
glDeleteVertexArrays(1, &rl.textVAO); glDeleteBuffers(1, &rl.textVBO);
glDeleteProgram(rl.prog); glDeleteProgram(rl.prog);
glDeleteProgram(rl.textProg);
glDeleteVertexArrays(1, &tl.vao); glDeleteBuffers(1, &tl.vbo); glDeleteVertexArrays(1, &tl.vao); glDeleteBuffers(1, &tl.vbo);
glDeleteProgram(tl.prog); glDeleteProgram(tl.prog);
glDeleteVertexArrays(1, &at.vao); glDeleteBuffers(1, &at.vbo);
glDeleteProgram(at.prog);
glDeleteVertexArrays(1, &cl.vao); glDeleteBuffers(1, &cl.vbo);
glDeleteProgram(cl.prog);
glfwDestroyWindow(win); glfwDestroyWindow(win);
glfwTerminate(); glfwTerminate();