This code was written with the _ALPHA_ version of Processing
and it may not run correctly with the current vesion.


// The Internet Age
// or The Rights of Spring
// Reed Kram

// Created    October 2002
// Revised    November 2002

class particle {
  float x, y, z;
  float Vx, Vy, Vz;
  float Ax, Ay, Az;
  float mass, drag;

  particle(float theMass, float theDrag, float startX,
  float startY, float startZ) {
    x = startX;
    y = startY;
    z = startZ;
    Vx = 0.0f;
    Vy = 0.0f;
    Vz = 0.0f;
    Ax = 0.0f;
    Ay = 0.0f;
    Az = 0.0f;
    mass = theMass;
    drag = theDrag;
  }

  // iterate for a single force acting on the particle
  void iterate(float Fkx, float Fky, float Fkz) {
    float Fx, Fy, Fz;

    Fx = Fkx;
    Fy = Fky;
    Fz = Fkz;
    Ax=Fx/mass;
    Ay=Fy/mass;
    Az=Fz/mass;

    Vx += Ax;
    Vy += Ay;
    Vz += Az;
    Vx *= (1.0f-drag);
    Vy *= (1.0f-drag);
    Vz *= (1.0f-drag);

    x += Vx;
    y += Vy;
    z += Vz;
  }
}

class imageDownloader implements Runnable {
  Vector imagesToDownload;
  Vector inbox;
  Thread runner;
  BApplet parent;

  public imageDownloader(BApplet parent) {
    this.parent = parent;
    imagesToDownload = new Vector();
    inbox = new Vector();
    
    runner = new Thread(this);
  }

  public void downloadFile(String filename) {
    imagesToDownload.addElement(filename);
    if (!runner.isAlive()) {
      runner.start();
    }
  }
  
  public Vector fetchDownloadedImages() {
    Vector theInbox = (Vector) inbox.clone();
    if (inbox != null) {
      inbox.removeAllElements();
    }
    if (imagesToDownload.size() < 1 && runner.isAlive()) {
      runner.stop();  // done downloading files...
    }
    return theInbox;
  }
  
  public void run() {
    Toolkit tk = Toolkit.getDefaultToolkit();
    Image imageToDownload = null;
    while(true) {
      try {
        if ((imageToDownload != null) && ((tk.checkImage(imageToDownload, -1, -1, null) & ImageObserver.ALLBITS) != 0)) {  // the image has downloaded fully
          int imageWidth = imageToDownload.getWidth(null);
          for (int i=0; i<imageToDownload.getHeight(null) / imageWidth; i++) {
            Image newImage = parent.createImage(imageWidth, imageWidth);
            if (newImage == null) {
              System.out.println("unable to allocate offscreen graphics. need more memory.");
              break;
            }
            Graphics newImageGraphics = newImage.getGraphics();
            newImageGraphics.drawImage(imageToDownload, 0, 0, 16, 16, 0, i*imageWidth, 16, (i+1)*imageWidth -1, null);
            inbox.addElement(newImage);
          }
          imageToDownload = null;
        }
        Thread.sleep(200);
        if (imageToDownload == null && imagesToDownload.size() > 0) {
          String imageFilename = (String) imagesToDownload.elementAt(0);
          imageToDownload = tk.getImage(getClass().getResource(imageFilename));
          tk.prepareImage(imageToDownload, -1, -1, null);
          imagesToDownload.removeElementAt(0);
        }
      } catch (Exception e) {
        System.out.println("downloading image >>>>"+e.toString()); e.printStackTrace(System.out);
      }
    }
  }
}

String logo[] = {"**",
          "**",
          "**",
          "**  ** ** ** ****  ****** ***",
          "** **  *****  ** **   **  **",
          "****   **  ***** **   **  **",
          "** **  **   **  ** **   **  **",
          "**  ** **  ***** **   **  **"};

static int MAX_NUM_IMAGES = 300;
static int NUM_IMAGE_FILES = 16;
imageDownloader theDownloader;
Vector frameImages = new Vector();
static int frameImageWidth = 16;
static int frameImageHeight = 16;
particle pixelParticles[][] = new particle[frameImageWidth][frameImageHeight];

int framePixels[] = new int[frameImageWidth * frameImageHeight];
Image frameOffImage;
Graphics frameOffGraphics;
PixelGrabber framePixelGrabber;

int numImagesLoaded = 0;
int thisImageIndex = 0;
long lastFrameTime = 0;

particle cameraRotation = new particle(3, 0.7f, PI, 1.0f, 1.0f);
float cameraRotationDestination[] = { 0.0f, 0.0f, 0.0f };

long creditTextTimer;

void setup()
{
  size(300, 300);
  background(49, 81, 74);
  noStroke();
  colorMode(RGB, 100);

  for (int i=0; i<frameImageWidth; i++) {
    for (int j=0; j<frameImageHeight; j++) {
      pixelParticles[i][j] = new particle(4.0f, 0.8f, 0.0f, 0.0f, 0.0f);
    }
  }

  creditTextTimer = millis();
}

void mousePressed() {
  cameraRotationDestination[0] = (mouseX - width/2) / 2.0f * (PI/180.0f);
  cameraRotationDestination[1] = (mouseY - height/2) / 2.0f * (PI/180.0f);
  cameraRotationDestination[2] = 100;
}

void mouseDragged() {
  cameraRotationDestination[0] = (mouseX - width/2) / 2.0f * (PI/180.0f);
  cameraRotationDestination[1] = (mouseY - height/2) / 2.0f * (PI/180.0f);
  cameraRotationDestination[2] = 100;
}

