Files
fahnen_esp32/.pio/libdeps/esp01_1m/FastLED/examples/Luminova/Luminova.h

180 lines
5.7 KiB
C++

#include <FastLED.h>
// ===== matrix + LED config =====
#define WIDTH 32
#define HEIGHT 32
#define NUM_LEDS (WIDTH * HEIGHT)
#define DATA_PIN 3
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define BRIGHTNESS 96
using fl::u16;
// Set to 1 if your panel is serpentine; 0 for progressive rows
const bool kMatrixSerpentineLayout = true;
// Scale down per-dot intensity to avoid blowout on small grids
const uint8_t kPointGain = 128; // 50%
CRGB leds[NUM_LEDS];
// UI control for animation speed (telemetry + control)
// Provide the XY() function FastLED expects (in fl namespace) so blur2d can map
// properly.
namespace fl {
fl::u16 XY(fl::u16 x, fl::u16 y) {
if (x >= WIDTH || y >= HEIGHT)
return 0;
if (kMatrixSerpentineLayout) {
if (y & 1) {
// odd rows run right-to-left
return y * WIDTH + (WIDTH - 1 - x);
} else {
// even rows run left-to-right
return y * WIDTH + x;
}
} else {
return y * WIDTH + x;
}
}
} // namespace fl
// ===== particle sim (Processing port) =====
struct P {
float x, y; // position
float a; // angle
int f; // direction (+1 or -1)
int g; // group (I in the original)
float s; // "stroke weight" / intensity
bool alive; // quick guard in case we want to reuse slots
};
static uint32_t t = 0; // frame-ish counter
static const int MAXP = 256; // fewer particles for small grids
P ps[MAXP];
void resetParticle(P &p, uint32_t tt) {
// Original: x=360,y=360 (center), a=t*1.25 + noise(I)*W, f=t%2*2-1, g=I,
// s=5
int I = (int)(tt / 50);
p.x = (WIDTH - 1) / 2.0f;
p.y = (HEIGHT - 1) / 2.0f;
// noise(I)*W -> use 1D Perlin noise via inoise8
uint8_t n1 = inoise8(I * 19); // arbitrary scale
float noiseW = (n1 / 255.0f) * WIDTH;
p.a = tt * 1.25f + noiseW; // base angle component
p.f = (tt & 1) ? +1 : -1; // alternate direction
p.g = I;
p.s = 3.0f; // lower initial intensity for small grids
p.alive = true;
}
inline void plotDot(int x, int y, uint8_t v) {
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT)
return;
// Additive white (Processing used stroke(W), i.e., white) with gain control
leds[fl::XY((fl::u8)x, (fl::u8)y)] += CHSV(0, 0, scale8(v, kPointGain));
}
// draw a small disk ~ strokeWeight; 1..3 pixels radius
void plotSoftDot(float fx, float fy, float s) {
// map s (decays from 5) to a pixel radius 1..3
float r = constrain(s * 0.5f, 1.0f, 3.0f);
int R = (int)ceilf(r);
int cx = (int)roundf(fx);
int cy = (int)roundf(fy);
float r2 = r * r;
for (int dy = -R; dy <= R; ++dy) {
for (int dx = -R; dx <= R; ++dx) {
float d2 = dx * dx + dy * dy;
if (d2 <= r2) {
// falloff toward edge
float fall = 1.0f - (d2 / (r2 + 0.0001f));
uint8_t v = (uint8_t)constrain(255.0f * fall, 0.0f, 255.0f);
plotDot(cx + dx, cy + dy, v);
}
}
}
}
void setup() {
CLEDController *controller =
&FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
FastLED.clear(true);
// When user adjusts UI, reflect into model speed
// Provide screen map to UI with a specific LED diameter
fl::XYMap xy = kMatrixSerpentineLayout
? fl::XYMap::constructSerpentine(WIDTH, HEIGHT)
: fl::XYMap::constructRectangularGrid(WIDTH, HEIGHT);
fl::ScreenMap screenmap = xy.toScreenMap();
screenmap.setDiameter(0.15f);
controller->setScreenMap(screenmap);
// init particles as "dead"
for (int i = 0; i < MAXP; ++i)
ps[i].alive = false;
}
// Needed for proper blur mapping.
u16 xy_map_function(u16 x, u16 y, u16 width, u16 height) { return fl::XY(x, y); }
fl::XYMap xymap = fl::XYMap::constructWithUserFunction(WIDTH, HEIGHT, xy_map_function);
void loop() {
// Processing does: background(0, t?9:!createCanvas(...)+W); filter(BLUR)
// We emulate a very light global fade + blur to leave trails.
// First a tiny global fade, then a mild Gaussian-ish blur.
fadeToBlackBy(leds, NUM_LEDS, 18); // stronger fade to prevent blowout
blur2d(leds, WIDTH, HEIGHT, 24, xymap); // slightly gentler blur for 32x32
// Spawn/overwrite one particle per frame, round-robin
resetParticle(ps[t % MAXP], t);
// Update & draw all particles
for (int i = 0; i < MAXP; ++i) {
if (!ps[i].alive)
continue;
P &p = ps[i];
// strokeWeight(p.s *= .997)
p.s *= 0.997f;
if (p.s < 0.5f) {
p.alive = false;
continue;
} // cheap cull
// a += (noise(t/99, p.g) - .5) / 9
// Use 2D noise: (t/99, g). inoise8 returns 0..255; center at ~128.
float tOver99 = (float)t / 99.0f;
uint8_t n2 = inoise8((uint16_t)(tOver99 * 4096), (uint16_t)(p.g * 37));
float n2c = ((int)n2 - 128) / 255.0f; // ~ -0.5 .. +0.5
p.a += (n2c) / 9.0f;
// x += cos((a)*f), y += sin(a*f) (original had (a+=...)*f inside cos)
float aa = p.a * (float)p.f;
p.x += cosf(aa);
p.y += sinf(aa);
// draw white point with softness according to s
plotSoftDot(p.x, p.y, p.s);
}
FastLED.show();
t++;
// Cap frame rate a bit like Processing's draw loop
FastLED.delay(16); // ~60 FPS
}
/*** Tips
* - Want sharper trails? Lower blur2d strength or raise fadeToBlackBy amount.
* - Too many or too few particles? Tweak MAXP.
* - Want color instead of white? Replace plotDot/plotSoftDot to use CHSV with
* hue based on p.g or p.a.
* - If your matrix isn't serpentine, set kMatrixSerpentineLayout=false.
*/