first commit

This commit is contained in:
stuce-bot 2025-06-30 20:47:33 +02:00
commit 5893b00dd2
1669 changed files with 1982740 additions and 0 deletions

View file

@ -0,0 +1,217 @@
/*
This demo is best viewed using the FastLED compiler.
Install: pip install fastled
Run: fastled <this sketch directory>
This will compile and preview the sketch in the browser, and enable
all the UI elements you see below.
*/
#include <Arduino.h>
#include <FastLED.h>
#include "fl/draw_visitor.h"
#include "fl/math_macros.h"
#include "fl/raster.h"
#include "fl/time_alpha.h"
#include "fl/ui.h"
#include "fl/xypath.h"
#include "fx/time.h"
#include "fl/bilinear_compression.h"
// Sketch.
#include "src/wave.h"
#include "src/xypaths.h"
using namespace fl;
#define HEIGHT 64
#define WIDTH 64
#define NUM_LEDS ((WIDTH) * (HEIGHT))
#define TIME_ANIMATION 1000 // ms
CRGB leds[NUM_LEDS];
CRGB leds_downscaled[NUM_LEDS / 4]; // Downscaled buffer
XYMap xyMap(WIDTH, HEIGHT, false);
XYMap xyMap_Dst(WIDTH / 2, HEIGHT / 2, false); // Framebuffer is regular rectangle LED matrix.
// XYPathPtr shape = XYPath::NewRosePath(WIDTH, HEIGHT);
// Speed up writing to the super sampled waveFx by writing
// to a raster. This will allow duplicate writes to be removed.
WaveEffect wave_fx; // init in setup().
fl::vector<XYPathPtr> shapes = CreateXYPaths(WIDTH, HEIGHT);
XYRaster raster(WIDTH, HEIGHT);
TimeWarp time_warp;
XYPathPtr getShape(int which) {
int len = shapes.size();
which = which % len;
if (which < 0) {
which += len;
}
return shapes[which];
}
//////////////////// UI Section /////////////////////////////
UITitle title("XYPath Demo");
UIDescription description("Use a path on the WaveFx");
UIButton trigger("Trigger");
UISlider whichShape("Which Shape", 0.0f, 0.0f, shapes.size() - 1, 1.0f);
UICheckbox useWaveFx("Use WaveFX", true);
UISlider transition("Transition", 0.0f, 0.0f, 1.0f, 0.01f);
UISlider scale("Scale", 1.0f, 0.0f, 1.0f, 0.01f);
UISlider speed("Speed", 1.0f, -20.0f, 20.0f, 0.01f);
UISlider numberOfSteps("Number of Steps", 32.0f, 1.0f, 100.0f, 1.0f);
UISlider maxAnimation("Max Animation", 1.0f, 5.0f, 20.0f, 1.f);
TimeClampedTransition shapeProgress(TIME_ANIMATION);
void setupUiCallbacks() {
speed.onChanged([](float value) { time_warp.setSpeed(speed.value()); });
maxAnimation.onChanged(
[](float value) { shapeProgress.set_max_clamp(maxAnimation.value()); });
trigger.onChanged([]() {
// shapeProgress.trigger(millis());
FASTLED_WARN("Trigger pressed");
});
useWaveFx.onChanged([](bool on) {
if (on) {
FASTLED_WARN("WaveFX enabled");
} else {
FASTLED_WARN("WaveFX disabled");
}
});
}
void setup() {
Serial.begin(115200);
auto screenmap = xyMap.toScreenMap();
screenmap.setDiameter(.2);
FastLED.addLeds<NEOPIXEL, 2>(leds, xyMap.getTotal()).setScreenMap(screenmap);
auto screenmap2 = xyMap_Dst.toScreenMap();
screenmap.setDiameter(.5);
screenmap2.addOffsetY(-HEIGHT / 2);
FastLED.addLeds<NEOPIXEL, 3>(leds_downscaled, xyMap_Dst.getTotal())
.setScreenMap(screenmap2);
setupUiCallbacks();
// Initialize wave simulation. Please don't use static constructors, keep it
// in setup().
trigger.click();
wave_fx = NewWaveSimulation2D(xyMap);
}
//////////////////// LOOP SECTION /////////////////////////////
float getAnimationTime(uint32_t now) {
float pointf = shapeProgress.updatef(now);
return pointf + transition.value();
}
void clearLeds() {
fl::clear(leds);
fl::clear(leds_downscaled);
};
void loop() {
// Your code here
clearLeds();
const uint32_t now = millis();
uint32_t now_warped = time_warp.update(now);
auto shape = getShape(whichShape.as<int>());
shape->setScale(scale.value());
float curr_alpha = getAnimationTime(now_warped);
static float s_prev_alpha = 0.0f;
// unconditionally apply the circle.
if (trigger) {
// trigger the transition
time_warp.reset(now);
now_warped = time_warp.update(now);
shapeProgress.trigger(now_warped);
FASTLED_WARN("Transition triggered on " << shape->name());
curr_alpha = getAnimationTime(now_warped);
s_prev_alpha = curr_alpha;
}
const bool is_active =
true || curr_alpha < maxAnimation.value() && curr_alpha > 0.0f;
static uint32_t frame = 0;
frame++;
clearLeds();
const CRGB purple = CRGB(255, 0, 255);
const int number_of_steps = numberOfSteps.value();
raster.reset();
float diff = curr_alpha - s_prev_alpha;
diff *= 1.0f;
float factor = MAX(s_prev_alpha - diff, 0.f);
for (int i = 0; i < number_of_steps; ++i) {
float a =
fl::map_range<float>(i, 0, number_of_steps - 1, factor, curr_alpha);
if (a < .04) {
// shorter tails at first.
a = map_range<float>(a, 0.0f, .04f, 0.0f, .04f);
}
float diff_max_alpha = maxAnimation.value() - curr_alpha;
if (diff_max_alpha < 0.94) {
// shorter tails at the end.
a = map_range<float>(a, curr_alpha, maxAnimation.value(),
curr_alpha, maxAnimation.value());
}
uint8_t alpha =
fl::map_range<uint8_t>(i, 0.0f, number_of_steps - 1, 64, 255);
if (!is_active) {
alpha = 0;
}
Tile2x2_u8 subpixel = shape->at_subpixel(a);
subpixel.scale(alpha);
// subpixels.push_back(subpixel);
raster.rasterize(subpixel);
}
s_prev_alpha = curr_alpha;
if (useWaveFx && is_active) {
DrawRasterToWaveSimulator draw_wave_fx(&wave_fx);
raster.draw(xyMap, draw_wave_fx);
} else {
raster.draw(purple, xyMap, leds);
}
int first = xyMap(1, 1);
int last = xyMap(WIDTH - 2, HEIGHT - 2);
leds[first] = CRGB(255, 0, 0);
leds[last] = CRGB(0, 255, 0);
if (useWaveFx) {
// fxBlend.draw(Fx::DrawContext(now, leds));
wave_fx.draw(Fx::DrawContext(now, leds));
}
// downscaleBilinear(leds, WIDTH, HEIGHT, leds_downscaled, WIDTH / 2,
// HEIGHT / 2);
downscaleHalf(leds, xyMap, leds_downscaled, xyMap_Dst);
// Print out the first 10 pixels of the original and downscaled
fl::vector_inlined<CRGB, 10> downscaled_pixels;
fl::vector_inlined<CRGB, 10> original_pixels;
for (int i = 0; i < 10; ++i) {
original_pixels.push_back(leds[i]);
downscaled_pixels.push_back(leds_downscaled[i]);
}
FastLED.show();
}

