//Simple model of a Water system using particle systems for PCS November 2014
//By Z. Wood and K. Davis for PCS 5th grade
//
// Run the program and watch the water particles fill the sea - when you click the
// mouse over the water, the particles evaporate and become "clouds" when the clouds
// become dense enough, the particles rain down

//TODO for students - fix the program to draw the sun correctly!
//Draw the sun as two triangles (one upside down) and an ellipse- see the handout
//By default, your sun should be centered at (50, 50) - 
//The ellipse for the center of the sun, should be 80 pixels wide and the triangle
//should have edges approximately 100 pixels long
//you may turn on grid lines to help you plot the triangle vertices if you'd like

//fix this 
void drawSun() {
  fill(255, 255, 255);
  //delete this rectangle and draw a sun instead
  rect(10, 10, 80, 80);
}

boolean grid = false;

//Do not edit the program below this line
/*-------------------------------------------------------------------------------------*/

void drawGrid() {
  //the horizontal lines
  stroke(0, 0, 255);
  line(0, 50, width, 50);
  line(0, 100, width, 100);
  line(0, 150, width, 150);
  line(0, 200, width, 200);
  line(0, 250, width, 250);
  line(0, 300, width, 300);
  line(0, 350, width, 350);
  //the vertical lines
  stroke(255, 0, 255);
  line(50, 0, 50, height);
  line(100, 0, 100, height);
  line(150, 0, 150, height);
  line(200, 0, 200, height);
  line(250, 0, 250, height);
  line(300, 0, 300, height);
  line(350, 0, 350, height);
}

boolean waterCycle = false;

void setup() {
  size(800, 500);
  colorMode(RGB, 255, 255, 255, 100);
  numB = 8;
  bPerSys = 200;
  raining = false;
  bubbles = new PSys[numB];
  for (int i=0; i< numB; i++) {
    bubbles[i] = new PSys(bPerSys, new PVector(20+i*(width/numB), height, 0));
  }
  smooth();
  frame = 0;
  frameRate(40);
  sun = false;
  sunStrength = 80;
  waterHght = 5*height/6;
  cloudHght = height/7;
  rainFrame = 0;
}

void mousePressed() {
  sun = !sun;
  raining = false;
}

