Fix errors

This commit is contained in:
2026-04-24 00:25:29 -07:00
parent f68524a6ae
commit 01c1db41db
34 changed files with 361 additions and 210 deletions

View File

@@ -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;
}

View File

@@ -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);
}