Hi,
I'm rewriting my Processing simulation code for Arduino + FastLED. The animation runs fine and looks as expected - but is way slower than I'd expect on a small 16x16 matrix. I guess I made a silly mistake and I#m too blind to see it.
My guess was: Is it possible, that sinf() gets really slow with (very) large angles? I tryed to avoid it by using fmodf() before, but no change.
The (in my eyes) critical part of the code is here, it gets called for every single pixel:
Code:
// calculate distance and angle of the point relative to
// the origin described by center_x & center_y
void get_polar_values() {
// calculate the cartesian distances (deltas) from polar origin point
// aka visual rotation center
float dx = x - center_x;
float dy = y - center_y;
// calculate distance between current point & polar origin
// it's hypotf(), length of the origin vector, pytharorean theroem
dist = hypotf(dx, dy);
// calculate the angle
// (where around the polar origin is the current point?)
angle = atan2f(dy, dx);
}
// convert polar coordinates back to cartesian
// & render noise value there
uint8_t render_pixel() {
// convert polar coordinates back to cartesian ones
float newx = (offset_x + center_x - (cosf(newangle) * newdist)) * scale_x;
float newy = (offset_y + center_y - (sinf(newangle) * newdist)) * scale_y;
// render noisevalue at this new cartesian point
uint16_t noise_field_value = inoise16(newx, newy, z);
// A) enhance histogram (improve contrast) by setting the black and white point
// B) scale the result to a 0-255 range
// it's the contrast boosting & the "colormapping" (technically brightness mapping)
if (noise_field_value<30000) noise_field_value = 30000;
if (noise_field_value>50000) noise_field_value = 50000;
uint8_t value = map(noise_field_value, 30000, 50000, 0, 255);
return value;
}
It runs on a Teensy 3.6 with 256 LEDs at the beginning @240fps and drops within minutes down to 45fps with only 3 animation layers. Both numbers feel just not right. I suspect I do something stupid I'm not aware of. I use no lookup table for the polar data, because later I want to move the polar origin during runtime, too.
Here the complete code for context what I'm doing:
Code:
// (c) by Stefan Petrick 2023
#include <FastLED.h>
#define WIDTH 16
#define HEIGHT 16
#define NUM_LEDS ((WIDTH) * (HEIGHT))
CRGB leds[WIDTH*HEIGHT];
float c, d, e, f, time; // some timedependant counters
float newdist, newangle; // parameters for reconstruction
float z; // 3d dimension for the 3d noise function
float offset_x, offset_y; // wanna shift the cartesians during runtime?
float scale_x, scale_y; // cartesian scaling in 2 dimensions
float dist, angle; // the actual polar coordinates
int x, y; // the cartesian coordiantes
int num_x = WIDTH; // horizontal pixel count
int num_y = HEIGHT; // vertical pixel count
float center_x = (num_x / 2) - 0.5; // the reference point for polar coordinates
float center_y = (num_y / 2) - 0.5; // (can also be outside of the actual xy matrix)
void setup() {
Serial.begin(115200);
FastLED.addLeds<APA102, 33, 32, BGR, DATA_RATE_MHZ(12)>(leds, 256);
}
void loop() {
// set timers
time = millis(); // save the current running time
time = time * 0.5 ; // global anaimation speed (higher value = faster)
c = time / 400; // speeds of the subtimers (smaller value = faster)
d = time / 800;
e = time / 600;
// calculate whats constant in the current frame
center_x = (num_x / 2) - 0.5; // the reference point for polar coordinates
center_y = (num_y / 2) - 0.5;
for (x = 0; x < num_x; x++) {
for (y = 0; y < num_y; y++) {
// layer No 1: RED-------------------------------------------------------------
// calculate distance and angle of the point relative to
// the origin described by center_x & center_y
get_polar_values();
// set all parameters for the first layer (red)
scale_x = 5000;
scale_y = 5000;
newangle = angle * 3 + c - dist/4;
newdist = dist;
z = c * 10000;
offset_x = 0;
offset_y = c*5;
// convert polar coordinates back to cartesian
// & render noise value there
byte show1 = render_pixel();
// layer 2: Green
scale_x = 6000;
scale_y = 6000;
newangle = angle * 3 + c*1.1 - dist/4;
newdist = dist;
z = c * 12000;
offset_x = 0;
offset_y = d*5;
// convert polar coordinates back to cartesian
// & render noise value there
byte show2 = render_pixel();
// layer 3: blue
scale_x = 7000;
scale_y = 7000;
newangle = angle * 3 + c*1.2 - dist/4;
newdist = dist;
z = c * 13000;
offset_x = 0;
offset_y = e*5;
// convert polar coordinates back to cartesian
// & render noise value there
byte show3 = render_pixel();
CRGB color = CRGB(show1,show2,show3);
leds[XY(x, y)] = color;
}
}
adjust_gamma();
FastLED.show();
EVERY_N_MILLIS(100) Serial.println(FastLED.getFPS());
}
void get_polar_values() {
// calculate the cartesian distances (deltas) from polar origin point
// aka visual rotation center
float dx = x - center_x;
float dy = y - center_y;
// calculate distance between current point & polar origin
// it's hypotf(), length of the origin vector, pytharorean theroem
//dist = sqrt((dx*dx)+(dy*dy));
dist = hypotf(dx, dy);
// calculate the angle
// (where around the polar origin is the current point?)
angle = atan2f(dy, dx);
// done, that's all we need
}
uint8_t render_pixel() {
// convert polar coordinates back to cartesian ones
fmodf(newangle, 2 * PI); // no solution to the fps decline, but slower to begin with
float newx = (offset_x + center_x - (cosf(newangle) * newdist)) * scale_x;
float newy = (offset_y + center_y - (sinf(newangle) * newdist)) * scale_y;
// render noisevalue at this new cartesian point
uint16_t noise_field_value = inoise16(newx, newy, z);
// A) enhance histogram (improve contrast) by setting the black and white point
// B) scale the result to a 0-255 range
// it's the contrast boosting & the "colormapping" (technically brightness mapping)
// #################### not sure if the following 3 lines are unperformant?!
// #################### do the datatypes make sense?
if (noise_field_value<30000) noise_field_value = 30000;
if (noise_field_value>50000) noise_field_value = 50000;
uint8_t value = map(noise_field_value, 30000, 50000, 0, 255);
return value;
// done, we've just rendered one color value for one single pixel
}
uint16_t XY(uint8_t x, uint8_t y) {
//if (x >= WIDTH) return NUM_LEDS;
//if (y >= HEIGHT) return NUM_LEDS;
if (y & 1)
return (y + 1) * WIDTH - 1 - x;
else
return y * WIDTH + x;
}
void adjust_gamma()
{
for (uint16_t i = 0; i < NUM_LEDS; i++)
{
leds[i].r = dim8_video(leds[i].r);
leds[i].g = dim8_video(leds[i].g);
leds[i].b = dim8_video(leds[i].b);
}
}
Any hint, comment or roast is appreciated! I'm a bit lost.