// Pierre MARZIN
// Trying to learn Processing... First project, much inspired by "Mycelium" by Scloopy?
// http://marzinp.free.fr/applets

//GUI and Drag 'n drop: great libraries ! Thanks to Andreas Schlegel (http://www.sojamo.de/libraries/index.html)
import controlP5.*;
import sojamo.drop.*;
//GUI variables
SDrop drop;
public String mUrl;
ControlP5 controlP5;
Textlabel weightlabel;
Textlabel seuilmaxlabel;
Textlabel seuilminlabel;
Textlabel modeCouleurLabel;
Textlabel pInfo;
Textlabel rangelabel;
RadioButton radio ;
Button onOffButton;
Button soundButton;
Button resetButton;
Button limitButton;
//PGraphics pInfo;
String monInfo;
PFont f;
//I just wanted to play with sound a bit. 
/*sound import ddf.minim.*;
import ddf.minim.signals.*;
Minim minim;
AudioOutput out;
SineWave [] sine;
boolean useSound;*/

PImage source;      // Source image
//for each "worm", variables: position
//position
PVector [] p;
//speed
PVector [] v;
//aimed direction
PVector vise=new PVector();
//"seeking" vector
PVector deviation=new PVector();
//worm's length
int [] longueurVer;
//worms maxwidth
int []maxwidth;
//worms decreaserate
int [] decreaserate;
//like pixels[], to keep track of how many times a point is reached by a worm
int [] buffer;
boolean[] typeVer;
int [] limiteDown;
float [] seekerWeight;
//number of worms at the beginning
int nbePoints;
//width and length of the drawing area
int larg;
int haut;
int largI;
int hautI;
//brightness of the destination pixel
float briMax;
//minimum brightness threshold
public int seuilBrillanceMini;
//maximum brightness threshold
public int seuilBrillanceMaxi;
//location of the tested point in pixels[]
int locTest;
//around a point (worms position), how many pixels will we look at...
int amplitudeTest;
//constrain the aimed direction vector into an r radius circle
int r;
//constrain the acceleration vector into a devMax radius circle
float devMax;
//constrain the speed vector into a vMax radius circle
int vMax;
//not used:random factor
int hasard;
//stroke's weight (slider) or radius of ellipse used for drawing
public float myweight;
//draw or not (button onOffButton)
public boolean dessine;
//different drawing options
public int modeCouleur;
//fill color
int macouleur;
boolean limite;

//setup only sets up what won't change:GUI and window params
//I use initialiser() to set up what has to be initialised
//when you hit "ResetButton" and dessin() to set the drawing parameters
void setup() {
  larg=largI=900;
  haut=hautI=600;
  size(1000,750);
  //sound useSound=false;
  limite=false;
  //sound minim = new Minim(this);
  drop = new SDrop(this);
  f = loadFont("ArialUnicodeMS-12.vlw");
  source = loadImage("tiger.jpg");
  if(hautI*source.width>largI*source.height){
    larg=largI;
    haut=larg*source.height/source.width;
  }else{
    haut=hautI;
    larg=haut*source.width/source.height;
  }
source.resize(larg,haut);
source.loadPixels();
  //info text
  textFont(f);
  fill(0);
  //GUI
  controlP5 = new ControlP5(this);
  controlP5.setControlFont(new ControlFont(createFont("ArialUnicodeMS",10,true),12));

  // add radioButton for drawing modes
  radio = controlP5.addRadioButton("radioButton",110,haut+115);
  //radio.disableCollapse();
  radio.setColorForeground(color(120));
  radio.setColorLabel(color(0));
  radio.setItemsPerRow(4);
  radio.setSpacingColumn(100);
  radio.addItem("Normal",1);
  radio.addItem("Pic Colors",2);
  radio.addItem("Growing",3);
  radio.activate("Normal");

  //add switch button for limit to threshold
  limitButton= controlP5.addButton("short_strokes",0,250,haut+40,110,15);
  limitButton.setSwitch(true);
  //add onOffButton,sound and ResetButton Buttons
  onOffButton=controlP5.addButton("onOff",0,610,haut+40,50,15);
  onOffButton.setSwitch(true);
  //sound soundButton=controlP5.addButton("sound",0,680,610,50,15);
  //sound soundButton.setSwitch(true);
  resetButton=controlP5.addButton("Reset",0,750,haut+40,50,15);
  // add horizontal sliders and labels for stroke, threshold and drawing modes
  controlP5.addSlider("myweight",1,15,1,100,haut+40,100,15);
  Slider s2 = (Slider)controlP5.controller("myweight");
  s2.setLabel("");
  s2.setDecimalPrecision(0);
  s2.setNumberOfTickMarks(10);
  s2.setSliderMode(Slider.FIX);
  weightlabel = new Textlabel(controlP5,"Stroke width:",10,haut+40,10,10);
  weightlabel.setColorValue(0);
  
  Range range=controlP5.addRange("rangeController",0,255, 0,128, 100,haut+85,200,15);
  seuilBrillanceMaxi=128;
  seuilBrillanceMini=0;
  range.setDecimalPrecision(0);
  range.setCaptionLabel("");
  range.setSliderMode(Slider.FIX);
  seuilmaxlabel = new Textlabel( controlP5,"light",350,haut+80,10,10);
  seuilmaxlabel.setColorValue(0);
  seuilminlabel = new Textlabel(controlP5,"dark",10,haut+80,10,10);
  seuilminlabel.setColorValue(0);
  modeCouleurLabel = new Textlabel(controlP5,"Drawing modes:",10,haut+110,10,10);
  modeCouleurLabel.setColorValue(0);
  pInfo=new Textlabel( controlP5,"You can drag and drop a picture (not too big!) from your web or file browser...",10,haut+10,10,10);
  pInfo.setColorValue(0);
  rangelabel=new Textlabel(controlP5,"Range",180,haut+67,10,10);
  rangelabel.setColorValue(0);
  //frameRate(15);

  initialiser();
}

