Skip to content

Commit d9595ba

Browse files
committed
Original unmodified file.
1 parent 0733d47 commit d9595ba

File tree

1 file changed

+345
-0
lines changed

1 file changed

+345
-0
lines changed
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
// Polar basics demo for the
2+
// FastLED Podcast #2
3+
// https://www.youtube.com/watch?v=KKjFRZFBUrQ
4+
//
5+
// VO.1 preview version
6+
// by Stefan Petrick 2023
7+
// This code is licenced under a
8+
// Creative Commons Attribution
9+
// License CC BY-NC 3.0
10+
11+
#include <FastLED.h>
12+
#include <FLOAT.h>
13+
14+
#define WIDTH 16 // how many LEDs are in one row?
15+
#define HEIGHT 16 // how many rows?
16+
#define NUM_LEDS ((WIDTH) * (HEIGHT))
17+
18+
float runtime; // elapse ms since startup
19+
float newdist, newangle; // parameters for image reconstruction
20+
float z; // 3rd dimension for the 3d noise function
21+
float offset_x, offset_y; // wanna shift the cartesians during runtime?
22+
float scale_x, scale_y; // cartesian scaling in 2 dimensions
23+
float dist, angle; // the actual polar coordinates
24+
25+
int x, y; // the cartesian coordiantes
26+
int num_x = WIDTH; // horizontal pixel count
27+
int num_y = HEIGHT; // vertical pixel count
28+
29+
// Background for setting the following 2 numbers: the FastLED inoise16() function returns
30+
// raw values ranging from 0-65535. In order to improve contrast we filter this output and
31+
// stretch the remains. In histogram (photography) terms this means setting a blackpoint and
32+
// a whitepoint. low_limit MUST be smaller than high_limit.
33+
34+
uint16_t low_limit = 30000; // everything lower drawns in black
35+
// higher numer = more black & more contrast present
36+
uint16_t high_limit = 50000; // everything higher gets maximum brightness & bleeds out
37+
// lower number = the result will be more bright & shiny
38+
39+
float center_x = (num_x / 2) - 0.5; // the reference point for polar coordinates
40+
float center_y = (num_y / 2) - 0.5; // (can also be outside of the actual xy matrix)
41+
//float center_x = 20; // the reference point for polar coordinates
42+
//float center_y = 20;
43+
44+
CRGB leds[WIDTH * HEIGHT]; // framebuffer
45+
46+
float theta [WIDTH] [HEIGHT]; // look-up table for all angles
47+
float distance[WIDTH] [HEIGHT]; // look-up table for all distances
48+
float vignette[WIDTH] [HEIGHT];
49+
float inverse_vignette[WIDTH] [HEIGHT];
50+
51+
float spd; // can be used for animation speed manipulation during runtime
52+
53+
float show1, show2, show3, show4, show5; // to save the rendered values of all animation layers
54+
float red, green, blue; // for the final RGB results after the colormapping
55+
56+
float c, d, e, f; // factors for oscillators
57+
float linear_c, linear_d, linear_e, linear_f; // linear offsets
58+
float angle_c, angle_d, angle_e, angle_f; // angle offsets
59+
float noise_angle_c, noise_angle_d, noise_angle_e, noise_angle_f; // angles based on linear noise travel
60+
float dir_c, dir_d, dir_e, dir_f; // direction multiplicators
61+
62+
63+
64+
void setup() {
65+
66+
Serial.begin(115200); // check serial monitor for current fps count
67+
68+
// Teensy users: make sure to use the hardware SPI pins 11 & 13
69+
// for best performance
70+
71+
FastLED.addLeds<APA102, 11, 13, BGR, DATA_RATE_MHZ(12)>(leds, NUM_LEDS);
72+
73+
// FastLED.addLeds<NEOPIXEL, 13>(leds, NUM_LEDS);
74+
75+
render_polar_lookup_table(); // precalculate all polar coordinates
76+
// to improve the framerate
77+
render_vignette_table(9.5); // the number is the desired radius in pixel
78+
// WIDTH/2 generates a circle
79+
}
80+
81+
82+
void loop() {
83+
84+
// set speedratios for the offsets & oscillators
85+
86+
spd = 0.05 ;
87+
c = 0.013 ;
88+
d = 0.017 ;
89+
e = 0.2 ;
90+
f = 0.007 ;
91+
92+
calculate_oscillators(); // get linear offsets and oscillators going
93+
94+
// ...and now let's generate a frame
95+
96+
for (x = 0; x < num_x; x++) {
97+
for (y = 0; y < num_y; y++) {
98+
99+
// pick polar coordinates from look the up table
100+
101+
dist = distance [x] [y];
102+
angle = theta [y] [x];
103+
104+
// Generation of one layer. Explore the parameters and what they do.
105+
106+
scale_x = 10000; // smaller value = zoom in, bigger structures, less detail
107+
scale_y = 10000; // higher = zoom out, more pixelated, more detail
108+
z = 0; // must be >= 0
109+
newangle = angle + angle_c;
110+
newdist = dist;
111+
offset_x = 0; // must be >=0
112+
offset_y = 0; // must be >=0
113+
114+
show1 = render_pixel();
115+
116+
117+
// Colormapping - Assign rendered values to colors
118+
119+
red = show1;
120+
green = 0;
121+
blue = 0;
122+
123+
// Check the final results.
124+
// Discard faulty RGB values & write the valid results into the framebuffer.
125+
126+
write_pixel_to_framebuffer();
127+
128+
}
129+
}
130+
131+
// BRING IT ON! SHOW WHAT YOU GOT!
132+
FastLED.show();
133+
134+
// check serial monitor for current performance data
135+
EVERY_N_MILLIS(500) report_performance();
136+
137+
}
138+
//-----------------------------------------------------------------------------------end main loop --------------------
139+
140+
void calculate_oscillators() {
141+
142+
runtime = millis(); // save elapsed ms since start up
143+
144+
runtime = runtime * spd; // global anaimation speed
145+
146+
linear_c = runtime * c; // some linear rising offsets 0 to max
147+
linear_d = runtime * d;
148+
linear_e = runtime * e;
149+
linear_f = runtime * f;
150+
151+
angle_c = fmodf(linear_c, 2 * PI); // some cyclic angle offsets 0 to 2*PI
152+
angle_d = fmodf(linear_d, 2 * PI);
153+
angle_e = fmodf(linear_e, 2 * PI);
154+
angle_f = fmodf(linear_f, 2 * PI);
155+
156+
dir_c = sinf(angle_c); // some direction oscillators -1 to 1
157+
dir_d = sinf(angle_d);
158+
dir_e = sinf(angle_e);
159+
dir_f = sinf(angle_f);
160+
161+
uint16_t noi;
162+
noi = inoise16(10000 + linear_c * 100000); // some noise controlled angular offsets
163+
noise_angle_c = map_float(noi, 0, 65535 , 0, 4*PI);
164+
noi = inoise16(20000 + linear_d * 100000);
165+
noise_angle_d = map_float(noi, 0, 65535 , 0, 4*PI);
166+
noi = inoise16(30000 + linear_e * 100000);
167+
noise_angle_e = map_float(noi, 0, 65535 , 0, 4*PI);
168+
noi = inoise16(40000 + linear_f * 100000);
169+
noise_angle_f = map_float(noi, 0, 65535 , 0, 4*PI);
170+
}
171+
172+
173+
// given a static polar origin we can precalculate
174+
// all the (expensive) polar coordinates
175+
176+
void render_polar_lookup_table() {
177+
178+
for (int xx = 0; xx < num_x; xx++) {
179+
for (int yy = 0; yy < num_y; yy++) {
180+
181+
float dx = xx - center_x;
182+
float dy = yy - center_y;
183+
184+
distance[xx] [yy] = hypotf(dx, dy);
185+
theta[xx] [yy] = atan2f(dy, dx);
186+
187+
}
188+
}
189+
}
190+
191+
192+
// calculate distance and angle of the point relative to
193+
// the polar origin defined by center_x & center_y
194+
195+
void get_polar_values() {
196+
197+
// calculate current cartesian distances (deltas) from polar origin point
198+
199+
float dx = x - center_x;
200+
float dy = y - center_y;
201+
202+
// calculate distance between current point & polar origin
203+
// (length of the origin vector, pythgorean theroem)
204+
// dist = sqrt((dx*dx)+(dy*dy));
205+
206+
dist = hypotf(dx, dy);
207+
208+
// calculate the angle
209+
// (where around the polar origin is the current point?)
210+
211+
angle = atan2f(dy, dx);
212+
213+
// done, that's all we need
214+
}
215+
216+
217+
// convert polar coordinates back to cartesian
218+
// & render noise value there
219+
220+
float render_pixel() {
221+
222+
// convert polar coordinates back to cartesian ones
223+
224+
float newx = (offset_x + center_x - (cosf(newangle) * newdist)) * scale_x;
225+
float newy = (offset_y + center_y - (sinf(newangle) * newdist)) * scale_y;
226+
227+
// render noisevalue at this new cartesian point
228+
229+
uint16_t raw_noise_field_value = inoise16(newx, newy, z);
230+
231+
// a lot is happening here, namely
232+
// A) enhance histogram (improve contrast) by setting the black and white point
233+
// B) scale the result to a 0-255 range
234+
// it's the contrast boosting & the "colormapping" (technically brightness mapping)
235+
236+
if (raw_noise_field_value < low_limit) raw_noise_field_value = low_limit;
237+
if (raw_noise_field_value > high_limit) raw_noise_field_value = high_limit;
238+
239+
float scaled_noise_value = map_float(raw_noise_field_value, low_limit, high_limit, 0, 255);
240+
241+
return scaled_noise_value;
242+
243+
// done, we've just rendered one color value for one single pixel
244+
}
245+
246+
247+
// float mapping maintaining 32 bit precision
248+
// we keep values with high resolution for potential later usage
249+
250+
float map_float(float x, float in_min, float in_max, float out_min, float out_max) {
251+
252+
float result = (x-in_min) * (out_max-out_min) / (in_max-in_min) + out_min;
253+
if (result < out_min) result = out_min;
254+
if( result > out_max) result = out_max;
255+
256+
return result;
257+
}
258+
259+
260+
// Avoid any possible color flicker by forcing the raw RGB values to be 0-255.
261+
// This enables to play freely with random equations for the colormapping
262+
// without causing flicker by accidentally missing the valid target range.
263+
264+
void rgb_sanity_check() {
265+
266+
// rescue data if possible: when negative return absolute value
267+
if (red < 0) red = abs(red);
268+
if (green < 0) green = abs(green);
269+
if (blue < 0) blue = abs(blue);
270+
271+
// discard everything above the valid 0-255 range
272+
if (red > 255) red = 255;
273+
if (green > 255) green = 255;
274+
if (blue > 255) blue = 255;
275+
276+
}
277+
278+
279+
// check result after colormapping and store the newly rendered rgb data
280+
281+
void write_pixel_to_framebuffer() {
282+
283+
// the final color values shall not exceed 255 (to avoid flickering pixels caused by >255 = black...)
284+
// negative values * -1
285+
286+
rgb_sanity_check();
287+
288+
CRGB finalcolor = CRGB(red, green, blue);
289+
290+
// write the rendered pixel into the framebutter
291+
leds[XY(x, y)] = finalcolor;
292+
}
293+
294+
295+
// find the right led index
296+
297+
uint16_t XY(uint8_t x, uint8_t y) {
298+
if (y & 1) // check last bit
299+
return (y + 1) * WIDTH - 1 - x; // reverse every second line for a serpentine lled layout
300+
else
301+
return y * WIDTH + x; // use this equation only for a line by line led layout
302+
} // remove the previous 3 lines of code in this case
303+
304+
305+
// make it look nicer - expand low brightness values and compress high brightness values,
306+
// basically we perform gamma curve bending for all 3 color chanels,
307+
// making more detail visible which otherwise tends to get lost in brightness
308+
309+
void adjust_gamma() {
310+
for (uint16_t i = 0; i < NUM_LEDS; i++)
311+
{
312+
leds[i].r = dim8_video(leds[i].r);
313+
leds[i].g = dim8_video(leds[i].g);
314+
leds[i].b = dim8_video(leds[i].b);
315+
}
316+
}
317+
318+
319+
320+
// precalculate a radial brightness mask
321+
322+
void render_vignette_table(float filter_radius) {
323+
324+
for (int xx = 0; xx < num_x; xx++) {
325+
for (int yy = 0; yy < num_y; yy++) {
326+
327+
vignette[xx] [yy] = (filter_radius - distance[xx] [yy]) / filter_radius;
328+
if (vignette[xx] [yy] < 0) vignette[xx] [yy] = 0;
329+
}
330+
}
331+
}
332+
333+
334+
335+
// show current framerate and rendered pixels per second
336+
337+
void report_performance() {
338+
339+
int fps = FastLED.getFPS(); // frames per second
340+
int kpps = (fps * HEIGHT * WIDTH) / 1000; // kilopixel per second
341+
342+
Serial.print(kpps); Serial.print(" kpps ... ");
343+
Serial.print(fps); Serial.print(" fps @ ");
344+
Serial.print(WIDTH*HEIGHT); Serial.println(" LEDs ... ");
345+
}

0 commit comments

Comments
 (0)