// by <[email protected]>
// License: Creative Commons CC0

//commented by [email protected]

#include <Arduino.h>
#include <FastLED.h>
#include <colorutils.h>

enum XY_matrix_config {
  SERPENTINE = 1,
  ROWMAJOR = 2,
  FLIPMAJOR = 4,
  FLIPMINOR = 8
};

#define BRIGHTNESS        255  // maximum brightness is 255  on real leds this should probably be about 100
// the purpose of this application is to share the brightness between the
// four closest lights to the calculated position of each leaper
// this gives the feel of a display with many more pixels
// the technoligy is called anti-alliasing
#define LED_PIN           3
#define COLOR_ORDER       GRB
#define CHIPSET           WS2812B
#define kMatrixWidth      40
#define kMatrixHeight     12
#define XY_MATRIX (SERPENTINE | ROWMAJOR | FLIPMINOR)

#define MS_GOAL           10   // to try maintain 1000 / 10ms == 100 FPS / set to 1000 during testing
#define NUM_LEAPERS       8    // the number of leapers you are displaying
#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         0    // allow output to the serial console.  If 1 allow if 0 stops all output
#define TEST_UI           0    // allow output to the serial console for testing

#define NUM_LEDS ((kMatrixWidth) * (kMatrixHeight))

///////////////////////////////////////////////////////////////////////

// further reading here on FastLED:
// https://fastled.io/docs/index.html#autotoc_md0

// reserve memory space for each physical pixel of the display

CRGB leds[NUM_LEDS + 1];  // 1 extra for XY() to use when out-of-bounds

// further reading here on FastLED:
// https://fastled.io/docs/group___predefined_palettes.html#gae6b55fb01ce77682c14e4f6989109d69

CRGBPalette16 currentPalette = RainbowColors_p;

uint32_t last_millis = 0;


// typedef struct creates a defines a structure with called LEAPER with four member variables
// x is the location of an object along the x axis, y is the location of an object along the y axis
// if x and y are negative or if they have values greater than the size of the display, then the object is not visible
// xd is the speed the object is traveling in the x direction, this can be a positive or negative number
// yd is the speed the object is traveling in the y direction, this can be a positive or negative number

typedef struct {
  int16_t x, y, xd, yd;
  // uint8_t state;
} Leaper;

// NOTE:  x, y, xd, yd are all 16 bit integers meaning you can place an object in any location on a grid of
// 65536 x 65536 pixels.  Obviously you will not have a display with that many lights.  This program
// works out where on you display of, for example, 48 x 12 lights your object sits.
// Your object will be somewhere between four actual lights. The program will work out how bright to light each of
// those four lights to reflect the location of the object in a grid of 256 x 256 between each of the four lights.
// So if the location of your object is nearer the top left hand light, it will light more of that light and less
// of the other four. The sum of brightness of the four lights will add up to the total brightness of the object you
// want to display. If the object location is slowly moved, the program will redistribute the total brightness
// between four new lights. This may be the same four lights or it might move into an area between adjacent lights
// or an entirely different set of four lights.

// Leaper leapers[NUM_LEAPERS]; creates an array to hold variable x,y,xd & yd for each object

Leaper leapers[NUM_LEAPERS];

// restart_leaper and move_leaper will not work unless declared in this extern "C" section
// I am still not clear why this should be commenting this out causes errors
// more googling required

extern "C" {
  void restart_leaper(Leaper * lpr);
  void move_leaper(Leaper * lpr);
}

void setup() {
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  // the code above tells the FastLED code what your display is made up of
  // It also resrves a memory location for each pixel in the display.
  FastLED.setCorrection(UncorrectedColor);          //optional but turns off any previously set color correction
  FastLED.setTemperature(UncorrectedTemperature);   //optional but turns of any previously set temperature correction
  FastLED.setDither(DISABLE_DITHER); // stops the lights flickering.  see this for explanation: https://github.com/FastLED/FastLED/wiki/FastLED-Temporal-Dithering
  FastLED.setBrightness(BRIGHTNESS); // set the maximum brightness of the whole display
  pinMode(LED_BUILTIN, OUTPUT);       // configure the pin used to send data as an output pin

  // if SERIAL_UI is set to 1 at the top of the definitions then turn on the serial output with a begin statement

  if (SERIAL_UI) {
    // Serial.begin(250000); // use this on a physical board.  All three lines will need to be uncommented
    //  while (!Serial) {
    //; // wait for serial port to connect. Needed for native USB
    //}
  }
  if (TEST_UI) {
    if (!SERIAL) {
      // Serial.begin(9600); // use this on a virtual board in the emulator
    }
  }
      Serial.begin(9600); // use this on a virtual board in the emulator

  // The for loop defines a starting x and a y location for each object and the direction and speed they will be traveling at.
  for (uint8_t lpr = 0; lpr < NUM_LEAPERS; lpr++) {
    restart_leaper(&leapers[lpr]); // for each object, run the restart_leaper code.
    // the restart_leaper code defines what direction and speed each object will travel in.
    // the two lines below define where each object will start from
    // read the comments by the restart_leaper function to undestrand more how information is handled.
    leapers[lpr].x = random8() * kMatrixWidth;
    leapers[lpr].y = random8() * kMatrixHeight;

    if (TEST_UI) {
      Serial.print("Setup leapers[lpr].x: ");
      Serial.print(leapers[lpr].x);
      Serial.print(", Setup leapers[lpr].y: ");
      Serial.print(leapers[lpr].y);
      Serial.print(", Setup lpr: ");
      Serial.print(lpr);
      Serial.print(", Setup &leapers[lpr]: ");
      Serial.println(uint32_t(&leapers[lpr]), HEX);
    }
  }


}

