Remove leftover aircraft trail rendering, fix proximity-blink black background box

The trail/tail-line rendering behind each aircraft marker was already
superseded by the heading arrowhead in drawAircraftMarker() but was
never removed, so it kept running in parallel (dimColorForAltitude,
TrailEntry, clearTrails/pruneStaleTrails/findOrCreateTrail/
pushTrailPoint/drawTrail, and their call sites in render(), tick() and
handleTap()) - all removed as dead weight.

The proximity-alert blink-off phase used to paint a flat 40x30px black
rectangle over each nearby aircraft to hide it, which also blacked out
the radar rings/compass lines underneath regardless of altitude color
(the blink logic is purely distance-based). Now redraws the exact same
shapes used for normal rendering (marker, emergency/watchlist ring,
label) in black instead, so only the aircraft itself disappears during
the blink-off phase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Eiswolf-BG
2026-08-10 22:09:12 +02:00
parent 97c6b140b8
commit c1cd240efc

View File

@@ -209,10 +209,7 @@ namespace {
} }
// Farbe je nach Flughoehe - gedaempft (dunkleres Gruen/Oliv) waehrend der // Farbe je nach Flughoehe - gedaempft (dunkleres Gruen/Oliv) waehrend der
// Nachtdimmung. Bewusst eigene, etwas hellere Toene als // Nachtdimmung.
// dimColorForAltitude() weiter unten (das ist die IMMER dunklere
// Trail-Farbe hinter jedem Flugzeug) - sonst waeren Kopf-Marker und
// Trail nachts nicht mehr auseinanderzuhalten.
// //
// Die rote Hoehenstufe wird bewusst NICHT gedaempft: der bisherige // Die rote Hoehenstufe wird bewusst NICHT gedaempft: der bisherige
// Dimm-Ton (160,30,0) sah auf dem Display eher orange-braun statt rot // Dimm-Ton (160,30,0) sah auf dem Display eher orange-braun statt rot
@@ -305,83 +302,6 @@ namespace {
gfx.drawLine(tipX, tipY, w2x, w2y, color); gfx.drawLine(tipX, tipY, w2x, w2y, color);
} }
uint16_t dimColorForAltitude(int32_t altFt) {
if (altFt < Config::COLOR_LOW_ALT_THRESHOLD_FT) return TFT_DARKGREEN;
if (altFt < Config::COLOR_MID_ALT_THRESHOLD_FT) return TFT_OLIVE;
return TFT_MAROON;
}
constexpr uint8_t TRAIL_LEN = 4;
constexpr uint32_t TRAIL_STALE_MS = Config::FETCH_INTERVAL_MS * 3;
struct TrailEntry {
char hex[7] = {0};
bool active = false;
int16_t xs[TRAIL_LEN] = {0};
int16_t ys[TRAIL_LEN] = {0};
uint8_t count = 0;
uint32_t lastUpdateMs = 0;
uint16_t dimColor = TFT_DARKGREEN;
};
constexpr uint8_t MAX_TRAILS = Config::MAX_TRACKED_AIRCRAFT;
TrailEntry trails[MAX_TRAILS];
// Alle Trails verwerfen, z.B. wenn sich der Radius (rangeKm) aendert:
// die gespeicherten Punkte sind Bildschirm-Pixelkoordinaten, die unter
// der ALTEN Skalierung berechnet wurden. Ohne diesen Reset zieht jedes
// Flugzeug fuer ein paar Heartbeat-Takte eine falsche "Schwanz"-Linie
// von der alten zur neuen Position, bis sich der kurze Ringpuffer
// (TRAIL_LEN=4) mit neuen, konsistenten Punkten gefuellt hat.
void clearTrails() {
for (uint8_t i = 0; i < MAX_TRAILS; i++) trails[i] = TrailEntry{};
}
void pruneStaleTrails() {
uint32_t now = millis();
for (uint8_t i = 0; i < MAX_TRAILS; i++) {
if (trails[i].active && now - trails[i].lastUpdateMs > TRAIL_STALE_MS) {
trails[i] = TrailEntry{};
}
}
}
TrailEntry* findOrCreateTrail(const char* hex) {
int16_t freeIdx = -1;
for (uint8_t i = 0; i < MAX_TRAILS; i++) {
if (trails[i].active && strcmp(trails[i].hex, hex) == 0) return &trails[i];
if (!trails[i].active && freeIdx < 0) freeIdx = (int16_t)i;
}
if (freeIdx < 0) return nullptr;
trails[freeIdx] = TrailEntry{};
strncpy(trails[freeIdx].hex, hex, sizeof(trails[freeIdx].hex) - 1);
trails[freeIdx].active = true;
return &trails[freeIdx];
}
void pushTrailPoint(TrailEntry* t, int16_t x, int16_t y) {
if (!t) return;
if (t->count < TRAIL_LEN) {
t->xs[t->count] = x;
t->ys[t->count] = y;
t->count++;
} else {
for (uint8_t i = 1; i < TRAIL_LEN; i++) {
t->xs[i - 1] = t->xs[i];
t->ys[i - 1] = t->ys[i];
}
t->xs[TRAIL_LEN - 1] = x;
t->ys[TRAIL_LEN - 1] = y;
}
t->lastUpdateMs = millis();
}
void drawTrail(TFT_eSPI& gfx, const TrailEntry* t, uint16_t color) {
if (!t || t->count < 2) return;
for (uint8_t i = 1; i < t->count; i++) {
gfx.drawLine(t->xs[i - 1], t->ys[i - 1], t->xs[i], t->ys[i], color);
}
}
void printLineTruncated(TFT_eSPI& gfx, int16_t x, int16_t y, int16_t maxWidth, const String& text) { void printLineTruncated(TFT_eSPI& gfx, int16_t x, int16_t y, int16_t maxWidth, const String& text) {
String s = text; String s = text;
if (gfx.textWidth(s) > maxWidth) { if (gfx.textWidth(s) > maxWidth) {
@@ -696,8 +616,6 @@ void render(TFT_eSPI& tft, int16_t top) {
for (uint8_t i = 0; i < MAX_HIT_POINTS; i++) hitPoints[i].valid = false; for (uint8_t i = 0; i < MAX_HIT_POINTS; i++) hitPoints[i].valid = false;
pruneStaleTrails();
for (uint8_t i = 0; i < count && i < MAX_HIT_POINTS; i++) { for (uint8_t i = 0; i < count && i < MAX_HIT_POINTS; i++) {
Aircraft& a = snapshot[i]; Aircraft& a = snapshot[i];
if (a.distanceKm > rangeKm * 1.05f) continue; if (a.distanceKm > rangeKm * 1.05f) continue;
@@ -716,12 +634,6 @@ void render(TFT_eSPI& tft, int16_t top) {
bool isEmergency = SettingsStore::emergencyAlertEnabled() && isEmergencySquawk(a.squawk); bool isEmergency = SettingsStore::emergencyAlertEnabled() && isEmergencySquawk(a.squawk);
bool isWatched = SettingsStore::watchlistAlertEnabled() && AircraftWatchlist::isWatched(a.callsign); bool isWatched = SettingsStore::watchlistAlertEnabled() && AircraftWatchlist::isWatched(a.callsign);
TrailEntry* trail = findOrCreateTrail(a.hex);
uint16_t trailColor = dimColorForAltitude(a.altBaroFt);
drawTrail(tft, trail, trailColor);
pushTrailPoint(trail, pt.x, pt.y);
if (trail) trail->dimColor = trailColor;
if (isSelected) { if (isSelected) {
tft.drawCircle(pt.x, pt.y, 9, TFT_WHITE); tft.drawCircle(pt.x, pt.y, 9, TFT_WHITE);
selectionStillPresent = true; selectionStillPresent = true;
@@ -819,28 +731,31 @@ void tick(TFT_eSPI& tft, int16_t top, uint32_t deltaMs) {
drawSweepLine(tft, L, sweepAngleDeg, sweepLineColor(tft)); drawSweepLine(tft, L, sweepAngleDeg, sweepLineColor(tft));
prevSweepAngleDeg = sweepAngleDeg; prevSweepAngleDeg = sweepAngleDeg;
for (uint8_t i = 0; i < MAX_TRAILS; i++) {
if (!trails[i].active) continue;
bool stillVisible = false;
for (uint8_t j = 0; j < MAX_HIT_POINTS; j++) {
if (hitPoints[j].valid && strcmp(hitPoints[j].hex, trails[i].hex) == 0) {
stillVisible = true;
break;
}
}
if (stillVisible) {
drawTrail(tft, &trails[i], trails[i].dimColor);
}
}
for (uint8_t i = 0; i < MAX_HIT_POINTS; i++) { for (uint8_t i = 0; i < MAX_HIT_POINTS; i++) {
if (!hitPoints[i].valid) continue; if (!hitPoints[i].valid) continue;
const HitPoint& hp = hitPoints[i]; const HitPoint& hp = hitPoints[i];
bool inAlertRange = hp.distanceKm <= Config::LED_ALERT_RADIUS_KM; bool inAlertRange = hp.distanceKm <= Config::LED_ALERT_RADIUS_KM;
if (inAlertRange && !ledBlinkOn) { if (inAlertRange && !ledBlinkOn) {
tft.fillRect(hp.x - 20, hp.y - 18, 40, 30, TFT_BLACK); // Blink-Aus-Phase: frueher wurde hier ein pauschales 40x30px
// schwarzes Rechteck gezeichnet, um den Marker zu "verstecken" -
// das hat dabei aber auch die Radar-Ringe/Kompasslinien darunter
// ueberdeckt (bei jeder Hoehenfarbe gleichermassen, da diese
// Blink-Logik rein distanzbasiert ist). Stattdessen werden hier
// jetzt exakt dieselben Formen, die beim normalen Zeichnen weiter
// unten entstehen (Marker, Notfall-/Beobachtungs-Ring, Label),
// einfach in Schwarz nachgezeichnet - das macht sie pixelgenau
// wieder unsichtbar, ohne irgendetwas ausserhalb dieser Formen zu
// beruehren.
drawAircraftMarker(tft, hp.x, hp.y, hp.headingDeg, TFT_BLACK);
if (hp.isEmergency || hp.isWatched) {
tft.drawCircle(hp.x, hp.y, 12, TFT_BLACK);
}
tft.setTextColor(TFT_BLACK);
tft.setTextDatum(BC_DATUM);
const char* blinkLabel = hp.callsign[0] ? hp.callsign : hp.hex;
tft.drawString(blinkLabel, hp.x, hp.y - 8);
tft.setTextDatum(TL_DATUM);
continue; continue;
} }
@@ -939,7 +854,6 @@ bool handleTap(int16_t x, int16_t y, int16_t top) {
if (L.rangeBtn.contains(x, y)) { if (L.rangeBtn.contains(x, y)) {
uint8_t idx = (SettingsStore::rangeIndex() + 1) % Config::RANGE_STEP_COUNT; uint8_t idx = (SettingsStore::rangeIndex() + 1) % Config::RANGE_STEP_COUNT;
SettingsStore::setRangeIndex(idx); SettingsStore::setRangeIndex(idx);
clearTrails();
return true; return true;
} }
@@ -966,7 +880,6 @@ bool handleTap(int16_t x, int16_t y, int16_t top) {
uint8_t idx = SettingsStore::rangeIndex(); uint8_t idx = SettingsStore::rangeIndex();
idx = (idx == 0) ? (Config::RANGE_STEP_COUNT - 1) : (idx - 1); idx = (idx == 0) ? (Config::RANGE_STEP_COUNT - 1) : (idx - 1);
SettingsStore::setRangeIndex(idx); SettingsStore::setRangeIndex(idx);
clearTrails();
lastEmptyTapMs = 0; lastEmptyTapMs = 0;
} }