// BIRTHDAY PARTY
// guthrie
// party girl == kelley

// takes a while to download, sorry.

// "shift-click" (and drag) to duplicate
// "control-click" to delete
// "UP arrow" to change location
// "d" to go back to default arrangement

// characters are movable and are more active when closer to other characters

boolean dragging=false;
int count=0;                   // increments every frame, for animation speed
int prevMouse=-1;              // fixes double-clicking bug
int buffer=15;
int friendDistance=60;

int currentLocation=0;         // background
int frameLocation=0;

int numPersons=4;
int numCharacters=1;
int numActions=3;
int numFrames=2;
int numLocations=3;

Person[] person = new Person[buffer];

PImage[][][] right = new PImage[numCharacters][numActions][numFrames];              // [which character][which action][which frame]
PImage[][][] left = new PImage[numCharacters][numActions][numFrames];     
PImage[][] location = new PImage[numLocations][numFrames];

float mx = 0.0;
float my = 0.0;

void setup()
{
  framerate(30);

  size(600,300);
  colorMode(HSB,1);
  angleMode(DEGREES);
  
  loadImages();
  
  orig();
    
}


void draw()
{
  mouse();
  
  image(location[currentLocation][frameLocation],0,0);  // background image

  if(dragging)  // we are dragging someone
  {
    person[numPersons-1].drag();
  }
  
  for(int i=0; i<numPersons; i++)
  {
    closest();                  // it might make more sense to make this function a part of the person class
    person[i].update();
    person[i].draw();
    
  }
  
  frameLocation++;    // cycle through background frames
  if(frameLocation==numFrames)
    frameLocation=0;
  
  count++;  // master count
}

void mousePressed()
{
  boolean deleted=false;
    for(int i=numPersons-1; i>=0 && !dragging && !deleted; i--)  // only if we aren't dragging anyone...count backwards so top get priority
    {
      if(person[i].isInside(mouseX,mouseY))
      {
        if(keyPressed && keyCode==SHIFT && numPersons<buffer) // A NEW PERSON IS BORN
        {
          person[i].startDrag();
          person[i].duplicate();
          person[numPersons].startDrag();
          
          dragging=true;
          
          numPersons++;
          
        }
        else if(keyPressed && keyCode==CONTROL && numPersons>1 && (count-prevMouse)>4)  // A PERSON IS DELETED
        {
           prevMouse=count;
 
          if(i==numPersons-1)
          {
            person[i]=person[numPersons-1];
            numPersons--;
          }
          else
          {
            for(int j=i+1; j<numPersons; j++)
            {
              person[j-1]=person[j];  
            }
             
            deleted=true;
            numPersons--;

           }
          
        }
        else
        {
          if(numPersons-1!=i)      // if i isn't already on top or tobedragged
          {
          
            Person oldtop=person[numPersons-1];
            Person newtop=person[i];
            
            for(int j=i+1; j<numPersons-1; j++)
            {
              person[j-1]=person[j];     
           
            }
            
            person[numPersons-1]=newtop;
            person[numPersons-2]=oldtop;
          }
          
          dragging=true;
          person[numPersons-1].startDrag();
        }
      }
    }
}


void mouseReleased()
{
  dragging=false;
}

void keyPressed()
{

  if(keyCode==UP)
  {
    currentLocation++;
    if(currentLocation>=numLocations)
      currentLocation=0;
  }
  else if(key == 'd' || key == 'D')
  {
    orig();
  }

}

void orig()
{
  numPersons=3;

  person[1] = new Person(70,10,0,0,1,true,1.2);
  person[0] = new Person(-30,40,0,0,1,false,.4);
  person[2] = new Person(320,40,0,0,1,true,.001);
 // person[2] = new Person(250,0,0,0,1,false,.2);
    

}

void mouse() 
{
  float speed=4.0;
  
  float difY = mouseY - my;
  if(abs(difY) > 1.0) {
    my = my + difY/speed;
  }
  my = constrain(my, 0, height);
  
  float difX = mouseX - mx;
  if(abs(difX) > 1.0) {
    mx = mx + difX/speed;
  }
  mx = constrain(mx, 0, width);
}

void loadImages()
{
  for(int i=0; i<numCharacters; i++)
  {
    for(int j=0; j<numActions; j++)
    {
      for(int k=0; k<numFrames; k++)
      {
        right[i][j][k]=loadImage("right_" + i + "_" + j + "_" + k + ".gif");
        left[i][j][k]=reflectImage(right[i][j][k]);
      }
    }
  }
  
  for(int i=0; i<numLocations; i++)
  {
    for(int j=0; j<numFrames; j++)
    {
      location[i][j]=loadImage("location_" + i + "_" + j + ".gif");  
    }
  }
}