View file

@ -0,0 +1,65 @@
#include "wave.h"
#include "FastLED.h"
DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
0, 0, 0, 0, // Black
32, 0, 0, 70, // Dark blue
128, 20, 57, 255, // Electric blue
255, 255, 255, 255 // White
};
DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
0, 0, 0, 0, // black
8, 128, 64, 64, // green
16, 255, 222, 222, // red
64, 255, 255, 255, // white
255, 255, 255, 255 // white
};
WaveFx::Args CreateArgsLower() {
WaveFx::Args out;
out.factor = SuperSample::SUPER_SAMPLE_2X;
out.half_duplex = true;
out.auto_updates = true;
out.speed = 0.18f;
out.dampening = 9.0f;
out.crgbMap = WaveCrgbGradientMapPtr::New(electricBlueFirePal);
return out;
}
WaveFx::Args CreateArgsUpper() {
WaveFx::Args out;
out.factor = SuperSample::SUPER_SAMPLE_2X;
out.half_duplex = true;
out.auto_updates = true;
out.speed = 0.25f;
out.dampening = 3.0f;
out.crgbMap = WaveCrgbGradientMapPtr::New(electricGreenFirePal);
return out;
}
WaveEffect NewWaveSimulation2D(const XYMap xymap) {
// only apply complex xymap as the last step after compositiing.
XYMap xy_rect =
XYMap::constructRectangularGrid(xymap.getWidth(), xymap.getHeight());
Blend2dPtr fxBlend =
NewPtr<Blend2d>(xymap); // Final transformation goes to the blend stack.
int width = xymap.getWidth();
int height = xymap.getHeight();
XYMap xyRect(width, height, false);
WaveFx::Args args_lower = CreateArgsLower();
WaveFx::Args args_upper = CreateArgsUpper();
WaveFxPtr wave_fx_low = NewPtr<WaveFx>(xy_rect, args_lower);
WaveFxPtr wave_fx_high = NewPtr<WaveFx>(xy_rect, args_upper);
Blend2dPtr blend_stack = NewPtr<Blend2d>(xymap);
blend_stack->add(wave_fx_low);
blend_stack->add(wave_fx_high);
WaveEffect out = {
.wave_fx_low = wave_fx_low,
.wave_fx_high = wave_fx_high,
.blend_stack = blend_stack,
};
return out;
}

