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

import processing.opengl.*; 
import javax.media.opengl.*; 

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 Test3D extends PApplet {




PGraphicsOpenGL pgl;
GL gl; 

PFont font;

public void setup() {
    size(640, 480, P3D);
    frameRate(60);
    
    frame.setTitle("Energy Hog");
    
    noStroke(); 
    colorMode(RGB, 1); 
    
    initializeGameObjects();
    loadData();
    
    font = loadFont("Courier10PitchBT-Roman-48.vlw");
    textFont(font, 24);
}

public void draw() {
    hint(ENABLE_DEPTH_TEST);
    //pgl = (PGraphicsOpenGL) g;
    //gl = pgl.gl; 
    //pgl.beginGL();
    //// This fixes the overlap issue
    //gl.glDisable(GL.GL_DEPTH_TEST);
    //// Turn on the blend mode
    //gl.glEnable(GL.GL_BLEND);
    //// Define the blend mode
    //gl.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE);
    //pgl.endGL(); 
    
    updateGame();
    
    background(0.5f);
    resetMatrix();
    
    setup3DCamera();
    drawScene();
}

public void updateGame() {
    long time = millis();
    
    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() {
    
    strokeWeight(0);
    pushMatrix();
        scale(200);
        drawMap();
    popMatrix();
    strokeWeight(1);
    
    //fill(1, 0, 0);
    //stroke(1.0, 0.0, 0.0);
    //pushMatrix();
    //    translate(marker[X], 200, marker[Z]);
    //    line(
    //        0, 0, 0,
    //        0, -1000, 0
    //    );
    //    sphere(10);
    //popMatrix();
    //
    //fill(0, 0, 1);
    //stroke(0.0, 1.0, 0.0);
    for (int i = 0; i < 50; i++) {
        pushMatrix();
            translate(statePos[i][X], 200, statePos[i][Z]);
            line(
                0, 0, 0,
                0, -1000, 0
            );
            fill(0, 0, 1);
            sphere(10);
        popMatrix();     
    }
    
    
    for (int i = 0; i < lights.length; i++) {
        if (lights[i] != null && lights[i].isFocus) {
            hint(DISABLE_DEPTH_TEST);
            fill(lights[i].lightColor.col[RED]*4, lights[i].lightColor.col[GREEN]*5, lights[i].lightColor.col[BLUE]*4);
            text(lights[i].data, statePos[lights[i].state][X], lights[i].ballHeight, statePos[lights[i].state][Z]);
            hint(ENABLE_DEPTH_TEST);
        }
    }
        
    //for (GameObject obj : objList) {
    //    pushMatrix();
    //        obj.draw();
    //    popMatrix();
    //}
    //drawAxis();
    
}


public void drawMap() {
    beginShape(QUADS);
        texture(map);
        
        vertex(-2,  1, -1,   0,   0);
        vertex( 2,  1, -1, 400,   0);
        vertex( 2,  1,  1, 400, 400);
        vertex(-2,  1,  1,   0, 400);
    endShape();
}

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, 52.5f, -325.0f, 262.7135f);
    mat4 rotS = makeRot3D(new vec4(0, 1, 0, 0), 0); //Create a matrix to rotate -axisY degrees about Y.
    viewport.right = (matMult(rotS, viewport.right)).normalize();
    mat4 rotU = makeRot3D(viewport.right, -50);
    mat4 rot = matMult(rotS, rotU);
    viewport.forward = (matMult(rot, viewport.forward)).normalize();
    viewport.up = (matMult(rot, viewport.up)).normalize();
    viewport.right = (crossProduct(viewport.forward, viewport.up)).normalize();
    
    cube = new Cube(0, 0, 0);
    
    objList = new ArrayList<GameObject>();
    objList.add(cube);
    
    setupStates();
    
    map = loadImage("Map_of_USA_trans.png");
}


public void mouseDragged() {
    float mX =  -mouseX/viewport.w;
    float mY =  mouseY/viewport.h * (invertY ? -1 : 1);
    float diffX = mX - viewport.mouseX;
    float diffY = mY - viewport.mouseY;
    float length = (float) Math.sqrt(diffX*diffX + diffY*diffY);
    if (length < 0.001f) {
        return;
    }
    
    float axisX = (float) (diffY / length);
    float axisY = (float) (diffX / length);
    
    mat4 rotS = makeRot3D(new vec4(0, 1, 0, 0), -axisY); //Create a matrix to rotate -axisY degrees about Y.
    viewport.right = (matMult(rotS, viewport.right)).normalize();
    mat4 rotU = makeRot3D(viewport.right, -axisX);
    mat4 rot = matMult(rotS, rotU);
    viewport.forward = (matMult(rot, viewport.forward)).normalize();
    viewport.up = (matMult(rot, viewport.up)).normalize();
    viewport.right = (crossProduct(viewport.forward, viewport.up)).normalize();
    
    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);
    }
}

