#include "FastLED.h"

// Matrix size
#define NUM_ROWS 16
#define NUM_COLS 16
#define WIDTH NUM_COLS
#define HEIGHT NUM_ROWS
#define NUM_LEDS NUM_ROWS * NUM_COLS
// LEDs pin
#define DATA_PIN 3
// LED brightness
#define BRIGHTNESS 255
// Define the array of leds
CRGB leds[NUM_LEDS];
//Blobs
//by stepko and kostyamat
bool loadingFlag = true;
#define Speed (255)
#define Scale (150)
#define Am (64)
#define Color (0) //0- HSV 1- FromPalette
#define regime (1) //0-small 1-big 

void drawPixelXYF(float x, float y, const CRGB & color) {
  if (x < 0 || y < 0 || x > ((float) WIDTH - 1) || y > ((float) HEIGHT - 1)) return;

  // extract the fractional parts and derive their inverses
  uint8_t xx = (x - (int) x) * 255, yy = (y - (int) y) * 255, 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++) {
    int16_t xn = x + (i & 1), yn = y + ((i >> 1) & 1);
    CRGB clr = leds[XY(xn, yn)];
    if (xn < (int) WIDTH - 1 && yn < (int) HEIGHT - 1 && yn > 0 && xn > 0) {
      clr.r = qadd8(clr.r, (color.r * wu[i]) >> 8);
      clr.g = qadd8(clr.g, (color.g * wu[i]) >> 8);
      clr.b = qadd8(clr.b, (color.b * wu[i]) >> 8);
    } else {
      clr.r = qadd8(clr.r, (color.r * 85) >> 8);
      clr.g = qadd8(clr.g, (color.g * 85) >> 8);
      clr.b = qadd8(clr.b, (color.b * 85) >> 8);
    }
    leds[XY(xn, yn)] = clr;
  }
#undef WU_WEIGHT
}
float ball[WIDTH][4]; //0-PosY 1-PosX 2-SpeedY 3-SpeedX
float radius[WIDTH];
bool rrad[WIDTH];
byte color[WIDTH];

void fill_circle(float cx, float cy, float radius, CRGB col) {
  for (float y = -radius; y <= radius; y++) {
    for (float x = -radius; x <= radius; x++) {
      if (x * x + y * y <= radius * radius)
        drawPixelXYF(cx + x, cy + y, col);
    }
  }
}


byte Amount = map(Am, 1, 255, 1, WIDTH);


//Draw the scene with the background defined by RGB colors and the 5 balls in different colors
void BlobS() {
  if (loadingFlag) { 
    for (byte i = 0; i < Amount; i++) {
      if (regime)
        radius[i] = random(1, 40) / 10;
      else
        radius[i] = 1;
      ball[i][2] = (float)random(5, 11) / (float)(257U - Speed) / 4.0;
      ball[i][3] = (float)random(5, 11) / (float)(257U - Speed) / 4.0;
      ball[i][0] = random(0, WIDTH);
      ball[i][1] = random(0, HEIGHT);//random(0,WIDTH);
      color[i] = random(0, 255);
      if ( ball[i][2] == 0)
        ball[i][2] = 1;
      if ( ball[i][3] == 0)
        ball[i][3] = 1;
    }
    loadingFlag = false;
  }
  //FastLED.clear();
  fadeToBlackBy(leds, NUM_LEDS, 20);
  // Bounce three balls around
  for (byte i = 0; i < Amount; i++) {
    if (rrad[i]) {  // тут у нас шарики надуваются\сдуваются по ходу движения
      radius[i] += (fabs(ball[i][2]) > fabs(ball[i][3])? fabs(ball[i][2]) : fabs(ball[i][3])) * 0.05;
      if (radius[i] >= 4.) {
        rrad[i] = false;
      }
    } else {
      radius[i] -= (fabs(ball[i][2]) > fabs(ball[i][3])? fabs(ball[i][2]) : fabs(ball[i][3])) * 0.05;
      if (radius[i] < 1) {
        rrad[i] = true;
        color[i] = random(0, 255);
      }
    }
    if (radius[i]> 1)
      fill_circle(ball[i][1], ball[i][0], radius[i], ColorFromPalette(RainbowColors_p, color[i]));
    else 
      drawPixelXYF(ball[i][1], ball[i][0], ColorFromPalette(RainbowColors_p, color[i]));
    //----------------------
    if (ball[i][0] + radius[i] >= HEIGHT - 1)
      ball[i][0] += (ball[i][2] * ((HEIGHT - 1 - ball[i][0]) / radius[i] + 0.005));
    else if (ball[i][0] - radius[i] <= 0)
      ball[i][0] += (ball[i][2] * (ball[i][0] / radius[i] + 0.005));
    else
      ball[i][0] += ball[i][2];
    //-----------------------
    if (ball[i][1] + radius[i] >= WIDTH - 1)
      ball[i][1] += (ball[i][3] * ((WIDTH - 1 - ball[i][1]) / radius[i] + 0.005));
    else if (ball[i][1] - radius[i] <= 0)
      ball[i][1] += (ball[i][3] * (ball[i][1] / radius[i] + 0.005));
    else
      ball[i][1] += ball[i][3];
    //------------------------
    if (ball[i][0] < 0.01) {
      ball[i][2] = (float)random8(5, 11) / (257U - Speed) / 4.0;
      ball[i][0] = 0.01;
    }
    else if (ball[i][0] > HEIGHT - 1.01) {
      ball[i][2] = (float)random8(5, 11) / (257U - Speed) / 4.0;
      ball[i][2] = -ball[i][2];
      ball[i][0] = HEIGHT - 1.01;
    }
    //----------------------
    if (ball[i][1] < 0.01) {
      ball[i][3] = (float)random8(5, 11) / (257U - Speed) / 4.0;
      ball[i][1] = 0.01;
    }
    else if (ball[i][1] > WIDTH - 1.01) {
      ball[i][3] = (float)random8(5, 11) / (257U - Speed) / 4.0;
      ball[i][3] = -ball[i][3];
      ball[i][1] = WIDTH - 1.01;
    }
  }
  blur2d(leds, WIDTH, HEIGHT, 128);
}



void setup() {
  //Serial.begin(250000);
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(BRIGHTNESS);
}

void loop() {
  BlobS();
  FastLED.show();
} //loop


uint16_t XY (uint8_t x, uint8_t y) {

  if ((y % 2 == 0) || 1)                     // если чётная строка
  {
    return ((uint32_t)y * WIDTH + x) % (WIDTH * HEIGHT);
  }
  else                                                      // если нечётная строка
  {
    return ((uint32_t)y  * WIDTH + WIDTH - x - 1) % (WIDTH * HEIGHT);
  }
}