org.smurn.jply.lwjgldemo.LWJGLDemo.java Source code

Java tutorial

Introduction

Here is the source code for org.smurn.jply.lwjgldemo.LWJGLDemo.java

Source

/*
 * Copyright 2011 Stefan C. Mueller.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.smurn.jply.lwjgldemo;

import org.smurn.jply.Element;
import java.nio.FloatBuffer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.apache.commons.io.IOUtils;
import org.lwjgl.BufferUtils;
import org.smurn.jply.PlyReader;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ARBShaderObjects;
import static org.lwjgl.opengl.ARBVertexBufferObject.*;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.Util;
import org.smurn.jply.ElementReader;
import org.smurn.jply.PlyReaderFile;
import org.smurn.jply.util.NormalMode;
import org.smurn.jply.util.NormalizingPlyReader;
import org.smurn.jply.util.TesselationMode;
import org.smurn.jply.util.TextureMode;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.ARBShaderObjects.*;
import static org.lwjgl.opengl.ARBVertexShader.*;
import static org.lwjgl.opengl.ARBFragmentShader.*;
import org.lwjgl.input.Mouse;

/**
 * jPLY Demo: jPLY viewer using JWJGL.
 * 
 * This example is using state-of-the-art vertex-buffer-objects and
 * GLSL shaders instead of the immediate mode rendering often seen in 
 * examples. This makes the code a bit more complicated but its the way
 * this things are done in real-world applications.
 * 
 * The code doesn't perform proper error checking and will just fail if
 * run on a computer with an old graphics-card (or old drivers). This way
 * the code remains much more readable and simple.
 */
public class LWJGLDemo {

