import com.sun.opengl.util.*;
import com.sun.opengl.util.texture.*;

 import java.io.*;
 import java.awt.event.*;
 import java.nio.*;
 import javax.swing.*;
 import javax.media.opengl.*;
 import javax.media.opengl.glu.*;

 public class VboFrame extends JFrame implements GLEventListener, 
                                                 MouseListener,
                                                 MouseMotionListener,
                                                 WindowListener,
                                                 KeyListener {

    private static final int SLICES = 200;

    private static final int STACKS = 100;

    private static final float RADIUS = 1.0f;

    private static final double PI_2 = Math.PI*2;

   private GLCanvas canvas;
   private Animator animator;

   private float angleX, angleY;
   private float angleX0, angleY0;
   private float dist = 2.0f;
   private float dist0;
   private int mouseX, mouseY;
   private int lastButton;

   //vertices
   private float [] vs;

   //vertex indices
   private int [][] vi;

   //colors
   private float [] cs;

   //normals
   private float [] ns;

   private GLU myGLU = new GLU();
   private GLUT myGLUT = new GLUT();

   private int theSphere;

   private FPSCounter fpscounter = new FPSCounter();

   private boolean linemode = false;

   private int fkey = KeyEvent.VK_F3;

   private FloatBuffer nbuf, cbuf, vbuf;
   private IntBuffer ibuf1, ibuf3;
   private IntBuffer[] ibuf2;
   private int[] ibufcount;
   private IntBuffer ibuf4;

   public VboFrame(String title) {
     super(title);

     //this.setUndecorated(true);

     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

     angleX = 0.0f; angleY = 0.0f;

     GLCapabilities capabilities = new GLCapabilities();
     capabilities.setHardwareAccelerated(true);
     capabilities.setDoubleBuffered(true);

     canvas = new GLCanvas(capabilities);
     canvas.addGLEventListener(this);
     canvas.addMouseListener(this);
     canvas.addMouseMotionListener(this);
     canvas.addKeyListener(this);
     canvas.setVisible(true);

     this.getContentPane().add(canvas);
     this.setSize(640, 480);
     this.setLocation(100, 100);
     animator = new Animator(canvas);
     this.addWindowListener(this);
     this.show();
     animator.start();
   }

   /*** implementing GLEventListener methods ***/
   public void init(GLAutoDrawable glDrawable) {
     GL myGL = glDrawable.getGL();

        initSphere(SLICES, STACKS, RADIUS);

        theSphere = myGL.glGenLists(1);
        myGL.glNewList(theSphere, GL.GL_COMPILE);
            renderSphere();
        myGL.glEndList();

     float[] lightAmbient = {0.3f, 0.3f, 0.3f, 1.0f};
     float[] lightDiffuse = {0.7f, 0.7f, 0.7f, 1.0f};
     float[] lightSpecular = {1.0f, 1.0f, 1.0f, 1.0f};
     float[] lightPosition = {10.0f, 10.0f, 10.0f, 0.0f};

     myGL.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
     myGL.glShadeModel(GL.GL_SMOOTH);

     myGL.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, lightAmbient, 0);
     myGL.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, lightDiffuse, 0);
     myGL.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, lightSpecular, 0);
     myGL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, lightPosition, 0);

     //myGL.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, GL.GL_TRUE);
     myGL.glColorMaterial(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT_AND_DIFFUSE);

     myGL.glCullFace(GL.GL_FRONT);

     myGL.glEnable(GL.GL_DEPTH_TEST);
     myGL.glEnable(GL.GL_LIGHT0);
     myGL.glEnable(GL.GL_LIGHTING);
     myGL.glEnable(GL.GL_COLOR_MATERIAL);
     myGL.glEnable(GL.GL_CULL_FACE);
   }

   public void reshape(GLAutoDrawable glDrawable, int x, int y, int w, int h) {
     GL myGL = glDrawable.getGL();
     float aspect = w/(float)h;

     myGL.glMatrixMode(GL.GL_PROJECTION);
     myGL.glLoadIdentity();
     myGL.glFrustum(-0.017*aspect, 0.017*aspect, -0.017, 0.017, 0.02, 100);
   }

   public void display(GLAutoDrawable glDrawable) {
     GL myGL = glDrawable.getGL();

     fpscounter.update();

     if (linemode) {
        myGL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
     } else {
        myGL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
     }

     myGL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
     myGL.glMatrixMode(GL.GL_MODELVIEW);
     myGL.glLoadIdentity();
     myGL.glTranslatef(0.0f, 0.0f, -dist);

     myGL.glRotatef(angleX, 1.0f, 0.0f, 0.0f);
     myGL.glRotatef(angleY, 0.0f, 1.0f, 0.0f);

     switch (fkey) {
        case KeyEvent.VK_F1 :
            renderSphere();
            break;
        case KeyEvent.VK_F2 :
            myGL.glCallList(theSphere);
            break;
        case KeyEvent.VK_F3 :
            renderArraySphere();
            break;
     }

     drawInfo(myGL);
   }

   public void displayChanged(GLAutoDrawable glDrawable, boolean b, boolean b1) {
   }

   /*** implementing MouseListener methods **/
   public void mouseClicked(MouseEvent e) {}

   public void mouseEntered(MouseEvent e) {}

   public void mouseExited(MouseEvent e) {}

   public void mousePressed(MouseEvent e) {
     lastButton = e.getButton();
     if (lastButton == MouseEvent.BUTTON1) {
       angleX0 = angleX; angleY0 = angleY;
       mouseX = e.getX(); mouseY = e.getY();
     } else if (lastButton == MouseEvent.BUTTON3) {
       dist0 = dist;
       mouseY = e.getY();
     }
   }

   public void mouseReleased(MouseEvent e) {  }

   /*** implementing MouseMotionListener methods ***/
   public void mouseDragged(MouseEvent e) {
     int dx, dy, dz;
     if (lastButton == MouseEvent.BUTTON1) {
       dx = mouseX - e.getX(); dy = mouseY - e.getY();
       angleY = angleY0 - dx;
       angleX = angleX0 - dy;
     }
     if (lastButton == MouseEvent.BUTTON3) {
       dz = mouseY - e.getY();
       dist = dist0 + dz/100.0f;
     }
   }

   public void mouseMoved(MouseEvent e) {}

   /***implementing WindowListener methods ***/
   public void windowActivated(WindowEvent e) {}

   public void windowClosed(WindowEvent e) {}

   public void windowClosing(WindowEvent e) {
     close();
   }

   public void windowDeactivated(WindowEvent e) {}

   public void windowDeiconified(WindowEvent e) {}

   public void windowIconified(WindowEvent e) {}

   public void windowOpened(WindowEvent e) {}

   /*** implementing KeyListener **/
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
        char keyChar = e.getKeyChar();

        switch (keyCode) {
            case KeyEvent.VK_ESCAPE :
                close();
                break;
            case KeyEvent.VK_F1 :
                fkey = KeyEvent.VK_F1;
                break;
            case KeyEvent.VK_F2 :
                fkey = KeyEvent.VK_F2;
                break;
            case KeyEvent.VK_F3 :
                fkey = KeyEvent.VK_F3;
                break;
        }

        switch (keyChar) {
            case 'f' :
                linemode=false;
                break;
            case 'l' :
                linemode=true;
                break;
        }
    }

    public void keyReleased(KeyEvent e) {
    }

    public void keyTyped(KeyEvent e) {
    }

   /*** private methods ***/
   private void stopAnimation() {
     if (animator != null) animator.stop();
   }

   private void close() {
     stopAnimation();

     System.exit(0);
   }

   private void fill3f(float[] fv, int index, float f1, float f2, float f3) {
        fv[index]=f1;
        fv[index+1]=f2;
        fv[index+2]=f3;
   }

   private void out(int index) {
        System.out.println(vs[index*3] + " " + vs[index*3+1] + " " + vs[index*3+2]);
        System.out.println(ns[index*3] + " " + ns[index*3+1] + " " + ns[index*3+2]);
        System.out.println(cs[index*3] + " " + cs[index*3+1] + " " + cs[index*3+2]);
   }

   private void initSphere(int slices, int stacks, float radius) {
        float x, y, z;

        vs = new float[(slices*(stacks-1)+2)*3];
        ns = new float[(slices*(stacks-1)+2)*3];
        cs = new float[(slices*(stacks-1)+2)*3];

        fill3f(vs, 0, 0.0f, 0.0f, radius);
        fill3f(ns, 0, 0.0f, 0.0f, 1.0f);
        fill3f(cs, 0, 0.5f, 0.5f, 1.0f);

        for (int j=1; j<stacks; ++j) {
            for (int i=0; i<slices; ++i) {
                x = (float)Math.sin((i*PI_2)/slices) * (float)Math.sin(j*Math.PI/stacks);
                y = (float)Math.cos((i*PI_2)/slices) * (float)Math.sin(j*Math.PI/stacks);
                z = (float)Math.cos(j*Math.PI/stacks);
                fill3f(vs, 3+(j-1)*3*slices+i*3, x*radius, y*radius, z*radius);
                fill3f(ns, 3+(j-1)*3*slices+i*3, x, y, z);
                fill3f(cs, 3+(j-1)*3*slices+i*3, x/2.0f+0.5f, y/2.0f+0.5f, z/2.0f+0.5f);
            }
        }

        fill3f(vs, (slices*(stacks-1)+1)*3, 0.0f, 0.0f, -radius);
        fill3f(ns, (slices*(stacks-1)+1)*3, 0.0f, 0.0f, -1.0f);
        fill3f(cs, (slices*(stacks-1)+1)*3, 0.5f, 0.5f, 0.0f);

        vi = new int[stacks][];

        vi[0]=new int[slices+2];
        vi[0][0]=0;

        for (int i=0; i<slices+1; ++i) {
            vi[0][1+i]=i < slices ? i+1 : 1;
        }

        for (int j=1; j<stacks-1; ++j) {
            vi[j]=new int[(slices+1)*2];

            for (int i=0; i<slices+1; ++i) {
                vi[j][i*2]=i < slices ? 1+(j-1)*slices+i : 1+(j-1)*slices;
                vi[j][i*2+1]=i < slices ? 1+j*slices+i : 1+j*slices;
            }
        }

        vi[stacks-1]=new int[slices+2];

        vi[stacks-1][0]=slices*(stacks-1)+1;

        for (int i=0; i<slices+1; ++i) {
            vi[stacks-1][slices-i+1]=i<slices ? 1+(stacks-2)*slices+i : 1+(stacks-2)*slices;
        }

        nbuf = BufferUtil.newFloatBuffer(ns.length);
        nbuf.put(ns);
        nbuf.rewind();

        cbuf = BufferUtil.newFloatBuffer(cs.length);
        cbuf.put(cs);
        cbuf.rewind();

        vbuf = BufferUtil.newFloatBuffer(vs.length);
        vbuf.put(vs);
        vbuf.rewind();


        ibuf1 = BufferUtil.newIntBuffer(vi[0].length);
        ibuf1.put(vi[0]);
        ibuf1.rewind();

        ibuf3 = BufferUtil.newIntBuffer(vi[vi.length-1].length);
        ibuf3.put(vi[vi.length-1]);
        ibuf3.rewind();

        ibuf2 = new IntBuffer[stacks-2];
        ibufcount = new int[stacks-2];
        for (int i=0; i<stacks-2; ++i) {
            ibufcount[i]=vi[i+1].length;
            ibuf2[i] = BufferUtil.newIntBuffer(vi[i+1].length);
            ibuf2[i].put(vi[i+1]);
            ibuf2[i].rewind();
        }

        ibuf4 = BufferUtil.newIntBuffer(ibufcount.length);
        ibuf4.put(ibufcount);
        ibuf4.rewind();

   }

   private void renderSphere() {
        GL myGL = GLContext.getCurrent().getGL();
        myGL.glBegin(GL.GL_TRIANGLE_FAN);
            for (int i=0; i<vi[0].length; ++i) {
                myGL.glColor3fv(cs, vi[0][i]*3);
                myGL.glNormal3fv(ns, vi[0][i]*3);
                myGL.glVertex3fv(vs, vi[0][i]*3);
            }
        myGL.glEnd();

        for (int j=0; j<vi.length-1; ++j) {
            myGL.glBegin(GL.GL_QUAD_STRIP);
                for (int i=0; i<vi[j].length; ++i) {
                    myGL.glColor3fv(cs, vi[j][i]*3);
                    myGL.glNormal3fv(ns, vi[j][i]*3);
                    myGL.glVertex3fv(vs, vi[j][i]*3);
                }
            myGL.glEnd();
        }

        myGL.glBegin(GL.GL_TRIANGLE_FAN);
            for (int i=0; i<vi[vi.length-1].length; ++i) {
                myGL.glColor3fv(cs, vi[vi.length-1][i]*3);
                myGL.glNormal3fv(ns, vi[vi.length-1][i]*3);
                myGL.glVertex3fv(vs, vi[vi.length-1][i]*3);
            }
        myGL.glEnd();

   }

   private void renderArraySphere() {
        GL myGL = GLContext.getCurrent().getGL();
        myGL.glEnableClientState(GL.GL_NORMAL_ARRAY);
        myGL.glEnableClientState(GL.GL_COLOR_ARRAY);
        myGL.glEnableClientState(GL.GL_VERTEX_ARRAY);

        myGL.glNormalPointer(GL.GL_FLOAT, 0, nbuf);
        myGL.glColorPointer(3, GL.GL_FLOAT, 0, cbuf);
        myGL.glVertexPointer(3, GL.GL_FLOAT, 0, vbuf);

        myGL.glDrawElements(GL.GL_TRIANGLE_FAN, vi[0].length, GL.GL_UNSIGNED_INT, ibuf1);

        /*for (int i=0; i<ibufcount.length; ++i) {
            myGL.glDrawElements(GL.GL_QUAD_STRIP, ibufcount[i], GL.GL_UNSIGNED_INT, ibuf2[i]);
        }*/
        /*myGL.glMultiDrawElements(GL.GL_QUAD_STRIP, ibufcount, 0, GL.GL_UNSIGNED_INT, ibuf2, ibufcount.length);*/
        myGL.glMultiDrawElements(GL.GL_QUAD_STRIP, ibuf4, GL.GL_UNSIGNED_INT, ibuf2, ibufcount.length);

        myGL.glDrawElements(GL.GL_TRIANGLE_FAN, vi[vi.length-1].length, GL.GL_UNSIGNED_INT, ibuf3);

        myGL.glDisableClientState(GL.GL_NORMAL_ARRAY);
        myGL.glDisableClientState(GL.GL_COLOR_ARRAY);
        myGL.glDisableClientState(GL.GL_VERTEX_ARRAY);
   }

   public synchronized void drawInfo(GL myGL) {
        myGL.glPushAttrib(GL.GL_ALL_ATTRIB_BITS);

        myGL.glDisable(GL.GL_TEXTURE_2D);
        myGL.glDisable(GL.GL_DEPTH_TEST);
        myGL.glDisable(GL.GL_BLEND);
        myGL.glDisable(GL.GL_LIGHTING);
        myGL.glDisable(GL.GL_LIGHT0);
        myGL.glDisable(GL.GL_CULL_FACE);
        myGL.glDisable(GL.GL_COLOR_MATERIAL);

        myGL.glMatrixMode(GL.GL_PROJECTION);
        myGL.glPushMatrix();
            myGL.glLoadIdentity();
            myGLU.gluOrtho2D(0, 500, 300, 0);

        myGL.glMatrixMode(GL.GL_MODELVIEW);
        myGL.glPushMatrix();
            myGL.glLoadIdentity();

            myGL.glColor3f(1.0f, 1.0f, 0.0f);
            myGL.glRasterPos2f(430, 15);
            myGLUT.glutBitmapString(GLUT.BITMAP_8_BY_13, "Fps : " + fpscounter.getFps());


        myGL.glMatrixMode(GL.GL_PROJECTION);
        myGL.glPopMatrix();

        myGL.glMatrixMode(GL.GL_MODELVIEW);
        myGL.glPopMatrix();

        myGL.glPopAttrib();

        myGL.glFlush();
    }

   /*** the main ***/
   public static void main(String args[]) {
     VboFrame frm = new VboFrame("Vbo Test");
   }

 }