/*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;
}