// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import com.jogamp.opengl.GL3;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.awt.GLCanvas;
import javax.swing.JFrame;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.FPSAnimator;

class Renderer {

  public float t = 0.0f;
  public int modeVal = 1;
  public boolean loadNext = false;
  public boolean loadNextShader = false;

  private enum VAOs {
    Scene, numVAOs
  };

  private enum VBOs {
    SceneAll, numVBOs
  };

  private int[] vaoID = new int[VAOs.numVAOs.ordinal()];
  private int[] bufID = new int[VBOs.numVBOs.ordinal()];
  private int sceneVertNo = 0;
  private int progID = 0;
  private int vertID = 0;
  private int fragID = 0;
  private int vertexLoc = 0;
  private int texCoordLoc = 0;
  private int normalLoc = 0;
  private int projectionLoc = 0;
  private int modelviewLoc = 0;
  private int normalMatrixLoc = 0;
  private int modeLoc = 0;
  private int lightDirectionLoc = 0;
  private int camPosLoc = 0;
  private int lookAtLoc = 0;
  private int camProjectionLoc = 0;
  private int meshTransformLoc = 0;
  private int meshTransformTransposeInvLoc = 0;
  private float[] projection = new float[16];
  private float[] modelview = new float[16];
  private String filename = "./knot.vbo";
  private String shaderFilename = "./PhongBrdfCameraSpace";
  private int file = 0;
  private int shaderFile = 0;

  public void nextModel(GLAutoDrawable d) {
    loadNext = false;
    file++;
    if (file > 4)
      file = 0;
    if (file == 0)
      filename = "./knot.vbo";
    if (file == 1)
      filename = "./teapot.vbo";
    if (file == 2)
      filename = "./sphere.vbo";
    if (file == 3)
      filename = "./hose.vbo";
    if (file == 4)
      filename = "./cube.vbo";
    deleteAll(d);
    init(d);
  }

  public void nextShaderExample(GLAutoDrawable d) {
    loadNextShader = false;
    shaderFile++;
    if (shaderFile > 4)
      shaderFile = 0;
    if (shaderFile == 0)
      shaderFilename = "./PhongBrdfCameraSpace";
    if (shaderFile == 1)
      shaderFilename = "./PhongBrdfWorldSpace";
    if (shaderFile == 2)
      shaderFilename = "./BlinnPhongBrdfCameraSpace";
    if (shaderFile == 3)
      shaderFilename = "./BlinnPhongBrdfWorldSpace";
    if (shaderFile == 4)
      shaderFilename = "./PhongBrdfPerVertex";
    System.out.println("loading shader: " + shaderFilename);
    deleteAll(d);
    init(d);
  }

  public void init(GLAutoDrawable d) {
    GL3 gl = d.getGL().getGL3(); // get the OpenGL 3 graphics context
    gl.glEnable(GL3.GL_DEPTH_TEST);

    setupShaders(d);

    // create a Vertex Array Objects (VAO)
    gl.glGenVertexArrays(VAOs.numVAOs.ordinal(), vaoID, 0);

    // generate a Vertex Buffer Object (VBO)
    gl.glGenBuffers(VBOs.numVBOs.ordinal(), bufID, 0);

    // binding the Triangle VAO
    gl.glBindVertexArray(vaoID[VAOs.Scene.ordinal()]);

    int perVertexFloats = (3 + 2 + 3);
    float data[] = loadVertexData(filename, perVertexFloats);

    sceneVertNo = data.length / perVertexFloats;

    FloatBuffer sceneVertexFB = Buffers.newDirectFloatBuffer(data.length);
    sceneVertexFB.put(data);
    sceneVertexFB.flip();

    gl.glBindBuffer(GL3.GL_ARRAY_BUFFER, bufID[VBOs.SceneAll.ordinal()]);
    gl.glBufferData(GL3.GL_ARRAY_BUFFER, sceneVertexFB.capacity() * Buffers.SIZEOF_FLOAT, sceneVertexFB,
        GL3.GL_STATIC_DRAW);

    int stride = (3 + 2 + 3) * Buffers.SIZEOF_FLOAT;
    int offset = 0;

    // position
    if (vertexLoc != -1) {
      gl.glVertexAttribPointer(vertexLoc, 3, GL3.GL_FLOAT, false, stride, offset);
      gl.glEnableVertexAttribArray(vertexLoc);
    }

    // texCoord
    if (texCoordLoc != -1) {
      offset = 0 + 3 * Buffers.SIZEOF_FLOAT;
      gl.glVertexAttribPointer(texCoordLoc, 2, GL3.GL_FLOAT, false, stride, offset);
      gl.glEnableVertexAttribArray(texCoordLoc);
    }

    // normal
    if (normalLoc != -1) {
      offset = 0 + (3 + 2) * Buffers.SIZEOF_FLOAT;
      gl.glVertexAttribPointer(normalLoc, 3, GL3.GL_FLOAT, false, stride, offset);
      gl.glEnableVertexAttribArray(normalLoc);
    }
    
    System.out.println("Press 1-5 to switch mode, 9 to switch shader, 0 to switch 3D mesh");
  }