    public static void main(String[] args) throws LWJGLException, IOException {

        // Open a openGL enabled window.
        Display.setTitle("jPLY LWJGL Demo");
        Display.setDisplayMode(new DisplayMode(600, 600));
        Display.create();

        // Open the PLY file
        PlyReader plyReader = new PlyReaderFile(ClassLoader.getSystemResourceAsStream("bunny.ply"));

        // Normalize the data in the PLY file to ensure that we only get
        // triangles and that the vertices have normal vectors assigned.
        plyReader = new NormalizingPlyReader(plyReader, TesselationMode.TRIANGLES, NormalMode.ADD_NORMALS_CCW,
                TextureMode.PASS_THROUGH);

        // We can get the number of vertices and triangles before
        // reading it. We will use this to allocate buffers of the right size.
        int vertexCount = plyReader.getElementCount("vertex");
        int triangleCount = plyReader.getElementCount("face");

        // Number of bytes we need per vertex.
        // Three position coordinates and three normal vector coordinates
        // all stored as 32-bit float.
        int vertexSize = 3 * 4 + 3 * 4;

        // Number of bytes we need per triangle.
        // Three indices to vertices stored as 32-bit integer.
        int triangleSize = 3 * 4;

        // A bit of opengl magic to allocate a vertex-buffer-object to
        // store the vertices.
        int vertexBufferId = glGenBuffersARB();
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertexBufferId);
        glBufferDataARB(GL_ARRAY_BUFFER_ARB, vertexCount * vertexSize, GL_STATIC_DRAW_ARB);
        FloatBuffer vertexBuffer = glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB, null).asFloatBuffer();

        // The same magic again for the index buffer storing the triangles.
        int indexBufferId = glGenBuffersARB();
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexBufferId);
        glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, triangleCount * triangleSize, GL_STATIC_DRAW_ARB);
        IntBuffer indexBuffer = glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB, null).asIntBuffer();

        // Now we READ THE PLY DATA into the buffers.
        // We also measure the size of the model so that we can later fit
        // arbitrary models into the screen.
        RectBounds bounds = fillBuffers(plyReader, vertexBuffer, indexBuffer);

        // Tell openGL that we filled the buffers.
        glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
        glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB);

        // Create the vertex and fragment shader for nice phong shading.
        int programId = createShaders();

        // The shaders have a few parameters to configure the three lights.
        // Each parameter has an identifier which we query here.

        // Vector in the direction towards the light.
        int[] lightDirection = new int[] { glGetUniformLocationARB(programId, "lightDir[0]"),
                glGetUniformLocationARB(programId, "lightDir[1]"),
                glGetUniformLocationARB(programId, "lightDir[2]") };
        // Color of the diffuse part of the light.
        int[] diffuseColor = new int[] { glGetUniformLocationARB(programId, "diffuseColor[0]"),
                glGetUniformLocationARB(programId, "diffuseColor[1]"),
                glGetUniformLocationARB(programId, "diffuseColor[2]") };
        // Color of the specular (high-lights) part of the light.
        int[] specularColor = new int[] { glGetUniformLocationARB(programId, "specularColor[0]"),
                glGetUniformLocationARB(programId, "specularColor[1]"),
                glGetUniformLocationARB(programId, "specularColor[2]") };
        // Exponent controlling the size of the specular light.
        int[] shininess = new int[] { glGetUniformLocationARB(programId, "shininess[0]"),
                glGetUniformLocationARB(programId, "shininess[1]"),
                glGetUniformLocationARB(programId, "shininess[2]") };

        // Configure the three light sources.
        glUseProgramObjectARB(programId);
        glUniform3fARB(lightDirection[0], -0.9f, 0.9f, 1f);
        glUniform4fARB(diffuseColor[0], 0.7f, 0.7f, 0.7f, 1.0f);
        glUniform4fARB(specularColor[0], 1.0f, 1.0f, 1.0f, 1.0f);
        glUniform1fARB(shininess[0], 50.0f);
        glUniform3fARB(lightDirection[1], 0.6f, 0.1f, 1f);
        glUniform4fARB(diffuseColor[1], 0.1f, 0.1f, 0.1f, 1.0f);
        glUniform4fARB(specularColor[1], 0.0f, 0.0f, 0.0f, 1.0f);
        glUniform1fARB(shininess[1], 50.0f);
        glUniform3fARB(lightDirection[2], 0.0f, 0.8f, -1f);
        glUniform4fARB(diffuseColor[2], 0.8f, 0.8f, 0.8f, 1.0f);
        glUniform4fARB(specularColor[2], 0.0f, 0.0f, 0.0f, 1.0f);
        glUniform1fARB(shininess[2], 50.0f);

        // Some more openGL setup.
        glFrontFace(GL_CCW);
        glCullFace(GL_BACK);
        glEnable(GL_CULL_FACE);
        glEnable(GL_DEPTH_TEST);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(-1, 1, -1, 1, -100, 100);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);

        // Tell openGL to use the buffers we filled with the data from
        // the PLY file.
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indexBufferId);
        glVertexPointer(3, GL11.GL_FLOAT, 24, 0);
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertexBufferId);
        glNormalPointer(GL11.GL_FLOAT, 24, 12);

        // Find out to where we need to move the object and how we need
        // to scale it such that it fits into the window.
        double scale = bounds.getScaleToUnityBox() * 1.5;
        double[] center = bounds.getCenter();

        // Some state variables to let the model rotate via mouse 
        double yawStart = 0;
        double pitchStart = 0;
        boolean mouseMoves = false;
        int mouseStartX = 0;
        int mouseStartY = 0;

        // See if we've run into a problem so far.
        Util.checkGLError();

        // Rendering loop until the window is clsoed.
        while (!Display.isCloseRequested()) {

            // Empty the buffers
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // Mouse handling for the model rotation
            double yaw = yawStart;
            double pitch = pitchStart;
            if (mouseMoves) {
                double deltaX = Mouse.getX() - mouseStartX;
                double deltaY = Mouse.getY() - mouseStartY;
                double deltaYaw = deltaX * 0.3;
                double deltaPitch = -deltaY * 0.3;
                yaw += deltaYaw;
                pitch += deltaPitch;
                if (!Mouse.isButtonDown(0)) {
                    mouseMoves = false;
                    yawStart += deltaYaw;
                    pitchStart += deltaPitch;
                }
            } else if (!mouseMoves && Mouse.isButtonDown(0)) {
                mouseStartX = Mouse.getX();
                mouseStartY = Mouse.getY();
                mouseMoves = true;
            }

            // Build the model matrix that fits the model into the
            // screen and does the rotation.
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            glRotated(pitch, 1, 0, 0);
            glRotated(yaw, 0, 1, 0);
            glScaled(scale, scale, scale);
            glTranslated(-center[0], -center[1], -center[2]);

            // Finally we can draw the model!
            glDrawElements(GL_TRIANGLES, triangleCount * 3, GL_UNSIGNED_INT, 0);

            // See if we've run into a problem.
            Util.checkGLError();

            // Lets show what we just have drawn.
            Display.update();
        }

        // Cleanup opengl.
        Display.destroy();
    }

    /**
     * Loads the data from the PLY file into the buffers.
     */
    private static RectBounds fillBuffers(PlyReader plyReader, FloatBuffer vertexBuffer, IntBuffer indexBuffer)
            throws IOException {

        // Get all element readers and find the two providing 
        // the vertices and triangles.
        ElementReader reader = plyReader.nextElementReader();
        RectBounds bounds = null;
        while (reader != null) {

            if (reader.getElementType().getName().equals("vertex")) {
                bounds = fillVertexBuffer(reader, vertexBuffer);
            } else if (reader.getElementType().getName().equals("face")) {
                fillIndexBuffer(reader, indexBuffer);
            }
            reader.close();
            reader = plyReader.nextElementReader();
        }
        return bounds;
    }

    /**
     * Fill the vertex buffer with the data from the PLY file.
     */
    private static RectBounds fillVertexBuffer(ElementReader reader, FloatBuffer vertexBuffer) throws IOException {

        // Just go though the vertices and store the coordinates in the buffer.
        Element vertex = reader.readElement();
        RectBounds bounds = new RectBounds();
        while (vertex != null) {
            double x = vertex.getDouble("x");
            double y = vertex.getDouble("y");
            double z = vertex.getDouble("z");
            double nx = vertex.getDouble("nx");
            double ny = vertex.getDouble("ny");
            double nz = vertex.getDouble("nz");
            vertexBuffer.put((float) x);
            vertexBuffer.put((float) y);
            vertexBuffer.put((float) z);
            vertexBuffer.put((float) nx);
            vertexBuffer.put((float) ny);
            vertexBuffer.put((float) nz);
            bounds.addPoint(x, y, z);
            vertex = reader.readElement();
        }
        return bounds;
    }

    /**
     * Fill the index buffer with the data from the PLY file.
     */
    private static void fillIndexBuffer(ElementReader reader, IntBuffer indexBuffer) throws IOException {

        // Just go though the triangles and store the indices in the buffer.
        Element triangle = reader.readElement();
        while (triangle != null) {

            int[] indices = triangle.getIntList("vertex_index");
            for (int index : indices) {
                indexBuffer.put(index);
            }

            triangle = reader.readElement();
        }
    }

    /**
     * Loads the vertex and fragment shader.
     */
    private static int createShaders() throws IOException {

        // load and compile the vertex shader.
        String vertexShaderCode = IOUtils.toString(ClassLoader.getSystemResourceAsStream("vertexShader.glsl"));
        int vertexShaderId = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
        glShaderSourceARB(vertexShaderId, vertexShaderCode);
        glCompileShaderARB(vertexShaderId);
        printLogInfo(vertexShaderId);

        // load and compile the fragment shader.
        String fragmentShaderCode = IOUtils.toString(ClassLoader.getSystemResourceAsStream("fragmentShader.glsl"));
        int fragmentShaderId = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
        glShaderSourceARB(fragmentShaderId, fragmentShaderCode);
        glCompileShaderARB(fragmentShaderId);
        printLogInfo(fragmentShaderId);

        // combine the two into a program.
        int programId = ARBShaderObjects.glCreateProgramObjectARB();
        glAttachObjectARB(programId, vertexShaderId);
        glAttachObjectARB(programId, fragmentShaderId);
        glValidateProgramARB(programId);
        glLinkProgramARB(programId);
        printLogInfo(programId);

        return programId;
    }

    /**
     * Helper to get the log messages from the shader compiler.
     */
    private static boolean printLogInfo(int obj) {
        IntBuffer iVal = BufferUtils.createIntBuffer(1);
        ARBShaderObjects.glGetObjectParameterARB(obj, ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB, iVal);

        int length = iVal.get();
        if (length > 1) {
            // We have some info we need to output.
            ByteBuffer infoLog = BufferUtils.createByteBuffer(length);
            iVal.flip();
            ARBShaderObjects.glGetInfoLogARB(obj, iVal, infoLog);
            byte[] infoBytes = new byte[length];
            infoLog.get(infoBytes);
            String out = new String(infoBytes);
            System.out.println("Info log:\n" + out);
        } else {
            return true;
        }
        return false;
    }
}