Skip to content

Instantly share code, notes, and snippets.

@sutaburosu
Last active February 26, 2024 13:34
Show Gist options
  • Save sutaburosu/4c2a64796635c1046ce4a29adc5373e9 to your computer and use it in GitHub Desktop.
Save sutaburosu/4c2a64796635c1046ce4a29adc5373e9 to your computer and use it in GitHub Desktop.
An 8x32 variant of the leaping dots thing for u/tomchaps
// by <[email protected]>
// License: Creative Commons CC0
#include <Arduino.h>
#include <FastLED.h>
#include <colorutils.h>
enum XY_matrix_config {
SERPENTINE = 1,
ROWMAJOR = 2,
FLIPMAJOR = 4,
FLIPMINOR = 8
};
#define BRIGHTNESS 32
#define LED_PIN 2
#define COLOR_ORDER GRB
#define CHIPSET WS2812B
#define kMatrixWidth 8
#define kMatrixHeight 32
#define XY_MATRIX (SERPENTINE | ROWMAJOR | FLIPMINOR)
#define MS_GOAL 10 // to try maintain 1000 / 10ms == 100 FPS
#define NUM_LEAPERS 8
#define GRAVITY 10
#define SETTLED_THRESHOLD 48
#define WALL_FRICTION 248 // 255 is no friction
#define DRAG 240 // 255 is no wind resistance
#define SERIAL_UI 1
#define NUM_LEDS ((kMatrixWidth) * (kMatrixHeight))
CRGB leds[NUM_LEDS + 1]; // 1 extra for XY() to use when out-of-bounds
uint16_t XY(uint8_t x, uint8_t y) {
uint8_t major, minor, sz_major, sz_minor;
if (x >= kMatrixWidth || y >= kMatrixHeight)
return NUM_LEDS;
if (XY_MATRIX & ROWMAJOR)
major = x, minor = y, sz_major = kMatrixWidth, sz_minor = kMatrixHeight;
else
major = y, minor = x, sz_major = kMatrixHeight, sz_minor = kMatrixWidth;
if ((XY_MATRIX & FLIPMAJOR) ^ (minor & 1 && (XY_MATRIX & SERPENTINE)))
major = sz_major - 1 - major;
if (XY_MATRIX & FLIPMINOR)
minor = sz_minor - 1 - minor;
return (uint16_t) minor * sz_major + major;
}
///////////////////////////////////////////////////////////////////////
CRGBPalette16 currentPalette = RainbowColors_p;
uint32_t last_millis = 0;
typedef struct {
int16_t x, y, xd, yd;
// uint8_t state;
} Leaper;
Leaper leapers[NUM_LEAPERS];
extern "C" {
void restart_leaper(Leaper * lpr);
void move_leaper(Leaper * lpr);
}
void setup() {
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setCorrection(UncorrectedColor);
FastLED.setTemperature(UncorrectedTemperature);
FastLED.setDither(DISABLE_DITHER);
FastLED.setBrightness(BRIGHTNESS);
pinMode(LED_BUILTIN, OUTPUT);
if (SERIAL_UI) {
Serial.begin(250000);
}
for (uint8_t lpr = 0; lpr < NUM_LEAPERS; lpr++) {
restart_leaper(&leapers[lpr]);
leapers[lpr].x = random8() * kMatrixWidth;
leapers[lpr].y = random8() * kMatrixHeight;
}
}
void loop() {
FastLED.clear();
// fadeToBlackBy(leds, NUM_LEDS, 64);
for (uint8_t lpr = 0; lpr < NUM_LEAPERS; lpr++) {
move_leaper(&leapers[lpr]);
CRGB rgb = ColorFromPalette(currentPalette, lpr * (255 / NUM_LEAPERS), 255, LINEARBLEND);
if (kMatrixWidth > 1)
wu_pixel(leapers[lpr].x, leapers[lpr].y, &rgb);
else
wu_pixel1d(leapers[lpr].y, &rgb);
}
// cap the frame rate and indicate idle time via the built-in LED
uint32_t frame_time = millis() - last_millis;
int8_t pause = MS_GOAL - frame_time;
if (pause < 0 && SERIAL_UI) { Serial.print(-pause); Serial.println("ms late"); }
digitalWrite(LED_BUILTIN, HIGH);
if (pause > 0) delay(pause);
digitalWrite(LED_BUILTIN, LOW);
last_millis = millis();
FastLED.show();
}
void restart_leaper(Leaper * lpr) {
// leap up and to the side with some random component
lpr->xd = random8() + 32;
lpr->yd = random8() + 512;
// for variety, sometimes go 50% faster
if (random8() < 12) {
lpr->xd += lpr->xd >> 1;
lpr->yd += lpr->yd >> 1;
}
// leap towards the centre of the screen
if (lpr->x > (kMatrixWidth / 2 * 256)) {
lpr->xd = -lpr->xd;
}
}
void move_leaper(Leaper * lpr) {
// add the X & Y velocities to the position
lpr->x += lpr->xd;
lpr->y += lpr->yd;
// bounce off the floor and ceiling?
if (lpr->y < 0 || lpr->y >= ((kMatrixHeight - 1) << 8)) {
lpr->xd = ((int32_t) lpr->xd * WALL_FRICTION) >> 8;
lpr->yd = ((int32_t) -lpr->yd * WALL_FRICTION) >> 8;
if (lpr->y < 0) lpr->y = -lpr->y;
// settled on the floor?
if (lpr->y <= SETTLED_THRESHOLD && abs(lpr->yd) <= SETTLED_THRESHOLD) {
restart_leaper(lpr);
}
}
// bounce off the sides of the screen?
if (lpr->x <= 0 || lpr->x >= (kMatrixWidth - 1) << 8) {
lpr->xd = ((int32_t) -lpr->xd * WALL_FRICTION) >> 8;
lpr->yd = ((int32_t) lpr->yd * WALL_FRICTION) >> 8;
if (lpr->x <= 0) {
lpr->x = -lpr->x;
} else {
lpr->x = ((2 * kMatrixWidth - 1) << 8) - lpr->x;
}
}
// gravity
lpr->yd -= GRAVITY;
// viscosity, done badly
// uint32_t speed2 = lpr->xd * lpr->xd + lpr->yd * lpr->yd;
lpr->xd = ((int32_t) lpr->xd * DRAG) >> 8;
lpr->yd = ((int32_t) lpr->yd * DRAG) >> 8;
}
// x and y are 24.8 fixed point
// Not Ray Wu. ;) The idea came from Xiaolin Wu.
void wu_pixel(uint32_t x, uint32_t y, CRGB * col) {
// extract the fractional parts and derive their inverses
uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
// calculate the intensities for each affected pixel
#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy),
WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)};
// multiply the intensities by the colour, and saturating-add them to the pixels
for (uint8_t i = 0; i < 4; i++) {
uint16_t xy = XY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1));
leds[xy].r = qadd8(leds[xy].r, col->r * wu[i] >> 8);
leds[xy].g = qadd8(leds[xy].g, col->g * wu[i] >> 8);
leds[xy].b = qadd8(leds[xy].b, col->b * wu[i] >> 8);
}
}
// note that this will write to NUM_LEDS + 1
void wu_pixel1d(uint32_t y, CRGB * col) {
// extract the fractional parts and derive their inverses
uint8_t yy = y & 0xff, iy = 255 - yy;
y = y >> 8;
uint8_t wu[2] = {iy, yy};
// multiply the intensities by the colour, and saturating-add them to the pixels
for (uint8_t i = 0; i < 2; i++) {
leds[y].r = qadd8(leds[y].r, col->r * wu[i] >> 8);
leds[y].g = qadd8(leds[y].g, col->g * wu[i] >> 8);
leds[y].b = qadd8(leds[y].b, col->b * wu[i] >> 8);
y++;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment