diff --git a/CLAUDE.md b/CLAUDE.md index f1e3ac9..9a78153 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,8 @@ The proposed location of the radar antenna is at the dock of the Community boating center in Bellingham, Washington. 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. Selectable ranges should be 2, 5, 10, and 15 miles @@ -119,7 +121,7 @@ Display colors: 1. A Scope is P1 (same as oscilloscope) 2. A Scope graticule is incandescent color 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 scatters, graticule range rings, shoreline 5. PPI scope bearing ring and ticks is incandescent color. @@ -135,12 +137,16 @@ is 15 miles from the center. Signal strength: Need to have following fixed signal strength: -1. large ship would be bright and blooming -2. yachts would be bright but not blooming -3. sailboats would be medium bright and not blooming -4. kayaks and rowboats would be small and dim + Size is part of data for some targets (based on AIS and ADS-B + data. Use size of boat (usually in feet) expressed in length + and width. Use those values in relation of the heading if known. + 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 - and sailboats above 3 miles + and sailboats above 10 miles 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 have the words "SIGNAL STRENGTH" and bottom of the screen you 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 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 1. Active boats/planes; brightness determined by size as noted above -2. Blue white color +2. Blue PPI Scope range rings -1. blue white (dim) +1. blue (dim) 2. These change if operator changes max range 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. 4. Use text for every 10 degrees, but text on outside of ring. 5. Have ring around tick marks +6. 2nd ring around the text marks Controls: @@ -283,12 +291,17 @@ Order of testing features. 4. PPI scope range rings; both active display and persistence display - test for each range settings; hold for 5 seconds each 5. PPI scope active target operation, as well as persistance. Create - four fake targets, one small, one large and two very large with - blooming. Do random range and bearing with one in earch quadrant. -5. PPI scope cursor - test by slowly changing range and bearing -6. PPI scope weather noise - test by changing noise level slowly -7. PPI scope waves noise - test by changing noise level slowly -8. PPI scope handling of shoreline - test by running for a few seconds + four fake targets: one small, one large and two very large with + 1. target 5 miles north of radar, 100 feet long heding 1 knot south + head on, width of target is 20 feet. + 2. target 5 miles south of radar, 20 feet long, 5 feet wide + headng away at 20 knots + 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 ======================================================== diff --git a/build/CMakeFiles/radar_simulation.dir/compiler_depend.internal b/build/CMakeFiles/radar_simulation.dir/compiler_depend.internal index 02b873e..8060d30 100644 --- a/build/CMakeFiles/radar_simulation.dir/compiler_depend.internal +++ b/build/CMakeFiles/radar_simulation.dir/compiler_depend.internal @@ -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/pthreadtypes-arch.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/select2.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-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/stdlib-bsearch.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_rwlock.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.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/select2.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-least.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/stdlib-bsearch.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_rwlock.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/waitstatus.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/wordsize.h /usr/include/x86_64-linux-gnu/bits/xopen_lim.h diff --git a/build/CMakeFiles/radar_simulation.dir/compiler_depend.make b/build/CMakeFiles/radar_simulation.dir/compiler_depend.make index 6132bd5..8ca0044 100644 --- a/build/CMakeFiles/radar_simulation.dir/compiler_depend.make +++ b/build/CMakeFiles/radar_simulation.dir/compiler_depend.make @@ -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/pthreadtypes-arch.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/select2.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-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/stdlib-bsearch.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_rwlock.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.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/select2.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-least.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/stdlib-bsearch.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_rwlock.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/waitstatus.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/wordsize.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: -/lib/x86_64-linux-gnu/libpng16.so.16: - /lib/x86_64-linux-gnu/libc.so.6: /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/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/postypes.h: - /usr/include/x86_64-linux-gnu/asm/posix_types.h: /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/bits/stl_algo.h: - /usr/include/c++/15/bits/cxxabi_forced.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/x86_64-linux-gnu/bits/posix2_lim.h: - /usr/include/c++/15/bits/ios_base.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/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/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/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/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/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: @@ -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/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/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/x86_64-linux-gnu/bits/wchar2-decl.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/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: -/home/maallyn/radar-simulation/glad/src/glad.c: - /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/dlfcn.h: +/usr/include/linux/types.h: /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/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/ctype.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/c++/15/bits/exception_ptr.h: @@ -695,10 +720,6 @@ CMakeFiles/radar_simulation.dir/src/main.cpp.o: /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/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/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h: - /usr/include/c++/15/bits/memoryfwd.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/ostream_insert.h: - -/usr/include/x86_64-linux-gnu/bits/errno.h: - /usr/include/features-time64.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/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: @@ -775,27 +806,53 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o: /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/c++/15/concepts: - /usr/include/freetype2/freetype/fterrors.h: +/usr/include/c++/15/concepts: + /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h: /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: @@ -829,6 +886,10 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o: /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/streambuf.tcc: @@ -859,10 +920,6 @@ CMakeFiles/radar_simulation.dir/glad/src/glad.c.o: /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/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/legendre_function.tcc: - /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/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/x86_64-linux-gnu/bits/stdio2-decl.h: + /usr/include/linux/posix_types.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/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/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/wchar2.h: + /usr/include/x86_64-linux-gnu/bits/fp-logb.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/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: diff --git a/build/CMakeFiles/radar_simulation.dir/src/main.cpp.o b/build/CMakeFiles/radar_simulation.dir/src/main.cpp.o index 276626f..aa63105 100644 Binary files a/build/CMakeFiles/radar_simulation.dir/src/main.cpp.o and b/build/CMakeFiles/radar_simulation.dir/src/main.cpp.o differ diff --git a/build/radar_simulation b/build/radar_simulation index 593b72e..124da7d 100755 Binary files a/build/radar_simulation and b/build/radar_simulation differ diff --git a/build/shaders/ppi_cursor.frag b/build/shaders/ppi_cursor.frag new file mode 100644 index 0000000..e845509 --- /dev/null +++ b/build/shaders/ppi_cursor.frag @@ -0,0 +1,6 @@ +#version 330 core +uniform vec3 uColor; +out vec4 FragColor; +void main() { + FragColor = vec4(uColor, 1.0); +} diff --git a/build/shaders/ppi_cursor.vert b/build/shaders/ppi_cursor.vert new file mode 100644 index 0000000..bd444ef --- /dev/null +++ b/build/shaders/ppi_cursor.vert @@ -0,0 +1,5 @@ +#version 330 core +layout(location = 0) in vec2 aPos; +void main() { + gl_Position = vec4(aPos, 0.0, 1.0); +} diff --git a/shaders/ascope_graticule.frag b/shaders/ascope_graticule.frag new file mode 100644 index 0000000..e845509 --- /dev/null +++ b/shaders/ascope_graticule.frag @@ -0,0 +1,6 @@ +#version 330 core +uniform vec3 uColor; +out vec4 FragColor; +void main() { + FragColor = vec4(uColor, 1.0); +} diff --git a/shaders/ascope_graticule.vert b/shaders/ascope_graticule.vert new file mode 100644 index 0000000..bd1a394 --- /dev/null +++ b/shaders/ascope_graticule.vert @@ -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); +} diff --git a/shaders/ascope_graticule_text.frag b/shaders/ascope_graticule_text.frag new file mode 100644 index 0000000..83e1f3d --- /dev/null +++ b/shaders/ascope_graticule_text.frag @@ -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); +} diff --git a/shaders/ascope_graticule_text.vert b/shaders/ascope_graticule_text.vert new file mode 100644 index 0000000..a27e74a --- /dev/null +++ b/shaders/ascope_graticule_text.vert @@ -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; +} diff --git a/shaders/ppi_cursor.frag b/shaders/ppi_cursor.frag new file mode 100644 index 0000000..e845509 --- /dev/null +++ b/shaders/ppi_cursor.frag @@ -0,0 +1,6 @@ +#version 330 core +uniform vec3 uColor; +out vec4 FragColor; +void main() { + FragColor = vec4(uColor, 1.0); +} diff --git a/shaders/ppi_cursor.vert b/shaders/ppi_cursor.vert new file mode 100644 index 0000000..bd444ef --- /dev/null +++ b/shaders/ppi_cursor.vert @@ -0,0 +1,5 @@ +#version 330 core +layout(location = 0) in vec2 aPos; +void main() { + gl_Position = vec4(aPos, 0.0, 1.0); +} diff --git a/shaders/ppi_range_rings.frag b/shaders/ppi_range_rings.frag new file mode 100644 index 0000000..e24db23 --- /dev/null +++ b/shaders/ppi_range_rings.frag @@ -0,0 +1,6 @@ +#version 330 core +in vec3 vColor; +out vec4 fragColor; +void main() { + fragColor = vec4(vColor, 1.0); +} diff --git a/shaders/ppi_range_rings.vert b/shaders/ppi_range_rings.vert new file mode 100644 index 0000000..5413791 --- /dev/null +++ b/shaders/ppi_range_rings.vert @@ -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; +} diff --git a/shaders/ppi_targets.frag b/shaders/ppi_targets.frag new file mode 100644 index 0000000..64d95b7 --- /dev/null +++ b/shaders/ppi_targets.frag @@ -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); +} diff --git a/shaders/ppi_targets.vert b/shaders/ppi_targets.vert new file mode 100644 index 0000000..f25d9e0 --- /dev/null +++ b/shaders/ppi_targets.vert @@ -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; +} diff --git a/src/main.cpp b/src/main.cpp index 0cea2e9..a8c5f9b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,12 @@ -// Radar Simulation — Feature Test: 1, 2 & 3 +// Radar Simulation — Feature Test: 1–6 // 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, -// 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. #include @@ -22,37 +26,47 @@ // ─── Constants ─────────────────────────────────────────────────────────────── -static constexpr float PI = 3.14159265358979323846f; -static constexpr int CIRCLE_SEGS = 360; +static constexpr float PI = 3.14159265358979323846f; +static constexpr int CIRCLE_SEGS = 360; // Incandescent (warm lamp) — bearing graticule and A scope graticule static constexpr float INCAN_R = 1.00f; static constexpr float INCAN_G = 0.78f; 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_G = 0.90f; 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' -static constexpr int GLYPH_FIRST = '0'; -static constexpr int GLYPH_COUNT = 10; +static constexpr int GLYPH_FIRST = '0'; +static constexpr int GLYPH_COUNT = 10; // Feature 3 timing -static constexpr float HOLD_SEC = 5.0f; -static constexpr float SLIDE_SEC = 0.5f; +static constexpr float HOLD_SEC = 20.0f; // seconds per range setting +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) -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; +// Feature 4 — sweep +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 head (°) +static constexpr int RING_SEGS = 300; +static constexpr int TRAIL_SEGS = 50; -// Sweep / persistence -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 TARG_PERSIST = 5.0f; // target glow lifetime (s) -static constexpr int RING_SEGS = 300; -static constexpr int TRAIL_SEGS = 50; +// Feature 5 — target glow lifetime (s) after sweep passes +static constexpr float TARG_PERSIST = 5.0f; // ─── NDC helpers ───────────────────────────────────────────────────────────── @@ -286,6 +300,14 @@ static Layout computeLayout(float W, float H, float marginPx) 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 ───────────────────────────────────────────── struct ScopeBounds { @@ -299,6 +321,7 @@ static ScopeBounds buildScopeBounds(const Layout& L, float W, float H) ScopeBounds sb{}; std::vector v; + // PPI scope circle sb.ppiStart = 0; sb.ppiCount = CIRCLE_SEGS + 1; for (int i = 0; i <= CIRCLE_SEGS; ++i) { @@ -307,16 +330,17 @@ static ScopeBounds buildScopeBounds(const Layout& L, float W, float H) v.push_back(ndcY(L.ppiCY + L.ppiR * std::sin(a), H)); } + // A scope bounding box sb.asStart = sb.ppiStart + sb.ppiCount; sb.asCount = 8; auto ln = [&](float x1, float y1, float x2, float y2) { v.push_back(ndcX(x1,W)); v.push_back(ndcY(y1,H)); v.push_back(ndcX(x2,W)); v.push_back(ndcY(y2,H)); }; - ln(L.asLeft, L.asTop, L.asRight, L.asTop); - ln(L.asRight, L.asTop, L.asRight, L.asBot); - ln(L.asRight, L.asBot, L.asLeft, L.asBot); - ln(L.asLeft, L.asBot, L.asLeft, L.asTop); + ln(L.asLeft, L.asTop, L.asRight, L.asTop); + ln(L.asRight, L.asTop, L.asRight, L.asBot); + ln(L.asRight, L.asBot, L.asLeft, L.asBot); + ln(L.asLeft, L.asBot, L.asLeft, L.asTop); makeLineVAO(sb.vao, sb.vbo, v); sb.prog = makeProgram("shaders/scope_bounds.vert", "shaders/scope_bounds.frag"); @@ -329,8 +353,9 @@ struct BearingGraticule { GLuint lineProg = 0, textProg = 0; GLuint lineVAO = 0, lineVBO = 0; GLuint textVAO = 0, textVBO = 0; - int ringStart = 0, ringCount = 0; - int tickStart = 0, tickCount = 0; + int ring1Start = 0, ring1Count = 0; // inner ring (at scope edge) + int tickStart = 0, tickCount = 0; // degree tick marks (outward) + int ring2Start = 0, ring2Count = 0; // outer ring (encircles labels) int textVerts = 0; GLuint fontTex = 0; }; @@ -343,33 +368,46 @@ static BearingGraticule buildBearingGraticule(const Layout& L, const FontAtlas& std::vector lineV; - bg.ringStart = 0; - bg.ringCount = CIRCLE_SEGS + 1; + // Inner ring (scope edge) + bg.ring1Start = 0; + bg.ring1Count = CIRCLE_SEGS + 1; for (int i = 0; i <= CIRCLE_SEGS; ++i) { float a = 2.0f * PI * i / CIRCLE_SEGS; lineV.push_back(ndcX(cx + R * std::cos(a), W)); 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 minorLen = 0.025f * R; - bg.tickStart = bg.ringStart + bg.ringCount; + bg.tickStart = bg.ring1Start + bg.ring1Count; bg.tickCount = 360 * 2; for (int b = 0; b < 360; ++b) { float brad = b * PI / 180.0f; - float sb = std::sin(brad); - float cb = std::cos(brad); + float sb_ = std::sin(brad); + float cb_ = std::cos(brad); float len = (b % 10 == 0) ? majorLen : minorLen; - lineV.push_back(ndcX(cx + R * sb, W)); - lineV.push_back(ndcY(cy - R * cb, H)); - lineV.push_back(ndcX(cx + (R-len) * sb, W)); - lineV.push_back(ndcY(cy - (R-len) * cb, H)); + lineV.push_back(ndcX(cx + R * sb_, W)); + lineV.push_back(ndcY(cy - R * cb_, H)); + lineV.push_back(ndcX(cx + (R + len) * sb_, W)); + 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); + // Degree labels (every 10°) between the tick tips and the outer ring std::vector textV; - const float textR = R * 1.07f; + const float textR = R * 1.09f; for (int b = 0; b < 360; b += 10) { float brad = b * PI / 180.0f; float tx = cx + textR * std::sin(brad); @@ -386,11 +424,8 @@ static BearingGraticule buildBearingGraticule(const Layout& L, const FontAtlas& 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 { float maxMiles; int numMajor; @@ -405,6 +440,8 @@ static const RangeConfig RANGE_CONFIGS[4] = { }; static constexpr int RANGE_COUNT = 4; +// ─── Feature 3: A scope replaceable graticule ──────────────────────────────── + struct AScopeGraticule { GLuint lineVAO = 0, lineVBO = 0; GLuint textVAO = 0, textVBO = 0; @@ -412,7 +449,6 @@ struct AScopeGraticule { int textVerts = 0; }; -// Shared shader programs for all A scope graticules (uYOffset drives animation) struct AScopeGratProg { GLuint line = 0; GLuint text = 0; @@ -436,10 +472,10 @@ static AScopeGraticule buildAScopeGraticule( const float asH = L.asBot - L.asTop; const float pad = 4.0f; - const float gx0 = L.asLeft + pad; // signal area left - const float gx1 = L.asRight - pad; // signal area right - const float gy0 = L.asTop + pad; // top of signal area - const float gy1 = L.asTop + asH * 0.80f; // baseline (signal y = 0) + const float gx0 = L.asLeft + pad; + const float gx1 = L.asRight - pad; + const float gy0 = L.asTop + pad; // top of signal area + const float gy1 = L.asTop + asH * 0.80f; // baseline const float sigW = gx1 - gx0; const float sigH = gy1 - gy0; @@ -449,7 +485,7 @@ static AScopeGraticule buildAScopeGraticule( 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.asRight, L.asTop, L.asRight, 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, 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) 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; 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; ln(xMaj, gy1, xMaj, gy0); - // Minor ticks between this major and the next float xBase = gx0 + m * majorSpan; for (int n = 1; n <= rc.numMinorPerMajor; ++n) { float xMin = xBase + n * minorSpan; @@ -484,12 +518,12 @@ static AScopeGraticule buildAScopeGraticule( ag.lineCount = (int)lineV.size() / 2; 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 textV; - const float labelY = L.asTop + asH * 0.90f; + const float labelY = L.asTop + asH * 0.90f; const float milesPerMajor = rc.maxMiles / rc.numMajor; for (int m = 1; m <= rc.numMajor; ++m) { - float x = gx0 + m * majorSpan; + float x = gx0 + m * majorSpan; int labelMi = (int)std::round(m * milesPerMajor); appendTextQuads(textV, fa, std::to_string(labelMi), x, labelY, W, H); } @@ -499,15 +533,11 @@ static AScopeGraticule buildAScopeGraticule( 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( const AScopeGratProg& prog, const AScopeGraticule& ag, float yOffNDC, const FontAtlas& fa, float W, float H, const Layout& L) { glEnable(GL_SCISSOR_TEST); - // OpenGL scissor uses window coords (y=0 at bottom of window) glScissor((GLint) L.asLeft, (GLint)(H - L.asBot), (GLint)(L.asRight - L.asLeft), @@ -535,20 +565,9 @@ static void drawAScopeGraticule( 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 ────────────────────────────────────────────── -// Build vertex data (x,y,r,g,b = 5 floats/vertex): -// 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 +// 5 floats/vertex: NDC x, y, r, g, b static void buildRingVerts(std::vector& v, const Layout& L, int rangeIdx, float sweepAngle, float W, float H) @@ -561,7 +580,7 @@ static void buildRingVerts(std::vector& v, 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) { float frac = (float)ri / nr; for (int i = 0; i <= RING_SEGS; ++i) { @@ -572,7 +591,7 @@ static void buildRingVerts(std::vector& 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) { float frac = (float)ri / nr; for (int i = 0; i <= TRAIL_SEGS; ++i) { @@ -585,17 +604,27 @@ static void buildRingVerts(std::vector& v, } } - // Sweep line (centre → edge) + // Sweep line (centre → edge) in active blue push(L.ppiCX, L.ppiCY, P7A_R, P7A_G, P7A_B); push(polarPx(L,sweepAngle,1.0f), polarPy(L,sweepAngle,1.0f), 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 { - 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{}; glGenVertexArrays(1, &rl.vao); glGenBuffers(1, &rl.vbo); @@ -609,11 +638,25 @@ static RingLayer buildRingLayer() glBindVertexArray(0); rl.prog = makeProgram("shaders/ppi_range_rings.vert", "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(2*sizeof(float))); + glEnableVertexAttribArray(1); + glBindVertexArray(0); + rl.textProg = makeProgram("shaders/text.vert", "shaders/text.frag"); + rl.fontTex = fa.texture; return rl; } 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 v; buildRingVerts(v, L, rangeIdx, sweepAngle, W, H); @@ -637,36 +680,203 @@ static void renderRingLayer(RingLayer& rl, const Layout& L, } glDrawArrays(GL_LINES, off, 2); 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 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 ────────────────────────────────────────────────── +// +// 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 { - float bearingDeg; - float rangeMiles; - float coreRadPx; // core blob radius (pixels) - float bloomRadPx; // bloom glow radius (pixels); 0 = no bloom - mutable float lastActT; // glfwGetTime() when sweep last lit this target + float bearingDeg; // bearing from radar to target (° true, CW from N) + float rangeMiles; // range from radar + float lengthFt; // vessel length + float beamFt; // vessel beam (width) + float headingDeg; // vessel heading (° true) + 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); - return d > 180.0f ? 360.0f - d : d; + float aspect = t.bearingDeg - t.headingDeg; + 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& v, float px, float py, float radiusPx, float W, float H) { float x0=ndcX(px-radiusPx,W), y0=ndcY(py-radiusPx,H); float x1=ndcX(px+radiusPx,W), y1=ndcY(py+radiusPx,H); - v.push_back(x0);v.push_back(y0);v.push_back(-1.f);v.push_back(-1.f); - v.push_back(x1);v.push_back(y0);v.push_back( 1.f);v.push_back(-1.f); - v.push_back(x1);v.push_back(y1);v.push_back( 1.f);v.push_back( 1.f); - v.push_back(x0);v.push_back(y0);v.push_back(-1.f);v.push_back(-1.f); - v.push_back(x1);v.push_back(y1);v.push_back( 1.f);v.push_back( 1.f); - v.push_back(x0);v.push_back(y1);v.push_back(-1.f);v.push_back( 1.f); + v.push_back(x0); v.push_back(y0); v.push_back(-1.f); v.push_back(-1.f); + v.push_back(x1); v.push_back(y0); v.push_back( 1.f); v.push_back(-1.f); + v.push_back(x1); v.push_back(y1); v.push_back( 1.f); v.push_back( 1.f); + v.push_back(x0); v.push_back(y0); v.push_back(-1.f); v.push_back(-1.f); + v.push_back(x1); v.push_back(y1); v.push_back( 1.f); v.push_back( 1.f); + v.push_back(x0); v.push_back(y1); v.push_back(-1.f); v.push_back( 1.f); } struct TargetLayer { @@ -691,7 +901,7 @@ static TargetLayer buildTargetLayer() static void renderTargets(TargetLayer& tl, FakeTarget* tgts, int nTgts, - const Layout& L, float maxRangeMi, + const Layout& L, float maxRangeMi, float ppiR, float sweepAngle, float curTime, float W, float H) { static const float THRESH = 3.5f; // sweep activation threshold (°) @@ -709,7 +919,7 @@ static void renderTargets(TargetLayer& tl, t.lastActT = curTime; 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 py = polarPy(L, t.bearingDeg, rangeFrac); @@ -717,9 +927,12 @@ static void renderTargets(TargetLayer& tl, float dt = curTime - t.lastActT; float fade = (dt < 0.0f) ? 0.0f : std::max(0.0f, 1.0f - dt/TARG_PERSIST); 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; if (active) { cr=P7A_R; cg=P7A_G; cb=P7A_B; } @@ -728,7 +941,7 @@ static void renderTargets(TargetLayer& tl, // Core blob { std::vector bv; - genBlob(bv, px, py, t.coreRadPx, W, H); + genBlob(bv, px, py, coreRad, W, H); glBindBuffer(GL_ARRAY_BUFFER, tl.vbo); glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(bv.size()*sizeof(float)), bv.data(), GL_DYNAMIC_DRAW); @@ -736,22 +949,83 @@ static void renderTargets(TargetLayer& tl, glUniform1f(locFall, 4.5f); glDrawArrays(GL_TRIANGLES, 0, 6); } - // Bloom glow (large targets only) - if (t.bloomRadPx > 0.0f && bright > 0.05f) { - std::vector 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); } -// ─── 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 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*/) { @@ -759,7 +1033,7 @@ static void onKey(GLFWwindow* win, int key, int /*scan*/, int action, int /*mods glfwSetWindowShouldClose(win, GLFW_TRUE); } -// ─── main ──────────────────────────────────────────────────────────────────── +// ─── main ───────────────────────────────────────────────────────────────────── int main() { @@ -774,7 +1048,7 @@ int main() GLFWwindow* win = glfwCreateWindow( mode->width, mode->height, - "Radar Test — Features 1–5", + "Radar Test — Features 1-6", nullptr, nullptr); if (!win) { std::cerr << "Window create failed\n"; glfwTerminate(); return 1; } @@ -794,7 +1068,7 @@ int main() int mmW, mmH; glfwGetMonitorPhysicalSize(mon, &mmW, &mmH); const float dpiX = static_cast(mode->width) / (static_cast(mmW) / 25.4f); - const float margin = 0.5f * dpiX; + const float margin = 1.0f * dpiX; const Layout layout = computeLayout(W, H, margin); @@ -820,29 +1094,43 @@ int main() graticules[i] = buildAScopeGraticule(layout, fa, RANGE_CONFIGS[i], W, H); // 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) - const float pR = layout.ppiR; + // Feature 5 — Four fake targets per CLAUDE.md spec: + // 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] = { - { 55.0f, 1.3f, pR*0.010f, 0.0f, -999.0f }, // NE – small (kayak) - { 135.0f, 3.8f, pR*0.022f, pR*0.048f, -999.0f }, // SE – large, blooming - { 215.0f, 2.2f, pR*0.032f, pR*0.075f, -999.0f }, // SW – very large - { 310.0f, 9.0f, pR*0.032f, pR*0.075f, -999.0f }, // NW – very large + { 0.0f, 10.0f, 100.0f, 20.0f, 180.0f, -999.0f }, // N – head-on, ~20 ft apparent + { 180.0f, 5.0f, 20.0f, 5.0f, 180.0f, -999.0f }, // S – stern-on, ~5 ft apparent + { 90.0f, 4.0f, 30.0f, 10.0f, 0.0f, -999.0f }, // E – full side, ~30 ft apparent + { 270.0f, 1.0f, 100.0f, 25.0f, 180.0f, -999.0f }, // W – full side, ~100 ft apparent }; 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; - // Animation state - int curRange = 0; - int nextRange = 1; - bool sliding = false; - float holdTimer = 0.0f; - float slideTimer = 0.0f; + // Feature 3 animation state — 4 phases per range cycle + enum class GratPhase { HOLD, SLIDE_OUT, WAIT, SLIDE_IN }; + int curRange = 0; + int nextRange = 1; + GratPhase gratPhase = GratPhase::HOLD; + float phaseTimer = 0.0f; + + // A scope bearing (° true, CW from N) + const float ascopeBearingDeg = 90.0f; + + float sweepAngle = 0.0f; float prevTime = static_cast(glfwGetTime()); glEnable(GL_BLEND); @@ -857,20 +1145,34 @@ int main() sweepAngle = std::fmod(sweepAngle + SWEEP_DEG_PS * dt, 360.0f); // ── Advance Feature 3 animation ─────────────────────────────────────── - if (!sliding) { - holdTimer += dt; - if (holdTimer >= HOLD_SEC) { - holdTimer = 0.0f; - slideTimer = 0.0f; - nextRange = (curRange + 1) % RANGE_COUNT; - sliding = true; - } - } else { - slideTimer += dt; - if (slideTimer >= SLIDE_SEC) { - curRange = nextRange; - sliding = false; - } + phaseTimer += dt; + switch (gratPhase) { + case GratPhase::HOLD: + if (phaseTimer >= HOLD_SEC) { + nextRange = (curRange + 1) % RANGE_COUNT; + gratPhase = GratPhase::SLIDE_OUT; + phaseTimer = 0.0f; + } + break; + case GratPhase::SLIDE_OUT: + if (phaseTimer >= SLIDE_OUT_SEC) { + gratPhase = GratPhase::WAIT; + phaseTimer = 0.0f; + } + break; + case GratPhase::WAIT: + if (phaseTimer >= WAIT_SEC) { + curRange = nextRange; + 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); @@ -881,7 +1183,7 @@ int main() const GLint sbCol = glGetUniformLocation(sb.prog, "uColor"); 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); glUniform3f(sbCol, P1_R, P1_G, P1_B); @@ -889,13 +1191,14 @@ int main() glBindVertexArray(0); - // ── Feature 2: PPI bearing ring + ticks ────────────────────────────── + // ── Feature 2: PPI bearing rings + ticks ───────────────────────────── glUseProgram(bg.lineProg); glUniform3f(glGetUniformLocation(bg.lineProg, "uColor"), INCAN_R, INCAN_G, INCAN_B); glBindVertexArray(bg.lineVAO); - glDrawArrays(GL_LINE_STRIP, bg.ringStart, bg.ringCount); - glDrawArrays(GL_LINES, bg.tickStart, bg.tickCount); + glDrawArrays(GL_LINE_STRIP, bg.ring1Start, bg.ring1Count); + glDrawArrays(GL_LINES, bg.tickStart, bg.tickCount); + glDrawArrays(GL_LINE_STRIP, bg.ring2Start, bg.ring2Count); glBindVertexArray(0); glUseProgram(bg.textProg); @@ -909,36 +1212,58 @@ int main() glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); - // ── Feature 3: A scope graticule (with slide animation) ─────────────── - if (!sliding) { - drawAScopeGraticule(agProg, graticules[curRange], - 0.0f, fa, W, H, layout); - } else { - float t = std::min(slideTimer / SLIDE_SEC, 1.0f); - // Old graticule slides UP (+NDC Y = up) and out through the top slot - drawAScopeGraticule(agProg, graticules[curRange], - t * scopeNDCH, fa, W, H, layout); - // New graticule descends from above into position - drawAScopeGraticule(agProg, graticules[nextRange], - (1.0f - t) * scopeNDCH, fa, W, H, layout); + // ── Feature 3: A scope graticule (4-phase slide animation) ────────── + switch (gratPhase) { + case GratPhase::HOLD: + drawAScopeGraticule(agProg, graticules[curRange], + 0.0f, fa, W, H, layout); + break; + case GratPhase::SLIDE_OUT: { + float t = std::min(phaseTimer / SLIDE_OUT_SEC, 1.0f); + drawAScopeGraticule(agProg, graticules[curRange], + t * scopeNDCH, fa, W, H, layout); + break; + } + 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); + break; + } } + // ── A scope signal trace ────────────────────────────────────────────── + renderAScopeTrace(at, layout, targets, 4, + RANGE_CONFIGS[curRange].maxMiles, ascopeBearingDeg, + sweepAngle, now, W, H); + // ── 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 ─────────────────────────── renderTargets(tl, targets, 4, layout, - RANGE_CONFIGS[curRange].maxMiles, + RANGE_CONFIGS[curRange].maxMiles, ppiR, 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); glfwPollEvents(); } // ── Cleanup ─────────────────────────────────────────────────────────────── - glDeleteVertexArrays(1, &sb.vao); glDeleteBuffers(1, &sb.vbo); - glDeleteVertexArrays(1, &bg.lineVAO); glDeleteBuffers(1, &bg.lineVBO); - glDeleteVertexArrays(1, &bg.textVAO); glDeleteBuffers(1, &bg.textVBO); + glDeleteVertexArrays(1, &sb.vao); glDeleteBuffers(1, &sb.vbo); + glDeleteVertexArrays(1, &bg.lineVAO); glDeleteBuffers(1, &bg.lineVBO); + glDeleteVertexArrays(1, &bg.textVAO); glDeleteBuffers(1, &bg.textVBO); glDeleteTextures(1, &fa.texture); glDeleteProgram(sb.prog); glDeleteProgram(bg.lineProg); @@ -952,10 +1277,16 @@ int main() } glDeleteProgram(agProg.line); 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); - glDeleteVertexArrays(1, &tl.vao); glDeleteBuffers(1, &tl.vbo); + glDeleteProgram(rl.textProg); + glDeleteVertexArrays(1, &tl.vao); glDeleteBuffers(1, &tl.vbo); 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); glfwTerminate();