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

import java.io.BufferedReader; 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.FileReader; 
import java.io.IOException; 
import java.util.ArrayList; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class CrittersServerProcessing extends PApplet {

public void setup() {
    size(1024, 768, P3D);
    frameRate(60);
    
    NORTH_WALL = 0      + (int)Seeker.size;
    SOUTH_WALL = 768    - (int)Seeker.size;
    EAST_WALL  = 1024   - (int)Seeker.size;
    WEST_WALL  = 0      + (int)Seeker.size;
    
    frame.setTitle("Critters");
    
    noStroke(); 
    colorMode(RGB, 1); 
    
    initializeGameObjects();
}

public void draw() {
    updateGame();
    
    background(0.5f);
    resetMatrix();
    
    camera(0.0f, 0.0f, 0.0f, // eyeX, eyeY, eyeZ
        viewport.forward.col[0], viewport.forward.col[1], viewport.forward.col[2], // centerX, centerY, centerZ
        viewport.up.col[0], viewport.up.col[1], viewport.up.col[2]); // upX, upY, upZ
    translate(-viewport.pos.col[0], -viewport.pos.col[1], -viewport.pos.col[2]);
    
    if (useLights) {
        setupLights();
    }
    
    drawScene();
}

public void setupLights() {
    //makeSpotLight(upLight, upColor, upDirection, upAngle, upConcentration, drawLights);
    makeSpotLight(downLight, downColor, downDirection, downAngle, downConcentration, drawLights);
    if (true) {
        makePointLight(new vec4(detractorX, -10.0f, detractorY), fearColor, drawLights);
    }
    ambientLight(.30f, .30f, .30f);
}

public void makePointLight(vec4 pos, vec4 lightColor, boolean drawSource) {
    pointLight(
        lightColor.col[RED], lightColor.col[GREEN], lightColor.col[BLUE], // Color
        pos.col[X], pos.col[Y], pos.col[Z] // Position
    ); 
    
    if (drawSource) {
        pushMatrix();
            translate(pos.col[X], pos.col[Y], pos.col[Z]);
            fill(.7f, .7f, .3f);
            sphere(2);
        popMatrix();
    }
}

public void makeSpotLight(
    vec4 pos, vec4 lightColor, vec4 dir, 
    float angle, float concentration, boolean drawSource) {
    
    spotLight(
        lightColor.col[RED], lightColor.col[GREEN], lightColor.col[BLUE],// Color
        pos.col[X], pos.col[Y], pos.col[Z],  // Position
        dir.col[X], dir.col[Y], dir.col[Z],  // Direction
        angle, concentration                 // Angle, concentration
    );
    
    if (drawSource) {
        pushMatrix();
            translate(pos.col[X], pos.col[Y], pos.col[Z]);
            fill(.3f, .7f, .7f);
            sphere(2);
        popMatrix();
    }
}

/* Point lights */
vec4 fearColor = new vec4( 0.8f,  0.0f,  0.0f);

/* Spot lights */
vec4  upLight = new vec4( 0, -1000, 0);
float upAngle = PI/2.0f;
float upConcentration = 2.0f;
vec4  upDirection = new vec4( 0, -1, 0);
vec4  upColor = new vec4( 1.0f,  1.0f,  1.0f);

vec4  downLight = new vec4( 512, -1000, 384);
float downAngle = PI/2.0f;
float downConcentration = 2.0f;
vec4  downDirection = new vec4( 0, 1, 0);
vec4  downColor = new vec4( 0.3f,  0.3f,  0.3f);

boolean useLights = false;
boolean drawLights = true;

public static final int RED     = 0;
public static final int GREEN   = 1;
public static final int BLUE    = 2;

public void updateGame() {
    int time = millis();
    
    pollMouse();
    
    for (Obstacle obstacle : obstacles) {
        obstacle.update(time);
    }
    
    for (Seeker seeker : seekers) {
        seeker.update(time);
    }
    
    if (moveForward ^ moveBackward) {
        vec4 v = new vec4(viewport.forward.col[0], 0.0f, viewport.forward.col[2], 0.0f);
        if (moveForward) {
            v.scale(moveSpeed);
        } else {
            v.scale(-moveSpeed);
        }
        viewport.pos.add(v);
    }

    if (strafeLeft ^ strafeRight) {
        vec4 v = new vec4(viewport.right.col[0], 0.0f, viewport.right.col[2], 0.0f);
        if (strafeLeft) {
            v.scale(-strafeSpeed);
        } else {
            v.scale(strafeSpeed);
        }
        viewport.pos.add(v);
    }

    if (flyUp ^ flyDown) {
        vec4 v = new vec4(0.0f, -1.0f, 0.0f, 0.0f);
        if (flyUp) {
            v.scale(flySpeed);
        } else {
            v.scale(-flySpeed);
        }
        viewport.pos.add(v);
    }
}

public void drawScene() {
    for (GameObject obj : objList) {
        pushMatrix();
            obj.draw();
        popMatrix();
    }
    
    //pushMatrix();
    //    translate(100, 0, 0);
    //    testModel.draw();
    //popMatrix();
    
    for (Obstacle obstacle : obstacles) {
        obstacle.draw();
    }
    
    for (Seeker seeker : seekers) {
        seeker.draw();
    }
    
    fill(0.0f, 1.0f, 0.0f);
    pushMatrix();
        translate(attractorX, 0.0f, attractorY);
        sphere(10);
    popMatrix();
    fill(1.0f, 0.0f, 0.0f);
    pushMatrix();
        translate(detractorX, 0.0f, detractorY);
        sphere(10);
    popMatrix();
    
    //drawAxis();
}

public void drawAxis() {
    // X-Axis (Red)
    stroke(1.0f, 0.0f, 0.0f);
    line(
          0, 0, 0,
        100, 0, 0
    );
    
    // Y-Axis (Green)
    stroke(0.0f, 1.0f, 0.0f);
    line(
        0,   0, 0,
        0, 100, 0
    );
    
    // Z-Axis (Blue)
    stroke(0.0f, 0.0f, 1.0f);
    line(
        0, 0,   0,
        0, 0, 100
    );
    
    noStroke();
}

public void initializeGameObjects() {
    viewport = new Viewport(width, height, playpenWidth/2.0f, -900.0f, playpenLength/2.0f);
    viewport.rotate(90, 0);
    
    targetRed = color(1.0f, 0.0f, 0.0f);
    targetGreen = color(0.0f, 1.0f, 0.0f);
    
    objList = new ArrayList<GameObject>();
    
    //cube = new Cube(0, 0, 0);
    //objList.add(cube);
    
    playpen = new BoundingBox(
        0.0f, 0.0f, 0.0f,
        playpenWidth, 0.0f, playpenLength,
        0.0f, playpenHeight, 0.0f
    );
    int w = color(1.0f, 1.0f, 1.0f);
    //playpen.cTop = color(0.0, 0.0, 0.0, 0.0);
    playpen.cBottom = w;
    //playpen.cLeft = w;
    //playpen.cRight = w;
    //playpen.cFront = w;
    //playpen.cBack = w;
    playpen.alphaColor = 1.0f;
    objList.add(playpen);
    
    testModel = new OBJModel("critters/bunny/", "bunny_critter_001.obj");
    testModel.drawBoundingBox = false;
    
    obstacles = new Obstacle[numObstacles];
    for (int i=0; i<numObstacles; i++) {
        float randomX = random(width);
        float randomY = random(height);
        
        obstacles[i] = new Obstacle(randomX, randomY, 50.0f+random(100));
    }
    
    seekers = new Seeker[numSeekers];
    for (int i=0; i<numSeekers; i++) {
        seekers[i] = new Seeker(random(width), random(height));
    }
}


public void mouseDragged() {
    //float mX =  -mouseX/viewport.w;
    //float mY =  mouseY/viewport.h;
    //float diffX = mX - viewport.mouseX;
    //float diffY = mY - viewport.mouseY;
    //float length = (float) Math.sqrt(diffX*diffX + diffY*diffY);
    //if (length < 0.001) {
    //    return;
    //}
    //
    //float axisX = (float) (diffY / length) * (invertY ? -1 : 1);
    //float axisY = (float) (diffX / length);
    //
    //viewport.rotate(axisX, axisY);
    //viewport.setMousePosition(mX, mY);
}

public void mousePressed() {
    //if (mouseButton == LEFT) {
    //    //print("Mouse Click - LEFT!\n");
    //    firstClick = false;
    //    viewport.setMousePosition(mouseX/viewport.w, mouseY/viewport.h);
    //}
}

float targetX;
float targetY;
float attractorX;
float attractorY;
float detractorX;
float detractorY;
int targetRed;
int targetGreen;
int targetColor = targetRed;
public void pollMouse() {
    if (mousePressed) {
        
        if (mouseButton == LEFT) {
            targetColor = targetGreen;
            attractorX = mouseX;
            attractorY = mouseY;
            
            if (seekers != null) {
                for (Seeker s : seekers) {
                    s.myAttractorX = attractorX;
                    s.myAttractorY = attractorY;
                    s.attractorSet = true;
                }
            }
        }
        
        if (mouseButton == RIGHT) {
            targetColor = targetRed;
            detractorX = mouseX;
            detractorY = mouseY;
            
            if (seekers != null) {
                for (Seeker s : seekers) {
                    s.myDetractorX = detractorX;
                    s.myDetractorY = detractorY;
                    s.detractorSet = true;
                }
            }
        }
    }
}

public void keyPressed() {
    switch(key) {
    case '0':
    case ')':
        viewport.printStatus();
        //print("" + (viewport.pos.col[X]) + ", " + (viewport.pos.col[Y]) + ", " + (viewport.pos.col[Z]) + "\n");
        break;
    case 'w':
    case 'W':
        moveForward = true;
        break;
    case 's':
    case 'S':
        moveBackward = true;
        break;
    case 'a':
    case 'A':
        strafeLeft = true;
        break;
    case 'd':
    case 'D':
        strafeRight = true;
        break;
    case 'q':
    case 'Q':
        flyUp = true;
        break;
    case 'e':
    case 'E':
        flyDown = true;
        break;
    case 'i':
    case 'I':
        if (invertYDebouncer) {
            invertY = !invertY;
            invertYDebouncer = false;
        }
        break;
    default:
        break;
    }
}

public void keyReleased() {
    switch(key) {
    case 'w':
    case 'W':
        moveForward = false;
        break;
    case 's':
    case 'S':
        moveBackward = false;
        break;
    case 'a':
    case 'A':
        strafeLeft = false;
        break;
    case 'd':
    case 'D':
        strafeRight = false;
        break;
    case 'q':
    case 'Q':
        flyUp = false;
        break;
    case 'e':
    case 'E':
        flyDown = false;
        break;
    case 'r':
    case 'R':
        viewport.reset();
        break;
    case 'i':
    case 'I':
        if (!invertYDebouncer) {
            invertYDebouncer = true;
        }
        break;
    default:
        break;
    }
}

Viewport viewport;
ArrayList<GameObject> objList;

Cube cube;
BoundingBox playpen;
OBJModel testModel;

float playpenWidth = 1024.0f;
float playpenHeight = -1000.0f;
float playpenLength = 768.0f;

Obstacle[] obstacles;
int numObstacles = 20;

Seeker[] seekers;
int numSeekers = 20;

int NORTH_WALL = 0;
int SOUTH_WALL = 768;
int EAST_WALL  = 1024;
int WEST_WALL  = 0;

boolean invertY = false;
boolean invertYDebouncer = false;
boolean firstClick = true;

float strafeSpeed = 3.0f;
float moveSpeed = 3.0f;
float flySpeed = 3.0f;

boolean strafeLeft   = false;
boolean strafeRight  = false;
boolean moveForward  = false;
boolean moveBackward = false;
boolean flyUp        = false;
boolean flyDown      = false;
/**
 * Useful for defining a box to surround something.
 */
public class BoundingBox extends GameObject {
    float maxX = 0.0f;
    float maxY = 0.0f;
    float maxZ = 0.0f;
    float minX = 0.0f;
    float minY = 0.0f;
    float minZ = 0.0f;
    
    int cBack = color(1.0f, 0.0f, 0.0f);
    int cFront = color(0.0f, 1.0f, 0.0f);
    int cRight = color(0.0f, 0.0f, 1.0f);
    int cLeft = color(1.0f, 1.0f, 0.0f);
    int cTop = color(0.0f, 1.0f, 1.0f);
    int cBottom = color(1.0f, 0.0f, 1.0f);
    float alphaColor = 0.25f;
    
    BoundingBox() {
        super(0.0f, 0.0f, 0.0f);
    }
    
    BoundingBox(float x, float y, float z) {
        super(x, y, z);
    }
    
    BoundingBox(float x, float y, float z,
        float maxX, float maxY, float maxZ,
        float minX, float minY, float minZ
    ) {
        super(x, y, z);
        this.maxX = maxX;
        this.maxY = maxY;
        this.maxZ = maxZ;
        this.minX = minX;
        this.minY = minY;
        this.minZ = minZ;
    }
    
    public void drawBack() {
        fill(cBack, alphaColor);
        beginShape();
            vertex(minX, minY, minZ);
            vertex(maxX, minY, minZ);
            vertex(maxX, maxY, minZ);
            vertex(minX, maxY, minZ);
        endShape();
    }
    
    public void drawFront() {
        fill(cFront, alphaColor);
        beginShape();
            vertex(minX, minY, maxZ);
            vertex(maxX, minY, maxZ);
            vertex(maxX, maxY, maxZ);
            vertex(minX, maxY, maxZ);
        endShape();
    }
    
    public void drawRight() {
        fill(cRight, alphaColor);
        beginShape();
            vertex(minX, minY, minZ);
            vertex(minX, minY, maxZ);
            vertex(minX, maxY, maxZ);
            vertex(minX, maxY, minZ);
        endShape();
    }
    
    public void drawLeft() {
        fill(cLeft, alphaColor);
        beginShape();
            vertex(maxX, minY, minZ);
            vertex(maxX, minY, maxZ);
            vertex(maxX, maxY, maxZ);
            vertex(maxX, maxY, minZ);
        endShape();
    }
    
    public void drawTop() {
        fill(cTop, alphaColor);
        beginShape();
            vertex(minX, minY, minZ);
            vertex(maxX, minY, minZ);
            vertex(maxX, minY, maxZ);
            vertex(minX, minY, maxZ);
        endShape();
    }
    
    public void drawBottom() {
        fill(cBottom, alphaColor);
        beginShape();
            vertex(minX, maxY, minZ);
            vertex(maxX, maxY, minZ);
            vertex(maxX, maxY, maxZ);
            vertex(minX, maxY, maxZ);
        endShape();
    }
    
    public void draw() {
        translate(pos.col[0], pos.col[1], pos.col[2]);
        drawBack();
        drawFront();
        drawLeft();
        drawRight();
        drawTop();
        drawBottom();
    }
}
public class Cube extends GameObject {
    Cube(float x, float y, float z) {
        super(x, y, z);
    }
    
    public void draw() {
        translate(pos.col[0], pos.col[1], pos.col[2]);
        
        scale(10);
        beginShape(QUADS);
            fill(0, 1, 1); vertex(-1,  1,  1);
            fill(1, 1, 1); vertex( 1,  1,  1);
            fill(1, 0, 1); vertex( 1, -1,  1);
            fill(0, 0, 1); vertex(-1, -1,  1);
            
            fill(1, 1, 1); vertex( 1,  1,  1);
            fill(1, 1, 0); vertex( 1,  1, -1);
            fill(1, 0, 0); vertex( 1, -1, -1);
            fill(1, 0, 1); vertex( 1, -1,  1);
            
            fill(1, 1, 0); vertex( 1,  1, -1);
            fill(0, 1, 0); vertex(-1,  1, -1);
            fill(0, 0, 0); vertex(-1, -1, -1);
            fill(1, 0, 0); vertex( 1, -1, -1);
            
            fill(0, 1, 0); vertex(-1,  1, -1);
            fill(0, 1, 1); vertex(-1,  1,  1);
            fill(0, 0, 1); vertex(-1, -1,  1);
            fill(0, 0, 0); vertex(-1, -1, -1);
            
            fill(0, 1, 0); vertex(-1,  1, -1);
            fill(1, 1, 0); vertex( 1,  1, -1);
            fill(1, 1, 1); vertex( 1,  1,  1);
            fill(0, 1, 1); vertex(-1,  1,  1);
            
            fill(0, 0, 0); vertex(-1, -1, -1);
            fill(1, 0, 0); vertex( 1, -1, -1);
            fill(1, 0, 1); vertex( 1, -1,  1);
            fill(0, 0, 1); vertex(-1, -1,  1);
        endShape();
    }
}
public abstract class GameObject {
    GameObject() {
        pos = new vec4(0, 0, 0, 1);
        tagged = false;
    }
    
    GameObject(float x, float y, float z) {
        pos = new vec4(x, y, z, 1);
    }
    
    GameObject(vec4 pos) {
        this.pos = pos;
    }
    
    public void draw() {
    }
    
    public int getMessage(int message, long time) {
        return 0;
    }
    
    public boolean isTagged() {
        return tagged;
    }
    
    boolean tagged;
    vec4 pos;
}
public static final int X = 0;
public static final int Y = 1;
public static final int Z = 2;
public static final int W = 3;

class vec4 {
    public vec4(float x, float y, float z) {
        this.col = new float[4];
        this.col[0] = x;
        this.col[1] = y;
        this.col[2] = z;
        this.col[3] = 0;
    }
    
    public vec4(float x, float y, float z, float w) {
        this.col = new float[4];
        this.col[0] = x;
        this.col[1] = y;
        this.col[2] = z;
        this.col[3] = w;
    }
    
    public vec4(double x, double y, double z, double w) {
        this.col = new float[4];
        this.col[0] = (float) x;
        this.col[1] = (float) y;
        this.col[2] = (float) z;
        this.col[3] = (float) w;
    }
    
    public vec4(vec4 v) {
        this.col = new float[4];
        this.col[0] = v.col[0];
        this.col[1] = v.col[1];
        this.col[2] = v.col[2];
        this.col[3] = v.col[3];
    }
    
    public void add(vec4 v) {
        this.col[0] += v.col[0];
        this.col[1] += v.col[1];
        this.col[2] += v.col[2];
        this.col[3] += v.col[3];
    }
    
    public void add(float x, float y, float z, float w) {
        this.col[0] += x;
        this.col[1] += y;
        this.col[2] += z;
        this.col[3] += w;
    }
    
    public void scale(float s) {
        this.col[0] *= s;
        this.col[1] *= s;
        this.col[2] *= s;
        this.col[3] *= s;
    }
    
    public vec4 normalize() {
        float len = this.length();
        return new vec4(col[0] / len, col[1] / len, col[2] / len, col[3] / len);
    }
    
    public float length() {
        return sqrt(dotProductV4(this, this));
    }
    
    public float[] col;
}

public vec4 addV3s(vec4 v1, vec4 v2) {
    vec4 v0 = new vec4(
        v1.col[X] + v2.col[X],
        v1.col[Y] + v2.col[Y],
        v1.col[Z] + v2.col[Z],
        0.0f
    );
    return v0;
}

public vec4 addV4s(vec4 v1, vec4 v2) {
    vec4 v0 = addV3s(v1, v2);
    v0.col[W] = v1.col[W] + v2.col[W];
    return v0;
}

public vec4 subV3s(vec4 v1, vec4 v2) {
    vec4 v0 = new vec4(
        v1.col[X] - v2.col[X],
        v1.col[Y] - v2.col[Y],
        v1.col[Z] - v2.col[Z],
        0.0f
    );
    return v0;
}

public vec4 subV4s(vec4 v1, vec4 v2) {
    vec4 v0 = subV3s(v1, v2);
    v0.col[W] = v1.col[W] - v2.col[W];
    return v0;
}

class mat4 {
    public mat4(vec4 v1, vec4 v2, vec4 v3, vec4 v4) {
        row = new vec4[4];
        this.row[0] = v1;
        this.row[1] = v2;
        this.row[2] = v3;
        this.row[3] = v4;
    }
    
    public mat4(float x, float y, float z, float w) {
        row = new vec4[4];
        this.row[0] = new vec4(x, 0, 0, 0);
        this.row[0] = new vec4(0, y, 0, 0);
        this.row[0] = new vec4(0, 0, z, 0);
        this.row[0] = new vec4(0, 0, 0, w);
    }
    
    public mat4(
        float v00, float v01, float v02, float v03,
        float v10, float v11, float v12, float v13,
        float v20, float v21, float v22, float v23,
        float v30, float v31, float v32, float v33) {
        
        row = new vec4[4];
        this.row[0] = new vec4( v00,  v01,  v02,  v03);
        this.row[0] = new vec4( v10,  v11,  v12,  v13);
        this.row[0] = new vec4( v20,  v21,  v22,  v23);
        this.row[0] = new vec4( v30,  v31,  v32,  v33);
    }
    
    public vec4 getColVec4(int c) {
        return new vec4(row[0].col[c], row[1].col[c], row[2].col[c], row[3].col[c]);
    }
    
    public vec4[] row;
}

public float dotProductV3(vec4 v1, vec4 v2) {
    return (v1.col[0]*v2.col[0] + v1.col[1]*v2.col[1] + v1.col[2]*v2.col[2]);
}

public float dotProductV4(vec4 v1, vec4 v2) {
    return (v1.col[0]*v2.col[0] + v1.col[1]*v2.col[1] + v1.col[2]*v2.col[2] + v1.col[3]*v2.col[3]);
}

public vec4 crossProduct(vec4 a, vec4 b) {
    return new vec4(
        a.col[1] * b.col[2] - a.col[2] * b.col[1], 
        a.col[2] * b.col[0] - a.col[0] * b.col[2], 
        a.col[0] * b.col[1] - a.col[1] * b.col[0]
    );
}

public vec4 matMult(mat4 m, vec4 v) {
    float value0 = dotProductV4(m.row[0], v);
    float value1 = dotProductV4(m.row[1], v);
    float value2 = dotProductV4(m.row[2], v);
    float value3 = dotProductV4(m.row[3], v);
    
    return new vec4(value0, value1, value2, value3);
}

public mat4 matMult(mat4 m1, mat4 m2) {
    vec4 col0 = m2.getColVec4(0);
    vec4 col1 = m2.getColVec4(1);
    vec4 col2 = m2.getColVec4(2);
    vec4 col3 = m2.getColVec4(3);
    
    vec4 row0 = matMult(m1, col0);
    vec4 row1 = matMult(m1, col1);
    vec4 row2 = matMult(m1, col2);
    vec4 row3 = matMult(m1, col3);
    
    return new mat4(row0, row1, row2, row3);
}

public mat4 makeRot3D(float x, float y, float z, float angleDeg) {
    double angleRad = angleDeg * PI / 180.0f;
    double c = Math.cos(angleRad);
    double s = Math.sin(angleRad);
    double t = 1.0f - c;
    
    return new mat4(
        new vec4(t * x * x + c, t * x * y - s * z, t * x * z + s * y, 0.0f), 
        new vec4(t * x * y + s * z, t * y * y + c, t * y * z - s * x, 0.0f), 
        new vec4(t * x * z - s * y, t * y * z + s * x, t * z * z + c, 0.0f), 
        new vec4(0.0f, 0.0f, 0.0f, 1.0f));
}

public mat4 makeRot3D(vec4 vec, float angleDeg) {
    double angleRad = angleDeg * PI / 180.0f;
    double c = Math.cos(angleRad);
    double s = Math.sin(angleRad);
    double t = 1.0f - c;
    
    vec4 v = vec.normalize();
    return new mat4(
        new vec4(t * v.col[0] * v.col[0] + c, t * v.col[0] * v.col[1] - s * v.col[2], t * v.col[0] * v.col[2] + s * v.col[1], 0.0f), 
        new vec4(t * v.col[0] * v.col[1] + s * v.col[2], t * v.col[1] * v.col[1] + c, t * v.col[1] * v.col[2] - s * v.col[0], 0.0f), 
        new vec4(t * v.col[0] * v.col[2] - s * v.col[1], t * v.col[1] * v.col[2] + s * v.col[0], t * v.col[2] * v.col[2] + c, 0.0f), 
        new vec4(0.0f, 0.0f, 0.0f, 1.0f));
}







/**
 * OBJLoader
 * Loads simple OBJ models. Written because I was tired of messing
 * with other peoples' wonky model loader code.
 *
 * Some code is commented out because it is specific to either Java 
 * or Processing, but not both.
 */
public class OBJModel {
    boolean loaded = false;
    
    int vCount = 0;
    int vtCount = 0;
    int vnCount = 0;
    int fCount = 0;
    
    float[]  v;
    float[] vt;
    float[] vn;
    Face[]   f;
    
    ArrayList<Material> materials = new ArrayList<Material>();
    
    BoundingBox boundingBox;
    boolean drawBoundingBox = false;
    
	OBJModel(String path, String filename) {
	    parseModelFile(path, filename);
	}
	
	public void parseModelFile(String path, String filename) {
	    //JAVA
	    //File file = new File(path+filename);
        //StringBuffer contents = new StringBuffer();
        //BufferedReader reader = null;
        //PROCESSING
        String[] reader=null;
        
        try {
            //JAVA
            //reader = new BufferedReader(new FileReader(file));
            //PROCESSING
            reader = loadStrings(path+filename);
            int si=0;
            
            String text = null;
            String[] tokens = null;
            boolean parsedMats = false;
            Material currentMaterial=null;
            
            ArrayList<Float>  vAL = new ArrayList<Float>();
            ArrayList<Float> vtAL = new ArrayList<Float>();
            ArrayList<Float> vnAL = new ArrayList<Float>();
            ArrayList<Face>   fAL = new ArrayList<Face>();
            
            float maxX = Float.NEGATIVE_INFINITY;
            float maxY = Float.NEGATIVE_INFINITY;
            float maxZ = Float.NEGATIVE_INFINITY;
            float minX = Float.POSITIVE_INFINITY;
            float minY = Float.POSITIVE_INFINITY;
            float minZ = Float.POSITIVE_INFINITY;
            
            while (si < reader.length && ((text = reader[si++]) != null)) {
                
                tokens = text.split("\\s");
                if (tokens[0].equals("v")) {
                    vCount++;
                    float tempX = Float.parseFloat(tokens[1]);
                    float tempY = Float.parseFloat(tokens[2]);
                    float tempZ = Float.parseFloat(tokens[3]);
                    
                    if (tempX < minX) minX = tempX;
                    else if (tempX > maxX) maxX = tempX;
                    
                    if (tempY < minY) minY = tempY;
                    else if (tempY > maxY) maxY = tempY;
                    
                    if (tempZ < minZ) minZ = tempZ;
                    else if (tempZ > maxZ) maxZ = tempZ;
                    
                    vAL.add(tempX);
                    vAL.add(tempY);
                    vAL.add(tempZ);
                } else if (tokens[0].equals("vt")) {
                    vtCount++;
                    vtAL.add(Float.parseFloat(tokens[1]));
                    vtAL.add(Float.parseFloat(tokens[2]));
                } else if (tokens[0].equals("vn")) {
                    vnCount++;
                    vnAL.add(Float.parseFloat(tokens[1]));
                    vnAL.add(Float.parseFloat(tokens[2]));
                    vnAL.add(Float.parseFloat(tokens[3]));
                } else if (tokens[0].equals("f")) {
                    fCount++;
                    Face face = new Face(tokens);
                    face.setMaterial(currentMaterial);
                    fAL.add(face);
                } else if (tokens[0].equals("mtllib")) {
                    parseMaterialsFile(path, tokens[1]);
                    parsedMats = true;
                } else if (tokens[0].equals("usemtl")) {
                    if (!parsedMats) {
                        //throw new Exception("Have not parsed materials file yet, can not assign mats to faces! BAILING OBJ-LOAD!");
                    	System.err.println("Have not parsed materials file yet, can not assign mats to faces! BAILING OBJ-LOAD!");
                    	return;
                    }
                    Material mat;
                    String name = tokens[1];
                    for (int i=0; i<materials.size(); i++) {
                        if ((mat=materials.get(i)).isNamed(name)) {
                            currentMaterial = mat;
                            break;
                        }
                    }
                } else if (tokens[0].equals("g")) {
                    //group
                } else if (tokens[0].equals("#")) {
                    continue; /* Skip comment line. */
                }
            }
            
            v = new float[vAL.size()];
            for (int i=0; i<vAL.size(); i++) {
                v[i] = vAL.get(i);
            }
            
            vt = new float[vtAL.size()];
            for (int i=0; i<vtAL.size(); i++) {
                vt[i] = vtAL.get(i);
            }
            
            vn = new float[vnAL.size()];
            for (int i=0; i<vnAL.size(); i++) {
                vn[i] = vnAL.get(i);
            }
            
            f = new Face[fAL.size()];
            for (int i=0; i<fAL.size(); i++) {
                f[i] = fAL.get(i);
            }
            
            boundingBox = new BoundingBox(
                0.0f, 0.0f, 0.0f,
                minX, minY, minZ,
                maxX, maxY, maxZ
            );
            
            loaded = true;
        //JAVA
        //} catch (FileNotFoundException e) {
        //    e.printStackTrace();
        //} catch (IOException e) {
        //    e.printStackTrace();
        //} finally {
        
        //PROCESSING
        } catch (Exception e) {
            println("Something wierd happened. :/");
        }
        
        // show file contents here
        //System.out.println(contents.toString());
    }
    
    public void parseMaterialsFile(String path, String filename) {
        //JAVA
        //File file = new File(path+filename);
        //StringBuffer contents = new StringBuffer();
        //BufferedReader reader = null;
        //PROCESSING
        String[] reader=null;
        
        try {
            //JAVA
            //reader = new BufferedReader(new FileReader(file));
            //PROCESSING
            reader = loadStrings(path+filename);
            int si=0;
            
            String text = null;
            String[] tokens = null;
            Material currentMaterial=null;
            
            while (si < reader.length && ((text = reader[si++]) != null)) {
                
                tokens = text.split("\\s");
                if (tokens[0].equals("newmtl")) {
                    currentMaterial = new Material(tokens[1]);
                    materials.add(currentMaterial);
                } else if (tokens[0].equals("Ka")) {
                    currentMaterial.ka[0] = Float.parseFloat(tokens[1]);
                    currentMaterial.ka[1] = Float.parseFloat(tokens[2]);
                    currentMaterial.ka[2] = Float.parseFloat(tokens[3]);
                } else if (tokens[0].equals("Kd")) {
                    currentMaterial.kd[0] = Float.parseFloat(tokens[1]);
                    currentMaterial.kd[1] = Float.parseFloat(tokens[2]);
                    currentMaterial.kd[2] = Float.parseFloat(tokens[3]);
                } else if (tokens[0].equals("Ks")) {
                    currentMaterial.ks[0] = Float.parseFloat(tokens[1]);
                    currentMaterial.ks[1] = Float.parseFloat(tokens[2]);
                    currentMaterial.ks[2] = Float.parseFloat(tokens[3]);
                } else if (tokens[0].equals("Ns")) {
                    currentMaterial.ns = Float.parseFloat(tokens[1]);
                } else if (tokens[0].equals("map_Kd")) {
                    currentMaterial.loadTexture(path, tokens[1]);
                } else if (tokens[0].equals("#")) {
                    continue;
                }
            }
        //PROCESSING
        } catch (Exception e) {
            println("Something wierd happened. :/");
        }
	}
    
	/**
	 * A set of points marking a polygon face.
	 */
    class Face {
        int[] points;
        int numPoints=0;
        Material m=null;
        
        Face(String[] s) {
            String[] nums = null;
            ArrayList<Integer> pointsAL = new ArrayList<Integer>();
            
            for (int i=1; i<s.length; i++, numPoints++) {
                nums = s[i].split("/");
                pointsAL.add(Integer.parseInt(nums[0]));
                pointsAL.add(Integer.parseInt(nums[1]));
                pointsAL.add(Integer.parseInt(nums[2]));
            }
            
            points = new int[pointsAL.size()];
            for (int i=0; i<pointsAL.size(); i++) {
                points[i] = pointsAL.get(i);
            }
        }
        
        public void draw() {
            if (m != null) {
                texture(m.tex);
                for (int i=0; i<points.length;) {
                    int vPoint = (points[i++]-1)*3;
                    int vtPoint = (points[i++]-1)*2;
                    int vnPoint = (points[i++]-1)*3;
                    vertex(v[vPoint], v[vPoint+1], v[vPoint+2], vt[vtPoint], vt[vtPoint+1]);
                }
            } else {
                for (int i=0; i<points.length;) {
                    int vPoint = (points[i++]-1)*3;
                    int vtPoint = (points[i++]-1)*2;
                    int vnPoint = (points[i++]-1)*3;
                    vertex(v[vPoint], v[vPoint+1], v[vPoint+2]);
                }
            }
        }
        
        public void setMaterial(Material m) {
            this.m = m;
        }
    }
    
    class Material {
        public String name;
        public float[] ka = new float[3];
        public float[] kd = new float[3];
        public float[] ks = new float[3];
        public float ns = 0.0f;
        
        public PImage tex;
        
        Material(String name) {
            this.name = name;
        }
        
        public void loadTexture(String path, String filename) {
            tex = loadImage(path+filename);
        }
        
        public boolean isNamed(String name) {
            return ((this.name != null) && (this.name.equals(name)));
        }
    }
    
    public void draw() {
        textureMode(NORMALIZED);
        for (int fi=0; fi<f.length; fi++) {
            beginShape();
                f[fi].draw();
            endShape();
        }
        
        if (drawBoundingBox) {
            boundingBox.draw();
        }
    }
}
int seekerIDs = 0;
class Seeker {
    public static final int SEEK_TARGET = 0;
    public static final int FLEE_TARGET = 1;
    public static final float size = 10.0f;
    public static final float halfSize = size/2.0f;
    int id = 0;
    int state = SEEK_TARGET;
    
    float x, y;
    float oldX, oldY;
    
    float myTargetX = 0.0f;
    float myTargetY = 0.0f;
    float myAttractorX = -1.0f;
    float myAttractorY = -1.0f;
    float myDetractorX = -1.0f;
    float myDetractorY = -1.0f;
    boolean attractorSet = false;
    boolean detractorSet = false;
    
    float speed = 10.0f;
    float angle = 0;
    
    float seekWeight =      1.0f;
    float obstacleWeight =  1.0f;
    float wanderWeight =    0.05f;
    float seekCap = 1.0f;
    float obstacleCap = 1.0f;
    float wanderCap = 1.0f;
    
    float obstacleMag = 1.0f; 
    float obstacleReach = 50.0f;
    
    float myDistanceToTarget = 0.0f;
    float curiosityDistance = 30.0f;
    float hideRadiusOffset = 5.0f;
    
    boolean DRAW_LINE_OF_SIGHT = false;
    
    public Seeker(float x, float y) {
        this.id = seekerIDs++;
        
        this.x = x;
        this.y = y;
        this.myTargetX = x;
        this.myTargetY = y;
        this.myAttractorX = x;
        this.myAttractorY = y;
        this.myDetractorX = x;
        this.myDetractorY = y;
    }
    
    public void draw() {
        //if (id == 0) {
        //    fill(0.0, 1.0, 0.0);
        //} else {
        //    fill(1.0, 1.0, 1.0);
        //}
        //pushMatrix();
        //    translate(x, y);
        //    rotate(angle + (PI/2.0));
        //    triangle(
        //        0.0,       -size, 
        //        -halfSize, size, 
        //        halfSize,  size);
        //popMatrix();
        
        pushMatrix();
            translate(x, 0.0f, y);
            scale(.25f);
            rotateY(-angle + (PI/2.0f));
            testModel.draw();
        popMatrix();
    }
    
    public void update(int time) {
        /* Call a specific behaviour here. */
        determineState();
        
        switch (state) {
        case SEEK_TARGET:
            behaviourSeekTargetAndAvoidObstacles(time);
            break;
        case FLEE_TARGET:
            behaviourHideBehindObstacles(time);
            break;
        }
        
        checkBounds();
    }
    
    public void determineState() {
        //determineByClosest();
        determineByLineOfSight();
    }
    
    /** 
     * Determine state based on whether or not the feared object has 
     * direct line of sight to us. 
     */
    public void determineByLineOfSight() {
        /* If neither flag set, seek self. */
        if (!(attractorSet || detractorSet)) {
            state = SEEK_TARGET;
            myTargetX = x;
            myTargetY = y;
            return;
        }
        
        /* If ONLY one flag is set, do that one! */
        if ((attractorSet ^ detractorSet) && !(attractorSet && detractorSet)) {
            if (attractorSet) {
                state = SEEK_TARGET;
                myTargetX = myAttractorX;
                myTargetY = myAttractorY;
            } else {
                state = FLEE_TARGET;
                myTargetX = myDetractorX;
                myTargetY = myDetractorY;
            }
            return;
        }
        
        /* Flee if detractor has line of sight to us. */
        float p1x = this.x;
        float p1y = this.y;
        float p2x = myDetractorX;
        float p2y = myDetractorY;
        float dx = p2x - p1x;
        float dy = p2y - p1y;
        float distSq = dx*dx + dy*dy;
        for (Obstacle ob : obstacles) {
            float p3x = ob.x;
            float p3y = ob.y;
            float radius = ob.halfSize;
            
            //if (((p3x > p1x) && (p3x > p2x)) ||
            //    ((p3x < p1x) && (p3x < p2x)) ||
            //    ((p3y > p1y) && (p3y > p2y)) ||
            //    ((p3y < p1y) && (p3y < p2y))
            //) {
            //    continue;
            //}
            
            if ((p3x-radius > p1x && p3x-radius > p2x) ||
                (p3x+radius < p1x && p3x+radius < p2x) ||
                (p3y-radius > p1y && p3y-radius > p2y) ||
                (p3y+radius < p1y && p3y+radius < p2y)
            ) {
                continue;
            }
            
            
            float a = distSq;
            float b = 2*( (p2x-p1x)*(p1x-p3x) + (p2y-p1y)*(p1y-p3y) );
            //float c = p3x*p3x + p3y*p3y - 2*(p3x*p1x + p3y*p1y) - (radius*radius);
            float c = ((p1x-p3x)*(p1x-p3x)) + ((p1y-p3y)*(p1y-p3y)) - (radius*radius);
            
            float discriminant = (b*b) - (4*a*c);
            
            if (id==0) println(
                "I= " + (discriminant >= 0.0f) + ", " +
                "p1=(" + p1x + "," + p1y + "), " +
                "p2=(" + p2x + "," + p2y + "), " +
                "p3=(" + p3x + "," + p3y + "), " +
                "disc=" + discriminant);
            
            /* If line of sight is blocked by any obstacle, 
             * seek target. */
            if (discriminant >= 0.0f) {
                if (DRAW_LINE_OF_SIGHT) {
                    stroke(0.0f, 1.0f, 0.0f);
                    strokeWeight(10);
                    line(p1x, -10.0f, p1y, p2x, -10.0f, p2y);
                    noStroke();
                }
                state = SEEK_TARGET;
                myTargetX = myAttractorX;
                myTargetY = myAttractorY;
                return;
            }
        }
        
        /* Line of sight not blocked, hide. */
        if (DRAW_LINE_OF_SIGHT) {
            stroke(1.0f, 0.0f, 0.0f);
            strokeWeight(10);
            line(p1x, -10.0f, p1y, p2x, -10.0f, p2y);
            noStroke();
        }
        state = FLEE_TARGET;
        myTargetX = myDetractorX;
        myTargetY = myDetractorY;
        return;
    }
    
    /** 
     * Determine state based on whether or not the feared object is
     * closer to us than the seek object.
     */
    public void determineByClosest() {
        /* If neither flag set, seek self. */
        if (!(attractorSet || detractorSet)) {
            state = SEEK_TARGET;
            myTargetX = x;
            myTargetY = y;
            return;
        }
        
        /* If ONLY one flag is set, do that one! */
        if ((attractorSet ^ detractorSet) && !(attractorSet && detractorSet)) {
            if (attractorSet) {
                state = SEEK_TARGET;
                myTargetX = myAttractorX;
                myTargetY = myAttractorY;
            } else {
                state = FLEE_TARGET;
                myTargetX = myDetractorX;
                myTargetY = myDetractorY;
            }
            return;
        }
        
        /* Find the distance to the attractor and detractor, 
         * act on the closest. Detractor has a weight factor. */
        float dxA = this.x - myAttractorX;
        float dyA = this.y - myAttractorY;
        float distSqA = (dxA*dxA + dyA*dyA);
        
        float dxD = this.x - myDetractorX;
        float dyD = this.y - myDetractorY;
        float distSqD = (dxD*dxD + dyD*dyD) * 0.5f;
        
        if (distSqA < distSqD) {
            state = SEEK_TARGET;
            myTargetX = myAttractorX;
            myTargetY = myAttractorY;
        } else {
            state = FLEE_TARGET;
            myTargetX = myDetractorX;
            myTargetY = myDetractorY;
        }
    }
    
    public void checkBounds() {
        if (x < WEST_WALL) {
            x = WEST_WALL;
        } else if (x > EAST_WALL) {
            x = EAST_WALL;
        }
        
        if (y < NORTH_WALL) {
            y = NORTH_WALL;
        } else if (y > SOUTH_WALL) {
            y = SOUTH_WALL;
        }
    }
    
    public void behaviourSeekTargetAndAvoidObstacles(int time) {
        float[] seekVec = seek(time, myTargetX, myTargetY);
        float[] obstacleVec = obstacleAvoidance(time);
        float[] wanderVec = wander(time);
        
        float[] dir = new float[] {
            seekVec[0]*seekWeight + ((obstacleVec != null) ? obstacleVec[0]*obstacleWeight : 0) + ((wanderVec != null) ? wanderVec[0]*wanderWeight : 0),
            seekVec[1]*seekWeight + ((obstacleVec != null) ? obstacleVec[1]*obstacleWeight : 0) + ((wanderVec != null) ? wanderVec[1]*wanderWeight : 0)
        };
        float denom = (float)Math.sqrt(
            seekVec[0]*seekVec[0] + seekVec[1]*seekVec[1]);
        if (denom == 0.0f) {
            seekVec[0] = 0.0f;
            seekVec[1] = 0.0f;
        } else {
            seekVec[0] = seekVec[0]/denom;
            seekVec[1] = seekVec[1]/denom;
        }
        
        oldX = this.x;
        oldY = this.y;
        this.x += dir[0]*speed;
        this.y += dir[1]*speed;
        
        angle = atan2(y-oldY, x-oldX);
    }
    
    public float[] seek(int time, float targetX, float targetY) {
        float[] dir = getDir(x, y, targetX, targetY);
        float dx = targetX-x;
        float dy = targetY-y;
        float distanceSquared = dx*dx + dy*dy;
        myDistanceToTarget = (float) Math.sqrt(distanceSquared);
        
        float mag = 0.0f;
        if (myDistanceToTarget > 5.0f) {
            float slowdownDistance = ((myDistanceToTarget-curiosityDistance)/100);
            mag = min(1.0f, abs(slowdownDistance));
        }
        
        dir[0] *= mag;
        dir[1] *= mag;
        return dir;
    };
    
    public float[] obstacleAvoidance(int time) {
        return avoidAll(time);
    }
    
    /**
     * Scales and sums repulsive forces of all obstacles.
     */
    public float[] avoidAll(int time) {
        if (obstacles.length == 0) {
            return null;
        }
        
        float[] sumVecs = new float[2];
        sumVecs[0] = 0.0f;
        sumVecs[1] = 0.0f;
        for (Obstacle ob : obstacles) {
            float dx = ob.x - x;
            float dy = ob.y - y;
            float lengthSquared = dx*dx + dy*dy;
            float distance = (float) Math.sqrt(lengthSquared);
            
            if (distance != 0.0f) {
                float mag = (ob.halfSize*ob.halfSize)/lengthSquared;
                sumVecs[0] -= min(1.0f, (dx/distance) * mag);
                sumVecs[1] -= min(1.0f, (dy/distance) * mag);
            }
        }
        
        for (Seeker s : seekers) {
            if (s == this) {
                continue;
            }
            float dx = s.x - x;
            float dy = s.y - y;
            float lengthSquared = dx*dx + dy*dy;
            float distance = (float) Math.sqrt(lengthSquared);
            
            if (distance != 0.0f) {
                float mag = (s.size*s.size)/lengthSquared;
                sumVecs[0] -= min(1.0f, (dx/distance) * mag);
                sumVecs[1] -= min(1.0f, (dy/distance) * mag);
            }
        }
        
        return sumVecs;
    }
    
    /** 
     * Calculates the repulsive force against the closest obstacle.
     * Avoidance force is strongest when radius of obstacle is 
     * equal to the distance between target and obstacle. 
     */
    public float[] avoidClosest(int time) {
        if (obstacles.length == 0) {
            return null;    
        }
        
        float closestLengthSquared = Float.POSITIVE_INFINITY;
        Obstacle closestOb = getClosestObstacle(this.x, this.y);
        if (closestOb == null) {
            return null;
        }
        
        float dx = closestOb.x - x;
        float dy = closestOb.y - y;
        float lengthSquared = dx*dx + dy*dy;
        float lengthToTarget = (float) Math.sqrt(lengthSquared);
        float lengthOfReach = closestOb.halfSize;
        float mag = max(0, (lengthOfReach/lengthToTarget)*obstacleMag);
        
        float[] dir = getDir(x, y, closestOb.x, closestOb.y);
        dir[0] *= -mag;
        dir[1] *= -mag;
        return dir;
    }
    
    public Obstacle getClosestObstacle(float x, float y) {
        float closestLengthSquared = Float.POSITIVE_INFINITY;
        Obstacle closestOb = null;
        for (Obstacle ob : obstacles) {
            float dx = ob.x - x;
            float dy = ob.y - y;
            float lengthSquared = dx*dx + dy*dy;
            if (lengthSquared < closestLengthSquared) {
                closestLengthSquared = lengthSquared;
                closestOb = ob;
            }
        }
        
        return closestOb;
    }
    
    public float[] wander(int time) {
        float[] dir;
        if (myDistanceToTarget > 3*curiosityDistance) {
            dir = getDir(x, y, random(width), random(height));
        } else {
            dir = null;
        }
        return dir;
    }
    
    /**
     * Returns the unit length vector from (x0, y0) to (x1, y1). 
     * If the length of the vector is 0, returns (0.0, 0.0).
     */
    public float[] getDir(float x0, float y0, float x1, float y1) {
        float dx = x1 - x0;
        float dy = y1 - y0;
        float denom = (float)Math.sqrt(dx*dx + dy*dy);
        if (denom == 0.0f) {
            return new float[] {0.0f, 0.0f};
        } else {
            return new float[] {
                min(1.0f, dx/denom), 
                min(1.0f, dy/denom)
            };
        }
    }
    
    float hideDistance = 100;
    /**
     * 1. Locate all of the hiding places.
     * 2. Find the closest one.
     * 3. Seek that hiding place.
     */
    public void behaviourHideBehindObstacles(int time) {
        Obstacle ob = getClosestObstacle(this.x, this.y);
        if (ob == null) {
            return;
        }
        
        float[] seekVec = hideFromObject(time, myTargetX, myTargetY, ob.x, ob.y, ob.halfSize+hideRadiusOffset);
        float[] obstacleVec = obstacleAvoidance(time);
        float[] wanderVec = wander(time);
        
        float[] dir = new float[] {
            seekVec[0]*2*seekWeight + ((obstacleVec != null) ? obstacleVec[0]*0.5f*obstacleWeight : 0) + ((wanderVec != null) ? wanderVec[0]*wanderWeight : 0),
            seekVec[1]*2*seekWeight + ((obstacleVec != null) ? obstacleVec[1]*0.5f*obstacleWeight : 0) + ((wanderVec != null) ? wanderVec[1]*wanderWeight : 0)
        };
        float denom = (float)Math.sqrt(
            seekVec[0]*seekVec[0] + seekVec[1]*seekVec[1]);
        
        if (denom == 0.0f) {
            seekVec[0] = 0.0f;
            seekVec[1] = 0.0f;
        } else {
            seekVec[0] = seekVec[0]/denom;
            seekVec[1] = seekVec[1]/denom;
        }
        
        oldX = this.x;
        oldY = this.y;
        this.x += dir[0]*speed;
        this.y += dir[1]*speed;
        
        angle = atan2(y-oldY, x-oldX);
        
    }
    
    public float[] hideFromObject(int time, float fearX, float fearY, float obstacleX, float obstacleY, float distanceFromObstacle) {
        float hideVecX = obstacleX - fearX;
        float hideVecY = obstacleY - fearY;
        float denom = (float) Math.sqrt(hideVecX*hideVecX + hideVecY*hideVecY);
        float scaler = (denom != 0) ? min(1.0f, (distanceFromObstacle/denom)) : 0.0f;
        
        hideVecX = (hideVecX * scaler) + obstacleX;
        hideVecY = (hideVecY * scaler) + obstacleY;
        
        float[] seekVec = seek(time, hideVecX, hideVecY);
        return seekVec;
    }
}

class Obstacle {
    float x, y;
    float size;
    float halfSize;
    
    Obstacle(float x, float y, float size) {
        this.x = x;
        this.y = y;
        this.size = size;
        this.halfSize = size/2.0f;
    }
    
    public void update(int time) {
    }
    
    public void draw() {
        //fill(0.0, 0.0, 1.0);
        //pushMatrix();
        //    translate(x, y);
        //    ellipse(0, 0, size, size);
        //popMatrix();
        
        //sphereDetail(20);
        pushMatrix();
            fill(0.0f, 0.0f, 1.0f);
            translate(x, 0.0f, y);
            sphere(halfSize);
        popMatrix();
    }
}
public class Viewport {
    Viewport(int w, int h) {
        this.w = w;
        this.h = h;
        startPos = new vec4(0.0f, 0.0f, 0.0f, 1.0f);
        reset();
    }
    
    Viewport(int w, int h, float x, float y, float z) {
        this.w = w;
        this.h = h;
        startPos = new vec4(x, y, z, 1.0f);
        reset();
    }
    
    public void reset() {
    	pos =     new vec4(startPos.col[0], startPos.col[1], startPos.col[2], startPos.col[3]);
    	forward = new vec4(0.0f,  0.0f,  -1.0f,  0.0f);
    	up =      new vec4(0.0f,  1.0f,   0.0f,  0.0f);
    	right = (crossProduct(forward, up)).normalize();
    	mouseX = 0.0f;
        mouseY = 0.0f;
    }
    
    public void setPosition(float x, float y, float z) {
        pos.col[0] = x;
        pos.col[1] = y;
        pos.col[2] = z;
    }
    
    public void setMousePosition(float x, float y) {
        mouseX = x;
        mouseY = y;
    }
    
    public void rotate(float xDeg, float yDeg) {
        mat4 rotS = makeRot3D(new vec4(0, 1, 0, 0), -yDeg); //Create a matrix to rotate -axisY degrees about Y.
        this.right = (matMult(rotS, this.right)).normalize();
        mat4 rotU = makeRot3D(this.right, -xDeg);
        mat4 rot = matMult(rotS, rotU);
        this.forward = (matMult(rot, this.forward)).normalize();
        this.up = (matMult(rot, this.up)).normalize();
        this.right = (crossProduct(this.forward, this.up)).normalize();
    }
    
    public void printStatus() {
        print("ViewPort" +
            ", pos=<" + pos.col[0] + ", " + pos.col[1] + ", " + pos.col[2] + ", " + pos.col[3] + ">" +
            ", forward=<" + forward.col[0] + ", " + forward.col[1] + ", " + forward.col[2] + ", " + forward.col[3] + ">" +
            ", up=<" + up.col[0] + ", " + up.col[1] + ", " + up.col[2] + ", " + up.col[3] + ">" +
            ", right=<" + right.col[0] + ", " + right.col[1] + ", " + right.col[2] + ", " + right.col[3] + ">\n\n");
    }
    
	public float w, h;
	public float mouseX;
	public float mouseY; 
	public vec4 forward, up, right;
	public vec4 pos, startPos;
}
  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#DFDFDF", "CrittersServerProcessing" });
  }
}
