import processing.core.*; 
import processing.xml.*; 

import ddf.minim.signals.*; 
import ddf.minim.*; 
import ddf.minim.analysis.*; 
import ddf.minim.effects.*; 
import processing.opengl.*; 
import mri.*; 
import javax.media.opengl.*; 
import java.nio.*; 

import mri.*; 
import mri.v3ds.*; 

import java.applet.*; 
import java.awt.*; 
import java.awt.image.*; 
import java.awt.event.*; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class coffeedemo extends PApplet {











V3dsScene beanscene;
V3dsScene pipescene;
PImage renderedCoffee;

Minim minim;
AudioPlayer player;
boolean soundsOn = true;

long startTime;
float drawCount = 0;
int screenHeight = 600;
int screenWidth = 800;

String shaderFileNameFirstPassF = "lightnormalfragmentshader.glsl";
String shaderFileNameFirstPassV = "lightnormalvertexshader.glsl";
String[] shaderFirstPassF = null;
String[] shaderFirstPassV = null;
int shaderProgramFirstPass = -1;

String shaderFileNameSecPassF = "blurhorizontalfragmentshader.glsl";
String shaderFileNameSecPassV = "blurhorizontalvertexshader.glsl";
//String shaderFileNameSecPassF = "bulkfragmentshader.glsl";
//String shaderFileNameSecPassV = "bulkvertexshader.glsl";
String[] shaderSecPassF = null;
String[] shaderSecPassV = null;
int shaderProgramSecPass = -1;

String shaderFileNameThirdPassF = "blurverticalfragmentshader.glsl";
String shaderFileNameThirdPassV = "blurverticalvertexshader.glsl";
String[] shaderThirdPassF = null;
String[] shaderThirdPassV = null;
int shaderProgramThirdPass = -1;

String shaderFileNameFourthPassF = "deferredlightingfragmentshader.glsl";
String shaderFileNameFourthPassV = "deferredlightingvertexshader.glsl";
String[] shaderFourthPassF = null;
String[] shaderFourthPassV = null;
int shaderProgramFourthPass = -1;

int[] textuId = new int[2];
int[] fboId = new int[2];

PImage[] messageImages1 = new PImage[4];
PImage[] messageImages2 = new PImage[4];
PImage[] messageImages3 = new PImage[4];
PImage[] messageImages4 = new PImage[4];

String coffeestain1FileName = "coffeestain.png";
String coffeestain10FileName = "coffeestainANANAS.png";
String coffeestain2FileName = "coffeestainARMADA.png";
String coffeestain13FileName = "coffeestainCATS.png";
String coffeestain8FileName = "coffeestainEVOFLASH.png";
String coffeestain12FileName = "coffeestainFARB.png";
String coffeestain3FileName = "coffeestainGRIN.png";
String coffeestain9FileName = "coffeestainMOPPI.png";
String coffeestain11FileName = "coffeestainNOICE.png";
String coffeestain6FileName = "coffeestainRECREATION.png";
String coffeestain7FileName = "coffeestainTAAT.png";
String coffeestain4FileName = "coffeestainTRAUMA.png";
String coffeestain5FileName = "coffeestainXZM.png";
String coffeestainBKGFileName = "stainbackground.png";
PImage coffeestain1;
PImage coffeestain2;
PImage coffeestain3;
PImage coffeestain4;
PImage coffeestain5;
PImage coffeestain6;
PImage coffeestain7;
PImage coffeestain8;
PImage coffeestain9;
PImage coffeestain10;
PImage coffeestain11;
PImage coffeestain12;
PImage coffeestain13;
PImage[] coffeestains;
PImage coffeestainBKG;
int stainPeriods = 0;
int insidePeriods = 0;
int stainInPhase = 0;
float scaleOfInside = 1;
float placeOfInside = 0;
V3dsScene mugScene;
V3dsScene insideScene;

final int particleAge = 100;
final int particleCount = 1200;
int spawnAngleMin = 0;
int spawnTopLeftY = 10;
float spawnAngleMax = 2*PI;
int spawnBottomRightY = screenHeight / 2 - 11;
PImage imgC;
PImage imgO;
PImage imgF;
PImage imgE;
String imgCFileName = "c.png";
String imgOFileName = "o.png";
String imgFFileName = "f.png";
String imgEFileName = "e.png";
int letterColor = color(255, 0, 0, 255);
int imageSizeScale = 7;
PImage imgBubble;
String imgBubbleFileName = "bubble.png";
float phaseMax = 0.98f;
float phase = 0;
int letterPeriods = 0;
int letterInPhase = 0;
PImage[] lettersImg;
class Particle{

  float a, y, startx, starty, endx, endy;
  boolean isForLetter = false;
  int w, h = 10;

  public void update(float t){
    if(a < 0 || y < 0 || y >= screenHeight){
      respawn(false);
    }
    a = (endx - startx) * t + startx;
    y = (endy - starty) * t + starty;
  }

  public void respawn(boolean nearLetter){
    w = h = (int)random(6, 10);
    if(nearLetter){
      a = startx = random(4, spawnAngleMax);
      y = starty = random(spawnTopLeftY, spawnBottomRightY);
    }
    else{
      a = startx = random(spawnAngleMin, spawnAngleMax);
      y = starty = random(spawnTopLeftY, spawnBottomRightY);
    }
  }
}
Particle[] particles = new Particle[particleCount];

final int START_X1 = 360;
final int START_Y1 = 190;
final int START_X2 = 480;
final int START_Y2 = 250;
PImage dotMorpherBKG;
class Dots
{
  int n;
  float[] x1, y1, x2, y2;
  public void init(PImage img, int startx, int starty, int endtopleftx, int endtoplefty, float imgscale)
  {
    float sx = startx;
    float sy = starty;
    img.loadPixels();
    n = 0;
    int o = 0;
    for (int y = 0; y < img.height; ++y)
    {
      for (int x = 0; x < img.width; ++x, ++o)
      {
        if ((img.pixels[o] & 0xffffff) == 0xffffff)
          ++n;
      }
    }
    o = 0;
    x1 = new float[n];
    y1 = new float[n];
    x2 = new float[n];
    y2 = new float[n];
    for (int a = 0; a < n; ++a)
    {
      x1[a] = sx;
      y1[a] = sy;
    }
    int px = 0;
    for (int x = 0; x < img.width; ++x)
    {
      for (int y = 0; y < img.height; ++y)
      {
        if ((img.pixels[y * img.width + x] & 0xffffff) == 0xffffff)
        {
          x2[px] = endtopleftx + x * imgscale;
          y2[px] = endtoplefty + y * imgscale;
          ++px;
        }
        ++o;
      }
    }
  }
};
Dots[] dots = new Dots[8];

public void setup(){

  size(screenWidth, screenHeight, OPENGL);

  renderedCoffee = loadImage("coffee-render.png");  
  initMessageQuake();
  initParticleBubbles();

  beanscene = new V3dsScene(this, "coffeebean.3DS");
  pipescene = new V3dsScene(this, "pipes.3DS");

  initCoffeeGreetz();
  initDotMorpher();

  initShaders();
  initMusic();

}

public void draw(){

  drawCount++;
  if(drawCount == 1){
    startTime = millis();  
  }

  perspective(PI/3, (float)screenWidth/(float)screenHeight, 0.1f, 1000);  

  float[] lightPos = new float[]{
    0.0f, 0.0f, 1.0f                                                            };
  float omniLighting = 0.0f;
  GL gl = ((PGraphicsOpenGL)g).beginGL();
  setLight(gl, lightPos, omniLighting);
  ((PGraphicsOpenGL)g).endGL();

  if(millis()-startTime <= 6300){
    background(255, 235, 210);
    perspective(PI/3, (float)screenWidth/(float)screenHeight, 1.0f, 1200); 
    camera(0.0f, 0.0f, 600.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    drawMessage(6000, startTime, messageImages1, "righttoleft");  
  }
  else if(millis()-startTime > 6300 && millis()-startTime <= 12600){
    background(255, 235, 210);
    perspective(PI/3, (float)screenWidth/(float)screenHeight, 1.0f, 1200); 
    camera(0.0f, 0.0f, 600.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    drawMessage(6000, startTime + 6000, messageImages2, "lefttoright");  
  }
  else if((millis()-startTime) >= 12600 && (millis()-startTime) <= 19500){   
    background(41, 14, 0);
    perspective(PI/3, (float)screenWidth/(float)screenHeight, 1.0f, 1200); 
    camera(0.0f, 0.0f, 600.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    drawMessage(6000, startTime + 12600, messageImages3, "lefttoright");  
  }
  else if((millis()-startTime) >= 19500 && (millis()-startTime) <= 26000){   
    background(41, 14, 0);
    perspective(PI/3, (float)screenWidth/(float)screenHeight, 1.0f, 1200); 
    camera(0.0f, 0.0f, 600.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    drawMessage(6000, startTime + 19500, messageImages4, "righttoleft");
    if((millis()-startTime) >= 25500){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 25500, 26000, true);
      ((PGraphicsOpenGL)g).endGL();
    }  
  }
  else if((millis()-startTime) >= 26000 && (millis()-startTime) <= 68600){   
    background(41, 14, 0);
    perspective(PI/3, (float)screenWidth/(float)screenHeight, 1.0f, 1200); 
    camera(width/2.0f, height/2.0f, 300, width/2.0f, height/2.0f, 0, 0, 1, 0)/*0.0, 0.0, 600.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0*/;
    drawParticleBubbles(40000, startTime + 26000);
    if((millis()-startTime) >= 26000 && (millis()-startTime) <= 27000){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 26000, 27000, false);      
      ((PGraphicsOpenGL)g).endGL();
    }
    if((millis()-startTime) >= 64000){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 64000, 68600, true);      
      ((PGraphicsOpenGL)g).endGL();
    }
  }
  else if((millis()-startTime) >= 68600 && (millis()-startTime) <= 71600){   
    gl = ((PGraphicsOpenGL)g).beginGL(); 

    shadersOn(gl);
    //background(164, 146, 122);
    background(0);
    camera(0.0f, 30.0f, 70.0f, 0.0f, 30.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    drawBeans(true);
    //    drawPipes(false, "back");
    shadersOff(gl);
    glDisables(gl);
    switchOrthogonal(gl);
    disableLighting(gl);
    renderShaderPass(gl, shaderProgramSecPass, fboId[1], textuId[0], true);
    renderShaderPass(gl, shaderProgramThirdPass, fboId[0], textuId[1], true);
    background(41, 14, 0);
    renderShaderPass(gl, shaderProgramFourthPass, -1, textuId[0], false);
    switchBackFromOrthogonal(gl);
    glEnables(gl);

    if((millis()-startTime) >= 68600 && (millis()-startTime) <= 70000){     
      drawFade(gl, (millis()-startTime), 68600, 70000, false);
    }	

    ((PGraphicsOpenGL)g).endGL();

  }
  else if((millis()-startTime) >= 71600 && (millis()-startTime) <= 73600){   
    gl = ((PGraphicsOpenGL)g).beginGL(); 

    shadersOn(gl);
    //background(164, 146, 122);
    background(0);
    camera(0.0f, 0.0f, 140.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    drawBeans(true);
    drawPipes(false, "back", 0);
    shadersOff(gl);
    glDisables(gl);
    switchOrthogonal(gl);
    disableLighting(gl);
    renderShaderPass(gl, shaderProgramSecPass, fboId[1], textuId[0], true);
    renderShaderPass(gl, shaderProgramThirdPass, fboId[0], textuId[1], true);
    background(41, 14, 0);
    renderShaderPass(gl, shaderProgramFourthPass, -1, textuId[0], false);
    switchBackFromOrthogonal(gl);
    glEnables(gl);	

    ((PGraphicsOpenGL)g).endGL();

  }
  else if((millis()-startTime) >= 73600 && (millis()-startTime) <= 93500){   
    gl = ((PGraphicsOpenGL)g).beginGL(); 

    shadersOn(gl);
    //background(164, 146, 122);
    background(0);
    camera(0.0f, 0.0f, 140.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
//    camera(0.0, 0.0, 280.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
    drawBeans(true);
    drawPipes(true, "back", sBoxStep(millis()-startTime, 73600, 74600));
    shadersOff(gl);
    glDisables(gl);
    switchOrthogonal(gl);
    disableLighting(gl);
    renderShaderPass(gl, shaderProgramSecPass, fboId[1], textuId[0], true);
    renderShaderPass(gl, shaderProgramThirdPass, fboId[0], textuId[1], true);
    //    background(164, 146, 122);
    background(41, 14, 0);
    renderShaderPass(gl, shaderProgramFourthPass, -1, textuId[0], false);
    switchBackFromOrthogonal(gl);
    glEnables(gl);

    if((millis()-startTime) >= 92500){     
      drawFade(gl, (millis()-startTime), 92500, 93500, true);
    }	

    ((PGraphicsOpenGL)g).endGL();

  }
  else if((millis()-startTime) >= 93500 && (millis()-startTime) <= 145400){   
    background(255, 235, 210);
    perspective(PI/3, 800.0f/600.0f, 0.1f, 1000); 
    camera(-120.0f, -200.0f, 150.0f, -120.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    scale(1, -1, 1);
    drawMug(51500, (millis()-startTime-93500), coffeestains);
    if((millis()-startTime) >= 93500 && (millis()-startTime) <= 95500){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 93500, 95500, false);      
      ((PGraphicsOpenGL)g).endGL();
    }
    if((millis()-startTime) >= 143400){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 143400, 145400, true);      
      ((PGraphicsOpenGL)g).endGL();
    }  
  }
  else if((millis()-startTime) >= 145400 && (millis()-startTime) <= 164000){   

    background(255, 235, 210);
    camera();  
    drawDotMorpher(startTime+145400, 18600, dots[0], dots[1]);

    if((millis()-startTime) >= 145400 && (millis()-startTime) <= 146400){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 145400, 146400, false);      
      ((PGraphicsOpenGL)g).endGL();
    }  
  }
  else if((millis()-startTime) >= 164000 && (millis()-startTime) <= 183000){   

    background(255, 235, 210);
    camera();  
    drawDotMorpher(startTime+164000, 19000, dots[6], dots[2]);
    
  }
  else if((millis()-startTime) >= 183000 && (millis()-startTime) <= 201000){   

    background(255, 235, 210);
    camera();  
    drawDotMorpher(startTime+183000, 18000, dots[5], dots[7]);
    
  }
  else if((millis()-startTime) >= 201000 && (millis()-startTime) <= 219700){   

    background(255, 235, 210);
    camera();  
    drawDotMorpher(startTime+201000, 18700, dots[3], dots[4]);

    if((millis()-startTime) >= 217700){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 217700, 219700, true);      
      ((PGraphicsOpenGL)g).endGL();
    }  
  }
  else if((millis()-startTime) >= 219700 && (millis()-startTime) <= 252000){   

    background(255, 235, 210);
    camera();  
    image(renderedCoffee, 0, 0);

    if((millis()-startTime) >= 219700 && (millis()-startTime) <= 222700){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 219700, 222700, false);      
      ((PGraphicsOpenGL)g).endGL();
    }
    if((millis()-startTime) >= 240000){
      gl = ((PGraphicsOpenGL)g).beginGL(); 
      drawFade(gl, (millis()-startTime), 240000, 252000, true);      
      ((PGraphicsOpenGL)g).endGL();
    }  
  }
  else if((millis()-startTime) > 252000){
    quit();
  }

}

public void initDotMorpher(){
  PImage i;
  for (int a = 0; a < dots.length; ++a){
    dots[a] = new Dots();
  }
  i = loadImage("cred-code.png");
  dots[0].init(i, START_X1, START_Y1, -100, 0, 2.0f);
  i = loadImage("cred-strana.png");
  dots[1].init(i, START_X2, START_Y2, 150, 400, 2.0f);
  i = loadImage("cred-tonic-spector.png");
  dots[2].init(i, START_X2, START_Y2, 150, 400, 2.0f);
  i = loadImage("cred-music.png");
  dots[3].init(i, START_X1, START_Y1, -100, 0, 2.0f);
  i = loadImage("cred-ghost.png");
  dots[4].init(i, START_X2, START_Y2, 150, 400, 2.0f);
  i = loadImage("cred-gfx.png");
  dots[5].init(i, START_X1, START_Y1, -100, 0, 2.0f);
  i = loadImage("cred-additional-code.png");
  dots[6].init(i, START_X1, START_Y1, -100, 0, 2.0f); 
  i = loadImage("cred-tero-strana.png");
  dots[7].init(i, START_X2, START_Y2, 150, 400, 2.0f); 
  
  dotMorpherBKG = loadImage("drinking_coffee.png");
}

public void drawDotMorpher(long startTime, long timeSpan, Dots credit, Dots name){
  GL gl = ((PGraphicsOpenGL)g).beginGL();
  randomSeed(0);
  int time = (int)(millis() - startTime);
  gl.glEnable(GL.GL_TEXTURE_2D);
  //  gl.glDisable(GL.GL_BLEND);
  ((PGraphicsOpenGL)g).endGL();
  image(dotMorpherBKG, 0, 0, 800, 600);
  gl = ((PGraphicsOpenGL)g).beginGL();
  disableLighting(gl);
  gl.glPointSize(8);
  gl.glDisable(GL.GL_TEXTURE_2D);
  gl.glDisable(GL.GL_DEPTH_TEST);
  gl.glEnable(GL.GL_BLEND);
//  gl.glBlendFunc(GL.GL_ZERO, GL.GL_SRC_COLOR);
  gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
  gl.glBegin(GL.GL_POINTS);
//  gl.glColor4f(0.9f, 0.85f, 0.8f, 1.0f);
  gl.glColor4f(41.f/255.f, 14.f/255.f, 0f, (1 - sSmoothStep(time, timeSpan - 1000, timeSpan))*0.2f);
//  gl.glColor4f(41.f/255.f, 14.f/255.f, 0.f, 1.f);
  Dots d = credit;
  for (int a = 0; a < d.n; ++a)
  {
    float ct = time * 0.001f + a * 0.001f + random(0, 0.1f);
    float t = sSmoothStep(ct, 2, 8);
    float t1 = 1 - t;
    float x = d.x1[a] * t1 + d.x2[a] * t;
    float y = d.y1[a] * t1 + d.y2[a] * t;
    float fxm = sBoxPulse(ct, 2, 5, 5, 8);
    float fxt = ct * 1.5f;
    x += fxm * cos(fxt * 2) * 50;
    y += fxm * sin(fxt) * 50;
    gl.glVertex2f(x, y);
  }
  d = name;
  for (int a = 0; a < d.n; ++a)
  {
    float ct = time * 0.001f + a * 0.001f + random(0, 0.5f);
    float t = sSmoothStep(ct, 6, 12);
    float t1 = 1 - t;
    float x = d.x1[a] * t1 + d.x2[a] * t;
    float y = d.y1[a] * t1 + d.y2[a] * t;
    float fxm = sBoxPulse(ct, 6, 9, 9, 12);
    float fxt = ct * 3.5f;
    x += fxm * cos(fxt) * 20;
    y += fxm * sin(fxt * 2) * 20;
    gl.glVertex2f(x, y);
  }
  gl.glEnd();
  gl.glEnable(GL.GL_TEXTURE_2D);
  gl.glColor4f(1.f, 1.f, 1.f, 1.f);
  gl.glBlendFunc(GL.GL_ZERO, GL.GL_SRC_COLOR);
  ((PGraphicsOpenGL)g).endGL();
}

public void drawFade(GL gl, long time, int startTime, int stopTime, boolean toBlack){
  shadersOff(gl);
  gl.glEnable(GL.GL_BLEND);
  gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
  glFadeDisables(gl);
  switchOrthogonal(gl);
  disableLighting(gl);
  if(toBlack){
    drawFadeQuad(gl, 0.0f, 0.0f, 0.0f, sBoxStep(time, startTime, stopTime));
  }
  else{
    drawFadeQuad(gl, 0.0f, 0.0f, 0.0f, 1 - sBoxStep(time, startTime, stopTime));
  }
  switchBackFromOrthogonal(gl);
  gl.glDisable(GL.GL_BLEND);
  //  gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
  glFadeEnables(gl);
  gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}

public void drawFadeQuad(GL gl, float r, float g, float b, float quadAlpha){
  // draw polygon to fit the screen
  gl.glBegin(GL.GL_QUADS);

  gl.glColor4f(r, g, b, quadAlpha);
  gl.glTexCoord2f(0, (float)screenHeight/1024);
  gl.glVertex2f(0,0);

  gl.glTexCoord2f(0,0);
  gl.glVertex2f(0, screenHeight);

  gl.glTexCoord2f((float)screenWidth/1024,0);
  gl.glVertex2f(screenWidth, screenHeight);

  gl.glTexCoord2f((float)screenWidth/1024, (float)screenHeight/1024);
  gl.glVertex2f(screenWidth, 0);

  gl.glEnd();
}

public void glFadeEnables(GL gl){
  gl.glEnable(GL.GL_DEPTH_TEST);
  gl.glEnable(GL.GL_TEXTURE_2D);
}

public void glFadeDisables(GL gl){
  gl.glDisable(GL.GL_TEXTURE_GEN_S);
  gl.glDisable(GL.GL_TEXTURE_GEN_T);
  gl.glDisable(GL.GL_TEXTURE_2D);
  gl.glDisable(GL.GL_DEPTH_TEST);    
}

public void initCoffeeGreetz(){
  coffeestains = new PImage[13];
  coffeestain1 = loadImage(coffeestain1FileName);
  coffeestains[0] = coffeestain1; 
  coffeestain2 = loadImage(coffeestain2FileName);
  coffeestains[1] = coffeestain2; 
  coffeestain3 = loadImage(coffeestain3FileName);
  coffeestains[2] = coffeestain3; 
  coffeestain4 = loadImage(coffeestain4FileName);
  coffeestains[3] = coffeestain4; 
  coffeestain5 = loadImage(coffeestain5FileName);
  coffeestains[4] = coffeestain5;
  coffeestain6 = loadImage(coffeestain6FileName);
  coffeestains[5] = coffeestain6;
  coffeestain7 = loadImage(coffeestain7FileName);
  coffeestains[6] = coffeestain7;
  coffeestain8 = loadImage(coffeestain8FileName);
  coffeestains[7] = coffeestain8;
  coffeestain9 = loadImage(coffeestain9FileName);
  coffeestains[8] = coffeestain9;
  coffeestain10 = loadImage(coffeestain10FileName);
  coffeestains[9] = coffeestain10;
  coffeestain11 = loadImage(coffeestain11FileName);
  coffeestains[10] = coffeestain11;
  coffeestain12 = loadImage(coffeestain12FileName);
  coffeestains[11] = coffeestain12;
  coffeestain13 = loadImage(coffeestain13FileName);
  coffeestains[12] = coffeestain13;

  coffeestainBKG = loadImage(coffeestainBKGFileName);  

  mugScene = new V3dsScene(this, "muki.3DS");
  mugScene.computeNormals(); 
  
  insideScene = new V3dsScene(this, "liemi.3DS");
  insideScene.computeNormals(); 
}

public void drawMug(int timeSpan, long startTime, PImage[] greetz){  

  float[] lightPos = new float[]{
    0.0f, 0.0f, 1.0f            };
  float omniLighting = 0.0f;
  GL gl = ((PGraphicsOpenGL)g).beginGL();
  setLight(gl, lightPos, omniLighting);
  ((PGraphicsOpenGL)g).endGL();

  int timePeriodPerStain = (int)(timeSpan/greetz.length);  
  float l = PI / timePeriodPerStain;
  double time = startTime*l;
  int period = (int)(time/PI);
  int periodHalf = (int)(time/PI + PI/2);

  boolean periodChanged = false;
  if(period != stainPeriods && stainInPhase < greetz.length - 1){
    ++stainInPhase;
    periodChanged = true;
    stainPeriods = period;    
  }
  if(periodHalf != insidePeriods && stainInPhase < greetz.length - 1){
    insidePeriods = periodHalf;
    scaleOfInside = scaleOfInside - (1.0f/greetz.length/3);
  }

  pushMatrix();
  gl = ((PGraphicsOpenGL)g).beginGL();
  gl.glDisable(GL.GL_LIGHTING);
  ((PGraphicsOpenGL)g).endGL();
  pushMatrix();
  scale(1, -1, 1);
  camera();
  image(coffeestainBKG, 0, 70);
  popMatrix();
  rotateX(PI/2);  
  image(greetz[stainInPhase], -190, 10, 90, 90);
  popMatrix();

  gl = ((PGraphicsOpenGL)g).beginGL();
  gl.glEnable(GL.GL_LIGHTING);
  ((PGraphicsOpenGL)g).endGL();
  randomSeed(7);
  rotateY(PI/6);
  float rotZ = -sSmoothPulse(startTime%timePeriodPerStain, 0, timePeriodPerStain/2-100, timePeriodPerStain/2, timePeriodPerStain)*PI/2;
  rotateZ(rotZ);  
  translate(-150, 0);

  mugScene.draw(); 
  scale(scaleOfInside);
  translate(0, 0 - ((1-scaleOfInside)*40));
  insideScene.draw();
  
}

public void initParticleBubbles(){
  imgBubble = loadImage(imgBubbleFileName);  

  imgC = loadImage(imgCFileName);  
  imgO = loadImage(imgOFileName);  
  imgF = loadImage(imgFFileName);  
  imgE = loadImage(imgEFileName);  

  lettersImg = new PImage[]{
    imgC, imgO, imgF, imgF, imgE, imgE                };

  int rows = imgO.pixels.length / imgO.width;
  int cols = imgO.pixels.length / imgO.height;
  int opaques = 0;
  for (int i = 0; i < imgO.pixels.length; i++){
    if(imgO.pixels[i] == letterColor){
      ++opaques;
    }
  } 

  for (int a = 0; a < particleCount; ++a)
  {
    particles[a] = new Particle();
    if(--opaques >= 0){
      particles[a].respawn(true);
    }
    else{
      particles[a].respawn(false);
    }
    particles[a].endx = particles[a].a;
    particles[a].endy = particles[a].y;
  }

  setLetterTarget(lettersImg[letterInPhase]);
}

public void drawParticleBubbles(int timeSpan, long startTime){

  int timePeriodPerLetter = (int)(timeSpan/lettersImg.length);  
  int timeSection = (int)(millis() - startTime);
  float l = PI / timePeriodPerLetter;

  double time = timeSection*l;
  int period = (int)(time/PI);

  boolean periodChanged = false;
  if(period != letterPeriods && letterInPhase < lettersImg.length - 1){
    ++letterInPhase;
    setLetterTarget(lettersImg[letterInPhase]);
    periodChanged = true;
    letterPeriods = period;
  }

  // update and draw particles
  stroke(164, 146, 122);
  for (int a = 0; a < particleCount; ++a)
  {
    Particle p = particles[a];
    float newPhase = abs(sin((float)time))*phaseMax;
    if(newPhase < phase && !periodChanged){
      phase = phaseMax;
    }
    else if(periodChanged && newPhase >= 0){
      p.startx = p.a;
      p.starty = p.y;
      phase = newPhase;
    }
    else{
      phase = newPhase;
    }
    p.update(phase);
    float x = cos(p.a + millis() * 0.0001f) * p.y;
    float y = sin(p.a + millis() * 0.0001f) * p.y * 0.7f;
    image(imgBubble, x + screenWidth/2, y + screenHeight/2, p.w, p.h);
  }
}

public void setLetterTarget(PImage img){
  int opaques = 0;
  for (int i = 0; i < img.pixels.length; i++){
    if(img.pixels[i] == letterColor){
      ++opaques;
    }
  }
  Particle[] tempA = new Particle[opaques];
  for(int o = opaques - 1; o >= 0; --o){        
    tempA[o] = particles[opaques - o];    
  }
  for(int o = 0; o < opaques; ++o){
    int r = (int)random(0, opaques);    
    Particle swap1 = tempA[r];    
    Particle swap2 = tempA[o];    
    tempA[r] = swap2;
    tempA[o] = swap1;
  }
  for(int p = 0; p < tempA.length; ++p){
    particles[p] = tempA[p];
  }
  for(int i = 0; i < particles.length -1; ++i){
    particles[i].startx = particles[i].endx;
    particles[i].starty = particles[i].endy;        
    if(particles[i].isForLetter){
      particles[i].endx = particles[i].endx + random(-1, 1);
      particles[i].endy = particles[i].endy + random(-25, 25);      
      particles[i].isForLetter = false;
    }
  }
  for(int p = 0; p < img.pixels.length; ++p){
    if(img.pixels[p] == letterColor && opaques >= 0){
      int o = opaques--;      
      particles[o].endx = 2*PI - (img.width * imageSizeScale * 0.0025f) - 1 + letterInPhase*0.2f + p / img.width * imageSizeScale * 0.0025f; //x = offset%w
      float kuvankoordeistaskaalattudistance = p % img.width * imageSizeScale - 5; //y = offset/w
      float sekalaisiaskaalaimia = screenHeight * 0.5f - 230;
      particles[o].endy = sekalaisiaskaalaimia + kuvankoordeistaskaalattudistance * 0.4f;
      particles[o].isForLetter = true;
    }
  }
}

public void initMessageQuake(){

  PImage img = loadImage("some1.png");
  messageImages1[0] = img;
  img = loadImage("like1.png");
  messageImages1[1] = img;
  img = loadImage("it1.png");
  messageImages1[2] = img;
  img = loadImage("hot1.png");
  messageImages1[3] = img; 

  img = loadImage("some2.png");
  messageImages2[0] = img;
  img = loadImage("like2.png");
  messageImages2[1] = img;
  img = loadImage("it2.png");
  messageImages2[2] = img;
  img = loadImage("black2.png");
  messageImages2[3] = img; 

  img = loadImage("some3.png");
  messageImages3[0] = img;
  img = loadImage("like3.png");
  messageImages3[1] = img;
  img = loadImage("it3.png");
  messageImages3[2] = img;
  img = loadImage("sweet3.png");
  messageImages3[3] = img;

  img = loadImage("some4.png");
  messageImages4[0] = img;
  img = loadImage("like4.png");
  messageImages4[1] = img;
  img = loadImage("it4.png");
  messageImages4[2] = img;
  img = loadImage("strong4.png");
  messageImages4[3] = img;

  for(int i = 0; i < messageImages1.length; i++){
    image(messageImages1[i], 0, 0);  
  }
  for(int i = 0; i < messageImages2.length; i++){
    image(messageImages2[i], 0, 0);  
  }
  for(int i = 0; i < messageImages3.length; i++){
    image(messageImages3[i], 0, 0);  
  }
  for(int i = 0; i < messageImages4.length; i++){
    image(messageImages4[i], 0, 0);  
  }
}

public void drawMessage(int timeSpan, long startTime, PImage[] images, String direction){

  int timeSection = (int)(millis()-startTime);  
  int imageInTurn = images.length * timeSection / timeSpan;
  if(imageInTurn >= images.length){
    imageInTurn = images.length - 1;  
  }

  int ts = timeSpan/images.length;

  int crP0 = -10;
  int crP3 = 1;
  
  if(direction.equals("lefttoright") && (millis()-startTime)%ts >= ts*0 && (millis()-startTime)%ts <= ts*0.1f){
    float x = random(0,(1-sSmoothStep((millis()-startTime)%ts,0, ts*0.1f))*10);
    float y = random(0,(1-sSmoothStep((millis()-startTime)%ts,0, ts*0.1f))*10);
    camera(x, y, (height/2.0f) / tan(PI*60.0f / 360.0f), x, y, 0, 0, 1, 0);
  }
  else{  
    camera(0, 0, (height/2.0f) / tan(PI*60.0f / 360.0f), 0, 0, 0, 0, 1, 0);
  }

  if(direction.equals("lefttoright")){
    crP0 = 1;
    crP3 = -10; 
    float x = -screenWidth*1.5f-70 + catmullRom(millis()-startTime, crP0, 0, 1, crP3, 0, timeSpan/4)*screenWidth*1.1f; 
    image(images[0], x, -screenHeight/2);

    x = -screenWidth*1.5f-70 + catmullRom(millis()-startTime, crP0, 0, 1, crP3, timeSpan/4, timeSpan/4*2)*screenWidth*1.1f;
    image(images[1], x, -screenHeight/2);

    x = -screenWidth*1.5f-70 + catmullRom(millis()-startTime, crP0, 0, 1, crP3, timeSpan/4*2, timeSpan/4*3)*screenWidth*1.1f;
    image(images[2], x, -screenHeight/2);

    x = -screenWidth*1.5f-70 + catmullRom(millis()-startTime, crP0, 0, 1, crP3, timeSpan/4*3, timeSpan)*screenWidth*1.1f;
    image(images[3], x, -screenHeight/2);
  }
  else{
    // right to left
    crP0 = -10;
    crP3 = 1; 
    float x = 400 - catmullRom(millis()-startTime, crP0, 0, 1, crP3, 0, timeSpan/4)*screenWidth; 
    image(images[0], x, -screenHeight/2);

    x = 400 - catmullRom(millis()-startTime, crP0, 0, 1, crP3, timeSpan/4, timeSpan/4*2)*screenWidth;
    image(images[1], x, -screenHeight/2);

    x = 400 - catmullRom(millis()-startTime, crP0, 0, 1, crP3, timeSpan/4*2, timeSpan/4*3)*screenWidth;
    image(images[2], x, -screenHeight/2);

    x = 400 - catmullRom(millis()-startTime, crP0, 0, 1, crP3, timeSpan/4*3, timeSpan)*screenWidth;
    image(images[3], x, -screenHeight/2);
  }

}

// p0 and p3 to modify
public float catmullRom(float t, float p0, float p1, float p2, float p3, float slopeStart, float slopeEnd)
{
  if(t < slopeStart)
    return 0;
  if(t >= slopeEnd)
    return 1;  

  t = (t - slopeStart) / (slopeEnd - slopeStart);     // normalize to [0..1]

  float retVal = 0.5f * (
  (2 * p1) + 
    (-p0 + p2) * t + 
    (2 * p0 - 5 * p1 + 4 * p2 - p3) * t * t + 
    (-p0 + 3 * p1 - 3 * p2 + p3) * t * t * t 
    );
  if(retVal < 0)
    retVal = abs(retVal);
  if(retVal > 1)
    retVal = 1 - (retVal - 1);
  return retVal;
}

public static float sClamp(float value, float min, float max)
{
  return value < min ? min : (value > max ? max : value);
}

public static float sStep(float value, float stepPosition)
{
  return (float)(value < stepPosition ? 0 : 1);
}

public static float sPulse(float value, float startPosition, float endPosition)
{
  return sStep(value, startPosition) - sStep(value, endPosition);
}

public static float sBoxStep(float value, float slopeStart, float slopeEnd)
{
  float diff = slopeEnd - slopeStart;
  if (diff == 0)
    return sStep(value, slopeStart);
  return sClamp((value - slopeStart) / diff, 0, 1);
}

public static float sBoxPulse(float value, float upSlopeStart, float upSlopeEnd,
float downSlopeStart, float downSlopeEnd)
{
  return sBoxStep(value, upSlopeStart, upSlopeEnd) -
    sBoxStep(value, downSlopeStart, downSlopeEnd);
}

public static float sSmoothPulse(float value, float upSlopeStart, float upSlopeEnd,
float downSlopeStart, float downSlopeEnd)
{
  return sSmoothStep(value, upSlopeStart, upSlopeEnd) -
    sSmoothStep(value, downSlopeStart, downSlopeEnd);
}

public static float sSmoothStep(float value, float slopeStart, float slopeEnd)
{
  float diff;
  if (value < slopeStart)
    return 0;
  if (value >= slopeEnd)
    return 1;
  diff = slopeEnd - slopeStart;
  if (diff == 0)
    return sStep(value, slopeStart);
  value = (value - slopeStart) / diff;     /* normalize to [0..1] */
  return value * value * (3 - 2 * value);
}

public void initMusic(){
  minim = new Minim(this);
  player = minim.loadFile("Second coffee nimbus.mp3");
  if(soundsOn){
    player.play();
  }
}

public void initShaders(){
  GL gl = ((PGraphicsOpenGL)g).beginGL();

  shaderFirstPassF = readShaderSource(shaderFileNameFirstPassF);
  shaderFirstPassV = readShaderSource(shaderFileNameFirstPassV);
  shaderProgramFirstPass = gl.glCreateProgram();
  setFragmentShader(gl, shaderFirstPassF, shaderProgramFirstPass);
  setVertexShader(gl, shaderFirstPassV, shaderProgramFirstPass);

  shaderSecPassF = readShaderSource(shaderFileNameSecPassF);
  shaderSecPassV = readShaderSource(shaderFileNameSecPassV);
  shaderProgramSecPass = gl.glCreateProgram();
  setFragmentShader(gl, shaderSecPassF, shaderProgramSecPass);
  setVertexShader(gl, shaderSecPassV, shaderProgramSecPass);

  shaderThirdPassF = readShaderSource(shaderFileNameThirdPassF);
  shaderThirdPassV = readShaderSource(shaderFileNameThirdPassV);
  shaderProgramThirdPass = gl.glCreateProgram();
  setFragmentShader(gl, shaderThirdPassF, shaderProgramThirdPass);
  setVertexShader(gl, shaderThirdPassV, shaderProgramThirdPass);

  shaderFourthPassF = readShaderSource(shaderFileNameFourthPassF);
  shaderFourthPassV = readShaderSource(shaderFileNameFourthPassV);
  shaderProgramFourthPass = gl.glCreateProgram();
  setFragmentShader(gl, shaderFourthPassF, shaderProgramFourthPass);
  setVertexShader(gl, shaderFourthPassV, shaderProgramFourthPass);

  initTexture(gl, textuId);
  initFBO(gl, fboId);

  ((PGraphicsOpenGL)g).endGL();
}


public void drawBeans(boolean rot){

  resetMatrix();

  scale(1, -1, 1); // rotate object upside up

  translate(-300, -240, 0); // move object row/cols starting point from origo
  randomSeed(7); // needed to keep rotation stabile
  for(int j=0; j<10; j++){ // rows of beans
    pushMatrix();
    for(int i=0; i<20; i++){ // cols of beans        
      pushMatrix();
      if(rot){
        rotateY((millis()*0.001f*random(0.3f, 1))+i*random(0, PI));
      }
      else{
        rotateY(random(0.3f, 1)+i*random(0, PI));
      }
      beanscene.draw();
      popMatrix();
      translate(50, 0);
    }
    popMatrix();
    translate(0, 60);    

  }

}

public void drawPipes(boolean move, String initPosition, float moveMultiplier){

  resetMatrix();

  translate(-300, -240, -10); // move object row/cols starting point from origo

  for(int j=0; j<10; j++){ // rows of beans
    pushMatrix();
    for(int i=0; i<20; i++){ // cols of beans        
      pushMatrix();
      if(move){
        float z = (sin(millis()*0.001f+i*0.1f) + cos(millis()*0.001f+j*0.2f)) * 20 * moveMultiplier;
        if(initPosition.equals("back") && moveMultiplier != 1){
          z = z - 20 * (1-moveMultiplier);
        }
        else if(initPosition.equals("front") && moveMultiplier != 1){
          z = z + 20 * (1-moveMultiplier);
        }
        else {
        }
        translate(0, 0, z);
      }
      else{
        if(initPosition.equals("back")){
          translate(0, 0, -20);
        }
        else if(initPosition.equals("front")){
          translate(0, 0, 20);
        }
        else {
        }
      }
      pipescene.draw();
      popMatrix();
      translate(50, 0);
    }
    popMatrix();
    translate(0, 60);    

  }

}

public void drawScreenQuad(GL gl){
  // draw polygon to fit the screen
  gl.glBegin(GL.GL_QUADS);

  gl.glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
  gl.glTexCoord2f(0, (float)screenHeight/1024);
  gl.glVertex2f(0,0);

  gl.glTexCoord2f(0,0);
  gl.glVertex2f(0, screenHeight);

  gl.glTexCoord2f((float)screenWidth/1024,0);
  gl.glVertex2f(screenWidth, screenHeight);

  gl.glTexCoord2f((float)screenWidth/1024, (float)screenHeight/1024);
  gl.glVertex2f(screenWidth, 0);

  gl.glEnd();
}

public void shadersOn(GL gl){
  gl.glUseProgram(shaderProgramFirstPass); // shaders on

  gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, fboId[0]);    
}

