View Javadoc

1   /*
2    * Copyright 2011 Stefan C. Mueller.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.smurn.jply.lwjgldemo;
17  
18  import org.smurn.jply.Element;
19  import java.nio.FloatBuffer;
20  import java.io.IOException;
21  import java.nio.ByteBuffer;
22  import java.nio.IntBuffer;
23  import org.apache.commons.io.IOUtils;
24  import org.lwjgl.BufferUtils;
25  import org.smurn.jply.PlyReader;
26  import org.lwjgl.LWJGLException;
27  import org.lwjgl.opengl.ARBShaderObjects;
28  import static org.lwjgl.opengl.ARBVertexBufferObject.*;
29  import org.lwjgl.opengl.Display;
30  import org.lwjgl.opengl.DisplayMode;
31  import org.lwjgl.opengl.GL11;
32  import org.lwjgl.opengl.Util;
33  import org.smurn.jply.ElementReader;
34  import org.smurn.jply.PlyReaderFile;
35  import org.smurn.jply.util.NormalMode;
36  import org.smurn.jply.util.NormalizingPlyReader;
37  import org.smurn.jply.util.TesselationMode;
38  import org.smurn.jply.util.TextureMode;
39  import static org.lwjgl.opengl.GL11.*;
40  import static org.lwjgl.opengl.ARBShaderObjects.*;
41  import static org.lwjgl.opengl.ARBVertexShader.*;
42  import static org.lwjgl.opengl.ARBFragmentShader.*;
43  import org.lwjgl.input.Mouse;
44  
45  /**
46   * jPLY Demo: jPLY viewer using JWJGL.
47   * 
48   * This example is using state-of-the-art vertex-buffer-objects and
49   * GLSL shaders instead of the immediate mode rendering often seen in 
50   * examples. This makes the code a bit more complicated but its the way
51   * this things are done in real-world applications.
52   * 
53   * The code doesn't perform proper error checking and will just fail if
54   * run on a computer with an old graphics-card (or old drivers). This way
55   * the code remains much more readable and simple.
56   */
57  public class LWJGLDemo {
58  
59      public static void main(String[] args) throws LWJGLException, IOException {
60  
61          // Open a openGL enabled window.
62          Display.setTitle("jPLY LWJGL Demo");
63          Display.setDisplayMode(new DisplayMode(600, 600));
64          Display.create();
65  
66          // Open the PLY file
67          PlyReader plyReader = new PlyReaderFile(
68                  ClassLoader.getSystemResourceAsStream("bunny.ply"));
69  
70          // Normalize the data in the PLY file to ensure that we only get
71          // triangles and that the vertices have normal vectors assigned.
72          plyReader = new NormalizingPlyReader(plyReader,
73                  TesselationMode.TRIANGLES,
74                  NormalMode.ADD_NORMALS_CCW,
75                  TextureMode.PASS_THROUGH);
76  
77          // We can get the number of vertices and triangles before
78          // reading it. We will use this to allocate buffers of the right size.
79          int vertexCount = plyReader.getElementCount("vertex");
80          int triangleCount = plyReader.getElementCount("face");
81  
82          // Number of bytes we need per vertex.
83          // Three position coordinates and three normal vector coordinates
84          // all stored as 32-bit float.
85          int vertexSize = 3 * 4 + 3 * 4;
86  
87          // Number of bytes we need per triangle.
88          // Three indices to vertices stored as 32-bit integer.
89          int triangleSize = 3 * 4;
90  
91          // A bit of opengl magic to allocate a vertex-buffer-object to
92          // store the vertices.
93          int vertexBufferId = glGenBuffersARB();
94          glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertexBufferId);
95          glBufferDataARB(GL_ARRAY_BUFFER_ARB,
96                  vertexCount * vertexSize, GL_STATIC_DRAW_ARB);
97          FloatBuffer vertexBuffer = glMapBufferARB(GL_ARRAY_BUFFER_ARB,
98                  GL_WRITE_ONLY_ARB, null).asFloatBuffer();
99  
100         // The same magic again for the index buffer storing the triangles.
101         int indexBufferId = glGenBuffersARB();
102         glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexBufferId);
103         glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB,
104                 triangleCount * triangleSize, GL_STATIC_DRAW_ARB);
105         IntBuffer indexBuffer = glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,
106                 GL_WRITE_ONLY_ARB, null).asIntBuffer();
107 
108         // Now we READ THE PLY DATA into the buffers.
109         // We also measure the size of the model so that we can later fit
110         // arbitrary models into the screen.
111         RectBounds bounds = fillBuffers(plyReader, vertexBuffer, indexBuffer);
112 
113         // Tell openGL that we filled the buffers.
114         glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
115         glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB);
116 
117         // Create the vertex and fragment shader for nice phong shading.
118         int programId = createShaders();
119 
120         // The shaders have a few parameters to configure the three lights.
121         // Each parameter has an identifier which we query here.
122 
123         // Vector in the direction towards the light.
124         int[] lightDirection = new int[]{
125             glGetUniformLocationARB(programId, "lightDir[0]"),
126             glGetUniformLocationARB(programId, "lightDir[1]"),
127             glGetUniformLocationARB(programId, "lightDir[2]")
128         };
129         // Color of the diffuse part of the light.
130         int[] diffuseColor = new int[]{
131             glGetUniformLocationARB(programId, "diffuseColor[0]"),
132             glGetUniformLocationARB(programId, "diffuseColor[1]"),
133             glGetUniformLocationARB(programId, "diffuseColor[2]")
134         };
135         // Color of the specular (high-lights) part of the light.
136         int[] specularColor = new int[]{
137             glGetUniformLocationARB(programId, "specularColor[0]"),
138             glGetUniformLocationARB(programId, "specularColor[1]"),
139             glGetUniformLocationARB(programId, "specularColor[2]")
140         };
141         // Exponent controlling the size of the specular light.
142         int[] shininess = new int[]{
143             glGetUniformLocationARB(programId, "shininess[0]"),
144             glGetUniformLocationARB(programId, "shininess[1]"),
145             glGetUniformLocationARB(programId, "shininess[2]")
146         };
147 
148         // Configure the three light sources.
149         glUseProgramObjectARB(programId);
150         glUniform3fARB(lightDirection[0], -0.9f, 0.9f, 1f);
151         glUniform4fARB(diffuseColor[0], 0.7f, 0.7f, 0.7f, 1.0f);
152         glUniform4fARB(specularColor[0], 1.0f, 1.0f, 1.0f, 1.0f);
153         glUniform1fARB(shininess[0], 50.0f);
154         glUniform3fARB(lightDirection[1], 0.6f, 0.1f, 1f);
155         glUniform4fARB(diffuseColor[1], 0.1f, 0.1f, 0.1f, 1.0f);
156         glUniform4fARB(specularColor[1], 0.0f, 0.0f, 0.0f, 1.0f);
157         glUniform1fARB(shininess[1], 50.0f);
158         glUniform3fARB(lightDirection[2], 0.0f, 0.8f, -1f);
159         glUniform4fARB(diffuseColor[2], 0.8f, 0.8f, 0.8f, 1.0f);
160         glUniform4fARB(specularColor[2], 0.0f, 0.0f, 0.0f, 1.0f);
161         glUniform1fARB(shininess[2], 50.0f);
162 
163         // Some more openGL setup.
164         glFrontFace(GL_CCW);
165         glCullFace(GL_BACK);
166         glEnable(GL_CULL_FACE);
167         glEnable(GL_DEPTH_TEST);
168         glMatrixMode(GL_PROJECTION);
169         glLoadIdentity();
170         glOrtho(-1, 1, -1, 1, -100, 100);
171         glEnableClientState(GL_VERTEX_ARRAY);
172         glEnableClientState(GL_NORMAL_ARRAY);
173 
174         // Tell openGL to use the buffers we filled with the data from
175         // the PLY file.
176         glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexBufferId);
177         glVertexPointer(3, GL11.GL_FLOAT, 24, 0);
178         glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertexBufferId);
179         glNormalPointer(GL11.GL_FLOAT, 24, 12);
180 
181         // Find out to where we need to move the object and how we need
182         // to scale it such that it fits into the window.
183         double scale = bounds.getScaleToUnityBox() * 1.5;
184         double[] center = bounds.getCenter();
185 
186         // Some state variables to let the model rotate via mouse 
187         double yawStart = 0;
188         double pitchStart = 0;
189         boolean mouseMoves = false;
190         int mouseStartX = 0;
191         int mouseStartY = 0;
192 
193         // See if we've run into a problem so far.
194         Util.checkGLError();
195 
196         // Rendering loop until the window is clsoed.
197         while (!Display.isCloseRequested()) {
198 
199             // Empty the buffers
200             glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
201 
202             // Mouse handling for the model rotation
203             double yaw = yawStart;
204             double pitch = pitchStart;
205             if (mouseMoves) {
206                 double deltaX = Mouse.getX() - mouseStartX;
207                 double deltaY = Mouse.getY() - mouseStartY;
208                 double deltaYaw = deltaX * 0.3;
209                 double deltaPitch = -deltaY * 0.3;
210                 yaw += deltaYaw;
211                 pitch += deltaPitch;
212                 if (!Mouse.isButtonDown(0)) {
213                     mouseMoves = false;
214                     yawStart += deltaYaw;
215                     pitchStart += deltaPitch;
216                 }
217             } else if (!mouseMoves && Mouse.isButtonDown(0)) {
218                 mouseStartX = Mouse.getX();
219                 mouseStartY = Mouse.getY();
220                 mouseMoves = true;
221             }
222 
223             // Build the model matrix that fits the model into the
224             // screen and does the rotation.
225             glMatrixMode(GL_MODELVIEW);
226             glLoadIdentity();
227             glRotated(pitch, 1, 0, 0);
228             glRotated(yaw, 0, 1, 0);
229             glScaled(scale, scale, scale);
230             glTranslated(-center[0], -center[1], -center[2]);
231 
232             // Finally we can draw the model!
233             glDrawElements(GL_TRIANGLES, triangleCount * 3, GL_UNSIGNED_INT, 0);
234 
235             // See if we've run into a problem.
236             Util.checkGLError();
237 
238             // Lets show what we just have drawn.
239             Display.update();
240         }
241 
242         // Cleanup opengl.
243         Display.destroy();
244     }
245 
246     /**
247      * Loads the data from the PLY file into the buffers.
248      */
249     private static RectBounds fillBuffers(
250             PlyReader plyReader,
251             FloatBuffer vertexBuffer,
252             IntBuffer indexBuffer) throws IOException {
253 
254         // Get all element readers and find the two providing 
255         // the vertices and triangles.
256         ElementReader reader = plyReader.nextElementReader();
257         RectBounds bounds = null;
258         while (reader != null) {
259 
260             if (reader.getElementType().getName().equals("vertex")) {
261                 bounds = fillVertexBuffer(reader, vertexBuffer);
262             } else if (reader.getElementType().getName().equals("face")) {
263                 fillIndexBuffer(reader, indexBuffer);
264             }
265             reader.close();
266             reader = plyReader.nextElementReader();
267         }
268         return bounds;
269     }
270 
271     /**
272      * Fill the vertex buffer with the data from the PLY file.
273      */
274     private static RectBounds fillVertexBuffer(
275             ElementReader reader,
276             FloatBuffer vertexBuffer) throws IOException {
277 
278         // Just go though the vertices and store the coordinates in the buffer.
279         Element vertex = reader.readElement();
280         RectBounds bounds = new RectBounds();
281         while (vertex != null) {
282             double x = vertex.getDouble("x");
283             double y = vertex.getDouble("y");
284             double z = vertex.getDouble("z");
285             double nx = vertex.getDouble("nx");
286             double ny = vertex.getDouble("ny");
287             double nz = vertex.getDouble("nz");
288             vertexBuffer.put((float) x);
289             vertexBuffer.put((float) y);
290             vertexBuffer.put((float) z);
291             vertexBuffer.put((float) nx);
292             vertexBuffer.put((float) ny);
293             vertexBuffer.put((float) nz);
294             bounds.addPoint(x, y, z);
295             vertex = reader.readElement();
296         }
297         return bounds;
298     }
299 
300     /**
301      * Fill the index buffer with the data from the PLY file.
302      */
303     private static void fillIndexBuffer(
304             ElementReader reader,
305             IntBuffer indexBuffer) throws IOException {
306 
307         // Just go though the triangles and store the indices in the buffer.
308         Element triangle = reader.readElement();
309         while (triangle != null) {
310 
311             int[] indices = triangle.getIntList("vertex_index");
312             for (int index : indices) {
313                 indexBuffer.put(index);
314             }
315 
316             triangle = reader.readElement();
317         }
318     }
319 
320     /**
321      * Loads the vertex and fragment shader.
322      */
323     private static int createShaders() throws IOException {
324 
325         // load and compile the vertex shader.
326         String vertexShaderCode = IOUtils.toString(
327                 ClassLoader.getSystemResourceAsStream("vertexShader.glsl"));
328         int vertexShaderId = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
329         glShaderSourceARB(vertexShaderId, vertexShaderCode);
330         glCompileShaderARB(vertexShaderId);
331         printLogInfo(vertexShaderId);
332 
333         // load and compile the fragment shader.
334         String fragmentShaderCode = IOUtils.toString(
335                 ClassLoader.getSystemResourceAsStream("fragmentShader.glsl"));
336         int fragmentShaderId = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
337         glShaderSourceARB(fragmentShaderId, fragmentShaderCode);
338         glCompileShaderARB(fragmentShaderId);
339         printLogInfo(fragmentShaderId);
340 
341         // combine the two into a program.
342         int programId = ARBShaderObjects.glCreateProgramObjectARB();
343         glAttachObjectARB(programId, vertexShaderId);
344         glAttachObjectARB(programId, fragmentShaderId);
345         glValidateProgramARB(programId);
346         glLinkProgramARB(programId);
347         printLogInfo(programId);
348 
349         return programId;
350     }
351 
352     /**
353      * Helper to get the log messages from the shader compiler.
354      */
355     private static boolean printLogInfo(int obj) {
356         IntBuffer iVal = BufferUtils.createIntBuffer(1);
357         ARBShaderObjects.glGetObjectParameterARB(obj,
358                 ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB, iVal);
359 
360         int length = iVal.get();
361         if (length > 1) {
362             // We have some info we need to output.
363             ByteBuffer infoLog = BufferUtils.createByteBuffer(length);
364             iVal.flip();
365             ARBShaderObjects.glGetInfoLogARB(obj, iVal, infoLog);
366             byte[] infoBytes = new byte[length];
367             infoLog.get(infoBytes);
368             String out = new String(infoBytes);
369             System.out.println("Info log:\n" + out);
370         } else {
371             return true;
372         }
373         return false;
374     }
375 }