Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion firmware/include/modes/MetaballsMode.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class MetaballsMode final : public ModeModule
static constexpr float speed{1e-6F * static_cast<float>(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<uint8_t, falloffResolution + 1U> contributions{};

struct Ball
{
Expand All @@ -27,7 +32,6 @@ class MetaballsMode final : public ModeModule
float yVelocity;
};

std::array<uint8_t, GRID_COLUMNS * GRID_ROWS> contributions{};
std::array<Ball, GRID_COLUMNS * GRID_ROWS / 50U> balls{};

public:
Expand Down
18 changes: 13 additions & 5 deletions firmware/src/modes/MetaballsMode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>(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<float>(falloffResolution - idx) / span};
contributions[idx] = static_cast<uint8_t>(peakBrightness * normalizedDistance * normalizedDistance);
}
for (Ball &ball : balls)
{
Expand Down Expand Up @@ -65,10 +72,11 @@ void MetaballsMode::handle()
const float distanceSq{(xDistance * xDistance) + (yDistance * yDistance)};
if (distanceSq < radiusSq)
{
brightness = static_cast<uint8_t>(
min<uint16_t>(static_cast<uint16_t>(brightness) +
contributions[static_cast<uint8_t>(distanceSq * (0b1U << 6U) / radiusSq)],
UINT8_MAX));
brightness = static_cast<uint8_t>(min<uint16_t>(
static_cast<uint16_t>(brightness) +
contributions[static_cast<uint8_t>(min<float>(distanceSq * falloffResolution / radiusSq,
static_cast<float>(falloffResolution)))],
UINT8_MAX));
if (brightness == UINT8_MAX)
{
break;
Expand Down
Loading