  public void resize(GLAutoDrawable d, int w, int h) {
    GL3 gl = d.getGL().getGL3(); // get the OpenGL 3 graphics context
    gl.glViewport(0, 0, w, h);

    // this function replaces gluPerspective
    mat4Perspective(projection, 45.0f, (float) w / (float) h, 0.5f, 4.0f);
    // mat4Print(projection);
  }

  public void display(GLAutoDrawable d) {
    if (loadNext)
      nextModel(d);
    if (loadNextShader)
      nextShaderExample(d);

    GL3 gl = d.getGL().getGL3(); // get the OpenGL >= 3 graphics context

    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    gl.glClear(GL3.GL_COLOR_BUFFER_BIT | GL3.GL_DEPTH_BUFFER_BIT);

    float lightDirection[] = { 0.0f, -1.0f, -1.0f, 0.0f};
    float cameraPos[] = { 1.5f, 0.0f, 1.5f};
    float lightDirectionTrans[] = new float[4];
    float modelviewInv[] = new float[16];
    float normalmatrix[] = new float[16];
    float lookAt[] = new float[16];
    float lookAtInv[] = new float[16];
    float m[] = new float[16];
    float meshTransform[] = new float[16];
    float meshTransformInv[] = new float[16];
    float meshTransformTransposeInv[] = new float[16];

    // mat4LookAt replaces gluLookAt
    mat4LookAt(lookAt, cameraPos[0], cameraPos[1], cameraPos[2], // eye
          0.0f, 0.0f, 0.0f, // look at
          0.0f, 0.0f, 1.0f); // up

    // apply lookAt matrix to light direction
    mat4Invert(lookAt, lookAtInv);
    mat4Transpose(lookAtInv, m);
    applyMatrixToVec4(m, lightDirection, lightDirectionTrans);

    // rotate mesh
    rotateZ(meshTransform, -t);

    // compute meshTransformTransposeInv
    mat4Invert(meshTransform, meshTransformInv);
    mat4Transpose(meshTransformInv, meshTransformTransposeInv);

    // compute modelview and normalmatrix
    mat4Multiply(lookAt, meshTransform, modelview);
    mat4Invert(modelview, modelviewInv);
    mat4Transpose(modelviewInv, normalmatrix);

    gl.glUseProgram(progID);

    // load the current projection and modelview matrix into the
    // corresponding UNIFORM variables of the shader
    //gl.glUniformMatrix4fv(projectionLoc, 1, false, projection, 0);
    //gl.glUniformMatrix4fv(modelviewLoc, 1, false, modelview, 0);
    //gl.glUniformMatrix4fv(normalMatrixLoc, 1, false, normalmatrix, 0);
    //gl.glUniform1i(modeLoc, modeVal);
    // load the current projection and modelview matrix into the
    // corresponding UNIFORM variables of the shader
    if(projectionLoc != -1) gl.glUniformMatrix4fv(projectionLoc, 1, false, projection, 0);
    if(camProjectionLoc != -1) gl.glUniformMatrix4fv(camProjectionLoc, 1, false, projection, 0);
    if(modelviewLoc != -1) gl.glUniformMatrix4fv(modelviewLoc, 1, false, modelview, 0);
    if(normalMatrixLoc != -1) gl.glUniformMatrix4fv(normalMatrixLoc, 1, false, normalmatrix, 0);
    if(modeLoc != -1) gl.glUniform1i(modeLoc, modeVal);
    if(lightDirectionLoc != -1) gl.glUniform3fv(lightDirectionLoc, 1, lightDirectionTrans, 0);
    if(camPosLoc != -1) gl.glUniform3fv(camPosLoc, 1, cameraPos, 0);
    if(lookAtLoc != -1)gl. glUniformMatrix4fv(lookAtLoc, 1, false, lookAt, 0);
    if(meshTransformLoc != -1) gl.glUniformMatrix4fv(meshTransformLoc, 1, false, meshTransform, 0);
    if(meshTransformTransposeInvLoc != -1) gl.glUniformMatrix4fv(meshTransformTransposeInvLoc, 1, false, meshTransformTransposeInv, 0);

    // bind scene VAO
    gl.glBindVertexArray(vaoID[VAOs.Scene.ordinal()]);

    // render data
    gl.glDrawArrays(GL3.GL_TRIANGLES, 0, sceneVertNo);

    gl.glFlush();
  }

