// coypyright: Stefan Petrick
#include "FastLED.h"
#define DATA_PIN 4
#define BRIGHTNESS 255
#define NUM_LEDS 256
#define num_x 16 // how many LEDs are in one row?
#define num_y 16 // how many rows?
#define LED_COLS 16
#define LED_ROWS 16
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
//#define FRAMES_PER_SECOND 60
const uint8_t kMatrixWidth = 16;
const uint8_t kMatrixHeight = 16;
const bool kMatrixSerpentineLayout = false;
static const byte p[] = { 151,160,137,91,90, 15,131, 13,201,95,96,
53,194,233, 7,225,140,36,103,30,69,142, 8,99,37,240,21,10,23,190, 6,
148,247,120,234,75, 0,26,197,62,94,252,219,203,117, 35,11,32,57,177,
33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,
48,27,166, 77,146,158,231,83,111,229,122, 60,211,133,230,220,105,92,
41,55,46,245,40,244,102,143,54,65,25,63,161, 1,216,80,73,209,76,132,
187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,
173,186, 3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,
212,207,206, 59,227, 47,16,58,17,182,189, 28,42,223,183,170,213,119,
248,152,2,44,154,163,70,221,153,101,155,167,43,172, 9,129,22,39,253,
19,98,108,110,79,113,224,232,178,185,112,104,218,246, 97,228,251,34,
242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,
150,254,138,236,205, 93,222,114, 67,29,24, 72,243,141,128,195,78,66,
215,61,156,180
};
#define P(x) p[(x) & 255]
float polar_theta[num_x][num_y]; // look-up table for polar angles
float distance[num_x][num_y]; // look-up table for polar distances
struct render_parameters {
float center_x = (num_x / 2) - 0.5; // center of the matrix
float center_y = (num_y / 2) - 0.5;
float dist, angle;
float scale_x = 0.1; // smaller values = zoom in
float scale_y = 0.1;
float scale_z = 0.1;
float offset_x, offset_y, offset_z;
float z;
float low_limit = 0; // getting contrast by highering the black point
float high_limit = 1;
};
render_parameters animation; // all animation parameters in one place
#define num_oscillators 10
struct oscillators {
float master_speed; // global transition speed
float offset[num_oscillators]; // oscillators can be shifted by a time offset
float ratio[num_oscillators]; // speed ratios for the individual oscillators
};
oscillators timings; // all speed settings in one place
struct modulators {
float linear[num_oscillators]; // returns 0 to FLT_MAX
float radial[num_oscillators]; // returns 0 to 2*PI
float directional[num_oscillators]; // returns -1 to 1
float noise_angle[num_oscillators]; // returns 0 to 2*PI
};
modulators move; // all oscillator based movers and shifters at one place
struct rgb {
float red, green, blue;
};
rgb pixel;
void setup() {
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS); //setCorrection(TypicalLEDStrip);
//FastLED.addLeds<LED_TYPE,DATA_PIN,CLK_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(BRIGHTNESS);
render_polar_lookup_table((num_x / 2) - 0.5, (num_y / 2) - 0.5);
//Serial.begin(115200);
}
void loop()
{
timings.master_speed = 0.01; // speed ratios for the oscillators
timings.ratio[0] = 1; // higher values = faster transitions
timings.ratio[1] = 1.1;
timings.ratio[2] = 1.2;
timings.offset[1] = 100;
timings.offset[2] = 200;
timings.offset[3] = 300;
calculate_oscillators(timings); // get linear movers and oscillators going
for (int x = 0; x < num_x; x++) {
for (int y = 0; y < num_y; y++) {
// describe and render animation layers
animation.angle = 5;
animation.scale_x = 0.2;
animation.scale_y = 0.2;
animation.scale_z = 1;
animation.dist = distance[x][y];
animation.offset_y = -move.linear[0];
animation.offset_x = 0;
float show1 = render_value(animation);
// describe and render animation layers
animation.angle = 10;
animation.dist = distance[x][y];
animation.offset_y = -move.linear[1];
float show2 = render_value(animation);
// describe and render animation layers
animation.angle = 12;
animation.dist = distance[x][y];
animation.offset_y = -move.linear[2];
float show3 = render_value(animation);
// colormapping
pixel.red = show1;
pixel.green = show2 / 4;
pixel.blue = show3 / 4;
pixel = rgb_sanity_check(pixel);
leds[xy(x, y)] = CRGB(pixel.red, pixel.green, pixel.blue);
}
}
// b = micros(); // for time measurement in report_performance()
FastLED.show();
// c = micros(); // for time measurement in report_performance()
// EVERY_N_MILLIS(500) report_performance(); // check serial monitor for report
}
float fade(float t){ return t * t * t * (t * (t * 6 - 15) + 10); }
float lerp(float t, float a, float b){ return a + t * (b - a); }
float grad(int hash, float x, float y, float z)
{
int h = hash & 15; /* CONVERT LO 4 BITS OF HASH CODE */
float u = h < 8 ? x : y, /* INTO 12 GRADIENT DIRECTIONS. */
v = h < 4 ? y : h==12||h==14 ? x : z;
return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
}
void render_polar_lookup_table(float cx, float cy) {
for (int xx = 0; xx < num_x; xx++) {
for (int yy = 0; yy < num_y; yy++) {
float dx = xx - cx;
float dy = yy - cy;
distance[xx][yy] = hypotf(dx, dy);
polar_theta[xx][yy] = atan2f(dy, dx);
}
}
}
void calculate_oscillators(oscillators &timings) {
double runtime = millis() * timings.master_speed; // global anaimation speed
for (int i = 0; i < num_oscillators; i++) {
move.linear[i] = (runtime + timings.offset[i]) * timings.ratio[i]; // continously rising offsets, returns 0 to max_float
move.radial[i] = fmodf(move.linear[i], 2 * PI); // angle offsets for continous rotation, returns 0 to 2 * PI
move.directional[i] = sinf(move.radial[i]); // directional offsets or factors, returns -1 to 1
move.noise_angle[i] = PI * (1 + pnoise(move.linear[i], 0, 0)); // noise based angle offset, returns 0 to 2 * PI
}
}
float render_value(render_parameters &animation) {
// convert polar coordinates back to cartesian ones
float newx = (animation.offset_x + animation.center_x - (cosf(animation.angle) * animation.dist)) * animation.scale_x;
float newy = (animation.offset_y + animation.center_y - (sinf(animation.angle) * animation.dist)) * animation.scale_y;
float newz = (animation.offset_z + animation.z) * animation.scale_z;
// render noisevalue at this new cartesian point
float raw_noise_field_value = pnoise(newx, newy, newz);
// A) enhance histogram (improve contrast) by setting the black and white point (low & high_limit)
// B) scale the result to a 0-255 range (assuming you want 8 bit color depth per rgb chanel)
// Here happens the contrast boosting & the brightness mapping
if (raw_noise_field_value < animation.low_limit) raw_noise_field_value = animation.low_limit;
if (raw_noise_field_value > animation.high_limit) raw_noise_field_value = animation.high_limit;
float scaled_noise_value = map_float(raw_noise_field_value, animation.low_limit, animation.high_limit, 0, 255);
return scaled_noise_value;
}
rgb rgb_sanity_check(rgb &pixel) {
// rescue data if possible, return absolute value
//if (pixel.red < 0) pixel.red = fabsf(pixel.red);
//if (pixel.green < 0) pixel.green = fabsf(pixel.green);
//if (pixel.blue < 0) pixel.blue = fabsf(pixel.blue);
// discard everything above the valid 8 bit colordepth 0-255 range
if (pixel.red > 255) pixel.red = 255;
if (pixel.green > 255) pixel.green = 255;
if (pixel.blue > 255) pixel.blue = 255;
return pixel;
}
// float mapping maintaining 32 bit precision
// we keep values with high resolution for potential later usage
float map_float(float x, float in_min, float in_max, float out_min, float out_max) {
float result = (x-in_min) * (out_max-out_min) / (in_max-in_min) + out_min;
if (result < out_min) result = out_min;
if( result > out_max) result = out_max;
return result;
}
float pnoise(float x, float y, float z) {
int X = (int)floorf(x) & 255, /* FIND UNIT CUBE THAT */
Y = (int)floorf(y) & 255, /* CONTAINS POINT. */
Z = (int)floorf(z) & 255;
x -= floorf(x); /* FIND RELATIVE X,Y,Z */
y -= floorf(y); /* OF POINT IN CUBE. */
z -= floorf(z);
float u = fade(x), /* COMPUTE FADE CURVES */
v = fade(y), /* FOR EACH OF X,Y,Z. */
w = fade(z);
int A = P(X)+Y,
AA = P(A)+Z,
AB = P(A+1)+Z, /* HASH COORDINATES OF */
B = P(X+1)+Y,
BA = P(B)+Z,
BB = P(B+1)+Z; /* THE 8 CUBE CORNERS, */
return lerp(w,lerp(v,lerp(u, grad(P(AA ), x, y, z), /* AND ADD */
grad(P(BA ), x-1, y, z)), /* BLENDED */
lerp(u, grad(P(AB ), x, y-1, z), /* RESULTS */
grad(P(BB ), x-1, y-1, z))), /* FROM 8 */
lerp(v, lerp(u, grad(P(AA+1), x, y, z-1), /* CORNERS */
grad(P(BA+1), x-1, y, z-1)), /* OF CUBE */
lerp(u, grad(P(AB+1), x, y-1, z-1),
grad(P(BB+1), x-1, y-1, z-1))));
}
uint16_t xy( uint8_t x, uint8_t y)
{
uint16_t i;
if( kMatrixSerpentineLayout == false) {
i = (y * kMatrixWidth) + x;
}
if( kMatrixSerpentineLayout == true) {
if( y & 0x01) {
// Odd rows run backwards
uint8_t reverseX = (kMatrixWidth - 1) - x;
i = (y * kMatrixWidth) + reverseX;
} else {
// Even rows run forwards
i = (y * kMatrixWidth) + x;
}
}
return i;
}