/*
 * good old starfield
 * 
 * The way this one works is by first filling the screen with pixels at
 * random coordinates. Then at each frame we calculate the new position of
 * each pixel as if it was going away from the center of the screen. When a
 * pixel is going off screen, it is reassigned at a new random position.
 * To simulate a bit of depth, some pixels move twice as fast.
 * 
 * Limitations: all the pixels/stars coordinates are stored in float arrays.
 * Each float is 4 bytes in size (32 bits) and the Nano has 2 KB of SRAM.
 * For each pixel/star we need to store 4 floats: x, y, vx, vy. That's
 * 16 bytes per pixel/star, so we should be able to store quite few.
 * However, the SRAM is already used by other parts of the code, and TVout.
 * In practice with the current implementation, there's just enough free
 * memory left for 25 pixels/stars :(
 * 
 * See starfield51 for a modification that allows to have 51 stars :)
 */

#include <TVout.h>

TVout TV;

#define STARS 25

float x[STARS];     // x coordinates of all the stars
float y[STARS];     // y coordinates of all the stars
float vx[STARS];    // vx and vy are the increments that we add to x
float vy[STARS];    // and y at each frame to get the x,y position

/*
 * new_star(i) generates the x, y, vx, and vy values for star i
 * x is generated at random x-axis position around the centre
 * y is generated at random y-axis position around the centre
 * we calculate the distances dx and dy from the screen center ox,oy
 * (we cheat a bit to make sure no 0 are left in a division later on)
 * we use dx and dy to calculte the distance vl between the star
 * and the center, ie the magnitude of the vector (center, star)
 * we use the magnitude to normalize the vector, namely calculating 
 * the normalized components vx and vy
 */
void
new_star(uint8_t i)
{
  uint8_t ox = 60;
  uint8_t oy = 48;
  float dx, dy, vl;
   
  x[i] = 30 + random(60);
  y[i] = 24 + random(48);
  
  dx = x[i] - ox;
  dy = y[i] - oy;

  switch(int(dx))
    case 0: dx = 1;                        // CHEAT
  
  vl = sqrt(dx*dx + dy*dy);
  vx[i] = dx / vl;
  vy[i] = dy / vl;
}

/*
 * new_stars() creates as many stars as set by the STARS constant
 */
void
new_stars()
{
  for (uint8_t i = 0; i < STARS; i++)
    new_star(i);
}

/*
 * Nothing exciting here, we initialize the PRNG with some noise,
 * configure TV out, and generate coordinates for all stars.
 */
void
setup()
{
  randomSeed(analogRead(6));              // effective?
  TV.begin(NTSC,120,96);
  new_stars();
}

/*
 * update_stars() goes through all the x and y values and update
 * them in such a way that:
 * new x = current x + vx
 * new y = current y + vy
 * we also test if the new coordinates of the star are still in the
 * visible part of the screen, and if not, we call new_star() to
 * generate new x, y, vx, vy all over again
 * Note: fancy effect, the first 20 stars are updated with regular 
 * vx and vy values, while the last 5 are a updated with 2 times vx and vy, 
 * which means that these 5 last stars will move twice the speed.
 */
void
update_stars()
{
  for (uint8_t i = 0; i < 20; i++) {
      x[i] = x[i] + vx[i];
      y[i] = y[i] + vy[i];
    if (x[i] > 120 || x[i] < 0 || y[i] < 0 || y[i] > 96)
      new_star(i);
  }
  for (uint8_t i = 20; i < STARS; i++) {
      x[i] = x[i] + vx[i] * 2;
      y[i] = y[i] + vy[i] * 2;
    if (x[i] > 120 || x[i] < 0 || y[i] < 0 || y[i] > 96)
      new_star(i);
  }
}

/*
 * draw_stars() walks through the x and y coordinates arrays and
 * uses the value to draw a pixel for each star
 */
void
draw_stars()
{
  for (uint8_t i = 0; i < STARS; i++)
    TV.set_pixel(x[i], y[i], 1);
}

/*
 * loop() is stuck doing the same stuff forever because it dared to trick
 * the gods...
 */
void
loop()
{
  TV.delay_frame(1);
  update_stars();
  TV.clear_screen();
  draw_stars();
}