//launched after setup and any time you hit the ResetButton button
void initialiser() { 
  nbePoints=6;
  weightlabel.draw(this);
  seuilmaxlabel.draw(this);
  seuilminlabel.draw(this);
  modeCouleurLabel.draw(this);
  rangelabel.draw(this);
  pInfo.draw(this);
  //sound minim.stop();
  // get a line out from Minim, default bufferSize is 1024, default sample rate is 44100, bit depth is 16
  //sound out = minim.getLineOut(Minim.STEREO);
  //sound sine=new SineWave[nbePoints]; 
  fill( 255 );
  stroke( 255 );
  rect( 0, 0, larg,haut );
  onOffButton.setOn();
  buffer=new int[haut*larg];
  smooth();
  dessine=true;
  p=new PVector[nbePoints];
  v=new PVector[nbePoints];
  longueurVer=new int[nbePoints] ;
  seekerWeight=new float[nbePoints];
  limiteDown=new int[nbePoints] ;
  typeVer=new boolean[nbePoints];
  decreaserate=new int[nbePoints];
  maxwidth=new int[nbePoints];
  for(int i=0;i<nbePoints;i++) {
    v[i]=new PVector(3-random(6),3-random(6));
    longueurVer[i]=0;
    limiteDown[i]=0;
    decreaserate[i]=10+int(random(100));
    maxwidth[i]=1+int(random(20));
    seekerWeight[i]=random(myweight);
    if(random(4)<2){
      typeVer[i]=true;
    }else{
      typeVer[i]=false;
    }
    // create a sine wave Oscillator, set to 440 Hz, at 0.5 amplitude, sample rate from line out
    //sound sine[i] = new SineWave(200+200*i, 0.02, out.sampleRate());
    // set the portamento speed on the oscillator to 200 milliseconds
    //sound sine[i].portamento(200);
    // add the oscillator to the line out
    //sound out.addSignal(sine[i]);
    p[i]=new PVector(random(larg),random(haut));
    while((brightness(source.pixels[floor(p[i].x)+floor(p[i].y)*larg])>seuilBrillanceMaxi)||(brightness(source.pixels[floor(p[i].x)+floor(p[i].y)*larg])<seuilBrillanceMini))
    {
      p[i].x=int(random(larg));
      p[i].y=int(random(haut));
    }
  }
  //sound out.noSound();
}