  public void deleteAll(GLAutoDrawable d) {
    GL3 gl = d.getGL().getGL3(); // get the OpenGL 3 graphics context
    gl.glDeleteVertexArrays(VAOs.numVAOs.ordinal(), vaoID, 0);
    gl.glDeleteBuffers(VBOs.numVBOs.ordinal(), bufID, 0);
    gl.glDeleteProgram(progID);
    gl.glDeleteShader(vertID);
    gl.glDeleteShader(fragID);
  }

  private void setupShaders(GLAutoDrawable d) {
    GL3 gl = d.getGL().getGL3(); // get the OpenGL 3 graphics context

    vertID = gl.glCreateShader(GL3.GL_VERTEX_SHADER);
    fragID = gl.glCreateShader(GL3.GL_FRAGMENT_SHADER);

    String[] vs = loadShaderSrc(shaderFilename + ".vert");
    String[] fs = loadShaderSrc(shaderFilename + ".frag");

    gl.glShaderSource(vertID, 1, vs, null, 0);
    gl.glShaderSource(fragID, 1, fs, null, 0);

    // compile the shader
    gl.glCompileShader(vertID);
    gl.glCompileShader(fragID);

    // check for errors
    printShaderInfoLog(d, vertID);
    printShaderInfoLog(d, fragID);

    // create program and attach shaders
    progID = gl.glCreateProgram();
    gl.glAttachShader(progID, vertID);
    gl.glAttachShader(progID, fragID);

    // "outColor" is a user-provided OUT variable
    // of the fragment shader.
    // Its output is bound to the first color buffer
    // in the framebuffer
    gl.glBindFragDataLocation(progID, 0, "outputColor");

    // link the program
    gl.glLinkProgram(progID);
    // output error messages
    printProgramInfoLog(d, progID);

    // retrieve the location of the IN variables of the vertex shader
    vertexLoc = gl.glGetAttribLocation(progID, "position");
    texCoordLoc = gl.glGetAttribLocation(progID, "texcoord");
    normalLoc = gl.glGetAttribLocation(progID, "normal");

    // retrieve the location of the UNIFORM variables of the vertex shader
    camProjectionLoc = gl.glGetUniformLocation(progID, "cameraProjection");
    projectionLoc = gl.glGetUniformLocation(progID, "projection");
    modelviewLoc = gl.glGetUniformLocation(progID, "modelview");
    normalMatrixLoc = gl.glGetUniformLocation(progID, "normalMat");
    modeLoc = gl.glGetUniformLocation(progID, "mode");
    lightDirectionLoc = gl.glGetUniformLocation(progID, "lightDirection");
    camPosLoc = gl.glGetUniformLocation(progID, "cameraPosition");
    lookAtLoc = gl.glGetUniformLocation(progID, "cameraLookAt");
    meshTransformLoc = gl.glGetUniformLocation(progID, "meshTransform");
    meshTransformTransposeInvLoc = gl.glGetUniformLocation(progID, "meshTransformTransposedInverse");
  }

  private String[] loadShaderSrc(String name) {
    StringBuilder sb = new StringBuilder();
    try {
      InputStream is = getClass().getResourceAsStream(name);
      BufferedReader br = new BufferedReader(new InputStreamReader(is));
      String line;
      while ((line = br.readLine()) != null) {
        sb.append(line);
        sb.append('\n');
      }
      is.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return new String[] { sb.toString() };
  }

  private void printShaderInfoLog(GLAutoDrawable d, int obj) {
    GL3 gl = d.getGL().getGL3(); // get the OpenGL 3 graphics context
    IntBuffer infoLogLengthBuf = IntBuffer.allocate(1);
    int infoLogLength;
    gl.glGetShaderiv(obj, GL3.GL_INFO_LOG_LENGTH, infoLogLengthBuf);
    infoLogLength = infoLogLengthBuf.get(0);
    if (infoLogLength > 0) {
      ByteBuffer byteBuffer = ByteBuffer.allocate(infoLogLength);
      gl.glGetShaderInfoLog(obj, infoLogLength, infoLogLengthBuf, byteBuffer);
      for (byte b : byteBuffer.array()) {
        System.err.print((char) b);
      }
    }
  }

  private void printProgramInfoLog(GLAutoDrawable d, int obj) {
    GL3 gl = d.getGL().getGL3(); // get the OpenGL 3 graphics context
    IntBuffer infoLogLengthBuf = IntBuffer.allocate(1);
    int infoLogLength;
    gl.glGetProgramiv(obj, GL3.GL_INFO_LOG_LENGTH, infoLogLengthBuf);
    infoLogLength = infoLogLengthBuf.get(0);
    if (infoLogLength > 0) {
      ByteBuffer byteBuffer = ByteBuffer.allocate(infoLogLength);
      gl.glGetProgramInfoLog(obj, infoLogLength, infoLogLengthBuf, byteBuffer);
      for (byte b : byteBuffer.array()) {
        System.err.print((char) b);
      }
    }
  }

  private float[] loadVertexData(String filename, int perVertexFloats) {

    float[] floatArray = new float[0];
    // read vertex data from file
    int vertSize = 0;
    try {
      InputStream is = getClass().getResourceAsStream(filename);
      BufferedReader br = new BufferedReader(new InputStreamReader(is));
      String line = br.readLine();
      if (line != null) {
        vertSize = Integer.parseInt(line);
        floatArray = new float[vertSize];
      }
      int i = 0;
      while ((line = br.readLine()) != null && i < floatArray.length) {
        floatArray[i] = Float.parseFloat(line);
        i++;
      }
      if (i != vertSize || (vertSize % perVertexFloats) != 0) {
        floatArray = new float[0];
      }
      br.close();
    } catch (FileNotFoundException e) {
      System.out.println("Can not find vbo data file " + filename);
    } catch (IOException e) {
      e.printStackTrace();
    }
    return floatArray;
  }

  // the following functions are some matrix and vector helpers
  // they work for this demo but in general it is recommended
  // to use more advanced matrix libraries
  private float vec3Dot(float[] a, float[] b) {
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
  }

  private void vec3Cross(float[] a, float[] b, float[] res) {
    res[0] = a[1] * b[2] - b[1] * a[2];
    res[1] = a[2] * b[0] - b[2] * a[0];
    res[2] = a[0] * b[1] - b[0] * a[1];
  }

  private void vec3Normalize(float[] a) {
    float mag = (float) Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
    a[0] /= mag;
    a[1] /= mag;
    a[2] /= mag;
  }

  private void mat4Identity(float[] a) {
    for (int i = 0; i < 16; ++i)
      a[i] = 0.0f;
    for (int i = 0; i < 4; ++i)
      a[i + i * 4] = 1.0f;
  }

  private void mat4Multiply(float[] a, float[] b, float[] res) {
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        res[j * 4 + i] = 0.0f;
        for (int k = 0; k < 4; ++k) {
          res[j * 4 + i] += a[k * 4 + i] * b[j * 4 + k];
        }
      }
    }
  }

  private void mat4Perspective(float[] a, float fov, float aspect, float zNear, float zFar) {
    float f = 1.0f / (float) (Math.tan(fov / 2.0f * (Math.PI / 180.0f)));
    mat4Identity(a);
    a[0] = f / aspect;
    a[1 * 4 + 1] = f;
    a[2 * 4 + 2] = (zFar + zNear) / (zNear - zFar);
    a[3 * 4 + 2] = (2.0f * zFar * zNear) / (zNear - zFar);
    a[2 * 4 + 3] = -1.0f;
    a[3 * 4 + 3] = 0.0f;
  }

  private void mat4LookAt(float[] viewMatrix, float eyeX, float eyeY, float eyeZ, float centerX, float centerY,
      float centerZ, float upX, float upY, float upZ) {

    float dir[] = new float[3];
    float right[] = new float[3];
    float up[] = new float[3];
    float eye[] = new float[3];

    up[0] = upX;
    up[1] = upY;
    up[2] = upZ;
    eye[0] = eyeX;
    eye[1] = eyeY;
    eye[2] = eyeZ;

    dir[0] = centerX - eyeX;
    dir[1] = centerY - eyeY;
    dir[2] = centerZ - eyeZ;
    vec3Normalize(dir);
    vec3Cross(dir, up, right);
    vec3Normalize(right);
    vec3Cross(right, dir, up);
    vec3Normalize(up);
    // first row
    viewMatrix[0] = right[0];
    viewMatrix[4] = right[1];
    viewMatrix[8] = right[2];
    viewMatrix[12] = -vec3Dot(right, eye);
    // second row
    viewMatrix[1] = up[0];
    viewMatrix[5] = up[1];
    viewMatrix[9] = up[2];
    viewMatrix[13] = -vec3Dot(up, eye);
    // third row
    viewMatrix[2] = -dir[0];
    viewMatrix[6] = -dir[1];
    viewMatrix[10] = -dir[2];
    viewMatrix[14] = vec3Dot(dir, eye);
    // forth row
    viewMatrix[3] = 0.0f;
    viewMatrix[7] = 0.0f;
    viewMatrix[11] = 0.0f;
    viewMatrix[15] = 1.0f;
  }

  private void mat4Print(float[] a) {
    // opengl uses column major order
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        System.out.print(a[j * 4 + i] + " ");
      }
      System.out.println(" ");
    }
  }

  private void mat4Transpose(float[] a, float[] transposed) {
    int t = 0;
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        transposed[t++] = a[j * 4 + i];
      }
    }
  }

  private boolean mat4Invert(float[] m, float[] inverse) {
    float inv[] = new float[16];
    inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14]
        + m[13] * m[6] * m[11] - m[13] * m[7] * m[10];
    inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14]
        - m[12] * m[6] * m[11] + m[12] * m[7] * m[10];
    inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13]
        + m[12] * m[5] * m[11] - m[12] * m[7] * m[9];
    inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13]
        - m[12] * m[5] * m[10] + m[12] * m[6] * m[9];
    inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14]
        - m[13] * m[2] * m[11] + m[13] * m[3] * m[10];
    inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14]
        + m[12] * m[2] * m[11] - m[12] * m[3] * m[10];
    inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13]
        - m[12] * m[1] * m[11] + m[12] * m[3] * m[9];
    inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13]
        + m[12] * m[1] * m[10] - m[12] * m[2] * m[9];
    inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7]
        - m[13] * m[3] * m[6];
    inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14]
        - m[12] * m[2] * m[7] + m[12] * m[3] * m[6];
    inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13]
        + m[12] * m[1] * m[7] - m[12] * m[3] * m[5];
    inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13]
        - m[12] * m[1] * m[6] + m[12] * m[2] * m[5];
    inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7]
        + m[9] * m[3] * m[6];
    inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7]
        - m[8] * m[3] * m[6];
    inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7]
        + m[8] * m[3] * m[5];
    inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6]
        - m[8] * m[2] * m[5];

    float det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];
    if (det == 0)
      return false;
    det = 1.0f / det;
    for (int i = 0; i < 16; i++)
      inverse[i] = inv[i] * det;
    return true;
  }

  private void applyMatrixToVec4(float[] m, float[] vector, float[] result) {
      result[0] = m[0 + 4 * 0] * vector[0] + m[0 + 4 * 1] * vector[1] + m[0 + 4 * 2] * vector[2] + m[0 + 4 * 3] * vector[3];
      result[1] = m[1 + 4 * 0] * vector[0] + m[1 + 4 * 1] * vector[1] + m[1 + 4 * 2] * vector[2] + m[1 + 4 * 3] * vector[3];
      result[2] = m[2 + 4 * 0] * vector[0] + m[2 + 4 * 1] * vector[1] + m[2 + 4 * 2] * vector[2] + m[2 + 4 * 3] * vector[3];
      result[3] = m[3 + 4 * 0] * vector[0] + m[3 + 4 * 1] * vector[1] + m[3 + 4 * 2] * vector[2] + m[3 + 4 * 3] * vector[3];
  }

  private void rotateZ(float[] rot, float degree) {
    mat4Identity(rot);
    float rad = (float)Math.PI / 180.0f * degree;
    rot[0 + 4 * 0] = (float)Math.cos(rad);
    rot[1 + 4 * 0] = (float)Math.sin(rad);
    rot[0 + 4 * 1] = -(float)Math.sin(rad);
    rot[1 + 4  *1] = (float)Math.cos(rad);
  }

}

