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()