Skip to content

Instantly share code, notes, and snippets.

@sutaburosu
Last active April 27, 2023 19:51
Show Gist options
  • Save sutaburosu/32a203c2efa2bb584f4b846a91066583 to your computer and use it in GitHub Desktop.
Save sutaburosu/32a203c2efa2bb584f4b846a91066583 to your computer and use it in GitHub Desktop.
Sub-pixel resolution is kinda essential for 16x16 displays.
// by <sutaburosu@gmail.com>
// License: Creative Commons CC0
#include <Arduino.h>
#include <FastLED.h>
#include <colorutils.h>
#define BRIGHTNESS 64
#define LED_PIN 2
#define COLOR_ORDER GRB
#define CHIPSET WS2812B
#define kMatrixWidth 16
#define kMatrixHeight 16
#define kMatrixSerpentineLayout true
#define kMatrixArrangedInRows true
#define kMatrixFlipMajorAxis false
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
#define MS_GOAL 10 // to try maintain 1000 / 10ms == 100 frames per second
#define SERIAL_UI 1
#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
CRGB leds[NUM_LEDS + 1]; // 1 extra for XY() to yield when out-of-bounds
uint16_t XY(uint8_t x, uint8_t y) {
if (x >= kMatrixWidth) return NUM_LEDS;
if (y >= kMatrixHeight) return NUM_LEDS;
if (kMatrixArrangedInRows) {
if (kMatrixFlipMajorAxis) x = (kMatrixWidth - 1) - x;
if (kMatrixSerpentineLayout && y & 1)
return y * kMatrixWidth + (kMatrixWidth - 1) - x;
return y * kMatrixWidth + x;
}
if (kMatrixFlipMajorAxis) y = (kMatrixHeight - 1) - y;
if (kMatrixSerpentineLayout && x & 1)
return x * kMatrixHeight + (kMatrixHeight - 1) - y;
return x * kMatrixHeight + y;
}
///////////////////////////////////////////////////////////////////////
CRGBPalette16 currentPalette = RainbowColors_p;
uint32_t last_millis = 0;
typedef struct {
int16_t x, y; int32_t xd, yd;
// uint8_t state;
} Leaper;
Leaper leapers[NUM_LEAPERS];
extern "C" {
void restart_leaper(Leaper * l);
void move_leaper(Leaper * l);
}
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 == 1) {
Serial.begin(250000);
}
for (uint8_t l = 0; l < NUM_LEAPERS; l++) {
// restart_leaper(&leapers[l]);
leapers[l].x = random8() * kMatrixWidth;
leapers[l].y = random8() * kMatrixHeight;
}
}
void loop() {
FastLED.clear();
// fadeToBlackBy(leds, NUM_LEDS, 64);
for (uint8_t l = 0; l < NUM_LEAPERS; l++) {
move_leaper(&leapers[l]);
CRGB rgb = ColorFromPalette(currentPalette, l * (255 / NUM_LEAPERS), 255, LINEARBLEND);
wu_pixel(leapers[l].x, leapers[l].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 * l) {
// leap up and to the side with some random component
l->xd = random8() + 32;
l->yd = random8() + 256;
// for variety, sometimes go 50% faster
if (random8() < 12) {
l->xd += l->xd >> 1;
l->yd += l->yd >> 1;
}
// leap towards the centre of the screen
if (l->x > (kMatrixWidth / 2 * 256)) {
l->xd = -l->xd;
}
}
void move_leaper(Leaper * l) {
// add the X & Y velocities to the position
l->x += l->xd;
l->y += l->yd;
// bounce off the floor and ceiling?
if (l->y < 0 || l->y >= ((kMatrixHeight - 1) << 8)) {
l->yd = (-l->yd * WALL_FRICTION) >> 8;
l->xd = ( l->xd * WALL_FRICTION) >> 8;
l->y += l->yd;
if (l->y < 0) l->y = 0;
// settled on the floor?
if (l->y <= SETTLED_THRESHOLD && abs(l->yd) <= SETTLED_THRESHOLD) {
restart_leaper(l);
}
}
// bounce off the sides of the screen?
if (l->x <= 0 || l->x >= (kMatrixWidth - 1) << 8) {
l->xd = (-l->xd * WALL_FRICTION) >> 8;
// l->yd = (-l->yd * WALL_FRICTION) >> 8;
// force back onto the screen, otherwise they eventually sneak away
if (l->x <= 0) {
l->x = l->xd;
} else {
l->x = ((kMatrixWidth - 1) << 8) - l->xd;
}
}
// gravity
l->yd -= GRAVITY;
// viscosity, done badly
// uint32_t speed2 = l->xd * l->xd + l->yd * l->yd;
l->xd = (l->xd * DRAG) >> 8;
l->yd = (l->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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment