// 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.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Random;

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;
  public int mode = 1;
  public int ayimutSegs = 8;
  public int polarSegs = 8;
  public boolean recreateVBONotify = false;
  
  private int[] vertBufID = { 0 };
  private int[] indicesBufID = { 0 };
  private int vertSize = 0;
  private Random gen2= new Random(); 
  public void init(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL graphics context
    gl.glEnable(GL2.GL_DEPTH_TEST);
    
    recreateVBOs(d);
  }

  private void recreateVBOs(GLAutoDrawable d) {

    GL2 gl = d.getGL().getGL2(); // get the OpenGL graphics context
    
    if(vertBufID[0] !=0) gl.glDeleteBuffers( 1, vertBufID, 0);
    if(indicesBufID[0] !=0) gl.glDeleteBuffers( 1, indicesBufID, 0);

    // generating VBO input data
    int totalSegs = ayimutSegs * polarSegs;
    FloatBuffer vertexData = Buffers.newDirectFloatBuffer(totalSegs * 3);
    IntBuffer indicesData = Buffers.newDirectIntBuffer(totalSegs * 4);
    
    double ayimutStep = 2.0 * Math.PI / (double)ayimutSegs;
    double polarStep = Math.PI / (double)polarSegs;
    double r = 1.0;
    vertSize = 0;
    Random generator = new Random();
    for(int m=0; m < ayimutSegs; m++) {
      for(int n=0; n < polarSegs; n++) {
        double phi = ayimutStep*m;
        double theta = polarStep * n;

        // random radius
        r = 1.0 - 0.3 * Math.abs(generator.nextDouble());

        // compute xyz from spherical coordinates
        float x = (float) (r * Math.sin(theta) * Math.cos(phi));
        float y = (float) (r * Math.sin(theta) * Math.sin(phi));
        float z = (float) (r * Math.cos(theta));

        vertexData.put(x);
        vertexData.put(y);
        vertexData.put(z);

        // create vertex indices
        indicesData.put(vertSize % totalSegs);
        indicesData.put((vertSize+ayimutSegs) % totalSegs);
        indicesData.put((vertSize+ayimutSegs + 1) % totalSegs);
        indicesData.put((vertSize+1) % totalSegs);

        vertSize++;
      }
    }

    vertexData.flip();
    indicesData.flip();
    
    // generating vertex VBO
    gl.glGenBuffers(1, vertBufID, 0);
    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertBufID[0]);
    gl.glBufferData(GL2.GL_ARRAY_BUFFER, vertexData.capacity()*Buffers.SIZEOF_FLOAT, vertexData, GL2.GL_STATIC_DRAW);

    // generating index VBO
    gl.glGenBuffers(1, indicesBufID, 0);
    gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indicesBufID[0]);
    gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, indicesData.capacity()*Buffers.SIZEOF_INT, indicesData, GL2.GL_STATIC_DRAW);
    
    recreateVBONotify = false;
  }
  
  private void updateRandomVBOVertex(GLAutoDrawable d) {
  
    GL2 gl = d.getGL().getGL2(); // get the OpenGL graphics context
  
    double ayimutStep = 2.0 * Math.PI / (double)ayimutSegs;
    double polarStep = Math.PI / (double)polarSegs;
    double r = 1.0;

    // randomly select point
    int m = (int)((ayimutSegs-1) * gen2.nextDouble());
    int n = (int)((polarSegs-1) *  gen2.nextDouble());

    double phi = ayimutStep*m;
    double theta = polarStep * n;

    // random radius
    r = 1.0 - 0.3 * gen2.nextDouble();

    // create xyz from spherical coordinates
    float x = (float) (r * Math.sin(theta) * Math.cos(phi));
    float y = (float) (r * Math.sin(theta) * Math.sin(phi));
    float z = (float) (r * Math.cos(theta));

    int vertPos = (m*polarSegs + n) * 3;

	FloatBuffer newVertexData = Buffers.newDirectFloatBuffer(3);
    newVertexData.put(x);
    newVertexData.put(y);
    newVertexData.put(z);
	newVertexData.flip();

    // update vertex in VBO
    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertBufID[0]);
    gl.glBufferSubData(GL2.GL_ARRAY_BUFFER, vertPos*Buffers.SIZEOF_FLOAT, 3*Buffers.SIZEOF_FLOAT, newVertexData);
  }  
  
  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 (30.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
    
	if(recreateVBONotify) recreateVBOs(d);
	
	// update up to 100 random vertices
	for(int i = 0; i < 100 && i  < (ayimutSegs/2); i++) {
        updateRandomVBOVertex(d);
    }
	
    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.5, -1.0, 3.5, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
    // draw scene
    gl.glRotatef(t, 0.0f, 0.0f, 1.0f);

    // activating VBO
    gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertBufID[0]);
    int stride = 0;
    gl.glVertexPointer(3, GL2.GL_FLOAT, stride, 0);
    gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);

    if(mode == 0) {
      gl.glColor3f(1.0f,1.0f,1.0f);
    }else{
      gl.glColorPointer(3, GL2.GL_FLOAT, stride, 0);
      gl.glEnableClientState(GL2.GL_COLOR_ARRAY);
    }

    gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indicesBufID[0]);
    gl.glDrawElements(GL2.GL_QUADS, vertSize*4, GL2.GL_UNSIGNED_INT, 0);

    gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
    gl.glDisableClientState(GL2.GL_COLOR_ARRAY);

    gl.glFlush();
  }
}

class MyGui extends JFrame implements GLEventListener {

  private Renderer renderer;

  public void createGUI() {
    setTitle("Press 1 to change color, 2 to increase vertex count");
    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;
        switch(event.getKeyCode()) {
          case '1':
            if(renderer.mode == 1) renderer.mode = 0;
            else renderer.mode = 1;
            redraw = true;
            break;
          case '2':
            if(renderer.ayimutSegs < 512) {
              renderer.ayimutSegs *= 4;
              renderer.polarSegs *= 4;
            }else{
              renderer.ayimutSegs = 8;
              renderer.polarSegs = 8;
            }
            renderer.recreateVBONotify = true;
        }
        if(redraw) { // done automatically
        }
      }
    });
    
    
    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 VboUpdate {
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyGui myGUI = new MyGui();
        myGUI.createGUI();
      }
    });
  }
}
