glitter Example: Mesh Viewer

Summary

This program will open a GLUT window and render a mesh from an HDF5 data file.

Front matter

Module docstring

The module docstring is used as a description of this example in the generated documentation:

"""Simple mesh viewer.

@author: Stephan Wenger
@date: 2012-02-29
"""

Imports

Our scene is going to rotate. The rotating modelview matrix is computed using the sine and cosine functions from the math module:

from math import sin, cos, pi

glitter uses numpy for representation of array data. We will use numpy's random() function to generate random textures:

from numpy.random import random

We need to read a mesh filename from sys.argv, so import sys.

import sys

We assume the mesh is stored in a HDF5 file, so import h5py.

import h5py

We can usually import classes and functions contained in glitter submodules directly from glitter:

from glitter import VertexArray, State, get_default_program

Modules with external dependencies other than numpy, such as platform dependent parts like methods for the generation of an OpenGL context, however, have to be imported from their respective submodules:

from glitter.contexts.glut import GlutWindow, main_loop, get_elapsed_time

Main class

We wrap all the OpenGL interaction in a class. The class will contain an __init__() method to set up all OpenGL objects, any required callback methods, as well as a run() method to trigger execution of the GLUT main loop.

class MeshViewer(object):

Initialization

When a MeshViewer instance is created, we need to initialize a few OpenGL objects.

    def __init__(self):

First, we create a window; this also creates an OpenGL context.

        self.window = GlutWindow(double=True, multisample=True)

Then, we set the GLUT display callback function which will be defined later.

        self.window.display_callback = self.display

In the OpenGL core profile, there is no such thing as a "standard pipeline" any more. We use the minimalistic defaultpipeline from the glitter.convenience module to create a shader program instead:

        self.shader = get_default_program()

We open the HDF5 file specified on the command line for reading:

        with h5py.File(sys.argv[1], "r") as f:

The vertices, colors and indices of the mesh are read from the corresponding datasets in the HDF5 file. Note that the names of the datasets are mere convention. Colors and indices are allowed to be undefined.

            vertices = f["vertices"]
            colors = f.get("colors", None)
            elements = f.get("indices", None)

If no colors were specified, we generate random ones so we can distinguish the triangles without fancy shading.

            if colors is None:
                colors = random((len(vertices), 3))[:, None, :][:, [0] * vertices.shape[1], :]

Here, we create a vertex array that contains buffers for two vertex array input variables as well as an index array. If elements is None, the vertex array class will draw all vertices in order.

            self.vao = VertexArray(vertices, colors, elements=elements)

Callback functions

Display function

Here we define the display function. It will be called by GLUT whenever the screen has to be redrawn.

    def display(self):

First we clear the default framebuffer:

        self.window.clear()

To draw the vertex array, we use:

        self.vao.draw()

After all rendering commands have been issued, we swap the back buffer to the front, making the rendered image visible all at once:

        self.window.swap_buffers()

Timer function

The animation is controlled by a GLUT timer. The timer callback changes the modelview matrix, schedules the next timer event, and causes a screen redraw:

    def timer(self):

We first get the elapsed time from GLUT using get_elapsed_time():

        phi = 2 * pi * get_elapsed_time() / 20.0

We then set the modelview_matrix uniform variable of the shader simply by setting an attribute:

        self.shader.modelview_matrix = ((cos(phi), 0, sin(phi), 0), (0, 1, 0, 0), (-sin(phi), 0, cos(phi), 0), (0, 0, 0, 1))

The following line schedules the next timer event to execute after ten milliseconds.

        self.window.add_timer(10, self.timer)

Finally, we tell GLUT to redraw the screen.

        self.window.post_redisplay()

Running

We will call the run() method later to run the OpenGL code.

    def run(self):

To start the animation, we call the timer once; all subsequent timer calls will be scheduled by the timer function itself.

        self.timer()

The shader program is bound by using a with statement:

        with self.shader:

The State class encapsulates state changes in the context. For example, to enable depth testing for the duration of the following function call, we would write:

            with State(depth_test=True):

With the shader bound and depth testing enabled, we enter the GLUT main loop.

                main_loop()

When the main loop exits, control is handed back to the script.

Main section

Finally, if this program is being run from the command line, we instanciate the main class and run it.

if __name__ == "__main__":
    MeshViewer().run()