public void shadersOff(GL gl){
  gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0);

  gl.glUseProgram(0); // shaders off
}

public void switchOrthogonal(GL gl){
  // switch to orthogonal view
  gl.glMatrixMode(GL.GL_PROJECTION);
  gl.glPushMatrix();
  gl.glLoadIdentity();
  gl.glOrtho(0, screenWidth, screenHeight, 0, -1, 1);
  gl.glMatrixMode(GL.GL_MODELVIEW);
  gl.glPushMatrix();
  gl.glLoadIdentity();
}

public void switchBackFromOrthogonal(GL gl){
  // back to perspective view
  gl.glMatrixMode(GL.GL_PROJECTION);
  gl.glPopMatrix();
  gl.glMatrixMode(GL.GL_MODELVIEW);
  gl.glPopMatrix();
}

public void disableLighting(GL gl){
  gl.glDisable(GL.GL_CULL_FACE);
  gl.glDisable(GL.GL_LIGHTING);
}

public void glEnables(GL gl){
  gl.glEnable(GL.GL_DEPTH_TEST);
  gl.glDisable(GL.GL_TEXTURE_2D);
  //    gl.glDisable(GL.GL_BLEND);
  gl.glBindTexture(GL.GL_TEXTURE_2D,0);
}

public void glDisables(GL gl){
  gl.glDisable(GL.GL_TEXTURE_GEN_S);
  gl.glDisable(GL.GL_TEXTURE_GEN_T);
  gl.glEnable(GL.GL_TEXTURE_2D);
  //gl.glDepthMask(false);
  gl.glDisable(GL.GL_DEPTH_TEST);    
  //    gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
  //    gl.glEnable(GL.GL_BLEND);    
  gl.glBindTexture(GL.GL_TEXTURE_2D, textuId[0]);
}