void draw() {

  if(mUrl!=null) {
    source=null;
    source=loadImage(mUrl);
  if(hautI*source.width>largI*source.height){
    larg=largI;
    haut=larg*source.height/source.width;
  }else{
    haut=hautI;
    larg=haut*source.width/source.height;
  }
//  println(source.width+" "+source.height);
background(200);
source.resize(larg,haut);
source.loadPixels();
    initialiser();
    mUrl=null;
  }
  if(dessine) {
    //sound if (useSound) {out.sound();}
    dessin();
  }
  /*sound else {
    if (useSound) {out.noSound();}
  }*/
}
void dessin() {
  
  amplitudeTest=2;
  vise.x=0;
  vise.y=0;
  r=12;
  hasard=0;
  devMax=2;
  vMax=50;
  locTest=0;
  //pour chaque mobile
  for(int t=0;t<p.length;t++) {//X.length
    //on va tester les pixels autour du mobile p[t] en direction de la vitesse du mobile
    //calcul du barycentre des points testés pondérés de la brillance (vise.x, vise.y)
    vise.x=0;
    vise.y=0;
    deviation.x=0;
    deviation.y=0;
    for (int i=-amplitudeTest+floor(deviation.x);i<amplitudeTest+1+floor(deviation.x);i++) {///rdessin
      for (int j=-amplitudeTest+floor(deviation.y);j<amplitudeTest+floor(deviation.y)+1;j++) {
        locTest=floor(p[t].x)+i+(floor(p[t].y)+j)*larg; 
        //on vérifie que le point fait partie de l'image
        if ((p[t].x+i>0)&& (p[t].y+j>0)&&(p[t].x+i<larg-1)&&(p[t].y+j<haut-1)) {
          vise.x+=i*(255-brightness(source.pixels[locTest]));
          vise.y+=j*(255-brightness(source.pixels[locTest]));
        }
      }
    }
    //on contraint vise.x et vise.y dans un cercle de rayon r
    //qd r augmente, les mobiles tournent plus serrés

    if ((p[t].x>0)&& (p[t].y>0)&&(p[t].x<larg-1)&&(p[t].y<haut-1)) {
      //buffer est une copie vide de l'image. on l'augmente pour chaque point teste
      buffer[floor(p[t].x)+floor(p[t].y)*larg]++;
    }
    //si on est passé plus de 10 fois on demenage le point
    if(buffer[floor(p[t].x)+floor(p[t].y)*larg]>10) {
      //source.pixels[floor(p[t].x)+floor(p[t].y)*larg]=0;
      deplacePoint(t);
    } 

    //on compte les segments de chaque ver 
    longueurVer[t]++;
    /*//si c'est trop long on déménage
     if (longueurVer[t]>200){       
     longueurVer[t]=0;
     deplacePoint(t);}*/

    //coeur du comportement de seeker:
    vise.normalize();
    vise.mult(r);          
    v[t].add(new PVector(vise.x,vise.y)); //+hasard-random(2*hasard)  
    deviation=v[t].get();
    deviation.normalize();
    deviation.mult(devMax);
    v[t].normalize();
    v[t].mult(vMax);     
    p[t].x=p[t].x+deviation.x;
    p[t].y=p[t].y+deviation.y;

    //if (t==1){print(" x:"+p[t].x+" p[t].x1:"+(p[t].x-deviation.x/rdessin)+" y:"+p[t].y+" p[t].y2:"+(p[t].y-deviation.y/rdessin)+"   ");}
    //noLoop();
    if (((p[t].x>0)&& (p[t].y>0)&&(p[t].x<larg-1)&&(p[t].y<haut-1))&&((limite)&&((brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])<=seuilBrillanceMaxi)&&(brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])>=seuilBrillanceMini))))
    {if (limiteDown[t]!=0){limiteDown[t]-=2;}  
    }
    if (((p[t].x<0)|| (p[t].y<0)||(p[t].x>larg-1)||(p[t].y>haut-1))||((limite)&&((brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])>seuilBrillanceMaxi)||(brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])<seuilBrillanceMini))))
    {if (limiteDown[t]==0){limiteDown[t]=2;}
    limiteDown[t]+=2;//print(limiteDown[t]+" ");
      if (limiteDown[t]>=52/myweight){
      limiteDown[t]=0;deplacePoint(t);}
    }
    if ((p[t].x<0)|| (p[t].y<0)||(p[t].x>larg-1)||(p[t].y>haut-1)||((deviation.x==0)&&(deviation.y==0)))//||
    {limiteDown[t]=0;
      deplacePoint(t);
    }
    else {
      briMax=brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg]);
    }
    //if (brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])<seuilBrillanceMaxi)

    //strokeWeight(myweight);
    //strokeJoin(ROUND);
    //strokeCap(ROUND);
    if(modeCouleur==1) {
      macouleur=0;
    }//stroke(macouleur,15);
    if(modeCouleur==2) {
      macouleur=source.pixels[floor(p[t].x)+floor(p[t].y)*larg];
    }//stroke(macouleur,15);
    if(modeCouleur==3) {            
      float vert=200;//50+(100*(1-sin(longueurVer[t]/200)));
      float rouge=50*(1-cos(longueurVer[t]/300));
      float bleu=100*(1-sin(longueurVer[t]/100));//100-vert-rouge;

      macouleur=color(rouge,vert,bleu);
    }//stroke(macouleur,15);
    //int macouleur=source.pixels[floor(p[t].x)+floor(p[t].y)*larg];
    //stroke(color(0,green(macouleur),blue(macouleur)),15);//briMax
    //line(round(p[t].x-deviation.x),round(p[t].y-deviation.y),round(p[t].x),round(p[t].y));
    noStroke();
    float incr=seekerWeight[t]*(1-sin(float(longueurVer[t])/decreaserate[t]));
    fill(macouleur,-limiteDown[t]+300/incr);   