View file

@ -0,0 +1,34 @@
#pragma once
#include "fx/2d/blend.h"
#include "fx/2d/wave.h"
#include "fx/fx2d.h"
#include "fl/raster.h"
using namespace fl;
struct WaveEffect {
WaveFxPtr wave_fx_low;
WaveFxPtr wave_fx_high;
Blend2dPtr blend_stack;
void draw(Fx::DrawContext context) { blend_stack->draw(context); }
void addf(size_t x, size_t y, float value) {
wave_fx_low->addf(x, y, value);
wave_fx_high->addf(x, y, value);
}
};
struct DrawRasterToWaveSimulator {
DrawRasterToWaveSimulator(WaveEffect* wave_fx) : mWaveFx(wave_fx) {}
void draw(const vec2<int> &pt, uint32_t index, uint8_t value) {
float valuef = value / 255.0f;
int xx = pt.x;
int yy = pt.y;
mWaveFx->addf(xx, yy, valuef);
}
WaveEffect* mWaveFx;
};
WaveEffect NewWaveSimulation2D(const XYMap xymap);

View file

@ -0,0 +1,41 @@
#include "fl/xypath.h"
#include "fl/vector.h"
#include "fl/map_range.h"
#include "xypaths.h"
using namespace fl;
namespace {
Ptr<CatmullRomParams> make_path(int width, int height) {
// make a triangle.
Ptr<CatmullRomParams> params = NewPtr<CatmullRomParams>();
vector_inlined<vec2f, 5> points;
points.push_back(vec2f(0.0f, 0.0f));
points.push_back(vec2f(width / 3, height / 2));
points.push_back(vec2f(width - 3, height - 1));
points.push_back(vec2f(0.0f, height - 1));
points.push_back(vec2f(0.0f, 0.0f));
for (auto &p : points) {
p.x = map_range<float, float>(p.x, 0.0f, width - 1, -1.0f, 1.0f);
p.y = map_range<float, float>(p.y, 0.0f, height - 1, -1.0f, 1.0f);
params->addPoint(p);
}
return params;
}
}
fl::vector<XYPathPtr> CreateXYPaths(int width, int height) {
fl::vector<XYPathPtr> out;
out.push_back(XYPath::NewCirclePath(width, height));
out.push_back(XYPath::NewRosePath(width, height));
out.push_back(XYPath::NewHeartPath(width, height));
out.push_back(XYPath::NewArchimedeanSpiralPath(width, height));
out.push_back(XYPath::NewPhyllotaxisPath(width, height));
out.push_back(XYPath::NewGielisCurvePath(width, height));
out.push_back(XYPath::NewCatmullRomPath(width, height, make_path(width, height)));
return out;
}

View file

@ -0,0 +1,10 @@
#include "fl/xypath.h"
#include "fl/vector.h"
using namespace fl;
// XYPath::NewRosePath(WIDTH, HEIGHT);
fl::vector<XYPathPtr> CreateXYPaths(int width, int height);