diff --git a/CLAUDE.md b/CLAUDE.md index d06b569..3294083 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -661,11 +661,87 @@ PPIScope : public Scope (abstract) — shared PPI behavior: - Cursor range/bearing readout under scope (white text) - Bearing offset for boat mode (k/j) - Cursor range clamped to max range - - Range rings and labels are beam-painted per sweep sector, NOT a static overlay; - the sweep shader evaluates ring radii for the CURRENT max range at the moment - each beam angle is rendered; the phosphor buffer retains whatever was last - written, so old ring positions fade naturally as the sweep overwrites them with - new geometry — no special transition code required + - Range rings are beam-painted per sweep sector with P7 persistence and decay; + however they are stored in the GAIN-INDEPENDENT G channel of the phosphor FBO + (see PHOSPHOR FBO ARCHITECTURE below) so operator gain does not dim the rings + - renderRingLabels() — virtual method (default no-op); concrete PPI scopes that + have labelled range rings override this to render mile-distance text labels in + P7 fresh-blue colour at a fixed bearing (RING_LABEL_BRG_DEG = 045°) + + IMPLEMENTER CHECKLIST — required in every new PPIScope subclass: + 1. computeRingRadii(): multiply each normalised ring radius by + GRAT_INNER_RING_FRAC (same as MarinePPIScope). Omitting this + places the outer ring at the scope boundary where it is clipped + and hidden behind the graticule. Target positions are scaled + automatically by PhosphorRenderer::update(); ring radii are not. + 2. Override renderRingLabels() using the same pattern as + MarinePPIScope::renderRingLabels() but with the scope's own + ring-mile table. The base-class no-op produces no labels. + The p7Color() fix, two-channel FBO gain-separation, and target + position scaling are all automatic via the shared PhosphorRenderer + and shaders — no per-scope action required for those. + +================================================================== + +PHOSPHOR FBO ARCHITECTURE + +================================================================== + +The phosphor FBO is GL_RG32F (two independent float channels): + + R channel — signal energy + Written by: target echoes in the sweep shader + Multiplied by: u_gain in the display shader + Effect: operator gain knob dims/brightens received echoes without + affecting the sweep beam or range rings + + G channel — timing/geometry energy + Written by: range rings + sweep background glow in the sweep shader + NOT multiplied by gain in the display shader + Effect: rings always appear at a fixed brightness; the rotating + sweep-line glow is always visible even at minimum gain + +Both channels decay at the same P7 rate (P7_DECAY_RATE in settings.h). +The display shader combines them: totalEnergy = max(R * gain, G). +This produces the correct visual priority: a strong target echo always +shows above the ring but a dim echo below gain threshold fades away +while the ring stays steady. + +RANGE POSITION NORMALISATION + +All ring radii and target range values are normalised so that +max-range maps to GRAT_INNER_RING_FRAC (0.915), NOT 1.0. + +Normalised 1.0 is the outer edge of the phosphor circle (scope boundary). +The bearing graticule overlay occupies 0.915 to 0.985 of scope radius. +If max-range mapped to 1.0, the outer ring would sit at the scope +boundary — half-clipped by the sweep shader's rng > 1.0 early-exit and +visually hidden behind the graticule outer ring. + +Mapping max-range → GRAT_INNER_RING_FRAC keeps all rings and targets +within the clean active display area inside the bearing scale overlay. + +Scale is applied in two places: + 1. PhosphorRenderer::update() — target range: × GRAT_INNER_RING_FRAC + 2. computeRingRadii() in each concrete PPI scope — ring radii: × GRAT_INNER_RING_FRAC + +P7 COLOUR FUNCTION + +p7Color() in phosphor.frag is a piecewise linear ramp over [0, 1]: + e ≥ T_BLUE (0.82) → pure C_BLUE + [T_GREEN, T_BLUE) → mix(C_GREEN, C_BLUE, normalised within range) + [T_YGREE, T_GREEN) → mix(C_YGREE, C_GREEN, normalised within range) + [T_DARK, T_YGREE) → mix(C_YELLW, C_YGREE, normalised within range) + [0, T_DARK) → mix(C_BLACK, C_YELLW, normalised within range) + +Each mix() factor is in [0, 1] and the function is continuous at every +threshold boundary. An earlier version had each branch using the formula +of the branch below it (off-by-one), which caused SWEEP_BACKGROUND_ENERGY += 0.10 to render as saturated yellow (factor 3.33) instead of dim +yellow-green, producing an unwanted solid-yellow band behind the sweep. + +================================================================== + MarinePPIScope : public PPIScope - Sweep time: 4 seconds diff --git a/build/CMakeCache.txt b/build/CMakeCache.txt index e380de2..1a8a55c 100644 --- a/build/CMakeCache.txt +++ b/build/CMakeCache.txt @@ -351,6 +351,8 @@ CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 CMAKE_DLLTOOL-ADVANCED:INTERNAL=1 //Path to cache edit program executable. CMAKE_EDIT_COMMAND:INTERNAL=/usr/bin/cmake-gui +//Whether to issue deprecation errors for macros and functions. +CMAKE_ERROR_DEPRECATED:INTERNAL=FALSE //Executable file format CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF //ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS @@ -440,12 +442,20 @@ CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 //ADVANCED property for variable: CMAKE_STRIP CMAKE_STRIP-ADVANCED:INTERNAL=1 +//Suppress errors that are meant for the author of the CMakeLists.txt +// files. +CMAKE_SUPPRESS_DEVELOPER_ERRORS:INTERNAL=TRUE +//Suppress Warnings that are meant for the author of the CMakeLists.txt +// files. +CMAKE_SUPPRESS_DEVELOPER_WARNINGS:INTERNAL=TRUE //ADVANCED property for variable: CMAKE_TAPI CMAKE_TAPI-ADVANCED:INTERNAL=1 //uname command CMAKE_UNAME:INTERNAL=/usr/bin/uname //ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1 +//Whether to issue warnings for deprecated functionality. +CMAKE_WARN_DEPRECATED:INTERNAL=FALSE //Details about finding Freetype FIND_PACKAGE_MESSAGE_DETAILS_Freetype:INTERNAL=[/usr/lib/x86_64-linux-gnu/libfreetype.so][/usr/include/freetype2][v2.14.2()] //Details about finding OpenGL diff --git a/build/CMakeFiles/Makefile.cmake b/build/CMakeFiles/Makefile.cmake index ac2a3db..03b6ef2 100644 --- a/build/CMakeFiles/Makefile.cmake +++ b/build/CMakeFiles/Makefile.cmake @@ -11,98 +11,21 @@ set(CMAKE_MAKEFILE_DEPENDS "CMakeFiles/4.2.3/CMakeCCompiler.cmake" "CMakeFiles/4.2.3/CMakeCXXCompiler.cmake" "CMakeFiles/4.2.3/CMakeSystem.cmake" - "/usr/share/cmake-4.2/Modules/CMakeCCompiler.cmake.in" - "/usr/share/cmake-4.2/Modules/CMakeCCompilerABI.c" "/usr/share/cmake-4.2/Modules/CMakeCInformation.cmake" - "/usr/share/cmake-4.2/Modules/CMakeCXXCompiler.cmake.in" - "/usr/share/cmake-4.2/Modules/CMakeCXXCompilerABI.cpp" "/usr/share/cmake-4.2/Modules/CMakeCXXInformation.cmake" "/usr/share/cmake-4.2/Modules/CMakeCommonLanguageInclude.cmake" - "/usr/share/cmake-4.2/Modules/CMakeCompilerIdDetection.cmake" - "/usr/share/cmake-4.2/Modules/CMakeDetermineCCompiler.cmake" - "/usr/share/cmake-4.2/Modules/CMakeDetermineCXXCompiler.cmake" - "/usr/share/cmake-4.2/Modules/CMakeDetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/CMakeDetermineCompilerABI.cmake" - "/usr/share/cmake-4.2/Modules/CMakeDetermineCompilerId.cmake" - "/usr/share/cmake-4.2/Modules/CMakeDetermineCompilerSupport.cmake" - "/usr/share/cmake-4.2/Modules/CMakeDetermineSystem.cmake" - "/usr/share/cmake-4.2/Modules/CMakeFindBinUtils.cmake" "/usr/share/cmake-4.2/Modules/CMakeGenericSystem.cmake" "/usr/share/cmake-4.2/Modules/CMakeInitializeConfigs.cmake" "/usr/share/cmake-4.2/Modules/CMakeLanguageInformation.cmake" - "/usr/share/cmake-4.2/Modules/CMakeParseImplicitIncludeInfo.cmake" - "/usr/share/cmake-4.2/Modules/CMakeParseImplicitLinkInfo.cmake" - "/usr/share/cmake-4.2/Modules/CMakeParseLibraryArchitecture.cmake" - "/usr/share/cmake-4.2/Modules/CMakeSystem.cmake.in" "/usr/share/cmake-4.2/Modules/CMakeSystemSpecificInformation.cmake" "/usr/share/cmake-4.2/Modules/CMakeSystemSpecificInitialize.cmake" - "/usr/share/cmake-4.2/Modules/CMakeTestCCompiler.cmake" - "/usr/share/cmake-4.2/Modules/CMakeTestCXXCompiler.cmake" - "/usr/share/cmake-4.2/Modules/CMakeTestCompilerCommon.cmake" - "/usr/share/cmake-4.2/Modules/CMakeUnixFindMake.cmake" "/usr/share/cmake-4.2/Modules/CheckCSourceCompiles.cmake" "/usr/share/cmake-4.2/Modules/CheckIncludeFile.cmake" "/usr/share/cmake-4.2/Modules/CheckLibraryExists.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/ADSP-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/ARMCC-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/ARMClang-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/AppleClang-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Borland-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Bruce-C-DetermineCompiler.cmake" "/usr/share/cmake-4.2/Modules/Compiler/CMakeCommonCompilerMacros.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Clang-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Clang-DetermineCompilerInternal.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Compaq-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Compaq-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Cray-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/CrayClang-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Diab-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Embarcadero-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Fujitsu-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/FujitsuClang-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/GHS-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/GNU-C-DetermineCompiler.cmake" "/usr/share/cmake-4.2/Modules/Compiler/GNU-C.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/GNU-CXX-DetermineCompiler.cmake" "/usr/share/cmake-4.2/Modules/Compiler/GNU-CXX.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/GNU-FindBinUtils.cmake" "/usr/share/cmake-4.2/Modules/Compiler/GNU.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/HP-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/HP-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/IAR-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/IBMCPP-C-DetermineVersionInternal.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/IBMCPP-CXX-DetermineVersionInternal.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/IBMClang-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/IBMClang-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Intel-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/IntelLLVM-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/LCC-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/LCC-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/MSVC-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/NVHPC-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/NVIDIA-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/OpenWatcom-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/OrangeC-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/PGI-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/PathScale-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Renesas-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/SCO-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/SDCC-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/SunPro-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/SunPro-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/TI-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/TIClang-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Tasking-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/TinyCC-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/VisualAge-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/VisualAge-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/Watcom-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/XL-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/XL-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/XLClang-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/XLClang-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/zOS-C-DetermineCompiler.cmake" - "/usr/share/cmake-4.2/Modules/Compiler/zOS-CXX-DetermineCompiler.cmake" "/usr/share/cmake-4.2/Modules/FindFreetype.cmake" "/usr/share/cmake-4.2/Modules/FindOpenGL.cmake" "/usr/share/cmake-4.2/Modules/FindPackageHandleStandardArgs.cmake" @@ -112,11 +35,7 @@ set(CMAKE_MAKEFILE_DEPENDS "/usr/share/cmake-4.2/Modules/Internal/CMakeCLinkerInformation.cmake" "/usr/share/cmake-4.2/Modules/Internal/CMakeCXXLinkerInformation.cmake" "/usr/share/cmake-4.2/Modules/Internal/CMakeCommonLinkerInformation.cmake" - "/usr/share/cmake-4.2/Modules/Internal/CMakeDetermineLinkerId.cmake" - "/usr/share/cmake-4.2/Modules/Internal/CMakeInspectCLinker.cmake" - "/usr/share/cmake-4.2/Modules/Internal/CMakeInspectCXXLinker.cmake" "/usr/share/cmake-4.2/Modules/Internal/CheckSourceCompiles.cmake" - "/usr/share/cmake-4.2/Modules/Internal/FeatureTesting.cmake" "/usr/share/cmake-4.2/Modules/Linker/GNU-C.cmake" "/usr/share/cmake-4.2/Modules/Linker/GNU-CXX.cmake" "/usr/share/cmake-4.2/Modules/Linker/GNU.cmake" @@ -124,7 +43,6 @@ set(CMAKE_MAKEFILE_DEPENDS "/usr/share/cmake-4.2/Modules/Platform/Linker/Linux-GNU-C.cmake" "/usr/share/cmake-4.2/Modules/Platform/Linker/Linux-GNU-CXX.cmake" "/usr/share/cmake-4.2/Modules/Platform/Linker/Linux-GNU.cmake" - "/usr/share/cmake-4.2/Modules/Platform/Linux-Determine-CXX.cmake" "/usr/share/cmake-4.2/Modules/Platform/Linux-GNU-C.cmake" "/usr/share/cmake-4.2/Modules/Platform/Linux-GNU-CXX.cmake" "/usr/share/cmake-4.2/Modules/Platform/Linux-GNU.cmake" @@ -142,13 +60,6 @@ set(CMAKE_MAKEFILE_OUTPUTS # Byproducts of CMake generate step: set(CMAKE_MAKEFILE_PRODUCTS - "CMakeFiles/4.2.3/CMakeSystem.cmake" - "CMakeFiles/4.2.3/CMakeCCompiler.cmake" - "CMakeFiles/4.2.3/CMakeCXXCompiler.cmake" - "CMakeFiles/4.2.3/CMakeCCompiler.cmake" - "CMakeFiles/4.2.3/CMakeCCompiler.cmake" - "CMakeFiles/4.2.3/CMakeCXXCompiler.cmake" - "CMakeFiles/4.2.3/CMakeCXXCompiler.cmake" "CMakeFiles/CMakeDirectoryInformation.cmake" ) diff --git a/build/CMakeFiles/radar.dir/compiler_depend.internal b/build/CMakeFiles/radar.dir/compiler_depend.internal index 7839caa..1aa987c 100644 --- a/build/CMakeFiles/radar.dir/compiler_depend.internal +++ b/build/CMakeFiles/radar.dir/compiler_depend.internal @@ -2432,6 +2432,7 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o /usr/include/c++/15/bits/ranges_util.h /usr/include/c++/15/bits/refwrap.h /usr/include/c++/15/bits/requires_hosted.h + /usr/include/c++/15/bits/specfun.h /usr/include/c++/15/bits/std_abs.h /usr/include/c++/15/bits/std_mutex.h /usr/include/c++/15/bits/stl_algobase.h @@ -2457,13 +2458,13 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o /usr/include/c++/15/cerrno /usr/include/c++/15/climits /usr/include/c++/15/clocale + /usr/include/c++/15/cmath /usr/include/c++/15/compare /usr/include/c++/15/concepts /usr/include/c++/15/cstddef /usr/include/c++/15/cstdint /usr/include/c++/15/cstdio /usr/include/c++/15/cstdlib - /usr/include/c++/15/cstring /usr/include/c++/15/ctime /usr/include/c++/15/cwchar /usr/include/c++/15/debug/assertions.h @@ -2484,6 +2485,18 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o /usr/include/c++/15/ratio /usr/include/c++/15/string /usr/include/c++/15/string_view + /usr/include/c++/15/tr1/bessel_function.tcc + /usr/include/c++/15/tr1/beta_function.tcc + /usr/include/c++/15/tr1/ell_integral.tcc + /usr/include/c++/15/tr1/exp_integral.tcc + /usr/include/c++/15/tr1/gamma.tcc + /usr/include/c++/15/tr1/hypergeometric.tcc + /usr/include/c++/15/tr1/legendre_function.tcc + /usr/include/c++/15/tr1/modified_bessel_func.tcc + /usr/include/c++/15/tr1/poly_hermite.tcc + /usr/include/c++/15/tr1/poly_laguerre.tcc + /usr/include/c++/15/tr1/riemann_zeta.tcc + /usr/include/c++/15/tr1/special_function_util.h /usr/include/c++/15/tuple /usr/include/c++/15/type_traits /usr/include/c++/15/unordered_map @@ -2502,14 +2515,13 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o /usr/include/linux/stddef.h /usr/include/linux/types.h /usr/include/locale.h + /usr/include/math.h /usr/include/pthread.h /usr/include/sched.h /usr/include/stdc-predef.h /usr/include/stdint.h /usr/include/stdio.h /usr/include/stdlib.h - /usr/include/string.h - /usr/include/strings.h /usr/include/syscall.h /usr/include/time.h /usr/include/unistd.h @@ -2531,12 +2543,22 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o /usr/include/x86_64-linux-gnu/bits/errno.h /usr/include/x86_64-linux-gnu/bits/floatn-common.h /usr/include/x86_64-linux-gnu/bits/floatn.h + /usr/include/x86_64-linux-gnu/bits/flt-eval-method.h + /usr/include/x86_64-linux-gnu/bits/fp-fast.h + /usr/include/x86_64-linux-gnu/bits/fp-logb.h /usr/include/x86_64-linux-gnu/bits/getopt_core.h /usr/include/x86_64-linux-gnu/bits/getopt_posix.h + /usr/include/x86_64-linux-gnu/bits/iscanonical.h /usr/include/x86_64-linux-gnu/bits/libc-header-start.h + /usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h /usr/include/x86_64-linux-gnu/bits/local_lim.h /usr/include/x86_64-linux-gnu/bits/locale.h /usr/include/x86_64-linux-gnu/bits/long-double.h + /usr/include/x86_64-linux-gnu/bits/math-vector.h + /usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h + /usr/include/x86_64-linux-gnu/bits/mathcalls-macros.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/posix1_lim.h /usr/include/x86_64-linux-gnu/bits/posix2_lim.h /usr/include/x86_64-linux-gnu/bits/posix_opt.h diff --git a/build/CMakeFiles/radar.dir/compiler_depend.make b/build/CMakeFiles/radar.dir/compiler_depend.make index 9cbf247..9950ec8 100644 --- a/build/CMakeFiles/radar.dir/compiler_depend.make +++ b/build/CMakeFiles/radar.dir/compiler_depend.make @@ -2421,6 +2421,7 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o: /home/maallyn/new-radar/src/sco /usr/include/c++/15/bits/ranges_util.h \ /usr/include/c++/15/bits/refwrap.h \ /usr/include/c++/15/bits/requires_hosted.h \ + /usr/include/c++/15/bits/specfun.h \ /usr/include/c++/15/bits/std_abs.h \ /usr/include/c++/15/bits/std_mutex.h \ /usr/include/c++/15/bits/stl_algobase.h \ @@ -2446,13 +2447,13 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o: /home/maallyn/new-radar/src/sco /usr/include/c++/15/cerrno \ /usr/include/c++/15/climits \ /usr/include/c++/15/clocale \ + /usr/include/c++/15/cmath \ /usr/include/c++/15/compare \ /usr/include/c++/15/concepts \ /usr/include/c++/15/cstddef \ /usr/include/c++/15/cstdint \ /usr/include/c++/15/cstdio \ /usr/include/c++/15/cstdlib \ - /usr/include/c++/15/cstring \ /usr/include/c++/15/ctime \ /usr/include/c++/15/cwchar \ /usr/include/c++/15/debug/assertions.h \ @@ -2473,6 +2474,18 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o: /home/maallyn/new-radar/src/sco /usr/include/c++/15/ratio \ /usr/include/c++/15/string \ /usr/include/c++/15/string_view \ + /usr/include/c++/15/tr1/bessel_function.tcc \ + /usr/include/c++/15/tr1/beta_function.tcc \ + /usr/include/c++/15/tr1/ell_integral.tcc \ + /usr/include/c++/15/tr1/exp_integral.tcc \ + /usr/include/c++/15/tr1/gamma.tcc \ + /usr/include/c++/15/tr1/hypergeometric.tcc \ + /usr/include/c++/15/tr1/legendre_function.tcc \ + /usr/include/c++/15/tr1/modified_bessel_func.tcc \ + /usr/include/c++/15/tr1/poly_hermite.tcc \ + /usr/include/c++/15/tr1/poly_laguerre.tcc \ + /usr/include/c++/15/tr1/riemann_zeta.tcc \ + /usr/include/c++/15/tr1/special_function_util.h \ /usr/include/c++/15/tuple \ /usr/include/c++/15/type_traits \ /usr/include/c++/15/unordered_map \ @@ -2491,14 +2504,13 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o: /home/maallyn/new-radar/src/sco /usr/include/linux/stddef.h \ /usr/include/linux/types.h \ /usr/include/locale.h \ + /usr/include/math.h \ /usr/include/pthread.h \ /usr/include/sched.h \ /usr/include/stdc-predef.h \ /usr/include/stdint.h \ /usr/include/stdio.h \ /usr/include/stdlib.h \ - /usr/include/string.h \ - /usr/include/strings.h \ /usr/include/syscall.h \ /usr/include/time.h \ /usr/include/unistd.h \ @@ -2520,12 +2532,22 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o: /home/maallyn/new-radar/src/sco /usr/include/x86_64-linux-gnu/bits/errno.h \ /usr/include/x86_64-linux-gnu/bits/floatn-common.h \ /usr/include/x86_64-linux-gnu/bits/floatn.h \ + /usr/include/x86_64-linux-gnu/bits/flt-eval-method.h \ + /usr/include/x86_64-linux-gnu/bits/fp-fast.h \ + /usr/include/x86_64-linux-gnu/bits/fp-logb.h \ /usr/include/x86_64-linux-gnu/bits/getopt_core.h \ /usr/include/x86_64-linux-gnu/bits/getopt_posix.h \ + /usr/include/x86_64-linux-gnu/bits/iscanonical.h \ /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \ + /usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h \ /usr/include/x86_64-linux-gnu/bits/local_lim.h \ /usr/include/x86_64-linux-gnu/bits/locale.h \ /usr/include/x86_64-linux-gnu/bits/long-double.h \ + /usr/include/x86_64-linux-gnu/bits/math-vector.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-macros.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/posix1_lim.h \ /usr/include/x86_64-linux-gnu/bits/posix2_lim.h \ /usr/include/x86_64-linux-gnu/bits/posix_opt.h \ diff --git a/build/CMakeFiles/radar.dir/src/graticule.cpp.o b/build/CMakeFiles/radar.dir/src/graticule.cpp.o index 8000719..fcdc5b3 100644 Binary files a/build/CMakeFiles/radar.dir/src/graticule.cpp.o and b/build/CMakeFiles/radar.dir/src/graticule.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/knob_panel.cpp.o b/build/CMakeFiles/radar.dir/src/knob_panel.cpp.o index 60492d4..4152fea 100644 Binary files a/build/CMakeFiles/radar.dir/src/knob_panel.cpp.o and b/build/CMakeFiles/radar.dir/src/knob_panel.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/left_panel.cpp.o b/build/CMakeFiles/radar.dir/src/left_panel.cpp.o index ca4be11..db844f8 100644 Binary files a/build/CMakeFiles/radar.dir/src/left_panel.cpp.o and b/build/CMakeFiles/radar.dir/src/left_panel.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/main.cpp.o b/build/CMakeFiles/radar.dir/src/main.cpp.o index 18c9f8a..1cd92e1 100644 Binary files a/build/CMakeFiles/radar.dir/src/main.cpp.o and b/build/CMakeFiles/radar.dir/src/main.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/phosphor.cpp.o b/build/CMakeFiles/radar.dir/src/phosphor.cpp.o index cb17e59..958ce74 100644 Binary files a/build/CMakeFiles/radar.dir/src/phosphor.cpp.o and b/build/CMakeFiles/radar.dir/src/phosphor.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/rpi_receiver.cpp.o b/build/CMakeFiles/radar.dir/src/rpi_receiver.cpp.o index 015c6a5..7792b44 100644 Binary files a/build/CMakeFiles/radar.dir/src/rpi_receiver.cpp.o and b/build/CMakeFiles/radar.dir/src/rpi_receiver.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/scope.cpp.o b/build/CMakeFiles/radar.dir/src/scope.cpp.o index cf756d9..58d3d81 100644 Binary files a/build/CMakeFiles/radar.dir/src/scope.cpp.o and b/build/CMakeFiles/radar.dir/src/scope.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/scope_intro.cpp.o b/build/CMakeFiles/radar.dir/src/scope_intro.cpp.o index 213954f..0f68855 100644 Binary files a/build/CMakeFiles/radar.dir/src/scope_intro.cpp.o and b/build/CMakeFiles/radar.dir/src/scope_intro.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/scope_manager.cpp.o b/build/CMakeFiles/radar.dir/src/scope_manager.cpp.o index 9958cba..6947050 100644 Binary files a/build/CMakeFiles/radar.dir/src/scope_manager.cpp.o and b/build/CMakeFiles/radar.dir/src/scope_manager.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o b/build/CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o index d94c092..1d81e10 100644 Binary files a/build/CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o and b/build/CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o.d b/build/CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o.d index 0df8cc6..ba3d657 100644 --- a/build/CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o.d +++ b/build/CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o.d @@ -199,4 +199,26 @@ CMakeFiles/radar.dir/src/scope_marine_ppi.cpp.o: \ /home/maallyn/new-radar/include/KHR/khrplatform.h \ /home/maallyn/new-radar/src/phosphor.h \ /home/maallyn/new-radar/src/graticule.h /usr/include/GLFW/glfw3.h \ - /usr/include/c++/15/cstring /usr/include/string.h /usr/include/strings.h + /usr/include/c++/15/cmath /usr/include/math.h \ + /usr/include/x86_64-linux-gnu/bits/math-vector.h \ + /usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h \ + /usr/include/x86_64-linux-gnu/bits/flt-eval-method.h \ + /usr/include/x86_64-linux-gnu/bits/fp-logb.h \ + /usr/include/x86_64-linux-gnu/bits/fp-fast.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-macros.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-narrow.h \ + /usr/include/x86_64-linux-gnu/bits/iscanonical.h \ + /usr/include/c++/15/bits/specfun.h /usr/include/c++/15/tr1/gamma.tcc \ + /usr/include/c++/15/tr1/special_function_util.h \ + /usr/include/c++/15/tr1/bessel_function.tcc \ + /usr/include/c++/15/tr1/beta_function.tcc \ + /usr/include/c++/15/tr1/ell_integral.tcc \ + /usr/include/c++/15/tr1/exp_integral.tcc \ + /usr/include/c++/15/tr1/hypergeometric.tcc \ + /usr/include/c++/15/tr1/legendre_function.tcc \ + /usr/include/c++/15/tr1/modified_bessel_func.tcc \ + /usr/include/c++/15/tr1/poly_hermite.tcc \ + /usr/include/c++/15/tr1/poly_laguerre.tcc \ + /usr/include/c++/15/tr1/riemann_zeta.tcc diff --git a/build/CMakeFiles/radar.dir/src/scope_ppi.cpp.o b/build/CMakeFiles/radar.dir/src/scope_ppi.cpp.o index a61caa5..75f83b3 100644 Binary files a/build/CMakeFiles/radar.dir/src/scope_ppi.cpp.o and b/build/CMakeFiles/radar.dir/src/scope_ppi.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/shared_render_state.cpp.o b/build/CMakeFiles/radar.dir/src/shared_render_state.cpp.o index 04a426b..b03fcec 100644 Binary files a/build/CMakeFiles/radar.dir/src/shared_render_state.cpp.o and b/build/CMakeFiles/radar.dir/src/shared_render_state.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/simulator.cpp.o b/build/CMakeFiles/radar.dir/src/simulator.cpp.o index fee8029..558efd1 100644 Binary files a/build/CMakeFiles/radar.dir/src/simulator.cpp.o and b/build/CMakeFiles/radar.dir/src/simulator.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/target_buffer.cpp.o b/build/CMakeFiles/radar.dir/src/target_buffer.cpp.o index c675d7b..f37fa06 100644 Binary files a/build/CMakeFiles/radar.dir/src/target_buffer.cpp.o and b/build/CMakeFiles/radar.dir/src/target_buffer.cpp.o differ diff --git a/build/CMakeFiles/radar.dir/src/traffic_cop.cpp.o b/build/CMakeFiles/radar.dir/src/traffic_cop.cpp.o index 9ec3b5f..588f75f 100644 Binary files a/build/CMakeFiles/radar.dir/src/traffic_cop.cpp.o and b/build/CMakeFiles/radar.dir/src/traffic_cop.cpp.o differ diff --git a/build/radar b/build/radar index b17ce3a..3b532ac 100755 Binary files a/build/radar and b/build/radar differ diff --git a/build/shaders/phosphor.frag b/build/shaders/phosphor.frag index 7e7ee0e..adc89ed 100644 --- a/build/shaders/phosphor.frag +++ b/build/shaders/phosphor.frag @@ -2,9 +2,15 @@ * MIT License * Author: Mark Allyn * - * phosphor.frag — maps the single-channel phosphor energy texture to - * the P7 colour sequence (blue → green → yellow-green → dark) and - * applies a simple inline bloom (box-filter glow) to bright pixels. + * phosphor.frag — maps the two-channel phosphor energy texture to the + * P7 colour sequence (blue → green → yellow-green → dark) and applies + * a simple inline bloom (box-filter glow) to bright pixels. + * + * The phosphor FBO is GL_RG32F: + * R channel — signal energy (target echoes, sweep background) + * multiplied by u_gain before display + * G channel — range ring energy, gain-independent; mixed with signal + * after gain is applied so rings never dim with gain * * Coordinate system: gl_FragCoord.xy in GL viewport pixels (origin * bottom-left). Scope centre is passed as u_scopeCenter in the same @@ -14,17 +20,19 @@ out vec4 fragColor; -uniform sampler2D u_phosphor; // GL_R32F phosphor energy FBO +uniform sampler2D u_phosphor; // GL_RG32F phosphor energy FBO uniform vec2 u_scopeCenter; // scope centre in GL viewport pixels (bottom-left origin) uniform float u_scopeRadius; // scope radius in pixels -uniform float u_gain; // receiver gain [0,1] — scales brightness +uniform float u_gain; // receiver gain [0,1] — scales signal (R) channel only uniform float u_bloomStep; // UV step for bloom sample (≈ 2.5 / FBO_SIZE) uniform float u_bloomStrength; // additive blend weight for bloom -// P7 energy thresholds (match settings.h) +// P7 energy thresholds — MUST match settings.h P7_THRESH_* constants. +// T_YGREE is intentionally low (0.05) to keep most of the decay in the +// GREEN zone; see the comment in settings.h for the full rationale. const float T_BLUE = 0.82; const float T_GREEN = 0.55; -const float T_YGREE = 0.22; +const float T_YGREE = 0.05; const float T_DARK = 0.03; // P7 colour anchors @@ -34,16 +42,22 @@ const vec3 C_YGREE = vec3(0.50, 1.00, 0.05); const vec3 C_YELLW = vec3(0.70, 0.70, 0.00); const vec3 C_BLACK = vec3(0.00, 0.00, 0.00); +// P7 colour ramp: hue selected by energy level, then scaled by energy so +// brightness decreases monotonically from fresh strike (peak) to dark. +// This prevents intermediate decay colours (yellow-green) from appearing +// brighter than the initial blue flash. vec3 p7Color(float e) { + if (e < T_DARK) return C_BLACK; + vec3 hue; if (e >= T_BLUE) - return mix(C_GREEN, C_BLUE, (e - T_GREEN) / (T_BLUE - T_GREEN)); - if (e >= T_GREEN) - return mix(C_YGREE, C_GREEN, (e - T_YGREE) / (T_GREEN - T_YGREE)); - if (e >= T_YGREE) - return mix(C_YELLW, C_YGREE, (e - T_DARK ) / (T_YGREE - T_DARK )); - if (e >= T_DARK) - return mix(C_BLACK, C_YELLW, e / T_DARK); - return C_BLACK; + hue = C_BLUE; + else if (e >= T_GREEN) + hue = mix(C_GREEN, C_BLUE, (e - T_GREEN) / (T_BLUE - T_GREEN)); + else if (e >= T_YGREE) + hue = mix(C_YGREE, C_GREEN, (e - T_YGREE) / (T_GREEN - T_YGREE)); + else + hue = mix(C_YELLW, C_YGREE, (e - T_DARK) / (T_YGREE - T_DARK)); + return hue * e; } void main() { @@ -57,10 +71,12 @@ void main() { } // Map from PPI delta [-1,+1] to phosphor texture UV [0,1] - // delta.x = east, delta.y = north (both y directions already match) vec2 uv = delta * 0.5 + 0.5; - float energy = texture(u_phosphor, uv).r * u_gain; + vec2 rg = texture(u_phosphor, uv).rg; + // Signal (R): gain-scaled received echoes. + // Ring (G): gain-independent timing reference; always at full brightness. + float energy = max(rg.r * u_gain, rg.g); // Inline bloom: weighted box-filter over a 5×5 neighbourhood float bloom = 0.0; @@ -68,7 +84,8 @@ void main() { for (int dx = -2; dx <= 2; dx++) { for (int dy = -2; dy <= 2; dy++) { float w = exp(-float(dx*dx + dy*dy) * 0.45); - float e = texture(u_phosphor, uv + vec2(dx, dy) * u_bloomStep).r; + vec2 srg = texture(u_phosphor, uv + vec2(dx, dy) * u_bloomStep).rg; + float e = max(srg.r * u_gain, srg.g); bloom += e * w; wsum += w; } diff --git a/build/shaders/sweep.frag b/build/shaders/sweep.frag index 0e22a12..1b45f32 100644 --- a/build/shaders/sweep.frag +++ b/build/shaders/sweep.frag @@ -4,13 +4,16 @@ * * sweep.frag — phosphor accumulation update shader. * - * For each texel in the 1024×1024 phosphor FBO: - * 1. Decay the previous frame's energy by u_decayFactor. - * 2. If the texel's PPI bearing falls within the current sweep arc - * [u_beamAnglePrev, u_beamAngle], add contributions from: - * - range rings (beam-painted per the P7 spec) - * - target echoes - * 3. Output the updated single-channel energy value. + * The FBO is GL_RG32F (two independent energy channels): + * R — signal energy: target echoes + sweep-background glow. + * Multiplied by u_gain in the display pass so operators can + * adjust received-signal brightness without touching rings. + * G — range-ring energy: written at u_ringBrightness; NOT scaled + * by gain. Rings are a precision timing reference, not a + * received echo. Both channels decay at the same P7 rate. + * + * The sweep background (u_sweepBg) goes into the G channel so the + * rotating beam is always visible regardless of the gain setting. * * PPI convention: north = +y, east = +x; bearing = atan2(x, y) * in degrees, clockwise from north. @@ -19,13 +22,13 @@ in vec2 vTexCoord; -layout(location = 0) out vec4 fragOut; // .r = energy; .gba unused +layout(location = 0) out vec4 fragOut; // .r = signal; .g = ring+sweep; .ba unused -uniform sampler2D u_prevPhosphor; // previous frame's energy texture (GL_R32F) +uniform sampler2D u_prevPhosphor; // previous frame's energy texture (GL_RG32F) uniform float u_decayFactor; // exp(-decay_rate * dt) uniform float u_beamAngle; // current beam angle, degrees CW from north uniform float u_beamAnglePrev; // beam angle at previous frame -uniform float u_sweepBg; // ambient sweep-line energy (makes beam visible) +uniform float u_sweepBg; // ambient sweep-line energy (gain-independent) uniform float u_halfBeamDeg; // half-beamwidth for target blobs (display widening) // Targets: .x = range_norm (0-1), .y = bearing_deg, .z = brightness, .w = size_norm @@ -59,8 +62,8 @@ bool inSweep(float b, float prev, float curr) { // ---------------------------------------------------------------- void main() { - vec2 pos = vTexCoord * 2.0 - 1.0; // PPI coords: (-1,-1) SW … (+1,+1) NE - float rng = length(pos); + vec2 pos = vTexCoord * 2.0 - 1.0; // PPI coords: (-1,-1) SW … (+1,+1) NE + float rng = length(pos); if (rng > 1.0) { fragOut = vec4(0.0); @@ -71,23 +74,25 @@ void main() { float brg = degrees(atan(pos.x, pos.y)); if (brg < 0.0) brg += 360.0; - // Decay previous value - float energy = texture(u_prevPhosphor, vTexCoord).r * u_decayFactor; + vec2 prev = texture(u_prevPhosphor, vTexCoord).rg; + float signal = prev.r * u_decayFactor; + float ring = prev.g * u_decayFactor; if (inSweep(brg, u_beamAnglePrev, u_beamAngle)) { - float contrib = u_sweepBg; // beam passage gives a faint ambient glow - - // ---- Range rings (painted at every bearing as beam sweeps) ---- + // ---- Range rings → G channel (gain-independent) ---- + float ringContrib = u_sweepBg; // sweep-background glow also in G channel for (int i = 0; i < u_ringCount; i++) { float d = abs(rng - u_ringRadii[i]); if (d < u_ringWidth) { float w = 1.0 - d / u_ringWidth; - contrib = max(contrib, u_ringBrightness * w * w); + ringContrib = max(ringContrib, u_ringBrightness * w * w); } } + ring = max(ring, ringContrib); - // ---- Target echoes ---- + // ---- Target echoes → R channel (gain-scaled in display pass) ---- + float sigContrib = 0.0; for (int i = 0; i < u_targetCount; i++) { float tRng = u_targets[i].x; float tBrg = u_targets[i].y; @@ -96,21 +101,18 @@ void main() { if (tRng <= 0.0 || tBrt <= 0.0) continue; - // Angular proximity: beam must be sweeping over the target's bearing float dBrg = angleDiff(brg, tBrg); if (dBrg >= u_halfBeamDeg) continue; - // Range proximity: pixel must be within the target blob float dRng = abs(rng - tRng); if (dRng >= tSize) continue; - float bw = 1.0 - dBrg / u_halfBeamDeg; // angular taper - float rw = 1.0 - dRng / tSize; // range taper - contrib = max(contrib, tBrt * bw * rw); + float bw = 1.0 - dBrg / u_halfBeamDeg; + float rw = 1.0 - dRng / tSize; + sigContrib = max(sigContrib, tBrt * bw * rw); } - - energy = max(energy, contrib); + signal = max(signal, sigContrib); } - fragOut = vec4(clamp(energy, 0.0, 1.0), 0.0, 0.0, 1.0); + fragOut = vec4(clamp(signal, 0.0, 1.0), clamp(ring, 0.0, 1.0), 0.0, 1.0); } diff --git a/issues b/issues new file mode 100644 index 0000000..83f985e --- /dev/null +++ b/issues @@ -0,0 +1,8 @@ +Only one range ring no matter the max range. Range ring has no number indicating ring (I forgot if I mentioned that) + +It seems that the empty scan line is excessively strong, along with the range rings. The gain does affect the range rings. I wonder +if this is how things are normal of a period radar. I thought that the gain would be only for targets and noise and land. Not the scan beam +itself and the range rings. Also the cursor seems to be the same color as the p7 fading. It should be more white, like an incandescent light. + +At 1/2 on the gain control, the sweeping beams leave a solid yellow for about 1/3 of a sweep. It is solid, not varying due to noise. + diff --git a/shaders/phosphor.frag b/shaders/phosphor.frag index 7e7ee0e..adc89ed 100644 --- a/shaders/phosphor.frag +++ b/shaders/phosphor.frag @@ -2,9 +2,15 @@ * MIT License * Author: Mark Allyn * - * phosphor.frag — maps the single-channel phosphor energy texture to - * the P7 colour sequence (blue → green → yellow-green → dark) and - * applies a simple inline bloom (box-filter glow) to bright pixels. + * phosphor.frag — maps the two-channel phosphor energy texture to the + * P7 colour sequence (blue → green → yellow-green → dark) and applies + * a simple inline bloom (box-filter glow) to bright pixels. + * + * The phosphor FBO is GL_RG32F: + * R channel — signal energy (target echoes, sweep background) + * multiplied by u_gain before display + * G channel — range ring energy, gain-independent; mixed with signal + * after gain is applied so rings never dim with gain * * Coordinate system: gl_FragCoord.xy in GL viewport pixels (origin * bottom-left). Scope centre is passed as u_scopeCenter in the same @@ -14,17 +20,19 @@ out vec4 fragColor; -uniform sampler2D u_phosphor; // GL_R32F phosphor energy FBO +uniform sampler2D u_phosphor; // GL_RG32F phosphor energy FBO uniform vec2 u_scopeCenter; // scope centre in GL viewport pixels (bottom-left origin) uniform float u_scopeRadius; // scope radius in pixels -uniform float u_gain; // receiver gain [0,1] — scales brightness +uniform float u_gain; // receiver gain [0,1] — scales signal (R) channel only uniform float u_bloomStep; // UV step for bloom sample (≈ 2.5 / FBO_SIZE) uniform float u_bloomStrength; // additive blend weight for bloom -// P7 energy thresholds (match settings.h) +// P7 energy thresholds — MUST match settings.h P7_THRESH_* constants. +// T_YGREE is intentionally low (0.05) to keep most of the decay in the +// GREEN zone; see the comment in settings.h for the full rationale. const float T_BLUE = 0.82; const float T_GREEN = 0.55; -const float T_YGREE = 0.22; +const float T_YGREE = 0.05; const float T_DARK = 0.03; // P7 colour anchors @@ -34,16 +42,22 @@ const vec3 C_YGREE = vec3(0.50, 1.00, 0.05); const vec3 C_YELLW = vec3(0.70, 0.70, 0.00); const vec3 C_BLACK = vec3(0.00, 0.00, 0.00); +// P7 colour ramp: hue selected by energy level, then scaled by energy so +// brightness decreases monotonically from fresh strike (peak) to dark. +// This prevents intermediate decay colours (yellow-green) from appearing +// brighter than the initial blue flash. vec3 p7Color(float e) { + if (e < T_DARK) return C_BLACK; + vec3 hue; if (e >= T_BLUE) - return mix(C_GREEN, C_BLUE, (e - T_GREEN) / (T_BLUE - T_GREEN)); - if (e >= T_GREEN) - return mix(C_YGREE, C_GREEN, (e - T_YGREE) / (T_GREEN - T_YGREE)); - if (e >= T_YGREE) - return mix(C_YELLW, C_YGREE, (e - T_DARK ) / (T_YGREE - T_DARK )); - if (e >= T_DARK) - return mix(C_BLACK, C_YELLW, e / T_DARK); - return C_BLACK; + hue = C_BLUE; + else if (e >= T_GREEN) + hue = mix(C_GREEN, C_BLUE, (e - T_GREEN) / (T_BLUE - T_GREEN)); + else if (e >= T_YGREE) + hue = mix(C_YGREE, C_GREEN, (e - T_YGREE) / (T_GREEN - T_YGREE)); + else + hue = mix(C_YELLW, C_YGREE, (e - T_DARK) / (T_YGREE - T_DARK)); + return hue * e; } void main() { @@ -57,10 +71,12 @@ void main() { } // Map from PPI delta [-1,+1] to phosphor texture UV [0,1] - // delta.x = east, delta.y = north (both y directions already match) vec2 uv = delta * 0.5 + 0.5; - float energy = texture(u_phosphor, uv).r * u_gain; + vec2 rg = texture(u_phosphor, uv).rg; + // Signal (R): gain-scaled received echoes. + // Ring (G): gain-independent timing reference; always at full brightness. + float energy = max(rg.r * u_gain, rg.g); // Inline bloom: weighted box-filter over a 5×5 neighbourhood float bloom = 0.0; @@ -68,7 +84,8 @@ void main() { for (int dx = -2; dx <= 2; dx++) { for (int dy = -2; dy <= 2; dy++) { float w = exp(-float(dx*dx + dy*dy) * 0.45); - float e = texture(u_phosphor, uv + vec2(dx, dy) * u_bloomStep).r; + vec2 srg = texture(u_phosphor, uv + vec2(dx, dy) * u_bloomStep).rg; + float e = max(srg.r * u_gain, srg.g); bloom += e * w; wsum += w; } diff --git a/shaders/sweep.frag b/shaders/sweep.frag index 0e22a12..322adb9 100644 --- a/shaders/sweep.frag +++ b/shaders/sweep.frag @@ -4,13 +4,16 @@ * * sweep.frag — phosphor accumulation update shader. * - * For each texel in the 1024×1024 phosphor FBO: - * 1. Decay the previous frame's energy by u_decayFactor. - * 2. If the texel's PPI bearing falls within the current sweep arc - * [u_beamAnglePrev, u_beamAngle], add contributions from: - * - range rings (beam-painted per the P7 spec) - * - target echoes - * 3. Output the updated single-channel energy value. + * The FBO is GL_RG32F (two independent energy channels): + * R — signal energy: target echoes + sweep-background glow. + * Multiplied by u_gain in the display pass so operators can + * adjust received-signal brightness without touching rings. + * G — range-ring energy: written at u_ringBrightness; NOT scaled + * by gain. Rings are a precision timing reference, not a + * received echo. Both channels decay at the same P7 rate. + * + * The sweep background (u_sweepBg) goes into the G channel so the + * rotating beam is always visible regardless of the gain setting. * * PPI convention: north = +y, east = +x; bearing = atan2(x, y) * in degrees, clockwise from north. @@ -19,17 +22,18 @@ in vec2 vTexCoord; -layout(location = 0) out vec4 fragOut; // .r = energy; .gba unused +layout(location = 0) out vec4 fragOut; // .r = signal; .g = ring+sweep; .ba unused -uniform sampler2D u_prevPhosphor; // previous frame's energy texture (GL_R32F) +uniform sampler2D u_prevPhosphor; // previous frame's energy texture (GL_RG32F) uniform float u_decayFactor; // exp(-decay_rate * dt) uniform float u_beamAngle; // current beam angle, degrees CW from north uniform float u_beamAnglePrev; // beam angle at previous frame -uniform float u_sweepBg; // ambient sweep-line energy (makes beam visible) +uniform float u_sweepBg; // ambient sweep-line energy (gain-independent) uniform float u_halfBeamDeg; // half-beamwidth for target blobs (display widening) -// Targets: .x = range_norm (0-1), .y = bearing_deg, .z = brightness, .w = size_norm +// Targets: .x = range_norm (0-1), .y = bearing_deg, .z = brightness, .w = radial_size_norm uniform vec4 u_targets[32]; +uniform float u_targetAngHalfDeg[32]; // per-target azimuthal half-width (degrees) uniform int u_targetCount; // Range rings: up to 4 normalised radii @@ -59,8 +63,8 @@ bool inSweep(float b, float prev, float curr) { // ---------------------------------------------------------------- void main() { - vec2 pos = vTexCoord * 2.0 - 1.0; // PPI coords: (-1,-1) SW … (+1,+1) NE - float rng = length(pos); + vec2 pos = vTexCoord * 2.0 - 1.0; // PPI coords: (-1,-1) SW … (+1,+1) NE + float rng = length(pos); if (rng > 1.0) { fragOut = vec4(0.0); @@ -71,23 +75,25 @@ void main() { float brg = degrees(atan(pos.x, pos.y)); if (brg < 0.0) brg += 360.0; - // Decay previous value - float energy = texture(u_prevPhosphor, vTexCoord).r * u_decayFactor; + vec2 prev = texture(u_prevPhosphor, vTexCoord).rg; + float signal = prev.r * u_decayFactor; + float ring = prev.g * u_decayFactor; if (inSweep(brg, u_beamAnglePrev, u_beamAngle)) { - float contrib = u_sweepBg; // beam passage gives a faint ambient glow - - // ---- Range rings (painted at every bearing as beam sweeps) ---- + // ---- Range rings → G channel (gain-independent) ---- + float ringContrib = u_sweepBg; // sweep-background glow also in G channel for (int i = 0; i < u_ringCount; i++) { float d = abs(rng - u_ringRadii[i]); if (d < u_ringWidth) { float w = 1.0 - d / u_ringWidth; - contrib = max(contrib, u_ringBrightness * w * w); + ringContrib = max(ringContrib, u_ringBrightness * w * w); } } + ring = max(ring, ringContrib); - // ---- Target echoes ---- + // ---- Target echoes → R channel (gain-scaled in display pass) ---- + float sigContrib = 0.0; for (int i = 0; i < u_targetCount; i++) { float tRng = u_targets[i].x; float tBrg = u_targets[i].y; @@ -96,21 +102,19 @@ void main() { if (tRng <= 0.0 || tBrt <= 0.0) continue; - // Angular proximity: beam must be sweeping over the target's bearing + float tAngHalf = u_targetAngHalfDeg[i]; float dBrg = angleDiff(brg, tBrg); - if (dBrg >= u_halfBeamDeg) continue; + if (dBrg >= tAngHalf) continue; - // Range proximity: pixel must be within the target blob float dRng = abs(rng - tRng); if (dRng >= tSize) continue; - float bw = 1.0 - dBrg / u_halfBeamDeg; // angular taper - float rw = 1.0 - dRng / tSize; // range taper - contrib = max(contrib, tBrt * bw * rw); + float bw = 1.0 - dBrg / tAngHalf; + float rw = 1.0 - dRng / tSize; + sigContrib = max(sigContrib, tBrt * bw * rw); } - - energy = max(energy, contrib); + signal = max(signal, sigContrib); } - fragOut = vec4(clamp(energy, 0.0, 1.0), 0.0, 0.0, 1.0); + fragOut = vec4(clamp(signal, 0.0, 1.0), clamp(ring, 0.0, 1.0), 0.0, 1.0); } diff --git a/src/phosphor.cpp b/src/phosphor.cpp index 6f65805..708e9e8 100644 --- a/src/phosphor.cpp +++ b/src/phosphor.cpp @@ -102,9 +102,9 @@ bool PhosphorRenderer::init(const std::string& shaderDir) { glGenTextures(1, &tex_[i]); glBindTexture(GL_TEXTURE_2D, tex_[i]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, fboSize_, fboSize_, 0, - GL_RED, GL_FLOAT, nullptr); + GL_RG, GL_FLOAT, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -193,16 +193,19 @@ void PhosphorRenderer::update( // Target array: pack into vec4 array float tdata[MAX_TARGETS * 4] = {}; + float angData[MAX_TARGETS] = {}; int tCount = (targetCount < MAX_TARGETS) ? targetCount : MAX_TARGETS; float invRange = (maxRangeM > 0.f) ? 1.0f / maxRangeM : 0.f; for (int i = 0; i < tCount; ++i) { - tdata[i*4+0] = targets[i].range_m * invRange; // normalised 0-1 + tdata[i*4+0] = targets[i].range_m * invRange * GRAT_INNER_RING_FRAC; tdata[i*4+1] = targets[i].bearing_deg; tdata[i*4+2] = targets[i].brightness; - tdata[i*4+3] = targets[i].size_m * invRange; // normalised blob half-radius + tdata[i*4+3] = targets[i].size_m * invRange * GRAT_INNER_RING_FRAC; + angData[i] = targets[i].ang_half_deg; } - glUniform4fv(glGetUniformLocation(sweepProg_, "u_targets"), tCount, tdata); - glUniform1i (glGetUniformLocation(sweepProg_, "u_targetCount"), tCount); + glUniform4fv(glGetUniformLocation(sweepProg_, "u_targets"), tCount, tdata); + glUniform1fv(glGetUniformLocation(sweepProg_, "u_targetAngHalfDeg"), tCount, angData); + glUniform1i (glGetUniformLocation(sweepProg_, "u_targetCount"), tCount); glBindVertexArray(quadVAO_); glDrawArrays(GL_TRIANGLES, 0, 6); diff --git a/src/scope_marine_ppi.cpp b/src/scope_marine_ppi.cpp index ed4c7ed..88b7f79 100644 --- a/src/scope_marine_ppi.cpp +++ b/src/scope_marine_ppi.cpp @@ -7,7 +7,7 @@ #include "scope_marine_ppi.h" #include -#include +#include // ---------------------------------------------------------------- @@ -18,8 +18,14 @@ float MarinePPIScope::maxRangeM() const { void MarinePPIScope::computeRingRadii() { int n = MARINE_RING_COUNT[maxRangeIdx_]; float maxMi = MARINE_RANGE_STEPS[maxRangeIdx_]; - for (int i = 0; i < n; ++i) - ringRadiiNorm_[i] = MARINE_RING_MILES[maxRangeIdx_][i] / maxMi; + for (int i = 0; i < n; ++i) { + // Scale by GRAT_INNER_RING_FRAC so max-range maps to the inner + // graticule ring boundary. Targets use the same scale factor + // (applied in PhosphorRenderer::update), so rings and targets + // remain co-registered in the display. + ringRadiiNorm_[i] = (MARINE_RING_MILES[maxRangeIdx_][i] / maxMi) + * GRAT_INNER_RING_FRAC; + } } void MarinePPIScope::handleKeyRange(int key, int /*action*/) { diff --git a/src/scope_ppi.cpp b/src/scope_ppi.cpp index 32b3769..6864da8 100644 --- a/src/scope_ppi.cpp +++ b/src/scope_ppi.cpp @@ -185,9 +185,16 @@ void PPIScope::render(float dt, float viewportW, float viewportH) { } graticule_.render(viewportW, viewportH, gratI, brgOffsetDeg_); - // 3. Yellow cursor overlay + // 3. Yellow cursor overlay — clamp cursor to max range each frame so it + // never escapes the active display area regardless of how it was set. + { + float maxMi = maxM / MILES_TO_METERS; + if (cursorRngMi_ > maxMi) cursorRngMi_ = maxMi; + } + // Map cursor range [0, maxM] → normalised radius [0, GRAT_INNER_RING_FRAC] + // so the cursor at max range sits on the outer ring, not on the bezel. float cursorNorm = (maxM > 0.f) - ? std::min((cursorRngMi_ * MILES_TO_METERS) / maxM, 1.0f) + ? (cursorRngMi_ * MILES_TO_METERS / maxM) * GRAT_INNER_RING_FRAC : 0.f; // Cursor bearing in display coordinates (subtract offset so it tracks the display) float dispBrg = std::fmod(cursorBrgDeg_ - brgOffsetDeg_ + 360.f, 360.f); diff --git a/src/settings.h b/src/settings.h index 6732757..7526e72 100644 --- a/src/settings.h +++ b/src/settings.h @@ -31,9 +31,16 @@ constexpr int PHOSPHOR_FBO_SIZE = 1024; // texels per side for phospho constexpr float P7_DECAY_RATE = 1.1513f; // s^-1; <1% remains after 4 s // Energy thresholds that define colour transitions in the display shader +// T_YGREE is deliberately low (0.05) so that the ring spends the vast +// majority of the 4-second sweep period in the GREEN zone rather than +// in the yellow-green/yellow zone. With exponential decay (rate 1.15), +// a ring painted at 0.72 stays green for ~2.5 s, dips through a faint +// yellow-green for ~0.45 s (≈ 40° of arc), then goes black. Setting +// T_YGREE = 0.22 (old value) extended the yellow zone to ~1.75 s (44% +// of the scope arc), which is the source of the "solid yellow" report. constexpr float P7_THRESH_BLUE = 0.82f; // above this: blue strike constexpr float P7_THRESH_GREEN = 0.55f; // above this: green persistence -constexpr float P7_THRESH_YELLOW_GR = 0.22f; // above this: yellow-green fade +constexpr float P7_THRESH_YELLOW_GR = 0.05f; // above this: yellow-green fade constexpr float P7_THRESH_DARK = 0.03f; // below this: essentially black // Colour anchors (RGB) @@ -42,8 +49,12 @@ constexpr float P7_GREEN_R = 0.05f, P7_GREEN_G = 1.00f, P7_GREEN_B = 0.30f; constexpr float P7_YGREE_R = 0.50f, P7_YGREE_G = 1.00f, P7_YGREE_B = 0.05f; constexpr float P7_YELLW_R = 0.70f, P7_YELLW_G = 0.70f, P7_YELLW_B = 0.00f; -// Sweep background energy — gives the rotating beam its visible glow -constexpr float SWEEP_BACKGROUND_ENERGY = 0.10f; +// Sweep background energy written to the G channel each frame the beam +// sweeps over a texel. Kept just above T_DARK so the rotating beam is +// barely visible as a dim trace; it decays to black in ~0.3 s (7% of +// the 4-second sweep arc). 0.10 (old value) left a visible glow for +// ~1.3 s (33% of arc) — major contributor to the "solid yellow" report. +constexpr float SWEEP_BACKGROUND_ENERGY = 0.04f; // Half-beamwidth used by sweep shader for target blob display (degrees) constexpr float SWEEP_HALF_BEAM_DEG = 1.0f; @@ -83,7 +94,13 @@ constexpr int GRAT_SEGMENTS = 360; // circle tessellation segm // RANGE RINGS (beam-painted; widths in normalised 0-1 scope space) // ================================================================ constexpr float RING_WIDTH_NORM = 0.005f; -constexpr float RING_BRIGHTNESS = 0.72f; +constexpr float RING_BRIGHTNESS = 1.0f; + +// Range-ring distance labels +// Labels appear at a fixed bearing so they don't clutter the display. +// Colour uses the P7 fresh-blue constants (P7_BLUE_R/G/B) already above. +constexpr float RING_LABEL_BRG_DEG = 45.0f; // NE: clear of bearing-mark clusters +constexpr int RING_LABEL_FONT_SIZE = 14; // pixels (matches graticule labels) // Marine PPI range steps (miles) and ring definitions // Index: 0 (max 2 mi) 1 (max 4 mi) 2 (max 6 mi) diff --git a/src/simulator.cpp b/src/simulator.cpp index aa84733..a7b9eeb 100644 --- a/src/simulator.cpp +++ b/src/simulator.cpp @@ -67,6 +67,11 @@ Simulator::Simulator() { // 7. Large barge under tow N — bearing 355°, 4.8 mi targets_[n++] = { 7, 48.8127f, -122.5739f, 3.08f, 175.0f, 1000.0f, 30.0f }; + // 8. DEBUG: large broadside cargo ship NE — bearing 045°, 1.0 mi + // Stationary (speed=0). RCS 500,000 m² (big steel hull broadside). + // sizeM=150 so the blob is clearly visible even in no-persistence mode. + targets_[n++] = { 8, 48.7538f, -122.5492f, 0.0f, 0.0f, 500000.0f, 150.0f }; + targetCount_ = n; } diff --git a/src/simulator.h b/src/simulator.h index 4460419..5545e70 100644 --- a/src/simulator.h +++ b/src/simulator.h @@ -27,7 +27,8 @@ struct SimTarget { float speedMps; // metres per second float headingDeg; // true heading, CW from north float sigmaM2; // RCS in m² - float sizeM; // physical blob half-radius in metres + float sizeM; // radial blob half-width in metres (range direction) + float angHalfDeg; // azimuthal half-width in degrees (bearing direction) }; class Simulator { diff --git a/src/target_buffer.h b/src/target_buffer.h index 690bdf3..a47be43 100644 --- a/src/target_buffer.h +++ b/src/target_buffer.h @@ -23,7 +23,8 @@ struct TargetState { float range_m = 0.0f; // metres from radar origin float bearing_deg = 0.0f; // degrees true, CW from north float brightness = 0.0f; // 0-1, from radar equation - float size_m = 50.0f; // physical blob half-radius in metres; scope normalises to [0,1] + float size_m = 50.0f; // radial blob half-width in metres (range direction) + float ang_half_deg = SWEEP_HALF_BEAM_DEG; // azimuthal half-width in degrees bool valid = false; };