//print(    limiteDown[1]+" ");
    
    ellipse(p[t].x,p[t].y,incr,incr);
    //myweight+=0.001;
    //if(myweight>10)myweight=1;
   /*sound if (useSound) { 
      sine[t].setFreq(900-p[t].y);
      sine[t].setPan((p[t].x/512)-1);
    }*/

    //important: on cree un nouveau vers de temps en temps (on pourrait tester selon la brilance de la zone...)
    if (random(1)>1-(255-briMax)/80000) {//0.997
      p=(PVector[])append(p,new PVector(p[t].x,p[t].y));
      v=(PVector[])append(v,new PVector(v[t].x*(5+random(6)),v[t].x*(5+random(6))));
      /*sound sine=(SineWave[])append(sine,new SineWave(p[t].y, 0.02, out.sampleRate()));
      if (random(1)>0.998) {
        out.addSignal(sine[sine.length-1]);
      }*/
      longueurVer=append(longueurVer,0);
      seekerWeight=append(seekerWeight,random(myweight));
      limiteDown=append(limiteDown,0);
      decreaserate=append(decreaserate,10+int(random(100)));
    }
  }
}
void deplacePoint(int t) {
  p[t].x=int(random(larg));
  p[t].y=int(random(haut));
  while((brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])>seuilBrillanceMaxi)||(brightness(source.pixels[floor(p[t].x)+floor(p[t].y)*larg])<seuilBrillanceMini))
  {
    p[t].x=int(random(larg));
    p[t].y=int(random(haut));
  }
}
void dropEvent(DropEvent theDropEvent) {

  // si le drop est une image jpg
  // on charge l'adresse dans  mUrl.
  if(theDropEvent.isFile()) {
    mUrl = theDropEvent.filePath();    
  }
  if(theDropEvent.isURL()==true) {
    if (theDropEvent.url().trim().substring(theDropEvent.url().trim().length()-3,theDropEvent.url().trim().length())=="jpg") {
      mUrl = theDropEvent.url().trim();
    }
  }
}
public void rangeController(ControlEvent theControlEvent) {
  if(theControlEvent.isFrom("rangeController")) {
    // min and max values are stored in an array.
    // access this array with controller().arrayValue().
    // min is at index 0, max is at index 1.
    seuilBrillanceMini = int(theControlEvent.getController().getArrayValue(0));
    seuilBrillanceMaxi = int(theControlEvent.getController().getArrayValue(1));
  }
  
}

public void onOff() {
  if(dessine) {
    dessine=false;
    //sound if(useSound){soundButton.setOff();useSound=false;}    
  }
  else {
    dessine=true;
  }
}
public void short_strokes() {
  limite=!limite;
}
/*sound  public void sound() {
  if(useSound) {
    out.noSound();
    useSound=false;
  }
  else {
    out.sound();
    useSound=true;
  }
}*/
public void Reset() {
 /*sound if (useSound) { 
    out.close();
    out.clearSignals();
    minim.stop();
    super.stop();
  }*/
  initialiser();
}
void controlEvent(ControlEvent theEvent) { 
  if(theEvent.isGroup()) {
    modeCouleur = int(theEvent.group().value());
  }
  
}