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

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import com.jogamp.opengl.GL2;
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 com.jogamp.opengl.glu.GLU;
import javax.swing.JFrame;

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

class Renderer {

  private GLU glu = new GLU();
  public float t = 0.0f;
  
  private enum VAOs {Pyramid, Cube, numVAOs};
  private enum VBOs {PyramidAll, CubePos, CubeCol, CubeIndices, numVBOs};
  
  private int[] vaoID  = new int[VAOs.numVAOs.ordinal()];
  private int[] bufID = new int[VBOs.numVBOs.ordinal()];
  private int pyramidVertNo = 0;
  private int cubeIndicesNo = 0;

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

    // now we create 2 Vertex Array Objects (VAO):
    // one for a pyramid and one for a cube.
    // The pyramid uses one interleaved VBO
    // which is later drawn with "glDrawArrays",
    // while the cube uses 3 separate VBOs
    // which are later drawn with "glDrawElements"

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

    // generating Vertex Buffer Objects (VBO)
    gl.glGenBuffers(VBOs.numVBOs.ordinal(), bufID, 0);

    // specifying the pyramid VAO
    gl.glBindVertexArray(vaoID[VAOs.Pyramid.ordinal()]);

    float pyramidVertexData[] = {
       0.0f, 0.0f, 2.0f, 1.0f, 0.0f, 0.0f, 0.0f,
      -0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
       0.5f,-0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
       0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 0.0f,
       0.5f,-0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
       0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
       0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 1.0f, 0.0f,
       0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
      -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
       0.0f, 0.0f, 2.0f, 1.0f, 1.0f, 0.0f, 0.0f,
      -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
      -0.5f,-0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
    };
    
    pyramidVertNo = 12;

    // assuming the following structure of input vertex data
    // struct Vertex {
    //   float position[3];
    //   float color[4];
    // };
    int floatItems = pyramidVertNo *(3+4);
    FloatBuffer pyramidVertexFB = Buffers.newDirectFloatBuffer(floatItems);
    pyramidVertexFB.put(pyramidVertexData);
    pyramidVertexFB.flip();
    
    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, bufID[VBOs.PyramidAll.ordinal()]);
    gl.glBufferData(GL2.GL_ARRAY_BUFFER, pyramidVertexFB.capacity()*Buffers.SIZEOF_FLOAT, pyramidVertexFB, GL2.GL_STATIC_DRAW);


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

    // position
    gl.glVertexPointer(3, GL2.GL_FLOAT, stride, offset);
    gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);

    // color
    offset = 0 + 3*Buffers.SIZEOF_FLOAT;
    gl.glColorPointer(4, GL2.GL_FLOAT, stride, offset);
    gl.glEnableClientState(GL2.GL_COLOR_ARRAY);

    // specifying the cube VAO
    gl.glBindVertexArray(vaoID[VAOs.Cube.ordinal()]);

    float cubePosData[] = {
      -1.0f,  1.0f,  1.0f,
      -1.0f, -1.0f,  1.0f,
       1.0f, -1.0f,  1.0f,
       1.0f,  1.0f,  1.0f,
      -1.0f,  1.0f, -1.0f,
      -1.0f, -1.0f, -1.0f,
       1.0f, -1.0f, -1.0f,
       1.0f,  1.0f, -1.0f
    };

    FloatBuffer cubePosFB = Buffers.newDirectFloatBuffer(8*3);
    cubePosFB.put(cubePosData);
    cubePosFB.flip();
    
    float cubeColorData[] = {
      1.0f, 1.0f, 0.0f,
      1.0f, 0.0f, 0.0f,
      0.0f, 1.0f, 0.0f,
      0.0f, 0.0f, 1.0f,
      1.0f, 1.0f, 0.0f,
      1.0f, 0.0f, 0.0f,
      0.0f, 1.0f, 0.0f,
      0.0f, 0.0f, 1.0f,
    };

    FloatBuffer cubeColorFB = Buffers.newDirectFloatBuffer(8*3);
    cubeColorFB.put(cubeColorData);
    cubeColorFB.flip();
    
    int cubeIndicesData[] =
    { 0, 1, 2, 3,
      0, 4, 5, 1,
      4, 7, 6, 5,
      2, 6, 7, 3,
      1, 5, 6, 2,
      3, 7, 4, 0
    };

    cubeIndicesNo = 24;

    IntBuffer cubeIndicesFB = Buffers.newDirectIntBuffer(cubeIndicesNo);
    cubeIndicesFB.put(cubeIndicesData);
    cubeIndicesFB.flip();
    
    // position
    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, bufID[VBOs.CubePos.ordinal()]);
    gl.glBufferData(GL2.GL_ARRAY_BUFFER, cubePosFB.capacity()*Buffers.SIZEOF_FLOAT, cubePosFB, GL2.GL_STATIC_DRAW);
    gl.glVertexPointer(3, GL2.GL_FLOAT, 0, 0);
    gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);

    // color
    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, bufID[VBOs.CubeCol.ordinal()]);
    gl.glBufferData(GL2.GL_ARRAY_BUFFER, cubeColorFB.capacity()*Buffers.SIZEOF_FLOAT, cubeColorFB, GL2.GL_STATIC_DRAW);
    gl.glColorPointer(3, GL2.GL_FLOAT, 0, 0);
    gl.glEnableClientState(GL2.GL_COLOR_ARRAY);

    // indices
    gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, bufID[VBOs.CubeIndices.ordinal()]);
    gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, cubeIndicesFB.capacity()*Buffers.SIZEOF_INT, cubeIndicesFB, GL2.GL_STATIC_DRAW);
  }
 
  
  public void resize(GLAutoDrawable d, int w, int h) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context
    gl.glViewport(0, 0, w, h);
    gl.glMatrixMode(GL2.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluPerspective(60.0, (float)w/(float)h, 0.1, 50.0);
  }

  public void display(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    
    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);

    gl.glMatrixMode(GL2.GL_MODELVIEW);
    gl.glLoadIdentity();
    // set camera
    glu.gluLookAt(3.0, -1.0, 4.5, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0);
    // draw scene
    gl.glRotatef(t, 0.0f, 0.0f, 1.0f);

    // bind cube VAO
    gl.glBindVertexArray(vaoID[VAOs.Cube.ordinal()]);
    // render data
    gl.glDrawElements(GL2.GL_QUADS, cubeIndicesNo, GL2.GL_UNSIGNED_INT, 0);

    gl.glTranslatef(0.0f, 0.0f, 1.0f);
    // bind pyramid VAO
    gl.glBindVertexArray(vaoID[VAOs.Pyramid.ordinal()]);
    // render data
    gl.glDrawArrays(GL2.GL_TRIANGLES, 0, pyramidVertNo);

    gl.glFlush();
  }
}

class MyGui extends JFrame implements GLEventListener {

  private Renderer renderer;

  public void createGUI() {
    setTitle("Vertex Array Object (VAO) Demo");
    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();

    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) {
  }
}

public class Vao {
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyGui myGUI = new MyGui();
        myGUI.createGUI();
      }
    });
  }
}
