140 lines
7.1 KiB
ReStructuredText
140 lines
7.1 KiB
ReStructuredText
***************
|
||
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 <http://swig.org>`_ 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
|
||
|
||
.. sourcecode:: c++
|
||
|
||
PolyVox::Vector3D<int32_t> my_vec(0,1,4);
|
||
|
||
but in Python it would be accessed as
|
||
|
||
.. sourcecode:: python
|
||
|
||
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 :doc:`install`.
|
||
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.
|
||
|
||
.. literalinclude:: ../examples/Python/PythonExample.py
|
||
:language: python
|
||
:lines: 29
|
||
|
||
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:`PolyVox::Vector` templated on a 32-bit integer.
|
||
|
||
The second line creates a :polyvox:`SimpleVolume` of the same size as the :polyvox:`Region` where each voxel in the volume is defined an unsigned 8-bit integer.
|
||
|
||
.. literalinclude:: ../examples/Python/PythonExample.py
|
||
:language: python
|
||
:lines: 31-33
|
||
|
||
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.
|
||
|
||
.. literalinclude:: ../examples/Python/PythonExample.py
|
||
:language: python
|
||
:lines: 35-37
|
||
|
||
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.
|
||
|
||
.. literalinclude:: ../examples/Python/PythonExample.py
|
||
:language: python
|
||
:lines: 38-41
|
||
|
||
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``.
|
||
|
||
.. literalinclude:: ../examples/Python/PythonExample.py
|
||
:language: python
|
||
:lines: 42-53
|
||
|
||
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.
|
||
|
||
.. literalinclude:: ../examples/Python/PythonExample.py
|
||
:language: python
|
||
:lines: 55-58
|
||
|
||
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 <http://pyopengl.sourceforge.net/>`_ 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 <http://numpy.org>`_ arrays and so we are going to copy our vertex data into two of these.
|
||
:polyvox:`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.
|
||
|
||
.. literalinclude:: ../examples/Python/PythonExample.py
|
||
:language: python
|
||
:lines: 62-69
|
||
|
||
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.
|