This section provides code snippets and some complete programs that illustrate how to use OpenVDB and how to perform common tasks.
Contents
"Hello, World" for OpenVDB
This is a very simple example showing how to create a grid and access its voxels. OpenVDB supports both random access to voxels by coordinates and sequential access by means of iterators. This example illustrates both types of access:
#include <openvdb/openvdb.h>
#include <iostream>
int main()
{
std::cout << "Testing random access:" << std::endl;
std::cout <<
"Grid" << xyz <<
" = " << accessor.
getValue(xyz) << std::endl;
xyz.reset(1000, 200000000, -30000000);
std::cout <<
"Grid" << xyz <<
" = " << accessor.
getValue(xyz) << std::endl;
std::cout << "Testing sequential access:" << std::endl;
std::cout << "Grid" << iter.getCoord() << " = " << *iter << std::endl;
}
}
Output:
Testing random access:
Grid[1000, -200000000, 30000000] = 1
Grid[1000, 200000000, -30000000] = 0
Testing sequential access:
Grid[-2147483648, -2147483648, -2147483648] = 3
Grid[1000, -200000000, 30000000] = 1
Grid[1000, 200000000, -30000000] = 2
Grid[2147483647, 2147483647, 2147483647] = 4
Compiling
See the Makefile
and INSTALL
file included in this distribution for details on how to build and install the OpenVDB library. By default, installation is into the directory tree rooted at /tmp/OpenVDB/
, but this can be changed either by editing the value of the INSTALL_DIR
variable in the makefile or by setting the desired value from the command line, as in the following example:
make install INSTALL_DIR=/usr/local
Once OpenVDB has been installed, the simplest way to compile a program like the “Hello, World” example above is to examine the commands that are used to build the vdb_print
tool:
rm vdb_print
make verbose=yes vdb_print
and then replace “-o vdb_print
” with, for example, “-o helloworld
” and “cmd/openvdb_print/main.cc
” with “helloworld.cc
”.
Creating and writing a grid
This example is a complete program that illustrates some of the basic steps to create grids and write them to disk. (See Populating a grid with values, below, for the implementation of the makeSphere()
function.)
#include <openvdb/openvdb.h>
int main()
{
grid->setTransform(
openvdb::math::Transform::createLinearTransform(0.5));
grid->setName("LevelSetSphere");
openvdb::io::File file("mygrids.vdb");
grids.push_back(grid);
file.write(grids);
file.close();
}
The OpenVDB library includes optimized routines for many common tasks. For example, most of the steps given above are encapsulated in the function tools::createLevelSetSphere(), so that the above can be written simply as follows:
#include <openvdb/openvdb.h>
#include <openvdb/tools/LevelSetSphere.h>
int main()
{
openvdb::tools::createLevelSetSphere<openvdb::FloatGrid>(
0.5, 4.0);
grid->setName("LevelSetSphere");
openvdb::io::File file("mygrids.vdb");
grids.push_back(grid);
file.write(grids);
file.close();
}
Populating a grid with values
The following code is templated so as to operate on grids containing values of any scalar type, provided that the value type supports negation and comparison. Note that this algorithm is only meant as an example and should never be used in production; use the much more efficient routines in tools/LevelSetSphere.h instead.
See Generic programming for more on processing grids of arbitrary type.
template<class GridType>
void
{
typedef typename GridType::ValueType ValueT;
const ValueT outside = grid.background();
const ValueT inside = -outside;
int dim = int(radius + padding);
typename GridType::Accessor accessor = grid.getAccessor();
int &i = ijk[0], &j = ijk[1], &k = ijk[2];
for (i = c[0] - dim; i < c[0] + dim; ++i) {
for (j = c[1] - dim; j < c[1] + dim; ++j) {
for (k = c[2] - dim; k < c[2] + dim; ++k) {
ValueT val = ValueT(dist);
if (val < inside || outside < val) continue;
accessor.setValue(ijk, val);
}
}
}
grid.signedFloodFill();
}
Reading and modifying a grid
#include <openvdb/openvdb.h>
openvdb::io::File file("mygrids.vdb");
file.open();
for (openvdb::io::File::NameIterator nameIter = file.beginName();
nameIter != file.endName(); ++nameIter)
{
if (nameIter.gridName() == "LevelSetSphere") {
baseGrid = file.readGrid(nameIter.gridName());
} else {
std::cout << "skipping grid " << nameIter.gridName() << std::endl;
}
}
file.close();
const float outside = grid->background();
const float width = 2.0 * outside;
float dist = iter.getValue();
iter.setValue((outside - dist) / width);
}
if (iter.getValue() < 0.0) {
iter.setValue(1.0);
iter.setValueOff();
}
}
grid->setBackground(0.0);
Stream I/O
The io::Stream class allows grids to be written to and read from streams that do not support random access, with the restriction that all grids must be written or read at once. (With io::File, grids can be read individually by name, provided that they were originally written with io::File
, rather than streamed to a file.)
#include <openvdb/openvdb.h>
#include <openvdb/io/Stream.h>
grids->push_back(...);
std::ostringstream ostr(std::ios_base::binary);
openvdb::io::Stream().write(ostr, *grids);
std::ofstream ofile("mygrids.vdb", std::ios_base::binary);
openvdb::io::Stream().write(ofile, *grids);
std::istringstream istr(ostr.str(), std::ios_base::binary);
openvdb::io::Stream strm(istr);
grids = strm.getGrids();
std::ifstream ifile("mygrids.vdb", std::ios_base::binary);
grids = openvdb::io::Stream(ifile).getGrids();
Handling metadata
Metadata of various types (string, floating point, integer, etc.—see metadata/Metadata.h for more) can be attached both to individual Grid
s and to files on disk. The examples that follow refer to Grid
s, but the usage is the same for the MetaMap that can optionally be supplied to a file or stream for writing.
Adding metadata
The Grid::insertMeta() method either adds a new (name, value) pair if the name is unique, or overwrites the existing value if the name matches an existing one. An existing value cannot be overwritten with a new value of a different type; the old metadata must be removed first.
#include <openvdb/openvdb.h>
Retrieving metadata
Call Grid::metaValue() to retrieve the value of metadata of a known type. For example,
std::string s = grid->metaValue<std::string>("vector type");
float r = grid->metaValue<float>("radius");
float center = grid->metaValue<float>("center");
Grid::beginMeta() and Grid::beginMeta() return STL std::map
iterators over all of the metadata associated with a grid:
iter != grid->endMeta(); ++iter)
{
const std::string& name = iter->first;
std::string valueAsString = value->str();
std::cout << name << " = " << valueAsString << std::endl;
}
If the type of the metadata is not known, use the index operator to retrieve a shared pointer to a generic Metadata object, then query its type:
std::cout << metadata->typeName() << std::endl;
}
Removing metadata
Grid::removeMeta() removes metadata by name. If the given name is not found, the call has no effect.
grid->removeMeta("vector type");
grid->removeMeta("center");
grid->removeMeta("vector type");
Iteration
Node Iterator
A Tree::NodeIter visits each node in a tree exactly once. In the following example, the tree is known to have a depth of 4; see the Overview for a discussion of why node iteration can be complicated when the tree depth is not known. There are techniques (beyond the scope of this Cookbook) for operating on trees of arbitrary depth.
#include <openvdb/openvdb.h>
typedef GridType::TreeType TreeType;
typedef TreeType::RootNodeType RootType;
assert(RootType::LEVEL == 3);
typedef RootType::ChildNodeType Int1Type;
typedef Int1Type::ChildNodeType Int2Type;
typedef TreeType::LeafNodeType LeafType;
GridType::Ptr grid = ...;
for (TreeType::NodeIter iter = grid->tree().beginNode(); iter; ++iter) {
switch (iter.getDepth()) {
case 0: { RootType* node = NULL; iter.getNode(node); if (node) ...; break; }
case 1: { Int1Type* node = NULL; iter.getNode(node); if (node) ...; break; }
case 2: { Int2Type* node = NULL; iter.getNode(node); if (node) ...; break; }
case 3: { LeafType* node = NULL; iter.getNode(node); if (node) ...; break; }
}
}
Calling tree() instead would return a const
reference to the existing tree, with no guarantee of exclusive ownership.
Leaf Node Iterator
A Tree::LeafIter visits each leaf node in a tree exactly once.
#include <openvdb/openvdb.h>
typedef GridType::TreeType TreeType;
GridType::Ptr grid = ...;
for (TreeType::LeafCIter iter = grid->tree().cbeginLeaf(); iter; ++iter) {
const TreeType::LeafNodeType& leaf = *iter;
...
}
for (TreeType::LeafIter iter = grid->tree().beginLeaf(); iter; ++iter) {
TreeType::LeafNodeType& leaf = *iter;
...
}
for (TreeType::LeafCIter iter = grid->tree().cbeginLeaf(); iter; ++iter) {
const TreeType::LeafNodeType* leaf = iter.getLeaf();
...
}
for (TreeType::LeafIter iter = grid->tree().beginLeaf(); iter; ++iter) {
TreeType::LeafNodeType* leaf = iter.getLeaf();
...
}
See the Overview for more on leaf node iterators.
Value Iterator
A Tree::ValueIter visits each value (both tile and voxel) in a tree exactly once. Iteration can be unrestricted or can be restricted to only active values or only inactive values. Note that tree-level value iterators (unlike the node iterators described above) can be accessed either through a grid's tree or directly through the grid itself, as in the following example:
#include <openvdb/openvdb.h>
typedef GridType::TreeType TreeType;
GridType::Ptr grid = ...;
for (GridType::ValueOnCIter iter = grid->cbeginValueOn(); iter.test(); ++iter) {
if (iter.isVoxelValue()) {
std::cout << iter.getCoord() << std::endl;
} else {
iter.getBoundingBox(bbox);
std::cout << bbox << std::endl;
}
}
}
for (GridType::ValueOffIter iter = grid->beginValueOff(); iter.test(); ++iter) {
iter.setValue(value);
}
grid->setBackground(grid->background().unit());
See the Overview for more on value iterators.
Transforming grids
Geometric transformation
A GridTransformer applies a geometric transformation to an input grid using one of several sampling schemes, and stores the result in an output grid. The operation is multithreaded by default, though threading can be disabled by calling setThreaded(false). A GridTransformer
object can be reused to apply the same transformation to multiple input grids, optionally using different sampling schemes.
#include <openvdb/openvdb.h>
#include <openvdb/tools/GridTransformer.h>
sourceGrid = ...
targetGrid = ...;
const openvdb::Transform
&sourceXform = sourceGrid->transform(),
&targetXform = targetGrid->transform();
sourceXform.getBaseMap()->getAffineMap()->getMat4() *
targetXform.getBaseMap()->getAffineMap()->getMat4().
inverse();
openvdb::tools::GridTransformer transformer(xform);
sourceGrid, *targetGrid);
sourceGrid, *targetGrid);
sourceGrid, *targetGrid);
targetGrid->
tree().prune();
Value transformation
This example uses tools::foreach() to multiply all values (both tile and voxel and both active and inactive) of a scalar, floating-point grid by two:
#include <openvdb/openvdb.h>
#include <openvdb/tools/ValueTransformer.h>
struct Local {
iter.setValue(*iter * 2);
}
};
This example uses tools::foreach() to rotate all active vectors of a vector-valued grid by 45 degrees about the y axis:
#include <openvdb/openvdb.h>
#include <openvdb/tools/ValueTransformer.h>
struct MatMul {
iter.setValue(M.transform(*iter));
}
};
tools::transformValues() is similar to tools::foreach(), but it populates an output grid with transformed values from an input grid that may have a different value type. The following example populates a scalar, floating-point grid with the lengths of all active vectors from a vector-valued grid (see also tools::Magnitude):
#include <openvdb/openvdb.h>
#include <openvdb/tools/ValueTransformer.h>
struct Local {
static inline void op(
openvdb::FloatGrid::ValueAccessor& accessor)
{
if (iter.isVoxelValue()) {
accessor.setValue(iter.getCoord(), iter->length());
} else {
iter.getBoundingBox(bbox);
accessor.getTree().fill(bbox, iter->length());
}
}
};
Combining grids
The following examples show various ways in which a pair of grids can be combined in index space. The assumption is that index coordinates
in both grids correspond to the same physical, world space location. When the grids have different transforms, it is usually necessary to first resample one grid into the other grid's index space.
Level set CSG operations
The level set CSG functions in tools/Composite.h operate on pairs of grids of the same type, using sparse traversal for efficiency. These operations always leave the second grid empty.
#include <openvdb/openvdb.h>
#include <openvdb/tools/Composite.h>
copyOfGridA = gridA->deepCopy(),
copyOfGridB = gridB->deepCopy();
gridA = copyOfGridA->deepCopy();
gridB = copyOfGridB->deepCopy();
gridA = copyOfGridA->deepCopy();
gridB = copyOfGridB->deepCopy();
Compositing operations
Like the CSG operations, the compositing functions in tools/Composite.h operate on pairs of grids of the same type, and they always leave the second grid empty.
#include <openvdb/openvdb.h>
#include <openvdb/tools/Composite.h>
copyOfGridA = gridA->deepCopy(),
copyOfGridB = gridB->deepCopy();
gridA = copyOfGridA->deepCopy();
gridB = copyOfGridB->deepCopy();
gridA = copyOfGridA->deepCopy();
gridB = copyOfGridB->deepCopy();
gridA = copyOfGridA->deepCopy();
gridB = copyOfGridB->deepCopy();
Generic combination
The Tree::combine() family of methods apply a user-supplied operator to pairs of corresponding values of two trees. These methods are efficient because they take into account the sparsity of the trees; they are not multithreaded, however.
This example uses the Tree::combine() method to compute the difference between corresponding voxels of two floating-point grids:
#include <openvdb/openvdb.h>
struct Local {
static inline void diff(const float& a, const float& b, float& result) {
result = a - b;
}
};
aGrid->tree().combine(bGrid->tree(), Local::diff);
Another Tree::combine() example, this time using a functor to preserve state:
#include <openvdb/openvdb.h>
struct Blend {
Blend(float f): frac(f) {}
inline void operator()(const float& a, const float& b, float& result) const {
result = frac * a + (1.0 - frac) * b;
}
float frac;
};
aGrid->tree().combine(bGrid->tree(), Blend(0.25));
The Tree::combineExtended() method invokes a function of the form void f(CombineArgs<T>& args)
, where the CombineArgs object encapsulates an a and a b value and their active states as well as a result value and its active state. In the following example, voxel values in floating-point aGrid
are replaced with corresponding values from floating-point bGrid
(leaving bGrid
empty) wherever the b values are larger. The active states of any transferred values are preserved.
#include <openvdb/openvdb.h>
struct Local {
static inline void max(CombineArgs<float>& args) {
if (args.b() > args.a()) {
args.setResult(args.b());
args.setResultIsActive(args.bIsActive());
} else {
args.setResult(args.a());
args.setResultIsActive(args.aIsActive());
}
}
};
aGrid->tree().combineExtended(bGrid->tree(),
Local::max);
Like combine()
, Tree::combine2() applies an operation to pairs of corresponding values of two trees. However, combine2()
writes the result to a third, output tree and does not modify either of the two input trees. (As a result, it is less space-efficient than the combine()
method.) Here, the voxel differencing example above is repeated using combine2()
:
#include
<openvdb/openvdb.h>
struct Local {
static inline void diff(const float& a, const float& b, float& result) {
result = a - b;
}
};
resultGrid->tree().combine2(aGrid->tree(), bGrid->tree(), Local::diff);
An extended combine2() is also available.
Generic programming
Calling Grid methods
A common task is to perform some operation on all of the grids in a file, where the operation involves Grid method calls and the grids are of different types. Only a handful of Grid
methods, such as activeVoxelCount(), are virtual and can be called through a GridBase pointer; most are not, because they require knowledge of the Grid
's value type. For example, one might want to prune() the trees of all of the grids in a file regardless of their type, but Tree::prune()
is non-virtual because it accepts an optional pruning tolerance argument whose type is the grid's value type.
The processTypedGrid()
function below makes this kind of task easier. It is called with a GridBase
pointer and a functor whose call operator accepts a pointer to a Grid
of arbitrary type. The call operator should be templated on the grid type and, if necessary, overloaded for specific grid types.
template<typename OpType>
{
#define CALL_OP(GridType) \
op.template operator()<GridType>(openvdb::gridPtrCast<GridType>(grid))
#undef CALL_OP
}
The following example shows how to use processTypedGrid()
to implement a generic pruning operation for grids of all built-in types:
struct PruneOp {
double tolerance;
PruneOp(double t): tolerance(t) {}
template<typename GridType>
void operator()(typename GridType::Ptr grid) const
{
grid->tree().prune(typename GridType::ValueType(tolerance));
}
{
grid->tree().prune();
}
};
openvdb::io::File file("mygrids.vdb");
file.open();
file.close();
const PruneOp pruner(0.01);
iter != myGrids->end(); ++iter)
{
processTypedGrid(grid, pruner);
}