From 78211448375277705318618c076191ba7f83f4c4 Mon Sep 17 00:00:00 2001 From: Solmath <33658856+Solmath@users.noreply.github.com> Date: Sat, 29 Aug 2026 18:39:45 +0200 Subject: [PATCH] Improve gradient handling in MetaballsMode by refining brightness contribution calculations --- firmware/include/modes/MetaballsMode.h | 6 +++++- firmware/src/modes/MetaballsMode.cpp | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/firmware/include/modes/MetaballsMode.h b/firmware/include/modes/MetaballsMode.h index 5b17efae..0fb2f4ac 100644 --- a/firmware/include/modes/MetaballsMode.h +++ b/firmware/include/modes/MetaballsMode.h @@ -18,6 +18,11 @@ class MetaballsMode final : public ModeModule static constexpr float speed{1e-6F * static_cast(GRID_COLUMNS * GRID_ROWS)}; static constexpr uint8_t multiplier{1U << 4U}; + // How many discrete distance steps a ball's brightness falloff is quantized into, from its + // center (0) to its edge (falloffResolution); this is the resolution of contributions below. + static constexpr uint8_t falloffResolution{UINT8_MAX}; + // Size must stay equal to falloffResolution + 1 + std::array contributions{}; struct Ball { @@ -27,7 +32,6 @@ class MetaballsMode final : public ModeModule float yVelocity; }; - std::array contributions{}; std::array balls{}; public: diff --git a/firmware/src/modes/MetaballsMode.cpp b/firmware/src/modes/MetaballsMode.cpp index 6802621a..6632da86 100644 --- a/firmware/src/modes/MetaballsMode.cpp +++ b/firmware/src/modes/MetaballsMode.cpp @@ -10,9 +10,16 @@ static_assert(GRID_COLUMNS * GRID_ROWS >= 50U, void MetaballsMode::begin() { + // Builds a lookup table of a single ball's brightness contribution by distance: full brightness + // at the center (idx 0), fading quadratically to none at the edge (idx == falloffResolution). + // Multiple overlapping balls add their contributions together, so peakBrightness is kept well + // below UINT8_MAX to require several overlapping balls before a pixel reaches full brightness. + constexpr float peakBrightness{64.0F}; + constexpr float span{static_cast(falloffResolution) + 1.0F}; for (size_t idx{0U}; idx < contributions.size(); ++idx) { - contributions[idx] = ((UINT8_MAX - idx) * (UINT8_MAX - idx) * (0b1U << 6U)) >> (0b1U << 4U); + const float normalizedDistance{static_cast(falloffResolution - idx) / span}; + contributions[idx] = static_cast(peakBrightness * normalizedDistance * normalizedDistance); } for (Ball &ball : balls) { @@ -65,10 +72,11 @@ void MetaballsMode::handle() const float distanceSq{(xDistance * xDistance) + (yDistance * yDistance)}; if (distanceSq < radiusSq) { - brightness = static_cast( - min(static_cast(brightness) + - contributions[static_cast(distanceSq * (0b1U << 6U) / radiusSq)], - UINT8_MAX)); + brightness = static_cast(min( + static_cast(brightness) + + contributions[static_cast(min(distanceSq * falloffResolution / radiusSq, + static_cast(falloffResolution)))], + UINT8_MAX)); if (brightness == UINT8_MAX) { break;