Files
klubhaus-doorbell/libraries/FastLED/src/fl/wave_simulation.cpp
2026-02-12 00:45:31 -08:00

389 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Based on works and code by Shawn Silverman.
#include "fl/stdint.h"
#include "fl/clamp.h"
#include "fl/namespace.h"
#include "fl/wave_simulation.h"
#include "fl/int.h"
namespace {
fl::u8 half_duplex_blend_sqrt_q15(fl::u16 x) {
x = MIN(x, 32767); // Q15
const int Q = 15;
fl::u32 X = (fl::u32)x << Q; // promote to Q30
fl::u32 y = (1u << Q); // start at “1.0” in Q15
// 34 iterations is plenty for 15bit precision:
for (int i = 0; i < 4; i++) {
y = (y + (X / y)) >> 1;
}
return static_cast<fl::i16>(y) >> 8;
}
fl::u8 half_duplex_blend_linear(fl::u16 x) {
x = MIN(x, 32767); // Q15
x *= 2;
return x >> 8;
}
} // namespace
namespace fl {
void WaveSimulation2D::setSpeed(float speed) { mSim->setSpeed(speed); }
WaveSimulation2D::WaveSimulation2D(u32 W, u32 H, SuperSample factor,
float speed, float dampening) {
init(W, H, factor, speed, dampening);
}
void WaveSimulation2D::init(u32 width, u32 height, SuperSample factor,
float speed, int dampening) {
mOuterWidth = width;
mOuterHeight = height;
mMultiplier = static_cast<u32>(factor);
mSim.reset(); // clear out memory first.
u32 w = width * mMultiplier;
u32 h = height * mMultiplier;
mSim.reset(new WaveSimulation2D_Real(w, h, speed, dampening));
// Only allocate change grid if it's enabled (saves memory when disabled)
if (mUseChangeGrid) {
mChangeGrid.reset(w, h);
}
// Extra frames are needed because the simulation slows down in
// proportion to the supersampling factor.
mExtraFrames = u8(factor) - 1;
}
void WaveSimulation2D::setDampening(int damp) { mSim->setDampening(damp); }
int WaveSimulation2D::getDampenening() const { return mSim->getDampenening(); }
float WaveSimulation2D::getSpeed() const { return mSim->getSpeed(); }
float WaveSimulation2D::getf(fl::size x, fl::size y) const {
if (!has(x, y))
return 0.0f;
float sum = 0.0f;
for (u32 j = 0; j < mMultiplier; ++j) {
for (u32 i = 0; i < mMultiplier; ++i) {
sum += mSim->getf(x * mMultiplier + i, y * mMultiplier + j);
}
}
return sum / static_cast<float>(mMultiplier * mMultiplier);
}
i16 WaveSimulation2D::geti16(fl::size x, fl::size y) const {
if (!has(x, y))
return 0;
i32 sum = 0;
u8 mult = MAX(1, mMultiplier);
for (u32 j = 0; j < mult; ++j) {
for (u32 i = 0; i < mult; ++i) {
u32 xx = x * mult + i;
u32 yy = y * mult + j;
i32 pt = mSim->geti16(xx, yy);
if (mUseChangeGrid) {
// i32 ch_pt = mChangeGrid[(yy * mMultiplier) + xx];
i32 ch_pt = mChangeGrid(xx, yy);
if (ch_pt != 0) { // we got a hit.
sum += ch_pt;
} else {
sum += pt;
}
} else {
sum += pt;
}
}
}
i16 out = static_cast<i16>(sum / (mult * mult));
return out;
}
i16 WaveSimulation2D::geti16Previous(fl::size x, fl::size y) const {
if (!has(x, y))
return 0;
i32 sum = 0;
u8 mult = MAX(1, mMultiplier);
for (u32 j = 0; j < mult; ++j) {
for (u32 i = 0; i < mult; ++i) {
sum +=
mSim->geti16Previous(x * mult + i, y * mult + j);
}
}
i16 out = static_cast<i16>(sum / (mult * mult));
return out;
}
bool WaveSimulation2D::geti16All(fl::size x, fl::size y, i16 *curr,
i16 *prev, i16 *diff) const {
if (!has(x, y))
return false;
*curr = geti16(x, y);
*prev = geti16Previous(x, y);
*diff = *curr - *prev;
return true;
}
i8 WaveSimulation2D::geti8(fl::size x, fl::size y) const {
return static_cast<i8>(geti16(x, y) >> 8);
}
u8 WaveSimulation2D::getu8(fl::size x, fl::size y) const {
i16 value = geti16(x, y);
if (mSim->getHalfDuplex()) {
u16 v2 = static_cast<u16>(value);
switch (mU8Mode) {
case WAVE_U8_MODE_LINEAR:
return half_duplex_blend_linear(v2);
case WAVE_U8_MODE_SQRT:
return half_duplex_blend_sqrt_q15(v2);
}
}
return static_cast<u8>(((static_cast<u16>(value) + 32768)) >> 8);
}
bool WaveSimulation2D::has(fl::size x, fl::size y) const {
return (x < mOuterWidth) && (y < mOuterHeight);
}
void WaveSimulation2D::seti16(fl::size x, fl::size y, i16 v16) {
if (!has(x, y))
return;
u8 mult = MAX(1, mMultiplier);
// radius in pixels of your diamond
int rad = static_cast<int>(mult) / 2;
for (fl::size j = 0; j < mult; ++j) {
for (fl::size i = 0; i < mult; ++i) {
// compute offset from the center of this block
int dx = static_cast<int>(i) - rad;
int dy = static_cast<int>(j) - rad;
// keep only those points whose Manhattan distance ≤ rad
if (ABS(dx) + ABS(dy) > rad) {
continue;
}
fl::size xx = x * mult + i;
fl::size yy = y * mult + j;
if (mSim->has(xx, yy)) {
if (mUseChangeGrid) {
i16 &pt = mChangeGrid.at(xx, yy);
if (pt == 0) {
// not set yet so set unconditionally.
pt = v16;
} else {
const bool sign_matches = (pt >= 0) == (v16 >= 0);
if (!sign_matches) {
// opposite signs, so overwrite
pt = v16;
} else {
// if the magnitude of the new pt is greater than what
// was already there, then overwrite.
u16 abs_pt = static_cast<u16>(ABS(pt));
u16 abs_v16 = static_cast<u16>(ABS(v16));
if (abs_v16 > abs_pt) {
pt = v16;
}
}
}
} else {
// Directly set the value in the simulation when change grid is disabled
mSim->seti16(xx, yy, v16);
}
}
}
}
}
void WaveSimulation2D::setf(fl::size x, fl::size y, float value) {
if (!has(x, y))
return;
value = fl::clamp(value, 0.0f, 1.0f);
i16 v16 = wave_detail::float_to_fixed(value);
seti16(x, y, v16);
}
void WaveSimulation2D::update() {
if (mUseChangeGrid) {
const vec2<i16> min_max = mChangeGrid.minMax();
const bool has_updates = min_max != vec2<i16>(0, 0);
for (u8 i = 0; i < mExtraFrames + 1; ++i) {
if (has_updates) {
// apply them
const u32 w = mChangeGrid.width();
const u32 h = mChangeGrid.height();
for (u32 x = 0; x < w; ++x) {
for (u32 y = 0; y < h; ++y) {
i16 v16 = mChangeGrid(x, y);
if (v16 != 0) {
mSim->seti16(x, y, v16);
}
}
}
}
mSim->update();
}
// zero out mChangeGrid
mChangeGrid.clear();
} else {
// When change grid is disabled, just run the simulation updates
for (u8 i = 0; i < mExtraFrames + 1; ++i) {
mSim->update();
}
}
}
u32 WaveSimulation2D::getWidth() const { return mOuterWidth; }
u32 WaveSimulation2D::getHeight() const { return mOuterHeight; }
void WaveSimulation2D::setExtraFrames(u8 extra) { mExtraFrames = extra; }
void WaveSimulation2D::setUseChangeGrid(bool enabled) {
if (mUseChangeGrid == enabled) {
return; // No change needed
}
mUseChangeGrid = enabled;
if (mUseChangeGrid) {
// Allocate change grid if enabling
u32 w = mOuterWidth * mMultiplier;
u32 h = mOuterHeight * mMultiplier;
mChangeGrid.reset(w, h);
} else {
// Deallocate change grid if disabling (saves memory)
mChangeGrid.reset(0, 0);
}
}
WaveSimulation1D::WaveSimulation1D(u32 length, SuperSample factor,
float speed, int dampening) {
init(length, factor, speed, dampening);
}
void WaveSimulation1D::init(u32 length, SuperSample factor, float speed,
int dampening) {
mOuterLength = length;
mMultiplier = static_cast<u32>(factor);
mSim.reset(); // clear out memory first.
mSim.reset(
new WaveSimulation1D_Real(length * mMultiplier, speed, dampening));
// Extra updates (frames) are applied because the simulation slows down in
// proportion to the supersampling factor.
mExtraFrames = static_cast<u8>(factor) - 1;
}
void WaveSimulation1D::setSpeed(float speed) { mSim->setSpeed(speed); }
void WaveSimulation1D::setDampening(int damp) { mSim->setDampening(damp); }
int WaveSimulation1D::getDampenening() const { return mSim->getDampenening(); }
void WaveSimulation1D::setExtraFrames(u8 extra) { mExtraFrames = extra; }
float WaveSimulation1D::getSpeed() const { return mSim->getSpeed(); }
float WaveSimulation1D::getf(fl::size x) const {
if (!has(x))
return 0.0f;
float sum = 0.0f;
u8 mult = MAX(1, mMultiplier);
for (u32 i = 0; i < mult; ++i) {
sum += mSim->getf(x * mult + i);
}
return sum / static_cast<float>(mult);
}
i16 WaveSimulation1D::geti16(fl::size x) const {
if (!has(x))
return 0;
u8 mult = MAX(1, mMultiplier);
i32 sum = 0;
for (u32 i = 0; i < mult; ++i) {
sum += mSim->geti16(x * mult + i);
}
return static_cast<i16>(sum / mult);
}
i16 WaveSimulation1D::geti16Previous(fl::size x) const {
if (!has(x))
return 0;
u8 mult = MAX(1, mMultiplier);
i32 sum = 0;
for (u32 i = 0; i < mult; ++i) {
sum += mSim->geti16Previous(x * mult + i);
}
return static_cast<i16>(sum / mult);
}
bool WaveSimulation1D::geti16All(fl::size x, i16 *curr, i16 *prev,
i16 *diff) const {
if (!has(x))
return false;
*curr = geti16(x);
*prev = geti16Previous(x);
*diff = *curr - *prev;
return true;
}
i8 WaveSimulation1D::geti8(fl::size x) const {
return static_cast<i8>(geti16(x) >> 8);
}
// u8 WaveSimulation2D::getu8(fl::size x, fl::size y) const {
// i16 value = geti16(x, y);
// if (mSim->getHalfDuplex()) {
// u16 v2 = static_cast<u16>(value);
// switch (mU8Mode) {
// case WAVE_U8_MODE_LINEAR:
// return half_duplex_blend_linear(v2);
// case WAVE_U8_MODE_SQRT:
// return half_duplex_blend_sqrt_q15(v2);
// }
// }
// return static_cast<u8>(((static_cast<u16>(value) + 32768)) >>
// 8);
// }
u8 WaveSimulation1D::getu8(fl::size x) const {
i16 value = geti16(x);
if (mSim->getHalfDuplex()) {
u16 v2 = static_cast<u16>(value);
switch (mU8Mode) {
case WAVE_U8_MODE_LINEAR:
return half_duplex_blend_linear(v2);
case WAVE_U8_MODE_SQRT:
return half_duplex_blend_sqrt_q15(v2);
}
}
return static_cast<u8>(((static_cast<u16>(value) + 32768)) >> 8);
}
bool WaveSimulation1D::has(fl::size x) const { return (x < mOuterLength); }
void WaveSimulation1D::setf(fl::size x, float value) {
if (!has(x))
return;
value = fl::clamp(value, -1.0f, 1.0f);
u8 mult = MAX(1, mMultiplier);
for (u32 i = 0; i < mult; ++i) {
mSim->set(x * mult + i, value);
}
}
void WaveSimulation1D::update() {
mSim->update();
for (u8 i = 0; i < mExtraFrames; ++i) {
mSim->update();
}
}
u32 WaveSimulation1D::getLength() const { return mOuterLength; }
} // namespace fl