// the loop will run the code repeatedly for every frame of the animation
// it will clear the screen and draw each object in a new location to create the optical illusion of movement.
// if the program takes 10ms to create each screen it will create 100 screens or frames a second so your brain
// can't see the frames being drawn and you interpret the consecutive drawings as movement

void loop() {
  // for each frame, reset every pixel by clearing the screen

  FastLED.clear();

  // alternatively by uncommenting the line below and using fadeToBlack,
  // you can clear the display by a fraction each frame to create light trails
  // if you do uncomment this line you must comment out the FastLED.clear() above for th fading to work

  // fadeToBlackBy(leds, NUM_LEDS, 32);

  for (uint8_t lpr = 0; lpr < NUM_LEAPERS; lpr++) {
    // run the code to move the objects to their next location for each frame
    if (TEST_UI) {
      Serial.print("lpr: ");
      Serial.print(lpr);
      Serial.print(", &leapers[lpr]: ");
      Serial.println(uint32_t(&leapers[lpr]), HEX);
    }
    // Using information from my project "Understanding pointers in Arduino",  a pointer is first defined
    // and then pointed at the memory location of a variable
    // e.g.:
    // a variable is initiated for example:
    //     int i;
    // a value is assigned to i:
    //    i = 12;
    // a pointer is defined:
    //    int *p;
    // The pointer is 'pointed' at the memory location of variable i:
    //     p = &i;
    // The value of variable i is updated by updating the memory location of pointer p:
    //      *p = 15;
    //
    // Below however, the memory location of an array of type struct is sent to the move_leaper function.
    // At this stage, a pointer has not been declared; this occurs in the function itself further down:
    // 'void move_leaper(Leaper * lpr) {...}', which is declared as void and as such should not return
    // anything, however it also has an internal pointer defined as Type 'Leaper' which is a structure
    // defined with multiple variables. This means that the memory location sent to the function will
    // have its values updated directly.
    // By sending memory locations and using pointers in this way can hugely speed up processing.

    move_leaper(&leapers[lpr]);

    // FastLED functionality is beyond the scope of these comments
    // further reading here on FastLED:
    // https://fastled.io/docs/index.html#autotoc_md0
    // https://fastled.io/docs/struct_c_r_g_b.html

    // Apply a color to each moving object
    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;
  int16_t pause = MS_GOAL - frame_time;
  // if SERIAL_UI is set to 1 at the top of the definitions then output any delays caused in writing a frame
  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
  // random8 generates a randmo 8 bit number from 0 (00000000) to 255 (11111111)
  // This code creates a fast moving ball once in every 21.25 bounces or 4 out of every 85 times.
  // or alternatively 4.7% of the time.

  if (random8() < 12) {

    // '>>' and '<<' represent bit shifting which is a rapid form of division or multiplication.
    // '>> 1' divides by 2 but discards any fractions,  much like rounding down e.g. 3 >> 1 = 1
    // '<< 1' doubles a value but if your starting number is greater than 127 doubling starts
    // to fail as you will lose one or more significant numbers
    // e.g. 127 << 1 = is   1111 1111 binary and fits in 8 bits which is 127 * 2 = 254
    // but  128 << 1 = is 1 0000 0000 binary, the most significant number '1' is lost
    // so the result is 0 if using 8 bits
    // Please note this method is used as it is very fast.

    lpr->xd += lpr->xd << 1; // doubles the x velocity of some leapers
    lpr->yd += lpr->yd << 1; // doubles the y velocity of some leapers
  }

  // leap towards the centre of the screen
  if (lpr->x > (kMatrixWidth / 2 * 256)) {
    lpr->xd = -lpr->xd;
  }
}

void move_leaper(Leaper * lpr) {

  // outside this function, lpr was a number from 0 to NUM_LEAPER
  // 'lpr' inside the function now represents the memory location of

  // Serial.print("lpr: ");
  // Serial.print(uint32_t(lpr), HEX);
  // Serial.print(", x: ");
  // Serial.print(lpr->x);
  // Serial.print(", y: ");
  // Serial.print(lpr->y);
  // Serial.print(", xd: ");
  // Serial.print(lpr->xd);
  // Serial.print(", yd: ");
  // Serial.println(lpr->yd);

  // add the xd & xy velocities to the position variables x and y
  // this will be the new location of the objects
  lpr->x += lpr->xd;
  lpr->y += lpr->yd;

  // if the object is going out of frame redirect it
  // 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);
    }
  }

  // if the object is going off the sids of the frame redirect it back into frame
  // 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;
    }
  }

  // for every object apply some gravity to ydelta to slowly bring the ball back down to rest
  // gravity
  lpr->yd -= GRAVITY;

  // slow the objects down due to the drag caused by the atmosphere
  // viscosity,  done badly (according to sutaburosu)
  // 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
// The idea came from Xiaolin Wu.
void wu_pixel(uint32_t x, uint32_t y, CRGB * col) {
   
  // Serial.print("x: ");
  // Serial.print(x);
  // Serial.print(", y: ");
  // Serial.println(y);

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

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