class MyGui extends JFrame implements GLEventListener {

  private Renderer renderer;

  public void createGUI() {
    setTitle("Shader Light/Material");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    GLProfile glp = GLProfile.getDefault();
    GLCapabilities caps = new GLCapabilities(glp);
    GLCanvas canvas = new GLCanvas(caps);
    setSize(320, 320);

    getContentPane().add(canvas);
    final FPSAnimator ani = new FPSAnimator(canvas, 60, true);
    canvas.addGLEventListener(this);
    setVisible(true);
    renderer = new Renderer();

    canvas.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent event) {
        boolean redraw = false;
        String modeStr = "";

        switch (event.getKeyCode()) {
        case '1':
          renderer.modeVal = 1;
          redraw = true;
          modeStr = "mode = 1";
          break;
        case '2':
          renderer.modeVal = 2;
          redraw = true;
          modeStr = "mode = 2";
          break;
        case '3':
          renderer.modeVal = 3;
          redraw = true;
          modeStr = "mode = 3";
          break;
        case '4':
          renderer.modeVal = 4;
          redraw = true;
          modeStr = "mode = 4";
          break;
        case '5':
          renderer.modeVal = 5;
          redraw = true;
          modeStr = "mode = 5";
          break;
        case '9':
          renderer.loadNextShader = true;
          redraw = true;
          modeStr = "Shader Light/Material";
          break;
        case '0':
          renderer.loadNext = true;
          redraw = true;
          modeStr = "Shader Light/Material";
          break;
        }
        if (redraw) {
          setTitle(modeStr);
        }
      }
    });

    ani.start();
  }

  @Override
  public void init(GLAutoDrawable d) {
    renderer.init(d);
  }

  @Override
  public void reshape(GLAutoDrawable d, int x, int y, int width, int height) {
    renderer.resize(d, width, height);
  }

  @Override
  public void display(GLAutoDrawable d) {
    float offset = 1.0f;
    renderer.t += offset;
    renderer.display(d);
  }

  @Override
  public void dispose(GLAutoDrawable d) {
    renderer.deleteAll(d);
  }
}

public class ShaderLightMat {
  public static void main(String[] args) {
	System.setProperty("sun.java2d.uiScale", "1.0");
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyGui myGUI = new MyGui();
        myGUI.createGUI();
      }
    });
  }
}