void mouseReleased() {
  cameraRotationDestination[0] = 0;
  cameraRotationDestination[1] = 0;
  cameraRotationDestination[2] = 0;
}

void drawBoxTop(float x1, float x2,
                float y1, float y2,
                float z1, float z2) {
  beginShape(QUADS);
  vertex(x1, y2, z2); // draw front
  vertex(x1, y1, z2);
  vertex(x2, y1, z2);
  vertex(x2, y2, z2);
  vertex(x2, y2, z2); // draw right side
  vertex(x2, y1, z2);
  vertex(x2, y1, z1);
  vertex(x2, y2, z1);
  vertex(x2, y2, z1); // draw back
  vertex(x2, y1, z1);
  vertex(x1, y1, z1);
  vertex(x1, y2, z1);
  vertex(x1, y2, z1); // draw left side
  vertex(x1, y1, z1);
  vertex(x1, y1, z2);
  vertex(x1, y2, z2);
  endShape();
}

static Color
getPixelColor(int pixels[], int X, int Y, int w) {
  int pixel = pixels[Y*w + X];

  int alpha = (pixel >> 24) & 0xff;
  int red   = (pixel >> 16) & 0xff;
  int green = (pixel >>  8) & 0xff;
  int blue  = (pixel    ) & 0xff;

  return new Color(red, green, blue);
}

void drawLogo(int x, int y) {
  int c = color(100, 100, 100);
  for (int i=0; i<logo.length; i++) {
    String thisLine = logo[i];
    for (int j=max(0, -x); j<thisLine.length(); j++) {
      if (thisLine.charAt(j) != ' ') {
        setPixel(x + j, y + i, c);
      }
    }
  }
}

void loop()
{
  if (frameOffImage == null || frameOffGraphics == null) {
    theDownloader = new imageDownloader(this);
    for (int i=0; i<NUM_IMAGE_FILES; i++) {
      theDownloader.downloadFile("media/images"+i+".jpg");
    }
    
    frameOffImage = createImage(frameImageWidth, frameImageHeight);
    frameOffGraphics = frameOffImage.getGraphics();
    creditTextTimer = millis();
  }

  cameraRotation.iterate(cameraRotationDestination[0] - cameraRotation.x,
               cameraRotationDestination[1] - cameraRotation.y,
               cameraRotationDestination[2] - cameraRotation.z);

  long curTime = millis();

  Vector downloadedImages = theDownloader.fetchDownloadedImages();
  if (downloadedImages != null) {
    for (int i=0; i<downloadedImages.size(); i++) {
      frameImages.addElement(downloadedImages.elementAt(i));
    }
  }
  if (curTime - lastFrameTime > 450) {
    if ((thisImageIndex < MAX_NUM_IMAGES-1) && (frameImages.size() > thisImageIndex+1) && ((checkImage((Image) frameImages.elementAt(thisImageIndex+1), null) & ImageObserver.ALLBITS) != 0)) {
      thisImageIndex++;
    } else {
      thisImageIndex = 0;
    }
    lastFrameTime = curTime;
  }

  if (frameImages.size() < 1 || !((checkImage((Image) frameImages.elementAt(thisImageIndex), null) & ImageObserver.ALLBITS) != 0)) {
    return;
  }
     frameOffGraphics.drawImage((Image) frameImages.elementAt(thisImageIndex), 0, 0, null);

  framePixelGrabber = new PixelGrabber(frameOffImage, 0, 0, frameImageWidth, frameImageHeight, framePixels, 0, frameImageWidth);
  try {
    framePixelGrabber.grabPixels();
  } catch (InterruptedException e) {
    System.err.println("interrupted waiting for pixels!");
    return;
  } catch (Exception e) { System.out.println("grabbing pixels>>>>" + e.toString()); e.printStackTrace(System.out); }
  if ((framePixelGrabber.getStatus() & ImageObserver.ABORT) != 0) {
    System.err.println("image fetch aborted or errored");
    return;
  }

  for (int X = 0; X < frameImageWidth-1; X++) {
    for (int Y = 0; Y < frameImageHeight-1; Y++) {
      Color pixelColor = getPixelColor(framePixels, X, Y, frameImageWidth);
      pixelParticles[X][Y].iterate(
        pixelColor.getRed()*100/255 - pixelParticles[X][Y].x,
        pixelColor.getGreen()*100/255 - pixelParticles[X][Y].y,
        pixelColor.getBlue()*100/255 - pixelParticles[X][Y].z);
    }
  }

  push();

  translate(width/2, height/2, 0);

  rotateY( cameraRotation.x);
  rotateX(-cameraRotation.y);

  float xOffset = frameImageWidth/2.0f -1;
  float yOffset = frameImageHeight/2.0f -1;
  float scale = 4.0f;
  for (int X = 0; X < frameImageWidth-1; X++) {
    for (int Y = 0; Y < frameImageHeight-1; Y++) {
      float x = 3*(X - xOffset);
      float y = 3*(Y - yOffset);
      int pixelColor = color(pixelParticles[X][Y].x, pixelParticles[X][Y].y, pixelParticles[X][Y].z);
      float z = red(pixelColor)/6.0f;

      push();
      translate(scale*x, scale*y);
      fill(red(pixelColor), green(pixelColor), blue(pixelColor));
      drawBoxTop(-scale, scale,
             -scale, scale,
             -scale*z, scale*z);
      pop();
    }
  }
  pop();
  
  if (curTime - creditTextTimer <= 15000) {
    drawLogo(5, height-5 -8);
  } else if (curTime - creditTextTimer < 18000) {
      drawLogo((int)(5 - 60 * (curTime - creditTextTimer - 15000)/700.0), height-5 -8);
  }
}