OpenVDB
3.2.0
|
This section provides code snippets and some complete programs that illustrate how to use OpenVDB and how to perform common tasks.
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:
Output:
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:
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:
and then replace “-o vdb_print
” with, for example, “-o helloworld
” and “cmd/openvdb_print/main.cc
” with “helloworld.cc
”.
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:
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.
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.)
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.
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.
Call Grid::metaValue() to retrieve the value of metadata of a known type. For example,
Grid::beginMeta() and Grid::beginMeta() return STL std::map
iterators over all of the metadata associated with a grid:
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:
Grid::removeMeta() removes metadata by name. If the given name is not found, the call has no effect.
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.
A Tree::LeafIter visits each leaf node in a tree exactly once.
See the Overview for more on leaf node iterators.
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:
See the Overview for more on value iterators.
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.
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.
OpenVDB offers low-level zero-, first- and second-order interpolators PointSampler, BoxSampler and QuadraticSampler, in addition to the variants StaggeredPointSampler, StaggeredBoxSampler and StaggeredQuadraticSampler for staggered velocity grids.
These examples invoke the getValue method on the grid’s tree to fetch sample values in the neighborhood of . 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):
Another issue with these low-level interpolators is that they operate only in index space. To interpolate in world space, use the higher-level classes discussed below.
The GridSampler class allows for continuous sampling in both world space and index space and can be used with grids, trees or value accessors.
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.
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.
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.
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:
This example uses tools::foreach() to rotate all active vectors of a vector-valued grid by 45 degrees about the y axis:
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()):
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.
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.
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.
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:
Another Tree::combine() example, this time using a functor to preserve state:
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()
:
An extended combine2() is also available.
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.
The following example shows how to use processTypedGrid()
to implement a generic pruning operation for grids of all built-in types: