import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; import com.jogamp.opengl.*; import com.jogamp.opengl.awt.GLJPanel; import com.jogamp.opengl.util.GLBuffers; import java.nio.FloatBuffer; /** * A Jogl/OpenGL application that an 11-by-11-by-11 cube of * spheres in different colors. Five rendering methods are * available, selected using a popup menu. The rendering time * to draw the scene is shown. Use the mouse to rotate * the scene and force a redraw. (You can also force a redraw * by resizing the window.) This program requires OpenGL 1.5 * or higher for support of vertex buffer objects. */ public class ColorCubeOfSpheres extends JPanel implements GLEventListener, ActionListener { public static void main(String[] args) { JFrame window = new JFrame("Color Cube of Spheres -- USE MOUSE TO ROTATE"); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.setContentPane(new ColorCubeOfSpheres()); window.pack(); window.setLocation(50,50); window.setVisible(true); } private Camera camera; private int sphereDisplayList; // A display list for drawing one sphere. private FloatBuffer sphereVertexBuffer; // Holds the vertex coords, for use with glDrawArrays private FloatBuffer sphereNormalBuffer; // Holds the normal vectors, for use with glDrawArrays private float[] sphereVertexArray; // The same data as in sphereVertexBuffer, stored in an array. private float[] sphereNormalArray; // The same data as in sphereNormalBuffer, stored in an array. private int vertexVboId; // identifier for the Vertex Buffer Object to hold the vertex coords private int normalVboId; // identifier for the Vertex Buffer Object to hold the normla vectors private JComboBox renderModeSelect; // For selecting the type of rendering private JLabel message; // Displays the elapsed time in milliseconds for the most // recent rendering of the display. private int currentRenderMode; // Which of the five render modes is selected. private int renderCount; // Number of time image has been rendered using current render technique. private double renderTimeSum; // Sum of the rendering times (since the current technique was selected). /** * Make a regular JPanel to hold the GLJPanel, Add a small panel at the * bottom to hold a check box that determines whether a display list is used * for rendering and a label that displays the rendering time of the most * recent display of the GLJPanel. The user can rotate the view with the * mouse. */ public ColorCubeOfSpheres() { renderModeSelect = new JComboBox(); renderModeSelect.addItem("Direct Draw, Recomputing Vertex Data"); renderModeSelect.addItem("Direct Draw, Precomputed Data"); renderModeSelect.addItem("Use Display List"); renderModeSelect.addItem("Use DrawArrays"); renderModeSelect.addItem("Use DrawArrays with VBOs"); renderModeSelect.setSelectedIndex(2); renderModeSelect.addActionListener(this); currentRenderMode = 2; message = new JLabel("Average Render time:"); JPanel bottom = new JPanel(); bottom.setLayout(new BorderLayout(20,20)); bottom.add(renderModeSelect, BorderLayout.WEST); bottom.add(message, BorderLayout.CENTER); bottom.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); GLJPanel drawable = new GLJPanel(); drawable.setPreferredSize(new Dimension(600,600)); drawable.addGLEventListener(this); setLayout(new BorderLayout()); add(drawable, BorderLayout.CENTER); add(bottom, BorderLayout.SOUTH); camera = new Camera(); camera.setLimits(-10,10,-10,10,-10,10); camera.installTrackball(this); } public void actionPerformed(ActionEvent evt) { repaint(); } /** * This method will be called when the GLJPanel is first * created. It can be used to initialize the GL context. */ public void init(GLAutoDrawable drawable) { GL2 gl = drawable.getGL().getGL2(); gl.glEnable(GL2.GL_LIGHTING); gl.glEnable(GL2.GL_LIGHT0); gl.glEnable(GL2.GL_DEPTH_TEST); gl.glShadeModel(GL2.GL_SMOOTH); gl.glEnable(GL2.GL_COLOR_MATERIAL); gl.glClearColor(0.5f,0.5f,0.5f,1); /* Create a display list that contains all the OpenGL * commands that are generated when the sphere is drawn. */ sphereDisplayList = gl.glGenLists(1); gl.glNewList(sphereDisplayList, GL2.GL_COMPILE); uvSphere(gl, 0.4, 32, 16, false); gl.glEndList(); /* Create the data for glDrawArrays, for render modes 2 and 3 */ createSphereArraysAndVBOs(gl); } /** * Draw a color cube of spheres, 11 spheres on each side, using * different colors. Each of the red, green, and blue components * of the color varies along one dimension of the cube. */ public void display(GLAutoDrawable drawable) { GL2 gl = drawable.getGL().getGL2(); gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT); camera.apply(gl); int mode = renderModeSelect.getSelectedIndex(); if (mode != currentRenderMode) { currentRenderMode = mode; // Change mode, and reset timing variables. renderCount = 0; renderTimeSum = 0; } long start = System.currentTimeMillis(); // Starting time for rendering. if (mode == 4 || mode == 3) { // We need to enable the vertex and normal arrays, and set // the vertex and normal points for these modes. gl.glEnableClientState(GL2.GL_VERTEX_ARRAY); gl.glEnableClientState(GL2.GL_NORMAL_ARRAY); if (mode == 4) { // When using VBOs, the vertex and normal pointers // refer to data in the VBOs. gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vertexVboId); gl.glVertexPointer(3,GL.GL_FLOAT,0,0); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, normalVboId); gl.glNormalPointer(GL.GL_FLOAT,0,0); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); } else { // When not using VBOs, the points point to the FloatBuffers. gl.glVertexPointer(3,GL.GL_FLOAT,0,sphereVertexBuffer); gl.glNormalPointer(GL.GL_FLOAT,0,sphereNormalBuffer); } } for (int i = 0; i <= 10; i++) { float r = i/10.0f; for (int j = 0; j <= 10; j++) { float g = j/10.0f; for (int k = 0; k <= 10; k++) { float b = k/10.0f; gl.glColor3f(r,g,b); gl.glPushMatrix(); gl.glTranslatef(i-5,j-5,k-5); if (mode == 0) uvSphere(gl, 0.4, 32, 16, false); // Draw sphere directly. else if (mode == 1) drawSphereDirectWithDataFromArrays(gl); else if (mode == 2) gl.glCallList(sphereDisplayList); // Draw by calling a display list. else drawSphereWithDrawArrays(gl); // Draw using DrawArrays gl.glPopMatrix(); } } } if (mode == 4 || mode == 3) { gl.glDisableClientState(GL2.GL_VERTEX_ARRAY); gl.glDisableClientState(GL2.GL_NORMAL_ARRAY); } gl.glFlush(); // Make sure all commands are sent to graphics card. gl.glFinish(); // Wait for all commands to complete, before checking time. long time = System.currentTimeMillis() - start; renderCount++; renderTimeSum += time; String text = String.format("Average Render Time = %1.2f", renderTimeSum/renderCount); message.setText(text); } public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { } public void dispose(GLAutoDrawable drawable) { } //----------------- for glDrawArrays and Vertex Buffer Object ---------------------------------- /** * Creates the vertex coordinate and normal vectors for a sphere. * The data is stored in the FloatBuffers sphereVertexBuffer and * sphereNormalBuffer. In addition, VBOs are created to hold * the data and the data is copied from the FloatBuffers into * the VBOs. (Note: The VBOs are used for render mode 4; the * FloatBuffers are used for render mode 3.) */ private void createSphereArraysAndVBOs(GL2 gl) { double radius = 0.4; int stacks = 16; int slices = 32; int size = stacks * (slices+1) * 2 * 3; sphereVertexBuffer = GLBuffers.newDirectFloatBuffer(size); sphereNormalBuffer = GLBuffers.newDirectFloatBuffer(size); sphereVertexArray = new float[size]; sphereNormalArray = new float[size]; for (int j = 0; j < stacks; j++) { double latitude1 = (Math.PI/stacks) * j - Math.PI/2; double latitude2 = (Math.PI/stacks) * (j+1) - Math.PI/2; double sinLat1 = Math.sin(latitude1); double cosLat1 = Math.cos(latitude1); double sinLat2 = Math.sin(latitude2); double cosLat2 = Math.cos(latitude2); for (int i = 0; i <= slices; i++) { double longitude = (2*Math.PI/slices) * i; double sinLong = Math.sin(longitude); double cosLong = Math.cos(longitude); double x1 = cosLong * cosLat1; double y1 = sinLong * cosLat1; double z1 = sinLat1; double x2 = cosLong * cosLat2; double y2 = sinLong * cosLat2; double z2 = sinLat2; sphereNormalBuffer.put( (float)x2 ); sphereNormalBuffer.put( (float)y2 ); sphereNormalBuffer.put( (float)z2 ); sphereVertexBuffer.put( (float)(radius*x2) ); sphereVertexBuffer.put( (float)(radius*y2) ); sphereVertexBuffer.put( (float)(radius*z2) ); sphereNormalBuffer.put( (float)x1 ); sphereNormalBuffer.put( (float)y1 ); sphereNormalBuffer.put( (float)z1 ); sphereVertexBuffer.put( (float)(radius*x1) ); sphereVertexBuffer.put( (float)(radius*y1) ); sphereVertexBuffer.put( (float)(radius*z1) ); } } for (int i = 0; i < size; i++) { sphereVertexArray[i] = sphereVertexBuffer.get(i); sphereNormalArray[i] = sphereNormalBuffer.get(i); } sphereVertexBuffer.rewind(); sphereNormalBuffer.rewind(); int[] bufferIDs = new int[2]; gl.glGenBuffers(2, bufferIDs,0); vertexVboId = bufferIDs[0]; normalVboId = bufferIDs[1]; gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertexVboId); gl.glBufferData(GL2.GL_ARRAY_BUFFER, size*4, sphereVertexBuffer, GL2.GL_STATIC_DRAW); gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, normalVboId); gl.glBufferData(GL2.GL_ARRAY_BUFFER, size*4, sphereNormalBuffer, GL2.GL_STATIC_DRAW); gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0); } /** * Draw one sphere. The VertexPointer and NormalPointer must already * be set to point to the data for the sphere, and they must be enabled. */ private void drawSphereWithDrawArrays(GL2 gl) { int slices = 32; int stacks = 16; int vertices = (slices+1)*2; for (int i = 0; i < stacks; i++) { int pos = i*(slices+1)*2; gl.glDrawArrays(GL2.GL_QUAD_STRIP, pos, vertices); } } void drawSphereDirectWithDataFromArrays(GL2 gl) { int i,j; int slices = 32; int stacks = 16; int vertices = (slices+1)*2; for (i = 0; i < stacks; i++) { int pos = i*(slices+1)*2*3; gl.glBegin(GL2.GL_QUAD_STRIP); for (j = 0; j < vertices; j++) { // gl.glNormal3fv(sphereNormalArray, pos+3*j); /* This worked but took 3 times as long!!! */ // gl.glVertex3fv(sphereVertexArray, pos+3*j); gl.glNormal3f(sphereNormalArray[pos+3*j],sphereNormalArray[pos+3*j+1],sphereNormalArray[pos+3*j+2]); gl.glVertex3f(sphereVertexArray[pos+3*j],sphereVertexArray[pos+3*j+1],sphereVertexArray[pos+3*j+2]); } gl.glEnd(); } } /** * Draw a sphere with a given radius, number of slices, and number * of stacks. The number of slices is the number of lines of longitude * (like the slices of an orange). The number of stacks is the number * of divisions perpendicular the axis; the lines of latitude are the * dividing lines between stacks, so there are stacks-1 lines of latitude. * The last parameter tells whether or not to generate texture * coordinates for the sphere. The texture wraps once around the sphere. * The sphere is centered at (0,0,0), and its axis lies along the z-axis. * (Copied from TexturedShapes.uvSphere().) */ public static void uvSphere(GL2 gl, double radius, int slices, int stacks, boolean makeTexCoords) { if (radius <= 0) throw new IllegalArgumentException("Radius must be positive."); if (slices < 3) throw new IllegalArgumentException("Number of slices must be at least 3."); if (stacks < 2) throw new IllegalArgumentException("Number of stacks must be at least 2."); for (int j = 0; j < stacks; j++) { double latitude1 = (Math.PI/stacks) * j - Math.PI/2; double latitude2 = (Math.PI/stacks) * (j+1) - Math.PI/2; double sinLat1 = Math.sin(latitude1); double cosLat1 = Math.cos(latitude1); double sinLat2 = Math.sin(latitude2); double cosLat2 = Math.cos(latitude2); gl.glBegin(GL2.GL_QUAD_STRIP); for (int i = 0; i <= slices; i++) { double longitude = (2*Math.PI/slices) * i; double sinLong = Math.sin(longitude); double cosLong = Math.cos(longitude); double x1 = cosLong * cosLat1; double y1 = sinLong * cosLat1; double z1 = sinLat1; double x2 = cosLong * cosLat2; double y2 = sinLong * cosLat2; double z2 = sinLat2; gl.glNormal3d(x2,y2,z2); if (makeTexCoords) gl.glTexCoord2d(1.0/slices * i, 1.0/stacks * (j+1)); gl.glVertex3d(radius*x2,radius*y2,radius*z2); gl.glNormal3d(x1,y1,z1); if (makeTexCoords) gl.glTexCoord2d(1.0/slices * i, 1.0/stacks * j); gl.glVertex3d(radius*x1,radius*y1,radius*z1); } gl.glEnd(); } } // end uvSphere }