public void mouseReleased() {
    //viewport.printStatus();
}


public void keyPressed() {
    switch(key) {
    case CODED:
        switch (keyCode) {
        case LEFT:
            marker[X] -= 10;
            break;
        case RIGHT:
            marker[X] += 10;
            break;
        case UP:
            marker[Z] -= 10;
            break;
        case DOWN:
            marker[Z] += 10;
            break;
        default:
            break;
        }
        break;
    case 'p':
    case 'P':
        if (pDebouncer) {
            int s = numPs++;
            print("" + stateAbrevs[((s < 50) ? s : 0)] + ", " + (marker[X]) + ", " + (marker[Y]) + ", " + (marker[Z]) + "\n");
            pDebouncer = false;
        }
        break;
    case '0':
    case ')':
        print("" + (viewport.pos.col[X]) + ", " + (viewport.pos.col[Y]) + ", " + (viewport.pos.col[Z]) + "\n" +
                   (viewport.up.col[X])  + ", " + (viewport.up.col[Y]) + ", " + (viewport.up.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 'p':
    case 'P':
        pDebouncer = true;
        break;
    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;


PImage map;

float[] marker = new float[3];

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

int numPs = 0;
float flySpeed      = 3.0f;
float strafeSpeed = 3.0f;
float moveSpeed = 3.0f;

boolean flyUp        = false;
boolean flyDown      = false;
boolean strafeLeft   = false;
boolean strafeRight  = false;
boolean moveForward  = false;
boolean moveBackward = false;
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 void setup3DCamera() {
    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();
    }
}

public void reorientCamera(float mouseX, float mouseY) {
    float mX =  -mouseX/viewport.w;
    float mY =  mouseY/viewport.h  * (invertY ? -1 : 1);
    float diffX = mX - viewport.mouseX;
    float diffY = mY - viewport.mouseY;
    float length = (float) Math.sqrt(diffX*diffX + diffY*diffY);
    if (length < 0.001f) {
        return;
    }
    
    float axisX = (float) (diffY / length);
    float axisY = (float) (diffX / length);
    
    mat4 rotS = makeRot3D(new vec4(0, 1, 0, 0), -axisY); //Create a matrix to rotate -axisY degrees about Y.
    viewport.right = (matMult(rotS, viewport.right)).normalize();
    mat4 rotU = makeRot3D(viewport.right, -axisX);
    mat4 rot = matMult(rotS, rotU);
    viewport.forward = (matMult(rot, viewport.forward)).normalize();
    viewport.up = (matMult(rot, viewport.up)).normalize();
    viewport.right = (crossProduct(viewport.forward, viewport.up)).normalize();
    
    viewport.setMousePosition(mX, mY);
}

public void setupLights() {
    ambientLight(0.5f, 0.5f, 0.5f);
    
    for (int i = 0; i < lights.length; i++) {
        if (lights[i] == null) {
            lights[i] = getNextLight();
        }
        
        lights[i].ballHeight = interpolateFloat(200.0f, -400.0f, lights[i].t);
        
        makeSpotLight(
            new vec4(statePos[lights[i].state][X], lights[i].ballHeight, statePos[lights[i].state][Z]), lights[i].lightColor, lights[i].direction,
            lights[i].angle, lights[i].concentration, lights[i].size, lights[i].isFocus
        );
        
        
        pushMatrix();
            translate(statePos[lights[i].state][X], 200, statePos[lights[i].state][Z]);
            line(
                0, 0, 0,
                0, -1000, 0
            );
            fill(0, 0, 1);
            //sphere(10);
        popMatrix();     
        
        if ((lights[i].t += lights[i].speed) >= 1.0f) {
            if (lights[i].isFocus) {
                hasFocus--;
            }           
            lights[i] = null;
        }
    }
    //makeSpotLight(upLight, upColor, upDirection, upAngle, upConcentration, drawLights);
    //makeSpotLight(downLight, downColor, downDirection, downAngle, downConcentration, drawLights);
}

public StateLight getNextLight() {
    StateLight light = new StateLight();
    
    //light.state = int(random(50)); //TODO: Make this based on the data!
    while (true) {
        //try {
            if (useCost) {
                String[] record = eCost[PApplet.parseInt(random(iCostMax))];
                light.state = matchAbbrev(record[1]);
                
                if (light.state == -1) {
                    continue;
                }
                
                float r = Float.parseFloat(record[3])/60.0f;
                float g = Float.parseFloat(record[4])/60.0f;
                float b = Float.parseFloat(record[5])/60.0f;
                light.lightColor = new vec4(r, g, b);
                
                float mod = Float.parseFloat(record[8]);
                light.size = mod;
                light.speed = mod*.01f;
                
                if (hasFocus < focusMax) {
                    hasFocus++;
                    light.isFocus = true;
                    
                    light.speed = mod*.003f;
                    
                    light.data = 
                        "Cents per kilowat hour\n" + 
                        record[0] + ", " + record[1] + "\n" +
                        "Industry Sector: " + record[3] + "\n" +
                        "Residential: " + record[3] + "\n" +
                        "Commercial: " + record[4] + "\n" +
                        "Industrial: " + record[5];
                }
                
                break;
            }
        //} catch (Exception e) {
        //    continue;
        //}
    }
    
    
    return light;
}

public float interpolateFloat(float a, float b, float t) {
    return (a + (t*(b-a)));
}

public vec4 interpolateVec4(float[] a, float[] b, float t) {
    return new vec4(
        a[X] + (t*(b[X]-a[X])),
        a[Y] + (t*(b[Y]-a[Y])),
        a[Z] + (t*(b[Z]-a[Z]))
    );
}

public void makePointLight(vec4 pos, vec4 lightColor) {
    pointLight(
        lightColor.col[RED], lightColor.col[GREEN], lightColor.col[BLUE], // Color
        pos.col[X], pos.col[Y], pos.col[Z] // Position
    ); 
    
    if (drawLightSource) {
        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, float size, boolean isFocus) {
    
    float lightMod = 3;//random(10);
    
    if (isFocus) {
        spotLight(
            lightColor.col[RED]*lightMod, lightColor.col[GREEN]*lightMod, 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 (drawLightSource) {
        pushMatrix();
            translate(pos.col[X], pos.col[Y], pos.col[Z]);
            fill(lightColor.col[RED], lightColor.col[GREEN], lightColor.col[BLUE]);
            stroke(lightColor.col[RED]*lightMod, lightColor.col[GREEN]*lightMod, lightColor.col[BLUE]*lightMod);
            sphere(PApplet.parseInt(2*size));
        popMatrix();
    }
}


/* Point lights */
//vec4 pinwheelLight = new vec4(-100, -60,    0);

/* 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( 0, -1000, 0);
float downAngle = PI/2.0f;
float downConcentration = 2.0f;
vec4  downDirection = new vec4( 0, 1, 0);
vec4  downColor = new vec4( 8.0f,  8.0f,  0.6f);

boolean useLights = true;
boolean drawLightSource = true;

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

StateLight[] lights = new StateLight[7];
class StateLight {
    int state;
    float t = 0.0f;
    float size = 1.0f;
    float speed = 0.075f;
    float angle = PI/2.0f;
    float concentration = 0.01f;
    vec4 direction = new vec4(0, 1, 0);
    vec4 lightColor = new vec4(1.0f, 1.0f, 1.0f);
    float ballHeight = 0.0f;
    
    String data = "";
    boolean isFocus = false;
}

int hasFocus = 0;
int focusMax = 3;
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));
}
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 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;
}
int iUsedMax = 14681;
int iUsed = 0;

int iCostMax = 2771;
int iCost = 0;

boolean useCost = true;

String[][] eUsed = new String[iUsedMax][5];
String[][] eCost = new String[iCostMax][9];

public void loadData() {
    String[] bulk = loadStrings("fossil_fuel_consumption_by_state_and_type.txt");
    for (int i = 0; i < bulk.length; i++) {
        eUsed[i] = split(bulk[i], "\t");
    }
    
    bulk = loadStrings("energy_price_by_state.txt");
    for (int i = 0; i < bulk.length; i++) {
        eCost[i] = split(bulk[i], "\t");
    }
}
public void setupStates() {
    int state = 0;
    
    stateAbrevs[state++] = "AL";
    stateAbrevs[state++] = "AR";
    stateAbrevs[state++] = "AZ";
    stateAbrevs[state++] = "CA";
    stateAbrevs[state++] = "CO";
    stateAbrevs[state++] = "CT";
    stateAbrevs[state++] = "DC";
    stateAbrevs[state++] = "DE";
    stateAbrevs[state++] = "FL";
    stateAbrevs[state++] = "GA";
    stateAbrevs[state++] = "HI";
    stateAbrevs[state++] = "IA";
    stateAbrevs[state++] = "ID";
    stateAbrevs[state++] = "IL";
    stateAbrevs[state++] = "IN";
    stateAbrevs[state++] = "KS";
    stateAbrevs[state++] = "KY";
    stateAbrevs[state++] = "LA";
    stateAbrevs[state++] = "MA";
    stateAbrevs[state++] = "MD";
    stateAbrevs[state++] = "ME";
    stateAbrevs[state++] = "MI";
    stateAbrevs[state++] = "MN";
    stateAbrevs[state++] = "MO";
    stateAbrevs[state++] = "MS";
    stateAbrevs[state++] = "MT";
    stateAbrevs[state++] = "NC";
    stateAbrevs[state++] = "ND";
    stateAbrevs[state++] = "NE";
    stateAbrevs[state++] = "NH";
    stateAbrevs[state++] = "NJ";
    stateAbrevs[state++] = "NM";
    stateAbrevs[state++] = "NV";
    stateAbrevs[state++] = "NY";
    stateAbrevs[state++] = "OH";
    stateAbrevs[state++] = "OK";
    stateAbrevs[state++] = "OR";
    stateAbrevs[state++] = "PA";
    stateAbrevs[state++] = "RI";
    stateAbrevs[state++] = "SC";
    stateAbrevs[state++] = "SD";
    stateAbrevs[state++] = "TN";
    stateAbrevs[state++] = "TX";
    stateAbrevs[state++] = "UT";
    stateAbrevs[state++] = "VA";
    stateAbrevs[state++] = "VT";
    stateAbrevs[state++] = "WA";
    stateAbrevs[state++] = "WI";
    stateAbrevs[state++] = "WV";
    stateAbrevs[state++] = "WY";
    
    state = 0;
    
    statePos = new float[][] {
        {-230.0f, 0.0f, 110.0f  }, // AL
        {170.0f, 0.0f, 70.0f    }, // AR
        {-110.0f, 0.0f, 70.0f   }, // AZ
        {-210.0f, 0.0f, 20.0f   }, // CA
        {20.0f, 0.0f, 30.0f     }, // CO
        {340.0f, 0.0f, 0.0f     }, // CT
        {300.0f, 0.0f, 20.0f    }, // DC
        {340.0f, 0.0f, 20.0f    }, // DE
        {300.0f, 0.0f, 120.0f   }, // FL
        {270.0f, 0.0f, 90.0f    }, // GA
        {-50.0f, 0.0f, 140.0f   }, // HI
        {140.0f, 0.0f, 0.0f     }, // IA
        {-80.0f, 0.0f, -30.0f   }, // ID
        {180.0f, 0.0f, 20.0f    }, // IL
        {210.0f, 0.0f, 20.0f    }, // IN
        {100.0f, 0.0f, 40.0f    }, // KS
        {240.0f, 0.0f, 40.0f    }, // KY
        {160.0f, 0.0f, 110.0f   }, // LA
        {350.0f, 0.0f, -30.0f   }, // MA
        {340.0f, 0.0f, 20.0f    }, // MD
        {360.0f, 0.0f, -70.0f   }, // ME
        {220.0f, 0.0f, -20.0f   }, // MI
        {120.0f, 0.0f, -50.0f   }, // MN
        {160.0f, 0.0f, 40.0f    }, // MO
        {200.0f, 0.0f, 90.0f    }, // MS
        {-30.0f, 0.0f, -60.0f   }, // MT
        {310.0f, 0.0f, 60.0f    }, // NC
        {50.0f, 0.0f, -70.0f    }, // ND
        {80.0f, 0.0f, 10.0f     }, // NE
        {340.0f, 0.0f, -40.0f   }, // NH
        {330.0f, 0.0f, 0.0f     }, // NJ
        {-20.0f, 0.0f, 70.0f    }, // NM
        {-140.0f, 0.0f, 10.0f   }, // NV
        {290.0f, 0.0f, -30.0f   }, // NY
        {240.0f, 0.0f, 10.0f    }, // OH
        {110.0f, 0.0f, 70.0f    }, // OK
        {-160.0f, 0.0f, -40.0f  }, // OR
        {290.0f, 0.0f, 0.0f     }, // PA
        {360.0f, 0.0f, 0.0f     }, // RI
        {280.0f, 0.0f, 70.0f    }, // SC
        {70.0f, 0.0f, -30.0f    }, // SD
        {230.0f, 0.0f, 60.0f    }, // TN
        {60.0f, 0.0f, 100.0f    }, // TX
        {-70.0f, 0.0f, 20.0f    }, // UT
        {290.0f, 0.0f, 40.0f    }, // VA
        {320.0f, 0.0f, -50.0f   }, // VT
        {-150.0f, 0.0f, -90.0f  }, // WA
        {160.0f, 0.0f, -30.0f   }, // WI
        {270.0f, 0.0f, 30.0f    }, // WV
        {0.0f, 0.0f, -10.0f     }  // WY
    };
}

String[] stateAbrevs = new String[50];

float[][] statePos = new float[50][3];

public int matchAbbrev(String s) {
    for (int i=0; i<stateAbrevs.length; i++) {
        if (stateAbrevs[i].equals(s)) {
            return i;
        }
    }
    
    return -1;
}
  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#DFDFDF", "Test3D" });
  }
}
