Puff puffs[] = new Puff[42];
int npuffs;
Puff inflating;

int newtimeout = 42;
int newtimer = 0;

color bg;

boolean mouseInside = false;
boolean invertMode = false;

float mousexbuf[] = new float[8];
float mouseybuf[] = new float[8];

void setup()
{
  size(700,500,JAVA2D);
  
  bg = color(0,0,0);
  
  frameRate(30);
  smooth();
}

void draw()
{
  if(inflating==null) {
    if(newtimer==0) {
      if(npuffs<puffs.length) {
        puffs[npuffs++] = new Puff(20+random(width-40), height, 10, 6, (int)random(18,40));
      } else {
        removePuff(0);
        puffs[npuffs-1] = new Puff(20+random(width-40), height, 10, 6, (int)random(18,40));
      }
      inflating = puffs[npuffs-1];
      newtimer = newtimeout;
    } else {
      newtimer--;
    }
  } else {
    inflating.inflate(0.2, -1.0);
  }
  
  for(int i=npuffs; --i>=0;) {
    puffs[i].react(0.04, 0.2, 0.1, 0.05);
    
    // Push from the bottom of the screen
    if(puffs[i].segment==puffs[i].maxseg) for(int j=puffs[i].n; --j>=0;) puffs[i].v[j].avoid(puffs[i].v[j].x,height,height/2,0.01);
    
    // Avoid other Puffs
    for(int j=npuffs; --j>=0;) {
      if(j!=i) puffs[i].avoid(puffs[j], puffs[i].maxseg*2, 0.4);
    }
    
    // Remove if off the screen
    int noff = 0;
    for(int j=puffs[i].n; --j>=0;) if(puffs[i].v[j].y<0) noff++;
    if(noff==puffs[i].n) removePuff(i);
  }
  
  // Mouse
  if(mouseInside && get(mouseX,mouseY)==bg) for(int i=npuffs; --i>=0;) puffs[i].avoid(mouseX,mouseY,80,0.06);
  
  int bgcol, fgcol;
  if(mousePressed) {
    bgcol = color(255);
    fgcol = color(0);
  }
  else {
    bgcol = color(0);
    fgcol = color(255);
  }
  
  background(bgcol);
  
  for(int i=npuffs; --i>=0;) {
    puffs[i].update(0.75);
    puffs[i].contain(12,-200,width-12,height, 0.6);
  }
  
  if(invertMode) {
    stroke(fgcol); noFill();
    strokeWeight(4);
    for(int i=npuffs; --i>=0;) puffs[i].displayLoop();
  } else {
    fill(fgcol); noStroke();
    for(int i=npuffs; --i>=0;) puffs[i].displayPoly();
  }
  
  if(inflating!=null && inflating.segment==inflating.maxseg) inflating = null;
}

void removePuff(int i)
{
  if(i < npuffs-1) System.arraycopy(puffs,i+1,puffs,i,npuffs-1-i);
  npuffs--;
}

public void mousePressed()
{
  invertMode = !invertMode;
}

public void mouseEntered(MouseEvent e) { mouseInside = true; }
public void mouseExited(MouseEvent e) { mouseInside = false; }




// Helpers
//
float rotatedX(float v,float angle) { return v*cos(angle); }
float rotatedY(float v,float angle) { return v*sin(angle); }
float rotatedX(float x,float y,float angle) { return x*cos(angle) - y*sin(angle); }
float rotatedY(float x,float y,float angle) { return x*sin(angle) + y*cos(angle); }

float dot(float x1, float y1, float x2, float y2) { return x1*x2 + y1*y2; }

// generic linear interpolation
int mix(int a,int b, float f) { return (int)(a+(b-a)*f); }
float mix(float a, float b, float f) { return a+(b-a)*f; }

// blend 2 colours
int blend(int a, int b, float f) {
  return mix(a>>16,b>>16,f)<<16 | mix(a>>8&0xff,b>>8&0xff,f)<<8 | mix(a&0xff,b&0xff,f);
}