public void renderShaderPass(GL gl, int shaderPass, int fboId, int textuId, boolean toFrameBuffer){
  gl.glUseProgram(shaderPass);
  if(toFrameBuffer){
    gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, fboId);
  }
  gl.glBindTexture(GL.GL_TEXTURE_2D, textuId);

  drawScreenQuad(gl);

  gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0);
  gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
  gl.glUseProgram(0);

}

public void initTexture(GL gl, int[] textuId){
  gl.glGenTextures(2, textuId, 0);
  gl.glBindTexture(GL.GL_TEXTURE_2D, textuId[0]);
  gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, 1024, 1024, 0,
  gl.GL_RGBA, GL.GL_UNSIGNED_BYTE, null);
  gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
  gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);

  gl.glBindTexture(GL.GL_TEXTURE_2D, textuId[1]);
  gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, 1024, 1024, 0,
  gl.GL_RGBA, GL.GL_UNSIGNED_BYTE, null);
  gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
  gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);

}

public void initFBO(GL gl, int[] FBO){    
  gl.glGenFramebuffersEXT(2, FBO, 0);
  gl.glBindTexture(GL.GL_TEXTURE_2D, textuId[0]);
  gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, FBO[0]);
  gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_COLOR_ATTACHMENT0_EXT, 
  GL.GL_TEXTURE_2D, textuId[0], 0);

  int[] depthId = new int[1];
  gl.glGenRenderbuffersEXT(1, depthId, 0);
  gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, depthId[0]);
  gl.glRenderbufferStorageEXT(GL.GL_RENDERBUFFER_EXT, GL.GL_DEPTH_COMPONENT24, 1024, 1024);
  gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_DEPTH_ATTACHMENT_EXT, 
  GL.GL_RENDERBUFFER_EXT, depthId[0]);

  gl.glBindTexture(GL.GL_TEXTURE_2D, textuId[1]);
  gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, FBO[1]);
  gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_COLOR_ATTACHMENT0_EXT, 
  GL.GL_TEXTURE_2D, textuId[1], 0);   

  depthId = new int[1];
  gl.glGenRenderbuffersEXT(1, depthId, 0);
  gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, depthId[0]);
  gl.glRenderbufferStorageEXT(GL.GL_RENDERBUFFER_EXT, GL.GL_DEPTH_COMPONENT24, 1024, 1024);
  gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_DEPTH_ATTACHMENT_EXT, 
  GL.GL_RENDERBUFFER_EXT, depthId[0]); 

  int fbostate = gl.glCheckFramebufferStatusEXT(gl.GL_FRAMEBUFFER_EXT);
  if(fbostate == GL.GL_FRAMEBUFFER_COMPLETE_EXT){
    println("framebuffer complete");    
  }
  else if(fbostate == GL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT){
    println("incomplete: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
  }
  else if(fbostate == GL.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT){
    println("incomplete: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
  }
  else if(fbostate == GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT){
    println("incomplete: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
  }
  else if(fbostate == GL.GL_FRAMEBUFFER_UNSUPPORTED_EXT){
    println("incomplete: GL_FRAMEBUFFER_INCOMPLETE_UNSUPPORTED");
  }
  gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0); // not yet

}

public void setFragmentShader(GL gl, String[] shaderSource, int createdShaderProgram){

  int f = gl.glCreateShader(GL.GL_FRAGMENT_SHADER);
  gl.glShaderSource(f, shaderSource.length, shaderSource, (int[])null, 0);
  gl.glCompileShader(f);  
  gl.glAttachShader(createdShaderProgram, f);
  gl.glLinkProgram(createdShaderProgram);
  gl.glValidateProgram(createdShaderProgram);

}

public void setVertexShader(GL gl, String[] shaderSource, int createdShaderProgram){

  int v = gl.glCreateShader(GL.GL_VERTEX_SHADER);
  gl.glShaderSource(v, shaderSource.length, shaderSource, (int[])null, 0);
  gl.glCompileShader(v);  
  gl.glAttachShader(createdShaderProgram, v);
  gl.glLinkProgram(createdShaderProgram);
  gl.glValidateProgram(createdShaderProgram);

}

public String[] readShaderSource(String dataFileName){

  String lines[] = loadStrings(dataFileName);
  println("there are " + lines.length + " lines");  
  return lines;

}

public void setLight(GL gl, float[] pos, float directionalLighting){

  float[] l_position = {
    pos[0], pos[1], pos[2], directionalLighting                                                          };
  float[] l_ambient = {
    0.0f, 0.0f, 0.0f, 1.0f                                                          };
  float[] l_diffuse = {
    1.0f, 1.0f, 1.0f, 1.0f                                                          };

  gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, l_position, 0);
  gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, l_diffuse, 0);
  gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, l_ambient, 0);
  gl.glEnable(GL.GL_LIGHT0);
  gl.glEnable(GL.GL_LIGHTING);

  gl.glEnable(GL.GL_NORMALIZE);  

}

public void quit(){

  player.close();
  minim.stop();
  exit();

}































  static public void main(String args[]) {
    PApplet.main(new String[] { "--present", "--bgcolor=#666666", "--stop-color=#cccccc", "coffeedemo" });
  }
}
