In this document you will learn to
Array math is used throughout the OpenGLContext project, and you will likely want to make use of it in your own code. This document focuses primarily on the array package itself, rather than on PyOpenGL. Mastering the features shown here will be useful when doing non-trivial programming in PyOpenGL, but it's not necessary to know this material before you start playing with OpenGLContext.
NumPy makes it easy to work with (potentially large) homogenous arrays of a given data type. It provides extended slicing semantics for dealing with multidimensional arrays, utility functions for processing all elements of an array, and mechanisms for storing and converting to/from arrays and native Python data structures.
The house style is import numpy as np, so that a
reader can see at a glance which names are the array package's.
OpenGLContext.arrays re-exports the same names for the modules
that use them unqualified.
Creating an array is done with one of a set of functions, each of which has a particular purpose (note, these are just the commonly used functions):
| zeros, ones | Generate an array filled with either 0 or 1 values of a given size and data type. |
np.zeros((2, 3), 'd') |
|---|---|---|
| array, asarray | Convert a sequence (often a
nested sequence) into an equivalently structured array of a given data
type (or of an automatically determined type if none is
specified).
asarray skips copying if the passed argument is already an array of the appropriate type. |
np.array([[2, 3, 4], [5, 6, 7]], 'i') |
| identity | Generate an x by x integer identity matrix for the given x. |
np.identity(4) |
| arange | An advanced form of the standard Python range, allowing for floating point ranges as well as integer ranges, generating an array as the result. |
np.arange(3.0, 0.0, -0.1, 'd') |
A data type can be given as a NumPy dtype, as its character code, or as a Python type. For OpenGL work these are the ones that matter, with the GL type each corresponds to:
| dtype | Code | OpenGL type |
|---|---|---|
np.float64 |
'd' |
GL_DOUBLE |
np.float32 |
'f' |
GL_FLOAT |
np.int32 |
'i' |
GL_INT |
np.uint32 |
'I' |
GL_UNSIGNED_INT |
np.int16 |
'h' |
GL_SHORT |
np.uint16 |
'H' |
GL_UNSIGNED_SHORT |
np.int8 |
'b' |
GL_BYTE |
np.uint8 |
'B' |
GL_UNSIGNED_BYTE |
Prefer the explicitly sized names. np.int_
and plain int are platform-width and are not the same type on every
machine, which is exactly the kind of difference a buffer handed to the driver
notices. Unsigned indices matter in particular: element-array buffers are
np.uint16 or np.uint32, never signed.
Array dimensions are specified as tuples, in the order of
indexing. The current shape of an array is its shape
attribute. You can use reshape, resize,
transpose and ravel to get an array with different
dimensions — usually a view onto the same memory rather than a copy, so
writing through one changes the other. Most NumPy functions also take an
out= argument naming where to put the result, which is how the
inner loops in this project avoid allocating per frame.
Mathematical operators tend to work element-wise for arrays, and allow for a fairly wide variety of second arguments.
For instance, to multiply an entire array by 3.0,
you would write result = myarray * 3.0 with
similar approaches for addition, subtraction, and division.
If, instead, you wanted to multiply each element in one array
by the corresponding element in another array, you would write result
= firstarray * secondarray again, with the same
approach working for most basic math operations.
NumPy uses an extended slicing notation, which allows for flexible and powerful manipulation of multidimensional arrays (though at the price of making the slice specifications somewhat less intuitive looking).
The syntax for the extended slicing notation goes like this:
[ start : stop : step, start : stop : step, ... ]
with one start, stop, step set allowed (but not required beyond the first) for each dimension of the array. The step argument and the preceding colon can be left off unless actually needed. As with standard slicing notation, if you leave off a start value, zero is assumed, and if you leave off a stop value, the end of the array is assumed.
Generally it is easiest to understand the slicing notation through examples. Consider this interactive session:
>>> m = array( [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], 'd')
>>> m
array([[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[ 13., 14., 15., 16.]])
# item 0 of the array in first dimension
>>> m[0]
array([ 1., 2., 3., 4.])
# item 1 of the array in first dimension
>>> m[1]
array([ 5., 6., 7., 8.])
# item 0 of the array in second dimension
>>> m[:,0]
array([ 1., 5., 9., 13.])
# item 1 of the array in second dimension
>>> m[:,1]
array([ 2., 6., 10., 14.])
# all items in first dimension taking every
# second item
>>> m[::2]
array([[ 1., 2., 3., 4.],
[ 9., 10., 11., 12.]])
# as previous, but now take item 1 in the
# second dimension for each row
>>> m[::2,1]
array([ 2., 10.])
# as previous, but starting the slice of first dimension
# at the second item (i.e. take 1 and 3 instead of 0 and 2)
>>> m[1::2,1]
array([ 6., 14.])
>>>
Common slices you'll see include:
# x-coordinate of an i*3 array
xes = points[:,0]
yes = points[:,1]
# first points of i*3 set of points describing triangles
firstPoints = trianglePoints[::3]
secondPoints = trianglePoints[1::3]
# r-values of an x*y*3 r,g,b image
reds = image[:,:,0]
greens = image[:,:,1]
There are three different types of multiplication commonly used in OpenGLContext. Element-wise multiplication was covered in Basic Mathematics above. The other two common multiplication types are dot product and cross product. NumPy provides both:
result = np.dot(first, second) result = np.cross(first, second)
np.cross takes 3-item vectors, and takes whole arrays of
them at once. OpenGLContext's own
OpenGLContext.utilities.crossProduct is the 4-item homogeneous
form, which is what the scenegraph's vectors are:
from OpenGLContext.utilities import crossProduct
result = crossProduct((ux, uy, uz, uw), (vx, vy, vz, vw))
When working with arrays of vectors, use
OpenGLContext.vectorutilities.crossProduct, which processes the
whole array at once rather than calling a Python function per vector.
The basic manipulations above are primarily useful for selecting a particular subset of an array for processing, while the array processing functions do the bulk of the calculations in most algorithms. There are far too many to list; the groups below are the ones that come up in this project, and the NumPy routine reference describes each in full.
These function-like objects provide for extremely flexible application of common mathematical operations on arguments of varying type.
absolute, add, arccos, arcsin, arctan, arctan2,
bitwise_and, bitwise_or, bitwise_xor, ceil, conjugate, cos, cosh,
divide, equal, exp, fabs, floor, fmod,
greater, greater_equal, hypot, invert, left_shift, less,
less_equal, log, log10, logical_and, logical_not, logical_or,
logical_xor, maximum, minimum, multiply, negative, not_equal,
power, remainder, right_shift, sin, sinh, sqrt, subtract, tan, tanh
These functions deal with index arrays or indexed arrays, that is, arrays which refer to other arrays, either by having equivalent structure, or including actual indices into the other array. These functions can provide efficient operation by not requiring you to copy the arrays being processed. For an example, see OpenGLContext.scenegraph.polygonsort's use of argsort to generate the indices required for rendering sorted transparent geometry without needing to copying/rearranging the point array.
argmax, argmin, argsort, choose, take, where, put,
putmask, compress
These functions deal with two "data arrays", combining their values in some way to create a new array. The most commonly used function of this group is dot.
convolve, correlate, dot, matmul, outer, inner, sum
These functions produce single value outputs from an entire array, checking, for instance, that the entire array is true, or calculating the sum of the entire array.
all, any, cumprod, cumsum, prod, trace
These functions produce a copy of an array with each value modified by the given function, or the array's ordering/contents modified as dictated by the function's rules of operation.
around, clip, sign, sort, compress
A few operations on the scenegraph's own vector conventions are common
enough to be worth having by name. OpenGLContext.utilities works on
one vector at a time:
rotMatrix((x, y, z, a))normalise(vector)crossProduct(first, second)np.cross is the 3-item form.magnitude(vector)OpenGLContext.vectorutilities is the same operations over
a whole array of vectors at once — crossProduct,
crossProduct4, normalise, magnitude,
orientToXYZR, colinear. Reach for these rather than
looping: they do the work in one array operation instead of one Python call per
vector, which is the whole reason the array package is here.
OpenGL 1.1 introduced a number of geometry-specification mechanisms based on specifying pointers to arrays of data values. The OpenGL implementation was passed the entire array of data, then would loop through the array (normally at hardware speed) dispatching a command for each of those values in the array which were indicated. The user-side code need only make a single call to have potentially thousands of data values passed rendering pipeline.
With C code, the difference between the array-based code and individual calls to transfer data values was noticeable, but it is still quite practical to use the individual calls in many instances. With Python code, however, it is generally impractical to use individual data transfer calls, as the overhead for the API call is enormous compared to the actual work being done with such low level functions.
What follows is the compatibility-profile client-array path, which is what the fixed-function tutorials use. A core profile has no client arrays: the same data goes into a vertex buffer object and is described to a vertex array object, and the array you build in NumPy is identical either way — only the call that hands it over differs. See Core-Profile Rendering.
The basic approach of the array-based geometry is to specify a pointer for a number of different array types: vertex, color, normal and textureCoordinate (potentially multiple coordinates if using multi-texturing).
glVertexPointerd( self.verticies )
glColorPointerd ( self.colours )
glNormalPointerd ( self.normals )
glTexCoordPointerd( self.textures )
This tells the OpenGL engine that these are the active arrays for each of these types. For all of the arrays save texture coordinates, only one active array is possible. Where multi-texturing is enabled (multi-texturing is an OpenGL extension not available on all platforms), you can specify one active texture coordinate array for each texture engine.
Once you have specified the active arrays, you enable or
disable each array type you wish to use for a given call.
Note glEnableClientState, not glEnable: the array
switches are client state, and glEnable will not touch them.
glEnableClientState( GL_VERTEX_ARRAY )
glEnableClientState( GL_COLOR_ARRAY )
glEnableClientState( GL_NORMAL_ARRAY )
glEnableClientState( GL_TEXTURE_COORD_ARRAY )
The array drawing calls such as glDrawArrays and glDrawElements are called in place of a glBegin, glEnd construct (including the glVertex, glColor, glNormal, glTexCoord, etc. calls that normally go between them). The array drawing call will specify the primitive type to draw (the same values as for glBegin), and potentially a subset of the arrays which is to be drawn (for instance, a start position, a count of items to draw, or a list of indices into the arrays which are to be drawn).
glDrawArrays( GL_TRIANGLES, 0, 32 )
glDrawElementsui( GL_TRIANGLES, indices )
For the current array package, see the NumPy documentation and the NumPy beginner's guide.
OpenGLContext.scenegraph.arraygeometry module for a simple implementation of array-based geometry rendering.
OpenGLContext.vectorutilities, OpenGLContext.triangleutilities for examples of array manipulation.
The following programs in the tests directory test array geometry functionality (and therefore include example drawing code): gldrawarrays, gldrawarrays_string, gldrawelements, glarrayelement, glinterleavedarrays.