void draw() {
  int numInWater;

  background(#87FCEC);

  int inClouds = 0;
  for (int i=0; i< numB; i++) {
    bubbles[i].run();
    inClouds += bubbles[i].numInClouds();
    if (sun && waterCycle) {
      //make all the bubble near the sun evaporate
      bubbles[i].nearSun(mouseX, mouseY, sunStrength);
    }
  }
  percInClouds = (double)inClouds / ((double)bPerSys * numB);

  if (percInClouds > 0.55) {
    println("rain!");
    raining = true;
    sun = false;
  }
  frame++;
  if (sun) {
    pushMatrix();
    translate(mouseX-50, mouseY-50);
    drawSun();
    popMatrix();
  }
  
  if (grid) {
    drawGrid();
  }
  
}

PSys bubbles[];
int frame;
int numB;
int bPerSys;
double percInClouds;
boolean raining;
boolean sun;
int sunStrength;
int waterHght; 
int cloudHght;
int rainFrame;
final int inWater = 0;
final int inClouds = 1;
final int evaporate = 2;
final int precipitate = 3;

//define a particle
class Particle {
  PVector loc;
  PVector vel;
  PVector accel;
  float r;
  float life;
  color pcolor;
  int state;
  int white;
  color col;

  //constructor that uses specified color
  Particle(PVector start, color in_c) {
    accel = new PVector(0, -0.05, 0); //gravity
    vel= new PVector(random(-1, 1), random(-0.5, 0), 0);
    pcolor = in_c;
    loc = start.get();
    r = 20 +random(10);
    life = 200;
    state = inWater;
    white = 255;
  }

  //constructor that chooses a random color
  Particle(PVector start) {
    accel.set(0, 0, 0); //gravity
    vel.set(random(-2, 2), random(-2, 0), 0);
    pcolor = color(random(255), random(255), random(255));
    loc = start.get();
    r = 18.0;
    life = 200;
    state = inWater;
  }

  //what to do each frame
  void run() {
    updateP();
    renderP();
  }

  void stopDraw() {
    renderP();
  }

  //how to move
  void updateP() {

    //if in the clouds
    if (state == inClouds) {
      //if its raining - all particles in the cloud start to rain
      if (raining) {
        //add randomness so all particles don't rain down at once
        if (random(-5, 10) < 0) {
          state = precipitate;
          pcolor = color(107, 127, random(200, 250), random(56, 128));
          vel.set(0, random(1, 2), 0);
          accel.set(0, random(0.1, 0.2), 0);
        }
      }
      else { //stop moving and turn a color dependent on density in the clouds
        accel.set(0, 0.02, 0);
        vel.set(0, 0, 0);
        pcolor= color((float)white * (float)(1 - percInClouds), random(56, 128));
      }
    }
    if (state == precipitate) {
      //if we haven't reached the water, keep falling down
      if (loc.y <= waterHght) {
        if (r > 10) {
          r -= 0.05;
        }
        if (vel.y <= 0) {
          pcolor = color(random(80, 150), random(80, 150), random(200, 250), random(56, 128));
          vel.set(0, random(1, 2), 0);
          accel.set(0, random(0.1, 0.2), 0);
        }
      } 
      else { //turn into water
        state = inWater;
        r = 18.0;
      }
    }
    if (state == evaporate) {
      //add some randomness so all particles don't evaporate immediately
      if (random(-5, 8) < 0) {
        //if we haven't reached the clouds, keep going up
        if (loc.y > cloudHght) {
          pcolor = color(random(180, 190), random(180, 190), random(180, 250), 50);
          if (vel.y >= 0) {
            vel.set(0, random(-1, -2.5), 0);
            accel.set(0, random(-.05, -0.2), 0);
          }
        } 
        else { //turn into water
          state = inClouds;
        }
      }
    }
    if (state == inWater) {
      r = 18;
      //if the particle hasn't reached the water top (ie at spawn)
      if (loc.y > waterHght) {
        //keep moving upward
        if (loc.y > height && vel.y >= 0) { //bounce off bottom
          pcolor = color(12, 34, random(160, 200), random(56, 180));
          vel.set(0, random(-1, -0.1), 0);
          accel.set(0, random(-0.01, -0.02), 0);
        }
      } 
      else {      
        if (vel.y <= 0) {
          pcolor = color(12, 34, random(160, 200), random(56, 180));
          vel.set(0, random(1, 0.1), 0);
          accel.set(0, random(0.01, 0.2), 0);
        }
      }
    }
    vel.add(accel);
    loc.add(vel);
  }

  //how to draw a particle
  void renderP() {
    pushMatrix();
    ellipseMode(CENTER);
    stroke(pcolor-40);
    fill(pcolor);
    translate(loc.x, loc.y);
    ellipse(0, 0, r, r);
    popMatrix();
  }

  boolean alive() {
    if (life <= 0.0) {
      return false;
    } 
    else {
      return true;
    }
  }
} //end of particle object definition


//define a group of particles as a particleSys
class PSys {
  ArrayList particles; //all the particles
  PVector source; //where all the particles emit from
  color shade;
  int total;

  //constructor
  PSys(int num, PVector init_loc) {
    particles = new ArrayList();
    source = init_loc.get();
    shade = color(12, 34, random(160, 200), random(56, 180));

    //start with one particle
    particles.add(new Particle(source, shade));
    total = num;
  }

  //what to do each frame
  void run() {
    //go through backwards for deletes
    for (int i=particles.size()-1; i >=0; i--) {
      Particle p = (Particle) particles.get(i);
      //update each particle per frame
      p.run();
      if (!p.alive()) {
        particles.remove(i);
      }
    }
    if (particles.size() < total) {
      particles.add(new Particle(source, shade));
    }
  }

  void stopDraw() {
    for (int i=0; i < particles.size(); i++) {
      Particle p = (Particle) particles.get(i);
      p.stopDraw();
    }
  }


  //options for adding particles to the system - default
  void addParticle() {
    particles.add(new Particle(source));
  }

  //add at a specific point
  void addParticle(float x, float y) {
    particles.add(new Particle(new PVector(x, y)));
  }

  //add for an already defined particle
  void addParticle(Particle p) {
    particles.add(p);
  }

  //is particle still populated?
  boolean dead() {
    if (particles.isEmpty() ) {
      return true;
    } 
    else {
      return false;
    }
  }

  int numInClouds() {
    int total = 0;
    for (int i=0; i < particles.size(); i++) {
      Particle p = (Particle) particles.get(i);

      if (p.state == inClouds) {
        total++;
      }
    }
    return total;
  }

  //for ever particle - if its near the sun, set its state to evaporate
  int nearSun(int cx, int cy, int radius) {

    int total = 0;
    for (int i=0; i < particles.size(); i++) {
      Particle p = (Particle) particles.get(i);

      if (dist(p.loc.x, p.loc.y, cx, cy) < radius) {
        p.state = evaporate;
        total += 1;
      }
    }
    return total;
  }
}