/*This is a fire effect based on the famous Fire2012; but with various small improvements.
Perlin noise is being used to make a fire layer and a smoke layer;
and the overlay of both can make a quite realistic effect.
The speed of both need to be adapted to the matrix size and width:
* Super small matrices (like 3x3 led) don't need the smoke
* medium sized matrices (8x8 for example) profit from fine tuning both Fire Speed/scale as well as Smoke speed/scale
This code was adapted for a matrix with just four LED columns in 90° around a core and a height of 28.
Right at the bottom of the code, you find a translation matrix that needs to be adapted to your set up. I included
a link to a helpful page for this.
@repo https://github.com/Anderas2/Fire2023
@author https://github.com/Anderas2
*/
#include "FastLED.h"
#include "fl/xymap.h"
// #include "fl/screenmap.h"
#include "fl/vector.h"
using namespace fl;
// matrix size
#define WIDTH 6
#define HEIGHT 7
#define CentreX (WIDTH / 2) - 1
#define CentreY (HEIGHT / 2) - 1
// NUM_LEDS = WIDTH * HEIGHT
#define PIXELPIN 3
#define NUM_LEDS 64
#define LAST_VISIBLE_LED 63
// Fire properties
#define BRIGHTNESS 255
#define FIRESPEED 17
#define FLAMEHEIGHT 3.8 // the higher the value, the higher the flame
#define FIRENOISESCALE 125 // small values, softer fire. Big values, blink fire. 0-255
// Smoke screen properties
// The smoke screen works best for big fire effects. It effectively cuts of a part of the flames
// from the rest, sometimes; which looks very much fire-like. For small fire effects with low
// LED count in the height, it doesn't help
// speed must be a little different and faster from Firespeed, to be visible.
// Dimmer should be somewhere in the middle for big fires, and low for small fires.
#define SMOKESPEED 25 // how fast the perlin noise is parsed for the smoke
#define SMOKENOISE_DIMMER 250 // thickness of smoke: the lower the value, the brighter the flames. 0-255
#define SMOKENOISESCALE 125 // small values, softer smoke. Big values, blink smoke. 0-255
CRGB leds[NUM_LEDS];
// fire palette roughly like matlab "hot" colormap
// This was one of the most important parts to improve - fire color makes fire impression.
// position, r, g, b value.
// max value for "position" is BRIGHTNESS
DEFINE_GRADIENT_PALETTE(hot_gp) {
27, 0, 0, 0, // black
28, 140, 40, 0, // red
30, 205, 80, 0, // orange
155, 255, 100, 0,
210, 255, 200, 0, // yellow
255, 255, 255, 255 // white
};
// CRGBPalette32 hotPalette = hot_gp;
CRGBPalette16 hotPalette = hot_gp; // **changed** reduced depth of palette
// Map XY coordinates to numbers on the LED strip
uint8_t XY (uint8_t x, uint8_t y);
// parameters and buffer for the noise array
#define NUM_LAYERS 1
// two layers of perlin noise make the fire effect
#define FIRENOISE 0
#define SMOKENOISE 1
uint32_t x[NUM_LAYERS];
uint32_t y[NUM_LAYERS];
uint32_t z[NUM_LAYERS];
uint32_t scale_x[NUM_LAYERS];
uint32_t scale_y[NUM_LAYERS];
uint8_t noise[NUM_LAYERS][WIDTH][HEIGHT];
uint8_t noise2[NUM_LAYERS][WIDTH][HEIGHT];
uint8_t heat[NUM_LEDS];
// ScreenMap makeScreenMap(); // **changed** we won't be using that
void setup() {
//Serial.begin(115200);
// Adjust this for you own setup. Use the hardware SPI pins if possible.
// On Teensy 3.1/3.2 the pins are 11 & 13
// Details here: https://github.com/FastLED/FastLED/wiki/SPI-Hardware-or-Bit-banging
// In case you see flickering / glitching leds, reduce the data rate to 12 MHZ or less
// auto screenMap = makeScreenMap(); // **changed**
FastLED.addLeds<NEOPIXEL, PIXELPIN>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
FastLED.setDither(DISABLE_DITHER);
}
void Fire2023(uint32_t now);
void loop() {
EVERY_N_MILLISECONDS(8) {
Fire2023(millis());
}
FastLED.show();
}
void Fire2023(uint32_t now) {
// some changing values
// these values are produced by perlin noise to add randomness and smooth transitions
uint16_t ctrl1 = inoise16(11 * now, 0, 0);
uint16_t ctrl2 = inoise16(13 * now, 100000, 100000);
uint16_t ctrl = ((ctrl1 + ctrl2) >> 1);
// parameters for the fire heat map
x[FIRENOISE] = 3 * ctrl * FIRESPEED;
y[FIRENOISE] = 20 * now * FIRESPEED;
z[FIRENOISE] = 5 * now * FIRESPEED;
scale_x[FIRENOISE] = scale8(ctrl1, FIRENOISESCALE);
scale_y[FIRENOISE] = scale8(ctrl2, FIRENOISESCALE);
//calculate the perlin noise data for the fire
for (uint8_t x_count = 0; x_count < WIDTH; x_count++) {
uint32_t xoffset = scale_x[FIRENOISE] * (x_count - CentreX);
for (uint8_t y_count = 0; y_count < HEIGHT; y_count++) {
uint32_t yoffset = scale_y[FIRENOISE] * (y_count - CentreY);
uint16_t data = ((inoise16(x[FIRENOISE] + xoffset, y[FIRENOISE] + yoffset, z[FIRENOISE])) + 1);
noise[FIRENOISE][x_count][y_count] = data >> 8;
}
}
// parameters for the smoke map
x[SMOKENOISE] = 3 * ctrl * SMOKESPEED;
y[SMOKENOISE] = 20 * now * SMOKESPEED;
z[SMOKENOISE] = 5 * now * SMOKESPEED;
scale_x[SMOKENOISE] = scale8(ctrl1, SMOKENOISESCALE);
scale_y[SMOKENOISE] = scale8(ctrl2, SMOKENOISESCALE);
//calculate the perlin noise data for the smoke
for (uint8_t x_count = 0; x_count < WIDTH; x_count++) {
uint32_t xoffset = scale_x[SMOKENOISE] * (x_count - CentreX);
for (uint8_t y_count = 0; y_count < HEIGHT; y_count++) {
uint32_t yoffset = scale_y[SMOKENOISE] * (y_count - CentreY);
uint16_t data = ((inoise16(x[SMOKENOISE] + xoffset, y[SMOKENOISE] + yoffset, z[SMOKENOISE])) + 1);
noise[SMOKENOISE][x_count][y_count] = data / SMOKENOISE_DIMMER;
}
}
//copy everything one line up
for (uint8_t y = 0; y < HEIGHT - 1; y++) {
for (uint8_t x = 0; x < WIDTH; x++) {
heat[XY(x, y)] = heat[XY(x, y + 1)];
}
}
// draw lowest line - seed the fire where it is brightest and hottest
for (uint8_t x = 0; x < WIDTH; x++) {
heat[XY(x, HEIGHT-1)] = noise[FIRENOISE][WIDTH - x][CentreX];
//if (heat[XY(x, HEIGHT-1)] < 200) heat[XY(x, HEIGHT-1)] = 150;
}
// dim the flames based on FIRENOISE noise.
// if the FIRENOISE noise is strong, the led goes out fast
// if the FIRENOISE noise is weak, the led stays on stronger.
// once the heat is gone, it stays dark.
for (uint8_t y = 0; y < HEIGHT - 1; y++) {
for (uint8_t x = 0; x < WIDTH; x++) {
uint8_t dim = noise[FIRENOISE][x][y];
// high value in FLAMEHEIGHT = less dimming = high flames
dim = dim / FLAMEHEIGHT;
dim = 255 - dim;
heat[XY(x, y)] = scale8(heat[XY(x, y)] , dim);
// map the colors based on heatmap
// use the heat map to set the color of the LED from the "hot" palette
// whichpalette position brightness blend or not
leds[XY(x, y)] = ColorFromPalette(hotPalette, heat[XY(x, y)], heat[XY(x, y)], LINEARBLEND);
// dim the result based on SMOKENOISE noise
// this is not saved in the heat map - the flame may dim away and come back
// next iteration.
leds[XY(x, y)].nscale8(noise[SMOKENOISE][x][y]);
}
}
}
// Params for width and height
const uint8_t kMatrixWidth = 8;
const uint8_t kMatrixHeight = 8;
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
#define LAST_VISIBLE_LED 63
uint8_t XY (uint8_t x, uint8_t y) {
// any out of bounds address maps to the first hidden pixel
if ( (x >= kMatrixWidth) || (y >= kMatrixHeight) ) {
return (LAST_VISIBLE_LED + 1);
}
return x + y * kMatrixWidth;
// const uint8_t XYTable[] = {
// 0, 1, 2, 3, 4, 5, 6, 7,
// 8, 9, 10, 11, 12, 13, 14, 15,
// 16, 17, 18, 19, 20, 21, 22, 23,
// 24, 25, 26, 27, 28, 29, 30, 31,
// 32, 33, 34, 35, 36, 37, 38, 39,
// 40, 41, 42, 43, 44, 45, 46, 47,
// 48, 49, 50, 51, 52, 53, 54, 55,
// 56, 57, 58, 59, 60, 61, 62, 63
// };
// uint8_t i = (y * kMatrixWidth) + x;
// uint8_t j = XYTable[i];
// return j;
}