#version 330 core // Feature 9: PPI Shoreline / Terrain // Each vertex encodes (bearingDeg, rangeMiles) in polar radar coordinates. // The vertex shader converts to NDC and computes sweep-based fade on the GPU, // so the geometry buffer is static — only uniforms change each frame. layout(location = 0) in vec2 aBearingRange; // x = bearing °CW-from-N, y = range miles uniform vec2 uCenter; // NDC centre of PPI scope uniform float uPpiRx; // ppiR * 2.0 / W (one full-radius in NDC, x axis) uniform float uPpiRy; // ppiR * 2.0 / H (one full-radius in NDC, y axis) uniform float uMaxRange; // current max range (miles) uniform float uSweepAngle; // current sweep angle (degrees CW from N) uniform float uSweepDegPS; // sweep speed (degrees / second) uniform float uPersist; // phosphor persistence duration (seconds) out float vFade; // 0..1 brightness multiplier out float vActive; // 1.0 when sweep head is over this point const float PI = 3.14159265358979; const float THRESH = 3.5; // degrees — active window around sweep head void main() { float bearDeg = aBearingRange.x; float rangeMi = aBearingRange.y; float rangeFrac = rangeMi / uMaxRange; // Push out-of-range points outside the NDC clip volume if (rangeFrac > 1.0) { gl_Position = vec4(2.0, 2.0, 2.0, 1.0); vFade = 0.0; vActive = 0.0; return; } // Convert polar radar coords → NDC // North (0°) = +Y in NDC; East (90°) = +X in NDC float bearRad = bearDeg * PI / 180.0; float nx = uCenter.x + rangeFrac * uPpiRx * sin(bearRad); float ny = uCenter.y + rangeFrac * uPpiRy * cos(bearRad); gl_Position = vec4(nx, ny, 0.0, 1.0); gl_PointSize = 2.0; // Degrees the sweep has travelled past this point since last illumination float angBehind = mod(uSweepAngle - bearDeg + 360.0, 360.0); float timeSinceLit = angBehind / uSweepDegPS; vFade = clamp(1.0 - timeSinceLit / uPersist, 0.0, 1.0); vActive = (angBehind < THRESH) ? 1.0 : 0.0; }