PImage reflectImage(PImage reflectMe)  // reflecting images with transparency == a pain
{
  PImage returnMe = new PImage(reflectMe.width,reflectMe.height);
  PImage alphaChannel = new PImage(reflectMe.width,reflectMe.height);
  
  for(int i=0; i<reflectMe.height; i++)
  {
    for(int j=0; j<reflectMe.width; j++)
    {
      if(reflectMe.get(j,i)==color(1,0,1,0))
      {
        returnMe.set(reflectMe.width-j-1,i,color(1,0,1,0));
        alphaChannel.set(reflectMe.width-j-1,i,color(1,1,0,1));
      }
      else
      {
        returnMe.set(reflectMe.width-j-1,i,reflectMe.get(j,i));
        alphaChannel.set(reflectMe.width-j-1,i,color(1,0,1,0));
      }
    }
  }
  
  alpha(returnMe,alphaChannel);
  
  return returnMe;

}

void closest()
{
// what about if there are only 2 or only 1 persons?

  if(numPersons==2)
  {
    person[0].setClosest(1);
    person[1].setClosest(0);
    
    if(person[0].distance(1)<friendDistance)
    {
      person[0].friendMode(true);
      person[1].friendMode(true);
    }
    else
    {
      person[0].friendMode(false);
      person[1].friendMode(false);
    }
        
  }
  else if(numPersons==1)
  {
    person[0].setClosest(-1);
    person[0].friendMode(false);
  }
  else
  {
    for(int i=numPersons-1; i>=0; i--)
    {
      int closestIndex = numPersons-1;
      
      if(i==closestIndex)
        closestIndex--;
      
      for(int j=numPersons-2; j>=0; j--)  // compare i to everyone else (the j's)
      {
        if(j!=i)
        {
          if(person[i].distance(j)<person[i].distance(closestIndex))
            closestIndex = j;
        }
      }

      person[i].setClosest(closestIndex);
      if(person[closestIndex].giveClosest()==i && person[i].distance(closestIndex)<friendDistance)
      {
        person[i].friendMode(true);
        person[closestIndex].friendMode(true);
      }
      else
        person[i].friendMode(false);

    }
  }
}



class Person
{
  // dont forget to pay attention to z-index, when something is clicked, etc
  int x,y;
  int dragX,dragY;
  
  int character;              // following 3 refer to PImage left and right
  int action;
  int frame;
  boolean isLeft;             // will face right if this is false, left if true
  float speed;
  
  int closest=-1;             // index of closest person
  boolean friend=false;
  
  Person(int myX, int myY, int myCharacter, int myAction, int myFrame, boolean myIsLeft, float mySpeed)
  { 
    character=myCharacter;
    action=myAction;
    frame=myFrame;
    isLeft=myIsLeft;
    speed=mySpeed;
    
    x=myX;
    y=constrain(myY,height-right[character][action][frame].height,height-20);
  }
  
  void draw()
  {

    if(isLeft)
      image(left[character][action][frame],x,y);
    else
      image(right[character][action][frame],x,y);
      
  }
  
  void update()
  {
  
    // messy code for alternating between blowers and regular action
    if(random(0,1)>.98)
    {
      if(action==1)
        action=0;
    }
    else if(random(0,1)>.99)
    {
      if(action==1)
      action=0;
      else if(action==0)
      action=1;
    }
    
    
    if(closest>=0)  // closest != -1 and therefore null
    {
      if(person[closest].giveCenterX()<giveCenterX())
        isLeft=true;
      else
        isLeft=false;
    }
    else
    {
      if(random(0,1)>.99)
        isLeft=!isLeft;
    
    }

    if(closest>=0)      // if its not null
      speed=distance(closest)/145.0;
    else                // if no defined closest, probably only person alive
      speed=2.2;
    
    if(count%random(2.0,2.6)>speed)
    frame++;
    
    if(frame==numFrames)
      frame=0;
  }
  
  boolean isInside(int myX, int myY)
  {
    if(myX>=x && myX<(x+right[character][action][frame].width) && myY>=y && myY<(y+right[character][action][frame].height))
    {
      if(right[character][action][frame].get(myX-x,myY-y)!=color(1,0,1,0))
        return true;
    }
    return false;
  }
  
  void startDrag()
  {
    mx=mouseX;
    my=mouseY;
    dragX=mouseX-x;
    dragY=mouseY-y;
    
  }
  
  void drag()
  {
    x=constrain(int(mx)-dragX,20-right[character][action][frame].width,width-20);
    y=constrain(int(my)-dragY,height-right[character][action][frame].height,height-20);
  }
  
  void setClosest(int myClosest)
  {
    closest = myClosest;

  }
  
  int giveClosest()
  {
    return closest;
  }
  
  int giveCenterX()
  // WARNING: GIVES CENTER X
  {
    return (x+(right[character][action][frame].width/2));
  }
  
  int distance(int index)
  {
    return (abs(giveCenterX()-person[index].giveCenterX()));
  }
  
  void duplicate()
  {
    person[numPersons] = new Person(mouseX-dragX,mouseY-dragY,character,action,frame,false,.2);
  }
  
  void friendMode(boolean which)
  {
    if(!friend && which)
    {
      action=2;
    }
    else if(friend && !which)
    {
      action=0;
    }
    
    friend=which;  
    
  }
  
}
Project 2: Soft Comics
Using McCloud's graphic essay as a foundation for understanding comics, imagine how graphic storytelling can be extended in software. Create a responsive visual environment as a test of your ideas.