Python bindings

Introduction

PolyVox itself is a C++ library but in order to make it useful to as many people as possible, we also provide bindings to a number of other languages. These bindings are all generated by SWIG and provide a bridge to a compiled PolyVox library (DLL or .so) via an interafce which is native to the language. This allows you in a Python script to simpy call import PolyVoxCore and get access to the whole library’s functionality.

The Python bindings are available for Python 3.

Comparison with C++

All the bindings available for PolyVox (so both the Python bindings and any future supported bindings such as for C♯) follow the PolyVox C++ API quite closely. This means that many PolyVox code examples written in C++ are mostly applicable also to Python. Classes and functions are named the same and take the same set of arguments. The main place this falls down is with templated C++ classes. Since C++ templates are essentially a code-generation system built into the C++ compiler there is no way for a user of the Python bindings to request, at run-time, a specific piece of code to be generated. The way we work around this is by, as part of the bindings generation process, pre-compiling a number of different versions of each templated class. For example, in C++ a 3D vector containing 32-bit integers would be declared as

PolyVox::Vector3D<int32_t> my_vec(0,1,4);

but in Python it would be accessed as

my_vec = PolyVoxCore.Vector3Dint32_t(0,1,4)

As a rule, any time in C++ where you see a template instantiation with <>, just remove the angle brackets and it will yield the Python class name.

The choice of which C++ templates to instantiate is made by the developers of PolyVox in order to try to cover the main use-cases that library users would have. If, however, you want to add your own versions, this can be done by editing the SWIG interface files and recompiling PolyVox.

Buildings the bindings

The bindings are built as part of the standard PolyVox build process. For details on this please follow the instructions at Building PolyVox. The important things to note there are the requirements for building the bindings: Python development libraries and SWIG. During the CMake phase of building, it should tell you whether or not the bindings will be built.

Compiling the whole PolyVox library should then give you two files inside the build/library/bindings directory:

PolyVoxCore.py
This is the main entry point to the library.
_PolyVoxCore.so (or .dll on Windows)

This contains the compiled code of the PolyVox library.

This file has a link dependency the main libPolyVoxCore.so library as well as the Python shared library.

Using the bindings

As discussed above, the Python API is very similar to the C++ one but none-the-less we’ll go through an example to see how it works. All the code in this section is taken from PythonExample.py found in the source distribution of PolyVox in the examples/Python folder.

Seting up the volume

The first this we do is import the PolyVoxCore module. We rename it as pv to make our life easier.

import PolyVoxCore as pv

We create a Region from two vectors defining the bounds of the area - a volume 64×64×64. Remember that pv.Vector3Dint32_t refers to a 3D PolyVox::Vector templated on a 32-bit integer.

The second line creates a SimpleVolume of the same size as the Region where each voxel in the volume is defined an unsigned 8-bit integer.

#Create a 64x64x64 volume of integers
r = pv.Region(pv.Vector3Dint32_t(0,0,0), pv.Vector3Dint32_t(63,63,63))
vol = pv.SimpleVolumeuint8(r)

We’re going to fill our volume with a sphere and so we start by finding out where the centre of the volume is and defining the radius of our desired shape.

#Now fill the volume with our data (a sphere)
v3dVolCenter = pv.Vector3Dint32_t(vol.getWidth() / 2, vol.getHeight() / 2, vol.getDepth() / 2)
sphereRadius = 30

Then we actually loop over each of the dimensions of the volume such that inside the loop, x, y and z refer to the current location.

#This three-level for loop iterates over every voxel in the volume
for z in range(vol.getDepth()):
	for y in range(vol.getHeight()):
		for x in range(vol.getWidth()):

All we do inside the loop is set all the voxels inside the sphere to have a value of 255 and all those outside to have a value of 0.

			#Compute how far the current position is from the center of the volume
			fDistToCenter = (pv.Vector3Dint32_t(x,y,z) - v3dVolCenter).length()
			
			#If the current voxel is less than 'radius' units from the center then we make it solid.
			if(fDistToCenter <= sphereRadius):
				#Our new voxel value
				uVoxelValue = 255
			else:
				uVoxelValue = 0
			
			#Write the voxel value into the volume
			vol.setVoxelAt(x, y, z, uVoxelValue);

Getting the mesh data

Extracting the surface mesh for the volume is a two-step process. First we tell PolyVox to generate the appropriate mesh, then we have to convert the PolyVox mesh data to something that our rendering library can understand.

First we define the sort of mesh that we want. For this example we want a mesh with information on the position, material and normal of each vertex. Once we have out mesh object ready to be filled, we pass it to our surface extractor of choice. PolyVox comes with a number of different surface extractors but for our example here, we want a cubic mesh.

You should also note that the ungainly looking CubicSurfaceExtractorWithNormalsSimpleVolumeuint8 refers to the C++ class CubicSurfaceExtractorWithNormals<SimpleVolume<uint8_t>>. The execute() call is when PolyVox actually goes off and generates the requested mesh based on the data contained in the volume.

#Create a mesh, pass it to the extractor and generate the mesh
mesh = pv.SurfaceMeshPositionMaterialNormal()
extractor = pv.CubicSurfaceExtractorWithNormalsSimpleVolumeuint8(vol, r, mesh)
extractor.execute()

Up until this point, the Python code has been totally generic with respect to your choice of rendering engine. For this example we will be using PyOpenGL as it provides a nice pythonic API for many OpenGL functions.

Regardless of which rendering engine you are using, you will need to be able to wrangle the PolyVox mesh output into something you can insert into your engine. In our case, we want two lists:

  1. All the vertices along with their normals
  2. The vertex indices which describes how the vertices are put together to make triangles.

PyOpenGL undersands NumPy arrays and so we are going to copy our vertex data into two of these. SurfaceMesh provides two useful functions here, getIndices() and getVertices(), the Python versions of which return Python tuples.

The indices we can pass directly to NumPy as long as we make sure we specify the correct type for the data inside. For the vertices, we want to rearange the data so that OpenGL can read it more efficiently. To this end we explicitly retrieve the vertex positions and normals for each vertex and place them such that the vertex x, y and z positions are placed contiguously in memory followed by the normal vector’s x, y and z values.

import numpy as np

indices = np.array(mesh.getIndices(), dtype='uint32') #Throw in the vertex indices into an array
#The vertices and normals are placed in an interpolated array like [vvvnnn,vvvnnn,vvvnnn]
vertices = np.array([[vertex.getPosition().getX(), vertex.getPosition().getY(), vertex.getPosition().getZ(),
                      vertex.getNormal().getX(), vertex.getNormal().getY(), vertex.getNormal().getZ()]
                      for vertex in mesh.getVertices()],
                    dtype='f')

From this point on in the example, PolyVox is no longer used directly and all the code is standard PyOpenGL. I won’t go through every line here but the source code in PythonExample.py is commented and should be sufficient to understand how things work.

Table Of Contents

Previous topic

Tutorial 1 - Basic use

Next topic

Changelog

This Page