“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:

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

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:

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.

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

Handling metadata

Metadata of various types (string, floating point, integer, etc.—see metadata/Metadata.h for more) can be attached both to individual Grids and to files on disk. The examples that follow refer to Grids, 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.

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"); // OK (no effect)

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.

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:

Iterator Range

A tree::IteratorRange wraps any grid or tree iterator and gives the iterator TBB splittable range semantics, so that it can be used as the Range argument to functions like tbb::parallel_for() and tbb::parallel_reduce(). (This is in fact how tools::foreach() and tools::transformValues() are implemented; see Value transformation, below, for more on those functions.) There is some overhead to splitting, since grid and tree iterators are not random-access, but the overhead should typically be negligible compared with the amount of work done per subrange.

The following is a complete program that uses tree::IteratorRange. The program iterates in parallel over the leaf nodes of a tree (by splitting the iteration range of a Tree::LeafCIter) and computes the total number of active leaf-level voxels by incrementing a global, thread-safe counter.

// since all of the active voxels in a level set grid are stored at the

// leaf level (that is, there are no active tiles in a level set grid).

assert(activeLeafVoxelCount == grid->activeVoxelCount());

}

Interpolation of grid values

Applications such as rendering require evaluation of grids at arbitrary, fractional coordinates in either index or world space. This is achieved, of course, by interpolating between known grid values at neighboring whole-voxel locations, that is, at integer coordinates in index space. The following sections introduce OpenVDB’s various interpolation schemes as well as the Grid Sampler and Dual Grid Sampler classes for efficient, continuous sampling of grids. In most cases, GridSampler is the preferred interface for interpolation, but note that when a fixed transform is to be applied to all values in a grid (that is, the grid is to be resampled), it is both easier and more efficient to use the multithreaded GridTransformer class, introduced in Transforming grids.

These examples invoke the getValue method on the grid’s tree to fetch sample values in the neighborhood of (i, j, k). Accessing values via the tree is thread-safe due to the lack of caching, but for that reason it is also suboptimal. For better performance, use value accessors (but be careful to use one accessor per computational thread):

Note that when constructing a GridSampler with either a tree or a value accessor, you must also supply an index-to-world transform. When constructing a GridSampler with a grid, the grid's transform is used automatically.

Dual Grid Sampler

It might sometimes be necessary to interpolate values from a source grid into the index space of a target grid. If this transformation is to be applied to all of the values in the source grid, then it is best to use the tools in GridTransformer.h. For other cases, consider using the DualGridSampler class. Like the GridSampler class, this class can be used with grids, trees or value accessors. In addition, DualGridSampler checks if the source and target grids are aligned (that is, they have the same transform), in which case it avoids unnecessary interpolation.

Note that interpolation is done by invoking a DualGridSampler as a functor, in contrast to the more general-purpose GridSampler.

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.

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 (like tools::magnitude()):

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 (i, j, k) 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.

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:

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.

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

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.

Converting Point Attributes

This example is the same as the “Hello, World” for OpenVDB Points example, however it demonstrates converting radius in addition to position. It uses a tailored attribute compression for the radius to demonstrate how to reduce memory.

Point Iteration

Iterating over point attribute data is most easily done by iterating over the leaf nodes of a PointDataGrid and then the index indices of the attribute within the leaf and extracting the values from a handle bound to the attribute stored within the leaf.

This example demonstrates single-threaded, read-only iteration over all float values of an attribute called "name".

Moving Points with a Custom Deformer

A custom deformer generates the new position of each existing point in a point set. This can use any number of mechanisms to achieve this such as a static value, a hard-coded list of positions, a function that uses the existing position to compute the new one or a function that uses the index of the point within the leaf array in some other way. This example simply takes the input position and adds a Y offset. Note that it is also possible to configure a custom deformer to operate in index-space.