openvdb/0000755000000000000000000000000012252453157011217 5ustar rootrootopenvdb/doc/0000755000000000000000000000000012252453247011764 5ustar rootrootopenvdb/doc/faq.txt0000644000000000000000000003172612252452022013273 0ustar rootroot/** @page faq Frequently Asked Questions @section Contents - @ref sWhatIsVDB - @ref sWhatLicense - @ref sWhatCLA - @ref sWhyUseVDB - @ref sVersionNumbering - @ref sGeneralizedOctree - @ref sLevelSet - @ref sCustomizeVDB - @ref sAdaptiveGrid - @ref sMeaningOfVDB - @ref sAccessor - @ref sValue - @ref sState - @ref sVoxel - @ref sTile - @ref sBackground - @ref sThreadSafe - @ref sMaxRes - @ref sCompareVDB - @ref sReplaceDense - @ref sFuture - @ref sContribute @section sWhatIsVDB What is OpenVDB? OpenVDB is a library comprising a compact hierarchical data structure and a suite of tools for the efficient manipulation of sparse, possibly time-varying, volumetric data discretized on a three-dimensional grid. It is based on VDB, which was developed by Ken Museth at DreamWorks Animation, and it offers an effectively infinite 3D index space, compact storage (both in memory and on disk), fast data access (both random and sequential), and a collection of algorithms specifically optimized for the data structure for common tasks such as filtering, constructive solid geometry (CSG), discretization of partial differential equations, voxelization of polygons, skinning of particles, volumetric compositing and sampling. The technical details of VDB are described in the paper "VDB: High-Resolution Sparse Volumes with Dynamic Topology". @section sWhatLicense What license is OpenVDB distributed under? OpenVDB is released under the Mozilla Public License Version 2.0, which is a free, open source, and detailed software license developed and maintained by the Mozilla Foundation. It is characterized as a hybridization of the modified BSD license and GNU General Public License (GPL) that seeks to balance the concerns of proprietary and open source developers. For more information about this license, see the Mozilla FAQ. @section sWhatCLA Is there a Contributor License Agreement for OpenVDB? Yes, developers who wish to contribute code to be considered for inclusion in the OpenVDB distribution must first complete this Contributor License Agreement and submit it to DreamWorks (directions are in the CLA). @section sWhyUseVDB Why should I use OpenVDB? The typical reasons to adopt OpenVDB are if you are storing sparse data and/or if you are performing sparse computations. OpenVDB is also effectively unbounded, which makes it very convenient for applications where the topology of the data is dynamic or unknown. Unlike many existing sparse data structures, OpenVDB is also optimized for numerical simulations, multithreaded volume compositing, near real-time boolean CSG operations, fast random and sequential data access and voxelization of points and polygons. @section sVersionNumbering What is the version numbering system for OpenVDB? We currently use a major.minor.patch version numbering system, and with every release of OpenVDB we change one or more of the three numbers. The patch number is incremented for new features and bug fixes that change neither the API nor the file format of the library nor the ABIs of the @c Grid and @c Transform classes and their constituent classes. The minor version is incremented for releases that change the API without changing the @c Grid or @c Transform ABIs, and for releases that change the file format in such a way that older files can still be read. The major version is incremented when the @c Grid or @c Transform ABIs change, or when the file format changes in a non-backward-compatible way (which should be rare). No release of OpenVDB guarantees ABI compatibility across the entire library, but rather only for the @c Grid and @c Transform classes, and primarily to allow third-party app plugins compiled against different versions of the library to coexist and exchange data. @section sCustomizeVDB Can I customize the configuration of OpenVDB? Yes! OpenVDB is specifically developed to be highly customizable. That is, the user can define the sizes of all the nodes at each level of a tree as well as the number of tree levels and the data type of values stored in the tree. However, note that since OpenVDB makes extensive use of C++ templating, configurations are fixed at compile time rather than at run time. This is a fundamental design characteristic of OpenVDB, and it leads to very high performance, thanks to techniques like inlining and template metaprogramming. @section sGeneralizedOctree Is OpenVDB merely a generalized octree or N-tree? No! While OpenVDB can conceptually be configured as a (height-balanced) octree, it is much more than an octree or N-tree. Whereas octrees and N-trees have fixed branching factors of respectively two and N in each coordinate direction, OpenVDB's branching factors typically vary between tree levels and are only limited to be powers of two. To understand why and also learn about other unique features of OpenVDB, refer to the paper in ACM Transactions on Graphics. @section sLevelSet Is OpenVDB primarily for level set applications? No! Don't let the fact that OpenVDB can represent and operate so well on level sets mislead you into thinking that it is limited to or even focusing on this special type of sparse volumetric application. OpenVDB was developed for general-purpose volumetric processing and numerical simulation, and we have even had success using it for volumetric applications that traditionally call for blocked or dense grids. However, the fact remains that narrow-band level sets play an essential role in many volumetric applications, and this explains why OpenVDB includes so many tools and algorithms specifically for level sets. @section sAdaptiveGrid Is OpenVDB an adaptive grid? Let's first stress that the term "adaptive grid" is somewhat ambiguous. Some use it to mean a grid that can store data sampled at adaptive voxel sizes typically derived from a so-called "refinement oracle", whereas others mean multiple grids with different fixed voxel sizes all sampling the same data. An example of the former is an octree and of the latter is a mipmap. Since OpenVDB stores both data values and child nodes at each level of the tree, it is adaptive only in the first sense, not the second. The level of adaptivity or refinement between the tree levels is defined by the branching factors of the nodes, which are fixed at compile time. @section sMeaningOfVDB What does "VDB" stand for? Over the years VDB has been interpreted to mean different things, none of which are very descriptive: "Voxel Data Base", "Volumetric Data Blocks", "Volumetric Dynamic B+tree", etc. In early presentations of VDB we even used a different name, "DB+Grid", which was abandoned to emphasize its distinction from similarly named, but different, existing sparse data structures like DT-Grid or DB-Grid. The simple truth is that "VDB" is just a name. :-) @section sAccessor Why are there no coordinate-based access methods on the grid? It might surprise you that the @c Grid class doesn't directly provide access to voxels via their @ijk coordinates. Instead, the recommended procedure is to ask the grid for a "value accessor", which is an accelerator object that performs bottom-up tree traversal using cached information from previous traversals. Caching greatly improves performance, but it is inherently not thread-safe. However, a single grid may have multiple value accessors, so each thread can safely be assigned its own value accessor. Uncached—and therefore slower, but thread-safe—random access is possible through a grid's tree, for example with a call like grid.tree().getValue(ijk). @section sValue How and where does OpenVDB store values? OpenVDB stores voxel data in a tree with a fixed maximum height (chosen at compile time), with a root node that has a dynamic branching factor, with internal nodes that have fixed branching factors, and with leaf nodes of fixed dimensions. Values can be stored in nodes at all levels of the tree. Values stored in leaf nodes correspond to individual voxels; all other values correspond to "tiles" (see below). @section sState What are active and inactive values? Every value in a grid has a binary state that we refer to as its "active state". The interpretation of this binary state is application-specific, but typically an active (on) value is "interesting" in some sense, and an inactive (off) value is less interesting or uninteresting. For example, the values in a narrow-band level set are all active, and values outside the narrow band are all inactive. Active states of values are stored in very compact bit masks that support sparse iteration, so visiting all active (or inactive) values in a grid can be done very efficiently. @section sVoxel How are voxels represented in OpenVDB? Values stored in nodes of type @c LeafNode (which, when they exist, have a fixed depth), correspond to individual voxels. These are the smallest addressable units of index space. @section sTile What are tiles? Values stored in nodes of type @c RootNode or @c InternalNode correspond to regions of index space with a constant value and active state and with power of two dimensions. We refer to such regions as "tiles". By construction, tiles have no child nodes. @section sBackground What is the background value? A tree's background value is the value that is returned whenever one accesses a region of index space that is not explicitly represented by voxels or tiles in the tree. Thus, you can think of the background value as the default value associated with an empty tree. Note that the background value is always inactive! @section sThreadSafe Is OpenVDB thread-safe? Yes and no. If you're asking if OpenVDB can safely be used in a multithreaded application then the answer is a resounding yes. In fact, many of the tools included in OpenVDB are multithreaded (using Intel's Threading Building Blocks library). However, like virtually all data structures that employ caching and allocate-on-insert, certain operations in OpenVDB are not thread-safe in the general sense. In particular, it is not safe to retrieve voxels from a grid while another thread is inserting voxels. For multithreaded insertion operations we typically assign a separate grid to each thread and then merge the grids as threads terminate. This technique works remarkably well: because OpenVDB is sparse and hierarchical, merging is very efficient. For more details, please consult the @subpage codeExamples and Appendix B in the Transactions on Graphics paper. @section sMaxRes Is OpenVDB unbounded? Yes, to within available memory and the 32-bit precision of the coordinates used to index voxels. And OpenVDB supports signed coordinates, unlike most existing sparse data structures, so there are almost no restrictions on the available grid resolution or the index range of OpenVDB grids. @section sCompareVDB How does OpenVDB compare to existing sparse data structures? OpenVDB is very different from existing sparse data structures that you are likely to have heard of. Foremost it is hierarchical (unlike DT-Grid and Field3D), supports simulations and dynamic topology (unlike GigaVoxels), is effectively unbounded (unlike Field3D), and offers fast random and sequential voxel access. @section sReplaceDense Does OpenVDB replace dense grids? This depends a lot on your application of dense grids and your configuration of OpenVDB. Clearly, if you are storing or processing sparse data, OpenVDB will offer a smaller memory footprint and faster (sparse) data processing. However, even in some cases where both data and computation are dense, OpenVDB can offer benefits like improved CPU cache performance due to its underlying blocking and hierarchical tree structure. Exceptions are of course algorithms that expect dense data to be laid out linearly in memory, or applications that use very small grids. The simple truth is only a benchmark comparison can tell you the preferred data structure, but for what it's worth it is our experience that for the volumetric applications we encounter in production, OpenVDB is almost always superior. @section sFuture What future improvements to OpenVDB are planned? We are continuing to expand the OpenVDB toolset and improve its performance. In the near future we plan to add OpenVDB Maya plugins, Python bindings to more library tools, support for accelerated particle storage and lookup, level set morphing and measures (e.g. area and volume), more rendering options, a fluid pressure solver, improved methods for particle skinning, asynchronous I/O, optimization by means of SIMD instructions, out-of-core support, advection of densities (vs. level sets) and much more. We would very much like to hear your suggestions and preferences. @section sContribute How can I contribute to OpenVDB? If you have bug reports or ideas for improvements or new features, please contact us! If you want to contribute code, please see our License page. */ openvdb/doc/api_0_98_0.txt0000644000000000000000000002016312252452022014244 0ustar rootroot/** @page api_0_98_0 Porting to OpenVDB 0.98.0 Starting in OpenVDB 0.98.0, @vdblink::tree::Tree Tree@endlink and @vdblink::math::Transform Transform@endlink objects (and @vdblink::Grid Grid@endlink objects in the context of Houdini SOPs) are passed and accessed primarily by reference rather than by shared pointer. (This is partly for performance reasons; the overhead of copying shared pointers, especially in a threaded environment, can be significant.) Furthermore, those objects now exhibit copy-on-write semantics, so that in most cases it is no longer necessary to make explicit deep copies. These changes were, for the most part, requested and implemented by Side Effects. Accessor methods like @vdblink::Grid::tree() Grid::tree@endlink, @vdblink::Grid::transform() Grid::transform@endlink and @c GEO_PrimVDB::getGrid that used to return shared pointers now return const references. Variants like @c Grid::constTree, which returned const shared pointers, have been removed, and new read/write accessors have been added. The latter, including @c Grid::treeRW(), @c Grid::transformRW() and @c GEO_PrimVDB::getGridRW, return non-const references, but they also ensure that ownership of the returned object is exclusive to the container (that is, they ensure that the grid has exclusive ownership of the tree or transform and that the primitive has exclusive ownership of the grid). The logic is as follows: if a grid, for example, has sole ownership of a tree, then @c Grid::treeRW() returns a non-const reference to that tree; but if the tree is shared (with other grids, perhaps), then @c Grid::treeRW() assigns the grid a new, deep copy of the tree and returns a non-const reference to the new tree. Shared pointers to @c Tree, @c Transform and @c Grid objects can still be requested from their respective containers via @c Grid::sharedTree(), @c Grid::sharedTransform() and @c GEO_PrimVDB::getSharedGrid, but their use is now discouraged. For Houdini SOPs, there are additional changes. First, VDB primitives are now normally processed in-place. That is, rather than extract a primitive's grid, make a deep copy of it and construct a new primitive to hold the copy, one now requests read/write access to a primitive's grid and then modifies the resulting grid. (SOPs that generate primitives or that replace grids of one type with another type can still do so using the old approach, however.) Second, grid metadata such as a grid's class (level set, fog volume, etc.), value type (@c float, @c vec3s, etc.), background value and so on (the full list is hardcoded into @c GEO_PrimVDB) is now exposed via "intrinsic" attributes of grid primitives, rather than via primitive attributes as before. As a result, this information no longer appears in a SOP's geometry spreadsheet, nor does any extra metadata that a SOP might add to a grid during processing. The metadata is still preserved in the @c Grid objects, though. Third, @c openvdb_houdini::processTypedGrid, which passes a shared grid pointer to a functor that also takes a shared pointer, is now deprecated in favor of @c openvdb_houdini::UTvdbProcessTypedGrid, which accepts shared pointers, raw pointers or references to grids (provided that the functor accepts an argument of the same kind). Below is a comparison of the old and new usage in the typical case of a SOP that processes input grids: Old API (0.97.0 and earlier) @code struct MyGridProcessor { template void operator()(typename GridT::Ptr grid) const { // Process the grid's tree. (1) grid->tree()->pruneInactive(); } }; SOP_OpenVDB_Example::cookMySop(OP_Context& context) { ... duplicateSource(0, context); // Process each VDB primitive that belongs to the selected group. for (openvdb_houdini::VdbPrimIterator it(gdp, group); it; ++it) { openvdb_houdini::GU_PrimVDB* vdbPrim = *it; // Deep copy the primitive's grid if it is going to be modified. openvdb_houdini::GridPtr grid = vdbPrim->getGrid()->deepCopyGrid(); // Otherwise, retrieve a read-only grid pointer. //openvdb_houdini::GridCPtr grid = vdbPrim->getGrid(); // Process the grid. MyGridProcessor proc; (2) openvdb_houdini::processTypedGrid(grid, proc); // Create a new VDB primitive that contains the modified grid, // and in the output detail replace the original primitive with // the new one. (3) openvdb_houdini::replaceVdbPrimitive(*gdp, grid, *vdbPrim); } } @endcode New API (0.98.0 and later) @code struct MyGridProcessor { template void operator()(GridT& grid) const { // Request write access to the grid's tree, then process the tree. (1) grid.treeRW().pruneInactive(); } }; SOP_OpenVDB_Example::cookMySop(OP_Context& context) { ... duplicateSource(0, context); // Process each VDB primitive that belongs to the selected group. for (openvdb_houdini::VdbPrimIterator it(gdp, group); it; ++it) { openvdb_houdini::GU_PrimVDB* vdbPrim = *it; // Get write access to the grid associated with the primitive. // If the grid is shared with other primitives, this will // make a deep copy of it. openvdb_houdini::Grid& grid = vdbPrim->getGridRW(); // If the grid is not going to be modified, get read-only access. //const openvdb_houdini::Grid& grid = vdbPrim->getGrid(); // Process the grid. MyGridProcessor proc; (2) openvdb_houdini::UTvdbProcessTypedGrid( (4) vdbPrim->getStorageType(), grid, proc); } } @endcode @b Notes
  1. In the old API, @vdblink::Grid::tree() Grid::tree@endlink returned either a shared, non-const pointer to a tree or a shared const pointer, depending on whether the grid itself was non-const or const. In the new API, @vdblink::Grid::tree() Grid::tree@endlink always returns a const reference, and @c Grid::treeRW() always returns a non-const reference.
  2. @c openvdb_houdini::processTypedGrid (old API) accepts only shared pointers to grids (or shared pointers to const grids), together with functors that accept shared pointers to grids. @c openvdb_houdini::UTvdbProcessTypedGrid (new API) accepts const and non-const references, shared pointers and raw pointers, together with matching functors. That is, all of the following are valid pairs of grid and functor arguments to @c UTvdbProcessTypedGrid(): @code openvdb_houdini::Grid& grid = vdbPrim->getGridRW(); struct MyProc { template operator()(GridT&) {...} }; const openvdb_houdini::Grid& grid = vdbPrim->getGrid(); struct MyProc { template operator()(const GridT&) {...} }; openvdb_houdini::GridPtr grid = vdbPrim->getSharedGrid(); struct MyProc { template operator()(typename GridT::Ptr) {...} }; openvdb_houdini::GridCPtr grid = vdbPrim->getSharedConstGrid(); struct MyProc { template operator()(typename GridT::ConstPtr) {...} }; openvdb_houdini::Grid* grid = &vdbPrim->getGridRW(); struct MyProc { template operator()(GridT*) {...} }; const openvdb_houdini::Grid* grid = &vdbPrim->getGrid(); struct MyProc { template operator()(const GridT*) {...} }; @endcode
  3. In the old API, input grid primitives were (usually) deleted after processing, and a new primitive was created for each output grid. @c openvdb_houdini::replaceVdbPrimitive() did both operations in one step and had the side effect of transferring the output grid's metadata to primitive attributes. In the new API, @c replaceVdbPrimitive() is rarely needed, because input grids can usually be processed in-place, and most commonly-used metadata is exposed via intrinsic attributes, which don't need to be manually updated.
  4. The first argument to @c openvdb_houdini::UTvdbProcessTypedGrid() is the grid's storage type, which can be retrieved either by passing the grid to @c openvdb_houdini::UTvdbGetGridType() or by calling @c GEO_PrimVDB::getStorageType() on the primitive.
*/ openvdb/doc/python.txt0000644000000000000000000004217512252452022014045 0ustar rootroot/** @page python Using OpenVDB in Python This section describes the OpenVDB Python module and includes Python code snippets and some complete programs that illustrate how to perform common tasks. (An API reference is also available, if Epydoc is installed.) As of OpenVDB 2.0, the Python module exposes most of the functionality of the C++ @vdblink::Grid Grid@endlink class, including I/O, metadata management, voxel access and iteration, but almost none of the many @ref secToolUtils "tools". We expect to add support for tools in forthcoming releases. The Python module supports a fixed set of grid types. If the symbol @c PY_OPENVDB_WRAP_ALL_GRID_TYPES is defined at compile time, most of the grid types declared in openvdb.h are accessible in Python, otherwise only @b FloatGrid, @b BoolGrid and @b Vec3SGrid are accessible. To add support for grids with other value types or configurations, search for @c PY_OPENVDB_WRAP_ALL_GRID_TYPES in the module source code, update the code as appropriate and recompile the module. (It is possible that this process will be streamlined in the future with a plugin mechanism.) Note however that adding grid types can significantly increase the time and memory needed to compile the module and can significantly increase the size of the resulting executable. In addition, grids of custom types that are saved to .vdb files or pickled will not be readable by clients using the standard version of the module. Also note that the @vdblink::tree::Tree Tree@endlink class is not exposed in Python. Much of its functionality is either available through the @vdblink::Grid Grid@endlink or is too low-level to be generally useful in Python. Although trees are not accessible in Python, they can of course be operated on indirectly. Of note are the grid methods @b copy, which returns a new grid that shares its tree with the original grid, @b deepCopy, which returns a new grid that owns its own tree, and @b sharesWith, which reports whether two grids share a tree. @section Contents - @ref sPyBasics - @ref sPyHandlingMetadata - @ref sPyAccessors - @ref sPyIteration - @ref sPyNumPy - @ref sPyCppAPI @section sPyBasics Getting started The following example is a complete program that illustrates some of the basic steps to create grids and write them to disk: @code{.py} import pyopenvdb as vdb # A grid comprises a sparse tree representation of voxel data, # user-supplied metadata and a voxel space to world space transform, # which defaults to the identity transform. # A FloatGrid stores one single-precision floating point value per voxel. # Other grid types include BoolGrid and Vec3SGrid. The module-level # attribute pyopenvdb.GridTypes gives the complete list. cube = vdb.FloatGrid() cube.fill(min=(100, 100, 100), max=(199, 199, 199), value=1.0) # Name the grid "cube". cube.name = 'cube' # Populate another FloatGrid with a sparse, narrow-band level set # representation of a sphere with radius 50 voxels, located at # (1.5, 2, 3) in index space. sphere = vdb.createLevelSetSphere(radius=50, center=(1.5, 2, 3)) # Associate some metadata with the grid. sphere['radius'] = 50.0 # Associate a scaling transform with the grid that sets the voxel size # to 0.5 units in world space. sphere.transform = vdb.createLinearTransform(voxelSize=0.5) # Name the grid "sphere". sphere.name = 'sphere' # Write both grids to a VDB file. vdb.write('mygrids.vdb', grids=[cube, sphere]) @endcode This example shows how to read grids from files, and some ways to modify grids: @code{.py} import pyopenvdb as vdb # Read a .vdb file and return a list of grids populated with # their metadata and transforms, but not their trees. filename = 'mygrids.vdb' grids = vdb.readAllGridMetadata(filename) # Look for and read in a level-set grid that has certain metadata. sphere = None for grid in grids: if (grid.gridClass == vdb.GridClass.LEVEL_SET and 'radius' in grid and grid['radius'] > 10.0): sphere = vdb.read(filename, grid.name) else: print 'skipping grid', grid.name if sphere: # Convert the level set sphere to a narrow-band fog volume, in which # interior voxels have value 1, exterior voxels have value 0, and # narrow-band voxels have values varying linearly from 0 to 1. outside = sphere.background width = 2.0 * outside # Visit and update all of the grid's active values, which correspond to # voxels in the narrow band. for iter in sphere.iterOnValues(): dist = iter.value iter.value = (outside - dist) / width # Visit all of the grid's inactive tile and voxel values and update # the values that correspond to the interior region. for iter in sphere.iterOffValues(): if iter.value < 0.0: iter.value = 1.0 iter.active = False # Set exterior voxels to 0. sphere.background = 0.0 sphere.gridClass = vdb.GridClass.FOG_VOLUME @endcode @section sPyHandlingMetadata Handling metadata Metadata of various types (string, bool, int, float, and 2- and 3-element sequences of ints or floats) can be attached both to individual grids and to files on disk, either by supplying a Python dictionary of (name, value) pairs or, in the case of grids, through a dictionary-like interface. Add (name, value) metadata pairs to a grid as you would to a dictionary. A new value overwrites an existing value if the name matches an existing name. @code{.py} >>> import pyopenvdb as vdb >>> grid = vdb.Vec3SGrid() >>> grid['vector'] = 'gradient' >>> grid['radius'] = 50.0 >>> grid['center'] = (10, 15, 10) >>> grid.metadata {'vector': 'gradient', 'radius': 50.0, 'center': (10, 15, 10)} >>> grid['radius'] 50.0 >>> 'radius' in grid, 'misc' in grid (True, False) # OK to overwrite an existing value with a value of another type: >>> grid['center'] = 0.0 # A 4-element sequence is not a supported metadata value type: >>> grid['center'] = (0, 0, 0, 0) File "", line 1, in TypeError: metadata value "(0, 0, 0, 0)" of type tuple is not allowed # Metadata names must be strings: >>> grid[0] = (10.5, 15, 30) File "", line 1, in TypeError: expected str, found int as argument 1 to __setitem__() @endcode Alternatively, replace all or some of a grid’s metadata by supplying a (name, value) dictionary: @code{.py} >>> metadata = { ... 'vector': 'gradient', ... 'radius': 50.0, ... 'center': (10, 15, 10) ... } # Replace all of the grid's metadata. >>> grid.metadata = metadata >>> metadata = { ... 'center': [10.5, 15, 30], ... 'scale': 3.14159 ... } # Overwrite "center" and add "scale": >>> grid.updateMetadata(metadata) @endcode Iterate over a grid’s metadata as you would over a dictionary: @code{.py} >>> for key in grid: ... print '%s = %s' % (key, grid[key]) ... vector = gradient radius = 50.0 scale = 3.14159 center = (10.5, 15.0, 30.0) @endcode Removing metadata is also straightforward: @code{.py} >>> del grid['vector'] >>> del grid['center'] >>> del grid['vector'] # error: already removed File "", line 1, in KeyError: 'vector' >>> grid.metadata = {} # remove all metadata @endcode Some grid metadata is exposed in the form of properties, either because it might be frequently accessed (a grid’s name, for example) or because its allowed values are somehow restricted: @code{.py} >>> grid = vdb.createLevelSetSphere(radius=10.0) >>> grid.metadata {'class': 'level set'} >>> grid.gridClass = vdb.GridClass.FOG_VOLUME >>> grid.metadata {'class': 'fog volume'} # The gridClass property requires a string value: >>> grid.gridClass = 123 File "", line 1, in TypeError: expected str, found int as argument 1 to setGridClass() # Only certain strings are recognized; see pyopenvdb.GridClass # for the complete list. >>> grid.gridClass = 'Hello, world.' >>> grid.metadata {'class': 'unknown'} >>> grid.metadata = {} >>> grid.vectorType = vdb.VectorType.COVARIANT >>> grid.metadata {'vector_type': 'covariant'} >>> grid.name = 'sphere' >>> grid.creator = 'Python' >>> grid.metadata {'vector_type': 'covariant', 'name': 'sphere', 'creator': 'Python'} @endcode Setting these properties to @c None removes the corresponding metadata, but the properties retain default values: @code{.py} >>> grid.creator = grid.vectorType = None >>> grid.metadata {'name': 'sphere'} >>> grid.creator, grid.vectorType ('', 'invariant') @endcode Metadata can be associated with a .vdb file at the time the file is written, by supplying a (name, value) dictionary as the optional @c metadata argument to the @b write function: @code{.py} >>> metadata = { ... 'creator': 'Python', ... 'time': '11:05:00' ... } >>> vdb.write('mygrids.vdb', grids=grid, metadata=metadata) @endcode File-level metadata can be retrieved with either the @b readMetadata function or the @b readAll function: @code{.py} >>> metadata = vdb.readMetadata('mygrids.vdb') >>> metadata {'creator': 'Python', 'time': '11:05:00'} >>> grids, metadata = vdb.readAll('mygrids.vdb') >>> metadata {'creator': 'Python', 'time': '11:05:00'} >>> [grid.name for grid in grids] ['sphere'] @endcode @section sPyAccessors Voxel access Grids provide read-only and read/write accessors for voxel lookup via @ijk index coordinates. Accessors store references to their parent grids, so a grid will not be deleted while it has accessors in use. @code{.py} >>> import pyopenvdb as vdb # Read two grids from a file. >>> grids, metadata = vdb.readAll('smoke2.vdb') >>> [grid.name for grid in grids] ['density', 'v'] # Get read/write accessors to the two grids. >>> dAccessor = grids[0].getAccessor() >>> vAccessor = grids[1].getAccessor() >>> ijk = (100, 103, 101) >>> dAccessor.probeValue(ijk) (0.17614534497261047, True) # Change the value of a voxel. >>> dAccessor.setValueOn(ijk, 0.125) >>> dAccessor.probeValue(ijk) (0.125, True) >>> vAccessor.probeValue(ijk) ((-2.90625, 9.84375, 0.84228515625), True) # Change the active state of a voxel. >>> vAccessor.setActiveState(ijk, False) >>> vAccessor.probeValue(ijk) ((-2.90625, 9.84375, 0.84228515625), False) # Get a read-only accessor to one of the grids. >>> dAccessor = grids[0].getConstAccessor() >>> dAccessor.setActiveState(ijk, False) File "", line 1, in TypeError: accessor is read-only # Delete the accessors once they are no longer needed, # so that the grids can be garbage-collected. >>> del dAccessor, vAccessor @endcode @section sPyIteration Iteration Grids provide read-only and read/write iterators over their values. Iteration is over sequences of value objects (BoolGrid.Values, FloatGrid.Values, etc.) that expose properties such as the number of voxels spanned by a value (one, for a voxel value, more than one for a tile value), its coordinates and its active state. Value objects returned by read-only iterators are immutable; those returned by read/write iterators permit assignment to their active state and value properties, which modifies the underlying grid. Value objects store references to their parent grids, so a grid will not be deleted while one of its value objects is in use. @code{.py} >>> import pyopenvdb as vdb >>> grid = vdb.read('smoke2.vdb', gridname='v') >>> grid.__class__.__name__ 'Vec3SGrid' # Iterate over inactive values and print the coordinates of the first # five voxel values and the bounding boxes of the first five tile values. >>> voxels = tiles = 0 ... N = 5 ... for item in grid.citerOffValues(): # read-only iterator ... if voxels == N and tiles == N: ... break ... if item.count == 1: ... if voxels < N: ... voxels += 1 ... print 'voxel', item.min ... else: ... if tiles < N: ... tiles += 1 ... print 'tile', item.min, item.max ... tile (0, 0, 0) (7, 7, 7) tile (0, 0, 8) (7, 7, 15) tile (0, 0, 16) (7, 7, 23) tile (0, 0, 24) (7, 7, 31) tile (0, 0, 32) (7, 7, 39) voxel (40, 96, 88) voxel (40, 96, 89) voxel (40, 96, 90) voxel (40, 96, 91) voxel (40, 96, 92) # Iterate over and normalize all active values. >>> from math import sqrt >>> for item in grid.iterOnValues(): # read/write iterator ... vector = item.value ... magnitude = sqrt(sum(x * x for x in vector)) ... item.value = [x / magnitude for x in vector] ... @endcode For some operations, it might be more convenient to use one of the grid methods @b mapOn, @b mapOff or @b mapAll. These methods iterate over a grid’s tiles and voxels (active, inactive or both, respectively) and replace each value x with f(x), where f is a callable object. These methods are not multithreaded. @code{.py} >>> import pyopenvdb as vdb >>> from math import sqrt >>> grid = vdb.read('smoke2.vdb', gridname='v') >>> def normalize(vector): ... magnitude = sqrt(sum(x * x for x in vector)) ... return [x / magnitude for x in vector] ... >>> grid.mapOn(normalize) @endcode Similarly, the @b combine method iterates over corresponding pairs of values (tile and voxel) of two grids @e A and @e B of the same type (FloatGrid, Vec3SGrid, etc.), replacing values in @e A with f(a, b), where f is a callable object. This operation assumes that index coordinates @ijk in both grids correspond to the same physical, world space location. Also, the operation always leaves grid @e B empty. @code{.py} >>> import pyopenvdb as vdb >>> density = vdb.read('smoke2.vdb', gridname='density') >>> density.__class__.__name__ 'FloatGrid' >>> sphere = vdb.createLevelSetSphere(radius=50.0, center=(100, 300, 100)) >>> density.combine(sphere, lambda a, b: min(a, b)) @endcode For now, @b combine operates only on tile and voxel values, not on their active states or other attributes. @section sPyNumPy Working with NumPy arrays Large data sets are often handled in Python using NumPy. The OpenVDB Python module can optionally be compiled with NumPy support. With NumPy enabled, the @b copyFromArray and @b copyToArray grid methods can be used to exchange data efficiently between scalar-valued grids and three-dimensional NumPy arrays and between vector-valued grids and four-dimensional NumPy arrays. @code{.py} >>> import pyopenvdb as vdb >>> import numpy >>> array = numpy.random.rand(200, 200, 200) >>> array.dtype dtype('float64') # Copy values from a three-dimensional array of doubles # into a grid of floats. >>> grid = vdb.FloatGrid() >>> grid.copyFromArray(array) >>> grid.activeVoxelCount() == array.size True >>> grid.evalActiveVoxelBoundingBox() ((0, 0, 0), (199, 199, 199)) # Copy values from a four-dimensional array of ints # into a grid of float vectors. >>> vecarray = numpy.ndarray((60, 70, 80, 3), int) >>> vecarray.fill(42) >>> vecgrid = vdb.Vec3SGrid() >>> vecgrid.copyFromArray(vecarray) >>> vecgrid.activeVoxelCount() == 60 * 70 * 80 True >>> vecgrid.evalActiveVoxelBoundingBox() ((0, 0, 0), (59, 69, 79)) @endcode When copying from a NumPy array, values in the array that are equal to the destination grid’s background value (or close to it, if the @c tolerance argument to @b copyFromArray is nonzero) are set to the background value and are marked inactive. All other values are marked active. @code{.py} >>> grid.clear() >>> grid.copyFromArray(array, tolerance=0.2) >>> print '%d%% of copied voxels are active' % ( ... round(100.0 * grid.activeVoxelCount() / array.size)) 80% of copied voxels are active @endcode The optional @c ijk argument specifies the index coordinates of the voxel in the destination grid into which to start copying values. That is, array index (0, 0, 0) maps to voxel @ijk. @code{.py} >>> grid.clear() >>> grid.copyFromArray(array, ijk=(-1, 2, 3)) >>> grid.evalActiveVoxelBoundingBox() ((-1, 2, 3), (198, 201, 202)) @endcode The @b copyToArray method also accepts an @c ijk argument. It specifies the index coordinates of the voxel to be copied to array index (0, 0, 0). @code{.py} >>> grid = vdb.createLevelSetSphere(radius=10.0) >>> array = numpy.ndarray((40, 40, 40), int) >>> array.fill(0) # Copy values from a grid of floats into # a three-dimensional array of ints. >>> grid.copyToArray(array, ijk=(-15, -20, -35)) >>> array[15, 20] array([ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3]) @endcode @b copyToArray has no @c tolerance argument, because there is no distinction between active and inactive values in the destination array. @section sPyCppAPI C++ glue routines Python objects of type @b FloatGrid, @b Vec3SGrid, etc. are backed by C structs that “inherit” from @c PyObject. The OpenVDB Python extension module includes public functions that you can call in your own extension modules to convert between @vdblink::Grid openvdb::Grids@endlink and PyObjects. See the pyopenvdb.h reference for a description of these functions and a usage example. Your extension module might need to link against the OpenVDB extension module in order to access these functions. On UNIX systems, it might also be necessary to specify the @c RTLD_GLOBAL flag when importing the OpenVDB module, to allow its symbols to be shared across modules. See @b setdlopenflags in the Python @b sys module for one way to do this. */ openvdb/doc/changes.txt0000644000000000000000000015044212252452022014131 0ustar rootroot/** @page changes Release Notes @htmlonly @endhtmlonly @par Version 2.1.0 - December 12, 2013 - Added a small number of Maya nodes, primarily for conversion of geometry to and from OpenVDB volumes and for visualization of volumes. - Added an initial implementation of @vdblink::tools::LevelSetMorphing level set morphing@endlink (with improvements to follow soon). - Added @vdblink::tools::LevelSetMeasure tools::LevelSetMeasure@endlink, which efficiently computes the surface area, volume and average mean-curvature of narrow-band level sets, in both world and voxel units. Those quantities are now exposed as intrinsic attributes on the Houdini VDB primitive and can be queried using the native Measure SOP. - @vdblink::tools::Dense tools::Dense@endlink now supports the XYZ memory layout used by Houdini and Maya in addition to the ZYX layout used in OpenVDB trees. - Improved the performance of masking in the @vdblink::tools::LevelSetFilter level set filter@endlink tool and added inversion and scaling of the mask input, so that any scalar-valued volume can be used as a mask, not just volumes with a [0, 1] range. - Added optional masking to the non-level-set filters, to the grid operators (CPT, curl, divergence, gradient, Laplacian, mean curvature, magnitude, and normalize) and to the Analysis and Filter SOPs. - Added more narrow band controls to the Rebuild Level Set SOP. - Improved the accuracy of the @vdblink::tools::levelSetRebuild() level set rebuild@endlink tool. - Added @vdblink::tools::activate() tools::activate@endlink and @vdblink::tools::deactivate() tools::deactivate@endlink, which set the active states of tiles and voxels whose values are equal to or approximately equal to a given value, and added a Deactivate Background Voxels toggle to the Combine SOP. - Added @vdblink::math::BBox::applyMap() BBox::applyMap@endlink and @vdblink::math::BBox::applyInverseMap() BBox::applyInverseMap@endlink, which allow for transformation of axis-aligned bounding boxes. - Added a @vdblink::tools::PositionShader position shader@endlink to the level set ray-tracer (primarily for debugging purposes). - Added an @vdblink::io::Queue io::Queue@endlink class that manages a concurrent queue for asynchronous serialization of grids to files or streams. - Fixed a bug in @vdblink::io::Archive io::Archive@endlink whereby writing unnamed, instanced grids (i.e., grids sharing a tree) to a file rendered the file unreadable. - Fixed a bug in the @vdblink::tools::VolumeToMesh volume to mesh@endlink converter that caused it to generate invalid polygons when the zero crossing lay between active and inactive regions. - Fixed a bug in the @vdblink::tools::UniformPointScatter point scatter@endlink tool (and the Scatter SOP) whereby the last voxel always remained empty. - Fixed a bug in the Read SOP that caused grids with the same name to be renamed with a numeric suffix (e.g., “grid[1]” “grid[2]”, etc.). - Fixed some unit test failures on 64-bit Itanium machines. @par API changes: - The @vdblink::tools::Filter Filter@endlink tool is now templated on a mask grid, and threading is controlled using a grain size, for consistency with most of the other level set tools. - The @vdblink::tools::LevelSetFilter LevelSetFilter@endlink tool is now templated on a mask grid. - All shaders now take a ray direction instead of a ray. @htmlonly @endhtmlonly @par Version 2.0.0 - October 31, 2013 - Added a @ref python "Python module" with functions for basic manipulation of grids (but no tools, yet). - Added ray intersector tools for efficient, hierarchical intersection of rays with @vdblink::tools::LevelSetRayIntersector level-set@endlink and @vdblink::tools::VolumeRayIntersector generic@endlink volumes. - Added a @vdblink::math::Ray Ray@endlink class and a hierarchical @vdblink::math::DDA Digital Differential Analyzer@endlink for fast ray traversal. - Added a fully multi-threaded @vdblink::tools::LevelSetRayTracer level set ray tracer@endlink and @vdblink::tools::PerspectiveCamera camera@endlink @vdblink::tools::OrthographicCamera classes@endlink that mimic Houdini’s cameras. - Added a simple, command-line renderer (currently for level sets only). - Implemented a new meshing scheme that produces topologically robust two-manifold meshes and is twice as fast as the previous scheme. - Implemented a new, topologically robust (producing two-manifold meshes) level-set-based seamless fracture scheme. The new scheme eliminates visible scarring seen in the previous implementation by subdividing internal, nonplanar quads near fracture seams. In addition, fracture seam points are now tagged, allowing them to be used to drive pre-fracture dynamics such as local surface buckling. - Improved the performance of @vdblink::tree::Tree::evalActiveVoxelBoundingBox() Tree::evalActiveVoxelBoundingBox@endlink and @vdblink::tree::Tree::activeVoxelCount() Tree::activeVoxelCount@endlink, and significantly improved the performance of @vdblink::tree::Tree::evalLeafBoundingBox() Tree::evalLeafBoundingBox@endlink (by about 30x). - Added a tool (and a Houdini SOP) that fills a volume with adaptively-sized overlapping or non-overlapping spheres. - Added a Ray SOP that can be used to perform geometry projections using level-set ray intersections or closest-point queries. - Added a @vdblink::tools::ClosestSurfacePoint tool@endlink that performs accelerated closest surface point queries from arbitrary points in world space to narrow-band level sets. - Increased the speed of masked level set filtering by 20% for the most common cases. - Added @vdblink::math::BoxStencil math::BoxStencil@endlink, with support for trilinear interpolation and gradient computation. - Added @vdblink::tree::Tree::topologyIntersection() Tree::topologyIntersection@endlink, which intersects a tree’s active values with those of another tree, and @vdblink::tree::Tree::topologyDifference() Tree::topologyDifference@endlink, which performs topological subtraction of one tree’s active values from another’s. In both cases, the ValueTypes of the two trees need not be the same. - Added @vdblink::tree::Tree::activeTileCount() Tree::activeTileCount@endlink, which returns the number of active tiles in a tree. - Added @vdblink::math::MinIndex() math::MinIndex@endlink and @vdblink::math::MaxIndex() math::MaxIndex@endlink, which find the minimum and maximum components of a vector without any branching. - Added @vdblink::math::BBox::minExtent() BBox::minExtent@endlink, which returns a bounding box’s shortest axis. - The default @vdblink::math::BBox BBox@endlink constructor now generates an invalid bounding box rather than an empty bounding box positioned at the origin. The new behavior is consistent with @vdblink::math::CoordBBox CoordBBox@endlink. [Thanks to Rick Hankins for suggesting this fix.] - Added @vdblink::math::CoordBBox::reset() CoordBBox::reset@endlink, which resets a bounding box to its initial, invalid state. - Fixed a bug in the default @vdblink::math::ScaleMap ScaleMap@endlink constructor that left some data used in the inverse uninitialized. - Added @vdblink::math::MapBase::applyJT MapBase::applyJT@endlink, which applies the Jacobian transpose to a vector (the Jacobian transpose takes a range-space vector to a domain-space vector, e.g., world to index), and added @vdblink::math::MapBase::inverseMap() MapBase::inverseMap@endlink, which returns a new map representing the inverse of the original map (except for @vdblink::math::NonlinearFrustumMap NonlinearFrustumMap@endlink, which does not currently have a defined inverse map).
@b Note: Houdini 12.5 uses an earlier version of OpenVDB, and maps created with that version lack virtual table entries for these new methods, so do not call these methods from Houdini 12.5. - Reimplemented @vdblink::math::RandomInt math::RandomInt@endlink using Boost.Random instead of @c rand() (which is not thread-safe), and deprecated @vdblink::math::randUniform math::randUniform@endlink and added @vdblink::math::Random01 math::Random01@endlink to replace it. - Modified @vdblink::tools::copyFromDense() tools::copyFromDense@endlink and @vdblink::tools::copyToDense() tools::copyToDense@endlink to allow for implicit type conversion (e.g., between a @vdblink::tools::Dense Dense<Int32>@endlink and a @vdblink::FloatTree FloatTree@endlink) and fixed several bugs in @vdblink::tools::CopyFromDense tools::CopyFromDense@endlink. - Fixed bugs in @vdblink::math::Stats math::Stats@endlink and @vdblink::math::Histogram math::Histogram@endlink that could produce NaNs or other incorrect behavior if certain methods were called on populations of size zero. - Renamed struct tolerance to @vdblink::math::Tolerance math::Tolerance@endlink and @c negative to @vdblink::math::negative() math::negative@endlink and removed @c math::toleranceValue(). - Implemented a closest point on line segment algorithm, @vdblink::math::closestPointOnSegmentToPoint() math::closestPointOnSegmentToPoint@endlink. - Fixed meshing issues relating to masking and automatic partitioning. - @vdblink::Grid::merge() Grid::merge@endlink and @vdblink::tree::Tree::merge() Tree::merge@endlink now accept an optional @vdblink::MergePolicy MergePolicy@endlink argument that specifies one of three new merging schemes. (The old merging scheme, which is no longer available, used logic for each tree level that was inconsistent with the other levels and that could result in active tiles being replaced with nodes having only inactive values.) - Renamed @c LeafNode::coord2offset(), @c LeafNode::offset2coord() and @c LeafNode::offset2globalCoord() to @vdblink::tree::LeafNode::coordToOffset() coordToOffset@endlink, @vdblink::tree::LeafNode::offsetToLocalCoord() offsetToLocalCoord@endlink, and @vdblink::tree::LeafNode::offsetToGlobalCoord() offsetToGlobalCoord@endlink, respectively, and likewise for @vdblink::tree::InternalNode::offsetToGlobalCoord() InternalNode@endlink. [Thanks to Rick Hankins for suggesting this change.] - Replaced @vdblink::tree::Tree Tree@endlink methods @c setValueOnMin, @c setValueOnMax and @c setValueOnSum with @vdblink::tools::setValueOnMin() tools::setValueOnMin@endlink, @vdblink::tools::setValueOnMax() tools::setValueOnMax@endlink and @vdblink::tools::setValueOnSum() tools::setValueOnSum@endlink (and a new @vdblink::tools::setValueOnMult() tools::setValueOnMult@endlink) and added @vdblink::tree::Tree::modifyValue() Tree::modifyValue@endlink and @vdblink::tree::Tree::modifyValueAndActiveState() Tree::modifyValueAndActiveState@endlink, which modify voxel values in-place via user-supplied functors. Similarly, replaced @c ValueAccessor::setValueOnSum() with @vdblink::tree::ValueAccessor::modifyValue() ValueAccessor::modifyValue@endlink and @vdblink::tree::ValueAccessor::modifyValueAndActiveState() ValueAccessor::modifyValueAndActiveState@endlink, and added a @vdblink::tree::TreeValueIteratorBase::modifyValue() modifyValue@endlink method to all value iterators. - Removed @c LeafNode::addValue and @c LeafNode::scaleValue. - Added convenience classes @vdblink::tree::Tree3 tree::Tree3@endlink and @vdblink::tree::Tree5 tree::Tree5@endlink for custom tree configurations. - Added an option to the From Particles SOP to generate an alpha mask, which can be used to constrain level set filtering so as to preserve surface details. - The mesh to volume converter now handles point-degenerate polygons. - Fixed a bug in the Level Set Smooth, Level Set Renormalize and Level Set Offset SOPs that caused the group name to be ignored. - Fixed various OS X and Windows build issues. [Contributions from SESI and DD] @htmlonly @endhtmlonly @par Version 1.2.0 - June 28 2013 - @vdblink::tools::LevelSetFilter Level set filters@endlink now accept an optional alpha mask grid. - Implemented sharp feature extraction for level set surfacing. This enhances the quality of the output mesh and reduces aliasing artifacts. - Added masking options to the meshing tools, as well as a spatial multiplier for the adaptivity threshold, automatic partitioning, and the ability to preserve edges and corners when mesh adaptivity is applied. - The mesh to volume attribute transfer scheme now takes surface orientation into account, which improves accuracy in proximity to edges and corners. - Added a @vdblink::tree::LeafManager::foreach() foreach@endlink method to @vdblink::tree::LeafManager tree::LeafManager@endlink that, like @vdblink::tools::foreach() tools::foreach@endlink, applies a user-supplied functor to each leaf node in parallel. - Rewrote the particle to level set converter, simplifying the API, improving performance (especially when particles have a fixed radius), adding the capability to transfer arbitrary point attributes, and fixing a velocity trail bug. - Added utility methods @vdblink::math::Sign() Sign@endlink, @vdblink::math::SignChange() SignChange@endlink, @vdblink::math::isApproxZero() isApproxZero@endlink, @vdblink::math::Cbrt() Cbrt@endlink and @vdblink::math::ZeroCrossing() ZeroCrossing@endlink to math/Math.h. - Added a @vdblink::tree::ValueAccessor3::probeNode() probeNode@endlink method to the value accessor and to tree nodes that returns a pointer to the node that contains a given voxel. - Deprecated @c LeafNode::addValue and @c LeafNode::scaleValue. - Doubled the speed of the mesh to volume converter (which also improves the performance of the fracture and level set rebuild tools) and improved its inside/outside voxel classification near edges and corners. - @vdblink::tools::GridSampler GridSampler@endlink now accepts either a grid, a tree or a value accessor, and it offers faster index-based access methods and much better performance in cases where many instances are allocated. - Extended @vdblink::tools::Dense tools::Dense@endlink to make it more compatible with existing tools. - Fixed a crash in @vdblink::io::Archive io::Archive@endlink whenever the library was unloaded from memory and then reloaded. [Contributed by Ollie Harding] - Fixed a bug in @c GU_PrimVDB::buildFromPrimVolume(), seen during the conversion from Houdini volumes to OpenVDB grids, that could cause signed flood fill to be applied to non-level set grids, resulting in active tiles with incorrect values. - Added a Prune SOP with several pruning schemes. @htmlonly @endhtmlonly @par Version 1.1.1 - May 10 2013 - Added a simple @vdblink::tools::Dense dense grid class@endlink and tools to copy data from dense voxel arrays into OpenVDB grids and vice-versa. - Starting with Houdini 12.5.396, plugins built with this version of OpenVDB can coexist with native Houdini OpenVDB nodes. - The level set fracture tool now smooths seam line edges during mesh extraction, eliminating staircase artifacts. - Significantly improved the performance of the @vdblink::util::leafTopologyIntersection() leafTopologyIntersection@endlink and @vdblink::util::leafTopologyDifference() leafTopologyDifference@endlink utilities and added a @vdblink::tree::LeafNode::topologyDifference() LeafNode::topologyDifference@endlink method. - Added convenience functions that provide simplified interfaces to the @vdblink::tools::meshToLevelSet() mesh to volume@endlink and @vdblink::tools::volumeToMesh() volume to mesh@endlink converters. - Added a @vdblink::tools::accumulate() tools::accumulate@endlink function that is similar to @vdblink::tools::foreach() tools::foreach@endlink but can be used to accumulate the results of computations over the values of a grid. - Added @vdblink::tools::statistics() tools::statistics@endlink, @vdblink::tools::opStatistics() tools::opStatistics@endlink and @vdblink::tools::histogram() tools::histogram@endlink, which efficiently compute statistics (mean, variance, etc.) and histograms of grid values (using @vdblink::math::Stats math::Stats@endlink and @vdblink::math::Histogram math::Histogram@endlink). - Modified @vdblink::math::CoordBBox CoordBBox@endlink to adhere to TBB’s splittable type requirements, so that, for example, a @c CoordBBox can be used as a blocked iteration range. - Added @vdblink::tree::Tree::addTile() Tree::addTile@endlink, @vdblink::tree::Tree::addLeaf() Tree::addLeaf@endlink and @vdblink::tree::Tree::stealNode() Tree::stealNode@endlink, for fine control over tree construction. - Addressed a numerical stability issue when performing Gaussian filtering of level set grids. - Changed the return type of @vdblink::math::CoordBBox::volume() CoordBBox::volume@endlink to reduce the risk of overflow. - When the input mesh is self-intersecting, the mesh to volume converter now produces a level set with a monotonic gradient field. - Fixed a threading bug in the mesh to volume converter that caused it to produce different results for the same input. - Fixed a bug in the particle to level set converter that prevented particles with zero velocity from being rasterized in Trail mode. - Added an optional input to the Create SOP into which to merge newly-created grids. - Fixed a bug in the Resample SOP that caused it to produce incorrect narrow-band widths when resampling level set grids. - Fixed a bug in the To Polygons SOP that caused intermittent crashes when the optional reference input was connected. - Fixed a bug in the Advect Level Set SOP that caused a crash when the velocity input was connected but empty. - The Scatter and Sample Point SOPs now warn instead of erroring when given empty grids. - Fixed a crash in @c vdb_view when stepping through multiple grids after changing render modes. - @c vdb_view can now render fog volumes and vector fields, and it now features interactively adjustable clipping planes that enable one to view the interior of a volume. @htmlonly @endhtmlonly @par Version 1.1.0 - April 4 2013 - The @vdblink::tools::resampleToMatch() resampleToMatch@endlink tool, the Resample SOP and the Combine SOP now use level set rebuild to correctly and safely resample level sets. Previously, scaling a level set would invalidate the signed distance field, leading to holes and other artifacts. - Added a mask-based topological @vdblink::tools::erodeVoxels erosion tool@endlink, and rewrote and simplified the @vdblink::tools::dilateVoxels dilation tool@endlink. - The @vdblink::tools::LevelSetAdvection LevelSetAdvection@endlink tool can now advect forward or backward in time. - @vdblink::tree::Tree::pruneLevelSet() Tree::pruneLevelSet@endlink now replaces each pruned node with a tile having the inside or outside background value, instead of arbitrarily selecting one of the node’s tile or voxel values. - When a grid is saved to a file with @vdblink::Grid::saveFloatAsHalf() saveFloatAsHalf@endlink set to @c true, the grid’s background value is now also quantized to 16 bits. (Not quantizing the background value caused a mismatch with the values of background tiles.) - As with @vdblink::tools::foreach() tools::foreach@endlink, it is now possible to specify whether functors passed to @vdblink::tools::transformValues() tools::transformValues@endlink should be shared across threads. - @vdblink::tree::LeafManager tree::LeafManager@endlink can now be instantiated with a @const tree, although buffer swapping with @const trees is disabled. - Added a @vdblink::Grid::signedFloodFill() Grid::signedFloodFill@endlink overload that allows one to specify inside and outside values. - Fixed a bug in @vdblink::Grid::setBackground() Grid::setBackground@endlink so that now only the values of inactive voxels change. - Fixed @vdblink::Grid::topologyUnion() Grid::topologyUnion@endlink so that it actually unions tree topology, instead of just the active states of tiles and voxels. The previous behavior broke multithreaded code that relied on input and output grids having compatible tree topology. - @vdblink::math::Transform math::Transform@endlink now includes an @vdblink::math::Transform::isIdentity() isIdentity@endlink predicate and methods to @vdblink::math::Transform::preMult(const Mat4d&) pre-@endlink and @vdblink::math::Transform::postMult(const Mat4d&) postmultiply@endlink by a matrix. - Modified the @link NodeMasks.h node mask@endlink classes to permit octree-like tree configurations (i.e., with a branching factor of two) and to use 64-bit operations instead of 32-bit operations. - Implemented a new, more efficient @vdblink::math::closestPointOnTriangleToPoint() closest point on triangle@endlink algorithm. - Implemented a new vertex normal scheme in the volume to mesh converter, and resolved some overlapping polygon issues. - The volume to mesh converter now meshes not just active voxels but also active tiles. - Fixed a bug in the mesh to volume converter that caused unsigned distance field conversion to produce empty grids. - Fixed a bug in the level set fracture tool whereby the cutter overlap toggle was ignored. - Fixed an infinite loop bug in @c vdb_view. - Updated @c vdb_view to use the faster and less memory-intensive OpenVDB volume to mesh converter instead of marching cubes, and rewrote the shader to be OpenGL 3.2 and GLSL 1.2 compatible. - Given multiple input files or a file containing multiple grids, @c vdb_view now displays one grid at a time. The left and right arrow keys cycle between grids. - The To Polygons SOP now has an option to associate the input grid’s name with each output polygon. @htmlonly @endhtmlonly @par Version 1.0.0 - March 14 2013 - @vdblink::tools::levelSetRebuild() tools::levelSetRebuild@endlink now throws an exception when given a non-scalar or non-floating-point grid. - The tools in tools/GridOperators.h are now interruptible, as is the Analysis SOP. - Added a @vdblink::tree::LeafManager::LeafRange::Iterator leaf node iterator@endlink and a TBB-compatible @vdblink::tree::LeafManager::LeafRange range class@endlink to the LeafManager. - Modified the @vdblink::tools::VolumeToMesh VolumeToMesh@endlink tool to handle surface topology issues around fracture seam lines. - Modified the Makefile to allow @c vdb_view to compile on OS X systems (provided that GLFW is available). - Fixed a bug in the Create SOP that resulted in "invalid parameter name" warnings. - The Combine SOP now optionally resamples the A grid into the B grid’s index space (or vice-versa) if the A and B transforms differ. - The Vector Split and Vector Merge SOPs now skip inactive voxels by default, but they can optionally be made to include inactive voxels, as they did before. - The @vdblink::tools::LevelSetFracture LevelSetFracture@endlink tool now supports custom rotations for each cutter instance, and the Fracture SOP now uses quaternions to generate uniformly-distributed random rotations. @htmlonly @endhtmlonly @par Version 0.104.0 - February 15 2013 - Added a @vdblink::tools::levelSetRebuild() tool@endlink and a SOP to rebuild a level set from any scalar volume. - @c .vdb files are now saved using a mask-based compression scheme that is an order of magnitude faster than Zip and produces comparable file sizes for level set and fog volume grids. (Zip compression is still enabled by default for other classes of grids). - The @vdblink::tools::Filter Filter@endlink and @vdblink::tools::LevelSetFilter LevelSetFilter@endlink tools now include a Gaussian filter, and mean (box) filtering is now 10-50x faster. - The isosurface @vdblink::tools::VolumeToMesh meshing tool@endlink is now more robust (to level sets with one voxel wide narrow bands, for example). - Mesh to volume conversion is on average 1.5x faster and up to 5.5x faster for high-resolution meshes where the polygon/voxel size ratio is small. - Added @vdblink::createLevelSet() createLevelSet@endlink and @vdblink::createLevelSetSphere() createLevelSetSphere@endlink factory functions for level set grids. - @vdblink::tree::ValueAccessor tree::ValueAccessor@endlink is now faster for trees of height 2, 3 and 4 (the latter is the default), and it now allows one to specify, via a template argument, the number of node levels to be cached, which can also improve performance in special cases. - Added a toggle to @vdblink::tools::foreach() tools::foreach@endlink to specify whether or not the functor should be shared across threads. - Added @vdblink::Mat4SMetadata Mat4s@endlink and @vdblink::Mat4DMetadata Mat4d@endlink metadata types. - Added explicit pre- and postmultiplication methods to the @c Transform, @c Map and @c Mat4 classes and deprecated the old accumulation methods. - Modified @vdblink::math::NonlinearFrustumMap NonlinearFrustumMap@endlink to be more compatible with Houdini’s frustum transform. - Fixed a @vdblink::tools::GridTransformer GridTransformer@endlink bug that caused it to translate the output grid incorrectly in some cases. - Fixed a bug in the tree-level @vdblink::tree::LeafIteratorBase LeafIterator@endlink that resulted in intermittent crashes in @vdblink::tools::dilateVoxels() tools::dilateVoxels@endlink. - The @c Hermite data type and Hermite grids are no longer supported. - Added tools/GridOperators.h, which includes new, cleaner implementations of the @vdblink::tools::cpt() closest point transform@endlink, @vdblink::tools::curl() curl@endlink, @vdblink::tools::divergence() divergence@endlink, @vdblink::tools::gradient() gradient@endlink, @vdblink::tools::laplacian() Laplacian@endlink, @vdblink::tools::magnitude() magnitude@endlink, @vdblink::tools::meanCurvature() mean curvature@endlink and @vdblink::tools::normalize() normalize@endlink tools. - Interrupt support has been improved in several tools, including @vdblink::tools::ParticlesToLevelSet tools::ParticlesToLevelSet@endlink. - Simplified the API of the @vdblink::math::BaseStencil Stencil@endlink class and added an @vdblink::math::BaseStencil::intersects() intersects@endlink method to test for intersection with a specified isovalue. - Renamed @c voxelDimensions to @c voxelSize in transform classes and elsewhere. - Deprecated @c houdini_utils::ParmFactory::setChoiceList in favor of @c houdini_utils::ParmFactory::setChoiceListItems, which requires a list of token, label string pairs. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Fixed a bug in @c houdini_utils::getNodeChain() that caused the Offset Level Set, Smooth Level Set and Renormalize Level Set SOPs to ignore frame changes. [Contributed by SESI] - The From Particles SOP now provides the option to write into an existing grid. - Added a SOP to edit grid metadata. - The Fracture SOP now supports multiple cutter objects. - Added a To Polygons SOP that complements the Fracture SOP and allows for elimination of seam lines, generation of correct vertex normals and grouping of polygons when surfacing fracture fragments, using the original level set or mesh as a reference. @htmlonly @endhtmlonly @par Version 0.103.1 - January 15 2013 - @vdblink::tree::ValueAccessor tree::ValueAccessor@endlink read operations are now faster for four-level trees. (Preliminary benchmark tests suggest a 30-40% improvement.) - For vector-valued grids, @vdblink::tools::compMin() tools::compMin@endlink and @vdblink::tools::compMax() tools::compMax@endlink now compare vector magnitudes instead of individual components. - Migrated grid sampling code to a new file, Interpolation.h, and deprecated old files and classes. - Added a level-set @vdblink::tools::LevelSetFracture fracture tool@endlink and a Fracture SOP. - Added @vdblink::tools::sdfInteriorMask() tools::sdfInteriorMask@endlink, which creates a mask of the interior region of a level set grid. - Fixed a bug in the mesh to volume converter that produced unexpected nonzero values for voxels at the intersection of two polygons, and another bug that produced narrow-band widths that didn’t respect the background value when the half-band width was less than three voxels. - @c houdini_utils::ParmFactory can now correctly generate ramp multi-parms. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - The Convert SOP can now convert between signed distance fields and fog volumes and from volumes to meshes. [Contributed by SESI] - For level sets, the From Mesh and From Particles SOPs now match the reference grid’s narrow-band width. - The Scatter SOP can now optionally scatter points in the interior of a level set. @htmlonly @endhtmlonly @par Version 0.103.0 - December 21 2012 - The mesh to volume converter is now 60% faster at generating level sets with wide bands, and the From Mesh SOP is now interruptible. - Fixed a threading bug in the recently-added @vdblink::tools::compReplace() compReplace@endlink tool that caused it to produce incorrect output. - Added a @vdblink::tree::Tree::probeConstLeaf() probeConstLeaf@endlink method to the @vdblink::tree::Tree::probeConstLeaf() Tree@endlink, @vdblink::tree::ValueAccessor::probeConstLeaf() ValueAccessor@endlink and @vdblink::tree::RootNode::probeConstLeaf() node@endlink classes. - The Houdini VDB primitive doesn’t create a @c name attribute unnecessarily (i.e., if its grid’s name is empty), but it now correctly allows the name to be changed to the empty string. - Fixed a crash in the Vector Merge SOP when fewer than three grids were merged. - The From Particles SOP now features a "maximum half-width" parameter to help avoid runaway computations. @htmlonly @endhtmlonly @par Version 0.102.0 - December 13 2012 - Added @vdblink::tools::compReplace() tools::compReplace@endlink, which copies the active values of one grid into another, and added a "Replace A With Active B" mode to the Combine SOP. - @vdblink::Grid::signedFloodFill() Grid::signedFloodFill@endlink no longer enters an infinite loop when filling an empty grid. - Fixed a bug in the particle to level set converter that sometimes produced level sets with holes, and fixed a bug in the SOP that could result in random output. - Fixed an issue in the frustum preview feature of the Create SOP whereby rendering very large frustums could cause high CPU usage. - Added streamline support to the constrained advection scheme in the Advect Points SOP. - Added an Advect Level Set SOP. @htmlonly @endhtmlonly @par Version 0.101.1 - December 11 2012 (DWA internal release) - Partially reverted the Houdini VDB primitive’s grid accessor methods to their pre-0.98.0 behavior. A primitive’s grid can once again be accessed by shared pointer, but now also by reference. Accessor methods for grid metadata have also been added, and the primitive now ensures that metadata and transforms are never shared. - Fixed an intermittent crash in the From Particles SOP. @htmlonly @endhtmlonly @par Version 0.101.0 - December 6 2012 (DWA internal release) - Partially reverted the @vdblink::Grid Grid@endlink’s @vdblink::Grid::tree() tree@endlink and @vdblink::Grid::transform() transform@endlink accessor methods to their pre-0.98.0 behavior, eliminating copy-on-write but preserving their return-by-reference semantics. These methods are now supplemented with a suite of @vdblink::Grid::treePtr() shared@endlink @vdblink::Grid::baseTreePtr() pointer@endlink @vdblink::Grid::transformPtr() accessors@endlink. - Restructured the @vdblink::tools::MeshToVolume mesh to volume converter@endlink for a 40% speedup and to be more robust to non-manifold geometry, to better preserve sharp features, to support arbitrary tree configurations and to respect narrow-band limits. - Added a @c getNodeBoundingBox method to @vdblink::tree::RootNode::getNodeBoundingBox() RootNode@endlink, @vdblink::tree::InternalNode::getNodeBoundingBox() InternalNode@endlink and @vdblink::tree::LeafNode::getNodeBoundingBox() LeafNode@endlink that returns the index space spanned by a node. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Renamed the Reshape Level Set SOP to Offset Level Set. - Fixed a crash in the Convert SOP and added support for conversion of empty grids. @htmlonly @endhtmlonly @par Version 0.100.0 - November 30 2012 (DWA internal release) - Greatly improved the performance of the level set to fog volume @vdblink::tools::sdfToFogVolume() converter@endlink. - Improved the performance of the @vdblink::tools::Filter::median() median filter@endlink and of level set @vdblink::tools::csgUnion() CSG@endlink operations. - Reintroduced @vdblink::tree::Tree::pruneLevelSet() Tree::pruneLevelSet@endlink, a specialized @vdblink::tree::Tree::pruneInactive() pruneInactive@endlink for level-set grids. - Added utilities to the @c houdini_utils library to facilitate the collection of a chain of adjacent nodes of a particular type so that they can be cooked in a single step. (For example, adjacent @c xform SOPs could be collapsed by composing their transformation matrices into a single matrix.) - Added pruning and flood-filling options to the Convert SOP. - Reimplemented the Filter SOP, omitting level-set-specific filters and adding node chaining (to reduce memory usage when applying several filters in sequence). - Added a toggle to the Read SOP to read grid metadata and transforms only. - Changed the attribute transfer scheme on the From Mesh and From Particles SOPs to allow for custom grid names and vector type metadata. @htmlonly @endhtmlonly @par Version 0.99.0 - November 21 2012 - Added @vdblink::Grid Grid@endlink methods that return non-const tree and transform references without triggering deep copies, as well as @c const methods that return @c const shared pointers. - Added @c Grid methods to @vdblink::Grid::addStatsMetadata populate@endlink a grid’s metadata with statistics like the active voxel count, and to @vdblink::Grid::getStatsMetadata retrieve@endlink that metadata. By default, statistics are now computed and added to grids whenever they are written to .vdb files. - Added @vdblink::io::File::readGridMetadata io::File::readGridMetadata@endlink and @vdblink::io::File::readAllGridMetadata io::File::readAllGridMetadata@endlink methods to read just the grid metadata and transforms from a .vdb file. - Fixed numerical precision issues in the @vdblink::tools::csgUnion csgUnion@endlink, @vdblink::tools::csgIntersection csgIntersection@endlink and @vdblink::tools::csgDifference csgDifference@endlink tools, and added toggles to optionally disable postprocess pruning. - Fixed an issue in @c vdb_view with the ordering of GL vertex buffer calls. [Contributed by Bill Katz] - Fixed an intermittent crash in the @vdblink::tools::ParticlesToLevelSet ParticlesToLevelSet@endlink tool, as well as a race condition that could cause data corruption. - The @c ParticlesToLevelSet tool and From Particles SOP can now transfer arbitrary point attribute values from the input particles to output voxels. - Fixed a bug in the Convert SOP whereby the names of primitives were lost during conversion, and another bug that resulted in arithmetic errors when converting empty grids. - Fixed a bug in the Combine SOP that caused the Operation selection to be lost. @htmlonly @endhtmlonly @par Version 0.98.0 - November 16 2012 - @vdblink::tree::Tree Tree@endlink and @vdblink::math::Transform Transform@endlink objects (and @vdblink::Grid Grid@endlink objects in the context of Houdini SOPs) are now passed and accessed primarily by reference rather than by shared pointer. See @subpage api_0_98_0 "Porting to OpenVDB 0.98.0" for details about this important API change. [Contributed by SESI] - Reimplemented @vdblink::math::CoordBBox CoordBBox@endlink to address several off-by-one bugs related to bounding box dimensions. - Fixed an off-by-one bug in @vdblink::Grid::evalActiveVoxelBoundingBox() evalActiveVoxelBoundingBox@endlink. - Introduced the @vdblink::tree::LeafManager LeafManager@endlink class, which will eventually replace the @c LeafArray class. @c LeafManager supports dynamic buffers stored as a structure of arrays (SOA), unlike @c LeafArray, which supports only static buffers stored as an array of structures (AOS). - Improved the performance of the @vdblink::tools::LevelSetFilter LevelSetFilter@endlink and @vdblink::tools::LevelSetTracker LevelSetTracker@endlink tools by rewriting them to use the new @vdblink::tree::LeafManager LeafManager@endlink class. - Added @vdblink::tree::Tree::setValueOnly() Tree::setValueOnly@endlink and @vdblink::tree::ValueAccessor::setValueOnly() ValueAccessor::setValueOnly@endlink methods, which change the value of a voxel without changing its active state, and @vdblink::tree::Tree::probeLeaf() Tree::probeLeaf@endlink and @vdblink::tree::ValueAccessor::probeLeaf() ValueAccessor::probeLeaf@endlink methods that return the leaf node that contains a given voxel (unless the voxel is represented by a tile). - Added a @vdblink::tools::LevelSetAdvection LevelSetAdvection@endlink tool that propagates and tracks narrow-band level sets. - Introduced a new @vdblink::tools::GridSampler GridSampler@endlink class that supports world-space (or index-space) sampling of grid values. - Changed the interpretation of the @vdblink::math::NonlinearFrustumMap NonlinearFrustumMap@endlink’s @em taper parameter to be the ratio of the near and far plane depths. - Added a @c ParmFactory::setChoiceList() overload that accepts (@em token, @em label) string pairs, and a @c setDefault() overload that accepts an STL string. - Fixed a crash in the Combine SOP in Copy B mode. - Split the Level Set Filter SOP into three separate SOPs, Level Set Smooth, Level Set Reshape and Level Set Renormalize. When two or more of these nodes are connected in sequence, they interact to reduce memory usage: the last node in the sequence performs all of the operations in one step. - The Advect Points SOP can now output polyline streamlines that trace the paths of the points. - Added an option to the Analysis SOP to specify names for output grids. - Added camera-derived frustum transform support to the Create SOP. @htmlonly @endhtmlonly @par Version 0.97.0 - October 18 2012 - Added a narrow-band @vdblink::tools::LevelSetTracker level set interface tracking tool@endlink (up to fifth-order in space but currently only first-order in time, with higher temporal orders to be added soon). - Added a @vdblink::tools::LevelSetFilter level set filter tool@endlink to perform unrestricted surface smoothing (e.g., Laplacian flow), filtering (e.g., mean value) and morphological operations (e.g., morphological opening). - Added adaptivity to the @vdblink::tools::VolumeToMesh level set meshing tool@endlink for faster mesh extraction with fewer polygons, without postprocessing. - Added a @vdblink::tree::ValueAccessor::touchLeaf() ValueAccessor::touchLeaf@endlink method that creates (if necessary) and returns the leaf node containing a given voxel. It can be used to preallocate leaf nodes over which to run parallel algorithms. - Fixed a bug in @vdblink::Grid::merge() Grid::merge@endlink whereby active tiles were sometimes lost. - Added @vdblink::tree::LeafManager LeafManager@endlink, which is similar to @c LeafArray but supports a dynamic buffer count and allocates buffers more efficiently. Useful for temporal integration (e.g., for level set propagation and interface tracking), @c LeafManager is meant to replace @c LeafArray, which will be deprecated in the next release. - Added a @vdblink::tree::LeafNode::fill() LeafNode::fill@endlink method to efficiently populate leaf nodes with constant values. - Added a @vdblink::tree::Tree::visitActiveBBox() Tree::visitActiveBBox@endlink method that applies a functor to the bounding boxes of all active tiles and leaf nodes and that can be used to improve the performance of ray intersection tests, rendering of bounding boxes, etc. - Added a @vdblink::tree::Tree::voxelizeActiveTiles() Tree::voxelizeActiveTiles@endlink method to densify active tiles. While convenient and fast, this can produce large dense grids, so use it with caution. - Repackaged @c Tree::pruneLevelSet() as a @vdblink::tree::Tree::pruneOp() Tree::pruneOp()@endlink-compatible functor. @vdblink::tree::LevelSetPrune LevelSetPrune@endlink is a specialized @vdblink::tree::Tree::pruneInactive() pruneInactive@endlink for level-set grids and is used in interface tracking. - Added a @vdblink::GridBase::pruneGrid() GridBase::pruneGrid@endlink method. - Added a @vdblink::Grid::hasUniformVoxels() Grid:hasUniformVoxels@endlink method. - Renamed @c tools::dilate to @vdblink::tools::dilateVoxels() dilateVoxels@endlink and improved its performance. The new name reflects the fact that the current implementation ignores active tiles. - Added a @vdblink::tools::resampleToMatch() tools::resampleToMatch@endlink function that resamples an input grid into an output grid with a different transform such that, after resampling, the input and output grids coincide, but the output grid’s transform is preserved. - Significantly improved the performance of depth-bounded value iterators (@vdblink::tree::Tree::ValueOnIter ValueOnIter@endlink, @vdblink::tree::Tree::ValueAllIter ValueAllIter@endlink, etc.) when the depth bound excludes leaf nodes. - Exposed the value buffers inside leaf nodes with @vdblink::tree::LeafNode::buffer() LeafNode::buffer@endlink. This allows for very fast access (const and non-const) to voxel values using linear array offsets instead of @ijk coordinates. - In openvdb_houdini/UT_VDBTools.h, added operators for use with @c processTypedGrid that resample grids in several different ways. - Added a policy mechanism to @c houdini_utils::OpFactory that allows for customization of operator names, icons, and Help URLs. - Renamed many of the Houdini SOPs to make the names more consistent. - Added an Advect Points SOP. - Added a Level Set Filter SOP that allows for unrestricted surface deformations, unlike the older Filter SOP, which restricts surface motion to the initial narrow band. - Added staggered vector sampling to the Sample Points SOP. - Added a minimum radius threshold to the particle voxelization tool and SOP. - Merged the Composite and CSG SOPs into a single Combine SOP. - Added a tool and a SOP to efficiently generate narrow-band level set representations of spheres. - In the Visualize SOP, improved the performance of tree topology generation, which is now enabled by default. @htmlonly @endhtmlonly @par Version 0.96.0 - September 24 2012 - Fixed a memory corruption bug in the mesh voxelizer tool. - Temporarily removed the optional clipping feature from the level set mesher. - Added "Staggered Vector Field" to the list of grid classes in the Create SOP. @htmlonly @endhtmlonly @par Version 0.95.0 - September 20 2012 - Added a quad @vdblink::tools::VolumeToMesh meshing@endlink tool for higher-quality level set meshing and updated the Visualizer SOP to use it. - Fixed a precision error in the @vdblink::tools::MeshToVolume mesh voxelizer@endlink and improved the quality of inside/outside voxel classification. Output grids are now also @vdblink::Grid::setGridClass() classified@endlink as either level sets or fog volumes. - Modified the @vdblink::tools::GridResampler GridResampler@endlink to use the signed flood fill optimization only on grids that are tagged as level sets. - Added a @vdblink::math::Quat quaternion@endlink class to the math library and a method to return the @vdblink::math::Mat3::trace trace@endlink of a @c Mat3. - Fixed a bug in the @vdblink::tree::ValueAccessor::ValueAccessor(const ValueAccessor&) ValueAccessor@endlink copy constructor that caused the copy to reference the original. - Fixed a bug in @vdblink::tree::RootNode::setActiveState() RootNode::setActiveState@endlink that caused a crash when marking a (virtual) background voxel as inactive. - Added a @c Tree::pruneLevelSet method that is similar to but faster than @vdblink::tree::Tree::pruneInactive() pruneInactive@endlink for level set grids. - Added fast leaf node voxel access @vdblink::tree::LeafNode::getValue(Index) const methods@endlink that index by linear offset (as returned by @vdblink::tree::LeafNode::ValueOnIter::pos() ValueIter::pos@endlink) instead of by @ijk coordinates. - Added a @vdblink::tree::Tree::touchLeaf() Tree::touchLeaf@endlink method that can be used to preallocate a static tree topology over which to safely perform multithreaded processing. - Added a grain size argument to @c LeafArray for finer control of parallelism. - Modified the Makefile to make it easier to omit the doc, @c vdb_test and @c vdb_view targets. - Added utility functions (in houdini/UT_VDBUtils.h) to convert between Houdini and OpenVDB matrix and vector types. [Contributed by SESI] - Added accessors to @c GEO_PrimVDB that make it easier to directly access voxel data and that are used by the HScript volume expression functions in Houdini 12.5. [Contributed by SESI] - As of Houdini 12.1.77, the native transform SOP operates on OpenVDB primitives. [Contributed by SESI] - Added a Convert SOP that converts OpenVDB grids to Houdini volumes and vice-versa. @htmlonly @endhtmlonly @par Version 0.94.1 - September 7 2012 - Fixed bugs in @vdblink::tree::RootNode RootNode@endlink and @vdblink::tree::InternalNode InternalNode@endlink @c setValue*() and @c fill() methods that could cause neighboring voxels to become inactive. - Fixed a bug in @vdblink::tree::Tree::hasSameTopology() Tree::hasSameTopology@endlink that caused false positives when only active states and not values differed. - Added a @vdblink::tree::Tree::hasActiveTiles() Tree::hasActiveTiles@endlink method. - For better cross-platform consistency, substituted bitwise AND operations for right shifts in the @vdblink::tree::ValueAccessor ValueAccessor@endlink hash key computation. - @c vdb_view no longer aborts when asked to surface a vector-valued grid—but it still doesn’t render the surface. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Added an option to the MeshVoxelizer SOP to convert both open and closed surfaces to unsigned distance fields. - The Filter SOP now allows multiple filters to be applied in user-specified order. @htmlonly @endhtmlonly @par Version 0.94.0 - August 30 2012 - Added a @vdblink::Grid::topologyUnion() method@endlink to union just the active states of voxels from one grid with those of another grid of a possibly different type. - Fixed an incorrect scale factor in the Laplacian diffusion @vdblink::tools::Filter::laplacian() filter@endlink. - Fixed a bug in @vdblink::tree::Tree::merge() Tree::merge@endlink that could leave a tree with invalid value accessors. - Added @vdblink::tree::TreeValueIteratorBase::setActiveState() TreeValueIteratorBase::setActiveState@endlink and deprecated @c setValueOn. - Removed @c tools/FastSweeping.h. It will be replaced with a much more efficient implementation in the near future. - ZIP compression of .vdb files is now @vdblink::io::File::setCompressionEnabled() optional@endlink, but enabled by default. [Contributed by SESI] - Made various changes for Clang and Visual C++ compatibility. [Contributed by SESI] - The MeshVoxelizer SOP can now transfer arbitrary point and primitive attribute values from the input mesh to output voxels. @htmlonly @endhtmlonly @par Version 0.93.0 - August 24 2012 - Renamed symbols in math/Operators.h to avoid ambiguities that GCC 4.4 reports as errors. - Simplified the API for the stencil version of the closest-point transform @vdblink::math::CPT operator@endlink. - Added logic to @vdblink::io::Archive::readGrid() io::Archive::readGrid@endlink to set the grid name metadata from the descriptor if the metadata doesn’t already exist. - Added guards to prevent nesting of @c openvdb_houdini::Interrupter::start() and @c end() calls. @htmlonly @endhtmlonly @par Version 0.92.0 - August 23 2012 - Added a Laplacian diffusion @vdblink::tools::Filter::laplacian() filter@endlink. - Fixed a bug in the initialization of the sparse contour tracer that caused mesh-to-volume conversion to fail in certain cases. - Fixed a bug in the curvature stencil that caused mean curvature filtering to produce wrong results. - Increased the speed of the @vdblink::tools::GridTransformer GridTransformer@endlink by as much as 20% for fog volumes. - Added optional pruning to the Resample SOP. - Modified the PointSample SOP to allow it to work with ungrouped, anonymous grids. - Fixed a crash in the LevelSetNoise SOP. @htmlonly @endhtmlonly @par Version 0.91.0 - August 16 2012 - @vdblink::tools::GridTransformer tools::GridTransformer@endlink and @vdblink::tools::GridResampler tools::GridResampler@endlink now correctly (but not yet efficiently) process tiles in sparse grids. - Added an optional @vdblink::CopyPolicy CopyPolicy@endlink argument to @vdblink::GridBase::copyGrid() GridBase::copyGrid@endlink and to @vdblink::Grid::copy() Grid::copy@endlink that specifies whether and how the grid’s tree should be copied. - Added a @vdblink::GridBase::newTree() GridBase::newTree@endlink method that replaces a grid’s tree with a new, empty tree of the correct type. - Fixed a crash in @vdblink::tree::Tree::setValueOff(const Coord& xyz, const ValueType& value) Tree::setValueOff@endlink when the new value was equal to the background value. - Fixed bugs in @vdblink::tree::Tree::prune() Tree::prune@endlink that could result in output tiles with incorrect active states. - Added @c librt to the link dependencies to address build failures on Ubuntu systems. - Made various small changes to the Makefile and the source code that should help with Mac OS X compatibility. - The Composite and Resample SOPs now correctly copy the input grid’s metadata to the output grid. @htmlonly @endhtmlonly @par Version 0.90.1 - August 7 2012 - Fixed a bug in the @vdblink::math::BBox::getCenter() BBox::getCenter()@endlink method. - Added missing header files to various files. - @vdblink::io::File::NameIterator::gridName() io::File::NameIterator::gridName()@endlink now returns a unique name of the form "name[1]", "name[2]", etc. if a file contains multiple grids with the same name. - Fixed a bug in the Writer SOP that caused grid names to be discarded. - The Resample SOP now correctly sets the background value of the output grid. @htmlonly @endhtmlonly @par Version 0.90.0 - August 3 2012 (initial public release) - Added a basic GL viewer for OpenVDB files. - Greatly improved the performance of two commonly-used @c Tree methods, @vdblink::tree::Tree::evalActiveVoxelBoundingBox() evalActiveVoxelBoundingBox()@endlink and @vdblink::tree::Tree::memUsage() memUsage()@endlink. - Eliminated the @c GridMap class. File I/O now uses STL containers of grid pointers instead. - Refactored stencil-based tools (Gradient, Laplacian, etc.) and rewrote some of them for generality and better performance. Most now behave correctly for grids with nonlinear index-to-world transforms. - Added a @link FiniteDifference.h library@endlink of index-space finite difference operators. - Added a @vdblink::math::Hermite "Hermite"@endlink grid type that compactly stores each voxel’s upwind normals and can be used to convert volumes to and from polygonal meshes. - Added a @link PointScatter.h tool@endlink (and a Houdini SOP) to scatter points randomly throughout a volume. */ openvdb/doc/codingstyle.txt0000644000000000000000000002706512252452022015051 0ustar rootroot/** @page codingStyle Coding Style @section Introduction This document details the coding practices that are used in the OpenVDB codebase. Contributed code should conform to these guidelines to maintain consistency and maintainability. If there is a rule that you would like clarified, changed, or added, please send a note to openvdb@gmail.com. @section Contents - @ref sNamingConventions - @ref sNamespaceConventions - @ref sClassConventions - @ref sClassMethods - @ref sClassInstanceVariables - @ref sClassStaticVariables - @ref sLocalVariablesAndArguments - @ref sConstants - @ref sEnumerationNames - @ref sEnumerationValues - @ref sTypedefs - @ref sGlobalVariables - @ref sGlobalFunctions - @ref sBooleans - @ref sPractices - @ref sGeneral - @ref sFormatting - @ref sIncludeStatements - @ref sComments - @ref sPrimitiveTypes - @ref sMacros - @ref sClasses - @ref sConditionalStatements - @ref sNamespaces - @ref sExceptions - @ref sTemplates - @ref sMiscellaneous @section sNamingConventions Naming Conventions @subsection sNamespaceConventions Namespaces -# Lowercase words, keep simple and short (e.g., @c tools, @c tree). @subsection sClassConventions Classes and Structs -# Mixed case; first letter uppercase (e.g., @c AffineMap, @c TreeIterator) -# Do not use a prefix. @subsection sClassMethods Class Methods -# Mixed case; first letter lowercase (e.g., getAccessor(), gridType()) -# Accessors that return a member variable by reference or a primitive type by value are just the variable name (e.g., Grid::tree()). -# Accessors that involve construction of objects or other computations are @c get + the variable name (e.g., Grid::getAccessor()). -# Simple mutators are @c set + the variable name (e.g., Grid::setTree(tree);). @subsection sClassInstanceVariables Class Instance Variables -# Mixed case; always prefixed by @c m (e.g., @c mTree, @c mTransform) @subsection sClassStaticVariables Class Static Variables -# Mixed case; always prefixed by @c s (e.g., @c sInitialized) @subsection sLocalVariablesAndArguments Local Variables and Arguments -# Use mixed case with an initial lower case (e.g., @c ijk, @c offset, @c range, @c rhsValue). @subsection sConstants Constants -# All uppercase; words separated by underscores. If not in a namespace or local to a source file, then prefixed with the library name in all caps (e.g., @c HALF_FLOAT_TYPENAME_SUFFIX, @c ZIP_COMPRESSION_LEVEL). @subsection sEnumerationNames Enumeration Names -# Mixed case; first letter uppercase (e.g., @c GridClass, @c VecType) @subsection sEnumerationValues Enumeration Values -# Same as constants; all uppercase; words separated by underscores (e.g., @c GRID_LEVEL_SET, @c VEC_INVARIANT) and with a common prefix (@c GRID_, @c VEC_, etc.) @subsection sTypedefs Typedefs -# Mixed case; first letter uppercase (e.g., @c ConstPtr, @c ValueType) -# Do not use a prefix. @subsection sGlobalVariables Global Variables -# Mixed case; always prefixed by @c g (e.g., @c gRegistry) -# In general, try to use class static data instead of globals. @subsection sGlobalFunctions Global Functions -# Mixed case; always prefixed by @c g (e.g., gFunc()). -# In general, try to use class static members instead of global functions. @subsection sBooleans Booleans -# When naming boolean functions and variables, use names that read as a condition (e.g., if (grid.empty()), if (matrix.isInvertible())). @section sPractices Practices @subsection sGeneral General -# Code must compile without any warning messages. if closely related. -# Prefer the C++ Standard Library to the C Standard Library. -# Restrict variables to the smallest scopes possible, and avoid defining local variables before giving them values. Prefer declarations inside conditionals: if (Grid::Ptr grid = createGrid()) { ... } instead of Grid::Ptr grid = createGrid(); if (grid) { ... } -# For new files, be sure to use the right license boilerplate per our license policy. @subsection sFormatting Formatting -# Use Doxygen-style comments to document public code. -# Indentation is 4 spaces. Do not use tabs. -# Use Unix-style carriage returns ("\n") rather than Windows/DOS ones ("\r\n"). -# Don’t put an @c else right after a @c return. It’s unnecessary and increases indentation level. -# Do not leave debug printfs or other debugging code lying around. -# Leave a blank line between a group of variable declarations and other code. -# Leave a space after the keywords @c if, @c switch, @c while, @c do, @c for, and @c return. -# Leave a space on each side of binary operators such as +, -, *, /, &&, and ||. For clarity in mathematical situations, you may omit the spaces on either side of * and / operators to illustrate their precedence over + and -. -# Do not leave a space between any dereferencing operator (such as *, &, [], ->, or .) and its operands. -# In parameter lists, leave a space after each comma. -# Do not leave a space after an opening parenthesis or before a closing parenthesis. -# Parentheses should be used to clarify operator precedence in expressions. -# Do not leave a space before an end-of-statement semicolon. -# Do not use literal tabs in strings or character constants. Rather, use spaces or "\t". -# If a parameter list is too long, break it between parameters. Group related parameters on lines if appropriate. -# Modified spacing is allowed to vertically align repetitive expressions. -# Always begin numeric constants with a digit (e.g., 0.001 not .001). -# Use K&R-style brace placement in public code. -# You may leave off braces for simple, single line flow control statements. -# The return type in a function definition should go on a line by itself. @subsection sIncludeStatements Include Statements -# Always use double quotes ("") to include header files that live in the same directory as your source code. -# Use angle brackets (<>) to include header files from outside a file’s directory. -# Do not use absolute paths in include directives. -# If there is a header corresponding to a given source file, list it first, followed by other local headers, library headers and then system headers. @subsection sHeaderFiles Header Files -# Header files have a .h extension. -# All header files should be bracketed by @c \#ifdef "guard" statements. -# In class definitions, list public, then protected, then private members. -# List methods before variables. -# Fully prototype all public functions and use descriptive naming for each argument. -# Declare every function defined in a header but outside a class definition explicitly as @c inline. -# Prefer forward declarations to @c \#include directives in headers. -# Do not take advantage of indirect inclusion of header files. @subsection sSourceFiles Source Files -# Source files have a .cc extension. -# Properly prototype all file static functions with usefully named arguments. -# Whenever possible, put variables and functions in an anonymous namespace. -# Avoid global variables. -# For the main file of an executable, define @c main() at the top and then utility functions below it in a top-down fashion. @subsection sComments Comments -# Use @c // style comments instead of / * * / style, even for multi-line comments. -# Use multi-line comments to describe following paragraphs of code. -# Use end-of-line comments to describe variable declarations or to clarify a single statement. -# Large blocks of code should be commented out with \#if 0 and @c \#endif. -# Do not leave obsolete code fragments within comments as an historical trail. @subsection sPrimitiveTypes Primitive Types -# Avoid writing code that is dependent on the bit size of primitive values, but when specific bit sizes are required, use explicitly-sized types such as @c int32_t or @c uint64_t. @subsection sMacros Macros -# Avoid macros for constants. Use static constants instead. -# Avoid macro functions. Use @c inline and templates instead. @subsection sClasses Classes -# Constructors that can be called with only one argument should be prefixed with the @c explicit keyword to avoid unintended type conversions. -# Always list the destructor. -# Never call virtual methods from destructors. -# If you have a copy constructor, make sure you have an assignment operator. -# If you have an assignment operator, you probably need a copy constructor. -# If you have data members that are pointers to dynamically allocated memory, make sure you have a copy constructor and an assignment operator, both of which do the right thing with that memory. -# Classes which are to be subclassed always have a virtual destructor, even if it is empty. -# Check against self assignment and return *this in assignment operators. -# Declare methods as const as much as possible. -# Declare object-valued input arguments as const references wherever possible. Primitives may be passed by value, however. -# Arithmetic, logical, bitwise, dereference, and address of operators should only be used when their semantics are clear, obvious, and unambiguous. -# The application operator [ () ] is allowed for functors. -# Conversion operators should be avoided. -# Never return const references to stack allocated or implicitly computed objects. -# If a class does not have a copy constructor and/or assignment operator, consider creating a private unimplemented copy constructor and/or assignment operator to prevent automatically generated versions from being used. @subsection sConditionalStatements Conditional Statements -# For test expressions, use a form that indicates as clearly as possible the types of operands by avoiding implicit casts. -# Assignments that occur within conditional statements must have no effect in the enclosing scope. -# Allow for arithmetic imprecision when comparing floating point values. -# In switch statements, always comment fallthroughs and empty cases. @section sNamespaces Namespaces -# Namespaces should be used whenever possible. -# Avoid pulling in entire namespaces with @c using directives (e.g., using namespace std;). -# @c using declarations are allowed for individual symbols (e.g., using std::vector;), but they should never appear in a header file. -# Define global operators in the namespace of their arguments. -# Namespaces are not indented. @subsection sExceptions Exceptions -# Appropriate use of exceptions is encouraged. -# Methods should declare all exceptions they might throw using comments, but not exception specifications. -# Throw scope local exception instances, not pointers or references or globals. -# Catch exceptions by reference. -# Never allow an exception to escape from a destructor. @subsection sTemplates Templates -# Use @c typename rather than @c class when declaring template type parameters. @subsection sMiscellaneous Miscellaneous -# Don’t use pointers to run through arrays of non-primitive types. Use explicit array indexing, iterators or generic algorithms instead. -# Use C++ casts (static_cast<int>(x) or int(x)), not C casts ((int)x). -# Multiple variables of the same data type may be declared on the same line -# Library code must never deliberately terminate the application in response to an error condition. -# Avoid using malloc/free when new/delete can be used instead. -# Avoid @c goto. -# Avoid \"magic numbers\". Use named constants when necessary. -# If you use typeid/typeinfo, be aware that although all runtimes support typeinfo::name(), the format of the string it returns varies between compilers and even for a given compiler the value is not guaranteed to be unique. */ openvdb/doc/doc.txt0000644000000000000000000005165012252452022013267 0ustar rootroot/** @mainpage OpenVDB The @b OpenVDB library comprises a hierarchical data structure and a suite of tools for the efficient manipulation of sparse, possibly time-varying, volumetric data discretized on a three-dimensional grid. It is based on VDB, which was developed by Ken Museth at DreamWorks Animation, and it offers an effectively infinite 3D index space, compact storage (both in memory and on disk), fast data access (both random and sequential), and a collection of algorithms specifically optimized for the data structure for common tasks such as filtering, CSG, compositing, sampling and voxelization from other geometric representations. The technical details of VDB are described in the paper “VDB: High-Resolution Sparse Volumes with Dynamic Topology”. @b OpenVDB is maintained by DreamWorks Animation and was developed primarily by - Ken Museth - Peter Cucka - Mihai Aldén - David Hill See the @subpage overview "Overview" for an introduction to the library. See @subpage transformsAndMaps "Transforms and Maps" for more discussion of the transforms used in @b OpenVDB. See the @subpage faq "FAQ" for frequently asked questions about @b OpenVDB. See the @subpage codeExamples "Cookbook" to get started using @b OpenVDB. See @subpage python "Using OpenVDB in Python" to get started with the @b OpenVDB Python module. See the @subpage changes "Release Notes" for what's new in this version of @b OpenVDB. Contributors, please familiarize yourselves with our @subpage codingStyle "coding standards". @page overview OpenVDB Overview @section Contents - @ref secOverview - @ref secTree - @ref subsecTreeConfig - @ref secSparsity - @ref subsecValues - @ref subsecInactive - @ref secSpaceAndTrans - @ref subsecVoxSpace - @ref subsecWorSpace - @ref subsecTrans - @ref secToolUtils - @ref secIterator - @ref subsecTreeIter - @ref subsecNodeIter - @ref subsecValueAccessor - @ref subsecTraversal @section secOverview Introduction This document is a high-level summary of the terminology and basic components of the OpenVDB library and is organized around two key motivating concepts. First, OpenVDB is designed specifically to work efficiently with sparse volumetric data locally sampled at a high spatial frequency, although it will function well for dense volumetric data. From this follows the need for a memory efficient representation of this sparsity and the need for fast iterators (and other tools) that respect sparsity. Second, data storage is separated from data interpretation. OpenVDB uses unit-less three-dimensional integer coordinates to address the sparse data, but introduces a unit-less continuous index space for interpolation, along with a transform to place the data in physical space. When manipulating data in OpenVDB, the three essential objects are (1) the @vdblink::tree::Tree Tree@endlink, a B-tree-like three-dimensional data structure; (2) the @vdblink::math::Transform Transform@endlink, which relates voxel indices @ijk to physical locations @xyz in @ref subsecWorSpace "world" space; and (3) the @vdblink::Grid Grid@endlink, a container that associates a @c Tree with a @c Transform and additional metadata. For instancing purposes (i.e., placing copies of the same volume in multiple locations), the same tree may be referenced (via smart pointers) by several different Grids, each having a unique transform. We now proceed to discuss the @c Tree and ideas of sparsity in some detail, followed by a briefer description of the different spaces and transforms as well as some of the tools that act on the sparse data. @section secTree The Tree In OpenVDB the @c Tree data structure exists to answer the question What value is stored at location @ijk in three-dimensional index space? Here i, j and k are arbitrary signed 32-bit integers, and the data type of the associated value (float, bool, vector, etc.) is the same for all @ijk. While the @c Tree serves the same purpose as a large three-dimensional array, it is a specially designed data structure that, given sparse unique values, minimizes the overall memory footprint while retaining fast access times. This is accomplished, as the name suggests, via a tree-based acceleration structure comprising a @vdblink::tree::RootNode RootNode@endlink, @vdblink::tree::LeafNode LeafNodes@endlink and usually one or more levels of @vdblink::tree::InternalNode InternalNodes@endlink with prescribed branching factors. @subsection subsecTreeConfig Tree Configuration The tree-based acceleration structure can be configured in various ways, but with the restriction that for a given tree all the LeafNodes are at the same depth. Conceptually, the @c RootNode and @c InternalNodes increasingly subdivide the three-dimensional index space, and the LeafNodes hold the actual unique voxels. The type of a @c Tree encodes both the type of the data to be stored in the tree (float, bool, etc.) and the tree's node configuration. In practice a four-level (root, internal, internal, leaf) configuration is standard, and several common tree types are defined in openvdb.h. For example, @code typedef tree::Tree4::Type FloatTree; typedef tree::Tree4::Type BoolTree; @endcode These predefined tree types share the same branching factors, which dictate the number of children of a given node. The branching factors (5, 4, 3) are specified as base two logarithms and should be read backwards from the leaf nodes up the tree. In the default tree configuration, each @c LeafNode holds a three-dimensional grid of 23 voxels on a side (i.e., an @f$8\times8\times8@f$ voxel grid). Internally, the @c LeafNode is said to be at "level 0" of the tree. At "level 1" of this tree is the first @c InternalNode, and it indexes a @f$2^4\times2^4\times2^4 = 16\times16\times16@f$ grid, each entry of which is either a @c LeafNode or a constant value that represents an @f$8\times8\times8@f$ block of voxels. At "level 2" is the second @c InternalNode in this configuration; it in turn indexes a @f$2^5\times2^5\times2^5 = 32\times32\times32@f$ grid of level-1 InternalNodes and/or values, and so the @c InternalNode at level 2 subsumes a three-dimensional block of voxels of size @f$32\times16\times8 = 4096@f$ on a side. Unlike the InternalNodes and LeafNodes, the @c RootNode ("level 3" for the default configuration) is not explicitly restricted in the number of children it may have, so the overall index space is limited only by the range of the integer indices, which are 32-bit by default. @section secSparsity Sparse Values and Voxels Like a tree's node configuration, the type of data held by a tree is determined at compile time. Conceptually the tree itself employs two different notions of data sparsity to reduce the memory footprint and at the same time accelerate access to its contents. The first is largely hidden from the user and concerns ways in which large regions of uniform values are compactly represented, and the second allows for fast sequential iteration, skipping user-specified "uninteresting" regions (that may or may not have uniform values). @subsection subsecValues Tile, Voxel, and Background Values Although the data in a tree is accessed and set on a per-voxel level (i.e., the value at @ijk) it need not be internally stored in that way. To reduce the memory footprint and accelerate data access, data values are stored in three distinct forms internal to the tree: voxel values, tile values, and a background value. A voxel value is a unique value indexed by the location of a voxel and is stored in the @c LeafNode responsible for that voxel. A tile value is a uniform value assigned to all voxels subsumed by a given node. (For example, a tile Value belonging to an @c InternalNode at level 1 is equivalent to a constant-value cube of voxels of the same size, @f$8\times8\times8@f$, as a @c LeafNode.) The tile value is returned when a request is made for the data associated with any @ijk location within the uniform tile. The background value is a unique value (stored at the root level) that is returned when accessing any @ijk location that does not resolve to either a tile or a @c LeafNode. @subsection subsecInactive Active and Inactive Voxels Any voxel or tile can be classified as either @b active or @b inactive. The interpretation of this state is application-specific, but generally active voxels are "interesting", and inactive somehow less so. The locations of active values may be sparse in the overall voxel topology, and OpenVDB provides @ref secIterator "iterators" that access active values only (as well as iterators over inactive values, all values, and general topology). An example of active vs. inactive: the voxels used to store the distance values of a narrow-band level set (i.e., close to a given surface) will be marked as active while the other ("far") voxel locations will be marked as inactive and will generally represent regions of space with constant distance values (e.g., two constant distance values of opposite sign to distinguish the enclosed inside region from the infinite outside or background embedding). The @vdblink::tree::Tree::prune() prune()@endlink method replaces with tile values any nodes that subsume voxels with the same values and active states. The resulting tree represents the same volume, but more sparsely. @section secSpaceAndTrans Coordinate Systems and Transforms The sampled data in the tree is accessed using signed index coordinates @ijk, but associating each indicial coordinate with a specific physical location is a job for a @c Transform. A simple linear transform assumes a lattice-like structure with a fixed physical distance @f$\Delta@f$ between indices, so that @f$(x,y,z) = (\Delta i, \Delta j, \Delta k)@f$. @subsection subsecVoxSpace Index Space To simplify transformations between physical space and lattice index coordinates, a continuous generalization of the index lattice points called index space is used. For example, index space coordinate (1.0, 1.0, 1.0) corresponds to the same point as (1,1,1) in the index lattice, but (1.5,1.0,1.0) also has meaning as halfway between the index coordinates (1,1,1) and (2,1,1). Index space can be used in constructing interpolated data values: given an arbitrary location in physical space, one can use a transform to compute the point in index space (which need not fall on an exact integer index) that maps to that location and locally interpolate from values with neighboring index coordinates. @subsection subsecWorSpace World Space The interpretation of the data in a tree takes place in world space. For example, the tree might hold data sampled at discrete physical locations in world space. @c Transform methods such as @vdblink::math::Transform::indexToWorld() indexToWorld() @endlink and its inverse @vdblink::math::Transform::worldToIndex() worldToIndex() @endlink may be used to relate coordinates in the two continuous spaces. In addition, methods such as @vdblink::math::Transform::worldToIndexCellCentered() worldToIndexCellCentered()@endlink actually return lattice points. @subsection subsecTrans Transforms and Maps A @c Grid contains smart pointers to both a @c Tree object and a @vdblink::math::Transform Transform @endlink object. The transform provides a context for interpreting the information held in the tree by associating a location in world space with each entry in the tree. The actual implementation of the @c Transform is managed by a @vdblink::math::MapBase Map@endlink object, which is an encapsulation of a continuous, mostly invertible function of three variables. A @c Map is required to provide @vdblink::math::MapBase::applyMap() applyMap()@endlink and @vdblink::math::MapBase::applyInverseMap() applyInverseMap()@endlink methods to relate locations in its domain to its range and vice versa. A @c Map is also required to provide information about its local derivatives. For more on these classes, see the @subpage transformsAndMaps "Transforms and Maps" page. @section secToolUtils Utilities and Tools OpenVDB provides utility functions and classes for the manipulation of grids and the data they hold. Tools such as those found in GridOperators.h compute vector quantities from scalar data or vice-versa. Other tools perform filtering (Filter.h and LevelSetFilter.h) and interpolation (Interpolation.h) as well as sampling (GridTransformer.h), compositing and constructive solid geometry (Composite.h), and other transformations (ValueTransformer.h). OpenVDB also supports advanced finite difference computations through a variety of local support stencils (Stencils.h). @section secIterator Iterators OpenVDB provides efficient, often multithreaded, implementations of a large variety of morphological, filtering and other algorithms that address common data manipulation tasks on three-dimensional grids. For more specialized tasks, OpenVDB provides lower-level data accessors that enable fast iteration over all or selected voxels and over the elements of a @c Tree. These take several forms: iterator classes of various types, functor-based @b visitor methods, and the @vdblink::tree::ValueAccessor ValueAccessor@endlink, an accelerator for indexed @ijk voxel lookups. Iterator classes follow a fairly consistent naming scheme. First, the @b CIter and @b Iter suffixes denote @const and non-@const iterators, i.e., iterators that offer, respectively, read-only and read/write access to the underlying tree or node. Second, iterators over tile and voxel values are denoted either @b On, @b Off or @b All, indicating that they visit only active values, only inactive values, or both active and inactive values. So, for example, @c Tree::ValueOnCIter is a read-only iterator over all active values (both tile and voxel) of a tree, whereas @c LeafNode::ValueAllIter is a read/write iterator over all values, both active and inactive, of a single leaf node. OpenVDB iterators are not STL-compatible in that one can always request an iterator that points to the beginning of a collection of elements (nodes, voxels, etc.), but one usually cannot request an iterator that points to the end of the collection. (This is because finding the end might require a full tree traversal.) Instead, all OpenVDB iterators implement a @c test() method that returns @c true as long as the iterator is not exhausted and @c false as soon as it is. Typical usage is as follows: @code typedef openvdb::FloatGrid GridType; GridType grid = ...; for (GridType::ValueOnCIter iter = grid.cbeginValueOn(); iter.test(); ++iter) ... @endcode or more compactly @code for (GridType::ValueOnCIter iter = grid.cbeginValueOn(); iter; ++iter) ... @endcode Note that the naming scheme for methods that return "begin" iterators closely mirrors that of the iterators themselves. That is, @c Grid::cbeginValueOn() returns a @const iterator to the first of a grid's active values, whereas @c LeafNode::beginValueAll() returns a non-@const iterator to the first of a leaf node's values, both active and inactive. (Const overloads of @c begin*() methods are usually provided, so that if the @c Grid is itself @const, @c Grid::begin*() will actually return a @const iterator. This makes it more convenient to use these methods in templated code.) Finally, note that modifying the tree or node over which one is iterating typically does not invalidate the iterator, though it might first need to be incremented to point to the next existing element (for example, if one deletes a child node to which the iterator is currently pointing). @subsection subsecTreeIter Tree Iterators @anchor treeValueIterRef @par Tree::ValueIter Tree-level value iterators traverse an entire tree, visiting each value (tile or voxel) exactly once. (It is also possible to restrict the traversal to minimum and maximum levels of the tree.) In addition to the methods common to all OpenVDB iterators, such as @c test() and @c next(), a @c Tree::ValueIter provides methods that return the depth in the tree of the node within which the iterator is pointing (the root node has depth 0) and the @ijk axis-aligned bounding box of the tile or voxel to which it is pointing, and methods to get and set both the value and the active state of the tile or voxel. See the @vdblink::tree::TreeValueIteratorBase TreeValueIteratorBase @endlink class for the complete list. @anchor treeLeafIterRef @par Tree::LeafIter Narrow-band level sets are represented by three distinct regions of voxels: a thin band of active voxels whose values are signed distances; an @b outside (or background) region of inactive voxels having a constant, positive distance; and an @b inside region of inactive voxels having a constant, negative distance. By convention in OpenVDB, narrow-band voxels are stored only at the leaf level of a tree, so to facilitate the implementation of level set algorithms that operate on narrow-band voxels, OpenVDB provides an iterator that visits each @c LeafNode in a tree exactly once. See the @vdblink::tree::LeafIteratorBase LeafIteratorBase @endlink class for details. @anchor treeNodeIterRef @par Tree::NodeIter A node iterator traverses a tree in depth-first order, starting from its root, and visits each node exactly once. (It is also possible to restrict the traversal to minimum and maximum node depths—see the @vdblink::tree::NodeIteratorBase NodeIteratorBase @endlink class for details.) Like the tree-level value iterator, the node iterator provides methods that return the depth in the tree of the node to which the iterator is pointing (the root node has depth 0) and the @ijk axis-aligned bounding box of the voxels subsumed by the node and all of its children. @par Naturally, a node iterator also provides access to the node to which it is pointing, but this is complicated somewhat by the fact that nodes of the various types (@c RootNode, @c InternalNode and @c LeafNode) do not inherit from a common base class. For efficiency, OpenVDB generally avoids class inheritance and virtual functions in favor of templates, allowing the compiler to optimize away function calls. In particular, each node type is templated on the type of its children, so even two InternalNodes at different levels of a tree have distinct types. As a result, it is necessary to know the type of the node to which a node iterator is pointing in order to request access to that node. See the @ref sNodeIterator "Cookbook" for an example of how to do this. @subsection subsecNodeIter Node Iterators Less commonly used than tree-level iterators (but found in the implementations of some of the narrow-band level set algorithms referred to @ref treeLeafIterRef "above") are node-level iterators. A node value iterator visits the values (active, inactive or both) stored in a single @c RootNode, @c InternalNode or @c LeafNode, whereas a node child iterator visits the children of a single root or internal node. (Recall that non-leaf nodes store either a tile value or a child node at each grid position.) @subsection subsecValueAccessor Value Accessor When traversing a grid by @ijk index in a spatially coherent pattern, such as when iterating over neighboring voxels, request a @vdblink::tree::ValueAccessor ValueAccessor@endlink from the grid (with @vdblink::Grid::getAccessor() Grid::getAccessor()@endlink) and use the accessor's @vdblink::tree::ValueAccessor::getValue() getValue()@endlink and @vdblink::tree::ValueAccessor::setValue() setValue()@endlink methods, since these will usually be significantly faster (a factor of three is typical) than accessing voxels directly in the grid's tree. The accessor records the sequence of nodes visited during the most recent access; on the next access, rather than traversing the tree from the root node down, it performs an inverted traversal from the deepest recorded node up. For neighboring voxels, the traversal need only proceed as far as the voxels' common ancestor node, which more often than not is the first node in the sequence. Multiple accessors may be associated with a single grid. In fact, for multithreaded, read-only access to a grid, it is recommended that each thread be assigned its own accessor. A thread-safe, mutex-locked accessor is provided (see @vdblink::tree::ValueAccessorRW ValueAccessorRW@endlink), but the locking negates much of the performance benefit of inverted traversal; and because it is the accessor object that is thread-safe, not the grid, concurrent reads and writes are not safe unless all threads share a single accessor. All accessors associated with a grid must be cleared after any operation that removes nodes from the grid's tree, such as pruning, CSG or compositing. For those and other built-in operations, this is done automatically via a callback mechanism, but developers must be careful to call @vdblink::tree::Tree::clearAllAccessors() Tree::clearAllAccessors()@endlink whenever deleting nodes directly. @subsection subsecTraversal Tree Traversal To be written */ openvdb/doc/math.txt0000644000000000000000000004372612252452022013460 0ustar rootroot/** @page transformsAndMaps Transforms and Maps @section Contents - @ref sTransforms - @ref sLinearTransforms - @ref sFrustumTransforms - @ref sCellVsVertex - @ref sVoxels - @ref sStaggered - @ref sMaps - @ref sGettingMat4 - @ref sCostOfMaps - @ref sGradientAndMaps @section sTransforms Transforms in OpenVDB The OpenVDB @vdblink::tree::Tree Tree@endlink is a sparse representation of a three-dimensional array of voxels, each element of which is addressed via discrete, three-dimensional index space coordinates, typically in the form of a @vdblink::math::Coord Coord@endlink. For example, the following code retrieves the floating-point value of a voxel with index coordinates (1, 2, 3): @code openvdb::FloatGrid grid = ...; openvdb::FloatGrid::Accessor accessor = grid.getAccessor(); openvdb::Coord ijk(1,2,3); float value = accessor.getValue(ijk); @endcode A @vdblink::math::Transform Transform@endlink relates index space coordinates to world space coordinates that give a spatial context for the discretized data.Translation from index coordinates @ijk to world space coordinates @xyz is done with a call to the @vdblink::math::Transform::indexToWorld() indexToWorld@endlink method, and from world space coordinates to index space coordinates with a call to @vdblink::math::Transform::worldToIndex() worldToIndex@endlink: @code // Create a linear transform that scales i, j and k by 0.1 openvdb::math::Transform::Ptr linearTransform = openvdb::math::Transform::createLinearTransform(0.1); // Compute the location in world space that is the image of (1,2,3). // The result will be (0.1, 0.2, 0.3). openvdb::Coord ijk(1,2,3); openvdb::Vec3d worldSpacePoint = linearTransform->indexToWorld(ijk); // Compute the location in index space that is the pre-image of (0.1, 0.2, 0.3). // The result will be (1.0, 2.0, 3.0). openvdb::Vec3d indexSpacePoint = linearTransform->worldToIndex(worldSpacePoint); @endcode In the above example, there are two things to notice. First, world space locations are specified as three-dimensional, double-precision, floating-point vectors, and second, @c worldToIndex does not return discrete coordinates, but rather a floating-point vector. This is a reflection of the fact that not every location in a continuous world space, i.e., not every @xyz, is the image of discrete integral coordinates @ijk. @subsection sLinearTransforms Linear Transforms Currently two different types of transforms are supported: linear and frustum transforms. A linear transform can be composed of scale, translation, rotation, and shear; essentially those things that can be mathematically represented by an invertible, constant-coefficient, @f$3 \times 3@f$ matrix and a translation (mathematically, an affine map). An essential feature of a linear transformation is that it treats all regions of index space equally: a small box in index space about origin @ijk=(0,0,0) is mapped (sheared, scaled, rotated, etc.) in just the same way that a small box about any other index point is mapped. @code // Create a linear transform from a 4x4 matrix (identity in this example). openvdb::math::Mat4d mat = openvdb::math::Mat4d::identity(); openvdb::math::Transform::Ptr linearTransform = openvdb::math::Transform::createLinearTransform(mat); // Rotate the transform by 90 degrees about the X axis. // As a result the j-index will now map into the -z physical direction, // and the k-index will map to the +y physical direction. linearTransform->preRotate(M_PI/2, openvdb::math::X_AXIS); @endcode @subsection sFrustumTransforms Frustum Transforms The frustum transform is a nonlinear transform that treats different index points differently. And while the linear transform can be applied to any point in index or world space, the frustum transform is designed to operate on a subset of space. Specifically, it transforms a given box in index space to a tapered box in world space that is a frustum of a rectangular pyramid. @code // Create the bounding box that will be mapped by the transform into // the shape of a frustum. // The points (0,0,0), (100,0,0), (0,50,0) and (100,50,0) will map to // the corners of the near plane of the frustum, while the corners // of the far plane will be the images of (0,0,120), (100,0,120), // (0,50,120) and (100,50,120). const openvdb::math::BBoxd bbox(/*min=*/openvdb::math::Vec3d(0,0,0), /*max=*/openvdb::math::Vec3d(100,50,120)); // The far plane of the frustum will be twice as big as the near plane. const double taper = 2; // The depth of the frustum will be 10 times the x-width of the near plane. cosnt double depth = 10; // The x-width of the frustum in world space units const double xWidth = 100; // Construct a frustum transform that results in a frustum whose // near plane is centered on the origin in world space. openvdb::math::Transform::Ptr frustumTransform = openvdb::math:::Transform::createFrustumTransform( bbox, taper, depth, xWidth); // The frustum shape can be rotated, scaled, translated and even // sheared if desired. For example, the following call translates // the frustum by 10,15,0 in world space: frustumTransform->postTranslate(openvdb::math::Vec3d(10,15,0)); // Compute the world space image of a given point within // the index space bounding box that defines the frustum. openvdb::Coord ijk(20,10,18); openvdb::Vec3d worldLocation = frustumTransform->indexToWorld(ijk); @endcode @subsection sCellVsVertex Cell-Centered vs. Vertex-Centered Transforms When partitioning world space with a regular grid, two popular configurations are cell-centered and vertex-centered grids. This is really a question of interpretation and transforms. The cell-centered interpretation imagines world space as divided into discrete cells (e.g., cubes) centered on the image of the index-space lattice points. So the physical location @xyz that is the image (result of @c indexToWorld) of a lattice point @ijk is the center of a cube. In the vertex-centered approach, the images of the lattice points form the vertices of cells, so the location @xyz would be a corner, not the center, of a cube. The link between transforms and cell-centered or vertex-centered partitioning of world space is tenuous. It boils down to this: the “first” cell vertex often is aligned with the origin. In the cell-centered case, when the cells are cubes of length @f$\Delta@f$ on a side, the transform @f$(x,y,z) = (\Delta i + \Delta/2, \Delta j + \Delta/2, \Delta k + \Delta /2 )@f$ will place the center of the first cube (i.e., the image of @f$(0,0,0)@f$) at the world space location of @f$(\Delta/2, \Delta/2, \Delta/2)@f$, and the cube will have walls coincident with the @f$x=0@f$, @f$y=0@f$ and @f$z=0@f$ planes. Using the OpenVDB transforms to create a so-called cell-centered transform could be done like this: @code // -- Constructing a uniform, cell-centered transform -- // The grid spacing const double delta = 0.1; // The offset to cell-center points const openvdb::math::Vec3d offset(delta/2., delta/2., delta/2.); // A linear transform with the correct spacing openvdb::math::Transform::Ptr transform = openvdb::math:::Transform::createLinearTransform(delta); // Add the offset. transform->postTranslate(offset); @endcode In contrast, for the vertex-centered partitions of space the first vertex is just the image of the first lattice point @f$ijk = (0,0,0)@f$, and the transform would lack the offset used in the cell-centered case; so it would simply be @f$(x,y,z) = (\Delta i , \Delta j ,\Delta k)@f$ @code // -- Constructing a uniform, vertex-centered transform -- // The grid spacing const double delta = 0.1; // A linear transform with the correct spacing openvdb::math::Transform::Ptr transform = openvdb::math:::Transform::createLinearTransform(delta); @endcode @subsection sVoxels Voxel Interpretations A similar and often related concept to cell- and vertex-centered partitioning of world space is the idea of a voxel. A voxel historically refers to the volumetric equivalent of a pixel and as such implies a small region of world space. A voxel could, for instance, be the image under transform of a vertex-centered (or cell-centered) box in index space. In this way the voxel can be seen as a generalization of regular grid cells. The interpretation of data stored in a grid can be related to the concept of a voxel but need not be. An application might interpret the grid value indexed by @ijk as the spatial average of a quantity in a defined world-space voxel centered on the image of that lattice point. But in other situations the value stored at @ijk might be a discrete sample taken from a single point in world space. The @vdblink::math::Transform Transform@endlink class does include methods such as @vdblink::math::Transform::voxelSize() voxelSize@endlink and @vdblink::math::Transform::voxelVolume() voxelVolume@endlink that suppose a particular interpretation of a voxel. They assume a voxel that is the image of a vertex-centered cube in index space, so the @c voxelSize methods return the lengths of line segments in world space that connect vertices: @code openvdb::Coord ijk(0,0,0); openvdb::Coord tmp0(1,0,0), tmp1(0,1,0), tmp2(0,0,1); openvdb::math::Vec3d size; size.x() = (xform.indexToWorld(ijk + tmp0) - xform.indexToWorld(ijk)).length(); size.y() = (xform.indexToWorld(ijk + tmp1) - xform.indexToWorld(ijk)).length(); size.z() = (xform.indexToWorld(ijk + tmp2) - xform.indexToWorld(ijk)).length(); // The voxelSize() for the voxel at (0,0,0) is consistent with // the computation above. assert(xform.voxelSize(ijk) == size); @endcode In the case where the transform is linear, the result of @c voxelSize will be independent of the actual location @ijk, but the voxel size for a nonlinear transform such as a frustum will be spatially varying. The related @c voxelVolume can not in general be computed from the @c voxelSize, because there is no guarantee that a general transform will convert a cube-shaped voxel into another cube. As a result, the @c voxelVolume actually returns the determinant of the transform, which will be a correct measure of volume even if the voxel is sheared into a general parallelepiped. @subsection sStaggered Staggered Velocity Grids Staggered velocity data is often used in fluid simulations, and the relationship between data interpretation and transforms is somewhat peculiar when using a vector grid to hold staggered velocity data. In this case the lattice point @ijk identifies a cell in world space by mapping to the cell’s center, but each element of the velocity vector is identified with a different face of the cell. The first element of the vector is identified with the image of the @f$(i-1/2,j,k)@f$ face, the second element with @f$(i,j-1/2,k)@f$, and the third element with @f$(i,j,k-1/2)@f$. @section sMaps Maps in OpenVDB Transforms The actual work of a @vdblink::math::Transform Transform@endlink is performed by an implementation object called a @vdblink::math::MapBase Map@endlink. The map in turn is a polymorphic object whose derived types are designed to optimally represent the most common transformations. Specifically, the @vdblink::math::Transform Transform@endlink holds a @vdblink::math::MapBase MapBase@endlink pointer to a derived type that has been simplified to minimize calculations. When the transform is updated by prepending or appending a linear operation (e.g., with @vdblink::math::Transform::preRotate() preRotate@endlink), the implementation map is recomputed and simplified if possible. For example, in many level-set-oriented applications the transform between index space and world space is simply a uniform scaling of the index points, i.e., @f$(x,y,z) = (\Delta i, \Delta j, \Delta k)@f$, where @f$\Delta@f$ has some world space units. For transforms such as this, the implementation is a @vdblink::math::UniformScaleMap UniformScaleMap@endlink. @code // Create a linear transform that scales i, j and k by 0.1 openvdb::math::Transform::Ptr linearTransform = openvdb::math::Transform::createLinearTransform(0.1); // Create an equivalent map. openvdb::math::UniformScaleMap uniformScale(0.1); // At this time the map holds a openvdb::math::UniformScaleMap. assert(linearTransform->mapType() == openvdb::math::UniformScaleMap::type()); openvdb::Coord ijk(1,2,3); // Applying the transform... openvdb::math::Vec3d transformResult = linearTransform->indexToWorld(ijk); // ...is equivalent to applying the map. openvdb::math::Vec3d mapResult = uniformScale.applyMap(ijk); assert(mapResult == transformResult); @endcode There are specialized maps for a variety of common linear transforms: pure translation (@vdblink::math::TranslationMap TranslationMap@endlink), uniform scale (@vdblink::math::UniformScaleMap UniformScaleMap@endlink), uniform scale and translation (@vdblink::math::UniformScaleTranslateMap UniformScaleTranslateMap@endlink), non-uniform scale (@vdblink::math::ScaleMap ScaleMap@endlink), and non-uniform scale and translation (@vdblink::math::ScaleTranslateMap ScaleTranslateMap@endlink). A general affine map (@vdblink::math::AffineMap AffineMap@endlink) is used for all other cases (those that include non-degenerate rotation or shear). @subsection sGettingMat4 An Equivalent Matrix Representation The matrix representation used within OpenVDB adheres to the minority convention of right-multiplication of the matrix against a vector: @code // Example matrix transform that scales, next translates, // and finally rotates an incoming vector openvdb::math::Mat4d transform = openvdb::math::Mat4d::identity(); transform.preScale(openvdb::math::Vec3d(2,3,2)); transform.postTranslate(openvdb::math::Vec3d(1,0,0)); transform.postRotate(openvdb::math::X_AXIS, M_PI/3.0); // Location of a point in index space openvdb::math::Vec3d indexSpace(1,2,3); // Apply the transform by right-multiplying the matrix. openvdb::math::Vec3d worldSpace = indexSpace * transform; @endcode Any linear map can produce an equivalent @vdblink::math::AffineMap AffineMap@endlink, which in turn can produce an equivalent @f$4 \times 4@f$ matrix. Starting with a linear transform one can produce a consistent matrix as follows: @code openvdb::math::Mat4d matrix; if (transform->isLinear()) { // Get the equivalent 4x4 matrix. matrix = transform->getBaseMap()->getAffineMap()->getMat4(); } @endcode This could be used as an intermediate form when constructing new linear transforms by combining old ones. @code // Get the matrix equivalent to linearTransformA. openvdb::math::Mat4d matrixA = linearTransformA->getBaseMap()->getAffineMap()->getMat4(); // Invert the matrix equivalent to linearTransformB. openvdb::math::Mat4d matrixBinv = (linearTransformB->getBaseMap()->getAffineMap()->getMat4()).inverse(); // Create a new transform that maps the index space of linearTransformA // to the index space of linearTransformB. openvdb::math::Transform::Ptr linearTransformAtoB = openvdb::math::Trasform::createLinearTransform(matrixA * matrixBinv); @endcode Notice that in the above example, the internal representation used by the transform will be simplified if possible to use one of the various map types. @subsection sCostOfMaps Working Directly with Maps Accessing a transform’s map through virtual function calls introduces some overhead and precludes compile-time optimizations such as inlining. For this reason, the more computationally intensive OpenVDB tools are templated to work directly with any underlying map. This allows the tools direct access to the map’s simplified representation and gives the compiler a free hand to inline. Maps themselves know nothing of index space and world space, but are simply functions @f$x_{range} = f(x_{domain})@f$ that map 3-vectors from one space (the domain) to another (the range), or from the range back to the domain (@f$x_{domain} = f^{-1}(x_{range})@f$), by means of the methods @vdblink::math::MapBase::applyMap() applyMap@endlink and @vdblink::math::MapBase::applyInverseMap() applyInverseMap@endlink. @code // Create a simple uniform scale map that scales by 10. openvdb::math::UniformScaleMap usm(10.0); // A point in the domain openvdb::math::Vec3d domainPoint(0,1,3); // The resulting range point openvdb::math::Vec3d rangePoint = usm.applyMap(domainPoint); // The map is inverted thus: assert(domainPoint == usm.applyInverseMap(rangePoint)); @endcode In many tools, the actual map type is not known a priori and must be deduced at runtime prior to calling the appropriate map-specific or map-templated code. The type of map currently being used by a transform can be determined using the @vdblink::math::Transform::mapType() mapType@endlink method: @code // Test for a uniform scale map. if (transform->mapType() == openvdb::math::UniformScaleMap::type()) { // This call would return a null pointer in the case of a map type mismatch. openvdb::math::UniformScaleMap::ConstPtr usm = transform->map(); // Call a function that accepts UniformScaleMaps. dofoo(*usm) } @endcode To simplify this process, the function @vdblink::math::processTypedMap processTypedMap@endlink has been provided. It accepts a transform and a functor templated on the map type. @subsection sGradientAndMaps Maps and Mathematical Operations In addition to supporting the mapping of a point from one space to another, maps also support mapping of local gradients. This results from use of the chain rule of calculus in computing the Jacobian of the map. Essentially, the gradient calculated in the domain of a map can be converted to the gradient in the range of the map, allowing one to compute a gradient in index space and then transform it to a gradient in world space. @code // Compute the gradient at a point in index space in a // floating-point grid using the second-order central difference. openvdb::FloatGrid grid = ...; openvdb::Coord ijk(2,3,5) openvdb::math::Vec3d isGrad = openvdb::math::ISGradient::result(grid, ijk); // Apply the inverse Jacobian transform to convert the result to the // gradient in the world space defined by a map that scales index space // to create voxels of size 0.1 x 0.2 x 0.1 openvdb::math::ScaleMap scalemap(0.1, 0.2, 0.1); openvdb::math::Vec3d wsGrad = scalemap.applyIJT(isGrad); @endcode */ openvdb/doc/examplecode.txt0000644000000000000000000010672112252452022015010 0ustar rootroot/** @page codeExamples OpenVDB Cookbook This section provides code snippets and some complete programs that illustrate how to use OpenVDB and how to perform common tasks. @section Contents - @ref sHelloWorld - @ref sCompilingHelloWorld - @ref sAllocatingGrids - @ref sPopulatingGrids - @ref sModifyingGrids - @ref sStreamIO - @ref sHandlingMetadata - @ref sAddingMetadata - @ref sGettingMetadata - @ref sRemovingMetadata - @ref sIteration - @ref sNodeIterator - @ref sLeafIterator - @ref sValueIterator - @ref sXformTools - @ref sResamplingTools - @ref sValueXformTools - @ref sCombiningGrids - @ref sCsgTools - @ref sCompTools - @ref sCombineTools - @ref sGenericProg - @ref sTypedGridMethods @section sHelloWorld "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: @code #include #include int main() { // Initialize the OpenVDB library. This must be called at least // once per program and may safely be called multiple times. openvdb::initialize(); // Create an empty floating-point grid with background value 0. openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(); std::cout << "Testing random access:" << std::endl; // Get an accessor for coordinate-based access to voxels. openvdb::FloatGrid::Accessor accessor = grid->getAccessor(); // Define a coordinate with large signed indices. openvdb::Coord xyz(1000, -200000000, 30000000); // Set the voxel value at (1000, -200000000, 30000000) to 1. accessor.setValue(xyz, 1.0); // Verify that the voxel value at (1000, -200000000, 30000000) is 1. std::cout << "Grid" << xyz << " = " << accessor.getValue(xyz) << std::endl; // Reset the coordinates to those of a different voxel. xyz.reset(1000, 200000000, -30000000); // Verify that the voxel value at (1000, 200000000, -30000000) is // the background value, 0. std::cout << "Grid" << xyz << " = " << accessor.getValue(xyz) << std::endl; // Set the voxel value at (1000, 200000000, -30000000) to 2. accessor.setValue(xyz, 2.0); // Set the voxels at the two extremes of the available coordinate space. // For 32-bit signed coordinates these are (-2147483648, -2147483648, -2147483648) // and (2147483647, 2147483647, 2147483647). accessor.setValue(openvdb::Coord::min(), 3.0f); accessor.setValue(openvdb::Coord::max(), 4.0f); std::cout << "Testing sequential access:" << std::endl; // Print all active ("on") voxels by means of an iterator. for (openvdb::FloatGrid::ValueOnCIter iter = grid->cbeginValueOn(); iter; ++iter) { std::cout << "Grid" << iter.getCoord() << " = " << *iter << std::endl; } } @endcode Output: @code 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 @endcode @subsection sCompilingHelloWorld Compiling See the @c Makefile and @c 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 @c INSTALL_DIR variable in the makefile or by setting the desired value from the command line, as in the following example: @code make install INSTALL_DIR=/usr/local @endcode 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 @c vdb_print tool: @code rm vdb_print make verbose=yes vdb_print @endcode and then replace “-o vdb_print” with, for example, “-o helloworld” and “cmd/openvdb_print/main.cc” with “helloworld.cc”. @section sAllocatingGrids 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 @ref sPopulatingGrids, below, for the implementation of the @c makeSphere() function.) @code #include int main() { openvdb::initialize(); // Create a shared pointer to a newly-allocated grid of a built-in type: // in this case, a FloatGrid, which stores one single-precision floating point // value per voxel. Other built-in grid types include BoolGrid, DoubleGrid, // Int32Grid and Vec3SGrid (see openvdb.h for the complete list). // The grid comprises a sparse tree representation of voxel data, // user-supplied metadata and a voxel space to world space transform, // which defaults to the identity transform. openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(/*background value=*/2.0); // Populate the grid with a sparse, narrow-band level set representation // of a sphere with radius 50 voxels, located at (1.5, 2, 3) in index space. makeSphere(*grid, /*radius=*/50.0, /*center=*/openvdb::Vec3f(1.5, 2, 3)); // Associate some metadata with the grid. grid->insertMeta("radius", openvdb::FloatMetadata(50.0)); // Associate a scaling transform with the grid that sets the voxel size // to 0.5 units in world space. grid->setTransform( openvdb::math::Transform::createLinearTransform(/*voxel size=*/0.5)); // Identify the grid as a level set. grid->setGridClass(openvdb::GRID_LEVEL_SET); // Name the grid "LevelSetSphere". grid->setName("LevelSetSphere"); // Create a VDB file object. openvdb::io::File file("mygrids.vdb"); // Add the grid pointer to a container. openvdb::GridPtrVec grids; grids.push_back(grid); // Write out the contents of the container. file.write(grids); file.close(); } @endcode The OpenVDB library includes optimized routines for many common tasks. For example, most of the steps given above are encapsulated in the function @vdblink::tools::createLevelSetSphere() tools::createLevelSetSphere()@endlink, so that the above can be written simply as follows: @code #include #include int main() { openvdb::initialize(); // Create a FloatGrid and populate it with a narrow-band // signed distance field of a sphere. openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( /*radius=*/50.0, /*center=*/openvdb::Vec3f(1.5, 2, 3), /*voxel size=*/0.5, /*width=*/4.0); // Associate some metadata with the grid. grid->insertMeta("radius", openvdb::FloatMetadata(50.0)); // Name the grid "LevelSetSphere". grid->setName("LevelSetSphere"); // Create a VDB file object. openvdb::io::File file("mygrids.vdb"); // Add the grid pointer to a container. openvdb::GridPtrVec grids; grids.push_back(grid); // Write out the contents of the container. file.write(grids); file.close(); } @endcode @section sPopulatingGrids 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 @ref sGenericProg for more on processing grids of arbitrary type. @anchor makeSphereCode @code // Populate the given grid with a narrow-band level set representation of a sphere. // The width of the narrow band is determined by the grid's background value. // (Example code only; use tools::createSphereSDF() in production.) template void makeSphere(GridType& grid, float radius, const openvdb::Vec3f& c) { typedef typename GridType::ValueType ValueT; // Distance value for the constant region exterior to the narrow band const ValueT outside = grid.background(); // Distance value for the constant region interior to the narrow band // (by convention, the signed distance is negative in the interior of // a level set) const ValueT inside = -outside; // Use the background value as the width in voxels of the narrow band. // (The narrow band is centered on the surface of the sphere, which // has distance 0.) int padding = int(openvdb::math::RoundUp(openvdb::math::Abs(outside))); // The bounding box of the narrow band is 2*dim voxels on a side. int dim = int(radius + padding); // Get a voxel accessor. typename GridType::Accessor accessor = grid.getAccessor(); // Compute the signed distance from the surface of the sphere of each // voxel within the bounding box and insert the value into the grid // if it is smaller in magnitude than the background value. openvdb::Coord ijk; int &i = ijk[0], &j = ijk[1], &k = ijk[2]; for (i = c[0] - dim; i < c[0] + dim; ++i) { const float x2 = openvdb::math::Pow2(i - c[0]); for (j = c[1] - dim; j < c[1] + dim; ++j) { const float x2y2 = openvdb::math::Pow2(j - c[1]) + x2; for (k = c[2] - dim; k < c[2] + dim; ++k) { // The distance from the sphere surface in voxels const float dist = openvdb::math::Sqrt(x2y2 + openvdb::math::Pow2(k - c[2])) - radius; // Convert the floating-point distance to the grid's value type. ValueT val = ValueT(dist); // Only insert distances that are smaller in magnitude than // the background value. if (val < inside || outside < val) continue; // Set the distance for voxel (i,j,k). accessor.setValue(ijk, val); } } } // Propagate the outside/inside sign information from the narrow band // throughout the grid. grid.signedFloodFill(); } @endcode @section sModifyingGrids Reading and modifying a grid @code #include openvdb::initialize(); // Create a VDB file object. openvdb::io::File file("mygrids.vdb"); // Open the file. This reads the file header, but not any grids. file.open(); // Loop over all grids in the file and retrieve a shared pointer // to the one named "LevelSetSphere". (This can also be done // more simply by calling file.readGrid("LevelSetSphere").) openvdb::GridBase::Ptr baseGrid; for (openvdb::io::File::NameIterator nameIter = file.beginName(); nameIter != file.endName(); ++nameIter) { // Read in only the grid we are interested in. if (nameIter.gridName() == "LevelSetSphere") { baseGrid = file.readGrid(nameIter.gridName()); } else { std::cout << "skipping grid " << nameIter.gridName() << std::endl; } } file.close(); // From the example above, "LevelSetSphere" is known to be a FloatGrid, // so cast the generic grid pointer to a FloatGrid pointer. openvdb::FloatGrid::Ptr grid = openvdb::gridPtrCast(baseGrid); // Convert the level set sphere to a narrow-band fog volume, in which // interior voxels have value 1, exterior voxels have value 0, and // narrow-band voxels have values varying linearly from 0 to 1. const float outside = grid->background(); const float width = 2.0 * outside; // Visit and update all of the grid's active values, which correspond to // voxels on the narrow band. for (openvdb::FloatGrid::ValueOnIter iter = grid->beginValueOn(); iter; ++iter) { float dist = iter.getValue(); iter.setValue((outside - dist) / width); } // Visit all of the grid's inactive tile and voxel values and update the values // that correspond to the interior region. for (openvdb::FloatGrid::ValueOffIter iter = grid->beginValueOff(); iter; ++iter) { if (iter.getValue() < 0.0) { iter.setValue(1.0); iter.setValueOff(); } } // Set exterior voxels to 0. grid->setBackground(0.0); @endcode @section sStreamIO Stream I/O The @vdblink::io::Stream io::Stream@endlink 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 @vdblink::io::File io::File@endlink, grids can be read individually by name, provided that they were originally written with @c io::File, rather than streamed to a file.) @code #include #include openvdb::initialize(); openvdb::GridPtrVecPtr grids(new GridPtrVec); grids->push_back(...); // Stream the grids to a string. std::ostringstream ostr(std::ios_base::binary); openvdb::io::Stream(ostr).write(*grids); // Stream the grids to a file. std::ofstream ofile("mygrids.vdb", std::ios_base::binary); openvdb::io::Stream(ofile).write(*grids); // Stream in grids from a string. // Note that io::Stream::getGrids() returns a shared pointer // to an openvdb::GridPtrVec. std::istringstream istr(ostr.str(), std::ios_base::binary); openvdb::io::Stream strm(istr); grids = strm.getGrids(); // Stream in grids from a file. std::ifstream ifile("mygrids.vdb", std::ios_base::binary); grids = openvdb::io::Stream(ifile).getGrids(); @endcode @section sHandlingMetadata 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 @vdblink::MetaMap MetaMap@endlink that can optionally be supplied to a @vdblink::io::File::write() file@endlink or @vdblink::io::Stream::write() stream@endlink for writing. @subsection sAddingMetadata Adding metadata The @vdblink::Grid::insertMeta() Grid::insertMeta()@endlink method either adds a new (@em name, @em 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. @code #include openvdb::Vec3SGrid::Ptr grid = openvdb::Vec3SGrid::create(); grid->insertMeta("vector type", openvdb::StringMetadata("covariant (gradient)")); grid->insertMeta("radius", openvdb::FloatMetadata(50.0)); grid->insertMeta("center", openvdb::Vec3SMetadata(openvdb::Vec3S(10, 15, 10))); // OK, overwrites existing value: grid->insertMeta("center", openvdb::Vec3SMetadata(openvdb::Vec3S(10.5, 15, 30))); // Error (throws openvdb::TypeError), can't overwrite a value of type Vec3S // with a value of type float: grid->insertMeta("center", openvdb::FloatMetadata(0.0)); @endcode @subsection sGettingMetadata Retrieving metadata Call @vdblink::Grid::metaValue() Grid::metaValue()@endlink to retrieve the value of metadata of a known type. For example, @code std::string s = grid->metaValue("vector type"); float r = grid->metaValue("radius"); // Error (throws openvdb::TypeError), can't read a value of type Vec3S as a float: float center = grid->metaValue("center"); @endcode @vdblink::Grid::beginMeta() Grid::beginMeta()@endlink and @vdblink::Grid::beginMeta() Grid::beginMeta()@endlink return STL @c std::map iterators over all of the metadata associated with a grid: @code for (openvdb::MetaMap::MetaIterator iter = grid->beginMeta(); iter != grid->endMeta(); ++iter) { const std::string& name = iter->first; openvdb::Metadata::Ptr value = iter->second; std::string valueAsString = value->str(); std::cout << name << " = " << valueAsString << std::endl; } @endcode If the type of the metadata is not known, use the @vdblink::Grid::operator[]() index operator@endlink to retrieve a shared pointer to a generic @vdblink::Metadata Metadata@endlink object, then query its type: @code openvdb::Metadata::Ptr metadata = grid["center"]; // See typenameAsString() in Types.h for a list of strings that can be // returned by the typeName() method. std::cout << metadata->typeName() << std::endl; // prints "vec3s" // One way to process metadata of arbitrary types: if (metadata->typeName() == openvdb::StringMetadata::staticTypeName()) { std::string s = static_cast(*metadata).value(); } else if (metadata->typeName() == openvdb::FloatMetadata::staticTypeName()) { float f = static_cast(*metadata).value(); } else if (metadata->typeName() == openvdb::Vec3SMetadata::staticTypeName()) { openvdb::Vec3S v = static_cast(*metadata).value(); } @endcode @subsection sRemovingMetadata Removing metadata @vdblink::Grid::removeMeta() Grid::removeMeta()@endlink removes metadata by name. If the given name is not found, the call has no effect. @code grid->removeMeta("vector type"); grid->removeMeta("center"); grid->removeMeta("vector type"); // OK (no effect) @endcode @section sIteration Iteration @subsection sNodeIterator Node Iterator A @vdblink::tree::Tree::NodeIter Tree::NodeIter@endlink visits each node in a tree exactly once. In the following example, the tree is known to have a depth of 4; see the @ref treeNodeIterRef "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. @code #include typedef openvdb::FloatGrid GridType; typedef GridType::TreeType TreeType; typedef TreeType::RootNodeType RootType; // level 3 RootNode assert(RootType::LEVEL == 3); typedef RootType::ChildNodeType Int1Type; // level 2 InternalNode typedef Int1Type::ChildNodeType Int2Type; // level 1 InternalNode typedef TreeType::LeafNodeType LeafType; // level 0 LeafNode 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; } } } @endcode @subsection sLeafIterator Leaf Node Iterator A @vdblink::tree::Tree::LeafIter Tree::LeafIter@endlink visits each leaf node in a tree exactly once. @code #include typedef openvdb::FloatGrid GridType; typedef GridType::TreeType TreeType; GridType::Ptr grid = ...; // Iterate over references to const LeafNodes. for (TreeType::LeafCIter iter = grid->tree().cbeginLeaf(); iter; ++iter) { const TreeType::LeafNodeType& leaf = *iter; ... } // Iterate over references to non-const LeafNodes. for (TreeType::LeafIter iter = grid->tree().beginLeaf(); iter; ++iter) { TreeType::LeafNodeType& leaf = *iter; ... } // Iterate over pointers to const LeafNodes. for (TreeType::LeafCIter iter = grid->tree().cbeginLeaf(); iter; ++iter) { const TreeType::LeafNodeType* leaf = iter.getLeaf(); ... } // Iterate over pointers to non-const LeafNodes. for (TreeType::LeafIter iter = grid->tree().beginLeaf(); iter; ++iter) { TreeType::LeafNodeType* leaf = iter.getLeaf(); ... } @endcode See the @ref treeLeafIterRef "Overview" for more on leaf node iterators. @subsection sValueIterator Value Iterator A @vdblink::tree::Tree::ValueAllIter Tree::ValueIter@endlink visits each @ref subsecValues "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: @code #include typedef openvdb::Vec3SGrid GridType; typedef GridType::TreeType TreeType; GridType::Ptr grid = ...; // Iterate over all active values but don't allow them to be changed. for (GridType::ValueOnCIter iter = grid->cbeginValueOn(); iter.test(); ++iter) { const openvdb::Vec3f& value = *iter; // Print the coordinates of all voxels whose vector value has // a length greater than 10, and print the bounding box coordinates // of all tiles whose vector value length is greater than 10. if (value.length() > 10.0) { if (iter.isVoxelValue()) { std::cout << iter.getCoord() << std::endl; } else { openvdb::CoordBBox bbox; iter.getBoundingBox(bbox); std::cout << bbox << std::endl; } } } // Iterate over and normalize all inactive values. for (GridType::ValueOffIter iter = grid->beginValueOff(); iter.test(); ++iter) { openvdb::Vec3f value = *iter; value.normalize(); iter.setValue(value); } // Normalize the (inactive) background value as well. grid->setBackground(grid->background().unit()); @endcode See the @ref treeValueIterRef "Overview" for more on value iterators. @section sXformTools Transforming grids @subsection sResamplingTools Geometric transformation A @vdblink::tools::GridTransformer GridTransformer@endlink 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 @vdblink::tools::GridTransformer::setThreaded() setThreaded(false)@endlink. A @c GridTransformer object can be reused to apply the same transformation to multiple input grids, optionally using different sampling schemes. @code #include #include openvdb::FloatGrid::Ptr sourceGrid = ... targetGrid = ...; // Get the source and target grids' index space to world space transforms. const openvdb::math::Transform &sourceXform = sourceGrid->transform(), &targetXform = targetGrid->transform(); // Compute a source grid to target grid transform. // (For this example, we assume that both grids' transforms are linear, // so that they can be represented as 4 x 4 matrices.) openvdb::Mat4R xform = sourceXform.baseMap()->getAffineMap()->getMat4() * targetXform.baseMap()->getAffineMap()->getMat4().inverse(); // Create the transformer. openvdb::tools::GridTransformer transformer(xform); // Resample using nearest-neighbor interpolation. transformer.transformGrid( *sourceGrid, *targetGrid); // Resample using trilinear interpolation. transformer.transformGrid( *sourceGrid, *targetGrid); // Resample using triquadratic interpolation. transformer.transformGrid( *sourceGrid, *targetGrid); // Prune the target tree for optimal sparsity. targetGrid->tree().prune(); @endcode @subsection sValueXformTools Value transformation This example uses @vdblink::tools::foreach() tools::foreach()@endlink to multiply all values (both tile and voxel and both active and inactive) of a scalar, floating-point grid by two: @code #include #include // Define a local function that doubles the value to which the given // value iterator points. struct Local { static inline void op(const openvdb::FloatGrid::ValueAllIter& iter) { iter.setValue(*iter * 2); } }; openvdb::FloatGrid::Ptr grid = ...; // Apply the function to all values. openvdb::tools::foreach(grid->beginValueAll(), Local::op); @endcode This example uses @vdblink::tools::foreach() tools::foreach()@endlink to rotate all active vectors of a vector-valued grid by 45 degrees about the @em y axis: @code #include #include // Define a functor that multiplies the vector to which the given // value iterator points by a fixed matrix. struct MatMul { openvdb::math::Mat3s M; MatMul(const openvdb::math::Mat3s& mat): M(mat) {} inline void operator()(const openvdb::Vec3SGrid::ValueOnIter& iter) const { iter.setValue(M.transform(*iter)); } }; openvdb::Vec3SGrid::Ptr grid = ...; // Construct the rotation matrix. openvdb::math::Mat3s rot45 = openvdb::math::rotation(openvdb::math::Y_AXIS, M_PI_4); // Apply the functor to all active values. openvdb::tools::foreach(grid->beginValueOn(), MatMul(rot45)); @endcode @vdblink::tools::transformValues() tools::transformValues()@endlink is similar to @vdblink::tools::foreach() tools::foreach()@endlink, 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 @vdblink::tools::Magnitude tools::Magnitude@endlink): @code #include #include // Define a local function that, given an iterator pointing to a vector value // in an input grid, sets the corresponding tile or voxel in a scalar, // floating-point output grid to the length of the vector. struct Local { static inline void op( const openvdb::Vec3SGrid::ValueOnCIter& iter, openvdb::FloatGrid::ValueAccessor& accessor) { if (iter.isVoxelValue()) { // set a single voxel accessor.setValue(iter.getCoord(), iter->length()); } else { // fill an entire tile openvdb::CoordBBox bbox; iter.getBoundingBox(bbox); accessor.getTree().fill(bbox, iter->length()); } } }; openvdb::Vec3SGrid::ConstPtr inGrid = ...; // Create a scalar grid to hold the transformed values. openvdb::FloatGrid::Ptr outGrid = openvdb::FloatGrid::create(); // Populate the output grid with transformed values. openvdb::tools::transformValues(inGrid->cbeginValueOn(), *outGrid, Local::op); @endcode @section sCombiningGrids Combining grids The following examples show various ways in which a pair of grids can be combined in @ref subsecVoxSpace "index space". The assumption is that index coordinates @ijk in both grids correspond to the same physical, @ref subsecWorSpace "world space" location. When the grids have different transforms, it is usually necessary to first @ref sResamplingTools "resample" one grid into the other grid's @ref subsecVoxSpace "index space". @subsection sCsgTools 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. @code #include #include // Two grids of the same type containing level set volumes openvdb::FloatGrid::Ptr gridA(...), gridB(...); // Save copies of the two grids; CSG operations always modify // the A grid and leave the B grid empty. openvdb::FloatGrid::ConstPtr copyOfGridA = gridA->deepCopy(), copyOfGridB = gridB->deepCopy(); // Compute the union (A u B) of the two level sets. openvdb::tools::csgUnion(*gridA, *gridB); // Restore the original level sets. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // Compute the intersection (A n B) of the two level sets. openvdb::tools::csgIntersection(gridA, gridB); // Restore the original level sets. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // Compute the difference (A / B) of the two level sets. openvdb::tools::csgDifference(gridA, gridB); @endcode @subsection sCompTools Compositing operations Like the @ref sCsgTools "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. @code #include #include // Two grids of the same type openvdb::FloatGrid::Ptr gridA = ..., gridB = ...; // Save copies of the two grids; compositing operations always // modify the A grid and leave the B grid empty. openvdb::FloatGrid::ConstPtr copyOfGridA = gridA->deepCopy(), copyOfGridB = gridB->deepCopy(); // At each voxel, compute a = max(a, b). openvdb::tools::compMax(*gridA, *gridB); // Restore the original grids. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // At each voxel, compute a = min(a, b). openvdb::tools::compMin(*gridA, *gridB); // Restore the original grids. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // At each voxel, compute a = a + b. openvdb::tools::compSum(*gridA, *gridB); // Restore the original grids. gridA = copyOfGridA->deepCopy(); gridB = copyOfGridB->deepCopy(); // At each voxel, compute a = a * b. openvdb::tools::compMul(*gridA, *gridB); @endcode @subsection sCombineTools Generic combination The @vdblink::tree::Tree::combine() Tree::combine()@endlink 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 @vdblink::tree::Tree::combine() Tree::combine()@endlink method to compute the difference between corresponding voxels of two floating-point grids: @code #include // Define a local function that subtracts two floating-point values. struct Local { static inline void diff(const float& a, const float& b, float& result) { result = a - b; } }; openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; // Compute the difference between corresponding voxels of aGrid and bGrid // and store the result in aGrid, leaving bGrid empty. aGrid->tree().combine(bGrid->tree(), Local::diff); @endcode Another @vdblink::tree::Tree::combine() Tree::combine()@endlink example, this time using a functor to preserve state: @code #include // Define a functor that computes f * a + (1 - f) * b for pairs of // floating-point values a and b. 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; }; openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; // Compute a = 0.25 * a + 0.75 * b for all corresponding voxels of // aGrid and bGrid. Store the result in aGrid and empty bGrid. aGrid->tree().combine(bGrid->tree(), Blend(0.25)); @endcode The @vdblink::tree::Tree::combineExtended() Tree::combineExtended()@endlink method invokes a function of the form void f(CombineArgs\& args), where the @vdblink::CombineArgs CombineArgs@endlink object encapsulates an @em a and a @em 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 @c aGrid are replaced with corresponding values from floating-point @c bGrid (leaving @c bGrid empty) wherever the @em b values are larger. The active states of any transferred values are preserved. @code #include // Define a local function that retrieves a and b values from a CombineArgs // struct and then sets the result member to the maximum of a and b. struct Local { static inline void max(CombineArgs& args) { if (args.b() > args.a()) { // Transfer the B value and its active state. args.setResult(args.b()); args.setResultIsActive(args.bIsActive()); } else { // Preserve the A value and its active state. args.setResult(args.a()); args.setResultIsActive(args.aIsActive()); } } }; openvdb::FloatGrid::Ptr aGrid = ..., bGrid = ...; aGrid->tree().combineExtended(bGrid->tree(), Local::max); @endcode Like @c combine(), @vdblink::tree::Tree::combine2() Tree::combine2()@endlink applies an operation to pairs of corresponding values of two trees. However, @c 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 @c combine() method.) Here, the voxel differencing example above is repeated using @c combine2(): @code #include struct Local { static inline void diff(const float& a, const float& b, float& result) { result = a - b; } }; openvdb::FloatGrid::ConstPtr aGrid = ..., bGrid = ...; openvdb::FloatGrid::Ptr resultGrid = openvdb::FloatGrid::create(); // Combine aGrid and bGrid and write the result into resultGrid. // The input grids are not modified. resultGrid->tree().combine2(aGrid->tree(), bGrid->tree(), Local::diff); @endcode An @vdblink::tree::Tree::combine2Extended() extended combine2()@endlink is also available. @section sGenericProg Generic programming @subsection sTypedGridMethods Calling Grid methods A common task is to perform some operation on all of the grids in a file, where the operation involves @vdblink::Grid Grid@endlink method calls and the grids are of different types. Only a handful of @c Grid methods, such as @vdblink::Grid::activeVoxelCount() activeVoxelCount()@endlink, are virtual and can be called through a @vdblink::GridBase GridBase@endlink pointer; most are not, because they require knowledge of the Grid's value type. For example, one might want to @vdblink::tree::Tree::prune() prune()@endlink the trees of all of the grids in a file regardless of their type, but @c Tree::prune() is non-virtual because it accepts an optional pruning tolerance argument whose type is the grid's value type. The @c processTypedGrid() function below makes this kind of task easier. It is called with a @c GridBase pointer and a functor whose call operator accepts a pointer to a @c Grid of arbitrary type. The call operator should be templated on the grid type and, if necessary, overloaded for specific grid types. @code template void processTypedGrid(openvdb::GridBase::Ptr grid, OpType& op) { #define CALL_OP(GridType) \ op.template operator()(openvdb::gridPtrCast(grid)) if (grid->isType()) CALL_OP(openvdb::BoolGrid); else if (grid->isType()) CALL_OP(openvdb::FloatGrid); else if (grid->isType()) CALL_OP(openvdb::DoubleGrid); else if (grid->isType()) CALL_OP(openvdb::Int32Grid); else if (grid->isType()) CALL_OP(openvdb::Int64Grid); else if (grid->isType()) CALL_OP(openvdb::Vec3IGrid); else if (grid->isType()) CALL_OP(openvdb::Vec3SGrid); else if (grid->isType()) CALL_OP(openvdb::Vec3DGrid); else if (grid->isType()) CALL_OP(openvdb::StringGrid); #undef CALL_OP } @endcode The following example shows how to use @c processTypedGrid() to implement a generic pruning operation for grids of all built-in types: @code #include // Define a functor that prunes the trees of grids of arbitrary type // with a fixed pruning tolerance. struct PruneOp { double tolerance; PruneOp(double t): tolerance(t) {} template void operator()(typename GridType::Ptr grid) const { grid->tree().prune(typename GridType::ValueType(tolerance)); } // Overload to handle string-valued grids void operator()(openvdb::StringGrid::Ptr grid) const { grid->tree().prune(); } }; // Read all grids from a file. openvdb::io::File file("mygrids.vdb"); file.open(); openvdb::GridPtrVecPtr myGrids = file.getGrids(); file.close(); // Prune each grid with a tolerance of 1%. const PruneOp pruner(/*tolerance=*/0.01); for (openvdb::GridPtrVecIter iter = myGrids->begin(); iter != myGrids->end(); ++iter) { openvdb::GridBase::Ptr grid = *iter; processTypedGrid(grid, pruner); } @endcode */ openvdb/unittest/0000755000000000000000000000000012252453157013076 5ustar rootrootopenvdb/unittest/TestLeafOrigin.cc0000644000000000000000000001104312252453157016263 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include class TestLeafOrigin: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestLeafOrigin); CPPUNIT_TEST(test); CPPUNIT_TEST(test2Values); CPPUNIT_TEST(testGetValue); CPPUNIT_TEST_SUITE_END(); void test(); void test2Values(); void testGetValue(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafOrigin); //////////////////////////////////////// void TestLeafOrigin::test() { using namespace openvdb; std::set indices; indices.insert(Coord( 0, 0, 0)); indices.insert(Coord( 1, 0, 0)); indices.insert(Coord( 0, 100, 8)); indices.insert(Coord(-9, 0, 8)); indices.insert(Coord(32, 0, 16)); indices.insert(Coord(33, -5, 16)); indices.insert(Coord(42, 17, 35)); indices.insert(Coord(43, 17, 64)); FloatTree tree(/*bg=*/256.0); std::set::iterator iter = indices.begin(); for ( ; iter != indices.end(); ++iter) tree.setValue(*iter, 1.0); for (FloatTree::LeafCIter leafIter = tree.cbeginLeaf(); leafIter; ++leafIter) { const Int32 mask = ~((1 << leafIter->log2dim()) - 1); const Coord leafOrigin = leafIter->origin(); for (FloatTree::LeafNodeType::ValueOnCIter valIter = leafIter->cbeginValueOn(); valIter; ++valIter) { Coord xyz = valIter.getCoord(); CPPUNIT_ASSERT_EQUAL(leafOrigin, xyz & mask); iter = indices.find(xyz); CPPUNIT_ASSERT(iter != indices.end()); indices.erase(iter); } } CPPUNIT_ASSERT(indices.empty()); } void TestLeafOrigin::test2Values() { using namespace openvdb; FloatGrid::Ptr grid = createGrid(/*bg=*/1.0f); FloatTree& tree = grid->tree(); tree.setValue(Coord(0, 0, 0), 5); tree.setValue(Coord(100, 0, 0), 6); grid->setTransform(math::Transform::createLinearTransform(0.1)); FloatTree::LeafCIter iter = tree.cbeginLeaf(); CPPUNIT_ASSERT_EQUAL(Coord(0, 0, 0), iter->origin()); ++iter; CPPUNIT_ASSERT_EQUAL(Coord(96, 0, 0), iter->origin()); } void TestLeafOrigin::testGetValue() { const openvdb::Coord c0(0,-10,0), c1(100,13,0); const float v0=5.0f, v1=6.0f, v2=1.0f; openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(v2)); tree->setValue(c0, v0); tree->setValue(c1, v1); openvdb::FloatTree::LeafCIter iter = tree->cbeginLeaf(); CPPUNIT_ASSERT_EQUAL(v0, iter->getValue(c0)); ++iter; CPPUNIT_ASSERT_EQUAL(v1, iter->getValue(c1)); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestVec2Metadata.cc0000644000000000000000000001061712252453157016512 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestVec2Metadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestVec2Metadata); CPPUNIT_TEST(testVec2i); CPPUNIT_TEST(testVec2s); CPPUNIT_TEST(testVec2d); CPPUNIT_TEST_SUITE_END(); void testVec2i(); void testVec2s(); void testVec2d(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestVec2Metadata); void TestVec2Metadata::testVec2i() { using namespace openvdb; Metadata::Ptr m(new Vec2IMetadata(openvdb::Vec2i(1, 1))); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("vec2i") == 0); CPPUNIT_ASSERT(m2->typeName().compare("vec2i") == 0); Vec2IMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec2i(1, 1)); s->value() = openvdb::Vec2i(2, 2); CPPUNIT_ASSERT(s->value() == openvdb::Vec2i(2, 2)); m2->copy(*s); s = dynamic_cast(m2.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec2i(2, 2)); } void TestVec2Metadata::testVec2s() { using namespace openvdb; Metadata::Ptr m(new Vec2SMetadata(openvdb::Vec2s(1, 1))); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("vec2s") == 0); CPPUNIT_ASSERT(m2->typeName().compare("vec2s") == 0); Vec2SMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec2s(1, 1)); s->value() = openvdb::Vec2s(2, 2); CPPUNIT_ASSERT(s->value() == openvdb::Vec2s(2, 2)); m2->copy(*s); s = dynamic_cast(m2.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec2s(2, 2)); } void TestVec2Metadata::testVec2d() { using namespace openvdb; Metadata::Ptr m(new Vec2DMetadata(openvdb::Vec2d(1, 1))); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("vec2d") == 0); CPPUNIT_ASSERT(m2->typeName().compare("vec2d") == 0); Vec2DMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec2d(1, 1)); s->value() = openvdb::Vec2d(2, 2); CPPUNIT_ASSERT(s->value() == openvdb::Vec2d(2, 2)); m2->copy(*s); s = dynamic_cast(m2.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec2d(2, 2)); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTreeIterators.cc0000644000000000000000000005333112252453157017046 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0) class TestTreeIterators: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTreeIterators); CPPUNIT_TEST(testLeafIterator); CPPUNIT_TEST(testEmptyLeafIterator); CPPUNIT_TEST(testOnlyNegative); CPPUNIT_TEST(testValueAllIterator); CPPUNIT_TEST(testValueOnIterator); CPPUNIT_TEST(testValueOffIterator); CPPUNIT_TEST(testModifyValue); CPPUNIT_TEST(testDepthBounds); CPPUNIT_TEST_SUITE_END(); void testLeafIterator(); void testEmptyLeafIterator(); void testOnlyNegative(); void testValueAllIterator(); void testValueOnIterator(); void testValueOffIterator(); void testModifyValue(); void testDepthBounds(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeIterators); typedef openvdb::FloatTree TreeType; void TestTreeIterators::testLeafIterator() { const float fillValue = 256.0f; TreeType tree(fillValue); tree.setValue(openvdb::Coord(0, 0, 0), 1.0); tree.setValue(openvdb::Coord(1, 0, 0), 1.5); tree.setValue(openvdb::Coord(0, 0, 8), 2.0); tree.setValue(openvdb::Coord(1, 0, 8), 2.5); tree.setValue(openvdb::Coord(0, 0, 16), 3.0); tree.setValue(openvdb::Coord(1, 0, 16), 3.5); tree.setValue(openvdb::Coord(0, 0, 24), 4.0); tree.setValue(openvdb::Coord(1, 0, 24), 4.5); float val = 1.0; for (TreeType::LeafCIter iter = tree.cbeginLeaf(); iter; ++iter) { const TreeType::LeafNodeType* leaf = iter.getLeaf(); CPPUNIT_ASSERT(leaf != NULL); ASSERT_DOUBLES_EXACTLY_EQUAL(val, leaf->getValue(openvdb::Coord(0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(val + 0.5, iter->getValue(openvdb::Coord(1, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, iter->getValue(openvdb::Coord(1, 1, 1))); val = val + 1.0; } } // Test the leaf iterator over a tree without any leaf nodes. void TestTreeIterators::testEmptyLeafIterator() { using namespace openvdb; TreeType tree(/*fillValue=*/256.0); std::vector dims; tree.getNodeLog2Dims(dims); CPPUNIT_ASSERT_EQUAL(4, int(dims.size())); // Start with an iterator over an empty tree. TreeType::LeafCIter iter = tree.cbeginLeaf(); CPPUNIT_ASSERT(!iter); // Using sparse fill, add internal nodes but no leaf nodes to the tree. // Fill the region subsumed by a level-2 internal node (assuming a four-level tree). Index log2Sum = dims[1] + dims[2] + dims[3]; CoordBBox bbox(Coord(0), Coord((1 << log2Sum) - 1)); tree.fill(bbox, /*value=*/1.0); iter = tree.cbeginLeaf(); CPPUNIT_ASSERT(!iter); // Fill the region subsumed by a level-1 internal node. log2Sum = dims[2] + dims[3]; bbox.reset(Coord(0), Coord((1 << log2Sum) - 1)); tree.fill(bbox, /*value=*/2.0); iter = tree.cbeginLeaf(); CPPUNIT_ASSERT(!iter); } void TestTreeIterators::testOnlyNegative() { using openvdb::Index64; const float fillValue = 5.0f; TreeType tree(fillValue); CPPUNIT_ASSERT(tree.empty()); ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, tree.getValue(openvdb::Coord(5, -10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue, tree.getValue(openvdb::Coord(-500, 200, 300))); tree.setValue(openvdb::Coord(-5, 10, 20), 0.1f); tree.setValue(openvdb::Coord( 5, -10, 20), 0.2f); tree.setValue(openvdb::Coord( 5, 10, -20), 0.3f); tree.setValue(openvdb::Coord(-5, -10, 20), 0.4f); tree.setValue(openvdb::Coord(-5, 10, -20), 0.5f); tree.setValue(openvdb::Coord( 5, -10, -20), 0.6f); tree.setValue(openvdb::Coord(-5, -10, -20), 0.7f); tree.setValue(openvdb::Coord(-500, 200, -300), 4.5678f); tree.setValue(openvdb::Coord( 500, -200, -300), 4.5678f); tree.setValue(openvdb::Coord(-500, -200, 300), 4.5678f); ASSERT_DOUBLES_EXACTLY_EQUAL(0.1f, tree.getValue(openvdb::Coord(-5, 10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.2f, tree.getValue(openvdb::Coord( 5, -10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.3f, tree.getValue(openvdb::Coord( 5, 10, -20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.4f, tree.getValue(openvdb::Coord(-5, -10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.5f, tree.getValue(openvdb::Coord(-5, 10, -20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.6f, tree.getValue(openvdb::Coord( 5, -10, -20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.7f, tree.getValue(openvdb::Coord(-5, -10, -20))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-500, 200, -300))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord( 500, -200, -300))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-500, -200, 300))); int count = 0; for (int i = -25; i < 25; ++i) { for (int j = -25; j < 25; ++j) { for (int k = -25; k < 25; ++k) { if (tree.getValue(openvdb::Coord(i, j, k)) < 1.0f) { //fprintf(stderr, "(%i, %i, %i) = %f\n", // i, j, k, tree.getValue(openvdb::Coord(i, j, k))); ++count; } } } } CPPUNIT_ASSERT_EQUAL(7, count); openvdb::Coord xyz; int count2 = 0; for (TreeType::ValueOnCIter iter = tree.cbeginValueOn();iter; ++iter) { ++count2; xyz = iter.getCoord(); //std::cerr << xyz << " = " << *iter << "\n"; } CPPUNIT_ASSERT_EQUAL(10, count2); CPPUNIT_ASSERT_EQUAL(Index64(10), tree.activeVoxelCount()); } void TestTreeIterators::testValueAllIterator() { const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; typedef openvdb::tree::Tree4::Type Tree323f; typedef Tree323f::RootNodeType RootT; typedef RootT::ChildNodeType Int1T; typedef Int1T::ChildNodeType Int2T; typedef Int2T::ChildNodeType LeafT; Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(4), 0.0f); tree.setValue(openvdb::Coord(-4), -1.0f); const size_t expectedNumOff = 2 * ((1 << (3 * DIM2)) - 1) // 2 8x8x8 InternalNodes - 1 child pointer each + 2 * ((1 << (3 * DIM1)) - 1) // 2 4x4x4 InternalNodes - 1 child pointer each + 2 * ((1 << (3 * DIM0)) - 1); // 2 8x8x8 LeafNodes - 1 active value each { Tree323f::ValueAllIter iter = tree.beginValueAll(); CPPUNIT_ASSERT(iter.test()); // Read all tile and voxel values through a non-const value iterator. size_t numOn = 0, numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(iter.getLevel() <= 3); const openvdb::Index iterLevel = iter.getLevel(); for (openvdb::Index lvl = 0; lvl <= 3; ++lvl) { RootT* root; Int1T* int1; Int2T* int2; LeafT* leaf; iter.getNode(root); CPPUNIT_ASSERT(root != NULL); iter.getNode(int1); CPPUNIT_ASSERT(iterLevel < 3 ? int1 != NULL: int1 == NULL); iter.getNode(int2); CPPUNIT_ASSERT(iterLevel < 2 ? int2 != NULL: int2 == NULL); iter.getNode(leaf); CPPUNIT_ASSERT(iterLevel < 1 ? leaf != NULL: leaf == NULL); } if (iter.isValueOn()) { ++numOn; const float f = iter.getValue(); if (openvdb::math::isZero(f)) { CPPUNIT_ASSERT(iter.getCoord() == openvdb::Coord(4)); CPPUNIT_ASSERT(iter.isVoxelValue()); } else { ASSERT_DOUBLES_EXACTLY_EQUAL(-1.0f, f); CPPUNIT_ASSERT(iter.getCoord() == openvdb::Coord(-4)); CPPUNIT_ASSERT(iter.isVoxelValue()); } } else { ++numOff; // For every tenth inactive value, check that the size of // the tile or voxel is as expected. if (numOff % 10 == 0) { const int dim[4] = { 1, 1 << DIM0, 1 << (DIM1 + DIM0), 1 << (DIM2 + DIM1 + DIM0) }; const int lvl = iter.getLevel(); CPPUNIT_ASSERT(lvl < 4); openvdb::CoordBBox bbox; iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL( bbox.extents(), openvdb::Coord(dim[lvl], dim[lvl], dim[lvl])); } } } CPPUNIT_ASSERT_EQUAL(2, int(numOn)); CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { Tree323f::ValueAllCIter iter = tree.cbeginValueAll(); CPPUNIT_ASSERT(iter.test()); // Read all tile and voxel values through a const value iterator. size_t numOn = 0, numOff = 0; for ( ; iter.test(); iter.next()) { if (iter.isValueOn()) ++numOn; else ++numOff; } CPPUNIT_ASSERT_EQUAL(2, int(numOn)); CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { Tree323f::ValueAllIter iter = tree.beginValueAll(); CPPUNIT_ASSERT(iter.test()); // Read all tile and voxel values through a non-const value iterator // and overwrite all active values. size_t numOn = 0, numOff = 0; for ( ; iter; ++iter) { if (iter.isValueOn()) { iter.setValue(iter.getValue() - 5); ++numOn; } else { ++numOff; } } CPPUNIT_ASSERT_EQUAL(2, int(numOn)); CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } } void TestTreeIterators::testValueOnIterator() { typedef openvdb::tree::Tree4::Type Tree323f; Tree323f tree(/*fillValue=*/256.0f); { Tree323f::ValueOnIter iter = tree.beginValueOn(); CPPUNIT_ASSERT(!iter.test()); // empty tree } const int STEP = 8/*100*/, NUM_STEPS = 10; for (int i = 0; i < NUM_STEPS; ++i) { tree.setValue(openvdb::Coord(STEP * i), 0.0f); } { Tree323f::ValueOnIter iter = tree.beginValueOn(); CPPUNIT_ASSERT(iter.test()); // Read all active tile and voxel values through a non-const value iterator. size_t numOn = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(iter.isVoxelValue()); CPPUNIT_ASSERT(iter.isValueOn()); ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); ++numOn; } CPPUNIT_ASSERT_EQUAL(NUM_STEPS, int(numOn)); } { Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); CPPUNIT_ASSERT(iter.test()); // Read all active tile and voxel values through a const value iterator. size_t numOn = 0; for ( ; iter.test(); iter.next()) { CPPUNIT_ASSERT(iter.isVoxelValue()); CPPUNIT_ASSERT(iter.isValueOn()); ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); ++numOn; } CPPUNIT_ASSERT_EQUAL(NUM_STEPS, int(numOn)); } { Tree323f::ValueOnIter iter = tree.beginValueOn(); CPPUNIT_ASSERT(iter.test()); // Read all active tile and voxel values through a non-const value iterator // and overwrite the values. size_t numOn = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(iter.isVoxelValue()); CPPUNIT_ASSERT(iter.isValueOn()); ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, iter.getValue()); iter.setValue(5.0f); ASSERT_DOUBLES_EXACTLY_EQUAL(5.0f, iter.getValue()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(STEP * numOn), iter.getCoord()); ++numOn; } CPPUNIT_ASSERT_EQUAL(NUM_STEPS, int(numOn)); } } void TestTreeIterators::testValueOffIterator() { const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; typedef openvdb::tree::Tree4::Type Tree323f; Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(4), 0.0f); tree.setValue(openvdb::Coord(-4), -1.0f); const size_t expectedNumOff = 2 * ((1 << (3 * DIM2)) - 1) // 2 8x8x8 InternalNodes - 1 child pointer each + 2 * ((1 << (3 * DIM1)) - 1) // 2 4x4x4 InternalNodes - 1 child pointer each + 2 * ((1 << (3 * DIM0)) - 1); // 2 8x8x8 LeafNodes - 1 active value each { Tree323f::ValueOffIter iter = tree.beginValueOff(); CPPUNIT_ASSERT(iter.test()); // Read all inactive tile and voxel values through a non-const value iterator. size_t numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(!iter.isValueOn()); ++numOff; // For every tenth inactive value, check that the size of // the tile or voxel is as expected. if (numOff % 10 == 0) { const int dim[4] = { 1, 1 << DIM0, 1 << (DIM1 + DIM0), 1 << (DIM2 + DIM1 + DIM0) }; const int lvl = iter.getLevel(); CPPUNIT_ASSERT(lvl < 4); openvdb::CoordBBox bbox; iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(bbox.extents(), openvdb::Coord(dim[lvl], dim[lvl], dim[lvl])); } } CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); CPPUNIT_ASSERT(iter.test()); // Read all inactive tile and voxel values through a const value iterator. size_t numOff = 0; for ( ; iter.test(); iter.next(), ++numOff) { CPPUNIT_ASSERT(!iter.isValueOn()); } CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { Tree323f::ValueOffIter iter = tree.beginValueOff(); CPPUNIT_ASSERT(iter.test()); // Read all inactive tile and voxel values through a non-const value iterator // and overwrite the values. size_t numOff = 0; for ( ; iter; ++iter, ++numOff) { iter.setValue(iter.getValue() - 5); iter.setValueOff(); } for (iter = tree.beginValueOff(); iter; ++iter, --numOff); CPPUNIT_ASSERT_EQUAL(size_t(0), numOff); } } void TestTreeIterators::testModifyValue() { using openvdb::Coord; const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; { typedef openvdb::tree::Tree4::Type IntTree323f; IntTree323f tree(/*background=*/256); tree.addTile(/*level=*/3, Coord(-1), /*value=*/ 4, /*active=*/true); tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/-3, /*active=*/true); tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/ 2, /*active=*/true); tree.addTile(/*level=*/0, Coord(0), /*value=*/-1, /*active=*/true); struct Local { static inline void negate(int32_t& n) { n = -n; } }; for (IntTree323f::ValueAllIter iter = tree.beginValueAll(); iter; ++iter) { iter.modifyValue(Local::negate); } for (IntTree323f::ValueAllCIter iter = tree.cbeginValueAll(); iter; ++iter) { const int32_t val = *iter; if (val < 0) CPPUNIT_ASSERT((-val) % 2 == 0); // negative values are even else CPPUNIT_ASSERT(val % 2 == 1); // positive values are odd } // Because modifying values through a const iterator is not allowed, // uncommenting the following line should result in a static assertion failure: //tree.cbeginValueOn().modifyValue(Local::negate); } { typedef openvdb::tree::Tree4::Type BoolTree323f; BoolTree323f tree; tree.addTile(/*level=*/3, Coord(-1), /*value=*/false, /*active=*/true); tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/ true, /*active=*/true); tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/false, /*active=*/true); tree.addTile(/*level=*/0, Coord(0), /*value=*/ true, /*active=*/true); struct Local { static inline void negate(bool& b) { b = !b; } }; for (BoolTree323f::ValueAllIter iter = tree.beginValueAll(); iter; ++iter) { iter.modifyValue(Local::negate); } CPPUNIT_ASSERT(!tree.getValue(Coord(0))); CPPUNIT_ASSERT( tree.getValue(Coord(1 << DIM0))); CPPUNIT_ASSERT(!tree.getValue(Coord(1 << (DIM0 + DIM1)))); CPPUNIT_ASSERT( tree.getValue(Coord(-1))); // Because modifying values through a const iterator is not allowed, // uncommenting the following line should result in a static assertion failure: //tree.cbeginValueOn().modifyValue(Local::negate); } { typedef openvdb::tree::Tree4::Type StringTree323f; StringTree323f tree(/*background=*/""); tree.addTile(/*level=*/3, Coord(-1), /*value=*/"abc", /*active=*/true); tree.addTile(/*level=*/2, Coord(1 << (DIM0 + DIM1)), /*value=*/"abc", /*active=*/true); tree.addTile(/*level=*/1, Coord(1 << DIM0), /*value=*/"abc", /*active=*/true); tree.addTile(/*level=*/0, Coord(0), /*value=*/"abc", /*active=*/true); struct Local { static inline void op(std::string& s) { s.append("def"); } }; for (StringTree323f::ValueOnIter iter = tree.beginValueOn(); iter; ++iter) { iter.modifyValue(Local::op); } const std::string expectedVal("abcdef"); for (StringTree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT_EQUAL(expectedVal, *iter); } for (StringTree323f::ValueOffCIter iter = tree.cbeginValueOff(); iter; ++iter) { CPPUNIT_ASSERT((*iter).empty()); } } } void TestTreeIterators::testDepthBounds() { const openvdb::Index DIM0 = 3, DIM1 = 2, DIM2 = 3; typedef openvdb::tree::Tree4::Type Tree323f; Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(4), 0.0f); tree.setValue(openvdb::Coord(-4), -1.0f); const size_t numDepth1 = 2 * ((1 << (3 * DIM2)) - 1), // 2 8x8x8 InternalNodes - 1 child pointer each numDepth2 = 2 * ((1 << (3 * DIM1)) - 1), // 2 4x4x4 InternalNodes - 1 child pointer each numDepth3 = 2 * ((1 << (3 * DIM0)) - 1), // 2 8x8x8 LeafNodes - 1 active value each expectedNumOff = numDepth1 + numDepth2 + numDepth3; { Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); CPPUNIT_ASSERT(iter.test()); // Read all inactive tile and voxel values through a non-const value iterator. size_t numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(!iter.isValueOn()); ++numOff; } CPPUNIT_ASSERT_EQUAL(expectedNumOff, numOff); } { // Repeat, setting the minimum iterator depth to 2. Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); CPPUNIT_ASSERT(iter.test()); iter.setMinDepth(2); CPPUNIT_ASSERT(iter.test()); size_t numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(!iter.isValueOn()); ++numOff; const int depth = iter.getDepth(); CPPUNIT_ASSERT(depth > 1); } CPPUNIT_ASSERT_EQUAL(expectedNumOff - numDepth1, numOff); } { // Repeat, setting the minimum and maximum depths to 2. Tree323f::ValueOffCIter iter = tree.cbeginValueOff(); CPPUNIT_ASSERT(iter.test()); iter.setMinDepth(2); CPPUNIT_ASSERT(iter.test()); iter.setMaxDepth(2); CPPUNIT_ASSERT(iter.test()); size_t numOff = 0; for ( ; iter; ++iter) { CPPUNIT_ASSERT(!iter.isValueOn()); ++numOff; const int depth = iter.getDepth(); CPPUNIT_ASSERT_EQUAL(2, depth); } CPPUNIT_ASSERT_EQUAL(expectedNumOff - numDepth1 - numDepth3, numOff); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMeanCurvature.cc0000644000000000000000000005720312252453157017035 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include "util.h" // for unittest_util::makeSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestMeanCurvature: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestMeanCurvature); CPPUNIT_TEST(testISMeanCurvature); // MeanCurvature in Index Space CPPUNIT_TEST(testISMeanCurvatureStencil); CPPUNIT_TEST(testWSMeanCurvature); // MeanCurvature in World Space CPPUNIT_TEST(testWSMeanCurvatureStencil); CPPUNIT_TEST(testMeanCurvatureTool); // MeanCurvature tool CPPUNIT_TEST(testMeanCurvatureMaskedTool); // MeanCurvature tool CPPUNIT_TEST(testOldStyleStencils); // old stencil impl - deprecate CPPUNIT_TEST_SUITE_END(); void testISMeanCurvature(); void testISMeanCurvatureStencil(); void testWSMeanCurvature(); void testWSMeanCurvatureStencil(); void testMeanCurvatureTool(); void testMeanCurvatureMaskedTool(); void testOldStyleStencils(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMeanCurvature); void TestMeanCurvature::testISMeanCurvature() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); Coord xyz(35,30,30); AccessorType inAccessor = grid->getConstAccessor(); AccessorType::ValueType alpha, beta; math::ISMeanCurvature::result(inAccessor, xyz, alpha, beta); AccessorType::ValueType meancurv = alpha/(2*math::Pow3(beta) ); AccessorType::ValueType normGrad = alpha/(2*math::Pow2(beta) ); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); math::ISMeanCurvature::result(inAccessor, xyz, alpha, beta); meancurv = alpha/(2*math::Pow3(beta) ); normGrad = alpha/(2*math::Pow2(beta) ); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); math::ISMeanCurvature::result(inAccessor, xyz, alpha, beta); meancurv = alpha/(2*math::Pow3(beta) ); normGrad = alpha/(2*math::Pow2(beta) ); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); xyz.reset(35,10,40); math::ISMeanCurvature::result(inAccessor, xyz, alpha, beta); meancurv = alpha/(2*math::Pow3(beta) ); normGrad = alpha/(2*math::Pow2(beta) ); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, normGrad, 0.001); } void TestMeanCurvature::testISMeanCurvatureStencil() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); Coord xyz(35,30,30); math::SecondOrderDenseStencil dense_2nd(*grid); math::FourthOrderDenseStencil dense_4th(*grid); math::SixthOrderDenseStencil dense_6th(*grid); dense_2nd.moveTo(xyz); dense_4th.moveTo(xyz); dense_6th.moveTo(xyz); AccessorType::ValueType alpha, beta; math::ISMeanCurvature::result(dense_2nd, alpha, beta); AccessorType::ValueType meancurv = alpha/(2*math::Pow3(beta) ); AccessorType::ValueType normGrad = alpha/(2*math::Pow2(beta) ); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); math::ISMeanCurvature::result(dense_4th, alpha, beta); meancurv = alpha/(2*math::Pow3(beta) ); normGrad = alpha/(2*math::Pow2(beta) ); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); math::ISMeanCurvature::result(dense_6th, alpha, beta); meancurv = alpha/(2*math::Pow3(beta) ); normGrad = alpha/(2*math::Pow2(beta) ); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); xyz.reset(35,10,40); dense_2nd.moveTo(xyz); math::ISMeanCurvature::result(dense_2nd, alpha, beta); meancurv = alpha/(2*math::Pow3(beta) ); normGrad = alpha/(2*math::Pow2(beta) ); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, normGrad, 0.001); } void TestMeanCurvature::testWSMeanCurvature() { using namespace openvdb; using math::AffineMap; using math::TranslationMap; using math::UniformScaleMap; typedef FloatGrid::ConstAccessor AccessorType; { // unit size voxel test FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); Coord xyz(35,30,30); AccessorType inAccessor = grid->getConstAccessor(); AccessorType::ValueType meancurv; AccessorType::ValueType normGrad; AffineMap affine; meancurv = math::MeanCurvature::result( affine, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( affine, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); meancurv = math::MeanCurvature::result( affine, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( affine, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); UniformScaleMap uniform; meancurv = math::MeanCurvature::result( uniform, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( uniform, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); xyz.reset(35,10,40); TranslationMap trans; meancurv = math::MeanCurvature::result( trans, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( trans, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, normGrad, 0.001); } { // non-unit sized voxel double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); AccessorType inAccessor = grid->getConstAccessor(); AccessorType::ValueType meancurv; AccessorType::ValueType normGrad; Coord xyz(20,16,20); AffineMap affine(voxel_size*math::Mat3d::identity()); meancurv = math::MeanCurvature::result( affine, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( affine, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, normGrad, 0.001); meancurv = math::MeanCurvature::result( affine, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( affine, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, normGrad, 0.001); UniformScaleMap uniform(voxel_size); meancurv = math::MeanCurvature::result( uniform, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( uniform, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, normGrad, 0.001); } { // NON-UNIFORM SCALING AND ROTATION Vec3d voxel_sizes(0.25, 0.45, 0.75); FloatGrid::Ptr grid = FloatGrid::create(); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); // apply rotation math::MapBase::Ptr rotated_map = base_map->preRotate(1.5, math::X_AXIS); grid->setTransform(math::Transform::Ptr(new math::Transform(rotated_map))); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); AccessorType inAccessor = grid->getConstAccessor(); AccessorType::ValueType meancurv; AccessorType::ValueType normGrad; Coord xyz(20,16,20); Vec3d location = grid->indexToWorld(xyz); double dist = (center - location).length(); AffineMap::ConstPtr affine = grid->transform().map(); meancurv = math::MeanCurvature::result( *affine, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( *affine, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/dist, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/dist, normGrad, 0.001); meancurv = math::MeanCurvature::result( *affine, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( *affine, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/dist, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/dist, normGrad, 0.001); } } void TestMeanCurvature::testWSMeanCurvatureStencil() { using namespace openvdb; using math::AffineMap; using math::TranslationMap; using math::UniformScaleMap; typedef FloatGrid::ConstAccessor AccessorType; { // unit-sized voxels FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f);//i.e. (35,30,40) in index space const float radius=0.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); Coord xyz(35,30,30); math::SecondOrderDenseStencil dense_2nd(*grid); math::FourthOrderDenseStencil dense_4th(*grid); math::SixthOrderDenseStencil dense_6th(*grid); dense_2nd.moveTo(xyz); dense_4th.moveTo(xyz); dense_6th.moveTo(xyz); AccessorType::ValueType meancurv; AccessorType::ValueType normGrad; AffineMap affine; meancurv = math::MeanCurvature::result( affine, dense_2nd); normGrad = math::MeanCurvature::normGrad( affine, dense_2nd); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); meancurv = math::MeanCurvature::result( affine, dense_4th); normGrad = math::MeanCurvature::normGrad( affine, dense_4th); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); UniformScaleMap uniform; meancurv = math::MeanCurvature::result( uniform, dense_6th); normGrad = math::MeanCurvature::normGrad( uniform, dense_6th); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, normGrad, 0.001); xyz.reset(35,10,40); dense_6th.moveTo(xyz); TranslationMap trans; meancurv = math::MeanCurvature::result( trans, dense_6th); normGrad = math::MeanCurvature::normGrad( trans, dense_6th); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, normGrad, 0.001); } { // non-unit sized voxel double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); AccessorType::ValueType meancurv; AccessorType::ValueType normGrad; Coord xyz(20,16,20); math::SecondOrderDenseStencil dense_2nd(*grid); math::FourthOrderDenseStencil dense_4th(*grid); math::SixthOrderDenseStencil dense_6th(*grid); dense_2nd.moveTo(xyz); dense_4th.moveTo(xyz); dense_6th.moveTo(xyz); AffineMap affine(voxel_size*math::Mat3d::identity()); meancurv = math::MeanCurvature::result( affine, dense_2nd); normGrad = math::MeanCurvature::normGrad( affine, dense_2nd); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, normGrad, 0.001); meancurv = math::MeanCurvature::result( affine, dense_4th); normGrad = math::MeanCurvature::normGrad( affine, dense_4th); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, normGrad, 0.001); UniformScaleMap uniform(voxel_size); meancurv = math::MeanCurvature::result( uniform, dense_6th); normGrad = math::MeanCurvature::normGrad( uniform, dense_6th); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, normGrad, 0.001); } { // NON-UNIFORM SCALING AND ROTATION Vec3d voxel_sizes(0.25, 0.45, 0.75); FloatGrid::Ptr grid = FloatGrid::create(); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); // apply rotation math::MapBase::Ptr rotated_map = base_map->preRotate(1.5, math::X_AXIS); grid->setTransform(math::Transform::Ptr(new math::Transform(rotated_map))); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); AccessorType::ValueType meancurv; AccessorType::ValueType normGrad; Coord xyz(20,16,20); math::SecondOrderDenseStencil dense_2nd(*grid); math::FourthOrderDenseStencil dense_4th(*grid); dense_2nd.moveTo(xyz); dense_4th.moveTo(xyz); Vec3d location = grid->indexToWorld(xyz); double dist = (center - location).length(); AffineMap::ConstPtr affine = grid->transform().map(); meancurv = math::MeanCurvature::result( *affine, dense_2nd); normGrad = math::MeanCurvature::normGrad( *affine, dense_2nd); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/dist, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/dist, normGrad, 0.001); meancurv = math::MeanCurvature::result( *affine, dense_4th); normGrad = math::MeanCurvature::normGrad( *affine, dense_4th); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/dist, meancurv, 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/dist, normGrad, 0.001); } } void TestMeanCurvature::testMeanCurvatureTool() { using namespace openvdb; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f);//i.e. (35,30,40) in index space const float radius=0.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); FloatGrid::Ptr curv = tools::meanCurvature(*grid); FloatGrid::ConstAccessor accessor = curv->getConstAccessor(); Coord xyz(35,30,30); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, accessor.getValue(xyz), 0.001); xyz.reset(35,10,40); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/20.0, accessor.getValue(xyz), 0.001); } void TestMeanCurvature::testMeanCurvatureMaskedTool() { using namespace openvdb; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f);//i.e. (35,30,40) in index space const float radius=0.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); BoolGrid::Ptr maskGrid = BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); FloatGrid::Ptr curv = tools::meanCurvature(*grid, *maskGrid); FloatGrid::ConstAccessor accessor = curv->getConstAccessor(); // test inside Coord xyz(35,30,30); CPPUNIT_ASSERT(maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/10.0, accessor.getValue(xyz), 0.001); // test outside xyz.reset(35,10,40); CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, accessor.getValue(xyz), 0.001); } void TestMeanCurvature::testOldStyleStencils() { using namespace openvdb; {// test of level set to sphere at (6,8,10) with R=10 and dx=0.5 FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(/*voxel size=*/0.5)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); math::CurvatureStencil cs(*grid); Coord xyz(20,16,20);//i.e. 8 voxel or 4 world units away from the center cs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/4.0, cs.meanCurvature(), 0.01);// 1/distance from center CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0/4.0, cs.meanCurvatureNormGrad(), 0.01);// 1/distance from center xyz.reset(12,16,10);//i.e. 10 voxel or 5 world units away from the center cs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0/5.0, cs.meanCurvature(), 0.01);// 1/distance from center CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0/5.0, cs.meanCurvatureNormGrad(), 0.01);// 1/distance from center } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestFile.cc0000644000000000000000000022267412252453157015141 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include // for tbb::this_tbb_thread::sleep() #include #include #include #include #include #include #include // for tools::sdfToFogVolume() #include #include #include "util.h" // for unittest_util::makeSphere() #include #include #include #include // for stat() #include #ifndef _WIN32 #include #endif class TestFile: public CppUnit::TestCase { public: virtual void setUp() {} virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestFile); CPPUNIT_TEST(testHeader); CPPUNIT_TEST(testWriteGrid); CPPUNIT_TEST(testWriteMultipleGrids); CPPUNIT_TEST(testWriteFloatAsHalf); CPPUNIT_TEST(testWriteInstancedGrids); CPPUNIT_TEST(testReadGridDescriptors); CPPUNIT_TEST(testGridNaming); CPPUNIT_TEST(testEmptyFile); CPPUNIT_TEST(testEmptyGridIO); CPPUNIT_TEST(testOpen); CPPUNIT_TEST(testNonVdbOpen); CPPUNIT_TEST(testGetMetadata); CPPUNIT_TEST(testReadAll); CPPUNIT_TEST(testWriteOpenFile); CPPUNIT_TEST(testReadGridMetadata); CPPUNIT_TEST(testReadGridPartial); CPPUNIT_TEST(testReadGrid); CPPUNIT_TEST(testMultipleBufferIO); CPPUNIT_TEST(testHasGrid); CPPUNIT_TEST(testNameIterator); CPPUNIT_TEST(testReadOldFileFormat); CPPUNIT_TEST(testCompression); CPPUNIT_TEST(testAsync); CPPUNIT_TEST_SUITE_END(); void testHeader(); void testWriteGrid(); void testWriteMultipleGrids(); void testWriteFloatAsHalf(); void testWriteInstancedGrids(); void testReadGridDescriptors(); void testGridNaming(); void testEmptyFile(); void testEmptyGridIO(); void testOpen(); void testNonVdbOpen(); void testGetMetadata(); void testReadAll(); void testWriteOpenFile(); void testReadGridMetadata(); void testReadGridPartial(); void testReadGrid(); void testMultipleBufferIO(); void testHasGrid(); void testNameIterator(); void testReadOldFileFormat(); void testCompression(); void testAsync(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); //////////////////////////////////////// void TestFile::testHeader() { using namespace openvdb::io; File file("something.vdb2"); std::ostringstream ostr(std::ios_base::binary); file.writeHeader(ostr, /*seekable=*/true); std::string uuid_str=file.getUniqueTag(); std::istringstream istr(ostr.str(), std::ios_base::binary); bool unique=true; CPPUNIT_ASSERT_NO_THROW(unique=file.readHeader(istr)); CPPUNIT_ASSERT(!unique);//reading same file again CPPUNIT_ASSERT_EQUAL(openvdb::OPENVDB_FILE_VERSION, file.fileVersion()); CPPUNIT_ASSERT_EQUAL(openvdb::OPENVDB_LIBRARY_MAJOR_VERSION, file.libraryVersion().first); CPPUNIT_ASSERT_EQUAL(openvdb::OPENVDB_LIBRARY_MINOR_VERSION, file.libraryVersion().second); CPPUNIT_ASSERT_EQUAL(uuid_str, file.getUniqueTag()); //std::cerr << "\nuuid=" << uuid_str << std::endl; CPPUNIT_ASSERT(file.isIdentical(uuid_str)); remove("something.vdb2"); } void TestFile::testWriteGrid() { using namespace openvdb; using namespace openvdb::io; typedef Int32Tree TreeType; typedef Grid GridType; File file("something.vdb2"); std::ostringstream ostr(std::ios_base::binary); // Create a grid with transform. math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); GridType::Ptr grid = createGrid(/*bg=*/1); TreeType& tree = grid->tree(); grid->setTransform(trans); tree.setValue(Coord(10, 1, 2), 10); tree.setValue(Coord(0, 0, 0), 5); // Add some metadata. Metadata::clearRegistry(); StringMetadata::registerType(); const std::string meta0Val, meta1Val("Hello, world."); Metadata::Ptr stringMetadata = Metadata::createMetadata(typeNameAsString()); CPPUNIT_ASSERT(stringMetadata); if (stringMetadata) { grid->insertMeta("meta0", *stringMetadata); grid->metaValue("meta0") = meta0Val; grid->insertMeta("meta1", *stringMetadata); grid->metaValue("meta1") = meta1Val; } // Create the grid descriptor out of this grid. GridDescriptor gd(Name("temperature"), grid->type()); // Write out the grid. file.writeGrid(gd, grid, ostr, /*seekable=*/true); CPPUNIT_ASSERT(gd.getGridPos() != 0); CPPUNIT_ASSERT(gd.getBlockPos() != 0); CPPUNIT_ASSERT(gd.getEndPos() != 0); // Read in the grid descriptor. GridDescriptor gd2; std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input is only a fragment of a VDB file (in particular, // it doesn't have a header), set the file format version number explicitly. io::setCurrentVersion(istr); GridBase::Ptr gd2_grid; CPPUNIT_ASSERT_THROW(gd2.read(istr), openvdb::LookupError); // Register the grid and the transform and the blocks. GridBase::clearRegistry(); GridType::registerGrid(); // Register transform maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::ScaleMap::registerMap(); math::UniformScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); math::UniformScaleTranslateMap::registerMap(); math::NonlinearFrustumMap::registerMap(); istr.seekg(0, std::ios_base::beg); CPPUNIT_ASSERT_NO_THROW(gd2_grid = gd2.read(istr)); CPPUNIT_ASSERT_EQUAL(gd.gridName(), gd2.gridName()); CPPUNIT_ASSERT_EQUAL(GridType::gridType(), gd2_grid->type()); CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), gd2.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), gd2.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd2.getEndPos()); // Position the stream to beginning of the grid storage and read the grid. gd2.seekToGrid(istr); Archive::readGridCompression(istr); gd2_grid->readMeta(istr); gd2_grid->readTransform(istr); gd2_grid->readTopology(istr); // Ensure that we have the same metadata. CPPUNIT_ASSERT_EQUAL(grid->metaCount(), gd2_grid->metaCount()); CPPUNIT_ASSERT((*gd2_grid)["meta0"]); CPPUNIT_ASSERT((*gd2_grid)["meta1"]); CPPUNIT_ASSERT_EQUAL(meta0Val, gd2_grid->metaValue("meta0")); CPPUNIT_ASSERT_EQUAL(meta1Val, gd2_grid->metaValue("meta1")); // Ensure that we have the same topology and transform. CPPUNIT_ASSERT_EQUAL( grid->baseTree().leafCount(), gd2_grid->baseTree().leafCount()); CPPUNIT_ASSERT_EQUAL( grid->baseTree().nonLeafCount(), gd2_grid->baseTree().nonLeafCount()); CPPUNIT_ASSERT_EQUAL( grid->baseTree().treeDepth(), gd2_grid->baseTree().treeDepth()); //CPPUNIT_ASSERT_EQUAL(0.1, gd2_grid->getTransform()->getVoxelSizeX()); //CPPUNIT_ASSERT_EQUAL(0.1, gd2_grid->getTransform()->getVoxelSizeY()); //CPPUNIT_ASSERT_EQUAL(0.1, gd2_grid->getTransform()->getVoxelSizeZ()); // Read in the data blocks. gd2.seekToBlocks(istr); gd2_grid->readBuffers(istr); TreeType::Ptr tree2 = boost::dynamic_pointer_cast(gd2_grid->baseTreePtr()); CPPUNIT_ASSERT(tree2.get() != NULL); CPPUNIT_ASSERT_EQUAL(10, tree2->getValue(Coord(10, 1, 2))); CPPUNIT_ASSERT_EQUAL(5, tree2->getValue(Coord(0, 0, 0))); CPPUNIT_ASSERT_EQUAL(1, tree2->getValue(Coord(1000, 1000, 16000))); // Clear registries. GridBase::clearRegistry(); Metadata::clearRegistry(); math::MapRegistry::clear(); remove("something.vdb2"); } void TestFile::testWriteMultipleGrids() { using namespace openvdb; using namespace openvdb::io; typedef Int32Tree TreeType; typedef Grid GridType; File file("something.vdb2"); std::ostringstream ostr(std::ios_base::binary); // Create a grid with transform. GridType::Ptr grid = createGrid(/*bg=*/1); TreeType& tree = grid->tree(); tree.setValue(Coord(10, 1, 2), 10); tree.setValue(Coord(0, 0, 0), 5); math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); grid->setTransform(trans); GridType::Ptr grid2 = createGrid(/*bg=*/2); TreeType& tree2 = grid2->tree(); tree2.setValue(Coord(0, 0, 0), 10); tree2.setValue(Coord(1000, 1000, 1000), 50); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.2); grid2->setTransform(trans2); // Create the grid descriptor out of this grid. GridDescriptor gd(Name("temperature"), grid->type()); GridDescriptor gd2(Name("density"), grid2->type()); // Write out the grids. file.writeGrid(gd, grid, ostr, /*seekable=*/true); file.writeGrid(gd2, grid2, ostr, /*seekable=*/true); CPPUNIT_ASSERT(gd.getGridPos() != 0); CPPUNIT_ASSERT(gd.getBlockPos() != 0); CPPUNIT_ASSERT(gd.getEndPos() != 0); CPPUNIT_ASSERT(gd2.getGridPos() != 0); CPPUNIT_ASSERT(gd2.getBlockPos() != 0); CPPUNIT_ASSERT(gd2.getEndPos() != 0); // register the grid GridBase::clearRegistry(); GridType::registerGrid(); // register maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::ScaleMap::registerMap(); math::UniformScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); math::UniformScaleTranslateMap::registerMap(); math::NonlinearFrustumMap::registerMap(); // Read in the first grid descriptor. GridDescriptor gd_in; std::istringstream istr(ostr.str(), std::ios_base::binary); io::setCurrentVersion(istr); GridBase::Ptr gd_in_grid; CPPUNIT_ASSERT_NO_THROW(gd_in_grid = gd_in.read(istr)); // Ensure read in the right values. CPPUNIT_ASSERT_EQUAL(gd.gridName(), gd_in.gridName()); CPPUNIT_ASSERT_EQUAL(GridType::gridType(), gd_in_grid->type()); CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), gd_in.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), gd_in.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd_in.getEndPos()); // Position the stream to beginning of the grid storage and read the grid. gd_in.seekToGrid(istr); Archive::readGridCompression(istr); gd_in_grid->readMeta(istr); gd_in_grid->readTransform(istr); gd_in_grid->readTopology(istr); // Ensure that we have the same topology and transform. CPPUNIT_ASSERT_EQUAL( grid->baseTree().leafCount(), gd_in_grid->baseTree().leafCount()); CPPUNIT_ASSERT_EQUAL( grid->baseTree().nonLeafCount(), gd_in_grid->baseTree().nonLeafCount()); CPPUNIT_ASSERT_EQUAL( grid->baseTree().treeDepth(), gd_in_grid->baseTree().treeDepth()); // CPPUNIT_ASSERT_EQUAL(0.1, gd_in_grid->getTransform()->getVoxelSizeX()); // CPPUNIT_ASSERT_EQUAL(0.1, gd_in_grid->getTransform()->getVoxelSizeY()); // CPPUNIT_ASSERT_EQUAL(0.1, gd_in_grid->getTransform()->getVoxelSizeZ()); // Read in the data blocks. gd_in.seekToBlocks(istr); gd_in_grid->readBuffers(istr); TreeType::Ptr grid_in = boost::dynamic_pointer_cast(gd_in_grid->baseTreePtr()); CPPUNIT_ASSERT(grid_in.get() != NULL); CPPUNIT_ASSERT_EQUAL(10, grid_in->getValue(Coord(10, 1, 2))); CPPUNIT_ASSERT_EQUAL(5, grid_in->getValue(Coord(0, 0, 0))); CPPUNIT_ASSERT_EQUAL(1, grid_in->getValue(Coord(1000, 1000, 16000))); ///////////////////////////////////////////////////////////////// // Now read in the second grid descriptor. Make use of hte end offset. /////////////////////////////////////////////////////////////// gd_in.seekToEnd(istr); GridDescriptor gd2_in; GridBase::Ptr gd2_in_grid; CPPUNIT_ASSERT_NO_THROW(gd2_in_grid = gd2_in.read(istr)); // Ensure that we read in the right values. CPPUNIT_ASSERT_EQUAL(gd2.gridName(), gd2_in.gridName()); CPPUNIT_ASSERT_EQUAL(TreeType::treeType(), gd2_in_grid->type()); CPPUNIT_ASSERT_EQUAL(gd2.getGridPos(), gd2_in.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd2.getBlockPos(), gd2_in.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), gd2_in.getEndPos()); // Position the stream to beginning of the grid storage and read the grid. gd2_in.seekToGrid(istr); Archive::readGridCompression(istr); gd2_in_grid->readMeta(istr); gd2_in_grid->readTransform(istr); gd2_in_grid->readTopology(istr); // Ensure that we have the same topology and transform. CPPUNIT_ASSERT_EQUAL( grid2->baseTree().leafCount(), gd2_in_grid->baseTree().leafCount()); CPPUNIT_ASSERT_EQUAL( grid2->baseTree().nonLeafCount(), gd2_in_grid->baseTree().nonLeafCount()); CPPUNIT_ASSERT_EQUAL( grid2->baseTree().treeDepth(), gd2_in_grid->baseTree().treeDepth()); // CPPUNIT_ASSERT_EQUAL(0.2, gd2_in_grid->getTransform()->getVoxelSizeX()); // CPPUNIT_ASSERT_EQUAL(0.2, gd2_in_grid->getTransform()->getVoxelSizeY()); // CPPUNIT_ASSERT_EQUAL(0.2, gd2_in_grid->getTransform()->getVoxelSizeZ()); // Read in the data blocks. gd2_in.seekToBlocks(istr); gd2_in_grid->readBuffers(istr); TreeType::Ptr grid2_in = boost::dynamic_pointer_cast(gd2_in_grid->baseTreePtr()); CPPUNIT_ASSERT(grid2_in.get() != NULL); CPPUNIT_ASSERT_EQUAL(50, grid2_in->getValue(Coord(1000, 1000, 1000))); CPPUNIT_ASSERT_EQUAL(10, grid2_in->getValue(Coord(0, 0, 0))); CPPUNIT_ASSERT_EQUAL(2, grid2_in->getValue(Coord(100000, 100000, 16000))); // Clear registries. GridBase::clearRegistry(); math::MapRegistry::clear(); remove("something.vdb2"); } void TestFile::testWriteFloatAsHalf() { using namespace openvdb; using namespace openvdb::io; typedef Vec3STree TreeType; typedef Grid GridType; // Register all grid types. initialize(); // Ensure that the registry is cleared on exit. struct Local { static void uninitialize(char*) { openvdb::uninitialize(); } }; boost::shared_ptr onExit((char*)(0), Local::uninitialize); // Create two test grids. GridType::Ptr grid1 = createGrid(/*bg=*/Vec3s(1, 1, 1)); TreeType& tree1 = grid1->tree(); CPPUNIT_ASSERT(grid1.get() != NULL); grid1->setTransform(math::Transform::createLinearTransform(0.1)); grid1->setName("grid1"); GridType::Ptr grid2 = createGrid(/*bg=*/Vec3s(2, 2, 2)); CPPUNIT_ASSERT(grid2.get() != NULL); TreeType& tree2 = grid2->tree(); grid2->setTransform(math::Transform::createLinearTransform(0.2)); // Flag this grid for 16-bit float output. grid2->setSaveFloatAsHalf(true); grid2->setName("grid2"); for (int x = 0; x < 40; ++x) { for (int y = 0; y < 40; ++y) { for (int z = 0; z < 40; ++z) { tree1.setValue(Coord(x, y, z), Vec3s(x, y, z)); tree2.setValue(Coord(x, y, z), Vec3s(x, y, z)); } } } GridPtrVec grids; grids.push_back(grid1); grids.push_back(grid2); const char* filename = "something.vdb2"; { // Write both grids to a file. File vdbFile(filename); vdbFile.write(grids); } { // Verify that both grids can be read back successfully from the file. File vdbFile(filename); vdbFile.open(); GridBase::Ptr bgrid1 = vdbFile.readGrid("grid1"), bgrid2 = vdbFile.readGrid("grid2"); vdbFile.close(); CPPUNIT_ASSERT(bgrid1.get() != NULL); CPPUNIT_ASSERT(bgrid1->isType()); CPPUNIT_ASSERT(bgrid2.get() != NULL); CPPUNIT_ASSERT(bgrid2->isType()); const TreeType& btree1 = boost::static_pointer_cast(bgrid1)->tree(); CPPUNIT_ASSERT_EQUAL(Vec3s(10, 10, 10), btree1.getValue(Coord(10, 10, 10))); const TreeType& btree2 = boost::static_pointer_cast(bgrid2)->tree(); CPPUNIT_ASSERT_EQUAL(Vec3s(10, 10, 10), btree2.getValue(Coord(10, 10, 10))); } } void TestFile::testWriteInstancedGrids() { using namespace openvdb; // Register data types. openvdb::initialize(); // Create grids. Int32Tree::Ptr tree1(new Int32Tree(1)); FloatTree::Ptr tree2(new FloatTree(2.0)); GridBase::Ptr grid1 = createGrid(tree1), grid2 = createGrid(tree1), // instance of grid1 grid3 = createGrid(tree2), grid4 = createGrid(tree2); // instance of grid3 grid1->setName("density"); grid2->setName("density_copy"); // Leave grid3 and grid4 unnamed. // Create transforms. math::Transform::Ptr trans1 = math::Transform::createLinearTransform(0.1); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); grid1->setTransform(trans1); grid2->setTransform(trans2); grid3->setTransform(trans2); grid4->setTransform(trans1); // Set some values. tree1->setValue(Coord(0, 0, 0), 5); tree1->setValue(Coord(100, 0, 0), 6); tree2->setValue(Coord(0, 0, 0), 10); tree2->setValue(Coord(0, 100, 0), 11); MetaMap::Ptr meta(new MetaMap); meta->insertMeta("author", StringMetadata("Einstein")); meta->insertMeta("year", Int32Metadata(2009)); GridPtrVecPtr grids(new GridPtrVec); grids->push_back(grid1); grids->push_back(grid2); grids->push_back(grid3); grids->push_back(grid4); // Write the grids to a file and then close the file. const char* filename = "something.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); { io::File vdbFile(filename); vdbFile.write(*grids, *meta); } meta.reset(); // Read the grids back in. io::File file(filename); file.open(); grids = file.getGrids(); meta = file.getMetadata(); // Verify the metadata. CPPUNIT_ASSERT(meta.get() != NULL); CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); CPPUNIT_ASSERT_EQUAL(std::string("Einstein"), meta->metaValue("author")); CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); // Verify the grids. CPPUNIT_ASSERT(grids.get() != NULL); CPPUNIT_ASSERT_EQUAL(4, int(grids->size())); GridBase::Ptr grid = findGridByName(*grids, "density"); CPPUNIT_ASSERT(grid.get() != NULL); Int32Tree::Ptr density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != NULL); grid.reset(); grid = findGridByName(*grids, "density_copy"); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); // Verify that "density_copy" is an instance of (i.e., shares a tree with) "density". CPPUNIT_ASSERT_EQUAL(density, gridPtrCast(grid)->treePtr()); grid.reset(); grid = findGridByName(*grids, ""); CPPUNIT_ASSERT(grid.get() != NULL); FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != NULL); grid.reset(); for (GridPtrVec::reverse_iterator it = grids->rbegin(); !grid && it != grids->rend(); ++it) { // Search for the second unnamed grid starting from the end of the list. if ((*it)->getName() == "") grid = *it; } CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); // Verify that the second unnamed grid is an instance of the first. CPPUNIT_ASSERT_EQUAL(temperature, gridPtrCast(grid)->treePtr()); CPPUNIT_ASSERT_DOUBLES_EQUAL(5, density->getValue(Coord(0, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(6, density->getValue(Coord(100, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(10, temperature->getValue(Coord(0, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(11, temperature->getValue(Coord(0, 100, 0)), /*tolerance=*/0); // Reread with instancing disabled. file.close(); file.setInstancingEnabled(false); file.open(); grids = file.getGrids(); CPPUNIT_ASSERT_EQUAL(4, int(grids->size())); grid = findGridByName(*grids, "density"); CPPUNIT_ASSERT(grid.get() != NULL); density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != NULL); grid = findGridByName(*grids, "density_copy"); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); // Verify that "density_copy" is *not* an instance of "density". CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != density); // Verify that the two unnamed grids are not instances of each other. grid = findGridByName(*grids, ""); CPPUNIT_ASSERT(grid.get() != NULL); temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != NULL); grid.reset(); for (GridPtrVec::reverse_iterator it = grids->rbegin(); !grid && it != grids->rend(); ++it) { // Search for the second unnamed grid starting from the end of the list. if ((*it)->getName() == "") grid = *it; } CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != temperature); // Rewrite with instancing disabled, then reread with instancing enabled. file.close(); { io::File vdbFile(filename); vdbFile.setInstancingEnabled(false); vdbFile.write(*grids, *meta); } file.setInstancingEnabled(true); file.open(); grids = file.getGrids(); CPPUNIT_ASSERT_EQUAL(4, int(grids->size())); // Verify that "density_copy" is not an instance of "density". grid = findGridByName(*grids, "density"); CPPUNIT_ASSERT(grid.get() != NULL); density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != NULL); grid = findGridByName(*grids, "density_copy"); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != density); // Verify that the two unnamed grids are not instances of each other. grid = findGridByName(*grids, ""); CPPUNIT_ASSERT(grid.get() != NULL); temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != NULL); grid.reset(); for (GridPtrVec::reverse_iterator it = grids->rbegin(); !grid && it != grids->rend(); ++it) { // Search for the second unnamed grid starting from the end of the list. if ((*it)->getName() == "") grid = *it; } CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != temperature); } void TestFile::testReadGridDescriptors() { using namespace openvdb; using namespace openvdb::io; typedef Int32Grid GridType; typedef GridType::TreeType TreeType; File file("something.vdb2"); std::ostringstream ostr(std::ios_base::binary); // Create a grid with transform. GridType::Ptr grid = createGrid(1); TreeType& tree = grid->tree(); tree.setValue(Coord(10, 1, 2), 10); tree.setValue(Coord(0, 0, 0), 5); math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); grid->setTransform(trans); // Create another grid with transform. GridType::Ptr grid2 = createGrid(2); TreeType& tree2 = grid2->tree(); tree2.setValue(Coord(0, 0, 0), 10); tree2.setValue(Coord(1000, 1000, 1000), 50); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.2); grid2->setTransform(trans2); // Create the grid descriptor out of this grid. GridDescriptor gd(Name("temperature"), grid->type()); GridDescriptor gd2(Name("density"), grid2->type()); // Write out the number of grids. int32_t gridCount = 2; ostr.write((char*)&gridCount, sizeof(int32_t)); // Write out the grids. file.writeGrid(gd, grid, ostr, /*seekable=*/true); file.writeGrid(gd2, grid2, ostr, /*seekable=*/true); // Register the grid and the transform and the blocks. GridBase::clearRegistry(); GridType::registerGrid(); // register maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::ScaleMap::registerMap(); math::UniformScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); math::UniformScaleTranslateMap::registerMap(); math::NonlinearFrustumMap::registerMap(); // Read in the grid descriptors. File file2("something.vdb2"); std::istringstream istr(ostr.str(), std::ios_base::binary); io::setCurrentVersion(istr); file2.readGridDescriptors(istr); // Compare with the initial grid descriptors. File::NameMapCIter it = file2.findDescriptor("temperature"); CPPUNIT_ASSERT(it != file2.mGridDescriptors.end()); GridDescriptor file2gd = it->second; CPPUNIT_ASSERT_EQUAL(gd.gridName(), file2gd.gridName()); CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), file2gd.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), file2gd.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), file2gd.getEndPos()); it = file2.findDescriptor("density"); CPPUNIT_ASSERT(it != file2.mGridDescriptors.end()); file2gd = it->second; CPPUNIT_ASSERT_EQUAL(gd2.gridName(), file2gd.gridName()); CPPUNIT_ASSERT_EQUAL(gd2.getGridPos(), file2gd.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd2.getBlockPos(), file2gd.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), file2gd.getEndPos()); // Clear registries. GridBase::clearRegistry(); math::MapRegistry::clear(); remove("something.vdb2"); } void TestFile::testGridNaming() { using namespace openvdb; using namespace openvdb::io; typedef Int32Tree TreeType; typedef Grid GridType; // Register data types. openvdb::initialize(); // Create several grids that share a single tree. TreeType::Ptr tree(new TreeType(1)); tree->setValue(Coord(10, 1, 2), 10); tree->setValue(Coord(0, 0, 0), 5); GridBase::Ptr grid1 = openvdb::createGrid(tree), grid2 = openvdb::createGrid(tree), grid3 = openvdb::createGrid(tree); std::vector gridVec; gridVec.push_back(grid1); gridVec.push_back(grid2); gridVec.push_back(grid3); // Give all grids the same name, but also some metadata to distinguish them. for (int n = 0; n <= 2; ++n) { gridVec[n]->setName("grid"); gridVec[n]->insertMeta("index", Int32Metadata(n)); } const char* filename = "/tmp/testGridNaming.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); // Test first with grid instancing disabled, then with instancing enabled. for (int instancing = 0; instancing <= 1; ++instancing) { { // Write the grids out to a file. File file(filename); file.setInstancingEnabled(instancing); file.write(gridVec); } // Open the file for reading. File file(filename); file.setInstancingEnabled(instancing); file.open(); int n = 0; for (File::NameIterator i = file.beginName(), e = file.endName(); i != e; ++i, ++n) { CPPUNIT_ASSERT(file.hasGrid(i.gridName())); } // Verify that the file contains three grids. CPPUNIT_ASSERT_EQUAL(3, n); // Read each grid. for (n = -1; n <= 2; ++n) { openvdb::Name name("grid"); // On the first iteration, read the grid named "grid", then read "grid[0]" // (which is synonymous with "grid"), then "grid[1]", then "grid[2]". if (n >= 0) { name = GridDescriptor::nameAsString(GridDescriptor::addSuffix(name, n)); } CPPUNIT_ASSERT(file.hasGrid(name)); // Partially read the current grid. GridBase::ConstPtr grid = file.readGridPartial(name); CPPUNIT_ASSERT(grid.get() != NULL); // Verify that the grid is named "grid". CPPUNIT_ASSERT_EQUAL(openvdb::Name("grid"), grid->getName()); CPPUNIT_ASSERT_EQUAL((n < 0 ? 0 : n), grid->metaValue("index")); // Fully read the current grid. grid = file.readGrid(name); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(openvdb::Name("grid"), grid->getName()); CPPUNIT_ASSERT_EQUAL((n < 0 ? 0 : n), grid->metaValue("index")); } // Read all three grids at once. GridPtrVecPtr allGrids = file.getGrids(); CPPUNIT_ASSERT(allGrids.get() != NULL); CPPUNIT_ASSERT_EQUAL(3, int(allGrids->size())); GridBase::ConstPtr firstGrid; std::vector indices; for (GridPtrVecCIter i = allGrids->begin(), e = allGrids->end(); i != e; ++i) { GridBase::ConstPtr grid = *i; CPPUNIT_ASSERT(grid.get() != NULL); indices.push_back(grid->metaValue("index")); // If instancing is enabled, verify that all grids share the same tree. if (instancing) { if (!firstGrid) firstGrid = grid; CPPUNIT_ASSERT_EQUAL(firstGrid->baseTreePtr(), grid->baseTreePtr()); } } // Verify that three distinct grids were read, // by examining their "index" metadata. CPPUNIT_ASSERT_EQUAL(3, int(indices.size())); std::sort(indices.begin(), indices.end()); CPPUNIT_ASSERT_EQUAL(0, indices[0]); CPPUNIT_ASSERT_EQUAL(1, indices[1]); CPPUNIT_ASSERT_EQUAL(2, indices[2]); } { // Try writing and then reading a grid with a weird name // that might conflict with the grid name indexing scheme. const openvdb::Name weirdName("grid[4]"); gridVec[0]->setName(weirdName); { File file(filename); file.write(gridVec); } File file(filename); file.open(); // Verify that the grid can be read and that its index is 0. GridBase::ConstPtr grid = file.readGrid(weirdName); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(weirdName, grid->getName()); CPPUNIT_ASSERT_EQUAL(0, grid->metaValue("index")); // Verify that the other grids can still be read successfully. grid = file.readGrid("grid[0]"); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(openvdb::Name("grid"), grid->getName()); // Because there are now only two grids named "grid", the one with // index 1 is now "grid[0]". CPPUNIT_ASSERT_EQUAL(1, grid->metaValue("index")); grid = file.readGrid("grid[1]"); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(openvdb::Name("grid"), grid->getName()); // Because there are now only two grids named "grid", the one with // index 2 is now "grid[1]". CPPUNIT_ASSERT_EQUAL(2, grid->metaValue("index")); // Verify that there is no longer a third grid named "grid". CPPUNIT_ASSERT_THROW(file.readGrid("grid[2]"), openvdb::KeyError); } } void TestFile::testEmptyFile() { using namespace openvdb; using namespace openvdb::io; const char* filename = "/tmp/testEmptyFile.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); { File file(filename); file.write(GridPtrVec(), MetaMap()); } File file(filename); file.open(); GridPtrVecPtr grids = file.getGrids(); MetaMap::Ptr meta = file.getMetadata(); CPPUNIT_ASSERT(grids.get() != NULL); CPPUNIT_ASSERT(grids->empty()); CPPUNIT_ASSERT(meta.get() != NULL); CPPUNIT_ASSERT(meta->empty()); } void TestFile::testEmptyGridIO() { using namespace openvdb; using namespace openvdb::io; typedef Int32Grid GridType; typedef GridType::TreeType TreeType; const char* filename = "/tmp/something.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); File file(filename); std::ostringstream ostr(std::ios_base::binary); // Create a grid with transform. GridType::Ptr grid = createGrid(/*bg=*/1); math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); grid->setTransform(trans); // Create another grid with transform. math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.2); GridType::Ptr grid2 = createGrid(/*bg=*/2); grid2->setTransform(trans2); // Create the grid descriptor out of this grid. GridDescriptor gd(Name("temperature"), grid->type()); GridDescriptor gd2(Name("density"), grid2->type()); // Write out the number of grids. int32_t gridCount = 2; ostr.write((char*)&gridCount, sizeof(int32_t)); // Write out the grids. file.writeGrid(gd, grid, ostr, /*seekable=*/true); file.writeGrid(gd2, grid2, ostr, /*seekable=*/true); // Ensure that the block offset and the end offsets are equivalent. CPPUNIT_ASSERT_EQUAL(0, int(grid->baseTree().leafCount())); CPPUNIT_ASSERT_EQUAL(0, int(grid2->baseTree().leafCount())); CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), gd2.getBlockPos()); // Register the grid and the transform and the blocks. GridBase::clearRegistry(); GridType::registerGrid(); // register maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::ScaleMap::registerMap(); math::UniformScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); math::UniformScaleTranslateMap::registerMap(); math::NonlinearFrustumMap::registerMap(); // Read in the grid descriptors. File file2(filename); std::istringstream istr(ostr.str(), std::ios_base::binary); io::setCurrentVersion(istr); file2.readGridDescriptors(istr); // Compare with the initial grid descriptors. File::NameMapCIter it = file2.findDescriptor("temperature"); CPPUNIT_ASSERT(it != file2.mGridDescriptors.end()); GridDescriptor file2gd = it->second; file2gd.seekToGrid(istr); GridBase::Ptr gd_grid = GridBase::createGrid(file2gd.gridType()); Archive::readGridCompression(istr); gd_grid->readMeta(istr); gd_grid->readTransform(istr); gd_grid->readTopology(istr); CPPUNIT_ASSERT_EQUAL(gd.gridName(), file2gd.gridName()); CPPUNIT_ASSERT(gd_grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(0, int(gd_grid->baseTree().leafCount())); //CPPUNIT_ASSERT_EQUAL(8, int(gd_grid->baseTree().nonLeafCount())); CPPUNIT_ASSERT_EQUAL(4, int(gd_grid->baseTree().treeDepth())); CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), file2gd.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), file2gd.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), file2gd.getEndPos()); it = file2.findDescriptor("density"); CPPUNIT_ASSERT(it != file2.mGridDescriptors.end()); file2gd = it->second; file2gd.seekToGrid(istr); gd_grid = GridBase::createGrid(file2gd.gridType()); Archive::readGridCompression(istr); gd_grid->readMeta(istr); gd_grid->readTransform(istr); gd_grid->readTopology(istr); CPPUNIT_ASSERT_EQUAL(gd2.gridName(), file2gd.gridName()); CPPUNIT_ASSERT(gd_grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(0, int(gd_grid->baseTree().leafCount())); //CPPUNIT_ASSERT_EQUAL(8, int(gd_grid->nonLeafCount())); CPPUNIT_ASSERT_EQUAL(4, int(gd_grid->baseTree().treeDepth())); CPPUNIT_ASSERT_EQUAL(gd2.getGridPos(), file2gd.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd2.getBlockPos(), file2gd.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd2.getEndPos(), file2gd.getEndPos()); // Clear registries. GridBase::clearRegistry(); math::MapRegistry::clear(); } void TestFile::testOpen() { using namespace openvdb; typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Int32Grid IntGrid; typedef FloatGrid::TreeType FloatTree; typedef Int32Grid::TreeType IntTree; // Create a VDB to write. // Create grids IntGrid::Ptr grid = createGrid(/*bg=*/1); IntTree& tree = grid->tree(); grid->setName("density"); FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); FloatTree& tree2 = grid2->tree(); grid2->setName("temperature"); // Create transforms math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); grid->setTransform(trans); grid2->setTransform(trans2); // Set some values tree.setValue(Coord(0, 0, 0), 5); tree.setValue(Coord(100, 0, 0), 6); tree2.setValue(Coord(0, 0, 0), 10); tree2.setValue(Coord(0, 100, 0), 11); MetaMap meta; meta.insertMeta("author", StringMetadata("Einstein")); meta.insertMeta("year", Int32Metadata(2009)); GridPtrVec grids; grids.push_back(grid); grids.push_back(grid2); CPPUNIT_ASSERT(findGridByName(grids, "density") == grid); CPPUNIT_ASSERT(findGridByName(grids, "temperature") == grid2); CPPUNIT_ASSERT(meta.metaValue("author") == "Einstein"); CPPUNIT_ASSERT_EQUAL(2009, meta.metaValue("year")); // Register grid and transform. GridBase::clearRegistry(); IntGrid::registerGrid(); FloatGrid::registerGrid(); Metadata::clearRegistry(); StringMetadata::registerType(); Int32Metadata::registerType(); // register maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::ScaleMap::registerMap(); math::UniformScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); math::UniformScaleTranslateMap::registerMap(); math::NonlinearFrustumMap::registerMap(); // Write the vdb out to a file. io::File vdbfile("something.vdb2"); vdbfile.write(grids, meta); // Now we can read in the file. CPPUNIT_ASSERT(!vdbfile.open());//opening the same file //Can't open same file multiple times without cloasing CPPUNIT_ASSERT_THROW(vdbfile.open(), openvdb::IoError); vdbfile.close(); CPPUNIT_ASSERT(!vdbfile.open());//opening the same file CPPUNIT_ASSERT(vdbfile.isOpen()); CPPUNIT_ASSERT_EQUAL(OPENVDB_FILE_VERSION, vdbfile.fileVersion()); CPPUNIT_ASSERT_EQUAL(OPENVDB_FILE_VERSION, io::getFormatVersion(vdbfile.mInStream)); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MAJOR_VERSION, vdbfile.libraryVersion().first); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MINOR_VERSION, vdbfile.libraryVersion().second); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MAJOR_VERSION, io::getLibraryVersion(vdbfile.mInStream).first); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MINOR_VERSION, io::getLibraryVersion(vdbfile.mInStream).second); // Ensure that we read in the vdb metadata. CPPUNIT_ASSERT(vdbfile.getMetadata()); CPPUNIT_ASSERT(vdbfile.getMetadata()->metaValue("author") == "Einstein"); CPPUNIT_ASSERT_EQUAL(2009, vdbfile.getMetadata()->metaValue("year")); // Ensure we got the grid descriptors. CPPUNIT_ASSERT_EQUAL(1, int(vdbfile.mGridDescriptors.count("density"))); CPPUNIT_ASSERT_EQUAL(1, int(vdbfile.mGridDescriptors.count("temperature"))); io::File::NameMapCIter it = vdbfile.findDescriptor("density"); CPPUNIT_ASSERT(it != vdbfile.mGridDescriptors.end()); io::GridDescriptor gd = it->second; CPPUNIT_ASSERT_EQUAL(IntTree::treeType(), gd.gridType()); it = vdbfile.findDescriptor("temperature"); CPPUNIT_ASSERT(it != vdbfile.mGridDescriptors.end()); gd = it->second; CPPUNIT_ASSERT_EQUAL(FloatTree::treeType(), gd.gridType()); // Ensure we throw an error if there is no file. io::File vdbfile2("somethingelses.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile2.open(), openvdb::IoError); CPPUNIT_ASSERT(vdbfile2.mInStream.is_open() == false); // Clear registries. GridBase::clearRegistry(); Metadata::clearRegistry(); math::MapRegistry::clear(); // Test closing the file. vdbfile.close(); CPPUNIT_ASSERT(vdbfile.isOpen() == false); CPPUNIT_ASSERT(vdbfile.mMeta.get() == NULL); CPPUNIT_ASSERT_EQUAL(0, int(vdbfile.mGridDescriptors.size())); CPPUNIT_ASSERT(vdbfile.mInStream.is_open() == false); remove("something.vdb2"); } void TestFile::testNonVdbOpen() { std::ofstream file("dummy.vdb2", std::ios_base::binary); int64_t something = 1; file.write((char*)&something, sizeof(int64_t)); file.close(); openvdb::io::File vdbfile("dummy.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile.open(), openvdb::IoError); CPPUNIT_ASSERT(vdbfile.mInStream.is_open() == false); remove("dummy.vdb2"); } void TestFile::testGetMetadata() { using namespace openvdb; GridPtrVec grids; MetaMap meta; meta.insertMeta("author", StringMetadata("Einstein")); meta.insertMeta("year", Int32Metadata(2009)); // Adjust registry before writing. Metadata::clearRegistry(); StringMetadata::registerType(); Int32Metadata::registerType(); // Write the vdb out to a file. io::File vdbfile("something.vdb2"); vdbfile.write(grids, meta); // Check if reading without opening the file CPPUNIT_ASSERT_THROW(vdbfile.getMetadata(), openvdb::IoError); vdbfile.open(); MetaMap::Ptr meta2 = vdbfile.getMetadata(); CPPUNIT_ASSERT_EQUAL(2, int(meta2->metaCount())); CPPUNIT_ASSERT(meta2->metaValue("author") == "Einstein"); CPPUNIT_ASSERT_EQUAL(2009, meta2->metaValue("year")); // Clear registry. Metadata::clearRegistry(); remove("something.vdb2"); } void TestFile::testReadAll() { using namespace openvdb; typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Int32Grid IntGrid; typedef FloatGrid::TreeType FloatTree; typedef Int32Grid::TreeType IntTree; // Create a vdb to write. // Create grids IntGrid::Ptr grid1 = createGrid(/*bg=*/1); IntTree& tree = grid1->tree(); grid1->setName("density"); FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); FloatTree& tree2 = grid2->tree(); grid2->setName("temperature"); // Create transforms math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); grid1->setTransform(trans); grid2->setTransform(trans2); // Set some values tree.setValue(Coord(0, 0, 0), 5); tree.setValue(Coord(100, 0, 0), 6); tree2.setValue(Coord(0, 0, 0), 10); tree2.setValue(Coord(0, 100, 0), 11); MetaMap meta; meta.insertMeta("author", StringMetadata("Einstein")); meta.insertMeta("year", Int32Metadata(2009)); GridPtrVec grids; grids.push_back(grid1); grids.push_back(grid2); // Register grid and transform. openvdb::initialize(); // Write the vdb out to a file. io::File vdbfile("something.vdb2"); vdbfile.write(grids, meta); io::File vdbfile2("something.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile2.getGrids(), openvdb::IoError); vdbfile2.open(); CPPUNIT_ASSERT(vdbfile2.isOpen()); GridPtrVecPtr grids2 = vdbfile2.getGrids(); MetaMap::Ptr meta2 = vdbfile2.getMetadata(); // Ensure we have the metadata. CPPUNIT_ASSERT_EQUAL(2, int(meta2->metaCount())); CPPUNIT_ASSERT(meta2->metaValue("author") == "Einstein"); CPPUNIT_ASSERT_EQUAL(2009, meta2->metaValue("year")); // Ensure we got the grids. CPPUNIT_ASSERT_EQUAL(2, int(grids2->size())); GridBase::Ptr grid; grid.reset(); grid = findGridByName(*grids2, "density"); CPPUNIT_ASSERT(grid.get() != NULL); IntTree::Ptr density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != NULL); grid.reset(); grid = findGridByName(*grids2, "temperature"); CPPUNIT_ASSERT(grid.get() != NULL); FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != NULL); CPPUNIT_ASSERT_DOUBLES_EQUAL(5, density->getValue(Coord(0, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(6, density->getValue(Coord(100, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(10, temperature->getValue(Coord(0, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(11, temperature->getValue(Coord(0, 100, 0)), /*tolerance=*/0); // Clear registries. GridBase::clearRegistry(); Metadata::clearRegistry(); math::MapRegistry::clear(); vdbfile2.close(); remove("something.vdb2"); } void TestFile::testWriteOpenFile() { using namespace openvdb; MetaMap::Ptr meta(new MetaMap); meta->insertMeta("author", StringMetadata("Einstein")); meta->insertMeta("year", Int32Metadata(2009)); // Register metadata Metadata::clearRegistry(); StringMetadata::registerType(); Int32Metadata::registerType(); // Write the metadata out to a file. io::File vdbfile("something.vdb2"); vdbfile.write(GridPtrVec(), *meta); io::File vdbfile2("something.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile2.getGrids(), openvdb::IoError); vdbfile2.open(); CPPUNIT_ASSERT(vdbfile2.isOpen()); GridPtrVecPtr grids = vdbfile2.getGrids(); meta = vdbfile2.getMetadata(); // Ensure we have the metadata. CPPUNIT_ASSERT(meta.get() != NULL); CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); CPPUNIT_ASSERT(meta->metaValue("author") == "Einstein"); CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); // Ensure we got the grids. CPPUNIT_ASSERT(grids.get() != NULL); CPPUNIT_ASSERT_EQUAL(0, int(grids->size())); // Cannot write an open file. CPPUNIT_ASSERT_THROW(vdbfile2.write(*grids), openvdb::IoError); vdbfile2.close(); CPPUNIT_ASSERT_NO_THROW(vdbfile2.write(*grids)); // Clear registries. Metadata::clearRegistry(); remove("something.vdb2"); } void TestFile::testReadGridMetadata() { using namespace openvdb; openvdb::initialize(); const char* filename = "/tmp/testReadGridMetadata.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); // Create grids Int32Grid::Ptr igrid = createGrid(/*bg=*/1); FloatGrid::Ptr fgrid = createGrid(/*bg=*/2.0); // Add metadata. igrid->setName("igrid"); igrid->insertMeta("author", StringMetadata("Einstein")); igrid->insertMeta("year", Int32Metadata(2012)); fgrid->setName("fgrid"); fgrid->insertMeta("author", StringMetadata("Einstein")); fgrid->insertMeta("year", Int32Metadata(2012)); // Add transforms. math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); igrid->setTransform(trans); fgrid->setTransform(trans); // Set some values. igrid->tree().setValue(Coord(0, 0, 0), 5); igrid->tree().setValue(Coord(100, 0, 0), 6); fgrid->tree().setValue(Coord(0, 0, 0), 10); fgrid->tree().setValue(Coord(0, 100, 0), 11); GridPtrVec srcGrids; srcGrids.push_back(igrid); srcGrids.push_back(fgrid); std::map srcGridMap; srcGridMap[igrid->getName()] = igrid; srcGridMap[fgrid->getName()] = fgrid; enum { OUTPUT_TO_FILE = 0, OUTPUT_TO_STREAM = 1 }; for (int outputMethod = OUTPUT_TO_FILE; outputMethod <= OUTPUT_TO_STREAM; ++outputMethod) { if (outputMethod == OUTPUT_TO_FILE) { // Write the grids to a file. io::File vdbfile(filename); vdbfile.write(srcGrids); } else { // Stream the grids to a file (i.e., without file offsets). std::ofstream ostrm(filename, std::ios_base::binary); io::Stream(ostrm).write(srcGrids); } // Read just the grid-level metadata from the file. io::File vdbfile(filename); // Verify that reading from an unopened file generates an exception. CPPUNIT_ASSERT_THROW(vdbfile.readGridMetadata("igrid"), openvdb::IoError); CPPUNIT_ASSERT_THROW(vdbfile.readGridMetadata("noname"), openvdb::IoError); CPPUNIT_ASSERT_THROW(vdbfile.readAllGridMetadata(), openvdb::IoError); vdbfile.open(); CPPUNIT_ASSERT(vdbfile.isOpen()); // Verify that reading a nonexistent grid generates an exception. CPPUNIT_ASSERT_THROW(vdbfile.readGridMetadata("noname"), openvdb::KeyError); // Read all grids and store them in a list. GridPtrVecPtr gridMetadata = vdbfile.readAllGridMetadata(); CPPUNIT_ASSERT(gridMetadata.get() != NULL); CPPUNIT_ASSERT_EQUAL(2, int(gridMetadata->size())); // Read individual grids and append them to the list. GridBase::Ptr grid = vdbfile.readGridMetadata("igrid"); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(std::string("igrid"), grid->getName()); gridMetadata->push_back(grid); grid = vdbfile.readGridMetadata("fgrid"); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(std::string("fgrid"), grid->getName()); gridMetadata->push_back(grid); // Verify that the grids' metadata and transforms match the original grids'. for (size_t i = 0, N = gridMetadata->size(); i < N; ++i) { grid = (*gridMetadata)[i]; CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT(grid->getName() == "igrid" || grid->getName() == "fgrid"); CPPUNIT_ASSERT(grid->baseTreePtr().get() != NULL); // Since we didn't read the grid's topology, the tree should be empty. CPPUNIT_ASSERT_EQUAL(0, int(grid->constBaseTreePtr()->leafCount())); CPPUNIT_ASSERT_EQUAL(0, int(grid->constBaseTreePtr()->activeVoxelCount())); // Retrieve the source grid of the same name. GridBase::ConstPtr srcGrid = srcGridMap[grid->getName()]; // Compare grid types and transforms. CPPUNIT_ASSERT_EQUAL(srcGrid->type(), grid->type()); CPPUNIT_ASSERT_EQUAL(srcGrid->transform(), grid->transform()); // Compare metadata, ignoring fields that were added when the file was written. MetaMap::Ptr statsMetadata = grid->getStatsMetadata(), otherMetadata = grid->copyMeta(); // shallow copy CPPUNIT_ASSERT(!statsMetadata->empty()); statsMetadata->insertMeta(GridBase::META_FILE_COMPRESSION, StringMetadata("")); for (MetaMap::ConstMetaIterator it = grid->beginMeta(), end = grid->endMeta(); it != end; ++it) { // Keep all fields that exist in the source grid. if ((*srcGrid)[it->first]) continue; // Remove any remaining grid statistics fields. if ((*statsMetadata)[it->first]) { otherMetadata->removeMeta(it->first); } } CPPUNIT_ASSERT_EQUAL(srcGrid->str(), otherMetadata->str()); const CoordBBox srcBBox = srcGrid->evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(srcBBox.min().asVec3i(), grid->metaValue("file_bbox_min")); CPPUNIT_ASSERT_EQUAL(srcBBox.max().asVec3i(), grid->metaValue("file_bbox_max")); CPPUNIT_ASSERT_EQUAL(srcGrid->activeVoxelCount(), Index64(grid->metaValue("file_voxel_count"))); CPPUNIT_ASSERT_EQUAL(srcGrid->memUsage(), Index64(grid->metaValue("file_mem_bytes"))); } } } void TestFile::testReadGridPartial() { using namespace openvdb; typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Int32Grid IntGrid; typedef FloatGrid::TreeType FloatTree; typedef Int32Grid::TreeType IntTree; // Create grids IntGrid::Ptr grid = createGrid(/*bg=*/1); IntTree& tree = grid->tree(); grid->setName("density"); FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); FloatTree& tree2 = grid2->tree(); grid2->setName("temperature"); // Create transforms math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); grid->setTransform(trans); grid2->setTransform(trans2); // Set some values tree.setValue(Coord(0, 0, 0), 5); tree.setValue(Coord(100, 0, 0), 6); tree2.setValue(Coord(0, 0, 0), 10); tree2.setValue(Coord(0, 100, 0), 11); MetaMap meta; meta.insertMeta("author", StringMetadata("Einstein")); meta.insertMeta("year", Int32Metadata(2009)); GridPtrVec grids; grids.push_back(grid); grids.push_back(grid2); // Register grid and transform. openvdb::initialize(); // Write the vdb out to a file. io::File vdbfile("something.vdb2"); vdbfile.write(grids, meta); io::File vdbfile2("something.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile2.readGridPartial("density"), openvdb::IoError); vdbfile2.open(); CPPUNIT_ASSERT(vdbfile2.isOpen()); CPPUNIT_ASSERT_THROW(vdbfile2.readGridPartial("noname"), openvdb::KeyError); GridBase::ConstPtr density = vdbfile2.readGridPartial("density"); CPPUNIT_ASSERT(density.get() != NULL); IntTree::ConstPtr typedDensity = gridConstPtrCast(density)->treePtr(); CPPUNIT_ASSERT(typedDensity.get() != NULL); // the following should cause a compiler error. // typedDensity->setValue(0, 0, 0, 0); // Clear registries. GridBase::clearRegistry(); Metadata::clearRegistry(); math::MapRegistry::clear(); vdbfile2.close(); remove("something.vdb2"); } void TestFile::testReadGrid() { using namespace openvdb; typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Int32Grid IntGrid; typedef FloatGrid::TreeType FloatTree; typedef Int32Grid::TreeType IntTree; // Create a vdb to write. // Create grids IntGrid::Ptr grid = createGrid(/*bg=*/1); IntTree& tree = grid->tree(); grid->setName("density"); FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); FloatTree& tree2 = grid2->tree(); grid2->setName("temperature"); // Create transforms math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); grid->setTransform(trans); grid2->setTransform(trans2); // Set some values tree.setValue(Coord(0, 0, 0), 5); tree.setValue(Coord(100, 0, 0), 6); tree2.setValue(Coord(0, 0, 0), 10); tree2.setValue(Coord(0, 100, 0), 11); MetaMap meta; meta.insertMeta("author", StringMetadata("Einstein")); meta.insertMeta("year", Int32Metadata(2009)); GridPtrVec grids; grids.push_back(grid); grids.push_back(grid2); // Register grid and transform. openvdb::initialize(); // Write the vdb out to a file. io::File vdbfile("something.vdb2"); vdbfile.write(grids, meta); io::File vdbfile2("something.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile2.readGridPartial("density"), openvdb::IoError); vdbfile2.open(); CPPUNIT_ASSERT(vdbfile2.isOpen()); CPPUNIT_ASSERT_THROW(vdbfile2.readGridPartial("noname"), openvdb::KeyError); // Get Temperature GridBase::Ptr temperature = vdbfile2.readGrid("temperature"); CPPUNIT_ASSERT(temperature.get() != NULL); FloatTree::Ptr typedTemperature = gridPtrCast(temperature)->treePtr(); CPPUNIT_ASSERT(typedTemperature.get() != NULL); CPPUNIT_ASSERT_DOUBLES_EQUAL(10, typedTemperature->getValue(Coord(0, 0, 0)), 0); CPPUNIT_ASSERT_DOUBLES_EQUAL(11, typedTemperature->getValue(Coord(0, 100, 0)), 0); // Get Density GridBase::Ptr density = vdbfile2.readGrid("density"); CPPUNIT_ASSERT(density.get() != NULL); IntTree::Ptr typedDensity = gridPtrCast(density)->treePtr(); CPPUNIT_ASSERT(typedDensity.get() != NULL); CPPUNIT_ASSERT_DOUBLES_EQUAL(5,typedDensity->getValue(Coord(0, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(6,typedDensity->getValue(Coord(100, 0, 0)), /*tolerance=*/0); // Clear registries. GridBase::clearRegistry(); Metadata::clearRegistry(); math::MapRegistry::clear(); vdbfile2.close(); remove("something.vdb2"); } void TestFile::testMultipleBufferIO() { using namespace openvdb; using namespace openvdb::io; typedef Int32Grid GridType; typedef GridType::TreeType TreeType; File file("something.vdb2"); std::ostringstream ostr(std::ios_base::binary); // Create a grid with transform. GridType::Ptr grid = createGrid(/*bg=*/1); TreeType& tree = grid->tree(); grid->setName("temperature"); math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); grid->setTransform(trans); tree.setValue(Coord(10, 1, 2), 10); tree.setValue(Coord(0, 0, 0), 5); GridPtrVec grids; grids.push_back(grid); // Register grid and transform. openvdb::initialize(); // write the vdb to the file. file.write(grids); // read into a different grid. File file2("something.vdb2"); file2.open(); GridBase::Ptr temperature = file2.readGrid("temperature"); CPPUNIT_ASSERT(temperature.get() != NULL); // Clear registries. GridBase::clearRegistry(); math::MapRegistry::clear(); remove("something.vdb2"); } void TestFile::testHasGrid() { using namespace openvdb; using namespace openvdb::io; typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Int32Grid IntGrid; typedef FloatGrid::TreeType FloatTree; typedef Int32Grid::TreeType IntTree; // Create a vdb to write. // Create grids IntGrid::Ptr grid = createGrid(/*bg=*/1); IntTree& tree = grid->tree(); grid->setName("density"); FloatGrid::Ptr grid2 = createGrid(/*bg=*/2.0); FloatTree& tree2 = grid2->tree(); grid2->setName("temperature"); // Create transforms math::Transform::Ptr trans = math::Transform::createLinearTransform(0.1); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); grid->setTransform(trans); grid2->setTransform(trans2); // Set some values tree.setValue(Coord(0, 0, 0), 5); tree.setValue(Coord(100, 0, 0), 6); tree2.setValue(Coord(0, 0, 0), 10); tree2.setValue(Coord(0, 100, 0), 11); MetaMap meta; meta.insertMeta("author", StringMetadata("Einstein")); meta.insertMeta("year", Int32Metadata(2009)); GridPtrVec grids; grids.push_back(grid); grids.push_back(grid2); // Register grid and transform. GridBase::clearRegistry(); IntGrid::registerGrid(); FloatGrid::registerGrid(); Metadata::clearRegistry(); StringMetadata::registerType(); Int32Metadata::registerType(); // register maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::ScaleMap::registerMap(); math::UniformScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); math::UniformScaleTranslateMap::registerMap(); math::NonlinearFrustumMap::registerMap(); // Write the vdb out to a file. io::File vdbfile("something.vdb2"); vdbfile.write(grids, meta); io::File vdbfile2("something.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile2.hasGrid("density"), openvdb::IoError); vdbfile2.open(); CPPUNIT_ASSERT(vdbfile2.hasGrid("density")); CPPUNIT_ASSERT(vdbfile2.hasGrid("temperature")); CPPUNIT_ASSERT(!vdbfile2.hasGrid("Temperature")); CPPUNIT_ASSERT(!vdbfile2.hasGrid("densitY")); // Clear registries. GridBase::clearRegistry(); Metadata::clearRegistry(); math::MapRegistry::clear(); vdbfile2.close(); remove("something.vdb2"); } void TestFile::testNameIterator() { using namespace openvdb; using namespace openvdb::io; typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Int32Grid IntGrid; typedef FloatGrid::TreeType FloatTree; typedef Int32Grid::TreeType IntTree; // Create trees. IntTree::Ptr itree(new IntTree(1)); itree->setValue(Coord(0, 0, 0), 5); itree->setValue(Coord(100, 0, 0), 6); FloatTree::Ptr ftree(new FloatTree(2.0)); ftree->setValue(Coord(0, 0, 0), 10.0); ftree->setValue(Coord(0, 100, 0), 11.0); // Create grids. GridPtrVec grids; GridBase::Ptr grid = createGrid(itree); grid->setName("density"); grids.push_back(grid); grid = createGrid(ftree); grid->setName("temperature"); grids.push_back(grid); // Create two unnamed grids. grids.push_back(createGrid(ftree)); grids.push_back(createGrid(ftree)); // Create two grids with the same name. grid = createGrid(ftree); grid->setName("level_set"); grids.push_back(grid); grid = createGrid(ftree); grid->setName("level_set"); grids.push_back(grid); // Register types. openvdb::initialize(); const char* filename = "/tmp/testNameIterator.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); // Write the grids out to a file. { io::File vdbfile(filename); vdbfile.write(grids); } io::File vdbfile(filename); // Verify that name iteration fails if the file is not open. CPPUNIT_ASSERT_THROW(vdbfile.beginName(), openvdb::IoError); vdbfile.open(); // Names should appear in lexicographic order. Name names[6] = { "[1]", "[2]", "density", "level_set", "level_set[1]", "temperature" }; int count = 0; for (io::File::NameIterator iter = vdbfile.beginName(); iter != vdbfile.endName(); ++iter) { CPPUNIT_ASSERT_EQUAL(names[count], *iter); CPPUNIT_ASSERT_EQUAL(names[count], iter.gridName()); ++count; grid = vdbfile.readGrid(*iter); CPPUNIT_ASSERT(grid); } CPPUNIT_ASSERT_EQUAL(6, count); vdbfile.close(); } void TestFile::testReadOldFileFormat() { /// @todo Save some old-format (prior to OPENVDB_FILE_VERSION) .vdb2 files /// to /work/rd/fx_tools/vdb_unittest/TestFile::testReadOldFileFormat/ /// Verify that the files can still be read correctly. } void TestFile::testCompression() { using namespace openvdb; using namespace openvdb::io; typedef openvdb::Int32Grid IntGrid; // Register types. openvdb::initialize(); // Create reference grids. IntGrid::Ptr intGrid = IntGrid::create(/*background=*/0); intGrid->fill(CoordBBox(Coord(0), Coord(49)), /*value=*/999, /*active=*/true); intGrid->fill(CoordBBox(Coord(6), Coord(43)), /*value=*/0, /*active=*/false); FloatGrid::Ptr lsGrid = createLevelSet(); unittest_util::makeSphere(/*dim=*/Coord(100), /*ctr=*/Vec3f(50, 50, 50), /*r=*/20.0, *lsGrid, unittest_util::SPHERE_SPARSE_NARROW_BAND); CPPUNIT_ASSERT_EQUAL(int(GRID_LEVEL_SET), int(lsGrid->getGridClass())); FloatGrid::Ptr fogGrid = lsGrid->deepCopy(); tools::sdfToFogVolume(*fogGrid); CPPUNIT_ASSERT_EQUAL(int(GRID_FOG_VOLUME), int(fogGrid->getGridClass())); GridPtrVec grids; grids.push_back(intGrid); grids.push_back(lsGrid); grids.push_back(fogGrid); const char* filename = "/tmp/testCompression.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); size_t uncompressedSize = 0; { // Write the grids out to a file with compression disabled. io::File vdbfile(filename); vdbfile.setCompressionFlags(io::COMPRESS_NONE); vdbfile.write(grids); vdbfile.close(); // Get the size of the file in bytes. struct stat buf; buf.st_size = 0; CPPUNIT_ASSERT_EQUAL(0, ::stat(filename, &buf)); uncompressedSize = buf.st_size; } // Write the grids out with various combinations of compression options // and verify that they can be read back successfully. // Currently, only bits 0 and 1 have meaning as compression flags // (see io/Compression.h), so the valid combinations range from 0x0 to 0x3. for (uint32_t flags = 0x0; flags <= 0x3; ++flags) { if (flags != io::COMPRESS_NONE) { io::File vdbfile(filename); vdbfile.setCompressionFlags(flags); vdbfile.write(grids); vdbfile.close(); } if (flags != io::COMPRESS_NONE) { // Verify that the compressed file is significantly smaller than // the uncompressed file. size_t compressedSize = 0; struct stat buf; buf.st_size = 0; CPPUNIT_ASSERT_EQUAL(0, ::stat(filename, &buf)); compressedSize = buf.st_size; CPPUNIT_ASSERT(compressedSize < size_t(0.75 * uncompressedSize)); } { // Verify that the grids can be read back successfully. io::File vdbfile(filename); vdbfile.open(); GridPtrVecPtr inGrids = vdbfile.getGrids(); CPPUNIT_ASSERT_EQUAL(3, int(inGrids->size())); // Verify that the original and input grids are equal. { const IntGrid::Ptr grid = gridPtrCast((*inGrids)[0]); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(int(intGrid->getGridClass()), int(grid->getGridClass())); CPPUNIT_ASSERT(grid->tree().hasSameTopology(intGrid->tree())); CPPUNIT_ASSERT_EQUAL( intGrid->tree().getValue(Coord(0)), grid->tree().getValue(Coord(0))); // Verify that the only active value in this grid is 999. Int32 minVal = -1, maxVal = -1; grid->evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(999, minVal); CPPUNIT_ASSERT_EQUAL(999, maxVal); } for (int idx = 1; idx <= 2; ++idx) { const FloatGrid::Ptr grid = gridPtrCast((*inGrids)[idx]), refGrid = gridPtrCast(grids[idx]); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(int(refGrid->getGridClass()), int(grid->getGridClass())); CPPUNIT_ASSERT(grid->tree().hasSameTopology(refGrid->tree())); FloatGrid::ConstAccessor refAcc = refGrid->getConstAccessor(); for (FloatGrid::ValueAllCIter it = grid->cbeginValueAll(); it; ++it) { CPPUNIT_ASSERT_EQUAL(refAcc.getValue(it.getCoord()), *it); } } } } } //////////////////////////////////////// namespace { using namespace openvdb; struct TestAsyncHelper { std::set ids; std::map filenames; size_t refFileSize; bool verbose; TestAsyncHelper(size_t _refFileSize): refFileSize(_refFileSize), verbose(false) {} ~TestAsyncHelper() { // Remove output files. for (std::map::iterator it = filenames.begin(); it != filenames.end(); ++it) { ::remove(it->second.c_str()); } filenames.clear(); ids.clear(); } io::Queue::Notifier notifier() { return boost::bind(&TestAsyncHelper::validate, this, _1, _2); } void insert(io::Queue::Id id, const std::string& filename) { ids.insert(id); filenames[id] = filename; if (verbose) std::cerr << "queued " << filename << " as task " << id << "\n"; } void validate(io::Queue::Id id, io::Queue::Status status) { if (verbose) { std::ostringstream ostr; ostr << "task " << id; switch (status) { case io::Queue::UNKNOWN: ostr << " is unknown"; break; case io::Queue::PENDING: ostr << " is pending"; break; case io::Queue::SUCCEEDED: ostr << " succeeded"; break; case io::Queue::FAILED: ostr << " failed"; break; } std::cerr << ostr.str() << "\n"; } if (status == io::Queue::SUCCEEDED) { // If the task completed successfully, verify that the output file's // size matches the reference file's size. struct stat buf; buf.st_size = 0; CPPUNIT_ASSERT_EQUAL(0, ::stat(filenames[id].c_str(), &buf)); CPPUNIT_ASSERT_EQUAL(Index64(refFileSize), Index64(buf.st_size)); } if (status == io::Queue::SUCCEEDED || status == io::Queue::FAILED) { ids.erase(id); } } }; // struct TestAsyncHelper } // unnamed namespace void TestFile::testAsync() { using namespace openvdb; typedef openvdb::Int32Grid IntGrid; // Register types. openvdb::initialize(); // Create a grid. FloatGrid::Ptr lsGrid = createLevelSet(); unittest_util::makeSphere(/*dim=*/Coord(100), /*ctr=*/Vec3f(50, 50, 50), /*r=*/20.0, *lsGrid, unittest_util::SPHERE_SPARSE_NARROW_BAND); MetaMap fileMetadata; fileMetadata.insertMeta("author", StringMetadata("Einstein")); fileMetadata.insertMeta("year", Int32Metadata(2013)); GridPtrVec grids; grids.push_back(lsGrid); grids.push_back(lsGrid->deepCopy()); grids.push_back(lsGrid->deepCopy()); size_t refFileSize = 0; { // Write a reference file without using asynchronous I/O. const char* filename = "/tmp/testAsyncref.vdb"; boost::shared_ptr scopedFile(filename, ::remove); io::File f(filename); f.write(grids, fileMetadata); // Record the size of the reference file. struct stat buf; buf.st_size = 0; CPPUNIT_ASSERT_EQUAL(0, ::stat(filename, &buf)); refFileSize = buf.st_size; } { // Output multiple files using asynchronous I/O. // Use polling to get the status of the I/O tasks. TestAsyncHelper helper(refFileSize); io::Queue queue; for (int i = 1; i < 10; ++i) { std::ostringstream ostr; ostr << "/tmp/testAsync." << i << ".vdb"; const std::string filename = ostr.str(); io::Queue::Id id = queue.write(grids, io::File(filename), fileMetadata); helper.insert(id, filename); } tbb::tick_count start = tbb::tick_count::now(); while (!helper.ids.empty()) { if ((tbb::tick_count::now() - start).seconds() > 60) break; // time out after 1 minute // Wait one second for tasks to complete. tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0/*sec*/)); // Poll each task in the pending map. std::set ids = helper.ids; // iterate over a copy for (std::set::iterator it = ids.begin(); it != ids.end(); ++it) { const io::Queue::Id id = *it; const io::Queue::Status status = queue.status(id); helper.validate(id, status); } } CPPUNIT_ASSERT(helper.ids.empty()); CPPUNIT_ASSERT(queue.empty()); } { // Output multiple files using asynchronous I/O. // Use notifications to get the status of the I/O tasks. TestAsyncHelper helper(refFileSize); io::Queue queue(/*capacity=*/2); queue.addNotifier(helper.notifier()); for (int i = 1; i < 10; ++i) { std::ostringstream ostr; ostr << "/tmp/testAsync" << i << ".vdb"; const std::string filename = ostr.str(); io::Queue::Id id = queue.write(grids, io::File(filename), fileMetadata); helper.insert(id, filename); } while (!queue.empty()) { tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0/*sec*/)); } } { // Test queue timeout. io::Queue queue(/*capacity=*/1); queue.setTimeout(0/*sec*/); boost::shared_ptr scopedFile1("/tmp/testAsyncIOa.vdb", ::remove), scopedFile2("/tmp/testAsyncIOb.vdb", ::remove); std::ofstream file1(scopedFile1.get()), file2(scopedFile2.get()); queue.write(grids, io::Stream(file1)); // With the queue length restricted to 1 and the timeout to 0 seconds, // the next write() call should time out immediately with an exception. // (It is possible, though highly unlikely, for the previous task to complete // in time for this write() to actually succeed.) CPPUNIT_ASSERT_THROW(queue.write(grids, io::Stream(file2)), openvdb::RuntimeError); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMetaMap.cc0000644000000000000000000002427212252453157015600 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include class TestMetaMap: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestMetaMap); CPPUNIT_TEST(testInsert); CPPUNIT_TEST(testRemove); CPPUNIT_TEST(testGetMetadata); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testEmptyIO); CPPUNIT_TEST(testCopyConstructor); CPPUNIT_TEST(testCopyConstructorEmpty); CPPUNIT_TEST(testAssignment); CPPUNIT_TEST_SUITE_END(); void testInsert(); void testRemove(); void testGetMetadata(); void testIO(); void testEmptyIO(); void testCopyConstructor(); void testCopyConstructorEmpty(); void testAssignment(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMetaMap); void TestMetaMap::testInsert() { using namespace openvdb; MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", FloatMetadata(2.0)); MetaMap::MetaIterator iter = meta.beginMeta(); int i = 1; for( ; iter != meta.endMeta(); ++iter, ++i) { if(i == 1) { CPPUNIT_ASSERT(iter->first.compare("meta1") == 0); std::string val = meta.metaValue("meta1"); CPPUNIT_ASSERT(val == "testing"); } else if(i == 2) { CPPUNIT_ASSERT(iter->first.compare("meta2") == 0); int32_t val = meta.metaValue("meta2"); CPPUNIT_ASSERT(val == 20); } else if(i == 3) { CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); float val = meta.metaValue("meta3"); //CPPUNIT_ASSERT(val == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); } } } void TestMetaMap::testRemove() { using namespace openvdb; MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", FloatMetadata(2.0)); meta.removeMeta("meta2"); MetaMap::MetaIterator iter = meta.beginMeta(); int i = 1; for( ; iter != meta.endMeta(); ++iter, ++i) { if(i == 1) { CPPUNIT_ASSERT(iter->first.compare("meta1") == 0); std::string val = meta.metaValue("meta1"); CPPUNIT_ASSERT(val == "testing"); } else if(i == 2) { CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); float val = meta.metaValue("meta3"); //CPPUNIT_ASSERT(val == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); } } meta.removeMeta("meta1"); iter = meta.beginMeta(); for( ; iter != meta.endMeta(); ++iter, ++i) { CPPUNIT_ASSERT(iter->first.compare("meta3") == 0); float val = meta.metaValue("meta3"); //CPPUNIT_ASSERT(val == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,val,0); } meta.removeMeta("meta3"); CPPUNIT_ASSERT(meta.empty()); } void TestMetaMap::testGetMetadata() { using namespace openvdb; MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", DoubleMetadata(2.0)); Metadata::Ptr metadata = meta["meta2"]; CPPUNIT_ASSERT(metadata); CPPUNIT_ASSERT(metadata->typeName().compare("int32") == 0); DoubleMetadata::Ptr dm = meta.getMetadata("meta3"); //CPPUNIT_ASSERT(dm->value() == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,dm->value(),0); const DoubleMetadata::Ptr cdm = meta.getMetadata("meta3"); //CPPUNIT_ASSERT(dm->value() == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,cdm->value(),0); CPPUNIT_ASSERT(meta.getMetadata("meta2") == NULL); CPPUNIT_ASSERT_THROW(meta.metaValue("meta3"), openvdb::TypeError); CPPUNIT_ASSERT_THROW(meta.metaValue("meta5"), openvdb::LookupError); } void TestMetaMap::testIO() { using namespace openvdb; Metadata::clearRegistry(); // Write some metadata using unregistered types. MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", DoubleMetadata(2.0)); std::ostringstream ostr(std::ios_base::binary); meta.writeMeta(ostr); // Verify that reading metadata of unregistered types is possible, // though the values cannot be retrieved. MetaMap meta2; std::istringstream istr(ostr.str(), std::ios_base::binary); CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istr)); CPPUNIT_ASSERT(meta2.empty()); // Register just one of the three types, then reread and verify that // the value of the registered type can be retrieved. Int32Metadata::registerType(); istr.seekg(0, std::ios_base::beg); CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istr)); CPPUNIT_ASSERT_EQUAL(size_t(1), meta2.metaCount()); CPPUNIT_ASSERT_EQUAL(meta.metaValue("meta2"), meta2.metaValue("meta2")); // Register the remaining types. StringMetadata::registerType(); DoubleMetadata::registerType(); // Now seek to beginning and read again. istr.seekg(0, std::ios_base::beg); meta2.clearMetadata(); CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istr)); CPPUNIT_ASSERT_EQUAL(meta.metaCount(), meta2.metaCount()); std::string val = meta.metaValue("meta1"); std::string val2 = meta2.metaValue("meta1"); CPPUNIT_ASSERT_EQUAL(0, val.compare(val2)); int intval = meta.metaValue("meta2"); int intval2 = meta2.metaValue("meta2"); CPPUNIT_ASSERT_EQUAL(intval, intval2); double dval = meta.metaValue("meta3"); double dval2 = meta2.metaValue("meta3"); CPPUNIT_ASSERT_DOUBLES_EQUAL(dval, dval2,0); // Clear the registry once the test is done. Metadata::clearRegistry(); } void TestMetaMap::testEmptyIO() { using namespace openvdb; MetaMap meta; // Write out an empty metadata std::ostringstream ostr(std::ios_base::binary); // Read in the metadata; MetaMap meta2; std::istringstream istr(ostr.str(), std::ios_base::binary); CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istr)); CPPUNIT_ASSERT(meta2.metaCount() == 0); } void TestMetaMap::testCopyConstructor() { using namespace openvdb; MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", FloatMetadata(2.0)); // copy constructor MetaMap meta2(meta); CPPUNIT_ASSERT(meta.metaCount() == meta2.metaCount()); std::string str = meta.metaValue("meta1"); std::string str2 = meta2.metaValue("meta1"); CPPUNIT_ASSERT(str == str2); CPPUNIT_ASSERT(meta.metaValue("meta2") == meta2.metaValue("meta2")); CPPUNIT_ASSERT_DOUBLES_EQUAL(meta.metaValue("meta3"), meta2.metaValue("meta3"),0); //CPPUNIT_ASSERT(meta.metaValue("meta3") == // meta2.metaValue("meta3")); } void TestMetaMap::testCopyConstructorEmpty() { using namespace openvdb; MetaMap meta; MetaMap meta2(meta); CPPUNIT_ASSERT(meta.metaCount() == 0); CPPUNIT_ASSERT(meta2.metaCount() == meta.metaCount()); } void TestMetaMap::testAssignment() { using namespace openvdb; // Populate a map with data. MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", FloatMetadata(2.0)); // Create an empty map. MetaMap meta2; CPPUNIT_ASSERT(meta2.empty()); // Copy the first map to the second. meta2 = meta; CPPUNIT_ASSERT_EQUAL(meta.metaCount(), meta2.metaCount()); // Verify that the contents of the two maps are the same. CPPUNIT_ASSERT_EQUAL( meta.metaValue("meta1"), meta2.metaValue("meta1")); CPPUNIT_ASSERT_EQUAL(meta.metaValue("meta2"), meta2.metaValue("meta2")); CPPUNIT_ASSERT_DOUBLES_EQUAL( meta.metaValue("meta3"), meta2.metaValue("meta3"), /*tolerance=*/0); // Verify that changing one map doesn't affect the other. meta.insertMeta("meta1", StringMetadata("changed")); std::string str = meta.metaValue("meta1"); CPPUNIT_ASSERT_EQUAL(std::string("testing"), meta2.metaValue("meta1")); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestVec3Metadata.cc0000644000000000000000000001070212252453157016506 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestVec3Metadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestVec3Metadata); CPPUNIT_TEST(testVec3i); CPPUNIT_TEST(testVec3s); CPPUNIT_TEST(testVec3d); CPPUNIT_TEST_SUITE_END(); void testVec3i(); void testVec3s(); void testVec3d(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestVec3Metadata); void TestVec3Metadata::testVec3i() { using namespace openvdb; Metadata::Ptr m(new Vec3IMetadata(openvdb::Vec3i(1, 1, 1))); Metadata::Ptr m3 = m->copy(); CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); CPPUNIT_ASSERT( m->typeName().compare("vec3i") == 0); CPPUNIT_ASSERT(m3->typeName().compare("vec3i") == 0); Vec3IMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec3i(1, 1, 1)); s->value() = openvdb::Vec3i(3, 3, 3); CPPUNIT_ASSERT(s->value() == openvdb::Vec3i(3, 3, 3)); m3->copy(*s); s = dynamic_cast(m3.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec3i(3, 3, 3)); } void TestVec3Metadata::testVec3s() { using namespace openvdb; Metadata::Ptr m(new Vec3SMetadata(openvdb::Vec3s(1, 1, 1))); Metadata::Ptr m3 = m->copy(); CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); CPPUNIT_ASSERT( m->typeName().compare("vec3s") == 0); CPPUNIT_ASSERT(m3->typeName().compare("vec3s") == 0); Vec3SMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec3s(1, 1, 1)); s->value() = openvdb::Vec3s(3, 3, 3); CPPUNIT_ASSERT(s->value() == openvdb::Vec3s(3, 3, 3)); m3->copy(*s); s = dynamic_cast(m3.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec3s(3, 3, 3)); } void TestVec3Metadata::testVec3d() { using namespace openvdb; Metadata::Ptr m(new Vec3DMetadata(openvdb::Vec3d(1, 1, 1))); Metadata::Ptr m3 = m->copy(); CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); CPPUNIT_ASSERT( m->typeName().compare("vec3d") == 0); CPPUNIT_ASSERT(m3->typeName().compare("vec3d") == 0); Vec3DMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec3d(1, 1, 1)); s->value() = openvdb::Vec3d(3, 3, 3); CPPUNIT_ASSERT(s->value() == openvdb::Vec3d(3, 3, 3)); m3->copy(*s); s = dynamic_cast(m3.get()); CPPUNIT_ASSERT(s->value() == openvdb::Vec3d(3, 3, 3)); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMetadata.cc0000644000000000000000000001024012252453157015762 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestMetadata : public CppUnit::TestCase { public: virtual void setUp() { openvdb::Metadata::clearRegistry(); } virtual void tearDown() { openvdb::Metadata::clearRegistry(); } CPPUNIT_TEST_SUITE(TestMetadata); CPPUNIT_TEST(testMetadataRegistry); CPPUNIT_TEST(testMetadataAsBool); CPPUNIT_TEST_SUITE_END(); void testMetadataRegistry(); void testMetadataAsBool(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadata); void TestMetadata::testMetadataRegistry() { using namespace openvdb; Int32Metadata::registerType(); StringMetadata strMetadata; CPPUNIT_ASSERT(!Metadata::isRegisteredType(strMetadata.typeName())); StringMetadata::registerType(); CPPUNIT_ASSERT(Metadata::isRegisteredType(strMetadata.typeName())); CPPUNIT_ASSERT( Metadata::isRegisteredType(Int32Metadata::staticTypeName())); Metadata::Ptr stringMetadata = Metadata::createMetadata(strMetadata.typeName()); CPPUNIT_ASSERT(stringMetadata->typeName() == strMetadata.typeName()); StringMetadata::unregisterType(); CPPUNIT_ASSERT_THROW(Metadata::createMetadata(strMetadata.typeName()), openvdb::LookupError); } void TestMetadata::testMetadataAsBool() { using namespace openvdb; { FloatMetadata meta(0.0); CPPUNIT_ASSERT(!meta.asBool()); meta.setValue(1.0); CPPUNIT_ASSERT(meta.asBool()); meta.setValue(-1.0); CPPUNIT_ASSERT(meta.asBool()); meta.setValue(999.0); CPPUNIT_ASSERT(meta.asBool()); } { Int32Metadata meta(0); CPPUNIT_ASSERT(!meta.asBool()); meta.setValue(1); CPPUNIT_ASSERT(meta.asBool()); meta.setValue(-1); CPPUNIT_ASSERT(meta.asBool()); meta.setValue(999); CPPUNIT_ASSERT(meta.asBool()); } { StringMetadata meta(""); CPPUNIT_ASSERT(!meta.asBool()); meta.setValue("abc"); CPPUNIT_ASSERT(meta.asBool()); } { Vec3IMetadata meta(Vec3i(0)); CPPUNIT_ASSERT(!meta.asBool()); meta.setValue(Vec3i(-1, 0, 1)); CPPUNIT_ASSERT(meta.asBool()); } { Vec3SMetadata meta(Vec3s(0.0)); CPPUNIT_ASSERT(!meta.asBool()); meta.setValue(Vec3s(-1.0, 0.0, 1.0)); CPPUNIT_ASSERT(meta.asBool()); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTools.cc0000644000000000000000000017467712252453157015373 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" // for unittest_util::makeSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); // Simple helper class to write out numbered vdbs template class FrameWriter { public: FrameWriter(int version, typename GridT::Ptr grid) : mFrame(0), mVersion(version), mGrid(grid) {} void operator()(const std::string& name, float time, size_t n) { std::ostringstream ostr; ostr << "/usr/pic1/tmp/" << name << "_" << mVersion << "_" << mFrame << ".vdb"; openvdb::io::File file(ostr.str()); openvdb::GridPtrVec grids; grids.push_back(mGrid); file.write(grids); std::cerr << "\nWrote \"" << ostr.str() << "\" with time = " << time << " after CFL-iterations = " << n << std::endl; ++mFrame; } private: int mFrame, mVersion; typename GridT::Ptr mGrid; }; class TestTools: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTools); CPPUNIT_TEST(testDilateVoxels); CPPUNIT_TEST(testErodeVoxels); CPPUNIT_TEST(testActivate); CPPUNIT_TEST(testFilter); CPPUNIT_TEST(testFloatApply); CPPUNIT_TEST(testLevelSetSphere); CPPUNIT_TEST(testLevelSetAdvect); CPPUNIT_TEST(testLevelSetMeasure); CPPUNIT_TEST(testLevelSetMorph); CPPUNIT_TEST(testMagnitude); CPPUNIT_TEST(testMaskedMagnitude); CPPUNIT_TEST(testNormalize); CPPUNIT_TEST(testMaskedNormalize); CPPUNIT_TEST(testPointAdvect); CPPUNIT_TEST(testPointScatter); CPPUNIT_TEST(testTransformValues); CPPUNIT_TEST(testVectorApply); CPPUNIT_TEST(testAccumulate); CPPUNIT_TEST(testUtil); CPPUNIT_TEST_SUITE_END(); void testDilateVoxels(); void testErodeVoxels(); void testActivate(); void testFilter(); void testFloatApply(); void testLevelSetSphere(); void testLevelSetAdvect(); void testLevelSetMeasure(); void testLevelSetMorph(); void testMagnitude(); void testMaskedMagnitude(); void testNormalize(); void testMaskedNormalize(); void testPointAdvect(); void testPointScatter(); void testTransformValues(); void testVectorApply(); void testAccumulate(); void testUtil(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTools); void TestTools::testDilateVoxels() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Index32; using openvdb::Index64; typedef openvdb::tree::Tree4::Type Tree543f; Tree543f::Ptr tree(new Tree543f); tree->setBackground(/*background=*/5.0); CPPUNIT_ASSERT(tree->empty()); const openvdb::Index leafDim = Tree543f::LeafNodeType::DIM; CPPUNIT_ASSERT_EQUAL(1 << 3, int(leafDim)); { // Set and dilate a single voxel at the center of a leaf node. tree->clear(); tree->setValue(Coord(leafDim >> 1), 1.0); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(Index64(7), tree->activeVoxelCount()); } { // Create an active, leaf node-sized tile. tree->clear(); tree->fill(CoordBBox(Coord(0), Coord(leafDim - 1)), 1.0); CPPUNIT_ASSERT_EQUAL(Index32(0), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim), tree->activeVoxelCount()); tree->setValue(Coord(leafDim, leafDim - 1, leafDim - 1), 1.0); CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim + 1), tree->activeVoxelCount()); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim + 1 + 5), tree->activeVoxelCount()); } { // Set and dilate a single voxel at each of the eight corners of a leaf node. for (int i = 0; i < 8; ++i) { tree->clear(); openvdb::Coord xyz( i & 1 ? leafDim - 1 : 0, i & 2 ? leafDim - 1 : 0, i & 4 ? leafDim - 1 : 0); tree->setValue(xyz, 1.0); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(Index64(7), tree->activeVoxelCount()); } } { tree->clear(); tree->setValue(Coord(0), 1.0); tree->setValue(Coord( 1, 0, 0), 1.0); tree->setValue(Coord(-1, 0, 0), 1.0); CPPUNIT_ASSERT_EQUAL(Index64(3), tree->activeVoxelCount()); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(Index64(17), tree->activeVoxelCount()); } { struct Info { int activeVoxelCount, leafCount, nonLeafCount; }; Info iterInfo[11] = { { 1, 1, 3 }, { 7, 1, 3 }, { 25, 1, 3 }, { 63, 1, 3 }, { 129, 4, 3 }, { 231, 7, 9 }, { 377, 7, 9 }, { 575, 7, 9 }, { 833, 10, 9 }, { 1159, 16, 9 }, { 1561, 19, 15 }, }; // Perform repeated dilations, starting with a single voxel. tree->clear(); tree->setValue(Coord(leafDim >> 1), 1.0); for (int i = 0; i < 11; ++i) { CPPUNIT_ASSERT_EQUAL(iterInfo[i].activeVoxelCount, int(tree->activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(iterInfo[i].leafCount, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(iterInfo[i].nonLeafCount, int(tree->nonLeafCount())); openvdb::tools::dilateVoxels(*tree); } } {// dialte a narrow band of a sphere typedef openvdb::Grid GridType; GridType grid(tree->background()); unittest_util::makeSphere(/*dim=*/openvdb::Coord(64, 64, 64), /*center=*/openvdb::Vec3f(0, 0, 0), /*radius=*/20, grid, /*dx=*/1.0f, unittest_util::SPHERE_DENSE_NARROW_BAND); const openvdb::Index64 count = grid.tree().activeVoxelCount(); openvdb::tools::dilateVoxels(grid.tree()); CPPUNIT_ASSERT(grid.tree().activeVoxelCount() > count); } {// dilate a fog volume of a sphere typedef openvdb::Grid GridType; GridType grid(tree->background()); unittest_util::makeSphere(/*dim=*/openvdb::Coord(64, 64, 64), /*center=*/openvdb::Vec3f(0, 0, 0), /*radius=*/20, grid, /*dx=*/1.0f, unittest_util::SPHERE_DENSE_NARROW_BAND); openvdb::tools::sdfToFogVolume(grid); const openvdb::Index64 count = grid.tree().activeVoxelCount(); //std::cerr << "\nBefore: active voxel count = " << count << std::endl; //grid.print(std::cerr,5); openvdb::tools::dilateVoxels(grid.tree()); CPPUNIT_ASSERT(grid.tree().activeVoxelCount() > count); //std::cerr << "\nAfter: active voxel count = " << grid.tree().activeVoxelCount() << std::endl; } // {// Test a grid from a file that has proven to be challenging // openvdb::initialize(); // openvdb::io::File file("/usr/home/kmuseth/Data/vdb/dilation.vdb"); // file.open(); // openvdb::GridBase::Ptr baseGrid = file.readGrid(file.beginName().gridName()); // file.close(); // openvdb::FloatGrid::Ptr grid = openvdb::gridPtrCast(baseGrid); // const openvdb::Index64 count = grid->tree().activeVoxelCount(); // //std::cerr << "\nBefore: active voxel count = " << count << std::endl; // //grid->print(std::cerr,5); // openvdb::tools::dilateVoxels(grid->tree()); // CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); // //std::cerr << "\nAfter: active voxel count = " << grid->tree().activeVoxelCount() << std::endl; // } } void TestTools::testErodeVoxels() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Index32; using openvdb::Index64; typedef openvdb::tree::Tree4::Type TreeType; TreeType::Ptr tree(new TreeType); tree->setBackground(/*background=*/5.0); CPPUNIT_ASSERT(tree->empty()); const int leafDim = TreeType::LeafNodeType::DIM; CPPUNIT_ASSERT_EQUAL(1 << 3, leafDim); { // Set, dilate and erode a single voxel at the center of a leaf node. tree->clear(); CPPUNIT_ASSERT_EQUAL(0, int(tree->activeVoxelCount())); tree->setValue(Coord(leafDim >> 1), 1.0); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(7, int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(0, int(tree->activeVoxelCount())); } { // Create an active, leaf node-sized tile. tree->clear(); tree->fill(CoordBBox(Coord(0), Coord(leafDim - 1)), 1.0); CPPUNIT_ASSERT_EQUAL(0, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim, int(tree->activeVoxelCount())); tree->setValue(Coord(leafDim, leafDim - 1, leafDim - 1), 1.0); CPPUNIT_ASSERT_EQUAL(1, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1,int(tree->activeVoxelCount())); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(3, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1 + 5,int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(1, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(leafDim * leafDim * leafDim + 1, int(tree->activeVoxelCount())); } { // Set and dilate a single voxel at each of the eight corners of a leaf node. for (int i = 0; i < 8; ++i) { tree->clear(); openvdb::Coord xyz( i & 1 ? leafDim - 1 : 0, i & 2 ? leafDim - 1 : 0, i & 4 ? leafDim - 1 : 0); tree->setValue(xyz, 1.0); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(7, int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); } } { // Set three active voxels and dilate and erode tree->clear(); tree->setValue(Coord(0), 1.0); tree->setValue(Coord( 1, 0, 0), 1.0); tree->setValue(Coord(-1, 0, 0), 1.0); CPPUNIT_ASSERT_EQUAL(3, int(tree->activeVoxelCount())); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(17, int(tree->activeVoxelCount())); openvdb::tools::erodeVoxels(*tree); CPPUNIT_ASSERT_EQUAL(3, int(tree->activeVoxelCount())); } { struct Info { void test(TreeType::Ptr tree) { CPPUNIT_ASSERT_EQUAL(activeVoxelCount, int(tree->activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(leafCount, int(tree->leafCount())); CPPUNIT_ASSERT_EQUAL(nonLeafCount, int(tree->nonLeafCount())); } int activeVoxelCount, leafCount, nonLeafCount; }; Info iterInfo[12] = { { 0, 0, 1 },//an empty tree only contains a root node { 1, 1, 3 }, { 7, 1, 3 }, { 25, 1, 3 }, { 63, 1, 3 }, { 129, 4, 3 }, { 231, 7, 9 }, { 377, 7, 9 }, { 575, 7, 9 }, { 833, 10, 9 }, { 1159, 16, 9 }, { 1561, 19, 15 }, }; // Perform repeated dilations, starting with a single voxel. tree->clear(); iterInfo[0].test(tree); tree->setValue(Coord(leafDim >> 1), 1.0); iterInfo[1].test(tree); for (int i = 2; i < 12; ++i) { openvdb::tools::dilateVoxels(*tree); iterInfo[i].test(tree); } for (int i = 10; i >= 0; --i) { openvdb::tools::erodeVoxels(*tree); iterInfo[i].test(tree); } // Now try it using the resursive calls for (int i = 2; i < 12; ++i) { tree->clear(); tree->setValue(Coord(leafDim >> 1), 1.0); openvdb::tools::dilateVoxels(*tree, i-1); iterInfo[i].test(tree); } for (int i = 10; i >= 0; --i) { tree->clear(); tree->setValue(Coord(leafDim >> 1), 1.0); openvdb::tools::dilateVoxels(*tree, 10); openvdb::tools::erodeVoxels(*tree, 11-i); iterInfo[i].test(tree); } } {// erode a narrow band of a sphere typedef openvdb::Grid GridType; GridType grid(tree->background()); unittest_util::makeSphere(/*dim=*/openvdb::Coord(64, 64, 64), /*center=*/openvdb::Vec3f(0, 0, 0), /*radius=*/20, grid, /*dx=*/1.0f, unittest_util::SPHERE_DENSE_NARROW_BAND); const openvdb::Index64 count = grid.tree().activeVoxelCount(); openvdb::tools::erodeVoxels(grid.tree()); CPPUNIT_ASSERT(grid.tree().activeVoxelCount() < count); } {// erode a fog volume of a sphere typedef openvdb::Grid GridType; GridType grid(tree->background()); unittest_util::makeSphere(/*dim=*/openvdb::Coord(64, 64, 64), /*center=*/openvdb::Vec3f(0, 0, 0), /*radius=*/20, grid, /*dx=*/1.0f, unittest_util::SPHERE_DENSE_NARROW_BAND); openvdb::tools::sdfToFogVolume(grid); const openvdb::Index64 count = grid.tree().activeVoxelCount(); openvdb::tools::erodeVoxels(grid.tree()); CPPUNIT_ASSERT(grid.tree().activeVoxelCount() < count); } } void TestTools::testActivate() { using namespace openvdb; const Vec3s background(0.0, -1.0, 1.0), foreground(42.0); Vec3STree tree(background); const CoordBBox bbox1(Coord(-200), Coord(-181)), bbox2(Coord(51), Coord(373)); // Set some non-background active voxels. tree.fill(bbox1, Vec3s(0.0), /*active=*/true); // Mark some background voxels as active. tree.fill(bbox2, background, /*active=*/true); CPPUNIT_ASSERT_EQUAL(bbox2.volume() + bbox1.volume(), tree.activeVoxelCount()); // Deactivate all voxels with the background value. tools::deactivate(tree, background, /*tolerance=*/Vec3s(1.0e-6)); // Verify that there are no longer any active voxels with the background value. CPPUNIT_ASSERT_EQUAL(bbox1.volume(), tree.activeVoxelCount()); // Set some voxels to the foreground value but leave them inactive. tree.fill(bbox2, foreground, /*active=*/false); // Verify that there are no active voxels with the background value. CPPUNIT_ASSERT_EQUAL(bbox1.volume(), tree.activeVoxelCount()); // Activate all voxels with the foreground value. tools::activate(tree, foreground); // Verify that the expected number of voxels are active. CPPUNIT_ASSERT_EQUAL(bbox1.volume() + bbox2.volume(), tree.activeVoxelCount()); } void TestTools::testFilter() { openvdb::FloatGrid::Ptr referenceGrid = openvdb::FloatGrid::create(/*background=*/5.0); const openvdb::Coord dim(40); const openvdb::Vec3f center(25.0f, 20.0f, 20.0f); const float radius = 10.0f; unittest_util::makeSphere( dim, center, radius, *referenceGrid, unittest_util::SPHERE_DENSE); const openvdb::FloatTree& sphere = referenceGrid->tree(); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(sphere.activeVoxelCount())); openvdb::Coord xyz; {// test Filter::offsetFilter openvdb::FloatGrid::Ptr grid = referenceGrid->deepCopy(); openvdb::FloatTree& tree = grid->tree(); openvdb::tools::Filter filter(*grid); const float offset = 2.34f; filter.setGrainSize(0);//i.e. disable threading filter.offset(offset); for (int x=0; x0.0001f) std::cerr << " failed at " << xyz << std::endl; CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0f, delta, /*tolerance=*/0.0001); } } } filter.setGrainSize(1);//i.e. enable threading filter.offset(-offset);//default is multi-threaded for (int x=0; x0.0001f) std::cerr << " failed at " << xyz << std::endl; CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0f, delta, /*tolerance=*/0.0001); } } } //std::cerr << "Successfully completed TestTools::testFilter offset test" << std::endl; } {// test Filter::median openvdb::FloatGrid::Ptr filteredGrid = referenceGrid->deepCopy(); openvdb::FloatTree& filteredTree = filteredGrid->tree(); const int width = 2; openvdb::math::DenseStencil stencil(*referenceGrid, width); openvdb::tools::Filter filter(*filteredGrid); filter.median(width, /*interations=*/1); std::vector tmp; for (int x=0; xdeepCopy(); openvdb::FloatTree& filteredTree = filteredGrid->tree(); const int width = 2; openvdb::math::DenseStencil stencil(*referenceGrid, width); openvdb::tools::Filter filter(*filteredGrid); filter.mean(width, /*interations=*/1); for (int x=0; x(radius, center, voxelSize, width); /// Also test ultra slow makeSphere in unittest/util.h openvdb::FloatGrid::Ptr grid2 = openvdb::createLevelSet(voxelSize, width); unittest_util::makeSphere( openvdb::Coord(dim), center, radius, *grid2, unittest_util::SPHERE_SPARSE_NARROW_BAND); const float outside = grid1->background(), inside = -outside; for (int i=0; itree().getValue(openvdb::Coord(i,j,k)); const float val2 = grid2->tree().getValue(openvdb::Coord(i,j,k)); if (dist > outside) { CPPUNIT_ASSERT_DOUBLES_EQUAL( outside, val1, 0.0001); CPPUNIT_ASSERT_DOUBLES_EQUAL( outside, val2, 0.0001); } else if (dist < inside) { CPPUNIT_ASSERT_DOUBLES_EQUAL( inside, val1, 0.0001); CPPUNIT_ASSERT_DOUBLES_EQUAL( inside, val2, 0.0001); } else { CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val1, 0.0001); CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val2, 0.0001); } } } } CPPUNIT_ASSERT_EQUAL(grid1->activeVoxelCount(), grid2->activeVoxelCount()); } void TestTools::testLevelSetAdvect() { // Uncomment sections below to run this (time-consuming) test /* const int dim = 64;//256 const openvdb::Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f, voxelSize = 1.0f/(dim-1); typedef openvdb::FloatGrid GridT; typedef openvdb::Vec3fGrid VectT; */ /* {//test tracker GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); typedef openvdb::tools::LevelSetTracker TrackerT; TrackerT tracker(*grid); tracker.setSpatialScheme(openvdb::math::HJWENO5_BIAS); tracker.setTemporalScheme(openvdb::math::TVD_RK1); FrameWriter fw(dim, grid); fw("Tracker",0, 0); //for (float t = 0, dt = 0.005f; !grid->empty() && t < 3.0f; t += dt) { // fw("Enright", t + dt, advect.advect(t, t + dt)); //} for (float t = 0, dt = 0.5f; !grid->empty() && t < 1.0f; t += dt) { tracker.track(); fw("Tracker", 0, 0); } } */ /* {//test EnrightField GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); typedef openvdb::tools::EnrightField FieldT; FieldT field; typedef openvdb::tools::LevelSetAdvection AdvectT; AdvectT advect(*grid, field); advect.setSpatialScheme(openvdb::math::HJWENO5_BIAS); advect.setTemporalScheme(openvdb::math::TVD_RK2); advect.setTrackerSpatialScheme(openvdb::math::HJWENO5_BIAS); advect.setTrackerTemporalScheme(openvdb::math::TVD_RK1); FrameWriter fw(dim, grid); fw("Enright",0, 0); //for (float t = 0, dt = 0.005f; !grid->empty() && t < 3.0f; t += dt) { // fw("Enright", t + dt, advect.advect(t, t + dt)); //} for (float t = 0, dt = 0.5f; !grid->empty() && t < 1.0f; t += dt) { fw("Enright", t + dt, advect.advect(t, t + dt)); } } */ /* {// test DiscreteGrid - Aligned GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); VectT vect(openvdb::Vec3f(1,0,0)); typedef openvdb::tools::DiscreteField FieldT; FieldT field(vect); typedef openvdb::tools::LevelSetAdvection AdvectT; AdvectT advect(*grid, field); advect.setSpatialScheme(openvdb::math::HJWENO5_BIAS); advect.setTemporalScheme(openvdb::math::TVD_RK2); FrameWriter fw(dim, grid); fw("Aligned",0, 0); //for (float t = 0, dt = 0.005f; !grid->empty() && t < 3.0f; t += dt) { // fw("Aligned", t + dt, advect.advect(t, t + dt)); //} for (float t = 0, dt = 0.5f; !grid->empty() && t < 1.0f; t += dt) { fw("Aligned", t + dt, advect.advect(t, t + dt)); } } */ /* {// test DiscreteGrid - Transformed GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); VectT vect(openvdb::Vec3f(0,0,0)); VectT::Accessor acc = vect.getAccessor(); for (openvdb::Coord ijk(0); ijk[0] FieldT; FieldT field(vect); typedef openvdb::tools::LevelSetAdvection AdvectT; AdvectT advect(*grid, field); advect.setSpatialScheme(openvdb::math::HJWENO5_BIAS); advect.setTemporalScheme(openvdb::math::TVD_RK2); FrameWriter fw(dim, grid); fw("Xformed",0, 0); //for (float t = 0, dt = 0.005f; !grid->empty() && t < 3.0f; t += dt) { // fw("Xformed", t + dt, advect.advect(t, t + dt)); //} for (float t = 0, dt = 0.5f; !grid->empty() && t < 1.0f; t += dt) { fw("Xformed", t + dt, advect.advect(t, t + dt)); } } */ }//testLevelSetAdvect //////////////////////////////////////// void TestTools::testLevelSetMorph() { typedef openvdb::FloatGrid GridT; {//test morphing overlapping but aligned spheres const int dim = 64; const openvdb::Vec3f C1(0.35f, 0.35f, 0.35f), C2(0.4f, 0.4f, 0.4f); const float radius = 0.15f, voxelSize = 1.0f/(dim-1); GridT::Ptr source = openvdb::tools::createLevelSetSphere(radius, C1, voxelSize); GridT::Ptr target = openvdb::tools::createLevelSetSphere(radius, C2, voxelSize); typedef openvdb::tools::LevelSetMorphing MorphT; MorphT morph(*source, *target); morph.setSpatialScheme(openvdb::math::HJWENO5_BIAS); morph.setTemporalScheme(openvdb::math::TVD_RK3); morph.setTrackerSpatialScheme(openvdb::math::HJWENO5_BIAS); morph.setTrackerTemporalScheme(openvdb::math::TVD_RK2); const std::string name("SphereToSphere"); FrameWriter fw(dim, source); //fw(name, 0.0f, 0); //unittest_util::CpuTimer timer; const float tMax = 0.05f/voxelSize; //std::cerr << "\nt-max = " << tMax << std::endl; //timer.start("\nMorphing"); for (float t = 0, dt = 0.1f; !source->empty() && t < tMax; t += dt) { morph.advect(t, t + dt); //fw(name, t + dt, morph.advect(t, t + dt)); } // timer.stop(); const float invDx = 1.0f/voxelSize; openvdb::math::Stats s; for (GridT::ValueOnCIter it = source->tree().cbeginValueOn(); it; ++it) { s.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); } for (GridT::ValueOnCIter it = target->tree().cbeginValueOn(); it; ++it) { s.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); } //s.print("Morph"); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.min(), 0.50); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.max(), 0.50); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.avg(), 0.02); /* openvdb::math::Histogram h(s, 30); for (GridT::ValueOnCIter it = source->tree().cbeginValueOn(); it; ++it) { h.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); } for (GridT::ValueOnCIter it = target->tree().cbeginValueOn(); it; ++it) { h.add( invDx*(*it - target->tree().getValue(it.getCoord())) ); } h.print("Morph"); */ } /* // Uncomment sections below to run this (very time-consuming) test {//test morphing between the bunny and the buddha models loaded from files unittest_util::CpuTimer timer; openvdb::initialize();//required whenever I/O of OpenVDB files is performed! openvdb::io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/bunny.vdb"); sourceFile.open(); GridT::Ptr source = openvdb::gridPtrCast(sourceFile.getGrids()->at(0)); openvdb::io::File targetFile("/usr/pic1/Data/OpenVDB/LevelSetModels/buddha.vdb"); targetFile.open(); GridT::Ptr target = openvdb::gridPtrCast(targetFile.getGrids()->at(0)); typedef openvdb::tools::LevelSetMorphing MorphT; MorphT morph(*source, *target); morph.setSpatialScheme(openvdb::math::FIRST_BIAS); //morph.setSpatialScheme(openvdb::math::HJWENO5_BIAS); morph.setTemporalScheme(openvdb::math::TVD_RK2); morph.setTrackerSpatialScheme(openvdb::math::FIRST_BIAS); //morph.setTrackerSpatialScheme(openvdb::math::HJWENO5_BIAS); morph.setTrackerTemporalScheme(openvdb::math::TVD_RK2); const std::string name("Bunny2Buddha"); FrameWriter fw(1, source); fw(name, 0.0f, 0); for (float t = 0, dt = 1.0f; !source->empty() && t < 300.0f; t += dt) { timer.start("Morphing"); const int cflCount = morph.advect(t, t + dt); timer.stop(); fw(name, t + dt, cflCount); } } */ /* // Uncomment sections below to run this (very time-consuming) test {//test morphing between the dragon and the teapot models loaded from files unittest_util::CpuTimer timer; openvdb::initialize();//required whenever I/O of OpenVDB files is performed! openvdb::io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/dragon.vdb"); sourceFile.open(); GridT::Ptr source = openvdb::gridPtrCast(sourceFile.getGrids()->at(0)); openvdb::io::File targetFile("/usr/pic1/Data/OpenVDB/LevelSetModels/utahteapot.vdb"); targetFile.open(); GridT::Ptr target = openvdb::gridPtrCast(targetFile.getGrids()->at(0)); typedef openvdb::tools::LevelSetMorphing MorphT; MorphT morph(*source, *target); morph.setSpatialScheme(openvdb::math::FIRST_BIAS); //morph.setSpatialScheme(openvdb::math::HJWENO5_BIAS); morph.setTemporalScheme(openvdb::math::TVD_RK2); //morph.setTrackerSpatialScheme(openvdb::math::HJWENO5_BIAS); morph.setTrackerSpatialScheme(openvdb::math::FIRST_BIAS); morph.setTrackerTemporalScheme(openvdb::math::TVD_RK2); const std::string name("Dragon2Teapot"); FrameWriter fw(5, source); fw(name, 0.0f, 0); for (float t = 0, dt = 0.4f; !source->empty() && t < 110.0f; t += dt) { timer.start("Morphing"); const int cflCount = morph.advect(t, t + dt); timer.stop(); fw(name, t + dt, cflCount); } } */ }//testLevelSetMorph //////////////////////////////////////// void TestTools::testLevelSetMeasure() { const double percentage = 0.1/100.0;//i.e. 0.1% typedef openvdb::FloatGrid GridT; const int dim = 256; openvdb::Real a, v, c, area, volume, curv; // First sphere openvdb::Vec3f C(0.35f, 0.35f, 0.35f); openvdb::Real r = 0.15, voxelSize = 1.0/(dim-1); const openvdb::Real Pi = boost::math::constants::pi(); GridT::Ptr sphere = openvdb::tools::createLevelSetSphere(r, C, voxelSize); typedef openvdb::tools::LevelSetMeasure MeasureT; MeasureT m(*sphere); /// Test area and volume of sphere in world units m.measure(a, v); area = 4*Pi*r*r; volume = 4.0/3.0*Pi*r*r*r; //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; //std::cerr << "\nVolume of sphere = " << volume << " " << v << std::endl; // Test accuracy of computed measures to within 0.1% of the exact measure. CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); // Test all measures of sphere in world units m.measure(a, v, c); area = 4*Pi*r*r; volume = 4.0/3.0*Pi*r*r*r; curv = 1.0/r; //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; // Test accuracy of computed measures to within 0.1% of the exact measure. CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); // Test all measures of sphere in index units m.measure(a, v, c, false); r /= voxelSize; area = 4*Pi*r*r; volume = 4.0/3.0*Pi*r*r*r; curv = 1.0/r; //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; // Test accuracy of computed measures to within 0.1% of the exact measure. CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); // Second sphere C = openvdb::Vec3f(5.4f, 6.4f, 8.4f); r = 0.57f; sphere = openvdb::tools::createLevelSetSphere(r, C, voxelSize); m.reinit(*sphere); // Test all measures of sphere in world units m.measure(a, v, c); area = 4*Pi*r*r; volume = 4.0/3.0*Pi*r*r*r; curv = 1.0/r; //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; // Test accuracy of computed measures to within 0.1% of the exact measure. CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); CPPUNIT_ASSERT_DOUBLES_EQUAL(area, openvdb::tools::levelSetArea(*sphere), percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume,openvdb::tools::levelSetVolume(*sphere),percentage*volume); // Test all measures of sphere in index units m.measure(a, v, c, false); r /= voxelSize; area = 4*Pi*r*r; volume = 4.0/3.0*Pi*r*r*r; curv = 1.0/r; //std::cerr << "\nArea of sphere = " << area << " " << a << std::endl; //std::cerr << "Volume of sphere = " << volume << " " << v << std::endl; //std::cerr << "Avg mean curvature of sphere = " << curv << " " << c << std::endl; // Test accuracy of computed measures to within 0.1% of the exact measure. CPPUNIT_ASSERT_DOUBLES_EQUAL(area, a, percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume, v, percentage*volume); CPPUNIT_ASSERT_DOUBLES_EQUAL(curv, c, percentage*curv); CPPUNIT_ASSERT_DOUBLES_EQUAL(area, openvdb::tools::levelSetArea(*sphere,false), percentage*area); CPPUNIT_ASSERT_DOUBLES_EQUAL(volume,openvdb::tools::levelSetVolume(*sphere,false), percentage*volume); // Read level set from file /* unittest_util::CpuTimer timer; openvdb::initialize();//required whenever I/O of OpenVDB files is performed! openvdb::io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/venusstatue.vdb"); sourceFile.open(); GridT::Ptr model = openvdb::gridPtrCast(sourceFile.getGrids()->at(0)); m.reinit(*model); //m.setGrainSize(1); timer.start("\nParallel measure of area and volume"); m.measure(a, v, false); timer.stop(); std::cerr << "Model: area = " << a << ", volume = " << v << std::endl; timer.start("\nParallel measure of area, volume and curvature"); m.measure(a, v, c, false); timer.stop(); std::cerr << "Model: area = " << a << ", volume = " << v << ", average curvature = " << c << std::endl; m.setGrainSize(0); timer.start("\nSerial measure of area and volume"); m.measure(a, v, false); timer.stop(); std::cerr << "Model: area = " << a << ", volume = " << v << std::endl; timer.start("\nSerial measure of area, volume and curvature"); m.measure(a, v, c, false); timer.stop(); std::cerr << "Model: area = " << a << ", volume = " << v << ", average curvature = " << c << std::endl; */ }//testLevelSetMeasure void TestTools::testMagnitude() { openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(/*background=*/5.0); openvdb::FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere(dim,center,radius,*grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); openvdb::VectorGrid::Ptr gradGrid = openvdb::tools::gradient(*grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(gradGrid->activeVoxelCount())); openvdb::FloatGrid::Ptr mag = openvdb::tools::magnitude(*gradGrid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(mag->activeVoxelCount())); openvdb::FloatGrid::ConstAccessor accessor = mag->getConstAccessor(); openvdb::Coord xyz(35,30,30); float v = accessor.getValue(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v, 0.01); xyz.reset(35,10,40); v = accessor.getValue(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v, 0.01); } void TestTools::testMaskedMagnitude() { openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(/*background=*/5.0); openvdb::FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere(dim,center,radius,*grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); openvdb::VectorGrid::Ptr gradGrid = openvdb::tools::gradient(*grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(gradGrid->activeVoxelCount())); // create a masking grid const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); openvdb::BoolGrid::Ptr maskGrid = openvdb::BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); // compute the magnitude in masked region openvdb::FloatGrid::Ptr mag = openvdb::tools::magnitude(*gradGrid, *maskGrid); openvdb::FloatGrid::ConstAccessor accessor = mag->getConstAccessor(); // test in the masked region openvdb::Coord xyz(35,30,30); CPPUNIT_ASSERT(maskbbox.isInside(xyz)); float v = accessor.getValue(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v, 0.01); // test outside the masked region xyz.reset(35,10,40); CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); v = accessor.getValue(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, v, 0.01); } void TestTools::testNormalize() { openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(5.0); openvdb::FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=10.0f; unittest_util::makeSphere( dim,center,radius,*grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); openvdb::Coord xyz(10, 20, 30); openvdb::VectorGrid::Ptr grad = openvdb::tools::gradient(*grid); typedef openvdb::VectorGrid::ValueType Vec3Type; typedef openvdb::VectorGrid::ValueOnIter ValueIter; struct Local { static inline Vec3Type op(const Vec3Type &x) { return x * 2.0f; } static inline void visit(const ValueIter& it) { it.setValue(op(*it)); } }; openvdb::tools::foreach(grad->beginValueOn(), Local::visit, true); openvdb::VectorGrid::ConstAccessor accessor = grad->getConstAccessor(); xyz = openvdb::Coord(35,10,40); Vec3Type v = accessor.getValue(xyz); //std::cerr << "\nPassed testNormalize(" << xyz << ")=" << v.length() << std::endl; CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,v.length(),0.001); openvdb::VectorGrid::Ptr norm = openvdb::tools::normalize(*grad); accessor = norm->getConstAccessor(); v = accessor.getValue(xyz); //std::cerr << "\nPassed testNormalize(" << xyz << ")=" << v.length() << std::endl; CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v.length(), 0.0001); } void TestTools::testMaskedNormalize() { openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(5.0); openvdb::FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=10.0f; unittest_util::makeSphere( dim,center,radius,*grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); openvdb::Coord xyz(10, 20, 30); openvdb::VectorGrid::Ptr grad = openvdb::tools::gradient(*grid); typedef openvdb::VectorGrid::ValueType Vec3Type; typedef openvdb::VectorGrid::ValueOnIter ValueIter; struct Local { static inline Vec3Type op(const Vec3Type &x) { return x * 2.0f; } static inline void visit(const ValueIter& it) { it.setValue(op(*it)); } }; openvdb::tools::foreach(grad->beginValueOn(), Local::visit, true); openvdb::VectorGrid::ConstAccessor accessor = grad->getConstAccessor(); xyz = openvdb::Coord(35,10,40); Vec3Type v = accessor.getValue(xyz); // create a masking grid const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); openvdb::BoolGrid::Ptr maskGrid = openvdb::BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0,v.length(),0.001); // compute the normalized valued in the masked region openvdb::VectorGrid::Ptr norm = openvdb::tools::normalize(*grad, *maskGrid); accessor = norm->getConstAccessor(); { // outside the masked region CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); v = accessor.getValue(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, v.length(), 0.0001); } { // inside the masked region xyz.reset(35, 30, 30); v = accessor.getValue(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, v.length(), 0.0001); } } //////////////////////////////////////// void TestTools::testPointAdvect() { { // Setup: Advect a number of points in a uniform velocity field (1,1,1). // over a time dt=1 with each of the 4 different advection schemes. // Points initialized at latice points. // // Uses: FloatTree (velocity), collocated sampling, advection // // Expected: All advection schemes will have the same result. Each point will // be advanced to a new latice point. The i-th point will be at (i+1,i+1,i+1) // const size_t numPoints = 2000000; // create a uniform velocity field in SINGLE PRECISION const openvdb::Vec3f velocityBackground(1, 1, 1); openvdb::Vec3fGrid::Ptr velocityGrid = openvdb::Vec3fGrid::create(velocityBackground); // using all the default template arguments openvdb::tools::PointAdvect<> advectionTool(*velocityGrid); // create points std::vector pointList(numPoints); /// larger than the tbb chunk size for (size_t i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3f(i, i, i); for (unsigned int order = 1; order < 5; ++order) { // check all four time integrations schemes // construct an advection tool. By default the number of cpt iterations is zero advectionTool.setIntegrationOrder(order); advectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); // check locations for (size_t i = 0; i < numPoints; i++) { openvdb::Vec3f expected(i + 1, i + 1 , i + 1); CPPUNIT_ASSERT_EQUAL(expected, pointList[i]); } // reset values for (size_t i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3f(i, i, i); } } { // Setup: Advect a number of points in a uniform velocity field (1,1,1). // over a time dt=1 with each of the 4 different advection schemes. // And then project the point location onto the x-y plane // Points initialized at latice points. // // Uses: DoubleTree (velocity), staggered sampling, constraint projection, advection // // Expected: All advection schemes will have the same result. Modes 1-4: Each point will // be advanced to a new latice point and projected to x-y plane. // The i-th point will be at (i+1,i+1,0). For mode 0 (no advection), i-th point // will be found at (i, i, 0) const size_t numPoints = 4; // create a uniform velocity field in DOUBLE PRECISION const openvdb::Vec3d velocityBackground(1, 1, 1); openvdb::Vec3dGrid::Ptr velocityGrid = openvdb::Vec3dGrid::create(velocityBackground); // create a simple (horizontal) constraint field valid for a // (-10,10)x(-10,10)x(-10,10) const openvdb::Vec3d cptBackground(0, 0, 0); openvdb::Vec3dGrid::Ptr cptGrid = openvdb::Vec3dGrid::create(cptBackground); openvdb::Vec3dTree& cptTree = cptGrid->tree(); // create points std::vector pointList(numPoints); for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); // Initialize the constraint field in a [-10,10]x[-10,10]x[-10,10] box // this test will only work if the points remain in the box openvdb::Coord ijk(0, 0, 0); for (int i = -10; i < 11; i++) { ijk.setX(i); for (int j = -10; j < 11; j++) { ijk.setY(j); for (int k = -10; k < 11; k++) { ijk.setZ(k); // set the value as projection onto the x-y plane cptTree.setValue(ijk, openvdb::Vec3d(i, j, 0)); } } } // construct an advection tool. By default the number of cpt iterations is zero openvdb::tools::ConstrainedPointAdvect, true> constrainedAdvectionTool(*velocityGrid, *cptGrid, 0); constrainedAdvectionTool.setThreaded(false); // change the number of constraint interation from default 0 to 5 constrainedAdvectionTool.setConstraintIterations(5); // test the pure-projection mode (order = 0) constrainedAdvectionTool.setIntegrationOrder(0); // change the number of constraint interation (from 0 to 5) constrainedAdvectionTool.setConstraintIterations(5); constrainedAdvectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); // check locations for (unsigned int i = 0; i < numPoints; i++) { openvdb::Vec3d expected(i, i, 0); // location (i, i, i) projected on to x-y plane for (int n=0; n<3; ++n) { CPPUNIT_ASSERT_DOUBLES_EQUAL(expected[n], pointList[i][n], /*tolerance=*/1e-6); } } // reset values for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); // test all four time integrations schemes for (unsigned int order = 1; order < 5; ++order) { constrainedAdvectionTool.setIntegrationOrder(order); constrainedAdvectionTool.advect(pointList, /*dt=*/1.0, /*iterations=*/1); // check locations for (unsigned int i = 0; i < numPoints; i++) { openvdb::Vec3d expected(i+1, i+1, 0); // location (i,i,i) projected onto x-y plane for (int n=0; n<3; ++n) { CPPUNIT_ASSERT_DOUBLES_EQUAL(expected[n], pointList[i][n], /*tolerance=*/1e-6); } } // reset values for (unsigned int i = 0; i < numPoints; i++) pointList[i] = openvdb::Vec3d(i, i, i); } } } //////////////////////////////////////// struct PointList { struct Point { float x,y,z; }; std::vector list; void add(const openvdb::Vec3R &p) { Point q={p[0],p[1],p[2]}; list.push_back(q); } }; void TestTools::testPointScatter() { typedef openvdb::FloatGrid GridType; const openvdb::Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius = 20.0; typedef boost::mt11213b RandGen; RandGen mtRand; GridType::Ptr grid = GridType::create(/*background=*/2.0); unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE_NARROW_BAND); { const int pointCount = 1000; PointList points; openvdb::tools::UniformPointScatter scatter(points, pointCount, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT_EQUAL( pointCount, scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( pointCount, int(points.list.size()) ); } { const float density = 1.0f;//per volume = per voxel since voxel size = 1 PointList points; openvdb::tools::UniformPointScatter scatter(points, density, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT_EQUAL( int(scatter.getVoxelCount()), scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( int(scatter.getVoxelCount()), int(points.list.size()) ); } { const float density = 1.0f;//per volume = per voxel since voxel size = 1 PointList points; openvdb::tools::NonUniformPointScatter scatter(points, density, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT( int(scatter.getVoxelCount()) < scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( scatter.getPointCount(), int(points.list.size()) ); } } //////////////////////////////////////// void TestTools::testFloatApply() { typedef openvdb::FloatTree::ValueOnIter ValueIter; struct Local { static inline float op(float x) { return x * 2.0; } static inline void visit(const ValueIter& it) { it.setValue(op(*it)); } }; const float background = 1.0; openvdb::FloatTree tree(background); const int MIN = -1000, MAX = 1000, STEP = 50; openvdb::Coord xyz; for (int z = MIN; z < MAX; z += STEP) { xyz.setZ(z); for (int y = MIN; y < MAX; y += STEP) { xyz.setY(y); for (int x = MIN; x < MAX; x += STEP) { xyz.setX(x); tree.setValue(xyz, x + y + z); } } } /// @todo set some tile values openvdb::tools::foreach(tree.begin(), Local::visit, /*threaded=*/true); float expected = Local::op(background); //CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, tree.background(), /*tolerance=*/0.0); //expected = Local::op(-background); //CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, -tree.background(), /*tolerance=*/0.0); for (openvdb::FloatTree::ValueOnCIter it = tree.cbeginValueOn(); it; ++it) { xyz = it.getCoord(); expected = Local::op(xyz[0] + xyz[1] + xyz[2]); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, it.getValue(), /*tolerance=*/0.0); } } //////////////////////////////////////// namespace { template struct MatMul { openvdb::math::Mat3s M; MatMul(const openvdb::math::Mat3s& M): M(M) {} openvdb::Vec3s xform(const openvdb::Vec3s& v) const { return M.transform(v); } void operator()(const IterT& it) const { it.setValue(xform(*it)); } }; } void TestTools::testVectorApply() { typedef openvdb::VectorTree::ValueOnIter ValueIter; const openvdb::Vec3s background(1, 1, 1); openvdb::VectorTree tree(background); const int MIN = -1000, MAX = 1000, STEP = 80; openvdb::Coord xyz; for (int z = MIN; z < MAX; z += STEP) { xyz.setZ(z); for (int y = MIN; y < MAX; y += STEP) { xyz.setY(y); for (int x = MIN; x < MAX; x += STEP) { xyz.setX(x); tree.setValue(xyz, openvdb::Vec3s(x, y, z)); } } } /// @todo set some tile values MatMul op(openvdb::math::Mat3s(1, 2, 3, -1, -2, -3, 3, 2, 1)); openvdb::tools::foreach(tree.beginValueOn(), op, /*threaded=*/true); openvdb::Vec3s expected; for (openvdb::VectorTree::ValueOnCIter it = tree.cbeginValueOn(); it; ++it) { xyz = it.getCoord(); expected = op.xform(openvdb::Vec3s(xyz[0], xyz[1], xyz[2])); CPPUNIT_ASSERT_EQUAL(expected, it.getValue()); } } //////////////////////////////////////// namespace { struct AccumSum { int64_t sum; int joins; AccumSum(): sum(0), joins(0) {} void operator()(const openvdb::Int32Tree::ValueOnCIter& it) { if (it.isVoxelValue()) sum += *it; else sum += (*it) * it.getVoxelCount(); } void join(AccumSum& other) { sum += other.sum; joins += 1 + other.joins; } }; struct AccumLeafVoxelCount { typedef openvdb::tree::LeafManager::LeafRange LeafRange; openvdb::Index64 count; AccumLeafVoxelCount(): count(0) {} void operator()(const LeafRange::Iterator& it) { count += it->onVoxelCount(); } void join(AccumLeafVoxelCount& other) { count += other.count; } }; } void TestTools::testAccumulate() { using namespace openvdb; const int value = 2; Int32Tree tree(/*background=*/0); tree.fill(CoordBBox::createCube(Coord(0), 198), value, /*active=*/true); const int64_t expected = tree.activeVoxelCount() * value; { AccumSum op; tools::accumulate(tree.cbeginValueOn(), op, /*threaded=*/false); CPPUNIT_ASSERT_EQUAL(expected, op.sum); CPPUNIT_ASSERT_EQUAL(0, op.joins); } { AccumSum op; tools::accumulate(tree.cbeginValueOn(), op, /*threaded=*/true); CPPUNIT_ASSERT_EQUAL(expected, op.sum); } { AccumLeafVoxelCount op; tree::LeafManager mgr(tree); tools::accumulate(mgr.leafRange().begin(), op, /*threaded=*/true); CPPUNIT_ASSERT_EQUAL(tree.activeLeafVoxelCount(), op.count); } } //////////////////////////////////////// namespace { template struct FloatToVec { typedef typename InIterT::ValueT ValueT; typedef typename openvdb::tree::ValueAccessor Accessor; // Transform a scalar value into a vector value. static openvdb::Vec3s toVec(const ValueT& v) { return openvdb::Vec3s(v, v*2, v*3); } FloatToVec() { numTiles = 0; } void operator()(const InIterT& it, Accessor& acc) { if (it.isVoxelValue()) { // set a single voxel acc.setValue(it.getCoord(), toVec(*it)); } else { // fill an entire tile numTiles.fetch_and_increment(); openvdb::CoordBBox bbox; it.getBoundingBox(bbox); acc.tree().fill(bbox, toVec(*it)); } } tbb::atomic numTiles; }; } void TestTools::testTransformValues() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3s; typedef openvdb::tree::Tree4::Type Tree323f; typedef openvdb::tree::Tree4::Type Tree323v; const float background = 1.0; Tree323f ftree(background); const int MIN = -1000, MAX = 1000, STEP = 80; Coord xyz; for (int z = MIN; z < MAX; z += STEP) { xyz.setZ(z); for (int y = MIN; y < MAX; y += STEP) { xyz.setY(y); for (int x = MIN; x < MAX; x += STEP) { xyz.setX(x); ftree.setValue(xyz, x + y + z); } } } // Set some tile values. ftree.fill(CoordBBox(Coord(1024), Coord(1024 + 8 - 1)), 3 * 1024); // level-1 tile ftree.fill(CoordBBox(Coord(2048), Coord(2048 + 32 - 1)), 3 * 2048); // level-2 tile ftree.fill(CoordBBox(Coord(3072), Coord(3072 + 256 - 1)), 3 * 3072); // level-3 tile for (int shareOp = 0; shareOp <= 1; ++shareOp) { FloatToVec op; Tree323v vtree; openvdb::tools::transformValues(ftree.cbeginValueOn(), vtree, op, /*threaded=*/true, shareOp); // The tile count is accurate only if the functor is shared. Otherwise, // it is initialized to zero in the main thread and never changed. CPPUNIT_ASSERT_EQUAL(shareOp ? 3 : 0, int(op.numTiles)); Vec3s expected; for (Tree323v::ValueOnCIter it = vtree.cbeginValueOn(); it; ++it) { xyz = it.getCoord(); expected = op.toVec(xyz[0] + xyz[1] + xyz[2]); CPPUNIT_ASSERT_EQUAL(expected, it.getValue()); } // Check values inside the tiles. CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 1024), vtree.getValue(Coord(1024 + 4))); CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 2048), vtree.getValue(Coord(2048 + 16))); CPPUNIT_ASSERT_EQUAL(op.toVec(3 * 3072), vtree.getValue(Coord(3072 + 128))); } } //////////////////////////////////////// void TestTools::testUtil() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3s; typedef openvdb::tree::Tree4::Type CharTree; // Test boolean operators CharTree treeA(false), treeB(false); treeA.fill(CoordBBox(Coord(-10), Coord(10)), true); treeA.voxelizeActiveTiles(); treeB.fill(CoordBBox(Coord(-10), Coord(10)), true); treeB.voxelizeActiveTiles(); const size_t voxelCountA = treeA.activeVoxelCount(); const size_t voxelCountB = treeB.activeVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCountA, voxelCountB); CharTree::Ptr tree = openvdb::util::leafTopologyDifference(treeA, treeB); CPPUNIT_ASSERT(tree->activeVoxelCount() == 0); tree = openvdb::util::leafTopologyIntersection(treeA, treeB); CPPUNIT_ASSERT(tree->activeVoxelCount() == voxelCountA); treeA.fill(CoordBBox(Coord(-10), Coord(22)), true); treeA.voxelizeActiveTiles(); const size_t voxelCount = treeA.activeVoxelCount(); tree = openvdb::util::leafTopologyDifference(treeA, treeB); CPPUNIT_ASSERT(tree->activeVoxelCount() == (voxelCount - voxelCountA)); tree = openvdb::util::leafTopologyIntersection(treeA, treeB); CPPUNIT_ASSERT(tree->activeVoxelCount() == voxelCountA); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestCoord.cc0000644000000000000000000002160712252453157015321 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include // for tbb::split class TestCoord: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestCoord); CPPUNIT_TEST(testCoord); CPPUNIT_TEST(testConversion); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testCoordBBox); CPPUNIT_TEST_SUITE_END(); void testCoord(); void testConversion(); void testIO(); void testCoordBBox(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestCoord); void TestCoord::testCoord() { using openvdb::Coord; Coord xyz(-1, 2, 4); Coord xyz2 = -xyz; CPPUNIT_ASSERT_EQUAL(Coord(1, -2, -4), xyz2); xyz2 = -xyz2; CPPUNIT_ASSERT_EQUAL(xyz, xyz2); xyz.setX(-xyz.x()); CPPUNIT_ASSERT_EQUAL(Coord(1, 2, 4), xyz); xyz2 = xyz >> 1; CPPUNIT_ASSERT_EQUAL(Coord(0, 1, 2), xyz2); xyz2 |= 1; CPPUNIT_ASSERT_EQUAL(Coord(1, 1, 3), xyz2); CPPUNIT_ASSERT(xyz2 != xyz); CPPUNIT_ASSERT(xyz2 < xyz); CPPUNIT_ASSERT(xyz2 <= xyz); xyz2 -= xyz2; CPPUNIT_ASSERT_EQUAL(Coord(), xyz2); xyz2.reset(0, 4, 4); xyz2.offset(-1); CPPUNIT_ASSERT_EQUAL(Coord(-1, 3, 3), xyz2); // xyz = (1, 2, 4), xyz2 = (-1, 3, 3) CPPUNIT_ASSERT_EQUAL(Coord(-1, 2, 3), Coord::minComponent(xyz, xyz2)); CPPUNIT_ASSERT_EQUAL(Coord(1, 3, 4), Coord::maxComponent(xyz, xyz2)); } void TestCoord::testConversion() { using openvdb::Coord; openvdb::Vec3I iv(1, 2, 4); Coord xyz(iv); CPPUNIT_ASSERT_EQUAL(Coord(1, 2, 4), xyz); CPPUNIT_ASSERT_EQUAL(iv, xyz.asVec3I()); CPPUNIT_ASSERT_EQUAL(openvdb::Vec3i(1, 2, 4), xyz.asVec3i()); iv = (xyz + iv) + xyz; CPPUNIT_ASSERT_EQUAL(openvdb::Vec3I(3, 6, 12), iv); iv = iv - xyz; CPPUNIT_ASSERT_EQUAL(openvdb::Vec3I(2, 4, 8), iv); openvdb::Vec3s fv = xyz.asVec3s(); CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(openvdb::Vec3s(1, 2, 4), fv)); } void TestCoord::testIO() { using openvdb::Coord; Coord xyz(-1, 2, 4), xyz2; std::ostringstream os(std::ios_base::binary); CPPUNIT_ASSERT_NO_THROW(xyz.write(os)); std::istringstream is(os.str(), std::ios_base::binary); CPPUNIT_ASSERT_NO_THROW(xyz2.read(is)); CPPUNIT_ASSERT_EQUAL(xyz, xyz2); os.str(""); os << xyz; CPPUNIT_ASSERT_EQUAL(std::string("[-1, 2, 4]"), os.str()); } void TestCoord::testCoordBBox() { {// Empty constructor openvdb::CoordBBox b; CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), b.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), b.max()); CPPUNIT_ASSERT(b.empty()); } {// Construct bbox from min and max const openvdb::Coord min(-1,-2,30), max(20,30,55); openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(min, b.min()); CPPUNIT_ASSERT_EQUAL(max, b.max()); } {// tbb::split constructor const openvdb::Coord min(-1,-2,30), max(20,30,55); openvdb::CoordBBox a(min, max), b(a, tbb::split()); CPPUNIT_ASSERT_EQUAL(min, b.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(20, 14, 55), b.max()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-1, 15, 30), a.min()); CPPUNIT_ASSERT_EQUAL(max, a.max()); } {// createCube const openvdb::Coord min(0,8,16); const openvdb::CoordBBox b = openvdb::CoordBBox::createCube(min, 8); CPPUNIT_ASSERT_EQUAL(min, b.min()); CPPUNIT_ASSERT_EQUAL(min + openvdb::Coord(8-1), b.max()); } {// inf const openvdb::CoordBBox b = openvdb::CoordBBox::inf(); CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), b.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), b.max()); } {// empty, hasVolume and volume const openvdb::Coord c(1,2,3); const openvdb::CoordBBox a(c, c), b(c, c.offsetBy(0,-1,0)); CPPUNIT_ASSERT( a.hasVolume() && !a.empty()); CPPUNIT_ASSERT(!b.hasVolume() && b.empty()); CPPUNIT_ASSERT_EQUAL(uint64_t(1), a.volume()); CPPUNIT_ASSERT_EQUAL(uint64_t(0), b.volume()); } {// volume and split constructor const openvdb::Coord min(-1,-2,30), max(20,30,55); const openvdb::CoordBBox bbox(min,max); openvdb::CoordBBox a(bbox), b(a, tbb::split()); CPPUNIT_ASSERT_EQUAL(bbox.volume(), a.volume() + b.volume()); openvdb::CoordBBox c(b, tbb::split()); CPPUNIT_ASSERT_EQUAL(bbox.volume(), a.volume() + b.volume() + c.volume()); } {// getCenter const openvdb::Coord min(1,2,3), max(6,10,15); const openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(openvdb::Vec3d(3.5, 6.0, 9.0), b.getCenter()); } {// a volume that overflows Int32. typedef openvdb::Int32 Int32; Int32 maxInt32 = std::numeric_limits::max(); const openvdb::Coord min(Int32(0), Int32(0), Int32(0)); const openvdb::Coord max(maxInt32-Int32(2), Int32(2), Int32(2)); const openvdb::CoordBBox b(min, max); uint64_t volume = UINT64_C(19327352814); CPPUNIT_ASSERT_EQUAL(volume, b.volume()); } {// minExtent and maxExtent const openvdb::Coord min(1,2,3); { const openvdb::Coord max = min + openvdb::Coord(1,2,3); const openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 0); CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 2); } { const openvdb::Coord max = min + openvdb::Coord(1,3,2); const openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 0); CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 1); } { const openvdb::Coord max = min + openvdb::Coord(2,1,3); const openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 1); CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 2); } { const openvdb::Coord max = min + openvdb::Coord(2,3,1); const openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 2); CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 1); } { const openvdb::Coord max = min + openvdb::Coord(3,1,2); const openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 1); CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 0); } { const openvdb::Coord max = min + openvdb::Coord(3,2,1); const openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(int(b.minExtent()), 2); CPPUNIT_ASSERT_EQUAL(int(b.maxExtent()), 0); } } {//reset openvdb::CoordBBox b; CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), b.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), b.max()); CPPUNIT_ASSERT(b.empty()); const openvdb::Coord min(-1,-2,30), max(20,30,55); b.reset(min, max); CPPUNIT_ASSERT_EQUAL(min, b.min()); CPPUNIT_ASSERT_EQUAL(max, b.max()); CPPUNIT_ASSERT(!b.empty()); b.reset(); CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), b.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), b.max()); CPPUNIT_ASSERT(b.empty()); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMetadataIO.cc0000644000000000000000000001556012252453157016224 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include // CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name // from the FixtureType. But if FixtureType is a templated type, the generated name // can become long and messy. This macro overrides the normal naming logic, // instead invoking FixtureType::testSuiteName(), which should be a static member // function that returns a std::string containing the suite name for the specific // template instantiation. #undef CPPUNIT_TESTNAMER_DECL #define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) template class TestMetadataIO: public CppUnit::TestCase { public: static std::string testSuiteName() { std::string name = openvdb::typeNameAsString(); if (!name.empty()) name[0] = ::toupper(name[0]); return "TestMetadataIO" + name; } CPPUNIT_TEST_SUITE(TestMetadataIO); CPPUNIT_TEST(test); CPPUNIT_TEST(testMultiple); CPPUNIT_TEST_SUITE_END(); void test(); void testMultiple(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestMetadataIO); template void TestMetadataIO::test() { using namespace openvdb; TypedMetadata m(1); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm; tm.read(istr); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1),tm.value(),0); //CPPUNIT_ASSERT(tm.value() == T(1)); } template void TestMetadataIO::testMultiple() { using namespace openvdb; TypedMetadata m(1); TypedMetadata m2(2); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); m2.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm, tm2; tm.read(istr); tm2.read(istr); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1),tm.value(),0); //CPPUNIT_ASSERT(tm.value() == T(1)); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(2),tm2.value(),0); //CPPUNIT_ASSERT(tm2.value() == T(2)); } template<> void TestMetadataIO::test() { using namespace openvdb; TypedMetadata m("test"); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm; tm.read(istr); CPPUNIT_ASSERT(tm.value() == "test"); } template<> void TestMetadataIO::testMultiple() { using namespace openvdb; TypedMetadata m("test"); TypedMetadata m2("test2"); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); m2.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm, tm2; tm.read(istr); tm2.read(istr); CPPUNIT_ASSERT(tm.value() == "test"); CPPUNIT_ASSERT(tm2.value() == "test2"); } template<> void TestMetadataIO::test() { using namespace openvdb; TypedMetadata m(Vec3R(1, 2, 3)); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm; tm.read(istr); CPPUNIT_ASSERT(tm.value() == Vec3R(1, 2, 3)); } template<> void TestMetadataIO::testMultiple() { using namespace openvdb; TypedMetadata m(Vec3R(1, 2, 3)); TypedMetadata m2(Vec3R(4, 5, 6)); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); m2.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm, tm2; tm.read(istr); tm2.read(istr); CPPUNIT_ASSERT(tm.value() == Vec3R(1, 2, 3)); CPPUNIT_ASSERT(tm2.value() == Vec3R(4, 5, 6)); } template<> void TestMetadataIO::test() { using namespace openvdb; TypedMetadata m(Vec2i(1, 2)); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm; tm.read(istr); CPPUNIT_ASSERT(tm.value() == Vec2i(1, 2)); } template<> void TestMetadataIO::testMultiple() { using namespace openvdb; TypedMetadata m(Vec2i(1, 2)); TypedMetadata m2(Vec2i(3, 4)); std::ostringstream ostr(std::ios_base::binary); m.write(ostr); m2.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); TypedMetadata tm, tm2; tm.read(istr); tm2.read(istr); CPPUNIT_ASSERT(tm.value() == Vec2i(1, 2)); CPPUNIT_ASSERT(tm2.value() == Vec2i(3, 4)); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestBBox.cc0000644000000000000000000001006412252453157015100 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include typedef float Real; class TestBBox: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestBBox); CPPUNIT_TEST(testBBox); CPPUNIT_TEST(testCenter); CPPUNIT_TEST(testExtent); CPPUNIT_TEST_SUITE_END(); void testBBox(); void testCenter(); void testExtent(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestBBox); void TestBBox::testBBox() { typedef openvdb::Vec3R Vec3R; typedef openvdb::math::BBox BBoxType; { BBoxType B(Vec3R(1,1,1),Vec3R(2,2,2)); CPPUNIT_ASSERT(B.isSorted()); CPPUNIT_ASSERT(B.isInside(Vec3R(1.5,2,2))); CPPUNIT_ASSERT(!B.isInside(Vec3R(2,3,2))); B.expand(Vec3R(3,3,3)); CPPUNIT_ASSERT(B.isInside(Vec3R(3,3,3))); } { BBoxType B; CPPUNIT_ASSERT(B.empty()); const Vec3R expected(1); B.expand(expected); CPPUNIT_ASSERT_EQUAL(expected, B.min()); CPPUNIT_ASSERT_EQUAL(expected, B.max()); } } void TestBBox::testCenter() { using namespace openvdb::math; const Vec3 expected(1.5); BBox fbox(openvdb::Vec3R(1.0), openvdb::Vec3R(2.0)); CPPUNIT_ASSERT_EQUAL(expected, fbox.getCenter()); BBox ibox(openvdb::Vec3i(1), openvdb::Vec3i(2)); CPPUNIT_ASSERT_EQUAL(expected, ibox.getCenter()); openvdb::CoordBBox cbox(openvdb::Coord(1), openvdb::Coord(2)); CPPUNIT_ASSERT_EQUAL(expected, cbox.getCenter()); } void TestBBox::testExtent() { typedef openvdb::Vec3R Vec3R; typedef openvdb::math::BBox BBoxType; { BBoxType B(Vec3R(-20,0,1),Vec3R(2,2,2)); CPPUNIT_ASSERT_EQUAL(size_t(2), B.minExtent()); CPPUNIT_ASSERT_EQUAL(size_t(0), B.maxExtent()); } { BBoxType B(Vec3R(1,0,1),Vec3R(2,21,20)); CPPUNIT_ASSERT_EQUAL(size_t(0), B.minExtent()); CPPUNIT_ASSERT_EQUAL(size_t(1), B.maxExtent()); } { BBoxType B(Vec3R(1,0,1),Vec3R(3,1.5,20)); CPPUNIT_ASSERT_EQUAL(size_t(1), B.minExtent()); CPPUNIT_ASSERT_EQUAL(size_t(2), B.maxExtent()); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestDense.cc0000644000000000000000000007275412252453157015322 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// //#define BENCHMARK_TEST #include #include #include #include #include #include #ifdef BENCHMARK_TEST #include "util.h" // for CpuTimer #endif class TestDense: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestDense); CPPUNIT_TEST(testDenseZYX); CPPUNIT_TEST(testDenseXYZ); CPPUNIT_TEST(testCopyZYX); CPPUNIT_TEST(testCopyXYZ); CPPUNIT_TEST(testCopyBoolZYX); CPPUNIT_TEST(testCopyBoolXYZ); CPPUNIT_TEST(testCopyFromDenseWithOffsetZYX); CPPUNIT_TEST(testCopyFromDenseWithOffsetXYZ); CPPUNIT_TEST(testDense2SparseZYX); CPPUNIT_TEST(testDense2SparseXYZ); CPPUNIT_TEST(testDense2Sparse2ZYX); CPPUNIT_TEST(testDense2Sparse2XYZ); CPPUNIT_TEST(testInvalidBBoxZYX); CPPUNIT_TEST(testInvalidBBoxXYZ); CPPUNIT_TEST(testDense2Sparse2DenseZYX); CPPUNIT_TEST(testDense2Sparse2DenseXYZ); CPPUNIT_TEST_SUITE_END(); void testDenseZYX(); void testDenseXYZ(); template void testCopy(); void testCopyZYX() { this->testCopy(); } void testCopyXYZ() { this->testCopy(); } template void testCopyBool(); void testCopyBoolZYX() { this->testCopyBool(); } void testCopyBoolXYZ() { this->testCopyBool(); } template void testCopyFromDenseWithOffset(); void testCopyFromDenseWithOffsetZYX() { this->testCopyFromDenseWithOffset(); } void testCopyFromDenseWithOffsetXYZ() { this->testCopyFromDenseWithOffset(); } template void testDense2Sparse(); void testDense2SparseZYX() { this->testDense2Sparse(); } void testDense2SparseXYZ() { this->testDense2Sparse(); } template void testDense2Sparse2(); void testDense2Sparse2ZYX() { this->testDense2Sparse2(); } void testDense2Sparse2XYZ() { this->testDense2Sparse2(); } template void testInvalidBBox(); void testInvalidBBoxZYX() { this->testInvalidBBox(); } void testInvalidBBoxXYZ() { this->testInvalidBBox(); } template void testDense2Sparse2Dense(); void testDense2Sparse2DenseZYX() { this->testDense2Sparse2Dense(); } void testDense2Sparse2DenseXYZ() { this->testDense2Sparse2Dense(); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDense); void TestDense::testDenseZYX() { const openvdb::CoordBBox bbox(openvdb::Coord(-40,-5, 6), openvdb::Coord(-11, 7,22)); openvdb::tools::Dense dense(bbox);//LayoutZYX is the default // Check Dense::valueCount const int size = dense.valueCount(); CPPUNIT_ASSERT_EQUAL(30*13*17, size); // Check Dense::fill(float) and Dense::getValue(size_t) const float v = 0.234f; dense.fill(v); for (int i=0; i dense(bbox); // Check Dense::valueCount const int size = dense.valueCount(); CPPUNIT_ASSERT_EQUAL(30*13*17, size); // Check Dense::fill(float) and Dense::getValue(size_t) const float v = 0.234f; dense.fill(v); for (int i=0; i > class CheckDense { public: typedef typename TreeT::ValueType ValueT; CheckDense() : mTree(NULL), mDense(NULL) { CPPUNIT_ASSERT(DenseT::memoryLayout() == openvdb::tools::LayoutZYX || DenseT::memoryLayout() == openvdb::tools::LayoutXYZ ); } void check(const TreeT& tree, const DenseT& dense) { mTree = &tree; mDense = &dense; tbb::parallel_for(dense.bbox(), *this); } void operator()(const openvdb::CoordBBox& bbox) const { openvdb::tree::ValueAccessor acc(*mTree); if (DenseT::memoryLayout() == openvdb::tools::LayoutZYX) {//resolved at compiletime for (openvdb::Coord P(bbox.min()); P[0] <= bbox.max()[0]; ++P[0]) { for (P[1] = bbox.min()[1]; P[1] <= bbox.max()[1]; ++P[1]) { for (P[2] = bbox.min()[2]; P[2] <= bbox.max()[2]; ++P[2]) { CPPUNIT_ASSERT_DOUBLES_EQUAL(acc.getValue(P), mDense->getValue(P), /*tolerance=*/0.0001); } } } } else { for (openvdb::Coord P(bbox.min()); P[2] <= bbox.max()[2]; ++P[2]) { for (P[1] = bbox.min()[1]; P[1] <= bbox.max()[1]; ++P[1]) { for (P[0] = bbox.min()[0]; P[0] <= bbox.max()[0]; ++P[0]) { CPPUNIT_ASSERT_DOUBLES_EQUAL(acc.getValue(P), mDense->getValue(P), /*tolerance=*/0.0001); } } } } } private: const TreeT* mTree; const DenseT* mDense; };// CheckDense template void TestDense::testCopy() { using namespace openvdb; //std::cerr << "\nTesting testCopy with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; CheckDense checkDense; const float radius = 10.0f, tolerance = 0.00001f; const Vec3f center(0.0f); // decrease the voxelSize to test larger grids #ifdef BENCHMARK_TEST const float voxelSize = 0.05f, width = 5.0f; #else const float voxelSize = 0.5f, width = 5.0f; #endif // Create a VDB containing a level set of a sphere FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); FloatTree& tree0 = grid->tree(); // Create an empty dense grid DenseT dense(grid->evalActiveVoxelBoundingBox()); #ifdef BENCHMARK_TEST std::cerr << "\nBBox = " << grid->evalActiveVoxelBoundingBox() << std::endl; #endif {//check Dense::fill dense.fill(voxelSize); #ifndef BENCHMARK_TEST checkDense.check(FloatTree(voxelSize), dense); #endif } {// parallel convert to dense #ifdef BENCHMARK_TEST unittest_util::CpuTimer ts; ts.start("CopyToDense"); #endif tools::copyToDense(*grid, dense); #ifdef BENCHMARK_TEST ts.stop(); #else checkDense.check(tree0, dense); #endif } {// Parallel create from dense #ifdef BENCHMARK_TEST unittest_util::CpuTimer ts; ts.start("CopyFromDense"); #endif FloatTree tree1(tree0.background()); tools::copyFromDense(dense, tree1, tolerance); #ifdef BENCHMARK_TEST ts.stop(); #else checkDense.check(tree1, dense); #endif } } template void TestDense::testCopyBool() { using namespace openvdb; //std::cerr << "\nTesting testCopyBool with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; const Coord bmin(-1), bmax(8); const CoordBBox bbox(bmin, bmax); BoolGrid::Ptr grid = createGrid(false); BoolGrid::ConstAccessor acc = grid->getConstAccessor(); typedef openvdb::tools::Dense DenseT; DenseT dense(bbox); dense.fill(false); // Start with sparse and dense grids both filled with false. Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = bmin.x(); x <= bmax.x(); ++x) { for (y = bmin.y(); y <= bmax.y(); ++y) { for (z = bmin.z(); z <= bmax.z(); ++z) { CPPUNIT_ASSERT_EQUAL(false, dense.getValue(xyz)); CPPUNIT_ASSERT_EQUAL(false, acc.getValue(xyz)); } } } // Fill the dense grid with true. dense.fill(true); // Copy the contents of the dense grid to the sparse grid. tools::copyFromDense(dense, *grid, /*tolerance=*/false); // Verify that both sparse and dense grids are now filled with true. for (x = bmin.x(); x <= bmax.x(); ++x) { for (y = bmin.y(); y <= bmax.y(); ++y) { for (z = bmin.z(); z <= bmax.z(); ++z) { CPPUNIT_ASSERT_EQUAL(true, dense.getValue(xyz)); CPPUNIT_ASSERT_EQUAL(true, acc.getValue(xyz)); } } } // Fill the dense grid with false. dense.fill(false); // Copy the contents (= true) of the sparse grid to the dense grid. tools::copyToDense(*grid, dense); // Verify that the dense grid is now filled with true. for (x = bmin.x(); x <= bmax.x(); ++x) { for (y = bmin.y(); y <= bmax.y(); ++y) { for (z = bmin.z(); z <= bmax.z(); ++z) { CPPUNIT_ASSERT_EQUAL(true, dense.getValue(xyz)); } } } } // Test copying from a dense grid to a sparse grid with various bounding boxes. template void TestDense::testCopyFromDenseWithOffset() { using namespace openvdb; //std::cerr << "\nTesting testCopyFromDenseWithOffset with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef openvdb::tools::Dense DenseT; const int DIM = 20, COUNT = DIM * DIM * DIM; const float FOREGROUND = 99.0f, BACKGROUND = 5000.0f; const int OFFSET[] = { 1, -1, 1001, -1001 }; for (int offsetIdx = 0; offsetIdx < 4; ++offsetIdx) { const int offset = OFFSET[offsetIdx]; const CoordBBox bbox = CoordBBox::createCube(Coord(offset), DIM); DenseT dense(bbox, FOREGROUND); CPPUNIT_ASSERT_EQUAL(bbox, dense.bbox()); FloatGrid grid(BACKGROUND); tools::copyFromDense(dense, grid, /*tolerance=*/0.0); const CoordBBox gridBBox = grid.evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(bbox, gridBBox); CPPUNIT_ASSERT_EQUAL(COUNT, int(grid.activeVoxelCount())); FloatGrid::ConstAccessor acc = grid.getConstAccessor(); for (int i = gridBBox.min()[0], ie = gridBBox.max()[0]; i < ie; ++i) { for (int j = gridBBox.min()[1], je = gridBBox.max()[1]; j < je; ++j) { for (int k = gridBBox.min()[2], ke = gridBBox.max()[2]; k < ke; ++k) { const Coord ijk(i, j, k); CPPUNIT_ASSERT_DOUBLES_EQUAL( FOREGROUND, acc.getValue(ijk), /*tolerance=*/0.0); CPPUNIT_ASSERT(acc.isValueOn(ijk)); } } } } } template void TestDense::testDense2Sparse() { // The following test revealed a bug in v2.0.0b2 using namespace openvdb; //std::cerr << "\nTesting testDense2Sparse with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; // Test Domain Resolution size_t sizeX = 8; size_t sizeY = 8; size_t sizeZ = 9; // Define a dense grid DenseT dense(Coord(sizeX, sizeY, sizeZ)); const CoordBBox bboxD = dense.bbox(); // std::cerr << "\nDense bbox" << bboxD << std::endl; // Verify that the CoordBBox is truely used as [inclusive, inclusive] CPPUNIT_ASSERT(dense.valueCount() == sizeX * sizeY * sizeZ ); // Fill the dense grid with constant value 1. dense.fill(1.0f); // Create two empty float grids FloatGrid::Ptr gridS = FloatGrid::create(0.0f /*background*/); FloatGrid::Ptr gridP = FloatGrid::create(0.0f /*background*/); // Convert in serial and parallel modes tools::copyFromDense(dense, *gridS, /*tolerance*/0.0f, /*serial = */ true); tools::copyFromDense(dense, *gridP, /*tolerance*/0.0f, /*serial = */ false); float minS, maxS; float minP, maxP; gridS->evalMinMax(minS, maxS); gridP->evalMinMax(minP, maxP); const float tolerance = 0.0001; CPPUNIT_ASSERT_DOUBLES_EQUAL(minS, minP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(maxS, maxP, tolerance); CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), Index64(sizeX * sizeY * sizeZ)); const FloatTree& treeS = gridS->tree(); const FloatTree& treeP = gridP->tree(); // Values in Test Domain are correct for (Coord ijk(bboxD.min()); ijk[0] <= bboxD.max()[0]; ++ijk[0]) { for (ijk[1] = bboxD.min()[1]; ijk[1] <= bboxD.max()[1]; ++ijk[1]) { for (ijk[2] = bboxD.min()[2]; ijk[2] <= bboxD.max()[2]; ++ijk[2]) { const float expected = bboxD.isInside(ijk) ? 1.f : 0.f; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, 1.f, tolerance); const float& vS = treeS.getValue(ijk); const float& vP = treeP.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); } } } CoordBBox bboxP = gridP->evalActiveVoxelBoundingBox(); const Index64 voxelCountP = gridP->activeVoxelCount(); //std::cerr << "\nParallel: bbox=" << bboxP << " voxels=" << voxelCountP << std::endl; CPPUNIT_ASSERT( bboxP == bboxD ); CPPUNIT_ASSERT_EQUAL( dense.valueCount(), voxelCountP); CoordBBox bboxS = gridS->evalActiveVoxelBoundingBox(); const Index64 voxelCountS = gridS->activeVoxelCount(); //std::cerr << "\nSerial: bbox=" << bboxS << " voxels=" << voxelCountS << std::endl; CPPUNIT_ASSERT( bboxS == bboxD ); CPPUNIT_ASSERT_EQUAL( dense.valueCount(), voxelCountS); // Topology CPPUNIT_ASSERT( bboxS.isInside(bboxS) ); CPPUNIT_ASSERT( bboxP.isInside(bboxP) ); CPPUNIT_ASSERT( bboxS.isInside(bboxP) ); CPPUNIT_ASSERT( bboxP.isInside(bboxS) ); /// Check that the two grids agree for (Coord ijk(bboxS.min()); ijk[0] <= bboxS.max()[0]; ++ijk[0]) { for (ijk[1] = bboxS.min()[1]; ijk[1] <= bboxS.max()[1]; ++ijk[1]) { for (ijk[2] = bboxS.min()[2]; ijk[2] <= bboxS.max()[2]; ++ijk[2]) { const float& vS = treeS.getValue(ijk); const float& vP = treeP.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(vS, vP, tolerance); // the value we should get based on the original domain const float expected = bboxD.isInside(ijk) ? 1.f : 0.f; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); } } } // Verify the tree topology matches. CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), gridS->activeVoxelCount()); CPPUNIT_ASSERT(gridP->evalActiveVoxelBoundingBox() == gridS->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT(treeP.hasSameTopology(treeS) ); } template void TestDense::testDense2Sparse2() { // The following tests copying a dense grid into a VDB tree with // existing values outside the bbox of the dense grid. using namespace openvdb; //std::cerr << "\nTesting testDense2Sparse2 with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; // Test Domain Resolution size_t sizeX = 8; size_t sizeY = 8; size_t sizeZ = 9; const Coord magicVoxel(sizeX, sizeY, sizeZ); // Define a dense grid DenseT dense(Coord(sizeX, sizeY, sizeZ)); const CoordBBox bboxD = dense.bbox(); //std::cerr << "\nDense bbox" << bboxD << std::endl; // Verify that the CoordBBox is truely used as [inclusive, inclusive] CPPUNIT_ASSERT(dense.valueCount() == sizeX * sizeY * sizeZ ); // Fill the dense grid with constant value 1. dense.fill(1.0f); // Create two empty float grids FloatGrid::Ptr gridS = FloatGrid::create(0.0f /*background*/); FloatGrid::Ptr gridP = FloatGrid::create(0.0f /*background*/); gridS->tree().setValue(magicVoxel, 5.0f); gridP->tree().setValue(magicVoxel, 5.0f); // Convert in serial and parallel modes tools::copyFromDense(dense, *gridS, /*tolerance*/0.0f, /*serial = */ true); tools::copyFromDense(dense, *gridP, /*tolerance*/0.0f, /*serial = */ false); float minS, maxS; float minP, maxP; gridS->evalMinMax(minS, maxS); gridP->evalMinMax(minP, maxP); const float tolerance = 0.0001; CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0f, minP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0f, minS, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0f, maxP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0f, maxS, tolerance); CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), Index64(1 + sizeX * sizeY * sizeZ)); const FloatTree& treeS = gridS->tree(); const FloatTree& treeP = gridP->tree(); // Values in Test Domain are correct for (Coord ijk(bboxD.min()); ijk[0] <= bboxD.max()[0]; ++ijk[0]) { for (ijk[1] = bboxD.min()[1]; ijk[1] <= bboxD.max()[1]; ++ijk[1]) { for (ijk[2] = bboxD.min()[2]; ijk[2] <= bboxD.max()[2]; ++ijk[2]) { const float expected = bboxD.isInside(ijk) ? 1.0f : 0.0f; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, 1.0f, tolerance); const float& vS = treeS.getValue(ijk); const float& vP = treeP.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); } } } CoordBBox bboxP = gridP->evalActiveVoxelBoundingBox(); const Index64 voxelCountP = gridP->activeVoxelCount(); //std::cerr << "\nParallel: bbox=" << bboxP << " voxels=" << voxelCountP << std::endl; CPPUNIT_ASSERT( bboxP != bboxD ); CPPUNIT_ASSERT( bboxP == CoordBBox(Coord(0,0,0), magicVoxel) ); CPPUNIT_ASSERT_EQUAL( dense.valueCount()+1, voxelCountP); CoordBBox bboxS = gridS->evalActiveVoxelBoundingBox(); const Index64 voxelCountS = gridS->activeVoxelCount(); //std::cerr << "\nSerial: bbox=" << bboxS << " voxels=" << voxelCountS << std::endl; CPPUNIT_ASSERT( bboxS != bboxD ); CPPUNIT_ASSERT( bboxS == CoordBBox(Coord(0,0,0), magicVoxel) ); CPPUNIT_ASSERT_EQUAL( dense.valueCount()+1, voxelCountS); // Topology CPPUNIT_ASSERT( bboxS.isInside(bboxS) ); CPPUNIT_ASSERT( bboxP.isInside(bboxP) ); CPPUNIT_ASSERT( bboxS.isInside(bboxP) ); CPPUNIT_ASSERT( bboxP.isInside(bboxS) ); /// Check that the two grids agree for (Coord ijk(bboxS.min()); ijk[0] <= bboxS.max()[0]; ++ijk[0]) { for (ijk[1] = bboxS.min()[1]; ijk[1] <= bboxS.max()[1]; ++ijk[1]) { for (ijk[2] = bboxS.min()[2]; ijk[2] <= bboxS.max()[2]; ++ijk[2]) { const float& vS = treeS.getValue(ijk); const float& vP = treeP.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(vS, vP, tolerance); // the value we should get based on the original domain const float expected = bboxD.isInside(ijk) ? 1.0f : ijk == magicVoxel ? 5.0f : 0.0f; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vP, tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, vS, tolerance); } } } // Verify the tree topology matches. CPPUNIT_ASSERT_EQUAL(gridP->activeVoxelCount(), gridS->activeVoxelCount()); CPPUNIT_ASSERT(gridP->evalActiveVoxelBoundingBox() == gridS->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT(treeP.hasSameTopology(treeS) ); } template void TestDense::testInvalidBBox() { using namespace openvdb; //std::cerr << "\nTesting testInvalidBBox with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; const CoordBBox badBBox(Coord(1, 1, 1), Coord(-1, 2, 2)); CPPUNIT_ASSERT(badBBox.empty()); CPPUNIT_ASSERT_THROW(DenseT dense(badBBox), ValueError); } template void TestDense::testDense2Sparse2Dense() { using namespace openvdb; //std::cerr << "\nTesting testDense2Sparse2Dense with " // << (Layout == tools::LayoutXYZ ? "XYZ" : "ZYX") << " memory layout" // << std::endl; typedef tools::Dense DenseT; const CoordBBox bboxBig(Coord(-12, 7, -32), Coord(12, 14, -15)); const CoordBBox bboxSmall(Coord(-10, 8, -31), Coord(10, 12, -20)); // A larger bbox CoordBBox bboxBigger = bboxBig; bboxBigger.expand(Coord(10)); // Small is in big CPPUNIT_ASSERT(bboxBig.isInside(bboxSmall)); // Big is in Bigger CPPUNIT_ASSERT(bboxBigger.isInside(bboxBig)); // Construct a small dense grid DenseT denseSmall(bboxSmall, 0.f); { // insert non-const values const int n = denseSmall.valueCount(); float* d = denseSmall.data(); for (int i = 0; i < n; ++i) { d[i] = i; } } // Construct large dense grid DenseT denseBig(bboxBig, 0.f); { // insert non-const values const int n = denseBig.valueCount(); float* d = denseBig.data(); for (int i = 0; i < n; ++i) { d[i] = i; } } // Make a sparse grid to copy this data into FloatGrid::Ptr grid = FloatGrid::create(3.3f /*background*/); tools::copyFromDense(denseBig, *grid, /*tolerance*/0.0f, /*serial = */ true); tools::copyFromDense(denseSmall, *grid, /*tolerance*/0.0f, /*serial = */ false); const FloatTree& tree = grid->tree(); // CPPUNIT_ASSERT_EQUAL(bboxBig.volume(), grid->activeVoxelCount()); // iterate over the Bigger for (Coord ijk(bboxBigger.min()); ijk[0] <= bboxBigger.max()[0]; ++ijk[0]) { for (ijk[1] = bboxBigger.min()[1]; ijk[1] <= bboxBigger.max()[1]; ++ijk[1]) { for (ijk[2] = bboxBigger.min()[2]; ijk[2] <= bboxBigger.max()[2]; ++ijk[2]) { float expected = 3.3f; if (bboxSmall.isInside(ijk)) { expected = denseSmall.getValue(ijk); } else if (bboxBig.isInside(ijk)) { expected = denseBig.getValue(ijk); } const float& value = tree.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); } } } // Convert to Dense in small bbox { DenseT denseSmall2(bboxSmall); tools::copyToDense(*grid, denseSmall2, true /* serial */); // iterate over the Bigger for (Coord ijk(bboxSmall.min()); ijk[0] <= bboxSmall.max()[0]; ++ijk[0]) { for (ijk[1] = bboxSmall.min()[1]; ijk[1] <= bboxSmall.max()[1]; ++ijk[1]) { for (ijk[2] = bboxSmall.min()[2]; ijk[2] <= bboxSmall.max()[2]; ++ijk[2]) { const float& expected = denseSmall.getValue(ijk); const float& value = denseSmall2.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); } } } } // Convert to Dense in large bbox { DenseT denseBig2(bboxBig); tools::copyToDense(*grid, denseBig2, false /* serial */); // iterate over the Bigger for (Coord ijk(bboxBig.min()); ijk[0] <= bboxBig.max()[0]; ++ijk[0]) { for (ijk[1] = bboxBig.min()[1]; ijk[1] <= bboxBig.max()[1]; ++ijk[1]) { for (ijk[2] = bboxBig.min()[2]; ijk[2] <= bboxBig.max()[2]; ++ijk[2]) { float expected = -1.f; // should never be this if (bboxSmall.isInside(ijk)) { expected = denseSmall.getValue(ijk); } else if (bboxBig.isInside(ijk)) { expected = denseBig.getValue(ijk); } const float& value = denseBig2.getValue(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, value, 0.0001); } } } } } #undef BENCHMARK_TEST // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestInt32Metadata.cc0000644000000000000000000000535512252453157016615 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestInt32Metadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestInt32Metadata); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestInt32Metadata); void TestInt32Metadata::test() { using namespace openvdb; Metadata::Ptr m(new Int32Metadata(123)); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("int32") == 0); CPPUNIT_ASSERT(m2->typeName().compare("int32") == 0); Int32Metadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == 123); s->value() = 456; CPPUNIT_ASSERT(s->value() == 456); m2->copy(*s); s = dynamic_cast(m2.get()); CPPUNIT_ASSERT(s->value() == 456); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestStats.cc0000644000000000000000000005550712252453157015357 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include // for ISGradient #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestStats: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestStats); CPPUNIT_TEST(testStats); CPPUNIT_TEST(testGridStats); CPPUNIT_TEST(testGridHistogram); CPPUNIT_TEST(testGridOperatorStats); CPPUNIT_TEST_SUITE_END(); void testStats(); void testGridStats(); void testGridHistogram(); void testGridOperatorStats(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestStats); void TestStats::testStats() { {// trivial test openvdb::math::Stats s; s.add(0); s.add(1); CPPUNIT_ASSERT_EQUAL(2, int(s.size())); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, s.mean(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.25, s.variance(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, s.stdDev(), 0.000001); //s.print("test"); } {// non-trivial test openvdb::math::Stats s; const int data[5]={600, 470, 170, 430, 300}; for (int i=0; i<5; ++i) s.add(data[i]); double sum = 0.0; for (int i=0; i<5; ++i) sum += data[i]; const double mean = sum/5.0; sum = 0.0; for (int i=0; i<5; ++i) sum += (data[i]-mean)*(data[i]-mean); const double var = sum/5.0; CPPUNIT_ASSERT_EQUAL(5, int(s.size())); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[2], s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[0], s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(mean, s.mean(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(var, s.variance(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(sqrt(var), s.stdDev(), 0.000001); //s.print("test"); } {// non-trivial test of Stats::add(Stats) openvdb::math::Stats s, t; const int data[5]={600, 470, 170, 430, 300}; for (int i=0; i<3; ++i) s.add(data[i]); for (int i=3; i<5; ++i) t.add(data[i]); s.add(t); double sum = 0.0; for (int i=0; i<5; ++i) sum += data[i]; const double mean = sum/5.0; sum = 0.0; for (int i=0; i<5; ++i) sum += (data[i]-mean)*(data[i]-mean); const double var = sum/5.0; CPPUNIT_ASSERT_EQUAL(5, int(s.size())); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[2], s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(data[0], s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(mean, s.mean(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(var, s.variance(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(sqrt(var), s.stdDev(), 0.000001); //s.print("test"); } {// Trivial test of Stats::add(value, n) openvdb::math::Stats s; const double val = 3.45; const uint64_t n = 57; s.add(val, 57); CPPUNIT_ASSERT_EQUAL(n, s.size()); CPPUNIT_ASSERT_DOUBLES_EQUAL(val, s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(val, s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(val, s.mean(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.variance(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.stdDev(), 0.000001); } {// Test 1 of Stats::add(value), Stats::add(value, n) and Stats::add(Stats) openvdb::math::Stats s, t; const double val1 = 1.0, val2 = 3.0, sum = val1 + val2; const uint64_t n1 = 1, n2 =1; s.add(val1, n1); CPPUNIT_ASSERT_EQUAL(uint64_t(n1), s.size()); CPPUNIT_ASSERT_DOUBLES_EQUAL(val1, s.min(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(val1, s.max(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(val1, s.mean(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.variance(), 0.000001); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, s.stdDev(), 0.000001); for (uint64_t i=0; i= h.min(j) && data[i] < h.max(j)) bin[j]++; } for (int i=0; i<5; ++i) CPPUNIT_ASSERT_EQUAL(bin[i],int(h.count(i))); //h.print("test"); } {//Test print of Histogram openvdb::math::Stats s; const int N=500000; for (int i=0; i GradT; if (it.isVoxelValue()) { stats.add(GradT::result(acc, it.getCoord()).length()); } else { openvdb::CoordBBox bbox = it.getBoundingBox(); openvdb::Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = bbox.min()[0]; x <= bbox.max()[0]; ++x) { for (y = bbox.min()[1]; y <= bbox.max()[1]; ++y) { for (z = bbox.min()[2]; z <= bbox.max()[2]; ++z) { stats.add(GradT::result(acc, xyz).length()); } } } } } }; } // unnamed namespace void TestStats::testGridStats() { using namespace openvdb; const int DIM = 109; { const float background = 0.0; FloatGrid grid(background); { // Compute active value statistics for a grid with a single active voxel. grid.tree().setValue(Coord(0), /*value=*/42.0); math::Stats stats = tools::statistics(grid.cbeginValueOn()); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, stats.variance(), /*tolerance=*/1.0e-8); // Compute inactive value statistics for a grid with only background voxels. grid.tree().setValueOff(Coord(0), background); stats = tools::statistics(grid.cbeginValueOff()); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, stats.variance(), /*tolerance=*/1.0e-8); } // Compute active value statistics for a grid with two active voxel populations // of the same size but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/-3.0); CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Stats stats = tools::statistics(grid.cbeginValueOn(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-3.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-1.0), stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(4.0), stats.variance(), /*tolerance=*/1.0e-8); } // Compute active value statistics for just the positive values. for (int threaded = 0; threaded <= 1; ++threaded) { struct Local { static void addIfPositive(const FloatGrid::ValueOnCIter& it, math::Stats& stats) { const float f = *it; if (f > 0.0) { if (it.isVoxelValue()) stats.add(f); else stats.add(f, it.getVoxelCount()); } } }; math::Stats stats = tools::statistics(grid.cbeginValueOn(), &Local::addIfPositive, threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.variance(), /*tolerance=*/1.0e-8); } // Compute active value statistics for the first-order gradient. for (int threaded = 0; threaded <= 1; ++threaded) { // First, using a custom ValueOp... math::Stats stats = tools::statistics(grid.cbeginValueOn(), GradOp(grid), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL( double(9.0 + 9.0 + 9.0), stats.max() * stats.max(), /*tol=*/1.0e-3); // max gradient is (dx, dy, dz) = (-3 - 0, -3 - 0, -3 - 0) // ...then using tools::opStatistics(). typedef math::ISOpMagnitude > MathOp; stats = tools::opStatistics(grid.cbeginValueOn(), MathOp(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL( double(9.0 + 9.0 + 9.0), stats.max() * stats.max(), /*tolerance=*/1.0e-3); // max gradient is (dx, dy, dz) = (-3 - 0, -3 - 0, -3 - 0) } } { const Vec3s background(0.0); Vec3SGrid grid(background); // Compute active vector magnitude statistics for a vector-valued grid // with two active voxel populations of the same size but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Stats stats = tools::statistics(grid.cbeginValueOn(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(3.0), stats.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(5.0), stats.max(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(4.0), stats.mean(), /*tolerance=*/1.0e-8); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), stats.variance(), /*tolerance=*/1.0e-8); } } } namespace { template inline void doTestGridOperatorStats(const GridT& grid, const OpT& op) { openvdb::math::Stats serialStats = openvdb::tools::opStatistics(grid.cbeginValueOn(), op, /*threaded=*/false); openvdb::math::Stats parallelStats = openvdb::tools::opStatistics(grid.cbeginValueOn(), op, /*threaded=*/true); // Verify that the results from threaded and serial runs are equivalent. CPPUNIT_ASSERT_EQUAL(serialStats.size(), parallelStats.size()); ASSERT_DOUBLES_EXACTLY_EQUAL(serialStats.min(), parallelStats.min()); ASSERT_DOUBLES_EXACTLY_EQUAL(serialStats.max(), parallelStats.max()); CPPUNIT_ASSERT_DOUBLES_EQUAL(serialStats.mean(), parallelStats.mean(), /*tolerance=*/1.0e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(serialStats.variance(), parallelStats.variance(), 1.0e-6); } } void TestStats::testGridOperatorStats() { using namespace openvdb; typedef math::UniformScaleMap MapType; MapType map; const int DIM = 109; { // Test operations on a scalar grid. const float background = 0.0; FloatGrid grid(background); grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/-3.0); { // Magnitude of gradient computed via first-order differencing typedef math::MapAdapter, MapType>, double> OpT; doTestGridOperatorStats(grid, OpT(map)); } { // Magnitude of index-space gradient computed via first-order differencing typedef math::ISOpMagnitude > OpT; doTestGridOperatorStats(grid, OpT()); } { // Laplacian of index-space gradient computed via second-order central differencing typedef math::ISLaplacian OpT; doTestGridOperatorStats(grid, OpT()); } } { // Test operations on a vector grid. const Vec3s background(0.0); Vec3SGrid grid(background); grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 { // Divergence computed via first-order differencing typedef math::MapAdapter, double> OpT; doTestGridOperatorStats(grid, OpT(map)); } { // Magnitude of curl computed via first-order differencing typedef math::MapAdapter, MapType>, double> OpT; doTestGridOperatorStats(grid, OpT(map)); } { // Magnitude of index-space curl computed via first-order differencing typedef math::ISOpMagnitude > OpT; doTestGridOperatorStats(grid, OpT()); } } } void TestStats::testGridHistogram() { using namespace openvdb; const int DIM = 109; { const float background = 0.0; FloatGrid grid(background); { const double value = 42.0; // Compute a histogram of the active values of a grid with a single active voxel. grid.tree().setValue(Coord(0), value); math::Histogram hist = tools::histogram(grid.cbeginValueOn(), /*min=*/0.0, /*max=*/100.0); for (size_t i = 0, N = hist.numBins(); i < N; ++i) { uint64_t expected = ((hist.min(i) <= value && value <= hist.max(i)) ? 1 : 0); CPPUNIT_ASSERT_EQUAL(expected, hist.count(i)); } } // Compute a histogram of the active values of a grid with two // active voxel populations of the same size but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), /*value=*/1.0); grid.fill(CoordBBox::createCube(Coord(-300), DIM), /*value=*/3.0); CPPUNIT_ASSERT_EQUAL(uint64_t(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Histogram hist = tools::histogram(grid.cbeginValueOn(), /*min=*/0.0, /*max=*/10.0, /*numBins=*/9, threaded); CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), hist.size()); for (size_t i = 0, N = hist.numBins(); i < N; ++i) { if (i == 0 || i == 2) { CPPUNIT_ASSERT_EQUAL(uint64_t(DIM * DIM * DIM), hist.count(i)); } else { CPPUNIT_ASSERT_EQUAL(uint64_t(0), hist.count(i)); } } } } { const Vec3s background(0.0); Vec3SGrid grid(background); // Compute a histogram of vector magnitudes of the active values of a // vector-valued grid with two active voxel populations of the same size // but two different values. grid.fill(CoordBBox::createCube(Coord(0), DIM), Vec3s(3.0, 0.0, 4.0)); // length = 5 grid.fill(CoordBBox::createCube(Coord(-300), DIM), Vec3s(1.0, 2.0, 2.0)); // length = 3 CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), grid.activeVoxelCount()); for (int threaded = 0; threaded <= 1; ++threaded) { math::Histogram hist = tools::histogram(grid.cbeginValueOn(), /*min=*/0.0, /*max=*/10.0, /*numBins=*/9, threaded); CPPUNIT_ASSERT_EQUAL(Index64(2 * DIM * DIM * DIM), hist.size()); for (size_t i = 0, N = hist.numBins(); i < N; ++i) { if (i == 2 || i == 4) { CPPUNIT_ASSERT_EQUAL(uint64_t(DIM * DIM * DIM), hist.count(i)); } else { CPPUNIT_ASSERT_EQUAL(uint64_t(0), hist.count(i)); } } } } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestDivergence.cc0000644000000000000000000006353212252453157016331 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); namespace { const int GRID_DIM = 10; } class TestDivergence: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestDivergence); CPPUNIT_TEST(testISDivergence); // Divergence in Index Space CPPUNIT_TEST(testISDivergenceStencil); CPPUNIT_TEST(testWSDivergence); // Divergence in World Space CPPUNIT_TEST(testWSDivergenceStencil); CPPUNIT_TEST(testDivergenceTool); // Divergence tool CPPUNIT_TEST(testDivergenceMaskedTool); // Divergence tool CPPUNIT_TEST(testStaggeredDivergence); CPPUNIT_TEST_SUITE_END(); void testISDivergence(); void testISDivergenceStencil(); void testWSDivergence(); void testWSDivergenceStencil(); void testDivergenceTool(); void testDivergenceMaskedTool(); void testStaggeredDivergence(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDivergence); void TestDivergence::testDivergenceTool() { using namespace openvdb; typedef VectorGrid::ConstAccessor Accessor; VectorGrid::Ptr inGrid = VectorGrid::create(); VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xactiveVoxelCount())); FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); --dim;//ignore boundary divergence for (int x = -dim; xtree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xfill(maskBBox, true /*value*/, true /*activate*/); FloatGrid::Ptr divGrid = tools::divergence(*inGrid, *maskGrid); CPPUNIT_ASSERT_EQUAL(math::Pow3(dim), int(divGrid->activeVoxelCount())); FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); --dim;//ignore boundary divergence for (int x = -dim; xsetGridClass( GRID_STAGGERED ); VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xactiveVoxelCount())); FloatGrid::ConstAccessor accessor = divGrid->getConstAccessor(); --dim;//ignore boundary divergence for (int x = -dim; xtree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xgetConstAccessor(); CPPUNIT_ASSERT(!inTree.empty()); CPPUNIT_ASSERT_EQUAL(math::Pow3(2*dim), int(inTree.activeVoxelCount())); --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); } } } --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); } } } --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); d = math::ISDivergence::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); } } } } void TestDivergence::testISDivergenceStencil() { using namespace openvdb; typedef VectorGrid::ConstAccessor Accessor; VectorGrid::Ptr inGrid = VectorGrid::create(); VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; x sevenpt(*inGrid); math::ThirteenPointStencil thirteenpt(*inGrid); math::NineteenPointStencil nineteenpt(*inGrid); --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); } } } --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(thirteenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(thirteenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(thirteenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); } } } --dim;//ignore boundary divergence // test index space divergence for (int x = -dim; x::result(nineteenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(2, d); d = math::ISDivergence::result(nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); d = math::ISDivergence::result(nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, d, /*tolerance=*/0.00001); } } } } void TestDivergence::testWSDivergence() { using namespace openvdb; typedef VectorGrid::ConstAccessor Accessor; { // non-unit voxel size double voxel_size = 0.5; VectorGrid::Ptr inGrid = VectorGrid::create(); inGrid->setTransform(math::Transform::createLinearTransform(voxel_size)); VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); int dim = GRID_DIM; for (int x = -dim; xindexToWorld(Vec3d(x,y,z)); inTree.setValue(Coord(x,y,z), VectorTree::ValueType(location.x(), location.y(), 0)); } } } Accessor inAccessor = inGrid->getConstAccessor(); CPPUNIT_ASSERT(!inTree.empty()); CPPUNIT_ASSERT_EQUAL(math::Pow3(2*dim), int(inTree.activeVoxelCount())); --dim;//ignore boundary divergence // test with a map // test with a map math::AffineMap map(voxel_size*math::Mat3d::identity()); math::UniformScaleMap uniform_map(voxel_size); math::UniformScaleTranslateMap uniform_translate_map(voxel_size, Vec3d(0,0,0)); for (int x = -dim; x #include #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestGrid: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestGrid); CPPUNIT_TEST(testGridRegistry); CPPUNIT_TEST(testConstPtr); CPPUNIT_TEST(testGetGrid); CPPUNIT_TEST(testIsType); CPPUNIT_TEST(testTransform); CPPUNIT_TEST(testCopyGrid); CPPUNIT_TEST_SUITE_END(); void testGridRegistry(); void testConstPtr(); void testGetGrid(); void testIsType(); void testTransform(); void testCopyGrid(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGrid); //////////////////////////////////////// class ProxyTree: public openvdb::TreeBase { public: typedef int ValueType; typedef void ValueAllCIter; typedef void ValueAllIter; typedef void ValueOffCIter; typedef void ValueOffIter; typedef void ValueOnCIter; typedef void ValueOnIter; typedef openvdb::TreeBase::Ptr TreeBasePtr; typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; static const openvdb::Index DEPTH; static const ValueType backg; ProxyTree() {} ProxyTree(const ValueType&) {} virtual ~ProxyTree() {} static const openvdb::Name& treeType() { static const openvdb::Name s("proxy"); return s; } virtual const openvdb::Name& type() const { return treeType(); } virtual openvdb::Name valueType() const { return "proxy"; } const ValueType& background() const { return backg; } virtual TreeBasePtr copy() const { return TreeBasePtr(new ProxyTree(*this)); } virtual void readTopology(std::istream& is, bool = false) { is.seekg(0, std::ios::beg); } virtual void writeTopology(std::ostream& os, bool = false) const { os.seekp(0); } virtual void readBuffers(std::istream& is, bool /*saveFloatAsHalf*/=false) { is.seekg(0); } virtual void writeBuffers(std::ostream& os, bool /*saveFloatAsHalf*/=false) const { os.seekp(0, std::ios::beg); } bool empty() const { return true; } void clear() {} void prune(const ValueType& = 0) {} virtual void getIndexRange(openvdb::CoordBBox&) const {} virtual bool evalLeafBoundingBox(openvdb::CoordBBox& bbox) const { bbox.min() = bbox.max() = openvdb::Coord(0, 0, 0); return false; } virtual bool evalActiveVoxelBoundingBox(openvdb::CoordBBox& bbox) const { bbox.min() = bbox.max() = openvdb::Coord(0, 0, 0); return false; } virtual bool evalActiveVoxelDim(openvdb::Coord& dim) const { dim = openvdb::Coord(0, 0, 0); return false; } virtual bool evalLeafDim(openvdb::Coord& dim) const { dim = openvdb::Coord(0, 0, 0); return false; } virtual openvdb::Index treeDepth() const { return 0; } virtual openvdb::Index leafCount() const { return 0; } virtual openvdb::Index nonLeafCount() const { return 0; } virtual openvdb::Index64 activeVoxelCount() const { return 0UL; } virtual openvdb::Index64 inactiveVoxelCount() const { return 0UL; } virtual openvdb::Index64 activeLeafVoxelCount() const { return 0UL; } virtual openvdb::Index64 inactiveLeafVoxelCount() const { return 0UL; } }; const openvdb::Index ProxyTree::DEPTH = 0; const ProxyTree::ValueType ProxyTree::backg = 0; typedef openvdb::Grid ProxyGrid; //////////////////////////////////////// void TestGrid::testGridRegistry() { using namespace openvdb::tree; typedef Tree, 2> > > TreeType; typedef openvdb::Grid GridType; openvdb::GridBase::clearRegistry(); CPPUNIT_ASSERT(!GridType::isRegistered()); GridType::registerGrid(); CPPUNIT_ASSERT(GridType::isRegistered()); CPPUNIT_ASSERT_THROW(GridType::registerGrid(), openvdb::KeyError); GridType::unregisterGrid(); CPPUNIT_ASSERT(!GridType::isRegistered()); CPPUNIT_ASSERT_NO_THROW(GridType::unregisterGrid()); CPPUNIT_ASSERT(!GridType::isRegistered()); CPPUNIT_ASSERT_NO_THROW(GridType::registerGrid()); CPPUNIT_ASSERT(GridType::isRegistered()); openvdb::GridBase::clearRegistry(); } void TestGrid::testConstPtr() { using namespace openvdb; GridBase::ConstPtr constgrid = ProxyGrid::create(); CPPUNIT_ASSERT_EQUAL(Name("proxy"), constgrid->type()); } void TestGrid::testGetGrid() { using namespace openvdb; GridBase::Ptr grid = FloatGrid::create(/*bg=*/0.0); GridBase::ConstPtr constGrid = grid; CPPUNIT_ASSERT(grid->baseTreePtr()); CPPUNIT_ASSERT(!gridPtrCast(grid)); CPPUNIT_ASSERT(!gridPtrCast(grid)); CPPUNIT_ASSERT(gridConstPtrCast(constGrid)); CPPUNIT_ASSERT(!gridConstPtrCast(constGrid)); } void TestGrid::testIsType() { using namespace openvdb; GridBase::Ptr grid = FloatGrid::create(); CPPUNIT_ASSERT(grid->isType()); CPPUNIT_ASSERT(!grid->isType()); } void TestGrid::testTransform() { ProxyGrid grid; // Verify that the grid has a valid default transform. CPPUNIT_ASSERT(grid.transformPtr()); // Verify that a null transform pointer is not allowed. CPPUNIT_ASSERT_THROW(grid.setTransform(openvdb::math::Transform::Ptr()), openvdb::ValueError); grid.setTransform(openvdb::math::Transform::createLinearTransform()); CPPUNIT_ASSERT(grid.transformPtr()); // Verify that calling Transform-related Grid methods (Grid::voxelSize(), etc.) // is the same as calling those methods on the Transform. CPPUNIT_ASSERT(grid.transform().voxelSize().eq(grid.voxelSize())); CPPUNIT_ASSERT(grid.transform().voxelSize(openvdb::Vec3d(0.1, 0.2, 0.3)).eq( grid.voxelSize(openvdb::Vec3d(0.1, 0.2, 0.3)))); CPPUNIT_ASSERT(grid.transform().indexToWorld(openvdb::Vec3d(0.1, 0.2, 0.3)).eq( grid.indexToWorld(openvdb::Vec3d(0.1, 0.2, 0.3)))); CPPUNIT_ASSERT(grid.transform().indexToWorld(openvdb::Coord(1, 2, 3)).eq( grid.indexToWorld(openvdb::Coord(1, 2, 3)))); CPPUNIT_ASSERT(grid.transform().worldToIndex(openvdb::Vec3d(0.1, 0.2, 0.3)).eq( grid.worldToIndex(openvdb::Vec3d(0.1, 0.2, 0.3)))); } void TestGrid::testCopyGrid() { using namespace openvdb; // set up a grid const float fillValue1=5.0f; FloatGrid::Ptr grid1 = createGrid(/*bg=*/fillValue1); FloatTree& tree1 = grid1->tree(); tree1.setValue(Coord(-10,40,845), 3.456f); tree1.setValue(Coord(1,-50,-8), 1.0f); // create a new grid, copying the first grid GridBase::Ptr grid2 = grid1->deepCopy(); // cast down to the concrete type to query values FloatTree& tree2 = gridPtrCast(grid2)->tree(); // compare topology CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); // trees should be equal ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, tree2.getValue(Coord(1,2,3))); ASSERT_DOUBLES_EXACTLY_EQUAL(3.456f, tree2.getValue(Coord(-10,40,845))); ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, tree2.getValue(Coord(1,-50,-8))); // change 1 value in tree2 Coord changeCoord(1, -500, -8); tree2.setValue(changeCoord, 1.0f); // topology should no longer match CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); // query changed value and make sure it's different between trees ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, tree1.getValue(changeCoord)); ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, tree2.getValue(changeCoord)); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestName.cc0000644000000000000000000000662012252453157015131 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestName : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestName); CPPUNIT_TEST(test); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testMultipleIO); CPPUNIT_TEST_SUITE_END(); void test(); void testIO(); void testMultipleIO(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestName); void TestName::test() { using namespace openvdb; Name name; Name name2("something"); Name name3 = std::string("something2"); name = "something"; CPPUNIT_ASSERT(name == name2); CPPUNIT_ASSERT(name != name3); CPPUNIT_ASSERT(name != Name("testing")); CPPUNIT_ASSERT(name == Name("something")); } void TestName::testIO() { using namespace openvdb; Name name("some name that i made up"); std::ostringstream ostr(std::ios_base::binary); openvdb::writeString(ostr, name); name = "some other name"; CPPUNIT_ASSERT(name == Name("some other name")); std::istringstream istr(ostr.str(), std::ios_base::binary); name = openvdb::readString(istr); CPPUNIT_ASSERT(name == Name("some name that i made up")); } void TestName::testMultipleIO() { using namespace openvdb; Name name("some name that i made up"); Name name2("something else"); std::ostringstream ostr(std::ios_base::binary); openvdb::writeString(ostr, name); openvdb::writeString(ostr, name2); std::istringstream istr(ostr.str(), std::ios_base::binary); Name n = openvdb::readString(istr), n2 = openvdb::readString(istr); CPPUNIT_ASSERT(name == n); CPPUNIT_ASSERT(name2 == n2); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestHermite.cc0000644000000000000000000002221512252453157015644 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include class TestHermite: public CppUnit::TestFixture { public: CPPUNIT_TEST_SUITE(TestHermite); CPPUNIT_TEST(testAccessors); CPPUNIT_TEST(testComparisons); CPPUNIT_TEST(testIO); CPPUNIT_TEST_SUITE_END(); void testAccessors(); void testComparisons(); void testIO(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestHermite); //////////////////////////////////////// void TestHermite::testAccessors() { using namespace openvdb; using namespace openvdb::math; const double offsetTol = 0.001; const double normalTol = 0.015; ////////// // Check initial values. Hermite hermite; CPPUNIT_ASSERT(!hermite); CPPUNIT_ASSERT(!hermite.isInside()); CPPUNIT_ASSERT(!hermite.hasOffsetX()); CPPUNIT_ASSERT(!hermite.hasOffsetY()); CPPUNIT_ASSERT(!hermite.hasOffsetZ()); ////////// // Check set & get // x Vec3s n0(1.0, 0.0, 0.0); hermite.setX(0.5f, n0); CPPUNIT_ASSERT(hermite.hasOffsetX()); CPPUNIT_ASSERT(!hermite.hasOffsetY()); CPPUNIT_ASSERT(!hermite.hasOffsetZ()); float offset = hermite.getOffsetX(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, offset, offsetTol); Vec3s n1 = hermite.getNormalX(); CPPUNIT_ASSERT(n0.eq(n1, normalTol)); // y n0 = Vec3s(0.0, 1.0, 0.0); hermite.setY(0.3f, n0); CPPUNIT_ASSERT(hermite.hasOffsetX()); CPPUNIT_ASSERT(hermite.hasOffsetY()); CPPUNIT_ASSERT(!hermite.hasOffsetZ()); offset = hermite.getOffsetY(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.3, offset, offsetTol); n1 = hermite.getNormalY(); CPPUNIT_ASSERT(n0.eq(n1, normalTol)); // z n0 = Vec3s(0.0, 0.0, 1.0); hermite.setZ(0.75f, n0); CPPUNIT_ASSERT(hermite.hasOffsetX()); CPPUNIT_ASSERT(hermite.hasOffsetY()); CPPUNIT_ASSERT(hermite.hasOffsetZ()); offset = hermite.getOffsetZ(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.75, offset, offsetTol); n1 = hermite.getNormalZ(); CPPUNIT_ASSERT(n0.eq(n1, normalTol)); ////////// // Check inside/outside state hermite.setIsInside(true); CPPUNIT_ASSERT(hermite.isInside()); hermite.clear(); CPPUNIT_ASSERT(!hermite); CPPUNIT_ASSERT(!hermite.isInside()); CPPUNIT_ASSERT(!hermite.hasOffsetX()); CPPUNIT_ASSERT(!hermite.hasOffsetY()); CPPUNIT_ASSERT(!hermite.hasOffsetZ()); n0 = Vec3s(0.0, 0.0, -1.0); hermite.setZ(0.15f, n0); CPPUNIT_ASSERT(!hermite.hasOffsetX()); CPPUNIT_ASSERT(!hermite.hasOffsetY()); CPPUNIT_ASSERT(hermite.hasOffsetZ()); offset = hermite.getOffsetZ(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.15, offset, offsetTol); n1 = hermite.getNormalZ(); CPPUNIT_ASSERT(n0.eq(n1, normalTol)); hermite.setIsInside(true); CPPUNIT_ASSERT(hermite.isInside()); CPPUNIT_ASSERT(hermite); } //////////////////////////////////////// void TestHermite::testComparisons() { using namespace openvdb; using namespace openvdb::math; const double offsetTol = 0.001; const double normalTol = 0.015; ////////// Vec3s offsets(0.50, 0.82, 0.14); Vec3s nX(1.0, 0.0, 0.0); Vec3s nY(0.0, 1.0, 0.0); Vec3s nZ(0.0, 0.0, 1.0); Hermite A, B; A.setX(offsets[0], nX); A.setY(offsets[1], nY); A.setZ(offsets[2], nZ); A.setIsInside(true); B = A; CPPUNIT_ASSERT(B); CPPUNIT_ASSERT(B == A); CPPUNIT_ASSERT(!(B != A)); CPPUNIT_ASSERT(B.isInside()); CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[0], B.getOffsetX(), offsetTol); CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[1], B.getOffsetY(), offsetTol); CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[2], B.getOffsetZ(), offsetTol); CPPUNIT_ASSERT(B.getNormalX().eq(nX, normalTol)); CPPUNIT_ASSERT(B.getNormalY().eq(nY, normalTol)); CPPUNIT_ASSERT(B.getNormalZ().eq(nZ, normalTol)); CPPUNIT_ASSERT(!A.isLessX(B)); CPPUNIT_ASSERT(!A.isLessY(B)); CPPUNIT_ASSERT(!A.isLessZ(B)); CPPUNIT_ASSERT(!A.isGreaterX(B)); CPPUNIT_ASSERT(!A.isGreaterY(B)); CPPUNIT_ASSERT(!A.isGreaterZ(B)); B = -B; CPPUNIT_ASSERT(B); CPPUNIT_ASSERT(B != A); CPPUNIT_ASSERT(!B.isInside()); CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[0], B.getOffsetX(), offsetTol); CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[1], B.getOffsetY(), offsetTol); CPPUNIT_ASSERT_DOUBLES_EQUAL(offsets[2], B.getOffsetZ(), offsetTol); CPPUNIT_ASSERT(B.getNormalX().eq(-nX, normalTol)); CPPUNIT_ASSERT(B.getNormalY().eq(-nY, normalTol)); CPPUNIT_ASSERT(B.getNormalZ().eq(-nZ, normalTol)); CPPUNIT_ASSERT(A.isLessX(B)); CPPUNIT_ASSERT(A.isLessY(B)); CPPUNIT_ASSERT(A.isLessZ(B)); CPPUNIT_ASSERT(!A.isGreaterX(B)); CPPUNIT_ASSERT(!A.isGreaterY(B)); CPPUNIT_ASSERT(!A.isGreaterZ(B)); ////////// // min / max Hermite C = min(A, B); CPPUNIT_ASSERT(C); CPPUNIT_ASSERT(C == A); CPPUNIT_ASSERT(C != B); C = max(A, B); CPPUNIT_ASSERT(C); CPPUNIT_ASSERT(C != A); CPPUNIT_ASSERT(C == B); A.clear(); B.clear(); C.clear(); A.setX(offsets[0], nX); A.setY(offsets[1], nY); A.setZ(offsets[2], nZ); A.setIsInside(true); B.setX(offsets[2], nX); B.setY(offsets[0], nY); B.setZ(offsets[1], nZ); B.setIsInside(true); C = max(A, B); CPPUNIT_ASSERT(C); CPPUNIT_ASSERT(C != A); CPPUNIT_ASSERT(C != B); CPPUNIT_ASSERT(C.isGreaterX(A)); CPPUNIT_ASSERT(C.isGreaterY(A)); CPPUNIT_ASSERT(C.isGreaterZ(B)); C = min(A, B); CPPUNIT_ASSERT(C); CPPUNIT_ASSERT(C != A); CPPUNIT_ASSERT(C != B); CPPUNIT_ASSERT(C.isLessX(B)); CPPUNIT_ASSERT(C.isLessY(B)); CPPUNIT_ASSERT(C.isLessZ(A)); A.clear(); B.clear(); C.clear(); A.setY(offsets[1], nY); A.setZ(offsets[2], nZ); B.setX(offsets[2], nX); B.setY(offsets[0], nY); C = min(A, B); CPPUNIT_ASSERT(C); CPPUNIT_ASSERT(C != A); CPPUNIT_ASSERT(C != B); CPPUNIT_ASSERT(!C.hasOffsetX()); CPPUNIT_ASSERT(C.hasOffsetY()); CPPUNIT_ASSERT(!C.hasOffsetZ()); CPPUNIT_ASSERT(C.isLessY(A)); C = max(A, B); CPPUNIT_ASSERT(C); CPPUNIT_ASSERT(C != A); CPPUNIT_ASSERT(C != B); CPPUNIT_ASSERT(C.hasOffsetX()); CPPUNIT_ASSERT(C.hasOffsetY()); CPPUNIT_ASSERT(C.hasOffsetZ()); CPPUNIT_ASSERT(C.isGreaterX(A)); CPPUNIT_ASSERT(C.isGreaterY(B)); CPPUNIT_ASSERT(C.isGreaterZ(B)); } //////////////////////////////////////// void TestHermite::testIO() { using namespace openvdb; using namespace openvdb::math; std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); Hermite A, B; A.setX(0.50f, Vec3s(1.0, 0.0, 0.0)); A.setY(0.82f, Vec3s(0.0, 1.0, 0.0)); A.setZ(0.14f, Vec3s(0.0, 0.0, 1.0)); A.setIsInside(true); CPPUNIT_ASSERT(A); CPPUNIT_ASSERT(!B); A.write(ss); B.read(ss); CPPUNIT_ASSERT(A); CPPUNIT_ASSERT(B); CPPUNIT_ASSERT(A == B); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestCurl.cc0000644000000000000000000005372112252453157015162 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1e-6); namespace { const int GRID_DIM = 10; } class TestCurl: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestCurl); CPPUNIT_TEST(testISCurl); // Gradient in Index Space CPPUNIT_TEST(testISCurlStencil); CPPUNIT_TEST(testWSCurl); // Gradient in World Space CPPUNIT_TEST(testWSCurlStencil); CPPUNIT_TEST(testCurlTool); // Gradient tool CPPUNIT_TEST(testCurlMaskedTool); // Gradient tool CPPUNIT_TEST_SUITE_END(); void testISCurl(); void testISCurlStencil(); void testWSCurl(); void testWSCurlStencil(); void testCurlTool(); void testCurlMaskedTool(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestCurl); void TestCurl::testCurlTool() { using namespace openvdb; typedef VectorGrid::ConstAccessor Accessor; VectorGrid::Ptr inGrid = VectorGrid::create(); const VectorTree& inTree = inGrid->tree(); CPPUNIT_ASSERT(inTree.empty()); VectorGrid::Accessor inAccessor = inGrid->getAccessor(); int dim = GRID_DIM; for (int x = -dim; xactiveVoxelCount())); VectorGrid::ConstAccessor curlAccessor = curl_grid->getConstAccessor(); --dim;//ignore boundary curl vectors for (int x = -dim; x #include #include #include #include #include #include "util.h" // for unittest_util::makeSphere() class TestLeafBool: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestLeafBool); CPPUNIT_TEST(testGetValue); CPPUNIT_TEST(testSetValue); CPPUNIT_TEST(testProbeValue); CPPUNIT_TEST(testIterators); CPPUNIT_TEST(testIteratorGetCoord); CPPUNIT_TEST(testEquivalence); CPPUNIT_TEST(testGetOrigin); CPPUNIT_TEST(testNegativeIndexing); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testTopologyCopy); CPPUNIT_TEST(testMerge); CPPUNIT_TEST(testCombine); CPPUNIT_TEST(testBoolTree); //CPPUNIT_TEST(testFilter); CPPUNIT_TEST_SUITE_END(); void testGetValue(); void testSetValue(); void testProbeValue(); void testIterators(); void testEquivalence(); void testGetOrigin(); void testIteratorGetCoord(); void testNegativeIndexing(); void testIO(); void testTopologyCopy(); void testMerge(); void testCombine(); void testBoolTree(); //void testFilter(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafBool); typedef openvdb::tree::LeafNode LeafType; //////////////////////////////////////// void TestLeafBool::testGetValue() { { LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/false); for (openvdb::Index n = 0; n < leaf.numValues(); ++n) { CPPUNIT_ASSERT_EQUAL(false, leaf.getValue(leaf.offsetToLocalCoord(n))); } } { LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/true); for (openvdb::Index n = 0; n < leaf.numValues(); ++n) { CPPUNIT_ASSERT_EQUAL(true, leaf.getValue(leaf.offsetToLocalCoord(n))); } } } void TestLeafBool::testSetValue() { LeafType leaf(openvdb::Coord(0, 0, 0), false); openvdb::Coord xyz(0, 0, 0); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); leaf.setValueOn(xyz); CPPUNIT_ASSERT(leaf.isValueOn(xyz)); xyz.reset(7, 7, 7); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); leaf.setValueOn(xyz); CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOn(xyz, /*value=*/true); // value argument should be ignored CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOn(xyz, /*value=*/false); // value argument should be ignored CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOff(xyz); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); xyz.reset(2, 3, 6); leaf.setValueOn(xyz); CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOff(xyz); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); } void TestLeafBool::testProbeValue() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(1, 6, 5)); bool val; CPPUNIT_ASSERT(leaf.probeValue(openvdb::Coord(1, 6, 5), val)); CPPUNIT_ASSERT(!leaf.probeValue(openvdb::Coord(1, 6, 4), val)); } void TestLeafBool::testIterators() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(1, 2, 3)); leaf.setValueOn(openvdb::Coord(5, 2, 3)); openvdb::Coord sum; for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) { sum += iter.getCoord(); } CPPUNIT_ASSERT_EQUAL(openvdb::Coord(1 + 5, 2 + 2, 3 + 3), sum); openvdb::Index count = 0; for (LeafType::ValueOffIter iter = leaf.beginValueOff(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(leaf.numValues() - 2, count); count = 0; for (LeafType::ValueAllIter iter = leaf.beginValueAll(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), count); count = 0; for (LeafType::ChildOnIter iter = leaf.beginChildOn(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(openvdb::Index(0), count); count = 0; for (LeafType::ChildOffIter iter = leaf.beginChildOff(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(openvdb::Index(0), count); count = 0; for (LeafType::ChildAllIter iter = leaf.beginChildAll(); iter; ++iter, ++count); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), count); } void TestLeafBool::testIteratorGetCoord() { using namespace openvdb; LeafType leaf(openvdb::Coord(8, 8, 0)); CPPUNIT_ASSERT_EQUAL(Coord(8, 8, 0), leaf.origin()); leaf.setValueOn(Coord(1, 2, 3), -3); leaf.setValueOn(Coord(5, 2, 3), 4); LeafType::ValueOnIter iter = leaf.beginValueOn(); Coord xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(9, 10, 3), xyz); ++iter; xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(13, 10, 3), xyz); } void TestLeafBool::testEquivalence() { using openvdb::CoordBBox; using openvdb::Coord; LeafType leaf(Coord(0, 0, 0), false); // false and inactive LeafType leaf2(Coord(0, 0, 0), true); // true and inactive CPPUNIT_ASSERT(leaf != leaf2); leaf.fill(CoordBBox(Coord(0), Coord(LeafType::DIM - 1)), true, /*active=*/false); CPPUNIT_ASSERT(leaf == leaf2); // true and inactive leaf.setValuesOn(); // true and active leaf2.fill(CoordBBox(Coord(0), Coord(LeafType::DIM - 1)), false); // false and active CPPUNIT_ASSERT(leaf != leaf2); leaf.negate(); // false and active CPPUNIT_ASSERT(leaf == leaf2); // Set some values. leaf.setValueOn(Coord(0, 0, 0), true); leaf.setValueOn(Coord(0, 1, 0), true); leaf.setValueOn(Coord(1, 1, 0), true); leaf.setValueOn(Coord(1, 1, 2), true); leaf2.setValueOn(Coord(0, 0, 0), true); leaf2.setValueOn(Coord(0, 1, 0), true); leaf2.setValueOn(Coord(1, 1, 0), true); leaf2.setValueOn(Coord(1, 1, 2), true); CPPUNIT_ASSERT(leaf == leaf2); leaf2.setValueOn(Coord(0, 0, 1), true); CPPUNIT_ASSERT(leaf != leaf2); leaf2.setValueOff(Coord(0, 0, 1), false); CPPUNIT_ASSERT(leaf != leaf2); leaf2.setValueOn(Coord(0, 0, 1)); CPPUNIT_ASSERT(leaf == leaf2); } void TestLeafBool::testGetOrigin() { { LeafType leaf(openvdb::Coord(1, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(0, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(8, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(8, 1, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(1024, 1, 3), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(128*8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(1023, 1, 3), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(127*8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(512, 512, 512), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(512, 512, 512), leaf.origin()); } { LeafType leaf(openvdb::Coord(2, 52, 515), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 48, 512), leaf.origin()); } } void TestLeafBool::testNegativeIndexing() { using namespace openvdb; LeafType leaf(openvdb::Coord(-9, -2, -8)); CPPUNIT_ASSERT_EQUAL(Coord(-16, -8, -8), leaf.origin()); leaf.setValueOn(Coord(1, 2, 3)); leaf.setValueOn(Coord(5, 2, 3)); CPPUNIT_ASSERT(leaf.isValueOn(Coord(1, 2, 3))); CPPUNIT_ASSERT(leaf.isValueOn(Coord(5, 2, 3))); LeafType::ValueOnIter iter = leaf.beginValueOn(); Coord xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(-15, -6, -5), xyz); ++iter; xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(-11, -6, -5), xyz); } void TestLeafBool::testIO() { LeafType leaf(openvdb::Coord(1, 3, 5)); const openvdb::Coord origin = leaf.origin(); leaf.setValueOn(openvdb::Coord(0, 1, 0)); leaf.setValueOn(openvdb::Coord(1, 0, 0)); std::ostringstream ostr(std::ios_base::binary); leaf.writeBuffers(ostr); leaf.setValueOff(openvdb::Coord(0, 1, 0)); leaf.setValueOn(openvdb::Coord(0, 1, 1)); std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(istr); leaf.readBuffers(istr); CPPUNIT_ASSERT_EQUAL(origin, leaf.origin()); CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); } void TestLeafBool::testTopologyCopy() { using openvdb::Coord; // LeafNode having the same Log2Dim as LeafType typedef LeafType::ValueConverter::Type FloatLeafType; FloatLeafType fleaf(Coord(10, 20, 30), /*background=*/-1.0); std::set coords; for (openvdb::Index n = 0; n < fleaf.numValues(); n += 10) { Coord xyz = fleaf.offsetToGlobalCoord(n); fleaf.setValueOn(xyz, n); coords.insert(xyz); } LeafType leaf(fleaf, openvdb::TopologyCopy()); CPPUNIT_ASSERT_EQUAL(fleaf.onVoxelCount(), leaf.onVoxelCount()); CPPUNIT_ASSERT(leaf.hasSameTopology(&fleaf)); for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) { coords.erase(iter.getCoord()); } CPPUNIT_ASSERT(coords.empty()); } void TestLeafBool::testMerge() { LeafType leaf(openvdb::Coord(0, 0, 0)); for (openvdb::Index n = 0; n < leaf.numValues(); n += 10) { leaf.setValueOn(n); } CPPUNIT_ASSERT(!leaf.isValueMaskOn()); CPPUNIT_ASSERT(!leaf.isValueMaskOff()); bool val = false, active = false; CPPUNIT_ASSERT(!leaf.isConstant(val, active)); LeafType leaf2(leaf); leaf2.getValueMask().toggle(); CPPUNIT_ASSERT(!leaf2.isValueMaskOn()); CPPUNIT_ASSERT(!leaf2.isValueMaskOff()); val = active = false; CPPUNIT_ASSERT(!leaf2.isConstant(val, active)); leaf.merge(leaf2); CPPUNIT_ASSERT(leaf.isValueMaskOn()); CPPUNIT_ASSERT(!leaf.isValueMaskOff()); val = active = false; CPPUNIT_ASSERT(leaf.isConstant(val, active)); CPPUNIT_ASSERT(active); } void TestLeafBool::testCombine() { struct Local { static void op(openvdb::CombineArgs& args) { args.setResult(false); // result should be ignored args.setResultIsActive(args.aIsActive() ^ args.bIsActive()); } }; LeafType leaf(openvdb::Coord(0, 0, 0)); for (openvdb::Index n = 0; n < leaf.numValues(); n += 10) { leaf.setValueOn(n); } CPPUNIT_ASSERT(!leaf.isValueMaskOn()); CPPUNIT_ASSERT(!leaf.isValueMaskOff()); const LeafType::NodeMaskType savedMask = leaf.getValueMask(); OPENVDB_LOG_DEBUG_RUNTIME(leaf.str()); LeafType leaf2(leaf); for (openvdb::Index n = 0; n < leaf.numValues(); n += 4) { leaf2.setValueOn(n); } CPPUNIT_ASSERT(!leaf2.isValueMaskOn()); CPPUNIT_ASSERT(!leaf2.isValueMaskOff()); OPENVDB_LOG_DEBUG_RUNTIME(leaf2.str()); leaf.combine(leaf2, Local::op); OPENVDB_LOG_DEBUG_RUNTIME(leaf.str()); CPPUNIT_ASSERT(leaf.getValueMask() == (savedMask ^ leaf2.getValueMask())); } void TestLeafBool::testBoolTree() { using namespace openvdb; #if 0 FloatGrid::Ptr inGrid; FloatTree::Ptr inTree; { //io::File vdbFile("/work/rd/fx_tools/vdb_unittest/TestGridCombine::testCsg/large1.vdb2"); io::File vdbFile("/hosts/whitestar/usr/pic1/VDB/bunny_0256.vdb2"); vdbFile.open(); inGrid = gridPtrCast(vdbFile.readGrid("LevelSet")); CPPUNIT_ASSERT(inGrid.get() != NULL); inTree = inGrid->treePtr(); CPPUNIT_ASSERT(inTree.get() != NULL); } #else FloatGrid::Ptr inGrid = FloatGrid::create(); CPPUNIT_ASSERT(inGrid.get() != NULL); FloatTree& inTree = inGrid->tree(); inGrid->setName("LevelSet"); unittest_util::makeSphere(/*dim =*/Coord(128), /*center=*/Vec3f(0, 0, 0), /*radius=*/5, *inGrid, unittest_util::SPHERE_DENSE); #endif const Index64 floatTreeMem = inTree.memUsage(), floatTreeLeafCount = inTree.leafCount(), floatTreeVoxelCount = inTree.activeVoxelCount(); TreeBase::Ptr outTree(new BoolTree(inTree, false, true, TopologyCopy())); CPPUNIT_ASSERT(outTree.get() != NULL); BoolGrid::Ptr outGrid = BoolGrid::create(*inGrid); // copy transform and metadata outGrid->setTree(outTree); outGrid->setName("Boolean"); const Index64 boolTreeMem = outTree->memUsage(), boolTreeLeafCount = outTree->leafCount(), boolTreeVoxelCount = outTree->activeVoxelCount(); #if 0 GridPtrVec grids; grids.push_back(inGrid); grids.push_back(outGrid); io::File vdbFile("/tmp/bool_tree.vdb2"); vdbFile.write(grids); vdbFile.close(); #endif CPPUNIT_ASSERT_EQUAL(floatTreeLeafCount, boolTreeLeafCount); CPPUNIT_ASSERT_EQUAL(floatTreeVoxelCount, boolTreeVoxelCount); //std::cerr << "\nboolTree mem=" << boolTreeMem << " bytes" << std::endl; //std::cerr << "floatTree mem=" << floatTreeMem << " bytes" << std::endl; // Considering only voxel buffer memory usage, the BoolTree would be expected // to use (2 mask bits/voxel / ((32 value bits + 1 mask bit)/voxel)) = ~1/16 // as much memory as the FloatTree. Considering total memory usage, verify that // the BoolTree is no more than 1/10 the size of the FloatTree. CPPUNIT_ASSERT(boolTreeMem * 10 <= floatTreeMem); } // void // TestLeafBool::testFilter() // { // using namespace openvdb; // BoolGrid::Ptr grid = BoolGrid::create(); // CPPUNIT_ASSERT(grid.get() != NULL); // BoolTree::Ptr tree = grid->treePtr(); // CPPUNIT_ASSERT(tree.get() != NULL); // grid->setName("filtered"); // unittest_util::makeSphere(/*dim=*/Coord(32), // /*ctr=*/Vec3f(0, 0, 0), // /*radius=*/10, // *grid, unittest_util::SPHERE_DENSE); // BoolTree::Ptr copyOfTree(new BoolTree(*tree)); // BoolGrid::Ptr copyOfGrid = BoolGrid::create(copyOfTree); // copyOfGrid->setName("original"); // tools::Filter filter(*grid); // filter.offset(1); // #if 0 // GridPtrVec grids; // grids.push_back(copyOfGrid); // grids.push_back(grid); // io::File vdbFile("/tmp/TestLeafBool::testFilter.vdb2"); // vdbFile.write(grids); // vdbFile.close(); // #endif // // Verify that offsetting all active voxels by 1 (true) has no effect, // // since the active voxels were all true to begin with. // CPPUNIT_ASSERT(tree->hasSameTopology(*copyOfTree)); // } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestVolumeToMesh.cc0000644000000000000000000001177112252453157016643 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include class TestVolumeToMesh: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestVolumeToMesh); CPPUNIT_TEST(testAuxData); CPPUNIT_TEST(testConversion); CPPUNIT_TEST_SUITE_END(); void testAuxData(); void testConversion(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestVolumeToMesh); //////////////////////////////////////// void TestVolumeToMesh::testAuxData() { typedef openvdb::tree::Tree4::Type Tree543f; Tree543f::Ptr tree(new Tree543f(0)); // create one voxel with 3 upwind edges (that have a sign change) tree->setValue(openvdb::Coord(0,0,0), -1); tree->setValue(openvdb::Coord(1,0,0), 1); tree->setValue(openvdb::Coord(0,1,0), 1); tree->setValue(openvdb::Coord(0,0,1), 1); typedef openvdb::tree::LeafManager LeafManager; LeafManager leafs(*tree); CPPUNIT_ASSERT(openvdb::tools::internal::needsActiveVoxePadding(leafs, 0.0, 1.0)); openvdb::tools::internal::SignData op(*tree, leafs, 0.0); op.run(); CPPUNIT_ASSERT(op.signTree()->activeVoxelCount() == 1); CPPUNIT_ASSERT(op.signTree()->activeVoxelCount() == op.idxTree()->activeVoxelCount()); int flags = int(op.signTree()->getValue(openvdb::Coord(0,0,0))); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::INSIDE)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::EDGES)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::XEDGE)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::YEDGE)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::ZEDGE)); tree->setValueOff(openvdb::Coord(0,0,1), -1); op.run(); CPPUNIT_ASSERT(op.signTree()->activeVoxelCount() == 1); CPPUNIT_ASSERT(op.signTree()->activeVoxelCount() == op.idxTree()->activeVoxelCount()); flags = int(op.signTree()->getValue(openvdb::Coord(0,0,0))); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::INSIDE)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::EDGES)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::XEDGE)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::internal::YEDGE)); CPPUNIT_ASSERT(!bool(flags & openvdb::tools::internal::ZEDGE)); } void TestVolumeToMesh::testConversion() { using namespace openvdb; typedef tree::Tree4::Type Tree543f; typedef Grid GridType; GridType::Ptr grid = createGrid(/*background=*/1); grid->fill(CoordBBox(Coord(0), Coord(7)), 0.0); grid->fill(CoordBBox(Coord(1), Coord(6)), -1.0); std::vector points; std::vector quads; std::vector triangles; openvdb::tools::volumeToMesh(*grid, points, quads); CPPUNIT_ASSERT(points.size() >= 4); CPPUNIT_ASSERT(!quads.empty()); /// @todo validate output points.clear(); quads.clear(); triangles.clear(); tools::volumeToMesh(*grid, points, triangles, quads, /*isovalue=*/0.01, /*adaptivity=*/0); CPPUNIT_ASSERT(points.size() >= 3); CPPUNIT_ASSERT(!triangles.empty() || !quads.empty()); /// @todo validate output } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestQuat.cc0000644000000000000000000002174412252453157015167 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include using namespace openvdb::math; class TestQuat: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE( TestQuat ); CPPUNIT_TEST( testConstructor ); CPPUNIT_TEST( testAxisAngle ); CPPUNIT_TEST( testOpPlus ); CPPUNIT_TEST( testOpMinus ); CPPUNIT_TEST( testOpMultiply ); CPPUNIT_TEST( testInvert ); CPPUNIT_TEST( testEulerAngles ); CPPUNIT_TEST_SUITE_END(); void testConstructor(); void testAxisAngle(); void testOpPlus(); void testOpMinus(); void testOpMultiply(); void testInvert(); void testEulerAngles(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestQuat); void TestQuat::testConstructor() { { Quat qq(1.23, 2.34, 3.45, 4.56); CPPUNIT_ASSERT( isExactlyEqual(qq.x(), 1.23f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.y(), 2.34f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.z(), 3.45f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.w(), 4.56f) ); } { float a[] = {1.23, 2.34, 3.45, 4.56}; Quat qq(a); CPPUNIT_ASSERT( isExactlyEqual(qq.x(), 1.23f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.y(), 2.34f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.z(), 3.45f) ); CPPUNIT_ASSERT( isExactlyEqual(qq.w(), 4.56f) ); } } void TestQuat::testAxisAngle() { float TOL = 1e-6; Quat q1(1 , 2 , 3 , 4 ); Quat q2(1.2, 2.3, 3.4, 4.5); Vec3s v(1, 2, 3); v.normalize(); float a = M_PI/4; Quat q(v,a); float b = q.angle(); Vec3s vv = q.axis(); CPPUNIT_ASSERT( isApproxEqual(a, b, TOL) ); CPPUNIT_ASSERT( v.eq(vv, TOL) ); q1.setAxisAngle(v,a); b = q1.angle(); vv = q1.axis(); CPPUNIT_ASSERT( isApproxEqual(a, b, TOL) ); CPPUNIT_ASSERT( v.eq(vv, TOL) ); } void TestQuat::testOpPlus() { Quat q1(1 , 2 , 3 , 4 ); Quat q2(1.2, 2.3, 3.4, 4.5); Quat q = q1 + q2; float x=q1.x()+q2.x(), y=q1.y()+q2.y(), z=q1.z()+q2.z(), w=q1.w()+q2.w(); CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); q = q1; q += q2; CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); q.add(q1,q2); CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); } void TestQuat::testOpMinus() { Quat q1(1 , 2 , 3 , 4 ); Quat q2(1.2, 2.3, 3.4, 4.5); Quat q = q1 - q2; float x=q1.x()-q2.x(), y=q1.y()-q2.y(), z=q1.z()-q2.z(), w=q1.w()-q2.w(); CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); q = q1; q -= q2; CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); q.sub(q1,q2); CPPUNIT_ASSERT( isExactlyEqual(q.x(), x) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), y) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), z) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), w) ); } void TestQuat::testOpMultiply() { Quat q1(1 , 2 , 3 , 4 ); Quat q2(1.2, 2.3, 3.4, 4.5); Quat q = q1 * 1.5f; CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); q = q1; q *= 1.5f; CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); q.scale(1.5f, q1); CPPUNIT_ASSERT( isExactlyEqual(q.x(), float(1.5f)*q1.x()) ); CPPUNIT_ASSERT( isExactlyEqual(q.y(), float(1.5f)*q1.y()) ); CPPUNIT_ASSERT( isExactlyEqual(q.z(), float(1.5f)*q1.z()) ); CPPUNIT_ASSERT( isExactlyEqual(q.w(), float(1.5f)*q1.w()) ); } void TestQuat::testInvert() { float TOL = 1e-6; Quat q1(1 , 2 , 3 , 4 ); Quat q2(1.2, 2.3, 3.4, 4.5); q1 = q2; q2 = q2.inverse(); Quat q = q1*q2; CPPUNIT_ASSERT( q.eq( Quat(0,0,0,1), TOL ) ); q1.normalize(); q2 = q1.conjugate(); q = q1*q2; CPPUNIT_ASSERT( q.eq( Quat(0,0,0,1), TOL ) ); } void TestQuat::testEulerAngles() { { double TOL = 1e-7; Mat4d rx, ry, rz; const double angle1 = 20. * M_PI / 180.; const double angle2 = 64. * M_PI / 180.; const double angle3 = 125. *M_PI / 180.; rx.setToRotation(Vec3d(1,0,0), angle1); ry.setToRotation(Vec3d(0,1,0), angle2); rz.setToRotation(Vec3d(0,0,1), angle3); Mat4d r = rx * ry * rz; const Quat rot(r.getMat3()); Vec3d result = rot.eulerAngles(ZYX_ROTATION); rx.setToRotation(Vec3d(1,0,0), result[0]); ry.setToRotation(Vec3d(0,1,0), result[1]); rz.setToRotation(Vec3d(0,0,1), result[2]); Mat4d rtest = rx * ry * rz; CPPUNIT_ASSERT(r.eq(rtest, TOL)); } { double TOL = 1e-7; Mat4d rx, ry, rz; const double angle1 = 20. * M_PI / 180.; const double angle2 = 64. * M_PI / 180.; const double angle3 = 125. *M_PI / 180.; rx.setToRotation(Vec3d(1,0,0), angle1); ry.setToRotation(Vec3d(0,1,0), angle2); rz.setToRotation(Vec3d(0,0,1), angle3); Mat4d r = rz * ry * rx; const Quat rot(r.getMat3()); Vec3d result = rot.eulerAngles(XYZ_ROTATION); rx.setToRotation(Vec3d(1,0,0), result[0]); ry.setToRotation(Vec3d(0,1,0), result[1]); rz.setToRotation(Vec3d(0,0,1), result[2]); Mat4d rtest = rz * ry * rx; CPPUNIT_ASSERT(r.eq(rtest, TOL)); } { double TOL = 1e-7; Mat4d rx, ry, rz; const double angle1 = 20. * M_PI / 180.; const double angle2 = 64. * M_PI / 180.; const double angle3 = 125. *M_PI / 180.; rx.setToRotation(Vec3d(1,0,0), angle1); ry.setToRotation(Vec3d(0,1,0), angle2); rz.setToRotation(Vec3d(0,0,1), angle3); Mat4d r = rz * rx * ry; const Quat rot(r.getMat3()); Vec3d result = rot.eulerAngles(YXZ_ROTATION); rx.setToRotation(Vec3d(1,0,0), result[0]); ry.setToRotation(Vec3d(0,1,0), result[1]); rz.setToRotation(Vec3d(0,0,1), result[2]); Mat4d rtest = rz * rx * ry; CPPUNIT_ASSERT(r.eq(rtest, TOL)); } { const Quat rot(X_AXIS, 1.0); Vec3s result = rot.eulerAngles(XZY_ROTATION); CPPUNIT_ASSERT_EQUAL(result, Vec3s(1,0,0)); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMath.cc0000644000000000000000000001706312252453157015145 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include class TestMath: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestMath); CPPUNIT_TEST(testAll); CPPUNIT_TEST(testRandomInt); CPPUNIT_TEST(testRandom01); CPPUNIT_TEST(testMinMaxIndex); CPPUNIT_TEST_SUITE_END(); void testAll(); void testRandomInt(); void testRandom01(); void testMinMaxIndex(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMath); // This suite of tests obviously needs to be expanded! void TestMath::testAll() { using namespace openvdb; {// Sign CPPUNIT_ASSERT_EQUAL(math::Sign( 3 ), 1); CPPUNIT_ASSERT_EQUAL(math::Sign(-1.0 ),-1); CPPUNIT_ASSERT_EQUAL(math::Sign( 0.0f), 0); } {// SignChange CPPUNIT_ASSERT( math::SignChange( -1, 1)); CPPUNIT_ASSERT(!math::SignChange( 0.0f, 0.5f)); CPPUNIT_ASSERT( math::SignChange( 0.0f,-0.5f)); CPPUNIT_ASSERT( math::SignChange(-0.1, 0.0001)); } {// isApproxZero CPPUNIT_ASSERT( math::isApproxZero( 0.0f)); CPPUNIT_ASSERT(!math::isApproxZero( 9.0e-6f)); CPPUNIT_ASSERT(!math::isApproxZero(-9.0e-6f)); CPPUNIT_ASSERT( math::isApproxZero( 9.0e-9f)); CPPUNIT_ASSERT( math::isApproxZero(-9.0e-9f)); CPPUNIT_ASSERT( math::isApproxZero( 0.01, 0.1)); } {// Cbrt const double a = math::Cbrt(3.0); CPPUNIT_ASSERT(math::isApproxEqual(a*a*a, 3.0, 1e-6)); } } void TestMath::testRandomInt() { using openvdb::math::RandomInt; int imin = -3, imax = 11; RandomInt rnd(/*seed=*/42, imin, imax); // Generate a sequence of random integers and verify that they all fall // in the interval [imin, imax]. std::vector seq(100); for (int i = 0; i < 100; ++i) { seq[i] = rnd(); CPPUNIT_ASSERT(seq[i] >= imin); CPPUNIT_ASSERT(seq[i] <= imax); } // Verify that generators with the same seed produce the same sequence. rnd = RandomInt(42, imin, imax); for (int i = 0; i < 100; ++i) { int r = rnd(); CPPUNIT_ASSERT_EQUAL(seq[i], r); } // Verify that generators with different seeds produce different sequences. rnd = RandomInt(101, imin, imax); std::vector newSeq(100); for (int i = 0; i < 100; ++i) newSeq[i] = rnd(); CPPUNIT_ASSERT(newSeq != seq); // Temporarily change the range. imin = -5; imax = 6; for (int i = 0; i < 100; ++i) { int r = rnd(imin, imax); CPPUNIT_ASSERT(r >= imin); CPPUNIT_ASSERT(r <= imax); } // Verify that the range change was temporary. imin = -3; imax = 11; for (int i = 0; i < 100; ++i) { int r = rnd(); CPPUNIT_ASSERT(r >= imin); CPPUNIT_ASSERT(r <= imax); } // Permanently change the range. imin = -5; imax = 6; rnd.setRange(imin, imax); for (int i = 0; i < 100; ++i) { int r = rnd(); CPPUNIT_ASSERT(r >= imin); CPPUNIT_ASSERT(r <= imax); } // Verify that it is OK to specify imin > imax (they are automatically swapped). imin = 5; imax = -6; rnd.setRange(imin, imax); rnd = RandomInt(42, imin, imax); } void TestMath::testRandom01() { using openvdb::math::Random01; using openvdb::math::isApproxEqual; Random01 rnd(/*seed=*/42); // Generate a sequence of random numbers and verify that they all fall // in the interval [0, 1). std::vector seq(100); for (int i = 0; i < 100; ++i) { seq[i] = rnd(); CPPUNIT_ASSERT(seq[i] >= 0.0); CPPUNIT_ASSERT(seq[i] < 1.0); } // Verify that generators with the same seed produce the same sequence. rnd = Random01(42); for (int i = 0; i < 100; ++i) { CPPUNIT_ASSERT_DOUBLES_EQUAL(seq[i], rnd(), /*tolerance=*/1.0e-6); } // Verify that generators with different seeds produce different sequences. rnd = Random01(101); bool allEqual = true; for (int i = 0; allEqual && i < 100; ++i) { if (!isApproxEqual(rnd(), seq[i])) allEqual = false; } CPPUNIT_ASSERT(!allEqual); } void TestMath::testMinMaxIndex() { const openvdb::Vec3R a(-1, 2, 0); CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MinIndex(a)); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(a)); const openvdb::Vec3R b(-1, -2, 0); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(b)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(b)); const openvdb::Vec3R c(5, 2, 1); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(c)); CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MaxIndex(c)); const openvdb::Vec3R d(0, 0, 1); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(d)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(d)); const openvdb::Vec3R e(1, 0, 0); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(e)); CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MaxIndex(e)); const openvdb::Vec3R f(0, 1, 0); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(f)); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(f)); const openvdb::Vec3R g(1, 1, 0); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(g)); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MaxIndex(g)); const openvdb::Vec3R h(1, 0, 1); CPPUNIT_ASSERT_EQUAL(size_t(1), openvdb::math::MinIndex(h)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(h)); const openvdb::Vec3R i(0, 1, 1); CPPUNIT_ASSERT_EQUAL(size_t(0), openvdb::math::MinIndex(i)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(i)); const openvdb::Vec3R j(1, 1, 1); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MinIndex(j)); CPPUNIT_ASSERT_EQUAL(size_t(2), openvdb::math::MaxIndex(j)); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGridIO.cc0000644000000000000000000002170012252453157015362 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestGridIO: public CppUnit::TestCase { public: typedef openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< openvdb::tree::LeafNode, 3>, 4>, 5> > > Float5432Tree; typedef openvdb::Grid Float5432Grid; virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestGridIO); CPPUNIT_TEST(testReadAllBool); CPPUNIT_TEST(testReadAllFloat); CPPUNIT_TEST(testReadAllVec3S); CPPUNIT_TEST(testReadAllFloat5432); CPPUNIT_TEST(testReadAllHermite); CPPUNIT_TEST_SUITE_END(); void testReadAllBool() { readAllTest(); } void testReadAllFloat() { readAllTest(); } void testReadAllVec3S() { readAllTest(); } void testReadAllFloat5432() { Float5432Grid::registerGrid(); readAllTest(); } void testReadAllHermite() { readAllTest(); } private: template void readAllTest(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGridIO); //////////////////////////////////////// template void TestGridIO::readAllTest() { using namespace openvdb; typedef typename GridType::TreeType TreeType; typedef typename TreeType::Ptr TreePtr; typedef typename TreeType::ValueType ValueT; typedef typename TreeType::NodeCIter NodeCIter; const ValueT zero = zeroVal(); // For each level of the tree, compute a bit mask for use in converting // global coordinates to node origins for nodes at that level. // That is, node_origin = global_coordinates & mask[node_level]. std::vector mask; TreeType::getNodeLog2Dims(mask); const size_t height = mask.size(); for (size_t i = 0; i < height; ++i) { Index dim = 0; for (size_t j = i; j < height; ++j) dim += mask[j]; mask[i] = ~((1 << dim) - 1); } const Index childDim = 1 + ~(mask[0]); // Choose sample coordinate pairs (coord0, coord1) and (coord0, coord2) // that are guaranteed to lie in different children of the root node // (because they are separated by more than the child node dimension). const Coord coord0(0, 0, 0), coord1(int(1.1 * childDim), 0, 0), coord2(0, int(1.1 * childDim), 0); // Create trees. TreePtr tree1(new TreeType(zero + 1)), tree2(new TreeType(zero + 2)); // Set some values. tree1->setValue(coord0, zero + 5); tree1->setValue(coord1, zero + 6); tree2->setValue(coord0, zero + 10); tree2->setValue(coord2, zero + 11); // Create grids with trees and assign transforms. math::Transform::Ptr trans1(math::Transform::createLinearTransform(0.1)), trans2(math::Transform::createLinearTransform(0.1)); GridBase::Ptr grid1 = createGrid(tree1), grid2 = createGrid(tree2); grid1->setTransform(trans1); grid1->setName("density"); grid2->setTransform(trans2); grid2->setName("temperature"); OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN CPPUNIT_ASSERT_EQUAL(ValueT(zero + 5), tree1->getValue(coord0)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 6), tree1->getValue(coord1)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 10), tree2->getValue(coord0)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 11), tree2->getValue(coord2)); OPENVDB_NO_FP_EQUALITY_WARNING_END // count[d] is the number of nodes already visited at depth d. // There should be exactly two nodes at each depth (apart from the root). std::vector count(height, 0); // Verify that tree1 has correct node origins. for (NodeCIter iter = tree1->cbeginNode(); iter; ++iter) { const Index depth = iter.getDepth(); const Coord expected[2] = { coord0 & mask[depth], // origin of the first node at this depth coord1 & mask[depth] // origin of the second node at this depth }; CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); ++count[depth]; } // Verify that tree2 has correct node origins. count.assign(height, 0); // reset node counts for (NodeCIter iter = tree2->cbeginNode(); iter; ++iter) { const Index depth = iter.getDepth(); const Coord expected[2] = { coord0 & mask[depth], coord2 & mask[depth] }; CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); ++count[depth]; } MetaMap::Ptr meta(new MetaMap); meta->insertMeta("author", StringMetadata("Einstein")); meta->insertMeta("year", Int32Metadata(2009)); GridPtrVecPtr grids(new GridPtrVec); grids->push_back(grid1); grids->push_back(grid2); // Write grids and metadata out to a file. { io::File vdbfile("something.vdb2"); vdbfile.write(*grids, *meta); } meta.reset(); grids.reset(); io::File vdbfile("something.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile.getGrids(), openvdb::IoError); // file has not been opened // Read the grids back in. vdbfile.open(); CPPUNIT_ASSERT(vdbfile.isOpen()); grids = vdbfile.getGrids(); meta = vdbfile.getMetadata(); // Ensure we have the metadata. CPPUNIT_ASSERT(meta.get() != NULL); CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); CPPUNIT_ASSERT_EQUAL(std::string("Einstein"), meta->metaValue("author")); CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); // Ensure we got both grids. CPPUNIT_ASSERT(grids.get() != NULL); CPPUNIT_ASSERT_EQUAL(2, int(grids->size())); grid1.reset(); grid1 = findGridByName(*grids, "density"); CPPUNIT_ASSERT(grid1.get() != NULL); TreePtr density = gridPtrCast(grid1)->treePtr(); CPPUNIT_ASSERT(density.get() != NULL); grid2.reset(); grid2 = findGridByName(*grids, "temperature"); CPPUNIT_ASSERT(grid2.get() != NULL); TreePtr temperature = gridPtrCast(grid2)->treePtr(); CPPUNIT_ASSERT(temperature.get() != NULL); OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN CPPUNIT_ASSERT_EQUAL(ValueT(zero + 5), density->getValue(coord0)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 6), density->getValue(coord1)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 10), temperature->getValue(coord0)); CPPUNIT_ASSERT_EQUAL(ValueT(zero + 11), temperature->getValue(coord2)); OPENVDB_NO_FP_EQUALITY_WARNING_END // Check if we got the correct node origins. count.assign(height, 0); for (NodeCIter iter = density->cbeginNode(); iter; ++iter) { const Index depth = iter.getDepth(); const Coord expected[2] = { coord0 & mask[depth], coord1 & mask[depth] }; CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); ++count[depth]; } count.assign(height, 0); for (NodeCIter iter = temperature->cbeginNode(); iter; ++iter) { const Index depth = iter.getDepth(); const Coord expected[2] = { coord0 & mask[depth], coord2 & mask[depth] }; CPPUNIT_ASSERT_EQUAL(expected[count[depth]], iter.getCoord()); ++count[depth]; } vdbfile.close(); remove("something.vdb2"); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestQuadraticInterp.cc0000644000000000000000000003205512252453157017351 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file TestQuadraticInterp.cc #include #include #include #include // CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name // from the FixtureType. But if FixtureType is a templated type, the generated name // can become long and messy. This macro overrides the normal naming logic, // instead invoking FixtureType::testSuiteName(), which should be a static member // function that returns a std::string containing the suite name for the specific // template instantiation. #undef CPPUNIT_TESTNAMER_DECL #define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) namespace { // Absolute tolerance for floating-point equality comparisons const double TOLERANCE = 1.0e-5; } //////////////////////////////////////// template class TestQuadraticInterp: public CppUnit::TestCase { public: typedef typename GridType::ValueType ValueT; typedef typename GridType::Ptr GridPtr; struct TestVal { float x, y, z; ValueT expected; }; static std::string testSuiteName() { std::string name = openvdb::typeNameAsString(); if (!name.empty()) name[0] = ::toupper(name[0]); return "TestQuadraticInterp" + name; } CPPUNIT_TEST_SUITE(TestQuadraticInterp); CPPUNIT_TEST(test); CPPUNIT_TEST(testConstantValues); CPPUNIT_TEST(testFillValues); CPPUNIT_TEST(testNegativeIndices); CPPUNIT_TEST_SUITE_END(); void test(); void testConstantValues(); void testFillValues(); void testNegativeIndices(); private: void executeTest(const GridPtr&, const TestVal*, size_t numVals) const; /// Initialize an arbitrary ValueType from a scalar. static inline ValueT constValue(double d) { return ValueT(d); } /// Compare two numeric values for equality within an absolute tolerance. static inline bool relEq(const ValueT& v1, const ValueT& v2) { return fabs(v1 - v2) <= TOLERANCE; } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); CPPUNIT_TEST_SUITE_REGISTRATION(TestQuadraticInterp); //////////////////////////////////////// /// Specialization for Vec3s grids template<> inline openvdb::Vec3s TestQuadraticInterp::constValue(double d) { return openvdb::Vec3s(d, d, d); } /// Specialization for Vec3s grids template<> inline bool TestQuadraticInterp::relEq( const openvdb::Vec3s& v1, const openvdb::Vec3s& v2) { return v1.eq(v2, TOLERANCE); } /// Sample the given tree at various locations and assert if /// any of the sampled values don't match the expected values. template void TestQuadraticInterp::executeTest(const GridPtr& grid, const TestVal* testVals, size_t numVals) const { openvdb::tools::GridSampler interpolator(*grid); //openvdb::tools::QuadraticInterp interpolator(*tree); for (size_t i = 0; i < numVals; ++i) { const TestVal& val = testVals[i]; const ValueT actual = interpolator.sampleVoxel(val.x, val.y, val.z); if (!relEq(val.expected, actual)) { std::ostringstream ostr; ostr << std::setprecision(10) << "sampleVoxel(" << val.x << ", " << val.y << ", " << val.z << "): expected " << val.expected << ", got " << actual; CPPUNIT_FAIL(ostr.str()); } } } template void TestQuadraticInterp::test() { const ValueT one = constValue(1), two = constValue(2), three = constValue(3), four = constValue(4), fillValue = constValue(256); GridPtr grid(new GridType(fillValue)); typename GridType::TreeType& tree = grid->tree(); tree.setValue(openvdb::Coord(10, 10, 10), one); tree.setValue(openvdb::Coord(11, 10, 10), two); tree.setValue(openvdb::Coord(11, 11, 10), two); tree.setValue(openvdb::Coord(10, 11, 10), two); tree.setValue(openvdb::Coord( 9, 11, 10), two); tree.setValue(openvdb::Coord( 9, 10, 10), two); tree.setValue(openvdb::Coord( 9, 9, 10), two); tree.setValue(openvdb::Coord(10, 9, 10), two); tree.setValue(openvdb::Coord(11, 9, 10), two); tree.setValue(openvdb::Coord(10, 10, 11), three); tree.setValue(openvdb::Coord(11, 10, 11), three); tree.setValue(openvdb::Coord(11, 11, 11), three); tree.setValue(openvdb::Coord(10, 11, 11), three); tree.setValue(openvdb::Coord( 9, 11, 11), three); tree.setValue(openvdb::Coord( 9, 10, 11), three); tree.setValue(openvdb::Coord( 9, 9, 11), three); tree.setValue(openvdb::Coord(10, 9, 11), three); tree.setValue(openvdb::Coord(11, 9, 11), three); tree.setValue(openvdb::Coord(10, 10, 9), four); tree.setValue(openvdb::Coord(11, 10, 9), four); tree.setValue(openvdb::Coord(11, 11, 9), four); tree.setValue(openvdb::Coord(10, 11, 9), four); tree.setValue(openvdb::Coord( 9, 11, 9), four); tree.setValue(openvdb::Coord( 9, 10, 9), four); tree.setValue(openvdb::Coord( 9, 9, 9), four); tree.setValue(openvdb::Coord(10, 9, 9), four); tree.setValue(openvdb::Coord(11, 9, 9), four); const TestVal testVals[] = { { 10.5, 10.5, 10.5, constValue(1.703125) }, { 10.0, 10.0, 10.0, one }, { 11.0, 10.0, 10.0, two }, { 11.0, 11.0, 10.0, two }, { 11.0, 11.0, 11.0, three }, { 9.0, 11.0, 9.0, four }, { 9.0, 10.0, 9.0, four }, { 10.1, 10.0, 10.0, constValue(1.01) }, { 10.8, 10.8, 10.8, constValue(2.513344) }, { 10.1, 10.8, 10.5, constValue(1.8577) }, { 10.8, 10.1, 10.5, constValue(1.8577) }, { 10.5, 10.1, 10.8, constValue(2.2927) }, { 10.5, 10.8, 10.1, constValue(1.6977) }, }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } template void TestQuadraticInterp::testConstantValues() { const ValueT two = constValue(2), fillValue = constValue(256); GridPtr grid(new GridType(fillValue)); typename GridType::TreeType& tree = grid->tree(); tree.setValue(openvdb::Coord(10, 10, 10), two); tree.setValue(openvdb::Coord(11, 10, 10), two); tree.setValue(openvdb::Coord(11, 11, 10), two); tree.setValue(openvdb::Coord(10, 11, 10), two); tree.setValue(openvdb::Coord( 9, 11, 10), two); tree.setValue(openvdb::Coord( 9, 10, 10), two); tree.setValue(openvdb::Coord( 9, 9, 10), two); tree.setValue(openvdb::Coord(10, 9, 10), two); tree.setValue(openvdb::Coord(11, 9, 10), two); tree.setValue(openvdb::Coord(10, 10, 11), two); tree.setValue(openvdb::Coord(11, 10, 11), two); tree.setValue(openvdb::Coord(11, 11, 11), two); tree.setValue(openvdb::Coord(10, 11, 11), two); tree.setValue(openvdb::Coord( 9, 11, 11), two); tree.setValue(openvdb::Coord( 9, 10, 11), two); tree.setValue(openvdb::Coord( 9, 9, 11), two); tree.setValue(openvdb::Coord(10, 9, 11), two); tree.setValue(openvdb::Coord(11, 9, 11), two); tree.setValue(openvdb::Coord(10, 10, 9), two); tree.setValue(openvdb::Coord(11, 10, 9), two); tree.setValue(openvdb::Coord(11, 11, 9), two); tree.setValue(openvdb::Coord(10, 11, 9), two); tree.setValue(openvdb::Coord( 9, 11, 9), two); tree.setValue(openvdb::Coord( 9, 10, 9), two); tree.setValue(openvdb::Coord( 9, 9, 9), two); tree.setValue(openvdb::Coord(10, 9, 9), two); tree.setValue(openvdb::Coord(11, 9, 9), two); const TestVal testVals[] = { { 10.5, 10.5, 10.5, two }, { 10.0, 10.0, 10.0, two }, { 10.1, 10.0, 10.0, two }, { 10.8, 10.8, 10.8, two }, { 10.1, 10.8, 10.5, two }, { 10.8, 10.1, 10.5, two }, { 10.5, 10.1, 10.8, two }, { 10.5, 10.8, 10.1, two } }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } template void TestQuadraticInterp::testFillValues() { const ValueT fillValue = constValue(256); GridPtr grid(new GridType(fillValue)); const TestVal testVals[] = { { 10.5, 10.5, 10.5, fillValue }, { 10.0, 10.0, 10.0, fillValue }, { 10.1, 10.0, 10.0, fillValue }, { 10.8, 10.8, 10.8, fillValue }, { 10.1, 10.8, 10.5, fillValue }, { 10.8, 10.1, 10.5, fillValue }, { 10.5, 10.1, 10.8, fillValue }, { 10.5, 10.8, 10.1, fillValue } }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } template void TestQuadraticInterp::testNegativeIndices() { const ValueT one = constValue(1), two = constValue(2), three = constValue(3), four = constValue(4), fillValue = constValue(256); GridPtr grid(new GridType(fillValue)); typename GridType::TreeType& tree = grid->tree(); tree.setValue(openvdb::Coord(-10, -10, -10), one); tree.setValue(openvdb::Coord(-11, -10, -10), two); tree.setValue(openvdb::Coord(-11, -11, -10), two); tree.setValue(openvdb::Coord(-10, -11, -10), two); tree.setValue(openvdb::Coord( -9, -11, -10), two); tree.setValue(openvdb::Coord( -9, -10, -10), two); tree.setValue(openvdb::Coord( -9, -9, -10), two); tree.setValue(openvdb::Coord(-10, -9, -10), two); tree.setValue(openvdb::Coord(-11, -9, -10), two); tree.setValue(openvdb::Coord(-10, -10, -11), three); tree.setValue(openvdb::Coord(-11, -10, -11), three); tree.setValue(openvdb::Coord(-11, -11, -11), three); tree.setValue(openvdb::Coord(-10, -11, -11), three); tree.setValue(openvdb::Coord( -9, -11, -11), three); tree.setValue(openvdb::Coord( -9, -10, -11), three); tree.setValue(openvdb::Coord( -9, -9, -11), three); tree.setValue(openvdb::Coord(-10, -9, -11), three); tree.setValue(openvdb::Coord(-11, -9, -11), three); tree.setValue(openvdb::Coord(-10, -10, -9), four); tree.setValue(openvdb::Coord(-11, -10, -9), four); tree.setValue(openvdb::Coord(-11, -11, -9), four); tree.setValue(openvdb::Coord(-10, -11, -9), four); tree.setValue(openvdb::Coord( -9, -11, -9), four); tree.setValue(openvdb::Coord( -9, -10, -9), four); tree.setValue(openvdb::Coord( -9, -9, -9), four); tree.setValue(openvdb::Coord(-10, -9, -9), four); tree.setValue(openvdb::Coord(-11, -9, -9), four); const TestVal testVals[] = { { -10.5, -10.5, -10.5, constValue(-104.75586) }, { -10.0, -10.0, -10.0, one }, { -11.0, -10.0, -10.0, two }, { -11.0, -11.0, -10.0, two }, { -11.0, -11.0, -11.0, three }, { -9.0, -11.0, -9.0, four }, { -9.0, -10.0, -9.0, four }, { -10.1, -10.0, -10.0, constValue(-10.28504) }, { -10.8, -10.8, -10.8, constValue(-62.84878) }, { -10.1, -10.8, -10.5, constValue(-65.68951) }, { -10.8, -10.1, -10.5, constValue(-65.68951) }, { -10.5, -10.1, -10.8, constValue(-65.40736) }, { -10.5, -10.8, -10.1, constValue(-66.30510) }, }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestPrePostAPI.cc0000644000000000000000000006013512252453157016200 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include class TestPrePostAPI: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestPrePostAPI); CPPUNIT_TEST(testMat4); CPPUNIT_TEST(testMat4Rotate); CPPUNIT_TEST(testMat4Scale); CPPUNIT_TEST(testMat4Shear); CPPUNIT_TEST(testMaps); CPPUNIT_TEST(testLinearTransform); CPPUNIT_TEST(testFrustumTransform); CPPUNIT_TEST_SUITE_END(); void testMat4(); void testMat4Rotate(); void testMat4Scale(); void testMat4Shear(); void testMaps(); void testLinearTransform(); void testFrustumTransform(); //void testIsType(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPrePostAPI); void TestPrePostAPI::testMat4() { using namespace openvdb::math; double TOL = 1e-7; Mat4d m = Mat4d::identity(); Mat4d minv = Mat4d::identity(); // create matrix with pre-API // Translate Shear Rotate Translate Scale matrix m.preScale(Vec3d(1, 2, 3)); m.preTranslate(Vec3d(2, 3, 4)); m.preRotate(X_AXIS, 20); m.preShear(X_AXIS, Y_AXIS, 2); m.preTranslate(Vec3d(2, 2, 2)); // create inverse using the post-API minv.postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); minv.postTranslate(-Vec3d(2, 3, 4)); minv.postRotate(X_AXIS,-20); minv.postShear(X_AXIS, Y_AXIS, -2); minv.postTranslate(-Vec3d(2, 2, 2)); Mat4d mtest = minv * m; // verify that the results is an identity CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); } void TestPrePostAPI::testMat4Rotate() { using namespace openvdb::math; double TOL = 1e-7; Mat4d rx, ry, rz; const double angle1 = 20. * M_PI / 180.; const double angle2 = 64. * M_PI / 180.; const double angle3 = 125. *M_PI / 180.; rx.setToRotation(Vec3d(1,0,0), angle1); ry.setToRotation(Vec3d(0,1,0), angle2); rz.setToRotation(Vec3d(0,0,1), angle3); Mat4d shear = Mat4d::identity(); shear.setToShear(X_AXIS, Z_AXIS, 2.0); shear.preShear(Y_AXIS, X_AXIS, 3.0); shear.preTranslate(Vec3d(2,4,1)); const Mat4d preResult = rz*ry*rx*shear; Mat4d mpre = shear; mpre.preRotate(X_AXIS, angle1); mpre.preRotate(Y_AXIS, angle2); mpre.preRotate(Z_AXIS, angle3); CPPUNIT_ASSERT( mpre.eq(preResult, TOL) ); const Mat4d postResult = shear*rx*ry*rz; Mat4d mpost = shear; mpost.postRotate(X_AXIS, angle1); mpost.postRotate(Y_AXIS, angle2); mpost.postRotate(Z_AXIS, angle3); CPPUNIT_ASSERT( mpost.eq(postResult, TOL) ); CPPUNIT_ASSERT( !mpost.eq(mpre, TOL)); } void TestPrePostAPI::testMat4Scale() { using namespace openvdb::math; double TOL = 1e-7; Mat4d mpre, mpost; double* pre = mpre.asPointer(); double* post = mpost.asPointer(); for (int i = 0; i < 16; ++i) { pre[i] = double(i); post[i] = double(i); } Mat4d scale = Mat4d::identity(); scale.setToScale(Vec3d(2, 3, 5.5)); Mat4d preResult = scale * mpre; Mat4d postResult = mpost * scale; mpre.preScale(Vec3d(2, 3, 5.5)); mpost.postScale(Vec3d(2, 3, 5.5)); CPPUNIT_ASSERT( mpre.eq(preResult, TOL) ); CPPUNIT_ASSERT( mpost.eq(postResult, TOL) ); } void TestPrePostAPI::testMat4Shear() { using namespace openvdb::math; double TOL = 1e-7; Mat4d mpre, mpost; double* pre = mpre.asPointer(); double* post = mpost.asPointer(); for (int i = 0; i < 16; ++i) { pre[i] = double(i); post[i] = double(i); } Mat4d shear = Mat4d::identity(); shear.setToShear(X_AXIS, Z_AXIS, 13.); Mat4d preResult = shear * mpre; Mat4d postResult = mpost * shear; mpre.preShear(X_AXIS, Z_AXIS, 13.); mpost.postShear(X_AXIS, Z_AXIS, 13.); CPPUNIT_ASSERT( mpre.eq(preResult, TOL) ); CPPUNIT_ASSERT( mpost.eq(postResult, TOL) ); } void TestPrePostAPI::testMaps() { using namespace openvdb::math; double TOL = 1e-7; { // pre translate UniformScaleMap usm; UniformScaleTranslateMap ustm; ScaleMap sm; ScaleTranslateMap stm; AffineMap am; const Vec3d trans(1,2,3); Mat4d correct = Mat4d::identity(); correct.preTranslate(trans); { MapBase::Ptr base = usm.preTranslate(trans); Mat4d result = (base->getAffineMap())->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = ustm.preTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = sm.preTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = stm.preTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = am.preTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } } { // post translate UniformScaleMap usm; UniformScaleTranslateMap ustm; ScaleMap sm; ScaleTranslateMap stm; AffineMap am; const Vec3d trans(1,2,3); Mat4d correct = Mat4d::identity(); correct.postTranslate(trans); { const Mat4d result = usm.postTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = ustm.postTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = sm.postTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = stm.postTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = am.postTranslate(trans)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } } { // pre scale UniformScaleMap usm; UniformScaleTranslateMap ustm; ScaleMap sm; ScaleTranslateMap stm; AffineMap am; const Vec3d scale(1,2,3); Mat4d correct = Mat4d::identity(); correct.preScale(scale); { const Mat4d result = usm.preScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = ustm.preScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = sm.preScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = stm.preScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = am.preScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } } { // post scale UniformScaleMap usm; UniformScaleTranslateMap ustm; ScaleMap sm; ScaleTranslateMap stm; AffineMap am; const Vec3d scale(1,2,3); Mat4d correct = Mat4d::identity(); correct.postScale(scale); { const Mat4d result = usm.postScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = ustm.postScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = sm.postScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = stm.postScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = am.postScale(scale)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } } { // pre shear UniformScaleMap usm; UniformScaleTranslateMap ustm; ScaleMap sm; ScaleTranslateMap stm; AffineMap am; Mat4d correct = Mat4d::identity(); correct.preShear(X_AXIS, Z_AXIS, 13.); { const Mat4d result = usm.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = ustm.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = sm.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = stm.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = am.preShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } } { // post shear UniformScaleMap usm; UniformScaleTranslateMap ustm; ScaleMap sm; ScaleTranslateMap stm; AffineMap am; Mat4d correct = Mat4d::identity(); correct.postShear(X_AXIS, Z_AXIS, 13.); { const Mat4d result = usm.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = ustm.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = sm.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = stm.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = am.postShear(13., X_AXIS, Z_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } } { // pre rotate const double angle1 = 20. * M_PI / 180.; UniformScaleMap usm; UniformScaleTranslateMap ustm; ScaleMap sm; ScaleTranslateMap stm; AffineMap am; Mat4d correct = Mat4d::identity(); correct.preRotate(X_AXIS, angle1); { const Mat4d result = usm.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = ustm.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = sm.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = stm.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = am.preRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } } { // post rotate const double angle1 = 20. * M_PI / 180.; UniformScaleMap usm; UniformScaleTranslateMap ustm; ScaleMap sm; ScaleTranslateMap stm; AffineMap am; Mat4d correct = Mat4d::identity(); correct.postRotate(X_AXIS, angle1); { const Mat4d result = usm.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = ustm.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = sm.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = stm.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } { const Mat4d result = am.postRotate(angle1, X_AXIS)->getAffineMap()->getConstMat4(); CPPUNIT_ASSERT( correct.eq(result, TOL)); } } } void TestPrePostAPI::testLinearTransform() { using namespace openvdb::math; double TOL = 1e-7; { Transform::Ptr t = Transform::createLinearTransform(1.f); Transform::Ptr tinv = Transform::createLinearTransform(1.f); // create matrix with pre-API // Translate Shear Rotate Translate Scale matrix t->preScale(Vec3d(1, 2, 3)); t->preTranslate(Vec3d(2, 3, 4)); t->preRotate(20); t->preShear(2, X_AXIS, Y_AXIS); t->preTranslate(Vec3d(2, 2, 2)); // create inverse using the post-API tinv->postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); tinv->postTranslate(-Vec3d(2, 3, 4)); tinv->postRotate(-20); tinv->postShear(-2, X_AXIS, Y_AXIS); tinv->postTranslate(-Vec3d(2, 2, 2)); // test this by verifying that equvilent interal matrix // represenations are inverses Mat4d m = t->baseMap()->getAffineMap()->getMat4(); Mat4d minv = tinv->baseMap()->getAffineMap()->getMat4(); Mat4d mtest = minv * m; // verify that the results is an identity CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); } { Transform::Ptr t = Transform::createLinearTransform(1.f); Mat4d m = Mat4d::identity(); // create matrix with pre-API // Translate Shear Rotate Translate Scale matrix m.preScale(Vec3d(1, 2, 3)); m.preTranslate(Vec3d(2, 3, 4)); m.preRotate(X_AXIS, 20); m.preShear(X_AXIS, Y_AXIS, 2); m.preTranslate(Vec3d(2, 2, 2)); t->preScale(Vec3d(1,2,3)); t->preMult(m); t->postMult(m); Mat4d minv = Mat4d::identity(); // create inverse using the post-API minv.postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); minv.postTranslate(-Vec3d(2, 3, 4)); minv.postRotate(X_AXIS,-20); minv.postShear(X_AXIS, Y_AXIS, -2); minv.postTranslate(-Vec3d(2, 2, 2)); t->preMult(minv); t->postMult(minv); Mat4d mtest = t->baseMap()->getAffineMap()->getMat4(); // verify that the results is the scale CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 2, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 3, 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); } } void TestPrePostAPI::testFrustumTransform() { using namespace openvdb::math; typedef BBox BBoxd; double TOL = 1e-7; { BBoxd bbox(Vec3d(-5,-5,0), Vec3d(5,5,10)); Transform::Ptr t = Transform::createFrustumTransform(bbox, /* taper*/ 1, /*depth*/10, /* voxel size */1.f); Transform::Ptr tinv = Transform::createFrustumTransform(bbox, /* taper*/ 1, /*depth*/10, /* voxel size */1.f); // create matrix with pre-API // Translate Shear Rotate Translate Scale matrix t->preScale(Vec3d(1, 2, 3)); t->preTranslate(Vec3d(2, 3, 4)); t->preRotate(20); t->preShear(2, X_AXIS, Y_AXIS); t->preTranslate(Vec3d(2, 2, 2)); // create inverse using the post-API tinv->postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); tinv->postTranslate(-Vec3d(2, 3, 4)); tinv->postRotate(-20); tinv->postShear(-2, X_AXIS, Y_AXIS); tinv->postTranslate(-Vec3d(2, 2, 2)); // test this by verifying that equvilent interal matrix // represenations are inverses NonlinearFrustumMap::Ptr frustum = boost::static_pointer_cast( t->baseMap() ); NonlinearFrustumMap::Ptr frustuminv = boost::static_pointer_cast( tinv->baseMap() ); Mat4d m = frustum->secondMap().getMat4(); Mat4d minv = frustuminv->secondMap().getMat4(); Mat4d mtest = minv * m; // verify that the results is an identity CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); } { BBoxd bbox(Vec3d(-5,-5,0), Vec3d(5,5,10)); Transform::Ptr t = Transform::createFrustumTransform(bbox, /* taper*/ 1, /*depth*/10, /* voxel size */1.f); Mat4d m = Mat4d::identity(); // create matrix with pre-API // Translate Shear Rotate Translate Scale matrix m.preScale(Vec3d(1, 2, 3)); m.preTranslate(Vec3d(2, 3, 4)); m.preRotate(X_AXIS, 20); m.preShear(X_AXIS, Y_AXIS, 2); m.preTranslate(Vec3d(2, 2, 2)); t->preScale(Vec3d(1,2,3)); t->preMult(m); t->postMult(m); Mat4d minv = Mat4d::identity(); // create inverse using the post-API minv.postScale(Vec3d(1.f, 1.f/2.f, 1.f/3.f)); minv.postTranslate(-Vec3d(2, 3, 4)); minv.postRotate(X_AXIS,-20); minv.postShear(X_AXIS, Y_AXIS, -2); minv.postTranslate(-Vec3d(2, 2, 2)); t->preMult(minv); t->postMult(minv); NonlinearFrustumMap::Ptr frustum = boost::static_pointer_cast( t->baseMap() ); Mat4d mtest = frustum->secondMap().getMat4(); // verify that the results is the scale CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][0], 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][1], 2, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][2], 3, 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[0][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[1][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][0], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][1], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[2][3], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][0], 0, 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][1], 0, 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][2], 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(mtest[3][3], 1, TOL); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGridDescriptor.cc0000644000000000000000000001467112252453157017202 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include class TestGridDescriptor: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestGridDescriptor); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testCopy); CPPUNIT_TEST(testName); CPPUNIT_TEST_SUITE_END(); void testIO(); void testCopy(); void testName(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGridDescriptor); void TestGridDescriptor::testIO() { using namespace openvdb::io; using namespace openvdb; typedef FloatGrid GridType; GridDescriptor gd(GridDescriptor::addSuffix("temperature", 2), GridType::gridType()); gd.setInstanceParentName("temperature_32bit"); gd.setGridPos(123); gd.setBlockPos(234); gd.setEndPos(567); // write out the gd. std::ostringstream ostr(std::ios_base::binary); gd.writeHeader(ostr); gd.writeStreamPos(ostr); // Read in the gd. std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input is only a fragment of a VDB file (in particular, // it doesn't have a header), set the file format version number explicitly. io::setCurrentVersion(istr); GridDescriptor gd2; CPPUNIT_ASSERT_THROW(gd2.read(istr), openvdb::LookupError); // Register the grid. GridBase::clearRegistry(); GridType::registerGrid(); // seek back and read again. istr.seekg(0, std::ios_base::beg); GridBase::Ptr grid; CPPUNIT_ASSERT_NO_THROW(grid = gd2.read(istr)); CPPUNIT_ASSERT_EQUAL(gd.gridName(), gd2.gridName()); CPPUNIT_ASSERT_EQUAL(gd.uniqueName(), gd2.uniqueName()); CPPUNIT_ASSERT_EQUAL(gd.gridType(), gd2.gridType()); CPPUNIT_ASSERT_EQUAL(gd.instanceParentName(), gd2.instanceParentName()); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(GridType::gridType(), grid->type()); CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), gd2.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), gd2.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd2.getEndPos()); // Clear the registry when we are done. GridBase::clearRegistry(); } void TestGridDescriptor::testCopy() { using namespace openvdb::io; using namespace openvdb; typedef FloatGrid GridType; GridDescriptor gd("temperature", GridType::gridType()); gd.setInstanceParentName("temperature_32bit"); gd.setGridPos(123); gd.setBlockPos(234); gd.setEndPos(567); GridDescriptor gd2; // do the copy gd2 = gd; CPPUNIT_ASSERT_EQUAL(gd.gridName(), gd2.gridName()); CPPUNIT_ASSERT_EQUAL(gd.uniqueName(), gd2.uniqueName()); CPPUNIT_ASSERT_EQUAL(gd.gridType(), gd2.gridType()); CPPUNIT_ASSERT_EQUAL(gd.instanceParentName(), gd2.instanceParentName()); CPPUNIT_ASSERT_EQUAL(gd.getGridPos(), gd2.getGridPos()); CPPUNIT_ASSERT_EQUAL(gd.getBlockPos(), gd2.getBlockPos()); CPPUNIT_ASSERT_EQUAL(gd.getEndPos(), gd2.getEndPos()); } void TestGridDescriptor::testName() { using openvdb::Name; using openvdb::io::GridDescriptor; const std::string typ = openvdb::FloatGrid::gridType(); Name name("test"); GridDescriptor gd(name, typ); // Verify that the grid name and the unique name are equivalent // when the unique name has no suffix. CPPUNIT_ASSERT_EQUAL(name, gd.gridName()); CPPUNIT_ASSERT_EQUAL(name, gd.uniqueName()); CPPUNIT_ASSERT_EQUAL(name, GridDescriptor::nameAsString(name)); CPPUNIT_ASSERT_EQUAL(name, GridDescriptor::stripSuffix(name)); // Add a suffix. name = GridDescriptor::addSuffix("test", 2); gd = GridDescriptor(name, typ); // Verify that the grid name and the unique name differ // when the unique name has a suffix. CPPUNIT_ASSERT_EQUAL(name, gd.uniqueName()); CPPUNIT_ASSERT(gd.gridName() != gd.uniqueName()); CPPUNIT_ASSERT_EQUAL(GridDescriptor::stripSuffix(name), gd.gridName()); CPPUNIT_ASSERT_EQUAL(Name("test[2]"), GridDescriptor::nameAsString(name)); // As above, but with a longer suffix name = GridDescriptor::addSuffix("test", 13); gd = GridDescriptor(name, typ); CPPUNIT_ASSERT_EQUAL(name, gd.uniqueName()); CPPUNIT_ASSERT(gd.gridName() != gd.uniqueName()); CPPUNIT_ASSERT_EQUAL(GridDescriptor::stripSuffix(name), gd.gridName()); CPPUNIT_ASSERT_EQUAL(Name("test[13]"), GridDescriptor::nameAsString(name)); // Multiple suffixes aren't supported, but verify that // they behave reasonably, at least. name = GridDescriptor::addSuffix(name, 4); gd = GridDescriptor(name, typ); CPPUNIT_ASSERT_EQUAL(name, gd.uniqueName()); CPPUNIT_ASSERT(gd.gridName() != gd.uniqueName()); CPPUNIT_ASSERT_EQUAL(GridDescriptor::stripSuffix(name), gd.gridName()); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestExceptions.cc0000644000000000000000000001142212252453157016366 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include class TestExceptions : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestExceptions); CPPUNIT_TEST(testArithmeticError); CPPUNIT_TEST(testIndexError); CPPUNIT_TEST(testIoError); CPPUNIT_TEST(testKeyError); CPPUNIT_TEST(testLookupError); CPPUNIT_TEST(testNotImplementedError); CPPUNIT_TEST(testReferenceError); CPPUNIT_TEST(testRuntimeError); CPPUNIT_TEST(testTypeError); CPPUNIT_TEST(testValueError); CPPUNIT_TEST_SUITE_END(); void testArithmeticError() { testException(); } void testIndexError() { testException(); } void testIoError() { testException(); } void testKeyError() { testException(); } void testLookupError() { testException(); } void testNotImplementedError() { testException(); } void testReferenceError() { testException(); } void testRuntimeError() { testException(); } void testTypeError() { testException(); } void testValueError() { testException(); } private: template void testException(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestExceptions); template struct ExceptionTraits { static std::string name() { return ""; } }; template<> struct ExceptionTraits { static std::string name() { return "ArithmeticError"; } }; template<> struct ExceptionTraits { static std::string name() { return "IndexError"; } }; template<> struct ExceptionTraits { static std::string name() { return "IoError"; } }; template<> struct ExceptionTraits { static std::string name() { return "KeyError"; } }; template<> struct ExceptionTraits { static std::string name() { return "LookupError"; } }; template<> struct ExceptionTraits { static std::string name() { return "NotImplementedError"; } }; template<> struct ExceptionTraits { static std::string name() { return "ReferenceError"; } }; template<> struct ExceptionTraits { static std::string name() { return "RuntimeError"; } }; template<> struct ExceptionTraits { static std::string name() { return "TypeError"; } }; template<> struct ExceptionTraits { static std::string name() { return "ValueError"; } }; template void TestExceptions::testException() { std::string ErrorMsg("Error message"); CPPUNIT_ASSERT_THROW(OPENVDB_THROW(ExceptionT, ErrorMsg), ExceptionT); try { OPENVDB_THROW(ExceptionT, ErrorMsg); } catch (openvdb::Exception& e) { const std::string expectedMsg = ExceptionTraits::name() + ": " + ErrorMsg; CPPUNIT_ASSERT_EQUAL(expectedMsg, std::string(e.what())); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestInt64Metadata.cc0000644000000000000000000000535512252453157016622 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestInt64Metadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestInt64Metadata); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestInt64Metadata); void TestInt64Metadata::test() { using namespace openvdb; Metadata::Ptr m(new Int64Metadata(123)); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("int64") == 0); CPPUNIT_ASSERT(m2->typeName().compare("int64") == 0); Int64Metadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == 123); s->value() = 456; CPPUNIT_ASSERT(s->value() == 456); m2->copy(*s); s = dynamic_cast(m2.get()); CPPUNIT_ASSERT(s->value() == 456); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestMat4Metadata.cc0000644000000000000000000001377412252453157016527 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestMat4Metadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestMat4Metadata); CPPUNIT_TEST(testMat4s); CPPUNIT_TEST(testMat4d); CPPUNIT_TEST_SUITE_END(); void testMat4s(); void testMat4d(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMat4Metadata); void TestMat4Metadata::testMat4s() { using namespace openvdb; Metadata::Ptr m(new Mat4SMetadata(openvdb::math::Mat4s(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f))); Metadata::Ptr m3 = m->copy(); CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); CPPUNIT_ASSERT( m->typeName().compare("mat4s") == 0); CPPUNIT_ASSERT(m3->typeName().compare("mat4s") == 0); Mat4SMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4s(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f)); s->value() = openvdb::math::Mat4s(3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f); CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4s(3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f)); m3->copy(*s); s = dynamic_cast(m3.get()); CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4s(3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f, 3.0f)); } void TestMat4Metadata::testMat4d() { using namespace openvdb; Metadata::Ptr m(new Mat4DMetadata(openvdb::math::Mat4d(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0))); Metadata::Ptr m3 = m->copy(); CPPUNIT_ASSERT(dynamic_cast( m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m3.get()) != 0); CPPUNIT_ASSERT( m->typeName().compare("mat4d") == 0); CPPUNIT_ASSERT(m3->typeName().compare("mat4d") == 0); Mat4DMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4d(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)); s->value() = openvdb::math::Mat4d(3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0); CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4d(3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0)); m3->copy(*s); s = dynamic_cast(m3.get()); CPPUNIT_ASSERT(s->value() == openvdb::math::Mat4d(3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0)); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestStringMetadata.cc0000644000000000000000000000547312252453157017165 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestStringMetadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestStringMetadata); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestStringMetadata); void TestStringMetadata::test() { using namespace openvdb; Metadata::Ptr m(new StringMetadata("testing")); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("string") == 0); CPPUNIT_ASSERT(m2->typeName().compare("string") == 0); StringMetadata *s = dynamic_cast(m.get()); CPPUNIT_ASSERT(s->value().compare("testing") == 0); s->value() = "testing2"; CPPUNIT_ASSERT(s->value().compare("testing2") == 0); m2->copy(*s); s = dynamic_cast(m2.get()); CPPUNIT_ASSERT(s->value().compare("testing2") == 0); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLaplacian.cc0000644000000000000000000004525612252453157016145 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include "util.h" // for unittest_util::makeSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestLaplacian: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestLaplacian); CPPUNIT_TEST(testISLaplacian); // Laplacian in Index Space CPPUNIT_TEST(testISLaplacianStencil); CPPUNIT_TEST(testWSLaplacian); // Laplacian in World Space CPPUNIT_TEST(testWSLaplacianFrustum); // Laplacian in World Space CPPUNIT_TEST(testWSLaplacianStencil); CPPUNIT_TEST(testLaplacianTool); // Laplacian tool CPPUNIT_TEST(testLaplacianMaskedTool); // Laplacian tool CPPUNIT_TEST(testOldStyleStencils); // old stencil impl CPPUNIT_TEST_SUITE_END(); void testISLaplacian(); void testISLaplacianStencil(); void testWSLaplacian(); void testWSLaplacianFrustum(); void testWSLaplacianStencil(); void testLaplacianTool(); void testLaplacianMaskedTool(); void testOldStyleStencils(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLaplacian); void TestLaplacian::testISLaplacian() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Coord c(35,30,40); const openvdb::Vec3f center(c[0],c[1],c[2]); const float radius=0.0f;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); Coord xyz(35,10,40); // Index Space Laplacian random access AccessorType inAccessor = grid->getConstAccessor(); FloatGrid::ValueType result; result = math::ISLaplacian::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); result = math::ISLaplacian::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); result = math::ISLaplacian::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); } void TestLaplacian::testISLaplacianStencil() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Coord c(35,30,40); const openvdb::Vec3f center(c[0],c[1],c[2]); const float radius=0;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); Coord xyz(35,10,40); // Index Space Laplacian stencil access FloatGrid::ValueType result; math::SevenPointStencil sevenpt(*grid); sevenpt.moveTo(xyz); result = math::ISLaplacian::result(sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); math::ThirteenPointStencil thirteenpt(*grid); thirteenpt.moveTo(xyz); result = math::ISLaplacian::result(thirteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); math::NineteenPointStencil nineteenpt(*grid); nineteenpt.moveTo(xyz); result = math::ISLaplacian::result(nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20.0, result, /*tolerance=*/0.01); } void TestLaplacian::testWSLaplacian() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Coord c(35,30,40); const openvdb::Vec3f center(c[0],c[1],c[2]); const float radius=0.0f;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); Coord xyz(35,10,40); FloatGrid::ValueType result; AccessorType inAccessor = grid->getConstAccessor(); // try with a map math::UniformScaleMap map; math::MapBase::Ptr rotated_map = map.preRotate(1.5, math::X_AXIS); // verify the new map is an affine map CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); math::AffineMap::Ptr affine_map = boost::static_pointer_cast(rotated_map); // the laplacian is invariant to rotation result = math::Laplacian::result(*affine_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(*affine_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(*affine_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); // test uniform map math::UniformScaleMap uniform; result = math::Laplacian::result(uniform, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(uniform, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(uniform, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); // test the GenericMap Grid interface { math::GenericMap generic_map(*grid); result = math::Laplacian::result(generic_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(generic_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); } { // test the GenericMap Transform interface math::GenericMap generic_map(grid->transform()); result = math::Laplacian::result(generic_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); } { // test the GenericMap Map interface math::GenericMap generic_map(rotated_map); result = math::Laplacian::result(generic_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); } } void TestLaplacian::testWSLaplacianFrustum() { using namespace openvdb; // Create a Frustum Map: openvdb::BBoxd bbox(Vec3d(0), Vec3d(100)); math::NonlinearFrustumMap frustum(bbox, 1./6., 5); /// frustum will have depth, far plane - near plane = 5 /// the frustum has width 1 in the front and 6 in the back math::Vec3d trans(2,2,2); math::NonlinearFrustumMap::Ptr map = boost::static_pointer_cast( frustum.preScale(Vec3d(10,10,10))->postTranslate(trans)); CPPUNIT_ASSERT(!map->hasUniformScale()); math::Vec3d result; result = map->voxelSize(); CPPUNIT_ASSERT( math::isApproxEqual(result.x(), 0.1)); CPPUNIT_ASSERT( math::isApproxEqual(result.y(), 0.1)); CPPUNIT_ASSERT( math::isApproxEqual(result.z(), 0.5, 0.0001)); // Create a tree FloatGrid::Ptr grid = FloatGrid::create(/*background=*/0.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); // Load cos(x)sin(y)cos(z) Coord ijk(10,10,10); for (Int32& i=ijk.x(); i < 20; ++i) { for (Int32& j=ijk.y(); j < 20; ++j) { for (Int32& k=ijk.z(); k < 20; ++k) { // world space image of the ijk coord const Vec3d ws = map->applyMap(ijk.asVec3d()); const float value = cos( ws.x() ) * sin( ws.y()) * cos(ws.z()); tree.setValue(ijk, value); } } } const Coord testloc(16,16,16); float test_result = math::Laplacian::result(*map, tree, testloc); float expected_result = -3.f * tree.getValue(testloc); // The exact solution of Laplacian( cos(x)sin(y)cos(z) ) = -3 cos(x) sin(y) cos(z) CPPUNIT_ASSERT( math::isApproxEqual(test_result, expected_result, /*tolerance=*/0.02f) ); } void TestLaplacian::testWSLaplacianStencil() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Coord c(35,30,40); const openvdb::Vec3f center(c[0],c[1],c[2]); const float radius=0.0f;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); Coord xyz(35,10,40); FloatGrid::ValueType result; // try with a map math::UniformScaleMap map; math::MapBase::Ptr rotated_map = map.preRotate(1.5, math::X_AXIS); // verify the new map is an affine map CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); math::AffineMap::Ptr affine_map = boost::static_pointer_cast(rotated_map); // the laplacian is invariant to rotation math::SevenPointStencil sevenpt(*grid); math::ThirteenPointStencil thirteenpt(*grid); math::NineteenPointStencil nineteenpt(*grid); math::SecondOrderDenseStencil dense_2nd(*grid); math::FourthOrderDenseStencil dense_4th(*grid); math::SixthOrderDenseStencil dense_6th(*grid); sevenpt.moveTo(xyz); thirteenpt.moveTo(xyz); nineteenpt.moveTo(xyz); dense_2nd.moveTo(xyz); dense_4th.moveTo(xyz); dense_6th.moveTo(xyz); result = math::Laplacian::result(*affine_map, dense_2nd); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(*affine_map, dense_4th); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(*affine_map, dense_6th); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); // test uniform map math::UniformScaleMap uniform; result = math::Laplacian::result(uniform, sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(uniform, thirteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(uniform, nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); // test the GenericMap Grid interface { math::GenericMap generic_map(*grid); result = math::Laplacian::result(generic_map, dense_2nd); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); result = math::Laplacian::result(generic_map, dense_4th); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); } { // test the GenericMap Transform interface math::GenericMap generic_map(grid->transform()); result = math::Laplacian::result(generic_map, dense_2nd); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); } { // test the GenericMap Map interface math::GenericMap generic_map(rotated_map); result = math::Laplacian::result(generic_map, dense_2nd); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/20., result, /*tolerance=*/0.01); } } void TestLaplacian::testOldStyleStencils() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(/*voxel size=*/0.5)); CPPUNIT_ASSERT(grid->empty()); const Coord dim(32, 32, 32); const Coord c(35,30,40); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); math::GradStencil gs(*grid); math::WenoStencil ws(*grid); math::CurvatureStencil cs(*grid); Coord xyz(20,16,20);//i.e. 8 voxel or 4 world units away from the center gs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/4.0, gs.laplacian(), 0.01);// 2/distance from center ws.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/4.0, ws.laplacian(), 0.01);// 2/distance from center cs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/4.0, cs.laplacian(), 0.01);// 2/distance from center xyz.reset(12,16,10);//i.e. 10 voxel or 5 world units away from the center gs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/5.0, gs.laplacian(), 0.01);// 2/distance from center ws.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/5.0, ws.laplacian(), 0.01);// 2/distance from center cs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0/5.0, cs.laplacian(), 0.01);// 2/distance from center } void TestLaplacian::testLaplacianTool() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); FloatGrid::Ptr lap = tools::laplacian(*grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(lap->activeVoxelCount())); Coord xyz(35,30,30); CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0/10.0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center xyz.reset(35,10,40); CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0/20.0, lap->getConstAccessor().getValue(xyz),0.01);// 2/distance from center } void TestLaplacian::testLaplacianMaskedTool() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); BoolGrid::Ptr maskGrid = BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); FloatGrid::Ptr lap = tools::laplacian(*grid, *maskGrid); {// outside the masked region Coord xyz(34,30,30); CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL( 0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center xyz.reset(35,10,40); CPPUNIT_ASSERT_DOUBLES_EQUAL( 0, lap->getConstAccessor().getValue(xyz),0.01);// 2/distance from center } {// inside the masked region Coord xyz(35,30,30); CPPUNIT_ASSERT(maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0/10.0, lap->getConstAccessor().getValue(xyz), 0.01);// 2/distance from center } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTreeGetSetValues.cc0000644000000000000000000004463212252453157017451 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include // for tools::setValueOnMin() et al. #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestTreeGetSetValues: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestTreeGetSetValues); CPPUNIT_TEST(testGetValues); CPPUNIT_TEST(testSetValues); CPPUNIT_TEST(testUnsetValues); CPPUNIT_TEST(testFill); CPPUNIT_TEST(testSetActiveStates); CPPUNIT_TEST(testHasActiveTiles); CPPUNIT_TEST_SUITE_END(); void testGetBackground(); void testGetValues(); void testSetValues(); void testUnsetValues(); void testFill(); void testSetActiveStates(); void testHasActiveTiles(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeGetSetValues); namespace { typedef openvdb::tree::Tree4::Type Tree323f; // 8^3 x 4^3 x 8^3 } void TestTreeGetSetValues::testGetBackground() { const float background = 256.0f; Tree323f tree(background); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.background()); } void TestTreeGetSetValues::testGetValues() { Tree323f tree(/*background=*/256.0f); tree.setValue(openvdb::Coord(0, 0, 0), 1.0); tree.setValue(openvdb::Coord(1, 0, 0), 1.5); tree.setValue(openvdb::Coord(0, 0, 8), 2.0); tree.setValue(openvdb::Coord(1, 0, 8), 2.5); tree.setValue(openvdb::Coord(0, 0, 16), 3.0); tree.setValue(openvdb::Coord(1, 0, 16), 3.5); tree.setValue(openvdb::Coord(0, 0, 24), 4.0); tree.setValue(openvdb::Coord(1, 0, 24), 4.5); ASSERT_DOUBLES_EXACTLY_EQUAL(1.0, tree.getValue(openvdb::Coord(0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(1.5, tree.getValue(openvdb::Coord(1, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(2.0, tree.getValue(openvdb::Coord(0, 0, 8))); ASSERT_DOUBLES_EXACTLY_EQUAL(2.5, tree.getValue(openvdb::Coord(1, 0, 8))); ASSERT_DOUBLES_EXACTLY_EQUAL(3.0, tree.getValue(openvdb::Coord(0, 0, 16))); ASSERT_DOUBLES_EXACTLY_EQUAL(3.5, tree.getValue(openvdb::Coord(1, 0, 16))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.0, tree.getValue(openvdb::Coord(0, 0, 24))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5, tree.getValue(openvdb::Coord(1, 0, 24))); } void TestTreeGetSetValues::testSetValues() { using namespace openvdb; const float background = 256.0; Tree323f tree(background); for (int activeTile = 0; activeTile < 2; ++activeTile) { if (activeTile) tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true); tree.setValue(openvdb::Coord(0, 0, 0), 1.0); tree.setValue(openvdb::Coord(1, 0, 0), 1.5); tree.setValue(openvdb::Coord(0, 0, 8), 2.0); tree.setValue(openvdb::Coord(1, 0, 8), 2.5); tree.setValue(openvdb::Coord(0, 0, 16), 3.0); tree.setValue(openvdb::Coord(1, 0, 16), 3.5); tree.setValue(openvdb::Coord(0, 0, 24), 4.0); tree.setValue(openvdb::Coord(1, 0, 24), 4.5); const int expectedActiveCount = (!activeTile ? 8 : 32 * 32 * 32); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); float val = 1.0; for (Tree323f::LeafCIter iter = tree.cbeginLeaf(); iter; ++iter) { ASSERT_DOUBLES_EXACTLY_EQUAL(val, iter->getValue(openvdb::Coord(0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(val+0.5, iter->getValue(openvdb::Coord(1, 0, 0))); val = val + 1.0; } } } void TestTreeGetSetValues::testUnsetValues() { using namespace openvdb; const float background = 256.0; Tree323f tree(background); for (int activeTile = 0; activeTile < 2; ++activeTile) { if (activeTile) tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true); Coord setCoords[8] = { Coord(0, 0, 0), Coord(1, 0, 0), Coord(0, 0, 8), Coord(1, 0, 8), Coord(0, 0, 16), Coord(1, 0, 16), Coord(0, 0, 24), Coord(1, 0, 24) }; for (int i = 0; i < 8; ++i) { tree.setValue(setCoords[i], 1.0); } const int expectedActiveCount = (!activeTile ? 8 : 32 * 32 * 32); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); // Unset some voxels. for (int i = 0; i < 8; i += 2) { tree.setValueOff(setCoords[i]); } CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 4, int(tree.activeVoxelCount())); // Unset some voxels, but change their values. for (int i = 0; i < 8; i += 2) { tree.setValueOff(setCoords[i], background); } CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 4, int(tree.activeVoxelCount())); for (int i = 0; i < 8; i += 2) { ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(setCoords[i])); } } } void TestTreeGetSetValues::testFill() { using openvdb::CoordBBox; using openvdb::Coord; const float background = 256.0; Tree323f tree(background); // Fill from (-2,-2,-2) to (2,2,2) with active value 2. tree.fill(CoordBBox(Coord(-2), Coord(2)), 2.0); Coord xyz, xyzMin = Coord::max(), xyzMax = Coord::min(); for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { xyz = iter.getCoord(); xyzMin = std::min(xyzMin, xyz); xyzMax = std::max(xyz, xyzMax); ASSERT_DOUBLES_EXACTLY_EQUAL(2.0, *iter); } CPPUNIT_ASSERT_EQUAL(openvdb::Index64(5*5*5), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); CPPUNIT_ASSERT_EQUAL(Coord( 2), xyzMax); // Fill from (1,1,1) to (3,3,3) with active value 3. tree.fill(CoordBBox(Coord(1), Coord(3)), 3.0); xyzMin = Coord::max(); xyzMax = Coord::min(); for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { xyz = iter.getCoord(); xyzMin = std::min(xyzMin, xyz); xyzMax = std::max(xyz, xyzMax); const float expectedValue = (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) ? 3.0 : 2.0; ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); } openvdb::Index64 expectedCount = 5*5*5 // (-2,-2,-2) to (2,2,2) + 3*3*3 // (1,1,1) to (3,3,3) - 2*2*2; // (1,1,1) to (2,2,2) overlap CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); CPPUNIT_ASSERT_EQUAL(Coord( 3), xyzMax); // Fill from (10,10,10) to (20,20,20) with active value 10. tree.fill(CoordBBox(Coord(10), Coord(20)), 10.0); xyzMin = Coord::max(); xyzMax = Coord::min(); for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { xyz = iter.getCoord(); xyzMin = std::min(xyzMin, xyz); xyzMax = std::max(xyz, xyzMax); float expectedValue = 2.0; if (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) { expectedValue = 3.0; } else if (xyz[0] >= 10 && xyz[1] >= 10 && xyz[2] >= 10 && xyz[0] <= 20 && xyz[1] <= 20 && xyz[2] <= 20) { expectedValue = 10.0; } ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); } expectedCount = 5*5*5 // (-2,-2,-2) to (2,2,2) + 3*3*3 // (1,1,1) to (3,3,3) - 2*2*2 // (1,1,1) to (2,2,2) overlap + 11*11*11; // (10,10,10) to (20,20,20) CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); CPPUNIT_ASSERT_EQUAL(Coord(20), xyzMax); // "Undo" previous fill from (10,10,10) to (20,20,20). tree.fill(CoordBBox(Coord(10), Coord(20)), background, /*active=*/false); xyzMin = Coord::max(); xyzMax = Coord::min(); for (Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { xyz = iter.getCoord(); xyzMin = std::min(xyzMin, xyz); xyzMax = std::max(xyz, xyzMax); const float expectedValue = (xyz[0] >= 1 && xyz[1] >= 1 && xyz[2] >= 1 && xyz[0] <= 3 && xyz[1] <= 3 && xyz[2] <= 3) ? 3.0 : 2.0; ASSERT_DOUBLES_EXACTLY_EQUAL(expectedValue, *iter); } expectedCount = 5*5*5 // (-2,-2,-2) to (2,2,2) + 3*3*3 // (1,1,1) to (3,3,3) - 2*2*2; // (1,1,1) to (2,2,2) overlap CPPUNIT_ASSERT_EQUAL(expectedCount, tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Coord(-2), xyzMin); CPPUNIT_ASSERT_EQUAL(Coord( 3), xyzMax); // The following tests assume a [3,2,3] tree configuration. tree.clear(); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node // Partially fill a single leaf node. tree.fill(CoordBBox(Coord(8), Coord(14)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Completely fill the leaf node, replacing it with a tile. tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); { const int activeVoxelCount = int(tree.activeVoxelCount()); // Fill a single voxel of the tile with a different (active) value. tree.fill(CoordBBox(Coord(10), Coord(10)), 1.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); CPPUNIT_ASSERT_EQUAL(activeVoxelCount, int(tree.activeVoxelCount())); // Fill the voxel with an inactive value. tree.fill(CoordBBox(Coord(10), Coord(10)), 1.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); CPPUNIT_ASSERT_EQUAL(activeVoxelCount - 1, int(tree.activeVoxelCount())); // Completely fill the leaf node, replacing it with a tile again. tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); } // Expand by one voxel, creating seven neighboring leaf nodes. tree.fill(CoordBBox(Coord(8), Coord(16)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Completely fill the internal node containing the tile, replacing it with // a tile at the next level of the tree. tree.fill(CoordBBox(Coord(0), Coord(31)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2), tree.nonLeafCount()); // Expand by one voxel, creating a layer of leaf nodes on three faces. tree.fill(CoordBBox(Coord(0), Coord(32)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(5*5 + 4*5 + 4*4), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2 + 7), tree.nonLeafCount()); // +7 internal nodes // Completely fill the second-level internal node, replacing it with a root-level tile. tree.fill(CoordBBox(Coord(0), Coord(255)), 0.0); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // Repeat, filling with an inactive value. tree.clear(); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node // Partially fill a single leaf node. tree.fill(CoordBBox(Coord(8), Coord(14)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Completely fill the leaf node, replacing it with a tile. tree.fill(CoordBBox(Coord(8), Coord(15)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Expand by one voxel, creating seven neighboring leaf nodes. tree.fill(CoordBBox(Coord(8), Coord(16)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree.nonLeafCount()); // Completely fill the internal node containing the tile, replacing it with // a tile at the next level of the tree. tree.fill(CoordBBox(Coord(0), Coord(31)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2), tree.nonLeafCount()); // Expand by one voxel, creating a layer of leaf nodes on three faces. tree.fill(CoordBBox(Coord(0), Coord(32)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(5*5 + 4*5 + 4*4), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(2 + 7), tree.nonLeafCount()); // +7 internal nodes // Completely fill the second-level internal node, replacing it with a root-level tile. tree.fill(CoordBBox(Coord(0), Coord(255)), 0.0, /*active=*/false); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); tree.clear(); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node // Partially fill a region with the background value. tree.fill(CoordBBox(Coord(27), Coord(254)), background, /*active=*/false); // Confirm that after pruning, the tree is empty. tree.prune(); CPPUNIT_ASSERT(tree.empty()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node } // Verify that setting voxels inside active tiles works correctly. // In particular, it should preserve the active states of surrounding voxels. void TestTreeGetSetValues::testSetActiveStates() { using namespace openvdb; const float background = 256.0; Tree323f tree(background); const Coord xyz(10); const float val = 42.0; const int expectedActiveCount = 32 * 32 * 32; #define RESET_TREE() \ tree.fill(CoordBBox(Coord(0), Coord(31)), background, /*active=*/true) // create an active tile RESET_TREE(); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); tree.setValueOff(xyz); CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); RESET_TREE(); tree.setValueOn(xyz); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); RESET_TREE(); tree.setValueOff(xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(val, tree.getValue(xyz)); RESET_TREE(); tree.setActiveState(xyz, true); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); RESET_TREE(); tree.setActiveState(xyz, false); CPPUNIT_ASSERT_EQUAL(expectedActiveCount - 1, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(xyz)); RESET_TREE(); tree.setValueOn(xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(val, tree.getValue(xyz)); RESET_TREE(); tools::setValueOnMin(tree, xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(std::min(val, background), tree.getValue(xyz)); RESET_TREE(); tools::setValueOnMax(tree, xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(std::max(val, background), tree.getValue(xyz)); RESET_TREE(); tools::setValueOnSum(tree, xyz, val); CPPUNIT_ASSERT_EQUAL(expectedActiveCount, int(tree.activeVoxelCount())); ASSERT_DOUBLES_EXACTLY_EQUAL(val + background, tree.getValue(xyz)); #undef RESET_TREE } void TestTreeGetSetValues::testHasActiveTiles() { Tree323f tree(/*background=*/256.0f); CPPUNIT_ASSERT(!tree.hasActiveTiles()); // Fill from (-2,-2,-2) to (2,2,2) with active value 2. tree.fill(openvdb::CoordBBox(openvdb::Coord(-2), openvdb::Coord(2)), 2.0f); CPPUNIT_ASSERT(!tree.hasActiveTiles()); // Fill from (-200,-200,-200) to (-4,-4,-4) with active value 3. tree.fill(openvdb::CoordBBox(openvdb::Coord(-200), openvdb::Coord(-4)), 3.0f); CPPUNIT_ASSERT(tree.hasActiveTiles()); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGradient.cc0000644000000000000000000007242112252453157016010 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include "util.h" // for unittest_util::makeSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestGradient: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestGradient); CPPUNIT_TEST(testISGradient); // Gradient in Index Space CPPUNIT_TEST(testISGradientStencil); CPPUNIT_TEST(testWSGradient); // Gradient in World Space CPPUNIT_TEST(testWSGradientStencil); CPPUNIT_TEST(testWSGradientNormSqr); // Gradient Norm Sqr (world space only) CPPUNIT_TEST(testWSGradientNormSqrStencil); // Gradient Norm Sqr (world space only) CPPUNIT_TEST(testGradientTool); // Gradient tool CPPUNIT_TEST(testGradientMaskedTool); // Gradient tool CPPUNIT_TEST(testIntersectsIsoValue); // zero-crossing CPPUNIT_TEST(testOldStyleStencils); // old stencil impl - deprecate CPPUNIT_TEST_SUITE_END(); void testISGradient(); void testISGradientStencil(); void testWSGradient(); void testWSGradientStencil(); void testWSGradientNormSqr(); void testWSGradientNormSqrStencil(); void testGradientTool(); void testGradientMaskedTool(); void testIntersectsIsoValue(); void testOldStyleStencils(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGradient); void TestGradient::testISGradient() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); const float radius=10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const Coord xyz(10, 20, 30); // Index Space Gradients: random access and stencil version AccessorType inAccessor = grid->getConstAccessor(); Vec3f result; result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.02); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.02); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } void TestGradient::testISGradientStencil() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const Coord xyz(10, 20, 30); // Index Space Gradients: stencil version Vec3f result; // this stencil is large enough for all thie different schemes used // in this test math::NineteenPointStencil stencil(*grid); stencil.moveTo(xyz); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.02); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.02); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::ISGradient::result(stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } void TestGradient::testWSGradient() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); const Coord xyz(11, 17, 26); AccessorType inAccessor = grid->getConstAccessor(); // try with a map // Index Space Gradients: stencil version Vec3f result; math::MapBase::Ptr rotated_map; { math::UniformScaleMap map(voxel_size); result = math::Gradient::result( map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); rotated_map = map.preRotate(1.5, math::X_AXIS); // verify the new map is an affine map CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); math::AffineMap::Ptr affine_map = boost::static_pointer_cast(rotated_map); // the gradient should have the same length even after rotation result = math::Gradient::result( *affine_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::Gradient::result( *affine_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { math::UniformScaleTranslateMap map(voxel_size, Vec3d(0,0,0)); result = math::Gradient::result( map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { math::ScaleTranslateMap map(Vec3d(voxel_size, voxel_size, voxel_size), Vec3d(0,0,0)); result = math::Gradient::result( map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // this map has no scale, expect result/voxel_spaceing = 1 math::TranslationMap map; result = math::Gradient::result(map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(voxel_size, result.length(), /*tolerance=*/0.01); } { // test the GenericMap Grid interface math::GenericMap generic_map(*grid); result = math::Gradient::result( generic_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // test the GenericMap Transform interface math::GenericMap generic_map(grid->transform()); result = math::Gradient::result( generic_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // test the GenericMap Map interface math::GenericMap generic_map(rotated_map); result = math::Gradient::result( generic_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // test a map with non-uniform SCALING AND ROTATION Vec3d voxel_sizes(0.25, 0.45, 0.75); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); // apply rotation rotated_map = base_map->preRotate(1.5, math::X_AXIS); grid->setTransform(math::Transform::Ptr(new math::Transform(rotated_map))); // remake the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); math::AffineMap::Ptr affine_map = boost::static_pointer_cast(rotated_map); // math::ScaleMap map(voxel_sizes); result = math::Gradient::result( *affine_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // test a map with non-uniform SCALING Vec3d voxel_sizes(0.25, 0.45, 0.75); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); // remake the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); math::ScaleMap::Ptr scale_map = boost::static_pointer_cast(base_map); // math::ScaleMap map(voxel_sizes); result = math::Gradient::result(*scale_map, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } } void TestGradient::testWSGradientStencil() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f ,10.0f);//i.e. (12,16,20) in index space const float radius = 10; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); const Coord xyz(11, 17, 26); // try with a map math::SevenPointStencil stencil(*grid); stencil.moveTo(xyz); math::SecondOrderDenseStencil dense_2ndOrder(*grid); dense_2ndOrder.moveTo(xyz); math::FourthOrderDenseStencil dense_4thOrder(*grid); dense_4thOrder.moveTo(xyz); Vec3f result; math::MapBase::Ptr rotated_map; { math::UniformScaleMap map(voxel_size); result = math::Gradient::result( map, stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); rotated_map = map.preRotate(1.5, math::X_AXIS); // verify the new map is an affine map CPPUNIT_ASSERT(rotated_map->type() == math::AffineMap::mapType()); math::AffineMap::Ptr affine_map = boost::static_pointer_cast(rotated_map); // the gradient should have the same length even after rotation result = math::Gradient::result( *affine_map, dense_2ndOrder); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); result = math::Gradient::result( *affine_map, dense_4thOrder); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { math::UniformScaleTranslateMap map(voxel_size, Vec3d(0,0,0)); result = math::Gradient::result(map, stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { math::ScaleTranslateMap map(Vec3d(voxel_size, voxel_size, voxel_size), Vec3d(0,0,0)); result = math::Gradient::result(map, stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { math::TranslationMap map; result = math::Gradient::result(map, stencil); // value = 1 because the translation map assumes uniform spacing CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, result.length(), /*tolerance=*/0.01); } { // test the GenericMap Grid interface math::GenericMap generic_map(*grid); result = math::Gradient::result( generic_map, dense_2ndOrder); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // test the GenericMap Transform interface math::GenericMap generic_map(grid->transform()); result = math::Gradient::result( generic_map, dense_2ndOrder); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // test the GenericMap Map interface math::GenericMap generic_map(rotated_map); result = math::Gradient::result( generic_map, dense_2ndOrder); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // test a map with non-uniform SCALING AND ROTATION Vec3d voxel_sizes(0.25, 0.45, 0.75); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); // apply rotation rotated_map = base_map->preRotate(1.5, math::X_AXIS); grid->setTransform(math::Transform::Ptr(new math::Transform(rotated_map))); // remake the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); math::AffineMap::Ptr affine_map = boost::static_pointer_cast(rotated_map); stencil.moveTo(xyz); result = math::Gradient::result(*affine_map, stencil); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } { // test a map with NON-UNIFORM SCALING Vec3d voxel_sizes(0.5, 1.0, 0.75); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); // remake the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); math::ScaleMap map(voxel_sizes); dense_2ndOrder.moveTo(xyz); result = math::Gradient::result(map, dense_2ndOrder); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); } } void TestGradient::testWSGradientNormSqr() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f,8.0f,10.0f);//i.e. (12,16,20) in index space const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); const Coord xyz(11, 17, 26); AccessorType inAccessor = grid->getConstAccessor(); // test gradient in index and world space using the 7-pt stencil math::UniformScaleMap uniform_scale(voxel_size); FloatTree::ValueType normsqrd; normsqrd = math::GradientNormSqrd::result( uniform_scale, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); // test world space using the 13pt stencil normsqrd = math::GradientNormSqrd::result( uniform_scale, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); math::AffineMap affine(voxel_size*math::Mat3d::identity()); normsqrd = math::GradientNormSqrd::result( affine, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); normsqrd = math::GradientNormSqrd::result( uniform_scale, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); } void TestGradient::testWSGradientNormSqrStencil() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); const Coord xyz(11, 17, 26); math::SevenPointStencil sevenpt(*grid); sevenpt.moveTo(xyz); math::ThirteenPointStencil thirteenpt(*grid); thirteenpt.moveTo(xyz); math::SecondOrderDenseStencil dense_2ndOrder(*grid); dense_2ndOrder.moveTo(xyz); math::NineteenPointStencil nineteenpt(*grid); nineteenpt.moveTo(xyz); // test gradient in index and world space using the 7-pt stencil math::UniformScaleMap uniform_scale(voxel_size); FloatTree::ValueType normsqrd; normsqrd = math::GradientNormSqrd::result( uniform_scale, sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); // test gradient in index and world space using the 13pt stencil normsqrd = math::GradientNormSqrd::result( uniform_scale, thirteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); math::AffineMap affine(voxel_size*math::Mat3d::identity()); normsqrd = math::GradientNormSqrd::result( affine, dense_2ndOrder); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.07); normsqrd = math::GradientNormSqrd::result( uniform_scale, nineteenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, normsqrd, /*tolerance=*/0.05); } void TestGradient::testGradientTool() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const Coord xyz(10, 20, 30); Vec3SGrid::Ptr grad = tools::gradient(*grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(grad->activeVoxelCount())); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, grad->getConstAccessor().getValue(xyz).length(), /*tolerance=*/0.01); } void TestGradient::testGradientMaskedTool() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); const openvdb::Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius = 10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); BoolGrid::Ptr maskGrid = BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); Vec3SGrid::Ptr grad = tools::gradient(*grid, *maskGrid); {// outside the masked region const Coord xyz(10, 20, 30); CPPUNIT_ASSERT(!maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, grad->getConstAccessor().getValue(xyz).length(), /*tolerance=*/0.01); } {// inside the masked region const Coord xyz(38, 35, 33); CPPUNIT_ASSERT(maskbbox.isInside(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, grad->getConstAccessor().getValue(xyz).length(), /*tolerance=*/0.01); } } void TestGradient::testIntersectsIsoValue() { using namespace openvdb; {// test zero crossing in -x FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(-1,0,0), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT( stencil.intersects( )); CPPUNIT_ASSERT( stencil.intersects( 0.0f)); CPPUNIT_ASSERT( stencil.intersects( 2.0f)); CPPUNIT_ASSERT(!stencil.intersects( 5.5f)); CPPUNIT_ASSERT(!stencil.intersects(-2.5f)); } {// test zero crossing in +x FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(1,0,0), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in -y FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(0,-1,0), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in y FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(0,1,0), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in -z FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(0,0,-1), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in z FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(0,0,1), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } {// test zero crossing in -x & z FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(-1,0,1), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(!stencil.intersects()); } {// test zero multiple crossings FloatGrid grid(/*backgroundValue=*/5.0); FloatTree& tree = grid.tree(); Coord xyz(2,-5,60); tree.setValue(xyz, 1.3f); tree.setValue(xyz.offsetBy(-1, 0, 1), -1.0f); tree.setValue(xyz.offsetBy( 0, 0, 1), -2.0f); tree.setValue(xyz.offsetBy( 0, 1, 0), -3.0f); tree.setValue(xyz.offsetBy( 0, 0,-1), -2.0f); math::SevenPointStencil stencil(grid); stencil.moveTo(xyz); CPPUNIT_ASSERT(stencil.intersects()); } } void TestGradient::testOldStyleStencils() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(/*voxel size=*/0.5)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f,8.0f,10.0f);//i.e. (12,16,20) in index space const float radius=10.0f; unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); const Coord xyz(11, 17, 26); math::GradStencil gs(*grid); gs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, gs.gradient().length(), /*tolerance=*/0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, gs.normSqGrad(), /*tolerance=*/0.10); math::WenoStencil ws(*grid); ws.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, ws.gradient().length(), /*tolerance=*/0.01); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, ws.normSqGrad(), /*tolerance=*/0.01); math::CurvatureStencil cs(*grid); cs.moveTo(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, cs.gradient().length(), /*tolerance=*/0.01); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestNodeMask.cc0000644000000000000000000001470212252453157015752 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include using openvdb::Index; template void TestAll(); class TestNodeMask: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestNodeMask); CPPUNIT_TEST(testAll4); CPPUNIT_TEST(testAll3); CPPUNIT_TEST(testAll2); CPPUNIT_TEST(testAll1); CPPUNIT_TEST_SUITE_END(); void testAll4() { TestAll >(); } void testAll3() { TestAll >(); } void testAll2() { TestAll >(); } void testAll1() { TestAll >(); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestNodeMask); template void TestAll() { CPPUNIT_ASSERT(MaskType::memUsage() == MaskType::SIZE/8); const Index SIZE = MaskType::SIZE > 512 ? 512 : MaskType::SIZE; {// default constructor MaskType m;//all bits are off for (Index i=0; i #include #include #include #include #include #include class TestTreeVisitor: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTreeVisitor); CPPUNIT_TEST(testVisitTreeBool); CPPUNIT_TEST(testVisitTreeInt32); CPPUNIT_TEST(testVisitTreeFloat); CPPUNIT_TEST(testVisitTreeVec2I); CPPUNIT_TEST(testVisitTreeVec3S); CPPUNIT_TEST(testVisit2Trees); CPPUNIT_TEST_SUITE_END(); void testVisitTreeBool() { visitTree(); } void testVisitTreeInt32() { visitTree(); } void testVisitTreeFloat() { visitTree(); } void testVisitTreeVec2I() { visitTree(); } void testVisitTreeVec3S() { visitTree(); } void testVisit2Trees(); private: template TreeT createTestTree() const; template void visitTree(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeVisitor); //////////////////////////////////////// template TreeT TestTreeVisitor::createTestTree() const { typedef typename TreeT::ValueType ValueT; const ValueT zero = openvdb::zeroVal(), one = zero + 1; // Create a sparse test tree comprising the eight corners of // a 200 x 200 x 200 cube. TreeT tree(/*background=*/one); tree.setValue(openvdb::Coord( 0, 0, 0), /*value=*/zero); tree.setValue(openvdb::Coord(200, 0, 0), zero); tree.setValue(openvdb::Coord( 0, 200, 0), zero); tree.setValue(openvdb::Coord( 0, 0, 200), zero); tree.setValue(openvdb::Coord(200, 0, 200), zero); tree.setValue(openvdb::Coord( 0, 200, 200), zero); tree.setValue(openvdb::Coord(200, 200, 0), zero); tree.setValue(openvdb::Coord(200, 200, 200), zero); // Verify that the bounding box of all On values is 200 x 200 x 200. openvdb::CoordBBox bbox; CPPUNIT_ASSERT(tree.evalActiveVoxelBoundingBox(bbox)); CPPUNIT_ASSERT(bbox.min() == openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT(bbox.max() == openvdb::Coord(200, 200, 200)); return tree; } //////////////////////////////////////// namespace { /// Single-tree visitor that accumulates node counts class Visitor { public: typedef std::map > NodeMap; Visitor(): mSkipLeafNodes(false) { reset(); } void reset() { mSkipLeafNodes = false; mNodes.clear(); mNonConstIterUseCount = mConstIterUseCount = 0; } void setSkipLeafNodes(bool b) { mSkipLeafNodes = b; } template bool operator()(IterT& iter) { incrementIterUseCount(boost::is_const::value); CPPUNIT_ASSERT(iter.getParentNode() != NULL); if (mSkipLeafNodes && iter.parent().getLevel() == 1) return true; typedef typename IterT::NonConstValueType ValueT; typedef typename IterT::ChildNodeType ChildT; ValueT value; if (const ChildT* child = iter.probeChild(value)) { insertChild(child); } return false; } openvdb::Index leafCount() const { NodeMap::const_iterator it = mNodes.find(0); return (it != mNodes.end()) ? it->second.size() : 0; } openvdb::Index nonLeafCount() const { openvdb::Index count = 1; // root node for (NodeMap::const_iterator i = mNodes.begin(), e = mNodes.end(); i != e; ++i) { if (i->first != 0) count += i->second.size(); } return count; } bool usedOnlyConstIterators() const { return (mConstIterUseCount > 0 && mNonConstIterUseCount == 0); } bool usedOnlyNonConstIterators() const { return (mConstIterUseCount == 0 && mNonConstIterUseCount > 0); } private: template void insertChild(const ChildT* child) { if (child != NULL) { const openvdb::Index level = child->getLevel(); if (!mSkipLeafNodes || level > 0) { mNodes[level].insert(child); } } } void incrementIterUseCount(bool isConst) { if (isConst) ++mConstIterUseCount; else ++mNonConstIterUseCount; } bool mSkipLeafNodes; NodeMap mNodes; int mNonConstIterUseCount, mConstIterUseCount; }; /// Specialization for LeafNode iterators, whose ChildNodeType is void /// (therefore can't call child->getLevel()) template<> inline void Visitor::insertChild(const void*) {} } // unnamed namespace template void TestTreeVisitor::visitTree() { TreeT tree = createTestTree(); { // Traverse the tree, accumulating node counts. Visitor visitor; const_cast(tree).visit(visitor); CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.leafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); } { // Traverse the tree, accumulating node counts as above, // but using non-const iterators. Visitor visitor; tree.visit(visitor); CPPUNIT_ASSERT(visitor.usedOnlyNonConstIterators()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.leafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); } { // Traverse the tree, accumulating counts of non-leaf nodes only. Visitor visitor; visitor.setSkipLeafNodes(true); const_cast(tree).visit(visitor); CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); CPPUNIT_ASSERT_EQUAL(0U, visitor.leafCount()); // leaf nodes were skipped CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.nonLeafCount()); } } //////////////////////////////////////// namespace { /// Two-tree visitor that accumulates node counts class Visitor2 { public: typedef std::map > NodeMap; Visitor2() { reset(); } void reset() { mSkipALeafNodes = mSkipBLeafNodes = false; mANodeCount.clear(); mBNodeCount.clear(); } void setSkipALeafNodes(bool b) { mSkipALeafNodes = b; } void setSkipBLeafNodes(bool b) { mSkipBLeafNodes = b; } openvdb::Index aLeafCount() const { return leafCount(/*useA=*/true); } openvdb::Index bLeafCount() const { return leafCount(/*useA=*/false); } openvdb::Index aNonLeafCount() const { return nonLeafCount(/*useA=*/true); } openvdb::Index bNonLeafCount() const { return nonLeafCount(/*useA=*/false); } template int operator()(AIterT& aIter, BIterT& bIter) { CPPUNIT_ASSERT(aIter.getParentNode() != NULL); CPPUNIT_ASSERT(bIter.getParentNode() != NULL); typename AIterT::NodeType& aNode = aIter.parent(); typename BIterT::NodeType& bNode = bIter.parent(); const openvdb::Index aLevel = aNode.getLevel(), bLevel = bNode.getLevel(); mANodeCount[aLevel].insert(&aNode); mBNodeCount[bLevel].insert(&bNode); int skipBranch = 0; if (aLevel == 1 && mSkipALeafNodes) skipBranch = (skipBranch | 1); if (bLevel == 1 && mSkipBLeafNodes) skipBranch = (skipBranch | 2); return skipBranch; } private: openvdb::Index leafCount(bool useA) const { const NodeMap& theMap = (useA ? mANodeCount : mBNodeCount); NodeMap::const_iterator it = theMap.find(0); if (it != theMap.end()) return it->second.size(); return 0; } openvdb::Index nonLeafCount(bool useA) const { openvdb::Index count = 0; const NodeMap& theMap = (useA ? mANodeCount : mBNodeCount); for (NodeMap::const_iterator i = theMap.begin(), e = theMap.end(); i != e; ++i) { if (i->first != 0) count += i->second.size(); } return count; } bool mSkipALeafNodes, mSkipBLeafNodes; NodeMap mANodeCount, mBNodeCount; }; } // unnamed namespace void TestTreeVisitor::testVisit2Trees() { typedef openvdb::FloatTree TreeT; typedef openvdb::VectorTree Tree2T; typedef TreeT::ValueType ValueT; typedef Tree2T::ValueType Value2T; // Create a test tree. TreeT tree = createTestTree(); // Create another test tree of a different type but with the same topology. Tree2T tree2 = createTestTree(); // Traverse both trees. Visitor2 visitor; tree.visit2(tree2, visitor); //CPPUNIT_ASSERT(visitor.usedOnlyConstIterators()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.bNonLeafCount()); visitor.reset(); // Change the topology of the first tree. tree.setValue(openvdb::Coord(-200, -200, -200), openvdb::zeroVal()); // Traverse both trees. tree.visit2(tree2, visitor); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.bNonLeafCount()); visitor.reset(); // Traverse the two trees in the opposite order. tree2.visit2(tree, visitor); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); // Repeat, skipping leaf nodes of tree2. visitor.reset(); visitor.setSkipALeafNodes(true); tree2.visit2(tree, visitor); CPPUNIT_ASSERT_EQUAL(0U, visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.leafCount(), visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); // Repeat, skipping leaf nodes of tree. visitor.reset(); visitor.setSkipBLeafNodes(true); tree2.visit2(tree, visitor); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), visitor.aLeafCount()); CPPUNIT_ASSERT_EQUAL(0U, visitor.bLeafCount()); CPPUNIT_ASSERT_EQUAL(tree2.nonLeafCount(), visitor.aNonLeafCount()); CPPUNIT_ASSERT_EQUAL(tree.nonLeafCount(), visitor.bNonLeafCount()); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestDoubleMetadata.cc0000644000000000000000000000564312252453157017130 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestDoubleMetadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestDoubleMetadata); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDoubleMetadata); void TestDoubleMetadata::test() { using namespace openvdb; Metadata::Ptr m(new DoubleMetadata(1.23)); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("double") == 0); CPPUNIT_ASSERT(m2->typeName().compare("double") == 0); DoubleMetadata *s = dynamic_cast(m.get()); //CPPUNIT_ASSERT(s->value() == 1.23); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.23,s->value(),0); s->value() = 4.56; //CPPUNIT_ASSERT(s->value() == 4.56); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.56,s->value(),0); m2->copy(*s); s = dynamic_cast(m2.get()); //CPPUNIT_ASSERT(s->value() == 4.56); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.56,s->value(),0); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestFloatMetadata.cc0000644000000000000000000000562212252453157016760 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestFloatMetadata : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestFloatMetadata); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFloatMetadata); void TestFloatMetadata::test() { using namespace openvdb; Metadata::Ptr m(new FloatMetadata(1.0)); Metadata::Ptr m2 = m->copy(); CPPUNIT_ASSERT(dynamic_cast(m.get()) != 0); CPPUNIT_ASSERT(dynamic_cast(m2.get()) != 0); CPPUNIT_ASSERT(m->typeName().compare("float") == 0); CPPUNIT_ASSERT(m2->typeName().compare("float") == 0); FloatMetadata *s = dynamic_cast(m.get()); //CPPUNIT_ASSERT(s->value() == 1.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0f,s->value(),0); s->value() = 2.0; //CPPUNIT_ASSERT(s->value() == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,s->value(),0); m2->copy(*s); s = dynamic_cast(m2.get()); //CPPUNIT_ASSERT(s->value() == 2.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0f,s->value(),0); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestParticlesToLevelSet.cc0000644000000000000000000005047512252453157020155 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestParticlesToLevelSet: public CppUnit::TestFixture { public: virtual void setUp() {openvdb::initialize();} virtual void tearDown() {openvdb::uninitialize();} void writeGrid(openvdb::GridBase::Ptr grid, std::string fileName) const { std::cout << "\nWriting \""<setName("TestParticlesToLevelSet"); openvdb::GridPtrVec grids; grids.push_back(grid); openvdb::io::File file("/tmp/" + fileName + ".vdb"); file.write(grids); file.close(); } CPPUNIT_TEST_SUITE(TestParticlesToLevelSet); CPPUNIT_TEST(testMyParticleList); CPPUNIT_TEST(testRasterizeSpheres); CPPUNIT_TEST(testRasterizeSpheresAndId); CPPUNIT_TEST(testRasterizeTrails); CPPUNIT_TEST(testRasterizeTrailsAndId); CPPUNIT_TEST_SUITE_END(); void testMyParticleList(); void testRasterizeSpheres(); void testRasterizeSpheresAndId(); void testRasterizeTrails(); void testRasterizeTrailsAndId(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestParticlesToLevelSet); class MyParticleList { protected: struct MyParticle { openvdb::Vec3R p, v; openvdb::Real r; }; openvdb::Real mRadiusScale; openvdb::Real mVelocityScale; std::vector mParticleList; public: MyParticleList(openvdb::Real rScale=1, openvdb::Real vScale=1) : mRadiusScale(rScale), mVelocityScale(vScale) {} void add(const openvdb::Vec3R &p, const openvdb::Real &r, const openvdb::Vec3R &v=openvdb::Vec3R(0,0,0)) { MyParticle pa; pa.p = p; pa.r = r; pa.v = v; mParticleList.push_back(pa); } /// @return coordinate bbox in the space of the specified transfrom openvdb::CoordBBox getBBox(const openvdb::GridBase& grid) { openvdb::CoordBBox bbox; openvdb::Coord &min= bbox.min(), &max = bbox.max(); openvdb::Vec3R pos; openvdb::Real rad, invDx = 1/grid.voxelSize()[0]; for (size_t n=0, e=this->size(); ngetPosRad(n, pos, rad); const openvdb::Vec3d xyz = grid.worldToIndex(pos); const openvdb::Real r = rad * invDx; for (int i=0; i<3; ++i) { min[i] = openvdb::math::Min(min[i], openvdb::math::Floor(xyz[i] - r)); max[i] = openvdb::math::Max(max[i], openvdb::math::Ceil( xyz[i] + r)); } } return bbox; } //typedef int AttributeType; // The methods below are only required for the unit-tests openvdb::Vec3R pos(int n) const {return mParticleList[n].p;} openvdb::Vec3R vel(int n) const {return mVelocityScale*mParticleList[n].v;} openvdb::Real radius(int n) const {return mRadiusScale*mParticleList[n].r;} ////////////////////////////////////////////////////////////////////////////// /// The methods below are the only ones required by tools::ParticleToLevelSet /// @note We return by value since the radius and velocities are modified /// by the scaling factors! Also these methods are all assumed to /// be thread-safe. /// Return the total number of particles in list. /// Always required! size_t size() const { return mParticleList.size(); } /// Get the world space position of n'th particle. /// Required by ParticledToLevelSet::rasterizeSphere(*this,radius). void getPos(size_t n, openvdb::Vec3R&pos) const { pos = mParticleList[n].p; } void getPosRad(size_t n, openvdb::Vec3R& pos, openvdb::Real& rad) const { pos = mParticleList[n].p; rad = mRadiusScale*mParticleList[n].r; } void getPosRadVel(size_t n, openvdb::Vec3R& pos, openvdb::Real& rad, openvdb::Vec3R& vel) const { pos = mParticleList[n].p; rad = mRadiusScale*mParticleList[n].r; vel = mVelocityScale*mParticleList[n].v; } // The method below is only required for attribute transfer void getAtt(size_t n, openvdb::Index32& att) const { att = n; } }; void TestParticlesToLevelSet::testMyParticleList() { MyParticleList pa; CPPUNIT_ASSERT_EQUAL(0, int(pa.size())); pa.add(openvdb::Vec3R(10,10,10), 2, openvdb::Vec3R(1,0,0)); CPPUNIT_ASSERT_EQUAL(1, int(pa.size())); ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(10, pa.pos(0)[2]); ASSERT_DOUBLES_EXACTLY_EQUAL(1 , pa.vel(0)[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(0)[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(0)[2]); ASSERT_DOUBLES_EXACTLY_EQUAL(2 , pa.radius(0)); pa.add(openvdb::Vec3R(20,20,20), 3); CPPUNIT_ASSERT_EQUAL(2, int(pa.size())); ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20, pa.pos(1)[2]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0 , pa.vel(1)[2]); ASSERT_DOUBLES_EXACTLY_EQUAL(3 , pa.radius(1)); const float voxelSize = 0.5f, halfWidth = 4.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); openvdb::CoordBBox bbox = pa.getBBox(*ls); ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[0]); ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[1]); ASSERT_DOUBLES_EXACTLY_EQUAL((10-2)/voxelSize, bbox.min()[2]); ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[0]); ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[1]); ASSERT_DOUBLES_EXACTLY_EQUAL((20+3)/voxelSize, bbox.max()[2]); } void TestParticlesToLevelSet::testRasterizeSpheres() { MyParticleList pa; pa.add(openvdb::Vec3R(10,10,10), 2); pa.add(openvdb::Vec3R(20,20,20), 2); // testing CSG pa.add(openvdb::Vec3R(31.0,31,31), 5); pa.add(openvdb::Vec3R(31.5,31,31), 5); pa.add(openvdb::Vec3R(32.0,31,31), 5); pa.add(openvdb::Vec3R(32.5,31,31), 5); pa.add(openvdb::Vec3R(33.0,31,31), 5); pa.add(openvdb::Vec3R(33.5,31,31), 5); pa.add(openvdb::Vec3R(34.0,31,31), 5); pa.add(openvdb::Vec3R(34.5,31,31), 5); pa.add(openvdb::Vec3R(35.0,31,31), 5); pa.add(openvdb::Vec3R(35.5,31,31), 5); pa.add(openvdb::Vec3R(36.0,31,31), 5); CPPUNIT_ASSERT_EQUAL(13, int(pa.size())); const float voxelSize = 1.0f, halfWidth = 2.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); openvdb::tools::ParticlesToLevelSet raster(*ls); raster.setGrainSize(1);//a value of zero disables threading raster.rasterizeSpheres(pa); raster.finalize(); //openvdb::FloatGrid::Ptr ls = raster.getSdfGrid(); //ls->tree().print(std::cout,4); //this->writeGrid(ls, "testRasterizeSpheres"); ASSERT_DOUBLES_EXACTLY_EQUAL(halfWidth * voxelSize, ls->tree().getValue(openvdb::Coord( 0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord( 6,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord( 7,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord( 8,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord( 9,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(10,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(11,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(12,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(13,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(14,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,16,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,17,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,18,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,19,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(20,20,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,21,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,22,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,23,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,24,20))); {// full but slow test of all voxels openvdb::CoordBBox bbox = pa.getBBox(*ls); bbox.expand(static_cast(halfWidth)+1); openvdb::Index64 count=0; const float outside = ls->background(), inside = -outside; const openvdb::Coord &min=bbox.min(), &max=bbox.max(); for (openvdb::Coord ijk=min; ijk[0]indexToWorld(ijk.asVec3d()); double dist = (xyz-pa.pos(0)).length()-pa.radius(0); for (int i=1, s=pa.size(); itree().getValue(ijk); if (dist >= outside) { CPPUNIT_ASSERT_DOUBLES_EQUAL(outside, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); } else if( dist <= inside ) { CPPUNIT_ASSERT_DOUBLES_EQUAL(inside, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); } else { CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOn(ijk)); ++count; } } } } //std::cerr << "\nExpected active voxel count = " << count // << ", actual active voxle count = " // << ls->activeVoxelCount() << std::endl; CPPUNIT_ASSERT_EQUAL(count, ls->activeVoxelCount()); } } void TestParticlesToLevelSet::testRasterizeSpheresAndId() { MyParticleList pa(0.5f); pa.add(openvdb::Vec3R(10,10,10), 4); pa.add(openvdb::Vec3R(20,20,20), 4); // testing CSG pa.add(openvdb::Vec3R(31.0,31,31),10); pa.add(openvdb::Vec3R(31.5,31,31),10); pa.add(openvdb::Vec3R(32.0,31,31),10); pa.add(openvdb::Vec3R(32.5,31,31),10); pa.add(openvdb::Vec3R(33.0,31,31),10); pa.add(openvdb::Vec3R(33.5,31,31),10); pa.add(openvdb::Vec3R(34.0,31,31),10); pa.add(openvdb::Vec3R(34.5,31,31),10); pa.add(openvdb::Vec3R(35.0,31,31),10); pa.add(openvdb::Vec3R(35.5,31,31),10); pa.add(openvdb::Vec3R(36.0,31,31),10); CPPUNIT_ASSERT_EQUAL(13, int(pa.size())); typedef openvdb::tools::ParticlesToLevelSet RasterT; const float voxelSize = 1.0f, halfWidth = 2.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); RasterT raster(*ls); raster.setGrainSize(1);//a value of zero disables threading raster.rasterizeSpheres(pa); raster.finalize(); const RasterT::AttGridType::Ptr id = raster.attributeGrid(); int minVal = std::numeric_limits::max(), maxVal = -minVal; for (RasterT::AttGridType::ValueOnCIter i=id->cbeginValueOn(); i; ++i) { minVal = openvdb::math::Min(minVal, int(*i)); maxVal = openvdb::math::Max(maxVal, int(*i)); } CPPUNIT_ASSERT_EQUAL(0 , minVal); CPPUNIT_ASSERT_EQUAL(12, maxVal); //grid.tree().print(std::cout,4); //id->print(std::cout,4); //this->writeGrid(ls, "testRasterizeSpheres"); ASSERT_DOUBLES_EXACTLY_EQUAL(halfWidth * voxelSize, ls->tree().getValue(openvdb::Coord( 0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord( 6,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord( 7,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord( 8,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord( 9,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(10,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(11,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(12,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(13,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(14,10,10))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,16,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,17,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,18,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,19,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-2, ls->tree().getValue(openvdb::Coord(20,20,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(-1, ls->tree().getValue(openvdb::Coord(20,21,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 0, ls->tree().getValue(openvdb::Coord(20,22,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 1, ls->tree().getValue(openvdb::Coord(20,23,20))); ASSERT_DOUBLES_EXACTLY_EQUAL( 2, ls->tree().getValue(openvdb::Coord(20,24,20))); {// full but slow test of all voxels openvdb::CoordBBox bbox = pa.getBBox(*ls); bbox.expand(static_cast(halfWidth)+1); openvdb::Index64 count = 0; const float outside = ls->background(), inside = -outside; const openvdb::Coord &min=bbox.min(), &max=bbox.max(); for (openvdb::Coord ijk=min; ijk[0]indexToWorld(ijk.asVec3d()); double dist = (xyz-pa.pos(0)).length()-pa.radius(0); openvdb::Index32 k =0; for (int i=1, s=pa.size(); itree().getValue(ijk); openvdb::Index32 m = id->tree().getValue(ijk); if (dist >= outside) { CPPUNIT_ASSERT_DOUBLES_EQUAL(outside, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); //CPPUNIT_ASSERT_EQUAL(openvdb::util::INVALID_IDX, m); CPPUNIT_ASSERT(id->tree().isValueOff(ijk)); } else if( dist <= inside ) { CPPUNIT_ASSERT_DOUBLES_EQUAL(inside, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); //CPPUNIT_ASSERT_EQUAL(openvdb::util::INVALID_IDX, m); CPPUNIT_ASSERT(id->tree().isValueOff(ijk)); } else { CPPUNIT_ASSERT_DOUBLES_EQUAL( dist, val, 0.0001); CPPUNIT_ASSERT(ls->tree().isValueOn(ijk)); CPPUNIT_ASSERT_EQUAL(k, m); CPPUNIT_ASSERT(id->tree().isValueOn(ijk)); ++count; } } } } //std::cerr << "\nExpected active voxel count = " << count // << ", actual active voxle count = " // << ls->activeVoxelCount() << std::endl; CPPUNIT_ASSERT_EQUAL(count, ls->activeVoxelCount()); } } /// This is not really a conventional unit-test since the result of /// the tests are written to a file and need to be visually verified! void TestParticlesToLevelSet::testRasterizeTrails() { const float voxelSize = 1.0f, halfWidth = 2.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); MyParticleList pa(1,5); // This particle radius = 1 < 1.5 i.e. it's below the Nyquist frequency and hence ignored pa.add(openvdb::Vec3R( 0, 0, 0), 1, openvdb::Vec3R( 0, 1, 0)); pa.add(openvdb::Vec3R(-10,-10,-10), 2, openvdb::Vec3R( 2, 0, 0)); pa.add(openvdb::Vec3R( 10, 10, 10), 3, openvdb::Vec3R( 0, 1, 0)); pa.add(openvdb::Vec3R( 0, 0, 0), 6, openvdb::Vec3R( 0, 0,-5)); pa.add(openvdb::Vec3R( 20, 0, 0), 2, openvdb::Vec3R( 0, 0, 0)); openvdb::tools::ParticlesToLevelSet raster(*ls); raster.rasterizeTrails(pa, 0.75);//scale offset between two instances //ls->tree().print(std::cout, 4); //this->writeGrid(ls, "testRasterizeTrails"); } void TestParticlesToLevelSet::testRasterizeTrailsAndId() { MyParticleList pa(1,5); // This particle radius = 1 < 1.5 i.e. it's below the Nyquist frequency and hence ignored pa.add(openvdb::Vec3R( 0, 0, 0), 1, openvdb::Vec3R( 0, 1, 0)); pa.add(openvdb::Vec3R(-10,-10,-10), 2, openvdb::Vec3R( 2, 0, 0)); pa.add(openvdb::Vec3R( 10, 10, 10), 3, openvdb::Vec3R( 0, 1, 0)); pa.add(openvdb::Vec3R( 0, 0, 0), 6, openvdb::Vec3R( 0, 0,-5)); typedef openvdb::tools::ParticlesToLevelSet RasterT; const float voxelSize = 1.0f, halfWidth = 2.0f; openvdb::FloatGrid::Ptr ls = openvdb::createLevelSet(voxelSize, halfWidth); RasterT raster(*ls); raster.rasterizeTrails(pa, 0.75);//scale offset between two instances raster.finalize(); const RasterT::AttGridType::Ptr id = raster.attributeGrid(); CPPUNIT_ASSERT(!ls->empty()); CPPUNIT_ASSERT(!id->empty()); CPPUNIT_ASSERT_EQUAL(ls->activeVoxelCount(),id->activeVoxelCount()); int min = std::numeric_limits::max(), max = -min; for (RasterT::AttGridType::ValueOnCIter i=id->cbeginValueOn(); i; ++i) { min = openvdb::math::Min(min, int(*i)); max = openvdb::math::Max(max, int(*i)); } CPPUNIT_ASSERT_EQUAL(1, min);//first particle is ignored because of its small rdadius! CPPUNIT_ASSERT_EQUAL(3, max); //ls->tree().print(std::cout, 4); //this->writeGrid(ls, "testRasterizeTrails"); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLevelSetUtil.cc0000644000000000000000000000677312252453157016643 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include class TestLevelSetUtil: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestLevelSetUtil); CPPUNIT_TEST(testMinMaxVoxel); CPPUNIT_TEST(testLevelSetToFogVolume); CPPUNIT_TEST_SUITE_END(); void testMinMaxVoxel(); void testRelativeIsoOffset(); void testLevelSetToFogVolume(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLevelSetUtil); //////////////////////////////////////// void TestLevelSetUtil::testMinMaxVoxel() { openvdb::FloatTree myTree(std::numeric_limits::max()); openvdb::tree::ValueAccessor acc(myTree); for (int i = -9; i < 10; ++i) { for (int j = -9; j < 10; ++j) { acc.setValue(openvdb::Coord(i,j,0), static_cast(j)); } } openvdb::tree::LeafManager leafs(myTree); openvdb::tools::MinMaxVoxel minmax(leafs); minmax.runParallel(); CPPUNIT_ASSERT(!(minmax.minVoxel() < -9.0)); CPPUNIT_ASSERT(!(minmax.maxVoxel() > 9.0)); } void TestLevelSetUtil::testLevelSetToFogVolume() { openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(10.0); grid->fill(openvdb::CoordBBox(openvdb::Coord(-100), openvdb::Coord(100)), 9.0); grid->fill(openvdb::CoordBBox(openvdb::Coord(-50), openvdb::Coord(50)), -9.0); openvdb::tools::sdfToFogVolume(*grid); CPPUNIT_ASSERT(grid->background() < 1e-7); openvdb::FloatGrid::ValueOnIter iter = grid->beginValueOn(); for (; iter; ++iter) { CPPUNIT_ASSERT(iter.getValue() > 0.0); CPPUNIT_ASSERT(std::abs(iter.getValue() - 1.0) < 1e-7); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLeaf.cc0000644000000000000000000002362512252453157015124 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestLeaf: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestLeaf); CPPUNIT_TEST(testBuffer); CPPUNIT_TEST(testGetValue); CPPUNIT_TEST(testSetValue); CPPUNIT_TEST(testIsValueSet); CPPUNIT_TEST(testProbeValue); CPPUNIT_TEST(testIterators); CPPUNIT_TEST(testEquivalence); CPPUNIT_TEST(testGetOrigin); CPPUNIT_TEST(testIteratorGetCoord); CPPUNIT_TEST(testNegativeIndexing); CPPUNIT_TEST(testSignedFloodFill); CPPUNIT_TEST_SUITE_END(); void testBuffer(); void testGetValue(); void testSetValue(); void testIsValueSet(); void testProbeValue(); void testIterators(); void testEquivalence(); void testGetOrigin(); void testIteratorGetCoord(); void testNegativeIndexing(); void testSignedFloodFill(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLeaf); typedef openvdb::tree::LeafNode LeafType; typedef LeafType::Buffer BufferType; using openvdb::Index; void TestLeaf::testBuffer() { {// access BufferType buf; for (Index i = 0; i < BufferType::size(); ++i) { buf.mData[i] = i; CPPUNIT_ASSERT(buf[i] == buf.mData[i]); } for (Index i = 0; i < BufferType::size(); ++i) { buf[i] = i; CPPUNIT_ASSERT_EQUAL(int(i), buf[i]); } } {// swap BufferType buf0, buf1, buf2; int *buf0Data = buf0.mData; int *buf1Data = buf1.mData; for (Index i = 0; i < BufferType::size(); ++i) { buf0[i] = i; buf1[i] = i * 2; } buf0.swap(buf1); CPPUNIT_ASSERT(buf0.mData == buf1Data); CPPUNIT_ASSERT(buf1.mData == buf0Data); buf1.swap(buf0); CPPUNIT_ASSERT(buf0.mData == buf0Data); CPPUNIT_ASSERT(buf1.mData == buf1Data); buf0.swap(buf2); CPPUNIT_ASSERT(buf2.mData == buf0Data); buf2.swap(buf0); CPPUNIT_ASSERT(buf0.mData == buf0Data); } } void TestLeaf::testGetValue() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.mBuffer[0] = 2; leaf.mBuffer[1] = 3; leaf.mBuffer[2] = 4; leaf.mBuffer[65] = 10; CPPUNIT_ASSERT_EQUAL(2, leaf.getValue(openvdb::Coord(0, 0, 0))); CPPUNIT_ASSERT_EQUAL(3, leaf.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT_EQUAL(4, leaf.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(openvdb::Coord(1, 0, 1))); } void TestLeaf::testSetValue() { LeafType leaf(openvdb::Coord(0, 0, 0), 3); openvdb::Coord xyz(0, 0, 0); leaf.setValueOn(xyz, 10); CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(xyz)); xyz.reset(7, 7, 7); leaf.setValueOn(xyz, 7); CPPUNIT_ASSERT_EQUAL(7, leaf.getValue(xyz)); leaf.setValueOnly(xyz, 10); CPPUNIT_ASSERT_EQUAL(10, leaf.getValue(xyz)); xyz.reset(2, 3, 6); leaf.setValueOn(xyz, 236); CPPUNIT_ASSERT_EQUAL(236, leaf.getValue(xyz)); leaf.setValueOff(xyz, 1); CPPUNIT_ASSERT_EQUAL(1, leaf.getValue(xyz)); CPPUNIT_ASSERT(!leaf.isValueOn(xyz)); } void TestLeaf::testIsValueSet() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(1, 5, 7), 10); CPPUNIT_ASSERT(leaf.isValueOn(openvdb::Coord(1, 5, 7))); CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(0, 5, 7))); CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(1, 6, 7))); CPPUNIT_ASSERT(!leaf.isValueOn(openvdb::Coord(0, 5, 6))); } void TestLeaf::testProbeValue() { LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(1, 6, 5), 10); LeafType::ValueType val; CPPUNIT_ASSERT(leaf.probeValue(openvdb::Coord(1, 6, 5), val)); CPPUNIT_ASSERT(!leaf.probeValue(openvdb::Coord(1, 6, 4), val)); } void TestLeaf::testIterators() { LeafType leaf(openvdb::Coord(0, 0, 0), 2); leaf.setValueOn(openvdb::Coord(1, 2, 3), -3); leaf.setValueOn(openvdb::Coord(5, 2, 3), 4); LeafType::ValueType sum = 0; for (LeafType::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) sum += *iter; CPPUNIT_ASSERT_EQUAL((-3 + 4), sum); } void TestLeaf::testEquivalence() { LeafType leaf( openvdb::Coord(0, 0, 0), 2); LeafType leaf2(openvdb::Coord(0, 0, 0), 3); CPPUNIT_ASSERT(leaf != leaf2); for(openvdb::Index32 i = 0; i < LeafType::size(); ++i) { leaf.setValueOnly(i, i); leaf2.setValueOnly(i, i); } CPPUNIT_ASSERT(leaf == leaf2); // set some values. leaf.setValueOn(openvdb::Coord(0, 0, 0), 1); leaf.setValueOn(openvdb::Coord(0, 1, 0), 1); leaf.setValueOn(openvdb::Coord(1, 1, 0), 1); leaf.setValueOn(openvdb::Coord(1, 1, 2), 1); leaf2.setValueOn(openvdb::Coord(0, 0, 0), 1); leaf2.setValueOn(openvdb::Coord(0, 1, 0), 1); leaf2.setValueOn(openvdb::Coord(1, 1, 0), 1); leaf2.setValueOn(openvdb::Coord(1, 1, 2), 1); CPPUNIT_ASSERT(leaf == leaf2); leaf2.setValueOn(openvdb::Coord(0, 0, 1), 1); CPPUNIT_ASSERT(leaf != leaf2); leaf2.setValueOff(openvdb::Coord(0, 0, 1), 1); CPPUNIT_ASSERT(leaf == leaf2); } void TestLeaf::testGetOrigin() { { LeafType leaf(openvdb::Coord(1, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(0, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(8, 0, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(8, 1, 0), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(1024, 1, 3), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(128*8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(1023, 1, 3), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(127*8, 0, 0), leaf.origin()); } { LeafType leaf(openvdb::Coord(512, 512, 512), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(512, 512, 512), leaf.origin()); } { LeafType leaf(openvdb::Coord(2, 52, 515), 1); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 48, 512), leaf.origin()); } } void TestLeaf::testIteratorGetCoord() { using namespace openvdb; LeafType leaf(openvdb::Coord(8, 8, 0), 2); CPPUNIT_ASSERT_EQUAL(Coord(8, 8, 0), leaf.origin()); leaf.setValueOn(Coord(1, 2, 3), -3); leaf.setValueOn(Coord(5, 2, 3), 4); LeafType::ValueOnIter iter = leaf.beginValueOn(); Coord xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(9, 10, 3), xyz); ++iter; xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(13, 10, 3), xyz); } void TestLeaf::testNegativeIndexing() { using namespace openvdb; LeafType leaf(openvdb::Coord(-9, -2, -8), 1); CPPUNIT_ASSERT_EQUAL(Coord(-16, -8, -8), leaf.origin()); leaf.setValueOn(Coord(1, 2, 3), -3); leaf.setValueOn(Coord(5, 2, 3), 4); CPPUNIT_ASSERT_EQUAL(-3, leaf.getValue(Coord(1, 2, 3))); CPPUNIT_ASSERT_EQUAL(4, leaf.getValue(Coord(5, 2, 3))); LeafType::ValueOnIter iter = leaf.beginValueOn(); Coord xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(-15, -6, -5), xyz); ++iter; xyz = iter.getCoord(); CPPUNIT_ASSERT_EQUAL(Coord(-11, -6, -5), xyz); } void TestLeaf::testSignedFloodFill() { using namespace openvdb; const int fill0=5, fill1=-fill0; int D=LeafType::dim(), C=D/2; Coord origin(0,0,0), left(0,0,C-1), right(0,0,C); LeafType leaf(origin,fill0); for (int i=0; i #include #include #include class TestMaps: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestMaps); CPPUNIT_TEST(testTranslation); CPPUNIT_TEST(testRotation); CPPUNIT_TEST(testScaleDefault); CPPUNIT_TEST(testScaleTranslate); CPPUNIT_TEST(testUniformScaleTranslate); CPPUNIT_TEST(testDecomposition); CPPUNIT_TEST(testFrustum); CPPUNIT_TEST(testCalcBoundingBox); CPPUNIT_TEST(testApproxInverse); CPPUNIT_TEST(testUniformScale); CPPUNIT_TEST(testJacobians); CPPUNIT_TEST_SUITE_END(); void testTranslation(); void testRotation(); void testScaleDefault(); void testScaleTranslate(); void testUniformScaleTranslate(); void testDecomposition(); void testFrustum(); void testCalcBoundingBox(); void testApproxInverse(); void testUniformScale(); void testJacobians(); //void testIsType(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMaps); void TestMaps::testApproxInverse() { using namespace openvdb::math; Mat4d singular = Mat4d::identity(); singular[1][1] = 0.f; { Mat4d singularInv = approxInverse(singular); CPPUNIT_ASSERT( singular == singularInv ); } { Mat4d rot = Mat4d::identity(); rot.setToRotation(X_AXIS, M_PI/4.); Mat4d rotInv = rot.inverse(); Mat4d mat = rotInv * singular * rot; Mat4d singularInv = approxInverse(mat); // this matrix is equal to its own singular inverse CPPUNIT_ASSERT( mat == singularInv ); } { Mat4d m = Mat4d::identity(); m[0][1] = 1; // should give true inverse, since this matrix has det=1 Mat4d minv = approxInverse(m); Mat4d prod = m * minv; CPPUNIT_ASSERT( prod.eq( Mat4d::identity() ) ); } { Mat4d m = Mat4d::identity(); m[0][1] = 1; m[1][1] = 0; // should give true inverse, since this matrix has det=1 Mat4d minv = approxInverse(m); Mat4d expected = Mat4d::zero(); expected[3][3] = 1; CPPUNIT_ASSERT( minv.eq(expected ) ); } } void TestMaps::testUniformScale() { using namespace openvdb::math; AffineMap map; CPPUNIT_ASSERT(map.hasUniformScale()); // Apply uniform scale: should still have square voxels map.accumPreScale(Vec3d(2, 2, 2)); CPPUNIT_ASSERT(map.hasUniformScale()); // Apply a rotation, should still have squaure voxels. map.accumPostRotation(X_AXIS, 2.5); CPPUNIT_ASSERT(map.hasUniformScale()); // non uniform scaling will stretch the voxels map.accumPostScale(Vec3d(1, 3, 1) ); CPPUNIT_ASSERT(!map.hasUniformScale()); } void TestMaps::testTranslation() { using namespace openvdb::math; double TOL = 1e-7; TranslationMap::Ptr translation(new TranslationMap(Vec3d(1,1,1))); CPPUNIT_ASSERT(is_linear::value); TranslationMap another_translation(Vec3d(1,1,1)); CPPUNIT_ASSERT(another_translation == *translation); TranslationMap::Ptr translate_by_two(new TranslationMap(Vec3d(2,2,2))); CPPUNIT_ASSERT(*translate_by_two != *translation); CPPUNIT_ASSERT_DOUBLES_EQUAL(translate_by_two->determinant(), 1, TOL); CPPUNIT_ASSERT(translate_by_two->hasUniformScale()); /// apply the map forward Vec3d unit(1,0,0); Vec3d result = translate_by_two->applyMap(unit); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), 3, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), 2, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), 2, TOL); /// invert the map result = translate_by_two->applyInverseMap(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), 0, TOL); /// Inverse Jacobian Transpose result = translate_by_two->applyIJT(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), 0, TOL); /// Jacobian Transpose result = translate_by_two->applyJT(translate_by_two->applyIJT(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); MapBase::Ptr inverse = translation->inverseMap(); CPPUNIT_ASSERT(inverse->type() == TranslationMap::mapType()); // apply the map forward and the inverse map back result = inverse->applyMap(translation->applyMap(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), 1, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), 0, TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), 0, TOL); } void TestMaps::testScaleDefault() { using namespace openvdb::math; double TOL = 1e-7; // testing default constructor // should be the identity ScaleMap::Ptr scale(new ScaleMap()); Vec3d unit(1, 1, 1); Vec3d result = scale->applyMap(unit); CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(0), result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(1), result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(2), result(2), TOL); result = scale->applyInverseMap(unit); CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(0), result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(1), result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(unit(2), result(2), TOL); MapBase::Ptr inverse = scale->inverseMap(); CPPUNIT_ASSERT(inverse->type() == ScaleMap::mapType()); // apply the map forward and the inverse map back result = inverse->applyMap(scale->applyMap(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); } void TestMaps::testRotation() { using namespace openvdb::math; double TOL = 1e-7; double pi = 4.*atan(1.); UnitaryMap::Ptr rotation(new UnitaryMap(Vec3d(1,0,0), pi/2)); CPPUNIT_ASSERT(is_linear::value); UnitaryMap another_rotation(Vec3d(1,0,0), pi/2.); CPPUNIT_ASSERT(another_rotation == *rotation); UnitaryMap::Ptr rotation_two(new UnitaryMap(Vec3d(1,0,0), pi/4.)); CPPUNIT_ASSERT(*rotation_two != *rotation); CPPUNIT_ASSERT_DOUBLES_EQUAL(rotation->determinant(), 1, TOL); CPPUNIT_ASSERT(rotation_two->hasUniformScale()); /// apply the map forward Vec3d unit(0,1,0); Vec3d result = rotation->applyMap(unit); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(2), TOL); /// invert the map result = rotation->applyInverseMap(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); /// Inverse Jacobian Transpose result = rotation_two->applyIJT(result); // rotate backwards CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(sqrt(2.)/2, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(sqrt(2.)/2, result(2), TOL); /// Jacobian Transpose result = rotation_two->applyJT(rotation_two->applyIJT(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); // Test inverse map MapBase::Ptr inverse = rotation->inverseMap(); CPPUNIT_ASSERT(inverse->type() == UnitaryMap::mapType()); // apply the map forward and the inverse map back result = inverse->applyMap(rotation->applyMap(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); } void TestMaps::testScaleTranslate() { using namespace openvdb::math; double TOL = 1e-7; CPPUNIT_ASSERT(is_linear::value); TranslationMap::Ptr translation(new TranslationMap(Vec3d(1,1,1))); ScaleMap::Ptr scale(new ScaleMap(Vec3d(1,2,3))); ScaleTranslateMap::Ptr scaleAndTranslate( new ScaleTranslateMap(*scale, *translation)); TranslationMap translate_by_two(Vec3d(2,2,2)); ScaleTranslateMap another_scaleAndTranslate(*scale, translate_by_two); CPPUNIT_ASSERT(another_scaleAndTranslate != *scaleAndTranslate); CPPUNIT_ASSERT(!scaleAndTranslate->hasUniformScale()); //CPPUNIT_ASSERT_DOUBLES_EQUAL(scaleAndTranslate->determinant(), 6, TOL); /// apply the map forward Vec3d unit(1,0,0); Vec3d result = scaleAndTranslate->applyMap(unit); CPPUNIT_ASSERT_DOUBLES_EQUAL(2, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(2), TOL); /// invert the map result = scaleAndTranslate->applyInverseMap(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); /// Inverse Jacobian Transpose result = Vec3d(0,2,0); result = scaleAndTranslate->applyIJT(result ); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); /// Jacobian Transpose result = scaleAndTranslate->applyJT(scaleAndTranslate->applyIJT(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); // Test inverse map MapBase::Ptr inverse = scaleAndTranslate->inverseMap(); CPPUNIT_ASSERT(inverse->type() == ScaleTranslateMap::mapType()); // apply the map forward and the inverse map back result = inverse->applyMap(scaleAndTranslate->applyMap(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); } void TestMaps::testUniformScaleTranslate() { using namespace openvdb::math; double TOL = 1e-7; CPPUNIT_ASSERT(is_linear::value); CPPUNIT_ASSERT(is_linear::value); TranslationMap::Ptr translation(new TranslationMap(Vec3d(1,1,1))); UniformScaleMap::Ptr scale(new UniformScaleMap(2)); UniformScaleTranslateMap::Ptr scaleAndTranslate( new UniformScaleTranslateMap(*scale, *translation)); TranslationMap translate_by_two(Vec3d(2,2,2)); UniformScaleTranslateMap another_scaleAndTranslate(*scale, translate_by_two); CPPUNIT_ASSERT(another_scaleAndTranslate != *scaleAndTranslate); CPPUNIT_ASSERT(scaleAndTranslate->hasUniformScale()); //CPPUNIT_ASSERT_DOUBLES_EQUAL(scaleAndTranslate->determinant(), 6, TOL); /// apply the map forward Vec3d unit(1,0,0); Vec3d result = scaleAndTranslate->applyMap(unit); CPPUNIT_ASSERT_DOUBLES_EQUAL(3, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(2), TOL); /// invert the map result = scaleAndTranslate->applyInverseMap(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); /// Inverse Jacobian Transpose result = Vec3d(0,2,0); result = scaleAndTranslate->applyIJT(result ); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1, result(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0, result(2), TOL); /// Jacobian Transpose result = scaleAndTranslate->applyJT(scaleAndTranslate->applyIJT(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); // Test inverse map MapBase::Ptr inverse = scaleAndTranslate->inverseMap(); CPPUNIT_ASSERT(inverse->type() == UniformScaleTranslateMap::mapType()); // apply the map forward and the inverse map back result = inverse->applyMap(scaleAndTranslate->applyMap(unit)); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), unit(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), unit(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), unit(2), TOL); } void TestMaps::testDecomposition() { using namespace openvdb::math; //double TOL = 1e-7; CPPUNIT_ASSERT(is_linear::value); CPPUNIT_ASSERT(is_linear::value); CPPUNIT_ASSERT(is_linear::value); CPPUNIT_ASSERT(is_linear::value); Mat4d matrix(Mat4d::identity()); Vec3d input_translation(0,0,1); matrix.setTranslation(input_translation); matrix(0,0) = 1.8930039; matrix(1,0) = -0.120080537; matrix(2,0) = -0.497615212; matrix(0,1) = -0.120080537; matrix(1,1) = 2.643265436; matrix(2,1) = 0.6176957495; matrix(0,2) = -0.497615212; matrix(1,2) = 0.6176957495; matrix(2,2) = 1.4637305884; FullyDecomposedMap::Ptr decomp = createFullyDecomposedMap(matrix); /// the singular values const Vec3& singular_values = decomp->firstMap().firstMap().secondMap().getScale(); /// expected values Vec3d expected_values(2, 3, 1); CPPUNIT_ASSERT( isApproxEqual(singular_values, expected_values) ); const Vec3& the_translation = decomp->secondMap().secondMap().getTranslation(); CPPUNIT_ASSERT( isApproxEqual(the_translation, input_translation)); } void TestMaps::testFrustum() { using namespace openvdb::math; openvdb::BBoxd bbox(Vec3d(0), Vec3d(100)); NonlinearFrustumMap frustum(bbox, 1./6., 5); /// frustum will have depth, far plane - near plane = 5 /// the frustum has width 1 in the front and 6 in the back Vec3d trans(2,2,2); NonlinearFrustumMap::Ptr map = boost::static_pointer_cast( frustum.preScale(Vec3d(10,10,10))->postTranslate(trans)); CPPUNIT_ASSERT(!map->hasUniformScale()); Vec3d result; result = map->voxelSize(); CPPUNIT_ASSERT( isApproxEqual(result.x(), 0.1)); CPPUNIT_ASSERT( isApproxEqual(result.y(), 0.1)); CPPUNIT_ASSERT( isApproxEqual(result.z(), 0.5, 0.0001)); //--------- Front face Vec3d corner(0,0,0); result = map->applyMap(corner); CPPUNIT_ASSERT(isApproxEqual(result, Vec3d(-5, -5, 0) + trans)); corner = Vec3d(100,0,0); result = map->applyMap(corner); CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(5, -5, 0) + trans)); corner = Vec3d(0,100,0); result = map->applyMap(corner); CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(-5, 5, 0) + trans)); corner = Vec3d(100,100,0); result = map->applyMap(corner); CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(5, 5, 0) + trans)); //--------- Back face corner = Vec3d(0,0,100); result = map->applyMap(corner); CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(-30, -30, 50) + trans)); // 10*(5/2 + 1/2) = 30 corner = Vec3d(100,0,100); result = map->applyMap(corner); CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(30, -30, 50) + trans)); corner = Vec3d(0,100,100); result = map->applyMap(corner); CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(-30, 30, 50) + trans)); corner = Vec3d(100,100,100); result = map->applyMap(corner); CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(30, 30, 50) + trans)); // invert a single corner result = map->applyInverseMap(Vec3d(30,30,50) + trans); CPPUNIT_ASSERT( isApproxEqual(result, Vec3d(100, 100, 100))); CPPUNIT_ASSERT(map->hasSimpleAffine()); /// create a frustum from from camera type information // the location of the camera Vec3d position(100,10,1); // the direction the camera is pointing Vec3d direction(0,1,1); direction.normalize(); // the up-direction for the camera Vec3d up(10,3,-3); // distance from camera to near-plane measured in the direction 'direction' double z_near = 100.; // depth of frustum to far-plane to near-plane double depth = 500.; //aspect ratio of frustum: width/height double aspect = 2; // voxel count in frustum. the y_count = x_count / aspect Coord::ValueType x_count = 500; Coord::ValueType z_count = 5000; NonlinearFrustumMap frustumMap_from_camera( position, direction, up, aspect, z_near, depth, x_count, z_count); Vec3d center; // find the center of the near plane and make sure it is in the correct place center = Vec3d(0,0,0); center += frustumMap_from_camera.applyMap(Vec3d(0,0,0)); center += frustumMap_from_camera.applyMap(Vec3d(500,0,0)); center += frustumMap_from_camera.applyMap(Vec3d(0,250,0)); center += frustumMap_from_camera.applyMap(Vec3d(500,250,0)); center = center /4.; CPPUNIT_ASSERT( isApproxEqual(center, position + z_near * direction)); // find the center of the far plane and make sure it is in the correct place center = Vec3d(0,0,0); center += frustumMap_from_camera.applyMap(Vec3d( 0, 0,5000)); center += frustumMap_from_camera.applyMap(Vec3d(500, 0,5000)); center += frustumMap_from_camera.applyMap(Vec3d( 0,250,5000)); center += frustumMap_from_camera.applyMap(Vec3d(500,250,5000)); center = center /4.; CPPUNIT_ASSERT( isApproxEqual(center, position + (z_near+depth) * direction)); // check that the frustum has the correct heigh on the near plane Vec3d corner1 = frustumMap_from_camera.applyMap(Vec3d(0,0,0)); Vec3d corner2 = frustumMap_from_camera.applyMap(Vec3d(0,250,0)); Vec3d side = corner2-corner1; CPPUNIT_ASSERT( isApproxEqual( side.length(), 2 * up.length())); // check that the frustum is correctly oriented w.r.t up side.normalize(); CPPUNIT_ASSERT( isApproxEqual( side * (up.length()), up)); // check that the linear map inside the frustum is a simple affine map (i.e. has no shear) CPPUNIT_ASSERT(frustumMap_from_camera.hasSimpleAffine()); } void TestMaps::testCalcBoundingBox() { using namespace openvdb::math; openvdb::BBoxd world_bbox(Vec3d(0,0,0), Vec3d(1,1,1)); openvdb::BBoxd voxel_bbox; openvdb::BBoxd expected; { AffineMap affine; affine.accumPreScale(Vec3d(2,2,2)); openvdb::util::calculateBounds(affine, world_bbox, voxel_bbox); expected = openvdb::BBoxd(Vec3d(0,0,0), Vec3d(0.5, 0.5, 0.5)); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); affine.accumPostTranslation(Vec3d(1,1,1)); openvdb::util::calculateBounds(affine, world_bbox, voxel_bbox); expected = openvdb::BBoxd(Vec3d(-0.5,-0.5,-0.5), Vec3d(0, 0, 0)); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); } { AffineMap affine; affine.accumPreScale(Vec3d(2,2,2)); affine.accumPostTranslation(Vec3d(1,1,1)); // test a sphere: Vec3d center(0,0,0); double radius = 10; openvdb::util::calculateBounds(affine, center, radius, voxel_bbox); expected = openvdb::BBoxd(Vec3d(-5.5,-5.5,-5.5), Vec3d(4.5, 4.5, 4.5)); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); } { AffineMap affine; affine.accumPreScale(Vec3d(2,2,2)); double pi = 4.*atan(1.); affine.accumPreRotation(X_AXIS, pi/4.); Vec3d center(0,0,0); double radius = 10; openvdb::util::calculateBounds(affine, center, radius, voxel_bbox); expected = openvdb::BBoxd(Vec3d(-5,-5,-5), Vec3d(5, 5, 5)); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); } { AffineMap affine; affine.accumPreScale(Vec3d(2,1,1)); double pi = 4.*atan(1.); affine.accumPreRotation(X_AXIS, pi/4.); Vec3d center(0,0,0); double radius = 10; openvdb::util::calculateBounds(affine, center, radius, voxel_bbox); expected = openvdb::BBoxd(Vec3d(-5,-10,-10), Vec3d(5, 10, 10)); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); } { AffineMap affine; affine.accumPreScale(Vec3d(2,1,1)); double pi = 4.*atan(1.); affine.accumPreRotation(X_AXIS, pi/4.); affine.accumPostTranslation(Vec3d(1,1,1)); Vec3d center(1,1,1); double radius = 10; openvdb::util::calculateBounds(affine, center, radius, voxel_bbox); expected = openvdb::BBoxd(Vec3d(-5,-10,-10), Vec3d(5, 10, 10)); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.min(), expected.min())); CPPUNIT_ASSERT(isApproxEqual(voxel_bbox.max(), expected.max())); } { openvdb::BBoxd bbox(Vec3d(0), Vec3d(100)); NonlinearFrustumMap frustum(bbox, 2, 5); NonlinearFrustumMap::Ptr map = boost::static_pointer_cast( frustum.preScale(Vec3d(2,2,2))); Vec3d center(20,20,10); double radius(1); openvdb::util::calculateBounds(*map, center, radius, voxel_bbox); } } void TestMaps::testJacobians() { using namespace openvdb::math; const double TOL = 1e-7; { AffineMap affine; const int n = 10; const double dtheta = M_PI / n; const Vec3d test(1,2,3); const Vec3d origin(0,0,0); for (int i = 0; i < n; ++i) { double theta = i * dtheta; affine.accumPostRotation(X_AXIS, theta); Vec3d result = affine.applyJacobian(test); Vec3d expected = affine.applyMap(test) - affine.applyMap(origin); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); Vec3d tmp = affine.applyInverseJacobian(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); } } { UniformScaleMap scale(3); const Vec3d test(1,2,3); const Vec3d origin(0,0,0); Vec3d result = scale.applyJacobian(test); Vec3d expected = scale.applyMap(test) - scale.applyMap(origin); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); Vec3d tmp = scale.applyInverseJacobian(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); } { ScaleMap scale(Vec3d(1,2,3)); const Vec3d test(1,2,3); const Vec3d origin(0,0,0); Vec3d result = scale.applyJacobian(test); Vec3d expected = scale.applyMap(test) - scale.applyMap(origin); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); Vec3d tmp = scale.applyInverseJacobian(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); } { TranslationMap map(Vec3d(1,2,3)); const Vec3d test(1,2,3); const Vec3d origin(0,0,0); Vec3d result = map.applyJacobian(test); Vec3d expected = map.applyMap(test) - map.applyMap(origin); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); Vec3d tmp = map.applyInverseJacobian(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); } { ScaleTranslateMap map(Vec3d(1,2,3), Vec3d(3,5,4)); const Vec3d test(1,2,3); const Vec3d origin(0,0,0); Vec3d result = map.applyJacobian(test); Vec3d expected = map.applyMap(test) - map.applyMap(origin); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(0), expected(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(1), expected(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(result(2), expected(2), TOL); Vec3d tmp = map.applyInverseJacobian(result); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); } { openvdb::BBoxd bbox(Vec3d(0), Vec3d(100)); NonlinearFrustumMap frustum(bbox, 1./6., 5); /// frustum will have depth, far plane - near plane = 5 /// the frustum has width 1 in the front and 6 in the back Vec3d trans(2,2,2); NonlinearFrustumMap::Ptr map = boost::static_pointer_cast( frustum.preScale(Vec3d(10,10,10))->postTranslate(trans)); const Vec3d test(1,2,3); const Vec3d origin(0, 0, 0); // these two drop down to just the linear part Vec3d lresult = map->applyJacobian(test); Vec3d ltmp = map->applyInverseJacobian(lresult); CPPUNIT_ASSERT_DOUBLES_EQUAL(ltmp(0), test(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(ltmp(1), test(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(ltmp(2), test(2), TOL); Vec3d isloc(4,5,6); // these two drop down to just the linear part Vec3d result = map->applyJacobian(test, isloc); Vec3d tmp = map->applyInverseJacobian(result, isloc); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(0), test(0), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(1), test(1), TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(tmp(2), test(2), TOL); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestValueAccessor.cc0000644000000000000000000005340012252453157017006 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); typedef float ValueType; typedef openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::LeafNode > > Tree2Type; typedef openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::InternalNode< openvdb::tree::LeafNode, 4> > > Tree3Type; typedef openvdb::tree::Tree4::Type Tree4Type; typedef openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< openvdb::tree::LeafNode, 4>, 5>, 5> > > Tree5Type; typedef Tree4Type TreeType; using namespace openvdb::tree; class TestValueAccessor: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestValueAccessor); CPPUNIT_TEST(testTree2Accessor); CPPUNIT_TEST(testTree2AccessorRW); CPPUNIT_TEST(testTree2ConstAccessor); CPPUNIT_TEST(testTree2ConstAccessorRW); CPPUNIT_TEST(testTree3Accessor); CPPUNIT_TEST(testTree3AccessorRW); CPPUNIT_TEST(testTree3ConstAccessor); CPPUNIT_TEST(testTree3ConstAccessorRW); CPPUNIT_TEST(testTree4Accessor); CPPUNIT_TEST(testTree4AccessorRW); CPPUNIT_TEST(testTree4ConstAccessor); CPPUNIT_TEST(testTree4ConstAccessorRW); CPPUNIT_TEST(testTree5Accessor); CPPUNIT_TEST(testTree5AccessorRW); CPPUNIT_TEST(testTree5ConstAccessor); CPPUNIT_TEST(testTree5ConstAccessorRW); CPPUNIT_TEST(testTree3Accessor2); CPPUNIT_TEST(testTree3ConstAccessor2); CPPUNIT_TEST(testTree4Accessor2); CPPUNIT_TEST(testTree4ConstAccessor2); CPPUNIT_TEST(testTree4Accessor1); CPPUNIT_TEST(testTree4ConstAccessor1); CPPUNIT_TEST(testTree4Accessor0); CPPUNIT_TEST(testTree4ConstAccessor0); CPPUNIT_TEST(testTree5Accessor2); CPPUNIT_TEST(testTree5ConstAccessor2); CPPUNIT_TEST(testTree4Accessor12);//cache node level 2 CPPUNIT_TEST(testTree5Accessor213);//cache node level 1 and 3 CPPUNIT_TEST(testMultithreadedAccessor); CPPUNIT_TEST(testAccessorRegistration); CPPUNIT_TEST(testGetNode); CPPUNIT_TEST_SUITE_END(); // cache all node levels void testTree2Accessor() { accessorTest >(); } void testTree2AccessorRW() { accessorTest >(); } void testTree2ConstAccessor() { constAccessorTest >(); } void testTree2ConstAccessorRW() { constAccessorTest >(); } // cache all node levels void testTree3Accessor() { accessorTest >(); } void testTree3AccessorRW() { accessorTest >(); } void testTree3ConstAccessor() { constAccessorTest >(); } void testTree3ConstAccessorRW() { constAccessorTest >(); } // cache all node levels void testTree4Accessor() { accessorTest >(); } void testTree4AccessorRW() { accessorTest >(); } void testTree4ConstAccessor() { constAccessorTest >(); } void testTree4ConstAccessorRW() { constAccessorTest >(); } // cache all node levels void testTree5Accessor() { accessorTest >(); } void testTree5AccessorRW() { accessorTest >(); } void testTree5ConstAccessor() { constAccessorTest >(); } void testTree5ConstAccessorRW() { constAccessorTest >(); } // Test odd combinations of trees and ValueAccessors // cache node level 0 and 1 void testTree3Accessor2() { accessorTest >(); } void testTree3ConstAccessor2() { constAccessorTest >(); } void testTree4Accessor2() { accessorTest >(); } void testTree4ConstAccessor2() { constAccessorTest >(); } void testTree5Accessor2() { accessorTest >(); } void testTree5ConstAccessor2() { constAccessorTest >(); } // only cache leaf level void testTree4Accessor1() { accessorTest >(); } void testTree4ConstAccessor1() { constAccessorTest >(); } // disable node caching void testTree4Accessor0() { accessorTest >(); } void testTree4ConstAccessor0() { constAccessorTest >(); } //cache node level 2 void testTree4Accessor12() { accessorTest >(); } //cache node level 1 and 3 void testTree5Accessor213() { accessorTest >(); } void testMultithreadedAccessor(); void testAccessorRegistration(); void testGetNode(); private: template void accessorTest(); template void constAccessorTest(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestValueAccessor); //////////////////////////////////////// namespace { struct Plus { float addend; Plus(float f): addend(f) {} inline void operator()(float& f) const { f += addend; } inline void operator()(float& f, bool& b) const { f += addend; b = false; } }; } template void TestValueAccessor::accessorTest() { typedef typename AccessorT::TreeType TreeType; const int leafDepth = int(TreeType::DEPTH) - 1; // subtract one because getValueDepth() returns 0 for values at the root const ValueType background = 5.0f, value = -9.345f; const openvdb::Coord c0(5, 10, 20), c1(500000, 200000, 300000); { TreeType tree(background); CPPUNIT_ASSERT(!tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); tree.setValue(c0, value); CPPUNIT_ASSERT(tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); } { TreeType tree(background); AccessorT acc(tree); ValueType v; CPPUNIT_ASSERT(!tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); CPPUNIT_ASSERT(!acc.probeValue(c0,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT(!acc.probeValue(c1,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); CPPUNIT_ASSERT(!acc.isVoxel(c0)); CPPUNIT_ASSERT(!acc.isVoxel(c1)); acc.setValue(c0, value); CPPUNIT_ASSERT(tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); CPPUNIT_ASSERT(acc.probeValue(c0,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, v); CPPUNIT_ASSERT(!acc.probeValue(c1,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); // leaf-level voxel value CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); // background value CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(openvdb::Coord(7, 10, 20))); const int depth = leafDepth == 1 ? -1 : leafDepth - 1; CPPUNIT_ASSERT_EQUAL(depth, acc.getValueDepth(openvdb::Coord(8, 10, 20))); CPPUNIT_ASSERT( acc.isVoxel(c0)); // leaf-level voxel value CPPUNIT_ASSERT(!acc.isVoxel(c1)); CPPUNIT_ASSERT( acc.isVoxel(openvdb::Coord(7, 10, 20))); CPPUNIT_ASSERT(!acc.isVoxel(openvdb::Coord(8, 10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c1)); // uncached background value CPPUNIT_ASSERT(!acc.isValueOn(c1)); // inactive background value ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT( (acc.numCacheLevels()>0) == acc.isCached(c0)); // active, leaf-level voxel value CPPUNIT_ASSERT(acc.isValueOn(c0)); acc.setValue(c1, value); CPPUNIT_ASSERT(acc.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c1)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); CPPUNIT_ASSERT(acc.isVoxel(c0)); CPPUNIT_ASSERT(acc.isVoxel(c1)); tree.setValueOff(c1); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); CPPUNIT_ASSERT( acc.isValueOn(c0)); CPPUNIT_ASSERT(!acc.isValueOn(c1)); acc.setValueOn(c1); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); CPPUNIT_ASSERT( acc.isValueOn(c0)); CPPUNIT_ASSERT( acc.isValueOn(c1)); acc.modifyValueAndActiveState(c1, Plus(-value)); // subtract value & mark inactive CPPUNIT_ASSERT(!acc.isValueOn(c1)); acc.modifyValue(c1, Plus(-value)); // subtract value again & mark active CPPUNIT_ASSERT(acc.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(-value, tree.getValue(c1)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(-value, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); CPPUNIT_ASSERT(acc.isVoxel(c0)); CPPUNIT_ASSERT(acc.isVoxel(c1)); acc.setValueOnly(c1, 3*value); CPPUNIT_ASSERT(acc.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(3*value, tree.getValue(c1)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(3*value, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); CPPUNIT_ASSERT(acc.isVoxel(c0)); CPPUNIT_ASSERT(acc.isVoxel(c1)); acc.clear(); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); } } template void TestValueAccessor::constAccessorTest() { typedef typename boost::remove_const::type TreeType; const int leafDepth = int(TreeType::DEPTH) - 1; // subtract one because getValueDepth() returns 0 for values at the root const ValueType background = 5.0f, value = -9.345f; const openvdb::Coord c0(5, 10, 20), c1(500000, 200000, 300000); ValueType v; TreeType tree(background); AccessorT acc(tree); CPPUNIT_ASSERT(!tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); CPPUNIT_ASSERT(!acc.probeValue(c0,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT(!acc.probeValue(c1,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); CPPUNIT_ASSERT(!acc.isVoxel(c0)); CPPUNIT_ASSERT(!acc.isVoxel(c1)); tree.setValue(c0, value); CPPUNIT_ASSERT(tree.isValueOn(c0)); CPPUNIT_ASSERT(!tree.isValueOn(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(acc.isValueOn(c0)); CPPUNIT_ASSERT(!acc.isValueOn(c1)); CPPUNIT_ASSERT(acc.probeValue(c0,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, v); CPPUNIT_ASSERT(!acc.probeValue(c1,v)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, v); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(c1)); CPPUNIT_ASSERT( acc.isVoxel(c0)); CPPUNIT_ASSERT(!acc.isVoxel(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(c1)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); CPPUNIT_ASSERT(acc.isValueOn(c0)); CPPUNIT_ASSERT(!acc.isValueOn(c1)); tree.setValue(c1, value); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(c1)); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT((acc.numCacheLevels()>0) == acc.isCached(c1)); CPPUNIT_ASSERT(acc.isValueOn(c0)); CPPUNIT_ASSERT(acc.isValueOn(c1)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c0)); CPPUNIT_ASSERT_EQUAL(leafDepth, acc.getValueDepth(c1)); CPPUNIT_ASSERT(acc.isVoxel(c0)); CPPUNIT_ASSERT(acc.isVoxel(c1)); // The next two lines should not compile, because the acc references a const tree: //acc.setValue(c1, value); //acc.setValueOff(c1); acc.clear(); CPPUNIT_ASSERT(!acc.isCached(c0)); CPPUNIT_ASSERT(!acc.isCached(c1)); } void TestValueAccessor::testMultithreadedAccessor() { #define MAX_COORD 5000 typedef openvdb::tree::ValueAccessorRW AccessorT; // Substituting the following typedef typically results in assertion failures: //typedef openvdb::tree::ValueAccessor AccessorT; // Task to perform multiple reads through a shared accessor struct ReadTask: public tbb::task { AccessorT& acc; ReadTask(AccessorT& c): acc(c) {} tbb::task* execute() { for (int i = -MAX_COORD; i < MAX_COORD; ++i) { ASSERT_DOUBLES_EXACTLY_EQUAL(double(i), acc.getValue(openvdb::Coord(i))); } return NULL; } }; // Task to perform multiple writes through a shared accessor struct WriteTask: public tbb::task { AccessorT& acc; WriteTask(AccessorT& c): acc(c) {} tbb::task* execute() { for (int i = -MAX_COORD; i < MAX_COORD; ++i) { float f = acc.getValue(openvdb::Coord(i)); ASSERT_DOUBLES_EXACTLY_EQUAL(float(i), f); acc.setValue(openvdb::Coord(i), i); ASSERT_DOUBLES_EXACTLY_EQUAL(float(i), acc.getValue(openvdb::Coord(i))); } return NULL; } }; // Parent task to spawn multiple parallel read and write tasks struct RootTask: public tbb::task { AccessorT& acc; RootTask(AccessorT& c): acc(c) {} tbb::task* execute() { ReadTask* r[3]; WriteTask* w[3]; for (int i = 0; i < 3; ++i) { r[i] = new(allocate_child()) ReadTask(acc); w[i] = new(allocate_child()) WriteTask(acc); } set_ref_count(6 /*children*/ + 1 /*wait*/); for (int i = 0; i < 3; ++i) { spawn(*r[i]); spawn(*w[i]); } wait_for_all(); return NULL; } }; Tree4Type tree(/*background=*/0.5); AccessorT acc(tree); // Populate the tree. for (int i = -MAX_COORD; i < MAX_COORD; ++i) { acc.setValue(openvdb::Coord(i), i); } // Run multiple read and write tasks in parallel. RootTask& root = *new(tbb::task::allocate_root()) RootTask(acc); tbb::task::spawn_root_and_wait(root); #undef MAX_COORD } void TestValueAccessor::testAccessorRegistration() { using openvdb::Index; const float background = 5.0f, value = -9.345f; const openvdb::Coord c0(5, 10, 20); openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); openvdb::tree::ValueAccessor acc(*tree); // Set a single leaf voxel via the accessor and verify that // the cache is populated. acc.setValue(c0, value); CPPUNIT_ASSERT_EQUAL(Index(1), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(tree->getRootNode().getLevel(), tree->nonLeafCount()); CPPUNIT_ASSERT(acc.getNode() != NULL); // Reset the voxel to the background value and verify that no nodes // have been deleted and that the cache is still populated. tree->setValueOff(c0, background); CPPUNIT_ASSERT_EQUAL(Index(1), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(tree->getRootNode().getLevel(), tree->nonLeafCount()); CPPUNIT_ASSERT(acc.getNode() != NULL); // Prune the tree and verify that only the root node remains and that // the cache has been cleared. tree->prune(); CPPUNIT_ASSERT_EQUAL(Index(0), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(Index(1), tree->nonLeafCount()); // root node only CPPUNIT_ASSERT(acc.getNode() == NULL); // Set the leaf voxel again and verify that the cache is repopulated. acc.setValue(c0, value); CPPUNIT_ASSERT_EQUAL(Index(1), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(tree->getRootNode().getLevel(), tree->nonLeafCount()); CPPUNIT_ASSERT(acc.getNode() != NULL); // Delete the tree and verify that the cache has been cleared. tree.reset(); CPPUNIT_ASSERT(acc.getTree() == NULL); CPPUNIT_ASSERT(acc.getNode() == NULL); CPPUNIT_ASSERT(acc.getNode() == NULL); } void TestValueAccessor::testGetNode() { typedef Tree4Type::LeafNodeType LeafT; const ValueType background = 5.0f, value = -9.345f; const openvdb::Coord c0(5, 10, 20); Tree4Type tree(background); tree.setValue(c0, value); { openvdb::tree::ValueAccessor acc(tree); // Prime the cache. acc.getValue(c0); // Verify that the cache contains a leaf node. LeafT* node = acc.getNode(); CPPUNIT_ASSERT(node != NULL); // Erase the leaf node from the cache and verify that it is gone. acc.eraseNode(); node = acc.getNode(); CPPUNIT_ASSERT(node == NULL); } { // As above, but with a const tree. openvdb::tree::ValueAccessor acc(tree); acc.getValue(c0); const LeafT* node = acc.getNode(); CPPUNIT_ASSERT(node != NULL); acc.eraseNode(); node = acc.getNode(); CPPUNIT_ASSERT(node == NULL); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLinearInterp.cc0000644000000000000000000011620612252453157016647 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include // CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name // from the FixtureType. But if FixtureType is a templated type, the generated name // can become long and messy. This macro overrides the normal naming logic, // instead invoking FixtureType::testSuiteName(), which should be a static member // function that returns a std::string containing the suite name for the specific // template instantiation. #undef CPPUNIT_TESTNAMER_DECL #define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) namespace { // Absolute tolerance for floating-point equality comparisons const double TOLERANCE = 1.e-6; } template class TestLinearInterp: public CppUnit::TestCase { public: static std::string testSuiteName() { std::string name = openvdb::typeNameAsString(); if (!name.empty()) name[0] = ::toupper(name[0]); return "TestLinearInterp" + name; } CPPUNIT_TEST_SUITE(TestLinearInterp); CPPUNIT_TEST(test); CPPUNIT_TEST(testTree); CPPUNIT_TEST(testAccessor); CPPUNIT_TEST(testConstantValues); CPPUNIT_TEST(testFillValues); CPPUNIT_TEST(testNegativeIndices); CPPUNIT_TEST_SUITE_END(); void test(); void testTree(); void testAccessor(); void testConstantValues(); void testFillValues(); void testNegativeIndices(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLinearInterp); CPPUNIT_TEST_SUITE_REGISTRATION(TestLinearInterp); CPPUNIT_TEST_SUITE_REGISTRATION(TestLinearInterp); template void TestLinearInterp::test() { typename GridType::TreeType TreeType; float fillValue = 256.0f; GridType grid(fillValue); typename GridType::TreeType& tree = grid.tree(); tree.setValue(openvdb::Coord(10, 10, 10), 1.0); tree.setValue(openvdb::Coord(11, 10, 10), 2.0); tree.setValue(openvdb::Coord(11, 11, 10), 2.0); tree.setValue(openvdb::Coord(10, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 10, 10), 2.0); tree.setValue(openvdb::Coord( 9, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 9, 10), 2.0); tree.setValue(openvdb::Coord(11, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 10, 11), 3.0); tree.setValue(openvdb::Coord(11, 10, 11), 3.0); tree.setValue(openvdb::Coord(11, 11, 11), 3.0); tree.setValue(openvdb::Coord(10, 11, 11), 3.0); tree.setValue(openvdb::Coord( 9, 11, 11), 3.0); tree.setValue(openvdb::Coord( 9, 10, 11), 3.0); tree.setValue(openvdb::Coord( 9, 9, 11), 3.0); tree.setValue(openvdb::Coord(10, 9, 11), 3.0); tree.setValue(openvdb::Coord(11, 9, 11), 3.0); tree.setValue(openvdb::Coord(10, 10, 9), 4.0); tree.setValue(openvdb::Coord(11, 10, 9), 4.0); tree.setValue(openvdb::Coord(11, 11, 9), 4.0); tree.setValue(openvdb::Coord(10, 11, 9), 4.0); tree.setValue(openvdb::Coord( 9, 11, 9), 4.0); tree.setValue(openvdb::Coord( 9, 10, 9), 4.0); tree.setValue(openvdb::Coord( 9, 9, 9), 4.0); tree.setValue(openvdb::Coord(10, 9, 9), 4.0); tree.setValue(openvdb::Coord(11, 9, 9), 4.0); // transform used for worldspace interpolation) openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } template<> void TestLinearInterp::test() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); Vec3STree& tree = grid.tree(); tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.375, 2.375, 2.375))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.0, 1.0, 1.0))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.0, 3.0, 3.0))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1, 1.1, 1.1))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792, 2.792, 2.792))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71, 2.71, 2.71))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01, 2.01, 2.01))); } template void TestLinearInterp::testTree() { float fillValue = 256.0f; typedef typename GridType::TreeType TreeType; TreeType tree(fillValue); tree.setValue(openvdb::Coord(10, 10, 10), 1.0); tree.setValue(openvdb::Coord(11, 10, 10), 2.0); tree.setValue(openvdb::Coord(11, 11, 10), 2.0); tree.setValue(openvdb::Coord(10, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 10, 10), 2.0); tree.setValue(openvdb::Coord( 9, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 9, 10), 2.0); tree.setValue(openvdb::Coord(11, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 10, 11), 3.0); tree.setValue(openvdb::Coord(11, 10, 11), 3.0); tree.setValue(openvdb::Coord(11, 11, 11), 3.0); tree.setValue(openvdb::Coord(10, 11, 11), 3.0); tree.setValue(openvdb::Coord( 9, 11, 11), 3.0); tree.setValue(openvdb::Coord( 9, 10, 11), 3.0); tree.setValue(openvdb::Coord( 9, 9, 11), 3.0); tree.setValue(openvdb::Coord(10, 9, 11), 3.0); tree.setValue(openvdb::Coord(11, 9, 11), 3.0); tree.setValue(openvdb::Coord(10, 10, 9), 4.0); tree.setValue(openvdb::Coord(11, 10, 9), 4.0); tree.setValue(openvdb::Coord(11, 11, 9), 4.0); tree.setValue(openvdb::Coord(10, 11, 9), 4.0); tree.setValue(openvdb::Coord( 9, 11, 9), 4.0); tree.setValue(openvdb::Coord( 9, 10, 9), 4.0); tree.setValue(openvdb::Coord( 9, 9, 9), 4.0); tree.setValue(openvdb::Coord(10, 9, 9), 4.0); tree.setValue(openvdb::Coord(11, 9, 9), 4.0); // transform used for worldspace interpolation) openvdb::tools::GridSampler interpolator(tree, openvdb::math::Transform()); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } template<> void TestLinearInterp::testTree() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3STree tree(fillValue); tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); openvdb::tools::GridSampler interpolator(tree, openvdb::math::Transform()); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.375, 2.375, 2.375))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.0, 1.0, 1.0))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.0, 3.0, 3.0))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1, 1.1, 1.1))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792, 2.792, 2.792))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71, 2.71, 2.71))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01, 2.01, 2.01))); } template void TestLinearInterp::testAccessor() { float fillValue = 256.0f; GridType grid(fillValue); typedef typename GridType::Accessor AccessorType; AccessorType acc = grid.getAccessor(); acc.setValue(openvdb::Coord(10, 10, 10), 1.0); acc.setValue(openvdb::Coord(11, 10, 10), 2.0); acc.setValue(openvdb::Coord(11, 11, 10), 2.0); acc.setValue(openvdb::Coord(10, 11, 10), 2.0); acc.setValue(openvdb::Coord( 9, 11, 10), 2.0); acc.setValue(openvdb::Coord( 9, 10, 10), 2.0); acc.setValue(openvdb::Coord( 9, 9, 10), 2.0); acc.setValue(openvdb::Coord(10, 9, 10), 2.0); acc.setValue(openvdb::Coord(11, 9, 10), 2.0); acc.setValue(openvdb::Coord(10, 10, 11), 3.0); acc.setValue(openvdb::Coord(11, 10, 11), 3.0); acc.setValue(openvdb::Coord(11, 11, 11), 3.0); acc.setValue(openvdb::Coord(10, 11, 11), 3.0); acc.setValue(openvdb::Coord( 9, 11, 11), 3.0); acc.setValue(openvdb::Coord( 9, 10, 11), 3.0); acc.setValue(openvdb::Coord( 9, 9, 11), 3.0); acc.setValue(openvdb::Coord(10, 9, 11), 3.0); acc.setValue(openvdb::Coord(11, 9, 11), 3.0); acc.setValue(openvdb::Coord(10, 10, 9), 4.0); acc.setValue(openvdb::Coord(11, 10, 9), 4.0); acc.setValue(openvdb::Coord(11, 11, 9), 4.0); acc.setValue(openvdb::Coord(10, 11, 9), 4.0); acc.setValue(openvdb::Coord( 9, 11, 9), 4.0); acc.setValue(openvdb::Coord( 9, 10, 9), 4.0); acc.setValue(openvdb::Coord( 9, 9, 9), 4.0); acc.setValue(openvdb::Coord(10, 9, 9), 4.0); acc.setValue(openvdb::Coord(11, 9, 9), 4.0); // transform used for worldspace interpolation) openvdb::tools::GridSampler interpolator(acc, grid.transform()); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } template<> void TestLinearInterp::testAccessor() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); typedef Vec3SGrid::Accessor AccessorType; AccessorType acc = grid.getAccessor(); acc.setValue(openvdb::Coord(10, 10, 10), Vec3s(1.0, 1.0, 1.0)); acc.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); acc.setValue(openvdb::Coord(10, 10, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(11, 10, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(11, 11, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(10, 11, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord( 9, 11, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord( 9, 10, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord( 9, 9, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(10, 9, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(11, 9, 11), Vec3s(3.0, 3.0, 3.0)); acc.setValue(openvdb::Coord(10, 10, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(11, 10, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(11, 11, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(10, 11, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord( 9, 11, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord( 9, 10, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord( 9, 9, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(10, 9, 9), Vec3s(4.0, 4.0, 4.0)); acc.setValue(openvdb::Coord(11, 9, 9), Vec3s(4.0, 4.0, 4.0)); openvdb::tools::GridSampler interpolator(acc, grid.transform()); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.375, 2.375, 2.375))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.0, 1.0, 1.0))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.0, 3.0, 3.0))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1, 1.1, 1.1))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792, 2.792, 2.792))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71, 2.71, 2.71))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01, 2.01, 2.01))); } template void TestLinearInterp::testConstantValues() { typedef typename GridType::TreeType TreeType; float fillValue = 256.0f; GridType grid(fillValue); TreeType& tree = grid.tree(); // Add values to buffer zero. tree.setValue(openvdb::Coord(10, 10, 10), 2.0); tree.setValue(openvdb::Coord(11, 10, 10), 2.0); tree.setValue(openvdb::Coord(11, 11, 10), 2.0); tree.setValue(openvdb::Coord(10, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 11, 10), 2.0); tree.setValue(openvdb::Coord( 9, 10, 10), 2.0); tree.setValue(openvdb::Coord( 9, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 9, 10), 2.0); tree.setValue(openvdb::Coord(11, 9, 10), 2.0); tree.setValue(openvdb::Coord(10, 10, 11), 2.0); tree.setValue(openvdb::Coord(11, 10, 11), 2.0); tree.setValue(openvdb::Coord(11, 11, 11), 2.0); tree.setValue(openvdb::Coord(10, 11, 11), 2.0); tree.setValue(openvdb::Coord( 9, 11, 11), 2.0); tree.setValue(openvdb::Coord( 9, 10, 11), 2.0); tree.setValue(openvdb::Coord( 9, 9, 11), 2.0); tree.setValue(openvdb::Coord(10, 9, 11), 2.0); tree.setValue(openvdb::Coord(11, 9, 11), 2.0); tree.setValue(openvdb::Coord(10, 10, 9), 2.0); tree.setValue(openvdb::Coord(11, 10, 9), 2.0); tree.setValue(openvdb::Coord(11, 11, 9), 2.0); tree.setValue(openvdb::Coord(10, 11, 9), 2.0); tree.setValue(openvdb::Coord( 9, 11, 9), 2.0); tree.setValue(openvdb::Coord( 9, 10, 9), 2.0); tree.setValue(openvdb::Coord( 9, 9, 9), 2.0); tree.setValue(openvdb::Coord(10, 9, 9), 2.0); tree.setValue(openvdb::Coord(11, 9, 9), 2.0); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); } template<> void TestLinearInterp::testConstantValues() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); Vec3STree& tree = grid.tree(); // Add values to buffer zero. tree.setValue(openvdb::Coord(10, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 10, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 10, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 11), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 10, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 10, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 11, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 11, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 11, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 10, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( 9, 9, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(10, 9, 9), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(11, 9, 9), Vec3s(2.0, 2.0, 2.0)); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); } template void TestLinearInterp::testFillValues() { typedef typename GridType::TreeType TreeType; float fillValue = 256.0f; GridType grid(fillValue); //typename GridType::TreeType& tree = grid.tree(); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); typename GridType::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(256.0, val, TOLERANCE); } template<> void TestLinearInterp::testFillValues() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); //Vec3STree& tree = grid.tree(); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(10.5, 10.5, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(256.0, 256.0, 256.0))); } template void TestLinearInterp::testNegativeIndices() { typedef typename GridType::TreeType TreeType; float fillValue = 256.0f; GridType grid(fillValue); TreeType& tree = grid.tree(); tree.setValue(openvdb::Coord(-10, -10, -10), 1.0); tree.setValue(openvdb::Coord(-11, -10, -10), 2.0); tree.setValue(openvdb::Coord(-11, -11, -10), 2.0); tree.setValue(openvdb::Coord(-10, -11, -10), 2.0); tree.setValue(openvdb::Coord( -9, -11, -10), 2.0); tree.setValue(openvdb::Coord( -9, -10, -10), 2.0); tree.setValue(openvdb::Coord( -9, -9, -10), 2.0); tree.setValue(openvdb::Coord(-10, -9, -10), 2.0); tree.setValue(openvdb::Coord(-11, -9, -10), 2.0); tree.setValue(openvdb::Coord(-10, -10, -11), 3.0); tree.setValue(openvdb::Coord(-11, -10, -11), 3.0); tree.setValue(openvdb::Coord(-11, -11, -11), 3.0); tree.setValue(openvdb::Coord(-10, -11, -11), 3.0); tree.setValue(openvdb::Coord( -9, -11, -11), 3.0); tree.setValue(openvdb::Coord( -9, -10, -11), 3.0); tree.setValue(openvdb::Coord( -9, -9, -11), 3.0); tree.setValue(openvdb::Coord(-10, -9, -11), 3.0); tree.setValue(openvdb::Coord(-11, -9, -11), 3.0); tree.setValue(openvdb::Coord(-10, -10, -9), 4.0); tree.setValue(openvdb::Coord(-11, -10, -9), 4.0); tree.setValue(openvdb::Coord(-11, -11, -9), 4.0); tree.setValue(openvdb::Coord(-10, -11, -9), 4.0); tree.setValue(openvdb::Coord( -9, -11, -9), 4.0); tree.setValue(openvdb::Coord( -9, -10, -9), 4.0); tree.setValue(openvdb::Coord( -9, -9, -9), 4.0); tree.setValue(openvdb::Coord(-10, -9, -9), 4.0); tree.setValue(openvdb::Coord(-11, -9, -9), 4.0); //openvdb::tools::LinearInterp interpolator(*tree); openvdb::tools::GridSampler interpolator(grid); typename GridType::ValueType val = interpolator.sampleVoxel(-10.5, -10.5, -10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.375, val, TOLERANCE); val = interpolator.sampleVoxel(-10.0, -10.0, -10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, val, TOLERANCE); val = interpolator.sampleVoxel(-11.0, -10.0, -10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(-11.0, -11.0, -10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, val, TOLERANCE); val = interpolator.sampleVoxel(-11.0, -11.0, -11.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, val, TOLERANCE); val = interpolator.sampleVoxel(-9.0, -11.0, -9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(-9.0, -10.0, -9.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, val, TOLERANCE); val = interpolator.sampleVoxel(-10.1, -10.0, -10.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.1, val, TOLERANCE); val = interpolator.sampleVoxel(-10.8, -10.8, -10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.792, val, TOLERANCE); val = interpolator.sampleVoxel(-10.1, -10.8, -10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(-10.8, -10.1, -10.5); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.41, val, TOLERANCE); val = interpolator.sampleVoxel(-10.5, -10.1, -10.8); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.71, val, TOLERANCE); val = interpolator.sampleVoxel(-10.5, -10.8, -10.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.01, val, TOLERANCE); } template<> void TestLinearInterp::testNegativeIndices() { using namespace openvdb; Vec3s fillValue = Vec3s(256.0f, 256.0f, 256.0f); Vec3SGrid grid(fillValue); Vec3STree& tree = grid.tree(); tree.setValue(openvdb::Coord(-10, -10, -10), Vec3s(1.0, 1.0, 1.0)); tree.setValue(openvdb::Coord(-11, -10, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-11, -11, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-10, -11, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( -9, -11, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( -9, -10, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord( -9, -9, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-10, -9, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-11, -9, -10), Vec3s(2.0, 2.0, 2.0)); tree.setValue(openvdb::Coord(-10, -10, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-11, -10, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-11, -11, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-10, -11, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( -9, -11, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( -9, -10, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord( -9, -9, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-10, -9, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-11, -9, -11), Vec3s(3.0, 3.0, 3.0)); tree.setValue(openvdb::Coord(-10, -10, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-11, -10, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-11, -11, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-10, -11, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( -9, -11, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( -9, -10, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord( -9, -9, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-10, -9, -9), Vec3s(4.0, 4.0, 4.0)); tree.setValue(openvdb::Coord(-11, -9, -9), Vec3s(4.0, 4.0, 4.0)); openvdb::tools::GridSampler interpolator(grid); //openvdb::tools::LinearInterp interpolator(*tree); Vec3SGrid::ValueType val = interpolator.sampleVoxel(-10.5, -10.5, -10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.375, 2.375, 2.375))); val = interpolator.sampleVoxel(-10.0, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.0, 1.0, 1.0))); val = interpolator.sampleVoxel(-11.0, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(-11.0, -11.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0, 2.0, 2.0))); val = interpolator.sampleVoxel(-11.0, -11.0, -11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.0, 3.0, 3.0))); val = interpolator.sampleVoxel(-9.0, -11.0, -9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); val = interpolator.sampleVoxel(-9.0, -10.0, -9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0, 4.0, 4.0))); val = interpolator.sampleVoxel(-10.1, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1, 1.1, 1.1))); val = interpolator.sampleVoxel(-10.8, -10.8, -10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792, 2.792, 2.792))); val = interpolator.sampleVoxel(-10.1, -10.8, -10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); val = interpolator.sampleVoxel(-10.8, -10.1, -10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41, 2.41, 2.41))); val = interpolator.sampleVoxel(-10.5, -10.1, -10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71, 2.71, 2.71))); val = interpolator.sampleVoxel(-10.5, -10.8, -10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01, 2.01, 2.01))); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGridBbox.cc0000644000000000000000000001016312252453157015746 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include class TestGridBbox: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestGridBbox); CPPUNIT_TEST(testLeafBbox); CPPUNIT_TEST(testGridBbox); CPPUNIT_TEST_SUITE_END(); void testLeafBbox(); void testGridBbox(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGridBbox); //////////////////////////////////////// void TestGridBbox::testLeafBbox() { openvdb::FloatTree tree(/*fillValue=*/256.0f); openvdb::CoordBBox bbox; CPPUNIT_ASSERT(!tree.evalLeafBoundingBox(bbox)); // Add values to buffer zero. tree.setValue(openvdb::Coord( 0, 9, 9), 2.0); tree.setValue(openvdb::Coord(100, 35, 800), 2.5); // Coordinates in CoordBBox are inclusive! CPPUNIT_ASSERT(tree.evalLeafBoundingBox(bbox)); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0, 8, 8), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(104-1, 40-1, 808-1), bbox.max()); // Test negative coordinates. tree.setValue(openvdb::Coord(-100, -35, -800), 2.5); CPPUNIT_ASSERT(tree.evalLeafBoundingBox(bbox)); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-104, -40, -800), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(104-1, 40-1, 808-1), bbox.max()); } void TestGridBbox::testGridBbox() { openvdb::FloatTree tree(/*fillValue=*/256.0f); openvdb::CoordBBox bbox; CPPUNIT_ASSERT(!tree.evalActiveVoxelBoundingBox(bbox)); // Add values to buffer zero. tree.setValue(openvdb::Coord( 1, 0, 0), 1.5); tree.setValue(openvdb::Coord( 0, 12, 8), 2.0); tree.setValue(openvdb::Coord( 1, 35, 800), 2.5); tree.setValue(openvdb::Coord(100, 0, 16), 3.0); tree.setValue(openvdb::Coord( 1, 0, 16), 3.5); // Coordinates in CoordBBox are inclusive! CPPUNIT_ASSERT(tree.evalActiveVoxelBoundingBox(bbox)); CPPUNIT_ASSERT_EQUAL(openvdb::Coord( 0, 0, 0), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(100, 35, 800), bbox.max()); // Test negative coordinates. tree.setValue(openvdb::Coord(-100, -35, -800), 2.5); CPPUNIT_ASSERT(tree.evalActiveVoxelBoundingBox(bbox)); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-100, -35, -800), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(100, 35, 800), bbox.max()); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestInternalOrigin.cc0000644000000000000000000001052412252453157017173 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include class TestInternalOrigin: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestInternalOrigin); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestInternalOrigin); void TestInternalOrigin::test() { std::set indices; indices.insert(openvdb::Coord( 0, 0, 0)); indices.insert(openvdb::Coord( 1, 0, 0)); indices.insert(openvdb::Coord( 0,100, 8)); indices.insert(openvdb::Coord(-9, 0, 8)); indices.insert(openvdb::Coord(32, 0, 16)); indices.insert(openvdb::Coord(33, -5, 16)); indices.insert(openvdb::Coord(42,707,-35)); indices.insert(openvdb::Coord(43, 17, 64)); typedef openvdb::tree::Tree4::Type FloatTree4; FloatTree4 tree(0.0f); std::set::iterator iter=indices.begin(); for (int n=0; iter!=indices.end(); ++n, ++iter) tree.setValue(*iter,1+n*0.5f); openvdb::Coord C3, G; typedef FloatTree4::RootNodeType Node0; typedef Node0::ChildNodeType Node1; typedef Node1::ChildNodeType Node2; typedef Node2::LeafNodeType Node3; for (Node0::ChildOnCIter iter0=tree.getRootNode().cbeginChildOn(); iter0; ++iter0) {//internal 1 openvdb::Coord C0=iter0->origin(); iter0.getCoord(G); CPPUNIT_ASSERT_EQUAL(C0,G); for (Node1::ChildOnCIter iter1=iter0->cbeginChildOn(); iter1; ++iter1) {//internal 2 openvdb::Coord C1=iter1->origin(); iter1.getCoord(G); CPPUNIT_ASSERT_EQUAL(C1,G); CPPUNIT_ASSERT(C0 <= C1); CPPUNIT_ASSERT(C1 <= C0 + openvdb::Coord(Node1::DIM,Node1::DIM,Node1::DIM)); for (Node2::ChildOnCIter iter2=iter1->cbeginChildOn(); iter2; ++iter2) {//leafs openvdb::Coord C2=iter2->origin(); iter2.getCoord(G); CPPUNIT_ASSERT_EQUAL(C2,G); CPPUNIT_ASSERT(C1 <= C2); CPPUNIT_ASSERT(C2 <= C1 + openvdb::Coord(Node2::DIM,Node2::DIM,Node2::DIM)); for (Node3::ValueOnCIter iter3=iter2->cbeginValueOn(); iter3; ++iter3) {//leaf voxels iter3.getCoord(G); iter = indices.find(G); CPPUNIT_ASSERT(iter != indices.end()); indices.erase(iter); } } } } CPPUNIT_ASSERT(indices.size() == 0); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/main.cc0000644000000000000000000001233312252453157014333 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifdef DWA_OPENVDB #include #include #else #include // for exit() #include // for strrchr() #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include // for getopt(), optarg #endif static void dump(CppUnit::Test* test) { if (test == NULL) { std::cerr << "Error: no tests found\n"; return; } std::cout << test->getName() << std::endl; for (int i = 0; i < test->getChildTestCount(); i++) { dump(test->getChildTestAt(i)); } } int run(int argc, char* argv[]) { int verbose = 0; std::string tests; int c = -1; while ((c = getopt(argc, argv, "lt:v")) != -1) { switch (c) { case 'l': { dump(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); return EXIT_SUCCESS; } case 'v': verbose = 1; break; case 't': if (optarg) tests = optarg; break; default: { const char* prog = argv[0]; if (const char* ptr = ::strrchr(prog, '/')) prog = ptr + 1; std::cerr << "Usage: " << prog << " [options]\n" << "Which: runs OpenVDB library unit tests\n" << "Options:\n" << " -l list all available tests\n" << " -t test specific suite or test to run, e.g., \"-t TestGrid\"\n" << " or \"-t TestGrid::testGetGrid\" (default: run all tests)\n" << " -v verbose output\n"; return EXIT_FAILURE; } } } try { CppUnit::TestFactoryRegistry& registry = CppUnit::TestFactoryRegistry::getRegistry(); CppUnit::TestRunner runner; runner.addTest(registry.makeTest()); CppUnit::TestResult controller; CppUnit::TestResultCollector result; controller.addListener(&result); CppUnit::TextTestProgressListener progress; CppUnit::BriefTestProgressListener vProgress; if (verbose) { controller.addListener(&vProgress); } else { controller.addListener(&progress); } runner.run(controller, tests); CppUnit::CompilerOutputter outputter(&result, std::cerr); outputter.write(); return result.wasSuccessful() ? EXIT_SUCCESS : EXIT_FAILURE; } catch (std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } } #endif int main(int argc, char *argv[]) { #ifdef DWA_OPENVDB // Disable logging by default ("-quiet") unless overridden // with "-debug" or "-info". bool quiet = false; { std::vector args(argv, argv + argc); int numArgs = int(args.size()); logging_base::Config config(numArgs, &args[0]); quiet = (!config.useInfo() && !config.useDebug()); } char* quietArg = "-quiet"; std::vector args(argv, argv + argc); if (quiet) args.insert(++args.begin(), quietArg); int numArgs = int(args.size()); logging_base::Config config(numArgs, &args[0]); logging_base::configure(config); return pdevunit::run(numArgs, &args[0]); #else return run(argc, argv); #endif } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestInit.cc0000644000000000000000000001165612252453157015161 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include class TestInit: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestInit); CPPUNIT_TEST(test); CPPUNIT_TEST_SUITE_END(); void test(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestInit); void TestInit::test() { using namespace openvdb; initialize(); // data types CPPUNIT_ASSERT(DoubleMetadata::isRegisteredType()); CPPUNIT_ASSERT(FloatMetadata::isRegisteredType()); CPPUNIT_ASSERT(Int32Metadata::isRegisteredType()); CPPUNIT_ASSERT(Int64Metadata::isRegisteredType()); CPPUNIT_ASSERT(StringMetadata::isRegisteredType()); CPPUNIT_ASSERT(Vec2IMetadata::isRegisteredType()); CPPUNIT_ASSERT(Vec2SMetadata::isRegisteredType()); CPPUNIT_ASSERT(Vec2DMetadata::isRegisteredType()); CPPUNIT_ASSERT(Vec3IMetadata::isRegisteredType()); CPPUNIT_ASSERT(Vec3SMetadata::isRegisteredType()); CPPUNIT_ASSERT(Vec3DMetadata::isRegisteredType()); // map types CPPUNIT_ASSERT(math::AffineMap::isRegistered()); CPPUNIT_ASSERT(math::UnitaryMap::isRegistered()); CPPUNIT_ASSERT(math::ScaleMap::isRegistered()); CPPUNIT_ASSERT(math::TranslationMap::isRegistered()); CPPUNIT_ASSERT(math::ScaleTranslateMap::isRegistered()); CPPUNIT_ASSERT(math::NonlinearFrustumMap::isRegistered()); // grid types CPPUNIT_ASSERT(BoolGrid::isRegistered()); CPPUNIT_ASSERT(FloatGrid::isRegistered()); CPPUNIT_ASSERT(DoubleGrid::isRegistered()); CPPUNIT_ASSERT(Int32Grid::isRegistered()); CPPUNIT_ASSERT(Int64Grid::isRegistered()); CPPUNIT_ASSERT(StringGrid::isRegistered()); CPPUNIT_ASSERT(Vec3IGrid::isRegistered()); CPPUNIT_ASSERT(Vec3SGrid::isRegistered()); CPPUNIT_ASSERT(Vec3DGrid::isRegistered()); uninitialize(); CPPUNIT_ASSERT(!DoubleMetadata::isRegisteredType()); CPPUNIT_ASSERT(!FloatMetadata::isRegisteredType()); CPPUNIT_ASSERT(!Int32Metadata::isRegisteredType()); CPPUNIT_ASSERT(!Int64Metadata::isRegisteredType()); CPPUNIT_ASSERT(!StringMetadata::isRegisteredType()); CPPUNIT_ASSERT(!Vec2IMetadata::isRegisteredType()); CPPUNIT_ASSERT(!Vec2SMetadata::isRegisteredType()); CPPUNIT_ASSERT(!Vec2DMetadata::isRegisteredType()); CPPUNIT_ASSERT(!Vec3IMetadata::isRegisteredType()); CPPUNIT_ASSERT(!Vec3SMetadata::isRegisteredType()); CPPUNIT_ASSERT(!Vec3DMetadata::isRegisteredType()); CPPUNIT_ASSERT(!math::AffineMap::isRegistered()); CPPUNIT_ASSERT(!math::UnitaryMap::isRegistered()); CPPUNIT_ASSERT(!math::ScaleMap::isRegistered()); CPPUNIT_ASSERT(!math::TranslationMap::isRegistered()); CPPUNIT_ASSERT(!math::ScaleTranslateMap::isRegistered()); CPPUNIT_ASSERT(!math::NonlinearFrustumMap::isRegistered()); CPPUNIT_ASSERT(!BoolGrid::isRegistered()); CPPUNIT_ASSERT(!FloatGrid::isRegistered()); CPPUNIT_ASSERT(!DoubleGrid::isRegistered()); CPPUNIT_ASSERT(!Int32Grid::isRegistered()); CPPUNIT_ASSERT(!Int64Grid::isRegistered()); CPPUNIT_ASSERT(!StringGrid::isRegistered()); CPPUNIT_ASSERT(!Vec3IGrid::isRegistered()); CPPUNIT_ASSERT(!Vec3SGrid::isRegistered()); CPPUNIT_ASSERT(!Vec3DGrid::isRegistered()); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestQuantizedUnitVec.cc0000644000000000000000000001332512252453157017513 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include class TestQuantizedUnitVec: public CppUnit::TestFixture { public: CPPUNIT_TEST_SUITE(TestQuantizedUnitVec); CPPUNIT_TEST(testQuantization); CPPUNIT_TEST_SUITE_END(); void testQuantization(); private: // Generate a random number in the range [0, 1]. double randNumber() { return double(rand()) / (double(RAND_MAX) + 1.0); } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestQuantizedUnitVec); //////////////////////////////////////// namespace { const uint16_t MASK_XSIGN = 0x8000, // 1000000000000000 MASK_YSIGN = 0x4000, // 0100000000000000 MASK_ZSIGN = 0x2000; // 0010000000000000 } //////////////////////////////////////// void TestQuantizedUnitVec::testQuantization() { using namespace openvdb; using namespace openvdb::math; // // Check sign bits // Vec3s unitVec = Vec3s(-1.0, -1.0, -1.0); unitVec.normalize(); uint16_t quantizedVec = QuantizedUnitVec::pack(unitVec); CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_ZSIGN)); unitVec[0] = -unitVec[0]; unitVec[2] = -unitVec[2]; quantizedVec = QuantizedUnitVec::pack(unitVec); CPPUNIT_ASSERT(!(quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); unitVec[1] = -unitVec[1]; quantizedVec = QuantizedUnitVec::pack(unitVec); CPPUNIT_ASSERT(!(quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT(!(quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); QuantizedUnitVec::flipSignBits(quantizedVec); CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_ZSIGN)); unitVec[2] = -unitVec[2]; quantizedVec = QuantizedUnitVec::pack(unitVec); QuantizedUnitVec::flipSignBits(quantizedVec); CPPUNIT_ASSERT((quantizedVec & MASK_XSIGN)); CPPUNIT_ASSERT((quantizedVec & MASK_YSIGN)); CPPUNIT_ASSERT(!(quantizedVec & MASK_ZSIGN)); // // Check conversion error // const double tol = 0.015; // component error tolerance const int numNormals = 40000; // init srand(0); const int n = int(std::sqrt(double(numNormals))); const double xScale = (2.0 * M_PI) / double(n); const double yScale = M_PI / double(n); double x, y, theta, phi; Vec3s n0, n1; // generate random normals, by uniformly distributing points on a unit-sphere. // loop over a [0 to n) x [0 to n) grid. for (int a = 0; a < n; ++a) { for (int b = 0; b < n; ++b) { // jitter, move to random pos. inside the current cell x = double(a) + randNumber(); y = double(b) + randNumber(); // remap to a lat/long map theta = y * yScale; // [0 to PI] phi = x * xScale; // [0 to 2PI] // convert to cartesian coordinates on a unit sphere. // spherical coordinate triplet (r=1, theta, phi) n0[0] = float(std::sin(theta)*std::cos(phi)); n0[1] = float(std::sin(theta)*std::sin(phi)); n0[2] = float(std::cos(theta)); CPPUNIT_ASSERT_DOUBLES_EQUAL(n0.length(), 1.0, 1e-6); n1 = QuantizedUnitVec::unpack(QuantizedUnitVec::pack(n0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(n1.length(), 1.0, 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(n0[0], n1[0], tol); CPPUNIT_ASSERT_DOUBLES_EQUAL(n0[1], n1[1], tol); CPPUNIT_ASSERT_DOUBLES_EQUAL(n0[2], n1[2], tol); float sumDiff = std::abs(n0[0] - n1[0]) + std::abs(n0[1] - n1[1]) + std::abs(n0[2] - n1[2]); CPPUNIT_ASSERT(sumDiff < (2.0 * tol)); } } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestGridTransformer.cc0000644000000000000000000002700712252453157017363 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestGridTransformer: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestGridTransformer); CPPUNIT_TEST(testTransformBoolPoint); CPPUNIT_TEST(testTransformFloatPoint); CPPUNIT_TEST(testTransformFloatBox); CPPUNIT_TEST(testTransformFloatQuadratic); CPPUNIT_TEST(testTransformDoubleBox); CPPUNIT_TEST(testTransformInt32Box); CPPUNIT_TEST(testTransformInt64Box); CPPUNIT_TEST(testTransformVec3SPoint); CPPUNIT_TEST(testTransformVec3DBox); CPPUNIT_TEST(testResampleToMatch); CPPUNIT_TEST_SUITE_END(); void testTransformBoolPoint() { transformGrid(); } void testTransformFloatPoint() { transformGrid(); } void testTransformFloatBox() { transformGrid(); } void testTransformFloatQuadratic() { transformGrid(); } void testTransformDoubleBox() { transformGrid(); } void testTransformInt32Box() { transformGrid(); } void testTransformInt64Box() { transformGrid(); } void testTransformVec3SPoint() { transformGrid(); } void testTransformVec3DBox() { transformGrid(); } void testResampleToMatch(); private: template void transformGrid(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGridTransformer); //////////////////////////////////////// template void TestGridTransformer::transformGrid() { using openvdb::Coord; using openvdb::CoordBBox; using openvdb::Vec3R; typedef typename GridType::ValueType ValueT; const int radius = Sampler::radius(); const openvdb::Vec3R zeroVec(0, 0, 0), oneVec(1, 1, 1); const ValueT zero = openvdb::zeroVal(), one = zero + 1, two = one + 1, background = one; const bool transformTiles = true; // Create a sparse test grid comprising the eight corners of a 20 x 20 x 20 cube. typename GridType::Ptr inGrid = GridType::create(background); typename GridType::Accessor inAcc = inGrid->getAccessor(); inAcc.setValue(Coord( 0, 0, 0), /*value=*/zero); inAcc.setValue(Coord(20, 0, 0), zero); inAcc.setValue(Coord( 0, 20, 0), zero); inAcc.setValue(Coord( 0, 0, 20), zero); inAcc.setValue(Coord(20, 0, 20), zero); inAcc.setValue(Coord( 0, 20, 20), zero); inAcc.setValue(Coord(20, 20, 0), zero); inAcc.setValue(Coord(20, 20, 20), zero); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(8), inGrid->activeVoxelCount()); // For various combinations of scaling, rotation and translation... for (int i = 0; i < 8; ++i) { const openvdb::Vec3R scale = i & 1 ? openvdb::Vec3R(10, 4, 7.5) : oneVec, rotate = (i & 2 ? openvdb::Vec3R(30, 230, -190) : zeroVec) * (M_PI / 180), translate = i & 4 ? openvdb::Vec3R(-5, 0, 10) : zeroVec, pivot = i & 8 ? openvdb::Vec3R(0.5, 4, -3.3) : zeroVec; openvdb::tools::GridTransformer transformer(pivot, scale, rotate, translate); transformer.setTransformTiles(transformTiles); // Add a tile (either active or inactive) in the interior of the cube. const bool tileIsActive = (i % 2); inGrid->fill(CoordBBox(Coord(8), Coord(15)), two, tileIsActive); if (tileIsActive) { CPPUNIT_ASSERT_EQUAL(openvdb::Index64(512 + 8), inGrid->activeVoxelCount()); } else { CPPUNIT_ASSERT_EQUAL(openvdb::Index64(8), inGrid->activeVoxelCount()); } // Verify that a voxel outside the cube has the background value. CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(inAcc.getValue(Coord(21, 0, 0)), background)); CPPUNIT_ASSERT_EQUAL(false, inAcc.isValueOn(Coord(21, 0, 0))); // Verify that a voxel inside the cube has value two. CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(inAcc.getValue(Coord(12)), two)); CPPUNIT_ASSERT_EQUAL(tileIsActive, inAcc.isValueOn(Coord(12))); // Verify that the bounding box of all active values is 20 x 20 x 20. CoordBBox activeVoxelBBox = inGrid->evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT(!activeVoxelBBox.empty()); const Coord imin = activeVoxelBBox.min(), imax = activeVoxelBBox.max(); CPPUNIT_ASSERT_EQUAL(Coord(0), imin); CPPUNIT_ASSERT_EQUAL(Coord(20), imax); // Transform the corners of the input grid's bounding box // and compute the enclosing bounding box in the output grid. const openvdb::Mat4R xform = transformer.getTransform(); const Vec3R inRMin(imin.x(), imin.y(), imin.z()), inRMax(imax.x(), imax.y(), imax.z()); Vec3R outRMin, outRMax; outRMin = outRMax = inRMin * xform; for (int j = 0; j < 8; ++j) { Vec3R corner( j & 1 ? inRMax.x() : inRMin.x(), j & 2 ? inRMax.y() : inRMin.y(), j & 4 ? inRMax.z() : inRMin.z()); outRMin = openvdb::math::minComponent(outRMin, corner * xform); outRMax = openvdb::math::maxComponent(outRMax, corner * xform); } CoordBBox bbox( Coord(openvdb::tools::local_util::floorVec3(outRMin) - radius), Coord(openvdb::tools::local_util::ceilVec3(outRMax) + radius)); // Transform the test grid. typename GridType::Ptr outGrid = GridType::create(background); transformer.transformGrid(*inGrid, *outGrid); outGrid->tree().prune(); // Verify that the bounding box of the transformed grid // matches the transformed bounding box of the original grid. activeVoxelBBox = outGrid->evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT(!activeVoxelBBox.empty()); const openvdb::Vec3i omin = activeVoxelBBox.min().asVec3i(), omax = activeVoxelBBox.max().asVec3i(); const int bboxTolerance = 1; // allow for rounding #if 0 if (!omin.eq(bbox.min().asVec3i(), bboxTolerance) || !omax.eq(bbox.max().asVec3i(), bboxTolerance)) { std::cerr << "\nS = " << scale << ", R = " << rotate << ", T = " << translate << ", P = " << pivot << "\n" << xform.transpose() << "\n" << "computed bbox = " << bbox << "\nactual bbox = " << omin << " -> " << omax << "\n"; } #endif CPPUNIT_ASSERT(omin.eq(bbox.min().asVec3i(), bboxTolerance)); CPPUNIT_ASSERT(omax.eq(bbox.max().asVec3i(), bboxTolerance)); // Verify that (a voxel in) the interior of the cube was // transformed correctly. const Coord center = Coord::round(Vec3R(12) * xform); const typename GridType::TreeType& outTree = outGrid->tree(); CPPUNIT_ASSERT(openvdb::math::isExactlyEqual(transformTiles ? two : background, outTree.getValue(center))); if (transformTiles && tileIsActive) CPPUNIT_ASSERT(outTree.isValueOn(center)); else CPPUNIT_ASSERT(!outTree.isValueOn(center)); } } //////////////////////////////////////// void TestGridTransformer::testResampleToMatch() { using namespace openvdb; // Create an input grid with an identity transform. FloatGrid inGrid; // Populate it with a 10 x 10 x 10 cube. inGrid.fill(CoordBBox(Coord(5), Coord(14)), /*value=*/1.0); CPPUNIT_ASSERT_EQUAL(1000, int(inGrid.activeVoxelCount())); {//test identity transform FloatGrid outGrid; CPPUNIT_ASSERT(outGrid.transform() == inGrid.transform()); // Resample the input grid into the output grid using point sampling. tools::resampleToMatch(inGrid, outGrid); CPPUNIT_ASSERT_EQUAL(int(inGrid.activeVoxelCount()), int(outGrid.activeVoxelCount())); for (openvdb::FloatTree::ValueOnCIter iter = inGrid.tree().cbeginValueOn(); iter; ++iter) { ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,outGrid.tree().getValue(iter.getCoord())); } // The output grid's transform should not have changed. CPPUNIT_ASSERT(outGrid.transform() == inGrid.transform()); } {//test nontrivial transform // Create an output grid with a different transform. math::Transform::Ptr xform = math::Transform::createLinearTransform(); xform->preScale(Vec3d(2.0, 2.0, 1.0)); FloatGrid outGrid; outGrid.setTransform(xform); CPPUNIT_ASSERT(outGrid.transform() != inGrid.transform()); // Resample the input grid into the output grid using point sampling. tools::resampleToMatch(inGrid, outGrid); // The output grid's transform should not have changed. CPPUNIT_ASSERT_EQUAL(*xform, outGrid.transform()); // The output grid should have half the resolution of the input grid in x and y // and the same resolution in z. CPPUNIT_ASSERT_EQUAL(250, int(outGrid.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(Coord(5, 5, 10), outGrid.evalActiveVoxelDim()), CPPUNIT_ASSERT_EQUAL(CoordBBox(Coord(3, 3, 5), Coord(7, 7, 14)), outGrid.evalActiveVoxelBoundingBox()); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTreeCombine.cc0000644000000000000000000006612412252453157016452 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include "util.h" // for unittest_util::makeSphere() #define TEST_CSG_VERBOSE 0 #if TEST_CSG_VERBOSE #include // Timer #endif namespace { typedef openvdb::tree::Tree4::Type Float433Tree; typedef openvdb::Grid Float433Grid; } class TestTreeCombine: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); Float433Grid::registerGrid(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTreeCombine); CPPUNIT_TEST(testCombine); CPPUNIT_TEST(testCombine2); CPPUNIT_TEST(testCompMax); CPPUNIT_TEST(testCompMin); CPPUNIT_TEST(testCompSum); CPPUNIT_TEST(testCompProd); CPPUNIT_TEST(testCompReplace); CPPUNIT_TEST(testBoolTree); #ifdef DWA_OPENVDB CPPUNIT_TEST(testCsg); #endif CPPUNIT_TEST_SUITE_END(); void testCombine(); void testCombine2(); void testCompMax(); void testCompMin(); void testCompSum(); void testCompProd(); void testCompReplace(); void testBoolTree(); void testCsg(); private: template void testComp(const TreeComp&, const ValueComp&); template void testCompRepl(); template typename TreeT::Ptr visitCsg(const TreeT& a, const TreeT& b, const TreeT& ref, const VisitorT&); #if TEST_CSG_VERBOSE struct Timer { tbb::tick_count t; Timer(): t(tbb::tick_count::now()) {} void start() { t = tbb::tick_count::now(); } double stop() { return (tbb::tick_count::now() - t).seconds(); }; }; #endif }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTreeCombine); //////////////////////////////////////// namespace { namespace Local { template struct OrderDependentCombineOp { OrderDependentCombineOp() {} void operator()(const ValueT& a, const ValueT& b, ValueT& result) const { result = a + 100 * b; // result is order-dependent on A and B } }; /// Test Tree::combine(), which takes a functor that accepts three arguments /// (the a, b and result values). template void combine(TreeT& a, TreeT& b) { a.combine(b, OrderDependentCombineOp()); } /// Test Tree::combineExtended(), which takes a functor that accepts a single /// CombineArgs argument, in which the functor can return a computed active state /// for the output value. template void extendedCombine(TreeT& a, TreeT& b) { typedef typename TreeT::ValueType ValueT; struct ArgsOp { static void order(openvdb::CombineArgs& args) { // The result is order-dependent on A and B. args.setResult(args.a() + 100 * args.b()); args.setResultIsActive(args.aIsActive() || args.bIsActive()); } }; a.combineExtended(b, ArgsOp::order); } template void compMax(TreeT& a, TreeT& b) { openvdb::tools::compMax(a, b); } template void compMin(TreeT& a, TreeT& b) { openvdb::tools::compMin(a, b); } template void compSum(TreeT& a, TreeT& b) { openvdb::tools::compSum(a, b); } template void compMul(TreeT& a, TreeT& b) { openvdb::tools::compMul(a, b); }\ float orderf(float a, float b) { return a + 100 * b; } float maxf(float a, float b) { return std::max(a, b); } float minf(float a, float b) { return std::min(a, b); } float sumf(float a, float b) { return a + b; } float mulf(float a, float b) { return a * b; } openvdb::Vec3f orderv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a + 100 * b; } openvdb::Vec3f maxv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { const float aMag = a.lengthSqr(), bMag = b.lengthSqr(); return (aMag > bMag ? a : (bMag > aMag ? b : std::max(a, b))); } openvdb::Vec3f minv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { const float aMag = a.lengthSqr(), bMag = b.lengthSqr(); return (aMag < bMag ? a : (bMag < aMag ? b : std::min(a, b))); } openvdb::Vec3f sumv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a + b; } openvdb::Vec3f mulv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a * b; } } // namespace Local } // unnamed namespace void TestTreeCombine::testCombine() { testComp(Local::combine, Local::orderf); testComp(Local::combine, Local::orderv); testComp(Local::extendedCombine, Local::orderf); testComp(Local::extendedCombine, Local::orderv); } void TestTreeCombine::testCompMax() { testComp(Local::compMax, Local::maxf); testComp(Local::compMax, Local::maxv); } void TestTreeCombine::testCompMin() { testComp(Local::compMin, Local::minf); testComp(Local::compMin, Local::minv); } void TestTreeCombine::testCompSum() { testComp(Local::compSum, Local::sumf); testComp(Local::compSum, Local::sumv); } void TestTreeCombine::testCompProd() { testComp(Local::compMul, Local::mulf); testComp(Local::compMul, Local::mulv); } void TestTreeCombine::testCompReplace() { testCompRepl(); testCompRepl(); } template void TestTreeCombine::testComp(const TreeComp& comp, const ValueComp& op) { typedef typename TreeT::ValueType ValueT; const ValueT zero = openvdb::zeroVal(), minusOne = zero + (-1), minusTwo = zero + (-2), one = zero + 1, three = zero + 3, four = zero + 4, five = zero + 5; { TreeT aTree(/*background=*/one); aTree.setValueOn(openvdb::Coord(0, 0, 0), three); aTree.setValueOn(openvdb::Coord(0, 0, 1), three); aTree.setValueOn(openvdb::Coord(0, 0, 2), aTree.background()); aTree.setValueOn(openvdb::Coord(0, 1, 2), aTree.background()); aTree.setValueOff(openvdb::Coord(1, 0, 0), three); aTree.setValueOff(openvdb::Coord(1, 0, 1), three); TreeT bTree(five); bTree.setValueOn(openvdb::Coord(0, 0, 0), minusOne); bTree.setValueOn(openvdb::Coord(0, 1, 0), four); bTree.setValueOn(openvdb::Coord(0, 1, 2), minusTwo); bTree.setValueOff(openvdb::Coord(1, 0, 0), minusOne); bTree.setValueOff(openvdb::Coord(1, 1, 0), four); // Call aTree.compMax(bTree), aTree.compSum(bTree), etc. comp(aTree, bTree); // a = 3 (On), b = -1 (On) CPPUNIT_ASSERT_EQUAL(op(three, minusOne), aTree.getValue(openvdb::Coord(0, 0, 0))); // a = 3 (On), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(three, five), aTree.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 0, 1))); // a = 1 (On, = bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(one, five), aTree.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 0, 2))); // a = 1 (On, = bg), b = -2 (On) CPPUNIT_ASSERT_EQUAL(op(one, minusTwo), aTree.getValue(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 1, 2))); // a = 1 (bg), b = 4 (On) CPPUNIT_ASSERT_EQUAL(op(one, four), aTree.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 1, 0))); // a = 3 (Off), b = -1 (Off) CPPUNIT_ASSERT_EQUAL(op(three, minusOne), aTree.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 0, 0))); // a = 3 (Off), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(three, five), aTree.getValue(openvdb::Coord(1, 0, 1))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 0, 1))); // a = 1 (bg), b = 4 (Off) CPPUNIT_ASSERT_EQUAL(op(one, four), aTree.getValue(openvdb::Coord(1, 1, 0))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 1, 0))); // a = 1 (bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(one, five), aTree.getValue(openvdb::Coord(1000, 1, 2))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1000, 1, 2))); } // As above, but combining the A grid into the B grid { TreeT aTree(/*bg=*/one); aTree.setValueOn(openvdb::Coord(0, 0, 0), three); aTree.setValueOn(openvdb::Coord(0, 0, 1), three); aTree.setValueOn(openvdb::Coord(0, 0, 2), aTree.background()); aTree.setValueOn(openvdb::Coord(0, 1, 2), aTree.background()); aTree.setValueOff(openvdb::Coord(1, 0, 0), three); aTree.setValueOff(openvdb::Coord(1, 0, 1), three); TreeT bTree(five); bTree.setValueOn(openvdb::Coord(0, 0, 0), minusOne); bTree.setValueOn(openvdb::Coord(0, 1, 0), four); bTree.setValueOn(openvdb::Coord(0, 1, 2), minusTwo); bTree.setValueOff(openvdb::Coord(1, 0, 0), minusOne); bTree.setValueOff(openvdb::Coord(1, 1, 0), four); // Call bTree.compMax(aTree), bTree.compSum(aTree), etc. comp(bTree, aTree); // a = 3 (On), b = -1 (On) CPPUNIT_ASSERT_EQUAL(op(minusOne, three), bTree.getValue(openvdb::Coord(0, 0, 0))); // a = 3 (On), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(five, three), bTree.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 0, 1))); // a = 1 (On, = bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(five, one), bTree.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 0, 2))); // a = 1 (On, = bg), b = -2 (On) CPPUNIT_ASSERT_EQUAL(op(minusTwo, one), bTree.getValue(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 1, 2))); // a = 1 (bg), b = 4 (On) CPPUNIT_ASSERT_EQUAL(op(four, one), bTree.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 1, 0))); // a = 3 (Off), b = -1 (Off) CPPUNIT_ASSERT_EQUAL(op(minusOne, three), bTree.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 0, 0))); // a = 3 (Off), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(five, three), bTree.getValue(openvdb::Coord(1, 0, 1))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 0, 1))); // a = 1 (bg), b = 4 (Off) CPPUNIT_ASSERT_EQUAL(op(four, one), bTree.getValue(openvdb::Coord(1, 1, 0))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 1, 0))); // a = 1 (bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(op(five, one), bTree.getValue(openvdb::Coord(1000, 1, 2))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1000, 1, 2))); } } //////////////////////////////////////// void TestTreeCombine::testCombine2() { using openvdb::Vec3d; struct Local { static void floatAverage(const float& a, const float& b, float& result) { result = 0.5 * (a + b); } static void vec3dAverage(const Vec3d& a, const Vec3d& b, Vec3d& result) { result = 0.5 * (a + b); } }; openvdb::FloatTree aFloatTree(/*bg=*/1.0), bFloatTree(5.0), outFloatTree(1.0); aFloatTree.setValue(openvdb::Coord(0, 0, 0), 3.0); aFloatTree.setValue(openvdb::Coord(0, 0, 1), 3.0); bFloatTree.setValue(openvdb::Coord(0, 0, 0), -1.0); bFloatTree.setValue(openvdb::Coord(0, 1, 0), 4.0); outFloatTree.combine2(aFloatTree, bFloatTree, Local::floatAverage); const float tolerance = 0.0; // Average of set value 3 and set value -1 CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, outFloatTree.getValue(openvdb::Coord(0, 0, 0)), tolerance); // Average of set value 3 and bg value 5 CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, outFloatTree.getValue(openvdb::Coord(0, 0, 1)), tolerance); // Average of bg value 1 and set value 4 CPPUNIT_ASSERT_DOUBLES_EQUAL(2.5, outFloatTree.getValue(openvdb::Coord(0, 1, 0)), tolerance); // Average of bg value 1 and bg value 5 CPPUNIT_ASSERT(outFloatTree.isValueOff(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(outFloatTree.isValueOff(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, outFloatTree.getValue(openvdb::Coord(1, 0, 0)), tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, outFloatTree.getValue(openvdb::Coord(1000, 1, 2)), tolerance); // As above, but combining vector grids: const Vec3d one(1, 1, 1), two(2, 2, 2), three(3, 3, 3), four(4, 4, 4), five(5, 5, 5); openvdb::Vec3DTree aVecTree(/*bg=*/one), bVecTree(five), outVecTree(one); aVecTree.setValue(openvdb::Coord(0, 0, 0), three); aVecTree.setValue(openvdb::Coord(0, 0, 1), three); bVecTree.setValue(openvdb::Coord(0, 0, 0), -1.0 * one); bVecTree.setValue(openvdb::Coord(0, 1, 0), four); outVecTree.combine2(aVecTree, bVecTree, Local::vec3dAverage); // Average of set value 3 and set value -1 CPPUNIT_ASSERT(outVecTree.getValue(openvdb::Coord(0, 0, 0)) == one); // Average of set value 3 and bg value 5 CPPUNIT_ASSERT(outVecTree.getValue(openvdb::Coord(0, 0, 1)) == four); // Average of bg value 1 and set value 4 CPPUNIT_ASSERT(outVecTree.getValue(openvdb::Coord(0, 1, 0)) == (2.5 * one)); // Average of bg value 1 and bg value 5 CPPUNIT_ASSERT(outVecTree.isValueOff(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(outVecTree.isValueOff(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(outVecTree.getValue(openvdb::Coord(1, 0, 0)) == three); CPPUNIT_ASSERT(outVecTree.getValue(openvdb::Coord(1000, 1, 2)) == three); } //////////////////////////////////////// void TestTreeCombine::testBoolTree() { openvdb::BoolGrid::Ptr sphere = openvdb::BoolGrid::create(); unittest_util::makeSphere(/*dim=*/openvdb::Coord(32), /*ctr=*/openvdb::Vec3f(0), /*radius=*/20.0, *sphere, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::BoolGrid::Ptr aGrid = sphere->copy(), bGrid = sphere->copy(); // CSG operations work only on level sets with a nonzero inside and outside values. CPPUNIT_ASSERT_THROW(openvdb::tools::csgUnion(aGrid->tree(), bGrid->tree()), openvdb::ValueError); CPPUNIT_ASSERT_THROW(openvdb::tools::csgIntersection(aGrid->tree(), bGrid->tree()), openvdb::ValueError); CPPUNIT_ASSERT_THROW(openvdb::tools::csgDifference(aGrid->tree(), bGrid->tree()), openvdb::ValueError); openvdb::tools::compSum(aGrid->tree(), bGrid->tree()); bGrid = sphere->copy(); openvdb::tools::compMax(aGrid->tree(), bGrid->tree()); int mismatches = 0; openvdb::BoolGrid::ConstAccessor acc = sphere->getConstAccessor(); for (openvdb::BoolGrid::ValueAllCIter it = aGrid->cbeginValueAll(); it; ++it) { if (*it != acc.getValue(it.getCoord())) ++mismatches; } CPPUNIT_ASSERT_EQUAL(0, mismatches); } //////////////////////////////////////// template void TestTreeCombine::testCompRepl() { typedef typename TreeT::ValueType ValueT; const ValueT zero = openvdb::zeroVal(), minusOne = zero + (-1), one = zero + 1, three = zero + 3, four = zero + 4, five = zero + 5; { TreeT aTree(/*bg=*/one); aTree.setValueOn(openvdb::Coord(0, 0, 0), three); aTree.setValueOn(openvdb::Coord(0, 0, 1), three); aTree.setValueOn(openvdb::Coord(0, 0, 2), aTree.background()); aTree.setValueOn(openvdb::Coord(0, 1, 2), aTree.background()); aTree.setValueOff(openvdb::Coord(1, 0, 0), three); aTree.setValueOff(openvdb::Coord(1, 0, 1), three); TreeT bTree(five); bTree.setValueOn(openvdb::Coord(0, 0, 0), minusOne); bTree.setValueOn(openvdb::Coord(0, 1, 0), four); bTree.setValueOn(openvdb::Coord(0, 1, 2), minusOne); bTree.setValueOff(openvdb::Coord(1, 0, 0), minusOne); bTree.setValueOff(openvdb::Coord(1, 1, 0), four); // Copy active voxels of bTree into aTree. openvdb::tools::compReplace(aTree, bTree); // a = 3 (On), b = -1 (On) CPPUNIT_ASSERT_EQUAL(minusOne, aTree.getValue(openvdb::Coord(0, 0, 0))); // a = 3 (On), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(three, aTree.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 0, 1))); // a = 1 (On, = bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(one, aTree.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 0, 2))); // a = 1 (On, = bg), b = -1 (On) CPPUNIT_ASSERT_EQUAL(minusOne, aTree.getValue(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 1, 2))); // a = 1 (bg), b = 4 (On) CPPUNIT_ASSERT_EQUAL(four, aTree.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(aTree.isValueOn(openvdb::Coord(0, 1, 0))); // a = 3 (Off), b = -1 (Off) CPPUNIT_ASSERT_EQUAL(three, aTree.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 0, 0))); // a = 3 (Off), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(three, aTree.getValue(openvdb::Coord(1, 0, 1))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 0, 1))); // a = 1 (bg), b = 4 (Off) CPPUNIT_ASSERT_EQUAL(one, aTree.getValue(openvdb::Coord(1, 1, 0))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1, 1, 0))); // a = 1 (bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(one, aTree.getValue(openvdb::Coord(1000, 1, 2))); CPPUNIT_ASSERT(aTree.isValueOff(openvdb::Coord(1000, 1, 2))); } // As above, but combining the A grid into the B grid { TreeT aTree(/*background=*/one); aTree.setValueOn(openvdb::Coord(0, 0, 0), three); aTree.setValueOn(openvdb::Coord(0, 0, 1), three); aTree.setValueOn(openvdb::Coord(0, 0, 2), aTree.background()); aTree.setValueOn(openvdb::Coord(0, 1, 2), aTree.background()); aTree.setValueOff(openvdb::Coord(1, 0, 0), three); aTree.setValueOff(openvdb::Coord(1, 0, 1), three); TreeT bTree(five); bTree.setValueOn(openvdb::Coord(0, 0, 0), minusOne); bTree.setValueOn(openvdb::Coord(0, 1, 0), four); bTree.setValueOn(openvdb::Coord(0, 1, 2), minusOne); bTree.setValueOff(openvdb::Coord(1, 0, 0), minusOne); bTree.setValueOff(openvdb::Coord(1, 1, 0), four); // Copy active voxels of aTree into bTree. openvdb::tools::compReplace(bTree, aTree); // a = 3 (On), b = -1 (On) CPPUNIT_ASSERT_EQUAL(three, bTree.getValue(openvdb::Coord(0, 0, 0))); // a = 3 (On), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(three, bTree.getValue(openvdb::Coord(0, 0, 1))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 0, 1))); // a = 1 (On, = bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(one, bTree.getValue(openvdb::Coord(0, 0, 2))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 0, 2))); // a = 1 (On, = bg), b = -1 (On) CPPUNIT_ASSERT_EQUAL(one, bTree.getValue(openvdb::Coord(0, 1, 2))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 1, 2))); // a = 1 (bg), b = 4 (On) CPPUNIT_ASSERT_EQUAL(four, bTree.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT(bTree.isValueOn(openvdb::Coord(0, 1, 0))); // a = 3 (Off), b = -1 (Off) CPPUNIT_ASSERT_EQUAL(minusOne, bTree.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 0, 0))); // a = 3 (Off), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(five, bTree.getValue(openvdb::Coord(1, 0, 1))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 0, 1))); // a = 1 (bg), b = 4 (Off) CPPUNIT_ASSERT_EQUAL(four, bTree.getValue(openvdb::Coord(1, 1, 0))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1, 1, 0))); // a = 1 (bg), b = 5 (bg) CPPUNIT_ASSERT_EQUAL(five, bTree.getValue(openvdb::Coord(1000, 1, 2))); CPPUNIT_ASSERT(bTree.isValueOff(openvdb::Coord(1000, 1, 2))); } } //////////////////////////////////////// void TestTreeCombine::testCsg() { typedef openvdb::FloatTree TreeT; typedef TreeT::Ptr TreePtr; typedef openvdb::Grid GridT; struct Local { static TreePtr readFile(const std::string& fname) { std::string filename(fname), gridName("LevelSet"); size_t space = filename.find_last_of(' '); if (space != std::string::npos) { gridName = filename.substr(space + 1); filename.erase(space); } TreePtr tree; openvdb::io::File file(filename); file.open(); if (openvdb::GridBase::Ptr basePtr = file.readGrid(gridName)) { if (GridT::Ptr gridPtr = openvdb::gridPtrCast(basePtr)) { tree = gridPtr->treePtr(); } } file.close(); return tree; } static void writeFile(TreePtr tree, const std::string& filename) { openvdb::io::File file(filename); openvdb::GridPtrVec grids; GridT::Ptr grid = openvdb::createGrid(tree); grid->setName("LevelSet"); grids.push_back(grid); file.write(grids); } static void visitorUnion(TreeT& a, TreeT& b) { openvdb::tools::csgUnion(a, b); } static void visitorIntersect(TreeT& a, TreeT& b) { openvdb::tools::csgIntersection(a, b); } static void visitorDiff(TreeT& a, TreeT& b) { openvdb::tools::csgDifference(a, b); } }; TreePtr smallTree1, smallTree2, largeTree1, largeTree2, refTree, outTree; #if TEST_CSG_VERBOSE Timer timer; timer.start(); #endif const std::string testDir("/work/rd/fx_tools/vdb_unittest/TestGridCombine::testCsg/"); smallTree1 = Local::readFile(testDir + "small1.vdb2 LevelSet"); CPPUNIT_ASSERT(smallTree1.get() != NULL); smallTree2 = Local::readFile(testDir + "small2.vdb2 Cylinder"); CPPUNIT_ASSERT(smallTree2.get() != NULL); largeTree1 = Local::readFile(testDir + "large1.vdb2 LevelSet"); CPPUNIT_ASSERT(largeTree1.get() != NULL); largeTree2 = Local::readFile(testDir + "large2.vdb2 LevelSet"); CPPUNIT_ASSERT(largeTree2.get() != NULL); #if TEST_CSG_VERBOSE std::cerr << "file read: " << timer.stop() << " sec\n"; #endif #if TEST_CSG_VERBOSE std::cerr << "\n\n"; #endif refTree = Local::readFile(testDir + "small_union.vdb2"); outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorUnion); //Local::writeFile(outTree, "/tmp/small_union_out.vdb2"); refTree = Local::readFile(testDir + "large_union.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorUnion); //Local::writeFile(outTree, "/tmp/large_union_out.vdb2"); #if TEST_CSG_VERBOSE std::cerr << "\n\n"; #endif refTree = Local::readFile(testDir + "small_intersection.vdb2"); outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorIntersect); //Local::writeFile(outTree, "/tmp/small_intersection_out.vdb2"); refTree = Local::readFile(testDir + "large_intersection.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorIntersect); //Local::writeFile(outTree, "/tmp/large_intersection_out.vdb2"); #if TEST_CSG_VERBOSE std::cerr << "\n\n"; #endif refTree = Local::readFile(testDir + "small_difference.vdb2"); outTree = visitCsg(*smallTree1, *smallTree2, *refTree, Local::visitorDiff); //Local::writeFile(outTree, "/tmp/small_difference_out.vdb2"); refTree = Local::readFile(testDir + "large_difference.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorDiff); //Local::writeFile(outTree, "/tmp/large_difference_out.vdb2"); } template typename TreeT::Ptr TestTreeCombine::visitCsg(const TreeT& aInputTree, const TreeT& bInputTree, const TreeT& refTree, const VisitorT& visitor) { typedef typename TreeT::Ptr TreePtr; #if TEST_CSG_VERBOSE Timer timer; timer.start(); #endif TreePtr aTree(new TreeT(aInputTree)); TreeT bTree(bInputTree); #if TEST_CSG_VERBOSE std::cerr << "deep copy: " << timer.stop() << " sec\n"; #endif #if (TEST_CSG_VERBOSE > 1) std::cerr << "\nA grid:\n"; aTree->print(std::cerr, /*verbose=*/3); std::cerr << "\nB grid:\n"; bTree.print(std::cerr, /*verbose=*/3); std::cerr << "\nExpected:\n"; refTree.print(std::cerr, /*verbose=*/3); std::cerr << "\n"; #endif // Compute the CSG combination of the two grids. #if TEST_CSG_VERBOSE timer.start(); #endif visitor(*aTree, bTree); #if TEST_CSG_VERBOSE std::cerr << "combine: " << timer.stop() << " sec\n"; #endif #if (TEST_CSG_VERBOSE > 1) std::cerr << "\nActual:\n"; aTree->print(std::cerr, /*verbose=*/3); #endif std::ostringstream aInfo, refInfo; aTree->print(aInfo, /*verbose=*/3); refTree.print(refInfo, /*verbose=*/3); CPPUNIT_ASSERT_EQUAL(refInfo.str(), aInfo.str()); CPPUNIT_ASSERT(aTree->hasSameTopology(refTree)); return aTree; } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestNodeIterator.cc0000644000000000000000000003370012252453157016647 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include class TestNodeIterator: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestNodeIterator); CPPUNIT_TEST(testEmpty); CPPUNIT_TEST(testSinglePositive); CPPUNIT_TEST(testSingleNegative); CPPUNIT_TEST(testMultipleBlocks); CPPUNIT_TEST(testDepthBounds); CPPUNIT_TEST_SUITE_END(); void testEmpty(); void testSinglePositive(); void testSingleNegative(); void testMultipleBlocks(); void testDepthBounds(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestNodeIterator); namespace { typedef openvdb::tree::Tree4::Type Tree323f; } //////////////////////////////////////// void TestNodeIterator::testEmpty() { Tree323f tree(/*fillValue=*/256.0f); { Tree323f::NodeCIter iter(tree); CPPUNIT_ASSERT(!iter.next()); } { tree.setValue(openvdb::Coord(8, 16, 24), 10.f); Tree323f::NodeIter iter(tree); // non-const CPPUNIT_ASSERT(iter); // Try modifying the tree through a non-const iterator. Tree323f::RootNodeType* root = NULL; iter.getNode(root); CPPUNIT_ASSERT(root != NULL); root->clear(); // Verify that the tree is now empty. iter = Tree323f::NodeIter(tree); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT(!iter.next()); } } void TestNodeIterator::testSinglePositive() { { Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(8, 16, 24), 10.f); Tree323f::NodeCIter iter(tree); CPPUNIT_ASSERT(Tree323f::LeafNodeType::DIM == 8); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getDepth()); CPPUNIT_ASSERT_EQUAL(tree.treeDepth(), 1 + iter.getLevel()); openvdb::CoordBBox range, bbox; tree.getIndexRange(range); iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(bbox.min(), range.min()); CPPUNIT_ASSERT_EQUAL(bbox.max(), range.max()); // Descend to the depth-1 internal node with bounding box // (0, 0, 0) -> (255, 255, 255) containing voxel (8, 16, 24). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord((1 << (3 + 2 + 3)) - 1), bbox.max()); // Descend to the depth-2 internal node with bounding box // (0, 0, 0) -> (31, 31, 31) containing voxel (8, 16, 24). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord((1 << (2 + 3)) - 1), bbox.max()); // Descend to the leaf node with bounding box (8, 16, 24) -> (15, 23, 31) // containing voxel (8, 16, 24). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); iter.getBoundingBox(bbox); range.min().reset(8, 16, 24); range.max() = range.min().offsetBy((1 << 3) - 1); // add leaf node size CPPUNIT_ASSERT_EQUAL(range.min(), bbox.min()); CPPUNIT_ASSERT_EQUAL(range.max(), bbox.max()); iter.next(); CPPUNIT_ASSERT(!iter); } { Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(129), 10.f); Tree323f::NodeCIter iter(tree); CPPUNIT_ASSERT(Tree323f::LeafNodeType::DIM == 8); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getDepth()); CPPUNIT_ASSERT_EQUAL(tree.treeDepth(), 1 + iter.getLevel()); openvdb::CoordBBox range, bbox; tree.getIndexRange(range); iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(bbox.min(), range.min()); CPPUNIT_ASSERT_EQUAL(bbox.max(), range.max()); // Descend to the depth-1 internal node with bounding box // (0, 0, 0) -> (255, 255, 255) containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord((1 << (3 + 2 + 3)) - 1), bbox.max()); // Descend to the depth-2 internal node with bounding box // (128, 128, 128) -> (159, 159, 159) containing voxel (129, 129, 129). // (128 is the nearest multiple of 32 less than 129.) iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); iter.getBoundingBox(bbox); range.min().reset(128, 128, 128); CPPUNIT_ASSERT_EQUAL(range.min(), bbox.min()); CPPUNIT_ASSERT_EQUAL(range.min().offsetBy((1 << (2 + 3)) - 1), bbox.max()); // Descend to the leaf node with bounding box // (128, 128, 128) -> (135, 135, 135) containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); iter.getBoundingBox(bbox); range.max() = range.min().offsetBy((1 << 3) - 1); // add leaf node size CPPUNIT_ASSERT_EQUAL(range.min(), bbox.min()); CPPUNIT_ASSERT_EQUAL(range.max(), bbox.max()); iter.next(); CPPUNIT_ASSERT(!iter); } } void TestNodeIterator::testSingleNegative() { Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(-1), 10.f); Tree323f::NodeCIter iter(tree); CPPUNIT_ASSERT(Tree323f::LeafNodeType::DIM == 8); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getDepth()); CPPUNIT_ASSERT_EQUAL(tree.treeDepth(), 1 + iter.getLevel()); openvdb::CoordBBox range, bbox; tree.getIndexRange(range); iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(bbox.min(), range.min()); CPPUNIT_ASSERT_EQUAL(bbox.max(), range.max()); // Descend to the depth-1 internal node with bounding box // (-256, -256, -256) -> (-1, -1, -1) containing voxel (-1, -1, -1). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-(1 << (3 + 2 + 3))), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-1), bbox.max()); // Descend to the depth-2 internal node with bounding box // (-32, -32, -32) -> (-1, -1, -1) containing voxel (-1, -1, -1). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-(1 << (2 + 3))), bbox.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(-1), bbox.max()); // Descend to the leaf node with bounding box (-8, -8, -8) -> (-1, -1, -1) // containing voxel (-1, -1, -1). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); iter.getBoundingBox(bbox); range.max().reset(-1, -1, -1); range.min() = range.max().offsetBy(-((1 << 3) - 1)); // add leaf node size CPPUNIT_ASSERT_EQUAL(range.min(), bbox.min()); CPPUNIT_ASSERT_EQUAL(range.max(), bbox.max()); iter.next(); CPPUNIT_ASSERT(!iter); } void TestNodeIterator::testMultipleBlocks() { Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(-1), 10.f); tree.setValue(openvdb::Coord(129), 10.f); Tree323f::NodeCIter iter(tree); CPPUNIT_ASSERT(Tree323f::LeafNodeType::DIM == 8); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getDepth()); CPPUNIT_ASSERT_EQUAL(tree.treeDepth(), 1 + iter.getLevel()); // Descend to the depth-1 internal node with bounding box // (-256, -256, -256) -> (-1, -1, -1) containing voxel (-1, -1, -1). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); // Descend to the depth-2 internal node with bounding box // (-32, -32, -32) -> (-1, -1, -1) containing voxel (-1, -1, -1). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); // Descend to the leaf node with bounding box (-8, -8, -8) -> (-1, -1, -1) // containing voxel (-1, -1, -1). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); openvdb::Coord expectedMin, expectedMax(-1, -1, -1); expectedMin = expectedMax.offsetBy(-((1 << 3) - 1)); // add leaf node size openvdb::CoordBBox bbox; iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(expectedMin, bbox.min()); CPPUNIT_ASSERT_EQUAL(expectedMax, bbox.max()); // Ascend to the depth-1 internal node with bounding box (0, 0, 0) -> (255, 255, 255) // containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); // Descend to the depth-2 internal node with bounding box // (128, 128, 128) -> (159, 159, 159) containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); // Descend to the leaf node with bounding box (128, 128, 128) -> (135, 135, 135) // containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); expectedMin.reset(128, 128, 128); expectedMax = expectedMin.offsetBy((1 << 3) - 1); // add leaf node size iter.getBoundingBox(bbox); CPPUNIT_ASSERT_EQUAL(expectedMin, bbox.min()); CPPUNIT_ASSERT_EQUAL(expectedMax, bbox.max()); iter.next(); CPPUNIT_ASSERT(!iter); } void TestNodeIterator::testDepthBounds() { Tree323f tree(/*fillValue=*/256.0f); tree.setValue(openvdb::Coord(-1), 10.f); tree.setValue(openvdb::Coord(129), 10.f); { // Iterate over internal nodes only. Tree323f::NodeCIter iter(tree); iter.setMaxDepth(2); iter.setMinDepth(1); // Begin at the depth-1 internal node with bounding box // (-256, -256, -256) -> (-1, -1, -1) containing voxel (-1, -1, -1). CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); // Descend to the depth-2 internal node with bounding box // (-32, -32, -32) -> (-1, -1, -1) containing voxel (-1, -1, -1). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); // Skipping the leaf node, ascend to the depth-1 internal node with bounding box // (0, 0, 0) -> (255, 255, 255) containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); // Descend to the depth-2 internal node with bounding box // (128, 128, 128) -> (159, 159, 159) containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(2U, iter.getDepth()); // Verify that no internal nodes remain unvisited. iter.next(); CPPUNIT_ASSERT(!iter); } { // Iterate over depth-1 internal nodes only. Tree323f::NodeCIter iter(tree); iter.setMaxDepth(1); iter.setMinDepth(1); // Begin at the depth-1 internal node with bounding box // (-256, -256, -256) -> (-1, -1, -1) containing voxel (-1, -1, -1). CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); // Skip to the depth-1 internal node with bounding box // (0, 0, 0) -> (255, 255, 255) containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(1U, iter.getDepth()); // Verify that no depth-1 nodes remain unvisited. iter.next(); CPPUNIT_ASSERT(!iter); } { // Iterate over leaf nodes only. Tree323f::NodeCIter iter = tree.cbeginNode(); iter.setMaxDepth(3); iter.setMinDepth(3); // Begin at the leaf node with bounding box (-8, -8, -8) -> (-1, -1, -1) // containing voxel (-1, -1, -1). CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); // Skip to the leaf node with bounding box (128, 128, 128) -> (135, 135, 135) // containing voxel (129, 129, 129). iter.next(); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(0U, iter.getLevel()); // Verify that no leaf nodes remain unvisited. iter.next(); CPPUNIT_ASSERT(!iter); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTransform.cc0000644000000000000000000004117312252453157016226 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include class TestTransform: public CppUnit::TestCase { public: virtual void setUp(); virtual void tearDown(); CPPUNIT_TEST_SUITE(TestTransform); CPPUNIT_TEST(testLinearTransform); CPPUNIT_TEST(testTransformEquality); CPPUNIT_TEST(testBackwardCompatibility); CPPUNIT_TEST(testIsIdentity); CPPUNIT_TEST_SUITE_END(); void testLinearTransform(); void testTransformEquality(); void testBackwardCompatibility(); void testIsIdentity(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTransform); //////////////////////////////////////// void TestTransform::setUp() { openvdb::math::MapRegistry::clear(); openvdb::math::AffineMap::registerMap(); openvdb::math::ScaleMap::registerMap(); openvdb::math::UniformScaleMap::registerMap(); openvdb::math::TranslationMap::registerMap(); openvdb::math::ScaleTranslateMap::registerMap(); openvdb::math::UniformScaleTranslateMap::registerMap(); } void TestTransform::tearDown() { openvdb::math::MapRegistry::clear(); } ////openvdb:://////////////////////////////////// void TestTransform::testLinearTransform() { using namespace openvdb; double TOL = 1e-7; // Test: Scaling math::Transform::Ptr t = math::Transform::createLinearTransform(0.5); Vec3R voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); CPPUNIT_ASSERT(t->hasUniformScale()); // world to index space Vec3R xyz(-1.0, 2.0, 4.0); xyz = t->worldToIndex(xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(-2.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 4.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 8.0, xyz[2], TOL); xyz = Vec3R(-0.7, 2.4, 4.7); // cell centered conversion Coord ijk = t->worldToIndexCellCentered(xyz); CPPUNIT_ASSERT_EQUAL(Coord(-1, 5, 9), ijk); // node centrered conversion ijk = t->worldToIndexNodeCentered(xyz); CPPUNIT_ASSERT_EQUAL(Coord(-2, 4, 9), ijk); // index to world space ijk = Coord(4, 2, -8); xyz = t->indexToWorld(ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL( 2.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(-4.0, xyz[2], TOL); // I/O test { std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); t->write(ss); t = math::Transform::createLinearTransform(); // Since we wrote only a fragment of a VDB file (in particular, we didn't // write the header), set the file format version number explicitly. io::setCurrentVersion(ss); t->read(ss); } // check map type CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); ////////// // Test: Scale, translation & rotation t = math::Transform::createLinearTransform(2.0); // rotate, 180 deg, (produces a diagonal matrix that can be simplified into a scale map) // with diagonal -2, 2, -2 const double PI = std::atan(1.0)*4; t->preRotate(PI, math::Y_AXIS); // this is just a rotation so it will have uniform scale CPPUNIT_ASSERT(t->hasUniformScale()); CPPUNIT_ASSERT_EQUAL(math::ScaleMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); // translate t->postTranslate(Vec3d(1.0, 0.0, 1.0)); CPPUNIT_ASSERT_EQUAL(math::ScaleTranslateMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[2], TOL); // I/O test { std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); t->write(ss); t = math::Transform::createLinearTransform(); // Since we wrote only a fragment of a VDB file (in particular, we didn't // write the header), set the file format version number explicitly. io::setCurrentVersion(ss); t->read(ss); } // check map type CPPUNIT_ASSERT_EQUAL(math::ScaleTranslateMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); xyz = t->worldToIndex(Vec3R(-2.0, -2.0, -2.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.5, xyz[2], TOL); // new transform t = math::Transform::createLinearTransform(1.0); // rotate 90 deg t->preRotate( std::atan(1.0) * 2 , math::Y_AXIS); // check map type CPPUNIT_ASSERT_EQUAL(math::AffineMap::mapType(), t->baseMap()->type()); xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); // I/O test { std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); t->write(ss); t = math::Transform::createLinearTransform(); CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, xyz[2], TOL); // Since we wrote only a fragment of a VDB file (in particular, we didn't // write the header), set the file format version number explicitly. io::setCurrentVersion(ss); t->read(ss); } // check map type CPPUNIT_ASSERT_EQUAL(math::AffineMap::mapType(), t->baseMap()->type()); xyz = t->worldToIndex(Vec3R(1.0, 1.0, 1.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); } //////////////////////////////////////// void TestTransform::testTransformEquality() { using namespace openvdb; // maps created in different ways may be equivalent math::Transform::Ptr t1 = math::Transform::createLinearTransform(0.5); math::Mat4d mat = math::Mat4d::identity(); mat.preScale(math::Vec3d(0.5, 0.5, 0.5)); math::Transform::Ptr t2 = math::Transform::createLinearTransform(mat); CPPUNIT_ASSERT( *t1 == *t2); // test that the auto-convert to the simplest form worked CPPUNIT_ASSERT( t1->mapType() == t2->mapType()); mat.preScale(math::Vec3d(1., 1., .4)); math::Transform::Ptr t3 = math::Transform::createLinearTransform(mat); CPPUNIT_ASSERT( *t1 != *t3); // test equality between different but equivalent maps math::UniformScaleTranslateMap::Ptr ustmap( new math::UniformScaleTranslateMap(1.0, math::Vec3d(0,0,0))); math::Transform::Ptr t4( new math::Transform( ustmap) ); CPPUNIT_ASSERT( t4->baseMap()->isType() ); math::Transform::Ptr t5( new math::Transform); // constructs with a scale map CPPUNIT_ASSERT( t5->baseMap()->isType() ); CPPUNIT_ASSERT( *t5 == *t4); CPPUNIT_ASSERT( t5->mapType() != t4->mapType() ); // test inequatlity of two maps of the same type math::UniformScaleTranslateMap::Ptr ustmap2( new math::UniformScaleTranslateMap(1.0, math::Vec3d(1,0,0))); math::Transform::Ptr t6( new math::Transform( ustmap2) ); CPPUNIT_ASSERT( t6->baseMap()->isType() ); CPPUNIT_ASSERT( *t6 != *t4); // test comparison of linear to nonlinear map openvdb::BBoxd bbox(math::Vec3d(0), math::Vec3d(100)); math::Transform::Ptr frustum = math::Transform::createFrustumTransform(bbox, 0.25, 10); CPPUNIT_ASSERT( *frustum != *t1 ); } //////////////////////////////////////// void TestTransform::testBackwardCompatibility() { using namespace openvdb; double TOL = 1e-7; // Register maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::ScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary); ////////// // Construct and write out an old transform that gets converted // into a ScaleMap on read. // First write the old transform type name writeString(ss, Name("LinearTransform")); // Second write the old transform's base class membes. Coord tmpMin(0), tmpMax(1); ss.write(reinterpret_cast(&tmpMin), sizeof(Coord::ValueType) * 3); ss.write(reinterpret_cast(&tmpMax), sizeof(Coord::ValueType) * 3); // Last write out the old linear transform's members math::Mat4d tmpLocalToWorld = math::Mat4d::identity(), tmpWorldToLocal = math::Mat4d::identity(), tmpVoxelToLocal = math::Mat4d::identity(), tmpLocalToVoxel = math::Mat4d::identity(); tmpVoxelToLocal.preScale(math::Vec3d(0.5, 0.5, 0.5)); tmpLocalToWorld.write(ss); tmpWorldToLocal.write(ss); tmpVoxelToLocal.write(ss); tmpLocalToVoxel.write(ss); // Read in the old transform and converting it to the new map based implementation. math::Transform::Ptr t = math::Transform::createLinearTransform(1.0); t->read(ss); // check map type CPPUNIT_ASSERT_EQUAL(math::UniformScaleMap::mapType(), t->baseMap()->type()); Vec3d voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, voxelSize[2], TOL); Vec3d xyz = t->worldToIndex(Vec3d(-1.0, 2.0, 4.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-2.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 4.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 8.0, xyz[2], TOL); ////////// // Construct and write out an old transform that gets converted // into a ScaleTranslateMap on read. ss.clear(); writeString(ss, Name("LinearTransform")); ss.write((char*)&tmpMin, sizeof(Coord::ValueType) * 3); ss.write((char*)&tmpMax, sizeof(Coord::ValueType) * 3); tmpLocalToWorld = math::Mat4d::identity(), tmpWorldToLocal = math::Mat4d::identity(), tmpVoxelToLocal = math::Mat4d::identity(), tmpLocalToVoxel = math::Mat4d::identity(); tmpVoxelToLocal.preScale(math::Vec3d(2.0, 2.0, 2.0)); tmpLocalToWorld.setTranslation(math::Vec3d(1.0, 0.0, 1.0)); tmpLocalToWorld.write(ss); tmpWorldToLocal.write(ss); tmpVoxelToLocal.write(ss); tmpLocalToVoxel.write(ss); // Read in the old transform and converting it to the new map based implementation. t = math::Transform::createLinearTransform(); // rest transform t->read(ss); CPPUNIT_ASSERT_EQUAL(math::UniformScaleTranslateMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, voxelSize[2], TOL); xyz = t->worldToIndex(Vec3d(1.0, 1.0, 1.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, xyz[2], TOL); ////////// // Construct and write out an old transform that gets converted // into a AffineMap on read. ss.clear(); writeString(ss, Name("LinearTransform")); ss.write((char*)&tmpMin, sizeof(Coord::ValueType) * 3); ss.write((char*)&tmpMax, sizeof(Coord::ValueType) * 3); tmpLocalToWorld = math::Mat4d::identity(), tmpWorldToLocal = math::Mat4d::identity(), tmpVoxelToLocal = math::Mat4d::identity(), tmpLocalToVoxel = math::Mat4d::identity(); tmpVoxelToLocal.preScale(math::Vec3d(1.0, 1.0, 1.0)); tmpLocalToWorld.preRotate( math::Y_AXIS, std::atan(1.0) * 2); tmpLocalToWorld.write(ss); tmpWorldToLocal.write(ss); tmpVoxelToLocal.write(ss); tmpLocalToVoxel.write(ss); // Read in the old transform and converting it to the new map based implementation. t = math::Transform::createLinearTransform(); // rest transform t->read(ss); CPPUNIT_ASSERT_EQUAL(math::AffineMap::mapType(), t->baseMap()->type()); voxelSize = t->voxelSize(); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, voxelSize[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, voxelSize[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, voxelSize[2], TOL); xyz = t->worldToIndex(Vec3d(1.0, 1.0, 1.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-1.0, xyz[0], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[1], TOL); CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, xyz[2], TOL); } void TestTransform::testIsIdentity() { using namespace openvdb; math::Transform::Ptr t = math::Transform::createLinearTransform(1.0); CPPUNIT_ASSERT(t->isIdentity()); t->preScale(Vec3d(2,2,2)); CPPUNIT_ASSERT(!t->isIdentity()); t->preScale(Vec3d(0.5,0.5,0.5)); CPPUNIT_ASSERT(t->isIdentity()); BBoxd bbox(math::Vec3d(-5,-5,0), Vec3d(5,5,10)); math::Transform::Ptr f = math::Transform::createFrustumTransform(bbox, /*taper*/ 1, /*depth*/ 1, /*voxel size*/ 1); f->preScale(Vec3d(10,10,10)); CPPUNIT_ASSERT(f->isIdentity()); // rotate by PI/2 f->postRotate(std::atan(1.0)*2, math::Y_AXIS); CPPUNIT_ASSERT(!f->isIdentity()); f->postRotate(std::atan(1.0)*6, math::Y_AXIS); CPPUNIT_ASSERT(f->isIdentity()); } //////////////////////////////////////// /// @todo Test the new frustum transform. /* void TestTransform::testNonlinearTransform() { using namespace openvdb; double TOL = 1e-7; } */ // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/util.h0000644000000000000000000001361112252453157014226 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED #define OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED #include #include #include namespace unittest_util { enum SphereMode { SPHERE_DENSE, SPHERE_DENSE_NARROW_BAND, SPHERE_SPARSE_NARROW_BAND }; /// @brief Generates the signed distance to a sphere located at @a center /// and with a specified @a radius (both in world coordinates). Only voxels /// in the domain [0,0,0] -> @a dim are considered. Also note that the /// level set is either dense, dense narrow-band or sparse narrow-band. /// /// @note This method is VERY SLOW and should only be used for debugging purposes! /// However it works for any transform and even with open level sets. /// A faster approch for closed narrow band generation is to only set voxels /// sparsely and then use grid::signedFloodFill to defined the sign /// of the background values and tiles! This is implemented in openvdb/tools/LevelSetSphere.h template inline void makeSphere(const openvdb::Coord& dim, const openvdb::Vec3f& center, float radius, GridType& grid, SphereMode mode) { typedef typename GridType::ValueType ValueT; const ValueT zero = openvdb::zeroVal(), outside = grid.background(), inside = -outside; typename GridType::Accessor acc = grid.getAccessor(); openvdb::Coord xyz; for (xyz[0]=0; xyz[0] inline void makeSphere(const openvdb::Coord& dim, const openvdb::Vec3f& center, float radius, openvdb::BoolGrid& grid, SphereMode) { openvdb::BoolGrid::Accessor acc = grid.getAccessor(); openvdb::Coord xyz; for (xyz[0]=0; xyz[0] inline void makeSphere(const openvdb::Coord& dim, const openvdb::Vec3f& center, float radius, GridType &grid, float dx, SphereMode mode) { grid.setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/dx)); makeSphere(dim, center, radius, grid, mode); } // @todo makePlane //////////////////////////////////////// class CpuTimer { public: CpuTimer() {} void start(const std::string& msg = std::string("Task")) { std::cerr << msg << " ... "; mT0 = tbb::tick_count::now(); } void stop() const { tbb::tick_count t1 = tbb::tick_count::now(); std::ostringstream ostr; ostr << "completed in " << std::setprecision(3) << (t1 - mT0).seconds() << " sec\n"; std::cerr << ostr.str(); } private: tbb::tick_count mT0; };// CpuTimer } // namespace unittest_util #endif // OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestVolumeRayIntersector.cc0000644000000000000000000001702712252453157020421 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// @author Ken Museth #include #include #include #include #include #include #include #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); #define ASSERT_DOUBLES_APPROX_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1.e-6); class TestVolumeRayIntersector : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestVolumeRayIntersector); CPPUNIT_TEST(testAll); CPPUNIT_TEST_SUITE_END(); void testAll(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestVolumeRayIntersector); void TestVolumeRayIntersector::testAll() { using namespace openvdb; typedef math::Ray RayT; typedef RayT::Vec3Type Vec3T; {//one single leaf node FloatGrid grid(0.0f); grid.tree().setValue(Coord(0,0,0), 1.0f); grid.tree().setValue(Coord(7,7,7), 1.0f); const Vec3T dir( 1.0, 0.0, 0.0); const Vec3T eye(-1.0, 0.0, 0.0); const RayT ray(eye, dir);//ray in index space tools::VolumeRayIntersector inter(grid); CPPUNIT_ASSERT(inter.setIndexRay(ray)); double t0=0, t1=0; CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t1); CPPUNIT_ASSERT_EQUAL(0, inter.march(t0, t1)); } {//two adjacent leaf nodes FloatGrid grid(0.0f); grid.tree().setValue(Coord(0,0,0), 1.0f); grid.tree().setValue(Coord(8,0,0), 1.0f); grid.tree().setValue(Coord(15,7,7), 1.0f); const Vec3T dir( 1.0, 0.0, 0.0); const Vec3T eye(-1.0, 0.0, 0.0); const RayT ray(eye, dir);//ray in index space tools::VolumeRayIntersector inter(grid); CPPUNIT_ASSERT(inter.setIndexRay(ray)); double t0=0, t1=0; CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t1); CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, t1); CPPUNIT_ASSERT_EQUAL(0, inter.march(t0, t1)); } {//two adjacent leafs followed by a gab and leaf FloatGrid grid(0.0f); grid.tree().setValue(Coord(0*8,0,0), 1.0f); grid.tree().setValue(Coord(1*8,0,0), 1.0f); grid.tree().setValue(Coord(3*8,0,0), 1.0f); grid.tree().setValue(Coord(3*8+7,7,7), 1.0f); const Vec3T dir( 1.0, 0.0, 0.0); const Vec3T eye(-1.0, 0.0, 0.0); const RayT ray(eye, dir);//ray in index space tools::VolumeRayIntersector inter(grid); CPPUNIT_ASSERT(inter.setIndexRay(ray)); double t0=0, t1=0; CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t1); CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, t1); CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(25.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(33.0, t1); CPPUNIT_ASSERT_EQUAL(0, inter.march(t0, t1)); } {//two adjacent leafs followed by a gab, a leaf and an active tile FloatGrid grid(0.0f); grid.tree().setValue(Coord(0*8,0,0), 1.0f); grid.tree().setValue(Coord(1*8,0,0), 1.0f); grid.tree().setValue(Coord(3*8,0,0), 1.0f); grid.fill(CoordBBox(Coord(4*8,0,0), Coord(4*8+7,7,7)), 2.0f, true); const Vec3T dir( 1.0, 0.0, 0.0); const Vec3T eye(-1.0, 0.0, 0.0); const RayT ray(eye, dir);//ray in index space tools::VolumeRayIntersector inter(grid); CPPUNIT_ASSERT(inter.setIndexRay(ray)); double t0=0, t1=0; CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t1); CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, t1); CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(25.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(33.0, t1); CPPUNIT_ASSERT_EQUAL(1, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(33.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(41.0, t1); CPPUNIT_ASSERT_EQUAL(0, inter.march(t0, t1)); } {// Test submitted by "Jan" @ GitHub FloatGrid grid(0.0f); grid.tree().setValue(Coord(0*8,0,0), 1.0f); grid.tree().setValue(Coord(1*8,0,0), 1.0f); grid.tree().setValue(Coord(3*8,0,0), 1.0f); tools::VolumeRayIntersector inter(grid); const Vec3T dir(-1.0, 0.0, 0.0); const Vec3T eye(50.0, 0.0, 0.0); const RayT ray(eye, dir); CPPUNIT_ASSERT(inter.setIndexRay(ray)); double t0=0, t1=0; CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(18.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(26.0, t1); CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(34.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(42.0, t1); CPPUNIT_ASSERT_EQUAL(2, inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(42.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(50.0, t1); CPPUNIT_ASSERT_EQUAL(0, inter.march(t0, t1)); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestTree.cc0000644000000000000000000032151712252453157015155 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include // for remove() #include #include #include #include #include #include // for tools::setValueOnMin(), et al. #include #include #include // for io::RealToHalf #include // for Abs() #include #include "util.h" // for unittest_util::makeSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); typedef float ValueType; typedef openvdb::tree::LeafNode LeafNodeType; typedef openvdb::tree::InternalNode InternalNodeType1; typedef openvdb::tree::InternalNode InternalNodeType2; typedef openvdb::tree::RootNode RootNodeType; class TestTree: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTree); CPPUNIT_TEST(testBackground); CPPUNIT_TEST(testHalf); CPPUNIT_TEST(testValues); CPPUNIT_TEST(testSetValue); CPPUNIT_TEST(testSetValueOnly); CPPUNIT_TEST(testEvalMinMax); CPPUNIT_TEST(testResize); CPPUNIT_TEST(testHasSameTopology); CPPUNIT_TEST(testTopologyCopy); CPPUNIT_TEST(testIterators); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testNegativeIndexing); CPPUNIT_TEST(testDeepCopy); CPPUNIT_TEST(testMerge); CPPUNIT_TEST(testVoxelizeActiveTiles); CPPUNIT_TEST(testTopologyUnion); CPPUNIT_TEST(testTopologyIntersection); CPPUNIT_TEST(testTopologyDifference); CPPUNIT_TEST(testSignedFloodFill); CPPUNIT_TEST(testPruneInactive); CPPUNIT_TEST(testPruneLevelSet); CPPUNIT_TEST(testTouchLeaf); CPPUNIT_TEST(testProbeLeaf); CPPUNIT_TEST(testAddLeaf); CPPUNIT_TEST(testAddTile); CPPUNIT_TEST(testLeafManager); CPPUNIT_TEST(testProcessBBox); CPPUNIT_TEST(testStealNode); CPPUNIT_TEST_SUITE_END(); void testBackground(); void testHalf(); void testValues(); void testSetValue(); void testSetValueOnly(); void testEvalMinMax(); void testResize(); void testHasSameTopology(); void testTopologyCopy(); void testIterators(); void testIO(); void testNegativeIndexing(); void testDeepCopy(); void testMerge(); void testVoxelizeActiveTiles(); void testTopologyUnion(); void testTopologyIntersection(); void testTopologyDifference(); void testSignedFloodFill(); void testPruneLevelSet(); void testPruneInactive(); void testTouchLeaf(); void testProbeLeaf(); void testAddLeaf(); void testAddTile(); void testLeafManager(); void testProcessBBox(); void testStealNode(); private: template void testWriteHalf(); template void doTestMerge(openvdb::MergePolicy); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTree); void TestTree::testBackground() { const ValueType background = 5.0f; RootNodeType root_node(background); CPPUNIT_ASSERT(root_node.getLevel()==3); ASSERT_DOUBLES_EXACTLY_EQUAL(background, root_node.getValue(openvdb::Coord(5,10,20))); const ValueType newBackground = 10.0f; root_node.setBackground(newBackground); ASSERT_DOUBLES_EXACTLY_EQUAL(newBackground, root_node.getValue(openvdb::Coord(5,10,20))); } void TestTree::testHalf() { testWriteHalf(); testWriteHalf(); testWriteHalf(); testWriteHalf(); testWriteHalf(); testWriteHalf(); // Verify that non-floating-point grids are saved correctly. testWriteHalf(); testWriteHalf(); testWriteHalf(); } template void TestTree::testWriteHalf() { typedef openvdb::Grid GridType; typedef typename TreeType::ValueType ValueT; ValueT background = openvdb::zeroVal(); background += 5; GridType grid(background); unittest_util::makeSphere(openvdb::Coord(64, 64, 64), openvdb::Vec3f(35, 30, 40), /*radius=*/10, grid, /*dx=*/1.0f, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid.tree().empty()); // Write grid blocks in both float and half formats. std::ostringstream outFull(std::ios_base::binary); grid.setSaveFloatAsHalf(false); grid.writeBuffers(outFull); outFull.flush(); const size_t fullBytes = outFull.str().size(); CPPUNIT_ASSERT_MESSAGE("wrote empty full float buffers", fullBytes > 0); std::ostringstream outHalf(std::ios_base::binary); grid.setSaveFloatAsHalf(true); grid.writeBuffers(outHalf); outHalf.flush(); const size_t halfBytes = outHalf.str().size(); CPPUNIT_ASSERT_MESSAGE("wrote empty half float buffers", halfBytes > 0); if (openvdb::io::RealToHalf::isReal) { // Verify that the half float file is "significantly smaller" than the full float file. std::ostringstream ostr; ostr << "half float buffers not significantly smaller than full float (" << halfBytes << " vs. " << fullBytes << " bytes)"; CPPUNIT_ASSERT_MESSAGE(ostr.str(), halfBytes < size_t(0.75 * fullBytes)); } else { // For non-real data types, "half float" and "full float" file sizes should be the same. CPPUNIT_ASSERT_MESSAGE("full float and half float file sizes differ for data of type " + std::string(openvdb::typeNameAsString()), halfBytes == fullBytes); } // Read back the half float data (converting back to full float in the process), // then write it out again in half float format. Verify that the resulting file // is identical to the original half float file. { openvdb::Grid gridCopy(grid); gridCopy.setSaveFloatAsHalf(true); std::istringstream is(outHalf.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(is); gridCopy.readBuffers(is); std::ostringstream outDiff(std::ios_base::binary); gridCopy.writeBuffers(outDiff); outDiff.flush(); CPPUNIT_ASSERT_MESSAGE("half-from-full and half-from-half buffers differ", outHalf.str() == outDiff.str()); } } void TestTree::testValues() { ValueType background=5.0f; { const openvdb::Coord c0(5,10,20), c1(50000,20000,30000); RootNodeType root_node(background); const float v0=0.234f, v1=4.5678f; CPPUNIT_ASSERT(root_node.empty()); ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(c0), background); ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(c1), background); root_node.setValueOn(c0, v0); root_node.setValueOn(c1, v1); ASSERT_DOUBLES_EXACTLY_EQUAL(v0,root_node.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(v1,root_node.getValue(c1)); int count=0; for (int i =0; i<256; ++i) { for (int j=0; j<256; ++j) { for (int k=0; k<256; ++k) { if (root_node.getValue(openvdb::Coord(i,j,k))<1.0f) ++count; } } } CPPUNIT_ASSERT(count == 1); } { const openvdb::Coord min(-30,-25,-60), max(60,80,100); const openvdb::Coord c0(-5,-10,-20), c1(50,20,90), c2(59,67,89); const float v0=0.234f, v1=4.5678f, v2=-5.673f; RootNodeType root_node(background); CPPUNIT_ASSERT(root_node.empty()); ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background,root_node.getValue(c2)); root_node.setValueOn(c0, v0); root_node.setValueOn(c1, v1); root_node.setValueOn(c2, v2); ASSERT_DOUBLES_EXACTLY_EQUAL(v0,root_node.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(v1,root_node.getValue(c1)); ASSERT_DOUBLES_EXACTLY_EQUAL(v2,root_node.getValue(c2)); int count=0; for (int i =min[0]; i void evalMinMaxTest() { typedef typename TreeT::ValueType ValueT; struct Local { static bool isEqual(const ValueT& a, const ValueT& b) { using namespace openvdb; // for operator>() return !(math::Abs(a - b) > zeroVal()); } }; const ValueT zero = openvdb::zeroVal(), minusTwo = zero + (-2), plusTwo = zero + 2, five = zero + 5; TreeT tree(/*background=*/five); // No set voxels (defaults to min = max = zero) ValueT minVal = five, maxVal = five; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT(Local::isEqual(minVal, zero)); CPPUNIT_ASSERT(Local::isEqual(maxVal, zero)); // Only one set voxel tree.setValue(openvdb::Coord(0, 0, 0), minusTwo); minVal = maxVal = five; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT(Local::isEqual(minVal, minusTwo)); CPPUNIT_ASSERT(Local::isEqual(maxVal, minusTwo)); // Multiple set voxels, single value tree.setValue(openvdb::Coord(10, 10, 10), minusTwo); minVal = maxVal = five; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT(Local::isEqual(minVal, minusTwo)); CPPUNIT_ASSERT(Local::isEqual(maxVal, minusTwo)); // Multiple set voxels, multiple values tree.setValue(openvdb::Coord(10, 10, 10), plusTwo); tree.setValue(openvdb::Coord(-10, -10, -10), zero); minVal = maxVal = five; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT(Local::isEqual(minVal, minusTwo)); CPPUNIT_ASSERT(Local::isEqual(maxVal, plusTwo)); } /// Specialization for boolean trees template<> void evalMinMaxTest() { openvdb::BoolTree tree(/*background=*/false); // No set voxels (defaults to min = max = zero) bool minVal = true, maxVal = false; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(false, minVal); CPPUNIT_ASSERT_EQUAL(false, maxVal); // Only one set voxel tree.setValue(openvdb::Coord(0, 0, 0), true); minVal = maxVal = false; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(true, minVal); CPPUNIT_ASSERT_EQUAL(true, maxVal); // Multiple set voxels, single value tree.setValue(openvdb::Coord(-10, -10, -10), true); minVal = maxVal = false; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(true, minVal); CPPUNIT_ASSERT_EQUAL(true, maxVal); // Multiple set voxels, multiple values tree.setValue(openvdb::Coord(10, 10, 10), false); minVal = true; maxVal = false; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(false, minVal); CPPUNIT_ASSERT_EQUAL(true, maxVal); } /// Specialization for string trees template<> void evalMinMaxTest() { const std::string echidna("echidna"), loris("loris"), pangolin("pangolin"); openvdb::StringTree tree(/*background=*/loris); // No set voxels (defaults to min = max = zero) std::string minVal, maxVal; tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(std::string(), minVal); CPPUNIT_ASSERT_EQUAL(std::string(), maxVal); // Only one set voxel tree.setValue(openvdb::Coord(0, 0, 0), pangolin); minVal.clear(); maxVal.clear(); tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(pangolin, minVal); CPPUNIT_ASSERT_EQUAL(pangolin, maxVal); // Multiple set voxels, single value tree.setValue(openvdb::Coord(-10, -10, -10), pangolin); minVal.clear(); maxVal.clear(); tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(pangolin, minVal); CPPUNIT_ASSERT_EQUAL(pangolin, maxVal); // Multiple set voxels, multiple values tree.setValue(openvdb::Coord(10, 10, 10), echidna); minVal.clear(); maxVal.clear(); tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(echidna, minVal); CPPUNIT_ASSERT_EQUAL(pangolin, maxVal); } } // unnamed namespace void TestTree::testEvalMinMax() { evalMinMaxTest(); evalMinMaxTest(); evalMinMaxTest(); evalMinMaxTest(); evalMinMaxTest(); evalMinMaxTest(); } void TestTree::testResize() { ValueType background=5.0f; //use this when resize is implemented RootNodeType root_node(background); CPPUNIT_ASSERT(root_node.getLevel()==3); ASSERT_DOUBLES_EXACTLY_EQUAL(background, root_node.getValue(openvdb::Coord(5,10,20))); //fprintf(stdout,"Root grid dim=(%i,%i,%i)\n", // root_node.getGridDim(0), root_node.getGridDim(1), root_node.getGridDim(2)); root_node.setValueOn(openvdb::Coord(5,10,20),0.234f); ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(openvdb::Coord(5,10,20)) , 0.234f); root_node.setValueOn(openvdb::Coord(500,200,300),4.5678f); ASSERT_DOUBLES_EXACTLY_EQUAL(root_node.getValue(openvdb::Coord(500,200,300)) , 4.5678f); { ValueType sum=0.0f; for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); root_iter.test(); ++root_iter) { for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); internal_iter2.test(); ++internal_iter2) { for (InternalNodeType1::ChildOnIter internal_iter1 = internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) { for (LeafNodeType::ValueOnIter block_iter = internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) { sum += *block_iter; } } } } ASSERT_DOUBLES_EXACTLY_EQUAL(sum, (0.234f + 4.5678f)); } CPPUNIT_ASSERT(root_node.getLevel()==3); ASSERT_DOUBLES_EXACTLY_EQUAL(background, root_node.getValue(openvdb::Coord(5,11,20))); { ValueType sum=0.0f; for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); root_iter.test(); ++root_iter) { for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); internal_iter2.test(); ++internal_iter2) { for (InternalNodeType1::ChildOnIter internal_iter1 = internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) { for (LeafNodeType::ValueOnIter block_iter = internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) { sum += *block_iter; } } } } ASSERT_DOUBLES_EXACTLY_EQUAL(sum, (0.234f + 4.5678f)); } } void TestTree::testHasSameTopology() { // Test using trees of the same type. { const float background1=5.0f; openvdb::FloatTree tree1(background1); const float background2=6.0f; openvdb::FloatTree tree2(background2); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); tree1.setValue(openvdb::Coord(-10,40,845),3.456f); CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); tree2.setValue(openvdb::Coord(-10,40,845),-3.456f); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); tree1.setValue(openvdb::Coord(1,-500,-8), 1.0f); CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); tree2.setValue(openvdb::Coord(1,-500,-8),1.0f); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); } // Test using trees of different types. { const float background1=5.0f; openvdb::FloatTree tree1(background1); const openvdb::Vec3f background2(1.0f,3.4f,6.0f); openvdb::Vec3fTree tree2(background2); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); tree1.setValue(openvdb::Coord(-10,40,845),3.456f); CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); tree2.setValue(openvdb::Coord(-10,40,845),openvdb::Vec3f(1.0f,2.0f,-3.0f)); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); tree1.setValue(openvdb::Coord(1,-500,-8), 1.0f); CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); tree2.setValue(openvdb::Coord(1,-500,-8),openvdb::Vec3f(1.0f,2.0f,-3.0f)); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); } } void TestTree::testTopologyCopy() { // Test using trees of the same type. { const float background1=5.0f; openvdb::FloatTree tree1(background1); tree1.setValue(openvdb::Coord(-10,40,845),3.456f); tree1.setValue(openvdb::Coord(1,-50,-8), 1.0f); const float background2=6.0f, setValue2=3.0f; openvdb::FloatTree tree2(tree1,background2,setValue2,openvdb::TopologyCopy()); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background2, tree2.getValue(openvdb::Coord(1,2,3))); ASSERT_DOUBLES_EXACTLY_EQUAL(setValue2, tree2.getValue(openvdb::Coord(-10,40,845))); ASSERT_DOUBLES_EXACTLY_EQUAL(setValue2, tree2.getValue(openvdb::Coord(1,-50,-8))); tree1.setValue(openvdb::Coord(1,-500,-8), 1.0f); CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); tree2.setValue(openvdb::Coord(1,-500,-8),1.0f); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); } // Test using trees of different types. { const openvdb::Vec3f background1(1.0f,3.4f,6.0f); openvdb::Vec3fTree tree1(background1); tree1.setValue(openvdb::Coord(-10,40,845),openvdb::Vec3f(3.456f,-2.3f,5.6f)); tree1.setValue(openvdb::Coord(1,-50,-8), openvdb::Vec3f(1.0f,3.0f,4.5f)); const float background2=6.0f, setValue2=3.0f; openvdb::FloatTree tree2(tree1,background2,setValue2,openvdb::TopologyCopy()); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); ASSERT_DOUBLES_EXACTLY_EQUAL(background2, tree2.getValue(openvdb::Coord(1,2,3))); ASSERT_DOUBLES_EXACTLY_EQUAL(setValue2, tree2.getValue(openvdb::Coord(-10,40,845))); ASSERT_DOUBLES_EXACTLY_EQUAL(setValue2, tree2.getValue(openvdb::Coord(1,-50,-8))); tree1.setValue(openvdb::Coord(1,-500,-8), openvdb::Vec3f(1.0f,0.0f,-3.0f)); CPPUNIT_ASSERT(!tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(!tree2.hasSameTopology(tree1)); tree2.setValue(openvdb::Coord(1,-500,-8), 1.0f); CPPUNIT_ASSERT(tree1.hasSameTopology(tree2)); CPPUNIT_ASSERT(tree2.hasSameTopology(tree1)); } } void TestTree::testIterators() { ValueType background=5.0f; RootNodeType root_node(background); root_node.setValueOn(openvdb::Coord(5,10,20),0.234f); root_node.setValueOn(openvdb::Coord(50000,20000,30000),4.5678f); { ValueType sum=0.0f; for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); root_iter.test(); ++root_iter) { for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); internal_iter2.test(); ++internal_iter2) { for (InternalNodeType1::ChildOnIter internal_iter1 = internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) { for (LeafNodeType::ValueOnIter block_iter = internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) { sum += *block_iter; } } } } ASSERT_DOUBLES_EXACTLY_EQUAL((0.234f + 4.5678f), sum); } { // As above, but using dense iterators. ValueType sum = 0.0f, val = 0.0f; for (RootNodeType::ChildAllIter rootIter = root_node.beginChildAll(); rootIter.test(); ++rootIter) { if (!rootIter.isChildNode()) continue; for (InternalNodeType2::ChildAllIter internalIter2 = rootIter.probeChild(val)->beginChildAll(); internalIter2; ++internalIter2) { if (!internalIter2.isChildNode()) continue; for (InternalNodeType1::ChildAllIter internalIter1 = internalIter2.probeChild(val)->beginChildAll(); internalIter1; ++internalIter1) { if (!internalIter1.isChildNode()) continue; for (LeafNodeType::ValueOnIter leafIter = internalIter1.probeChild(val)->beginValueOn(); leafIter; ++leafIter) { sum += *leafIter; } } } } ASSERT_DOUBLES_EXACTLY_EQUAL((0.234f + 4.5678f), sum); } { ValueType v_sum=0.0f; openvdb::Coord xyz0, xyz1, xyz2, xyz3, xyzSum(0, 0, 0); for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); root_iter.test(); ++root_iter) { root_iter.getCoord(xyz3); for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); internal_iter2.test(); ++internal_iter2) { internal_iter2.getCoord(xyz2); xyz2 = xyz2 - internal_iter2.parent().origin(); for (InternalNodeType1::ChildOnIter internal_iter1 = internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) { internal_iter1.getCoord(xyz1); xyz1 = xyz1 - internal_iter1.parent().origin(); for (LeafNodeType::ValueOnIter block_iter = internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) { block_iter.getCoord(xyz0); xyz0 = xyz0 - block_iter.parent().origin(); v_sum += *block_iter; xyzSum = xyzSum + xyz0 + xyz1 + xyz2 + xyz3; } } } } ASSERT_DOUBLES_EXACTLY_EQUAL((0.234f + 4.5678f), v_sum); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(5 + 50000, 10 + 20000, 20 + 30000), xyzSum); } } void TestTree::testIO() { const char* filename = "/tmp/test.dbg"; boost::shared_ptr scopedFile(filename, ::remove); { ValueType background=5.0f; RootNodeType root_node(background); root_node.setValueOn(openvdb::Coord(5,10,20),0.234f); root_node.setValueOn(openvdb::Coord(50000,20000,30000),4.5678f); std::ofstream os(filename, std::ios_base::binary); root_node.writeTopology(os); root_node.writeBuffers(os); CPPUNIT_ASSERT(!os.fail()); } { ValueType background=2.0f; RootNodeType root_node(background); ASSERT_DOUBLES_EXACTLY_EQUAL(background, root_node.getValue(openvdb::Coord(5,10,20))); { std::ifstream is(filename, std::ios_base::binary); // Since the test file doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(is); root_node.readTopology(is); root_node.readBuffers(is); CPPUNIT_ASSERT(!is.fail()); } ASSERT_DOUBLES_EXACTLY_EQUAL(0.234f, root_node.getValue(openvdb::Coord(5,10,20))); ASSERT_DOUBLES_EXACTLY_EQUAL(5.0f, root_node.getValue(openvdb::Coord(5,11,20))); ValueType sum=0.0f; for (RootNodeType::ChildOnIter root_iter = root_node.beginChildOn(); root_iter.test(); ++root_iter) { for (InternalNodeType2::ChildOnIter internal_iter2 = root_iter->beginChildOn(); internal_iter2.test(); ++internal_iter2) { for (InternalNodeType1::ChildOnIter internal_iter1 = internal_iter2->beginChildOn(); internal_iter1.test(); ++internal_iter1) { for (LeafNodeType::ValueOnIter block_iter = internal_iter1->beginValueOn(); block_iter.test(); ++block_iter) { sum += *block_iter; } } } } ASSERT_DOUBLES_EXACTLY_EQUAL(sum, (0.234f + 4.5678f)); } } void TestTree::testNegativeIndexing() { ValueType background=5.0f; openvdb::FloatTree tree(background); CPPUNIT_ASSERT(tree.empty()); ASSERT_DOUBLES_EXACTLY_EQUAL(tree.getValue(openvdb::Coord(5,-10,20)), background); ASSERT_DOUBLES_EXACTLY_EQUAL(tree.getValue(openvdb::Coord(-5000,2000,3000)), background); tree.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree.setValue(openvdb::Coord(-5, 10, 20),0.1f); tree.setValue(openvdb::Coord( 5,-10, 20),0.2f); tree.setValue(openvdb::Coord( 5, 10,-20),0.3f); tree.setValue(openvdb::Coord(-5,-10, 20),0.4f); tree.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); ASSERT_DOUBLES_EXACTLY_EQUAL(0.0f, tree.getValue(openvdb::Coord( 5, 10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.1f, tree.getValue(openvdb::Coord(-5, 10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.2f, tree.getValue(openvdb::Coord( 5,-10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.3f, tree.getValue(openvdb::Coord( 5, 10,-20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.4f, tree.getValue(openvdb::Coord(-5,-10, 20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.5f, tree.getValue(openvdb::Coord(-5, 10,-20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.6f, tree.getValue(openvdb::Coord( 5,-10,-20))); ASSERT_DOUBLES_EXACTLY_EQUAL(0.7f, tree.getValue(openvdb::Coord(-5,-10,-20))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-5000, 2000,-3000))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord( 5000,-2000,-3000))); ASSERT_DOUBLES_EXACTLY_EQUAL(4.5678f, tree.getValue(openvdb::Coord(-5000,-2000, 3000))); int count=0; for (int i =-25; i<25; ++i) { for (int j=-25; j<25; ++j) { for (int k=-25; k<25; ++k) { if (tree.getValue(openvdb::Coord(i,j,k))<1.0f) { //fprintf(stderr,"(%i,%i,%i)=%f\n",i,j,k,tree.getValue(openvdb::Coord(i,j,k))); ++count; } } } } CPPUNIT_ASSERT(count == 8); int count2 = 0; openvdb::Coord xyz; for (openvdb::FloatTree::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { ++count2; xyz = iter.getCoord(); //std::cerr << xyz << " = " << *iter << "\n"; } CPPUNIT_ASSERT(count2 == 11); CPPUNIT_ASSERT(tree.activeVoxelCount() == 11); { count2 = 0; for (openvdb::FloatTree::ValueOnCIter iter = tree.cbeginValueOn(); iter; ++iter) { ++count2; xyz = iter.getCoord(); //std::cerr << xyz << " = " << *iter << "\n"; } CPPUNIT_ASSERT(count2 == 11); CPPUNIT_ASSERT(tree.activeVoxelCount() == 11); } } void TestTree::testDeepCopy() { // set up a tree const float fillValue1=5.0f; openvdb::FloatTree tree1(fillValue1); tree1.setValue(openvdb::Coord(-10,40,845), 3.456f); tree1.setValue(openvdb::Coord(1,-50,-8), 1.0f); // make a deep copy of the tree openvdb::TreeBase::Ptr newTree = tree1.copy(); // cast down to the concrete type to query values openvdb::FloatTree *pTree2 = dynamic_cast(newTree.get()); // compare topology CPPUNIT_ASSERT(tree1.hasSameTopology(*pTree2)); CPPUNIT_ASSERT(pTree2->hasSameTopology(tree1)); // trees should be equal ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, pTree2->getValue(openvdb::Coord(1,2,3))); ASSERT_DOUBLES_EXACTLY_EQUAL(3.456f, pTree2->getValue(openvdb::Coord(-10,40,845))); ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, pTree2->getValue(openvdb::Coord(1,-50,-8))); // change 1 value in tree2 openvdb::Coord changeCoord(1, -500, -8); pTree2->setValue(changeCoord, 1.0f); // topology should no longer match CPPUNIT_ASSERT(!tree1.hasSameTopology(*pTree2)); CPPUNIT_ASSERT(!pTree2->hasSameTopology(tree1)); // query changed value and make sure it's different between trees ASSERT_DOUBLES_EXACTLY_EQUAL(fillValue1, tree1.getValue(changeCoord)); ASSERT_DOUBLES_EXACTLY_EQUAL(1.0f, pTree2->getValue(changeCoord)); } void TestTree::testMerge() { ValueType background=5.0f; openvdb::FloatTree tree0(background), tree1(background), tree2(background); CPPUNIT_ASSERT(tree2.empty()); tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree0.setValue(openvdb::Coord(-5, 10, 20),0.1f); tree0.setValue(openvdb::Coord( 5,-10, 20),0.2f); tree0.setValue(openvdb::Coord( 5, 10,-20),0.3f); tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree1.setValue(openvdb::Coord(-5, 10, 20),0.1f); tree1.setValue(openvdb::Coord( 5,-10, 20),0.2f); tree1.setValue(openvdb::Coord( 5, 10,-20),0.3f); tree0.setValue(openvdb::Coord(-5,-10, 20),0.4f); tree0.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree0.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree0.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree0.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree0.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree0.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); tree2.setValue(openvdb::Coord(-5,-10, 20),0.4f); tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); CPPUNIT_ASSERT(tree0.leafCount()!=tree1.leafCount()); CPPUNIT_ASSERT(tree0.leafCount()!=tree2.leafCount()); CPPUNIT_ASSERT(!tree2.empty()); tree1.merge(tree2, openvdb::MERGE_ACTIVE_STATES); CPPUNIT_ASSERT(tree2.empty()); CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); for (openvdb::FloatTree::ValueOnCIter iter0 = tree0.cbeginValueOn(); iter0; ++iter0) { ASSERT_DOUBLES_EXACTLY_EQUAL(*iter0,tree1.getValue(iter0.getCoord())); } // Test active tile support. { using namespace openvdb; FloatTree treeA(/*background*/0.0), treeB(/*background*/0.0); treeA.fill(CoordBBox(Coord(16,16,16), Coord(31,31,31)), /*value*/1.0); treeB.fill(CoordBBox(Coord(0,0,0), Coord(15,15,15)), /*value*/1.0); CPPUNIT_ASSERT_EQUAL(4096, int(treeA.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(4096, int(treeB.activeVoxelCount())); treeA.merge(treeB, MERGE_ACTIVE_STATES); CPPUNIT_ASSERT_EQUAL(8192, int(treeA.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(0, int(treeB.activeVoxelCount())); } doTestMerge(openvdb::MERGE_NODES); doTestMerge(openvdb::MERGE_ACTIVE_STATES); doTestMerge(openvdb::MERGE_ACTIVE_STATES_AND_NODES); doTestMerge(openvdb::MERGE_NODES); doTestMerge(openvdb::MERGE_ACTIVE_STATES); doTestMerge(openvdb::MERGE_ACTIVE_STATES_AND_NODES); } template void TestTree::doTestMerge(openvdb::MergePolicy policy) { using namespace openvdb; TreeType treeA, treeB; typedef typename TreeType::RootNodeType RootT; typedef typename TreeType::LeafNodeType LeafT; const typename TreeType::ValueType val(1); const int depth = static_cast(treeA.treeDepth()), leafDim = static_cast(LeafT::dim()), leafSize = static_cast(LeafT::size()); // Coords that are in a different top-level branch than (0, 0, 0) const Coord pos(static_cast(RootT::getChildDim())); treeA.setValueOff(pos, val); treeA.setValueOff(-pos, val); treeB.setValueOff(Coord(0), val); treeB.fill(CoordBBox(pos, pos.offsetBy(leafDim - 1)), val, /*active=*/true); treeB.setValueOn(-pos, val); // treeA treeB . // . // R R . // / \ /|\ . // I I I I I . // / \ / | \ . // I I I I I . // / \ / | on x SIZE . // L L L L . // off off on off . CPPUNIT_ASSERT_EQUAL(0, int(treeA.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(leafSize + 1, int(treeB.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(2, int(treeA.leafCount())); CPPUNIT_ASSERT_EQUAL(2, int(treeB.leafCount())); CPPUNIT_ASSERT_EQUAL(2*(depth-2)+1, int(treeA.nonLeafCount())); // 2 branches (II+II+R) CPPUNIT_ASSERT_EQUAL(3*(depth-2)+1, int(treeB.nonLeafCount())); // 3 branches (II+II+II+R) treeA.merge(treeB, policy); // MERGE_NODES MERGE_ACTIVE_STATES MERGE_ACTIVE_STATES_AND_NODES . // . // R R R . // /|\ /|\ /|\ . // I I I I I I I I I . // / | \ / | \ / | \ . // I I I I I I I I I . // / | \ / | on x SIZE / | \ . // L L L L L L L L . // off off off on off on off on x SIZE . switch (policy) { case MERGE_NODES: CPPUNIT_ASSERT_EQUAL(0, int(treeA.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(2 + 1, int(treeA.leafCount())); // 1 leaf node stolen from B CPPUNIT_ASSERT_EQUAL(3*(depth-2)+1, int(treeA.nonLeafCount())); // 3 branches (II+II+II+R) break; case MERGE_ACTIVE_STATES: CPPUNIT_ASSERT_EQUAL(2, int(treeA.leafCount())); // 1 leaf stolen, 1 replaced with tile CPPUNIT_ASSERT_EQUAL(3*(depth-2)+1, int(treeA.nonLeafCount())); // 3 branches (II+II+II+R) CPPUNIT_ASSERT_EQUAL(leafSize + 1, int(treeA.activeVoxelCount())); break; case MERGE_ACTIVE_STATES_AND_NODES: CPPUNIT_ASSERT_EQUAL(2 + 1, int(treeA.leafCount())); // 1 leaf node stolen from B CPPUNIT_ASSERT_EQUAL(3*(depth-2)+1, int(treeA.nonLeafCount())); // 3 branches (II+II+II+R) CPPUNIT_ASSERT_EQUAL(leafSize + 1, int(treeA.activeVoxelCount())); break; } CPPUNIT_ASSERT(treeB.empty()); } void TestTree::testVoxelizeActiveTiles() { using openvdb::CoordBBox; using openvdb::Coord; // Use a small custom tree so we don't run out of memory when // tiles are converted to dense leafs :) typedef openvdb::tree::Tree4::Type MyTree; float background=5.0f; const Coord xyz[] = {Coord(-1,-2,-3),Coord( 1, 2, 3)}; //check two leaf nodes and two tiles at each level 1, 2 and 3 const int tile_size[4]={0, 1<<2, 1<<(2*2), 1<<(3*2)}; for (int level=0; level<=3; ++level) { MyTree tree(background); CPPUNIT_ASSERT_EQUAL(-1,tree.getValueDepth(xyz[0])); CPPUNIT_ASSERT_EQUAL(-1,tree.getValueDepth(xyz[1])); if (level==0) { tree.setValue(xyz[0], 1.0f); tree.setValue(xyz[1], 1.0f); } else { const int n = tile_size[level]; tree.fill(CoordBBox::createCube(Coord(-n,-n,-n), n), 1.0f, true); tree.fill(CoordBBox::createCube(Coord( 0, 0, 0), n), 1.0f, true); } CPPUNIT_ASSERT_EQUAL(3-level,tree.getValueDepth(xyz[0])); CPPUNIT_ASSERT_EQUAL(3-level,tree.getValueDepth(xyz[1])); tree.voxelizeActiveTiles(); CPPUNIT_ASSERT_EQUAL(3 ,tree.getValueDepth(xyz[0])); CPPUNIT_ASSERT_EQUAL(3 ,tree.getValueDepth(xyz[1])); } } void TestTree::testTopologyUnion() { {//super simple test with only two active values const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); openvdb::FloatTree tree2(tree1); tree1.topologyUnion(tree0); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter = tree2.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree2.getValue(iter.getCoord())); } } {// test using setValue ValueType background=5.0f; openvdb::FloatTree tree0(background), tree1(background), tree2(background); CPPUNIT_ASSERT(tree2.empty()); // tree0 = tree1.topologyUnion(tree2) tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree0.setValue(openvdb::Coord(-5, 10, 20),0.1f); tree0.setValue(openvdb::Coord( 5,-10, 20),0.2f); tree0.setValue(openvdb::Coord( 5, 10,-20),0.3f); tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree1.setValue(openvdb::Coord(-5, 10, 20),0.1f); tree1.setValue(openvdb::Coord( 5,-10, 20),0.2f); tree1.setValue(openvdb::Coord( 5, 10,-20),0.3f); tree0.setValue(openvdb::Coord(-5,-10, 20),background); tree0.setValue(openvdb::Coord(-5, 10,-20),background); tree0.setValue(openvdb::Coord( 5,-10,-20),background); tree0.setValue(openvdb::Coord(-5,-10,-20),background); tree0.setValue(openvdb::Coord(-5000, 2000,-3000),background); tree0.setValue(openvdb::Coord( 5000,-2000,-3000),background); tree0.setValue(openvdb::Coord(-5000,-2000, 3000),background); tree2.setValue(openvdb::Coord(-5,-10, 20),0.4f); tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); // tree3 has the same topology as tree2 but a different value type const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); openvdb::Vec3fTree tree3(background2); for (openvdb::FloatTree::ValueOnCIter iter2 = tree2.cbeginValueOn(); iter2; ++iter2) { tree3.setValue(iter2.getCoord(), vec_val); } CPPUNIT_ASSERT(tree0.leafCount()!=tree1.leafCount()); CPPUNIT_ASSERT(tree0.leafCount()!=tree2.leafCount()); CPPUNIT_ASSERT(tree0.leafCount()!=tree3.leafCount()); CPPUNIT_ASSERT(!tree2.empty()); CPPUNIT_ASSERT(!tree3.empty()); openvdb::FloatTree tree1_copy(tree1); //tree1.topologyUnion(tree2);//should make tree1 = tree0 tree1.topologyUnion(tree3);//should make tree1 = tree0 CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); CPPUNIT_ASSERT(tree0.hasSameTopology(tree1)); for (openvdb::FloatTree::ValueOnCIter iter2 = tree2.cbeginValueOn(); iter2; ++iter2) { CPPUNIT_ASSERT(tree1.isValueOn(iter2.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter1 = tree1.cbeginValueOn(); iter1; ++iter1) { CPPUNIT_ASSERT(tree0.isValueOn(iter1.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter0 = tree0.cbeginValueOn(); iter0; ++iter0) { CPPUNIT_ASSERT(tree1.isValueOn(iter0.getCoord())); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter0,tree1.getValue(iter0.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter = tree1_copy.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(iter.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree3.isValueOn(p) || tree1_copy.isValueOn(p)); } } { ValueType background=5.0f; openvdb::FloatTree tree0(background), tree1(background), tree2(background); CPPUNIT_ASSERT(tree2.empty()); // tree0 = tree1.topologyUnion(tree2) tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree0.setValue(openvdb::Coord(-5, 10, 20),0.1f); tree0.setValue(openvdb::Coord( 5,-10, 20),0.2f); tree0.setValue(openvdb::Coord( 5, 10,-20),0.3f); tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree1.setValue(openvdb::Coord(-5, 10, 20),0.1f); tree1.setValue(openvdb::Coord( 5,-10, 20),0.2f); tree1.setValue(openvdb::Coord( 5, 10,-20),0.3f); tree0.setValue(openvdb::Coord(-5,-10, 20),background); tree0.setValue(openvdb::Coord(-5, 10,-20),background); tree0.setValue(openvdb::Coord( 5,-10,-20),background); tree0.setValue(openvdb::Coord(-5,-10,-20),background); tree0.setValue(openvdb::Coord(-5000, 2000,-3000),background); tree0.setValue(openvdb::Coord( 5000,-2000,-3000),background); tree0.setValue(openvdb::Coord(-5000,-2000, 3000),background); tree2.setValue(openvdb::Coord(-5,-10, 20),0.4f); tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); // tree3 has the same topology as tree2 but a different value type const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); openvdb::Vec3fTree tree3(background2); for (openvdb::FloatTree::ValueOnCIter iter2 = tree2.cbeginValueOn(); iter2; ++iter2) { tree3.setValue(iter2.getCoord(), vec_val); } openvdb::FloatTree tree4(tree1);//tree4 = tree1 openvdb::FloatTree tree5(tree1);//tree5 = tree1 tree1.topologyUnion(tree3);//should make tree1 = tree0 CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); for (openvdb::Vec3fTree::ValueOnCIter iter3 = tree3.cbeginValueOn(); iter3; ++iter3) { tree4.setValueOn(iter3.getCoord()); const openvdb::Coord p = iter3.getCoord(); ASSERT_DOUBLES_EXACTLY_EQUAL(tree1.getValue(p),tree5.getValue(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(tree4.getValue(p),tree5.getValue(p)); } CPPUNIT_ASSERT(tree4.hasSameTopology(tree0)); for (openvdb::FloatTree::ValueOnCIter iter4 = tree4.cbeginValueOn(); iter4; ++iter4) { const openvdb::Coord p = iter4.getCoord(); ASSERT_DOUBLES_EXACTLY_EQUAL(tree0.getValue(p),tree5.getValue(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(tree1.getValue(p),tree5.getValue(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(tree4.getValue(p),tree5.getValue(p)); } for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree3.isValueOn(p) || tree4.isValueOn(p)); } } {// test overlapping spheres const float background=5.0f, R0=10.0f, R1=5.6f; const openvdb::Vec3f C0(35.0f, 30.0f, 40.0f), C1(22.3f, 30.5f, 31.0f); const openvdb::Coord dim(32, 32, 32); openvdb::FloatGrid grid0(background); openvdb::FloatGrid grid1(background); unittest_util::makeSphere(dim, C0, R0, grid0, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); unittest_util::makeSphere(dim, C1, R1, grid1, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::FloatTree& tree0 = grid0.tree(); openvdb::FloatTree& tree1 = grid1.tree(); openvdb::FloatTree tree0_copy(tree0); tree0.topologyUnion(tree1); const openvdb::Index64 n0 = tree0_copy.activeVoxelCount(); const openvdb::Index64 n = tree0.activeVoxelCount(); const openvdb::Index64 n1 = tree1.activeVoxelCount(); //fprintf(stderr,"Union of spheres: n=%i, n0=%i n1=%i n0+n1=%i\n",n,n0,n1, n0+n1); CPPUNIT_ASSERT( n > n0 ); CPPUNIT_ASSERT( n > n1 ); CPPUNIT_ASSERT( n < n0 + n1 ); for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree0.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(tree0.getValue(p), tree0_copy.getValue(p)); } for (openvdb::FloatTree::ValueOnCIter iter = tree0_copy.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree0.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(tree0.getValue(p), *iter); } } }// testTopologyUnion void TestTree::testTopologyIntersection() { {//no overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.activeVoxelCount()); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL(tree1.activeVoxelCount(), openvdb::Index64(0)); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT(tree1.empty()); } {//two overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); tree1.setValue(openvdb::Coord( 500, 300, 200), 1.0f); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT(!tree1.empty()); } {//4 overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 2.0f); tree0.setValue(openvdb::Coord( 8, 11, 11), 3.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT(!tree1.empty()); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(2), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); } {//passive tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree0.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, false); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree1.setValue(openvdb::Coord( dim, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(0), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(0), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(tree1.empty()); } {//active tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree1.leafCount() ); tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree0.setValue(openvdb::Coord( dim, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); tree1.topologyIntersection(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(2), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT(!tree1.empty()); } {// use tree with different voxel type ValueType background=5.0f; openvdb::FloatTree tree0(background), tree1(background), tree2(background); CPPUNIT_ASSERT(tree2.empty()); // tree0 = tree1.topologyIntersection(tree2) tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree0.setValue(openvdb::Coord(-5, 10,-20),0.1f); tree0.setValue(openvdb::Coord( 5,-10,-20),0.2f); tree0.setValue(openvdb::Coord(-5,-10,-20),0.3f); tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree1.setValue(openvdb::Coord(-5, 10,-20),0.1f); tree1.setValue(openvdb::Coord( 5,-10,-20),0.2f); tree1.setValue(openvdb::Coord(-5,-10,-20),0.3f); tree2.setValue(openvdb::Coord( 5, 10, 20),0.4f); tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); openvdb::FloatTree tree1_copy(tree1); // tree3 has the same topology as tree2 but a different value type const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); openvdb::Vec3fTree tree3(background2); for (openvdb::FloatTree::ValueOnCIter iter = tree2.cbeginValueOn(); iter; ++iter) { tree3.setValue(iter.getCoord(), vec_val); } CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree0.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree1.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree2.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree3.leafCount()); //tree1.topologyInterection(tree2);//should make tree1 = tree0 tree1.topologyIntersection(tree3);//should make tree1 = tree0 CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); CPPUNIT_ASSERT(tree0.hasSameTopology(tree1)); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree1.isValueOn(p)); CPPUNIT_ASSERT(tree2.isValueOn(p)); CPPUNIT_ASSERT(tree3.isValueOn(p)); CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(p)); } for (openvdb::FloatTree::ValueOnCIter iter = tree1_copy.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(iter.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree0.isValueOn(p)); CPPUNIT_ASSERT(tree2.isValueOn(p)); CPPUNIT_ASSERT(tree3.isValueOn(p)); CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree0.getValue(p)); } } {// test overlapping spheres const float background=5.0f, R0=10.0f, R1=5.6f; const openvdb::Vec3f C0(35.0f, 30.0f, 40.0f), C1(22.3f, 30.5f, 31.0f); const openvdb::Coord dim(32, 32, 32); openvdb::FloatGrid grid0(background); openvdb::FloatGrid grid1(background); unittest_util::makeSphere(dim, C0, R0, grid0, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); unittest_util::makeSphere(dim, C1, R1, grid1, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::FloatTree& tree0 = grid0.tree(); openvdb::FloatTree& tree1 = grid1.tree(); openvdb::FloatTree tree0_copy(tree0); tree0.topologyIntersection(tree1); const openvdb::Index64 n0 = tree0_copy.activeVoxelCount(); const openvdb::Index64 n = tree0.activeVoxelCount(); const openvdb::Index64 n1 = tree1.activeVoxelCount(); //fprintf(stderr,"Intersection of spheres: n=%i, n0=%i n1=%i n0+n1=%i\n",n,n0,n1, n0+n1); CPPUNIT_ASSERT( n < n0 ); CPPUNIT_ASSERT( n < n1 ); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree1.isValueOn(p)); CPPUNIT_ASSERT(tree0_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter, tree0_copy.getValue(p)); } } {// Test based on boolean grids openvdb::CoordBBox big( openvdb::Coord(-9), openvdb::Coord(10)); openvdb::CoordBBox small(openvdb::Coord( 1), openvdb::Coord(10)); openvdb::BoolGrid::Ptr gridBig = openvdb::BoolGrid::create(false); gridBig->fill(big, true/*value*/, true /*make active*/); CPPUNIT_ASSERT_EQUAL(8, int(gridBig->tree().activeTileCount())); CPPUNIT_ASSERT_EQUAL((20 * 20 * 20), int(gridBig->activeVoxelCount())); openvdb::BoolGrid::Ptr gridSmall = openvdb::BoolGrid::create(false); gridSmall->fill(small, true/*value*/, true /*make active*/); CPPUNIT_ASSERT_EQUAL(0, int(gridSmall->tree().activeTileCount())); CPPUNIT_ASSERT_EQUAL((10 * 10 * 10), int(gridSmall->activeVoxelCount())); // change the topology of gridBig by intersecting with gridSmall gridBig->topologyIntersection(*gridSmall); // Should be unchanged CPPUNIT_ASSERT_EQUAL(0, int(gridSmall->tree().activeTileCount())); CPPUNIT_ASSERT_EQUAL((10 * 10 * 10), int(gridSmall->activeVoxelCount())); // In this case the interesection should be exactly "small" CPPUNIT_ASSERT_EQUAL(0, int(gridBig->tree().activeTileCount())); CPPUNIT_ASSERT_EQUAL((10 * 10 * 10), int(gridBig->activeVoxelCount())); } }// testTopologyIntersection void TestTree::testTopologyDifference() { {//no overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.activeVoxelCount()); tree1.topologyDifference(tree0); CPPUNIT_ASSERT_EQUAL(tree1.activeVoxelCount(), openvdb::Index64(1)); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT(!tree1.empty()); } {//two overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 2.0f); tree1.setValue(openvdb::Coord( 500, 300, 200), 1.0f); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(2), tree1.activeVoxelCount() ); CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( 500, 300, 200))); CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 500, 300, 200))); CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 8, 11, 11))); tree1.topologyDifference(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( 500, 300, 200))); CPPUNIT_ASSERT(!tree1.isValueOn(openvdb::Coord( 500, 300, 200))); CPPUNIT_ASSERT( tree1.isValueOn(openvdb::Coord( 8, 11, 11))); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT(!tree1.empty()); } {//4 overlapping voxels const ValueType background=0.0f; openvdb::FloatTree tree0(background), tree1(background); tree0.setValue(openvdb::Coord( 500, 300, 200), 1.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 2.0f); tree0.setValue(openvdb::Coord( 8, 11, 11), 3.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree1.setValue(openvdb::Coord( 8, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); tree1.topologyDifference(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT(!tree1.empty()); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree1.activeVoxelCount() ); } {//passive tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree0.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, false); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.activeVoxelCount()); CPPUNIT_ASSERT(!tree0.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree0.root().onTileCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); tree1.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree1.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree1.setValue(openvdb::Coord( dim, 11, 11), 6.0f); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree1.activeVoxelCount()); CPPUNIT_ASSERT(!tree1.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree1.leafCount() ); tree1.topologyDifference(tree0); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(3), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(3), tree1.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(3), tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); } {//active tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.root().onTileCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree0.setValue(openvdb::Coord( int(dim), 11, 11), 6.0f); CPPUNIT_ASSERT(!tree0.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); CPPUNIT_ASSERT( tree0.isValueOn(openvdb::Coord( int(dim), 11, 11))); CPPUNIT_ASSERT(!tree1.isValueOn(openvdb::Coord( int(dim), 11, 11))); tree1.topologyDifference(tree0); CPPUNIT_ASSERT(tree1.root().onTileCount() > 1); CPPUNIT_ASSERT_EQUAL( dim*dim*dim - 2, tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); tree1.pruneInactive(); CPPUNIT_ASSERT_EQUAL( dim*dim*dim - 2, tree1.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); } {//active tile const ValueType background=0.0f; const openvdb::Index64 dim = openvdb::FloatTree::RootNodeType::ChildNodeType::DIM; openvdb::FloatTree tree0(background), tree1(background); tree1.fill(openvdb::CoordBBox(openvdb::Coord(0),openvdb::Coord(dim-1)),2.0f, true); CPPUNIT_ASSERT_EQUAL(dim*dim*dim, tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(1), tree1.root().onTileCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree0.leafCount() ); tree0.setValue(openvdb::Coord( 500, 301, 200), 4.0f); tree0.setValue(openvdb::Coord( 400, 30, 20), 5.0f); tree0.setValue(openvdb::Coord( dim, 11, 11), 6.0f); CPPUNIT_ASSERT(!tree0.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(3), tree0.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(3), tree0.leafCount() ); tree0.topologyDifference(tree1); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree0.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); CPPUNIT_ASSERT(!tree0.empty()); tree0.pruneInactive(); CPPUNIT_ASSERT_EQUAL( openvdb::Index32(1), tree0.leafCount() ); CPPUNIT_ASSERT_EQUAL( openvdb::Index64(1), tree0.activeVoxelCount() ); CPPUNIT_ASSERT(!tree1.empty()); } {// use tree with different voxel type ValueType background=5.0f; openvdb::FloatTree tree0(background), tree1(background), tree2(background); CPPUNIT_ASSERT(tree2.empty()); // tree0 = tree1.topologyIntersection(tree2) tree0.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree0.setValue(openvdb::Coord(-5, 10,-20),0.1f); tree0.setValue(openvdb::Coord( 5,-10,-20),0.2f); tree0.setValue(openvdb::Coord(-5,-10,-20),0.3f); tree1.setValue(openvdb::Coord( 5, 10, 20),0.0f); tree1.setValue(openvdb::Coord(-5, 10,-20),0.1f); tree1.setValue(openvdb::Coord( 5,-10,-20),0.2f); tree1.setValue(openvdb::Coord(-5,-10,-20),0.3f); tree2.setValue(openvdb::Coord( 5, 10, 20),0.4f); tree2.setValue(openvdb::Coord(-5, 10,-20),0.5f); tree2.setValue(openvdb::Coord( 5,-10,-20),0.6f); tree2.setValue(openvdb::Coord(-5,-10,-20),0.7f); tree2.setValue(openvdb::Coord(-5000, 2000,-3000),4.5678f); tree2.setValue(openvdb::Coord( 5000,-2000,-3000),4.5678f); tree2.setValue(openvdb::Coord(-5000,-2000, 3000),4.5678f); openvdb::FloatTree tree1_copy(tree1); // tree3 has the same topology as tree2 but a different value type const openvdb::Vec3f background2(1.0f,3.4f,6.0f), vec_val(3.1f,5.3f,-9.5f); openvdb::Vec3fTree tree3(background2); for (openvdb::FloatTree::ValueOnCIter iter = tree2.cbeginValueOn(); iter; ++iter) { tree3.setValue(iter.getCoord(), vec_val); } CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree0.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(4), tree1.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree2.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(7), tree3.leafCount()); //tree1.topologyInterection(tree2);//should make tree1 = tree0 tree1.topologyIntersection(tree3);//should make tree1 = tree0 CPPUNIT_ASSERT(tree0.leafCount()==tree1.leafCount()); CPPUNIT_ASSERT(tree0.nonLeafCount()==tree1.nonLeafCount()); CPPUNIT_ASSERT(tree0.activeLeafVoxelCount()==tree1.activeLeafVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveLeafVoxelCount()==tree1.inactiveLeafVoxelCount()); CPPUNIT_ASSERT(tree0.activeVoxelCount()==tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree0.inactiveVoxelCount()==tree1.inactiveVoxelCount()); CPPUNIT_ASSERT(tree1.hasSameTopology(tree0)); CPPUNIT_ASSERT(tree0.hasSameTopology(tree1)); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree1.isValueOn(p)); CPPUNIT_ASSERT(tree2.isValueOn(p)); CPPUNIT_ASSERT(tree3.isValueOn(p)); CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(p)); } for (openvdb::FloatTree::ValueOnCIter iter = tree1_copy.cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(tree1.isValueOn(iter.getCoord())); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree1.getValue(iter.getCoord())); } for (openvdb::FloatTree::ValueOnCIter iter = tree1.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree0.isValueOn(p)); CPPUNIT_ASSERT(tree2.isValueOn(p)); CPPUNIT_ASSERT(tree3.isValueOn(p)); CPPUNIT_ASSERT(tree1_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter,tree0.getValue(p)); } } {// test overlapping spheres const float background=5.0f, R0=10.0f, R1=5.6f; const openvdb::Vec3f C0(35.0f, 30.0f, 40.0f), C1(22.3f, 30.5f, 31.0f); const openvdb::Coord dim(32, 32, 32); openvdb::FloatGrid grid0(background); openvdb::FloatGrid grid1(background); unittest_util::makeSphere(dim, C0, R0, grid0, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); unittest_util::makeSphere(dim, C1, R1, grid1, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::FloatTree& tree0 = grid0.tree(); openvdb::FloatTree& tree1 = grid1.tree(); openvdb::FloatTree tree0_copy(tree0); tree0.topologyDifference(tree1); const openvdb::Index64 n0 = tree0_copy.activeVoxelCount(); const openvdb::Index64 n = tree0.activeVoxelCount(); CPPUNIT_ASSERT( n < n0 ); for (openvdb::FloatTree::ValueOnCIter iter = tree0.cbeginValueOn(); iter; ++iter) { const openvdb::Coord p = iter.getCoord(); CPPUNIT_ASSERT(tree1.isValueOff(p)); CPPUNIT_ASSERT(tree0_copy.isValueOn(p)); ASSERT_DOUBLES_EXACTLY_EQUAL(*iter, tree0_copy.getValue(p)); } } }// testTopologyDifference void TestTree::testSignedFloodFill() { // Use a custom tree configuration to ensure we flood-fill at all levels! typedef openvdb::tree::LeafNode LeafT;//4^3 typedef openvdb::tree::InternalNode InternalT;//4^3 typedef openvdb::tree::RootNode RootT;// child nodes are 16^3 typedef openvdb::tree::Tree TreeT; const float outside = 2.0f, inside = -outside, radius = 20.0f; openvdb::Grid::Ptr grid = openvdb::Grid::create(outside); TreeT& tree = grid->tree(); const RootT& root = tree.getRootNode(); const openvdb::Coord dim(3*16, 3*16, 3*16); const openvdb::Coord C(16+8,16+8,16+8); CPPUNIT_ASSERT(!tree.isValueOn(C)); CPPUNIT_ASSERT(root.getTableSize()==0); //make narrow band of sphere without setting sign for the background values! openvdb::Grid::Accessor acc = grid->getAccessor(); const openvdb::Vec3f center(C[0], C[1], C[2]); openvdb::Coord xyz; for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); const float dist = (p-center).length() - radius; if (fabs(dist) > outside) continue; acc.setValue(xyz, dist); } } } // Check narrow band with incorrect background const size_t size_before = root.getTableSize(); CPPUNIT_ASSERT(size_before>0); CPPUNIT_ASSERT(!tree.isValueOn(C)); ASSERT_DOUBLES_EXACTLY_EQUAL(outside,tree.getValue(C)); for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); const float dist = (p-center).length() - radius; const float val = acc.getValue(xyz); if (dist < inside) { ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); } else if (dist>outside) { ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); } else { ASSERT_DOUBLES_EXACTLY_EQUAL( val, dist ); } } } } CPPUNIT_ASSERT(tree.getValueDepth(C) == -1);//i.e. background value tree.signedFloodFill(); CPPUNIT_ASSERT(tree.getValueDepth(C) == 0);//added inside tile to root // Check narrow band with correct background for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); const float dist = (p-center).length() - radius; const float val = acc.getValue(xyz); if (dist < inside) { ASSERT_DOUBLES_EXACTLY_EQUAL( val, inside); } else if (dist>outside) { ASSERT_DOUBLES_EXACTLY_EQUAL( val, outside); } else { ASSERT_DOUBLES_EXACTLY_EQUAL( val, dist ); } } } } CPPUNIT_ASSERT(root.getTableSize()>size_before);//added inside root tiles CPPUNIT_ASSERT(!tree.isValueOn(C)); ASSERT_DOUBLES_EXACTLY_EQUAL(inside,tree.getValue(C)); } void TestTree::testPruneInactive() { using openvdb::Coord; using openvdb::Index32; using openvdb::Index64; float background = 5.0; openvdb::FloatTree tree(background); // Verify that the newly-constructed tree is empty and that pruning it has no effect. CPPUNIT_ASSERT(tree.empty()); tree.prune(); CPPUNIT_ASSERT(tree.empty()); tree.pruneInactive(background); CPPUNIT_ASSERT(tree.empty()); // Set some active values. tree.setValue(Coord(-5, 10, 20), 0.1); tree.setValue(Coord(-5,-10, 20), 0.4); tree.setValue(Coord(-5, 10,-20), 0.5); tree.setValue(Coord(-5,-10,-20), 0.7); tree.setValue(Coord( 5, 10, 20), 0.0); tree.setValue(Coord( 5,-10, 20), 0.2); tree.setValue(Coord( 5,-10,-20), 0.6); tree.setValue(Coord( 5, 10,-20), 0.3); // Verify that the tree has the expected numbers of active voxels and leaf nodes. CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that prune() has no effect, since the values are all different. tree.prune(); CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that pruneInactive() has no effect, since the values are active. tree.pruneInactive(background); CPPUNIT_ASSERT_EQUAL(Index64(8), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Make some of the active values inactive, without changing their values. tree.setValueOff(Coord(-5, 10, 20)); tree.setValueOff(Coord(-5,-10, 20)); tree.setValueOff(Coord(-5, 10,-20)); tree.setValueOff(Coord(-5,-10,-20)); CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that prune() has no effect, since the values are still different. tree.prune(); CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that pruneInactive() prunes the nodes containing only inactive voxels. tree.pruneInactive(background); CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); // Make all of the active values inactive, without changing their values. tree.setValueOff(Coord( 5, 10, 20)); tree.setValueOff(Coord( 5,-10, 20)); tree.setValueOff(Coord( 5,-10,-20)); tree.setValueOff(Coord( 5, 10,-20)); CPPUNIT_ASSERT_EQUAL(Index64(0), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); // Verify that prune() has no effect, since the values are still different. tree.prune(); CPPUNIT_ASSERT_EQUAL(Index64(0), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); // Verify that pruneInactive() prunes all of the remaining leaf nodes. tree.pruneInactive(background); CPPUNIT_ASSERT(tree.empty()); } void TestTree::testPruneLevelSet() { const float background=10.0f, R=5.6f; const openvdb::Vec3f C(12.3f, 15.5f, 10.0f); const openvdb::Coord dim(32, 32, 32); openvdb::FloatGrid grid(background); unittest_util::makeSphere(dim, C, R, grid, 1.0f, unittest_util::SPHERE_SPARSE_NARROW_BAND); openvdb::FloatTree& tree = grid.tree(); openvdb::Index64 count = 0; openvdb::Coord xyz; for (xyz[0]=0; xyz[0]beginValueOn(); vIter; ++vIter) { if (fabs(*vIter)setValueOff(vIter.pos(), *vIter > 0.0f ? background : -background); ++removed; } } // The following version is slower since it employs // FloatTree::ValueOnIter that visits both tiles and voxels and // also uses random acceess to set the voxels off. /* for (openvdb::FloatTree::ValueOnIter i = tree.beginValueOn(); i; ++i) { if (fabs(*i) 0.0f ? background : -background); ++removed2; } */ CPPUNIT_ASSERT_EQUAL(leafCount, tree.leafCount()); //std::cerr << "Leaf count=" << tree.leafCount() << std::endl; CPPUNIT_ASSERT_EQUAL(tree.activeVoxelCount(), count-removed); CPPUNIT_ASSERT_EQUAL(tree.activeLeafVoxelCount(), count-removed); tree.pruneLevelSet(); CPPUNIT_ASSERT(tree.leafCount() < leafCount); //std::cerr << "Leaf count=" << tree.leafCount() << std::endl; CPPUNIT_ASSERT_EQUAL(tree.activeVoxelCount(), count-removed); CPPUNIT_ASSERT_EQUAL(tree.activeLeafVoxelCount(), count-removed); openvdb::FloatTree::ValueOnCIter i = tree.cbeginValueOn(); for (; i; ++i) CPPUNIT_ASSERT( *i < new_width); for (xyz[0]=0; xyz[0]getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); CPPUNIT_ASSERT(tree->touchLeaf(xyz)!=NULL); CPPUNIT_ASSERT_EQUAL( 3, tree->getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); CPPUNIT_ASSERT(!tree->isValueOn(xyz)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, tree->getValue(xyz)); } {// test accessor openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); openvdb::tree::ValueAccessor acc(*tree); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); CPPUNIT_ASSERT(acc.touchLeaf(xyz)!=NULL); CPPUNIT_ASSERT_EQUAL( 3, tree->getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); CPPUNIT_ASSERT(!acc.isValueOn(xyz)); ASSERT_DOUBLES_EXACTLY_EQUAL(background, acc.getValue(xyz)); } } void TestTree::testProbeLeaf() { const float background=10.0f, value = 2.0f; const openvdb::Coord xyz(-20,30,10); {// test Tree::probeLeaf openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); CPPUNIT_ASSERT_EQUAL(-1, tree->getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); CPPUNIT_ASSERT(tree->probeLeaf(xyz)==NULL); CPPUNIT_ASSERT_EQUAL(-1, tree->getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); tree->setValue(xyz, value); CPPUNIT_ASSERT_EQUAL( 3, tree->getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); CPPUNIT_ASSERT(tree->probeLeaf(xyz)!=NULL); CPPUNIT_ASSERT_EQUAL( 3, tree->getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); CPPUNIT_ASSERT(tree->isValueOn(xyz)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree->getValue(xyz)); } {// test Tree::probeConstLeaf const openvdb::FloatTree tree1(background); CPPUNIT_ASSERT_EQUAL(-1, tree1.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree1.leafCount())); CPPUNIT_ASSERT(tree1.probeConstLeaf(xyz)==NULL); CPPUNIT_ASSERT_EQUAL(-1, tree1.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree1.leafCount())); openvdb::FloatTree tmp(tree1); tmp.setValue(xyz, value); const openvdb::FloatTree tree2(tmp); CPPUNIT_ASSERT_EQUAL( 3, tree2.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree2.leafCount())); CPPUNIT_ASSERT(tree2.probeConstLeaf(xyz)!=NULL); CPPUNIT_ASSERT_EQUAL( 3, tree2.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree2.leafCount())); CPPUNIT_ASSERT(tree2.isValueOn(xyz)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, tree2.getValue(xyz)); } {// test ValueAccessor::probeLeaf openvdb::FloatTree::Ptr tree(new openvdb::FloatTree(background)); openvdb::tree::ValueAccessor acc(*tree); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); CPPUNIT_ASSERT(acc.probeLeaf(xyz)==NULL); CPPUNIT_ASSERT_EQUAL(-1, acc.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree->leafCount())); acc.setValue(xyz, value); CPPUNIT_ASSERT_EQUAL( 3, acc.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); CPPUNIT_ASSERT(acc.probeLeaf(xyz)!=NULL); CPPUNIT_ASSERT_EQUAL( 3, acc.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree->leafCount())); CPPUNIT_ASSERT(acc.isValueOn(xyz)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc.getValue(xyz)); } {// test ValueAccessor::probeConstLeaf const openvdb::FloatTree tree1(background); openvdb::tree::ValueAccessor acc1(tree1); CPPUNIT_ASSERT_EQUAL(-1, acc1.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree1.leafCount())); CPPUNIT_ASSERT(acc1.probeConstLeaf(xyz)==NULL); CPPUNIT_ASSERT_EQUAL(-1, acc1.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 0, int(tree1.leafCount())); openvdb::FloatTree tmp(tree1); tmp.setValue(xyz, value); const openvdb::FloatTree tree2(tmp); openvdb::tree::ValueAccessor acc2(tree2); CPPUNIT_ASSERT_EQUAL( 3, acc2.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree2.leafCount())); CPPUNIT_ASSERT(acc2.probeConstLeaf(xyz)!=NULL); CPPUNIT_ASSERT_EQUAL( 3, acc2.getValueDepth(xyz)); CPPUNIT_ASSERT_EQUAL( 1, int(tree2.leafCount())); CPPUNIT_ASSERT(acc2.isValueOn(xyz)); ASSERT_DOUBLES_EXACTLY_EQUAL(value, acc2.getValue(xyz)); } } void TestTree::testAddLeaf() { using namespace openvdb; typedef FloatTree::LeafNodeType LeafT; const Coord ijk(100); FloatGrid grid; FloatTree& tree = grid.tree(); tree.setValue(ijk, 5.0); const LeafT* oldLeaf = tree.probeLeaf(ijk); CPPUNIT_ASSERT(oldLeaf != NULL); ASSERT_DOUBLES_EXACTLY_EQUAL(5.0, oldLeaf->getValue(ijk)); LeafT* newLeaf = new LeafT; newLeaf->setOrigin(oldLeaf->origin()); newLeaf->fill(3.0); tree.addLeaf(*newLeaf); CPPUNIT_ASSERT_EQUAL(newLeaf, tree.probeLeaf(ijk)); ASSERT_DOUBLES_EXACTLY_EQUAL(3.0, tree.getValue(ijk)); } void TestTree::testAddTile() { using namespace openvdb; const Coord ijk(100); FloatGrid grid; FloatTree& tree = grid.tree(); tree.setValue(ijk, 5.0); CPPUNIT_ASSERT(tree.probeLeaf(ijk) != NULL); const Index lvl = FloatTree::DEPTH >> 1; if (lvl > 0) tree.addTile(lvl,ijk, 3.0, /*active=*/true); else tree.addTile(1,ijk, 3.0, /*active=*/true); CPPUNIT_ASSERT(tree.probeLeaf(ijk) == NULL); ASSERT_DOUBLES_EXACTLY_EQUAL(3.0, tree.getValue(ijk)); } struct BBoxOp { std::vector bbox; std::vector level; // This method is required by Tree::visitActiveBBox // Since it will return false if LEVEL==0 it will never descent to // the active voxels. In other words the smallest BBoxes // correspond to LeafNodes or active tiles at LEVEL=1 template inline bool descent() { return LEVEL>0; } // This method is required by Tree::visitActiveBBox template inline void operator()(const openvdb::CoordBBox &_bbox) { bbox.push_back(_bbox); level.push_back(LEVEL); } }; void TestTree::testProcessBBox() { using openvdb::Coord; using openvdb::CoordBBox; //check two leaf nodes and two tiles at each level 1, 2 and 3 const int size[4]={1<<3, 1<<3, 1<<(3+4), 1<<(3+4+5)}; for (int level=0; level<=3; ++level) { openvdb::FloatTree tree; const int n = size[level]; const CoordBBox bbox[]={CoordBBox::createCube(Coord(-n,-n,-n), n), CoordBBox::createCube(Coord( 0, 0, 0), n)}; if (level==0) { tree.setValue(Coord(-1,-2,-3), 1.0f); tree.setValue(Coord( 1, 2, 3), 1.0f); } else { tree.fill(bbox[0], 1.0f, true); tree.fill(bbox[1], 1.0f, true); } BBoxOp op; tree.visitActiveBBox(op); CPPUNIT_ASSERT_EQUAL(2, int(op.bbox.size())); for (int i=0; i<2; ++i) { //std::cerr <<"\nLevel="< #include #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); #define ASSERT_DOUBLES_APPROX_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1.e-6); class TestRay : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestRay); CPPUNIT_TEST(testInfinity); CPPUNIT_TEST(testRay); CPPUNIT_TEST(testDDA); CPPUNIT_TEST_SUITE_END(); void testInfinity(); void testRay(); void testDDA(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestRay); // the Ray class makes use of infinity=1/0 so we test for it void TestRay::testInfinity() { // This code generates compiler warnings which is why it's not // enabled by default. /* const double one=1, zero = 0, infinity = one / zero; CPPUNIT_ASSERT_DOUBLES_EQUAL( infinity , infinity,0);//not a NAN CPPUNIT_ASSERT_DOUBLES_EQUAL( infinity , infinity+1,0);//not a NAN CPPUNIT_ASSERT_DOUBLES_EQUAL( infinity , infinity*10,0);//not a NAN CPPUNIT_ASSERT( zero < infinity); CPPUNIT_ASSERT( zero > -infinity); CPPUNIT_ASSERT_DOUBLES_EQUAL( zero , one/infinity,0); CPPUNIT_ASSERT_DOUBLES_EQUAL( zero , -one/infinity,0); CPPUNIT_ASSERT_DOUBLES_EQUAL( infinity , one/zero,0); CPPUNIT_ASSERT_DOUBLES_EQUAL(-infinity , -one/zero,0); std::cerr << "inf: " << infinity << "\n"; std::cerr << "1 / inf: " << one / infinity << "\n"; std::cerr << "1 / (-inf): " << one / (-infinity) << "\n"; std::cerr << " inf * 0: " << infinity * 0 << "\n"; std::cerr << "-inf * 0: " << (-infinity) * 0 << "\n"; std::cerr << "(inf): " << (bool)(infinity) << "\n"; std::cerr << "inf == inf: " << (infinity == infinity) << "\n"; std::cerr << "inf > 0: " << (infinity > 0) << "\n"; std::cerr << "-inf > 0: " << ((-infinity) > 0) << "\n"; */ } void TestRay::testRay() { using namespace openvdb; typedef double RealT; typedef math::Ray RayT; typedef RayT::Vec3T Vec3T; typedef math::BBox BBoxT; {//default constructor RayT ray; CPPUNIT_ASSERT(ray.eye() == Vec3T(0,0,0)); CPPUNIT_ASSERT(ray.dir() == Vec3T(1,0,0)); ASSERT_DOUBLES_APPROX_EQUAL( math::Delta::value(), ray.t0()); ASSERT_DOUBLES_APPROX_EQUAL( std::numeric_limits::max(), ray.t1()); } {// simple construction Vec3T eye(1.5,1.5,1.5), dir(1.5,1.5,1.5); dir.normalize(); RealT t0=0.1, t1=12589.0; RayT ray(eye, dir, t0, t1); CPPUNIT_ASSERT(ray.eye()==eye); CPPUNIT_ASSERT(ray.dir()==dir); ASSERT_DOUBLES_APPROX_EQUAL( t0, ray.t0()); ASSERT_DOUBLES_APPROX_EQUAL( t1, ray.t1()); } {// test transformation math::Transform::Ptr xform = math::Transform::createLinearTransform(); xform->preRotate(M_PI, math::Y_AXIS ); xform->postTranslate(math::Vec3d(1, 2, 3)); xform->preScale(Vec3R(0.1, 0.2, 0.4)); Vec3T eye(9,1,1), dir(1,2,0); dir.normalize(); RealT t0=0.1, t1=12589.0; RayT ray0(eye, dir, t0, t1); CPPUNIT_ASSERT( ray0.test(t0)); CPPUNIT_ASSERT( ray0.test(t1)); CPPUNIT_ASSERT( ray0.test(0.5*(t0+t1))); CPPUNIT_ASSERT(!ray0.test(t0-1)); CPPUNIT_ASSERT(!ray0.test(t1+1)); //std::cerr << "Ray0: " << ray0 << std::endl; RayT ray1 = ray0.applyMap( *(xform->baseMap()) ); //std::cerr << "Ray1: " << ray1 << std::endl; RayT ray2 = ray1.applyInverseMap( *(xform->baseMap()) ); //std::cerr << "Ray2: " << ray2 << std::endl; ASSERT_DOUBLES_APPROX_EQUAL( eye[0], ray2.eye()[0]); ASSERT_DOUBLES_APPROX_EQUAL( eye[1], ray2.eye()[1]); ASSERT_DOUBLES_APPROX_EQUAL( eye[2], ray2.eye()[2]); ASSERT_DOUBLES_APPROX_EQUAL( dir[0], ray2.dir()[0]); ASSERT_DOUBLES_APPROX_EQUAL( dir[1], ray2.dir()[1]); ASSERT_DOUBLES_APPROX_EQUAL( dir[2], ray2.dir()[2]); ASSERT_DOUBLES_APPROX_EQUAL( dir[0], 1.0/ray2.invDir()[0]); ASSERT_DOUBLES_APPROX_EQUAL( dir[1], 1.0/ray2.invDir()[1]); ASSERT_DOUBLES_APPROX_EQUAL( dir[2], 1.0/ray2.invDir()[2]); ASSERT_DOUBLES_APPROX_EQUAL( t0, ray2.t0()); ASSERT_DOUBLES_APPROX_EQUAL( t1, ray2.t1()); } {// test transformation // This is the index to world transform math::Transform::Ptr xform = math::Transform::createLinearTransform(); xform->postRotate(M_PI, math::Y_AXIS ); xform->postTranslate(math::Vec3d(1, 2, 3)); xform->postScale(Vec3R(0.1, 0.1, 0.1));//voxel size // Define a ray in world space Vec3T eye(9,1,1), dir(1,2,0); dir.normalize(); RealT t0=0.1, t1=12589.0; RayT ray0(eye, dir, t0, t1); //std::cerr << "\nWorld Ray0: " << ray0 << std::endl; CPPUNIT_ASSERT( ray0.test(t0)); CPPUNIT_ASSERT( ray0.test(t1)); CPPUNIT_ASSERT( ray0.test(0.5*(t0+t1))); CPPUNIT_ASSERT(!ray0.test(t0-1)); CPPUNIT_ASSERT(!ray0.test(t1+1)); Vec3T xyz0[3] = {ray0.start(), ray0.mid(), ray0.end()}; // Transform the ray to index space RayT ray1 = ray0.applyInverseMap( *(xform->baseMap()) ); //std::cerr << "\nIndex Ray1: " << ray1 << std::endl; Vec3T xyz1[3] = {ray1.start(), ray1.mid(), ray1.end()}; for (int i=0; i<3; ++i) { Vec3T pos = xform->baseMap()->applyMap(xyz1[i]); //std::cerr << "world0 ="< DDAType; const RayType::Vec3T eye( 0, 0, 0); for (int s = -1; s<=1; s+=2) { for (int a = 0; a<3; ++a) { const int d[3]={s*(a==0), s*(a==1), s*(a==2)}; const RayType::Vec3T dir(d[0], d[1], d[2]); RayType ray(eye, dir); DDAType dda(ray); //std::cerr << "\nray: "< #include #include #include #include #include class TestMeshToVolume: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestMeshToVolume); CPPUNIT_TEST(testUtils); CPPUNIT_TEST(testVoxelizer); CPPUNIT_TEST(testPrimitiveVoxelRatio); CPPUNIT_TEST(testIntersectingVoxelCleaner); CPPUNIT_TEST(testShellVoxelCleaner); CPPUNIT_TEST(testConversion); CPPUNIT_TEST_SUITE_END(); void testUtils(); void testVoxelizer(); void testPrimitiveVoxelRatio(); void testIntersectingVoxelCleaner(); void testShellVoxelCleaner(); void testConversion(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMeshToVolume); //////////////////////////////////////// void TestMeshToVolume::testUtils() { /// Test nearestCoord openvdb::Vec3d xyz(0.7, 2.2, -2.7); openvdb::Coord ijk = openvdb::util::nearestCoord(xyz); CPPUNIT_ASSERT(ijk[0] == 0 && ijk[1] == 2 && ijk[2] == -3); xyz = openvdb::Vec3d(-22.1, 4.6, 202.34); ijk = openvdb::util::nearestCoord(xyz); CPPUNIT_ASSERT(ijk[0] == -23 && ijk[1] == 4 && ijk[2] == 202); /// Test the coordinate offset table for neghbouring voxels openvdb::Coord sum(0, 0, 0); unsigned int pX = 0, pY = 0, pZ = 0, mX = 0, mY = 0, mZ = 0; for (unsigned int i = 0; i < 26; ++i) { ijk = openvdb::util::COORD_OFFSETS[i]; sum += ijk; if (ijk[0] == 1) ++pX; else if (ijk[0] == -1) ++mX; if (ijk[1] == 1) ++pY; else if (ijk[1] == -1) ++mY; if (ijk[2] == 1) ++pZ; else if (ijk[2] == -1) ++mZ; } CPPUNIT_ASSERT(sum == openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT( pX == 9); CPPUNIT_ASSERT( pY == 9); CPPUNIT_ASSERT( pZ == 9); CPPUNIT_ASSERT( mX == 9); CPPUNIT_ASSERT( mY == 9); CPPUNIT_ASSERT( mZ == 9); } void TestMeshToVolume::testVoxelizer() { std::vector pointList; std::vector polygonList; typedef openvdb::tools::internal::MeshVoxelizer MeshVoxelizer; // CASE 1: One triangle pointList.push_back(openvdb::Vec3s(0.0, 0.0, 0.0)); pointList.push_back(openvdb::Vec3s(0.0, 0.0, 3.0)); pointList.push_back(openvdb::Vec3s(0.0, 3.0, 0.0)); polygonList.push_back(openvdb::Vec4I(0, 1, 2, openvdb::util::INVALID_IDX)); { MeshVoxelizer voxelizer(pointList, polygonList); voxelizer.run(); // Check for mesh intersecting voxels CPPUNIT_ASSERT(13== voxelizer.intersectionTree().activeVoxelCount()); // topologically unique voxels. CPPUNIT_ASSERT(99 == voxelizer.sqrDistTree().activeVoxelCount()); CPPUNIT_ASSERT(99 == voxelizer.primIndexTree().activeVoxelCount()); } // CASE 2: Two triangles pointList.push_back(openvdb::Vec3s(0.0, 3.0, 3.0)); polygonList.push_back(openvdb::Vec4I(1, 3, 2, openvdb::util::INVALID_IDX)); { MeshVoxelizer voxelizer(pointList, polygonList); voxelizer.run(); // Check for mesh intersecting voxels CPPUNIT_ASSERT(16 == voxelizer.intersectionTree().activeVoxelCount()); // topologically unique voxels. CPPUNIT_ASSERT(108 == voxelizer.sqrDistTree().activeVoxelCount()); CPPUNIT_ASSERT(108 == voxelizer.primIndexTree().activeVoxelCount()); } // CASE 3: One quad polygonList.clear(); polygonList.push_back(openvdb::Vec4I(0, 1, 3, 2)); { MeshVoxelizer voxelizer(pointList, polygonList); voxelizer.run(); // Check for mesh intersecting voxels CPPUNIT_ASSERT(16 == voxelizer.intersectionTree().activeVoxelCount()); // topologically unique voxels. CPPUNIT_ASSERT(108 == voxelizer.sqrDistTree().activeVoxelCount()); CPPUNIT_ASSERT(108 == voxelizer.primIndexTree().activeVoxelCount()); } // CASE 4: Two triangles and one quad pointList.push_back(openvdb::Vec3s(0.0, 0.0, 6.0)); pointList.push_back(openvdb::Vec3s(0.0, 3.0, 6.0)); polygonList.clear(); polygonList.push_back(openvdb::Vec4I(0, 1, 2, openvdb::util::INVALID_IDX)); polygonList.push_back(openvdb::Vec4I(1, 3, 2, openvdb::util::INVALID_IDX)); polygonList.push_back(openvdb::Vec4I(1, 4, 5, 3)); { MeshVoxelizer voxelizer(pointList, polygonList); voxelizer.run(); // Check for 28 mesh intersecting voxels CPPUNIT_ASSERT(28 == voxelizer.intersectionTree().activeVoxelCount()); // 154 topologically unique voxels. CPPUNIT_ASSERT(162 == voxelizer.sqrDistTree().activeVoxelCount()); CPPUNIT_ASSERT(162 == voxelizer.primIndexTree().activeVoxelCount()); } } void TestMeshToVolume::testPrimitiveVoxelRatio() { std::vector pointList; std::vector polygonList; // Create one big triangle pointList.push_back(openvdb::Vec3s(0.0, 0.0, 0.0)); pointList.push_back(openvdb::Vec3s(0.0, 0.0, 250.0)); pointList.push_back(openvdb::Vec3s(0.0, 100.0, 0.0)); polygonList.push_back(openvdb::Vec4I(0, 1, 2, openvdb::util::INVALID_IDX)); openvdb::tools::internal::MeshVoxelizer voxelizer(pointList, polygonList); voxelizer.run(); CPPUNIT_ASSERT(0 != voxelizer.intersectionTree().activeVoxelCount()); } void TestMeshToVolume::testIntersectingVoxelCleaner() { // Empty tree's openvdb::FloatTree distTree(std::numeric_limits::max()); openvdb::BoolTree intersectionTree(false); openvdb::Int32Tree indexTree(openvdb::util::INVALID_IDX); openvdb::tree::ValueAccessor distAcc(distTree); openvdb::tree::ValueAccessor intersectionAcc(intersectionTree); openvdb::tree::ValueAccessor indexAcc(indexTree); // Add a row of intersecting voxels surrounded by both positive and negative distance values. for (int i = 0; i < 10; ++i) { for (int j = -1; j < 2; ++j) { distAcc.setValue(openvdb::Coord(i,j,0), (float)j); indexAcc.setValue(openvdb::Coord(i,j,0), 10); } intersectionAcc.setValue(openvdb::Coord(i,0,0), 1); } unsigned int numSDFVoxels = distTree.activeVoxelCount(); unsigned int numIVoxels = intersectionTree.activeVoxelCount(); unsigned int numCPVoxels = indexTree.activeVoxelCount(); { openvdb::tree::LeafManager leafs(intersectionTree); openvdb::tools::internal::IntersectingVoxelCleaner cleaner(distTree, indexTree, intersectionTree, leafs); cleaner.run(); } CPPUNIT_ASSERT(numSDFVoxels == distTree.activeVoxelCount()); CPPUNIT_ASSERT(numIVoxels == intersectionTree.activeVoxelCount()); CPPUNIT_ASSERT(numCPVoxels == indexTree.activeVoxelCount()); // Add a row of intersecting voxels that are not surrounded by any positive distance values. for (int i = 0; i < 10; ++i) { for (int j = -1; j < 2; ++j) { distAcc.setValue(openvdb::Coord(i,j,0), -1.0); indexAcc.setValue(openvdb::Coord(i,j,0), 10); } intersectionAcc.setValue(openvdb::Coord(i,0,0), 1); } numIVoxels = 0; { openvdb::tree::LeafManager leafs(intersectionTree); openvdb::tools::internal::IntersectingVoxelCleaner cleaner(distTree, indexTree, intersectionTree, leafs); cleaner.run(); } CPPUNIT_ASSERT(numSDFVoxels == distTree.activeVoxelCount()); CPPUNIT_ASSERT(numIVoxels == intersectionTree.activeVoxelCount()); CPPUNIT_ASSERT(numCPVoxels == indexTree.activeVoxelCount()); } void TestMeshToVolume::testShellVoxelCleaner() { // Empty tree's openvdb::FloatTree distTree(std::numeric_limits::max()); openvdb::BoolTree intersectionTree(false); openvdb::Int32Tree indexTree(openvdb::util::INVALID_IDX); openvdb::tree::ValueAccessor distAcc(distTree); openvdb::tree::ValueAccessor intersectionAcc(intersectionTree); openvdb::tree::ValueAccessor indexAcc(indexTree); /// Add a row of intersecting voxels surrounded by negative distance values. for (int i = 0; i < 10; ++i) { for (int j = -1; j < 2; ++j) { distAcc.setValue(openvdb::Coord(i,j,0), -1.0); indexAcc.setValue(openvdb::Coord(i,j,0), 10); } intersectionAcc.setValue(openvdb::Coord(i,0,0), 1); } unsigned int numSDFVoxels = distTree.activeVoxelCount(); unsigned int numIVoxels = intersectionTree.activeVoxelCount(); unsigned int numCPVoxels = indexTree.activeVoxelCount(); { openvdb::tree::LeafManager leafs(distTree); openvdb::tools::internal::ShellVoxelCleaner cleaner(distTree, leafs, indexTree, intersectionTree); cleaner.run(); } CPPUNIT_ASSERT(numSDFVoxels == distTree.activeVoxelCount()); CPPUNIT_ASSERT(numIVoxels == intersectionTree.activeVoxelCount()); CPPUNIT_ASSERT(numCPVoxels == indexTree.activeVoxelCount()); intersectionTree.clear(); { openvdb::tree::LeafManager leafs(distTree); openvdb::tools::internal::ShellVoxelCleaner cleaner(distTree, leafs, indexTree, intersectionTree); cleaner.run(); } CPPUNIT_ASSERT(0 == distTree.activeVoxelCount()); CPPUNIT_ASSERT(0 == intersectionTree.activeVoxelCount()); CPPUNIT_ASSERT(0 == indexTree.activeVoxelCount());; } void TestMeshToVolume::testConversion() { using namespace openvdb; std::vector points; std::vector quads; // cube vertices points.push_back(Vec3s(2, 2, 2)); // 0 6--------7 points.push_back(Vec3s(5, 2, 2)); // 1 /| /| points.push_back(Vec3s(2, 5, 2)); // 2 2--------3 | points.push_back(Vec3s(5, 5, 2)); // 3 | | | | points.push_back(Vec3s(2, 2, 5)); // 4 | 4----- |-5 points.push_back(Vec3s(5, 2, 5)); // 5 |/ |/ points.push_back(Vec3s(2, 5, 5)); // 6 0--------1 points.push_back(Vec3s(5, 5, 5)); // 7 // cube faces quads.push_back(Vec4I(0, 1, 3, 2)); // front quads.push_back(Vec4I(5, 4, 6, 7)); // back quads.push_back(Vec4I(0, 2, 6, 4)); // left quads.push_back(Vec4I(1, 5, 7, 3)); // right quads.push_back(Vec4I(2, 3, 7, 6)); // top quads.push_back(Vec4I(0, 4, 5, 1)); // bottom FloatGrid::Ptr grid = tools::meshToLevelSet( *math::Transform::createLinearTransform(), points, quads); //io::File("/tmp/cube.vdb").write(GridPtrVec(1, grid)); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(int(GRID_LEVEL_SET), int(grid->getGridClass())); CPPUNIT_ASSERT_EQUAL(1, int(grid->baseTree().leafCount())); /// @todo validate output } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestStream.cc0000644000000000000000000002077512252453157015513 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(a, b) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((a), (b), /*tolerance=*/0.0); class TestStream: public CppUnit::TestCase { public: virtual void setUp(); virtual void tearDown(); CPPUNIT_TEST_SUITE(TestStream); CPPUNIT_TEST(testWrite); CPPUNIT_TEST(testRead); CPPUNIT_TEST(testFileReadFromStream); CPPUNIT_TEST_SUITE_END(); void testWrite(); void testRead(); void testFileReadFromStream(); private: static openvdb::GridPtrVecPtr createTestGrids(openvdb::MetaMap::Ptr&); static void verifyTestGrids(openvdb::GridPtrVecPtr, openvdb::MetaMap::Ptr); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestStream); //////////////////////////////////////// void TestStream::setUp() { openvdb::uninitialize(); openvdb::Int32Grid::registerGrid(); openvdb::FloatGrid::registerGrid(); openvdb::StringMetadata::registerType(); openvdb::Int32Metadata::registerType(); openvdb::Int64Metadata::registerType(); openvdb::Vec3IMetadata::registerType(); // Register maps openvdb::math::MapRegistry::clear(); openvdb::math::AffineMap::registerMap(); openvdb::math::ScaleMap::registerMap(); openvdb::math::UniformScaleMap::registerMap(); openvdb::math::TranslationMap::registerMap(); openvdb::math::ScaleTranslateMap::registerMap(); openvdb::math::UniformScaleTranslateMap::registerMap(); openvdb::math::NonlinearFrustumMap::registerMap(); } void TestStream::tearDown() { openvdb::uninitialize(); } //////////////////////////////////////// openvdb::GridPtrVecPtr TestStream::createTestGrids(openvdb::MetaMap::Ptr& metadata) { using namespace openvdb; // Create trees Int32Tree::Ptr tree1(new Int32Tree(1)); FloatTree::Ptr tree2(new FloatTree(2.0)); // Set some values tree1->setValue(Coord(0, 0, 0), 5); tree1->setValue(Coord(100, 0, 0), 6); tree2->setValue(Coord(0, 0, 0), 10); tree2->setValue(Coord(0, 100, 0), 11); // Create grids GridBase::Ptr grid1 = createGrid(tree1), grid2 = createGrid(tree1), // instance of grid1 grid3 = createGrid(tree2); grid1->setName("density"); grid2->setName("density_copy"); grid3->setName("temperature"); // Create transforms math::Transform::Ptr trans1 = math::Transform::createLinearTransform(0.1); math::Transform::Ptr trans2 = math::Transform::createLinearTransform(0.1); grid1->setTransform(trans1); grid2->setTransform(trans2); grid3->setTransform(trans2); metadata.reset(new MetaMap); metadata->insertMeta("author", StringMetadata("Einstein")); metadata->insertMeta("year", Int32Metadata(2009)); GridPtrVecPtr grids(new GridPtrVec); grids->push_back(grid1); grids->push_back(grid2); grids->push_back(grid3); return grids; } void TestStream::verifyTestGrids(openvdb::GridPtrVecPtr grids, openvdb::MetaMap::Ptr meta) { using namespace openvdb; CPPUNIT_ASSERT(grids.get() != NULL); CPPUNIT_ASSERT(meta.get() != NULL); // Verify the metadata. CPPUNIT_ASSERT_EQUAL(2, int(meta->metaCount())); CPPUNIT_ASSERT_EQUAL(std::string("Einstein"), meta->metaValue("author")); CPPUNIT_ASSERT_EQUAL(2009, meta->metaValue("year")); // Verify the grids. CPPUNIT_ASSERT_EQUAL(3, int(grids->size())); GridBase::Ptr grid = findGridByName(*grids, "density"); CPPUNIT_ASSERT(grid.get() != NULL); Int32Tree::Ptr density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != NULL); grid.reset(); grid = findGridByName(*grids, "density_copy"); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != NULL); // Verify that "density_copy" is an instance of (i.e., shares a tree with) "density". CPPUNIT_ASSERT_EQUAL(density, gridPtrCast(grid)->treePtr()); grid.reset(); grid = findGridByName(*grids, "temperature"); CPPUNIT_ASSERT(grid.get() != NULL); FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != NULL); ASSERT_DOUBLES_EXACTLY_EQUAL(5, density->getValue(Coord(0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(6, density->getValue(Coord(100, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(10, temperature->getValue(Coord(0, 0, 0))); ASSERT_DOUBLES_EXACTLY_EQUAL(11, temperature->getValue(Coord(0, 100, 0))); } //////////////////////////////////////// void TestStream::testWrite() { using namespace openvdb; // Create test grids and stream them to a string. MetaMap::Ptr meta; GridPtrVecPtr grids = createTestGrids(meta); std::ostringstream ostr(std::ios_base::binary); io::Stream(ostr).write(*grids, *meta); //std::ofstream file("debug.vdb2", std::ios_base::binary); //file << ostr.str(); // Stream the grids back in. std::istringstream is(ostr.str(), std::ios_base::binary); io::Stream strm(is); meta = strm.getMetadata(); grids = strm.getGrids(); verifyTestGrids(grids, meta); } void TestStream::testRead() { using namespace openvdb; // Create test grids and write them to a file. MetaMap::Ptr meta; GridPtrVecPtr grids = createTestGrids(meta); const char* filename = "something.vdb2"; io::File(filename).write(*grids, *meta); boost::shared_ptr scopedFile(filename, ::remove); // Stream the grids back in. std::ifstream is(filename, std::ios_base::binary); io::Stream strm(is); meta = strm.getMetadata(); grids = strm.getGrids(); verifyTestGrids(grids, meta); } /// Stream grids to a file using io::Stream, then read the file back using io::File. void TestStream::testFileReadFromStream() { using namespace openvdb; MetaMap::Ptr meta; GridPtrVecPtr grids; // Create test grids and stream them to a file (and then close the file). const char* filename = "something.vdb2"; boost::shared_ptr scopedFile(filename, ::remove); { std::ofstream os(filename, std::ios_base::binary); grids = createTestGrids(meta); io::Stream(os).write(*grids, *meta); } // Read the grids back in. io::File file(filename); CPPUNIT_ASSERT(file.inputHasGridOffsets()); CPPUNIT_ASSERT_THROW(file.getGrids(), IoError); file.open(); meta = file.getMetadata(); grids = file.getGrids(); CPPUNIT_ASSERT(!file.inputHasGridOffsets()); CPPUNIT_ASSERT(meta.get() != NULL); CPPUNIT_ASSERT(grids.get() != NULL); CPPUNIT_ASSERT(!grids->empty()); verifyTestGrids(grids, meta); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestCpt.cc0000644000000000000000000005267512252453157015012 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include // for old GradientStencil #include "util.h" // for unittest_util::makeSphere() #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestCpt: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestCpt); CPPUNIT_TEST(testCpt); // Cpt in World Space CPPUNIT_TEST(testCptStencil); CPPUNIT_TEST(testCptTool); // Cpt tool CPPUNIT_TEST(testCptMaskedTool); CPPUNIT_TEST(testOldStyleStencils); // old stencil impl CPPUNIT_TEST_SUITE_END(); void testCpt(); void testCptStencil(); void testCptTool(); void testCptMaskedTool(); void testOldStyleStencils(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestCpt); void TestCpt::testCpt() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; { // unit voxel size tests FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); const FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const Vec3f center(35.0, 30.0f, 40.0f); const float radius=0;//point at {35,30,40} unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); AccessorType inAccessor = grid->getConstAccessor(); // this uses the gradient. Only test for a few maps, since the gradient is // tested elsewhere Coord xyz(35,30,30); math::TranslationMap translate; // Note the CPT::result is in continuous index space Vec3f P = math::CPT::result(translate, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); // CPT_RANGE::result is in the range of the map // CPT_RANGE::result = map.applyMap(CPT::result()) // for our tests, the map is an identity so in this special case // the two versions of the Cpt should exactly agree P = math::CPT_RANGE::result(translate, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,35); P = math::CPT::result(translate, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); P = math::CPT_RANGE::result(translate, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); } { // NON-UNIT VOXEL SIZE double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); AccessorType inAccessor = grid->getConstAccessor(); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere math::AffineMap affine(voxel_size*math::Mat3d::identity()); Vec3f P = math::CPT::result(affine, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); P = math::CPT_RANGE::result(affine, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(10,P[2]); xyz.reset(12,16,10); P = math::CPT::result(affine, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); P = math::CPT_RANGE::result(affine, inAccessor, xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(6,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); } { // NON-UNIFORM SCALING Vec3d voxel_sizes(0.5, 1, 0.5); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); CPPUNIT_ASSERT(grid->empty()); AccessorType inAccessor = grid->getConstAccessor(); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); Coord ijk = grid->transform().worldToIndexNodeCentered(Vec3d(10,8,10)); //Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere math::ScaleMap scale(voxel_sizes); Vec3f P; P = math::CPT::result(scale, inAccessor, ijk); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); // world space result P = math::CPT_RANGE::result(scale, inAccessor, ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(16,P[0], 0.02 ); CPPUNIT_ASSERT_DOUBLES_EQUAL(8, P[1], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(10,P[2], 0.02); //xyz.reset(12,16,10); ijk = grid->transform().worldToIndexNodeCentered(Vec3d(6,8,5)); P = math::CPT::result(scale, inAccessor, ijk); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); P = math::CPT_RANGE::result(scale, inAccessor, ijk); CPPUNIT_ASSERT_DOUBLES_EQUAL(6,P[0], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(8,P[1], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(0,P[2], 0.02); } } void TestCpt::testCptStencil() { using namespace openvdb; typedef FloatGrid::ConstAccessor AccessorType; { // UNIT VOXEL TEST FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); const FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f ,30.0f, 40.0f); const float radius=0.0f; unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); // this uses the gradient. Only test for a few maps, since the gradient is // tested elsewhere math::SevenPointStencil sevenpt(*grid); math::SecondOrderDenseStencil dense_2nd(*grid); Coord xyz(35,30,30); CPPUNIT_ASSERT(tree.isValueOn(xyz)); sevenpt.moveTo(xyz); dense_2nd.moveTo(xyz); math::TranslationMap translate; // Note the CPT::result is in continuous index space Vec3f P = math::CPT::result(translate, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); // CPT_RANGE::result_stencil is in the range of the map // CPT_RANGE::result_stencil = map.applyMap(CPT::result_stencil()) // for our tests, the map is an identity so in this special case // the two versions of the Cpt should exactly agree P = math::CPT_RANGE::result(translate, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,35); sevenpt.moveTo(xyz); dense_2nd.moveTo(xyz); CPPUNIT_ASSERT(tree.isValueOn(xyz)); P = math::CPT::result(translate, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); P = math::CPT_RANGE::result(translate, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,30); sevenpt.moveTo(xyz); dense_2nd.moveTo(xyz); math::AffineMap affine; P = math::CPT::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); P = math::CPT_RANGE::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,35); sevenpt.moveTo(xyz); dense_2nd.moveTo(xyz); CPPUNIT_ASSERT(tree.isValueOn(xyz)); P = math::CPT::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); CPPUNIT_ASSERT(tree.isValueOn(xyz)); P = math::CPT_RANGE::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); } { // NON-UNIT VOXEL SIZE double voxel_size = 0.5; FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(voxel_size)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); math::SecondOrderDenseStencil dense_2nd(*grid); Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere math::AffineMap affine(voxel_size*math::Mat3d::identity()); dense_2nd.moveTo(xyz); Vec3f P = math::CPT::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); P = math::CPT_RANGE::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(10,P[2]); xyz.reset(12,16,10); dense_2nd.moveTo(xyz); P = math::CPT::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); P = math::CPT_RANGE::result(affine, dense_2nd); ASSERT_DOUBLES_EXACTLY_EQUAL(6,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); } { // NON-UNIFORM SCALING Vec3d voxel_sizes(0.5, 1, 0.5); math::MapBase::Ptr base_map( new math::ScaleMap(voxel_sizes)); FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::Ptr(new math::Transform(base_map))); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f, 8.0f, 10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); Coord ijk = grid->transform().worldToIndexNodeCentered(Vec3d(10,8,10)); math::SevenPointStencil sevenpt(*grid); sevenpt.moveTo(ijk); //Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere math::ScaleMap scale(voxel_sizes); Vec3f P; P = math::CPT::result(scale, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); // world space result P = math::CPT_RANGE::result(scale, sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(16,P[0], 0.02 ); CPPUNIT_ASSERT_DOUBLES_EQUAL(8, P[1], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(10,P[2], 0.02); //xyz.reset(12,16,10); ijk = grid->transform().worldToIndexNodeCentered(Vec3d(6,8,5)); sevenpt.moveTo(ijk); P = math::CPT::result(scale, sevenpt); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(8,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(0,P[2]); P = math::CPT_RANGE::result(scale, sevenpt); CPPUNIT_ASSERT_DOUBLES_EQUAL(6,P[0], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(8,P[1], 0.02); CPPUNIT_ASSERT_DOUBLES_EQUAL(0,P[2], 0.02); } } void TestCpt::testCptTool() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); const FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); // run the tool typedef openvdb::tools::Cpt FloatCpt; FloatCpt cpt(*grid); FloatCpt::OutGridType::Ptr cptGrid = cpt.process(true/*threaded*/, false/*use world transform*/); FloatCpt::OutGridType::ConstAccessor cptAccessor = cptGrid->getConstAccessor(); Coord xyz(35,30,30); CPPUNIT_ASSERT(tree.isValueOn(xyz)); Vec3f P = cptAccessor.getValue(xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); xyz.reset(35,30,35); CPPUNIT_ASSERT(tree.isValueOn(xyz)); P = cptAccessor.getValue(xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0],P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1],P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2],P[2]); } void TestCpt::testCptMaskedTool() { using namespace openvdb; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); const FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const openvdb::Coord dim(64,64,64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius=0;//point at {35,30,40} unittest_util::makeSphere(dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(tree.activeVoxelCount())); const openvdb::CoordBBox maskbbox(openvdb::Coord(35, 30, 30), openvdb::Coord(41, 41, 41)); BoolGrid::Ptr maskGrid = BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); // run the tool typedef openvdb::tools::Cpt FloatCpt; FloatCpt cpt(*grid, *maskGrid); FloatCpt::OutGridType::Ptr cptGrid = cpt.process(true/*threaded*/, false/*use world transform*/); FloatCpt::OutGridType::ConstAccessor cptAccessor = cptGrid->getConstAccessor(); // inside the masked region Coord xyz(35,30,30); CPPUNIT_ASSERT(tree.isValueOn(xyz)); Vec3f P = cptAccessor.getValue(xyz); ASSERT_DOUBLES_EXACTLY_EQUAL(center[0], P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[1], P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(center[2], P[2]); // outside the masked region xyz.reset(42,42,42); CPPUNIT_ASSERT(!cptAccessor.isValueOn(xyz)); } void TestCpt::testOldStyleStencils() { using namespace openvdb; {// test of level set to sphere at (6,8,10) with R=10 and dx=0.5 FloatGrid::Ptr grid = FloatGrid::create(/*backgroundValue=*/5.0); grid->setTransform(math::Transform::createLinearTransform(/*voxel size=*/0.5)); CPPUNIT_ASSERT(grid->empty()); const openvdb::Coord dim(32,32,32); const openvdb::Vec3f center(6.0f,8.0f,10.0f);//i.e. (12,16,20) in index space const float radius=10;//i.e. (16,8,10) and (6,8,0) are on the sphere unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE); CPPUNIT_ASSERT(!grid->empty()); CPPUNIT_ASSERT_EQUAL(dim[0]*dim[1]*dim[2], int(grid->activeVoxelCount())); math::GradStencil gs(*grid); Coord xyz(20,16,20);//i.e. (10,8,10) in world space or 6 world units inside the sphere gs.moveTo(xyz); float dist = gs.getValue();//signed closest distance to sphere in world coordinates Vec3f P = gs.cpt();//closes point to sphere in index space ASSERT_DOUBLES_EXACTLY_EQUAL(dist,-6); ASSERT_DOUBLES_EXACTLY_EQUAL(32,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL(20,P[2]); xyz.reset(12,16,10);//i.e. (6,8,5) in world space or 15 world units inside the sphere gs.moveTo(xyz); dist = gs.getValue();//signed closest distance to sphere in world coordinates P = gs.cpt();//closes point to sphere in index space ASSERT_DOUBLES_EXACTLY_EQUAL(-5,dist); ASSERT_DOUBLES_EXACTLY_EQUAL(12,P[0]); ASSERT_DOUBLES_EXACTLY_EQUAL(16,P[1]); ASSERT_DOUBLES_EXACTLY_EQUAL( 0,P[2]); } } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLeafIO.cc0000644000000000000000000001442712252453157015354 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include // for toupper() #include #include // CPPUNIT_TEST_SUITE() invokes CPPUNIT_TESTNAMER_DECL() to generate a suite name // from the FixtureType. But if FixtureType is a templated type, the generated name // can become long and messy. This macro overrides the normal naming logic, // instead invoking FixtureType::testSuiteName(), which should be a static member // function that returns a std::string containing the suite name for the specific // template instantiation. #undef CPPUNIT_TESTNAMER_DECL #define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType ) \ CPPUNIT_NS::TestNamer variableName( FixtureType::testSuiteName() ) template class TestLeafIO: public CppUnit::TestCase { public: static std::string testSuiteName() { std::string name = openvdb::typeNameAsString(); if (!name.empty()) name[0] = ::toupper(name[0]); return "TestLeafIO" + name; } CPPUNIT_TEST_SUITE(TestLeafIO); CPPUNIT_TEST(testBuffer); CPPUNIT_TEST_SUITE_END(); void testBuffer(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafIO); template void TestLeafIO::testBuffer() { openvdb::tree::LeafNode leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(0, 1, 0), T(1)); leaf.setValueOn(openvdb::Coord(1, 0, 0), T(1)); std::ostringstream ostr(std::ios_base::binary); leaf.writeBuffers(ostr); leaf.setValueOn(openvdb::Coord(0, 1, 0), T(0)); leaf.setValueOn(openvdb::Coord(0, 1, 1), T(1)); std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(istr); leaf.readBuffers(istr); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1), leaf.getValue(openvdb::Coord(0, 1, 0)), /*tolerance=*/0); CPPUNIT_ASSERT_DOUBLES_EQUAL(T(1), leaf.getValue(openvdb::Coord(1, 0, 0)), /*tolerance=*/0); CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); } template<> void TestLeafIO::testBuffer() { openvdb::tree::LeafNode leaf(openvdb::Coord(0, 0, 0), std::string()); leaf.setValueOn(openvdb::Coord(0, 1, 0), std::string("test")); leaf.setValueOn(openvdb::Coord(1, 0, 0), std::string("test")); std::ostringstream ostr(std::ios_base::binary); leaf.writeBuffers(ostr); leaf.setValueOn(openvdb::Coord(0, 1, 0), std::string("douche")); leaf.setValueOn(openvdb::Coord(0, 1, 1), std::string("douche")); std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(istr); leaf.readBuffers(istr); CPPUNIT_ASSERT_EQUAL(std::string("test"), leaf.getValue(openvdb::Coord(0, 1, 0))); CPPUNIT_ASSERT_EQUAL(std::string("test"), leaf.getValue(openvdb::Coord(1, 0, 0))); CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); } template<> void TestLeafIO::testBuffer() { openvdb::tree::LeafNode leaf(openvdb::Coord(0, 0, 0)); leaf.setValueOn(openvdb::Coord(0, 1, 0), openvdb::Vec3R(1, 1, 1)); leaf.setValueOn(openvdb::Coord(1, 0, 0), openvdb::Vec3R(1, 1, 1)); std::ostringstream ostr(std::ios_base::binary); leaf.writeBuffers(ostr); leaf.setValueOn(openvdb::Coord(0, 1, 0), openvdb::Vec3R(0, 0, 0)); leaf.setValueOn(openvdb::Coord(0, 1, 1), openvdb::Vec3R(1, 1, 1)); std::istringstream istr(ostr.str(), std::ios_base::binary); // Since the input stream doesn't include a VDB header with file format version info, // tag the input stream explicitly with the current version number. openvdb::io::setCurrentVersion(istr); leaf.readBuffers(istr); CPPUNIT_ASSERT(leaf.getValue(openvdb::Coord(0, 1, 0)) == openvdb::Vec3R(1, 1, 1)); CPPUNIT_ASSERT(leaf.getValue(openvdb::Coord(1, 0, 0)) == openvdb::Vec3R(1, 1, 1)); CPPUNIT_ASSERT(leaf.onVoxelCount() == 2); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/unittest/TestLevelSetRayIntersector.cc0000644000000000000000000003477312252453157020704 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// @author Ken Museth // Uncomment to enable statistics of ray-intersections //#define STATS_TEST #include #include #include #include #include #include #include #include #include // for Film #ifdef STATS_TEST //only needed for statistics #include #include "util.h"//for CpuTimer #include #endif #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); #define ASSERT_DOUBLES_APPROX_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/1.e-6); class TestLevelSetRayIntersector : public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestLevelSetRayIntersector); CPPUNIT_TEST(tests); #ifdef STATS_TEST CPPUNIT_TEST(stats); #endif CPPUNIT_TEST_SUITE_END(); void tests(); #ifdef STATS_TEST void stats(); #endif }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLevelSetRayIntersector); void TestLevelSetRayIntersector::tests() { using namespace openvdb; typedef math::Ray RayT; typedef RayT::Vec3Type Vec3T; {// voxel intersection against a level set sphere const float r = 5.0f; const Vec3f c(20.0f, 0.0f, 0.0f); const float s = 0.5f, w = 2.0f; FloatGrid::Ptr ls = tools::createLevelSetSphere(r, c, s, w); tools::LevelSetRayIntersector lsri(*ls); const Vec3T dir(1.0, 0.0, 0.0); const Vec3T eye(2.0, 0.0, 0.0); const RayT ray(eye, dir); //std::cerr << ray << std::endl; Vec3T xyz(0); CPPUNIT_ASSERT(lsri.intersectsWS(ray, xyz)); ASSERT_DOUBLES_APPROX_EQUAL(15.0, xyz[0]); ASSERT_DOUBLES_APPROX_EQUAL( 0.0, xyz[1]); ASSERT_DOUBLES_APPROX_EQUAL( 0.0, xyz[2]); double t0=0, t1=0; CPPUNIT_ASSERT(ray.intersects(c, r, t0, t1)); //std::cerr << "\nray("< 0.01) { film.pixel(i, j) = tools::Film::RGBA(1.0f, 0.0f, 0.0f); } else { film.pixel(i, j) = tools::Film::RGBA(0.0f, 1.0f, 0.0f); } } } } timer.stop(); film.savePPM("/tmp/sphere_serial"); stats.print("First hit"); hist.print("First hit"); } } #endif // STATS_TEST #undef STATS_TEST // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/0000755000000000000000000000000012252453157012520 5ustar rootrootopenvdb/viewer/Font.cc0000644000000000000000000002627012252453157013744 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Font.h" #include // for OPENVDB_START_THREADSAFE_STATIC_WRITE #include namespace openvdb_viewer { GLuint BitmapFont13::sOffset = 0; GLubyte BitmapFont13::sCharacters[95][13] = { { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X18, 0X18, 0X00, 0X00, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18 }, { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X36, 0X36, 0X36, 0X36 }, { 0X00, 0X00, 0X00, 0X66, 0X66, 0XFF, 0X66, 0X66, 0XFF, 0X66, 0X66, 0X00, 0X00 }, { 0X00, 0X00, 0X18, 0X7E, 0XFF, 0X1B, 0X1F, 0X7E, 0XF8, 0XD8, 0XFF, 0X7E, 0X18 }, { 0X00, 0X00, 0X0E, 0X1B, 0XDB, 0X6E, 0X30, 0X18, 0X0C, 0X76, 0XDB, 0XD8, 0X70 }, { 0X00, 0X00, 0X7F, 0XC6, 0XCF, 0XD8, 0X70, 0X70, 0XD8, 0XCC, 0XCC, 0X6C, 0X38 }, { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X18, 0X1C, 0X0C, 0X0E }, { 0X00, 0X00, 0X0C, 0X18, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X18, 0X0C }, { 0X00, 0X00, 0X30, 0X18, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X18, 0X30 }, { 0X00, 0X00, 0X00, 0X00, 0X99, 0X5A, 0X3C, 0XFF, 0X3C, 0X5A, 0X99, 0X00, 0X00 }, { 0X00, 0X00, 0X00, 0X18, 0X18, 0X18, 0XFF, 0XFF, 0X18, 0X18, 0X18, 0X00, 0X00 }, { 0X00, 0X00, 0X30, 0X18, 0X1C, 0X1C, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X00, 0X38, 0X38, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X60, 0X60, 0X30, 0X30, 0X18, 0X18, 0X0C, 0X0C, 0X06, 0X06, 0X03, 0X03 }, { 0X00, 0X00, 0X3C, 0X66, 0XC3, 0XE3, 0XF3, 0XDB, 0XCF, 0XC7, 0XC3, 0X66, 0X3C }, { 0X00, 0X00, 0X7E, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X78, 0X38, 0X18 }, { 0X00, 0X00, 0XFF, 0XC0, 0XC0, 0X60, 0X30, 0X18, 0X0C, 0X06, 0X03, 0XE7, 0X7E }, { 0X00, 0X00, 0X7E, 0XE7, 0X03, 0X03, 0X07, 0X7E, 0X07, 0X03, 0X03, 0XE7, 0X7E }, { 0X00, 0X00, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0XFF, 0XCC, 0X6C, 0X3C, 0X1C, 0X0C }, { 0X00, 0X00, 0X7E, 0XE7, 0X03, 0X03, 0X07, 0XFE, 0XC0, 0XC0, 0XC0, 0XC0, 0XFF }, { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XC7, 0XFE, 0XC0, 0XC0, 0XC0, 0XE7, 0X7E }, { 0X00, 0X00, 0X30, 0X30, 0X30, 0X30, 0X18, 0X0C, 0X06, 0X03, 0X03, 0X03, 0XFF }, { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XE7, 0X7E, 0XE7, 0XC3, 0XC3, 0XE7, 0X7E }, { 0X00, 0X00, 0X7E, 0XE7, 0X03, 0X03, 0X03, 0X7F, 0XE7, 0XC3, 0XC3, 0XE7, 0X7E }, { 0X00, 0X00, 0X00, 0X38, 0X38, 0X00, 0X00, 0X38, 0X38, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X30, 0X18, 0X1C, 0X1C, 0X00, 0X00, 0X1C, 0X1C, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X06, 0X0C, 0X18, 0X30, 0X60, 0XC0, 0X60, 0X30, 0X18, 0X0C, 0X06 }, { 0X00, 0X00, 0X00, 0X00, 0XFF, 0XFF, 0X00, 0XFF, 0XFF, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X60, 0X30, 0X18, 0X0C, 0X06, 0X03, 0X06, 0X0C, 0X18, 0X30, 0X60 }, { 0X00, 0X00, 0X18, 0X00, 0X00, 0X18, 0X18, 0X0C, 0X06, 0X03, 0XC3, 0XC3, 0X7E }, { 0X00, 0X00, 0X3F, 0X60, 0XCF, 0XDB, 0XD3, 0XDD, 0XC3, 0X7E, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XC3, 0XC3, 0XC3, 0XC3, 0XFF, 0XC3, 0XC3, 0XC3, 0X66, 0X3C, 0X18 }, { 0X00, 0X00, 0XFE, 0XC7, 0XC3, 0XC3, 0XC7, 0XFE, 0XC7, 0XC3, 0XC3, 0XC7, 0XFE }, { 0X00, 0X00, 0X7E, 0XE7, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XE7, 0X7E }, { 0X00, 0X00, 0XFC, 0XCE, 0XC7, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC7, 0XCE, 0XFC }, { 0X00, 0X00, 0XFF, 0XC0, 0XC0, 0XC0, 0XC0, 0XFC, 0XC0, 0XC0, 0XC0, 0XC0, 0XFF }, { 0X00, 0X00, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XFC, 0XC0, 0XC0, 0XC0, 0XFF }, { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XCF, 0XC0, 0XC0, 0XC0, 0XC0, 0XE7, 0X7E }, { 0X00, 0X00, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XFF, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3 }, { 0X00, 0X00, 0X7E, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X7E }, { 0X00, 0X00, 0X7C, 0XEE, 0XC6, 0X06, 0X06, 0X06, 0X06, 0X06, 0X06, 0X06, 0X06 }, { 0X00, 0X00, 0XC3, 0XC6, 0XCC, 0XD8, 0XF0, 0XE0, 0XF0, 0XD8, 0XCC, 0XC6, 0XC3 }, { 0X00, 0X00, 0XFF, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0 }, { 0X00, 0X00, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XDB, 0XFF, 0XFF, 0XE7, 0XC3 }, { 0X00, 0X00, 0XC7, 0XC7, 0XCF, 0XCF, 0XDF, 0XDB, 0XFB, 0XF3, 0XF3, 0XE3, 0XE3 }, { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XE7, 0X7E }, { 0X00, 0X00, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XFE, 0XC7, 0XC3, 0XC3, 0XC7, 0XFE }, { 0X00, 0X00, 0X3F, 0X6E, 0XDF, 0XDB, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0X66, 0X3C }, { 0X00, 0X00, 0XC3, 0XC6, 0XCC, 0XD8, 0XF0, 0XFE, 0XC7, 0XC3, 0XC3, 0XC7, 0XFE }, { 0X00, 0X00, 0X7E, 0XE7, 0X03, 0X03, 0X07, 0X7E, 0XE0, 0XC0, 0XC0, 0XE7, 0X7E }, { 0X00, 0X00, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0XFF }, { 0X00, 0X00, 0X7E, 0XE7, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3 }, { 0X00, 0X00, 0X18, 0X3C, 0X3C, 0X66, 0X66, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3 }, { 0X00, 0X00, 0XC3, 0XE7, 0XFF, 0XFF, 0XDB, 0XDB, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3 }, { 0X00, 0X00, 0XC3, 0X66, 0X66, 0X3C, 0X3C, 0X18, 0X3C, 0X3C, 0X66, 0X66, 0XC3 }, { 0X00, 0X00, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X3C, 0X3C, 0X66, 0X66, 0XC3 }, { 0X00, 0X00, 0XFF, 0XC0, 0XC0, 0X60, 0X30, 0X7E, 0X0C, 0X06, 0X03, 0X03, 0XFF }, { 0X00, 0X00, 0X3C, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X30, 0X3C }, { 0X00, 0X03, 0X03, 0X06, 0X06, 0X0C, 0X0C, 0X18, 0X18, 0X30, 0X30, 0X60, 0X60 }, { 0X00, 0X00, 0X3C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X3C }, { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0XC3, 0X66, 0X3C, 0X18 }, { 0XFF, 0XFF, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X18, 0X38, 0X30, 0X70 }, { 0X00, 0X00, 0X7F, 0XC3, 0XC3, 0X7F, 0X03, 0XC3, 0X7E, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XFE, 0XC3, 0XC3, 0XC3, 0XC3, 0XFE, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0 }, { 0X00, 0X00, 0X7E, 0XC3, 0XC0, 0XC0, 0XC0, 0XC3, 0X7E, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X7F, 0XC3, 0XC3, 0XC3, 0XC3, 0X7F, 0X03, 0X03, 0X03, 0X03, 0X03 }, { 0X00, 0X00, 0X7F, 0XC0, 0XC0, 0XFE, 0XC3, 0XC3, 0X7E, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X30, 0X30, 0X30, 0X30, 0X30, 0XFC, 0X30, 0X30, 0X30, 0X33, 0X1E }, { 0X7E, 0XC3, 0X03, 0X03, 0X7F, 0XC3, 0XC3, 0XC3, 0X7E, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XC3, 0XFE, 0XC0, 0XC0, 0XC0, 0XC0 }, { 0X00, 0X00, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X00, 0X00, 0X18, 0X00 }, { 0X38, 0X6C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X0C, 0X00, 0X00, 0X0C, 0X00 }, { 0X00, 0X00, 0XC6, 0XCC, 0XF8, 0XF0, 0XD8, 0XCC, 0XC6, 0XC0, 0XC0, 0XC0, 0XC0 }, { 0X00, 0X00, 0X7E, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X78 }, { 0X00, 0X00, 0XDB, 0XDB, 0XDB, 0XDB, 0XDB, 0XDB, 0XFE, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0XFC, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X7C, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0X7C, 0X00, 0X00, 0X00, 0X00 }, { 0XC0, 0XC0, 0XC0, 0XFE, 0XC3, 0XC3, 0XC3, 0XC3, 0XFE, 0X00, 0X00, 0X00, 0X00 }, { 0X03, 0X03, 0X03, 0X7F, 0XC3, 0XC3, 0XC3, 0XC3, 0X7F, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XC0, 0XC0, 0XC0, 0XC0, 0XC0, 0XE0, 0XFE, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XFE, 0X03, 0X03, 0X7E, 0XC0, 0XC0, 0X7F, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X1C, 0X36, 0X30, 0X30, 0X30, 0X30, 0XFC, 0X30, 0X30, 0X30, 0X00 }, { 0X00, 0X00, 0X7E, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0XC6, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X18, 0X3C, 0X3C, 0X66, 0X66, 0XC3, 0XC3, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XC3, 0XE7, 0XFF, 0XDB, 0XC3, 0XC3, 0XC3, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XC3, 0X66, 0X3C, 0X18, 0X3C, 0X66, 0XC3, 0X00, 0X00, 0X00, 0X00 }, { 0XC0, 0X60, 0X60, 0X30, 0X18, 0X3C, 0X66, 0X66, 0XC3, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0XFF, 0X60, 0X30, 0X18, 0X0C, 0X06, 0XFF, 0X00, 0X00, 0X00, 0X00 }, { 0X00, 0X00, 0X0F, 0X18, 0X18, 0X18, 0X38, 0XF0, 0X38, 0X18, 0X18, 0X18, 0X0F }, { 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18, 0X18 }, { 0X00, 0X00, 0XF0, 0X18, 0X18, 0X18, 0X1C, 0X0F, 0X1C, 0X18, 0X18, 0X18, 0XF0 }, { 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X06, 0X8F, 0XF1, 0X60, 0X00, 0X00, 0X00 } }; // sCharacters void BitmapFont13::initialize() { OPENVDB_START_THREADSAFE_STATIC_WRITE glShadeModel(GL_FLAT); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); BitmapFont13::sOffset = glGenLists(128); for (GLuint c = 32; c < 127; ++c) { glNewList(c + BitmapFont13::sOffset, GL_COMPILE); glBitmap(8, 13, 0.0, 2.0, 10.0, 0.0, BitmapFont13::sCharacters[c-32]); glEndList(); } OPENVDB_FINISH_THREADSAFE_STATIC_WRITE } void BitmapFont13::enableFontRendering() { glPushMatrix(); int width, height; glfwGetWindowSize(&width, &height); height = height < 1 ? 1 : height; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho (0, width, 0, height, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); //glShadeModel(GL_FLAT); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); } void BitmapFont13::disableFontRendering() { glFlush(); glPopMatrix(); } void BitmapFont13::print(GLint px, GLint py, const std::string& str) { glRasterPos2i(px, py); glPushAttrib(GL_LIST_BIT); glListBase(BitmapFont13::sOffset); glCallLists(str.length(), GL_UNSIGNED_BYTE, reinterpret_cast(str.c_str())); glPopAttrib(); } } // namespace openvdb_viewer // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/ClipBox.h0000644000000000000000000000622712252453157014240 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_VIEWER_CLIPBOX_HAS_BEEN_INCLUDED #define OPENVDB_VIEWER_CLIPBOX_HAS_BEEN_INCLUDED #include #if defined(__APPLE__) || defined(MACOSX) #include #include #else #include #include #endif namespace openvdb_viewer { class ClipBox { public: ClipBox(); void enableClipping() const; void disableClipping() const; void setBBox(const openvdb::BBoxd&); void setStepSize(const openvdb::Vec3d& s) { mStepSize = s; } void render(); void update(double steps); void reset(); bool isActive() const { return (mXIsActive || mYIsActive ||mZIsActive); } bool& activateXPlanes() { return mXIsActive; } bool& activateYPlanes() { return mYIsActive; } bool& activateZPlanes() { return mZIsActive; } bool& shiftIsDown() { return mShiftIsDown; } bool& ctrlIsDown() { return mCtrlIsDown; } bool mouseButtonCallback(int button, int action); bool mousePosCallback(int x, int y); private: void update() const; openvdb::Vec3d mStepSize; openvdb::BBoxd mBBox; bool mXIsActive, mYIsActive, mZIsActive, mShiftIsDown, mCtrlIsDown; GLdouble mFrontPlane[4], mBackPlane[4], mLeftPlane[4], mRightPlane[4], mTopPlane[4], mBottomPlane[4]; double mMouseXPos, mMouseYPos; }; // class ClipBox } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_CLIPBOX_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/RenderModules.cc0000644000000000000000000004030412252453157015600 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "RenderModules.h" #include #include namespace openvdb_viewer { // BufferObject BufferObject::BufferObject(): mVertexBuffer(0), mNormalBuffer(0), mIndexBuffer(0), mColorBuffer(0), mPrimType(GL_POINTS), mPrimNum(0) { } BufferObject::~BufferObject() { clear(); } void BufferObject::render() const { if (mPrimNum == 0 || !glIsBuffer(mIndexBuffer) || !glIsBuffer(mVertexBuffer)) { OPENVDB_LOG_DEBUG_RUNTIME("request to render empty or uninitialized buffer"); return; } const bool usesColorBuffer = glIsBuffer(mColorBuffer); const bool usesNormalBuffer = glIsBuffer(mNormalBuffer); glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, 0); if (usesColorBuffer) { glBindBuffer(GL_ARRAY_BUFFER, mColorBuffer); glEnableClientState(GL_COLOR_ARRAY); glColorPointer(3, GL_FLOAT, 0, 0); } if (usesNormalBuffer) { glEnableClientState(GL_NORMAL_ARRAY); glBindBuffer(GL_ARRAY_BUFFER, mNormalBuffer); glNormalPointer(GL_FLOAT, 0, 0); } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); glDrawElements(mPrimType, mPrimNum, GL_UNSIGNED_INT, 0); // disable client-side capabilities if (usesColorBuffer) glDisableClientState(GL_COLOR_ARRAY); if (usesNormalBuffer) glDisableClientState(GL_NORMAL_ARRAY); // release vbo's glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } void BufferObject::genIndexBuffer(const std::vector& v, GLenum primType) { // clear old buffer if (glIsBuffer(mIndexBuffer) == GL_TRUE) glDeleteBuffers(1, &mIndexBuffer); // gen new buffer glGenBuffers(1, &mIndexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); if (glIsBuffer(mIndexBuffer) == GL_FALSE) throw "Error: Unable to create index buffer"; // upload data glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * v.size(), &v[0], GL_STATIC_DRAW); // upload data if (GL_NO_ERROR != glGetError()) throw "Error: Unable to upload index buffer data"; // release buffer glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); mPrimNum = v.size(); mPrimType = primType; } void BufferObject::genVertexBuffer(const std::vector& v) { if (glIsBuffer(mVertexBuffer) == GL_TRUE) glDeleteBuffers(1, &mVertexBuffer); glGenBuffers(1, &mVertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); if (glIsBuffer(mVertexBuffer) == GL_FALSE) throw "Error: Unable to create vertex buffer"; glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * v.size(), &v[0], GL_STATIC_DRAW); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to upload vertex buffer data"; glBindBuffer(GL_ARRAY_BUFFER, 0); } void BufferObject::genNormalBuffer(const std::vector& v) { if (glIsBuffer(mNormalBuffer) == GL_TRUE) glDeleteBuffers(1, &mNormalBuffer); glGenBuffers(1, &mNormalBuffer); glBindBuffer(GL_ARRAY_BUFFER, mNormalBuffer); if (glIsBuffer(mNormalBuffer) == GL_FALSE) throw "Error: Unable to create normal buffer"; glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * v.size(), &v[0], GL_STATIC_DRAW); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to upload normal buffer data"; glBindBuffer(GL_ARRAY_BUFFER, 0); } void BufferObject::genColorBuffer(const std::vector& v) { if (glIsBuffer(mColorBuffer) == GL_TRUE) glDeleteBuffers(1, &mColorBuffer); glGenBuffers(1, &mColorBuffer); glBindBuffer(GL_ARRAY_BUFFER, mColorBuffer); if (glIsBuffer(mColorBuffer) == GL_FALSE) throw "Error: Unable to create color buffer"; glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * v.size(), &v[0], GL_STATIC_DRAW); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to upload color buffer data"; glBindBuffer(GL_ARRAY_BUFFER, 0); } void BufferObject::clear() { if (glIsBuffer(mIndexBuffer) == GL_TRUE) glDeleteBuffers(1, &mIndexBuffer); if (glIsBuffer(mVertexBuffer) == GL_TRUE) glDeleteBuffers(1, &mVertexBuffer); if (glIsBuffer(mColorBuffer) == GL_TRUE) glDeleteBuffers(1, &mColorBuffer); if (glIsBuffer(mNormalBuffer) == GL_TRUE) glDeleteBuffers(1, &mNormalBuffer); mPrimType = GL_POINTS; mPrimNum = 0; } //////////////////////////////////////// ShaderProgram::ShaderProgram(): mProgram(0), mVertShader(0), mFragShader(0) { } ShaderProgram::~ShaderProgram() { clear(); } void ShaderProgram::setVertShader(const std::string& s) { mVertShader = glCreateShader(GL_VERTEX_SHADER); if (glIsShader(mVertShader) == GL_FALSE) throw "Error: Unable to create shader program."; GLint length = s.length(); const char *str = s.c_str(); glShaderSource(mVertShader, 1, &str, &length); glCompileShader(mVertShader); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to compile vertex shader."; } void ShaderProgram::setFragShader(const std::string& s) { mFragShader = glCreateShader(GL_FRAGMENT_SHADER); if (glIsShader(mFragShader) == GL_FALSE) throw "Error: Unable to create shader program."; GLint length = s.length(); const char *str = s.c_str(); glShaderSource(mFragShader, 1, &str, &length); glCompileShader(mFragShader); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to compile fragment shader."; } void ShaderProgram::build() { mProgram = glCreateProgram(); if (glIsProgram(mProgram) == GL_FALSE) throw "Error: Unable to create shader program."; if (glIsShader(mVertShader) == GL_TRUE) glAttachShader(mProgram, mVertShader); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to attach vertex shader."; if (glIsShader(mFragShader) == GL_TRUE) glAttachShader(mProgram, mFragShader); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to attach fragment shader."; glLinkProgram(mProgram); GLint linked; glGetProgramiv(mProgram, GL_LINK_STATUS, &linked); if (!linked) throw "Error: Unable to link shader program."; } void ShaderProgram::build(const std::vector& attributes) { mProgram = glCreateProgram(); if (glIsProgram(mProgram) == GL_FALSE) throw "Error: Unable to create shader program."; for (GLuint n = 0, N = attributes.size(); n < N; ++n) { glBindAttribLocation(mProgram, n, attributes[n]); } if (glIsShader(mVertShader) == GL_TRUE) glAttachShader(mProgram, mVertShader); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to attach vertex shader."; if (glIsShader(mFragShader) == GL_TRUE) glAttachShader(mProgram, mFragShader); if (GL_NO_ERROR != glGetError()) throw "Error: Unable to attach fragment shader."; glLinkProgram(mProgram); GLint linked; glGetProgramiv(mProgram, GL_LINK_STATUS, &linked); if (!linked) throw "Error: Unable to link shader program."; } void ShaderProgram::startShading() const { if (glIsProgram(mProgram) == GL_FALSE) throw "Error: called startShading() on uncompiled shader program."; glUseProgram(mProgram); } void ShaderProgram::stopShading() const { glUseProgram(0); } void ShaderProgram::clear() { GLsizei numShaders; GLuint shaders[2]; glGetAttachedShaders(mProgram, 2, &numShaders, shaders); // detach and remove shaders for (GLsizei n = 0; n < numShaders; ++n) { glDetachShader(mProgram, shaders[n]); if (glIsShader(shaders[n]) == GL_TRUE) glDeleteShader(shaders[n]); } // remove program if (glIsProgram(mProgram)) glDeleteProgram(mProgram); } //////////////////////////////////////// openvdb::Vec3s TreeTopologyOp::sNodeColors[] = { openvdb::Vec3s(0.045, 0.045, 0.045), // root openvdb::Vec3s(0.0432, 0.33, 0.0411023), // first internal node level openvdb::Vec3s(0.871, 0.394, 0.01916), // intermediate internal node levels openvdb::Vec3s(0.00608299, 0.279541, 0.625) // leaf nodes }; //////////////////////////////////////// // ViewportModule ViewportModule::ViewportModule(): mAxisGnomonScale(1.5), mGroundPlaneScale(8.0) { } void ViewportModule::render() { if (!mIsVisible) return; /// @todo use VBO's // Ground plane glPushMatrix(); glScalef(mGroundPlaneScale, mGroundPlaneScale, mGroundPlaneScale); glColor3f( 0.6, 0.6, 0.6); OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN float step = 0.125; for (float x = -1; x < 1.125; x+=step) { if (fabs(x) == 0.5 || fabs(x) == 0.0) { glLineWidth(1.5); } else { glLineWidth(1.0); } glBegin( GL_LINES ); glVertex3f(x, 0, 1); glVertex3f(x, 0, -1); glVertex3f(1, 0, x); glVertex3f(-1, 0, x); glEnd(); } OPENVDB_NO_FP_EQUALITY_WARNING_END glPopMatrix(); // Axis gnomon GLfloat modelview[16]; glGetFloatv(GL_MODELVIEW_MATRIX, &modelview[0]); // Stash current viewport settigs. GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, &viewport[0]); GLint width = viewport[2] / 20; GLint height = viewport[3] / 20; glViewport(0, 0, width, height); glPushMatrix(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); GLfloat campos[3] = { modelview[2], modelview[6], modelview[10] }; GLfloat up[3] = { modelview[1], modelview[5], modelview[9] }; gluLookAt(campos[0], campos[1], campos[2], 0.0, 0.0, 0.0, up[0], up[1], up[2]); glScalef(mAxisGnomonScale, mAxisGnomonScale, mAxisGnomonScale); glLineWidth(1.0); glBegin(GL_LINES); glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(0, 0, 0); glVertex3f(1, 0, 0); glColor3f(0.0f, 1.0f, 0.0f ); glVertex3f(0, 0, 0); glVertex3f(0, 1, 0); glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(0, 0, 0); glVertex3f(0, 0, 1); glEnd(); glLineWidth(1.0); // reset viewport glPopMatrix(); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); } //////////////////////////////////////// // Tree topology render module TreeTopologyModule::TreeTopologyModule(const openvdb::GridBase::ConstPtr& grid): mGrid(grid), mIsInitialized(false) { mShader.setVertShader( "#version 120\n" "void main() {\n" "gl_FrontColor = gl_Color;\n" "gl_Position = ftransform();\n" "gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" "}\n"); mShader.setFragShader( "#version 120\n" "void main() {\n" "gl_FragColor = gl_Color;}\n"); mShader.build(); } void TreeTopologyModule::init() { mIsInitialized = true; // extract grid topology TreeTopologyOp drawTopology(mBufferObject); if (!util::processTypedGrid(mGrid, drawTopology)) { OPENVDB_LOG_INFO("Ignoring unrecognized grid type" " during tree topology module initialization."); } } void TreeTopologyModule::render() { if (!mIsVisible) return; if (!mIsInitialized) init(); mShader.startShading(); mBufferObject.render(); mShader.stopShading(); } //////////////////////////////////////// // Active value render module ActiveValueModule::ActiveValueModule(const openvdb::GridBase::ConstPtr& grid): mGrid(grid), mIsInitialized(false) { mFlatShader.setVertShader( "#version 120\n" "void main() {\n" "gl_FrontColor = gl_Color;\n" "gl_Position = ftransform();\n" "gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" "}\n"); mFlatShader.setFragShader( "#version 120\n" "void main() {\n" "gl_FragColor = gl_Color;}\n"); mFlatShader.build(); mSurfaceShader.setVertShader( "#version 120\n" "varying vec3 normal;\n" "void main() {\n" "gl_FrontColor = gl_Color;\n" "normal = normalize(gl_NormalMatrix * gl_Normal);\n" "gl_Position = ftransform();\n" "gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" "}\n"); mSurfaceShader.setFragShader( "#version 120\n" "varying vec3 normal;\n" "void main() {\n" "vec3 normalized_normal = normalize(normal);\n" "float w = 0.5 * (1.0 + dot(normalized_normal, vec3(0.0, 1.0, 0.0)));\n" "vec4 diffuseColor = w * gl_Color + (1.0 - w) * (gl_Color * 0.3);\n" "gl_FragColor = diffuseColor;\n" "}\n"); mSurfaceShader.build(); } void ActiveValueModule::init() { mIsInitialized = true; ActiveScalarValuesOp drawScalars(mInteriorBuffer, mSurfaceBuffer); if (!util::processTypedScalarGrid(mGrid, drawScalars)) { ActiveVectorValuesOp drawVectors(mVectorBuffer); if(!util::processTypedVectorGrid(mGrid, drawVectors)) { OPENVDB_LOG_INFO("Ignoring unrecognized grid type" " during active value module initialization."); } } } void ActiveValueModule::render() { if (!mIsVisible) return; if (!mIsInitialized) init(); mFlatShader.startShading(); mInteriorBuffer.render(); mVectorBuffer.render(); mFlatShader.stopShading(); mSurfaceShader.startShading(); mSurfaceBuffer.render(); mSurfaceShader.stopShading(); } //////////////////////////////////////// // Meshing module MeshModule::MeshModule(const openvdb::GridBase::ConstPtr& grid): mGrid(grid), mIsInitialized(false) { mShader.setVertShader( "#version 120\n" "varying vec3 normal;\n" "void main() {\n" "normal = normalize(gl_NormalMatrix * gl_Normal);\n" "gl_Position = ftransform();\n" "gl_ClipVertex = gl_ModelViewMatrix * gl_Vertex;\n" "}\n"); mShader.setFragShader( "#version 120\n" "varying vec3 normal;\n" "const vec4 skyColor = vec4(0.9, 0.9, 1.0, 1.0);\n" "const vec4 groundColor = vec4(0.3, 0.3, 0.2, 1.0);\n" "void main() {\n" "vec3 normalized_normal = normalize(normal);\n" "float w = 0.5 * (1.0 + dot(normalized_normal, vec3(0.0, 1.0, 0.0)));\n" "vec4 diffuseColor = w * skyColor + (1.0 - w) * groundColor;\n" "gl_FragColor = diffuseColor;\n" "}\n"); mShader.build(); } void MeshModule::init() { mIsInitialized = true; MeshOp drawMesh(mBufferObject); if (!util::processTypedScalarGrid(mGrid, drawMesh)) { OPENVDB_LOG_INFO( "Ignoring non-scalar grid type during mesh module initialization."); } } void MeshModule::render() { if (!mIsVisible) return; if (!mIsInitialized) init(); mShader.startShading(); mBufferObject.render(); mShader.stopShading(); } } // namespace openvdb_viewer // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/ClipBox.cc0000644000000000000000000002334612252453157014377 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "ClipBox.h" #include #include namespace openvdb_viewer { ClipBox::ClipBox() : mStepSize(1.0) , mBBox() , mXIsActive(false) , mYIsActive(false) , mZIsActive(false) , mShiftIsDown(false) , mCtrlIsDown(false) { GLdouble front [] = { 0.0, 0.0, 1.0, 0.0}; std::copy(front, front + 4, mFrontPlane); GLdouble back [] = { 0.0, 0.0,-1.0, 0.0}; std::copy(back, back + 4, mBackPlane); GLdouble left [] = { 1.0, 0.0, 0.0, 0.0}; std::copy(left, left + 4, mLeftPlane); GLdouble right [] = {-1.0, 0.0, 0.0, 0.0}; std::copy(right, right + 4, mRightPlane); GLdouble top [] = { 0.0, 1.0, 0.0, 0.0}; std::copy(top, top + 4, mTopPlane); GLdouble bottom [] = { 0.0,-1.0, 0.0, 0.0}; std::copy(bottom, bottom + 4, mBottomPlane); } void ClipBox::setBBox(const openvdb::BBoxd& bbox) { mBBox = bbox; reset(); } void ClipBox::update(double steps) { if (mXIsActive) { GLdouble s = steps * mStepSize.x() * 4.0; if (mShiftIsDown || mCtrlIsDown) { mLeftPlane[3] -= s; mLeftPlane[3] = -std::min(-mLeftPlane[3], (mRightPlane[3] - mStepSize.x())); mLeftPlane[3] = -std::max(-mLeftPlane[3], mBBox.min().x()); } if (!mShiftIsDown || mCtrlIsDown) { mRightPlane[3] += s; mRightPlane[3] = std::min(mRightPlane[3], mBBox.max().x()); mRightPlane[3] = std::max(mRightPlane[3], (-mLeftPlane[3] + mStepSize.x())); } } if (mYIsActive) { GLdouble s = steps * mStepSize.y() * 4.0; if (mShiftIsDown || mCtrlIsDown) { mTopPlane[3] -= s; mTopPlane[3] = -std::min(-mTopPlane[3], (mBottomPlane[3] - mStepSize.y())); mTopPlane[3] = -std::max(-mTopPlane[3], mBBox.min().y()); } if (!mShiftIsDown || mCtrlIsDown) { mBottomPlane[3] += s; mBottomPlane[3] = std::min(mBottomPlane[3], mBBox.max().y()); mBottomPlane[3] = std::max(mBottomPlane[3], (-mTopPlane[3] + mStepSize.y())); } } if (mZIsActive) { GLdouble s = steps * mStepSize.z() * 4.0; if (mShiftIsDown || mCtrlIsDown) { mFrontPlane[3] -= s; mFrontPlane[3] = -std::min(-mFrontPlane[3], (mBackPlane[3] - mStepSize.z())); mFrontPlane[3] = -std::max(-mFrontPlane[3], mBBox.min().z()); } if (!mShiftIsDown || mCtrlIsDown) { mBackPlane[3] += s; mBackPlane[3] = std::min(mBackPlane[3], mBBox.max().z()); mBackPlane[3] = std::max(mBackPlane[3], (-mFrontPlane[3] + mStepSize.z())); } } } void ClipBox::reset() { mFrontPlane[3] = std::abs(mBBox.min().z()); mBackPlane[3] = mBBox.max().z(); mLeftPlane[3] = std::abs(mBBox.min().x()); mRightPlane[3] = mBBox.max().x(); mTopPlane[3] = std::abs(mBBox.min().y()); mBottomPlane[3] = mBBox.max().y(); } void ClipBox::update() const { glClipPlane(GL_CLIP_PLANE0, mFrontPlane); glClipPlane(GL_CLIP_PLANE1, mBackPlane); glClipPlane(GL_CLIP_PLANE2, mLeftPlane); glClipPlane(GL_CLIP_PLANE3, mRightPlane); glClipPlane(GL_CLIP_PLANE4, mTopPlane); glClipPlane(GL_CLIP_PLANE5, mBottomPlane); } void ClipBox::enableClipping() const { update(); if (-mFrontPlane[3] > mBBox.min().z()) glEnable(GL_CLIP_PLANE0); if (mBackPlane[3] < mBBox.max().z()) glEnable(GL_CLIP_PLANE1); if (-mLeftPlane[3] > mBBox.min().x()) glEnable(GL_CLIP_PLANE2); if (mRightPlane[3] < mBBox.max().x()) glEnable(GL_CLIP_PLANE3); if (-mTopPlane[3] > mBBox.min().y()) glEnable(GL_CLIP_PLANE4); if (mBottomPlane[3] < mBBox.max().y()) glEnable(GL_CLIP_PLANE5); } void ClipBox::disableClipping() const { glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); glDisable(GL_CLIP_PLANE2); glDisable(GL_CLIP_PLANE3); glDisable(GL_CLIP_PLANE4); glDisable(GL_CLIP_PLANE5); } void ClipBox::render() { bool drawBbox = false; const GLenum geoMode = GL_LINE_LOOP; glColor3f(0.1, 0.1, 0.9); if (-mFrontPlane[3] > mBBox.min().z()) { glBegin(geoMode); glVertex3f(mBBox.min().x(), mBBox.min().y(), -mFrontPlane[3]); glVertex3f(mBBox.min().x(), mBBox.max().y(), -mFrontPlane[3]); glVertex3f(mBBox.max().x(), mBBox.max().y(), -mFrontPlane[3]); glVertex3f(mBBox.max().x(), mBBox.min().y(), -mFrontPlane[3]); glEnd(); drawBbox = true; } if (mBackPlane[3] < mBBox.max().z()) { glBegin(geoMode); glVertex3f(mBBox.min().x(), mBBox.min().y(), mBackPlane[3]); glVertex3f(mBBox.min().x(), mBBox.max().y(), mBackPlane[3]); glVertex3f(mBBox.max().x(), mBBox.max().y(), mBackPlane[3]); glVertex3f(mBBox.max().x(), mBBox.min().y(), mBackPlane[3]); glEnd(); drawBbox = true; } glColor3f(0.9, 0.1, 0.1); if (-mLeftPlane[3] > mBBox.min().x()) { glBegin(geoMode); glVertex3f(-mLeftPlane[3], mBBox.min().y(), mBBox.min().z()); glVertex3f(-mLeftPlane[3], mBBox.max().y(), mBBox.min().z()); glVertex3f(-mLeftPlane[3], mBBox.max().y(), mBBox.max().z()); glVertex3f(-mLeftPlane[3], mBBox.min().y(), mBBox.max().z()); glEnd(); drawBbox = true; } if (mRightPlane[3] < mBBox.max().x()) { glBegin(geoMode); glVertex3f(mRightPlane[3], mBBox.min().y(), mBBox.min().z()); glVertex3f(mRightPlane[3], mBBox.max().y(), mBBox.min().z()); glVertex3f(mRightPlane[3], mBBox.max().y(), mBBox.max().z()); glVertex3f(mRightPlane[3], mBBox.min().y(), mBBox.max().z()); glEnd(); drawBbox = true; } glColor3f(0.1, 0.9, 0.1); if (-mTopPlane[3] > mBBox.min().y()) { glBegin(geoMode); glVertex3f(mBBox.min().x(), -mTopPlane[3], mBBox.min().z()); glVertex3f(mBBox.min().x(), -mTopPlane[3], mBBox.max().z()); glVertex3f(mBBox.max().x(), -mTopPlane[3], mBBox.max().z()); glVertex3f(mBBox.max().x(), -mTopPlane[3], mBBox.min().z()); glEnd(); drawBbox = true; } if (mBottomPlane[3] < mBBox.max().y()) { glBegin(geoMode); glVertex3f(mBBox.min().x(), mBottomPlane[3], mBBox.min().z()); glVertex3f(mBBox.min().x(), mBottomPlane[3], mBBox.max().z()); glVertex3f(mBBox.max().x(), mBottomPlane[3], mBBox.max().z()); glVertex3f(mBBox.max().x(), mBottomPlane[3], mBBox.min().z()); glEnd(); drawBbox = true; } if (drawBbox) { glColor3f(0.5, 0.5, 0.5); glBegin(GL_LINE_LOOP); glVertex3f(mBBox.min().x(), mBBox.min().y(), mBBox.min().z()); glVertex3f(mBBox.min().x(), mBBox.min().y(), mBBox.max().z()); glVertex3f(mBBox.max().x(), mBBox.min().y(), mBBox.max().z()); glVertex3f(mBBox.max().x(), mBBox.min().y(), mBBox.min().z()); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(mBBox.min().x(), mBBox.max().y(), mBBox.min().z()); glVertex3f(mBBox.min().x(), mBBox.max().y(), mBBox.max().z()); glVertex3f(mBBox.max().x(), mBBox.max().y(), mBBox.max().z()); glVertex3f(mBBox.max().x(), mBBox.max().y(), mBBox.min().z()); glEnd(); glBegin(GL_LINES); glVertex3f(mBBox.min().x(), mBBox.min().y(), mBBox.min().z()); glVertex3f(mBBox.min().x(), mBBox.max().y(), mBBox.min().z()); glVertex3f(mBBox.min().x(), mBBox.min().y(), mBBox.max().z()); glVertex3f(mBBox.min().x(), mBBox.max().y(), mBBox.max().z()); glVertex3f(mBBox.max().x(), mBBox.min().y(), mBBox.max().z()); glVertex3f(mBBox.max().x(), mBBox.max().y(), mBBox.max().z()); glVertex3f(mBBox.max().x(), mBBox.min().y(), mBBox.min().z()); glVertex3f(mBBox.max().x(), mBBox.max().y(), mBBox.min().z()); glEnd(); } } //////////////////////////////////////// bool ClipBox::mouseButtonCallback(int /*button*/, int /*action*/) { return false; // unhandled } bool ClipBox::mousePosCallback(int /*x*/, int /*y*/) { return false; // unhandled } } // namespace openvdb_viewer // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Font.h0000644000000000000000000000464112252453157013604 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_VIEWER_FONT_HAS_BEEN_INCLUDED #define OPENVDB_VIEWER_FONT_HAS_BEEN_INCLUDED #include #if defined(__APPLE__) || defined(MACOSX) #include #include #else #include #include #endif namespace openvdb_viewer { class BitmapFont13 { public: BitmapFont13() {} static void initialize(); static void enableFontRendering(); static void disableFontRendering(); static void print(GLint px, GLint py, const std::string&); private: static GLuint sOffset; static GLubyte sCharacters[95][13]; }; } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_FONT_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/RenderModules.h0000644000000000000000000010244412252453157015446 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_VIEWER_RENDERMODULES_HAS_BEEN_INCLUDED #define OPENVDB_VIEWER_RENDERMODULES_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #if defined(__APPLE__) || defined(MACOSX) #include #include #else #include #include #endif namespace openvdb_viewer { // OpenGL helper objects. class BufferObject { public: BufferObject(); ~BufferObject(); void render() const; /// @note accepted @c primType: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, /// GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, /// GL_QUAD_STRIP, GL_QUADS and GL_POLYGON void genIndexBuffer(const std::vector&, GLenum primType); void genVertexBuffer(const std::vector&); void genNormalBuffer(const std::vector&); void genColorBuffer(const std::vector&); void clear(); private: GLuint mVertexBuffer, mNormalBuffer, mIndexBuffer, mColorBuffer; GLenum mPrimType; GLsizei mPrimNum; }; class ShaderProgram { public: ShaderProgram(); ~ShaderProgram(); void setVertShader(const std::string&); void setFragShader(const std::string&); void build(); void build(const std::vector& attributes); void startShading() const; void stopShading() const; void clear(); private: GLuint mProgram, mVertShader, mFragShader; }; //////////////////////////////////////// /// @brief interface class. class RenderModule { public: virtual void render() = 0; virtual ~RenderModule() {} virtual bool& visible() { return mIsVisible; } protected: RenderModule() : mIsVisible(true) {} bool mIsVisible; }; //////////////////////////////////////// /// @brief Basic render module, axis gnomon and ground plane. class ViewportModule: public RenderModule { public: ViewportModule(); void render(); private: float mAxisGnomonScale, mGroundPlaneScale; }; //////////////////////////////////////// /// @brief Tree topology render module class TreeTopologyModule: public RenderModule { public: TreeTopologyModule(const openvdb::GridBase::ConstPtr&); ~TreeTopologyModule() {} void render(); private: void init(); const openvdb::GridBase::ConstPtr& mGrid; BufferObject mBufferObject; bool mIsInitialized; ShaderProgram mShader; }; //////////////////////////////////////// /// @brief Tree topology render module class ActiveValueModule: public RenderModule { public: ActiveValueModule(const openvdb::GridBase::ConstPtr&); ~ActiveValueModule() {} void render(); private: void init(); const openvdb::GridBase::ConstPtr& mGrid; BufferObject mInteriorBuffer, mSurfaceBuffer, mVectorBuffer; bool mIsInitialized; ShaderProgram mFlatShader, mSurfaceShader; }; //////////////////////////////////////// /// @brief Surfacing render module class MeshModule: public RenderModule { public: MeshModule(const openvdb::GridBase::ConstPtr&); ~MeshModule() {} void render(); private: void init(); const openvdb::GridBase::ConstPtr& mGrid; BufferObject mBufferObject; bool mIsInitialized; ShaderProgram mShader; }; //////////////////////////////////////// class TreeTopologyOp { public: TreeTopologyOp(BufferObject& buffer) : mBuffer(&buffer) {} template void operator()(typename GridType::ConstPtr grid) { using openvdb::Index64; Index64 nodeCount = grid->tree().leafCount() + grid->tree().nonLeafCount(); const Index64 N = nodeCount * 8 * 3; std::vector points(N); std::vector colors(N); std::vector indices(N); openvdb::Vec3d ptn; openvdb::Vec3s color; openvdb::CoordBBox bbox; Index64 pOffset = 0, iOffset = 0, cOffset = 0, idx = 0; for (typename GridType::TreeType::NodeCIter iter = grid->tree().cbeginNode(); iter; ++iter) { iter.getBoundingBox(bbox); // Nodes are rendered as cell-centered const openvdb::Vec3d min(bbox.min().x()-0.5, bbox.min().y()-0.5, bbox.min().z()-0.5); const openvdb::Vec3d max(bbox.max().x()+0.5, bbox.max().y()+0.5, bbox.max().z()+0.5); // corner 1 ptn = grid->indexToWorld(min); points[pOffset++] = ptn[0]; points[pOffset++] = ptn[1]; points[pOffset++] = ptn[2]; // corner 2 ptn = openvdb::Vec3d(min.x(), min.y(), max.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = ptn[0]; points[pOffset++] = ptn[1]; points[pOffset++] = ptn[2]; // corner 3 ptn = openvdb::Vec3d(max.x(), min.y(), max.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = ptn[0]; points[pOffset++] = ptn[1]; points[pOffset++] = ptn[2]; // corner 4 ptn = openvdb::Vec3d(max.x(), min.y(), min.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = ptn[0]; points[pOffset++] = ptn[1]; points[pOffset++] = ptn[2]; // corner 5 ptn = openvdb::Vec3d(min.x(), max.y(), min.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = ptn[0]; points[pOffset++] = ptn[1]; points[pOffset++] = ptn[2]; // corner 6 ptn = openvdb::Vec3d(min.x(), max.y(), max.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = ptn[0]; points[pOffset++] = ptn[1]; points[pOffset++] = ptn[2]; // corner 7 ptn = grid->indexToWorld(max); points[pOffset++] = ptn[0]; points[pOffset++] = ptn[1]; points[pOffset++] = ptn[2]; // corner 8 ptn = openvdb::Vec3d(max.x(), max.y(), min.z()); ptn = grid->indexToWorld(ptn); points[pOffset++] = ptn[0]; points[pOffset++] = ptn[1]; points[pOffset++] = ptn[2]; // edge 1 indices[iOffset++] = idx; indices[iOffset++] = idx + 1; // edge 2 indices[iOffset++] = idx + 1; indices[iOffset++] = idx + 2; // edge 3 indices[iOffset++] = idx + 2; indices[iOffset++] = idx + 3; // edge 4 indices[iOffset++] = idx + 3; indices[iOffset++] = idx; // edge 5 indices[iOffset++] = idx + 4; indices[iOffset++] = idx + 5; // edge 6 indices[iOffset++] = idx + 5; indices[iOffset++] = idx + 6; // edge 7 indices[iOffset++] = idx + 6; indices[iOffset++] = idx + 7; // edge 8 indices[iOffset++] = idx + 7; indices[iOffset++] = idx + 4; // edge 9 indices[iOffset++] = idx; indices[iOffset++] = idx + 4; // edge 10 indices[iOffset++] = idx + 1; indices[iOffset++] = idx + 5; // edge 11 indices[iOffset++] = idx + 2; indices[iOffset++] = idx + 6; // edge 12 indices[iOffset++] = idx + 3; indices[iOffset++] = idx + 7; // node vertex color const int level = iter.getLevel(); color = sNodeColors[(level == 0) ? 3 : (level == 1) ? 2 : 1]; for (Index64 n = 0; n < 8; ++n) { colors[cOffset++] = color[0]; colors[cOffset++] = color[1]; colors[cOffset++] = color[2]; } idx += 8; } // end node iteration // gen buffers and upload data to GPU mBuffer->genVertexBuffer(points); mBuffer->genColorBuffer(colors); mBuffer->genIndexBuffer(indices, GL_LINES); } private: BufferObject *mBuffer; static openvdb::Vec3s sNodeColors[]; }; // TreeTopologyOp //////////////////////////////////////// template class PointGenerator { public: typedef openvdb::tree::LeafManager LeafManagerType; PointGenerator( std::vector& points, std::vector& indices, LeafManagerType& leafs, std::vector& indexMap, const openvdb::math::Transform& transform, openvdb::Index64 voxelsPerLeaf = TreeType::LeafNodeType::NUM_VOXELS) : mPoints(points) , mIndices(indices) , mLeafs(leafs) , mIndexMap(indexMap) , mTransform(transform) , mVoxelsPerLeaf(voxelsPerLeaf) { } void runParallel() { tbb::parallel_for(mLeafs.getRange(), *this); } inline void operator()(const typename LeafManagerType::RangeType& range) const { using openvdb::Index64; typedef typename TreeType::LeafNodeType::ValueOnCIter ValueOnCIter; openvdb::Vec3d pos; size_t index = 0; Index64 activeVoxels = 0; for (size_t n = range.begin(); n < range.end(); ++n) { index = mIndexMap[n]; ValueOnCIter it = mLeafs.leaf(n).cbeginValueOn(); activeVoxels = mLeafs.leaf(n).onVoxelCount(); if (activeVoxels <= mVoxelsPerLeaf) { for ( ; it; ++it) { pos = mTransform.indexToWorld(it.getCoord()); insertPoint(pos, index); ++index; } } else if (1 == mVoxelsPerLeaf) { pos = mTransform.indexToWorld(it.getCoord()); insertPoint(pos, index); } else { std::vector coords; coords.reserve(static_cast(activeVoxels)); for ( ; it; ++it) { coords.push_back(it.getCoord()); } pos = mTransform.indexToWorld(coords[0]); insertPoint(pos, index); ++index; pos = mTransform.indexToWorld(coords[static_cast(activeVoxels-1)]); insertPoint(pos, index); ++index; Index64 r = Index64(std::floor(double(mVoxelsPerLeaf) / activeVoxels)); for (Index64 i = 1, I = mVoxelsPerLeaf - 2; i < I; ++i) { pos = mTransform.indexToWorld(coords[static_cast(i * r)]); insertPoint(pos, index); ++index; } } } } private: void insertPoint(const openvdb::Vec3d& pos, size_t index) const { mIndices[index] = index; const size_t element = index * 3; mPoints[element ] = pos[0]; mPoints[element + 1] = pos[1]; mPoints[element + 2] = pos[2]; } std::vector& mPoints; std::vector& mIndices; LeafManagerType& mLeafs; std::vector& mIndexMap; const openvdb::math::Transform& mTransform; const openvdb::Index64 mVoxelsPerLeaf; }; // PointGenerator template class PointAttributeGenerator { public: typedef typename GridType::ValueType ValueType; PointAttributeGenerator( std::vector& points, std::vector& colors, const GridType& grid, ValueType minValue, ValueType maxValue, openvdb::Vec3s (&colorMap)[4], bool isLevelSet = false) : mPoints(points) , mColors(colors) , mNormals(NULL) , mGrid(grid) , mAccessor(grid.tree()) , mMinValue(minValue) , mMaxValue(maxValue) , mColorMap(colorMap) , mIsLevelSet(isLevelSet) , mZeroValue(openvdb::zeroVal()) { init(); } PointAttributeGenerator( std::vector& points, std::vector& colors, std::vector& normals, const GridType& grid, ValueType minValue, ValueType maxValue, openvdb::Vec3s (&colorMap)[4], bool isLevelSet = false) : mPoints(points) , mColors(colors) , mNormals(&normals) , mGrid(grid) , mAccessor(grid.tree()) , mMinValue(minValue) , mMaxValue(maxValue) , mColorMap(colorMap) , mIsLevelSet(isLevelSet) , mZeroValue(openvdb::zeroVal()) { init(); } void runParallel() { tbb::parallel_for(tbb::blocked_range(0, (mPoints.size() / 3)), *this); } inline void operator()(const tbb::blocked_range& range) const { openvdb::Coord ijk; openvdb::Vec3d pos, tmpNormal, normal(0.0, -1.0, 0.0); openvdb::Vec3s color(0.9, 0.3, 0.3); float w = 0.0; size_t e1, e2, e3, voxelNum = 0; for (size_t n = range.begin(); n < range.end(); ++n) { e1 = 3 * n; e2 = e1 + 1; e3 = e2 + 1; pos[0] = mPoints[e1]; pos[1] = mPoints[e2]; pos[2] = mPoints[e3]; pos = mGrid.worldToIndex(pos); ijk[0] = int(pos[0]); ijk[1] = int(pos[1]); ijk[2] = int(pos[2]); const ValueType& value = mAccessor.getValue(ijk); if (value < mZeroValue) { // is negative if (mIsLevelSet) { color = mColorMap[1]; } else { w = (float(value) - mOffset[1]) * mScale[1]; color = w * mColorMap[0] + (1.0 - w) * mColorMap[1]; } } else { if (mIsLevelSet) { color = mColorMap[2]; } else { w = (float(value) - mOffset[0]) * mScale[0]; color = w * mColorMap[2] + (1.0 - w) * mColorMap[3]; } } mColors[e1] = color[0]; mColors[e2] = color[1]; mColors[e3] = color[2]; if (mNormals) { if ((voxelNum % 2) == 0) { tmpNormal = openvdb::math::ISGradient< openvdb::math::CD_2ND>::result(mAccessor, ijk); double length = tmpNormal.length(); if (length > 1.0e-7) { tmpNormal *= 1.0 / length; normal = tmpNormal; } } ++voxelNum; (*mNormals)[e1] = normal[0]; (*mNormals)[e2] = normal[1]; (*mNormals)[e3] = normal[2]; } } } private: void init() { mOffset[0] = float(std::min(mZeroValue, mMinValue)); mScale[0] = 1.0 / float(std::abs(std::max(mZeroValue, mMaxValue) - mOffset[0])); mOffset[1] = float(std::min(mZeroValue, mMinValue)); mScale[1] = 1.0 / float(std::abs(std::max(mZeroValue, mMaxValue) - mOffset[1])); } std::vector& mPoints; std::vector& mColors; std::vector* mNormals; const GridType& mGrid; openvdb::tree::ValueAccessor mAccessor; ValueType mMinValue, mMaxValue; openvdb::Vec3s (&mColorMap)[4]; const bool mIsLevelSet; ValueType mZeroValue; float mOffset[2], mScale[2]; }; // PointAttributeGenerator //////////////////////////////////////// class ActiveScalarValuesOp { public: ActiveScalarValuesOp( BufferObject& interiorBuffer, BufferObject& surfaceBuffer) : mInteriorBuffer(&interiorBuffer) , mSurfaceBuffer(&surfaceBuffer) { } template void operator()(typename GridType::ConstPtr grid) { using openvdb::Index64; const Index64 maxVoxelPoints = 26000000; openvdb::Vec3s colorMap[4]; colorMap[0] = openvdb::Vec3s(0.3, 0.9, 0.3); // green colorMap[1] = openvdb::Vec3s(0.9, 0.3, 0.3); // red colorMap[2] = openvdb::Vec3s(0.9, 0.9, 0.3); // yellow colorMap[3] = openvdb::Vec3s(0.3, 0.3, 0.9); // blue ////////// typedef typename GridType::ValueType ValueType; typedef typename GridType::TreeType TreeType; typedef typename TreeType::template ValueConverter::Type BoolTreeT; const TreeType& tree = grid->tree(); const bool isLevelSetGrid = grid->getGridClass() == openvdb::GRID_LEVEL_SET; ValueType minValue, maxValue; openvdb::tree::LeafManager leafs(tree); { openvdb::tools::MinMaxVoxel minmax(leafs); minmax.runParallel(); minValue = minmax.minVoxel(); maxValue = minmax.maxVoxel(); } openvdb::Index64 voxelsPerLeaf = TreeType::LeafNodeType::NUM_VOXELS; if (!isLevelSetGrid) { typename BoolTreeT::Ptr interiorMask(new BoolTreeT(false)); { // Generate Interior Points interiorMask->topologyUnion(tree); interiorMask->voxelizeActiveTiles(); if (interiorMask->activeLeafVoxelCount() > maxVoxelPoints) { voxelsPerLeaf = std::max(1, (maxVoxelPoints / interiorMask->leafCount())); } openvdb::tools::erodeVoxels(*interiorMask, 2); openvdb::tree::LeafManager maskleafs(*interiorMask); std::vector indexMap(maskleafs.leafCount()); size_t voxelCount = 0; for (Index64 l = 0, L = maskleafs.leafCount(); l < L; ++l) { indexMap[l] = voxelCount; voxelCount += std::min(maskleafs.leaf(l).onVoxelCount(), voxelsPerLeaf); } std::vector points(voxelCount * 3), colors(voxelCount * 3); std::vector indices(voxelCount); PointGenerator pointGen( points, indices, maskleafs, indexMap, grid->transform(), voxelsPerLeaf); pointGen.runParallel(); PointAttributeGenerator attributeGen( points, colors, *grid, minValue, maxValue, colorMap); attributeGen.runParallel(); // gen buffers and upload data to GPU mInteriorBuffer->genVertexBuffer(points); mInteriorBuffer->genColorBuffer(colors); mInteriorBuffer->genIndexBuffer(indices, GL_POINTS); } { // Generate Surface Points typename BoolTreeT::Ptr surfaceMask(new BoolTreeT(false)); surfaceMask->topologyUnion(tree); surfaceMask->voxelizeActiveTiles(); openvdb::tree::ValueAccessor interiorAcc(*interiorMask); for (typename BoolTreeT::LeafIter leafIt = surfaceMask->beginLeaf(); leafIt; ++leafIt) { const typename BoolTreeT::LeafNodeType* leaf = interiorAcc.probeConstLeaf(leafIt->origin()); if (leaf) leafIt->topologyDifference(*leaf, false); } surfaceMask->pruneInactive(); openvdb::tree::LeafManager maskleafs(*surfaceMask); std::vector indexMap(maskleafs.leafCount()); size_t voxelCount = 0; for (Index64 l = 0, L = maskleafs.leafCount(); l < L; ++l) { indexMap[l] = voxelCount; voxelCount += std::min(maskleafs.leaf(l).onVoxelCount(), voxelsPerLeaf); } std::vector points(voxelCount * 3), colors(voxelCount * 3), normals(voxelCount * 3); std::vector indices(voxelCount); PointGenerator pointGen( points, indices, maskleafs, indexMap, grid->transform(), voxelsPerLeaf); pointGen.runParallel(); PointAttributeGenerator attributeGen( points, colors, normals, *grid, minValue, maxValue, colorMap); attributeGen.runParallel(); mSurfaceBuffer->genVertexBuffer(points); mSurfaceBuffer->genColorBuffer(colors); mSurfaceBuffer->genNormalBuffer(normals); mSurfaceBuffer->genIndexBuffer(indices, GL_POINTS); } return; } // Level set rendering if (tree.activeLeafVoxelCount() > maxVoxelPoints) { voxelsPerLeaf = std::max(1, (maxVoxelPoints / tree.leafCount())); } std::vector indexMap(leafs.leafCount()); size_t voxelCount = 0; for (Index64 l = 0, L = leafs.leafCount(); l < L; ++l) { indexMap[l] = voxelCount; voxelCount += std::min(leafs.leaf(l).onVoxelCount(), voxelsPerLeaf); } std::vector points(voxelCount * 3), colors(voxelCount * 3), normals(voxelCount * 3); std::vector indices(voxelCount); PointGenerator pointGen( points, indices, leafs, indexMap, grid->transform(), voxelsPerLeaf); pointGen.runParallel(); PointAttributeGenerator attributeGen( points, colors, normals, *grid, minValue, maxValue, colorMap, isLevelSetGrid); attributeGen.runParallel(); mSurfaceBuffer->genVertexBuffer(points); mSurfaceBuffer->genColorBuffer(colors); mSurfaceBuffer->genNormalBuffer(normals); mSurfaceBuffer->genIndexBuffer(indices, GL_POINTS); } private: BufferObject *mInteriorBuffer; BufferObject *mSurfaceBuffer; }; // ActiveScalarValuesOp class ActiveVectorValuesOp { public: ActiveVectorValuesOp(BufferObject& vectorBuffer) : mVectorBuffer(&vectorBuffer) { } template void operator()(typename GridType::ConstPtr grid) { using openvdb::Index64; typedef typename GridType::ValueType ValueType; typedef typename GridType::TreeType TreeType; typedef typename TreeType::template ValueConverter::Type BoolTreeT; const TreeType& tree = grid->tree(); double length = 0.0; { ValueType minVal, maxVal; tree.evalMinMax(minVal, maxVal); length = maxVal.length(); } typename BoolTreeT::Ptr mask(new BoolTreeT(false)); mask->topologyUnion(tree); mask->voxelizeActiveTiles(); ///@todo thread and restructure. const Index64 voxelCount = mask->activeLeafVoxelCount(); const Index64 pointCount = voxelCount * 2; std::vector points(pointCount*3), colors(pointCount*3); std::vector indices(pointCount); openvdb::Coord ijk; openvdb::Vec3d pos, color, normal; openvdb::tree::LeafManager leafs(*mask); openvdb::tree::ValueAccessor acc(tree); Index64 idx = 0, pt = 0, cc = 0; for (Index64 l = 0, L = leafs.leafCount(); l < L; ++l) { typename BoolTreeT::LeafNodeType::ValueOnIter iter = leafs.leaf(l).beginValueOn(); for (; iter; ++iter) { ijk = iter.getCoord(); ValueType vec = acc.getValue(ijk); pos = grid->indexToWorld(ijk); points[idx++] = pos[0]; points[idx++] = pos[1]; points[idx++] = pos[2]; indices[pt] = pt; ++pt; indices[pt] = pt; ++pt; double w = vec.length() / length; vec.normalize(); pos += grid->voxelSize()[0] * 0.9 * vec; points[idx++] = pos[0]; points[idx++] = pos[1]; points[idx++] = pos[2]; color = w * openvdb::Vec3d(0.9, 0.3, 0.3) + (1.0 - w) * openvdb::Vec3d(0.3, 0.3, 0.9); colors[cc++] = color[0] * 0.3; colors[cc++] = color[1] * 0.3; colors[cc++] = color[2] * 0.3; colors[cc++] = color[0]; colors[cc++] = color[1]; colors[cc++] = color[2]; } } mVectorBuffer->genVertexBuffer(points); mVectorBuffer->genColorBuffer(colors); mVectorBuffer->genIndexBuffer(indices, GL_LINES); } private: BufferObject *mVectorBuffer; }; // ActiveVectorValuesOp //////////////////////////////////////// class MeshOp { public: MeshOp(BufferObject& buffer) : mBuffer(&buffer) {} template void operator()(typename GridType::ConstPtr grid) { using openvdb::Index64; openvdb::tools::VolumeToMesh mesher( grid->getGridClass() == openvdb::GRID_LEVEL_SET ? 0.0 : 0.01); mesher(*grid); // Copy points and generate point normals. std::vector points(mesher.pointListSize() * 3); std::vector normals(mesher.pointListSize() * 3); openvdb::tree::ValueAccessor acc(grid->tree()); typedef openvdb::math::Gradient Gradient; openvdb::math::GenericMap map(grid->transform()); openvdb::Coord ijk; for (Index64 n = 0, i = 0, N = mesher.pointListSize(); n < N; ++n) { const openvdb::Vec3s& p = mesher.pointList()[n]; points[i++] = p[0]; points[i++] = p[1]; points[i++] = p[2]; } // Copy primitives openvdb::tools::PolygonPoolList& polygonPoolList = mesher.polygonPoolList(); Index64 numQuads = 0; for (Index64 n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { numQuads += polygonPoolList[n].numQuads(); } std::vector indices; indices.reserve(numQuads * 4); openvdb::Vec3d normal, e1, e2; for (Index64 n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { const openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; for (Index64 i = 0, I = polygons.numQuads(); i < I; ++i) { const openvdb::Vec4I& quad = polygons.quad(i); indices.push_back(quad[0]); indices.push_back(quad[1]); indices.push_back(quad[2]); indices.push_back(quad[3]); e1 = mesher.pointList()[quad[1]]; e1 -= mesher.pointList()[quad[0]]; e2 = mesher.pointList()[quad[2]]; e2 -= mesher.pointList()[quad[1]]; normal = e1.cross(e2); const double length = normal.length(); if (length > 1.0e-7) normal *= (1.0 / length); for (Index64 v = 0; v < 4; ++v) { normals[quad[v]*3] = -normal[0]; normals[quad[v]*3+1] = -normal[1]; normals[quad[v]*3+2] = -normal[2]; } } } // Construct and transfer GPU buffers. mBuffer->genVertexBuffer(points); mBuffer->genNormalBuffer(normals); mBuffer->genIndexBuffer(indices, GL_QUADS); } private: BufferObject *mBuffer; static openvdb::Vec3s sNodeColors[]; }; // MeshOp //////////////////////////////////////// namespace util { /// Helper class used internally by processTypedGrid() template struct GridProcessor { static inline void call(OpType& op, openvdb::GridBase::Ptr grid) { #ifdef _MSC_VER op.operator()(openvdb::gridPtrCast(grid)); #else op.template operator()(openvdb::gridPtrCast(grid)); #endif } }; /// Helper class used internally by processTypedGrid() template struct GridProcessor { static inline void call(OpType& op, openvdb::GridBase::ConstPtr grid) { #ifdef _MSC_VER op.operator()(openvdb::gridConstPtrCast(grid)); #else op.template operator()(openvdb::gridConstPtrCast(grid)); #endif } }; /// Helper function used internally by processTypedGrid() template inline void doProcessTypedGrid(GridPtrType grid, OpType& op) { GridProcessor::value>::call(op, grid); } //////////////////////////////////////// /// @brief Utility function that, given a generic grid pointer, /// calls a functor on the fully-resolved grid /// /// Usage: /// @code /// struct PruneOp { /// template /// void operator()(typename GridT::Ptr grid) const { grid->tree()->prune(); } /// }; /// /// processTypedGrid(myGridPtr, PruneOp()); /// @endcode /// /// @return @c false if the grid type is unknown or unhandled. template bool processTypedGrid(GridPtrType grid, OpType& op) { using namespace openvdb; if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else return false; return true; } /// @brief Utility function that, given a generic grid pointer, calls /// a functor on the fully-resolved grid, provided that the grid's /// voxel values are scalars /// /// Usage: /// @code /// struct PruneOp { /// template /// void operator()(typename GridT::Ptr grid) const { grid->tree()->prune(); } /// }; /// /// processTypedScalarGrid(myGridPtr, PruneOp()); /// @endcode /// /// @return @c false if the grid type is unknown or non-scalar. template bool processTypedScalarGrid(GridPtrType grid, OpType& op) { using namespace openvdb; if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else return false; return true; } /// @brief Utility function that, given a generic grid pointer, calls /// a functor on the fully-resolved grid, provided that the grid's /// voxel values are vectors template bool processTypedVectorGrid(GridPtrType grid, OpType& op) { using namespace openvdb; if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else if (grid->template isType()) doProcessTypedGrid(grid, op); else return false; return true; } } // namespace util } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_RENDERMODULES_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Viewer.cc0000644000000000000000000004275412252453157014304 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Viewer.h" #include "Camera.h" #include "ClipBox.h" #include "Font.h" #include "RenderModules.h" #include // for formattedInt() #include #include // for std::setprecision() #include #include #include #include #if defined(__APPLE__) || defined(MACOSX) #include #include #else #include #include #endif #include namespace openvdb_viewer { class ViewerImpl { public: typedef boost::shared_ptr CameraPtr; typedef boost::shared_ptr ClipBoxPtr; typedef boost::shared_ptr RenderModulePtr; ViewerImpl(); void init(const std::string& progName, bool verbose = false); void view(const openvdb::GridCPtrVec&, int width = 900, int height = 800); void showPrevGrid(); void showNextGrid(); bool needsDisplay(); void setNeedsDisplay(); void toggleRenderModule(size_t n); void toggleInfoText(); void setWindowTitle(double fps = 0.0); void viewGrids(const openvdb::GridCPtrVec&, int width, int height); void render(); void showNthGrid(size_t n); void updateCutPlanes(int wheelPos); void keyCallback(int key, int action); void mouseButtonCallback(int button, int action); void mousePosCallback(int x, int y); void mouseWheelCallback(int pos); void windowSizeCallback(int width, int height); void windowRefreshCallback(); private: CameraPtr mCamera; ClipBoxPtr mClipBox; std::vector mRenderModules; openvdb::GridCPtrVec mGrids; size_t mGridIdx, mUpdates; std::string mGridName, mProgName, mGridInfo, mTransformInfo, mTreeInfo; int mWheelPos; bool mShiftIsDown, mCtrlIsDown, mShowInfo; }; // class ViewerImpl //////////////////////////////////////// namespace { ViewerImpl* sViewer = NULL; tbb::mutex sLock; void keyCB(int key, int action) { if (sViewer) sViewer->keyCallback(key, action); } void mouseButtonCB(int button, int action) { if (sViewer) sViewer->mouseButtonCallback(button, action); } void mousePosCB(int x, int y) { if (sViewer) sViewer->mousePosCallback(x, y); } void mouseWheelCB(int pos) { if (sViewer) sViewer->mouseWheelCallback(pos); } void windowSizeCB(int width, int height) { if (sViewer) sViewer->windowSizeCallback(width, height); } void windowRefreshCB() { if (sViewer) sViewer->windowRefreshCallback(); } } // unnamed namespace //////////////////////////////////////// Viewer init(const std::string& progName, bool verbose) { if (sViewer == NULL) { tbb::mutex::scoped_lock(sLock); if (sViewer == NULL) { OPENVDB_START_THREADSAFE_STATIC_WRITE sViewer = new ViewerImpl; OPENVDB_FINISH_THREADSAFE_STATIC_WRITE } } sViewer->init(progName, verbose); return Viewer(); } //////////////////////////////////////// Viewer::Viewer() { } void Viewer::view(const openvdb::GridCPtrVec& grids, int width, int height) { if (sViewer) sViewer->view(grids, width, height); } void Viewer::showPrevGrid() { if (sViewer) sViewer->showPrevGrid(); } void Viewer::showNextGrid() { if (sViewer) sViewer->showNextGrid(); } //////////////////////////////////////// ViewerImpl::ViewerImpl() : mCamera(new Camera) , mClipBox(new ClipBox) , mRenderModules(0) , mGridIdx(0) , mUpdates(0) , mWheelPos(0) , mShiftIsDown(false) , mCtrlIsDown(false) , mShowInfo(true) { } void ViewerImpl::init(const std::string& progName, bool verbose) { mProgName = progName; if (glfwInit() != GL_TRUE) { OPENVDB_LOG_ERROR("GLFW Initialization Failed."); } if (verbose) { if (glfwOpenWindow(100, 100, 8, 8, 8, 8, 24, 0, GLFW_WINDOW)) { int major, minor, rev; glfwGetVersion(&major, &minor, &rev); std::cout << "GLFW: " << major << "." << minor << "." << rev << "\n" << "OpenGL: " << glGetString(GL_VERSION) << std::endl; glfwCloseWindow(); } } } //////////////////////////////////////// void ViewerImpl::setWindowTitle(double fps) { std::ostringstream ss; ss << mProgName << ": " << (mGridName.empty() ? std::string("OpenVDB") : mGridName) << " (" << (mGridIdx + 1) << " of " << mGrids.size() << ") @ " << std::setprecision(1) << std::fixed << fps << " fps"; glfwSetWindowTitle(ss.str().c_str()); } //////////////////////////////////////// void ViewerImpl::render() { mCamera->aim(); // draw scene mRenderModules[0]->render(); // ground plane. mClipBox->render(); mClipBox->enableClipping(); for (size_t n = 1, N = mRenderModules.size(); n < N; ++n) { mRenderModules[n]->render(); } mClipBox->disableClipping(); // Render text if (mShowInfo) { BitmapFont13::enableFontRendering(); glColor3f (0.2, 0.2, 0.2); int width, height; glfwGetWindowSize(&width, &height); BitmapFont13::print(10, height - 13 - 10, mGridInfo); BitmapFont13::print(10, height - 13 - 30, mTransformInfo); BitmapFont13::print(10, height - 13 - 50, mTreeInfo); BitmapFont13::disableFontRendering(); } } //////////////////////////////////////// void ViewerImpl::view(const openvdb::GridCPtrVec& gridList, int width, int height) { viewGrids(gridList, width, height); } openvdb::BBoxd worldSpaceBBox(const openvdb::math::Transform& xform, const openvdb::CoordBBox& bbox) { openvdb::Vec3d pMin = openvdb::Vec3d(std::numeric_limits::max()); openvdb::Vec3d pMax = -pMin; const openvdb::Coord& min = bbox.min(); const openvdb::Coord& max = bbox.max(); openvdb::Coord ijk; // corner 1 openvdb::Vec3d ptn = xform.indexToWorld(min); for (int i = 0; i < 3; ++i) { if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; } // corner 2 ijk[0] = min.x(); ijk[1] = min.y(); ijk[2] = max.z(); ptn = xform.indexToWorld(ijk); for (int i = 0; i < 3; ++i) { if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; } // corner 3 ijk[0] = max.x(); ijk[1] = min.y(); ijk[2] = max.z(); ptn = xform.indexToWorld(ijk); for (int i = 0; i < 3; ++i) { if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; } // corner 4 ijk[0] = max.x(); ijk[1] = min.y(); ijk[2] = min.z(); ptn = xform.indexToWorld(ijk); for (int i = 0; i < 3; ++i) { if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; } // corner 5 ijk[0] = min.x(); ijk[1] = max.y(); ijk[2] = min.z(); ptn = xform.indexToWorld(ijk); for (int i = 0; i < 3; ++i) { if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; } // corner 6 ijk[0] = min.x(); ijk[1] = max.y(); ijk[2] = max.z(); ptn = xform.indexToWorld(ijk); for (int i = 0; i < 3; ++i) { if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; } // corner 7 ptn = xform.indexToWorld(max); for (int i = 0; i < 3; ++i) { if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; } // corner 8 ijk[0] = max.x(); ijk[1] = max.y(); ijk[2] = min.z(); ptn = xform.indexToWorld(ijk); for (int i = 0; i < 3; ++i) { if (ptn[i] < pMin[i]) pMin[i] = ptn[i]; if (ptn[i] > pMax[i]) pMax[i] = ptn[i]; } return openvdb::BBoxd(pMin, pMax); } void ViewerImpl::viewGrids(const openvdb::GridCPtrVec& gridList, int width, int height) { mGrids = gridList; mGridIdx = size_t(-1); mGridName.clear(); // Create window if (!glfwOpenWindow(width, height, // Window size 8, 8, 8, 8, // # of R,G,B, & A bits 32, 0, // # of depth & stencil buffer bits GLFW_WINDOW)) // Window mode { glfwTerminate(); return; } glfwSetWindowTitle(mProgName.c_str()); glfwSwapBuffers(); BitmapFont13::initialize(); ////////// // Eval grid bbox openvdb::BBoxd bbox(openvdb::Vec3d(0.0), openvdb::Vec3d(0.0)); if (!gridList.empty()) { bbox = worldSpaceBBox(gridList[0]->transform(), gridList[0]->evalActiveVoxelBoundingBox()); openvdb::Vec3d voxelSize = gridList[0]->voxelSize(); for (size_t n = 1; n < gridList.size(); ++n) { bbox.expand(worldSpaceBBox(gridList[n]->transform(), gridList[n]->evalActiveVoxelBoundingBox())); voxelSize = minComponent(voxelSize, gridList[n]->voxelSize()); } mClipBox->setStepSize(voxelSize); } mClipBox->setBBox(bbox); // setup camera openvdb::Vec3d extents = bbox.extents(); double max_extent = std::max(extents[0], std::max(extents[1], extents[2])); mCamera->setTarget(bbox.getCenter(), max_extent); mCamera->lookAtTarget(); mCamera->setSpeed(/*zoom=*/0.1, /*strafe=*/0.002, /*tumbling=*/0.02); ////////// // register callback functions glfwSetKeyCallback(keyCB); glfwSetMouseButtonCallback(mouseButtonCB); glfwSetMousePosCallback(mousePosCB); glfwSetMouseWheelCallback(mouseWheelCB); glfwSetWindowSizeCallback(windowSizeCB); glfwSetWindowRefreshCallback(windowRefreshCB); ////////// // Screen color glClearColor(0.85, 0.85, 0.85, 0.0f); glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); glShadeModel(GL_SMOOTH); glPointSize(4); glLineWidth(2); ////////// // construct render modules showNthGrid(/*n=*/0); // main loop size_t frame = 0; double time = glfwGetTime(); glfwSwapInterval(1); do { if (needsDisplay()) render(); // eval fps ++frame; double elapsed = glfwGetTime() - time; if (elapsed > 1.0) { time = glfwGetTime(); setWindowTitle(/*fps=*/double(frame) / elapsed); frame = 0; } // Swap front and back buffers glfwSwapBuffers(); // exit if the esc key is pressed or the window is closed. } while (!glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED)); glfwTerminate(); } //////////////////////////////////////// void ViewerImpl::updateCutPlanes(int wheelPos) { double speed = std::abs(mWheelPos - wheelPos); if (mWheelPos < wheelPos) mClipBox->update(speed); else mClipBox->update(-speed); setNeedsDisplay(); } //////////////////////////////////////// void ViewerImpl::showPrevGrid() { const size_t numGrids = mGrids.size(); size_t idx = ((numGrids + mGridIdx) - 1) % numGrids; showNthGrid(idx); } void ViewerImpl::showNextGrid() { const size_t numGrids = mGrids.size(); size_t idx = (mGridIdx + 1) % numGrids; showNthGrid(idx); } void ViewerImpl::showNthGrid(size_t n) { n = n % mGrids.size(); if (n == mGridIdx) return; mGridName = mGrids[n]->getName(); mGridIdx = n; // save render settings std::vector active(mRenderModules.size()); for (size_t i = 0, I = active.size(); i < I; ++i) { active[i] = mRenderModules[i]->visible(); } mRenderModules.clear(); mRenderModules.push_back(RenderModulePtr(new ViewportModule)); mRenderModules.push_back(RenderModulePtr(new TreeTopologyModule(mGrids[n]))); mRenderModules.push_back(RenderModulePtr(new MeshModule(mGrids[n]))); mRenderModules.push_back(RenderModulePtr(new ActiveValueModule(mGrids[n]))); if (active.empty()) { for (size_t i = 2, I = mRenderModules.size(); i < I; ++i) { mRenderModules[i]->visible() = false; } } else { for (size_t i = 0, I = active.size(); i < I; ++i) { mRenderModules[i]->visible() = active[i]; } } // Collect info { std::ostringstream ostrm; std::string s = mGrids[n]->getName(); const openvdb::GridClass cls = mGrids[n]->getGridClass(); if (!s.empty()) ostrm << s << " / "; ostrm << mGrids[n]->valueType() << " / "; if (cls == openvdb::GRID_UNKNOWN) ostrm << " class unknown"; else ostrm << " " << openvdb::GridBase::gridClassToString(cls); mGridInfo = ostrm.str(); } { openvdb::Coord dim = mGrids[n]->evalActiveVoxelDim(); std::ostringstream ostrm; ostrm << dim[0] << " x " << dim[1] << " x " << dim[2] << " / voxel size " << std::setprecision(4) << mGrids[n]->voxelSize()[0] << " (" << mGrids[n]->transform().mapType() << ")"; mTransformInfo = ostrm.str(); } { std::ostringstream ostrm; const openvdb::Index64 count = mGrids[n]->activeVoxelCount(); ostrm << openvdb::util::formattedInt(count) << " active voxel" << (count == 1 ? "" : "s"); mTreeInfo = ostrm.str(); } setWindowTitle(); } //////////////////////////////////////// void ViewerImpl::keyCallback(int key, int action) { OPENVDB_START_THREADSAFE_STATIC_WRITE mCamera->keyCallback(key, action); const bool keyPress = glfwGetKey(key) == GLFW_PRESS; mShiftIsDown = glfwGetKey(GLFW_KEY_LSHIFT); mCtrlIsDown = glfwGetKey(GLFW_KEY_LCTRL); if (keyPress) { switch (key) { case '1': toggleRenderModule(1); break; case '2': toggleRenderModule(2); break; case '3': toggleRenderModule(3); break; case 'c': case 'C': mClipBox->reset(); break; case 'h': case 'H': // center home mCamera->lookAt(openvdb::Vec3d(0.0), 10.0); break; case 'g': case 'G': // center geometry mCamera->lookAtTarget(); break; case 'i': case 'I': toggleInfoText(); break; case GLFW_KEY_LEFT: showPrevGrid(); break; case GLFW_KEY_RIGHT: showNextGrid(); break; } } switch (key) { case 'x': case 'X': mClipBox->activateXPlanes() = keyPress; break; case 'y': case 'Y': mClipBox->activateYPlanes() = keyPress; break; case 'z': case 'Z': mClipBox->activateZPlanes() = keyPress; break; } mClipBox->shiftIsDown() = mShiftIsDown; mClipBox->ctrlIsDown() = mCtrlIsDown; setNeedsDisplay(); OPENVDB_FINISH_THREADSAFE_STATIC_WRITE } void ViewerImpl::mouseButtonCallback(int button, int action) { mCamera->mouseButtonCallback(button, action); mClipBox->mouseButtonCallback(button, action); if (mCamera->needsDisplay()) setNeedsDisplay(); } void ViewerImpl::mousePosCallback(int x, int y) { bool handled = mClipBox->mousePosCallback(x, y); if (!handled) mCamera->mousePosCallback(x, y); if (mCamera->needsDisplay()) setNeedsDisplay(); } void ViewerImpl::mouseWheelCallback(int pos) { if (mClipBox->isActive()) { updateCutPlanes(pos); } else { mCamera->mouseWheelCallback(pos, mWheelPos); if (mCamera->needsDisplay()) setNeedsDisplay(); } mWheelPos = pos; } void ViewerImpl::windowSizeCallback(int, int) { setNeedsDisplay(); } void ViewerImpl::windowRefreshCallback() { setNeedsDisplay(); } //////////////////////////////////////// bool ViewerImpl::needsDisplay() { if (mUpdates < 2) { mUpdates += 1; return true; } return false; } void ViewerImpl::setNeedsDisplay() { mUpdates = 0; } void ViewerImpl::toggleRenderModule(size_t n) { mRenderModules[n]->visible() = !mRenderModules[n]->visible(); } void ViewerImpl::toggleInfoText() { mShowInfo = !mShowInfo; } } // namespace openvdb_viewer // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Camera.h0000644000000000000000000000621312252453157014063 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Camera.h /// @brief Basic GL camera class #ifndef OPENVDB_VIEWER_CAMERA_HAS_BEEN_INCLUDED #define OPENVDB_VIEWER_CAMERA_HAS_BEEN_INCLUDED #include namespace openvdb_viewer { class Camera { public: Camera(); void aim(); void lookAt(const openvdb::Vec3d& p, double dist = 1.0); void lookAtTarget(); void setTarget(const openvdb::Vec3d& p, double dist = 1.0); void setNearFarPlanes(double n, double f) { mNearPlane = n; mFarPlane = f; } void setFieldOfView(double degrees) { mFov = degrees; } void setSpeed(double zoomSpeed, double strafeSpeed, double tumblingSpeed); void keyCallback(int key, int action); void mouseButtonCallback(int button, int action); void mousePosCallback(int x, int y); void mouseWheelCallback(int pos, int prevPos); bool needsDisplay() const { return mNeedsDisplay; } private: // Camera parameters double mFov, mNearPlane, mFarPlane; openvdb::Vec3d mTarget, mLookAt, mUp, mForward, mRight, mEye; double mTumblingSpeed, mZoomSpeed, mStrafeSpeed; double mHead, mPitch, mTargetDistance, mDistance; // Input states bool mMouseDown, mStartTumbling, mZoomMode, mChanged, mNeedsDisplay; double mMouseXPos, mMouseYPos; int mWheelPos; static const double sDeg2rad; }; // class Camera } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_CAMERA_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Viewer.h0000644000000000000000000000605512252453157014140 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_VIEWER_VIEWER_HAS_BEEN_INCLUDED #define OPENVDB_VIEWER_VIEWER_HAS_BEEN_INCLUDED #include #include namespace openvdb_viewer { class Viewer; /// @brief Initialize and return a viewer. /// @param progName the name of the calling program (for use in info displays) /// @param verbose if true, print diagnostic info to stdout /// @note Currently, the viewer window is a singleton (but that might change /// in the future), so although this function returns a new Viewer instance /// on each call, all instances are associated with the same window. /// Typically, then, this function should be called only once. Viewer init(const std::string& progName, bool verbose = false); /// Manager for a window that displays OpenVDB grids class Viewer { public: /// Resize the window associated with this viewer and display the given grids. void view(const openvdb::GridCPtrVec&, int width = 900, int height = 800); /// When multiple grids are being viewed, advance to the next grid. void showNextGrid(); /// When multiple grids are being viewed, return to the previous grid. void showPrevGrid(); private: friend Viewer init(const std::string&, bool); Viewer(); }; } // namespace openvdb_viewer #endif // OPENVDB_VIEWER_VIEWER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/viewer/Camera.cc0000644000000000000000000001531212252453157014221 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Camera.h" #include #if defined(__APPLE__) || defined(MACOSX) #include #include #else #include #include #endif #include namespace openvdb_viewer { const double Camera::sDeg2rad = M_PI / 180.0; Camera::Camera() : mFov(65.0) , mNearPlane(0.1) , mFarPlane(10000.0) , mTarget(openvdb::Vec3d(0.0)) , mLookAt(mTarget) , mUp(openvdb::Vec3d(0.0, 1.0, 0.0)) , mForward(openvdb::Vec3d(0.0, 0.0, 1.0)) , mRight(openvdb::Vec3d(1.0, 0.0, 0.0)) , mEye(openvdb::Vec3d(0.0, 0.0, -1.0)) , mTumblingSpeed(0.5) , mZoomSpeed(0.2) , mStrafeSpeed(0.05) , mHead(30.0) , mPitch(45.0) , mTargetDistance(25.0) , mDistance(mTargetDistance) , mMouseDown(false) , mStartTumbling(false) , mZoomMode(false) , mChanged(true) , mNeedsDisplay(true) , mMouseXPos(0.0) , mMouseYPos(0.0) , mWheelPos(0) { } void Camera::lookAt(const openvdb::Vec3d& p, double dist) { mLookAt = p; mDistance = dist; mNeedsDisplay = true; } void Camera::lookAtTarget() { mLookAt = mTarget; mDistance = mTargetDistance; mNeedsDisplay = true; } void Camera::setSpeed(double zoomSpeed, double strafeSpeed, double tumblingSpeed) { mZoomSpeed = std::max(0.0001, zoomSpeed); mStrafeSpeed = std::max(0.0001, strafeSpeed); mTumblingSpeed = std::max(0.2, tumblingSpeed); mTumblingSpeed = std::min(1.0, tumblingSpeed); } void Camera::setTarget(const openvdb::Vec3d& p, double dist) { mTarget = p; mTargetDistance = dist; } void Camera::aim() { // Get the window size int width, height; glfwGetWindowSize(&width, &height); // Make sure that height is non-zero to avoid division by zero height = std::max(1, height); glViewport(0, 0, width, height); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Set up the projection matrix glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Window aspect (assumes square pixels) double aspectRatio = (double)width / (double)height; // Set perspective view (fov is in degrees in the y direction.) gluPerspective(mFov, aspectRatio, mNearPlane, mFarPlane); if (mChanged) { mChanged = false; mEye[0] = mLookAt[0] + mDistance * std::cos(mHead * sDeg2rad) * std::cos(mPitch * sDeg2rad); mEye[1] = mLookAt[1] + mDistance * std::sin(mHead * sDeg2rad); mEye[2] = mLookAt[2] + mDistance * std::cos(mHead * sDeg2rad) * std::sin(mPitch * sDeg2rad); mForward = mLookAt - mEye; mForward.normalize(); mUp[1] = std::cos(mHead * sDeg2rad) > 0 ? 1.0 : -1.0; mRight = mForward.cross(mUp); } // Set up modelview matrix glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(mEye[0], mEye[1], mEye[2], mLookAt[0], mLookAt[1], mLookAt[2], mUp[0], mUp[1], mUp[2]); mNeedsDisplay = false; } void Camera::keyCallback(int key, int ) { if (glfwGetKey(key) == GLFW_PRESS) { switch(key) { case GLFW_KEY_SPACE: mZoomMode = true; break; } } else if (glfwGetKey(key) == GLFW_RELEASE) { switch(key) { case GLFW_KEY_SPACE: mZoomMode = false; break; } } mChanged = true; } void Camera::mouseButtonCallback(int button, int action) { if (button == GLFW_MOUSE_BUTTON_LEFT) { if (action == GLFW_PRESS) mMouseDown = true; else if (action == GLFW_RELEASE) mMouseDown = false; } else if (button == GLFW_MOUSE_BUTTON_RIGHT) { if (action == GLFW_PRESS) { mMouseDown = true; mZoomMode = true; } else if (action == GLFW_RELEASE) { mMouseDown = false; mZoomMode = false; } } if (action == GLFW_RELEASE) mMouseDown = false; mStartTumbling = true; mChanged = true; } void Camera::mousePosCallback(int x, int y) { if (mStartTumbling) { mMouseXPos = x; mMouseYPos = y; mStartTumbling = false; } double dx, dy; dx = x - mMouseXPos; dy = y - mMouseYPos; if (mMouseDown && !mZoomMode) { mNeedsDisplay = true; mHead += dy * mTumblingSpeed; mPitch += dx * mTumblingSpeed; } else if (mMouseDown && mZoomMode) { mNeedsDisplay = true; mLookAt += (dy * mUp - dx * mRight) * mStrafeSpeed; } mMouseXPos = x; mMouseYPos = y; mChanged = true; } void Camera::mouseWheelCallback(int pos, int prevPos) { double speed = std::abs(prevPos - pos); if (prevPos < pos) { mDistance += speed * mZoomSpeed; setSpeed(mDistance * 0.1, mDistance * 0.002, mDistance * 0.02); } else { double temp = mDistance - speed * mZoomSpeed; mDistance = std::max(0.0, temp); setSpeed(mDistance * 0.1, mDistance * 0.002, mDistance * 0.02); } mChanged = true; mNeedsDisplay = true; } } // namespace openvdb_viewer // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/0000755000000000000000000000000012252453157012174 5ustar rootrootopenvdb/util/Util.cc0000644000000000000000000000544712252453157013432 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Util.h" #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { const Index32 INVALID_IDX = std::numeric_limits::max(); const Coord COORD_OFFSETS[26] = { Coord( 1, 0, 0), /// Voxel-face adjacent neghbours Coord(-1, 0, 0), /// 0 to 5 Coord( 0, 1, 0), Coord( 0, -1, 0), Coord( 0, 0, 1), Coord( 0, 0, -1), Coord( 1, 0, -1), /// Voxel-edge adjacent neghbours Coord(-1, 0, -1), /// 6 to 17 Coord( 1, 0, 1), Coord(-1, 0, 1), Coord( 1, 1, 0), Coord(-1, 1, 0), Coord( 1, -1, 0), Coord(-1, -1, 0), Coord( 0, -1, 1), Coord( 0, -1, -1), Coord( 0, 1, 1), Coord( 0, 1, -1), Coord(-1, -1, -1), /// Voxel-corner adjacent neghbours Coord(-1, -1, 1), /// 18 to 25 Coord( 1, -1, 1), Coord( 1, -1, -1), Coord(-1, 1, -1), Coord(-1, 1, 1), Coord( 1, 1, 1), Coord( 1, 1, -1) }; } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/Util.h0000644000000000000000000001350012252453157013261 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { OPENVDB_API extern const Index32 INVALID_IDX; /// @brief coordinate offset table for neighboring voxels OPENVDB_API extern const Coord COORD_OFFSETS[26]; //////////////////////////////////////// /// Return @a voxelCoord rounded to the closest integer coordinates. inline Coord nearestCoord(const Vec3d& voxelCoord) { Coord ijk; ijk[0] = int(std::floor(voxelCoord[0])); ijk[1] = int(std::floor(voxelCoord[1])); ijk[2] = int(std::floor(voxelCoord[2])); return ijk; } //////////////////////////////////////// /// @brief Functor for use with tools::foreach() to compute the boolean intersection /// between the value masks of corresponding leaf nodes in two trees template class LeafTopologyIntOp { public: LeafTopologyIntOp(const TreeType2& tree): mOtherTree(&tree) {} inline void operator()(const typename TreeType1::LeafIter& lIter) const { const Coord xyz = lIter->origin(); const typename TreeType2::LeafNodeType* leaf = mOtherTree->probeConstLeaf(xyz); if (leaf) {//leaf node lIter->topologyIntersection(*leaf, zeroVal()); } else if (!mOtherTree->isValueOn(xyz)) {//inactive tile lIter->setValuesOff(); } } private: const TreeType2* mOtherTree; }; /// @brief Functor for use with tools::foreach() to compute the boolean difference /// between the value masks of corresponding leaf nodes in two trees template class LeafTopologyDiffOp { public: LeafTopologyDiffOp(const TreeType2& tree): mOtherTree(&tree) {} inline void operator()(const typename TreeType1::LeafIter& lIter) const { const Coord xyz = lIter->origin(); const typename TreeType2::LeafNodeType* leaf = mOtherTree->probeConstLeaf(xyz); if (leaf) {//leaf node lIter->topologyDifference(*leaf, zeroVal()); } else if (mOtherTree->isValueOn(xyz)) {//active tile lIter->setValuesOff(); } } private: const TreeType2* mOtherTree; }; //////////////////////////////////////// /// @brief Perform a boolean intersection between two leaf nodes' topology masks. /// @return a pointer to a new, boolean-valued tree containing the overlapping voxels. template inline typename TreeType1::template ValueConverter::Type::Ptr leafTopologyIntersection(const TreeType1& lhs, const TreeType2& rhs, bool threaded = true) { typedef typename TreeType1::template ValueConverter::Type BoolTreeType; typename BoolTreeType::Ptr topologyTree(new BoolTreeType( lhs, /*inactiveValue=*/false, /*activeValue=*/true, TopologyCopy())); tools::foreach(topologyTree->beginLeaf(), LeafTopologyIntOp(rhs), threaded); topologyTree->pruneInactive(); return topologyTree; } /// @brief Perform a boolean difference between two leaf nodes' topology masks. /// @return a pointer to a new, boolean-valued tree containing the non-overlapping /// voxels from the lhs. template inline typename TreeType1::template ValueConverter::Type::Ptr leafTopologyDifference(const TreeType1& lhs, const TreeType2& rhs, bool threaded = true) { typedef typename TreeType1::template ValueConverter::Type BoolTreeType; typename BoolTreeType::Ptr topologyTree(new BoolTreeType( lhs, /*inactiveValue=*/false, /*activeValue=*/true, TopologyCopy())); tools::foreach(topologyTree->beginLeaf(), LeafTopologyDiffOp(rhs), threaded); topologyTree->pruneInactive(); return topologyTree; } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/NullInterrupter.h0000644000000000000000000001006712252453157015527 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file NullInterrupter.h #ifndef OPENVDB_UTIL_NULL_INTERRUPTER_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_NULL_INTERRUPTER_HAS_BEEN_INCLUDED #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { /// @brief Dummy NOOP interrupter class defining interface /// /// This shows the required interface for the @c InterrupterType template argument /// using by several threaded applications (e.g. tools/PointAdvect.h). The host /// application calls start() at the beginning of an interruptible operation, end() /// at the end of the operation, and wasInterrupted() periodically during the operation. /// If any call to wasInterrupted() returns @c true, the operation will be aborted. /// @note This Dummy interrupter will NEVER interrupt since wasInterrupted() always /// returns false! struct NullInterrupter { /// Default constructor NullInterrupter () {} /// Signal the start of an interruptible operation. /// @param name an optional descriptive name for the operation void start(const char* name = NULL) { (void)name; } /// Signal the end of an interruptible operation. void end() {} /// Check if an interruptible operation should be aborted. /// @param percent an optional (when >= 0) percentage indicating /// the fraction of the operation that has been completed /// @note this method is assumed to be thread-safe. The current /// implementation is clearly a NOOP and should compile out during /// optimization! inline bool wasInterrupted(int percent = -1) { (void)percent; return false; } }; /// This method allows NullInterrupter::wasInterrupted to be compiled /// out when client code only has a pointer (vs reference) to the interrupter. /// /// @note This is a free-standing function since C++ doesn't allow for /// partial template specialization (in client code of the interrupter). template inline bool wasInterrupted(T* i, int percent = -1) { return i && i->wasInterrupted(percent); } /// Specialization for NullInterrupter template<> inline bool wasInterrupted(util::NullInterrupter*, int) { return false; } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_NULL_INTERRUPTER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/NodeMasks.h0000644000000000000000000012467312252453157014246 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file NodeMasks.h #ifndef OPENVDB_UTIL_NODEMASKS_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_NODEMASKS_HAS_BEEN_INCLUDED #include #include #include // for cout #include //#include //#include // for ffs namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { /// Return the number of on bits in the given 8-bit value. inline Index32 CountOn(Byte v) { // Simple LUT: static const Byte numBits[256] = { # define B2(n) n, n+1, n+1, n+2 # define B4(n) B2(n), B2(n+1), B2(n+1), B2(n+2) # define B6(n) B4(n), B4(n+1), B4(n+1), B4(n+2) B6(0), B6(1), B6(1), B6(2) }; return numBits[v]; // Sequentially clear least significant bits //Index32 c; //for (c = 0; v; c++) v &= v - 0x01U; //return c; // This version is only fast on CPUs with fast "%" and "*" operations //return (v * UINT64_C(0x200040008001) & UINT64_C(0x111111111111111)) % 0xF; } /// Return the number of off bits in the given 8-bit value. inline Index32 CountOff(Byte v) { return CountOn(~v); } /// Return the number of on bits in the given 32-bit value. inline Index32 CountOn(Index32 v) { v = v - ((v >> 1) & 0x55555555U); v = (v & 0x33333333U) + ((v >> 2) & 0x33333333U); return (((v + (v >> 4)) & 0xF0F0F0FU) * 0x1010101U) >> 24; } /// Return the number of off bits in the given 32-bit value. inline Index32 CountOff(Index32 v) { return CountOn(~v); } /// Return the number of on bits in the given 64-bit value. inline Index32 CountOn(Index64 v) { v = v - ((v >> 1) & UINT64_C(0x5555555555555555)); v = (v & UINT64_C(0x3333333333333333)) + ((v >> 2) & UINT64_C(0x3333333333333333)); return (((v + (v >> 4)) & UINT64_C(0xF0F0F0F0F0F0F0F)) * UINT64_C(0x101010101010101)) >> 56; } /// Return the number of off bits in the given 64-bit value. inline Index32 CountOff(Index64 v) { return CountOn(~v); } /// Return the least significant on bit of the given 8-bit value. inline Index32 FindLowestOn(Byte v) { assert(v); static const Byte DeBruijn[8] = {0, 1, 6, 2, 7, 5, 4, 3}; return DeBruijn[Byte((v & -v) * 0x1DU) >> 5]; } /// Return the least significant on bit of the given 32-bit value. inline Index32 FindLowestOn(Index32 v) { assert(v); //return ffs(v); static const Byte DeBruijn[32] = { 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; return DeBruijn[Index32((v & -v) * 0x077CB531U) >> 27]; } /// Return the least significant on bit of the given 64-bit value. inline Index32 FindLowestOn(Index64 v) { assert(v); //return ffsll(v); static const Byte DeBruijn[64] = { 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12, }; return DeBruijn[Index64((v & -v) * UINT64_C(0x022FDD63CC95386D)) >> 58]; } /// Return the most significant on bit of the given 32-bit value. inline Index32 FindHighestOn(Index32 v) { static const Byte DeBruijn[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 }; v |= v >> 1; // first round down to one less than a power of 2 v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return DeBruijn[Index32(v * 0x07C4ACDDU) >> 27]; } //////////////////////////////////////// /// Base class for the bit mask iterators template class BaseMaskIterator { protected: Index32 mPos;//bit position const NodeMask* mParent;//this iterator can't change the parent_mask! public: BaseMaskIterator() : mPos(NodeMask::SIZE), mParent(NULL) {} BaseMaskIterator(Index32 pos,const NodeMask *parent) : mPos(pos), mParent(parent) { assert( (parent==NULL && pos==0 ) || (parent!=NULL && pos<=NodeMask::SIZE) ); } bool operator==(const BaseMaskIterator &iter) const {return mPos == iter.mPos;} bool operator!=(const BaseMaskIterator &iter) const {return mPos != iter.mPos;} bool operator< (const BaseMaskIterator &iter) const {return mPos < iter.mPos;} void operator= (const BaseMaskIterator &iter) { mPos = iter.mPos; mParent = iter.mParent; } Index32 offset() const {return mPos;} Index32 pos() const {return mPos;} bool test() const { assert(mPos <= NodeMask::SIZE); return (mPos != NodeMask::SIZE); } operator bool() const {return this->test();} }; // class BaseMaskIterator /// @note This happens to be a const-iterator! template class OnMaskIterator: public BaseMaskIterator { private: typedef BaseMaskIterator BaseType; using BaseType::mPos;//bit position; using BaseType::mParent;//this iterator can't change the parent_mask! public: OnMaskIterator() : BaseType() {} OnMaskIterator(Index32 pos,const NodeMask *parent) : BaseType(pos,parent) {} void increment() { assert(mParent != NULL); mPos = mParent->findNextOn(mPos+1); assert(mPos <= NodeMask::SIZE); } void increment(Index n) { while(n-- && this->next()) ; } bool next() { this->increment(); return this->test(); } bool operator*() const {return true;} OnMaskIterator& operator++() { this->increment(); return *this; } }; // class OnMaskIterator template class OffMaskIterator: public BaseMaskIterator { private: typedef BaseMaskIterator BaseType; using BaseType::mPos;//bit position; using BaseType::mParent;//this iterator can't change the parent_mask! public: OffMaskIterator() : BaseType() {} OffMaskIterator(Index32 pos,const NodeMask *parent) : BaseType(pos,parent) {} void increment() { assert(mParent != NULL); mPos=mParent->findNextOff(mPos+1); assert(mPos <= NodeMask::SIZE); } void increment(Index n) { while(n-- && this->next()) ; } bool next() { this->increment(); return this->test(); } bool operator*() const {return false;} OffMaskIterator& operator++() { this->increment(); return *this; } }; // class OffMaskIterator template class DenseMaskIterator: public BaseMaskIterator { private: typedef BaseMaskIterator BaseType; using BaseType::mPos;//bit position; using BaseType::mParent;//this iterator can't change the parent_mask! public: DenseMaskIterator() : BaseType() {} DenseMaskIterator(Index32 pos,const NodeMask *parent) : BaseType(pos,parent) {} void increment() { assert(mParent != NULL); mPos += 1;//careful - the increment might go beyond the end assert(mPos<= NodeMask::SIZE); } void increment(Index n) { while(n-- && this->next()) ; } bool next() { this->increment(); return this->test(); } bool operator*() const {return mParent->isOn(mPos);} DenseMaskIterator& operator++() { this->increment(); return *this; } }; // class DenseMaskIterator /// @brief Bit mask for the internal and leaf nodes of VDB. This /// is a 64-bit implementation. /// /// @note A template specialization for Log2Dim=1 and Log2Dim=2 are /// given below. template class NodeMask { public: BOOST_STATIC_ASSERT( Log2Dim>2 ); static const Index32 LOG2DIM = Log2Dim; static const Index32 DIM = 1<> 6;// 2^6=64 typedef Index64 Word; private: // The bits are represented as a linear array of Words, and the // size of a Word is 32 or 64 bits depending on the platform. // The BIT_MASK is defined as the number of bits in a Word - 1 //static const Index32 BIT_MASK = sizeof(void*) == 8 ? 63 : 31; //static const Index32 LOG2WORD = BIT_MASK == 63 ? 6 : 5; //static const Index32 WORD_COUNT = SIZE >> LOG2WORD; //typedef boost::mpl::if_c::type Word; Word mWords[WORD_COUNT];//only member data! public: /// Default constructor sets all bits off NodeMask() { this->setOff(); } /// All bits are set to the specified state NodeMask(bool on) { this->set(on); } /// Copy constructor NodeMask(const NodeMask &other) { *this = other; } /// Destructor ~NodeMask() {} /// Assignment operator void operator = (const NodeMask &other) { Index32 n = WORD_COUNT; const Word* w2 = other.mWords; for ( Word* w1 = mWords; n--; ++w1, ++w2) *w1 = *w2; } typedef OnMaskIterator OnIterator; typedef OffMaskIterator OffIterator; typedef DenseMaskIterator DenseIterator; OnIterator beginOn() const { return OnIterator(this->findFirstOn(),this); } OnIterator endOn() const { return OnIterator(SIZE,this); } OffIterator beginOff() const { return OffIterator(this->findFirstOff(),this); } OffIterator endOff() const { return OffIterator(SIZE,this); } DenseIterator beginDense() const { return DenseIterator(0,this); } DenseIterator endDense() const { return DenseIterator(SIZE,this); } bool operator == (const NodeMask &other) const { int n = WORD_COUNT; for (const Word *w1=mWords, *w2=other.mWords; n-- && *w1++ == *w2++;) ; return n == -1; } bool operator != (const NodeMask &other) const { return !(*this == other); } // // Bitwise logical operations // NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } const NodeMask& operator&=(const NodeMask& other) { Index32 n = WORD_COUNT; const Word* w2 = other.mWords; for ( Word* w1 = mWords; n--; ++w1, ++w2) *w1 &= *w2; return *this; } const NodeMask& operator|=(const NodeMask& other) { Index32 n = WORD_COUNT; const Word* w2 = other.mWords; for ( Word* w1 = mWords; n--; ++w1, ++w2) *w1 |= *w2; return *this; } const NodeMask& operator^=(const NodeMask& other) { Index32 n = WORD_COUNT; const Word* w2 = other.mWords; for ( Word* w1 = mWords; n--; ++w1, ++w2) *w1 ^= *w2; return *this; } NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } /// Return the byte size of this NodeMask static Index32 memUsage() { return WORD_COUNT*sizeof(Word); } /// Return the byte size of this NodeMask OPENVDB_DEPRECATED Index32 getMemUsage() const {return sizeof(*this);} /// Return the total number of on bits Index32 countOn() const { Index32 sum = 0, n = WORD_COUNT; for (const Word* w = mWords; n--; ++w) sum += CountOn(*w); return sum; } /// Return the total number of on bits Index32 countOff() const { return SIZE-this->countOn(); } /// Set the nth bit on void setOn(Index32 n) { assert( (n >> 6) < WORD_COUNT ); mWords[n >> 6] |= Word(1) << (n & 63); } /// Set the nth bit off void setOff(Index32 n) { assert( (n >> 6) < WORD_COUNT ); mWords[n >> 6] &= ~(Word(1) << (n & 63)); } /// Set the nth bit to the specified state void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } /// Set all bits to the specified state void set(bool on) { const Word state = on ? ~Word(0) : Word(0); Index32 n = WORD_COUNT; for (Word* w = mWords; n--; ++w) *w = state; } /// Set all bits on void setOn() { Index32 n = WORD_COUNT; for (Word* w = mWords; n--; ++w) *w = ~Word(0); } /// Set all bits off void setOff() { Index32 n = WORD_COUNT; for (Word* w = mWords; n--; ++w) *w = Word(0); } /// Toggle the state of the nth bit void toggle(Index32 n) { assert( (n >> 6) < WORD_COUNT ); mWords[n >> 6] ^= 1 << (n & 63); } /// Toggle the state of all bits in the mask void toggle() { Index32 n = WORD_COUNT; for (Word* w = mWords; n--; ++w) *w = ~*w; } /// Set the first bit on void setFirstOn() { this->setOn(0); } /// Set the last bit on void setLastOn() { this->setOn(SIZE-1); } /// Set the first bit off void setFirstOff() { this->setOff(0); } /// Set the last bit off void setLastOff() { this->setOff(SIZE-1); } /// Return @c true if the nth bit is on bool isOn(Index32 n) const { assert( (n >> 6) < WORD_COUNT ); return 0 != (mWords[n >> 6] & (Word(1) << (n & 63))); } /// Return @c true if the nth bit is off bool isOff(Index32 n) const {return !this->isOn(n); } /// Return @c true if all the bits are on bool isOn() const { int n = WORD_COUNT; for (const Word *w = mWords; n-- && *w++ == ~Word(0);) ; return n == -1; } /// Return @c true if all the bits are off bool isOff() const { int n = WORD_COUNT; for (const Word *w = mWords; n-- && *w++ == Word(0);) ; return n == -1; } Index32 findFirstOn() const { Index32 n = 0; const Word* w = mWords; for (; nnth word of the bit mask, for a word of arbitrary size. template WordT getWord(Index n) const { assert(n*8*sizeof(WordT) < SIZE); return reinterpret_cast(mWords)[n]; } template WordT& getWord(Index n) { assert(n*8*sizeof(WordT) < SIZE); return reinterpret_cast(mWords)[n]; } //@} void save(std::ostream& os) const { os.write(reinterpret_cast(mWords), this->memUsage()); } void load(std::istream& is) { is.read(reinterpret_cast(mWords), this->memUsage()); } /// @brief simple print method for debugging void printInfo(std::ostream& os=std::cout) const { os << "NodeMask: Dim=" << DIM << " Log2Dim=" << Log2Dim << " Bit count=" << SIZE << " word count=" << WORD_COUNT << std::endl; } void printBits(std::ostream& os=std::cout, Index32 max_out=80u) const { const Index32 n=(SIZE>max_out ? max_out : SIZE); for (Index32 i=0; i < n; ++i) { if ( !(i & 63) ) os << "||"; else if ( !(i%8) ) os << "|"; os << this->isOn(i); } os << "|" << std::endl; } void printAll(std::ostream& os=std::cout, Index32 max_out=80u) const { this->printInfo(os); this->printBits(os, max_out); } Index32 findNextOn(Index32 start) const { Index32 n = start >> 6;//initiate if (n >= WORD_COUNT) return SIZE; // check for out of bounds Index32 m = start & 63; Word b = mWords[n]; if (b & (Word(1) << m)) return start;//simpel case: start is on b &= ~Word(0) << m;// mask out lower bits while(!b && ++n> 6;//initiate if (n >= WORD_COUNT) return SIZE; // check for out of bounds Index32 m = start & 63; Word b = ~mWords[n]; if (b & (Word(1) << m)) return start;//simpel case: start is on b &= ~Word(0) << m;// mask out lower bits while(!b && ++n class NodeMask<1> { public: static const Index32 LOG2DIM = 1; static const Index32 DIM = 2; static const Index32 SIZE = 8; static const Index32 WORD_COUNT = 1; typedef Byte Word; private: Byte mByte;//only member data! public: /// Default constructor sets all bits off NodeMask() : mByte(0x00U) {} /// All bits are set to the specified state NodeMask(bool on) : mByte(on ? 0xFFU : 0x00U) {} /// Copy constructor NodeMask(const NodeMask &other) : mByte(other.mByte) {} /// Destructor ~NodeMask() {} /// Assignment operator void operator = (const NodeMask &other) { mByte = other.mByte; } typedef OnMaskIterator OnIterator; typedef OffMaskIterator OffIterator; typedef DenseMaskIterator DenseIterator; OnIterator beginOn() const { return OnIterator(this->findFirstOn(),this); } OnIterator endOn() const { return OnIterator(SIZE,this); } OffIterator beginOff() const { return OffIterator(this->findFirstOff(),this); } OffIterator endOff() const { return OffIterator(SIZE,this); } DenseIterator beginDense() const { return DenseIterator(0,this); } DenseIterator endDense() const { return DenseIterator(SIZE,this); } bool operator == (const NodeMask &other) const { return mByte == other.mByte; } bool operator != (const NodeMask &other) const {return mByte != other.mByte; } // // Bitwise logical operations // NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } const NodeMask& operator&=(const NodeMask& other) { mByte &= other.mByte; return *this; } const NodeMask& operator|=(const NodeMask& other) { mByte |= other.mByte; return *this; } const NodeMask& operator^=(const NodeMask& other) { mByte ^= other.mByte; return *this; } NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } /// Return the byte size of this NodeMask static Index32 memUsage() { return 1; } /// Return the byte size of this NodeMask OPENVDB_DEPRECATED Index32 getMemUsage() const {return sizeof(*this);} /// Return the total number of on bits Index32 countOn() const { return CountOn(mByte); } /// Return the total number of on bits Index32 countOff() const { return CountOff(mByte); } /// Set the nth bit on void setOn(Index32 n) { assert( n < 8 ); mByte |= 0x01U << (n & 7); } /// Set the nth bit off void setOff(Index32 n) { assert( n < 8 ); mByte &= ~(0x01U << (n & 7)); } /// Set the nth bit to the specified state void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } /// Set all bits to the specified state void set(bool on) { mByte = on ? 0xFFU : 0x00U; } /// Set all bits on void setOn() { mByte = 0xFFU; } /// Set all bits off void setOff() { mByte = 0x00U; } /// Toggle the state of the nth bit void toggle(Index32 n) { assert( n < 8 ); mByte ^= 0x01U << (n & 7); } /// Toggle the state of all bits in the mask void toggle() { mByte = ~mByte; } /// Set the first bit on void setFirstOn() { this->setOn(0); } /// Set the last bit on void setLastOn() { this->setOn(7); } /// Set the first bit off void setFirstOff() { this->setOff(0); } /// Set the last bit off void setLastOff() { this->setOff(7); } /// Return true if the nth bit is on bool isOn(Index32 n) const { assert( n < 8 ); return mByte & (0x01U << (n & 7)); } /// Return true if the nth bit is off bool isOff(Index32 n) const {return !this->isOn(n); } /// Return true if all the bits are on bool isOn() const { return mByte == 0xFFU; } /// Return true if all the bits are off bool isOff() const { return mByte == 0; } Index32 findFirstOn() const { return mByte ? FindLowestOn(mByte) : 8; } Index32 findFirstOff() const { const Byte b = ~mByte; return b ? FindLowestOn(b) : 8; } /* //@{ /// Return the nth word of the bit mask, for a word of arbitrary size. /// @note This version assumes WordT=Byte and n=0! template WordT getWord(Index n) const { BOOST_STATIC_ASSERT(sizeof(WordT) == sizeof(Byte)); assert(n == 0); return reinterpret_cast(mByte); } template WordT& getWord(Index n) { BOOST_STATIC_ASSERT(sizeof(WordT) == sizeof(Byte)); assert(n == 0); return reinterpret_cast(mByte); } //@} */ void save(std::ostream& os) const { os.write(reinterpret_cast(&mByte), 1); } void load(std::istream& is) { is.read(reinterpret_cast(&mByte), 1); } /// @brief simple print method for debugging void printInfo(std::ostream& os=std::cout) const { os << "NodeMask: Dim=2, Log2Dim=1, Bit count=8, Word count=1"<isOn(i); os << "||" << std::endl; } void printAll(std::ostream& os=std::cout) const { this->printInfo(os); this->printBits(os); } Index32 findNextOn(Index32 start) const { if (start>=8) return 8; const Byte b = mByte & (0xFFU << start); return b ? FindLowestOn(b) : 8; } Index32 findNextOff(Index32 start) const { if (start>=8) return 8; const Byte b = ~mByte & (0xFFU << start); return b ? FindLowestOn(b) : 8; } };// NodeMask<1> /// @brief Template specialization of NodeMask for Log2Dim=2, i.e. 4^3 nodes template<> class NodeMask<2> { public: static const Index32 LOG2DIM = 2; static const Index32 DIM = 4; static const Index32 SIZE = 64; static const Index32 WORD_COUNT = 1; typedef Index64 Word; private: Word mWord;//only member data! public: /// Default constructor sets all bits off NodeMask() : mWord(UINT64_C(0x00)) {} /// All bits are set to the specified state NodeMask(bool on) : mWord(on ? UINT64_C(0xFFFFFFFFFFFFFFFF) : UINT64_C(0x00)) {} /// Copy constructor NodeMask(const NodeMask &other) : mWord(other.mWord) {} /// Destructor ~NodeMask() {} /// Assignment operator void operator = (const NodeMask &other) { mWord = other.mWord; } typedef OnMaskIterator OnIterator; typedef OffMaskIterator OffIterator; typedef DenseMaskIterator DenseIterator; OnIterator beginOn() const { return OnIterator(this->findFirstOn(),this); } OnIterator endOn() const { return OnIterator(SIZE,this); } OffIterator beginOff() const { return OffIterator(this->findFirstOff(),this); } OffIterator endOff() const { return OffIterator(SIZE,this); } DenseIterator beginDense() const { return DenseIterator(0,this); } DenseIterator endDense() const { return DenseIterator(SIZE,this); } bool operator == (const NodeMask &other) const { return mWord == other.mWord; } bool operator != (const NodeMask &other) const {return mWord != other.mWord; } // // Bitwise logical operations // NodeMask operator!() const { NodeMask m(*this); m.toggle(); return m; } const NodeMask& operator&=(const NodeMask& other) { mWord &= other.mWord; return *this; } const NodeMask& operator|=(const NodeMask& other) { mWord |= other.mWord; return *this; } const NodeMask& operator^=(const NodeMask& other) { mWord ^= other.mWord; return *this; } NodeMask operator&(const NodeMask& other) const { NodeMask m(*this); m &= other; return m; } NodeMask operator|(const NodeMask& other) const { NodeMask m(*this); m |= other; return m; } NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } /// Return the byte size of this NodeMask static Index32 memUsage() { return 8; } /// Return the byte size of this NodeMask OPENVDB_DEPRECATED Index32 getMemUsage() const {return sizeof(*this);} /// Return the total number of on bits Index32 countOn() const { return CountOn(mWord); } /// Return the total number of on bits Index32 countOff() const { return CountOff(mWord); } /// Set the nth bit on void setOn(Index32 n) { assert( n < 64 ); mWord |= UINT64_C(0x01) << (n & 63); } /// Set the nth bit off void setOff(Index32 n) { assert( n < 64 ); mWord &= ~(UINT64_C(0x01) << (n & 63)); } /// Set the nth bit to the specified state void set(Index32 n, bool On) { On ? this->setOn(n) : this->setOff(n); } /// Set all bits to the specified state void set(bool on) { mWord = on ? UINT64_C(0xFFFFFFFFFFFFFFFF) : UINT64_C(0x00); } /// Set all bits on void setOn() { mWord = UINT64_C(0xFFFFFFFFFFFFFFFF); } /// Set all bits off void setOff() { mWord = UINT64_C(0x00); } /// Toggle the state of the nth bit void toggle(Index32 n) { assert( n < 64 ); mWord ^= UINT64_C(0x01) << (n & 63); } /// Toggle the state of all bits in the mask void toggle() { mWord = ~mWord; } /// Set the first bit on void setFirstOn() { this->setOn(0); } /// Set the last bit on void setLastOn() { this->setOn(63); } /// Set the first bit off void setFirstOff() { this->setOff(0); } /// Set the last bit off void setLastOff() { this->setOff(63); } /// Return true if the nth bit is on bool isOn(Index32 n) const { assert( n < 64 ); return 0 != (mWord & (UINT64_C(0x01) << (n & 63))); } /// Return true if the nth bit is off bool isOff(Index32 n) const {return !this->isOn(n); } /// Return true if all the bits are on bool isOn() const { return mWord == UINT64_C(0xFFFFFFFFFFFFFFFF); } /// Return true if all the bits are off bool isOff() const { return mWord == 0; } Index32 findFirstOn() const { return mWord ? FindLowestOn(mWord) : 64; } Index32 findFirstOff() const { const Word w = ~mWord; return w ? FindLowestOn(w) : 64; } //@{ /// Return the nth word of the bit mask, for a word of arbitrary size. template WordT getWord(Index n) const { assert(n*8*sizeof(WordT) < SIZE); return reinterpret_cast(&mWord)[n]; } template WordT& getWord(Index n) { assert(n*8*sizeof(WordT) < SIZE); return reinterpret_cast(mWord)[n]; } //@} void save(std::ostream& os) const { os.write(reinterpret_cast(&mWord), 8); } void load(std::istream& is) { is.read(reinterpret_cast(&mWord), 8); } /// @brief simple print method for debugging void printInfo(std::ostream& os=std::cout) const { os << "NodeMask: Dim=4, Log2Dim=2, Bit count=64, Word count=1"<isOn(i); } os << "||" << std::endl; } void printAll(std::ostream& os=std::cout) const { this->printInfo(os); this->printBits(os); } Index32 findNextOn(Index32 start) const { if (start>=64) return 64; const Word w = mWord & (UINT64_C(0xFFFFFFFFFFFFFFFF) << start); return w ? FindLowestOn(w) : 64; } Index32 findNextOff(Index32 start) const { if (start>=64) return 64; const Word w = ~mWord & (UINT64_C(0xFFFFFFFFFFFFFFFF) << start); return w ? FindLowestOn(w) : 64; } };// NodeMask<2> // Unlike NodeMask above this RootNodeMask has a run-time defined size. // It is only included for backward compatibility and will likely be // deprecated in the future! // This class is 32-bit specefic, hence the use if Index32 vs Index! class RootNodeMask { protected: Index32 mBitSize, mIntSize; Index32 *mBits; public: RootNodeMask(): mBitSize(0), mIntSize(0), mBits(NULL) {} RootNodeMask(Index32 bit_size): mBitSize(bit_size), mIntSize(((bit_size-1)>>5)+1), mBits(new Index32[mIntSize]) { for (Index32 i=0; i>5)+1; delete [] mBits; mBits = new Index32[mIntSize]; for (Index32 i=0; igetBitSize()), mParent(parent) { assert( pos<=mBitSize ); } bool operator==(const BaseIterator &iter) const {return mPos == iter.mPos;} bool operator!=(const BaseIterator &iter) const {return mPos != iter.mPos;} bool operator< (const BaseIterator &iter) const {return mPos < iter.mPos;} void operator=(const BaseIterator &iter) { mPos = iter.mPos; mBitSize = iter.mBitSize; mParent = iter.mParent; } Index32 offset() const {return mPos;} Index32 pos() const {return mPos;} bool test() const { assert(mPos <= mBitSize); return (mPos != mBitSize); } operator bool() const {return this->test();} }; // class BaseIterator /// @note This happens to be a const-iterator! class OnIterator: public BaseIterator { protected: using BaseIterator::mPos;//bit position; using BaseIterator::mBitSize;//bit size; using BaseIterator::mParent;//this iterator can't change the parent_mask! public: OnIterator() : BaseIterator() {} OnIterator(Index32 pos,const RootNodeMask *parent) : BaseIterator(pos,parent) {} void increment() { assert(mParent!=NULL); mPos=mParent->findNextOn(mPos+1); assert(mPos <= mBitSize); } void increment(Index n) { for (Index i=0; inext(); ++i) {} } bool next() { this->increment(); return this->test(); } bool operator*() const {return true;} OnIterator& operator++() { this->increment(); return *this; } }; // class OnIterator class OffIterator: public BaseIterator { protected: using BaseIterator::mPos;//bit position; using BaseIterator::mBitSize;//bit size; using BaseIterator::mParent;//this iterator can't change the parent_mask! public: OffIterator() : BaseIterator() {} OffIterator(Index32 pos,const RootNodeMask *parent) : BaseIterator(pos,parent) {} void increment() { assert(mParent!=NULL); mPos=mParent->findNextOff(mPos+1); assert(mPos <= mBitSize); } void increment(Index n) { for (Index i=0; inext(); ++i) {} } bool next() { this->increment(); return this->test(); } bool operator*() const {return true;} OffIterator& operator++() { this->increment(); return *this; } }; // class OffIterator class DenseIterator: public BaseIterator { protected: using BaseIterator::mPos;//bit position; using BaseIterator::mBitSize;//bit size; using BaseIterator::mParent;//this iterator can't change the parent_mask! public: DenseIterator() : BaseIterator() {} DenseIterator(Index32 pos,const RootNodeMask *parent) : BaseIterator(pos,parent) {} void increment() { assert(mParent!=NULL); mPos += 1;//carefull - the increament might go beyond the end assert(mPos<= mBitSize); } void increment(Index n) { for (Index i=0; inext(); ++i) {} } bool next() { this->increment(); return this->test(); } bool operator*() const {return mParent->isOn(mPos);} DenseIterator& operator++() { this->increment(); return *this; } }; // class DenseIterator OnIterator beginOn() const { return OnIterator(this->findFirstOn(),this); } OnIterator endOn() const { return OnIterator(mBitSize,this); } OffIterator beginOff() const { return OffIterator(this->findFirstOff(),this); } OffIterator endOff() const { return OffIterator(mBitSize,this); } DenseIterator beginDense() const { return DenseIterator(0,this); } DenseIterator endDense() const { return DenseIterator(mBitSize,this); } bool operator == (const RootNodeMask &B) const { if (mBitSize != B.mBitSize) return false; for (Index32 i=0; icountOn(); } void setOn(Index32 i) { assert(mBits); assert( (i>>5) < mIntSize); mBits[i>>5] |= 1<<(i&31); } void setOff(Index32 i) { assert(mBits); assert( (i>>5) < mIntSize); mBits[i>>5] &= ~(1<<(i&31)); } void set(Index32 i, bool On) { On ? this->setOn(i) : this->setOff(i); } void setOn() { assert(mBits); for (Index32 i=0; i>5) < mIntSize); mBits[i>>5] ^= 1<<(i&31); } void toggle() { assert(mBits); for (Index32 i=0; isetOn(0); } void setLastOn() { this->setOn(mBitSize-1); } void setFirstOff() { this->setOff(0); } void setLastOff() { this->setOff(mBitSize-1); } bool isOn(Index32 i) const { assert(mBits); assert( (i>>5) < mIntSize); return ( mBits[i >> 5] & (1<<(i&31)) ); } bool isOff(Index32 i) const { assert(mBits); assert( (i>>5) < mIntSize); return ( ~mBits[i >> 5] & (1<<(i&31)) ); } bool isOn() const { if (!mBits) return false;//undefined is off for (Index32 i=0; iisOn(i); } os << "|" << std::endl; } void printAll(std::ostream& os=std::cout, Index32 max_out=80u) const { this->printInfo(os); this->printBits(os,max_out); } Index32 findNextOn(Index32 start) const { assert(mBits); Index32 n = start >> 5, m = start & 31;//initiate if (n>=mIntSize) return mBitSize; // check for out of bounds Index32 b = mBits[n]; if (b & (1<> 5, m = start & 31;//initiate if (n>=mIntSize) return mBitSize; // check for out of bounds Index32 b = ~mBits[n]; if (b & (1< #define OPENVDB_LOG_INFO(message) LOG_INFO(message) #define OPENVDB_LOG_WARN(message) LOG_WARN(message) #define OPENVDB_LOG_ERROR(message) LOG_ERROR(message) #define OPENVDB_LOG_FATAL(message) LOG_FATAL(message) #ifdef DEBUG /// Log debugging messages in debug builds only. #define OPENVDB_LOG_DEBUG(message) LOG_DEBUG(message) #else #define OPENVDB_LOG_DEBUG(message) #endif /// Log debugging messages even in non-debug builds. /// Don't use this in performance-critical code. #define OPENVDB_LOG_DEBUG_RUNTIME(message) LOG_DEBUG_RUNTIME(message) #endif // OPENVDB_USE_LOG4CPLUS #endif // OPENVDB_UTIL_LOGGING_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/Name.h0000644000000000000000000000511212252453157013224 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { typedef std::string Name; inline Name readString(std::istream& is) { uint32_t size; is.read(reinterpret_cast(&size), sizeof(uint32_t)); std::string buffer(size, ' '); is.read(&buffer[0], size); return buffer; } inline void writeString(std::ostream& os, const Name& name) { uint32_t size = uint32_t(name.size()); os.write(reinterpret_cast(&size), sizeof(uint32_t)); os.write(&name[0], size); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_NAME_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/MapsUtil.h0000644000000000000000000002767612252453157014125 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file MapsUtil.h #ifndef OPENVDB_UTIL_MAPSUTIL_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_MAPSUTIL_HAS_BEEN_INCLUDED #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { // Utility methods for calculating bounding boxes /// @brief Calculate an axis-aligned bounding box in the given map's domain /// (e.g., index space) from an axis-aligned bounding box in its range /// (e.g., world space) template inline void calculateBounds(const MapType& map, const BBoxd& in, BBoxd& out) { const Vec3d& min = in.min(); const Vec3d& max = in.max(); // the pre-image of the 8 corners of the box Vec3d corners[8]; corners[0] = in.min();; corners[1] = Vec3d(min(0), min(1), min(2)); corners[2] = Vec3d(max(0), max(1), min(2)); corners[3] = Vec3d(min(0), max(1), min(2)); corners[4] = Vec3d(min(0), min(1), max(2)); corners[5] = Vec3d(max(0), min(1), max(2)); corners[6] = max; corners[7] = Vec3d(min(0), max(1), max(2)); Vec3d pre_image; Vec3d& out_min = out.min(); Vec3d& out_max = out.max(); out_min = map.applyInverseMap(corners[0]); out_max = min; for (int i = 1; i < 8; ++i) { pre_image = map.applyInverseMap(corners[i]); for (int j = 0; j < 3; ++j) { out_min(j) = std::min( out_min(j), pre_image(j)); out_max(j) = std::max( out_max(j), pre_image(j)); } } } /// @brief Calculate an axis-aligned bounding box in the given map's domain /// from a spherical bounding box in its range. template inline void calculateBounds(const MapType& map, const Vec3d& center, const Real radius, BBoxd& out) { // On return, out gives a bounding box in continuous index space // that encloses the sphere. // // the image of a sphere under the inverse of the linearMap will be an ellipsoid. if (math::is_linear::value) { // I want to find extrema for three functions f(x', y', z') = x', or = y', or = z' // with the constraint that g = (x-xo)^2 + (y-yo)^2 + (z-zo)^2 = r^2. // Where the point x,y,z is the image of x',y',z' // Solve: \lambda Grad(g) = Grad(f) and g = r^2. // Note: here (x,y,z) is the image of (x',y',z'), and the gradient // is w.r.t the (') space. // // This can be solved exactly: e_a^T (x' -xo') =\pm r\sqrt(e_a^T J^(-1)J^(-T)e_a) // where e_a is one of the three unit vectors. - djh. /// find the image of the center of the sphere Vec3d center_pre_image = map.applyInverseMap(center); std::vector coordinate_units; coordinate_units.push_back(Vec3d(1,0,0)); coordinate_units.push_back(Vec3d(0,1,0)); coordinate_units.push_back(Vec3d(0,0,1)); Vec3d& out_min = out.min(); Vec3d& out_max = out.max(); for (int direction = 0; direction < 3; ++direction) { Vec3d temp = map.applyIJT(coordinate_units[direction]); double offset = radius * sqrt(temp.x()*temp.x() + temp.y()*temp.y() + temp.z()*temp.z()); out_min(direction) = center_pre_image(direction) - offset; out_max(direction) = center_pre_image(direction) + offset; } } else { // This is some unknown map type. In this case, we form an axis-aligned // bounding box for the sphere in world space and find the pre-images of // the corners in index space. From these corners we compute an axis-aligned // bounding box in index space. BBoxd bounding_box(center - radius*Vec3d(1,1,1), center + radius*Vec3d(1,1,1)); calculateBounds(map, bounding_box, out); } } namespace { // anonymous namespace for this helper function /// @brief Find the intersection of a line passing through the point /// \f$ (x=0, z=-1/g)\f$ with the circle \f$ (x-xo)^2 + (z-zo)^2 = r^2 \f$ /// at a point tangent to the circle. /// @return 0 if the focal point (0, -1/g) is inside the circle, /// 1 if the focal point touches the circle, or 2 when both points are found. inline int findTangentPoints(const double g, const double xo, const double zo, const double r, double& xp, double& zp, double& xm, double& zm) { double x2 = xo * xo; double r2 = r * r; double xd = g * xo; double xd2 = xd*xd; double zd = g * zo + 1.; double zd2 = zd*zd; double rd2 = r2*g*g; double distA = xd2 + zd2; double distB = distA - rd2; if (distB > 0) { double discriminate = sqrt(distB); xp = xo - xo*rd2/distA + r * zd *discriminate / distA; xm = xo - xo*rd2/distA - r * zd *discriminate / distA; zp = (zo*zd2 + zd*g*(x2 - r2) - xo*xo*g - r*xd*discriminate) / distA; zm = (zo*zd2 + zd*g*(x2 - r2) - xo*xo*g + r*xd*discriminate) / distA; return 2; } if (0 >= distB && distB >= -1e-9) { // the circle touches the focal point (x=0, z = -1/g) xp = 0; xm = 0; zp = -1/g; zm = -1/g; return 1; } return 0; } } // end anonymous namespace /// @brief Calculate an axis-aligned bounding box in index space /// from a spherical bounding box in world space. /// @note This specialization is optimized for a frustum map template<> inline void calculateBounds(const math::NonlinearFrustumMap& frustum, const Vec3d& center, const Real radius, BBoxd& out) { // The frustum is a nonlinear map followed by a uniform scale, rotation, translation. // First we invert the translation, rotation and scale to find the spherical pre-image // of the sphere in "local" coordinates where the frustum is aligned with the near plane // on the z=0 plane and the "camera" is located at (x=0, y=0, z=-1/g). // check that the internal map has no shear. const math::AffineMap& secondMap = frustum.secondMap(); // test if the linear part has shear or non-uniform scaling if (!frustum.hasSimpleAffine()) { // In this case, we form an axis-aligned bounding box for sphere in world space // and find the pre_images of the corners in voxel space. From these corners we // compute an axis-algined bounding box in voxel-spae BBoxd bounding_box(center - radius*Vec3d(1,1,1), center + radius*Vec3d(1,1,1)); calculateBounds(frustum, bounding_box, out); return; } // for convenience Vec3d& out_min = out.min(); Vec3d& out_max = out.max(); Vec3d centerLS = secondMap.applyInverseMap(center); Vec3d voxelSize = secondMap.voxelSize(); // all the voxels have the same size since we know this is a simple affine map double radiusLS = radius / voxelSize(0); double gamma = frustum.getGamma(); double xp; double zp; double xm; double zm; int soln_number; // the bounding box in index space for the points in the frustum const BBoxd& bbox = frustum.getBBox(); // initialize min and max const double x_min = bbox.min().x(); const double y_min = bbox.min().y(); const double z_min = bbox.min().z(); const double x_max = bbox.max().x(); const double y_max = bbox.max().y(); const double z_max = bbox.max().z(); out_min.x() = x_min; out_max.x() = x_max; out_min.y() = y_min; out_max.y() = y_max; Vec3d extreme; Vec3d extreme2; Vec3d pre_image; // find the x-range soln_number = findTangentPoints(gamma, centerLS.x(), centerLS.z(), radiusLS, xp, zp, xm, zm); if (soln_number == 2) { extreme.x() = xp; extreme.y() = centerLS.y(); extreme.z() = zp; // location in world space of the tangent point extreme2 = secondMap.applyMap(extreme); // convert back to voxel space pre_image = frustum.applyInverseMap(extreme2); out_max.x() = std::max(x_min, std::min(x_max, pre_image.x())); const Vec3d tmpPlus = extreme2; extreme.x() = xm; extreme.y() = centerLS.y(); extreme.z() = zm; // location in world space of the tangent point extreme2 = secondMap.applyMap(extreme); const Vec3d tmpMinus = extreme2; // convert back to voxel space pre_image = frustum.applyInverseMap(extreme2); out_min.x() = std::max(x_min, std::min(x_max, pre_image.x())); } else if (soln_number == 1) { // the circle was tangent at the focal point } else if (soln_number == 0) { // the focal point was inside the circle } // find the y-range soln_number = findTangentPoints(gamma, centerLS.y(), centerLS.z(), radiusLS, xp, zp, xm, zm); if (soln_number == 2) { extreme.x() = centerLS.x(); extreme.y() = xp; extreme.z() = zp; // location in world space of the tangent point extreme2 = secondMap.applyMap(extreme); // convert back to voxel space pre_image = frustum.applyInverseMap(extreme2); out_max.y() = std::max(y_min, std::min(y_max, pre_image.y())); const Vec3d tmpPlus = extreme2; extreme.x() = centerLS.x(); extreme.y() = xm; extreme.z() = zm; extreme2 = secondMap.applyMap(extreme); const Vec3d tmpMinus = extreme2; // convert back to voxel space pre_image = frustum.applyInverseMap(extreme2); out_min.y() = std::max(y_min, std::min(y_max, pre_image.y())); } else if (soln_number == 1) { // the circle was tangent at the focal point } else if (soln_number == 0) { // the focal point was inside the circle } // the near and far // the closest point. The front of the frustum is at 0 in index space double near_dist = std::max(centerLS.z() - radiusLS, 0.); // the farthest point. The back of the frustum is at mDepth in index space double far_dist = std::min(centerLS.z() + radiusLS, frustum.getDepth() ); Vec3d near_point(0.f, 0.f, near_dist); Vec3d far_point(0.f, 0.f, far_dist); out_min.z() = std::max(z_min, frustum.applyInverseMap(secondMap.applyMap(near_point)).z()); out_max.z() = std::min(z_max, frustum.applyInverseMap(secondMap.applyMap(far_point)).z()); } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_MAPSUTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/Formats.h0000644000000000000000000001245612252453157013770 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file Formats.h /// /// @brief Utility routines to output nicely-formatted numeric values #ifndef OPENVDB_UTIL_FORMATS_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_FORMATS_HAS_BEEN_INCLUDED #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { /// Output a byte count with the correct binary suffix (KB, MB, GB or TB). /// @param os the output stream /// @param bytes the byte count to be output /// @param head a string to be output before the numeric text /// @param tail a string to be output after the numeric text /// @param exact if true, also output the unmodified count, e.g., "4.6 KB (4620 Bytes)" /// @param width a fixed width for the numeric text /// @param precision the number of digits after the decimal point /// @return 0, 1, 2, 3 or 4, denoting the order of magnitude of the count. OPENVDB_API int printBytes(std::ostream& os, uint64_t bytes, const std::string& head = "", const std::string& tail = "\n", bool exact = false, int width = 8, int precision = 3); /// Output a number with the correct SI suffix (thousand, million, billion or trillion) /// @param os the output stream /// @param number the number to be output /// @param head a string to be output before the numeric text /// @param tail a string to be output after the numeric text /// @param exact if true, also output the unmodified count, e.g., "4.6 Thousand (4620)" /// @param width a fixed width for the numeric text /// @param precision the number of digits after the decimal point /// @return 0, 1, 2, 3 or 4, denoting the order of magnitude of the number. OPENVDB_API int printNumber(std::ostream& os, uint64_t number, const std::string& head = "", const std::string& tail = "\n", bool exact = true, int width = 8, int precision = 3); //////////////////////////////////////// /// @brief I/O manipulator that formats integer values with thousands separators template class FormattedInt { public: static char sep() { return ','; } FormattedInt(IntT n): mInt(n) {} std::ostream& put(std::ostream& os) const { // Convert the integer to a string. std::ostringstream ostr; ostr << mInt; std::string s = ostr.str(); // Prefix the string with spaces if its length is not a multiple of three. size_t padding = (s.size() % 3) ? 3 - (s.size() % 3) : 0; s = std::string(padding, ' ') + s; // Construct a new string in which groups of three digits are followed // by a separator character. ostr.str(""); for (size_t i = 0, N = s.size(); i < N; ) { ostr << s[i]; ++i; if (i >= padding && i % 3 == 0 && i < s.size()) { ostr << sep(); } } // Remove any padding that was added and output the string. s = ostr.str(); os << s.substr(padding, s.size()); return os; } private: IntT mInt; }; template std::ostream& operator<<(std::ostream& os, const FormattedInt& n) { return n.put(os); } /// @return an I/O manipulator that formats the given integer value for output to a stream. template FormattedInt formattedInt(IntT n) { return FormattedInt(n); } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_FORMATS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/util/Formats.cc0000644000000000000000000001036412252453157014122 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Formats.h" #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { int printBytes(std::ostream& os, uint64_t bytes, const std::string& head, const std::string& tail, bool exact, int width, int precision) { const uint64_t one = 1; int group = 0; // Write to a string stream so that I/O manipulators like // std::setprecision() don't alter the output stream. std::ostringstream ostr; ostr << head; ostr << std::setprecision(precision) << std::setiosflags(std::ios::fixed); if (bytes >> 40) { ostr << std::setw(width) << (bytes / double(one << 40)) << " TB"; group = 4; } else if (bytes >> 30) { ostr << std::setw(width) << (bytes / double(one << 30)) << " GB"; group = 3; } else if (bytes >> 20) { ostr << std::setw(width) << (bytes / double(one << 20)) << " MB"; group = 2; } else if (bytes >> 10) { ostr << std::setw(width) << (bytes / double(one << 10)) << " KB"; group = 1; } else { ostr << std::setw(width) << bytes << " Bytes"; } if (exact && group) ostr << " (" << bytes << " Bytes)"; ostr << tail; os << ostr.str(); return group; } int printNumber(std::ostream& os, uint64_t number, const std::string& head, const std::string& tail, bool exact, int width, int precision) { int group = 0; // Write to a string stream so that I/O manipulators like // std::setprecision() don't alter the output stream. std::ostringstream ostr; ostr << head; ostr << std::setprecision(precision) << std::setiosflags(std::ios::fixed); if (number / UINT64_C(1000000000000)) { ostr << std::setw(width) << (number / 1000000000000.0) << " trillion"; group = 4; } else if (number / UINT64_C(1000000000)) { ostr << std::setw(width) << (number / 1000000000.0) << " billion"; group = 3; } else if (number / UINT64_C(1000000)) { ostr << std::setw(width) << (number / 1000000.0) << " million"; group = 2; } else if (number / UINT64_C(1000)) { ostr << std::setw(width) << (number / 1000.0) << " thousand"; group = 1; } else { ostr << std::setw(width) << number; } if (exact && group) ostr << " (" << number << ")"; ostr << tail; os << ostr.str(); return group; } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/0000755000000000000000000000000012252453157012777 5ustar rootrootopenvdb/metadata/MetaMap.h0000644000000000000000000002137612252453157014505 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_METADATA_METAMAP_HAS_BEEN_INCLUDED #define OPENVDB_METADATA_METAMAP_HAS_BEEN_INCLUDED #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { /// @brief Provides functionality storing type agnostic metadata information. /// Grids and other structures can inherit from this to attain metadata /// functionality. class OPENVDB_API MetaMap { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; typedef std::map MetadataMap; typedef MetadataMap::iterator MetaIterator; typedef MetadataMap::const_iterator ConstMetaIterator; ///< @todo this should really iterate over a map of Metadata::ConstPtrs /// Constructor MetaMap() {} MetaMap(const MetaMap& other); /// Destructor virtual ~MetaMap() {} /// Return a copy of this map whose fields are shared with this map. MetaMap::Ptr copyMeta() const; /// Return a deep copy of this map that shares no data with this map. MetaMap::Ptr deepCopyMeta() const; /// Assign to this map a deep copy of another map. MetaMap& operator=(const MetaMap&); /// Read in all the Meta information the given stream. void readMeta(std::istream&); /// Write out all the Meta information to the given stream. void writeMeta(std::ostream&) const; /// Insert a new metadata or overwrite existing. If Metadata with given name /// doesn't exist, a new Metadata field is added. If it does exist and given /// metadata is of the same type, then overwrite existing with new value. If /// it does exist and not of the same type, then throw an exception. /// /// @param name the name of the metadata. /// @param metadata the actual metadata to store. void insertMeta(const Name& name, const Metadata& metadata); /// Removes an existing metadata field from the grid. If the metadata with /// the given name doesn't exist, do nothing. /// /// @param name the name of the metadata field to remove. void removeMeta(const Name &name); //@{ /// @return a pointer to the metadata with the given name, NULL if no such /// field exists. Metadata::Ptr operator[](const Name&); Metadata::ConstPtr operator[](const Name&) const; //@} //@{ /// @return pointer to TypedMetadata, NULL if type and name mismatch. template typename T::Ptr getMetadata(const Name &name); template typename T::ConstPtr getMetadata(const Name &name) const; //@} /// @return direct access to the underlying value stored by the given /// metadata name. Here T is the type of the value stored. If there is a /// mismatch, then throws an exception. template T& metaValue(const Name &name); template const T& metaValue(const Name &name) const; /// Functions for iterating over the Metadata. MetaIterator beginMeta() { return mMeta.begin(); } MetaIterator endMeta() { return mMeta.end(); } ConstMetaIterator beginMeta() const { return mMeta.begin(); } ConstMetaIterator endMeta() const { return mMeta.end(); } void clearMetadata() { mMeta.clear(); } size_t metaCount() const { return mMeta.size(); } bool empty() const { return mMeta.empty(); } /// @return string representation of MetaMap std::string str() const; private: /// @return a pointer to TypedMetadata with the given template parameter. /// @throw LookupError if no field with the given name is found. /// @throw TypeError if the given field is not of type T. template typename TypedMetadata::Ptr getValidTypedMetadata(const Name&) const; MetadataMap mMeta; }; /// Write a MetaMap to an output stream std::ostream& operator<<(std::ostream&, const MetaMap&); //////////////////////////////////////// inline Metadata::Ptr MetaMap::operator[](const Name& name) { MetaIterator iter = mMeta.find(name); return (iter == mMeta.end() ? Metadata::Ptr() : iter->second); } inline Metadata::ConstPtr MetaMap::operator[](const Name &name) const { ConstMetaIterator iter = mMeta.find(name); return (iter == mMeta.end() ? Metadata::Ptr() : iter->second); } //////////////////////////////////////// template inline typename T::Ptr MetaMap::getMetadata(const Name &name) { ConstMetaIterator iter = mMeta.find(name); if(iter == mMeta.end()) { return typename T::Ptr(); } // To ensure that we get valid conversion if the metadata pointers cross dso // boundaries, we have to check the qualified typename and then do a static // cast. This is slower than doing a dynamic_pointer_cast, but is safer when // pointers cross dso boundaries. if (iter->second->typeName() == T::staticTypeName()) { return boost::static_pointer_cast(iter->second); } // else return typename T::Ptr(); } template inline typename T::ConstPtr MetaMap::getMetadata(const Name &name) const { ConstMetaIterator iter = mMeta.find(name); if(iter == mMeta.end()) { return typename T::ConstPtr(); } // To ensure that we get valid conversion if the metadata pointers cross dso // boundaries, we have to check the qualified typename and then do a static // cast. This is slower than doing a dynamic_pointer_cast, but is safer when // pointers cross dso boundaries. if (iter->second->typeName() == T::staticTypeName()) { return boost::static_pointer_cast(iter->second); } // else return typename T::ConstPtr(); } //////////////////////////////////////// template inline typename TypedMetadata::Ptr MetaMap::getValidTypedMetadata(const Name &name) const { ConstMetaIterator iter = mMeta.find(name); if (iter == mMeta.end()) OPENVDB_THROW(LookupError, "Cannot find metadata " << name); // To ensure that we get valid conversion if the metadata pointers cross dso // boundaries, we have to check the qualified typename and then do a static // cast. This is slower than doing a dynamic_pointer_cast, but is safer when // pointers cross dso boundaries. typename TypedMetadata::Ptr m; if (iter->second->typeName() == TypedMetadata::staticTypeName()) { m = boost::static_pointer_cast, Metadata>(iter->second); } if (!m) OPENVDB_THROW(TypeError, "Invalid type for metadata " << name); return m; } //////////////////////////////////////// template inline T& MetaMap::metaValue(const Name &name) { typename TypedMetadata::Ptr m = getValidTypedMetadata(name); return m->value(); } template inline const T& MetaMap::metaValue(const Name &name) const { typename TypedMetadata::Ptr m = getValidTypedMetadata(name); return m->value(); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_METADATA_METAMAP_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/Metadata.cc0000644000000000000000000001203212252453157015024 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Metadata.h" #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { typedef tbb::mutex Mutex; typedef Mutex::scoped_lock Lock; typedef Metadata::Ptr (*createMetadata)(); typedef std::map MetadataFactoryMap; typedef MetadataFactoryMap::const_iterator MetadataFactoryMapCIter; struct LockedMetadataTypeRegistry { LockedMetadataTypeRegistry() {} ~LockedMetadataTypeRegistry() {} Mutex mMutex; MetadataFactoryMap mMap; }; // Declare this at file scope to ensure thread-safe initialization static Mutex theInitMetadataTypeRegistryMutex; // Global function for accessing the regsitry static LockedMetadataTypeRegistry* getMetadataTypeRegistry() { Lock lock(theInitMetadataTypeRegistryMutex); static LockedMetadataTypeRegistry *registry = NULL; if(registry == NULL) { #if defined(__ICC) __pragma(warning(disable:1711)) // disable ICC "assignment to static variable" warnings #endif registry = new LockedMetadataTypeRegistry(); #if defined(__ICC) __pragma(warning(default:1711)) #endif } return registry; } bool Metadata::isRegisteredType(const Name &typeName) { LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); Lock lock(registry->mMutex); return (registry->mMap.find(typeName) != registry->mMap.end()); } void Metadata::registerType(const Name &typeName, Metadata::Ptr (*createMetadata)()) { LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); Lock lock(registry->mMutex); if (registry->mMap.find(typeName) != registry->mMap.end()) { OPENVDB_THROW(KeyError, "Cannot register " << typeName << ". Type is already registered"); } registry->mMap[typeName] = createMetadata; } void Metadata::unregisterType(const Name &typeName) { LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); Lock lock(registry->mMutex); registry->mMap.erase(typeName); } Metadata::Ptr Metadata::createMetadata(const Name &typeName) { LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); Lock lock(registry->mMutex); MetadataFactoryMapCIter iter = registry->mMap.find(typeName); if (iter == registry->mMap.end()) { OPENVDB_THROW(LookupError, "Cannot create metadata for unregistered type " << typeName); } return (iter->second)(); } void Metadata::clearRegistry() { LockedMetadataTypeRegistry *registry = getMetadataTypeRegistry(); Lock lock(registry->mMutex); registry->mMap.clear(); } //////////////////////////////////////// void UnknownMetadata::readValue(std::istream& is, Index32 numBytes) { // Read and discard the metadata (without seeking, because // the stream might not be seekable). const size_t BUFFER_SIZE = 1024; std::vector buffer(BUFFER_SIZE); for (Index32 bytesRemaining = numBytes; bytesRemaining > 0; ) { const Index32 bytesToSkip = std::min(bytesRemaining, BUFFER_SIZE); is.read(&buffer[0], bytesToSkip); bytesRemaining -= bytesToSkip; } } void UnknownMetadata::writeValue(std::ostream&) const { OPENVDB_THROW(TypeError, "Metadata has unknown type"); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/MetaMap.cc0000644000000000000000000001416412252453157014640 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { MetaMap::MetaMap(const MetaMap &other) { // Insert all metadata into this map. ConstMetaIterator iter = other.beginMeta(); for( ; iter != other.endMeta(); ++iter) { this->insertMeta(iter->first, *(iter->second)); } } MetaMap::Ptr MetaMap::copyMeta() const { MetaMap::Ptr ret(new MetaMap); ret->mMeta = this->mMeta; return ret; } MetaMap::Ptr MetaMap::deepCopyMeta() const { return MetaMap::Ptr(new MetaMap(*this)); } MetaMap& MetaMap::operator=(const MetaMap& other) { if (&other != this) { this->clearMetadata(); // Insert all metadata into this map. ConstMetaIterator iter = other.beginMeta(); for ( ; iter != other.endMeta(); ++iter) { this->insertMeta(iter->first, *(iter->second)); } } return *this; } void MetaMap::readMeta(std::istream &is) { // Clear out the current metamap if need be. this->clearMetadata(); // Read in the number of metadata items. Index32 count = 0; is.read(reinterpret_cast(&count), sizeof(Index32)); // Read in each metadata. for (Index32 i = 0; i < count; ++i) { // Read in the name. Name name = readString(is); // Read in the metadata typename. Name typeName = readString(is); // Create a metadata type from the typename. Make sure that the type is // registered. if (!Metadata::isRegisteredType(typeName)) { OPENVDB_LOG_WARN("cannot read metadata \"" << name << "\" of unregistered type \"" << typeName << "\""); UnknownMetadata metadata; metadata.read(is); } else { Metadata::Ptr metadata = Metadata::createMetadata(typeName); // Read the value from the stream. metadata->read(is); // Add the name and metadata to the map. insertMeta(name, *metadata); } } } void MetaMap::writeMeta(std::ostream &os) const { // Write out the number of metadata items we have in the map. Note that we // save as Index32 to save a 32-bit number. Using size_t would be platform // dependent. Index32 count = (Index32)metaCount(); os.write(reinterpret_cast(&count), sizeof(Index32)); // Iterate through each metadata and write it out. for (ConstMetaIterator iter = beginMeta(); iter != endMeta(); ++iter) { // Write the name of the metadata. writeString(os, iter->first); // Write the type name of the metadata. writeString(os, iter->second->typeName()); // Write out the metadata value. iter->second->write(os); } } void MetaMap::insertMeta(const Name &name, const Metadata &m) { if(name.size() == 0) OPENVDB_THROW(ValueError, "Metadata name cannot be an empty string"); // See if the value already exists, if so then replace the existing one. MetaIterator iter = mMeta.find(name); if(iter == mMeta.end()) { // Create a copy of hte metadata and store it in the map Metadata::Ptr tmp = m.copy(); mMeta[name] = tmp; } else { if(iter->second->typeName() != m.typeName()) { std::ostringstream ostr; ostr << "Cannot assign value of type " << m.typeName() << " to metadata attribute " << name << " of " << "type " << iter->second->typeName(); OPENVDB_THROW(TypeError, ostr.str()); } // else Metadata::Ptr tmp = m.copy(); iter->second = tmp; } } void MetaMap::removeMeta(const Name &name) { // Find the required metadata MetaIterator iter = mMeta.find(name); if(iter == mMeta.end()) return; // else, delete the metadata and remove from the map mMeta.erase(iter); } std::string MetaMap::str() const { std::ostringstream buffer; buffer << "MetaMap:\n"; for(ConstMetaIterator iter = beginMeta(); iter != endMeta(); ++iter) { buffer << " " << iter->first << " = " << iter->second->str(); buffer << std::endl; } return buffer.str(); } std::ostream& operator<<(std::ostream& ostr, const MetaMap& metamap) { ostr << metamap.str(); return ostr; } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/Metadata.h0000644000000000000000000002637112252453157014701 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_METADATA_METADATA_HAS_BEEN_INCLUDED #define OPENVDB_METADATA_METADATA_HAS_BEEN_INCLUDED #include #include #include #include // for math::isZero() #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { /// @brief Base class for storing metadata information in a grid. class OPENVDB_API Metadata { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; /// Constructor Metadata() {} /// Destructor virtual ~Metadata() {} /// @return the type name of the metadata. virtual Name typeName() const = 0; /// @return a copy of the metadata. virtual Metadata::Ptr copy() const = 0; /// Copy value from the given metadata into the curent metadata virtual void copy(const Metadata &other) = 0; /// @return string representation of Metadata virtual std::string str() const = 0; /// Return the boolean representation of this metadata (empty strings /// and zeroVals evaluate to false; most other values evaluate to true). virtual bool asBool() const = 0; /// @return the size of the attribute in bytes. virtual Index32 size() const = 0; /// Read the attribute from a stream. void read(std::istream&); /// Write the attribute to a stream. void write(std::ostream&) const; /// Creates a new Metadata from the metadata type registry. static Metadata::Ptr createMetadata(const Name &typeName); /// @return true if the given type is known by the metadata type registry. static bool isRegisteredType(const Name &typeName); /// Clears out the metadata registry. static void clearRegistry(); protected: /// Read the size of the attribute from a stream. static Index32 readSize(std::istream&); /// Write the size of the attribute to a stream. void writeSize(std::ostream&) const; /// Read the attribute from a stream. virtual void readValue(std::istream&, Index32 numBytes) = 0; /// Write the attribute to a stream. virtual void writeValue(std::ostream&) const = 0; /// Register the given metadata type along with a factory function. static void registerType(const Name& typeName, Metadata::Ptr (*createMetadata)()); static void unregisterType(const Name& typeName); private: // Disallow copying of instances of this class. Metadata(const Metadata&); Metadata& operator=(const Metadata&); }; /// @brief Subclass to read (and ignore) data of an unregistered type class OPENVDB_API UnknownMetadata: public Metadata { public: UnknownMetadata() {} virtual ~UnknownMetadata() {} virtual Name typeName() const { return ""; } virtual Metadata::Ptr copy() const { OPENVDB_THROW(TypeError, "Metadata has unknown type"); } virtual void copy(const Metadata&) { OPENVDB_THROW(TypeError, "Destination has unknown type"); } virtual std::string str() const { return ""; } virtual bool asBool() const { return false; } virtual Index32 size() const { return 0; } protected: virtual void readValue(std::istream&s, Index32 numBytes); virtual void writeValue(std::ostream&) const; }; /// @brief Templated metadata class to hold specific types. template class TypedMetadata: public Metadata { public: typedef boost::shared_ptr > Ptr; typedef boost::shared_ptr > ConstPtr; // Constructors & destructors TypedMetadata(); TypedMetadata(const T &value); TypedMetadata(const TypedMetadata &other); virtual ~TypedMetadata(); /// @return the type name of the metadata. virtual Name typeName() const; /// @return a copy of the metadata virtual Metadata::Ptr copy() const; /// Copy value from the given metadata into the curent metadata virtual void copy(const Metadata &other); /// @return string representation of value virtual std::string str() const; /// Return the boolean representation of this metadata (empty strings /// and zeroVals evaluate to false; most other values evaluate to true). virtual bool asBool() const; /// @return the size of the attribute in bytes. virtual Index32 size() const { return static_cast(sizeof(T)); } /// Set this metadata's value. void setValue(const T&); /// @return this metadata's value. T& value(); const T& value() const; /// Static specialized function for the type name. This function must be /// template specialized for each type T. static Name staticTypeName() { return typeNameAsString(); } /// Creates a new metadata of this type. static Metadata::Ptr createMetadata(); /// Register the given metadata type and a function that knows how to create /// the metadata type. This way the registry will know how to create certain /// metadata types. static void registerType(); static void unregisterType(); static bool isRegisteredType(); protected: /// Read the attribute from a stream. virtual void readValue(std::istream&, Index32 numBytes); /// Write the attribute to a stream. virtual void writeValue(std::ostream&) const; private: T mValue; }; /// Write a Metadata to an output stream std::ostream& operator<<(std::ostream& ostr, const Metadata& metadata); //////////////////////////////////////// inline void Metadata::writeSize(std::ostream& os) const { const Index32 n = this->size(); os.write(reinterpret_cast(&n), sizeof(Index32)); } inline Index32 Metadata::readSize(std::istream& is) { Index32 n = 0; is.read(reinterpret_cast(&n), sizeof(Index32)); return n; } inline void Metadata::read(std::istream& is) { const Index32 numBytes = this->readSize(is); this->readValue(is, numBytes); } inline void Metadata::write(std::ostream& os) const { this->writeSize(os); this->writeValue(os); } //////////////////////////////////////// template inline TypedMetadata::TypedMetadata() : mValue(T()) { } template inline TypedMetadata::TypedMetadata(const T &value) : mValue(value) { } template inline TypedMetadata::TypedMetadata(const TypedMetadata &other) : Metadata(), mValue(other.mValue) { } template inline TypedMetadata::~TypedMetadata() { } template inline Name TypedMetadata::typeName() const { return TypedMetadata::staticTypeName(); } template inline void TypedMetadata::setValue(const T& val) { mValue = val; } template inline T& TypedMetadata::value() { return mValue; } template inline const T& TypedMetadata::value() const { return mValue; } template inline Metadata::Ptr TypedMetadata::copy() const { Metadata::Ptr metadata(new TypedMetadata()); metadata->copy(*this); return metadata; } template inline void TypedMetadata::copy(const Metadata &other) { const TypedMetadata* t = dynamic_cast*>(&other); if (t == NULL) OPENVDB_THROW(TypeError, "Incompatible type during copy"); mValue = t->mValue; } template inline void TypedMetadata::readValue(std::istream& is, Index32 /*numBytes*/) { //assert(this->size() == numBytes); is.read(reinterpret_cast(&mValue), this->size()); } template inline void TypedMetadata::writeValue(std::ostream& os) const { os.write(reinterpret_cast(&mValue), this->size()); } template inline std::string TypedMetadata::str() const { std::ostringstream ostr; ostr << mValue; return ostr.str(); } template inline bool TypedMetadata::asBool() const { return !math::isZero(mValue); } template inline Metadata::Ptr TypedMetadata::createMetadata() { Metadata::Ptr ret(new TypedMetadata()); return ret; } template inline void TypedMetadata::registerType() { Metadata::registerType(TypedMetadata::staticTypeName(), TypedMetadata::createMetadata); } template inline void TypedMetadata::unregisterType() { Metadata::unregisterType(TypedMetadata::staticTypeName()); } template inline bool TypedMetadata::isRegisteredType() { return Metadata::isRegisteredType(TypedMetadata::staticTypeName()); } template<> inline std::string TypedMetadata::str() const { return (mValue ? "true" : "false"); } inline std::ostream& operator<<(std::ostream& ostr, const Metadata& metadata) { ostr << metadata.str(); return ostr; } typedef TypedMetadata BoolMetadata; typedef TypedMetadata DoubleMetadata; typedef TypedMetadata FloatMetadata; typedef TypedMetadata Int32Metadata; typedef TypedMetadata Int64Metadata; typedef TypedMetadata Vec2DMetadata; typedef TypedMetadata Vec2IMetadata; typedef TypedMetadata Vec2SMetadata; typedef TypedMetadata Vec3DMetadata; typedef TypedMetadata Vec3IMetadata; typedef TypedMetadata Vec3SMetadata; typedef TypedMetadata Mat4SMetadata; typedef TypedMetadata Mat4DMetadata; } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_METADATA_METADATA_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/metadata/StringMetadata.h0000644000000000000000000000506712252453157016067 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_METADATA_STRINGMETADATA_HAS_BEEN_INCLUDED #define OPENVDB_METADATA_STRINGMETADATA_HAS_BEEN_INCLUDED #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { typedef TypedMetadata StringMetadata; template <> inline Index32 StringMetadata::size() const { return mValue.size(); } template<> inline void StringMetadata::readValue(std::istream& is, Index32 size) { mValue.resize(size, '\0'); is.read(&mValue[0], size); } template<> inline void StringMetadata::writeValue(std::ostream &os) const { os.write(reinterpret_cast(&mValue[0]), this->size()); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_METADATA_STRINGMETADATA_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/LICENSE0000644000000000000000000004052612252452020012217 0ustar rootrootMozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. openvdb/version.h0000644000000000000000000001642712252453157013067 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_VERSION_HAS_BEEN_INCLUDED #define OPENVDB_VERSION_HAS_BEEN_INCLUDED #include "Platform.h" #include // for std::istream #include /// The version namespace name for this library version /// /// Fully-namespace-qualified symbols are named as follows: /// vdb::vX_Y_Z::Vec3i, vdb::vX_Y_Z::io::File, vdb::vX_Y_Z::tree::Tree, etc., /// where X, Y and Z are OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION /// and OPENVDB_LIBRARY_PATCH_VERSION, respectively (defined below). #define OPENVDB_VERSION_NAME v2_1_0 // Library major, minor and patch version numbers #define OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER 2 #define OPENVDB_LIBRARY_MINOR_VERSION_NUMBER 1 #define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER 0 /// Library version number as a packed integer ("%02x%02x%04x", major, minor, patch) #define OPENVDB_LIBRARY_VERSION_NUMBER \ ((OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER << 24) | \ ((OPENVDB_LIBRARY_MINOR_VERSION_NUMBER & 0xFF) << 16) | \ (OPENVDB_LIBRARY_PATCH_VERSION_NUMBER & 0xFFFF)) /// If OPENVDB_REQUIRE_VERSION_NAME is undefined, symbols from the version /// namespace are promoted to the top-level namespace (e.g., vdb::v1_0_0::io::File /// can be referred to simply as vdb::io::File). Otherwise, symbols must be fully /// namespace-qualified. #ifdef OPENVDB_REQUIRE_VERSION_NAME #define OPENVDB_USE_VERSION_NAMESPACE #else /// @note The empty namespace clause below ensures that /// OPENVDB_VERSION_NAME is recognized as a namespace name. #define OPENVDB_USE_VERSION_NAMESPACE \ namespace OPENVDB_VERSION_NAME {} \ using namespace OPENVDB_VERSION_NAME; #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { /// @brief The magic number is stored in the first four bytes of every VDB file. /// @details This can be used to quickly test whether we have a valid file or not. const int32_t OPENVDB_MAGIC = 0x56444220; // Library major, minor and patch version numbers const uint32_t OPENVDB_LIBRARY_MAJOR_VERSION = OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER, OPENVDB_LIBRARY_MINOR_VERSION = OPENVDB_LIBRARY_MINOR_VERSION_NUMBER, OPENVDB_LIBRARY_PATCH_VERSION = OPENVDB_LIBRARY_PATCH_VERSION_NUMBER; /// Library version number as a packed integer ("%02x%02x%04x", major, minor, patch) const uint32_t OPENVDB_LIBRARY_VERSION = OPENVDB_LIBRARY_VERSION_NUMBER; /// @brief The current version number of the VDB file format /// @details This can be used to enable various backwards compatability switches /// or to reject files that cannot be read. const uint32_t OPENVDB_FILE_VERSION = 222; /// Notable file format version numbers enum { OPENVDB_FILE_VERSION_ROOTNODE_MAP = 213, OPENVDB_FILE_VERSION_INTERNALNODE_COMPRESSION = 214, OPENVDB_FILE_VERSION_SIMPLIFIED_GRID_TYPENAME = 215, OPENVDB_FILE_VERSION_GRID_INSTANCING = 216, OPENVDB_FILE_VERSION_BOOL_LEAF_OPTIMIZATION = 217, OPENVDB_FILE_VERSION_BOOST_UUID = 218, OPENVDB_FILE_VERSION_NO_GRIDMAP = 219, OPENVDB_FILE_VERSION_NEW_TRANSFORM = 219, OPENVDB_FILE_VERSION_SELECTIVE_COMPRESSION = 220, OPENVDB_FILE_VERSION_FLOAT_FRUSTUM_BBOX = 221, OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION = 222 }; struct VersionId { uint32_t first, second; VersionId(): first(0), second(0) {} }; namespace io { /// @brief Return the file format version number associated with the given input stream. OPENVDB_API uint32_t getFormatVersion(std::istream&); /// @brief Return the (major, minor) library version number associated with the given input stream. OPENVDB_API VersionId getLibraryVersion(std::istream&); /// @brief Return a string of the form "./", giving the library /// and file format version numbers associated with the given input stream. OPENVDB_API std::string getVersion(std::istream&); // Associate the current file format and library version numbers with the given input stream. OPENVDB_API void setCurrentVersion(std::istream&); // Associate specific file format and library version numbers with the given stream. OPENVDB_API void setVersion(std::ios_base&, const VersionId& libraryVersion, uint32_t fileVersion); // Return a bitwise OR of compression option flags (COMPRESS_ZIP, COMPRESS_ACTIVE_MASK, etc.) // specifying whether and how input data is compressed or output data should be compressed. OPENVDB_API uint32_t getDataCompression(std::ios_base&); // Associate with the given stream a bitwise OR of compression option flags (COMPRESS_ZIP, // COMPRESS_ACTIVE_MASK, etc.) specifying whether and how input data is compressed // or output data should be compressed. OPENVDB_API void setDataCompression(std::ios_base&, uint32_t compressionFlags); // Return the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) of the grid // currently being read from or written to the given stream. OPENVDB_API uint32_t getGridClass(std::ios_base&); // brief Associate with the given stream the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) // of the grid currently being read or written. OPENVDB_API void setGridClass(std::ios_base&, uint32_t); // Return a pointer to the background value of the grid currently being // read from or written to the given stream. OPENVDB_API const void* getGridBackgroundValuePtr(std::ios_base&); // Specify (a pointer to) the background value of the grid currently being // read from or written to the given stream. // The pointer must remain valid until the entire grid has been read or written. OPENVDB_API void setGridBackgroundValuePtr(std::ios_base&, const void* background); } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_VERSION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/openvdb.h0000644000000000000000000000753212252453157013034 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_INIT_HAS_BEEN_INCLUDED #define OPENVDB_INIT_HAS_BEEN_INCLUDED #include "Platform.h" #include "Types.h" #include "Metadata.h" #include "math/Maps.h" #include "math/Transform.h" #include "Grid.h" #include "tree/Tree.h" #include "io/File.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { /// Common tree types typedef tree::Tree4::Type BoolTree; typedef tree::Tree4::Type FloatTree; typedef tree::Tree4::Type DoubleTree; typedef tree::Tree4::Type Int32Tree; typedef tree::Tree4::Type UInt32Tree; typedef tree::Tree4::Type Int64Tree; typedef tree::Tree4::Type HermiteTree; typedef tree::Tree4::Type Vec2ITree; typedef tree::Tree4::Type Vec2STree; typedef tree::Tree4::Type Vec2DTree; typedef tree::Tree4::Type Vec3ITree; typedef tree::Tree4::Type Vec3STree; typedef tree::Tree4::Type Vec3DTree; typedef tree::Tree4::Type StringTree; typedef Vec3STree Vec3fTree; typedef Vec3DTree Vec3dTree; typedef FloatTree ScalarTree; typedef Vec3fTree VectorTree; /// Common grid types typedef Grid BoolGrid; typedef Grid FloatGrid; typedef Grid DoubleGrid; typedef Grid Int32Grid; typedef Grid Int64Grid; typedef Grid HermiteGrid; typedef Grid Vec3IGrid; typedef Grid Vec3SGrid; typedef Grid Vec3DGrid; typedef Grid StringGrid; typedef Vec3SGrid Vec3fGrid; typedef Vec3DGrid Vec3dGrid; typedef FloatGrid ScalarGrid; typedef Vec3fGrid VectorGrid; /// Global registration of basic types OPENVDB_API void initialize(); /// Global deregistration of basic types OPENVDB_API void uninitialize(); } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_INIT_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Platform.h0000644000000000000000000001434612252453157013164 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// /// @file Platform.h #ifndef OPENVDB_PLATFORM_HAS_BEEN_INCLUDED #define OPENVDB_PLATFORM_HAS_BEEN_INCLUDED #include "PlatformConfig.h" /// Use OPENVDB_DEPRECATED to mark functions as deprecated. /// It should be placed right before the signature of the function, /// e.g., "OPENVDB_DEPRECATED void functionName();". #ifdef OPENVDB_DEPRECATED #undef OPENVDB_DEPRECATED #endif #ifdef _MSC_VER #define OPENVDB_DEPRECATED __declspec(deprecated) #else #define OPENVDB_DEPRECATED __attribute__ ((deprecated)) #endif /// Macro for determining if GCC version is >= than X.Y #if defined(__GNUC__) #define OPENVDB_CHECK_GCC(MAJOR, MINOR) \ (__GNUC__ > MAJOR || (__GNUC__ == MAJOR && __GNUC_MINOR__ >= MINOR)) #else #define OPENVDB_CHECK_GCC(MAJOR, MINOR) 0 #endif /// For compilers that need templated function specializations to have /// storage qualifiers, we need to declare the specializations as static inline. /// Otherwise, we'll get linker errors about multiply defined symbols. #if defined(__GNUC__) && OPENVDB_CHECK_GCC(4, 4) #define OPENVDB_STATIC_SPECIALIZATION #else #define OPENVDB_STATIC_SPECIALIZATION static #endif /// Bracket code with OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN/_END, /// as in the following example, to inhibit ICC remarks about unreachable code: /// @code /// template /// void processNode(NodeType& node) /// { /// OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN /// if (NodeType::LEVEL == 0) return; // ignore leaf nodes /// int i = 0; /// ... /// OPENVDB_NO_UNREACHABLE_CODE_WARNING_END /// } /// @endcode /// In the above, NodeType::LEVEL == 0 is a compile-time constant expression, /// so for some template instantiations, the line below it is unreachable. #if defined(__INTEL_COMPILER) // Disable ICC remarks 111 ("statement is unreachable"), 128 ("loop is not reachable"), // 185 ("dynamic initialization in unreachable code"), and 280 ("selector expression // is constant"). #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN \ _Pragma("warning (push)") \ _Pragma("warning (disable:111)") \ _Pragma("warning (disable:128)") \ _Pragma("warning (disable:185)") \ _Pragma("warning (disable:280)") #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_END \ _Pragma("warning (pop)") #else #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_END #endif /// Visual C++ does not have constants like M_PI unless this is defined. /// @note This is needed even though the core library is built with this but /// hcustom 12.1 doesn't define it. So this is needed for HDK operators. #ifndef _USE_MATH_DEFINES #define _USE_MATH_DEFINES #endif /// Visual C++ does not have round #ifdef _MSC_VER #include using boost::math::round; #endif /// Visual C++ uses _copysign() instead of copysign() #ifdef _MSC_VER #include static inline double copysign(double x, double y) { return _copysign(x, y); } #endif /// Visual C++ does not have stdint.h which defines types like uint64_t. /// So for portability we instead include boost/cstdint.hpp. #include using boost::int8_t; using boost::int16_t; using boost::int32_t; using boost::int64_t; using boost::uint8_t; using boost::uint16_t; using boost::uint32_t; using boost::uint64_t; /// Helper macros for defining library symbol visibility #ifdef OPENVDB_EXPORT #undef OPENVDB_EXPORT #endif #ifdef OPENVDB_IMPORT #undef OPENVDB_IMPORT #endif #ifdef __GNUC__ #define OPENVDB_EXPORT __attribute__((visibility("default"))) #define OPENVDB_IMPORT __attribute__((visibility("default"))) #endif #ifdef _WIN32 #ifdef OPENVDB_DLL #define OPENVDB_EXPORT __declspec(dllexport) #define OPENVDB_IMPORT __declspec(dllimport) #else #define OPENVDB_EXPORT #define OPENVDB_IMPORT #endif #endif /// All classes and public free standing functions must be explicitly marked /// as \_API to be exported. The \_PRIVATE macros are defined when /// building that particular library. #ifdef OPENVDB_API #undef OPENVDB_API #endif #ifdef OPENVDB_PRIVATE #define OPENVDB_API OPENVDB_EXPORT #else #define OPENVDB_API OPENVDB_IMPORT #endif #ifdef OPENVDB_HOUDINI_API #undef OPENVDB_HOUDINI_API #endif #ifdef OPENVDB_HOUDINI_PRIVATE #define OPENVDB_HOUDINI_API OPENVDB_EXPORT #else #define OPENVDB_HOUDINI_API OPENVDB_IMPORT #endif #endif // OPENVDB_PLATFORM_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Types.h0000644000000000000000000003624512252453157012506 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_TYPES_HAS_BEEN_INCLUDED #define OPENVDB_TYPES_HAS_BEEN_INCLUDED #include "version.h" #include "Platform.h" #include #include #include #include #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { // One-dimensional scalar types typedef uint32_t Index32; typedef uint64_t Index64; typedef Index32 Index; typedef int16_t Int16; typedef int32_t Int32; typedef int64_t Int64; typedef Int32 Int; typedef unsigned char Byte; typedef double Real; // Two-dimensional vector types typedef math::Vec2 Vec2R; typedef math::Vec2 Vec2I; typedef math::Vec2 Vec2f; typedef math::Vec2 Vec2H; using math::Vec2i; using math::Vec2s; using math::Vec2d; // Three-dimensional vector types typedef math::Vec3 Vec3R; typedef math::Vec3 Vec3I; typedef math::Vec3 Vec3f; typedef math::Vec3 Vec3H; using math::Vec3i; using math::Vec3s; using math::Vec3d; using math::Coord; using math::CoordBBox; typedef math::BBox BBoxd; // Four-dimensional vector types typedef math::Vec4 Vec4R; typedef math::Vec4 Vec4I; typedef math::Vec4 Vec4f; typedef math::Vec4 Vec4H; using math::Vec4i; using math::Vec4s; using math::Vec4d; // Three-dimensional matrix types typedef math::Mat3 Mat3R; // Four-dimensional matrix types typedef math::Mat4 Mat4R; typedef math::Mat4 Mat4d; typedef math::Mat4 Mat4s; // Compressed Hermite data typedef math::Hermite Hermite; // Quaternions typedef math::Quat QuatR; //////////////////////////////////////// template struct VecTraits { static const bool IsVec = false; static const int Size = 1; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 2; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 3; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 4; }; //////////////////////////////////////// // Add new items to the *end* of this list, and update NUM_GRID_CLASSES. enum GridClass { GRID_UNKNOWN = 0, GRID_LEVEL_SET, GRID_FOG_VOLUME, GRID_STAGGERED }; enum { NUM_GRID_CLASSES = GRID_STAGGERED + 1 }; static const Real LEVEL_SET_HALF_WIDTH = 3; /// The type of a vector determines how transforms are applied to it: ///
///
Invariant ///
Does not transform (e.g., tuple, uvw, color) /// ///
Covariant ///
Apply inverse-transpose transformation: @e w = 0, ignores translation /// (e.g., gradient/normal) /// ///
Covariant Normalize ///
Apply inverse-transpose transformation: @e w = 0, ignores translation, /// vectors are renormalized (e.g., unit normal) /// ///
Contravariant Relative ///
Apply "regular" transformation: @e w = 0, ignores translation /// (e.g., displacement, velocity, acceleration) /// ///
Contravariant Absolute ///
Apply "regular" transformation: @e w = 1, vector translates (e.g., position) ///
enum VecType { VEC_INVARIANT = 0, VEC_COVARIANT, VEC_COVARIANT_NORMALIZE, VEC_CONTRAVARIANT_RELATIVE, VEC_CONTRAVARIANT_ABSOLUTE }; enum { NUM_VEC_TYPES = VEC_CONTRAVARIANT_ABSOLUTE + 1 }; /// Specify how grids should be merged during certain (typically multithreaded) operations. ///
///
MERGE_ACTIVE_STATES ///
The output grid is active wherever any of the input grids is active. /// ///
MERGE_NODES ///
The output grid's tree has a node wherever any of the input grids' trees /// has a node, regardless of any active states. /// ///
MERGE_ACTIVE_STATES_AND_NODES ///
The output grid is active wherever any of the input grids is active, /// and its tree has a node wherever any of the input grids' trees has a node. ///
enum MergePolicy { MERGE_ACTIVE_STATES = 0, MERGE_NODES, MERGE_ACTIVE_STATES_AND_NODES }; //////////////////////////////////////// template const char* typeNameAsString() { return typeid(T).name(); } template<> inline const char* typeNameAsString() { return "bool"; } template<> inline const char* typeNameAsString() { return "float"; } template<> inline const char* typeNameAsString() { return "double"; } template<> inline const char* typeNameAsString() { return "int32"; } template<> inline const char* typeNameAsString() { return "uint32"; } template<> inline const char* typeNameAsString() { return "int64"; } template<> inline const char* typeNameAsString() { return "Hermite"; } template<> inline const char* typeNameAsString() { return "vec2i"; } template<> inline const char* typeNameAsString() { return "vec2s"; } template<> inline const char* typeNameAsString() { return "vec2d"; } template<> inline const char* typeNameAsString() { return "vec3i"; } template<> inline const char* typeNameAsString() { return "vec3s"; } template<> inline const char* typeNameAsString() { return "vec3d"; } template<> inline const char* typeNameAsString() { return "string"; } template<> inline const char* typeNameAsString() { return "mat4s"; } template<> inline const char* typeNameAsString() { return "mat4d"; } //////////////////////////////////////// /// @brief This struct collects both input and output arguments to "grid combiner" functors /// used with the tree::TypedGrid::combineExtended() and combine2Extended() methods. /// ValueType is the value type of the two grids being combined. /// /// @see openvdb/tree/Tree.h for usage information. /// /// Setter methods return references to this object, to facilitate the following usage: /// @code /// CombineArgs args; /// myCombineOp(args.setARef(aVal).setBRef(bVal).setAIsActive(true).setBIsActive(false)); /// @endcode template class CombineArgs { public: typedef ValueType ValueT; CombineArgs(): mAValPtr(NULL), mBValPtr(NULL), mResultValPtr(&mResultVal), mAIsActive(false), mBIsActive(false), mResultIsActive(false) {} /// Use this constructor when the result value is stored externally. CombineArgs(const ValueType& a, const ValueType& b, ValueType& result, bool aOn = false, bool bOn = false): mAValPtr(&a), mBValPtr(&b), mResultValPtr(&result), mAIsActive(aOn), mBIsActive(bOn) { updateResultActive(); } /// Use this constructor when the result value should be stored in this struct. CombineArgs(const ValueType& a, const ValueType& b, bool aOn = false, bool bOn = false): mAValPtr(&a), mBValPtr(&b), mResultValPtr(&mResultVal), mAIsActive(aOn), mBIsActive(bOn) { updateResultActive(); } /// Get the A input value. const ValueType& a() const { return *mAValPtr; } /// Get the B input value. const ValueType& b() const { return *mBValPtr; } //@{ /// Get the output value. const ValueType& result() const { return *mResultValPtr; } ValueType& result() { return *mResultValPtr; } //@} /// Set the output value. CombineArgs& setResult(const ValueType& val) { *mResultValPtr = val; return *this; } /// Redirect the A value to a new external source. CombineArgs& setARef(const ValueType& a) { mAValPtr = &a; return *this; } /// Redirect the B value to a new external source. CombineArgs& setBRef(const ValueType& b) { mBValPtr = &b; return *this; } /// Redirect the result value to a new external destination. CombineArgs& setResultRef(ValueType& val) { mResultValPtr = &val; return *this; } /// @return true if the A value is active bool aIsActive() const { return mAIsActive; } /// @return true if the B value is active bool bIsActive() const { return mBIsActive; } /// @return true if the output value is active bool resultIsActive() const { return mResultIsActive; } /// Set the active state of the A value. CombineArgs& setAIsActive(bool b) { mAIsActive = b; updateResultActive(); return *this; } /// Set the active state of the B value. CombineArgs& setBIsActive(bool b) { mBIsActive = b; updateResultActive(); return *this; } /// Set the active state of the output value. CombineArgs& setResultIsActive(bool b) { mResultIsActive = b; return *this; } protected: /// By default, the result value is active if either of the input values is active, /// but this behavior can be overridden by calling setResultIsActive(). void updateResultActive() { mResultIsActive = mAIsActive || mBIsActive; } const ValueType* mAValPtr; // pointer to input value from A grid const ValueType* mBValPtr; // pointer to input value from B grid ValueType mResultVal; // computed output value (unused if stored externally) ValueType* mResultValPtr; // pointer to either mResultVal or an external value bool mAIsActive, mBIsActive; // active states of A and B values bool mResultIsActive; // computed active state (default: A active || B active) }; /// This struct adapts a "grid combiner" functor to swap the A and B grid values /// (e.g., so that if the original functor computes a + 2 * b, the adapted functor /// will compute b + 2 * a). template struct SwappedCombineOp { SwappedCombineOp(CombineOp& op): op(op) {} void operator()(CombineArgs& args) { CombineArgs swappedArgs(args.b(), args.a(), args.result(), args.bIsActive(), args.aIsActive()); op(swappedArgs); } CombineOp& op; }; //////////////////////////////////////// /// In copy constructors, members stored as shared pointers can be handled /// in several ways: ///
///
CP_NEW ///
Don't copy the member; default construct a new member object instead. /// ///
CP_SHARE ///
Copy the shared pointer, so that the original and new objects share /// the same member. /// ///
CP_COPY ///
Create a deep copy of the member. ///
enum CopyPolicy { CP_NEW, CP_SHARE, CP_COPY }; // Dummy class that distinguishes shallow copy constructors from // deep copy constructors class ShallowCopy {}; // Dummy class that distinguishes topology copy constructors from // deep copy constructors class TopologyCopy {}; } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #if defined(__ICC) // Use these defines to bracket a region of code that has safe static accesses. // Keep the region as small as possible. #define OPENVDB_START_THREADSAFE_STATIC_REFERENCE __pragma(warning(disable:1710)) #define OPENVDB_FINISH_THREADSAFE_STATIC_REFERENCE __pragma(warning(default:1710)) #define OPENVDB_START_THREADSAFE_STATIC_WRITE __pragma(warning(disable:1711)) #define OPENVDB_FINISH_THREADSAFE_STATIC_WRITE __pragma(warning(default:1711)) #define OPENVDB_START_THREADSAFE_STATIC_ADDRESS __pragma(warning(disable:1712)) #define OPENVDB_FINISH_THREADSAFE_STATIC_ADDRESS __pragma(warning(default:1712)) // Use these defines to bracket a region of code that has unsafe static accesses. // Keep the region as small as possible. #define OPENVDB_START_NON_THREADSAFE_STATIC_REFERENCE __pragma(warning(disable:1710)) #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_REFERENCE __pragma(warning(default:1710)) #define OPENVDB_START_NON_THREADSAFE_STATIC_WRITE __pragma(warning(disable:1711)) #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_WRITE __pragma(warning(default:1711)) #define OPENVDB_START_NON_THREADSAFE_STATIC_ADDRESS __pragma(warning(disable:1712)) #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_ADDRESS __pragma(warning(default:1712)) // Simpler version for one-line cases #define OPENVDB_THREADSAFE_STATIC_REFERENCE(CODE) \ __pragma(warning(disable:1710)); CODE; __pragma(warning(default:1710)) #define OPENVDB_THREADSAFE_STATIC_WRITE(CODE) \ __pragma(warning(disable:1711)); CODE; __pragma(warning(default:1711)) #define OPENVDB_THREADSAFE_STATIC_ADDRESS(CODE) \ __pragma(warning(disable:1712)); CODE; __pragma(warning(default:1712)) #else // GCC does not support these compiler warnings #define OPENVDB_START_THREADSAFE_STATIC_REFERENCE #define OPENVDB_FINISH_THREADSAFE_STATIC_REFERENCE #define OPENVDB_START_THREADSAFE_STATIC_WRITE #define OPENVDB_FINISH_THREADSAFE_STATIC_WRITE #define OPENVDB_START_THREADSAFE_STATIC_ADDRESS #define OPENVDB_FINISH_THREADSAFE_STATIC_ADDRESS #define OPENVDB_START_NON_THREADSAFE_STATIC_REFERENCE #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_REFERENCE #define OPENVDB_START_NON_THREADSAFE_STATIC_WRITE #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_WRITE #define OPENVDB_START_NON_THREADSAFE_STATIC_ADDRESS #define OPENVDB_FINISH_NON_THREADSAFE_STATIC_ADDRESS #define OPENVDB_THREADSAFE_STATIC_REFERENCE(CODE) CODE #define OPENVDB_THREADSAFE_STATIC_WRITE(CODE) CODE #define OPENVDB_THREADSAFE_STATIC_ADDRESS(CODE) CODE #endif // defined(__ICC) #endif // OPENVDB_TYPES_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/PlatformConfig.h0000644000000000000000000000474412252453157014313 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// /// @file PlatformConfig.h #ifndef OPENVDB_PLATFORMCONFIG_HAS_BEEN_INCLUDED #define OPENVDB_PLATFORMCONFIG_HAS_BEEN_INCLUDED // Windows specific configuration #ifdef _WIN32 // By default, assume we're building OpenVDB as a DLL if we're dynamically // linking in the CRT, unless OPENVDB_STATICLIB is defined. #if defined(_DLL) && !defined(OPENVDB_STATICLIB) && !defined(OPENVDB_DLL) #define OPENVDB_DLL #endif // By default, assume that we're dynamically linking OpenEXR, unless // OPENVDB_OPENEXR_STATICLIB is defined. #if !defined(OPENVDB_OPENEXR_STATICLIB) && !defined(OPENEXR_DLL) #define OPENEXR_DLL #endif #endif // _WIN32 #endif // OPENVDB_PLATFORMCONFIG_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/cmd/0000755000000000000000000000000012252453157011762 5ustar rootrootopenvdb/cmd/openvdb_print/0000755000000000000000000000000012252453157014633 5ustar rootrootopenvdb/cmd/openvdb_print/main.cc0000644000000000000000000002764112252453157016100 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #ifdef DWA_OPENVDB #include #include #endif namespace { typedef std::vector StringVec; const char* INDENT = " "; const char* gProgName = ""; void usage(int exitStatus = EXIT_FAILURE) { std::cerr << "Usage: " << gProgName << " in.vdb [in.vdb ...] [options]\n" << "Which: prints information about OpenVDB grids\n" << "Options:\n" << " -l, -stats long printout, including grid statistics\n" << " -m, -metadata print per-file and per-grid metadata\n"; exit(exitStatus); } std::string sizeAsString(openvdb::Index64 n, const std::string& units) { std::ostringstream ostr; ostr << std::setprecision(3); if (n < 1000) { ostr << n; } else if (n < 1000000) { ostr << (n / 1.0e3) << "K"; } else if (n < 1000000000) { ostr << (n / 1.0e6) << "M"; } else { ostr << (n / 1.0e9) << "G"; } ostr << units; return ostr.str(); } std::string bytesAsString(openvdb::Index64 n) { std::ostringstream ostr; ostr << std::setprecision(3); if (n >> 30) { ostr << (n / double(uint64_t(1) << 30)) << "GB"; } else if (n >> 20) { ostr << (n / double(uint64_t(1) << 20)) << "MB"; } else if (n >> 10) { ostr << (n / double(uint64_t(1) << 10)) << "KB"; } else { ostr << n << "B"; } return ostr.str(); } std::string coordAsString(const openvdb::Coord ijk, const std::string& sep) { std::ostringstream ostr; ostr << ijk[0] << sep << ijk[1] << sep << ijk[2]; return ostr.str(); } /// Return a string representation of the given metadata key, value pairs std::string metadataAsString( const openvdb::MetaMap::ConstMetaIterator& begin, const openvdb::MetaMap::ConstMetaIterator& end, const std::string& indent = "") { std::ostringstream ostr; char sep[2] = { 0, 0 }; for (openvdb::MetaMap::ConstMetaIterator it = begin; it != end; ++it) { ostr << sep << indent << it->first; if (it->second) { const std::string value = it->second->str(); if (!value.empty()) ostr << ": " << value; } sep[0] = '\n'; } return ostr.str(); } std::string bkgdValueAsString(const openvdb::GridBase::ConstPtr& grid) { std::ostringstream ostr; if (grid) { const openvdb::TreeBase& tree = grid->baseTree(); ostr << "background: "; openvdb::Metadata::Ptr background = tree.getBackgroundValue(); if (background) ostr << background->str(); } return ostr.str(); } /// Print detailed information about the given VDB files. /// If @a metadata is true, include file-level metadata key, value pairs. void printLongListing(const StringVec& filenames) { bool oneFile = (filenames.size() == 1), firstFile = true; for (size_t i = 0, N = filenames.size(); i < N; ++i, firstFile = false) { openvdb::io::File file(filenames[i]); std::string version; openvdb::GridPtrVecPtr grids; openvdb::MetaMap::Ptr meta; try { file.open(); grids = file.getGrids(); meta = file.getMetadata(); version = file.version(); file.close(); } catch (openvdb::Exception& e) { OPENVDB_LOG_ERROR(e.what() << " (" << filenames[i] << ")"); } if (!grids) continue; if (!oneFile) { if (!firstFile) { std::cout << "\n" << std::string(40, '-') << "\n\n"; } std::cout << filenames[i] << "\n\n"; } // Print file-level metadata. std::cout << "VDB version: " << version << "\n"; if (meta) { std::string str = metadataAsString(meta->beginMeta(), meta->endMeta()); if (!str.empty()) std::cout << str << "\n"; } std::cout << "\n"; // For each grid in the file... bool firstGrid = true; for (openvdb::GridPtrVec::const_iterator it = grids->begin(); it != grids->end(); ++it) { if (openvdb::GridBase::ConstPtr grid = *it) { if (!firstGrid) std::cout << "\n\n"; std::cout << "Name: " << grid->getName() << std::endl; grid->print(std::cout, /*verboseLevel=*/3); firstGrid = false; } } } } /// Print condensed information about the given VDB files. /// If @a metadata is true, include file- and grid-level metadata. void printShortListing(const StringVec& filenames, bool metadata) { bool oneFile = (filenames.size() == 1), firstFile = true; for (size_t i = 0, N = filenames.size(); i < N; ++i, firstFile = false) { const std::string indent(oneFile ? "": INDENT), indent2(indent + INDENT); if (!oneFile) { if (metadata && !firstFile) std::cout << "\n"; std::cout << filenames[i] << ":\n"; } openvdb::GridPtrVecPtr grids; openvdb::MetaMap::Ptr meta; openvdb::io::File file(filenames[i]); try { file.open(); grids = file.getGrids(); meta = file.getMetadata(); file.close(); } catch (openvdb::Exception& e) { OPENVDB_LOG_ERROR(e.what() << " (" << filenames[i] << ")"); } if (!grids) continue; if (metadata) { // Print file-level metadata. std::string str = metadataAsString(meta->beginMeta(), meta->endMeta(), indent); if (!str.empty()) std::cout << str << "\n"; } // For each grid in the file... for (openvdb::GridPtrVec::const_iterator it = grids->begin(); it != grids->end(); ++it) { const openvdb::GridBase::ConstPtr grid = *it; if (!grid) continue; // Print the grid name and its voxel value datatype. std::cout << indent << std::left << std::setw(11) << grid->getName() << " " << std::right << std::setw(6) << grid->valueType(); // Print the grid's bounding box and dimensions. openvdb::CoordBBox bbox = grid->evalActiveVoxelBoundingBox(); std::string boxStr = coordAsString(bbox.min()," ") + " " + coordAsString(bbox.max()," "), dimStr = coordAsString(bbox.extents(), "x"); boxStr += std::string(std::max(1, 40 - boxStr.size() - dimStr.size()), ' ') + dimStr; std::cout << " " << std::left << std::setw(40) << boxStr; // Print the number of active voxels. std::cout << " " << std::right << std::setw(8) << sizeAsString(grid->activeVoxelCount(), "Vox"); // Print the grid's in-core size, in bytes. std::cout << " " << std::right << std::setw(6) << bytesAsString(grid->memUsage()); std::cout << std::endl; // Print grid-specific metadata. if (metadata) { // Print background value. std::string str = bkgdValueAsString(grid); if (!str.empty()) { std::cout << indent2 << str << "\n"; } // Print local and world transforms. grid->transform().print(std::cout, indent2); // Print custom metadata. str = metadataAsString(grid->beginMeta(), grid->endMeta(), indent2); if (!str.empty()) std::cout << str << "\n"; std::cout << std::flush; } } } } } // unnamed namespace int main(int argc, char *argv[]) { #ifdef DWA_OPENVDB USAGETRACK_report_basic_tool_usage(argc, argv, /*duration=*/0); logging_base::configure(argc, argv); #endif OPENVDB_START_THREADSAFE_STATIC_WRITE gProgName = argv[0]; if (const char* ptr = ::strrchr(gProgName, '/')) gProgName = ptr + 1; OPENVDB_FINISH_THREADSAFE_STATIC_WRITE int exitStatus = EXIT_SUCCESS; if (argc == 1) usage(); bool stats = false, metadata = false; StringVec filenames; for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg[0] == '-') { if (arg == "-m" || arg == "-metadata") { metadata = true; } else if (arg == "-l" || arg == "-stats") { stats = true; } else if (arg == "-h" || arg == "-help" || arg == "--help") { usage(EXIT_SUCCESS); } else { std::cerr << gProgName << ": \"" << arg << "\" is not a valid option\n"; usage(); } } else if (!arg.empty()) { filenames.push_back(arg); } } if (filenames.empty()) { std::cerr << gProgName << ": expected one or more OpenVDB files\n"; usage(); } try { openvdb::initialize(); /// @todo Remove the following at some point: openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); openvdb::Grid::Type>::registerGrid(); if (stats) { printLongListing(filenames); } else { printShortListing(filenames, metadata); } } catch (const std::exception& e) { OPENVDB_LOG_FATAL(e.what()); exitStatus = EXIT_FAILURE; } catch (...) { OPENVDB_LOG_FATAL("Exception caught (unexpected type)"); std::unexpected(); } return exitStatus; } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/cmd/openvdb_render/0000755000000000000000000000000012252453157014756 5ustar rootrootopenvdb/cmd/openvdb_render/main.cc0000644000000000000000000005241312252453157016216 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file main.cc /// /// @brief Simple ray tracer for OpenVDB volumes /// /// @note This is intended mainly as an example of how to ray-trace OpenVDB volumes. /// It is not a production-quality renderer, and it is currently limited to /// rendering level set volumes. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DWA_OPENVDB #include #include #endif namespace { const char* gProgName = ""; struct RenderOpts { std::string shader; std::string camera; float aperture, focal, frame, znear, zfar; openvdb::Vec3R rotate; openvdb::Vec3R translate; openvdb::Vec3R target; openvdb::Vec3R up; bool lookat; size_t samples; size_t width, height; std::string compression; int threads; bool verbose; RenderOpts(): shader("diffuse"), camera("perspective"), aperture(41.2136), focal(50.0), frame(1.0), znear(1.0e-3), zfar(std::numeric_limits::max()), rotate(0.0), translate(0.0), target(0.0), up(0.0, 1.0, 0.0), lookat(false), samples(1), width(1920), height(1080), compression("zip"), threads(0), verbose(false) {} std::string validate() const { if (shader != "diffuse" && shader != "matte" && shader != "normal" && shader != "position"){ return "expected diffuse, matte, normal or position shader, got \"" + shader + "\""; } if (!boost::starts_with(camera, "ortho") && !boost::starts_with(camera, "persp")) { return "expected perspective or orthographic camera, got \"" + camera + "\""; } if (compression != "none" && compression != "rle" && compression != "zip") { return "expected none, rle or zip compression, got \"" + compression + "\""; } if (width < 1 || height < 1) { std::ostringstream ostr; ostr << "expected width > 0 and height > 0, got " << width << "x" << height; return ostr.str(); } return ""; } std::ostream& put(std::ostream& os) const { os << "-aperture " << aperture << " -camera " << camera << " -compression " << compression << " -cpus " << threads << " -far " << zfar << " -focal " << focal << " -frame " << frame; if (lookat) os << " -lookat " << target[0] << "," << target[1] << "," << target[2]; os << " -near " << znear << " -res " << width << "x" << height; if (!lookat) os << " -rotate " << rotate[0] << "," << rotate[1] << "," << rotate[2]; os << " -shader " << shader << " -samples " << samples << " -translate " << translate[0] << "," << translate[1] << "," << translate[2]; if (lookat) os << " -up " << up[0] << "," << up[1] << "," << up[2]; if (verbose) os << " -v"; return os; } }; std::ostream& operator<<(std::ostream& os, const RenderOpts& opts) { return opts.put(os); } void usage(int exitStatus = EXIT_FAILURE) { RenderOpts opts; // default options const float fov = openvdb::tools::PerspectiveCamera::focalLengthToFieldOfView( opts.focal, opts.aperture); std::ostringstream ostr; ostr << std::setprecision(3) << "Usage: " << gProgName << " in.vdb out.{exr,ppm} [options]\n" << "Which: ray-traces OpenVDB volumes\n" << "Options:\n" << " -aperture F perspective camera aperture in mm (default: " << opts.aperture << ")\n" << " -camera S camera type; either \"persp[ective]\" or \"ortho[graphic]\"\n" << " (default: " << opts.camera << ")\n" << " -compression S EXR compression scheme; either \"none\" (uncompressed),\n" << " \"rle\" or \"zip\" (default: " << opts.compression << ")\n" << " -cpus N number of rendering threads, or 1 to disable threading,\n" << " or 0 to use all available CPUs (default: " << opts.threads << ")\n" << " -far F camera far plane depth (default: " << opts.zfar << ")\n" << " -focal F perspective camera focal length in mm (default: " << opts.focal << ")\n" << " -fov F perspective camera field of view in degrees\n" << " (default: " << fov << ")\n" << " -frame F ortho camera frame width in world units (default: " << opts.frame << ")\n" << " -lookat X,Y,Z rotate the camera to point to (X, Y, Z)\n" << " -name S name of the grid to be rendered (default: render\n" << " the first floating-point grid found in in.vdb)\n" << " -near F camera near plane depth (default: " << opts.znear << ")\n" << " -res WxH image dimensions in pixels (default: " << opts.width << "x" << opts.height << ")\n" << " -r X,Y,Z \n" << " -rotate X,Y,Z camera rotation in degrees\n" << " (default: look at the center of the grid)\n" << " -shader S shader name; either \"diffuse\", \"matte\", \"normal\"\n" << " or \"position\" (default: " << opts.shader << ")\n" << " -samples N number of samples (rays) per pixel (default: " << opts.samples << ")\n" << " -t X,Y,Z \n" << " -translate X,Y,Z camera translation\n" << " -up X,Y,Z vector that should point up after rotation with -lookat\n" << " (default: " << opts.up << ")\n" << "\n" << " -v verbose (print timing and diagnostics)\n" << " -h, -help print this usage message and exit\n" << "\n" << "Example:\n" << " " << gProgName << " crawler.vdb crawler.exr -shader diffuse -res 1920x1080 \\\n" << " -focal 35 -samples 4 -translate 0,210.5,400 -compression rle -v\n" << "\n" << "This is not (and is not intended to be) a production-quality renderer,\n" << "and it is currently limited to rendering level set volumes.\n"; std::cerr << ostr.str(); exit(exitStatus); } void saveEXR(const std::string& fname, const openvdb::tools::Film& film, const RenderOpts& opts) { typedef openvdb::tools::Film::RGBA RGBA; std::string filename = fname; if (!boost::iends_with(filename, ".exr")) filename += ".exr"; if (opts.verbose) { std::cout << gProgName << ": writing " << filename << "..." << std::endl; } const tbb::tick_count start = tbb::tick_count::now(); int threads = (opts.threads == 0 ? 8 : opts.threads); Imf::setGlobalThreadCount(threads); Imf::Header header(film.width(), film.height()); if (opts.compression == "none") { header.compression() = Imf::NO_COMPRESSION; } else if (opts.compression == "rle") { header.compression() = Imf::RLE_COMPRESSION; } else if (opts.compression == "zip") { header.compression() = Imf::ZIP_COMPRESSION; } else { OPENVDB_THROW(openvdb::ValueError, "expected none, rle or zip compression, got \"" << opts.compression << "\""); } header.channels().insert("R", Imf::Channel(Imf::FLOAT)); header.channels().insert("G", Imf::Channel(Imf::FLOAT)); header.channels().insert("B", Imf::Channel(Imf::FLOAT)); header.channels().insert("A", Imf::Channel(Imf::FLOAT)); const size_t pixelBytes = sizeof(RGBA), rowBytes = pixelBytes * film.width(); RGBA& pixel0 = const_cast(film.pixels())[0]; Imf::FrameBuffer framebuffer; framebuffer.insert("R", Imf::Slice(Imf::FLOAT, reinterpret_cast(&pixel0.r), pixelBytes, rowBytes)); framebuffer.insert("G", Imf::Slice(Imf::FLOAT, reinterpret_cast(&pixel0.g), pixelBytes, rowBytes)); framebuffer.insert("B", Imf::Slice(Imf::FLOAT, reinterpret_cast(&pixel0.b), pixelBytes, rowBytes)); framebuffer.insert("A", Imf::Slice(Imf::FLOAT, reinterpret_cast(&pixel0.a), pixelBytes, rowBytes)); Imf::OutputFile imgFile(filename.c_str(), header); imgFile.setFrameBuffer(framebuffer); imgFile.writePixels(film.height()); if (opts.verbose) { std::ostringstream ostr; ostr << gProgName << ": ...completed in " << std::setprecision(3) << (tbb::tick_count::now() - start).seconds() << " sec"; std::cout << ostr.str() << std::endl; } } template void render(const GridType& grid, const std::string& imgFilename, const RenderOpts& opts) { using namespace openvdb; const GridClass cls = grid.getGridClass(); if (cls != GRID_LEVEL_SET) { OPENVDB_THROW(ValueError, "expected level set, got " << GridBase::gridClassToString(cls)); } tools::Film film(opts.width, opts.height); boost::scoped_ptr camera; if (boost::starts_with(opts.camera, "persp")) { camera.reset(new tools::PerspectiveCamera(film, opts.rotate, opts.translate, opts.focal, opts.aperture, opts.znear, opts.zfar)); } else if (boost::starts_with(opts.camera, "ortho")) { camera.reset(new tools::OrthographicCamera(film, opts.rotate, opts.translate, opts.frame, opts.znear, opts.zfar)); } else { OPENVDB_THROW(ValueError, "expected perspective or orthographic camera, got \"" << opts.camera << "\""); } if (opts.lookat) camera->lookAt(opts.target, opts.up); boost::scoped_ptr shader; if (opts.shader == "diffuse") { shader.reset(new tools::DiffuseShader); } else if (opts.shader == "matte") { shader.reset(new tools::MatteShader); } else if (opts.shader == "normal") { shader.reset(new tools::NormalShader); } else if (opts.shader == "position") { const CoordBBox b = grid.evalActiveVoxelBoundingBox(); math::BBox bbox(b.min().asVec3d(), b.max().asVec3d()); shader.reset(new tools::PositionShader(bbox.applyMap(*(grid.transform().baseMap())))); } else { OPENVDB_THROW(ValueError, "expected diffuse, matte, normal or position shader, got \"" << opts.shader << "\""); } if (opts.verbose) { std::cout << gProgName << ": ray-tracing..." << std::endl; } const tbb::tick_count start = tbb::tick_count::now(); tools::rayTrace(grid, *shader, *camera, opts.samples, 0, (opts.threads != 1)); if (opts.verbose) { std::ostringstream ostr; ostr << gProgName << ": ...completed in " << std::setprecision(3) << (tbb::tick_count::now() - start).seconds() << " sec"; std::cout << ostr.str() << std::endl; } if (boost::iends_with(imgFilename, ".ppm")) { // Save as PPM (fast, but large file size). std::string filename = imgFilename; filename.erase(filename.size() - 4); // strip .ppm extension film.savePPM(filename); } else if (boost::iends_with(imgFilename, ".exr")) { // Save as EXR (slow, but small file size). saveEXR(imgFilename, film, opts); } else { OPENVDB_THROW(ValueError, "unsupported image file format (" + imgFilename + ")"); } } void strToSize(const std::string& s, size_t& x, size_t& y) { std::vector elems; boost::split(elems, s, boost::algorithm::is_any_of(",x")); const size_t numElems = elems.size(); if (numElems > 0) x = size_t(std::max(0, atoi(elems[0].c_str()))); if (numElems > 1) y = size_t(std::max(0, atoi(elems[1].c_str()))); } openvdb::Vec3R strToVec3R(const std::string& s) { openvdb::Vec3R result; std::vector elems; boost::split(elems, s, boost::algorithm::is_any_of(",")); for (size_t i = 0, N = elems.size(); i < N; ++i) { result[i] = atof(elems[i].c_str()); } return result; } struct OptParse { int argc; char** argv; OptParse(int argc_, char* argv_[]): argc(argc_), argv(argv_) {} bool check(int idx, const std::string& name, int numArgs = 1) const { if (argv[idx] == name) { if (idx + numArgs >= argc) { std::cerr << gProgName << ": option " << name << " requires " << numArgs << " argument" << (numArgs == 1 ? "" : "s") << "\n"; usage(); } return true; } return false; } }; } // unnamed namespace int main(int argc, char *argv[]) { #ifdef DWA_OPENVDB USAGETRACK_report_basic_tool_usage(argc, argv, /*duration=*/0); logging_base::configure(argc, argv); #endif OPENVDB_START_THREADSAFE_STATIC_WRITE gProgName = argv[0]; if (const char* ptr = ::strrchr(gProgName, '/')) gProgName = ptr + 1; OPENVDB_FINISH_THREADSAFE_STATIC_WRITE int retcode = EXIT_SUCCESS; if (argc == 1) usage(); std::string vdbFilename, imgFilename, gridName; RenderOpts opts; bool hasFocal = false, hasFov = false, hasRotate = false, hasLookAt = false; float fov = 0.0; OptParse parser(argc, argv); for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg[0] == '-') { if (parser.check(i, "-aperture")) { ++i; opts.aperture = atof(argv[i]); } else if (parser.check(i, "-camera")) { ++i; opts.camera = argv[i]; } else if (parser.check(i, "-compression")) { ++i; opts.compression = argv[i]; } else if (parser.check(i, "-cpus")) { ++i; opts.threads = std::max(0, atoi(argv[i])); } else if (parser.check(i, "-far")) { ++i; opts.zfar = atof(argv[i]); } else if (parser.check(i, "-focal")) { ++i; opts.focal = atof(argv[i]); hasFocal = true; } else if (parser.check(i, "-fov")) { ++i; fov = atof(argv[i]); hasFov = true; } else if (parser.check(i, "-frame")) { ++i; opts.frame = atof(argv[i]); } else if (parser.check(i, "-lookat")) { ++i; opts.lookat = true; opts.target = strToVec3R(argv[i]); hasLookAt = true; } else if (parser.check(i, "-name")) { ++i; gridName = argv[i]; } else if (parser.check(i, "-near")) { ++i; opts.znear = atof(argv[i]); } else if (parser.check(i, "-r") || parser.check(i, "-rotate")) { ++i; opts.rotate = strToVec3R(argv[i]); hasRotate = true; } else if (parser.check(i, "-res")) { ++i; strToSize(argv[i], opts.width, opts.height); } else if (parser.check(i, "-shader")) { ++i; opts.shader = argv[i]; } else if (parser.check(i, "-samples")) { ++i; opts.samples = size_t(std::max(0, atoi(argv[i]))); } else if (parser.check(i, "-t") || parser.check(i, "-translate")) { ++i; opts.translate = strToVec3R(argv[i]); } else if (parser.check(i, "-up")) { ++i; opts.up = strToVec3R(argv[i]); } else if (arg == "-v") { opts.verbose = true; } else if (arg == "-h" || arg == "-help" || arg == "--help") { usage(EXIT_SUCCESS); } else { std::cerr << gProgName << ": \"" << arg << "\" is not a valid option\n"; usage(); } } else if (vdbFilename.empty()) { vdbFilename = arg; } else if (imgFilename.empty()) { imgFilename = arg; } else { usage(); } } if (vdbFilename.empty() || imgFilename.empty()) { usage(); } if (hasFov) { if (hasFocal) { OPENVDB_LOG_FATAL("specify -focal or -fov, but not both"); usage(); } opts.focal = openvdb::tools::PerspectiveCamera::fieldOfViewToFocalLength(fov, opts.aperture); } if (hasLookAt && hasRotate) { OPENVDB_LOG_FATAL("specify -lookat or -r[otate], but not both"); usage(); } { const std::string err = opts.validate(); if (!err.empty()) { OPENVDB_LOG_FATAL(err); usage(); } } try { tbb::task_scheduler_init schedulerInit( (opts.threads == 0) ? tbb::task_scheduler_init::automatic : opts.threads); openvdb::initialize(); const tbb::tick_count start = tbb::tick_count::now(); if (opts.verbose) { std::cout << gProgName << ": reading "; if (!gridName.empty()) std::cout << gridName << " from "; std::cout << vdbFilename << "..." << std::endl; } openvdb::FloatGrid::Ptr grid; { openvdb::io::File file(vdbFilename); file.open(); if (!gridName.empty()) { grid = openvdb::gridPtrCast(file.readGrid(gridName)); if (!grid) { OPENVDB_THROW(openvdb::ValueError, gridName + " is not a scalar, floating-point grid"); } } else { // If no grid was specified by name, retrieve the first float grid from the file. openvdb::GridPtrVecPtr grids = file.readAllGridMetadata(); for (size_t i = 0; i < grids->size(); ++i) { grid = openvdb::gridPtrCast(grids->at(i)); if (grid) { gridName = grid->getName(); file.close(); file.open(); grid = openvdb::gridPtrCast(file.readGrid(gridName)); break; } } if (!grid) { OPENVDB_THROW(openvdb::ValueError, "no scalar, floating-point grids in file " + vdbFilename); } } } if (opts.verbose) { std::ostringstream ostr; ostr << gProgName << ": ...completed in " << std::setprecision(3) << (tbb::tick_count::now() - start).seconds() << " sec"; std::cout << ostr.str() << std::endl; } if (grid) { if (!hasLookAt && !hasRotate) { // If the user specified neither the camera rotation nor a target // to look at, orient the camera to point to the center of the grid. opts.target = grid->evalActiveVoxelBoundingBox().getCenter(); opts.target = grid->constTransform().indexToWorld(opts.target); opts.lookat = true; } if (opts.verbose) std::cout << opts << std::endl; render(*grid, imgFilename, opts); } } catch (std::exception& e) { OPENVDB_LOG_FATAL(e.what()); retcode = EXIT_FAILURE; } catch (...) { OPENVDB_LOG_FATAL("Exception caught (unexpected type)"); std::unexpected(); } return retcode; } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/cmd/openvdb_view/0000755000000000000000000000000012252453157014451 5ustar rootrootopenvdb/cmd/openvdb_view/main.cc0000644000000000000000000001357712252453157015721 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #ifdef DWA_OPENVDB #include #include #endif void usage(const char* progName, int status) { (status == EXIT_SUCCESS ? std::cout : std::cerr) << "Usage: " << progName << " file.vdb [file.vdb ...] [options]\n" << "Which: displays OpenVDB grids\n" << "Options:\n" << " -i print grid info\n" << " -d print debugging info\n" << "Controls:\n" << " Esc exit\n" << " -> (Right) show next grid\n" << " <- (Left) show previous grid\n" << " 1 toggle tree topology view on/off\n" << " 2 toggle surface view on/off\n" << " 3 toggle data view on/off\n" << " G (\"geometry\") look at center of geometry\n" << " H (\"home\") look at origin\n" << " I toggle on-screen grid info on/off\n" << " left mouse tumble\n" << " right mouse pan\n" << " mouse wheel zoom\n" << "\n" << " X + wheel move right cut plane\n" << " Shift + X + wheel move left cut plane\n" << " Y + wheel move top cut plane\n" << " Shift + Y + wheel move bottom cut plane\n" << " Z + wheel move front cut plane\n" << " Shift + Z + wheel move back cut plane\n" << " Ctrl + X + wheel move both X cut planes\n" << " Ctrl + Y + wheel move both Y cut planes\n" << " Ctrl + Z + wheel move both Z cut planes\n"; exit(status); } //////////////////////////////////////// int main(int argc, char *argv[]) { #ifdef DWA_OPENVDB USAGETRACK_report_basic_tool_usage(argc, argv, /*duration=*/0); logging_base::configure(argc, argv); #endif const char* progName = argv[0]; if (const char* ptr = ::strrchr(progName, '/')) progName = ptr + 1; int status = EXIT_SUCCESS; try { openvdb::initialize(); bool printInfo = false, printDebugInfo = false; // Parse the command line. std::vector filenames; for (int n = 1; n < argc; ++n) { std::string str(argv[n]); if (str[0] != '-') { filenames.push_back(str); } else if (str == "-i") { printInfo = true; } else if (str == "-d") { printDebugInfo = true; } else if (str == "-h" || str == "--help") { usage(progName, EXIT_SUCCESS); } else { usage(progName, EXIT_FAILURE); } } openvdb_viewer::Viewer viewer = openvdb_viewer::init(progName, printDebugInfo); const size_t numFiles = filenames.size(); if (numFiles == 0) usage(progName, EXIT_FAILURE); openvdb::GridCPtrVec allGrids; // Load VDB files. std::string indent(numFiles == 1 ? "" : " "); for (size_t n = 0; n < numFiles; ++n) { openvdb::io::File file(filenames[n]); file.open(); openvdb::GridPtrVecPtr grids = file.getGrids(); if (grids->empty()) { OPENVDB_LOG_WARN(filenames[n] << " is empty"); continue; } allGrids.insert(allGrids.end(), grids->begin(), grids->end()); if (printInfo) { if (numFiles > 1) std::cout << filenames[n] << ":\n"; for (size_t i = 0; i < grids->size(); ++i) { const std::string name = (*grids)[i]->getName(); openvdb::Coord dim = (*grids)[i]->evalActiveVoxelDim(); std::cout << indent << (name.empty() ? "" : name) << " (" << dim[0] << " x " << dim[1] << " x " << dim[2] << " voxels)" << std::endl; } } } viewer.view(allGrids); } catch (const char* s) { OPENVDB_LOG_ERROR(progName << ": " << s); status = EXIT_FAILURE; } catch (std::exception& e) { OPENVDB_LOG_ERROR(progName << ": " << e.what()); status = EXIT_FAILURE; } return status; } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/0000755000000000000000000000000012252453157012357 5ustar rootrootopenvdb/tools/Morphology.h0000644000000000000000000004404712252453157014700 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED #include #include #include // for isApproxEqual() #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { //@{ /// Topologically dilate all leaf-level active voxels in the given tree, /// i.e., expand the set of active voxels by @a count voxels in the +x, -x, /// +y, -y, +z and -z directions, but don't change the values of any voxels, /// only their active states. /// @todo Currently operates only on leaf voxels; need to extend to tiles. template OPENVDB_STATIC_SPECIALIZATION inline void dilateVoxels(TreeType& tree, int count=1); template OPENVDB_STATIC_SPECIALIZATION inline void dilateVoxels(tree::LeafManager& manager, int count = 1); //@} //@{ /// Topologically erode all leaf-level active voxels in the given tree, /// i.e., shrink the set of active voxels by @a count voxels in the +x, -x, /// +y, -y, +z and -z directions, but don't change the values of any voxels, /// only their active states. /// @todo Currently operates only on leaf voxels; need to extend to tiles. template OPENVDB_STATIC_SPECIALIZATION inline void erodeVoxels(TreeType& tree, int count=1); template OPENVDB_STATIC_SPECIALIZATION inline void erodeVoxels(tree::LeafManager& manager, int count = 1); //@} /// @brief Mark as active any inactive tiles or voxels in the given grid or tree /// whose values are equal to @a value (optionally to within the given @a tolerance). template inline void activate( GridOrTree&, const typename GridOrTree::ValueType& value, const typename GridOrTree::ValueType& tolerance = zeroVal() ); /// @brief Mark as inactive any active tiles or voxels in the given grid or tree /// whose values are equal to @a value (optionally to within the given @a tolerance). template inline void deactivate( GridOrTree&, const typename GridOrTree::ValueType& value, const typename GridOrTree::ValueType& tolerance = zeroVal() ); //////////////////////////////////////// /// Mapping from a Log2Dim to a data type of size 2^Log2Dim bits template struct DimToWord {}; template<> struct DimToWord<3> { typedef uint8_t Type; }; template<> struct DimToWord<4> { typedef uint16_t Type; }; template<> struct DimToWord<5> { typedef uint32_t Type; }; template<> struct DimToWord<6> { typedef uint64_t Type; }; //////////////////////////////////////// template class Morphology { public: typedef tree::LeafManager ManagerType; Morphology(TreeType& tree): mOwnsManager(true), mManager(new ManagerType(tree)), mAcc(tree), mSteps(1) {} Morphology(ManagerType* mgr): mOwnsManager(false), mManager(mgr), mAcc(mgr->tree()), mSteps(1) {} virtual ~Morphology() { if (mOwnsManager) delete mManager; } void dilateVoxels(); void dilateVoxels(int count) { for (int i=0; idilateVoxels(); } void erodeVoxels(int count = 1) { mSteps = count; this->doErosion(); } private: void doErosion(); typedef typename TreeType::LeafNodeType LeafType; typedef typename LeafType::NodeMaskType MaskType; typedef tree::ValueAccessor AccessorType; const bool mOwnsManager; ManagerType* mManager; AccessorType mAcc; int mSteps; static const int LEAF_DIM = LeafType::DIM; static const int LEAF_LOG2DIM = LeafType::LOG2DIM; typedef typename DimToWord::Type Word; struct Neighbor { LeafType* leaf;//null if a tile bool init;//true if initialization is required bool isOn;//true if an active tile Neighbor() : leaf(NULL), init(true) {} inline void clear() { init = true; } template void scatter(AccessorType& acc, const Coord &xyz, int indx, Word oldWord) { if (init) { init = false; Coord orig = xyz.offsetBy(DX*LEAF_DIM, DY*LEAF_DIM, DZ*LEAF_DIM); leaf = acc.probeLeaf(orig); if (leaf==NULL && !acc.isValueOn(orig)) leaf = acc.touchLeaf(orig); } static const int N = (LEAF_DIM -1 )*(DY + DX*LEAF_DIM); if (leaf) leaf->getValueMask().template getWord(indx-N) |= oldWord; } template Word gather(AccessorType& acc, const Coord &xyz, int indx) { if (init) { init = false; Coord orig = xyz.offsetBy(DX*LEAF_DIM, DY*LEAF_DIM, DZ*LEAF_DIM); leaf = acc.probeLeaf(orig); isOn = leaf ? false : acc.isValueOn(orig); } static const int N = (LEAF_DIM -1 )*(DY + DX*LEAF_DIM); return leaf ? leaf->getValueMask().template getWord(indx-N) : isOn ? ~Word(0) : Word(0); } };// Neighbor struct ErodeVoxelsOp { ErodeVoxelsOp(std::vector& masks, ManagerType& manager) : mSavedMasks(masks) , mManager(manager) {} void runParallel() { tbb::parallel_for(mManager.getRange(), *this); } void operator()(const tbb::blocked_range& range) const; private: std::vector& mSavedMasks; ManagerType& mManager; };// ErodeVoxelsOp struct MaskManager { MaskManager(std::vector& masks, ManagerType& manager) : mMasks(masks) , mManager(manager), mSaveMasks(true) {} void save() { mSaveMasks = true; tbb::parallel_for(mManager.getRange(), *this); } void update() { mSaveMasks = false; tbb::parallel_for(mManager.getRange(), *this); } void operator()(const tbb::blocked_range& range) const { if (mSaveMasks) { for (size_t i = range.begin(); i < range.end(); ++i) { mMasks[i] = mManager.leaf(i).getValueMask(); } } else { for (size_t i = range.begin(); i < range.end(); ++i) { mManager.leaf(i).setValueMask(mMasks[i]); } } } private: std::vector& mMasks; ManagerType& mManager; bool mSaveMasks; };// MaskManager }; template void Morphology::dilateVoxels() { /// @todo Currently operates only on leaf voxels; need to extend to tiles. const int leafCount = mManager->leafCount(); // Save the value masks of all leaf nodes. std::vector savedMasks(leafCount); MaskManager masks(savedMasks, *mManager); masks.save(); Neighbor NN[6]; Coord origin; for (int leafIdx = 0; leafIdx < leafCount; ++leafIdx) { const MaskType& oldMask = savedMasks[leafIdx];//original bit-mask of current leaf node LeafType& leaf = mManager->leaf(leafIdx);//current leaf node leaf.getOrigin(origin);// origin of the current leaf node. for (int x = 0; x < LEAF_DIM; ++x ) { for (int y = 0, n = (x << LEAF_LOG2DIM); y < LEAF_DIM; ++y, ++n) { // Extract the portion of the original mask that corresponds to a row in z. const Word oldWord = oldMask.template getWord(n); if (oldWord == 0) continue; // no active voxels // dilate current leaf or neighbor in negative x-direction if (x > 0) { leaf.getValueMask().template getWord(n-LEAF_DIM) |= oldWord; } else { NN[0].template scatter<-1, 0, 0>(mAcc, origin, n, oldWord); } // dilate current leaf or neighbor in positive x-direction if (x < LEAF_DIM - 1) { leaf.getValueMask().template getWord(n+LEAF_DIM) |= oldWord; } else { NN[1].template scatter< 1, 0, 0>(mAcc, origin, n, oldWord); } // dilate current leaf or neighbor in negative y-direction if (y > 0) { leaf.getValueMask().template getWord(n-1) |= oldWord; } else { NN[2].template scatter< 0,-1, 0>(mAcc, origin, n, oldWord); } // dilate current leaf or neighbor in positive y-direction if (y < LEAF_DIM - 1) { leaf.getValueMask().template getWord(n+1) |= oldWord; } else { NN[3].template scatter< 0, 1, 0>(mAcc, origin, n, oldWord); } // Dilate the current leaf node in the z direction by ORing its mask // with itself shifted first left and then right by one bit. leaf.getValueMask().template getWord(n) |= (oldWord >> 1) | (oldWord << 1); // dilate neighbor in negative z-direction if (Word w = oldWord<<(LEAF_DIM-1)) { NN[4].template scatter< 0, 0,-1>(mAcc, origin, n, w); } // dilate neighbot in positive z-direction if (Word w = oldWord>>(LEAF_DIM-1)) { NN[5].template scatter< 0, 0, 1>(mAcc, origin, n, w); } }// loop over y }//loop over x for (int i=0; i<6; ++i) NN[i].clear(); }//loop over leafs mManager->rebuildLeafArray(); } template void Morphology::ErodeVoxelsOp::operator()(const tbb::blocked_range& range) const { AccessorType acc(mManager.tree()); Neighbor NN[6]; Coord origin; for (size_t leafIdx = range.begin(); leafIdx < range.end(); ++leafIdx) { LeafType& leaf = mManager.leaf(leafIdx);//current leaf node if (leaf.isEmpty()) continue; MaskType& newMask = mSavedMasks[leafIdx];//original bit-mask of current leaf node leaf.getOrigin(origin);// origin of the current leaf node. for (int x = 0; x < LEAF_DIM; ++x ) { for (int y = 0, n = (x << LEAF_LOG2DIM); y < LEAF_DIM; ++y, ++n) { // Extract the portion of the original mask that corresponds to a row in z. Word& w = newMask.template getWord(n); if (w == 0) continue; // no active voxels // Erode in two z directions (this is first since it uses the original w) w &= (w<<1 | (NN[4].template gather<0,0,-1>(acc, origin, n)>>(LEAF_DIM-1))) & (w>>1 | (NN[5].template gather<0,0, 1>(acc, origin, n)<<(LEAF_DIM-1))); // dilate current leaf or neighbor in negative x-direction w &= (x == 0) ? NN[0].template gather<-1, 0, 0>(acc, origin, n) : leaf.getValueMask().template getWord(n-LEAF_DIM); // dilate current leaf or neighbor in positive x-direction w &= (x == LEAF_DIM-1) ? NN[1].template gather< 1, 0, 0>(acc, origin, n) : leaf.getValueMask().template getWord(n+LEAF_DIM); // dilate current leaf or neighbor in negative y-direction w &= (y == 0) ? NN[2].template gather< 0,-1, 0>(acc, origin, n) : leaf.getValueMask().template getWord(n-1); // dilate current leaf or neighbor in positive y-direction w &= (y == LEAF_DIM-1) ? NN[3].template gather< 0, 1, 0>(acc, origin, n) : leaf.getValueMask().template getWord(n+1); }// loop over y }//loop over x for (int i=0; i<6; ++i) NN[i].clear(); }//loop over leafs } template void Morphology::doErosion() { /// @todo Currently operates only on leaf voxels; need to extend to tiles. const int leafCount = mManager->leafCount(); // Save the value masks of all leaf nodes. std::vector savedMasks(leafCount); MaskManager masks(savedMasks, *mManager); masks.save(); ErodeVoxelsOp erode(savedMasks, *mManager); for (int i = 0; i < mSteps; ++i) { erode.runParallel(); masks.update(); } mManager->tree().pruneLevelSet(); } //////////////////////////////////////// template OPENVDB_STATIC_SPECIALIZATION inline void dilateVoxels(tree::LeafManager& manager, int count) { Morphology m(&manager); m.dilateVoxels(count); } template OPENVDB_STATIC_SPECIALIZATION inline void dilateVoxels(TreeType& tree, int count) { Morphology m(tree); m.dilateVoxels(count); } template OPENVDB_STATIC_SPECIALIZATION inline void erodeVoxels(tree::LeafManager& manager, int count) { Morphology m(&manager); m.erodeVoxels(count); } template OPENVDB_STATIC_SPECIALIZATION inline void erodeVoxels(TreeType& tree, int count) { Morphology m(tree); m.erodeVoxels(count); } //////////////////////////////////////// namespace activation { template class ActivationOp { public: typedef typename TreeType::ValueType ValueT; ActivationOp(bool state, const ValueT& val, const ValueT& tol) : mActivate(state) , mValue(val) , mTolerance(tol) {} void operator()(const typename TreeType::ValueOnIter& it) const { if (math::isApproxEqual(*it, mValue, mTolerance)) { it.setValueOff(); } } void operator()(const typename TreeType::ValueOffIter& it) const { if (math::isApproxEqual(*it, mValue, mTolerance)) { it.setActiveState(/*on=*/true); } } void operator()(const typename TreeType::LeafIter& lit) const { typedef typename TreeType::LeafNodeType LeafT; LeafT& leaf = *lit; if (mActivate) { for (typename LeafT::ValueOffIter it = leaf.beginValueOff(); it; ++it) { if (math::isApproxEqual(*it, mValue, mTolerance)) { leaf.setValueOn(it.pos()); } } } else { for (typename LeafT::ValueOnIter it = leaf.beginValueOn(); it; ++it) { if (math::isApproxEqual(*it, mValue, mTolerance)) { leaf.setValueOff(it.pos()); } } } } private: bool mActivate; const ValueT mValue, mTolerance; }; // class ActivationOp } // namespace activation template inline void activate(GridOrTree& gridOrTree, const typename GridOrTree::ValueType& value, const typename GridOrTree::ValueType& tolerance) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeType; TreeType& tree = Adapter::tree(gridOrTree); activation::ActivationOp op(/*activate=*/true, value, tolerance); // Process all leaf nodes in parallel. foreach(tree.beginLeaf(), op); // Process all other inactive values serially (because changing active states // is not thread-safe unless no two threads modify the same node). typename TreeType::ValueOffIter it = tree.beginValueOff(); it.setMaxDepth(tree.treeDepth() - 2); foreach(it, op, /*threaded=*/false); } template inline void deactivate(GridOrTree& gridOrTree, const typename GridOrTree::ValueType& value, const typename GridOrTree::ValueType& tolerance) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeType; TreeType& tree = Adapter::tree(gridOrTree); activation::ActivationOp op(/*activate=*/false, value, tolerance); // Process all leaf nodes in parallel. foreach(tree.beginLeaf(), op); // Process all other active values serially (because changing active states // is not thread-safe unless no two threads modify the same node). typename TreeType::ValueOnIter it = tree.beginValueOn(); it.setMaxDepth(tree.treeDepth() - 2); foreach(it, op, /*threaded=*/false); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_MORPHOLOGY_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/GridTransformer.h0000644000000000000000000010750712252453157015652 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file GridTransformer.h #ifndef OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include // for isApproxEqual() #include #include "Interpolation.h" #include "LevelSetRebuild.h" // for doLevelSetRebuild() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Resample an input grid into an output grid of the same type such that, /// after resampling, the input and output grids coincide (apart from sampling /// artifacts), but the output grid's transform is unchanged. /// @details Specifically, this function resamples the input grid into the output /// grid's index space, using a sampling kernel like PointSampler, BoxSampler, /// or QuadraticSampler. /// @param inGrid the grid to be resampled /// @param outGrid the grid into which to write the resampled voxel data /// @param interrupter an object adhering to the util::NullInterrupter interface /// @par Example: /// @code /// // Create an input grid with the default identity transform /// // and populate it with a level-set sphere. /// FloatGrid::ConstPtr src = tools::makeSphere(...); /// // Create an output grid and give it a uniform-scale transform. /// FloatGrid::Ptr dest = FloatGrid::create(); /// const float voxelSize = 0.5; /// dest->setTransform(math::Transform::createLinearTransform(voxelSize)); /// // Resample the input grid into the output grid, reproducing /// // the level-set sphere at a smaller voxel size. /// MyInterrupter interrupter = ...; /// tools::resampleToMatch(*src, *dest, interrupter); /// @endcode template inline void resampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter); /// @brief Resample an input grid into an output grid of the same type such that, /// after resampling, the input and output grids coincide (apart from sampling /// artifacts), but the output grid's transform is unchanged. /// @details Specifically, this function resamples the input grid into the output /// grid's index space, using a sampling kernel like PointSampler, BoxSampler, /// or QuadraticSampler. /// @param inGrid the grid to be resampled /// @param outGrid the grid into which to write the resampled voxel data /// @par Example: /// @code /// // Create an input grid with the default identity transform /// // and populate it with a level-set sphere. /// FloatGrid::ConstPtr src = tools::makeSphere(...); /// // Create an output grid and give it a uniform-scale transform. /// FloatGrid::Ptr dest = FloatGrid::create(); /// const float voxelSize = 0.5; /// dest->setTransform(math::Transform::createLinearTransform(voxelSize)); /// // Resample the input grid into the output grid, reproducing /// // the level-set sphere at a smaller voxel size. /// tools::resampleToMatch(*src, *dest); /// @endcode template inline void resampleToMatch(const GridType& inGrid, GridType& outGrid); //////////////////////////////////////// namespace internal { /// @brief A TileSampler wraps a grid sampler of another type (BoxSampler, /// QuadraticSampler, etc.), and for samples that fall within a given tile /// of the grid, it returns a cached tile value instead of accessing the grid. template class TileSampler: public Sampler { public: typedef typename TreeT::ValueType ValueT; /// @param b the index-space bounding box of a particular grid tile /// @param tileVal the tile's value /// @param on the tile's active state TileSampler(const CoordBBox& b, const ValueT& tileVal, bool on): mBBox(b.min().asVec3d(), b.max().asVec3d()), mVal(tileVal), mActive(on), mEmpty(false) { mBBox.expand(-this->radius()); // shrink the bounding box by the sample radius mEmpty = mBBox.empty(); } bool sample(const TreeT& inTree, const Vec3R& inCoord, ValueT& result) const { if (!mEmpty && mBBox.isInside(inCoord)) { result = mVal; return mActive; } return Sampler::sample(inTree, inCoord, result); } protected: BBoxd mBBox; ValueT mVal; bool mActive, mEmpty; }; /// @brief For point sampling, tree traversal is less expensive than testing /// bounding box membership. template struct TileSampler: public PointSampler { TileSampler(const CoordBBox&, const typename TreeT::ValueType&, bool) {} }; /// @brief For point sampling, tree traversal is less expensive than testing /// bounding box membership. template struct TileSampler: public StaggeredPointSampler { TileSampler(const CoordBBox&, const typename TreeT::ValueType&, bool) {} }; } // namespace internal //////////////////////////////////////// /// A GridResampler applies a geometric transformation to an /// input grid using one of several sampling schemes, and stores /// the result in an output grid. /// /// Usage: /// @code /// GridResampler resampler(); /// resampler.transformGrid(xform, inGrid, outGrid); /// @endcode /// where @c xform is a functor that implements the following methods: /// @code /// bool isAffine() const /// openvdb::Vec3d transform(const openvdb::Vec3d&) const /// openvdb::Vec3d invTransform(const openvdb::Vec3d&) const /// @endcode /// @note When the transform is affine and can be expressed as a 4 x 4 matrix, /// a GridTransformer is much more efficient than a GridResampler. class GridResampler { public: typedef boost::shared_ptr Ptr; typedef boost::function InterruptFunc; GridResampler(): mThreaded(true), mTransformTiles(true) {} virtual ~GridResampler() {} /// Enable or disable threading. (Threading is enabled by default.) void setThreaded(bool b) { mThreaded = b; } /// Return @c true if threading is enabled. bool threaded() const { return mThreaded; } /// Enable or disable processing of tiles. (Enabled by default, except for level set grids.) void setTransformTiles(bool b) { mTransformTiles = b; } /// Return @c true if tile processing is enabled. bool transformTiles() const { return mTransformTiles; } /// @brief Allow processing to be aborted by providing an interrupter object. /// The interrupter will be queried periodically during processing. /// @see util/NullInterrupter.h for interrupter interface requirements. template void setInterrupter(InterrupterType&); template void transformGrid(const Transformer&, const GridT& inGrid, GridT& outGrid) const; protected: template void applyTransform(const Transformer&, const GridT& inGrid, GridT& outGrid) const; bool interrupt() const { return mInterrupt && mInterrupt(); } private: template static void transformBBox(const Transformer&, const CoordBBox& inBBox, const InTreeT& inTree, OutTreeT& outTree, const InterruptFunc&, const Sampler& = Sampler()); template class RangeProcessor; bool mThreaded, mTransformTiles; InterruptFunc mInterrupt; }; //////////////////////////////////////// /// @brief A GridTransformer applies a geometric transformation to an /// input grid using one of several sampling schemes, and stores /// the result in an output grid. /// /// @note GridTransformer is optimized for affine transformations. /// /// Usage: /// @code /// Mat4R xform = ...; /// GridTransformer transformer(xform); /// transformer.transformGrid(inGrid, outGrid); /// @endcode /// or /// @code /// Vec3R pivot = ..., scale = ..., rotate = ..., translate = ...; /// GridTransformer transformer(pivot, scale, rotate, translate); /// transformer.transformGrid(inGrid, outGrid); /// @endcode class GridTransformer: public GridResampler { public: typedef boost::shared_ptr Ptr; GridTransformer(const Mat4R& xform); GridTransformer( const Vec3R& pivot, const Vec3R& scale, const Vec3R& rotate, const Vec3R& translate, const std::string& xformOrder = "tsr", const std::string& rotationOrder = "zyx"); virtual ~GridTransformer() {} const Mat4R& getTransform() const { return mTransform; } template void transformGrid(const GridT& inGrid, GridT& outGrid) const; private: struct MatrixTransform; inline void init(const Vec3R& pivot, const Vec3R& scale, const Vec3R& rotate, const Vec3R& translate, const std::string& xformOrder, const std::string& rotOrder); Vec3R mPivot; Vec3i mMipLevels; Mat4R mTransform, mPreScaleTransform, mPostScaleTransform; }; //////////////////////////////////////// namespace local_util { /// @brief Decompose an affine transform into scale, rotation and translation components. /// @return @c false if the given matrix is not affine or cannot otherwise be decomposed. /// @todo This is not safe for matrices with shear. template inline bool decompose(const math::Mat4& m, math::Vec3& scale, math::Vec3& rotate, math::Vec3& translate) { if (!math::isAffine(m)) return false; // this is the translation in world space translate = m.getTranslation(); // Extract translation. math::Mat3 temp = m.getMat3(); scale.init( (math::Vec3(1, 0, 0) * temp).length(), (math::Vec3(0, 1, 0) * temp).length(), (math::Vec3(0, 0, 1) * temp).length()); // Extract scale. temp *= math::scale >(scale).inverse(); rotate = math::eulerAngles(temp, math::XYZ_ROTATION); if (!rotate.eq(math::Vec3::zero()) && !scale.eq(math::Vec3(scale[0]))) { // No unique decomposition if scale is nonuniform and rotation is nonzero. return false; } return true; } } // namespace local_util //////////////////////////////////////// /// This class implements the Transformer functor interface (specifically, /// the isAffine(), transform() and invTransform() methods) for a transform /// that is expressed as a 4 x 4 matrix. struct GridTransformer::MatrixTransform { MatrixTransform(): mat(Mat4R::identity()), invMat(Mat4R::identity()) {} MatrixTransform(const Mat4R& xform): mat(xform), invMat(xform.inverse()) {} bool isAffine() const { return math::isAffine(mat); } Vec3R transform(const Vec3R& pos) const { return mat.transformH(pos); } Vec3R invTransform(const Vec3R& pos) const { return invMat.transformH(pos); } Mat4R mat, invMat; }; //////////////////////////////////////// /// @brief This class implements the Transformer functor interface (specifically, /// the isAffine(), transform() and invTransform() methods) for a transform /// that maps an A grid into a B grid's index space such that, after resampling, /// A's index space and transform match B's index space and transform. class ABTransform { public: /// @param aXform the A grid's transform /// @param bXform the B grid's transform ABTransform(const math::Transform& aXform, const math::Transform& bXform): mAXform(aXform), mBXform(bXform), mIsAffine(mAXform.isLinear() && mBXform.isLinear()), mIsIdentity(mIsAffine && mAXform == mBXform) {} bool isAffine() const { return mIsAffine; } bool isIdentity() const { return mIsIdentity; } openvdb::Vec3R transform(const openvdb::Vec3R& pos) const { return mBXform.worldToIndex(mAXform.indexToWorld(pos)); } openvdb::Vec3R invTransform(const openvdb::Vec3R& pos) const { return mAXform.worldToIndex(mBXform.indexToWorld(pos)); } const math::Transform& getA() const { return mAXform; } const math::Transform& getB() const { return mBXform; } private: const math::Transform &mAXform, &mBXform; const bool mIsAffine; const bool mIsIdentity; }; /// The normal entry points for resampling are the resampleToMatch() functions, /// which correctly handle level set grids under scaling and shearing. /// doResampleToMatch() is mainly for internal use but is typically faster /// for level sets, and correct provided that no scaling or shearing is needed. /// /// @warning Do not use this function to scale or shear a level set grid. template inline void doResampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter) { ABTransform xform(inGrid.transform(), outGrid.transform()); if (Sampler::consistent() && xform.isIdentity()) { // If the transforms of the input and output are identical, the // output tree is simply a deep copy of the input tree. outGrid.setTree(inGrid.tree().copy()); } else if (xform.isAffine()) { // If the input and output transforms are both affine, create an // input to output transform (in:index-to-world * out:world-to-index) // and use the fast GridTransformer API. Mat4R mat = xform.getA().baseMap()->getAffineMap()->getMat4() * ( xform.getB().baseMap()->getAffineMap()->getMat4().inverse() ); GridTransformer transformer(mat); transformer.setInterrupter(interrupter); // Transform the input grid and store the result in the output grid. transformer.transformGrid(inGrid, outGrid); } else { // If either the input or the output transform is non-affine, // use the slower GridResampler API. GridResampler resampler; resampler.setInterrupter(interrupter); resampler.transformGrid(xform, inGrid, outGrid); } } template inline void resampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter) { if (inGrid.getGridClass() == GRID_LEVEL_SET) { // If the input grid is a level set, resample it using the level set rebuild tool. if (inGrid.constTransform() == outGrid.constTransform()) { // If the transforms of the input and output grids are identical, // the output tree is simply a deep copy of the input tree. outGrid.setTree(inGrid.tree().copy()); return; } // If the output grid is a level set, resample the input grid to have the output grid's // background value. Otherwise, preserve the input grid's background value. typedef typename GridType::ValueType ValueT; const ValueT halfWidth = ((outGrid.getGridClass() == openvdb::GRID_LEVEL_SET) ? ValueT(outGrid.background() * (1.0 / outGrid.voxelSize()[0])) : ValueT(inGrid.background() * (1.0 / inGrid.voxelSize()[0]))); typename GridType::Ptr tempGrid; try { tempGrid = doLevelSetRebuild(inGrid, /*iso=*/zeroVal(), /*exWidth=*/halfWidth, /*inWidth=*/halfWidth, &outGrid.constTransform(), &interrupter); } catch (TypeError&) { // The input grid is classified as a level set, but it has a value type // that is not supported by the level set rebuild tool. Fall back to // using the generic resampler. tempGrid.reset(); } if (tempGrid) { outGrid.setTree(tempGrid->treePtr()); return; } } // If the input grid is not a level set, use the generic resampler. doResampleToMatch(inGrid, outGrid, interrupter); } template inline void resampleToMatch(const GridType& inGrid, GridType& outGrid) { util::NullInterrupter interrupter; resampleToMatch(inGrid, outGrid, interrupter); } //////////////////////////////////////// inline GridTransformer::GridTransformer(const Mat4R& xform): mPivot(0, 0, 0), mMipLevels(0, 0, 0), mTransform(xform), mPreScaleTransform(Mat4R::identity()), mPostScaleTransform(Mat4R::identity()) { Vec3R scale, rotate, translate; if (local_util::decompose(mTransform, scale, rotate, translate)) { // If the transform can be decomposed into affine components, // use them to set up a mipmapping-like scheme for downsampling. init(mPivot, scale, rotate, translate, "srt", "zyx"); } } inline GridTransformer::GridTransformer( const Vec3R& pivot, const Vec3R& scale, const Vec3R& rotate, const Vec3R& translate, const std::string& xformOrder, const std::string& rotOrder): mPivot(0, 0, 0), mMipLevels(0, 0, 0), mPreScaleTransform(Mat4R::identity()), mPostScaleTransform(Mat4R::identity()) { init(pivot, scale, rotate, translate, xformOrder, rotOrder); } //////////////////////////////////////// inline void GridTransformer::init( const Vec3R& pivot, const Vec3R& scale, const Vec3R& rotate, const Vec3R& translate, const std::string& xformOrder, const std::string& rotOrder) { if (xformOrder.size() != 3) { OPENVDB_THROW(ValueError, "invalid transform order (" + xformOrder + ")"); } if (rotOrder.size() != 3) { OPENVDB_THROW(ValueError, "invalid rotation order (" + rotOrder + ")"); } mPivot = pivot; // Scaling is handled via a mipmapping-like scheme of successive // halvings of the tree resolution, until the remaining scale // factor is greater than or equal to 1/2. Vec3R scaleRemainder = scale; for (int i = 0; i < 3; ++i) { double s = std::fabs(scale(i)); if (s < 0.5) { mMipLevels(i) = int(std::floor(-std::log(s)/std::log(2.0))); scaleRemainder(i) = scale(i) * (1 << mMipLevels(i)); } } // Build pre-scale and post-scale transform matrices based on // the user-specified order of operations. // Note that we iterate over the transform order string in reverse order // (e.g., "t", "r", "s", given "srt"). This is because math::Mat matrices // postmultiply row vectors rather than premultiplying column vectors. mTransform = mPreScaleTransform = mPostScaleTransform = Mat4R::identity(); Mat4R* remainder = &mPostScaleTransform; int rpos, spos, tpos; rpos = spos = tpos = 3; for (int ix = 2; ix >= 0; --ix) { // reverse iteration switch (xformOrder[ix]) { case 'r': rpos = ix; mTransform.preTranslate(pivot); remainder->preTranslate(pivot); int xpos, ypos, zpos; xpos = ypos = zpos = 3; for (int ir = 2; ir >= 0; --ir) { switch (rotOrder[ir]) { case 'x': xpos = ir; mTransform.preRotate(math::X_AXIS, rotate.x()); remainder->preRotate(math::X_AXIS, rotate.x()); break; case 'y': ypos = ir; mTransform.preRotate(math::Y_AXIS, rotate.y()); remainder->preRotate(math::Y_AXIS, rotate.y()); break; case 'z': zpos = ir; mTransform.preRotate(math::Z_AXIS, rotate.z()); remainder->preRotate(math::Z_AXIS, rotate.z()); break; } } // Reject rotation order strings that don't contain exactly one // instance of "x", "y" and "z". if (xpos > 2 || ypos > 2 || zpos > 2) { OPENVDB_THROW(ValueError, "invalid rotation order (" + rotOrder + ")"); } mTransform.preTranslate(-pivot); remainder->preTranslate(-pivot); break; case 's': spos = ix; mTransform.preTranslate(pivot); mTransform.preScale(scale); mTransform.preTranslate(-pivot); remainder->preTranslate(pivot); remainder->preScale(scaleRemainder); remainder->preTranslate(-pivot); remainder = &mPreScaleTransform; break; case 't': tpos = ix; mTransform.preTranslate(translate); remainder->preTranslate(translate); break; } } // Reject transform order strings that don't contain exactly one // instance of "t", "r" and "s". if (tpos > 2 || rpos > 2 || spos > 2) { OPENVDB_THROW(ValueError, "invalid transform order (" + xformOrder + ")"); } } //////////////////////////////////////// template void GridResampler::setInterrupter(InterrupterType& interrupter) { mInterrupt = boost::bind(&InterrupterType::wasInterrupted, /*this=*/&interrupter, /*percent=*/-1); } template void GridResampler::transformGrid(const Transformer& xform, const GridT& inGrid, GridT& outGrid) const { outGrid.setBackground(inGrid.background()); applyTransform(xform, inGrid, outGrid); } template void GridTransformer::transformGrid(const GridT& inGrid, GridT& outGrid) const { outGrid.setBackground(inGrid.background()); if (!Sampler::mipmap() || mMipLevels == Vec3i::zero()) { // Skip the mipmapping step. const MatrixTransform xform(mTransform); applyTransform(xform, inGrid, outGrid); } else { bool firstPass = true; const typename GridT::ValueType background = inGrid.background(); typename GridT::Ptr tempGrid = GridT::create(background); if (!mPreScaleTransform.eq(Mat4R::identity())) { firstPass = false; // Apply the pre-scale transform to the input grid // and store the result in a temporary grid. const MatrixTransform xform(mPreScaleTransform); applyTransform(xform, inGrid, *tempGrid); } // While the scale factor along one or more axes is less than 1/2, // scale the grid by half along those axes. Vec3i count = mMipLevels; // # of halvings remaining per axis while (count != Vec3i::zero()) { MatrixTransform xform; xform.mat.setTranslation(mPivot); xform.mat.preScale(Vec3R( count.x() ? .5 : 1, count.y() ? .5 : 1, count.z() ? .5 : 1)); xform.mat.preTranslate(-mPivot); xform.invMat = xform.mat.inverse(); if (firstPass) { firstPass = false; // Scale the input grid and store the result in a temporary grid. applyTransform(xform, inGrid, *tempGrid); } else { // Scale the temporary grid and store the result in a transient grid, // then swap the two and discard the transient grid. typename GridT::Ptr destGrid = GridT::create(background); applyTransform(xform, *tempGrid, *destGrid); tempGrid.swap(destGrid); } // (3, 2, 1) -> (2, 1, 0) -> (1, 0, 0) -> (0, 0, 0), etc. count = math::maxComponent(count - 1, Vec3i::zero()); } // Apply the post-scale transform and store the result in the output grid. if (!mPostScaleTransform.eq(Mat4R::identity())) { const MatrixTransform xform(mPostScaleTransform); applyTransform(xform, *tempGrid, outGrid); } else { outGrid.setTree(tempGrid->treePtr()); } } } //////////////////////////////////////// template class GridResampler::RangeProcessor { public: typedef typename TreeT::LeafCIter LeafIterT; typedef typename TreeT::ValueAllCIter TileIterT; typedef typename tree::IteratorRange LeafRange; typedef typename tree::IteratorRange TileRange; typedef typename tree::ValueAccessor InTreeAccessor; typedef typename tree::ValueAccessor OutTreeAccessor; RangeProcessor(const Transformer& xform, const CoordBBox& b, const TreeT& inT, TreeT& outT): mIsRoot(true), mXform(xform), mBBox(b), mInTree(inT), mOutTree(&outT), mInAcc(mInTree), mOutAcc(*mOutTree) {} RangeProcessor(const Transformer& xform, const CoordBBox& b, const TreeT& inTree): mIsRoot(false), mXform(xform), mBBox(b), mInTree(inTree), mOutTree(new TreeT(inTree.background())), mInAcc(mInTree), mOutAcc(*mOutTree) {} ~RangeProcessor() { if (!mIsRoot) delete mOutTree; } /// Splitting constructor: don't copy the original processor's output tree RangeProcessor(RangeProcessor& other, tbb::split): mIsRoot(false), mXform(other.mXform), mBBox(other.mBBox), mInTree(other.mInTree), mOutTree(new TreeT(mInTree.background())), mInAcc(mInTree), mOutAcc(*mOutTree), mInterrupt(other.mInterrupt) {} void setInterrupt(const InterruptFunc& f) { mInterrupt = f; } /// Transform each leaf node in the given range. void operator()(LeafRange& r) { for ( ; r; ++r) { if (interrupt()) break; LeafIterT i = r.iterator(); CoordBBox bbox(i->origin(), i->origin() + Coord(i->dim())); if (!mBBox.empty()) { // Intersect the leaf node's bounding box with mBBox. bbox = CoordBBox( Coord::maxComponent(bbox.min(), mBBox.min()), Coord::minComponent(bbox.max(), mBBox.max())); } if (!bbox.empty()) { transformBBox(mXform, bbox, mInAcc, mOutAcc, mInterrupt); } } } /// Transform each non-background tile in the given range. void operator()(TileRange& r) { for ( ; r; ++r) { if (interrupt()) break; TileIterT i = r.iterator(); // Skip voxels and background tiles. if (!i.isTileValue()) continue; if (!i.isValueOn() && math::isApproxEqual(*i, mOutTree->background())) continue; CoordBBox bbox; i.getBoundingBox(bbox); if (!mBBox.empty()) { // Intersect the tile's bounding box with mBBox. bbox = CoordBBox( Coord::maxComponent(bbox.min(), mBBox.min()), Coord::minComponent(bbox.max(), mBBox.max())); } if (!bbox.empty()) { /// @todo This samples the tile voxel-by-voxel, which is much too slow. /// Instead, compute the largest axis-aligned bounding box that is /// contained in the transformed tile (adjusted for the sampler radius) /// and fill it with the tile value. Then transform the remaining voxels. internal::TileSampler sampler(bbox, i.getValue(), i.isValueOn()); transformBBox(mXform, bbox, mInAcc, mOutAcc, mInterrupt, sampler); } } } /// Merge another processor's output tree into this processor's tree. void join(RangeProcessor& other) { if (!interrupt()) mOutTree->merge(*other.mOutTree); } private: bool interrupt() const { return mInterrupt && mInterrupt(); } const bool mIsRoot; // true if mOutTree is the top-level tree Transformer mXform; CoordBBox mBBox; const TreeT& mInTree; TreeT* mOutTree; InTreeAccessor mInAcc; OutTreeAccessor mOutAcc; InterruptFunc mInterrupt; }; //////////////////////////////////////// template void GridResampler::applyTransform(const Transformer& xform, const GridT& inGrid, GridT& outGrid) const { typedef typename GridT::TreeType TreeT; const TreeT& inTree = inGrid.tree(); TreeT& outTree = outGrid.tree(); typedef RangeProcessor RangeProc; const GridClass gridClass = inGrid.getGridClass(); if (gridClass != GRID_LEVEL_SET && mTransformTiles) { // Independently transform the tiles of the input grid. // Note: Tiles in level sets can only be background tiles, and they // are handled more efficiently with a signed flood fill (see below). RangeProc proc(xform, CoordBBox(), inTree, outTree); proc.setInterrupt(mInterrupt); typename RangeProc::TileIterT tileIter = inTree.cbeginValueAll(); tileIter.setMaxDepth(tileIter.getLeafDepth() - 1); // skip leaf nodes typename RangeProc::TileRange tileRange(tileIter); if (mThreaded) { tbb::parallel_reduce(tileRange, proc); } else { proc(tileRange); } } CoordBBox clipBBox; if (gridClass == GRID_LEVEL_SET) { // Inactive voxels in level sets can only be background voxels, and they // are handled more efficiently with a signed flood fill (see below). clipBBox = inGrid.evalActiveVoxelBoundingBox(); } // Independently transform the leaf nodes of the input grid. RangeProc proc(xform, clipBBox, inTree, outTree); proc.setInterrupt(mInterrupt); typename RangeProc::LeafRange leafRange(inTree.cbeginLeaf()); if (mThreaded) { tbb::parallel_reduce(leafRange, proc); } else { proc(leafRange); } // If the grid is a level set, mark inactive voxels as inside or outside. if (gridClass == GRID_LEVEL_SET) { outTree.pruneInactive(); outTree.signedFloodFill(); } } //////////////////////////////////////// //static template void GridResampler::transformBBox( const Transformer& xform, const CoordBBox& bbox, const InTreeT& inTree, OutTreeT& outTree, const InterruptFunc& interrupt, const Sampler& sampler) { typedef typename OutTreeT::ValueType ValueT; typedef math::Vec4 Vec4R; // Transform the corners of the input tree's bounding box // and compute the enclosing bounding box in the output tree. Vec3R inRMin(bbox.min().x(), bbox.min().y(), bbox.min().z()), inRMax(bbox.max().x(), bbox.max().y(), bbox.max().z()), outRMin = math::minComponent(xform.transform(inRMin), xform.transform(inRMax)), outRMax = math::maxComponent(xform.transform(inRMin), xform.transform(inRMax)); for (int i = 0; i < 8; ++i) { Vec3R corner( i & 1 ? inRMax.x() : inRMin.x(), i & 2 ? inRMax.y() : inRMin.y(), i & 4 ? inRMax.z() : inRMin.z()); outRMin = math::minComponent(outRMin, xform.transform(corner)); outRMax = math::maxComponent(outRMax, xform.transform(corner)); } Vec3i outMin = local_util::floorVec3(outRMin) - Sampler::radius(), outMax = local_util::ceilVec3(outRMax) + Sampler::radius(); if (!xform.isAffine()) { // If the transform is not affine, back-project each output voxel // into the input tree. Vec3R xyz, inXYZ; Coord outXYZ; int &x = outXYZ.x(), &y = outXYZ.y(), &z = outXYZ.z(); for (x = outMin.x(); x <= outMax.x(); ++x) { if (interrupt && interrupt()) break; xyz.x() = x; for (y = outMin.y(); y <= outMax.y(); ++y) { if (interrupt && interrupt()) break; xyz.y() = y; for (z = outMin.z(); z <= outMax.z(); ++z) { xyz.z() = z; inXYZ = xform.invTransform(xyz); ValueT result; if (sampler.sample(inTree, inXYZ, result)) { outTree.setValueOn(outXYZ, result); } else { // Note: Don't overwrite existing active values with inactive values. if (!outTree.isValueOn(outXYZ)) { outTree.setValueOff(outXYZ, result); } } } } } } else { // affine // Compute step sizes in the input tree that correspond to // unit steps in x, y and z in the output tree. const Vec3R translation = xform.invTransform(Vec3R(0, 0, 0)), deltaX = xform.invTransform(Vec3R(1, 0, 0)) - translation, deltaY = xform.invTransform(Vec3R(0, 1, 0)) - translation, deltaZ = xform.invTransform(Vec3R(0, 0, 1)) - translation; #if defined(__ICC) /// @todo The following line is a workaround for bad code generation /// in opt-icc11.1_64 (but not debug or gcc) builds. It should be /// removed once the problem has been addressed at its source. const Vec3R dummy = deltaX; #endif // Step by whole voxels through the output tree, sampling the // corresponding fractional voxels of the input tree. Vec3R inStartX = xform.invTransform(Vec3R(outMin)); Coord outXYZ; int &x = outXYZ.x(), &y = outXYZ.y(), &z = outXYZ.z(); for (x = outMin.x(); x <= outMax.x(); ++x, inStartX += deltaX) { if (interrupt && interrupt()) break; Vec3R inStartY = inStartX; for (y = outMin.y(); y <= outMax.y(); ++y, inStartY += deltaY) { if (interrupt && interrupt()) break; Vec3R inXYZ = inStartY; for (z = outMin.z(); z <= outMax.z(); ++z, inXYZ += deltaZ) { ValueT result; if (sampler.sample(inTree, inXYZ, result)) { outTree.setValueOn(outXYZ, result); } else { // Note: Don't overwrite existing active values with inactive values. if (!outTree.isValueOn(outXYZ)) { outTree.setValueOff(outXYZ, result); } } } } } } } // GridResampler::transformBBox() } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Composite.h0000644000000000000000000004642512252453157014505 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Composite.h /// /// @brief Functions to efficiently perform various compositing operations on grids /// /// @author Peter Cucka #ifndef OPENVDB_TOOLS_COMPOSITE_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_COMPOSITE_HAS_BEEN_INCLUDED #include #include #include #include #include // for isExactlyEqual() #include "ValueTransformer.h" // for transformValues() #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Given two level set grids, replace the A grid with the union of A and B. /// @throw ValueError if the background value of either grid is not greater than zero. /// @note This operation always leaves the B grid empty. template OPENVDB_STATIC_SPECIALIZATION inline void csgUnion(GridOrTreeT& a, GridOrTreeT& b, bool prune = true); /// @brief Given two level set grids, replace the A grid with the intersection of A and B. /// @throw ValueError if the background value of either grid is not greater than zero. /// @note This operation always leaves the B grid empty. template OPENVDB_STATIC_SPECIALIZATION inline void csgIntersection(GridOrTreeT& a, GridOrTreeT& b, bool prune = true); /// @brief Given two level set grids, replace the A grid with the difference A / B. /// @throw ValueError if the background value of either grid is not greater than zero. /// @note This operation always leaves the B grid empty. template OPENVDB_STATIC_SPECIALIZATION inline void csgDifference(GridOrTreeT& a, GridOrTreeT& b, bool prune = true); /// @brief Given grids A and B, compute max(a, b) per voxel (using sparse traversal). /// Store the result in the A grid and leave the B grid empty. template OPENVDB_STATIC_SPECIALIZATION inline void compMax(GridOrTreeT& a, GridOrTreeT& b); /// @brief Given grids A and B, compute min(a, b) per voxel (using sparse traversal). /// Store the result in the A grid and leave the B grid empty. template OPENVDB_STATIC_SPECIALIZATION inline void compMin(GridOrTreeT& a, GridOrTreeT& b); /// @brief Given grids A and B, compute a + b per voxel (using sparse traversal). /// Store the result in the A grid and leave the B grid empty. template OPENVDB_STATIC_SPECIALIZATION inline void compSum(GridOrTreeT& a, GridOrTreeT& b); /// @brief Given grids A and B, compute a * b per voxel (using sparse traversal). /// Store the result in the A grid and leave the B grid empty. template OPENVDB_STATIC_SPECIALIZATION inline void compMul(GridOrTreeT& a, GridOrTreeT& b); /// Copy the active voxels of B into A. template OPENVDB_STATIC_SPECIALIZATION inline void compReplace(GridOrTreeT& a, const GridOrTreeT& b); //////////////////////////////////////// namespace composite { // composite::min() and composite::max() for non-vector types compare with operator<(). template inline const typename boost::disable_if_c::IsVec, T>::type& // = T if T is not a vector type min(const T& a, const T& b) { return std::min(a, b); } template inline const typename boost::disable_if_c::IsVec, T>::type& max(const T& a, const T& b) { return std::max(a, b); } // composite::min() and composite::max() for OpenVDB vector types compare by magnitude. template inline const typename boost::enable_if_c::IsVec, T>::type& // = T if T is a vector type min(const T& a, const T& b) { const typename T::ValueType aMag = a.lengthSqr(), bMag = b.lengthSqr(); return (aMag < bMag ? a : (bMag < aMag ? b : std::min(a, b))); } template inline const typename boost::enable_if_c::IsVec, T>::type& max(const T& a, const T& b) { const typename T::ValueType aMag = a.lengthSqr(), bMag = b.lengthSqr(); return (aMag < bMag ? b : (bMag < aMag ? a : std::max(a, b))); } } // namespace composite template OPENVDB_STATIC_SPECIALIZATION inline void compMax(GridOrTreeT& aTree, GridOrTreeT& bTree) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; typedef typename TreeT::ValueType ValueT; struct Local { static inline void op(CombineArgs& args) { args.setResult(composite::max(args.a(), args.b())); } }; Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); } template OPENVDB_STATIC_SPECIALIZATION inline void compMin(GridOrTreeT& aTree, GridOrTreeT& bTree) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; typedef typename TreeT::ValueType ValueT; struct Local { static inline void op(CombineArgs& args) { args.setResult(composite::min(args.a(), args.b())); } }; Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); } template OPENVDB_STATIC_SPECIALIZATION inline void compSum(GridOrTreeT& aTree, GridOrTreeT& bTree) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; struct Local { static inline void op(CombineArgs& args) { args.setResult(args.a() + args.b()); } }; Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); } template OPENVDB_STATIC_SPECIALIZATION inline void compMul(GridOrTreeT& aTree, GridOrTreeT& bTree) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; struct Local { static inline void op(CombineArgs& args) { args.setResult(args.a() * args.b()); } }; Adapter::tree(aTree).combineExtended(Adapter::tree(bTree), Local::op, /*prune=*/false); } //////////////////////////////////////// template struct CompReplaceOp { TreeT* const aTree; CompReplaceOp(TreeT& _aTree): aTree(&_aTree) {} void operator()(const typename TreeT::ValueOnCIter& iter) const { CoordBBox bbox; iter.getBoundingBox(bbox); aTree->fill(bbox, *iter); } void operator()(const typename TreeT::LeafCIter& leafIter) const { tree::ValueAccessor acc(*aTree); for (typename TreeT::LeafCIter::LeafNodeT::ValueOnCIter iter = leafIter->cbeginValueOn(); iter; ++iter) { acc.setValue(iter.getCoord(), *iter); } } }; template OPENVDB_STATIC_SPECIALIZATION inline void compReplace(GridOrTreeT& aTree, const GridOrTreeT& bTree) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; typedef typename TreeT::ValueOnCIter ValueOnCIterT; // Copy active states (but not values) from B to A. Adapter::tree(aTree).topologyUnion(Adapter::tree(bTree)); CompReplaceOp op(Adapter::tree(aTree)); // Copy all active tile values from B to A. ValueOnCIterT iter = bTree.cbeginValueOn(); iter.setMaxDepth(iter.getLeafDepth() - 1); // don't descend into leaf nodes foreach(iter, op); // Copy all active voxel values from B to A. foreach(Adapter::tree(bTree).cbeginLeaf(), op); } //////////////////////////////////////// /// Base visitor class for CSG operations /// (not intended to be used polymorphically, so no virtual functions) template class CsgVisitorBase { public: typedef TreeType TreeT; typedef typename TreeT::ValueType ValueT; typedef typename TreeT::LeafNodeType::ChildAllIter ChildIterT; enum { STOP = 3 }; CsgVisitorBase(const TreeT& aTree, const TreeT& bTree): mAOutside(aTree.background()), mAInside(math::negative(mAOutside)), mBOutside(bTree.background()), mBInside(math::negative(mBOutside)) { const ValueT zero = zeroVal(); if (!(mAOutside > zero)) { OPENVDB_THROW(ValueError, "expected grid A outside value > 0, got " << mAOutside); } if (!(mAInside < zero)) { OPENVDB_THROW(ValueError, "expected grid A inside value < 0, got " << mAInside); } if (!(mBOutside > zero)) { OPENVDB_THROW(ValueError, "expected grid B outside value > 0, got " << mBOutside); } if (!(mBInside < zero)) { OPENVDB_THROW(ValueError, "expected grid B outside value < 0, got " << mBOutside); } } protected: ValueT mAOutside, mAInside, mBOutside, mBInside; }; //////////////////////////////////////// template struct CsgUnionVisitor: public CsgVisitorBase { typedef TreeType TreeT; typedef typename TreeT::ValueType ValueT; typedef typename TreeT::LeafNodeType::ChildAllIter ChildIterT; enum { STOP = CsgVisitorBase::STOP }; CsgUnionVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {} /// Don't process nodes that are at different tree levels. template inline int operator()(AIterT&, BIterT&) { return 0; } /// Process root and internal nodes. template inline int operator()(IterT& aIter, IterT& bIter) { ValueT aValue = zeroVal(); typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue); if (!aChild && aValue < zeroVal()) { // A is an inside tile. Leave it alone and stop traversing this branch. return STOP; } ValueT bValue = zeroVal(); typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue); if (!bChild && bValue < zeroVal()) { // B is an inside tile. Make A an inside tile and stop traversing this branch. aIter.setValue(this->mAInside); aIter.setValueOn(bIter.isValueOn()); delete aChild; return STOP; } if (!aChild && aValue > zeroVal()) { // A is an outside tile. If B has a child, transfer it to A, // otherwise leave A alone. if (bChild) { bIter.setValue(this->mBOutside); bIter.setValueOff(); bChild->resetBackground(this->mBOutside, this->mAOutside); aIter.setChild(bChild); // transfer child delete aChild; } return STOP; } // If A has a child and B is an outside tile, stop traversing this branch. // Continue traversal only if A and B both have children. return (aChild && bChild) ? 0 : STOP; } /// Process leaf node values. inline int operator()(ChildIterT& aIter, ChildIterT& bIter) { ValueT aValue, bValue; aIter.probeValue(aValue); bIter.probeValue(bValue); if (aValue > bValue) { // a = min(a, b) aIter.setValue(bValue); aIter.setValueOn(bIter.isValueOn()); } return 0; } }; //////////////////////////////////////// template struct CsgIntersectVisitor: public CsgVisitorBase { typedef TreeType TreeT; typedef typename TreeT::ValueType ValueT; typedef typename TreeT::LeafNodeType::ChildAllIter ChildIterT; enum { STOP = CsgVisitorBase::STOP }; CsgIntersectVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {} /// Don't process nodes that are at different tree levels. template inline int operator()(AIterT&, BIterT&) { return 0; } /// Process root and internal nodes. template inline int operator()(IterT& aIter, IterT& bIter) { ValueT aValue = zeroVal(); typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue); if (!aChild && !(aValue < zeroVal())) { // A is an outside tile. Leave it alone and stop traversing this branch. return STOP; } ValueT bValue = zeroVal(); typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue); if (!bChild && !(bValue < zeroVal())) { // B is an outside tile. Make A an outside tile and stop traversing this branch. aIter.setValue(this->mAOutside); aIter.setValueOn(bIter.isValueOn()); delete aChild; return STOP; } if (!aChild && aValue < zeroVal()) { // A is an inside tile. If B has a child, transfer it to A, // otherwise leave A alone. if (bChild) { bIter.setValue(this->mBOutside); bIter.setValueOff(); bChild->resetBackground(this->mBOutside, this->mAOutside); aIter.setChild(bChild); // transfer child delete aChild; } return STOP; } // If A has a child and B is an outside tile, stop traversing this branch. // Continue traversal only if A and B both have children. return (aChild && bChild) ? 0 : STOP; } /// Process leaf node values. inline int operator()(ChildIterT& aIter, ChildIterT& bIter) { ValueT aValue, bValue; aIter.probeValue(aValue); bIter.probeValue(bValue); if (aValue < bValue) { // a = max(a, b) aIter.setValue(bValue); aIter.setValueOn(bIter.isValueOn()); } return 0; } }; //////////////////////////////////////// template struct CsgDiffVisitor: public CsgVisitorBase { typedef TreeType TreeT; typedef typename TreeT::ValueType ValueT; typedef typename TreeT::LeafNodeType::ChildAllIter ChildIterT; enum { STOP = CsgVisitorBase::STOP }; CsgDiffVisitor(const TreeT& a, const TreeT& b): CsgVisitorBase(a, b) {} /// Don't process nodes that are at different tree levels. template inline int operator()(AIterT&, BIterT&) { return 0; } /// Process root and internal nodes. template inline int operator()(IterT& aIter, IterT& bIter) { ValueT aValue = zeroVal(); typename IterT::ChildNodeType* aChild = aIter.probeChild(aValue); if (!aChild && !(aValue < zeroVal())) { // A is an outside tile. Leave it alone and stop traversing this branch. return STOP; } ValueT bValue = zeroVal(); typename IterT::ChildNodeType* bChild = bIter.probeChild(bValue); if (!bChild && bValue < zeroVal()) { // B is an inside tile. Make A an inside tile and stop traversing this branch. aIter.setValue(this->mAOutside); aIter.setValueOn(bIter.isValueOn()); delete aChild; return STOP; } if (!aChild && aValue < zeroVal()) { // A is an inside tile. If B has a child, transfer it to A, // otherwise leave A alone. if (bChild) { bIter.setValue(this->mBOutside); bIter.setValueOff(); bChild->resetBackground(this->mBOutside, this->mAOutside); aIter.setChild(bChild); // transfer child bChild->negate(); delete aChild; } return STOP; } // If A has a child and B is an outside tile, stop traversing this branch. // Continue traversal only if A and B both have children. return (aChild && bChild) ? 0 : STOP; } /// Process leaf node values. inline int operator()(ChildIterT& aIter, ChildIterT& bIter) { ValueT aValue, bValue; aIter.probeValue(aValue); bIter.probeValue(bValue); bValue = math::negative(bValue); if (aValue < bValue) { // a = max(a, -b) aIter.setValue(bValue); aIter.setValueOn(bIter.isValueOn()); } return 0; } }; //////////////////////////////////////// template OPENVDB_STATIC_SPECIALIZATION inline void csgUnion(GridOrTreeT& a, GridOrTreeT& b, bool prune) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b); CsgUnionVisitor visitor(aTree, bTree); aTree.visit2(bTree, visitor); if (prune) aTree.pruneLevelSet(); //if (prune) aTree.prune(); } template OPENVDB_STATIC_SPECIALIZATION inline void csgIntersection(GridOrTreeT& a, GridOrTreeT& b, bool prune) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b); CsgIntersectVisitor visitor(aTree, bTree); aTree.visit2(bTree, visitor); if (prune) aTree.pruneLevelSet(); //if (prune) aTree.prune(); } template OPENVDB_STATIC_SPECIALIZATION inline void csgDifference(GridOrTreeT& a, GridOrTreeT& b, bool prune) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; TreeT &aTree = Adapter::tree(a), &bTree = Adapter::tree(b); CsgDiffVisitor visitor(aTree, bTree); aTree.visit2(bTree, visitor); if (prune) aTree.pruneLevelSet(); //if (prune) aTree.prune(); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_COMPOSITE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/PointScatter.h0000644000000000000000000003272512252453157015160 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file PointScatter.h #ifndef OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief The two point scatters UniformPointScatter and /// NonUniformPointScatter depend on the following two classes: /// /// The @c PointAccessorType template argument below refers to any class /// with the following interface: /// @code /// class PointAccessor { /// ... /// public: /// void add(const openvdb::Vec3R &pos);// appends point with world positions pos /// }; /// @endcode /// /// /// The @c InterruptType template argument below refers to any class /// with the following interface: /// @code /// class Interrupter { /// ... /// public: /// void start(const char* name = NULL)// called when computations begin /// void end() // called when computations end /// bool wasInterrupted(int percent=-1)// return true to break computation ///}; /// @endcode /// /// @note If no template argument is provided for this InterruptType /// the util::NullInterrupter is used which implies that all /// interrupter calls are no-ops (i.e. incurs no computational overhead). /// @brief Uniform scatters of point in the active voxels. /// The point count is either explicitly defined or implicitly /// through the specification of a global density (=points-per-volume) /// /// @note This uniform scattering technique assumes that the number of /// points is generally smaller than the number of active voxels /// (including virtual active voxels in active tiles). template class UniformPointScatter { public: UniformPointScatter(PointAccessorType& points, int pointCount, RandomGenerator& randGen, InterruptType* interrupt = NULL): mPoints(points), mInterrupter(interrupt), mPointCount(pointCount), mPointsPerVolume(0.0f), mVoxelCount(0), mRandomGen(randGen) { } UniformPointScatter(PointAccessorType& points, float pointsPerVolume, RandomGenerator& randGen, InterruptType* interrupt = NULL): mPoints(points), mInterrupter(interrupt), mPointCount(0), mPointsPerVolume(pointsPerVolume), mVoxelCount(0), mRandomGen(randGen) { } /// This is the main functor method implementing the actual scattering of points. template void operator()(const GridT& grid) { mVoxelCount = grid.activeVoxelCount(); if (mVoxelCount == 0) return; const openvdb::Index64 voxelId = mVoxelCount - 1; const openvdb::Vec3d dim = grid.voxelSize(); if (mPointsPerVolume>0) { if (mInterrupter) mInterrupter->start("Uniform scattering with fixed point density"); mPointCount = int(mPointsPerVolume * dim[0]*dim[1]*dim[2] * mVoxelCount); } else if (mPointCount>0) { if (mInterrupter) mInterrupter->start("Uniform scattering with fixed point count"); mPointsPerVolume = mPointCount/float(dim[0]*dim[1]*dim[2] * mVoxelCount); } else { return; } openvdb::CoordBBox bbox; /// build sorted multi-map of random voxel-ids to contain a point std::multiset mVoxelSet; const double maxId = static_cast(voxelId); for (int i=0, chunks=100000; i::iterator voxelIter = mVoxelSet.begin(), voxelEnd = mVoxelSet.end(); typename GridT::ValueOnCIter valueIter = grid.cbeginValueOn(); mPointCount = 0;//addPoint increments this counter size_t interruptCount = 0; for (openvdb::Index64 n=valueIter.getVoxelCount(); voxelIter != voxelEnd; ++voxelIter) { //only check interrupter for every 32'th particle if (!(interruptCount++ & (1<<5)-1) && util::wasInterrupted(mInterrupter)) return; while ( n <= *voxelIter ) { ++valueIter; n += valueIter.getVoxelCount(); } if (valueIter.isVoxelValue()) {// a majorty is expected to be voxels const openvdb::Coord min = valueIter.getCoord(); const openvdb::Vec3R dmin(min.x()-0.5, min.y()-0.5, min.z()-0.5); this->addPoint(grid, dmin); } else {// tiles contain multiple (virtual) voxels valueIter.getBoundingBox(bbox); const openvdb::Coord size(bbox.extents()); const openvdb::Vec3R dmin(bbox.min().x()-0.5, bbox.min().y()-0.5, bbox.min().z()-0.5); this->addPoint(grid, dmin, size); } } if (mInterrupter) mInterrupter->end(); } // The following methods should only be called after the // the operator() method was called void print(const std::string &name, std::ostream& os = std::cout) const { os << "Uniformely scattered " << mPointCount << " points into " << mVoxelCount << " active voxels in \"" << name << "\" corresponding to " << mPointsPerVolume << " points per volume." << std::endl; } int getPointCount() const { return mPointCount; } float getPointsPerVolume() const { return mPointsPerVolume; } openvdb::Index64 getVoxelCount() const { return mVoxelCount; } private: PointAccessorType& mPoints; InterruptType* mInterrupter; int mPointCount; float mPointsPerVolume; openvdb::Index64 mVoxelCount; RandomGenerator& mRandomGen; boost::uniform_01 mRandom; double getRand() { return mRandom(mRandomGen); } template inline void addPoint(const GridT &grid, const openvdb::Vec3R &pos, const openvdb::Vec3R &delta) { mPoints.add(grid.indexToWorld(pos + delta)); ++mPointCount; } template inline void addPoint(const GridT &grid, const openvdb::Vec3R &dmin) { this->addPoint(grid, dmin, openvdb::Vec3R(getRand(),getRand(),getRand())); } template inline void addPoint(const GridT &grid, const openvdb::Vec3R &dmin, const openvdb::Coord &size) { const openvdb::Vec3R d(size.x()*getRand(),size.y()*getRand(),size.z()*getRand()); this->addPoint(grid, dmin, d); } }; // class UniformPointScatter /// @brief Non-uniform scatters of point in the active voxels. /// The local point count is implicitly defined as a product of /// of a global density and the local voxel (or tile) value. /// /// @note This scattering technique can be significantly slower /// than a uniform scattering since its computational complexity /// is proportional to the active voxel (and tile) count. template class NonUniformPointScatter { public: NonUniformPointScatter(PointAccessorType& points, float pointsPerVolume, RandomGenerator& randGen, InterruptType* interrupt = NULL): mPoints(points), mInterrupter(interrupt), mPointCount(0), mPointsPerVolume(pointsPerVolume),//note this is NOT the local point density mVoxelCount(0), mRandomGen(randGen) { } /// This is the main functor method implementing the actual scattering of points. template void operator()(const GridT& grid) { mVoxelCount = grid.activeVoxelCount(); if (mVoxelCount == 0) return;//throw std::runtime_error("No voxels in which to scatter points!"); if (mInterrupter) mInterrupter->start("Non-uniform scattering with local point density"); const openvdb::Vec3d dim = grid.voxelSize(); const double volumePerVoxel = dim[0]*dim[1]*dim[2], pointsPerVoxel = mPointsPerVolume * volumePerVoxel; openvdb::CoordBBox bbox; size_t interruptCount = 0; for (typename GridT::ValueOnCIter iter = grid.cbeginValueOn(); iter; ++iter) { //only check interrupter for every 32'th active value if (!(interruptCount++ & (1<<5)-1) && util::wasInterrupted(mInterrupter)) return; const double d = (*iter) * pointsPerVoxel * iter.getVoxelCount(); const int n = int(d); if (iter.isVoxelValue()) { // a majorty is expected to be voxels const openvdb::Coord min = iter.getCoord(); const openvdb::Vec3R dmin(min.x()-0.5, min.y()-0.5, min.z()-0.5); for (int i = 0; i < n; ++i) this->addPoint(grid, dmin); if (getRand() < (d - n)) this->addPoint(grid, dmin); } else { // tiles contain multiple (virtual) voxels iter.getBoundingBox(bbox); const openvdb::Coord size(bbox.extents()); const openvdb::Vec3R dmin(bbox.min().x()-0.5, bbox.min().y()-0.5, bbox.min().z()-0.5); for (int i = 0; i < n; ++i) this->addPoint(grid, dmin, size); if (getRand() < (d - n)) this->addPoint(grid, dmin, size); } }//loop over the active values if (mInterrupter) mInterrupter->end(); } // The following methods should only be called after the // the operator() method was called void print(const std::string &name, std::ostream& os = std::cout) const { os << "Non-uniformely scattered " << mPointCount << " points into " << mVoxelCount << " active voxels in \"" << name << "\"." << std::endl; } int getPointCount() const { return mPointCount; } openvdb::Index64 getVoxelCount() const { return mVoxelCount; } private: PointAccessorType& mPoints; InterruptType* mInterrupter; int mPointCount; float mPointsPerVolume; openvdb::Index64 mVoxelCount; RandomGenerator& mRandomGen; boost::uniform_01 mRandom; double getRand() { return mRandom(mRandomGen); } template inline void addPoint(const GridT &grid, const openvdb::Vec3R &pos, const openvdb::Vec3R &delta) { mPoints.add(grid.indexToWorld(pos + delta)); ++mPointCount; } template inline void addPoint(const GridT &grid, const openvdb::Vec3R &dmin) { this->addPoint(grid, dmin, openvdb::Vec3R(getRand(),getRand(),getRand())); } template inline void addPoint(const GridT &grid, const openvdb::Vec3R &dmin, const openvdb::Coord &size) { const openvdb::Vec3R d(size.x()*getRand(),size.y()*getRand(),size.z()*getRand()); this->addPoint(grid, dmin, d); } }; // class NonUniformPointScatter } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_POINT_SCATTER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Filter.h0000644000000000000000000004141312252453157013760 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file Filter.h /// /// @brief Filtering of VDB volumes. Note that only the values in the /// grid are changed, not its topology! All operations can optionally /// be masked with another grid that acts as an alpha-mask. #ifndef OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include "Interpolation.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Volume filtering (e.g., diffusion) with optional alpha masking /// /// @note Only the values in the grid are changed, not its topology! template::Type, typename InterruptT = util::NullInterrupter> class Filter { public: typedef GridT GridType; typedef MaskT MaskType; typedef typename GridType::TreeType TreeType; typedef typename TreeType::LeafNodeType LeafType; typedef typename GridType::ValueType ValueType; typedef typename MaskType::ValueType AlphaType; typedef typename tree::LeafManager LeafManagerType; typedef typename LeafManagerType::LeafRange RangeType; typedef typename LeafManagerType::BufferType BufferType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); /// Constructor /// @param grid Grid to be filtered. /// @param interrupt Optional interrupter. Filter(GridT& grid, InterruptT* interrupt = NULL) : mGrid(&grid) , mTask(0) , mInterrupter(interrupt) , mMask(NULL) , mGrainSize(1) , mMinMask(0) , mMaxMask(1) , mInvertMask(false) { } /// @brief Shallow copy constructor called by tbb::parallel_for() /// threads during filtering. /// @param other The other Filter from which to copy. Filter(const Filter& other) : mGrid(other.mGrid) , mTask(other.mTask) , mInterrupter(other.mInterrupter) , mMask(other.mMask) , mGrainSize(other.mGrainSize) , mMinMask(other.mMinMask) , mMaxMask(other.mMaxMask) , mInvertMask(other.mInvertMask) { } /// @return the grain-size used for multi-threading int getGrainSize() const { return mGrainSize; } /// @brief Set the grain-size used for multi-threading. /// @note A grainsize of 0 or less disables multi-threading! void setGrainSize(int grainsize) { mGrainSize = grainsize; } /// @brief Return the minimum value of the mask to be used for the /// derivation of a smooth alpha value. AlphaType minMask() const { return mMinMask; } /// @brief Return the maximum value of the mask to be used for the /// derivation of a smooth alpha value. AlphaType maxMask() const { return mMaxMask; } /// @brief Define the range for the (optional) scalar mask. /// @param min Minimum value of the range. /// @param max Maximum value of the range. /// @details Mask values outside the range are clamped to zero or one, and /// values inside the range map smoothly to 0->1 (unless the mask is inverted). /// @throw ValueError if @a min is not smaller then @a max. void setMaskRange(AlphaType min, AlphaType max) { if (!(min < max)) OPENVDB_THROW(ValueError, "Invalid mask range (expects min < max)"); mMinMask = min; mMaxMask = max; } /// @brief Return true if the mask is inverted, i.e. min->max in the /// original mask maps to 1->0 in the inverted alpha mask. bool isMaskInverted() const { return mInvertMask; } /// @brief Invert the optional mask, i.e. min->max in the original /// mask maps to 1->0 in the inverted alpha mask. void invertMask(bool invert=true) { mInvertMask = invert; } /// @brief One iteration of a fast separable mean-value (i.e. box) filter. /// @param width The width of the mean-value filter is 2*width+1 voxels. /// @param iterations Number of times the mean-value filter is applied. /// @param mask Optional alpha mask. void mean(int width = 1, int iterations = 1, const MaskType* mask = NULL); /// @brief One iteration of a fast separable gaussian filter. /// /// @note This is approximated as 4 iterations of a separable mean filter /// which typically leads an approximation that's better than 95%! /// @param width The width of the mean-value filter is 2*width+1 voxels. /// @param iterations Numer of times the mean-value filter is applied. /// @param mask Optional alpha mask. void gaussian(int width = 1, int iterations = 1, const MaskType* mask = NULL); /// @brief One iteration of a median-value filter /// /// @note This filter is not separable and is hence relatively slow! /// @param width The width of the mean-value filter is 2*width+1 voxels. /// @param iterations Numer of times the mean-value filter is applied. /// @param mask Optional alpha mask. void median(int width = 1, int iterations = 1, const MaskType* mask = NULL); /// Offsets (i.e. adds) a constant value to all active voxels. /// @param offset Offset in the same units as the grid. /// @param mask Optional alpha mask. void offset(ValueType offset, const MaskType* mask = NULL); /// @brief Used internally by tbb::parallel_for() /// @param range Range of LeafNodes over which to multi-thread. /// /// @warning Never call this method directly! void operator()(const RangeType& range) const { if (mTask) mTask(const_cast(this), range); else OPENVDB_THROW(ValueError, "task is undefined - call median(), mean(), etc."); } private: typedef typename TreeType::LeafNodeType LeafT; typedef typename LeafT::ValueOnIter VoxelIterT; typedef typename LeafT::ValueOnCIter VoxelCIterT; typedef typename tree::LeafManager::BufferType BufferT; typedef typename RangeType::Iterator LeafIterT; void cook(LeafManagerType& leafs); // Private class to derive the normalized alpha mask struct AlphaMask { AlphaMask(const GridType& grid, const MaskType& mask, AlphaType min, AlphaType max, bool invert) : mSampler(mask, grid), mMin(min), mInvNorm(1/(max-min)), mInvert(invert) { assert(min < max); } inline bool operator()(const Coord& xyz, AlphaType& a, AlphaType& b) const { a = mSampler(xyz); const AlphaType t = (a-mMin)*mInvNorm; a = t > 0 ? t < 1 ? (3-2*t)*t*t : 1 : 0;//smooth mapping to 0->1 b = 1 - a; if (mInvert) std::swap(a,b); return a>0; } tools::DualGridSampler mSampler; const AlphaType mMin, mInvNorm; const bool mInvert; }; template struct Avg { Avg(const GridT* grid, Int32 w) : acc(grid->tree()), width(w), frac(1/ValueType(2*w+1)) {} ValueType operator()(Coord xyz) { ValueType sum = zeroVal(); Int32& i = xyz[Axis], j = i + width; for (i -= width; i <= j; ++i) sum += acc.getValue(xyz); return sum*frac; } typename GridT::ConstAccessor acc; const Int32 width; const ValueType frac; }; // Private filter methods called by tbb::parallel_for threads template void doBox( const RangeType& r, Int32 w); void doBoxX(const RangeType& r, Int32 w) { this->doBox >(r,w); } void doBoxZ(const RangeType& r, Int32 w) { this->doBox >(r,w); } void doBoxY(const RangeType& r, Int32 w) { this->doBox >(r,w); } void doMedian(const RangeType&, int); void doOffset(const RangeType&, ValueType); /// @return true if the process was interrupted bool wasInterrupted(); GridType* mGrid; typename boost::function mTask; InterruptT* mInterrupter; const MaskType* mMask; int mGrainSize; AlphaType mMinMask, mMaxMask; bool mInvertMask; }; // end of Filter class //////////////////////////////////////// template inline void Filter::mean(int width, int iterations, const MaskType* mask) { mMask = mask; if (mInterrupter) mInterrupter->start("Applying mean filter"); const int w = std::max(1, width); LeafManagerType leafs(mGrid->tree(), 1, mGrainSize==0); for (int i=0; iwasInterrupted(); ++i) { mTask = boost::bind(&Filter::doBoxX, _1, _2, w); this->cook(leafs); mTask = boost::bind(&Filter::doBoxY, _1, _2, w); this->cook(leafs); mTask = boost::bind(&Filter::doBoxZ, _1, _2, w); this->cook(leafs); } if (mInterrupter) mInterrupter->end(); } template inline void Filter::gaussian(int width, int iterations, const MaskType* mask) { mMask = mask; if (mInterrupter) mInterrupter->start("Applying gaussian filter"); const int w = std::max(1, width); LeafManagerType leafs(mGrid->tree(), 1, mGrainSize==0); for (int i=0; iwasInterrupted(); ++n) { mTask = boost::bind(&Filter::doBoxX, _1, _2, w); this->cook(leafs); mTask = boost::bind(&Filter::doBoxY, _1, _2, w); this->cook(leafs); mTask = boost::bind(&Filter::doBoxZ, _1, _2, w); this->cook(leafs); } } if (mInterrupter) mInterrupter->end(); } template inline void Filter::median(int width, int iterations, const MaskType* mask) { mMask = mask; if (mInterrupter) mInterrupter->start("Applying median filter"); LeafManagerType leafs(mGrid->tree(), 1, mGrainSize==0); mTask = boost::bind(&Filter::doMedian, _1, _2, std::max(1, width)); for (int i=0; iwasInterrupted(); ++i) this->cook(leafs); if (mInterrupter) mInterrupter->end(); } template inline void Filter::offset(ValueType value, const MaskType* mask) { mMask = mask; if (mInterrupter) mInterrupter->start("Applying offset"); LeafManagerType leafs(mGrid->tree(), 0, mGrainSize==0); mTask = boost::bind(&Filter::doOffset, _1, _2, value); this->cook(leafs); if (mInterrupter) mInterrupter->end(); } //////////////////////////////////////// /// Private method to perform the task (serial or threaded) and /// subsequently swap the leaf buffers. template inline void Filter::cook(LeafManagerType& leafs) { if (mGrainSize>0) { tbb::parallel_for(leafs.leafRange(mGrainSize), *this); } else { (*this)(leafs.leafRange()); } leafs.swapLeafBuffer(1, mGrainSize==0); } /// One dimensional convolution of a separable box filter template template inline void Filter::doBox(const RangeType& range, Int32 w) { this->wasInterrupted(); AvgT avg(mGrid, w); if (mMask) { AlphaType a, b; AlphaMask alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { const Coord xyz = iter.getCoord(); if (alpha(xyz, a, b)) { buffer.setValue(iter.pos(), ValueType(b*(*iter) + a*avg(xyz))); } } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { buffer.setValue(iter.pos(), avg(iter.getCoord())); } } } } /// Performs simple but slow median-value diffusion template inline void Filter::doMedian(const RangeType& range, int width) { this->wasInterrupted(); typename math::DenseStencil stencil(*mGrid, width);//creates local cache! if (mMask) { AlphaType a, b; AlphaMask alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { if (alpha(iter.getCoord(), a, b)) { stencil.moveTo(iter); buffer.setValue(iter.pos(), ValueType(b*(*iter) + a*stencil.median())); } } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); buffer.setValue(iter.pos(), stencil.median()); } } } } /// Offsets the values by a constant template inline void Filter::doOffset(const RangeType& range, ValueType offset) { this->wasInterrupted(); if (mMask) { AlphaType a, b; AlphaMask alpha(*mGrid, *mMask, mMinMask, mMaxMask, mInvertMask); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { if (alpha(iter.getCoord(), a, b)) iter.setValue(ValueType(*iter + a*offset)); } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { iter.setValue(*iter + offset); } } } } template inline bool Filter::wasInterrupted() { if (util::wasInterrupted(mInterrupter)) { tbb::task::self().cancel_group_execution(); return true; } return false; } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_FILTER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetAdvect.h0000644000000000000000000007456312252453157015421 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file LevelSetAdvect.h /// /// @brief Hyperbolic advection of narrow-band level sets #ifndef OPENVDB_TOOLS_LEVEL_SET_ADVECT_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVEL_SET_ADVECT_HAS_BEEN_INCLUDED #include "LevelSetTracker.h" #include "Interpolation.h" // for BoxSampler, etc. #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// Below are two simple wrapper classes for advection velocity fields /// DiscreteField wraps a velocity grid and EnrightField is mostly /// intended for debugging (it's an analytical divergence free and /// periodic field). They both share the same API required by the /// LevelSetAdvection class defined below. Thus, any class with this /// API should work with LevelSetAdvection. /// Note the Field wrapper classes below always assume the velocity /// is represented in the world-frame of reference. For DiscreteField /// this implies the input grid must contain velocities in world /// coordinates. /// @brief Thin wrapper class for a velocity grid /// @note Consider replacing BoxSampler with StaggeredBoxSampler template class DiscreteField { public: typedef typename VelGridT::ValueType VectorType; typedef typename VectorType::ValueType ScalarType; DiscreteField(const VelGridT &vel): mAccessor(vel.tree()), mTransform(&vel.transform()) {} /// @return const reference to the transfrom between world and index space /// @note Use this method to determine if a client grid is /// aligned with the coordinate space of the velocity grid. const math::Transform& transform() const { return *mTransform; } /// @return the interpolated velocity at the world space position xyz inline VectorType operator() (const Vec3d& xyz, ScalarType) const { VectorType result = zeroVal(); Interpolator::sample(mAccessor, mTransform->worldToIndex(xyz), result); return result; } /// @return the velocity at the coordinate space position ijk inline VectorType operator() (const Coord& ijk, ScalarType) const { return mAccessor.getValue(ijk); } private: const typename VelGridT::ConstAccessor mAccessor;//Not thread-safe const math::Transform* mTransform; }; // end of DiscreteField /// @brief Analytical, divergence-free and periodic vecloity field /// @note Primarily intended for debugging! /// @warning This analytical velocity only produce meaningfull values /// in the unitbox in world space. In other words make sure any level /// set surface in fully enclodes in the axis aligned bounding box /// spanning 0->1 in world units. template class EnrightField { public: typedef ScalarT ScalarType; typedef math::Vec3 VectorType; EnrightField() {} /// @return const reference to the identity transfrom between world and index space /// @note Use this method to determine if a client grid is /// aligned with the coordinate space of this velocity field math::Transform transform() const { return math::Transform(); } /// @return the velocity in world units, evaluated at the world /// position xyz and at the specified time inline VectorType operator() (const Vec3d& xyz, ScalarType time) const; /// @return the velocity at the coordinate space position ijk inline VectorType operator() (const Coord& ijk, ScalarType time) const { return (*this)(ijk.asVec3d(), time); } }; // end of EnrightField /// @brief Hyperbolic advection of narrow-band level sets in an /// external velocity field /// /// The @c FieldType template argument below refers to any functor /// with the following interface (see tools/VelocityFields.h /// for examples): /// /// @code /// class VelocityField { /// ... /// public: /// openvdb::VectorType operator() (const openvdb::Coord& xyz, ScalarType time) const; /// ... /// }; /// @endcode /// /// @note The functor method returns the velocity field at coordinate /// position xyz of the advection grid, and for the specified /// time. Note that since the velocity is returned in the local /// coordinate space of the grid that is being advected, the functor /// typically depends on the transformation of that grid. This design /// is chosen for performance reasons. /// /// The @c InterruptType template argument below refers to any class /// with the following interface: /// @code /// class Interrupter { /// ... /// public: /// void start(const char* name = NULL)// called when computations begin /// void end() // called when computations end /// bool wasInterrupted(int percent=-1)// return true to break computation ///}; /// @endcode /// /// @note If no template argument is provided for this InterruptType /// the util::NullInterrupter is used which implies that all /// interrupter calls are no-ops (i.e. incurs no computational overhead). /// template, typename InterruptT = util::NullInterrupter> class LevelSetAdvection { public: typedef GridT GridType; typedef LevelSetTracker TrackerT; typedef typename TrackerT::RangeType RangeType; typedef typename TrackerT::LeafType LeafType; typedef typename TrackerT::BufferType BufferType; typedef typename TrackerT::ValueType ScalarType; typedef typename FieldT::VectorType VectorType; /// Main constructor LevelSetAdvection(GridT& grid, const FieldT& field, InterruptT* interrupt = NULL): mTracker(grid, interrupt), mField(field), mSpatialScheme(math::HJWENO5_BIAS), mTemporalScheme(math::TVD_RK2) {} virtual ~LevelSetAdvection() {}; /// @return the spatial finite difference scheme math::BiasedGradientScheme getSpatialScheme() const { return mSpatialScheme; } /// @brief Set the spatial finite difference scheme void setSpatialScheme(math::BiasedGradientScheme scheme) { mSpatialScheme = scheme; } /// @return the temporal integration scheme math::TemporalIntegrationScheme getTemporalScheme() const { return mTemporalScheme; } /// @brief Set the spatial finite difference scheme void setTemporalScheme(math::TemporalIntegrationScheme scheme) { mTemporalScheme = scheme; } /// @return the spatial finite difference scheme math::BiasedGradientScheme getTrackerSpatialScheme() const { return mTracker.getSpatialScheme(); } /// @brief Set the spatial finite difference scheme void setTrackerSpatialScheme(math::BiasedGradientScheme scheme) { mTracker.setSpatialScheme(scheme); } /// @return the temporal integration scheme math::TemporalIntegrationScheme getTrackerTemporalScheme() const { return mTracker.getTemporalScheme(); } /// @brief Set the spatial finite difference scheme void setTrackerTemporalScheme(math::TemporalIntegrationScheme scheme) { mTracker.setTemporalScheme(scheme); } /// @return The number of normalizations performed per track or /// normalize call. int getNormCount() const { return mTracker.getNormCount(); } /// @brief Set the number of normalizations performed per track or /// normalize call. void setNormCount(int n) { mTracker.setNormCount(n); } /// @return the grain-size used for multi-threading int getGrainSize() const { return mTracker.getGrainSize(); } /// @brief Set the grain-size used for multi-threading. /// @note A grainsize of 0 or less disables multi-threading! void setGrainSize(int grainsize) { mTracker.setGrainSize(grainsize); } /// Advect the level set from it's current time, time0, to it's /// final time, time1. If time0>time1 backward advection is performed. /// /// @return number of CFL iterations used to advect from time0 to time1 size_t advect(ScalarType time0, ScalarType time1); private: // This templated private class implements all the level set magic. template class LevelSetAdvect { public: /// Main constructor LevelSetAdvect(LevelSetAdvection& parent); /// Shallow copy constructor called by tbb::parallel_for() threads LevelSetAdvect(const LevelSetAdvect& other); /// Shallow copy constructor called by tbb::parallel_reduce() threads LevelSetAdvect(LevelSetAdvect& other, tbb::split); /// destructor virtual ~LevelSetAdvect() {if (mIsMaster) this->clearField();}; /// Advect the level set from it's current time, time0, to it's final time, time1. /// @return number of CFL iterations size_t advect(ScalarType time0, ScalarType time1); /// Used internally by tbb::parallel_for() void operator()(const RangeType& r) const { if (mTask) mTask(const_cast(this), r); else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); } /// Used internally by tbb::parallel_reduce() void operator()(const RangeType& r) { if (mTask) mTask(this, r); else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); } /// This is only called by tbb::parallel_reduce() threads void join(const LevelSetAdvect& other) { mMaxAbsV = math::Max(mMaxAbsV, other.mMaxAbsV); } private: typedef typename boost::function FuncType; LevelSetAdvection& mParent; VectorType** mVec; const ScalarType mMinAbsV; ScalarType mMaxAbsV; const MapT* mMap; FuncType mTask; const bool mIsMaster; /// Enum to defeing the type of multi-threading enum ThreadingMode { PARALLEL_FOR, PARALLEL_REDUCE }; // for internal use // method calling tbb void cook(ThreadingMode mode, size_t swapBuffer = 0); /// Sample field and return the CFT time step typename GridT::ValueType sampleField(ScalarType time0, ScalarType time1); void clearField(); void sampleXformedField(const RangeType& r, ScalarType time0, ScalarType time1); void sampleAlignedField(const RangeType& r, ScalarType time0, ScalarType time1); // Forward Euler advection steps: Phi(result) = Phi(0) - dt * V.Grad(0); void euler1(const RangeType& r, ScalarType dt, Index resultBuffer); // Convex combination of Phi and a forward Euler advection steps: // Phi(result) = alpha * Phi(phi) + (1-alpha) * (Phi(0) - dt * V.Grad(0)); void euler2(const RangeType& r, ScalarType dt, ScalarType alpha, Index phiBuffer, Index resultBuffer); }; // end of private LevelSetAdvect class template size_t advect1(ScalarType time0, ScalarType time1); template size_t advect2(ScalarType time0, ScalarType time1); template size_t advect3(ScalarType time0, ScalarType time1); TrackerT mTracker; //each thread needs a deep copy of the field since it might contain a ValueAccessor const FieldT mField; math::BiasedGradientScheme mSpatialScheme; math::TemporalIntegrationScheme mTemporalScheme; // disallow copy by assignment void operator=(const LevelSetAdvection& other) {} };//end of LevelSetAdvection template inline size_t LevelSetAdvection::advect(ScalarType time0, ScalarType time1) { switch (mSpatialScheme) { case math::FIRST_BIAS: return this->advect1(time0, time1); case math::SECOND_BIAS: return this->advect1(time0, time1); case math::THIRD_BIAS: return this->advect1(time0, time1); case math::WENO5_BIAS: return this->advect1(time0, time1); case math::HJWENO5_BIAS: return this->advect1(time0, time1); default: OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); } return 0; } template template inline size_t LevelSetAdvection::advect1(ScalarType time0, ScalarType time1) { switch (mTemporalScheme) { case math::TVD_RK1: return this->advect2(time0, time1); case math::TVD_RK2: return this->advect2(time0, time1); case math::TVD_RK3: return this->advect2(time0, time1); default: OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); } return 0; } template template inline size_t LevelSetAdvection::advect2(ScalarType time0, ScalarType time1) { const math::Transform& trans = mTracker.grid().transform(); if (trans.mapType() == math::UniformScaleMap::mapType()) { return this->advect3(time0, time1); } else if (trans.mapType() == math::UniformScaleTranslateMap::mapType()) { return this->advect3(time0, time1); } else if (trans.mapType() == math::UnitaryMap::mapType()) { return this->advect3(time0, time1); } else if (trans.mapType() == math::TranslationMap::mapType()) { return this->advect3(time0, time1); } else { OPENVDB_THROW(ValueError, "MapType not supported!"); } return 0; } template template inline size_t LevelSetAdvection::advect3(ScalarType time0, ScalarType time1) { LevelSetAdvect tmp(*this); return tmp.advect(time0, time1); } /////////////////////////////////////////////////////////////////////// template inline math::Vec3 EnrightField::operator() (const Vec3d& xyz, ScalarType time) const { static const ScalarT pi = ScalarT(3.1415926535897931), phase = pi / ScalarT(3.0); const ScalarT Px = pi * ScalarT(xyz[0]), Py = pi * ScalarT(xyz[1]), Pz = pi * ScalarT(xyz[2]); const ScalarT tr = cos(ScalarT(time) * phase); const ScalarT a = sin(ScalarT(2.0)*Py); const ScalarT b = -sin(ScalarT(2.0)*Px); const ScalarT c = sin(ScalarT(2.0)*Pz); return math::Vec3( tr * ( ScalarT(2) * math::Pow2(sin(Px)) * a * c ), tr * ( b * math::Pow2(sin(Py)) * c ), tr * ( b * a * math::Pow2(sin(Pz)) )); } /////////////////////////////////////////////////////////////////////// template template inline LevelSetAdvection:: LevelSetAdvect:: LevelSetAdvect(LevelSetAdvection& parent): mParent(parent), mVec(NULL), mMinAbsV(1e-6), mMap(parent.mTracker.grid().transform().template constMap().get()), mTask(0), mIsMaster(true) { } template template inline LevelSetAdvection:: LevelSetAdvect:: LevelSetAdvect(const LevelSetAdvect& other): mParent(other.mParent), mVec(other.mVec), mMinAbsV(other.mMinAbsV), mMaxAbsV(other.mMaxAbsV), mMap(other.mMap), mTask(other.mTask), mIsMaster(false) { } template template inline LevelSetAdvection:: LevelSetAdvect:: LevelSetAdvect(LevelSetAdvect& other, tbb::split): mParent(other.mParent), mVec(other.mVec), mMinAbsV(other.mMinAbsV), mMaxAbsV(other.mMaxAbsV), mMap(other.mMap), mTask(other.mTask), mIsMaster(false) { } template template inline size_t LevelSetAdvection:: LevelSetAdvect:: advect(ScalarType time0, ScalarType time1) { size_t countCFL = 0; if ( math::isZero(time0 - time1) ) return countCFL; const bool isForward = time0 < time1; while ((isForward ? time0time1) && mParent.mTracker.checkInterrupter()) { /// Make sure we have enough temporal auxiliary buffers mParent.mTracker.leafs().rebuildAuxBuffers(TemporalScheme == math::TVD_RK3 ? 2 : 1); const ScalarType dt = this->sampleField(time0, time1); if ( math::isZero(dt) ) break;//V is essentially zero so terminate OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN //switch is resolved at compile-time switch(TemporalScheme) { case math::TVD_RK1: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(0) mTask = boost::bind(&LevelSetAdvect::euler1, _1, _2, dt, /*result=*/1); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(PARALLEL_FOR, 1); break; case math::TVD_RK2: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(0) mTask = boost::bind(&LevelSetAdvect::euler1, _1, _2, dt, /*result=*/1); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(PARALLEL_FOR, 1); // Convex combine explict Euler step: t2 = t0 + dt // Phi_t2(1) = 1/2 * Phi_t0(1) + 1/2 * (Phi_t1(0) - dt * V.Grad_t1(0)) mTask = boost::bind(&LevelSetAdvect::euler2, _1, _2, dt, ScalarType(0.5), /*phi=*/1, /*result=*/1); // Cook and swap buffer 0 and 1 such that Phi_t2(0) and Phi_t1(1) this->cook(PARALLEL_FOR, 1); break; case math::TVD_RK3: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(0) mTask = boost::bind(&LevelSetAdvect::euler1, _1, _2, dt, /*result=*/1); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(PARALLEL_FOR, 1); // Convex combine explict Euler step: t2 = t0 + dt/2 // Phi_t2(2) = 3/4 * Phi_t0(1) + 1/4 * (Phi_t1(0) - dt * V.Grad_t1(0)) mTask = boost::bind(&LevelSetAdvect::euler2, _1, _2, dt, ScalarType(0.75), /*phi=*/1, /*result=*/2); // Cook and swap buffer 0 and 2 such that Phi_t2(0) and Phi_t1(2) this->cook(PARALLEL_FOR, 2); // Convex combine explict Euler step: t3 = t0 + dt // Phi_t3(2) = 1/3 * Phi_t0(1) + 2/3 * (Phi_t2(0) - dt * V.Grad_t2(0) mTask = boost::bind(&LevelSetAdvect::euler2, _1, _2, dt, ScalarType(1.0/3.0), /*phi=*/1, /*result=*/2); // Cook and swap buffer 0 and 2 such that Phi_t3(0) and Phi_t2(2) this->cook(PARALLEL_FOR, 2); break; default: OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); }//end of compile-time resolved switch OPENVDB_NO_UNREACHABLE_CODE_WARNING_END time0 += isForward ? dt : -dt; ++countCFL; mParent.mTracker.leafs().removeAuxBuffers(); this->clearField(); /// Track the narrow band mParent.mTracker.track(); }//end wile-loop over time return countCFL;//number of CLF propagation steps } template template inline typename GridT::ValueType LevelSetAdvection:: LevelSetAdvect:: sampleField(ScalarType time0, ScalarType time1) { mMaxAbsV = mMinAbsV; const size_t leafCount = mParent.mTracker.leafs().leafCount(); if (leafCount==0) return ScalarType(0.0); mVec = new VectorType*[leafCount]; if (mParent.mField.transform() == mParent.mTracker.grid().transform()) { mTask = boost::bind(&LevelSetAdvect::sampleAlignedField, _1, _2, time0, time1); } else { mTask = boost::bind(&LevelSetAdvect::sampleXformedField, _1, _2, time0, time1); } this->cook(PARALLEL_REDUCE); if (math::isExactlyEqual(mMinAbsV, mMaxAbsV)) return ScalarType(0.0);//V is essentially zero static const ScalarType CFL = (TemporalScheme == math::TVD_RK1 ? ScalarType(0.3) : TemporalScheme == math::TVD_RK2 ? ScalarType(0.9) : ScalarType(1.0))/math::Sqrt(ScalarType(3.0)); const ScalarType dt = math::Abs(time1 - time0), dx = mParent.mTracker.voxelSize(); return math::Min(dt, ScalarType(CFL*dx/math::Sqrt(mMaxAbsV))); } template template inline void LevelSetAdvection:: LevelSetAdvect:: sampleXformedField(const RangeType& range, ScalarType time0, ScalarType time1) { const bool isForward = time0 < time1; typedef typename LeafType::ValueOnCIter VoxelIterT; const MapT& map = *mMap; mParent.mTracker.checkInterrupter(); for (size_t n=range.begin(), e=range.end(); n != e; ++n) { const LeafType& leaf = mParent.mTracker.leafs().leaf(n); VectorType* vec = new VectorType[leaf.onVoxelCount()]; int m = 0; for (VoxelIterT iter = leaf.cbeginValueOn(); iter; ++iter, ++m) { const VectorType V = mParent.mField(map.applyMap(iter.getCoord().asVec3d()), time0); mMaxAbsV = math::Max(mMaxAbsV, ScalarType(math::Pow2(V[0])+math::Pow2(V[1])+math::Pow2(V[2]))); vec[m] = isForward ? V : -V; } mVec[n] = vec; } } template template inline void LevelSetAdvection:: LevelSetAdvect:: sampleAlignedField(const RangeType& range, ScalarType time0, ScalarType time1) { const bool isForward = time0 < time1; typedef typename LeafType::ValueOnCIter VoxelIterT; mParent.mTracker.checkInterrupter(); for (size_t n=range.begin(), e=range.end(); n != e; ++n) { const LeafType& leaf = mParent.mTracker.leafs().leaf(n); VectorType* vec = new VectorType[leaf.onVoxelCount()]; int m = 0; for (VoxelIterT iter = leaf.cbeginValueOn(); iter; ++iter, ++m) { const VectorType V = mParent.mField(iter.getCoord(), time0); mMaxAbsV = math::Max(mMaxAbsV, ScalarType(math::Pow2(V[0])+math::Pow2(V[1])+math::Pow2(V[2]))); vec[m] = isForward ? V : -V; } mVec[n] = vec; } } template template inline void LevelSetAdvection:: LevelSetAdvect:: clearField() { if (mVec == NULL) return; for (size_t n=0, e=mParent.mTracker.leafs().leafCount(); n template inline void LevelSetAdvection:: LevelSetAdvect:: cook(ThreadingMode mode, size_t swapBuffer) { mParent.mTracker.startInterrupter("Advecting level set"); if (mParent.mTracker.getGrainSize()==0) { (*this)(mParent.mTracker.leafs().getRange()); } else if (mode == PARALLEL_FOR) { tbb::parallel_for(mParent.mTracker.leafs().getRange(mParent.mTracker.getGrainSize()), *this); } else if (mode == PARALLEL_REDUCE) { tbb::parallel_reduce(mParent.mTracker.leafs().getRange(mParent.mTracker.getGrainSize()), *this); } else { throw std::runtime_error("Undefined threading mode"); } mParent.mTracker.leafs().swapLeafBuffer(swapBuffer, mParent.mTracker.getGrainSize()==0); mParent.mTracker.endInterrupter(); } // Forward Euler advection steps: // Phi(result) = Phi(0) - dt * V.Grad(0); template template inline void LevelSetAdvection:: LevelSetAdvect:: euler1(const RangeType& range, ScalarType dt, Index resultBuffer) { typedef math::BIAS_SCHEME Scheme; typedef typename Scheme::template ISStencil::StencilType Stencil; typedef typename LeafType::ValueOnCIter VoxelIterT; mParent.mTracker.checkInterrupter(); const MapT& map = *mMap; typename TrackerT::LeafManagerType& leafs = mParent.mTracker.leafs(); Stencil stencil(mParent.mTracker.grid()); for (size_t n=range.begin(), e=range.end(); n != e; ++n) { BufferType& result = leafs.getBuffer(n, resultBuffer); const VectorType* vec = mVec[n]; int m=0; for (VoxelIterT iter = leafs.leaf(n).cbeginValueOn(); iter; ++iter, ++m) { stencil.moveTo(iter); const VectorType V = vec[m], G = math::GradientBiased::result(map, stencil, V); result.setValue(iter.pos(), *iter - dt * V.dot(G)); } } } // Convex combination of Phi and a forward Euler advection steps: // Phi(result) = alpha * Phi(phi) + (1-alpha) * (Phi(0) - dt * V.Grad(0)); template template inline void LevelSetAdvection:: LevelSetAdvect:: euler2(const RangeType& range, ScalarType dt, ScalarType alpha, Index phiBuffer, Index resultBuffer) { typedef math::BIAS_SCHEME Scheme; typedef typename Scheme::template ISStencil::StencilType Stencil; typedef typename LeafType::ValueOnCIter VoxelIterT; mParent.mTracker.checkInterrupter(); const MapT& map = *mMap; typename TrackerT::LeafManagerType& leafs = mParent.mTracker.leafs(); const ScalarType beta = ScalarType(1.0) - alpha; Stencil stencil(mParent.mTracker.grid()); for (size_t n=range.begin(), e=range.end(); n != e; ++n) { const BufferType& phi = leafs.getBuffer(n, phiBuffer); BufferType& result = leafs.getBuffer(n, resultBuffer); const VectorType* vec = mVec[n]; int m=0; for (VoxelIterT iter = leafs.leaf(n).cbeginValueOn(); iter; ++iter, ++m) { stencil.moveTo(iter); const VectorType V = vec[m], G = math::GradientBiased::result(map, stencil, V); result.setValue(iter.pos(), alpha*phi[iter.pos()] + beta*(*iter - dt * V.dot(G))); } } } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVEL_SET_ADVECT_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/ParticlesToLevelSet.h0000644000000000000000000010137412252453157016433 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file ParticlesToLevelSet.h /// /// @brief This tool converts particles (with position, radius and /// velocity) into a singed distance field encoded as a narrow band /// level set. Optionally arbitrary attributes on the particles can /// be transferred resulting in an additional attribute grid with the /// same topology as the level set grid. /// /// @note This fast particle to level set converter is always intended /// to be combined with some kind of surface post processing, /// i.e. tools::Filter. Without such post processing the generated /// surface is typically too noisy and blooby. However it serves as a /// great and fast starting point for subsequent level set surface /// processing and convolution. /// /// The @c ParticleListT template argument below refers to any class /// with the following interface (see unittest/TestParticlesToLevelSet.cc /// and SOP_DW_OpenVDBParticleVoxelizer for practical examples): /// @code /// /// class ParticleList { /// ... /// public: /// /// // Return the total number of particles in list. /// // Always required! /// size_t size() const; /// /// // Get the world space position of n'th particle. /// // Required by ParticledToLevelSet::rasterizeSphere(*this,radius). /// void getPos(size_t n, Vec3R& xyz) const; /// /// // Get the world space position and radius of n'th particle. /// // Required by ParticledToLevelSet::rasterizeSphere(*this). /// void getPosRad(size_t n, Vec3R& xyz, Real& rad) const; /// /// // Get the world space position, radius and velocity of n'th particle. /// // Required by ParticledToLevelSet::rasterizeSphere(*this,radius). /// void getPosRadVel(size_t n, Vec3R& xyz, Real& rad, Vec3R& vel) const; /// /// // Get the attribute of the n'th particle. AttributeType is user-defined! /// // Only required is attribute transfer is enabled in ParticledToLevelSet. /// void getAtt(AttributeType& att) const; /// }; /// @endcode /// /// @note See unittest/TestParticlesToLevelSet.cc for an example. /// /// The @c InterruptT template argument below refers to any class /// with the following interface: /// @code /// class Interrupter { /// ... /// public: /// void start(const char* name = NULL)// called when computations begin /// void end() // called when computations end /// bool wasInterrupted(int percent=-1)// return true to break computation /// }; /// @endcode /// /// @note If no template argument is provided for this InterruptT /// the util::NullInterrupter is used which implies that all /// interrupter calls are no-ops (i.e. incurs no computational overhead). #ifndef OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Composite.h" // for csgUnion() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { // This is a simple type that combines a distance value and a particle // attribute. It's required for attribute transfer which is performed // in the ParticlesToLevelSet::Raster memberclass defined below. namespace local {template class BlindData;} template class ParticlesToLevelSet { public: typedef typename boost::is_void::type DisableT; typedef InterrupterT InterrupterType; typedef SdfGridT SdfGridType; typedef typename SdfGridT::ValueType SdfType; typedef typename boost::mpl::if_::type AttType; typedef typename SdfGridT::template ValueConverter::Type AttGridType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); /// @brief Constructor using an exiting signed distance, /// i.e. narrow band level set, grid. /// /// @param grid Level set grid in which particles are rasterized /// @param interrupt Callback to interrupt a long-running process /// /// @note The input grid is assumed to be a valid level set and if /// it already contains voxels (with SDF values) partices are unioned /// onto the exisinting level set surface. However, if attribute tranfer /// is enabled, i.e. AttributeT != void, attributes are only /// generated for voxels that overlap with particles, not the existing /// voxels in the input grid (for which no attributes exist!). /// /// @details The width in voxel units of the generated narrow band level set is /// given by 2*background/dx, where background is the background value /// stored in the grid, and dx is the voxel size derived from the /// transform also stored in the grid. Also note that -background /// corresponds to the constant value inside the generated narrow /// band level sets. Finally the default NullInterrupter should /// compile out interruption checks during optimization, thus /// incurring no run-time overhead. explicit ParticlesToLevelSet(SdfGridT& grid, InterrupterT* interrupt = NULL); /// Destructor ~ParticlesToLevelSet() { delete mBlindGrid; } /// @brief This methods syncs up the level set and attribute grids /// and therefore needs to be called before any of these grids are /// used and after the last call to any of the rasterizer methods. /// /// @note Avoid calling this method more then once and only after /// all the particles have been rasterized. It has no effect if /// attribute transfer is disabled, i.e. AttributeT = void. void finalize(); /// @brief Return a shared pointer to the grid containing the /// (optional) attribute. /// /// @warning If attribute transfer was disabled, i.e. AttributeT = /// void, or finalize() was not called the pointer is NULL! typename AttGridType::Ptr attributeGrid() { return mAttGrid; } /// @brief Return the size of a voxel in world units Real getVoxelSize() const { return mDx; } /// @brief Return the half-width of the narrow band in voxel units Real getHalfWidth() const { return mHalfWidth; } /// @brief Return the smallest radius allowed in voxel units Real getRmin() const { return mRmin; } /// @brief Return the largest radius allowed in voxel units Real getRmax() const { return mRmax; } /// @brief Return true if any particles were ignored due to their size bool ignoredParticles() const { return mMinCount>0 || mMaxCount>0; } /// @brief Return number of small particles that were ignore due to Rmin size_t getMinCount() const { return mMinCount; } /// @brief Return number of large particles that were ignore due to Rmax size_t getMaxCount() const { return mMaxCount; } /// @brief set the smallest radius allowed in voxel units void setRmin(Real Rmin) { mRmin = math::Max(Real(0),Rmin); } /// @brief set the largest radius allowed in voxel units void setRmax(Real Rmax) { mRmax = math::Max(mRmin,Rmax); } /// @brief Rreturn the grain-size used for multi-threading int getGrainSize() const { return mGrainSize; } /// @brief Set the grain-size used for multi-threading. /// @note A grainsize of 0 or less disables multi-threading! void setGrainSize(int grainSize) { mGrainSize = grainSize; } /// @brief Rasterize a sphere per particle derived from their /// position and radius. All spheres are CSG unioned. /// /// @param pa Particles with position and radius. template void rasterizeSpheres(const ParticleListT& pa); /// @brief Rasterize a sphere per particle derived from their /// position and constant radius. All spheres are CSG unioned. /// /// @param pa Particles with position. /// @param radius Constant particle radius in world units. template void rasterizeSpheres(const ParticleListT& pa, Real radius); /// @brief Rasterize a trail per particle derived from their /// position, radius and velocity. Each trail is generated /// as CSG unions of sphere instances with decreasing radius. /// /// @param pa particles with position, radius and velocity. /// @param delta controls distance between sphere instances /// (default=1). Be careful not to use too small values since this /// can lead to excessive computation per trail (which the /// interrupter can't stop). /// /// @note The direction of a trail is inverse to the direction of /// the velocity vector, and the length is given by |V|. The radius /// at the head of the trail is given by the radius of the particle /// and the radius at the tail of the trail is Rmin voxel units which /// has a default value of 1.5 corresponding to the Nyquist /// frequency! template void rasterizeTrails(const ParticleListT& pa, Real delta=1.0); private: typedef local::BlindData BlindType; typedef typename SdfGridT::template ValueConverter::Type BlindGridType; /// Class with multi-threaded implementation of particle rasterization template struct Raster; SdfGridType* mSdfGrid; typename AttGridType::Ptr mAttGrid; BlindGridType* mBlindGrid; InterrupterT* mInterrupter; Real mDx, mHalfWidth; Real mRmin, mRmax;//ignore particles outside this range of radii in voxel size_t mMinCount, mMaxCount;//counters for ignored particles! int mGrainSize; };//end of ParticlesToLevelSet class template inline ParticlesToLevelSet:: ParticlesToLevelSet(SdfGridT& grid, InterrupterT* interrupter) : mSdfGrid(&grid), mBlindGrid(NULL), mInterrupter(interrupter), mDx(grid.voxelSize()[0]), mHalfWidth(grid.background()/mDx), mRmin(1.5),// corresponds to the Nyquist grid sampling frequency mRmax(100.0),// corresponds to a huge particle (probably too large!) mMinCount(0), mMaxCount(0), mGrainSize(1) { if (!mSdfGrid->hasUniformVoxels() ) { OPENVDB_THROW(RuntimeError, "ParticlesToLevelSet only supports uniform voxels!"); } if (mSdfGrid->getGridClass() != GRID_LEVEL_SET) { OPENVDB_THROW(RuntimeError, "ParticlesToLevelSet only supports level sets!" "\nUse Grid::setGridClass(openvdb::GRID_LEVEL_SET)"); } if (!DisableT::value) { mBlindGrid = new BlindGridType(BlindType(grid.background())); mBlindGrid->setTransform(mSdfGrid->transform().copy()); } } template template inline void ParticlesToLevelSet:: rasterizeSpheres(const ParticleListT& pa) { if (DisableT::value) { Raster r(*this, mSdfGrid, pa); r.rasterizeSpheres(); } else { Raster r(*this, mBlindGrid, pa); r.rasterizeSpheres(); } } template template inline void ParticlesToLevelSet:: rasterizeSpheres(const ParticleListT& pa, Real radius) { if (DisableT::value) { Raster r(*this, mSdfGrid, pa); r.rasterizeSpheres(radius/mDx); } else { Raster r(*this, mBlindGrid, pa); r.rasterizeSpheres(radius/mDx); } } template template inline void ParticlesToLevelSet:: rasterizeTrails(const ParticleListT& pa, Real delta) { if (DisableT::value) { Raster r(*this, mSdfGrid, pa); r.rasterizeTrails(delta); } else { Raster r(*this, mBlindGrid, pa); r.rasterizeTrails(delta); } } template inline void ParticlesToLevelSet::finalize() { if (mBlindGrid==NULL) return; typedef typename SdfGridType::TreeType SdfTreeT; typedef typename AttGridType::TreeType AttTreeT; typedef typename BlindGridType::TreeType BlindTreeT; // Use topology copy constructors since output grids have the same topology as mBlindDataGrid const BlindTreeT& tree = mBlindGrid->tree(); // New level set tree typename SdfTreeT::Ptr sdfTree(new SdfTreeT( tree, tree.background().visible(), openvdb::TopologyCopy())); // Note this overwrites any existing attribute grids! typename AttTreeT::Ptr attTree(new AttTreeT( tree, tree.background().blind(), openvdb::TopologyCopy())); mAttGrid = typename AttGridType::Ptr(new AttGridType(attTree)); mAttGrid->setTransform(mBlindGrid->transform().copy()); // Extract the level set and IDs from mBlindDataGrid. We will // explore the fact that by design active values always live // at the leaf node level, i.e. no active tiles exist in level sets typedef typename BlindTreeT::LeafCIter LeafIterT; typedef typename BlindTreeT::LeafNodeType LeafT; typedef typename SdfTreeT::LeafNodeType SdfLeafT; typedef typename AttTreeT::LeafNodeType AttLeafT; for (LeafIterT n = tree.cbeginLeaf(); n; ++n) { const LeafT& leaf = *n; const openvdb::Coord xyz = leaf.origin(); // Get leafnodes that were allocated during topology contruction! SdfLeafT* sdfLeaf = sdfTree->probeLeaf(xyz); AttLeafT* attLeaf = attTree->probeLeaf(xyz); for (typename LeafT::ValueOnCIter m=leaf.cbeginValueOn(); m; ++m) { // Use linear offset (vs coordinate) access for better performance! const openvdb::Index k = m.pos(); const BlindType& v = *m; sdfLeaf->setValueOnly(k, v.visible()); attLeaf->setValueOnly(k, v.blind()); } } sdfTree->signedFloodFill();//required since we only transferred active voxels! if (mSdfGrid->empty()) { mSdfGrid->setTree(sdfTree); } else { tools::csgUnion(mSdfGrid->tree(), *sdfTree, /*prune=*/true); } } /////////////////////////////////////////////////////////// template template struct ParticlesToLevelSet::Raster { typedef typename boost::is_void::type DisableT; typedef ParticlesToLevelSet ParticlesToLevelSetT; typedef typename ParticlesToLevelSetT::SdfType SdfT;//type of signed distance values typedef typename ParticlesToLevelSetT::AttType AttT;//type of particle attribute typedef typename GridT::ValueType ValueT; typedef typename GridT::Accessor AccessorT; /// @brief Main constructor Raster(ParticlesToLevelSetT& parent, GridT* grid, const ParticleListT& particles) : mParent(parent), mParticles(particles), mGrid(grid), mMap(*(mGrid->transform().baseMap())), mMinCount(0), mMaxCount(0), mOwnsGrid(false) { } /// @brief Copy constructor called by tbb threads Raster(Raster& other, tbb::split) : mParent(other.mParent), mParticles(other.mParticles), mGrid(new GridT(*other.mGrid, openvdb::ShallowCopy())), mMap(other.mMap), mMinCount(0), mMaxCount(0), mTask(other.mTask), mOwnsGrid(true) { mGrid->newTree(); } virtual ~Raster() { if (mOwnsGrid) delete mGrid; } /// @brief Rasterize a sphere per particle derived from their /// position and radius. All spheres are CSG unioned. void rasterizeSpheres() { mMinCount = mMaxCount = 0; if (mParent.mInterrupter) { mParent.mInterrupter->start("Rasterizing particles to level set using spheres"); } mTask = boost::bind(&Raster::rasterSpheres, _1, _2); this->cook(); if (mParent.mInterrupter) mParent.mInterrupter->end(); } /// @brief Rasterize a sphere per particle derived from their /// position and constant radius. All spheres are CSG unioned. /// @param radius constant radius of all particles in voxel units. void rasterizeSpheres(Real radius) { mMinCount = radius < mParent.mRmin ? mParticles.size() : 0; mMaxCount = radius > mParent.mRmax ? mParticles.size() : 0; if (mMinCount>0 || mMaxCount>0) {//skipping all particles! mParent.mMinCount = mMinCount; mParent.mMaxCount = mMaxCount; } else { if (mParent.mInterrupter) { mParent.mInterrupter->start( "Rasterizing particles to level set using const spheres"); } mTask = boost::bind(&Raster::rasterFixedSpheres, _1, _2, SdfT(radius)); this->cook(); if (mParent.mInterrupter) mParent.mInterrupter->end(); } } /// @brief Rasterize a trail per particle derived from their /// position, radius and velocity. Each trail is generated /// as CSG unions of sphere instances with decreasing radius. /// /// @param delta controls distance between sphere instances /// (default=1). Be careful not to use too small values since this /// can lead to excessive computation per trail (which the /// interrupter can't stop). /// /// @note The direction of a trail is inverse to the direction of /// the velocity vector, and the length is given by |V|. The radius /// at the head of the trail is given by the radius of the particle /// and the radius at the tail of the trail is Rmin voxel units which /// has a default value of 1.5 corresponding to the Nyquist frequency! void rasterizeTrails(Real delta=1.0) { mMinCount = mMaxCount = 0; if (mParent.mInterrupter) { mParent.mInterrupter->start("Rasterizing particles to level set using trails"); } mTask = boost::bind(&Raster::rasterTrails, _1, _2, SdfT(delta)); this->cook(); if (mParent.mInterrupter) mParent.mInterrupter->end(); } /// @brief Kicks off the optionally multithreaded computation void operator()(const tbb::blocked_range& r) { assert(mTask); mTask(this, r); mParent.mMinCount = mMinCount; mParent.mMaxCount = mMaxCount; } /// @brief Reguired by tbb::parallel_reduce void join(Raster& other) { tools::csgUnion(*mGrid, *other.mGrid, /*prune=*/true); mMinCount += other.mMinCount; mMaxCount += other.mMaxCount; } private: /// Disallow assignment since some of the members are references Raster& operator=(const Raster& other) { return *this; } /// @return true if the particle is too small or too large bool ignoreParticle(SdfT R) { if (R < mParent.mRmin) {// below the cutoff radius ++mMinCount; return true; } if (R > mParent.mRmax) {// above the cutoff radius ++mMaxCount; return true; } return false; } /// @brief Reguired by tbb::parallel_reduce to multithreaded /// rasterization of particles as spheres with variable radius /// /// @param r tbb's default range referring to the list of particles void rasterSpheres(const tbb::blocked_range& r) { AccessorT acc = mGrid->getAccessor(); // local accessor bool run = true; const SdfT invDx = 1/mParent.mDx; AttT att; Vec3R pos; Real rad; for (Index32 id = r.begin(), e=r.end(); run && id != e; ++id) { mParticles.getPosRad(id, pos, rad); const SdfT R = invDx * rad;// in voxel units if (this->ignoreParticle(R)) continue; const Vec3R P = mMap.applyInverseMap(pos); this->getAtt(id, att); run = this->makeSphere(P, R, att, acc); }//end loop over particles } /// @brief Reguired by tbb::parallel_reduce to multithreaded /// rasterization of particles as spheres with a fixed radius /// /// @param r tbb's default range referring to the list of particles void rasterFixedSpheres(const tbb::blocked_range& r, SdfT R) { const SdfT dx = mParent.mDx, w = mParent.mHalfWidth;// in voxel units AccessorT acc = mGrid->getAccessor(); // local accessor const ValueT inside = -mGrid->background(); const SdfT max = R + w;// maximum distance in voxel units const SdfT max2 = math::Pow2(max);//square of maximum distance in voxel units const SdfT min2 = math::Pow2(math::Max(SdfT(0), R - w));//square of minimum distance ValueT v; size_t count = 0; AttT att; Vec3R pos; for (size_t id = r.begin(), e=r.end(); id != e; ++id) { this->getAtt(id, att); mParticles.getPos(id, pos); const Vec3R P = mMap.applyInverseMap(pos); const Coord a(math::Floor(P[0]-max),math::Floor(P[1]-max),math::Floor(P[2]-max)); const Coord b(math::Ceil( P[0]+max),math::Ceil( P[1]+max),math::Ceil( P[2]+max)); for ( Coord c = a; c.x() <= b.x(); ++c.x() ) { //only check interrupter every 32'th scan in x if (!(count++ & (1<<5)-1) && util::wasInterrupted(mParent.mInterrupter)) { tbb::task::self().cancel_group_execution(); return; } SdfT x2 = math::Pow2( c.x() - P[0] ); for ( c.y() = a.y(); c.y() <= b.y(); ++c.y() ) { SdfT x2y2 = x2 + math::Pow2( c.y() - P[1] ); for ( c.z() = a.z(); c.z() <= b.z(); ++c.z() ) { SdfT x2y2z2 = x2y2 + math::Pow2(c.z()- P[2]);//square distance from c to P if ( x2y2z2 >= max2 || (!acc.probeValue(c,v) && v& r, SdfT delta) { AccessorT acc = mGrid->getAccessor(); // local accessor bool run = true; AttT att; Vec3R pos, vel; Real rad; const Vec3R origin = mMap.applyInverseMap(Vec3R(0,0,0)); const SdfT Rmin = mParent.mRmin, invDx = 1/mParent.mDx; for (size_t id = r.begin(), e=r.end(); run && id != e; ++id) { mParticles.getPosRadVel(id, pos, rad, vel); const SdfT R0 = invDx*rad; if (this->ignoreParticle(R0)) continue; this->getAtt(id, att); const Vec3R P0 = mMap.applyInverseMap(pos); const Vec3R V = mMap.applyInverseMap(vel) - origin;//exclude translation const SdfT speed = V.length(), inv_speed=1.0/speed; const Vec3R N = -V*inv_speed;// inverse normalized direction Vec3R P = P0;// local position of instance SdfT R = R0, d=0;// local radius and length of trail for (size_t m=0; run && d <= speed ; ++m) { run = this->makeSphere(P, R, att, acc); P += 0.5*delta*R*N;// adaptive offset along inverse velocity direction d = (P-P0).length();// current length of trail R = R0-(R0-Rmin)*d*inv_speed;// R = R0 -> mRmin(e.g. 1.5) }//end loop over sphere instances }//end loop over particles } void cook() { if (mParent.mGrainSize>0) { tbb::parallel_reduce( tbb::blocked_range(0,mParticles.size(),mParent.mGrainSize), *this); } else { (*this)(tbb::blocked_range(0, mParticles.size())); } } /// @brief Rasterize sphere at position P and radius R into a /// narrow-band level set with half-width, mHalfWidth. /// @return false if it was interrupted /// /// @param P coordinates of the particle position in voxel units /// @param R radius of particle in voxel units /// @param id /// @param accessor grid accessor with a private copy of the grid /// /// @note For best performance all computations are performed in /// voxel-space with the important exception of the final level set /// value that is converted to world units (e.g. the grid stores /// the closest Euclidian signed distances measured in world /// units). Also note we use the convention of positive distances /// outside the surface an negative distances inside the surface. bool makeSphere(const Vec3R &P, SdfT R, const AttT& att, AccessorT& acc) { const ValueT inside = -mGrid->background(); const SdfT dx = mParent.mDx, w = mParent.mHalfWidth; const SdfT max = R + w;// maximum distance in voxel units const Coord a(math::Floor(P[0]-max),math::Floor(P[1]-max),math::Floor(P[2]-max)); const Coord b(math::Ceil( P[0]+max),math::Ceil( P[1]+max),math::Ceil( P[2]+max)); const SdfT max2 = math::Pow2(max);//square of maximum distance in voxel units const SdfT min2 = math::Pow2(math::Max(SdfT(0), R - w));//square of minimum distance ValueT v; size_t count = 0; for ( Coord c = a; c.x() <= b.x(); ++c.x() ) { //only check interrupter every 32'th scan in x if (!(count++ & (1<<5)-1) && util::wasInterrupted(mParent.mInterrupter)) { tbb::task::self().cancel_group_execution(); return false; } SdfT x2 = math::Pow2( c.x() - P[0] ); for ( c.y() = a.y(); c.y() <= b.y(); ++c.y() ) { SdfT x2y2 = x2 + math::Pow2( c.y() - P[1] ); for ( c.z() = a.z(); c.z() <= b.z(); ++c.z() ) { SdfT x2y2z2 = x2y2 + math::Pow2( c.z() - P[2] );//square distance from c to P if ( x2y2z2 >= max2 || (!acc.probeValue(c,v) && v&)> FuncType; template typename boost::enable_if::type getAtt(size_t, AttT&) const {;} template typename boost::disable_if::type getAtt(size_t n, AttT& a) const {mParticles.getAtt(n, a);} template typename boost::enable_if, ValueT>::type Merge(T s, const AttT&) const { return s; } template typename boost::disable_if, ValueT>::type Merge(T s, const AttT& a) const { return ValueT(s,a); } ParticlesToLevelSetT& mParent; const ParticleListT& mParticles;//list of particles GridT* mGrid; const math::MapBase& mMap; size_t mMinCount, mMaxCount;//counters for ignored particles! FuncType mTask; const bool mOwnsGrid; };//end of Raster struct ///////////////////// YOU CAN SAFELY IGNORE THIS SECTION ///////////////////// namespace local { // This is a simple type that combines a distance value and a particle // attribute. It's required for attribute transfer which is defined in the // Raster class above. template class BlindData { public: typedef VisibleT type; typedef VisibleT VisibleType; typedef BlindT BlindType; explicit BlindData() {} explicit BlindData(VisibleT v) : mVisible(v) {} BlindData(VisibleT v, BlindT b) : mVisible(v), mBlind(b) {} BlindData& operator=(const BlindData& rhs) { mVisible = rhs.mVisible; mBlind = rhs.mBlind; return *this; } const VisibleT& visible() const { return mVisible; } const BlindT& blind() const { return mBlind; } bool operator==(const BlindData& rhs) const { return mVisible == rhs.mVisible; } bool operator< (const BlindData& rhs) const { return mVisible < rhs.mVisible; }; bool operator> (const BlindData& rhs) const { return mVisible > rhs.mVisible; }; BlindData operator+(const BlindData& rhs) const { return BlindData(mVisible + rhs.mVisible); }; BlindData operator+(const VisibleT& rhs) const { return BlindData(mVisible + rhs); }; BlindData operator-(const BlindData& rhs) const { return BlindData(mVisible - rhs.mVisible); }; BlindData operator-() const { return BlindData(-mVisible, mBlind); } protected: VisibleT mVisible; BlindT mBlind; }; // Required by several of the tree nodes template inline std::ostream& operator<<(std::ostream& ostr, const BlindData& rhs) { ostr << rhs.visible(); return ostr; } // Required by math::Abs template inline BlindData Abs(const BlindData& x) { return BlindData(math::Abs(x.visible()), x.blind()); } }// local namespace ////////////////////////////////////////////////////////////////////////////// } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_PARTICLES_TO_LEVELSET_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Dense.h0000644000000000000000000005172612252453157013601 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Dense.h /// /// @brief This file defines a simple dense grid and efficient /// converters to and from VDB grids. #ifndef OPENVDB_TOOLS_DENSE_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_DENSE_HAS_BEEN_INCLUDED #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Populate a dense grid with the values of voxels from a sparse grid, /// where the sparse grid intersects the dense grid. /// @param sparse an OpenVDB grid or tree from which to copy values /// @param dense the dense grid into which to copy values /// @param serial if false, process voxels in parallel template void copyToDense( const GridOrTreeT& sparse, DenseT& dense, bool serial = false); /// @brief Populate a sparse grid with the values of all of the voxels of a dense grid. /// @param dense the dense grid from which to copy values /// @param sparse an OpenVDB grid or tree into which to copy values /// @param tolerance values in the dense grid that are within this tolerance of the sparse /// grid's background value become inactive background voxels or tiles in the sparse grid /// @param serial if false, process voxels in parallel template void copyFromDense( const DenseT& dense, GridOrTreeT& sparse, const typename GridOrTreeT::ValueType& tolerance, bool serial = false); //////////////////////////////////////// /// We currently support the following two 3D memory layouts for dense /// volumes: XYZ, i.e. x is the fastest moving index, and ZYX, i.e. z /// is the fastest moving index. The ZYX memory layout leads to nested /// for-loops of the order x, y, z, which we find to be the most /// intuitive. Hence, ZYX is the layout used throughout VDB. However, /// other data structures, e.g. Houdini and Maya, employ the XYZ /// layout. Clearly a dense volume with the ZYX layout converts more /// efficiently to a VDB, but we support both for convenience. enum MemoryLayout { LayoutXYZ, LayoutZYX }; /// @brief Base class for Dense which is defined below. /// @note The constructor of this class is protected to prevent direct /// instantiation. template class DenseBase; /// @brief Partial template specialization of DenseBase. /// @note ZYX is the memory-layout in VDB. It leads to nested /// for-loops of the order x, y, z which we find to be the most intuitive. template class DenseBase { public: /// @brief Return the linear offset into this grid's value array given by /// unsigned coordinates (i, j, k), i.e., coordinates relative to /// the origin of this grid's bounding box. inline size_t coordToOffset(size_t i, size_t j, size_t k) const { return i*mX + j*mY + k; } /// @brief Return the stride of the array in the x direction ( = dimY*dimZ). /// @note This method is required by both CopyToDense and CopyFromDense. inline size_t xStride() const { return mX; } /// @brief Return the stride of the array in the y direction ( = dimZ). /// @note This method is required by both CopyToDense and CopyFromDense. inline size_t yStride() const { return mY; } /// @brief Return the stride of the array in the z direction ( = 1). /// @note This method is required by both CopyToDense and CopyFromDense. static size_t zStride() { return 1; } protected: /// Protected constructor so as to prevent direct instantiation DenseBase(const CoordBBox& bbox) : mBBox(bbox), mY(bbox.dim()[2]), mX(mY*bbox.dim()[1]) {} const CoordBBox mBBox;//signed coordinates of the domain represented by the grid const size_t mY, mX;//strides in the y and x direction };// end of DenseBase /// @brief Partial template specialization of DenseBase. /// @note This is the memory-layout emplayed in Houdini and Maya. It leads /// to nested for-loops of the order z, y, x. template class DenseBase { public: /// @brief Return the linear offset into this grid's value array given by /// unsigned coordinates (i, j, k), i.e., coordinates relative to /// the origin of this grid's bounding box. inline size_t coordToOffset(size_t i, size_t j, size_t k) const { return i + j*mY + k*mZ; } /// @brief Return the stride of the array in the x direction ( = 1). /// @note This method is required by both CopyToDense and CopyFromDense. static size_t xStride() { return 1; } /// @brief Return the stride of the array in the y direction ( = dimX). /// @note This method is required by both CopyToDense and CopyFromDense. inline size_t yStride() const { return mY; } /// @brief Return the stride of the array in the y direction ( = dimX*dimY). /// @note This method is required by both CopyToDense and CopyFromDense. inline size_t zStride() const { return mZ; } protected: /// Protected constructor so as to prevent direct instantiation DenseBase(const CoordBBox& bbox) : mBBox(bbox), mY(bbox.dim()[0]), mZ(mY*bbox.dim()[1]) {} const CoordBBox mBBox;//signed coordinates of the domain represented by the grid const size_t mY, mZ;//strides in the y and z direction };// end of DenseBase /// @brief Dense is a simple dense grid API used by the CopyToDense and /// CopyFromDense classes defined below. /// @details Use the Dense class to efficiently produce a dense in-memory /// representation of an OpenVDB grid. However, be aware that a dense grid /// could have a memory footprint that is orders of magnitude larger than /// the sparse grid from which it originates. /// /// @note This class can be used as a simple wrapper for existing dense grid /// classes if they provide access to the raw data array. /// @note This implementation allows for the 3D memory layout to be /// defined by the MemoryLayout template parameter (see above for definition). /// The default memory layout is ZYX since that's the layout used by OpenVDB grids. template class Dense : public DenseBase { public: typedef ValueT ValueType; typedef DenseBase BaseT; /// @brief Construct a dense grid with a given range of coordinates. /// /// @param bbox the bounding box of the (signed) coordinate range of this grid /// @throw ValueError if the bounding box is empty. /// @note The min and max coordinates of the bounding box are inclusive. Dense(const CoordBBox& bbox) : BaseT(bbox) { this->init(); } /// @brief Construct a dense grid with a given range of coordinates and initial value /// /// @param bbox the bounding box of the (signed) coordinate range of this grid /// @param value the initial value of the grid. /// @throw ValueError if the bounding box is empty. /// @note The min and max coordinates of the bounding box are inclusive. Dense(const CoordBBox& bbox, const ValueT& value) : BaseT(bbox) { this->init(); this->fill(value); } /// @brief Construct a dense grid that wraps an external array. /// /// @param bbox the bounding box of the (signed) coordinate range of this grid /// @param data a raw C-style array whose size is commensurate with /// the coordinate domain of @a bbox /// /// @note The data array is assumed to have a stride of one in the @e z direction. /// @throw ValueError if the bounding box is empty. /// @note The min and max coordinates of the bounding box are inclusive. Dense(const CoordBBox& bbox, ValueT* data) : BaseT(bbox), mData(data) { if (BaseT::mBBox.empty()) { OPENVDB_THROW(ValueError, "can't construct a dense grid with an empty bounding box"); } } /// @brief Construct a dense grid with a given origin and dimensions. /// /// @param dim the desired dimensions of the grid /// @param min the signed coordinates of the first voxel in the dense grid /// @throw ValueError if any of the dimensions are zero. /// @note The @a min coordinate is inclusive, and the max coordinate will be /// @a min + @a dim - 1. Dense(const Coord& dim, const Coord& min = Coord(0)) : BaseT(CoordBBox(min, min+dim.offsetBy(-1))) { this->init(); } /// @brief Return the memory layout for this grid (see above for definitions). static MemoryLayout memoryLayout() { return Layout; } /// @brief Return a raw pointer to this grid's value array. /// @note This method is required by CopyToDense. inline ValueT* data() { return mData; } /// @brief Return a raw pointer to this grid's value array. /// @note This method is required by CopyFromDense. inline const ValueT* data() const { return mData; } /// @brief Return the bounding box of the signed index domain of this grid. /// @note This method is required by both CopyToDense and CopyFromDense. inline const CoordBBox& bbox() const { return BaseT::mBBox; } /// @brief Return the number of voxels contained in this grid. inline Index64 valueCount() const { return BaseT::mBBox.volume(); } /// @brief Set the value of the voxel at the given array offset. inline void setValue(size_t offset, const ValueT& value) { mData[offset] = value; } /// @brief Return the value of the voxel at the given array offset. const ValueT& getValue(size_t offset) const { return mData[offset]; } /// @brief Set the value of the voxel at unsigned index coordinates (i, j, k). /// @note This is somewhat slower than using an array offset. inline void setValue(size_t i, size_t j, size_t k, const ValueT& value) { mData[BaseT::coordToOffset(i,j,k)] = value; } /// @brief Return the value of the voxel at unsigned index coordinates (i, j, k). /// @note This is somewhat slower than using an array offset. inline const ValueT& getValue(size_t i, size_t j, size_t k) const { return mData[BaseT::coordToOffset(i,j,k)]; } /// @brief Set the value of the voxel at the given signed coordinates. /// @note This is slower than using either an array offset or unsigned index coordinates. inline void setValue(const Coord& xyz, const ValueT& value) { mData[this->coordToOffset(xyz)] = value; } /// @brief Return the value of the voxel at the given signed coordinates. /// @note This is slower than using either an array offset or unsigned index coordinates. inline const ValueT& getValue(const Coord& xyz) const { return mData[this->coordToOffset(xyz)]; } /// @brief Fill this grid with a constant value. inline void fill(const ValueT& value) { size_t size = this->valueCount(); ValueT* a = mData; while(size--) *a++ = value; } /// @brief Return the linear offset into this grid's value array given by /// the specified signed coordinates, i.e., coordinates in the space of /// this grid's bounding box. /// /// @note This method reflects the fact that we assume the same /// layout of values as an OpenVDB grid, i.e., the fastest coordinate is @e z. inline size_t coordToOffset(Coord xyz) const { assert(BaseT::mBBox.isInside(xyz)); return BaseT::coordToOffset(size_t(xyz[0]-BaseT::mBBox.min()[0]), size_t(xyz[1]-BaseT::mBBox.min()[1]), size_t(xyz[2]-BaseT::mBBox.min()[2])); } /// @brief Return the memory footprint of this Dense grid in bytes. inline Index64 memUsage() const { return sizeof(*this) + BaseT::mBBox.volume() * sizeof(ValueType); } private: /// @brief Private method to initialize the dense value array. void init() { if (BaseT::mBBox.empty()) { OPENVDB_THROW(ValueError, "can't construct a dense grid with an empty bounding box"); } mArray.reset(new ValueT[BaseT::mBBox.volume()]); mData = mArray.get(); } boost::shared_array mArray; ValueT* mData;//raw c-style pointer to values };// end of Dense //////////////////////////////////////// /// @brief Copy an OpenVDB tree into an existing dense grid. /// /// @note Only voxels that intersect the dense grid's bounding box are copied /// from the OpenVDB tree. But both active and inactive voxels are copied, /// so all existing values in the dense grid are overwritten, regardless of /// the OpenVDB tree's tolopogy. template > class CopyToDense { public: typedef _DenseT DenseT; typedef _TreeT TreeT; typedef typename TreeT::ValueType ValueT; CopyToDense(const TreeT& tree, DenseT& dense) : mRoot(&(tree.root())), mDense(&dense) {} void copy(bool serial = false) const { if (serial) { mRoot->copyToDense(mDense->bbox(), *mDense); } else { tbb::parallel_for(mDense->bbox(), *this); } } /// @brief Public method called by tbb::parallel_for void operator()(const CoordBBox& bbox) const { mRoot->copyToDense(bbox, *mDense); } private: const typename TreeT::RootNodeType* mRoot; DenseT* mDense; };// CopyToDense // Convenient wrapper function for the CopyToDense class template void copyToDense(const GridOrTreeT& sparse, DenseT& dense, bool serial) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; CopyToDense op(Adapter::constTree(sparse), dense); op.copy(serial); } //////////////////////////////////////// /// @brief Copy the values from a dense grid into an OpenVDB tree. /// /// @details Values in the dense grid that are within a tolerance of /// the background value are truncated to inactive background voxels or tiles. /// This allows the tree to form a sparse representation of the dense grid. /// /// @note Since this class allocates leaf nodes concurrently it is recommended /// to use a scalable implementation of @c new like the one provided by TBB, /// rather than the mutex-protected standard library @c new. template > class CopyFromDense { public: typedef _DenseT DenseT; typedef _TreeT TreeT; typedef typename TreeT::ValueType ValueT; typedef typename TreeT::LeafNodeType LeafT; typedef tree::ValueAccessor AccessorT; CopyFromDense(const DenseT& dense, TreeT& tree, const ValueT& tolerance) : mDense(&dense), mTree(&tree), mBlocks(NULL), mTolerance(tolerance), mAccessor(tree.empty() ? NULL : new AccessorT(tree)) { } CopyFromDense(const CopyFromDense& other) : mDense(other.mDense), mTree(other.mTree), mBlocks(other.mBlocks), mTolerance(other.mTolerance), mAccessor(other.mAccessor.get() == NULL ? NULL : new AccessorT(*mTree)) { } /// @brief Copy values from the dense grid to the sparse tree. void copy(bool serial = false) { mBlocks = new std::vector(); const CoordBBox& bbox = mDense->bbox(); // Pre-process: Construct a list of blocks alligned with (potential) leaf nodes for (CoordBBox sub=bbox; sub.min()[0] <= bbox.max()[0]; sub.min()[0] = sub.max()[0] + 1) { for (sub.min()[1] = bbox.min()[1]; sub.min()[1] <= bbox.max()[1]; sub.min()[1] = sub.max()[1] + 1) { for (sub.min()[2] = bbox.min()[2]; sub.min()[2] <= bbox.max()[2]; sub.min()[2] = sub.max()[2] + 1) { sub.max() = Coord::minComponent(bbox.max(), (sub.min()&(~(LeafT::DIM-1u))).offsetBy(LeafT::DIM-1u)); mBlocks->push_back(Block(sub)); } } } // Multi-threaded process: Convert dense grid into leaf nodes and tiles if (serial) { (*this)(tbb::blocked_range(0, mBlocks->size())); } else { tbb::parallel_for(tbb::blocked_range(0, mBlocks->size()), *this); } // Post-process: Insert leaf nodes and tiles into the tree, and prune the tiles only! tree::ValueAccessor acc(*mTree); for (size_t m=0, size = mBlocks->size(); mroot().pruneTiles(mTolerance); } /// @brief Public method called by tbb::parallel_for /// @warning Never call this method directly! void operator()(const tbb::blocked_range &r) const { assert(mBlocks); LeafT* leaf = new LeafT(); for (size_t m=r.begin(), n=0, end = r.end(); m != end; ++m, ++n) { Block& block = (*mBlocks)[m]; const CoordBBox &bbox = block.bbox; if (mAccessor.get() == NULL) {//i.e. empty target tree leaf->fill(mTree->background(), false); } else {//account for existing leaf nodes in the target tree if (const LeafT* target = mAccessor->probeConstLeaf(bbox.min())) { (*leaf) = (*target); } else { ValueT value = zeroVal(); bool state = mAccessor->probeValue(bbox.min(), value); leaf->fill(value, state); } } leaf->copyFromDense(bbox, *mDense, mTree->background(), mTolerance); if (!leaf->isConstant(block.tile.first, block.tile.second, mTolerance)) { leaf->setOrigin(bbox.min() & (~(LeafT::DIM - 1))); block.leaf = leaf; leaf = new LeafT(); } }// loop over blocks delete leaf; } private: struct Block { CoordBBox bbox; LeafT* leaf; std::pair tile; Block(const CoordBBox& b) : bbox(b), leaf(NULL) {} }; const DenseT* mDense; TreeT* mTree; std::vector* mBlocks; ValueT mTolerance; boost::scoped_ptr mAccessor; };// CopyFromDense // Convenient wrapper function for the CopyFromDense class template void copyFromDense(const DenseT& dense, GridOrTreeT& sparse, const typename GridOrTreeT::ValueType& tolerance, bool serial) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType TreeT; CopyFromDense op(dense, Adapter::tree(sparse), tolerance); op.copy(serial); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_DENSE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetUtil.h0000644000000000000000000002770212252453157015121 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file tools/LevelSetUtil.h /// /// @brief Miscellaneous utilities that operate primarily or exclusively /// on level set grids #ifndef OPENVDB_TOOLS_LEVELSETUTIL_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVELSETUTIL_HAS_BEEN_INCLUDED #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { // MS Visual C++ requires this extra level of indirection in order to compile // THIS MUST EXIST IN AN UNNAMED NAMESPACE IN ORDER TO COMPILE ON WINDOWS namespace { template inline typename GridType::ValueType lsutilGridMax() { return std::numeric_limits::max(); } template inline typename GridType::ValueType lsutilGridZero() { return zeroVal(); } } // unnamed namespace //////////////////////////////////////// /// @brief Threaded method to convert a sparse level set/SDF into a sparse fog volume /// /// @details For a level set, the active and negative-valued interior half of the /// narrow band becomes a linear ramp from 0 to 1; the inactive interior becomes /// active with a constant value of 1; and the exterior, including the background /// and the active exterior half of the narrow band, becomes inactive with a constant /// value of 0. The interior, though active, remains sparse. /// @details For a generic SDF, a specified cutoff distance determines the width /// of the ramp, but otherwise the result is the same as for a level set. /// /// @param grid level set/SDF grid to transform /// @param cutoffDistance optional world space cutoff distance for the ramp /// (automatically clamped if greater than the interior /// narrow band width) template inline void sdfToFogVolume( GridType& grid, typename GridType::ValueType cutoffDistance = lsutilGridMax()); //////////////////////////////////////// /// @brief Threaded method to extract an interior region mask from a level set/SDF grid /// /// @return a shared pointer to a new boolean grid with the same tree configuration and /// transform as the incoming @c grid and whose active voxels correspond to /// the interior of the input SDF /// /// @param grid a level set/SDF grid /// @param iso threshold below which values are considered to be part of the interior region /// template inline typename Grid::Type>::Ptr sdfInteriorMask( const GridType& grid, typename GridType::ValueType iso = lsutilGridZero()); //////////////////////////////////////// /// @brief Threaded operator that finds the minimum and maximum values /// among the active leaf-level voxels of a grid /// @details This is useful primarily for level set grids, which have /// no active tiles (all of their active voxels are leaf-level). template class MinMaxVoxel { public: typedef tree::LeafManager LeafArray; typedef typename TreeType::ValueType ValueType; // LeafArray = openvdb::tree::LeafManager leafs(myTree) MinMaxVoxel(LeafArray&); void runParallel(); void runSerial(); const ValueType& minVoxel() const { return mMin; } const ValueType& maxVoxel() const { return mMax; } inline MinMaxVoxel(const MinMaxVoxel&, tbb::split); inline void operator()(const tbb::blocked_range&); inline void join(const MinMaxVoxel&); private: LeafArray& mLeafArray; ValueType mMin, mMax; }; //////////////////////////////////////// // Internal utility objects and implementation details namespace internal { template class FogVolumeOp { public: FogVolumeOp(ValueType cutoffDistance) : mWeight(ValueType(1.0) / cutoffDistance) { } // cutoff has to be < 0.0 template void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const { const ValueType zero = zeroVal(); for (typename LeafNodeType::ValueAllIter iter = leaf.beginValueAll(); iter; ++iter) { ValueType& value = const_cast(iter.getValue()); if (value > zero) { value = zero; iter.setValueOff(); } else { value = std::min(ValueType(1.0), value * mWeight); iter.setValueOn(value > zero); } } } private: ValueType mWeight; }; // class FogVolumeOp template class InteriorMaskOp { public: InteriorMaskOp(const TreeType& tree, typename TreeType::ValueType iso) : mTree(tree) , mIso(iso) { } template void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const { const Coord origin = leaf.origin(); const typename TreeType::LeafNodeType* refLeafPt = mTree.probeConstLeaf(origin); if (refLeafPt != NULL) { const typename TreeType::LeafNodeType& refLeaf = *refLeafPt; typename LeafNodeType::ValueAllIter iter = leaf.beginValueAll(); for (; iter; ++iter) { if (refLeaf.getValue(iter.pos()) < mIso) { iter.setValueOn(); } else { iter.setValueOff(); } } } } private: const TreeType& mTree; typename TreeType::ValueType mIso; }; // class InteriorMaskOp } // namespace internal //////////////////////////////////////// template MinMaxVoxel::MinMaxVoxel(LeafArray& leafs) : mLeafArray(leafs) , mMin(std::numeric_limits::max()) , mMax(-mMin) { } template inline MinMaxVoxel::MinMaxVoxel(const MinMaxVoxel& rhs, tbb::split) : mLeafArray(rhs.mLeafArray) , mMin(rhs.mMin) , mMax(rhs.mMax) { } template void MinMaxVoxel::runParallel() { tbb::parallel_reduce(mLeafArray.getRange(), *this); } template void MinMaxVoxel::runSerial() { (*this)(mLeafArray.getRange()); } template inline void MinMaxVoxel::operator()(const tbb::blocked_range& range) { typename TreeType::LeafNodeType::ValueOnCIter iter; for (size_t n = range.begin(); n < range.end(); ++n) { iter = mLeafArray.leaf(n).cbeginValueOn(); for (; iter; ++iter) { const ValueType value = iter.getValue(); mMin = std::min(mMin, value); mMax = std::max(mMax, value); } } } template inline void MinMaxVoxel::join(const MinMaxVoxel& rhs) { mMin = std::min(mMin, rhs.mMin); mMax = std::max(mMax, rhs.mMax); } //////////////////////////////////////// template inline void sdfToFogVolume(GridType& grid, typename GridType::ValueType cutoffDistance) { typedef typename GridType::TreeType TreeType; typedef typename GridType::ValueType ValueType; cutoffDistance = -std::abs(cutoffDistance); TreeType& tree = const_cast(grid.tree()); { // Transform all voxels (parallel, over leaf nodes) tree::LeafManager leafs(tree); MinMaxVoxel minmax(leafs); minmax.runParallel(); // Clamp to the interior band width. if (minmax.minVoxel() > cutoffDistance) { cutoffDistance = minmax.minVoxel(); } leafs.foreach(internal::FogVolumeOp(cutoffDistance)); } // Transform all tile values (serial, but the iteration // is constrained from descending into leaf nodes) const ValueType zero = zeroVal(); typename TreeType::ValueAllIter iter(tree); iter.setMaxDepth(TreeType::ValueAllIter::LEAF_DEPTH - 1); for ( ; iter; ++iter) { ValueType& value = const_cast(iter.getValue()); if (value > zero) { value = zero; iter.setValueOff(); } else { value = ValueType(1.0); iter.setActiveState(true); } } // Update the tree background value. typename TreeType::Ptr newTree(new TreeType(/*background=*/zero)); newTree->merge(tree); // This is faster than calling Tree::setBackground, since we only need // to update the value that is returned for coordinates that don't fall // inside an allocated node. All inactive tiles and voxels have already // been updated in the previous step so the Tree::setBackground method // will in this case do a redundant traversal of the tree to update the // inactive values once more. //newTree->pruneInactive(); grid.setTree(newTree); grid.setGridClass(GRID_FOG_VOLUME); } //////////////////////////////////////// template inline typename Grid::Type>::Ptr sdfInteriorMask(const GridType& grid, typename GridType::ValueType iso) { typedef typename GridType::TreeType::template ValueConverter::Type BoolTreeType; typedef Grid BoolGridType; typename BoolGridType::Ptr maskGrid(BoolGridType::create(false)); maskGrid->setTransform(grid.transform().copy()); BoolTreeType& maskTree = maskGrid->tree(); maskTree.topologyUnion(grid.tree()); { // Evaluate voxels (parallel, over leaf nodes) tree::LeafManager leafs(maskTree); leafs.foreach(internal::InteriorMaskOp(grid.tree(), iso)); } // Evaluate tile values (serial, but the iteration // is constrained from descending into leaf nodes) tree::ValueAccessor acc(grid.tree()); typename BoolTreeType::ValueAllIter iter(maskTree); iter.setMaxDepth(BoolTreeType::ValueAllIter::LEAF_DEPTH - 1); for ( ; iter; ++iter) { iter.setActiveState(acc.getValue(iter.getCoord()) < iso); } maskTree.pruneInactive(); return maskGrid; } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVELSETUTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/VolumeToMesh.h0000644000000000000000000042717212252453157015134 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_TOOLS_VOLUME_TO_MESH_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_VOLUME_TO_MESH_HAS_BEEN_INCLUDED #include #include // for COORD_OFFSETS #include // for ISGradient #include // for dilateVoxels() #include #include #include #include #include #include #include #include // for auto_pointer ////////// namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { //////////////////////////////////////// // Wrapper functions for the VolumeToMesh converter /// @brief Uniformly mesh any scalar grid that has a continuous isosurface. /// /// @param grid a scalar grid to mesh /// @param points output list of world space points /// @param quads output quad index list /// @param isovalue determines which isosurface to mesh template void volumeToMesh( const GridType& grid, std::vector& points, std::vector& quads, double isovalue = 0.0); /// @brief Adaptively mesh any scalar grid that has a continuous isosurface. /// /// @param grid a scalar grid to mesh /// @param points output list of world space points /// @param triangles output quad index list /// @param quads output quad index list /// @param isovalue determines which isosurface to mesh /// @param adaptivity surface adaptivity threshold [0 to 1] template void volumeToMesh( const GridType& grid, std::vector& points, std::vector& triangles, std::vector& quads, double isovalue = 0.0, double adaptivity = 0.0); //////////////////////////////////////// /// @brief Polygon flags, used for reference based meshing. enum { POLYFLAG_EXTERIOR = 0x1, POLYFLAG_FRACTURE_SEAM = 0x2, POLYFLAG_SUBDIVIDED = 0x4}; /// @brief Collection of quads and triangles class PolygonPool { public: inline PolygonPool(); inline PolygonPool(const size_t numQuads, const size_t numTriangles); inline void copy(const PolygonPool& rhs); inline void resetQuads(size_t size); inline void clearQuads(); inline void resetTriangles(size_t size); inline void clearTriangles(); // polygon accessor methods const size_t& numQuads() const { return mNumQuads; } openvdb::Vec4I& quad(size_t n) { return mQuads[n]; } const openvdb::Vec4I& quad(size_t n) const { return mQuads[n]; } const size_t& numTriangles() const { return mNumTriangles; } openvdb::Vec3I& triangle(size_t n) { return mTriangles[n]; } const openvdb::Vec3I& triangle(size_t n) const { return mTriangles[n]; } // polygon flags accessor methods char& quadFlags(size_t n) { return mQuadFlags[n]; } const char& quadFlags(size_t n) const { return mQuadFlags[n]; } char& triangleFlags(size_t n) { return mTriangleFlags[n]; } const char& triangleFlags(size_t n) const { return mTriangleFlags[n]; } // reduce the polygon containers, n has to // be smaller than the current container size. inline bool trimQuads(const size_t n, bool reallocate = false); inline bool trimTrinagles(const size_t n, bool reallocate = false); private: // disallow copy by assignment void operator=(const PolygonPool&) {} size_t mNumQuads, mNumTriangles; boost::scoped_array mQuads; boost::scoped_array mTriangles; boost::scoped_array mQuadFlags, mTriangleFlags; }; /// @{ /// @brief Point and primitive list types. typedef boost::scoped_array PointList; typedef boost::scoped_array PolygonPoolList; /// @} //////////////////////////////////////// /// @brief Mesh any scalar grid that has a continuous isosurface. class VolumeToMesh { public: /// @param isovalue Determines which isosurface to mesh. /// @param adaptivity Adaptivity threshold [0 to 1] VolumeToMesh(double isovalue = 0, double adaptivity = 0); ////////// // Mesh data accessors const size_t& pointListSize() const; PointList& pointList(); const size_t& polygonPoolListSize() const; PolygonPoolList& polygonPoolList(); const PolygonPoolList& polygonPoolList() const; std::vector& pointFlags(); const std::vector& pointFlags() const; ////////// /// @brief Main call /// @note Call with scalar typed grid. template void operator()(const GridT&); ////////// /// @brief When surfacing fractured SDF fragments, the original unfractured /// SDF grid can be used to eliminate seam lines and tag polygons that are /// coincident with the reference surface with the @c POLYFLAG_EXTERIOR /// flag and polygons that are in proximity to the seam lines with the /// @c POLYFLAG_FRACTURE_SEAM flag. (The performance cost for using this /// reference based scheme compared to the regular meshing scheme is /// approximately 15% for the first fragment and neglect-able for /// subsequent fragments.) /// /// @note Attributes from the original asset such as uv coordinates, normals etc. /// are typically transfered to polygons that are marked with the /// @c POLYFLAG_EXTERIOR flag. Polygons that are not marked with this flag /// are interior to reference surface and might need projected UV coordinates /// or a different material. Polygons marked as @c POLYFLAG_FRACTURE_SEAM can /// be used to drive secondary elements such as debris and dust in a FX pipeline. /// /// @param grid reference surface grid of @c GridT type. /// @param secAdaptivity Secondary adaptivity threshold [0 to 1]. Used in regions /// that do not exist in the reference grid. (Parts of the /// fragment surface that are not coincident with the /// reference surface.) void setRefGrid(const GridBase::ConstPtr& grid, double secAdaptivity = 0); /// @param mask A boolean grid whose active topology defines the region to mesh. /// @param invertMask Toggle to mesh the complement of the mask. /// @note The mask's tree configuration has to match @c GridT's tree configuration. void setSurfaceMask(const GridBase::ConstPtr& mask, bool invertMask = false); /// @param grid A scalar grid used as an spatial multiplier for the adaptivity threshold. /// @note The grid's tree configuration has to match @c GridT's tree configuration. void setSpatialAdaptivity(const GridBase::ConstPtr& grid); /// @param tree A boolean tree whose active topology defines the adaptivity mask. /// @note The tree configuration has to match @c GridT's tree configuration. void setAdaptivityMask(const TreeBase::ConstPtr& tree); /// @brief Subdivide volume and mesh into disjoint parts /// @param partitions Number of partitions. /// @param activePart Specific partition to mesh, 0 to @c partitions - 1. void partition(unsigned partitions = 1, unsigned activePart = 0); private: PointList mPoints; PolygonPoolList mPolygons; size_t mPointListSize, mSeamPointListSize, mPolygonPoolListSize; double mIsovalue, mPrimAdaptivity, mSecAdaptivity; GridBase::ConstPtr mRefGrid, mSurfaceMaskGrid, mAdaptivityGrid; TreeBase::ConstPtr mAdaptivityMaskTree; TreeBase::Ptr mRefSignTree, mRefIdxTree; bool mInvertSurfaceMask; unsigned mPartitions, mActivePart; boost::scoped_array mQuantizedSeamPoints; std::vector mPointFlags; }; //////////////////////////////////////// /// @brief Given a set of tangent elements, @c points with corresponding @c normals, /// this method returns the intersection point of all tangent elements. /// /// @note Used to extract surfaces with sharp edges and corners from volume data, /// see the following paper for details: "Feature Sensitive Surface /// Extraction from Volume Data, Kobbelt et al. 2001". inline Vec3d findFeaturePoint( const std::vector& points, const std::vector& normals) { typedef math::Mat3d Mat3d; Vec3d avgPos(0.0); if (points.empty()) return avgPos; for (size_t n = 0, N = points.size(); n < N; ++n) { avgPos += points[n]; } avgPos /= double(points.size()); // Unique components of the 3x3 A^TA matrix, where A is // the matrix of normals. double m00=0,m01=0,m02=0, m11=0,m12=0, m22=0; // The rhs vector, A^Tb, where b = n dot p Vec3d rhs(0.0); for (size_t n = 0, N = points.size(); n < N; ++n) { const Vec3d& n_ref = normals[n]; // A^TA m00 += n_ref[0] * n_ref[0]; // diagonal m11 += n_ref[1] * n_ref[1]; m22 += n_ref[2] * n_ref[2]; m01 += n_ref[0] * n_ref[1]; // Upper-tri m02 += n_ref[0] * n_ref[2]; m12 += n_ref[1] * n_ref[2]; // A^Tb (centered around the origin) rhs += n_ref * n_ref.dot(points[n] - avgPos); } Mat3d A(m00,m01,m02, m01,m11,m12, m02,m12,m22); /* // Inverse const double det = A.det(); if (det > 0.01) { Mat3d A_inv = A.adjoint(); A_inv *= (1.0 / det); return avgPos + A_inv * rhs; } */ // Compute the pseudo inverse math::Mat3d eigenVectors; Vec3d eigenValues; diagonalizeSymmetricMatrix(A, eigenVectors, eigenValues, 300); Mat3d D = Mat3d::identity(); double tolerance = std::max(std::abs(eigenValues[0]), std::abs(eigenValues[1])); tolerance = std::max(tolerance, std::abs(eigenValues[2])); tolerance *= 0.01; int clamped = 0; for (int i = 0; i < 3; ++i ) { if (std::abs(eigenValues[i]) < tolerance) { D[i][i] = 0.0; ++clamped; } else { D[i][i] = 1.0 / eigenValues[i]; } } // Assemble the pseudo inverse and calc. the intersection point if (clamped < 3) { Mat3d pseudoInv = eigenVectors * D * eigenVectors.transpose(); return avgPos + pseudoInv * rhs; } return avgPos; } //////////////////////////////////////// // Internal utility methods namespace internal { /// @brief Bit-flags used to classify cells. enum { SIGNS = 0xFF, EDGES = 0xE00, INSIDE = 0x100, XEDGE = 0x200, YEDGE = 0x400, ZEDGE = 0x800, SEAM = 0x1000}; /// @brief Used to quickly determine if a given cell is adaptable. const bool sAdaptable[256] = { 1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,1,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1, 1,0,1,1,0,0,1,1,0,0,0,1,0,0,1,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,0,1, 1,0,0,0,1,0,1,1,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,0,1,1,1,0,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,0,1,0,0,0,0,1,1,0,1,1,1,0,1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,1,0,0,0,1, 1,0,0,0,1,0,1,0,1,1,0,0,1,1,1,1,1,1,0,0,1,0,0,0,1,1,0,0,1,1,0,1, 1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,1,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1}; /// @brief Contains the ambiguous face index for certain cell configuration. const unsigned char sAmbiguousFace[256] = { 0,0,0,0,0,5,0,0,0,0,5,0,0,0,0,0,0,0,1,0,0,5,1,0,4,0,0,0,4,0,0,0, 0,1,0,0,2,0,0,0,0,1,5,0,2,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0, 0,0,2,2,0,5,0,0,3,3,0,0,0,0,0,0,6,6,0,0,6,0,0,0,0,0,0,0,0,0,0,0, 0,1,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,4,0,4,3,0,3,0,0,0,5,0,0,0,0,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0, 6,0,6,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; /// @brief Lookup table for different cell sign configurations. The first entry specifies /// the total number of points that need to be generated inside a cell and the /// remaining 12 entries indicate different edge groups. const unsigned char sEdgeGroupTable[256][13] = { {0,0,0,0,0,0,0,0,0,0,0,0,0},{1,1,0,0,1,0,0,0,0,1,0,0,0},{1,1,1,0,0,0,0,0,0,0,1,0,0}, {1,0,1,0,1,0,0,0,0,1,1,0,0},{1,0,1,1,0,0,0,0,0,0,0,1,0},{1,1,1,1,1,0,0,0,0,1,0,1,0}, {1,1,0,1,0,0,0,0,0,0,1,1,0},{1,0,0,1,1,0,0,0,0,1,1,1,0},{1,0,0,1,1,0,0,0,0,0,0,0,1}, {1,1,0,1,0,0,0,0,0,1,0,0,1},{1,1,1,1,1,0,0,0,0,0,1,0,1},{1,0,1,1,0,0,0,0,0,1,1,0,1}, {1,0,1,0,1,0,0,0,0,0,0,1,1},{1,1,1,0,0,0,0,0,0,1,0,1,1},{1,1,0,0,1,0,0,0,0,0,1,1,1}, {1,0,0,0,0,0,0,0,0,1,1,1,1},{1,0,0,0,0,1,0,0,1,1,0,0,0},{1,1,0,0,1,1,0,0,1,0,0,0,0}, {1,1,1,0,0,1,0,0,1,1,1,0,0},{1,0,1,0,1,1,0,0,1,0,1,0,0},{2,0,1,1,0,2,0,0,2,2,0,1,0}, {1,1,1,1,1,1,0,0,1,0,0,1,0},{1,1,0,1,0,1,0,0,1,1,1,1,0},{1,0,0,1,1,1,0,0,1,0,1,1,0}, {1,0,0,1,1,1,0,0,1,1,0,0,1},{1,1,0,1,0,1,0,0,1,0,0,0,1},{2,2,1,1,2,1,0,0,1,2,1,0,1}, {1,0,1,1,0,1,0,0,1,0,1,0,1},{1,0,1,0,1,1,0,0,1,1,0,1,1},{1,1,1,0,0,1,0,0,1,0,0,1,1}, {2,1,0,0,1,2,0,0,2,1,2,2,2},{1,0,0,0,0,1,0,0,1,0,1,1,1},{1,0,0,0,0,1,1,0,0,0,1,0,0}, {1,1,0,0,1,1,1,0,0,1,1,0,0},{1,1,1,0,0,1,1,0,0,0,0,0,0},{1,0,1,0,1,1,1,0,0,1,0,0,0}, {1,0,1,1,0,1,1,0,0,0,1,1,0},{2,2,2,1,1,1,1,0,0,1,2,1,0},{1,1,0,1,0,1,1,0,0,0,0,1,0}, {1,0,0,1,1,1,1,0,0,1,0,1,0},{2,0,0,2,2,1,1,0,0,0,1,0,2},{1,1,0,1,0,1,1,0,0,1,1,0,1}, {1,1,1,1,1,1,1,0,0,0,0,0,1},{1,0,1,1,0,1,1,0,0,1,0,0,1},{1,0,1,0,1,1,1,0,0,0,1,1,1}, {2,1,1,0,0,2,2,0,0,2,1,2,2},{1,1,0,0,1,1,1,0,0,0,0,1,1},{1,0,0,0,0,1,1,0,0,1,0,1,1}, {1,0,0,0,0,0,1,0,1,1,1,0,0},{1,1,0,0,1,0,1,0,1,0,1,0,0},{1,1,1,0,0,0,1,0,1,1,0,0,0}, {1,0,1,0,1,0,1,0,1,0,0,0,0},{1,0,1,1,0,0,1,0,1,1,1,1,0},{2,1,1,2,2,0,2,0,2,0,1,2,0}, {1,1,0,1,0,0,1,0,1,1,0,1,0},{1,0,0,1,1,0,1,0,1,0,0,1,0},{1,0,0,1,1,0,1,0,1,1,1,0,1}, {1,1,0,1,0,0,1,0,1,0,1,0,1},{2,1,2,2,1,0,2,0,2,1,0,0,2},{1,0,1,1,0,0,1,0,1,0,0,0,1}, {2,0,2,0,2,0,1,0,1,2,2,1,1},{2,2,2,0,0,0,1,0,1,0,2,1,1},{2,2,0,0,2,0,1,0,1,2,0,1,1}, {1,0,0,0,0,0,1,0,1,0,0,1,1},{1,0,0,0,0,0,1,1,0,0,0,1,0},{2,1,0,0,1,0,2,2,0,1,0,2,0}, {1,1,1,0,0,0,1,1,0,0,1,1,0},{1,0,1,0,1,0,1,1,0,1,1,1,0},{1,0,1,1,0,0,1,1,0,0,0,0,0}, {1,1,1,1,1,0,1,1,0,1,0,0,0},{1,1,0,1,0,0,1,1,0,0,1,0,0},{1,0,0,1,1,0,1,1,0,1,1,0,0}, {1,0,0,1,1,0,1,1,0,0,0,1,1},{1,1,0,1,0,0,1,1,0,1,0,1,1},{2,1,2,2,1,0,1,1,0,0,1,2,1}, {2,0,1,1,0,0,2,2,0,2,2,1,2},{1,0,1,0,1,0,1,1,0,0,0,0,1},{1,1,1,0,0,0,1,1,0,1,0,0,1}, {1,1,0,0,1,0,1,1,0,0,1,0,1},{1,0,0,0,0,0,1,1,0,1,1,0,1},{1,0,0,0,0,1,1,1,1,1,0,1,0}, {1,1,0,0,1,1,1,1,1,0,0,1,0},{2,1,1,0,0,2,2,1,1,1,2,1,0},{2,0,2,0,2,1,1,2,2,0,1,2,0}, {1,0,1,1,0,1,1,1,1,1,0,0,0},{2,2,2,1,1,2,2,1,1,0,0,0,0},{2,2,0,2,0,1,1,2,2,2,1,0,0}, {2,0,0,1,1,2,2,1,1,0,2,0,0},{2,0,0,1,1,1,1,2,2,1,0,1,2},{2,2,0,2,0,2,2,1,1,0,0,2,1}, {4,3,2,2,3,4,4,1,1,3,4,2,1},{3,0,2,2,0,1,1,3,3,0,1,2,3},{2,0,2,0,2,2,2,1,1,2,0,0,1}, {2,1,1,0,0,1,1,2,2,0,0,0,2},{3,1,0,0,1,2,2,3,3,1,2,0,3},{2,0,0,0,0,1,1,2,2,0,1,0,2}, {1,0,0,0,0,1,0,1,0,0,1,1,0},{1,1,0,0,1,1,0,1,0,1,1,1,0},{1,1,1,0,0,1,0,1,0,0,0,1,0}, {1,0,1,0,1,1,0,1,0,1,0,1,0},{1,0,1,1,0,1,0,1,0,0,1,0,0},{2,1,1,2,2,2,0,2,0,2,1,0,0}, {1,1,0,1,0,1,0,1,0,0,0,0,0},{1,0,0,1,1,1,0,1,0,1,0,0,0},{1,0,0,1,1,1,0,1,0,0,1,1,1}, {2,2,0,2,0,1,0,1,0,1,2,2,1},{2,2,1,1,2,2,0,2,0,0,0,1,2},{2,0,2,2,0,1,0,1,0,1,0,2,1}, {1,0,1,0,1,1,0,1,0,0,1,0,1},{2,2,2,0,0,1,0,1,0,1,2,0,1},{1,1,0,0,1,1,0,1,0,0,0,0,1}, {1,0,0,0,0,1,0,1,0,1,0,0,1},{1,0,0,0,0,0,0,1,1,1,1,1,0},{1,1,0,0,1,0,0,1,1,0,1,1,0}, {1,1,1,0,0,0,0,1,1,1,0,1,0},{1,0,1,0,1,0,0,1,1,0,0,1,0},{1,0,1,1,0,0,0,1,1,1,1,0,0}, {2,2,2,1,1,0,0,1,1,0,2,0,0},{1,1,0,1,0,0,0,1,1,1,0,0,0},{1,0,0,1,1,0,0,1,1,0,0,0,0}, {2,0,0,2,2,0,0,1,1,2,2,2,1},{2,1,0,1,0,0,0,2,2,0,1,1,2},{3,2,1,1,2,0,0,3,3,2,0,1,3}, {2,0,1,1,0,0,0,2,2,0,0,1,2},{2,0,1,0,1,0,0,2,2,1,1,0,2},{2,1,1,0,0,0,0,2,2,0,1,0,2}, {2,1,0,0,1,0,0,2,2,1,0,0,2},{1,0,0,0,0,0,0,1,1,0,0,0,1},{1,0,0,0,0,0,0,1,1,0,0,0,1}, {1,1,0,0,1,0,0,1,1,1,0,0,1},{2,1,1,0,0,0,0,2,2,0,1,0,2},{1,0,1,0,1,0,0,1,1,1,1,0,1}, {1,0,1,1,0,0,0,1,1,0,0,1,1},{2,1,1,2,2,0,0,1,1,1,0,1,2},{1,1,0,1,0,0,0,1,1,0,1,1,1}, {2,0,0,1,1,0,0,2,2,2,2,2,1},{1,0,0,1,1,0,0,1,1,0,0,0,0},{1,1,0,1,0,0,0,1,1,1,0,0,0}, {1,1,1,1,1,0,0,1,1,0,1,0,0},{1,0,1,1,0,0,0,1,1,1,1,0,0},{1,0,1,0,1,0,0,1,1,0,0,1,0}, {1,1,1,0,0,0,0,1,1,1,0,1,0},{1,1,0,0,1,0,0,1,1,0,1,1,0},{1,0,0,0,0,0,0,1,1,1,1,1,0}, {1,0,0,0,0,1,0,1,0,1,0,0,1},{1,1,0,0,1,1,0,1,0,0,0,0,1},{1,1,1,0,0,1,0,1,0,1,1,0,1}, {1,0,1,0,1,1,0,1,0,0,1,0,1},{1,0,1,1,0,1,0,1,0,1,0,1,1},{2,2,2,1,1,2,0,2,0,0,0,2,1}, {2,1,0,1,0,2,0,2,0,1,2,2,1},{2,0,0,2,2,1,0,1,0,0,1,1,2},{1,0,0,1,1,1,0,1,0,1,0,0,0}, {1,1,0,1,0,1,0,1,0,0,0,0,0},{2,1,2,2,1,2,0,2,0,1,2,0,0},{1,0,1,1,0,1,0,1,0,0,1,0,0}, {1,0,1,0,1,1,0,1,0,1,0,1,0},{1,1,1,0,0,1,0,1,0,0,0,1,0},{2,2,0,0,2,1,0,1,0,2,1,1,0}, {1,0,0,0,0,1,0,1,0,0,1,1,0},{1,0,0,0,0,1,1,1,1,0,1,0,1},{2,1,0,0,1,2,1,1,2,2,1,0,1}, {1,1,1,0,0,1,1,1,1,0,0,0,1},{2,0,2,0,2,1,2,2,1,1,0,0,2},{2,0,1,1,0,1,2,2,1,0,1,2,1}, {4,1,1,3,3,2,4,4,2,2,1,4,3},{2,2,0,2,0,2,1,1,2,0,0,1,2},{3,0,0,1,1,2,3,3,2,2,0,3,1}, {1,0,0,1,1,1,1,1,1,0,1,0,0},{2,2,0,2,0,1,2,2,1,1,2,0,0},{2,2,1,1,2,2,1,1,2,0,0,0,0}, {2,0,1,1,0,2,1,1,2,2,0,0,0},{2,0,2,0,2,2,1,1,2,0,2,1,0},{3,1,1,0,0,3,2,2,3,3,1,2,0}, {2,1,0,0,1,1,2,2,1,0,0,2,0},{2,0,0,0,0,2,1,1,2,2,0,1,0},{1,0,0,0,0,0,1,1,0,1,1,0,1}, {1,1,0,0,1,0,1,1,0,0,1,0,1},{1,1,1,0,0,0,1,1,0,1,0,0,1},{1,0,1,0,1,0,1,1,0,0,0,0,1}, {2,0,2,2,0,0,1,1,0,2,2,1,2},{3,1,1,2,2,0,3,3,0,0,1,3,2},{2,1,0,1,0,0,2,2,0,1,0,2,1}, {2,0,0,1,1,0,2,2,0,0,0,2,1},{1,0,0,1,1,0,1,1,0,1,1,0,0},{1,1,0,1,0,0,1,1,0,0,1,0,0}, {2,2,1,1,2,0,1,1,0,2,0,0,0},{1,0,1,1,0,0,1,1,0,0,0,0,0},{2,0,1,0,1,0,2,2,0,1,1,2,0}, {2,1,1,0,0,0,2,2,0,0,1,2,0},{2,1,0,0,1,0,2,2,0,1,0,2,0},{1,0,0,0,0,0,1,1,0,0,0,1,0}, {1,0,0,0,0,0,1,0,1,0,0,1,1},{1,1,0,0,1,0,1,0,1,1,0,1,1},{1,1,1,0,0,0,1,0,1,0,1,1,1}, {2,0,2,0,2,0,1,0,1,1,1,2,2},{1,0,1,1,0,0,1,0,1,0,0,0,1},{2,2,2,1,1,0,2,0,2,2,0,0,1}, {1,1,0,1,0,0,1,0,1,0,1,0,1},{2,0,0,2,2,0,1,0,1,1,1,0,2},{1,0,0,1,1,0,1,0,1,0,0,1,0}, {1,1,0,1,0,0,1,0,1,1,0,1,0},{2,2,1,1,2,0,2,0,2,0,2,1,0},{2,0,2,2,0,0,1,0,1,1,1,2,0}, {1,0,1,0,1,0,1,0,1,0,0,0,0},{1,1,1,0,0,0,1,0,1,1,0,0,0},{1,1,0,0,1,0,1,0,1,0,1,0,0}, {1,0,0,0,0,0,1,0,1,1,1,0,0},{1,0,0,0,0,1,1,0,0,1,0,1,1},{1,1,0,0,1,1,1,0,0,0,0,1,1}, {2,2,2,0,0,1,1,0,0,2,1,2,2},{2,0,1,0,1,2,2,0,0,0,2,1,1},{1,0,1,1,0,1,1,0,0,1,0,0,1}, {2,1,1,2,2,1,1,0,0,0,0,0,2},{2,1,0,1,0,2,2,0,0,1,2,0,1},{2,0,0,2,2,1,1,0,0,0,1,0,2}, {1,0,0,1,1,1,1,0,0,1,0,1,0},{1,1,0,1,0,1,1,0,0,0,0,1,0},{3,1,2,2,1,3,3,0,0,1,3,2,0}, {2,0,1,1,0,2,2,0,0,0,2,1,0},{1,0,1,0,1,1,1,0,0,1,0,0,0},{1,1,1,0,0,1,1,0,0,0,0,0,0}, {2,2,0,0,2,1,1,0,0,2,1,0,0},{1,0,0,0,0,1,1,0,0,0,1,0,0},{1,0,0,0,0,1,0,0,1,0,1,1,1}, {2,2,0,0,2,1,0,0,1,1,2,2,2},{1,1,1,0,0,1,0,0,1,0,0,1,1},{2,0,1,0,1,2,0,0,2,2,0,1,1}, {1,0,1,1,0,1,0,0,1,0,1,0,1},{3,1,1,3,3,2,0,0,2,2,1,0,3},{1,1,0,1,0,1,0,0,1,0,0,0,1}, {2,0,0,2,2,1,0,0,1,1,0,0,2},{1,0,0,1,1,1,0,0,1,0,1,1,0},{2,1,0,1,0,2,0,0,2,2,1,1,0}, {2,1,2,2,1,1,0,0,1,0,0,2,0},{2,0,1,1,0,2,0,0,2,2,0,1,0},{1,0,1,0,1,1,0,0,1,0,1,0,0}, {2,1,1,0,0,2,0,0,2,2,1,0,0},{1,1,0,0,1,1,0,0,1,0,0,0,0},{1,0,0,0,0,1,0,0,1,1,0,0,0}, {1,0,0,0,0,0,0,0,0,1,1,1,1},{1,1,0,0,1,0,0,0,0,0,1,1,1},{1,1,1,0,0,0,0,0,0,1,0,1,1}, {1,0,1,0,1,0,0,0,0,0,0,1,1},{1,0,1,1,0,0,0,0,0,1,1,0,1},{2,1,1,2,2,0,0,0,0,0,1,0,2}, {1,1,0,1,0,0,0,0,0,1,0,0,1},{1,0,0,1,1,0,0,0,0,0,0,0,1},{1,0,0,1,1,0,0,0,0,1,1,1,0}, {1,1,0,1,0,0,0,0,0,0,1,1,0},{2,1,2,2,1,0,0,0,0,1,0,2,0},{1,0,1,1,0,0,0,0,0,0,0,1,0}, {1,0,1,0,1,0,0,0,0,1,1,0,0},{1,1,1,0,0,0,0,0,0,0,1,0,0},{1,1,0,0,1,0,0,0,0,1,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0}}; //////////////////////////////////////// inline bool isPlanarQuad( const Vec3d& p0, const Vec3d& p1, const Vec3d& p2, const Vec3d& p3, double epsilon = 0.001) { // compute representative plane Vec3d normal = (p2-p0).cross(p1-p3); normal.normalize(); const Vec3d centroid = (p0 + p1 + p2 + p3); const double d = centroid.dot(normal) * 0.25; // test vertice distance to plane double absDist = std::abs(p0.dot(normal) - d); if (absDist > epsilon) return false; absDist = std::abs(p1.dot(normal) - d); if (absDist > epsilon) return false; absDist = std::abs(p2.dot(normal) - d); if (absDist > epsilon) return false; absDist = std::abs(p3.dot(normal) - d); if (absDist > epsilon) return false; return true; } //////////////////////////////////////// /// @{ /// @brief Utility methods for point quantization. enum { MASK_FIRST_10_BITS = 0x000003FF, MASK_DIRTY_BIT = 0x80000000, MASK_INVALID_BIT = 0x40000000 }; inline uint32_t packPoint(const Vec3d& v) { uint32_t data = 0; // values are expected to be in the [0.0 to 1.0] range. assert(!(v.x() > 1.0) && !(v.y() > 1.0) && !(v.z() > 1.0)); assert(!(v.x() < 0.0) && !(v.y() < 0.0) && !(v.z() < 0.0)); data |= (uint32_t(v.x() * 1023.0) & MASK_FIRST_10_BITS) << 20; data |= (uint32_t(v.y() * 1023.0) & MASK_FIRST_10_BITS) << 10; data |= (uint32_t(v.z() * 1023.0) & MASK_FIRST_10_BITS); return data; } inline Vec3d unpackPoint(uint32_t data) { Vec3d v; v.z() = double(data & MASK_FIRST_10_BITS) * 0.0009775171; data = data >> 10; v.y() = double(data & MASK_FIRST_10_BITS) * 0.0009775171; data = data >> 10; v.x() = double(data & MASK_FIRST_10_BITS) * 0.0009775171; return v; } /// @} //////////////////////////////////////// /// @brief General method that computes the cell-sign configuration at the given /// @c ijk coordinate. template inline unsigned char evalCellSigns(const AccessorT& accessor, const Coord& ijk, typename AccessorT::ValueType iso) { unsigned signs = 0; Coord coord = ijk; // i, j, k if (accessor.getValue(coord) < iso) signs |= 1u; coord[0] += 1; // i+1, j, k if (accessor.getValue(coord) < iso) signs |= 2u; coord[2] += 1; // i+1, j, k+1 if (accessor.getValue(coord) < iso) signs |= 4u; coord[0] = ijk[0]; // i, j, k+1 if (accessor.getValue(coord) < iso) signs |= 8u; coord[1] += 1; coord[2] = ijk[2]; // i, j+1, k if (accessor.getValue(coord) < iso) signs |= 16u; coord[0] += 1; // i+1, j+1, k if (accessor.getValue(coord) < iso) signs |= 32u; coord[2] += 1; // i+1, j+1, k+1 if (accessor.getValue(coord) < iso) signs |= 64u; coord[0] = ijk[0]; // i, j+1, k+1 if (accessor.getValue(coord) < iso) signs |= 128u; return signs; } /// @brief Leaf node optimized method that computes the cell-sign configuration /// at the given local @c offset template inline unsigned char evalCellSigns(const LeafT& leaf, const Index offset, typename LeafT::ValueType iso) { unsigned char signs = 0; // i, j, k if (leaf.getValue(offset) < iso) signs |= 1u; // i, j, k+1 if (leaf.getValue(offset + 1) < iso) signs |= 8u; // i, j+1, k if (leaf.getValue(offset + LeafT::DIM) < iso) signs |= 16u; // i, j+1, k+1 if (leaf.getValue(offset + LeafT::DIM + 1) < iso) signs |= 128u; // i+1, j, k if (leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) ) < iso) signs |= 2u; // i+1, j, k+1 if (leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + 1) < iso) signs |= 4u; // i+1, j+1, k if (leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + LeafT::DIM) < iso) signs |= 32u; // i+1, j+1, k+1 if (leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + LeafT::DIM + 1) < iso) signs |= 64u; return signs; } /// @brief Used to correct topological ambiguities related to two adjacent cells /// that share an ambiguous face. template inline void correctCellSigns(unsigned char& signs, unsigned char face, const AccessorT& acc, Coord ijk, typename AccessorT::ValueType iso) { if (face == 1) { ijk[2] -= 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 3) signs = ~signs; } else if (face == 3) { ijk[2] += 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 1) signs = ~signs; } else if (face == 2) { ijk[0] += 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 4) signs = ~signs; } else if (face == 4) { ijk[0] -= 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 2) signs = ~signs; } else if (face == 5) { ijk[1] -= 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 6) signs = ~signs; } else if (face == 6) { ijk[1] += 1; if (sAmbiguousFace[evalCellSigns(acc, ijk, iso)] == 5) signs = ~signs; } } template inline bool isNonManifold(const AccessorT& accessor, const Coord& ijk, typename AccessorT::ValueType isovalue, const int dim) { int hDim = dim >> 1; bool m, p[8]; // Corner signs Coord coord = ijk; // i, j, k p[0] = accessor.getValue(coord) < isovalue; coord[0] += dim; // i+dim, j, k p[1] = accessor.getValue(coord) < isovalue; coord[2] += dim; // i+dim, j, k+dim p[2] = accessor.getValue(coord) < isovalue; coord[0] = ijk[0]; // i, j, k+dim p[3] = accessor.getValue(coord) < isovalue; coord[1] += dim; coord[2] = ijk[2]; // i, j+dim, k p[4] = accessor.getValue(coord) < isovalue; coord[0] += dim; // i+dim, j+dim, k p[5] = accessor.getValue(coord) < isovalue; coord[2] += dim; // i+dim, j+dim, k+dim p[6] = accessor.getValue(coord) < isovalue; coord[0] = ijk[0]; // i, j+dim, k+dim p[7] = accessor.getValue(coord) < isovalue; // Check if the corner sign configuration is ambiguous unsigned signs = 0; if (p[0]) signs |= 1u; if (p[1]) signs |= 2u; if (p[2]) signs |= 4u; if (p[3]) signs |= 8u; if (p[4]) signs |= 16u; if (p[5]) signs |= 32u; if (p[6]) signs |= 64u; if (p[7]) signs |= 128u; if (!sAdaptable[signs]) return true; // Manifold check // Evaluate edges int i = ijk[0], ip = ijk[0] + hDim, ipp = ijk[0] + dim; int j = ijk[1], jp = ijk[1] + hDim, jpp = ijk[1] + dim; int k = ijk[2], kp = ijk[2] + hDim, kpp = ijk[2] + dim; // edge 1 coord.reset(ip, j, k); m = accessor.getValue(coord) < isovalue; if (p[0] != m && p[1] != m) return true; // edge 2 coord.reset(ipp, j, kp); m = accessor.getValue(coord) < isovalue; if (p[1] != m && p[2] != m) return true; // edge 3 coord.reset(ip, j, kpp); m = accessor.getValue(coord) < isovalue; if (p[2] != m && p[3] != m) return true; // edge 4 coord.reset(i, j, kp); m = accessor.getValue(coord) < isovalue; if (p[0] != m && p[3] != m) return true; // edge 5 coord.reset(ip, jpp, k); m = accessor.getValue(coord) < isovalue; if (p[4] != m && p[5] != m) return true; // edge 6 coord.reset(ipp, jpp, kp); m = accessor.getValue(coord) < isovalue; if (p[5] != m && p[6] != m) return true; // edge 7 coord.reset(ip, jpp, kpp); m = accessor.getValue(coord) < isovalue; if (p[6] != m && p[7] != m) return true; // edge 8 coord.reset(i, jpp, kp); m = accessor.getValue(coord) < isovalue; if (p[7] != m && p[4] != m) return true; // edge 9 coord.reset(i, jp, k); m = accessor.getValue(coord) < isovalue; if (p[0] != m && p[4] != m) return true; // edge 10 coord.reset(ipp, jp, k); m = accessor.getValue(coord) < isovalue; if (p[1] != m && p[5] != m) return true; // edge 11 coord.reset(ipp, jp, kpp); m = accessor.getValue(coord) < isovalue; if (p[2] != m && p[6] != m) return true; // edge 12 coord.reset(i, jp, kpp); m = accessor.getValue(coord) < isovalue; if (p[3] != m && p[7] != m) return true; // Evaluate faces // face 1 coord.reset(ip, jp, k); m = accessor.getValue(coord) < isovalue; if (p[0] != m && p[1] != m && p[4] != m && p[5] != m) return true; // face 2 coord.reset(ipp, jp, kp); m = accessor.getValue(coord) < isovalue; if (p[1] != m && p[2] != m && p[5] != m && p[6] != m) return true; // face 3 coord.reset(ip, jp, kpp); m = accessor.getValue(coord) < isovalue; if (p[2] != m && p[3] != m && p[6] != m && p[7] != m) return true; // face 4 coord.reset(i, jp, kp); m = accessor.getValue(coord) < isovalue; if (p[0] != m && p[3] != m && p[4] != m && p[7] != m) return true; // face 5 coord.reset(ip, j, kp); m = accessor.getValue(coord) < isovalue; if (p[0] != m && p[1] != m && p[2] != m && p[3] != m) return true; // face 6 coord.reset(ip, jpp, kp); m = accessor.getValue(coord) < isovalue; if (p[4] != m && p[5] != m && p[6] != m && p[7] != m) return true; // test cube center coord.reset(ip, jp, kp); m = accessor.getValue(coord) < isovalue; if (p[0] != m && p[1] != m && p[2] != m && p[3] != m && p[4] != m && p[5] != m && p[6] != m && p[7] != m) return true; return false; } //////////////////////////////////////// template inline void mergeVoxels(LeafType& leaf, const Coord& start, int dim, int regionId) { Coord ijk, end = start; end[0] += dim; end[1] += dim; end[2] += dim; for (ijk[0] = start[0]; ijk[0] < end[0]; ++ijk[0]) { for (ijk[1] = start[1]; ijk[1] < end[1]; ++ijk[1]) { for (ijk[2] = start[2]; ijk[2] < end[2]; ++ijk[2]) { if(leaf.isValueOn(ijk)) leaf.setValue(ijk, regionId); } } } } // Note that we must use ValueType::value_type or else Visual C++ gets confused // thinking that it is a constructor. template inline bool isMergable(LeafType& leaf, const Coord& start, int dim, typename LeafType::ValueType::value_type adaptivity) { if (adaptivity < 1e-6) return false; typedef typename LeafType::ValueType VecT; Coord ijk, end = start; end[0] += dim; end[1] += dim; end[2] += dim; std::vector norms; for (ijk[0] = start[0]; ijk[0] < end[0]; ++ijk[0]) { for (ijk[1] = start[1]; ijk[1] < end[1]; ++ijk[1]) { for (ijk[2] = start[2]; ijk[2] < end[2]; ++ijk[2]) { if(!leaf.isValueOn(ijk)) continue; norms.push_back(leaf.getValue(ijk)); } } } size_t N = norms.size(); for (size_t ni = 0; ni < N; ++ni) { VecT n_i = norms[ni]; for (size_t nj = 0; nj < N; ++nj) { VecT n_j = norms[nj]; if ((1.0 - n_i.dot(n_j)) > adaptivity) return false; } } return true; } //////////////////////////////////////// template class SignData { public: typedef typename TreeT::ValueType ValueT; typedef tree::ValueAccessor AccessorT; typedef typename TreeT::template ValueConverter::Type IntTreeT; typedef tree::ValueAccessor IntAccessorT; typedef typename TreeT::template ValueConverter::Type Int16TreeT; typedef tree::ValueAccessor Int16AccessorT; ////////// SignData(const TreeT& distTree, const LeafManagerT& leafs, ValueT iso); void run(bool threaded = true); typename Int16TreeT::Ptr signTree() const { return mSignTree; } typename IntTreeT::Ptr idxTree() const { return mIdxTree; } ////////// SignData(SignData&, tbb::split); void operator()(const tbb::blocked_range&); void join(const SignData& rhs) { mSignTree->merge(*rhs.mSignTree); mIdxTree->merge(*rhs.mIdxTree); } private: const TreeT& mDistTree; AccessorT mDistAcc; const LeafManagerT& mLeafs; ValueT mIsovalue; typename Int16TreeT::Ptr mSignTree; Int16AccessorT mSignAcc; typename IntTreeT::Ptr mIdxTree; IntAccessorT mIdxAcc; }; template SignData::SignData(const TreeT& distTree, const LeafManagerT& leafs, ValueT iso) : mDistTree(distTree) , mDistAcc(mDistTree) , mLeafs(leafs) , mIsovalue(iso) , mSignTree(new Int16TreeT(0)) , mSignAcc(*mSignTree) , mIdxTree(new IntTreeT(int(util::INVALID_IDX))) , mIdxAcc(*mIdxTree) { } template SignData::SignData(SignData& rhs, tbb::split) : mDistTree(rhs.mDistTree) , mDistAcc(mDistTree) , mLeafs(rhs.mLeafs) , mIsovalue(rhs.mIsovalue) , mSignTree(new Int16TreeT(0)) , mSignAcc(*mSignTree) , mIdxTree(new IntTreeT(int(util::INVALID_IDX))) , mIdxAcc(*mIdxTree) { } template void SignData::run(bool threaded) { if (threaded) tbb::parallel_reduce(mLeafs.getRange(), *this); else (*this)(mLeafs.getRange()); } template void SignData::operator()(const tbb::blocked_range& range) { typedef typename Int16TreeT::LeafNodeType Int16LeafT; typedef typename IntTreeT::LeafNodeType IntLeafT; typename LeafManagerT::TreeType::LeafNodeType::ValueOnCIter iter; unsigned char signs, face; Coord ijk, coord; std::auto_ptr signLeafPt(new Int16LeafT(ijk, 0)); for (size_t n = range.begin(); n != range.end(); ++n) { bool collectedData = false; coord = mLeafs.leaf(n).origin(); if (!signLeafPt.get()) signLeafPt.reset(new Int16LeafT(coord, 0)); else signLeafPt->setOrigin(coord); const typename TreeT::LeafNodeType *leafPt = mDistAcc.probeConstLeaf(coord); coord.offset(TreeT::LeafNodeType::DIM - 1); for (iter = mLeafs.leaf(n).cbeginValueOn(); iter; ++iter) { ijk = iter.getCoord(); if (leafPt && ijk[0] < coord[0] && ijk[1] < coord[1] && ijk[2] < coord[2]) { signs = evalCellSigns(*leafPt, iter.pos(), mIsovalue); } else { signs = evalCellSigns(mDistAcc, ijk, mIsovalue); } if (signs != 0 && signs != 0xFF) { Int16 flags = (signs & 0x1) ? INSIDE : 0; if (bool(signs & 0x1) != bool(signs & 0x2)) flags |= XEDGE; if (bool(signs & 0x1) != bool(signs & 0x10)) flags |= YEDGE; if (bool(signs & 0x1) != bool(signs & 0x8)) flags |= ZEDGE; face = internal::sAmbiguousFace[signs]; if (face != 0) correctCellSigns(signs, face, mDistAcc, ijk, mIsovalue); flags |= Int16(signs); signLeafPt->setValue(ijk, flags); collectedData = true; } } if (collectedData) { IntLeafT* idxLeaf = mIdxAcc.touchLeaf(coord); idxLeaf->topologyUnion(*signLeafPt); typename IntLeafT::ValueOnIter it = idxLeaf->beginValueOn(); for (; it; ++it) { it.setValue(0); } mSignAcc.addLeaf(signLeafPt.release()); } } } //////////////////////////////////////// /// @brief Counts the total number of points per leaf, accounts for cells with multiple points. class CountPoints { public: CountPoints(std::vector& pointList) : mPointList(pointList) {} template void operator()(LeafNodeType &leaf, size_t leafIndex) const { size_t points = 0; typename LeafNodeType::ValueOnCIter iter = leaf.cbeginValueOn(); for (; iter; ++iter) { points += size_t(sEdgeGroupTable[(SIGNS & iter.getValue())][0]); } mPointList[leafIndex] = points; } private: std::vector& mPointList; }; /// @brief Computes the point list indices for the index tree. template class MapPoints { public: typedef tree::ValueAccessor Int16AccessorT; MapPoints(std::vector& pointList, const Int16TreeT& signTree) : mPointList(pointList) , mSignAcc(signTree) { } template void operator()(LeafNodeType &leaf, size_t leafIndex) const { size_t ptnIdx = mPointList[leafIndex]; typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); const typename Int16TreeT::LeafNodeType *signLeafPt = mSignAcc.probeConstLeaf(leaf.origin()); for (; iter; ++iter) { iter.setValue(ptnIdx); unsigned signs = SIGNS & signLeafPt->getValue(iter.pos()); ptnIdx += size_t(sEdgeGroupTable[signs][0]); } } private: std::vector& mPointList; Int16AccessorT mSignAcc; }; /// @brief Counts the total number of points per collapsed region template class CountRegions { public: typedef tree::ValueAccessor IntAccessorT; typedef typename IntTreeT::LeafNodeType IntLeafT; CountRegions(IntTreeT& idxTree, std::vector& regions) : mIdxAcc(idxTree) , mRegions(regions) { } template void operator()(LeafNodeType &leaf, size_t leafIndex) const { size_t regions = 0; IntLeafT tmpLeaf(*mIdxAcc.probeConstLeaf(leaf.origin())); typename IntLeafT::ValueOnIter iter = tmpLeaf.beginValueOn(); for (; iter; ++iter) { if(iter.getValue() == 0) { iter.setValueOff(); regions += size_t(sEdgeGroupTable[(SIGNS & leaf.getValue(iter.pos()))][0]); } } int onVoxelCount = int(tmpLeaf.onVoxelCount()); while (onVoxelCount > 0) { ++regions; iter = tmpLeaf.beginValueOn(); int regionId = iter.getValue(); for (; iter; ++iter) { if (iter.getValue() == regionId) { iter.setValueOff(); --onVoxelCount; } } } mRegions[leafIndex] = regions; } private: IntAccessorT mIdxAcc; std::vector& mRegions; }; //////////////////////////////////////// // @brief linear interpolation. inline double evalRoot(double v0, double v1, double iso) { return (iso - v0) / (v1 - v0); } /// @brief Extracts the eight corner values for leaf inclusive cells. template inline void collectCornerValues(const LeafT& leaf, const Index offset, std::vector& values) { values[0] = double(leaf.getValue(offset)); // i, j, k values[3] = double(leaf.getValue(offset + 1)); // i, j, k+1 values[4] = double(leaf.getValue(offset + LeafT::DIM)); // i, j+1, k values[7] = double(leaf.getValue(offset + LeafT::DIM + 1)); // i, j+1, k+1 values[1] = double(leaf.getValue(offset + (LeafT::DIM * LeafT::DIM))); // i+1, j, k values[2] = double(leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + 1)); // i+1, j, k+1 values[5] = double(leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + LeafT::DIM)); // i+1, j+1, k values[6] = double(leaf.getValue(offset + (LeafT::DIM * LeafT::DIM) + LeafT::DIM + 1)); // i+1, j+1, k+1 } /// @brief Extracts the eight corner values for a cell starting at the given @ijk coordinate. template inline void collectCornerValues(const AccessorT& acc, const Coord& ijk, std::vector& values) { Coord coord = ijk; values[0] = double(acc.getValue(coord)); // i, j, k coord[0] += 1; values[1] = double(acc.getValue(coord)); // i+1, j, k coord[2] += 1; values[2] = double(acc.getValue(coord)); // i+i, j, k+1 coord[0] = ijk[0]; values[3] = double(acc.getValue(coord)); // i, j, k+1 coord[1] += 1; coord[2] = ijk[2]; values[4] = double(acc.getValue(coord)); // i, j+1, k coord[0] += 1; values[5] = double(acc.getValue(coord)); // i+1, j+1, k coord[2] += 1; values[6] = double(acc.getValue(coord)); // i+1, j+1, k+1 coord[0] = ijk[0]; values[7] = double(acc.getValue(coord)); // i, j+1, k+1 } /// @brief Computes the average cell point for a given edge group. inline Vec3d computePoint(const std::vector& values, unsigned char signs, unsigned char edgeGroup, double iso) { Vec3d avg(0.0, 0.0, 0.0); int samples = 0; if (sEdgeGroupTable[signs][1] == edgeGroup) { // Edged: 0 - 1 avg[0] += evalRoot(values[0], values[1], iso); ++samples; } if (sEdgeGroupTable[signs][2] == edgeGroup) { // Edged: 1 - 2 avg[0] += 1.0; avg[2] += evalRoot(values[1], values[2], iso); ++samples; } if (sEdgeGroupTable[signs][3] == edgeGroup) { // Edged: 3 - 2 avg[0] += evalRoot(values[3], values[2], iso); avg[2] += 1.0; ++samples; } if (sEdgeGroupTable[signs][4] == edgeGroup) { // Edged: 0 - 3 avg[2] += evalRoot(values[0], values[3], iso); ++samples; } if (sEdgeGroupTable[signs][5] == edgeGroup) { // Edged: 4 - 5 avg[0] += evalRoot(values[4], values[5], iso); avg[1] += 1.0; ++samples; } if (sEdgeGroupTable[signs][6] == edgeGroup) { // Edged: 5 - 6 avg[0] += 1.0; avg[1] += 1.0; avg[2] += evalRoot(values[5], values[6], iso); ++samples; } if (sEdgeGroupTable[signs][7] == edgeGroup) { // Edged: 7 - 6 avg[0] += evalRoot(values[7], values[6], iso); avg[1] += 1.0; avg[2] += 1.0; ++samples; } if (sEdgeGroupTable[signs][8] == edgeGroup) { // Edged: 4 - 7 avg[1] += 1.0; avg[2] += evalRoot(values[4], values[7], iso); ++samples; } if (sEdgeGroupTable[signs][9] == edgeGroup) { // Edged: 0 - 4 avg[1] += evalRoot(values[0], values[4], iso); ++samples; } if (sEdgeGroupTable[signs][10] == edgeGroup) { // Edged: 1 - 5 avg[0] += 1.0; avg[1] += evalRoot(values[1], values[5], iso); ++samples; } if (sEdgeGroupTable[signs][11] == edgeGroup) { // Edged: 2 - 6 avg[0] += 1.0; avg[1] += evalRoot(values[2], values[6], iso); avg[2] += 1.0; ++samples; } if (sEdgeGroupTable[signs][12] == edgeGroup) { // Edged: 3 - 7 avg[1] += evalRoot(values[3], values[7], iso); avg[2] += 1.0; ++samples; } if (samples > 1) { double w = 1.0 / double(samples); avg[0] *= w; avg[1] *= w; avg[2] *= w; } return avg; } /// @brief Computes the average cell point for a given edge group, ignoring edge /// samples present in the @c signsMask configuration. inline int computeMaskedPoint(Vec3d& avg, const std::vector& values, unsigned char signs, unsigned char signsMask, unsigned char edgeGroup, double iso) { avg = Vec3d(0.0, 0.0, 0.0); int samples = 0; if (sEdgeGroupTable[signs][1] == edgeGroup && sEdgeGroupTable[signsMask][1] == 0) { // Edged: 0 - 1 avg[0] += evalRoot(values[0], values[1], iso); ++samples; } if (sEdgeGroupTable[signs][2] == edgeGroup && sEdgeGroupTable[signsMask][2] == 0) { // Edged: 1 - 2 avg[0] += 1.0; avg[2] += evalRoot(values[1], values[2], iso); ++samples; } if (sEdgeGroupTable[signs][3] == edgeGroup && sEdgeGroupTable[signsMask][3] == 0) { // Edged: 3 - 2 avg[0] += evalRoot(values[3], values[2], iso); avg[2] += 1.0; ++samples; } if (sEdgeGroupTable[signs][4] == edgeGroup && sEdgeGroupTable[signsMask][4] == 0) { // Edged: 0 - 3 avg[2] += evalRoot(values[0], values[3], iso); ++samples; } if (sEdgeGroupTable[signs][5] == edgeGroup && sEdgeGroupTable[signsMask][5] == 0) { // Edged: 4 - 5 avg[0] += evalRoot(values[4], values[5], iso); avg[1] += 1.0; ++samples; } if (sEdgeGroupTable[signs][6] == edgeGroup && sEdgeGroupTable[signsMask][6] == 0) { // Edged: 5 - 6 avg[0] += 1.0; avg[1] += 1.0; avg[2] += evalRoot(values[5], values[6], iso); ++samples; } if (sEdgeGroupTable[signs][7] == edgeGroup && sEdgeGroupTable[signsMask][7] == 0) { // Edged: 7 - 6 avg[0] += evalRoot(values[7], values[6], iso); avg[1] += 1.0; avg[2] += 1.0; ++samples; } if (sEdgeGroupTable[signs][8] == edgeGroup && sEdgeGroupTable[signsMask][8] == 0) { // Edged: 4 - 7 avg[1] += 1.0; avg[2] += evalRoot(values[4], values[7], iso); ++samples; } if (sEdgeGroupTable[signs][9] == edgeGroup && sEdgeGroupTable[signsMask][9] == 0) { // Edged: 0 - 4 avg[1] += evalRoot(values[0], values[4], iso); ++samples; } if (sEdgeGroupTable[signs][10] == edgeGroup && sEdgeGroupTable[signsMask][10] == 0) { // Edged: 1 - 5 avg[0] += 1.0; avg[1] += evalRoot(values[1], values[5], iso); ++samples; } if (sEdgeGroupTable[signs][11] == edgeGroup && sEdgeGroupTable[signsMask][11] == 0) { // Edged: 2 - 6 avg[0] += 1.0; avg[1] += evalRoot(values[2], values[6], iso); avg[2] += 1.0; ++samples; } if (sEdgeGroupTable[signs][12] == edgeGroup && sEdgeGroupTable[signsMask][12] == 0) { // Edged: 3 - 7 avg[1] += evalRoot(values[3], values[7], iso); avg[2] += 1.0; ++samples; } if (samples > 1) { double w = 1.0 / double(samples); avg[0] *= w; avg[1] *= w; avg[2] *= w; } return samples; } /// @brief Computes the average cell point for a given edge group, by computing /// convex weights based on the distance from the sample point @c p. inline Vec3d computeWeightedPoint(const Vec3d& p, const std::vector& values, unsigned char signs, unsigned char edgeGroup, double iso) { std::vector samples; samples.reserve(8); std::vector weights; weights.reserve(8); Vec3d avg(0.0, 0.0, 0.0); if (sEdgeGroupTable[signs][1] == edgeGroup) { // Edged: 0 - 1 avg[0] = evalRoot(values[0], values[1], iso); avg[1] = 0.0; avg[2] = 0.0; samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][2] == edgeGroup) { // Edged: 1 - 2 avg[0] = 1.0; avg[1] = 0.0; avg[2] = evalRoot(values[1], values[2], iso); samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][3] == edgeGroup) { // Edged: 3 - 2 avg[0] = evalRoot(values[3], values[2], iso); avg[1] = 0.0; avg[2] = 1.0; samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][4] == edgeGroup) { // Edged: 0 - 3 avg[0] = 0.0; avg[1] = 0.0; avg[2] = evalRoot(values[0], values[3], iso); samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][5] == edgeGroup) { // Edged: 4 - 5 avg[0] = evalRoot(values[4], values[5], iso); avg[1] = 1.0; avg[2] = 0.0; samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][6] == edgeGroup) { // Edged: 5 - 6 avg[0] = 1.0; avg[1] = 1.0; avg[2] = evalRoot(values[5], values[6], iso); samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][7] == edgeGroup) { // Edged: 7 - 6 avg[0] = evalRoot(values[7], values[6], iso); avg[1] = 1.0; avg[2] = 1.0; samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][8] == edgeGroup) { // Edged: 4 - 7 avg[0] = 0.0; avg[1] = 1.0; avg[2] = evalRoot(values[4], values[7], iso); samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][9] == edgeGroup) { // Edged: 0 - 4 avg[0] = 0.0; avg[1] = evalRoot(values[0], values[4], iso); avg[2] = 0.0; samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][10] == edgeGroup) { // Edged: 1 - 5 avg[0] = 1.0; avg[1] = evalRoot(values[1], values[5], iso); avg[2] = 0.0; samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][11] == edgeGroup) { // Edged: 2 - 6 avg[0] = 1.0; avg[1] = evalRoot(values[2], values[6], iso); avg[2] = 1.0; samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } if (sEdgeGroupTable[signs][12] == edgeGroup) { // Edged: 3 - 7 avg[0] = 0.0; avg[1] = evalRoot(values[3], values[7], iso); avg[2] = 1.0; samples.push_back(avg); weights.push_back((avg-p).lengthSqr()); } double minWeight = std::numeric_limits::max(); double maxWeight = -std::numeric_limits::max(); for (size_t i = 0, I = weights.size(); i < I; ++i) { minWeight = std::min(minWeight, weights[i]); maxWeight = std::max(maxWeight, weights[i]); } const double offset = maxWeight + minWeight * 0.1; for (size_t i = 0, I = weights.size(); i < I; ++i) { weights[i] = offset - weights[i]; } double weightSum = 0.0; for (size_t i = 0, I = weights.size(); i < I; ++i) { weightSum += weights[i]; } avg[0] = 0.0; avg[1] = 0.0; avg[2] = 0.0; if (samples.size() > 1) { for (size_t i = 0, I = samples.size(); i < I; ++i) { avg += samples[i] * (weights[i] / weightSum); } } else { avg = samples.front(); } return avg; } /// @brief Computes the average cell points defined by the sign configuration /// @c signs and the given corner values @c values. inline void computeCellPoints(std::vector& points, const std::vector& values, unsigned char signs, double iso) { for (size_t n = 1, N = sEdgeGroupTable[signs][0] + 1; n < N; ++n) { points.push_back(computePoint(values, signs, n, iso)); } } /// @brief Given a sign configuration @c lhsSigns and an edge group @c groupId, /// finds the corresponding edge group in a different sign configuration /// @c rhsSigns. Returns -1 if no match is found. inline int matchEdgeGroup(unsigned char groupId, unsigned char lhsSigns, unsigned char rhsSigns) { int id = -1; for (size_t i = 1; i <= 12; ++i) { if (sEdgeGroupTable[lhsSigns][i] == groupId && sEdgeGroupTable[rhsSigns][i] != 0) { id = sEdgeGroupTable[rhsSigns][i]; break; } } return id; } /// @brief Computes the average cell points defined by the sign configuration /// @c signs and the given corner values @c values. Combines data from /// two different level sets to eliminate seam lines when meshing /// fractured segments. inline void computeCellPoints(std::vector& points, std::vector& weightedPointMask, const std::vector& lhsValues, const std::vector& rhsValues, unsigned char lhsSigns, unsigned char rhsSigns, double iso, size_t pointIdx, const boost::scoped_array& seamPoints) { for (size_t n = 1, N = sEdgeGroupTable[lhsSigns][0] + 1; n < N; ++n) { int id = matchEdgeGroup(n, lhsSigns, rhsSigns); if (id != -1) { const unsigned char e(id); uint32_t& quantizedPoint = seamPoints[pointIdx + (id - 1)]; if ((quantizedPoint & MASK_DIRTY_BIT) && !(quantizedPoint & MASK_INVALID_BIT)) { Vec3d p = unpackPoint(quantizedPoint); points.push_back(computeWeightedPoint(p, rhsValues, rhsSigns, e, iso)); weightedPointMask.push_back(true); } else { points.push_back(computePoint(rhsValues, rhsSigns, e, iso)); weightedPointMask.push_back(false); } } else { points.push_back(computePoint(lhsValues, lhsSigns, n, iso)); weightedPointMask.push_back(false); } } } template class GenPoints { public: typedef tree::ValueAccessor AccessorT; typedef typename TreeT::template ValueConverter::Type IntTreeT; typedef tree::ValueAccessor IntAccessorT; typedef tree::ValueAccessor IntCAccessorT; typedef typename TreeT::template ValueConverter::Type Int16TreeT; typedef tree::ValueAccessor Int16CAccessorT; typedef boost::scoped_array QuantizedPointList; ////////// GenPoints(const LeafManagerT& signLeafs, const TreeT& distTree, IntTreeT& idxTree, PointList& points, std::vector& indices, const math::Transform& xform, double iso); void run(bool threaded = true); void setRefData(const Int16TreeT* refSignTree = NULL, const TreeT* refDistTree = NULL, IntTreeT* refIdxTree = NULL, const QuantizedPointList* seamPoints = NULL, std::vector* mSeamPointMaskPt = NULL); ////////// void operator()(const tbb::blocked_range&) const; private: const LeafManagerT& mSignLeafs; AccessorT mDistAcc; IntTreeT& mIdxTree; PointList& mPoints; std::vector& mIndices; const math::Transform& mTransform; const double mIsovalue; // reference data const Int16TreeT *mRefSignTreePt; const TreeT* mRefDistTreePt; const IntTreeT* mRefIdxTreePt; const QuantizedPointList* mSeamPointsPt; std::vector* mSeamPointMaskPt; }; template GenPoints::GenPoints(const LeafManagerT& signLeafs, const TreeT& distTree, IntTreeT& idxTree, PointList& points, std::vector& indices, const math::Transform& xform, double iso) : mSignLeafs(signLeafs) , mDistAcc(distTree) , mIdxTree(idxTree) , mPoints(points) , mIndices(indices) , mTransform(xform) , mIsovalue(iso) , mRefSignTreePt(NULL) , mRefDistTreePt(NULL) , mRefIdxTreePt(NULL) , mSeamPointsPt(NULL) , mSeamPointMaskPt(NULL) { } template void GenPoints::run(bool threaded) { if (threaded) tbb::parallel_for(mSignLeafs.getRange(), *this); else (*this)(mSignLeafs.getRange()); } template void GenPoints::setRefData(const Int16TreeT *refSignTree, const TreeT *refDistTree, IntTreeT* refIdxTree, const QuantizedPointList* seamPoints, std::vector* seamPointMask) { mRefSignTreePt = refSignTree; mRefDistTreePt = refDistTree; mRefIdxTreePt = refIdxTree; mSeamPointsPt = seamPoints; mSeamPointMaskPt = seamPointMask; } template void GenPoints::operator()( const tbb::blocked_range& range) const { typename IntTreeT::LeafNodeType::ValueOnIter iter; unsigned char signs, refSigns; Index offset; Coord ijk, coord; std::vector points(4); std::vector weightedPointMask(4); std::vector values(8), refValues(8); IntAccessorT idxAcc(mIdxTree); // reference data accessors boost::scoped_ptr refSignAcc; if (mRefSignTreePt) refSignAcc.reset(new Int16CAccessorT(*mRefSignTreePt)); boost::scoped_ptr refIdxAcc; if (mRefIdxTreePt) refIdxAcc.reset(new IntCAccessorT(*mRefIdxTreePt)); boost::scoped_ptr refDistAcc; if (mRefDistTreePt) refDistAcc.reset(new AccessorT(*mRefDistTreePt)); for (size_t n = range.begin(); n != range.end(); ++n) { coord = mSignLeafs.leaf(n).origin(); const typename TreeT::LeafNodeType *leafPt = mDistAcc.probeConstLeaf(coord); typename IntTreeT::LeafNodeType *idxLeafPt = idxAcc.probeLeaf(coord); // reference data leafs const typename Int16TreeT::LeafNodeType *refSignLeafPt = NULL; if (refSignAcc) refSignLeafPt = refSignAcc->probeConstLeaf(coord); const typename IntTreeT::LeafNodeType *refIdxLeafPt = NULL; if (refIdxAcc) refIdxLeafPt = refIdxAcc->probeConstLeaf(coord); const typename TreeT::LeafNodeType *refDistLeafPt = NULL; if (refDistAcc) refDistLeafPt = refDistAcc->probeConstLeaf(coord); // generate cell points size_t ptnIdx = mIndices[n]; coord.offset(TreeT::LeafNodeType::DIM - 1); for (iter = idxLeafPt->beginValueOn(); iter; ++iter) { if(iter.getValue() != 0) continue; iter.setValue(ptnIdx); iter.setValueOff(); offset = iter.pos(); ijk = iter.getCoord(); const bool inclusiveCell = ijk[0] < coord[0] && ijk[1] < coord[1] && ijk[2] < coord[2]; const Int16& flags = mSignLeafs.leaf(n).getValue(offset); signs = SIGNS & flags; refSigns = 0; if ((flags & SEAM) && refSignLeafPt && refIdxLeafPt) { if (refSignLeafPt->isValueOn(offset)) { refSigns = (SIGNS & refSignLeafPt->getValue(offset)); } } if (inclusiveCell) collectCornerValues(*leafPt, offset, values); else collectCornerValues(mDistAcc, ijk, values); points.clear(); weightedPointMask.clear(); if (refSigns == 0) { computeCellPoints(points, values, signs, mIsovalue); } else { if (inclusiveCell) collectCornerValues(*refDistLeafPt, offset, refValues); else collectCornerValues(*refDistAcc, ijk, refValues); computeCellPoints(points, weightedPointMask, values, refValues, signs, refSigns, mIsovalue, refIdxLeafPt->getValue(offset), *mSeamPointsPt); } for (size_t i = 0, I = points.size(); i < I; ++i) { // offset by cell-origin points[i][0] += double(ijk[0]); points[i][1] += double(ijk[1]); points[i][2] += double(ijk[2]); points[i] = mTransform.indexToWorld(points[i]); mPoints[ptnIdx][0] = float(points[i][0]); mPoints[ptnIdx][1] = float(points[i][1]); mPoints[ptnIdx][2] = float(points[i][2]); if (mSeamPointMaskPt && !weightedPointMask.empty() && weightedPointMask[i]) { (*mSeamPointMaskPt)[ptnIdx] = 1; } ++ptnIdx; } } // generate collapsed region points int onVoxelCount = int(idxLeafPt->onVoxelCount()); while (onVoxelCount > 0) { iter = idxLeafPt->beginValueOn(); int regionId = iter.getValue(), count = 0; Vec3d avg(0.0), point; for (; iter; ++iter) { if (iter.getValue() != regionId) continue; iter.setValue(ptnIdx); iter.setValueOff(); --onVoxelCount; ijk = iter.getCoord(); offset = iter.pos(); signs = (SIGNS & mSignLeafs.leaf(n).getValue(offset)); if (ijk[0] < coord[0] && ijk[1] < coord[1] && ijk[2] < coord[2]) { collectCornerValues(*leafPt, offset, values); } else { collectCornerValues(mDistAcc, ijk, values); } points.clear(); computeCellPoints(points, values, signs, mIsovalue); avg[0] += double(ijk[0]) + points[0][0]; avg[1] += double(ijk[1]) + points[0][1]; avg[2] += double(ijk[2]) + points[0][2]; ++count; } if (count > 1) { double w = 1.0 / double(count); avg[0] *= w; avg[1] *= w; avg[2] *= w; } avg = mTransform.indexToWorld(avg); mPoints[ptnIdx][0] = float(avg[0]); mPoints[ptnIdx][1] = float(avg[1]); mPoints[ptnIdx][2] = float(avg[2]); ++ptnIdx; } } } //////////////////////////////////////// template class SeamWeights { public: typedef tree::ValueAccessor AccessorT; typedef typename TreeT::template ValueConverter::Type IntTreeT; typedef tree::ValueAccessor IntAccessorT; typedef typename TreeT::template ValueConverter::Type Int16TreeT; typedef tree::ValueAccessor Int16AccessorT; typedef boost::scoped_array QuantizedPointList; ////////// SeamWeights(const TreeT& distTree, const Int16TreeT& refSignTree, IntTreeT& refIdxTree, QuantizedPointList& points, double iso); template void operator()(LeafNodeType &signLeaf, size_t leafIndex) const; private: AccessorT mDistAcc; Int16AccessorT mRefSignAcc; IntAccessorT mRefIdxAcc; QuantizedPointList& mPoints; const double mIsovalue; }; template SeamWeights::SeamWeights(const TreeT& distTree, const Int16TreeT& refSignTree, IntTreeT& refIdxTree, QuantizedPointList& points, double iso) : mDistAcc(distTree) , mRefSignAcc(refSignTree) , mRefIdxAcc(refIdxTree) , mPoints(points) , mIsovalue(iso) { } template template void SeamWeights::operator()(LeafNodeType &signLeaf, size_t /*leafIndex*/) const { Coord coord = signLeaf.origin(); const typename Int16TreeT::LeafNodeType *refSignLeafPt = mRefSignAcc.probeConstLeaf(coord); if (!refSignLeafPt) return; const typename TreeT::LeafNodeType *distLeafPt = mDistAcc.probeConstLeaf(coord); const typename IntTreeT::LeafNodeType *refIdxLeafPt = mRefIdxAcc.probeConstLeaf(coord); std::vector values(8); unsigned char lhsSigns, rhsSigns; Vec3d point; Index offset; Coord ijk; coord.offset(TreeT::LeafNodeType::DIM - 1); typename LeafNodeType::ValueOnCIter iter = signLeaf.cbeginValueOn(); for (; iter; ++iter) { offset = iter.pos(); ijk = iter.getCoord(); const bool inclusiveCell = ijk[0] < coord[0] && ijk[1] < coord[1] && ijk[2] < coord[2]; if ((iter.getValue() & SEAM) && refSignLeafPt->isValueOn(offset)) { lhsSigns = SIGNS & iter.getValue(); rhsSigns = SIGNS & refSignLeafPt->getValue(offset); if (inclusiveCell) { collectCornerValues(*distLeafPt, offset, values); } else { collectCornerValues(mDistAcc, ijk, values); } for (size_t n = 1, N = sEdgeGroupTable[lhsSigns][0] + 1; n < N; ++n) { int id = matchEdgeGroup(n, lhsSigns, rhsSigns); if (id != -1) { uint32_t& data = mPoints[refIdxLeafPt->getValue(offset) + (id - 1)]; if (!(data & MASK_DIRTY_BIT)) { int smaples = computeMaskedPoint(point, values, lhsSigns, rhsSigns, n, mIsovalue); if (smaples > 0) data = packPoint(point); else data = MASK_INVALID_BIT; data |= MASK_DIRTY_BIT; } } } } } } //////////////////////////////////////// template class MergeVoxelRegions { public: typedef typename TreeT::ValueType ValueT; typedef tree::ValueAccessor AccessorT; typedef typename TreeT::template ValueConverter::Type IntTreeT; typedef tree::ValueAccessor IntAccessorT; typedef typename TreeT::template ValueConverter::Type BoolTreeT; typedef typename LeafManagerT::TreeType::template ValueConverter::Type Int16TreeT; typedef tree::ValueAccessor Int16AccessorT; typedef typename TreeT::template ValueConverter::Type FloatTreeT; typedef Grid FloatGridT; ////////// MergeVoxelRegions(const LeafManagerT& signLeafs, const Int16TreeT& signTree, const TreeT& distTree, IntTreeT& idxTree, ValueT iso, ValueT adaptivity); void run(bool threaded = true); void setSpatialAdaptivity( const math::Transform& distGridXForm, const FloatGridT& adaptivityField); void setAdaptivityMask(const BoolTreeT* mask); void setRefData(const Int16TreeT* signTree, ValueT adaptivity); ////////// void operator()(const tbb::blocked_range&) const; private: const LeafManagerT& mSignLeafs; const Int16TreeT& mSignTree; Int16AccessorT mSignAcc; const TreeT& mDistTree; AccessorT mDistAcc; IntTreeT& mIdxTree; ValueT mIsovalue, mSurfaceAdaptivity, mInternalAdaptivity; const math::Transform* mTransform; const FloatGridT* mAdaptivityGrid; const BoolTreeT* mMask; const Int16TreeT* mRefSignTree; }; template MergeVoxelRegions::MergeVoxelRegions( const LeafManagerT& signLeafs, const Int16TreeT& signTree, const TreeT& distTree, IntTreeT& idxTree, ValueT iso, ValueT adaptivity) : mSignLeafs(signLeafs) , mSignTree(signTree) , mSignAcc(mSignTree) , mDistTree(distTree) , mDistAcc(mDistTree) , mIdxTree(idxTree) , mIsovalue(iso) , mSurfaceAdaptivity(adaptivity) , mInternalAdaptivity(adaptivity) , mTransform(NULL) , mAdaptivityGrid(NULL) , mMask(NULL) , mRefSignTree(NULL) { } template void MergeVoxelRegions::run(bool threaded) { if (threaded) tbb::parallel_for(mSignLeafs.getRange(), *this); else (*this)(mSignLeafs.getRange()); } template void MergeVoxelRegions::setSpatialAdaptivity( const math::Transform& distGridXForm, const FloatGridT& adaptivityField) { mTransform = &distGridXForm; mAdaptivityGrid = &adaptivityField; } template void MergeVoxelRegions::setAdaptivityMask(const BoolTreeT* mask) { mMask = mask; } template void MergeVoxelRegions::setRefData(const Int16TreeT* signTree, ValueT adaptivity) { mRefSignTree = signTree; mInternalAdaptivity = adaptivity; } template void MergeVoxelRegions::operator()(const tbb::blocked_range& range) const { typedef math::Vec3 Vec3T; typedef typename TreeT::LeafNodeType LeafT; typedef typename IntTreeT::LeafNodeType IntLeafT; typedef typename BoolTreeT::LeafNodeType BoolLeafT; typedef typename LeafT::template ValueConverter::Type Vec3LeafT; const int LeafDim = LeafT::DIM; IntAccessorT idxAcc(mIdxTree); typename LeafManagerT::TreeType::LeafNodeType::ValueOnCIter iter; typedef typename tree::ValueAccessor FloatTreeCAccessorT; boost::scoped_ptr adaptivityAcc; if (mAdaptivityGrid) { adaptivityAcc.reset(new FloatTreeCAccessorT(mAdaptivityGrid->tree())); } typedef typename tree::ValueAccessor Int16TreeCAccessorT; boost::scoped_ptr refAcc; if (mRefSignTree) { refAcc.reset(new Int16TreeCAccessorT(*mRefSignTree)); } typedef typename tree::ValueAccessor BoolTreeCAccessorT; boost::scoped_ptr maskAcc; if (mMask) { maskAcc.reset(new BoolTreeCAccessorT(*mMask)); } // Allocate reusable leaf buffers BoolLeafT mask; Vec3LeafT gradientBuffer; Coord ijk, nijk, coord, end; for (size_t n = range.begin(); n != range.end(); ++n) { const Coord& origin = mSignLeafs.leaf(n).origin(); ValueT adaptivity = mSurfaceAdaptivity; if (refAcc && refAcc->probeConstLeaf(origin) == NULL) { adaptivity = mInternalAdaptivity; } IntLeafT& idxLeaf = *idxAcc.probeLeaf(origin); end[0] = origin[0] + LeafDim; end[1] = origin[1] + LeafDim; end[2] = origin[2] + LeafDim; mask.setValuesOff(); // Mask off seam line adjacent voxels if (maskAcc) { const BoolLeafT* maskLeaf = maskAcc->probeConstLeaf(origin); if (maskLeaf != NULL) { typename BoolLeafT::ValueOnCIter it; for (it = maskLeaf->cbeginValueOn(); it; ++it) { ijk = it.getCoord(); coord[0] = ijk[0] - (ijk[0] % 2); coord[1] = ijk[1] - (ijk[1] % 2); coord[2] = ijk[2] - (ijk[2] % 2); mask.setActiveState(coord, true); } } } LeafT adaptivityLeaf(origin, adaptivity); if (mAdaptivityGrid) { for (Index offset = 0; offset < LeafT::NUM_VALUES; ++offset) { ijk = adaptivityLeaf.offsetToGlobalCoord(offset); Vec3d xyz = mAdaptivityGrid->transform().worldToIndex( mTransform->indexToWorld(ijk)); ValueT tmpA = ValueT(adaptivityAcc->getValue(util::nearestCoord(xyz))); adaptivityLeaf.setValueOnly(offset, tmpA * adaptivity); } } // Mask off ambiguous voxels for (iter = mSignLeafs.leaf(n).cbeginValueOn(); iter; ++iter) { ijk = iter.getCoord(); coord[0] = ijk[0] - (ijk[0] % 2); coord[1] = ijk[1] - (ijk[1] % 2); coord[2] = ijk[2] - (ijk[2] % 2); if(mask.isValueOn(coord)) continue; int flags = int(iter.getValue()); unsigned char signs = SIGNS & flags; if ((flags & SEAM) || !sAdaptable[signs] || sEdgeGroupTable[signs][0] > 1) { mask.setActiveState(coord, true); continue; } for (int i = 0; i < 26; ++i) { nijk = ijk + util::COORD_OFFSETS[i]; signs = SIGNS & mSignAcc.getValue(nijk); if (!sAdaptable[signs] || sEdgeGroupTable[signs][0] > 1) { mask.setActiveState(coord, true); break; } } } int dim = 2; // Mask off topologically ambiguous 2x2x2 voxel sub-blocks for (ijk[0] = origin[0]; ijk[0] < end[0]; ijk[0] += dim) { for (ijk[1] = origin[1]; ijk[1] < end[1]; ijk[1] += dim) { for (ijk[2] = origin[2]; ijk[2] < end[2]; ijk[2] += dim) { if (isNonManifold(mDistAcc, ijk, mIsovalue, dim)) { mask.setActiveState(ijk, true); } } } } // Compute the gradient for the remaining voxels gradientBuffer.setValuesOff(); for (iter = mSignLeafs.leaf(n).cbeginValueOn(); iter; ++iter) { ijk = iter.getCoord(); coord[0] = ijk[0] - (ijk[0] % dim); coord[1] = ijk[1] - (ijk[1] % dim); coord[2] = ijk[2] - (ijk[2] % dim); if(mask.isValueOn(coord)) continue; Vec3T norm(math::ISGradient::result(mDistAcc, ijk)); // Normalize (Vec3's normalize uses isApproxEqual, which uses abs and does more work) ValueT length = norm.length(); if (length > ValueT(1.0e-7)) { norm *= ValueT(1.0) / length; } gradientBuffer.setValue(ijk, norm); } int regionId = 1, next_dim = dim << 1; // Process the first adaptivity level. for (ijk[0] = 0; ijk[0] < LeafDim; ijk[0] += dim) { coord[0] = ijk[0] - (ijk[0] % next_dim); for (ijk[1] = 0; ijk[1] < LeafDim; ijk[1] += dim) { coord[1] = ijk[1] - (ijk[1] % next_dim); for (ijk[2] = 0; ijk[2] < LeafDim; ijk[2] += dim) { coord[2] = ijk[2] - (ijk[2] % next_dim); adaptivity = adaptivityLeaf.getValue(ijk); if (mask.isValueOn(ijk) || !isMergable(gradientBuffer, ijk, dim, adaptivity)) { mask.setActiveState(coord, true); continue; } mergeVoxels(idxLeaf, ijk, dim, regionId++); } } } // Process remaining adaptivity levels for (dim = 4; dim < LeafDim; dim = dim << 1) { next_dim = dim << 1; coord[0] = ijk[0] - (ijk[0] % next_dim); for (ijk[0] = origin[0]; ijk[0] < end[0]; ijk[0] += dim) { coord[1] = ijk[1] - (ijk[1] % next_dim); for (ijk[1] = origin[1]; ijk[1] < end[1]; ijk[1] += dim) { coord[2] = ijk[2] - (ijk[2] % next_dim); for (ijk[2] = origin[2]; ijk[2] < end[2]; ijk[2] += dim) { adaptivity = adaptivityLeaf.getValue(ijk); if (mask.isValueOn(ijk) || isNonManifold(mDistAcc, ijk, mIsovalue, dim) || !isMergable(gradientBuffer, ijk, dim, adaptivity)) { mask.setActiveState(coord, true); continue; } mergeVoxels(idxLeaf, ijk, dim, regionId++); } } } } adaptivity = adaptivityLeaf.getValue(origin); if (!(mask.isValueOn(origin) || isNonManifold(mDistAcc, origin, mIsovalue, LeafDim)) && isMergable(gradientBuffer, origin, LeafDim, adaptivity)) { mergeVoxels(idxLeaf, origin, LeafDim, regionId++); } } } //////////////////////////////////////// // Constructs qudas struct UniformPrimBuilder { UniformPrimBuilder(): mIdx(0), mPolygonPool(NULL) {} void init(const size_t upperBound, PolygonPool& quadPool) { mPolygonPool = &quadPool; mPolygonPool->resetQuads(upperBound); mIdx = 0; } void addPrim(const Vec4I& verts, bool reverse, char flags = 0) { if (!reverse) { mPolygonPool->quad(mIdx) = verts; } else { Vec4I& quad = mPolygonPool->quad(mIdx); quad[0] = verts[3]; quad[1] = verts[2]; quad[2] = verts[1]; quad[3] = verts[0]; } mPolygonPool->quadFlags(mIdx) = flags; ++mIdx; } void done() { mPolygonPool->trimQuads(mIdx); } private: size_t mIdx; PolygonPool* mPolygonPool; }; // Constructs qudas and triangles struct AdaptivePrimBuilder { AdaptivePrimBuilder() : mQuadIdx(0), mTriangleIdx(0), mPolygonPool(NULL) {} void init(const size_t upperBound, PolygonPool& polygonPool) { mPolygonPool = &polygonPool; mPolygonPool->resetQuads(upperBound); mPolygonPool->resetTriangles(upperBound); mQuadIdx = 0; mTriangleIdx = 0; } void addPrim(const Vec4I& verts, bool reverse, char flags = 0) { if (verts[0] != verts[1] && verts[0] != verts[2] && verts[0] != verts[3] && verts[1] != verts[2] && verts[1] != verts[3] && verts[2] != verts[3]) { mPolygonPool->quadFlags(mQuadIdx) = flags; addQuad(verts, reverse); } else if ( verts[0] == verts[3] && verts[1] != verts[2] && verts[1] != verts[0] && verts[2] != verts[0]) { mPolygonPool->triangleFlags(mTriangleIdx) = flags; addTriangle(verts[0], verts[1], verts[2], reverse); } else if ( verts[1] == verts[2] && verts[0] != verts[3] && verts[0] != verts[1] && verts[3] != verts[1]) { mPolygonPool->triangleFlags(mTriangleIdx) = flags; addTriangle(verts[0], verts[1], verts[3], reverse); } else if ( verts[0] == verts[1] && verts[2] != verts[3] && verts[2] != verts[0] && verts[3] != verts[0]) { mPolygonPool->triangleFlags(mTriangleIdx) = flags; addTriangle(verts[0], verts[2], verts[3], reverse); } else if ( verts[2] == verts[3] && verts[0] != verts[1] && verts[0] != verts[2] && verts[1] != verts[2]) { mPolygonPool->triangleFlags(mTriangleIdx) = flags; addTriangle(verts[0], verts[1], verts[2], reverse); } } void done() { mPolygonPool->trimQuads(mQuadIdx, /*reallocate=*/true); mPolygonPool->trimTrinagles(mTriangleIdx, /*reallocate=*/true); } private: void addQuad(const Vec4I& verts, bool reverse) { if (!reverse) { mPolygonPool->quad(mQuadIdx) = verts; } else { Vec4I& quad = mPolygonPool->quad(mQuadIdx); quad[0] = verts[3]; quad[1] = verts[2]; quad[2] = verts[1]; quad[3] = verts[0]; } ++mQuadIdx; } void addTriangle(unsigned v0, unsigned v1, unsigned v2, bool reverse) { Vec3I& prim = mPolygonPool->triangle(mTriangleIdx); prim[1] = v1; if (!reverse) { prim[0] = v0; prim[2] = v2; } else { prim[0] = v2; prim[2] = v0; } ++mTriangleIdx; } size_t mQuadIdx, mTriangleIdx; PolygonPool *mPolygonPool; }; template inline void constructPolygons(Int16 flags, Int16 refFlags, const Vec4i& offsets, const Coord& ijk, const SignAccT& signAcc, const IdxAccT& idxAcc, PrimBuilder& mesher, Index32 pointListSize) { const Index32 v0 = idxAcc.getValue(ijk); if (v0 == util::INVALID_IDX) return; char tag[2]; tag[0] = (flags & SEAM) ? POLYFLAG_FRACTURE_SEAM : 0; tag[1] = tag[0] | char(POLYFLAG_EXTERIOR); const bool isInside = flags & INSIDE; Coord coord; openvdb::Vec4I quad; unsigned char cell; Index32 tmpIdx = 0; if (flags & XEDGE) { quad[0] = v0 + offsets[0]; // i, j-1, k coord[0] = ijk[0]; coord[1] = ijk[1] - 1; coord[2] = ijk[2]; quad[1] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[1] + Index32(sEdgeGroupTable[cell][5] - 1); if (tmpIdx < pointListSize) quad[1] = tmpIdx; } // i, j-1, k-1 coord[2] -= 1; quad[2] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[2] + Index32(sEdgeGroupTable[cell][7] - 1); if (tmpIdx < pointListSize) quad[2] = tmpIdx; } // i, j, k-1 coord[1] = ijk[1]; quad[3] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[3] + Index32(sEdgeGroupTable[cell][3] - 1); if (tmpIdx < pointListSize) quad[3] = tmpIdx; } if (quad[1] != util::INVALID_IDX && quad[2] != util::INVALID_IDX && quad[3] != util::INVALID_IDX) { mesher.addPrim(quad, isInside, tag[bool(refFlags & XEDGE)]); } } if (flags & YEDGE) { quad[0] = v0 + offsets[1]; // i, j, k-1 coord[0] = ijk[0]; coord[1] = ijk[1]; coord[2] = ijk[2] - 1; quad[1] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[1] + Index32(sEdgeGroupTable[cell][12] - 1); if (tmpIdx < pointListSize) quad[1] = tmpIdx; } // i-1, j, k-1 coord[0] -= 1; quad[2] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[2] + Index32(sEdgeGroupTable[cell][11] - 1); if (tmpIdx < pointListSize) quad[2] = tmpIdx; } // i-1, j, k coord[2] = ijk[2]; quad[3] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[3] + Index32(sEdgeGroupTable[cell][10] - 1); if (tmpIdx < pointListSize) quad[3] = tmpIdx; } if (quad[1] != util::INVALID_IDX && quad[2] != util::INVALID_IDX && quad[3] != util::INVALID_IDX) { mesher.addPrim(quad, isInside, tag[bool(refFlags & YEDGE)]); } } if (flags & ZEDGE) { quad[0] = v0 + offsets[2]; // i, j-1, k coord[0] = ijk[0]; coord[1] = ijk[1] - 1; coord[2] = ijk[2]; quad[1] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[1] + Index32(sEdgeGroupTable[cell][8] - 1); if (tmpIdx < pointListSize) quad[1] = tmpIdx; } // i-1, j-1, k coord[0] -= 1; quad[2] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[2] + Index32(sEdgeGroupTable[cell][6] - 1); if (tmpIdx < pointListSize) quad[2] = tmpIdx; } // i-1, j, k coord[1] = ijk[1]; quad[3] = idxAcc.getValue(coord); cell = SIGNS & signAcc.getValue(coord); if (sEdgeGroupTable[cell][0] > 1) { tmpIdx = quad[3] + Index32(sEdgeGroupTable[cell][2] - 1); if (tmpIdx < pointListSize) quad[3] = tmpIdx; } if (quad[1] != util::INVALID_IDX && quad[2] != util::INVALID_IDX && quad[3] != util::INVALID_IDX) { mesher.addPrim(quad, !isInside, tag[bool(refFlags & ZEDGE)]); } } } //////////////////////////////////////// template class GenPolygons { public: typedef typename LeafManagerT::TreeType::template ValueConverter::Type IntTreeT; typedef typename LeafManagerT::TreeType::template ValueConverter::Type Int16TreeT; typedef tree::ValueAccessor IntAccessorT; typedef tree::ValueAccessor Int16AccessorT; ////////// GenPolygons(const LeafManagerT& signLeafs, const Int16TreeT& signTree, const IntTreeT& idxTree, PolygonPoolList& polygons, Index32 pointListSize); void run(bool threaded = true); void setRefSignTree(const Int16TreeT *r) { mRefSignTree = r; } ////////// void operator()(const tbb::blocked_range&) const; private: const LeafManagerT& mSignLeafs; const Int16TreeT& mSignTree; const IntTreeT& mIdxTree; const PolygonPoolList& mPolygonPoolList; const Index32 mPointListSize; const Int16TreeT *mRefSignTree; }; template GenPolygons::GenPolygons(const LeafManagerT& signLeafs, const Int16TreeT& signTree, const IntTreeT& idxTree, PolygonPoolList& polygons, Index32 pointListSize) : mSignLeafs(signLeafs) , mSignTree(signTree) , mIdxTree(idxTree) , mPolygonPoolList(polygons) , mPointListSize(pointListSize) , mRefSignTree(NULL) { } template void GenPolygons::run(bool threaded) { if (threaded) tbb::parallel_for(mSignLeafs.getRange(), *this); else (*this)(mSignLeafs.getRange()); } template void GenPolygons::operator()( const tbb::blocked_range& range) const { typename LeafManagerT::TreeType::LeafNodeType::ValueOnCIter iter; IntAccessorT idxAcc(mIdxTree); Int16AccessorT signAcc(mSignTree); PrimBuilder mesher; size_t edgeCount; Coord ijk, origin; // reference data boost::scoped_ptr refSignAcc; if (mRefSignTree) refSignAcc.reset(new Int16AccessorT(*mRefSignTree)); for (size_t n = range.begin(); n != range.end(); ++n) { origin = mSignLeafs.leaf(n).origin(); // Get an upper bound on the number of primitives. edgeCount = 0; iter = mSignLeafs.leaf(n).cbeginValueOn(); for (; iter; ++iter) { if (iter.getValue() & XEDGE) ++edgeCount; if (iter.getValue() & YEDGE) ++edgeCount; if (iter.getValue() & ZEDGE) ++edgeCount; } if(edgeCount == 0) continue; mesher.init(edgeCount, mPolygonPoolList[n]); const typename Int16TreeT::LeafNodeType *signleafPt = signAcc.probeConstLeaf(origin); const typename IntTreeT::LeafNodeType *idxLeafPt = idxAcc.probeConstLeaf(origin); if (!signleafPt || !idxLeafPt) continue; const typename Int16TreeT::LeafNodeType *refSignLeafPt = NULL; if (refSignAcc) refSignLeafPt = refSignAcc->probeConstLeaf(origin); Vec4i offsets; iter = mSignLeafs.leaf(n).cbeginValueOn(); for (; iter; ++iter) { ijk = iter.getCoord(); Int16 flags = iter.getValue(); if (!(flags & 0xE00)) continue; Int16 refFlags = 0; if (refSignLeafPt) { refFlags = refSignLeafPt->getValue(iter.pos()); } offsets[0] = 0; offsets[1] = 0; offsets[2] = 0; const unsigned char cell = (SIGNS & flags); if (sEdgeGroupTable[cell][0] > 1) { offsets[0] = (sEdgeGroupTable[cell][1] - 1); offsets[1] = (sEdgeGroupTable[cell][9] - 1); offsets[2] = (sEdgeGroupTable[cell][4] - 1); } if (ijk[0] > origin[0] && ijk[1] > origin[1] && ijk[2] > origin[2]) { constructPolygons(flags, refFlags, offsets, ijk, *signleafPt, *idxLeafPt, mesher, mPointListSize); } else { constructPolygons(flags, refFlags, offsets, ijk, signAcc, idxAcc, mesher, mPointListSize); } } mesher.done(); } } //////////////////////////////////////// // Masking and mesh partitioning struct PartOp { PartOp(size_t leafCount, size_t partitions, size_t activePart) { size_t leafSegments = leafCount / partitions; mStart = leafSegments * activePart; mEnd = activePart >= (partitions - 1) ? leafCount : mStart + leafSegments; } template void operator()(LeafNodeType &leaf, size_t leafIndex) const { if (leafIndex < mStart || leafIndex >= mEnd) leaf.setValuesOff(); } private: size_t mStart, mEnd; }; //////////////////////////////////////// template class PartGen { public: typedef tree::LeafManager LeafManagerT; typedef typename SrcTreeT::template ValueConverter::Type BoolTreeT; typedef tree::ValueAccessor BoolAccessorT; ////////// PartGen(const LeafManagerT& leafs, size_t partitions, size_t activePart); void run(bool threaded = true); BoolTreeT& tree() { return mTree; } ////////// PartGen(PartGen&, tbb::split); void operator()(const tbb::blocked_range&); void join(PartGen& rhs) { mTree.merge(rhs.mTree); } private: const LeafManagerT& mLeafManager; BoolTreeT mTree; size_t mStart, mEnd; }; template PartGen::PartGen(const LeafManagerT& leafs, size_t partitions, size_t activePart) : mLeafManager(leafs) , mTree(false) , mStart(0) , mEnd(0) { size_t leafCount = leafs.leafCount(); size_t leafSegments = leafCount / partitions; mStart = leafSegments * activePart; mEnd = activePart >= (partitions - 1) ? leafCount : mStart + leafSegments; } template PartGen::PartGen(PartGen& rhs, tbb::split) : mLeafManager(rhs.mLeafManager) , mTree(false) , mStart(rhs.mStart) , mEnd(rhs.mEnd) { } template void PartGen::run(bool threaded) { if (threaded) tbb::parallel_reduce(mLeafManager.getRange(), *this); else (*this)(mLeafManager.getRange()); } template void PartGen::operator()(const tbb::blocked_range& range) { Coord ijk; BoolAccessorT acc(mTree); typedef typename BoolTreeT::LeafNodeType BoolLeafT; typename SrcTreeT::LeafNodeType::ValueOnCIter iter; for (size_t n = range.begin(); n != range.end(); ++n) { if (n < mStart || n >= mEnd) continue; BoolLeafT* leaf = acc.touchLeaf(mLeafManager.leaf(n).origin()); leaf->topologyUnion(mLeafManager.leaf(n)); } } //////////////////////////////////////// template class GenSeamMask { public: typedef typename TreeT::template ValueConverter::Type BoolTreeT; ////////// GenSeamMask(const LeafManagerT& leafs, const TreeT& tree); void run(bool threaded = true); BoolTreeT& mask() { return mMaskTree; } ////////// GenSeamMask(GenSeamMask&, tbb::split); void operator()(const tbb::blocked_range&); void join(GenSeamMask& rhs) { mMaskTree.merge(rhs.mMaskTree); } private: const LeafManagerT& mLeafManager; const TreeT& mTree; BoolTreeT mMaskTree; }; template GenSeamMask::GenSeamMask(const LeafManagerT& leafs, const TreeT& tree) : mLeafManager(leafs) , mTree(tree) , mMaskTree(false) { } template GenSeamMask::GenSeamMask(GenSeamMask& rhs, tbb::split) : mLeafManager(rhs.mLeafManager) , mTree(rhs.mTree) , mMaskTree(false) { } template void GenSeamMask::run(bool threaded) { if (threaded) tbb::parallel_reduce(mLeafManager.getRange(), *this); else (*this)(mLeafManager.getRange()); } template void GenSeamMask::operator()(const tbb::blocked_range& range) { Coord ijk; tree::ValueAccessor acc(mTree); tree::ValueAccessor maskAcc(mMaskTree); typename LeafManagerT::TreeType::LeafNodeType::ValueOnCIter it; for (size_t n = range.begin(); n != range.end(); ++n) { it = mLeafManager.leaf(n).cbeginValueOn(); for (; it; ++it) { ijk = it.getCoord(); unsigned char rhsSigns = acc.getValue(ijk) & SIGNS; if (sEdgeGroupTable[rhsSigns][0] > 0) { unsigned char lhsSigns = it.getValue() & SIGNS; if (rhsSigns != lhsSigns) { maskAcc.setValueOn(ijk); } } } } } //////////////////////////////////////// template class TagSeamEdges { public: typedef tree::ValueAccessor AccessorT; TagSeamEdges(const TreeT& tree) : mAcc(tree) {} template void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const { const typename TreeT::LeafNodeType *maskLeaf = mAcc.probeConstLeaf(leaf.origin()); if (!maskLeaf) return; typename LeafNodeType::ValueOnIter it = leaf.beginValueOn(); for (; it; ++it) { if (maskLeaf->isValueOn(it.pos())) { it.setValue(it.getValue() | SEAM); } } } private: AccessorT mAcc; }; template struct MaskEdges { typedef tree::ValueAccessor BoolAccessorT; MaskEdges(const BoolTreeT& valueMask) : mMaskAcc(valueMask) {} template void operator()(LeafNodeType &leaf, size_t /*leafIndex*/) const { typename LeafNodeType::ValueOnIter it = leaf.beginValueOn(); const typename BoolTreeT::LeafNodeType * maskLeaf = mMaskAcc.probeConstLeaf(leaf.origin()); if (maskLeaf) { for (; it; ++it) { if (!maskLeaf->isValueOn(it.pos())) { it.setValue(0x1FF & it.getValue()); } } } else { for (; it; ++it) { it.setValue(0x1FF & it.getValue()); } } } private: BoolAccessorT mMaskAcc; }; class FlagUsedPoints { public: ////////// FlagUsedPoints(const PolygonPoolList& polygons, size_t polyListCount, std::vector& usedPointMask) : mPolygons(polygons) , mPolyListCount(polyListCount) , mUsedPointMask(usedPointMask) { } void run(bool threaded = true) { if (threaded) { tbb::parallel_for(tbb::blocked_range(0, mPolyListCount), *this); } else { (*this)(tbb::blocked_range(0, mPolyListCount)); } } ////////// void operator()(const tbb::blocked_range& range) const { // Concurrent writes to same memory address can occur, but // all threads are writing the same value and char is atomic. for (size_t n = range.begin(); n != range.end(); ++n) { const PolygonPool& polygons = mPolygons[n]; for (size_t i = 0; i < polygons.numQuads(); ++i) { const Vec4I& quad = polygons.quad(i); mUsedPointMask[quad[0]] = 1; mUsedPointMask[quad[1]] = 1; mUsedPointMask[quad[2]] = 1; mUsedPointMask[quad[3]] = 1; } for (size_t i = 0; i < polygons.numTriangles(); ++i) { const Vec3I& triangle = polygons.triangle(i); mUsedPointMask[triangle[0]] = 1; mUsedPointMask[triangle[1]] = 1; mUsedPointMask[triangle[2]] = 1; } } } private: const PolygonPoolList& mPolygons; size_t mPolyListCount; std::vector& mUsedPointMask; }; class RemapIndices { public: ////////// RemapIndices(PolygonPoolList& polygons, size_t polyListCount, const std::vector& indexMap) : mPolygons(polygons) , mPolyListCount(polyListCount) , mIndexMap(indexMap) { } void run(bool threaded = true) { if (threaded) { tbb::parallel_for(tbb::blocked_range(0, mPolyListCount), *this); } else { (*this)(tbb::blocked_range(0, mPolyListCount)); } } ////////// void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(); n != range.end(); ++n) { PolygonPool& polygons = mPolygons[n]; for (size_t i = 0; i < polygons.numQuads(); ++i) { Vec4I& quad = polygons.quad(i); quad[0] = mIndexMap[quad[0]]; quad[1] = mIndexMap[quad[1]]; quad[2] = mIndexMap[quad[2]]; quad[3] = mIndexMap[quad[3]]; } for (size_t i = 0; i < polygons.numTriangles(); ++i) { Vec3I& triangle = polygons.triangle(i); triangle[0] = mIndexMap[triangle[0]]; triangle[1] = mIndexMap[triangle[1]]; triangle[2] = mIndexMap[triangle[2]]; } } } private: PolygonPoolList& mPolygons; size_t mPolyListCount; const std::vector& mIndexMap; }; class MovePoints { public: ////////// MovePoints( std::auto_ptr& newPointList, const PointList& oldPointList, const std::vector& indexMap, const std::vector& usedPointMask) : mNewPointList(newPointList) , mOldPointList(oldPointList) , mIndexMap(indexMap) , mUsedPointMask(usedPointMask) { } void run(bool threaded = true) { if (threaded) { tbb::parallel_for(tbb::blocked_range(0, mIndexMap.size()), *this); } else { (*this)(tbb::blocked_range(0, mIndexMap.size())); } } ////////// void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(); n != range.end(); ++n) { if (mUsedPointMask[n]) { const size_t index = mIndexMap[n]; mNewPointList.get()[index] = mOldPointList[n]; } } } private: std::auto_ptr& mNewPointList; const PointList& mOldPointList; const std::vector& mIndexMap; const std::vector& mUsedPointMask; }; //////////////////////////////////////// template class GenTopologyMask { public: typedef tree::LeafManager LeafManagerT; typedef typename SrcTreeT::template ValueConverter::Type BoolTreeT; typedef tree::ValueAccessor SrcAccessorT; typedef tree::ValueAccessor BoolAccessorT; typedef Grid BoolGridT; ////////// GenTopologyMask(const BoolGridT& mask, const LeafManagerT& srcLeafs, const math::Transform& srcXForm, bool invertMask); void run(bool threaded = true); BoolTreeT& tree() { return mTree; } ////////// GenTopologyMask(GenTopologyMask&, tbb::split); void operator()(const tbb::blocked_range&); void join(GenTopologyMask& rhs) { mTree.merge(rhs.mTree); } private: const BoolGridT& mMask; const LeafManagerT& mLeafManager; const math::Transform& mSrcXForm; bool mInvertMask; BoolTreeT mTree; }; template GenTopologyMask::GenTopologyMask(const BoolGridT& mask, const LeafManagerT& srcLeafs, const math::Transform& srcXForm, bool invertMask) : mMask(mask) , mLeafManager(srcLeafs) , mSrcXForm(srcXForm) , mInvertMask(invertMask) , mTree(false) { } template GenTopologyMask::GenTopologyMask(GenTopologyMask& rhs, tbb::split) : mMask(rhs.mMask) , mLeafManager(rhs.mLeafManager) , mSrcXForm(rhs.mSrcXForm) , mInvertMask(rhs.mInvertMask) , mTree(false) { } template void GenTopologyMask::run(bool threaded) { if (threaded) { tbb::parallel_reduce(mLeafManager.getRange(), *this); } else { (*this)(mLeafManager.getRange()); } } template void GenTopologyMask::operator()(const tbb::blocked_range& range) { Coord ijk; Vec3d xyz; typedef typename BoolTreeT::LeafNodeType BoolLeafT; const math::Transform& maskXForm = mMask.transform(); tree::ValueAccessor maskAcc(mMask.tree()); tree::ValueAccessor acc(mTree); typename SrcTreeT::LeafNodeType::ValueOnCIter iter; for (size_t n = range.begin(); n != range.end(); ++n) { ijk = mLeafManager.leaf(n).origin(); BoolLeafT* leaf = new BoolLeafT(ijk, false); bool addLeaf = false; if (maskXForm == mSrcXForm) { const BoolLeafT* maskLeaf = maskAcc.probeConstLeaf(ijk); if (maskLeaf) { for (iter = mLeafManager.leaf(n).cbeginValueOn(); iter; ++iter) { Index pos = iter.pos(); if(maskLeaf->isValueOn(pos) != mInvertMask) { leaf->setValueOn(pos); addLeaf = true; } } } else if (maskAcc.isValueOn(ijk) != mInvertMask) { leaf->topologyUnion(mLeafManager.leaf(n)); addLeaf = true; } } else { for (iter = mLeafManager.leaf(n).cbeginValueOn(); iter; ++iter) { ijk = iter.getCoord(); xyz = maskXForm.worldToIndex(mSrcXForm.indexToWorld(ijk)); if(maskAcc.isValueOn(util::nearestCoord(xyz)) != mInvertMask) { leaf->setValueOn(iter.pos()); addLeaf = true; } } } if (addLeaf) acc.addLeaf(leaf); else delete leaf; } } //////////////////////////////////////// template class GenBoundaryMask { public: typedef typename SrcTreeT::template ValueConverter::Type IntTreeT; typedef typename SrcTreeT::template ValueConverter::Type BoolTreeT; typedef tree::LeafManager LeafManagerT; ////////// GenBoundaryMask(const LeafManagerT& leafs, const BoolTreeT&, const IntTreeT&); void run(bool threaded = true); BoolTreeT& tree() { return mTree; } ////////// GenBoundaryMask(GenBoundaryMask&, tbb::split); void operator()(const tbb::blocked_range&); void join(GenBoundaryMask& rhs) { mTree.merge(rhs.mTree); } private: // This typedef is needed for Windows typedef tree::ValueAccessor IntTreeAccessorT; bool neighboringLeaf(const Coord&, const IntTreeAccessorT&) const; const LeafManagerT& mLeafManager; const BoolTreeT& mMaskTree; const IntTreeT& mIdxTree; BoolTreeT mTree; CoordBBox mLeafBBox; }; template GenBoundaryMask::GenBoundaryMask(const LeafManagerT& leafs, const BoolTreeT& maskTree, const IntTreeT& auxTree) : mLeafManager(leafs) , mMaskTree(maskTree) , mIdxTree(auxTree) , mTree(false) { mIdxTree.evalLeafBoundingBox(mLeafBBox); mLeafBBox.expand(IntTreeT::LeafNodeType::DIM); } template GenBoundaryMask::GenBoundaryMask(GenBoundaryMask& rhs, tbb::split) : mLeafManager(rhs.mLeafManager) , mMaskTree(rhs.mMaskTree) , mIdxTree(rhs.mIdxTree) , mTree(false) , mLeafBBox(rhs.mLeafBBox) { } template void GenBoundaryMask::run(bool threaded) { if (threaded) { tbb::parallel_reduce(mLeafManager.getRange(), *this); } else { (*this)(mLeafManager.getRange()); } } template bool GenBoundaryMask::neighboringLeaf(const Coord& ijk, const IntTreeAccessorT& acc) const { if (acc.probeConstLeaf(ijk)) return true; const int dim = IntTreeT::LeafNodeType::DIM; // face adjacent neghbours if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1], ijk[2]))) return true; if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1], ijk[2]))) return true; if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] + dim, ijk[2]))) return true; if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] - dim, ijk[2]))) return true; if (acc.probeConstLeaf(Coord(ijk[0], ijk[1], ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0], ijk[1], ijk[2] - dim))) return true; // edge adjacent neighbors if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1], ijk[2] - dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1], ijk[2] - dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1], ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1], ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] + dim, ijk[2]))) return true; if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] + dim, ijk[2]))) return true; if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] - dim, ijk[2]))) return true; if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] - dim, ijk[2]))) return true; if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] - dim, ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] - dim, ijk[2] - dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] + dim, ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0], ijk[1] + dim, ijk[2] - dim))) return true; // corner adjacent neighbors if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] - dim, ijk[2] - dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] - dim, ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] - dim, ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] - dim, ijk[2] - dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] + dim, ijk[2] - dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] - dim, ijk[1] + dim, ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] + dim, ijk[2] + dim))) return true; if (acc.probeConstLeaf(Coord(ijk[0] + dim, ijk[1] + dim, ijk[2] - dim))) return true; return false; } template void GenBoundaryMask::operator()(const tbb::blocked_range& range) { Coord ijk; tree::ValueAccessor maskAcc(mMaskTree); tree::ValueAccessor idxAcc(mIdxTree); tree::ValueAccessor acc(mTree); typename SrcTreeT::LeafNodeType::ValueOnCIter iter; for (size_t n = range.begin(); n != range.end(); ++n) { const typename SrcTreeT::LeafNodeType& leaf = mLeafManager.leaf(n); ijk = leaf.origin(); if (!mLeafBBox.isInside(ijk) || !neighboringLeaf(ijk, idxAcc)) continue; const typename BoolTreeT::LeafNodeType* maskLeaf = maskAcc.probeConstLeaf(ijk); if (!maskLeaf || !leaf.hasSameTopology(maskLeaf)) { acc.touchLeaf(ijk)->topologyUnion(leaf); } } } //////////////////////////////////////// template class GenTileMask { public: typedef typename TreeT::template ValueConverter::Type BoolTreeT; typedef typename TreeT::ValueType ValueT; ////////// GenTileMask(const std::vector& tiles, const TreeT& distTree, ValueT iso); void run(bool threaded = true); BoolTreeT& tree() { return mTree; } ////////// GenTileMask(GenTileMask&, tbb::split); void operator()(const tbb::blocked_range&); void join(GenTileMask& rhs) { mTree.merge(rhs.mTree); } private: const std::vector& mTiles; const TreeT& mDistTree; ValueT mIsovalue; BoolTreeT mTree; }; template GenTileMask::GenTileMask( const std::vector& tiles, const TreeT& distTree, ValueT iso) : mTiles(tiles) , mDistTree(distTree) , mIsovalue(iso) , mTree(false) { } template GenTileMask::GenTileMask(GenTileMask& rhs, tbb::split) : mTiles(rhs.mTiles) , mDistTree(rhs.mDistTree) , mIsovalue(rhs.mIsovalue) , mTree(false) { } template void GenTileMask::run(bool threaded) { if (threaded) tbb::parallel_reduce(tbb::blocked_range(0, mTiles.size()), *this); else (*this)(tbb::blocked_range(0, mTiles.size())); } template void GenTileMask::operator()(const tbb::blocked_range& range) { tree::ValueAccessor distAcc(mDistTree); CoordBBox region, bbox; Coord ijk, nijk; bool processRegion = true; ValueT value; for (size_t n = range.begin(); n != range.end(); ++n) { const Vec4i& tile = mTiles[n]; bbox.min()[0] = tile[0]; bbox.min()[1] = tile[1]; bbox.min()[2] = tile[2]; bbox.max() = bbox.min(); bbox.max().offset(tile[3]); const bool thisInside = (distAcc.getValue(bbox.min()) < mIsovalue); const int thisDepth = distAcc.getValueDepth(bbox.min()); // eval x-edges ijk = bbox.max(); nijk = ijk; ++nijk[0]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(nijk)) { processRegion = thisInside != (distAcc.getValue(nijk) < mIsovalue); } if (processRegion) { region = bbox; region.min()[0] = region.max()[0] = ijk[0]; mTree.fill(region, true); } ijk = bbox.min(); --ijk[0]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(ijk)) { processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); } if (processRegion) { region = bbox; region.min()[0] = region.max()[0] = ijk[0]; mTree.fill(region, true); } // eval y-edges ijk = bbox.max(); nijk = ijk; ++nijk[1]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(nijk)) { processRegion = thisInside != (distAcc.getValue(nijk) < mIsovalue); } if (processRegion) { region = bbox; region.min()[1] = region.max()[1] = ijk[1]; mTree.fill(region, true); } ijk = bbox.min(); --ijk[1]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(ijk)) { processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); } if (processRegion) { region = bbox; region.min()[1] = region.max()[1] = ijk[1]; mTree.fill(region, true); } // eval z-edges ijk = bbox.max(); nijk = ijk; ++nijk[2]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(nijk)) { processRegion = thisInside != (distAcc.getValue(nijk) < mIsovalue); } if (processRegion) { region = bbox; region.min()[2] = region.max()[2] = ijk[2]; mTree.fill(region, true); } ijk = bbox.min(); --ijk[2]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(ijk)) { processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); } if (processRegion) { region = bbox; region.min()[2] = region.max()[2] = ijk[2]; mTree.fill(region, true); } ijk = bbox.min(); --ijk[1]; --ijk[2]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(ijk)) { processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); } if (processRegion) { region = bbox; region.min()[1] = region.max()[1] = ijk[1]; region.min()[2] = region.max()[2] = ijk[2]; mTree.fill(region, true); } ijk = bbox.min(); --ijk[0]; --ijk[1]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(ijk)) { processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); } if (processRegion) { region = bbox; region.min()[1] = region.max()[1] = ijk[1]; region.min()[0] = region.max()[0] = ijk[0]; mTree.fill(region, true); } ijk = bbox.min(); --ijk[0]; --ijk[2]; processRegion = true; if (thisDepth >= distAcc.getValueDepth(ijk)) { processRegion = !distAcc.probeValue(ijk, value) && thisInside != (value < mIsovalue); } if (processRegion) { region = bbox; region.min()[2] = region.max()[2] = ijk[2]; region.min()[0] = region.max()[0] = ijk[0]; mTree.fill(region, true); } } } //////////////////////////////////////// template inline void tileData(const DistTreeT& distTree, SignTreeT& signTree, IdxTreeT& idxTree, double iso) { typename DistTreeT::ValueOnCIter tileIter(distTree); tileIter.setMaxDepth(DistTreeT::ValueOnCIter::LEAF_DEPTH - 1); if (!tileIter) return; // volume has no active tiles. size_t tileCount = 0; for ( ; tileIter; ++tileIter) { ++tileCount; } std::vector tiles(tileCount); tileCount = 0; tileIter = distTree.cbeginValueOn(); tileIter.setMaxDepth(DistTreeT::ValueOnCIter::LEAF_DEPTH - 1); CoordBBox bbox; for (; tileIter; ++tileIter) { Vec4i& tile = tiles[tileCount++]; tileIter.getBoundingBox(bbox); tile[0] = bbox.min()[0]; tile[1] = bbox.min()[1]; tile[2] = bbox.min()[2]; tile[3] = bbox.max()[0] - bbox.min()[0]; } typename DistTreeT::ValueType isovalue = typename DistTreeT::ValueType(iso); GenTileMask tileMask(tiles, distTree, isovalue); tileMask.run(); typedef typename DistTreeT::template ValueConverter::Type BoolTreeT; typedef tree::LeafManager BoolLeafManagerT; BoolLeafManagerT leafs(tileMask.tree()); internal::SignData op(distTree, leafs, isovalue); op.run(); signTree.merge(*op.signTree()); idxTree.merge(*op.idxTree()); } //////////////////////////////////////// // Utility class for the volumeToMesh wrapper class PointListCopy { public: PointListCopy(const PointList& pointsIn, std::vector& pointsOut) : mPointsIn(pointsIn) , mPointsOut(pointsOut) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(); n < range.end(); ++n) { mPointsOut[n] = mPointsIn[n]; } } private: const PointList& mPointsIn; std::vector& mPointsOut; }; // Checks if the isovalue is in proximity to the active voxel boundary. template inline bool needsActiveVoxePadding(const LeafManagerT& leafs, double iso, double voxelSize) { double interiorWidth = 0.0, exteriorWidth = 0.0; { typename LeafManagerT::TreeType::LeafNodeType::ValueOffCIter it; bool foundInterior = false, foundExterior = false; for (size_t n = 0, N = leafs.leafCount(); n < N; ++n) { for (it = leafs.leaf(n).cbeginValueOff(); it; ++it) { double value = double(it.getValue()); if (value < 0.0) { interiorWidth = value; foundInterior = true; } else if (value > 0.0) { exteriorWidth = value; foundExterior = true; } if (foundInterior && foundExterior) break; } if (foundInterior && foundExterior) break; } } double minDist = std::min(std::abs(interiorWidth - iso), std::abs(exteriorWidth - iso)); return !(minDist > (2.0 * voxelSize)); } } // end namespace internal //////////////////////////////////////// inline PolygonPool::PolygonPool() : mNumQuads(0) , mNumTriangles(0) , mQuads(NULL) , mTriangles(NULL) , mQuadFlags(NULL) , mTriangleFlags(NULL) { } inline PolygonPool::PolygonPool(const size_t numQuads, const size_t numTriangles) : mNumQuads(numQuads) , mNumTriangles(numTriangles) , mQuads(new openvdb::Vec4I[mNumQuads]) , mTriangles(new openvdb::Vec3I[mNumTriangles]) , mQuadFlags(new char[mNumQuads]) , mTriangleFlags(new char[mNumTriangles]) { } inline void PolygonPool::copy(const PolygonPool& rhs) { resetQuads(rhs.numQuads()); resetTriangles(rhs.numTriangles()); for (size_t i = 0; i < mNumQuads; ++i) { mQuads[i] = rhs.mQuads[i]; mQuadFlags[i] = rhs.mQuadFlags[i]; } for (size_t i = 0; i < mNumTriangles; ++i) { mTriangles[i] = rhs.mTriangles[i]; mTriangleFlags[i] = rhs.mTriangleFlags[i]; } } inline void PolygonPool::resetQuads(size_t size) { mNumQuads = size; mQuads.reset(new openvdb::Vec4I[mNumQuads]); mQuadFlags.reset(new char[mNumQuads]); } inline void PolygonPool::clearQuads() { mNumQuads = 0; mQuads.reset(NULL); mQuadFlags.reset(NULL); } inline void PolygonPool::resetTriangles(size_t size) { mNumTriangles = size; mTriangles.reset(new openvdb::Vec3I[mNumTriangles]); mTriangleFlags.reset(new char[mNumTriangles]); } inline void PolygonPool::clearTriangles() { mNumTriangles = 0; mTriangles.reset(NULL); mTriangleFlags.reset(NULL); } inline bool PolygonPool::trimQuads(const size_t n, bool reallocate) { if (!(n < mNumQuads)) return false; if (reallocate) { if (n == 0) { mQuads.reset(NULL); } else { boost::scoped_array quads(new openvdb::Vec4I[n]); boost::scoped_array flags(new char[n]); for (size_t i = 0; i < n; ++i) { quads[i] = mQuads[i]; flags[i] = mQuadFlags[i]; } mQuads.swap(quads); mQuadFlags.swap(flags); } } mNumQuads = n; return true; } inline bool PolygonPool::trimTrinagles(const size_t n, bool reallocate) { if (!(n < mNumTriangles)) return false; if (reallocate) { if (n == 0) { mTriangles.reset(NULL); } else { boost::scoped_array triangles(new openvdb::Vec3I[n]); boost::scoped_array flags(new char[n]); for (size_t i = 0; i < n; ++i) { triangles[i] = mTriangles[i]; flags[i] = mTriangleFlags[i]; } mTriangles.swap(triangles); mTriangleFlags.swap(flags); } } mNumTriangles = n; return true; } //////////////////////////////////////// inline VolumeToMesh::VolumeToMesh(double isovalue, double adaptivity) : mPoints(NULL) , mPolygons() , mPointListSize(0) , mSeamPointListSize(0) , mPolygonPoolListSize(0) , mIsovalue(isovalue) , mPrimAdaptivity(adaptivity) , mSecAdaptivity(0.0) , mRefGrid(GridBase::ConstPtr()) , mSurfaceMaskGrid(GridBase::ConstPtr()) , mAdaptivityGrid(GridBase::ConstPtr()) , mAdaptivityMaskTree(TreeBase::ConstPtr()) , mRefSignTree(TreeBase::Ptr()) , mRefIdxTree(TreeBase::Ptr()) , mInvertSurfaceMask(false) , mPartitions(1) , mActivePart(0) , mQuantizedSeamPoints(NULL) , mPointFlags(0) { } inline PointList& VolumeToMesh::pointList() { return mPoints; } inline const size_t& VolumeToMesh::pointListSize() const { return mPointListSize; } inline PolygonPoolList& VolumeToMesh::polygonPoolList() { return mPolygons; } inline const PolygonPoolList& VolumeToMesh::polygonPoolList() const { return mPolygons; } inline const size_t& VolumeToMesh::polygonPoolListSize() const { return mPolygonPoolListSize; } inline void VolumeToMesh::setRefGrid(const GridBase::ConstPtr& grid, double secAdaptivity) { mRefGrid = grid; mSecAdaptivity = secAdaptivity; // Clear out old auxiliary data mRefSignTree = TreeBase::Ptr(); mRefIdxTree = TreeBase::Ptr(); mSeamPointListSize = 0; mQuantizedSeamPoints.reset(NULL); } inline void VolumeToMesh::setSurfaceMask(const GridBase::ConstPtr& mask, bool invertMask) { mSurfaceMaskGrid = mask; mInvertSurfaceMask = invertMask; } inline void VolumeToMesh::setSpatialAdaptivity(const GridBase::ConstPtr& grid) { mAdaptivityGrid = grid; } inline void VolumeToMesh::setAdaptivityMask(const TreeBase::ConstPtr& tree) { mAdaptivityMaskTree = tree; } inline void VolumeToMesh::partition(unsigned partitions, unsigned activePart) { mPartitions = std::max(partitions, unsigned(1)); mActivePart = std::min(activePart, mPartitions-1); } inline std::vector& VolumeToMesh::pointFlags() { return mPointFlags; } inline const std::vector& VolumeToMesh::pointFlags() const { return mPointFlags; } template inline void VolumeToMesh::operator()(const GridT& distGrid) { typedef typename GridT::TreeType DistTreeT; typedef tree::LeafManager DistLeafManagerT; typedef typename DistTreeT::ValueType DistValueT; typedef typename DistTreeT::template ValueConverter::Type BoolTreeT; typedef tree::LeafManager BoolLeafManagerT; typedef Grid BoolGridT; typedef typename DistTreeT::template ValueConverter::Type Int16TreeT; typedef tree::LeafManager Int16LeafManagerT; typedef typename DistTreeT::template ValueConverter::Type IntTreeT; typedef typename DistTreeT::template ValueConverter::Type FloatTreeT; typedef Grid FloatGridT; const openvdb::math::Transform& transform = distGrid.transform(); const DistTreeT& distTree = distGrid.tree(); const DistValueT isovalue = DistValueT(mIsovalue); typename Int16TreeT::Ptr signTreePt; typename IntTreeT::Ptr idxTreePt; typename BoolTreeT::Ptr pointMask; BoolTreeT valueMask(false), seamMask(false); const bool adaptive = mPrimAdaptivity > 1e-7 || mSecAdaptivity > 1e-7; bool maskEdges = false; const BoolGridT * surfaceMask = NULL; if (mSurfaceMaskGrid && mSurfaceMaskGrid->type() == BoolGridT::gridType()) { surfaceMask = static_cast(mSurfaceMaskGrid.get()); } const FloatGridT * adaptivityField = NULL; if (mAdaptivityGrid && mAdaptivityGrid->type() == FloatGridT::gridType()) { adaptivityField = static_cast(mAdaptivityGrid.get()); } if (mAdaptivityMaskTree && mAdaptivityMaskTree->type() == BoolTreeT::treeType()) { const BoolTreeT *adaptivityMaskPt = static_cast(mAdaptivityMaskTree.get()); seamMask.topologyUnion(*adaptivityMaskPt); } // Collect auxiliary data { DistLeafManagerT distLeafs(distTree); // Check if the isovalue is in proximity to the active voxel boundary. bool padActiveVoxels = false; int padVoxels = 3; if (distGrid.getGridClass() != GRID_LEVEL_SET) { padActiveVoxels = true; } else { padActiveVoxels = internal::needsActiveVoxePadding(distLeafs, mIsovalue, transform.voxelSize()[0]); } // always pad the active region for small volumes (the performance hit is neglectable). if (!padActiveVoxels) { Coord dim; distTree.evalActiveVoxelDim(dim); int maxDim = std::max(std::max(dim[0], dim[1]), dim[2]); if (maxDim < 1000) { padActiveVoxels = true; padVoxels = 1; } } if (surfaceMask || mPartitions > 1) { maskEdges = true; if (surfaceMask) { { // Mask internal::GenTopologyMask masking( *surfaceMask, distLeafs, transform, mInvertSurfaceMask); masking.run(); valueMask.merge(masking.tree()); } if (mPartitions > 1) { // Partition tree::LeafManager leafs(valueMask); leafs.foreach(internal::PartOp(leafs.leafCount() , mPartitions, mActivePart)); valueMask.pruneInactive(); } } else { // Partition internal::PartGen partitioner(distLeafs, mPartitions, mActivePart); partitioner.run(); valueMask.merge(partitioner.tree()); } { if (padActiveVoxels) tools::dilateVoxels(valueMask, padVoxels); BoolLeafManagerT leafs(valueMask); internal::SignData signDataOp(distTree, leafs, isovalue); signDataOp.run(); signTreePt = signDataOp.signTree(); idxTreePt = signDataOp.idxTree(); } { internal::GenBoundaryMask boundary(distLeafs, valueMask, *idxTreePt); boundary.run(); BoolLeafManagerT bleafs(boundary.tree()); internal::SignData signDataOp(distTree, bleafs, isovalue); signDataOp.run(); signTreePt->merge(*signDataOp.signTree()); idxTreePt->merge(*signDataOp.idxTree()); } } else { // Collect voxel-sign configurations if (padActiveVoxels) { BoolTreeT regionMask(false); regionMask.topologyUnion(distTree); tools::dilateVoxels(regionMask, padVoxels); BoolLeafManagerT leafs(regionMask); internal::SignData signDataOp(distTree, leafs, isovalue); signDataOp.run(); signTreePt = signDataOp.signTree(); idxTreePt = signDataOp.idxTree(); } else { internal::SignData signDataOp(distTree, distLeafs, isovalue); signDataOp.run(); signTreePt = signDataOp.signTree(); idxTreePt = signDataOp.idxTree(); } } } // Collect auxiliary data from active tiles internal::tileData(distTree, *signTreePt, *idxTreePt, isovalue); // Optionally collect auxiliary data from a reference level set. Int16TreeT *refSignTreePt = NULL; IntTreeT *refIdxTreePt = NULL; const DistTreeT *refDistTreePt = NULL; if (mRefGrid && mRefGrid->type() == GridT::gridType()) { const GridT* refGrid = static_cast(mRefGrid.get()); refDistTreePt = &refGrid->tree(); // Collect and cache auxiliary data from the reference grid. if (!mRefSignTree && !mRefIdxTree) { DistLeafManagerT refDistLeafs(*refDistTreePt); internal::SignData signDataOp(*refDistTreePt, refDistLeafs, isovalue); signDataOp.run(); mRefSignTree = signDataOp.signTree(); mRefIdxTree = signDataOp.idxTree(); } // Get cached auxiliary data if (mRefSignTree && mRefIdxTree) { refSignTreePt = static_cast(mRefSignTree.get()); refIdxTreePt = static_cast(mRefIdxTree.get()); } } // Process auxiliary data Int16LeafManagerT signLeafs(*signTreePt); if (maskEdges) { signLeafs.foreach(internal::MaskEdges(valueMask)); valueMask.clear(); } // Generate the seamline mask if (refSignTreePt) { internal::GenSeamMask seamOp(signLeafs, *refSignTreePt); seamOp.run(); tools::dilateVoxels(seamOp.mask(), 3); signLeafs.foreach(internal::TagSeamEdges(seamOp.mask())); seamMask.merge(seamOp.mask()); } std::vector regions(signLeafs.leafCount(), 0); if (regions.empty()) return; if (adaptive) { internal::MergeVoxelRegions merge( signLeafs, *signTreePt, distTree, *idxTreePt, isovalue, DistValueT(mPrimAdaptivity)); if (adaptivityField) { merge.setSpatialAdaptivity(transform, *adaptivityField); } if (refSignTreePt || mAdaptivityMaskTree) { merge.setAdaptivityMask(&seamMask); } if (refSignTreePt) { merge.setRefData(refSignTreePt, DistValueT(mSecAdaptivity)); } merge.run(); signLeafs.foreach(internal::CountRegions(*idxTreePt, regions)); } else { signLeafs.foreach(internal::CountPoints(regions)); } { mPointListSize = 0; size_t tmp = 0; for (size_t n = 0, N = regions.size(); n < N; ++n) { tmp = regions[n]; regions[n] = mPointListSize; mPointListSize += tmp; } } // Generate the unique point list mPoints.reset(new openvdb::Vec3s[mPointListSize]); mPointFlags.clear(); // Generate seam line sample points if (refSignTreePt && refIdxTreePt) { if (mSeamPointListSize == 0) { std::vector pointMap; { Int16LeafManagerT refSignLeafs(*refSignTreePt); pointMap.resize(refSignLeafs.leafCount(), 0); refSignLeafs.foreach(internal::CountPoints(pointMap)); size_t tmp = 0; for (size_t n = 0, N = pointMap.size(); n < N; ++n) { tmp = pointMap[n]; pointMap[n] = mSeamPointListSize; mSeamPointListSize += tmp; } } if (!pointMap.empty() && mSeamPointListSize != 0) { mQuantizedSeamPoints.reset(new uint32_t[mSeamPointListSize]); memset(mQuantizedSeamPoints.get(), 0, sizeof(uint32_t) * mSeamPointListSize); typedef tree::LeafManager IntLeafManagerT; IntLeafManagerT refIdxLeafs(*refIdxTreePt); refIdxLeafs.foreach(internal::MapPoints(pointMap, *refSignTreePt)); } } if (mSeamPointListSize != 0) { signLeafs.foreach(internal::SeamWeights( distTree, *refSignTreePt, *refIdxTreePt, mQuantizedSeamPoints, mIsovalue)); } } internal::GenPoints pointOp(signLeafs, distTree, *idxTreePt, mPoints, regions, transform, mIsovalue); if (mSeamPointListSize != 0) { mPointFlags.resize(mPointListSize); pointOp.setRefData(refSignTreePt, refDistTreePt, refIdxTreePt, &mQuantizedSeamPoints, &mPointFlags); } pointOp.run(); mPolygonPoolListSize = signLeafs.leafCount(); mPolygons.reset(new PolygonPool[mPolygonPoolListSize]); if (adaptive) { internal::GenPolygons mesher(signLeafs, *signTreePt, *idxTreePt, mPolygons, Index32(mPointListSize)); mesher.setRefSignTree(refSignTreePt); mesher.run(); } else { internal::GenPolygons mesher(signLeafs, *signTreePt, *idxTreePt, mPolygons, Index32(mPointListSize)); mesher.setRefSignTree(refSignTreePt); mesher.run(); } // Clean up unused points, only necessary if masking and/or // automatic mesh partitioning is enabled. if ((surfaceMask || mPartitions > 1) && mPointListSize > 0) { // Flag used points std::vector usedPointMask(mPointListSize, 0); internal::FlagUsedPoints flagPoints(mPolygons, mPolygonPoolListSize, usedPointMask); flagPoints.run(); // Create index map std::vector indexMap(mPointListSize); size_t usedPointCount = 0; for (size_t p = 0; p < mPointListSize; ++p) { if (usedPointMask[p]) indexMap[p] = usedPointCount++; } if (usedPointCount < mPointListSize) { // move points std::auto_ptr newPointList(new openvdb::Vec3s[usedPointCount]); internal::MovePoints movePoints(newPointList, mPoints, indexMap, usedPointMask); movePoints.run(); mPointListSize = usedPointCount; mPoints.reset(newPointList.release()); // update primitives internal::RemapIndices remap(mPolygons, mPolygonPoolListSize, indexMap); remap.run(); } } // Subdivide nonplanar quads near the seamline edges // todo: thread and clean up if (refSignTreePt || refIdxTreePt || refDistTreePt) { std::vector newPoints; for (size_t n = 0; n < mPolygonPoolListSize; ++n) { PolygonPool& polygons = mPolygons[n]; std::vector nonPlanarQuads; nonPlanarQuads.reserve(polygons.numQuads()); for (size_t i = 0; i < polygons.numQuads(); ++i) { char& flags = polygons.quadFlags(i); if ((flags & POLYFLAG_FRACTURE_SEAM) && !(flags & POLYFLAG_EXTERIOR)) { openvdb::Vec4I& quad = polygons.quad(i); const bool edgePoly = mPointFlags[quad[0]] || mPointFlags[quad[1]] || mPointFlags[quad[2]] || mPointFlags[quad[3]]; if (!edgePoly) continue; const Vec3s& p0 = mPoints[quad[0]]; const Vec3s& p1 = mPoints[quad[1]]; const Vec3s& p2 = mPoints[quad[2]]; const Vec3s& p3 = mPoints[quad[3]]; if (!internal::isPlanarQuad(p0, p1, p2, p3, 1e-6f)) { nonPlanarQuads.push_back(i); } } } if (!nonPlanarQuads.empty()) { PolygonPool tmpPolygons; tmpPolygons.resetQuads(polygons.numQuads() - nonPlanarQuads.size()); tmpPolygons.resetTriangles(polygons.numTriangles() + 4 * nonPlanarQuads.size()); size_t triangleIdx = 0; for (size_t i = 0; i < nonPlanarQuads.size(); ++i) { size_t& quadIdx = nonPlanarQuads[i]; openvdb::Vec4I& quad = polygons.quad(quadIdx); char& quadFlags = polygons.quadFlags(quadIdx); //quadFlags |= POLYFLAG_SUBDIVIDED; Vec3s centroid = (mPoints[quad[0]] + mPoints[quad[1]] + mPoints[quad[2]] + mPoints[quad[3]]) * 0.25; size_t pointIdx = newPoints.size() + mPointListSize; newPoints.push_back(centroid); { Vec3I& triangle = tmpPolygons.triangle(triangleIdx); triangle[0] = quad[0]; triangle[1] = pointIdx; triangle[2] = quad[3]; tmpPolygons.triangleFlags(triangleIdx) = quadFlags; if (mPointFlags[triangle[0]] || mPointFlags[triangle[2]]) { tmpPolygons.triangleFlags(triangleIdx) |= POLYFLAG_SUBDIVIDED; } } ++triangleIdx; { Vec3I& triangle = tmpPolygons.triangle(triangleIdx); triangle[0] = quad[0]; triangle[1] = quad[1]; triangle[2] = pointIdx; tmpPolygons.triangleFlags(triangleIdx) = quadFlags; if (mPointFlags[triangle[0]] || mPointFlags[triangle[1]]) { tmpPolygons.triangleFlags(triangleIdx) |= POLYFLAG_SUBDIVIDED; } } ++triangleIdx; { Vec3I& triangle = tmpPolygons.triangle(triangleIdx); triangle[0] = quad[1]; triangle[1] = quad[2]; triangle[2] = pointIdx; tmpPolygons.triangleFlags(triangleIdx) = quadFlags; if (mPointFlags[triangle[0]] || mPointFlags[triangle[1]]) { tmpPolygons.triangleFlags(triangleIdx) |= POLYFLAG_SUBDIVIDED; } } ++triangleIdx; { Vec3I& triangle = tmpPolygons.triangle(triangleIdx); triangle[0] = quad[2]; triangle[1] = quad[3]; triangle[2] = pointIdx; tmpPolygons.triangleFlags(triangleIdx) = quadFlags; if (mPointFlags[triangle[0]] || mPointFlags[triangle[1]]) { tmpPolygons.triangleFlags(triangleIdx) |= POLYFLAG_SUBDIVIDED; } } ++triangleIdx; quad[0] = util::INVALID_IDX; } for (size_t i = 0; i < polygons.numTriangles(); ++i) { tmpPolygons.triangle(triangleIdx) = polygons.triangle(i); tmpPolygons.triangleFlags(triangleIdx) = polygons.triangleFlags(i); ++triangleIdx; } size_t quadIdx = 0; for (size_t i = 0; i < polygons.numQuads(); ++i) { openvdb::Vec4I& quad = polygons.quad(i); if (quad[0] != util::INVALID_IDX) { tmpPolygons.quad(quadIdx) = quad; tmpPolygons.quadFlags(quadIdx) = polygons.quadFlags(i); ++quadIdx; } } polygons.copy(tmpPolygons); } } if (!newPoints.empty()) { size_t newPointCount = newPoints.size() + mPointListSize; std::auto_ptr newPointList(new openvdb::Vec3s[newPointCount]); for (size_t i = 0; i < mPointListSize; ++i) { newPointList.get()[i] = mPoints[i]; } for (size_t i = mPointListSize; i < newPointCount; ++i) { newPointList.get()[i] = newPoints[i - mPointListSize]; } mPointListSize = newPointCount; mPoints.reset(newPointList.release()); mPointFlags.resize(mPointListSize, 0); } } } //////////////////////////////////////// template inline void volumeToMesh( const GridType& grid, std::vector& points, std::vector& triangles, std::vector& quads, double isovalue, double adaptivity) { VolumeToMesh mesher(isovalue, adaptivity); mesher(grid); // Preallocate the point list points.clear(); points.resize(mesher.pointListSize()); { // Copy points internal::PointListCopy ptnCpy(mesher.pointList(), points); tbb::parallel_for(tbb::blocked_range(0, points.size()), ptnCpy); mesher.pointList().reset(NULL); } PolygonPoolList& polygonPoolList = mesher.polygonPoolList(); { // Preallocate primitive lists size_t numQuads = 0, numTriangles = 0; for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; numTriangles += polygons.numTriangles(); numQuads += polygons.numQuads(); } triangles.clear(); triangles.resize(numTriangles); quads.clear(); quads.resize(numQuads); } // Copy primitives size_t qIdx = 0, tIdx = 0; for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; for (size_t i = 0, I = polygons.numQuads(); i < I; ++i) { quads[qIdx++] = polygons.quad(i); } for (size_t i = 0, I = polygons.numTriangles(); i < I; ++i) { triangles[tIdx++] = polygons.triangle(i); } } } template void volumeToMesh( const GridType& grid, std::vector& points, std::vector& quads, double isovalue) { std::vector triangles(0); volumeToMesh(grid,points, triangles, quads, isovalue, 0.0); } //////////////////////////////////////// } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_VOLUME_TO_MESH_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/GridOperators.h0000644000000000000000000011153212252453157015317 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file GridOperators.h /// /// @brief Applies an operator on an input grid to produce an output /// grid with the same topology but potentially different value type. #ifndef OPENVDB_TOOLS_GRID_OPERATORS_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_GRID_OPERATORS_HAS_BEEN_INCLUDED #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief VectorToScalarConverter::Type is the type of a grid /// having the same tree configuration as VectorGridType but a scalar value type, T, /// where T is the type of the original vector components. /// @details For example, VectorToScalarConverter::Type is equivalent to DoubleGrid. template struct VectorToScalarConverter { typedef typename VectorGridType::ValueType::value_type VecComponentValueT; typedef typename VectorGridType::template ValueConverter::Type Type; }; /// @brief ScalarToVectorConverter::Type is the type of a grid /// having the same tree configuration as ScalarGridType but value type Vec3 /// where T is ScalarGridType::ValueType. /// @details For example, ScalarToVectorConverter::Type is equivalent to Vec3DGrid. template struct ScalarToVectorConverter { typedef math::Vec3 VectorValueT; typedef typename ScalarGridType::template ValueConverter::Type Type; }; /// @brief Compute the Closest-Point Transform (CPT) from a distance field. /// @return a new vector-valued grid with the same numerical precision as the input grid /// (for example, if the input grid is a DoubleGrid, the output grid will be a Vec3DGrid) /// When a mask grid is specified, the solution is calculated only in the intersection of /// the mask active topology and the input active topology independent of the /// transforms associated with either grid. /// @note The current implementation assumes all the input distance values /// are represented by leaf voxels and not tiles. This is true for all /// narrow-band level sets, which this class was originally developed for. /// In the future we will expand this class to also handle tile values. template inline typename ScalarToVectorConverter::Type::Ptr cpt(const GridType& grid, bool threaded, InterruptT* interrupt); template inline typename ScalarToVectorConverter::Type::Ptr cpt(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); template inline typename ScalarToVectorConverter::Type::Ptr cpt(const GridType& grid, bool threaded = true) { return cpt(grid, threaded, NULL); } template inline typename ScalarToVectorConverter::Type::Ptr cpt(const GridType& grid, const MaskT& mask, bool threaded = true) { return cpt(grid, mask, threaded, NULL); } /// @brief Compute the curl of the given vector-valued grid. /// @return a new vector-valued grid /// When a mask grid is specified, the solution is calculated only in the intersection of /// the mask active topology and the input active topology independent of the /// transforms associated with either grid. template inline typename GridType::Ptr curl(const GridType& grid, bool threaded, InterruptT* interrupt); template inline typename GridType::Ptr curl(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); template inline typename GridType::Ptr curl(const GridType& grid, bool threaded = true) { return curl(grid, threaded, NULL); } template inline typename GridType::Ptr curl(const GridType& grid, const MaskT& mask, bool threaded = true) { return curl(grid, mask, threaded, NULL); } /// @brief Compute the divergence of the given vector-valued grid. /// @return a new scalar-valued grid with the same numerical precision as the input grid /// (for example, if the input grid is a Vec3DGrid, the output grid will be a DoubleGrid) /// When a mask grid is specified, the solution is calculated only in the intersection of /// the mask active topology and the input active topology independent of the /// transforms associated with either grid. template inline typename VectorToScalarConverter::Type::Ptr divergence(const GridType& grid, bool threaded, InterruptT* interrupt); template inline typename VectorToScalarConverter::Type::Ptr divergence(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); template inline typename VectorToScalarConverter::Type::Ptr divergence(const GridType& grid, bool threaded = true) { return divergence(grid, threaded, NULL); } template inline typename VectorToScalarConverter::Type::Ptr divergence(const GridType& grid, const MaskT& mask, bool threaded = true) { return divergence(grid, mask, threaded, NULL); } /// @brief Compute the gradient of the given scalar grid. /// @return a new vector-valued grid with the same numerical precision as the input grid /// (for example, if the input grid is a DoubleGrid, the output grid will be a Vec3DGrid) /// When a mask grid is specified, the solution is calculated only in the intersection of /// the mask active topology and the input active topology independent of the /// transforms associated with either grid. template inline typename ScalarToVectorConverter::Type::Ptr gradient(const GridType& grid, bool threaded, InterruptT* interrupt); template inline typename ScalarToVectorConverter::Type::Ptr gradient(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); template inline typename ScalarToVectorConverter::Type::Ptr gradient(const GridType& grid, bool threaded = true) { return gradient(grid, threaded, NULL); } template inline typename ScalarToVectorConverter::Type::Ptr gradient(const GridType& grid, const MaskT& mask, bool threaded = true) { return gradient(grid, mask, threaded, NULL); } /// @brief Compute the Laplacian of the given scalar grid. /// @return a new scalar grid /// When a mask grid is specified, the solution is calculated only in the intersection of /// the mask active topology and the input active topology independent of the /// transforms associated with either grid. template inline typename GridType::Ptr laplacian(const GridType& grid, bool threaded, InterruptT* interrupt); template inline typename GridType::Ptr laplacian(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); template inline typename GridType::Ptr laplacian(const GridType& grid, bool threaded = true) { return laplacian(grid, threaded, NULL); } template inline typename GridType::Ptr laplacian(const GridType& grid, const MaskT mask, bool threaded = true) { return laplacian(grid, mask, threaded, NULL); } /// @brief Compute the mean curvature of the given grid. /// @return a new grid /// When a mask grid is specified, the solution is calculated only in the intersection of /// the mask active topology and the input active topology independent of the /// transforms associated with either grid. template inline typename GridType::Ptr meanCurvature(const GridType& grid, bool threaded, InterruptT* interrupt); template inline typename GridType::Ptr meanCurvature(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); template inline typename GridType::Ptr meanCurvature(const GridType& grid, bool threaded = true) { return meanCurvature(grid, threaded, NULL); } template inline typename GridType::Ptr meanCurvature(const GridType& grid, const MaskT& mask, bool threaded = true) { return meanCurvature(grid, mask, threaded, NULL); } /// @brief Compute the magnitudes of the vectors of the given vector-valued grid. /// @return a new scalar-valued grid with the same numerical precision as the input grid /// (for example, if the input grid is a Vec3DGrid, the output grid will be a DoubleGrid) /// When a mask grid is specified, the solution is calculated only in the intersection of /// the mask active topology and the input active topology independent of the /// transforms associated with either grid. template inline typename VectorToScalarConverter::Type::Ptr magnitude(const GridType& grid, bool threaded, InterruptT* interrupt); template inline typename VectorToScalarConverter::Type::Ptr magnitude(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); template inline typename VectorToScalarConverter::Type::Ptr magnitude(const GridType& grid, bool threaded = true) { return magnitude(grid, threaded, NULL); } template inline typename VectorToScalarConverter::Type::Ptr magnitude(const GridType& grid, const MaskT& mask, bool threaded = true) { return magnitude(grid, mask, threaded, NULL); } /// @brief Normalize the vectors of the given vector-valued grid. /// @return a new vector-valued grid /// When a mask grid is specified, the solution is calculated only in the intersection of /// the mask active topology and the input active topology independent of the /// transforms associated with either grid. template inline typename GridType::Ptr normalize(const GridType& grid, bool threaded, InterruptT* interrupt); template inline typename GridType::Ptr normalize(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt); template inline typename GridType::Ptr normalize(const GridType& grid, bool threaded = true) { return normalize(grid, threaded, NULL); } template inline typename GridType::Ptr normalize(const GridType& grid, const MaskT& mask, bool threaded = true) { return normalize(grid, mask, threaded, NULL); } //////////////////////////////////////// namespace { /// @brief Apply an operator on an input grid to produce an output grid /// with the same topology but a possibly different value type. /// @details To facilitate inlining, this class is also templated on a Map type. /// /// @note This is a helper class and should never be used directly. /// /// @note The current implementation assumes all the input /// values are represented by leaf voxels and not tiles. In the /// future we will expand this class to also handle tile values. template class GridOperator { public: typedef typename OutGridT::TreeType OutTreeT; typedef typename OutTreeT::LeafNodeType OutLeafT; typedef typename tree::LeafManager LeafManagerT; GridOperator(const InGridT& grid, const MaskGridType* mask, const MapT& map, InterruptT* interrupt = NULL): mAcc(grid.getConstAccessor()), mMap(map), mInterrupt(interrupt), mMask(mask) { } virtual ~GridOperator() {} typename OutGridT::Ptr process(bool threaded = true) { if (mInterrupt) mInterrupt->start("Processing grid"); // Derive background value of the output grid typename InGridT::TreeType tmp(mAcc.tree().background()); typename OutGridT::ValueType backg = OperatorT::result(mMap, tmp, math::Coord(0)); // output tree = topology copy of input tree! typename OutTreeT::Ptr tree(new OutTreeT(mAcc.tree(), backg, TopologyCopy())); // create grid with output tree and unit transform typename OutGridT::Ptr result(new OutGridT(tree)); // Modify the solution area if a mask was supplied. if (mMask) { result->topologyIntersection(*mMask); } // transform of output grid = transform of input grid result->setTransform(math::Transform::Ptr(new math::Transform( mMap.copy() ))); LeafManagerT leafManager(*tree); if (threaded) { tbb::parallel_for(leafManager.leafRange(), *this); } else { (*this)(leafManager.leafRange()); } if (mInterrupt) mInterrupt->end(); return result; } /// @brief Iterate sequentially over LeafNodes and voxels in the output /// grid and compute the laplacian using a valueAccessor for the /// input grid. /// /// @note Never call this public method directly - it is called by /// TBB threads only! void operator()(const typename LeafManagerT::LeafRange& range) const { if (util::wasInterrupted(mInterrupt)) tbb::task::self().cancel_group_execution(); for (typename LeafManagerT::LeafRange::Iterator leaf=range.begin(); leaf; ++leaf) { for (typename OutLeafT::ValueOnIter value=leaf->beginValueOn(); value; ++value) { value.setValue(OperatorT::result(mMap, mAcc, value.getCoord())); } } } protected: typedef typename InGridT::ConstAccessor AccessorT; mutable AccessorT mAcc; const MapT& mMap; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of GridOperator class } //end of anonymous namespaceo //////////////////////////////////////// /// @brief Compute the closest-point transform of a scalar grid. template class Cpt { public: typedef InGridT InGridType; typedef typename ScalarToVectorConverter::Type OutGridType; Cpt(const InGridType& grid, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) { } Cpt(const InGridType& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) { } typename OutGridType::Ptr process(bool threaded = true, bool useWorldTransform = true) { Functor functor(mInputGrid, mMask, threaded, useWorldTransform, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } private: struct IsOpT { template static typename OutGridType::ValueType result(const MapT& map, const AccT& acc, const Coord& xyz) { return math::CPT::result(map, acc, xyz); } }; struct WsOpT { template static typename OutGridType::ValueType result(const MapT& map, const AccT& acc, const Coord& xyz) { return math::CPT_RANGE::result(map, acc, xyz); } }; struct Functor { Functor(const InGridType& grid, const MaskGridType* mask, bool threaded, bool worldspace, InterruptT* interrupt): mThreaded(threaded), mWorldSpace(worldspace), mInputGrid(grid), mInterrupt(interrupt), mMask(mask){} template void operator()(const MapT& map) { if (mWorldSpace) { GridOperator op(mInputGrid, mMask, map, mInterrupt); mOutputGrid = op.process(mThreaded); // cache the result } else { GridOperator op(mInputGrid, mMask, map, mInterrupt); mOutputGrid = op.process(mThreaded); // cache the result } } const bool mThreaded; const bool mWorldSpace; const InGridType& mInputGrid; typename OutGridType::Ptr mOutputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; const InGridType& mInputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of Cpt class //////////////////////////////////////// /// @brief Compute the curl of a scalar grid. template class Curl { public: typedef GridT InGridType; typedef GridT OutGridType; Curl(const GridT& grid, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) { } Curl(const GridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) { } typename GridT::Ptr process(bool threaded = true) { Functor functor(mInputGrid, mMask, threaded, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } private: struct Functor { Functor(const GridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask){} template void operator()(const MapT& map) { typedef math::Curl OpT; GridOperator op(mInputGrid, mMask, map, mInterrupt); mOutputGrid = op.process(mThreaded); // cache the result } const bool mThreaded; const GridT& mInputGrid; typename GridT::Ptr mOutputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // Private Functor const GridT& mInputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of Curl class //////////////////////////////////////// /// @brief Computes the Divergence of a scalar grid template class Divergence { public: typedef InGridT InGridType; typedef typename VectorToScalarConverter::Type OutGridType; Divergence(const InGridT& grid, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) { } Divergence(const InGridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) { } typename OutGridType::Ptr process(bool threaded = true) { if( mInputGrid.getGridClass() == GRID_STAGGERED ) { Functor functor(mInputGrid, mMask, threaded, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } else { Functor functor(mInputGrid, mMask, threaded, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } } protected: template struct Functor { Functor(const InGridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} template void operator()(const MapT& map) { typedef math::Divergence OpT; GridOperator op(mInputGrid, mMask, map, mInterrupt); mOutputGrid = op.process(mThreaded); // cache the result } const bool mThreaded; const InGridType& mInputGrid; typename OutGridType::Ptr mOutputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // Private Functor const InGridType& mInputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of Divergence class //////////////////////////////////////// /// @brief Computes the Gradient of a scalar grid template class Gradient { public: typedef InGridT InGridType; typedef typename ScalarToVectorConverter::Type OutGridType; Gradient(const InGridT& grid, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) { } Gradient(const InGridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) { } typename OutGridType::Ptr process(bool threaded = true) { Functor functor(mInputGrid, mMask, threaded, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } protected: struct Functor { Functor(const InGridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} template void operator()(const MapT& map) { typedef math::Gradient OpT; GridOperator op(mInputGrid, mMask, map, mInterrupt); mOutputGrid = op.process(mThreaded); // cache the result } const bool mThreaded; const InGridT& mInputGrid; typename OutGridType::Ptr mOutputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // Private Functor const InGridT& mInputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of Gradient class //////////////////////////////////////// /// @brief Computes the Laplacian of a scalar grid template class Laplacian { public: typedef GridT InGridType; typedef GridT OutGridType; Laplacian(const GridT& grid, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) { } Laplacian(const GridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) { } typename GridT::Ptr process(bool threaded = true) { Functor functor(mInputGrid, mMask, threaded, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } protected: struct Functor { Functor(const GridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} template void operator()(const MapT& map) { typedef math::Laplacian OpT; GridOperator op(mInputGrid, mMask, map); mOutputGrid = op.process(mThreaded); // cache the result } const bool mThreaded; const GridT& mInputGrid; typename GridT::Ptr mOutputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // Private Functor const GridT& mInputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of Laplacian class //////////////////////////////////////// template class MeanCurvature { public: typedef GridT InGridType; typedef GridT OutGridType; MeanCurvature(const GridT& grid, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) { } MeanCurvature(const GridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) { } typename GridT::Ptr process(bool threaded = true) { Functor functor(mInputGrid, mMask, threaded, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } protected: struct Functor { Functor(const GridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} template void operator()(const MapT& map) { typedef math::MeanCurvature OpT; GridOperator op(mInputGrid, mMask, map); mOutputGrid = op.process(mThreaded); // cache the result } const bool mThreaded; const GridT& mInputGrid; typename GridT::Ptr mOutputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // Private Functor const GridT& mInputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of MeanCurvature class //////////////////////////////////////// template class Magnitude { public: typedef InGridT InGridType; typedef typename VectorToScalarConverter::Type OutGridType; Magnitude(const InGridType& grid, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) { } Magnitude(const InGridType& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) { } typename OutGridType::Ptr process(bool threaded = true) { Functor functor(mInputGrid, mMask, threaded, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } protected: struct OpT { template static typename OutGridType::ValueType result(const MapT&, const AccT& acc, const Coord& xyz) { return acc.getValue(xyz).length();} }; struct Functor { Functor(const InGridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} template void operator()(const MapT& map) { GridOperator op(mInputGrid, mMask, map); mOutputGrid = op.process(mThreaded); // cache the result } const bool mThreaded; const InGridType& mInputGrid; typename OutGridType::Ptr mOutputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // Private Functor const InGridType& mInputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of Magnitude class //////////////////////////////////////// template class Normalize { public: typedef GridT InGridType; typedef GridT OutGridType; Normalize(const GridT& grid, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(NULL) { } Normalize(const GridT& grid, const MaskGridType& mask, InterruptT* interrupt = NULL): mInputGrid(grid), mInterrupt(interrupt), mMask(&mask) { } typename GridT::Ptr process(bool threaded = true) { Functor functor(mInputGrid, mMask, threaded, mInterrupt); processTypedMap(mInputGrid.transform(), functor); return functor.mOutputGrid; } protected: struct OpT { template static typename OutGridType::ValueType result(const MapT&, const AccT& acc, const Coord& xyz) { typename OutGridType::ValueType vec = acc.getValue(xyz); if ( !vec.normalize() ) vec.setZero(); return vec; } }; struct Functor { Functor(const GridT& grid, const MaskGridType* mask, bool threaded, InterruptT* interrupt): mThreaded(threaded), mInputGrid(grid), mInterrupt(interrupt), mMask(mask) {} template void operator()(const MapT& map) { GridOperator op(mInputGrid, mMask,map); mOutputGrid = op.process(mThreaded); // cache the result } const bool mThreaded; const GridT& mInputGrid; typename GridT::Ptr mOutputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // Private Functor const GridT& mInputGrid; InterruptT* mInterrupt; const MaskGridType* mMask; }; // end of Normalize class //////////////////////////////////////// template inline typename ScalarToVectorConverter::Type::Ptr cpt(const GridType& grid, bool threaded, InterruptT* interrupt) { Cpt op(grid, interrupt); return op.process(threaded); } template inline typename ScalarToVectorConverter::Type::Ptr cpt(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) { Cpt op(grid, mask, interrupt); return op.process(threaded); } template inline typename GridType::Ptr curl(const GridType& grid, bool threaded, InterruptT* interrupt) { Curl op(grid, interrupt); return op.process(threaded); } template inline typename GridType::Ptr curl(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) { Curl op(grid, mask, interrupt); return op.process(threaded); } template inline typename VectorToScalarConverter::Type::Ptr divergence(const GridType& grid, bool threaded, InterruptT* interrupt) { Divergence op(grid, interrupt); return op.process(threaded); } template inline typename VectorToScalarConverter::Type::Ptr divergence(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) { Divergence op(grid, mask, interrupt); return op.process(threaded); } template inline typename ScalarToVectorConverter::Type::Ptr gradient(const GridType& grid, bool threaded, InterruptT* interrupt) { Gradient op(grid, interrupt); return op.process(threaded); } template inline typename ScalarToVectorConverter::Type::Ptr gradient(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) { Gradient op(grid, mask, interrupt); return op.process(threaded); } template inline typename GridType::Ptr laplacian(const GridType& grid, bool threaded, InterruptT* interrupt) { Laplacian op(grid, interrupt); return op.process(threaded); } template inline typename GridType::Ptr laplacian(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) { Laplacian op(grid, mask, interrupt); return op.process(threaded); } template inline typename GridType::Ptr meanCurvature(const GridType& grid, bool threaded, InterruptT* interrupt) { MeanCurvature op(grid, interrupt); return op.process(threaded); } template inline typename GridType::Ptr meanCurvature(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) { MeanCurvature op(grid, mask, interrupt); return op.process(threaded); } template inline typename VectorToScalarConverter::Type::Ptr magnitude(const GridType& grid, bool threaded, InterruptT* interrupt) { Magnitude op(grid, interrupt); return op.process(threaded); } template inline typename VectorToScalarConverter::Type::Ptr magnitude(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) { Magnitude op(grid, mask, interrupt); return op.process(threaded); } template inline typename GridType::Ptr normalize(const GridType& grid, bool threaded, InterruptT* interrupt) { Normalize op(grid, interrupt); return op.process(threaded); } template inline typename GridType::Ptr normalize(const GridType& grid, const MaskT& mask, bool threaded, InterruptT* interrupt) { Normalize op(grid, mask, interrupt); return op.process(threaded); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_GRID_OPERATORS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetRebuild.h0000644000000000000000000003110712252453157015564 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Return a new grid of type @c GridType that contains a narrow-band level set /// representation of an isosurface of a given grid. /// /// @param grid a scalar, floating-point grid with one or more disjoint, /// closed isosurfaces at the given @a isovalue /// @param isovalue the isovalue that defines the implicit surface (defaults to zero, /// which is typical if the input grid is already a level set or a SDF). /// @param halfWidth half the width of the narrow band, in voxel units /// (defaults to 3 voxels, which is required for some level set operations) /// @param xform optional transform for the output grid /// (if not provided, the transform of the input @a grid will be matched) /// /// @throw TypeError if @a grid is not scalar or not floating-point /// /// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. template inline typename GridType::Ptr levelSetRebuild(const GridType& grid, float isovalue = 0, float halfWidth = float(LEVEL_SET_HALF_WIDTH), const math::Transform* xform = NULL); /// @brief Return a new grid of type @c GridType that contains a narrow-band level set /// representation of an isosurface of a given grid. /// /// @param grid a scalar, floating-point grid with one or more disjoint, /// closed isosurfaces at the given @a isovalue /// @param isovalue the isovalue that defines the implicit surface /// @param exBandWidth the exterior narrow-band width in voxel units /// @param inBandWidth the interior narrow-band width in voxel units /// @param xform optional transform for the output grid /// (if not provided, the transform of the input @a grid will be matched) /// /// @throw TypeError if @a grid is not scalar or not floating-point /// /// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. template inline typename GridType::Ptr levelSetRebuild(const GridType& grid, float isovalue, float exBandWidth, float inBandWidth, const math::Transform* xform = NULL); /// @brief Return a new grid of type @c GridType that contains a narrow-band level set /// representation of an isosurface of a given grid. /// /// @param grid a scalar, floating-point grid with one or more disjoint, /// closed isosurfaces at the given @a isovalue /// @param isovalue the isovalue that defines the implicit surface /// @param exBandWidth the exterior narrow-band width in voxel units /// @param inBandWidth the interior narrow-band width in voxel units /// @param xform optional transform for the output grid /// (if not provided, the transform of the input @a grid will be matched) /// @param interrupter optional interrupter object /// /// @throw TypeError if @a grid is not scalar or not floating-point /// /// @note If the input grid contains overlapping isosurfaces, interior edges will be lost. template inline typename GridType::Ptr levelSetRebuild(const GridType& grid, float isovalue, float exBandWidth, float inBandWidth, const math::Transform* xform = NULL, InterruptT* interrupter = NULL); //////////////////////////////////////// // Internal utility objects and implementation details namespace internal { class PointListTransform { public: PointListTransform(const PointList& pointsIn, std::vector& pointsOut, const math::Transform& xform) : mPointsIn(pointsIn) , mPointsOut(&pointsOut) , mXform(xform) { } void runParallel() { tbb::parallel_for(tbb::blocked_range(0, mPointsOut->size()), *this); } void runSerial() { (*this)(tbb::blocked_range(0, mPointsOut->size())); } inline void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(); n < range.end(); ++n) { (*mPointsOut)[n] = mXform.worldToIndex(mPointsIn[n]); } } private: const PointList& mPointsIn; std::vector * const mPointsOut; const math::Transform& mXform; }; class PrimCpy { public: PrimCpy(const PolygonPoolList& primsIn, const std::vector& indexList, std::vector& primsOut) : mPrimsIn(primsIn) , mIndexList(indexList) , mPrimsOut(&primsOut) { } void runParallel() { tbb::parallel_for(tbb::blocked_range(0, mIndexList.size()), *this); } void runSerial() { (*this)(tbb::blocked_range(0, mIndexList.size())); } inline void operator()(const tbb::blocked_range& range) const { openvdb::Vec4I quad; quad[3] = openvdb::util::INVALID_IDX; std::vector& primsOut = *mPrimsOut; for (size_t n = range.begin(); n < range.end(); ++n) { size_t index = mIndexList[n]; PolygonPool& polygons = mPrimsIn[n]; // Copy quads for (size_t i = 0, I = polygons.numQuads(); i < I; ++i) { primsOut[index++] = polygons.quad(i); } polygons.clearQuads(); // Copy triangles (adaptive mesh) for (size_t i = 0, I = polygons.numTriangles(); i < I; ++i) { const openvdb::Vec3I& triangle = polygons.triangle(i); quad[0] = triangle[0]; quad[1] = triangle[1]; quad[2] = triangle[2]; primsOut[index++] = quad; } polygons.clearTriangles(); } } private: const PolygonPoolList& mPrimsIn; const std::vector& mIndexList; std::vector * const mPrimsOut; }; } // namespace internal //////////////////////////////////////// /// The normal entry points for level set rebuild are the levelSetRebuild() functions. /// doLevelSetRebuild() is mainly for internal use, but when the isovalue and half band /// widths are given in ValueType units (for example, if they are queried from /// a grid), it might be more convenient to call this function directly. /// /// @internal This overload is enabled only for grids with a scalar, floating-point ValueType. template inline typename boost::enable_if, typename GridType::Ptr>::type doLevelSetRebuild(const GridType& grid, typename GridType::ValueType iso, typename GridType::ValueType exWidth, typename GridType::ValueType inWidth, const math::Transform* xform, InterruptT* interrupter) { const float isovalue = float(iso), exBandWidth = float(exWidth), inBandWidth = float(inWidth); tools::VolumeToMesh mesher(isovalue, 0.0005); mesher(grid); math::Transform::Ptr transform = (xform != NULL) ? xform->copy() : grid.transform().copy(); std::vector points(mesher.pointListSize()); { // Copy and transform (required for MeshToVolume) points to grid space. internal::PointListTransform ptnXForm(mesher.pointList(), points, *transform); ptnXForm.runParallel(); mesher.pointList().reset(NULL); } std::vector primitives; { // Copy primitives. PolygonPoolList& polygonPoolList = mesher.polygonPoolList(); size_t numPrimitives = 0; std::vector indexlist(mesher.polygonPoolListSize()); for (size_t n = 0, N = mesher.polygonPoolListSize(); n < N; ++n) { const openvdb::tools::PolygonPool& polygons = polygonPoolList[n]; indexlist[n] = numPrimitives; numPrimitives += polygons.numQuads(); numPrimitives += polygons.numTriangles(); } primitives.resize(numPrimitives); internal::PrimCpy primCpy(polygonPoolList, indexlist, primitives); primCpy.runParallel(); } MeshToVolume vol(transform, OUTPUT_RAW_DATA, interrupter); vol.convertToLevelSet(points, primitives, exBandWidth, inBandWidth); return vol.distGridPtr(); } /// @internal This overload is enabled only for grids that do not have a scalar, /// floating-point ValueType. template inline typename boost::disable_if, typename GridType::Ptr>::type doLevelSetRebuild(const GridType&, typename GridType::ValueType /*isovalue*/, typename GridType::ValueType /*exWidth*/, typename GridType::ValueType /*inWidth*/, const math::Transform*, InterruptT*) { OPENVDB_THROW(TypeError, "level set rebuild is supported only for scalar, floating-point grids"); } //////////////////////////////////////// template inline typename GridType::Ptr levelSetRebuild(const GridType& grid, float iso, float exWidth, float inWidth, const math::Transform* xform, InterruptT* interrupter) { typedef typename GridType::ValueType ValueT; ValueT isovalue(zeroVal() + iso), exBandWidth(zeroVal() + exWidth), inBandWidth(zeroVal() + inWidth); return doLevelSetRebuild(grid, isovalue, exBandWidth, inBandWidth, xform, interrupter); } template inline typename GridType::Ptr levelSetRebuild(const GridType& grid, float iso, float exWidth, float inWidth, const math::Transform* xform) { typedef typename GridType::ValueType ValueT; ValueT isovalue(zeroVal() + iso), exBandWidth(zeroVal() + exWidth), inBandWidth(zeroVal() + inWidth); return doLevelSetRebuild( grid, isovalue, exBandWidth, inBandWidth, xform, NULL); } template inline typename GridType::Ptr levelSetRebuild(const GridType& grid, float iso, float halfVal, const math::Transform* xform) { typedef typename GridType::ValueType ValueT; ValueT isovalue(zeroVal() + iso), halfWidth(zeroVal() + halfVal); return doLevelSetRebuild( grid, isovalue, halfWidth, halfWidth, xform, NULL); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVELSETREBUILD_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetTracker.h0000644000000000000000000005027612252453157015601 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file LevelSetTracker.h /// /// @brief Performs multi-threaded interface tracking of narrow band /// level sets. This is the building-block for most level set /// computations that involve dynamic topology, e.g. advection. #ifndef OPENVDB_TOOLS_LEVEL_SET_TRACKER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVEL_SET_TRACKER_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Morphology.h"//for tools::dilateVoxels namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Performs multi-threaded interface tracking of narrow band level sets template class LevelSetTracker { public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename TreeType::LeafNodeType LeafType; typedef typename TreeType::ValueType ValueType; typedef typename tree::LeafManager LeafManagerType; // leafs + buffers typedef typename LeafManagerType::RangeType RangeType; typedef typename LeafManagerType::LeafRange LeafRange; typedef typename LeafManagerType::BufferType BufferType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); /// Main constructor /// @throw RuntimeError if the grid is not a level set LevelSetTracker(GridT& grid, InterruptT* interrupt = NULL); /// Shallow copy constructor called by tbb::parallel_for() threads during filtering LevelSetTracker(const LevelSetTracker& other); virtual ~LevelSetTracker() { if (mIsMaster) delete mLeafs; } /// Iterative normalization, i.e. solving the Eikonal equation void normalize(); /// Track the level set interface, i.e. rebuild and normalize the /// narrow band of the level set. void track(); /// Remove voxels that are outside the narrow band. (substep of track) void prune(); /// @return the spatial finite difference scheme math::BiasedGradientScheme getSpatialScheme() const { return mSpatialScheme; } /// @brief Set the spatial finite difference scheme void setSpatialScheme(math::BiasedGradientScheme scheme) { mSpatialScheme = scheme; } /// @return the temporal integration scheme math::TemporalIntegrationScheme getTemporalScheme() const { return mTemporalScheme; } /// @brief Set the spatial finite difference scheme void setTemporalScheme(math::TemporalIntegrationScheme scheme) { mTemporalScheme = scheme; } /// @return The number of normalizations performed per track or /// normalize call. int getNormCount() const { return mNormCount; } /// @brief Set the number of normalizations performed per track or /// normalize call. void setNormCount(int n) { mNormCount = n; } /// @return the grain-size used for multi-threading int getGrainSize() const { return mGrainSize; } /// @brief Set the grain-size used for multi-threading. /// @note A grainsize of 0 or less disables multi-threading! void setGrainSize(int grainsize) { mGrainSize = grainsize; } ValueType voxelSize() const { return mDx; } void startInterrupter(const char* msg); void endInterrupter(); /// @return false if the process was interrupted bool checkInterrupter(); const GridType& grid() const { return *mGrid; } LeafManagerType& leafs() { return *mLeafs; } const LeafManagerType& leafs() const { return *mLeafs; } /// @brief Public functor called by tbb::parallel_for() /// @note Never call this method directly void operator()(const RangeType& r) const { if (mTask) mTask(const_cast(this), r); else OPENVDB_THROW(ValueError, "task is undefined - call track(), etc"); } private: template struct Normalizer { Normalizer(LevelSetTracker& tracker): mTracker(tracker), mTask(0) {} void normalize(); void operator()(const RangeType& r) const {mTask(const_cast(this), r);} typedef typename boost::function FuncType; LevelSetTracker& mTracker; FuncType mTask; void cook(int swapBuffer=0); void euler1(const RangeType& range, ValueType dt, Index resultBuffer); void euler2(const RangeType& range, ValueType dt, ValueType alpha, Index phiBuffer, Index resultBuffer); }; // end of protected Normalizer class typedef typename boost::function FuncType; void trim(const RangeType& r); template void normalize1(); template void normalize2(); // Throughout the methods below mLeafs is always assumed to contain // a list of the current LeafNodes! The auxiliary buffers on the // other hand always have to be allocated locally, since some // methods need them and others don't! GridType* mGrid; LeafManagerType* mLeafs; InterruptT* mInterrupter; const ValueType mDx; math::BiasedGradientScheme mSpatialScheme; math::TemporalIntegrationScheme mTemporalScheme; int mNormCount;// Number of iteratations of normalization int mGrainSize; FuncType mTask; const bool mIsMaster; // disallow copy by assignment void operator=(const LevelSetTracker& other) {} }; // end of LevelSetTracker class template LevelSetTracker::LevelSetTracker(GridT& grid, InterruptT* interrupt): mGrid(&grid), mLeafs(new LeafManagerType(grid.tree())), mInterrupter(interrupt), mDx(grid.voxelSize()[0]), mSpatialScheme(math::HJWENO5_BIAS), mTemporalScheme(math::TVD_RK1), mNormCount(static_cast(LEVEL_SET_HALF_WIDTH)), mGrainSize(1), mTask(0), mIsMaster(true)// N.B. { if ( !grid.hasUniformVoxels() ) { OPENVDB_THROW(RuntimeError, "The transform must have uniform scale for the LevelSetTracker to function"); } if ( grid.getGridClass() != GRID_LEVEL_SET) { OPENVDB_THROW(RuntimeError, "LevelSetTracker only supports level sets!\n" "However, only level sets are guaranteed to work!\n" "Hint: Grid::setGridClass(openvdb::GRID_LEVEL_SET)"); } } template LevelSetTracker::LevelSetTracker(const LevelSetTracker& other): mGrid(other.mGrid), mLeafs(other.mLeafs), mInterrupter(other.mInterrupter), mDx(other.mDx), mSpatialScheme(other.mSpatialScheme), mTemporalScheme(other.mTemporalScheme), mNormCount(other.mNormCount), mGrainSize(other.mGrainSize), mTask(other.mTask), mIsMaster(false)// N.B. { } template inline void LevelSetTracker::prune() { this->startInterrupter("Pruning Level Set"); // Prune voxels that are too far away from the zero-crossing mTask = boost::bind(&LevelSetTracker::trim, _1, _2); if (mGrainSize>0) { tbb::parallel_for(mLeafs->getRange(mGrainSize), *this); } else { (*this)(mLeafs->getRange()); } // Remove inactive nodes from tree mGrid->tree().pruneLevelSet(); // The tree topology has changes so rebuild the list of leafs mLeafs->rebuildLeafArray(); this->endInterrupter(); } template inline void LevelSetTracker::track() { // Dilate narrow-band (this also rebuilds the leaf array!) tools::dilateVoxels(*mLeafs); // Compute signed distances in dilated narrow-band this->normalize(); // Remove voxels that are outside the narrow band this->prune(); } template inline void LevelSetTracker::startInterrupter(const char* msg) { if (mInterrupter) mInterrupter->start(msg); } template inline void LevelSetTracker::endInterrupter() { if (mInterrupter) mInterrupter->end(); } template inline bool LevelSetTracker::checkInterrupter() { if (util::wasInterrupted(mInterrupter)) { tbb::task::self().cancel_group_execution(); return false; } return true; } /// Prunes away voxels that have moved outside the narrow band template inline void LevelSetTracker::trim(const RangeType& range) { typedef typename LeafType::ValueOnIter VoxelIterT; const_cast(this)->checkInterrupter(); const ValueType gamma = mGrid->background(); for (size_t n=range.begin(), e=range.end(); n != e; ++n) { LeafType &leaf = mLeafs->leaf(n); for (VoxelIterT iter = leaf.beginValueOn(); iter; ++iter) { const ValueType val = *iter; if (val < -gamma) leaf.setValueOff(iter.pos(), -gamma); else if (val > gamma) leaf.setValueOff(iter.pos(), gamma); } } } template inline void LevelSetTracker::normalize() { switch (mSpatialScheme) { case math::FIRST_BIAS: this->normalize1(); break; case math::SECOND_BIAS: this->normalize1(); break; case math::THIRD_BIAS: this->normalize1(); break; case math::WENO5_BIAS: this->normalize1(); break; case math::HJWENO5_BIAS: this->normalize1(); break; default: OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); } } template template inline void LevelSetTracker::normalize1() { switch (mTemporalScheme) { case math::TVD_RK1: this->normalize2(); break; case math::TVD_RK2: this->normalize2(); break; case math::TVD_RK3: this->normalize2(); break; default: OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); } } template template inline void LevelSetTracker::normalize2() { Normalizer tmp(*this); tmp.normalize(); } template template inline void LevelSetTracker::Normalizer:: normalize() { /// Make sure we have enough temporal auxiliary buffers mTracker.mLeafs->rebuildAuxBuffers(TemporalScheme == math::TVD_RK3 ? 2 : 1); const ValueType dt = (TemporalScheme == math::TVD_RK1 ? ValueType(0.3) : TemporalScheme == math::TVD_RK2 ? ValueType(0.9) : ValueType(1.0)) * ValueType(mTracker.voxelSize()); for (int n=0, e=mTracker.getNormCount(); n < e; ++n) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN switch(TemporalScheme) {//switch is resolved at compile-time case math::TVD_RK1: //std::cerr << "1"; // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(0) = Phi_t0(0) - dt * VdotG_t0(1) mTask = boost::bind(&Normalizer::euler1, _1, _2, dt, /*result=*/1); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(1); break; case math::TVD_RK2: //std::cerr << "2"; // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(1) mTask = boost::bind(&Normalizer::euler1, _1, _2, dt, /*result=*/1); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(1); // Convex combine explict Euler step: t2 = t0 + dt // Phi_t2(1) = 1/2 * Phi_t0(1) + 1/2 * (Phi_t1(0) - dt * V.Grad_t1(0)) mTask = boost::bind(&Normalizer::euler2, _1, _2, dt, ValueType(0.5), /*phi=*/1, /*result=*/1); // Cook and swap buffer 0 and 1 such that Phi_t2(0) and Phi_t1(1) this->cook(1); break; case math::TVD_RK3: //std::cerr << "3"; // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * VdotG_t0(1) mTask = boost::bind(&Normalizer::euler1, _1, _2, dt, /*result=*/1); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(1); // Convex combine explict Euler step: t2 = t0 + dt/2 // Phi_t2(2) = 3/4 * Phi_t0(1) + 1/4 * (Phi_t1(0) - dt * V.Grad_t1(0)) mTask = boost::bind(&Normalizer::euler2, _1, _2, dt, ValueType(0.75), /*phi=*/1, /*result=*/2); // Cook and swap buffer 0 and 2 such that Phi_t2(0) and Phi_t1(2) this->cook(2); // Convex combine explict Euler step: t3 = t0 + dt // Phi_t3(2) = 1/3 * Phi_t0(1) + 2/3 * (Phi_t2(0) - dt * V.Grad_t2(0) mTask = boost::bind(&Normalizer::euler2, _1, _2, dt, ValueType(1.0/3.0), /*phi=*/1, /*result=*/2); // Cook and swap buffer 0 and 2 such that Phi_t3(0) and Phi_t2(2) this->cook(2); break; default: OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } mTracker.mLeafs->removeAuxBuffers(); } /// Private method to perform the task (serial or threaded) and /// subsequently swap the leaf buffers. template template inline void LevelSetTracker::Normalizer:: cook(int swapBuffer) { mTracker.startInterrupter("Normalizing Level Set"); if (mTracker.getGrainSize()>0) { tbb::parallel_for(mTracker.mLeafs->getRange(mTracker.getGrainSize()), *this); } else { (*this)(mTracker.mLeafs->getRange()); } mTracker.mLeafs->swapLeafBuffer(swapBuffer, mTracker.getGrainSize()==0); mTracker.endInterrupter(); } /// Perform normalization using one of the upwinding schemes /// This currently supports only forward Euler time integration /// and is not expected to work well with the higher-order spactial schemes template template inline void LevelSetTracker::Normalizer:: euler1(const RangeType &range, ValueType dt, Index resultBuffer) { typedef math::BIAS_SCHEME Scheme; typedef typename Scheme::template ISStencil::StencilType Stencil; typedef typename LeafType::ValueOnCIter VoxelIterT; mTracker.checkInterrupter(); const ValueType one(1.0), invDx = one/mTracker.voxelSize(); Stencil stencil(mTracker.grid()); for (size_t n=range.begin(), e=range.end(); n != e; ++n) { BufferType& result = mTracker.mLeafs->getBuffer(n, resultBuffer); const LeafType& leaf = mTracker.mLeafs->leaf(n); for (VoxelIterT iter = leaf.cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); const ValueType normSqGradPhi = math::ISGradientNormSqrd::result(stencil); const ValueType phi0 = stencil.getValue(); const ValueType diff = math::Sqrt(normSqGradPhi)*invDx - one; const ValueType S = phi0 / (math::Sqrt(math::Pow2(phi0) + normSqGradPhi)); result.setValue(iter.pos(), phi0 - dt * S * diff); } } } template template inline void LevelSetTracker::Normalizer:: euler2(const RangeType& range, ValueType dt, ValueType alpha, Index phiBuffer, Index resultBuffer) { typedef math::BIAS_SCHEME Scheme; typedef typename Scheme::template ISStencil::StencilType Stencil; typedef typename LeafType::ValueOnCIter VoxelIterT; mTracker.checkInterrupter(); const ValueType one(1.0), beta = one - alpha, invDx = one/mTracker.voxelSize(); Stencil stencil(mTracker.grid()); for (size_t n=range.begin(), e=range.end(); n != e; ++n) { const BufferType& phi = mTracker.mLeafs->getBuffer(n, phiBuffer); BufferType& result = mTracker.mLeafs->getBuffer(n, resultBuffer); const LeafType& leaf = mTracker.mLeafs->leaf(n); for (VoxelIterT iter = leaf.cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); const ValueType normSqGradPhi = math::ISGradientNormSqrd::result(stencil); const ValueType phi0 = stencil.getValue(); const ValueType diff = math::Sqrt(normSqGradPhi)*invDx - one; const ValueType S = phi0 / (math::Sqrt(math::Pow2(phi0) + normSqGradPhi)); result.setValue(iter.pos(), alpha*phi[iter.pos()] + beta*(phi0 - dt * S * diff)); } } } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVEL_SET_TRACKER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Statistics.h0000644000000000000000000003414612252453157014672 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Statistics.h /// /// @brief Functions to efficiently compute histograms and statistics /// (mean, variance, etc.) of grid values #ifndef OPENVDB_TOOLS_STATISTICS_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_STATISTICS_HAS_BEEN_INCLUDED #include #include #include #include "ValueTransformer.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Iterate over a scalar grid and compute a histogram of the values /// of the voxels that are visited, or iterate over a vector-valued grid /// and compute a histogram of the magnitudes of the vectors. /// @param iter an iterator over the values of a grid or its tree /// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) /// @param minVal the smallest value that can be added to the histogram /// @param maxVal the largest value that can be added to the histogram /// @param numBins the number of histogram bins /// @param threaded if true, iterate over the grid in parallel template inline math::Histogram histogram(const IterT& iter, double minVal, double maxVal, size_t numBins = 10, bool threaded = true); /// @brief Iterate over a scalar grid and compute statistics (mean, variance, etc.) /// of the values of the voxels that are visited, or iterate over a vector-valued grid /// and compute statistics of the magnitudes of the vectors. /// @param iter an iterator over the values of a grid or its tree /// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) /// @param threaded if true, iterate over the grid in parallel template inline math::Stats statistics(const IterT& iter, bool threaded = true); /// @brief Iterate over a grid and compute statistics (mean, variance, etc.) of /// the values produced by applying the given functor at each voxel that is visited. /// @param iter an iterator over the values of a grid or its tree /// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) /// @param op a functor of the form void op(const IterT&, math::Stats&), /// where @c IterT is the type of @a iter, that inserts zero or more /// floating-point values into the provided @c math::Stats object /// @param threaded if true, iterate over the grid in parallel /// @note When @a threaded is true, each thread gets its own copy of the functor. /// /// @par Example: /// Compute statistics of just the active and positive-valued voxels of a scalar, /// floating-point grid. /// @code /// struct Local { /// static inline /// void addIfPositive(const FloatGrid::ValueOnCIter& iter, math::Stats& stats) /// { /// const float f = *iter; /// if (f > 0.0) { /// if (iter.isVoxelValue()) stats.add(f); /// else stats.add(f, iter.getVoxelCount()); /// } /// } /// }; /// FloatGrid grid = ...; /// math::Stats stats = /// tools::statistics(grid.cbeginValueOn(), Local::addIfPositive, /*threaded=*/true); /// @endcode template inline math::Stats statistics(const IterT& iter, const ValueOp& op, bool threaded); /// @brief Iterate over a grid and compute statistics (mean, variance, etc.) /// of the values produced by applying a given operator (see math/Operators.h) /// at each voxel that is visited. /// @param iter an iterator over the values of a grid or its tree /// (@c Grid::ValueOnCIter, @c Tree::ValueOffIter, etc.) /// @param op an operator object with a method of the form /// double result(Accessor&, const Coord&) /// @param threaded if true, iterate over the grid in parallel /// @note World-space operators, whose @c result() methods are of the form /// double result(const Map&, Accessor&, const Coord&), must be wrapped /// in a math::MapAdapter. /// @note Vector-valued operators like math::Gradient must be wrapped in an adapter /// such as math::OpMagnitude. /// /// @par Example: /// Compute statistics of the magnitude of the gradient at the active voxels of /// a scalar, floating-point grid. (Note the use of the math::MapAdapter and /// math::OpMagnitude adapters.) /// @code /// FloatGrid grid = ...; /// /// // Assume that we know that the grid has a uniform scale map. /// typedef math::UniformScaleMap MapType; /// // Specify a world-space gradient operator that uses first-order differencing. /// typedef math::Gradient GradientOp; /// // Wrap the operator with an adapter that computes the magnitude of the gradient. /// typedef math::OpMagnitude MagnitudeOp; /// // Wrap the operator with an adapter that associates a map with it. /// typedef math::MapAdapter CompoundOp; /// /// if (MapType::Ptr map = grid.constTransform().constMap()) { /// math::Stats stats = tools::opStatistics(grid.cbeginValueOn(), CompoundOp(*map)); /// } /// @endcode /// /// @par Example: /// Compute statistics of the divergence at the active voxels of a vector-valued grid. /// @code /// Vec3SGrid grid = ...; /// /// // Assume that we know that the grid has a uniform scale map. /// typedef math::UniformScaleMap MapType; /// // Specify a world-space divergence operator that uses first-order differencing. /// typedef math::Divergence DivergenceOp; /// // Wrap the operator with an adapter that associates a map with it. /// typedef math::MapAdapter CompoundOp; /// /// if (MapType::Ptr map = grid.constTransform().constMap()) { /// math::Stats stats = tools::opStatistics(grid.cbeginValueOn(), CompoundOp(*map)); /// } /// @endcode /// /// @par Example: /// As above, but computing the divergence in index space. /// @code /// Vec3SGrid grid = ...; /// /// // Specify an index-space divergence operator that uses first-order differencing. /// typedef math::ISDivergence DivergenceOp; /// /// math::Stats stats = tools::opStatistics(grid.cbeginValueOn(), DivergenceOp()); /// @endcode template inline math::Stats opStatistics(const IterT& iter, const OperatorT& op = OperatorT(), bool threaded = true); //////////////////////////////////////// namespace stats_internal { /// @todo This traits class is needed because tree::TreeValueIteratorBase uses /// the name ValueT for the type of the value to which the iterator points, /// whereas node-level iterators use the name ValueType. template struct IterTraits { typedef typename IterT::ValueType ValueType; }; template struct IterTraits > { typedef typename tree::TreeValueIteratorBase::ValueT ValueType; }; // Helper class to compute a scalar value from either a scalar or a vector value // (the latter by computing the vector's magnitude) template struct GetValImpl; template struct GetValImpl { static inline double get(const T& val) { return double(val); } }; template struct GetValImpl { static inline double get(const T& val) { return val.length(); } }; // Helper class to compute a scalar value from a tree or node iterator // that points to a value in either a scalar or a vector grid, and to // add that value to a math::Stats object. template struct GetVal { typedef typename IterTraits::ValueType ValueT; typedef GetValImpl::IsVec> ImplT; inline void operator()(const IterT& iter, StatsT& stats) const { if (iter.isVoxelValue()) stats.add(ImplT::get(*iter)); else stats.add(ImplT::get(*iter), iter.getVoxelCount()); } }; // Helper class to accumulate scalar voxel values or vector voxel magnitudes // into a math::Stats object template struct StatsOp { StatsOp(const ValueOp& op): getValue(op) {} // Accumulate voxel and tile values into this functor's Stats object. inline void operator()(const IterT& iter) { getValue(iter, stats); } // Accumulate another functor's Stats object into this functor's. inline void join(StatsOp& other) { stats.add(other.stats); } math::Stats stats; ValueOp getValue; }; // Helper class to accumulate scalar voxel values or vector voxel magnitudes // into a math::Histogram object template struct HistOp { HistOp(const ValueOp& op, double vmin, double vmax, size_t bins): hist(vmin, vmax, bins), getValue(op) {} // Accumulate voxel and tile values into this functor's Histogram object. inline void operator()(const IterT& iter) { getValue(iter, hist); } // Accumulate another functor's Histogram object into this functor's. inline void join(HistOp& other) { hist.add(other.hist); } math::Histogram hist; ValueOp getValue; }; // Helper class to apply an operator such as math::Gradient or math::Laplacian // to voxels and accumulate the scalar results or the magnitudes of vector results // into a math::Stats object template struct MathOp { typedef typename IterT::TreeT TreeT; typedef typename TreeT::ValueType ValueT; typedef typename tree::ValueAccessor ConstAccessor; // Each thread gets its own accessor and its own copy of the operator. ConstAccessor mAcc; OpT mOp; math::Stats mStats; template static inline TreeT* THROW_IF_NULL(TreeT* ptr) { if (ptr == NULL) OPENVDB_THROW(ValueError, "iterator references a null tree"); return ptr; } MathOp(const IterT& iter, const OpT& op): mAcc(*THROW_IF_NULL(iter.getTree())), mOp(op) {} // Accumulate voxel and tile values into this functor's Stats object. void operator()(const IterT& it) { if (it.isVoxelValue()) { // Add the magnitude of the gradient at a single voxel. mStats.add(mOp.result(mAcc, it.getCoord())); } else { // Iterate over the voxels enclosed by a tile and add the results // of applying the operator at each voxel. /// @todo This could be specialized to be done more efficiently for some operators. /// For example, all voxels in the interior of a tile (i.e., not on the borders) /// have gradient zero, so there's no need to apply the operator to every voxel. CoordBBox bbox = it.getBoundingBox(); Coord xyz; int &x = xyz.x(), &y = xyz.y(), &z = xyz.z(); for (x = bbox.min().x(); x <= bbox.max().x(); ++x) { for (y = bbox.min().y(); y <= bbox.max().y(); ++y) { for (z = bbox.min().z(); z <= bbox.max().z(); ++z) { mStats.add(mOp.result(mAcc, it.getCoord())); } } } } } // Accumulate another functor's Stats object into this functor's. inline void join(MathOp& other) { mStats.add(other.mStats); } }; // struct MathOp } // namespace stats_internal template inline math::Histogram histogram(const IterT& iter, double vmin, double vmax, size_t numBins, bool threaded) { typedef stats_internal::GetVal ValueOp; ValueOp valOp; stats_internal::HistOp op(valOp, vmin, vmax, numBins); tools::accumulate(iter, op, threaded); return op.hist; } template inline math::Stats statistics(const IterT& iter, bool threaded) { stats_internal::GetVal valOp; return statistics(iter, valOp, threaded); } template inline math::Stats statistics(const IterT& iter, const ValueOp& valOp, bool threaded) { stats_internal::StatsOp op(valOp); tools::accumulate(iter, op, threaded); return op.stats; } template inline math::Stats opStatistics(const IterT& iter, const OperatorT& op, bool threaded) { stats_internal::MathOp func(iter, op); tools::accumulate(iter, func, threaded); return func.mStats; } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_STATISTICS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/MeshToVolume.h0000644000000000000000000030271412252453157015126 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED #include #include #include // for ISGradientNormSqrd #include // for closestPointOnTriangleToPoint() #include // for dilateVoxels() #include #include // for nearestCoord() #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { //////////////////////////////////////// // Wrapper functions for the MeshToVolume converter /// @brief Convert a triangle mesh to a level set volume. /// /// @return a grid of type @c GridType containing a narrow-band level set /// representation of the input mesh. /// /// @throw TypeError if @c GridType is not scalar or not floating-point /// /// @note Requires a closed surface but not necessarily a manifold surface. /// Supports surfaces with self intersections and degenerate faces /// and is independent of mesh surface normals. /// /// @param xform transform for the output grid /// @param points list of world space point positions /// @param triangles triangle index list /// @param halfWidth half the width of the narrow band, in voxel units template inline typename GridType::Ptr meshToLevelSet( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, float halfWidth = float(LEVEL_SET_HALF_WIDTH)); /// @brief Convert a quad mesh to a level set volume. /// /// @return a grid of type @c GridType containing a narrow-band level set /// representation of the input mesh. /// /// @throw TypeError if @c GridType is not scalar or not floating-point /// /// @note Requires a closed surface but not necessarily a manifold surface. /// Supports surfaces with self intersections and degenerate faces /// and is independent of mesh surface normals. /// /// @param xform transform for the output grid /// @param points list of world space point positions /// @param quads quad index list /// @param halfWidth half the width of the narrow band, in voxel units template inline typename GridType::Ptr meshToLevelSet( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& quads, float halfWidth = float(LEVEL_SET_HALF_WIDTH)); /// @brief Convert a triangle and quad mesh to a level set volume. /// /// @return a grid of type @c GridType containing a narrow-band level set /// representation of the input mesh. /// /// @throw TypeError if @c GridType is not scalar or not floating-point /// /// @note Requires a closed surface but not necessarily a manifold surface. /// Supports surfaces with self intersections and degenerate faces /// and is independent of mesh surface normals. /// /// @param xform transform for the output grid /// @param points list of world space point positions /// @param triangles triangle index list /// @param quads quad index list /// @param halfWidth half the width of the narrow band, in voxel units template inline typename GridType::Ptr meshToLevelSet( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, const std::vector& quads, float halfWidth = float(LEVEL_SET_HALF_WIDTH)); /// @brief Convert a triangle and quad mesh to a signed distance field /// with an asymmetrical narrow band. /// /// @return a grid of type @c GridType containing a narrow-band signed /// distance field representation of the input mesh. /// /// @throw TypeError if @c GridType is not scalar or not floating-point /// /// @note Requires a closed surface but not necessarily a manifold surface. /// Supports surfaces with self intersections and degenerate faces /// and is independent of mesh surface normals. /// /// @param xform transform for the output grid /// @param points list of world space point positions /// @param triangles triangle index list /// @param quads quad index list /// @param exBandWidth the exterior narrow-band width in voxel units /// @param inBandWidth the interior narrow-band width in voxel units template inline typename GridType::Ptr meshToSignedDistanceField( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, const std::vector& quads, float exBandWidth, float inBandWidth); /// @brief Convert a triangle and quad mesh to an unsigned distance field. /// /// @return a grid of type @c GridType containing a narrow-band unsigned /// distance field representation of the input mesh. /// /// @throw TypeError if @c GridType is not scalar or not floating-point /// /// @note Does not requires a closed surface. /// /// @param xform transform for the output grid /// @param points list of world space point positions /// @param triangles triangle index list /// @param quads quad index list /// @param bandWidth the width of the narrow band, in voxel units template inline typename GridType::Ptr meshToUnsignedDistanceField( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, const std::vector& quads, float bandWidth); //////////////////////////////////////// /// Conversion flags, used to control the MeshToVolume output enum { GENERATE_PRIM_INDEX_GRID = 0x1, OUTPUT_RAW_DATA = 0x2}; // MeshToVolume template class MeshToVolume { public: typedef typename FloatGridT::TreeType FloatTreeT; typedef typename FloatTreeT::ValueType FloatValueT; typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; typedef Grid IntGridT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef Grid BoolGridT; MeshToVolume(openvdb::math::Transform::Ptr&, int conversionFlags = 0, InterruptT *interrupter = NULL, int signSweeps = 1); /// @brief Mesh to Level Set / Signed Distance Field conversion /// /// @note Requires a closed surface but not necessarily a manifold surface. /// Supports surfaces with self intersections, degenerate faces and /// is independent of mesh surface normals. /// /// @param pointList List of points in grid index space, preferably unique /// and shared by different polygons. /// @param polygonList List of triangles and/or quads. /// @param exBandWidth The exterior narrow-band width in voxel units. /// @param inBandWidth The interior narrow-band width in voxel units. void convertToLevelSet( const std::vector& pointList, const std::vector& polygonList, FloatValueT exBandWidth = FloatValueT(LEVEL_SET_HALF_WIDTH), FloatValueT inBandWidth = FloatValueT(LEVEL_SET_HALF_WIDTH)); /// @brief Mesh to Unsigned Distance Field conversion /// /// @note Does not requires a closed surface. /// /// @param pointList List of points in grid index space, preferably unique /// and shared by different polygons. /// @param polygonList List of triangles and/or quads. /// @param exBandWidth The narrow-band width in voxel units. void convertToUnsignedDistanceField(const std::vector& pointList, const std::vector& polygonList, FloatValueT exBandWidth); void clear(); /// Returns a narrow-band (signed) distance field / level set grid. typename FloatGridT::Ptr distGridPtr() const { return mDistGrid; } /// Returns a grid containing the closest-primitive index for each /// voxel in the narrow-band. typename IntGridT::Ptr indexGridPtr() const { return mIndexGrid; } private: // disallow copy by assignment void operator=(const MeshToVolume&) {} void doConvert(const std::vector&, const std::vector&, FloatValueT exBandWidth, FloatValueT inBandWidth, bool unsignedDistField = false); bool wasInterrupted(int percent = -1) const { return mInterrupter && mInterrupter->wasInterrupted(percent); } openvdb::math::Transform::Ptr mTransform; int mConversionFlags, mSignSweeps; typename FloatGridT::Ptr mDistGrid; typename IntGridT::Ptr mIndexGrid; typename BoolGridT::Ptr mIntersectingVoxelsGrid; InterruptT *mInterrupter; }; //////////////////////////////////////// /// @brief Extracts and stores voxel edge intersection data from a mesh. class MeshToVoxelEdgeData { public: ////////// ///@brief Internal edge data type. struct EdgeData { EdgeData(float dist = 1.0) : mXDist(dist), mYDist(dist), mZDist(dist) , mXPrim(util::INVALID_IDX) , mYPrim(util::INVALID_IDX) , mZPrim(util::INVALID_IDX) { } //@{ /// Required by several of the tree nodes /// @note These methods don't perform meaningful operations. bool operator< (const EdgeData&) const { return false; }; bool operator> (const EdgeData&) const { return false; }; template EdgeData operator+(const T&) const { return *this; } template EdgeData operator-(const T&) const { return *this; } EdgeData operator-() const { return *this; } //@} bool operator==(const EdgeData& rhs) const { return mXPrim == rhs.mXPrim && mYPrim == rhs.mYPrim && mZPrim == rhs.mZPrim; } float mXDist, mYDist, mZDist; Index32 mXPrim, mYPrim, mZPrim; }; typedef tree::Tree4::Type TreeType; typedef tree::ValueAccessor Accessor; ////////// MeshToVoxelEdgeData(); /// @brief Threaded method to extract voxel edge data, the closest /// intersection point and corresponding primitive index, /// from the given mesh. /// /// @param pointList List of points in grid index space, preferably unique /// and shared by different polygons. /// @param polygonList List of triangles and/or quads. void convert(const std::vector& pointList, const std::vector& polygonList); /// @brief Returns intersection points with corresponding primitive /// indices for the given @c ijk voxel. void getEdgeData(Accessor& acc, const Coord& ijk, std::vector& points, std::vector& primitives); /// @return An accessor of @c MeshToVoxelEdgeData::Accessor type that /// provides random read access to the internal tree. Accessor getAccessor() { return Accessor(mTree); } private: void operator=(const MeshToVoxelEdgeData&) {} TreeType mTree; class GenEdgeData; }; //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Internal utility objects and implementation details namespace internal { class PointTransform { public: PointTransform(const std::vector& pointsIn, std::vector& pointsOut, const math::Transform& xform) : mPointsIn(pointsIn) , mPointsOut(&pointsOut) , mXform(xform) { } void run(bool threaded = true) { if (threaded) { tbb::parallel_for(tbb::blocked_range(0, mPointsOut->size()), *this); } else { (*this)(tbb::blocked_range(0, mPointsOut->size())); } } inline void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(); n < range.end(); ++n) { (*mPointsOut)[n] = mXform.worldToIndex(mPointsIn[n]); } } private: const std::vector& mPointsIn; std::vector * const mPointsOut; const math::Transform& mXform; }; template struct Tolerance { static ValueType epsilon() { return ValueType(1e-7); } static ValueType minNarrowBandWidth() { return ValueType(1.0 + 1e-6); } }; template inline void combine(FloatTreeT& lhsDist, IntTreeT& lhsIndex, FloatTreeT& rhsDist, IntTreeT& rhsIndex) { typedef typename FloatTreeT::ValueType FloatValueT; typename tree::ValueAccessor lhsDistAccessor(lhsDist); typename tree::ValueAccessor lhsIndexAccessor(lhsIndex); typename tree::ValueAccessor rhsIndexAccessor(rhsIndex); typename FloatTreeT::LeafCIter iter = rhsDist.cbeginLeaf(); FloatValueT rhsValue; Coord ijk; for ( ; iter; ++iter) { typename FloatTreeT::LeafNodeType::ValueOnCIter it = iter->cbeginValueOn(); for ( ; it; ++it) { ijk = it.getCoord(); rhsValue = it.getValue(); FloatValueT& lhsValue = const_cast(lhsDistAccessor.getValue(ijk)); if (-rhsValue < std::abs(lhsValue)) { lhsValue = rhsValue; lhsIndexAccessor.setValue(ijk, rhsIndexAccessor.getValue(ijk)); } } } } //////////////////////////////////////// /// MeshVoxelizer /// @brief TBB body object to voxelize a mesh of triangles and/or quads into a collection /// of VDB grids, namely a squared distance grid, a closest primitive grid and an /// intersecting voxels grid (masks the mesh intersecting voxels) /// @note Only the leaf nodes that intersect the mesh are allocated, and only voxels in /// a narrow band (of two to three voxels in proximity to the mesh's surface) are activated. /// They are populated with distance values and primitive indices. template class MeshVoxelizer { public: typedef typename FloatTreeT::ValueType FloatValueT; typedef typename FloatTreeT::LeafNodeType FloatLeafT; typedef typename tree::ValueAccessor FloatAccessorT; typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; typedef typename IntTreeT::LeafNodeType IntLeafT; typedef typename tree::ValueAccessor IntAccessorT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef typename BoolTreeT::LeafNodeType BoolLeafT; typedef typename tree::ValueAccessor BoolAccessorT; MeshVoxelizer(const std::vector& pointList, const std::vector& polygonList, InterruptT *interrupter = NULL); ~MeshVoxelizer() {} void run(bool threaded = true); MeshVoxelizer(MeshVoxelizer& rhs, tbb::split); void operator() (const tbb::blocked_range &range); void join(MeshVoxelizer& rhs); FloatTreeT& sqrDistTree() { return mSqrDistTree; } IntTreeT& primIndexTree() { return mPrimIndexTree; } BoolTreeT& intersectionTree() { return mIntersectionTree; } private: // disallow copy by assignment void operator=(const MeshVoxelizer&) {} bool wasInterrupted() const { return mInterrupter && mInterrupter->wasInterrupted(); } bool evalVoxel(const Coord& ijk, const Int32 polyIdx); const std::vector& mPointList; const std::vector& mPolygonList; FloatTreeT mSqrDistTree; FloatAccessorT mSqrDistAccessor; IntTreeT mPrimIndexTree; IntAccessorT mPrimIndexAccessor; BoolTreeT mIntersectionTree; BoolAccessorT mIntersectionAccessor; // Used internally for acceleration IntTreeT mLastPrimTree; IntAccessorT mLastPrimAccessor; InterruptT *mInterrupter; struct Primitive { Vec3d a, b, c, d; Int32 index; }; template bool evalPrimitive(const Coord&, const Primitive&); template void voxelize(const Primitive&); }; template void MeshVoxelizer::run(bool threaded) { if (threaded) { tbb::parallel_reduce(tbb::blocked_range(0, mPolygonList.size()), *this); } else { (*this)(tbb::blocked_range(0, mPolygonList.size())); } } template MeshVoxelizer::MeshVoxelizer( const std::vector& pointList, const std::vector& polygonList, InterruptT *interrupter) : mPointList(pointList) , mPolygonList(polygonList) , mSqrDistTree(std::numeric_limits::max()) , mSqrDistAccessor(mSqrDistTree) , mPrimIndexTree(Int32(util::INVALID_IDX)) , mPrimIndexAccessor(mPrimIndexTree) , mIntersectionTree(false) , mIntersectionAccessor(mIntersectionTree) , mLastPrimTree(Int32(util::INVALID_IDX)) , mLastPrimAccessor(mLastPrimTree) , mInterrupter(interrupter) { } template MeshVoxelizer::MeshVoxelizer( MeshVoxelizer& rhs, tbb::split) : mPointList(rhs.mPointList) , mPolygonList(rhs.mPolygonList) , mSqrDistTree(std::numeric_limits::max()) , mSqrDistAccessor(mSqrDistTree) , mPrimIndexTree(Int32(util::INVALID_IDX)) , mPrimIndexAccessor(mPrimIndexTree) , mIntersectionTree(false) , mIntersectionAccessor(mIntersectionTree) , mLastPrimTree(Int32(util::INVALID_IDX)) , mLastPrimAccessor(mLastPrimTree) , mInterrupter(rhs.mInterrupter) { } template void MeshVoxelizer::operator()(const tbb::blocked_range &range) { Primitive prim; for (size_t n = range.begin(); n < range.end(); ++n) { if (mInterrupter && mInterrupter->wasInterrupted()) { tbb::task::self().cancel_group_execution(); break; } const Vec4I& verts = mPolygonList[n]; prim.index = Int32(n); prim.a = Vec3d(mPointList[verts[0]]); prim.b = Vec3d(mPointList[verts[1]]); prim.c = Vec3d(mPointList[verts[2]]); if (util::INVALID_IDX != verts[3]) { prim.d = Vec3d(mPointList[verts[3]]); voxelize(prim); } else { voxelize(prim); } } } template template void MeshVoxelizer::voxelize(const Primitive& prim) { std::deque coordList; Coord ijk, nijk; ijk = util::nearestCoord(prim.a); coordList.push_back(ijk); evalPrimitive(ijk, prim); while (!coordList.empty()) { if(wasInterrupted()) break; ijk = coordList.back(); coordList.pop_back(); mIntersectionAccessor.setActiveState(ijk, true); for (Int32 i = 0; i < 26; ++i) { nijk = ijk + util::COORD_OFFSETS[i]; if (prim.index != mLastPrimAccessor.getValue(nijk)) { mLastPrimAccessor.setValue(nijk, prim.index); if(evalPrimitive(nijk, prim)) coordList.push_back(nijk); } } } } template template bool MeshVoxelizer::evalPrimitive(const Coord& ijk, const Primitive& prim) { Vec3d uvw, voxelCenter(ijk[0], ijk[1], ijk[2]); // Evaluate first triangle FloatValueT dist = FloatValueT((voxelCenter - closestPointOnTriangleToPoint(prim.a, prim.c, prim.b, voxelCenter, uvw)).lengthSqr()); if (IsQuad) { // Split quad into a second triangle and calculate distance. FloatValueT secondDist = FloatValueT((voxelCenter - closestPointOnTriangleToPoint(prim.a, prim.d, prim.c, voxelCenter, uvw)).lengthSqr()); if (secondDist < dist) dist = secondDist; } FloatValueT oldDist = std::abs(mSqrDistAccessor.getValue(ijk)); if (dist < oldDist) { mSqrDistAccessor.setValue(ijk, -dist); mPrimIndexAccessor.setValue(ijk, prim.index); } else if (math::isExactlyEqual(dist, oldDist)) { // makes reduction deterministic when different polygons // produce the same distance value. mPrimIndexAccessor.setValue(ijk, std::min(prim.index, mPrimIndexAccessor.getValue(ijk))); } return (dist < 0.86602540378443861); } template void MeshVoxelizer::join(MeshVoxelizer& rhs) { typedef typename FloatTreeT::RootNodeType FloatRootNodeT; typedef typename FloatRootNodeT::NodeChainType FloatNodeChainT; BOOST_STATIC_ASSERT(boost::mpl::size::value > 1); typedef typename boost::mpl::at >::type FloatInternalNodeT; typedef typename IntTreeT::RootNodeType IntRootNodeT; typedef typename IntRootNodeT::NodeChainType IntNodeChainT; BOOST_STATIC_ASSERT(boost::mpl::size::value > 1); typedef typename boost::mpl::at >::type IntInternalNodeT; const FloatValueT background = std::numeric_limits::max(); Coord ijk; Index offset; rhs.mSqrDistTree.clearAllAccessors(); rhs.mPrimIndexTree.clearAllAccessors(); typename FloatTreeT::LeafIter leafIt = rhs.mSqrDistTree.beginLeaf(); for ( ; leafIt; ++leafIt) { ijk = leafIt->origin(); FloatLeafT* lhsDistLeafPt = mSqrDistAccessor.probeLeaf(ijk); if (!lhsDistLeafPt) { // Steals leaf nodes through their parent, always the last internal-node // stored in the ValueAccessor's node chain, avoiding the overhead of // the root node. This is significantly faster than going through the // tree or root node. mSqrDistAccessor.addLeaf(rhs.mSqrDistAccessor.probeLeaf(ijk)); FloatInternalNodeT* floatNode = rhs.mSqrDistAccessor.template getNode(); floatNode->template stealNode(ijk, background, false); mPrimIndexAccessor.addLeaf(rhs.mPrimIndexAccessor.probeLeaf(ijk)); IntInternalNodeT* intNode = rhs.mPrimIndexAccessor.template getNode(); intNode->template stealNode(ijk, util::INVALID_IDX, false); } else { IntLeafT* lhsIdxLeafPt = mPrimIndexAccessor.probeLeaf(ijk); IntLeafT* rhsIdxLeafPt = rhs.mPrimIndexAccessor.probeLeaf(ijk); FloatValueT lhsValue, rhsValue; typename FloatLeafT::ValueOnCIter it = leafIt->cbeginValueOn(); for ( ; it; ++it) { offset = it.pos(); lhsValue = std::abs(lhsDistLeafPt->getValue(offset)); rhsValue = std::abs(it.getValue()); if (rhsValue < lhsValue) { lhsDistLeafPt->setValueOn(offset, it.getValue()); lhsIdxLeafPt->setValueOn(offset, rhsIdxLeafPt->getValue(offset)); } else if (math::isExactlyEqual(rhsValue, lhsValue)) { lhsIdxLeafPt->setValueOn(offset, std::min(lhsIdxLeafPt->getValue(offset), rhsIdxLeafPt->getValue(offset))); } } } } mIntersectionTree.merge(rhs.mIntersectionTree); rhs.mSqrDistTree.clear(); rhs.mPrimIndexTree.clear(); rhs.mIntersectionTree.clear(); } //////////////////////////////////////// // ContourTracer /// @brief TBB body object that partitions a volume into 2D slices that can be processed /// in parallel and marks the exterior contour of disjoint voxel sets in each slice template class ContourTracer { public: typedef typename FloatTreeT::ValueType FloatValueT; typedef typename tree::ValueAccessor DistAccessorT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef typename tree::ValueAccessor BoolAccessorT; ContourTracer(FloatTreeT&, const BoolTreeT&, InterruptT *interrupter = NULL); ~ContourTracer() {} void run(bool threaded = true); ContourTracer(const ContourTracer& rhs); void operator()(const tbb::blocked_range &range) const; private: void operator=(const ContourTracer&) {} int sparseScan(int slice) const; FloatTreeT& mDistTree; DistAccessorT mDistAccessor; const BoolTreeT& mIntersectionTree; BoolAccessorT mIntersectionAccessor; CoordBBox mBBox; /// List of value-depth dependant step sizes. std::vector mStepSize; InterruptT *mInterrupter; }; template void ContourTracer::run(bool threaded) { if (threaded) { tbb::parallel_for(tbb::blocked_range(mBBox.min()[0], mBBox.max()[0]+1), *this); } else { (*this)(tbb::blocked_range(mBBox.min()[0], mBBox.max()[0]+1)); } } template ContourTracer::ContourTracer( FloatTreeT& distTree, const BoolTreeT& intersectionTree, InterruptT *interrupter) : mDistTree(distTree) , mDistAccessor(mDistTree) , mIntersectionTree(intersectionTree) , mIntersectionAccessor(mIntersectionTree) , mBBox(CoordBBox()) , mStepSize(0) , mInterrupter(interrupter) { // Build the step size table for different tree value depths. std::vector dims; mDistTree.getNodeLog2Dims(dims); mStepSize.resize(dims.size()+1, 1); Index exponent = 0; for (int idx = static_cast(dims.size()) - 1; idx > -1; --idx) { exponent += dims[idx]; mStepSize[idx] = 1 << exponent; } mDistTree.evalLeafBoundingBox(mBBox); // Make sure that mBBox coincides with the min and max corners of the internal nodes. const int tileDim = mStepSize[0]; for (size_t i = 0; i < 3; ++i) { int n; double diff = std::abs(double(mBBox.min()[i])) / double(tileDim); if (mBBox.min()[i] <= tileDim) { n = int(std::ceil(diff)); mBBox.min()[i] = - n * tileDim; } else { n = int(std::floor(diff)); mBBox.min()[i] = n * tileDim; } n = int(std::ceil(std::abs(double(mBBox.max()[i] - mBBox.min()[i])) / double(tileDim))); mBBox.max()[i] = mBBox.min()[i] + n * tileDim; } } template ContourTracer::ContourTracer( const ContourTracer &rhs) : mDistTree(rhs.mDistTree) , mDistAccessor(mDistTree) , mIntersectionTree(rhs.mIntersectionTree) , mIntersectionAccessor(mIntersectionTree) , mBBox(rhs.mBBox) , mStepSize(rhs.mStepSize) , mInterrupter(rhs.mInterrupter) { } template void ContourTracer::operator()(const tbb::blocked_range &range) const { // Slice up the volume and trace contours. int iStep = 1; for (int n = range.begin(); n < range.end(); n += iStep) { if (mInterrupter && mInterrupter->wasInterrupted()) { tbb::task::self().cancel_group_execution(); break; } iStep = sparseScan(n); } } template int ContourTracer::sparseScan(int slice) const { bool lastVoxelWasOut = true; int last_k; Coord ijk(slice, mBBox.min()[1], mBBox.min()[2]); Coord step(mStepSize[mDistAccessor.getValueDepth(ijk) + 1]); Coord n_ijk; for (ijk[1] = mBBox.min()[1]; ijk[1] <= mBBox.max()[1]; ijk[1] += step[1]) { // j if (mInterrupter && mInterrupter->wasInterrupted()) { break; } step[1] = mStepSize[mDistAccessor.getValueDepth(ijk) + 1]; step[0] = std::min(step[0], step[1]); for (ijk[2] = mBBox.min()[2]; ijk[2] <= mBBox.max()[2]; ijk[2] += step[2]) { // k step[2] = mStepSize[mDistAccessor.getValueDepth(ijk) + 1]; step[1] = std::min(step[1], step[2]); step[0] = std::min(step[0], step[2]); // If the current voxel is set? if (mDistAccessor.isValueOn(ijk)) { // Is this a boundary voxel? if (mIntersectionAccessor.isValueOn(ijk)) { lastVoxelWasOut = false; last_k = ijk[2]; } else if (lastVoxelWasOut) { FloatValueT& val = const_cast(mDistAccessor.getValue(ijk)); val = -val; // flip sign } else { FloatValueT val; for (Int32 n = 3; n < 6; n += 2) { n_ijk = ijk + util::COORD_OFFSETS[n]; if (mDistAccessor.probeValue(n_ijk, val) && val > 0) { lastVoxelWasOut = true; break; } } if (lastVoxelWasOut) { FloatValueT& v = const_cast(mDistAccessor.getValue(ijk)); v = -v; // flip sign const int tmp_k = ijk[2]; // backtrace for (--ijk[2]; ijk[2] >= last_k; --ijk[2]) { if (mIntersectionAccessor.isValueOn(ijk)) break; FloatValueT& vb = const_cast(mDistAccessor.getValue(ijk)); if (vb < FloatValueT(0.0)) vb = -vb; // flip sign } last_k = tmp_k; ijk[2] = tmp_k; } else { last_k = std::min(ijk[2], last_k); } } } // end isValueOn check } // end k } // end j return step[0]; } //////////////////////////////////////// /// @brief TBB body object that that finds seed points for the parallel flood fill. template class SignMask { public: typedef typename FloatTreeT::ValueType FloatValueT; typedef typename FloatTreeT::LeafNodeType FloatLeafT; typedef tree::LeafManager FloatLeafManager; typedef typename tree::ValueAccessor FloatAccessorT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef typename BoolTreeT::LeafNodeType BoolLeafT; typedef typename tree::ValueAccessor BoolAccessorT; typedef typename tree::ValueAccessor BoolConstAccessorT; SignMask(const FloatLeafManager&, const FloatTreeT&, const BoolTreeT&, InterruptT *interrupter = NULL); ~SignMask() {} void run(bool threaded = true); SignMask(SignMask& rhs, tbb::split); void operator() (const tbb::blocked_range &range); void join(SignMask& rhs); BoolTreeT& signMaskTree() { return mSignMaskTree; } private: // disallow copy by assignment void operator=(const SignMask&) {} bool wasInterrupted() const { return mInterrupter && mInterrupter->wasInterrupted(); } const FloatLeafManager& mDistLeafs; const FloatTreeT& mDistTree; const BoolTreeT& mIntersectionTree; BoolTreeT mSignMaskTree; InterruptT *mInterrupter; }; // class SignMask template SignMask::SignMask( const FloatLeafManager& distLeafs, const FloatTreeT& distTree, const BoolTreeT& intersectionTree, InterruptT *interrupter) : mDistLeafs(distLeafs) , mDistTree(distTree) , mIntersectionTree(intersectionTree) , mSignMaskTree(false) , mInterrupter(interrupter) { } template SignMask::SignMask( SignMask& rhs, tbb::split) : mDistLeafs(rhs.mDistLeafs) , mDistTree(rhs.mDistTree) , mIntersectionTree(rhs.mIntersectionTree) , mSignMaskTree(false) , mInterrupter(rhs.mInterrupter) { } template void SignMask::run(bool threaded) { if (threaded) tbb::parallel_reduce(mDistLeafs.getRange(), *this); else (*this)(mDistLeafs.getRange()); } template void SignMask::operator()(const tbb::blocked_range &range) { FloatAccessorT distAcc(mDistTree); BoolConstAccessorT intersectionAcc(mIntersectionTree); BoolAccessorT maskAcc(mSignMaskTree); FloatValueT value; CoordBBox bbox; Coord& maxCoord = bbox.max(); Coord& minCoord = bbox.min(); Coord ijk; const int extent = BoolLeafT::DIM - 1; for (size_t n = range.begin(); n < range.end(); ++n) { const FloatLeafT& distLeaf = mDistLeafs.leaf(n); minCoord = distLeaf.origin(); maxCoord[0] = minCoord[0] + extent; maxCoord[1] = minCoord[1] + extent; maxCoord[2] = minCoord[2] + extent; const BoolLeafT* intersectionLeaf = intersectionAcc.probeConstLeaf(minCoord); BoolLeafT* maskLeafPt = new BoolLeafT(minCoord, false); BoolLeafT& maskLeaf = *maskLeafPt; bool addLeaf = false; bbox.expand(-1); typename FloatLeafT::ValueOnCIter it = distLeaf.cbeginValueOn(); for (; it; ++it) { if (intersectionLeaf && intersectionLeaf->isValueOn(it.pos())) continue; if (it.getValue() < FloatValueT(0.0)) { ijk = it.getCoord(); if (bbox.isInside(ijk)) { for (size_t i = 0; i < 6; ++i) { if (distLeaf.probeValue(ijk+util::COORD_OFFSETS[i], value) && value>0.0) { maskLeaf.setValueOn(ijk); addLeaf = true; break; } } } else { for (size_t i = 0; i < 6; ++i) { if (distAcc.probeValue(ijk+util::COORD_OFFSETS[i], value) && value>0.0) { maskLeaf.setValueOn(ijk); addLeaf = true; break; } } } } } if (addLeaf) maskAcc.addLeaf(maskLeafPt); else delete maskLeafPt; } } template void SignMask::join(SignMask& rhs) { mSignMaskTree.merge(rhs.mSignMaskTree); } //////////////////////////////////////// /// @brief TBB body object that performs a parallel flood fill template class PropagateSign { public: typedef typename FloatTreeT::ValueType FloatValueT; typedef typename FloatTreeT::LeafNodeType FloatLeafT; typedef typename tree::ValueAccessor FloatAccessorT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef typename BoolTreeT::LeafNodeType BoolLeafT; typedef tree::LeafManager BoolLeafManager; typedef typename tree::ValueAccessor BoolAccessorT; typedef typename tree::ValueAccessor BoolConstAccessorT; PropagateSign(BoolLeafManager&, FloatTreeT&, const BoolTreeT&, InterruptT *interrupter = NULL); ~PropagateSign() {} void run(bool threaded = true); PropagateSign(PropagateSign& rhs, tbb::split); void operator() (const tbb::blocked_range &range); void join(PropagateSign& rhs); BoolTreeT& signMaskTree() { return mSignMaskTree; } private: // disallow copy by assignment void operator=(const PropagateSign&) {} bool wasInterrupted() const { return mInterrupter && mInterrupter->wasInterrupted(); } BoolLeafManager& mOldSignMaskLeafs; FloatTreeT& mDistTree; const BoolTreeT& mIntersectionTree; BoolTreeT mSignMaskTree; InterruptT *mInterrupter; }; template PropagateSign::PropagateSign(BoolLeafManager& signMaskLeafs, FloatTreeT& distTree, const BoolTreeT& intersectionTree, InterruptT *interrupter) : mOldSignMaskLeafs(signMaskLeafs) , mDistTree(distTree) , mIntersectionTree(intersectionTree) , mSignMaskTree(false) , mInterrupter(interrupter) { } template PropagateSign::PropagateSign( PropagateSign& rhs, tbb::split) : mOldSignMaskLeafs(rhs.mOldSignMaskLeafs) , mDistTree(rhs.mDistTree) , mIntersectionTree(rhs.mIntersectionTree) , mSignMaskTree(false) , mInterrupter(rhs.mInterrupter) { } template void PropagateSign::run(bool threaded) { if (threaded) tbb::parallel_reduce(mOldSignMaskLeafs.getRange(), *this); else (*this)(mOldSignMaskLeafs.getRange()); } template void PropagateSign::operator()(const tbb::blocked_range &range) { FloatAccessorT distAcc(mDistTree); BoolConstAccessorT intersectionAcc(mIntersectionTree); BoolAccessorT maskAcc(mSignMaskTree); std::deque coordList; FloatValueT value; CoordBBox bbox; Coord& maxCoord = bbox.max(); Coord& minCoord = bbox.min(); Coord ijk, nijk; const int extent = BoolLeafT::DIM - 1; for (size_t n = range.begin(); n < range.end(); ++n) { BoolLeafT& oldMaskLeaf = mOldSignMaskLeafs.leaf(n); minCoord = oldMaskLeaf.origin(); maxCoord[0] = minCoord[0] + extent; maxCoord[1] = minCoord[1] + extent; maxCoord[2] = minCoord[2] + extent; FloatLeafT& distLeaf = *distAcc.probeLeaf(minCoord); const BoolLeafT* intersectionLeaf = intersectionAcc.probeConstLeaf(minCoord); typename BoolLeafT::ValueOnCIter it = oldMaskLeaf.cbeginValueOn(); for (; it; ++it) { coordList.push_back(it.getCoord()); while (!coordList.empty()) { ijk = coordList.back(); coordList.pop_back(); FloatValueT& dist = const_cast(distLeaf.getValue(ijk)); if (dist < FloatValueT(0.0)) { dist = -dist; // flip sign for (size_t i = 0; i < 6; ++i) { nijk = ijk + util::COORD_OFFSETS[i]; if (bbox.isInside(nijk)) { if (intersectionLeaf && intersectionLeaf->isValueOn(nijk)) continue; if (distLeaf.probeValue(nijk, value) && value < 0.0) { coordList.push_back(nijk); } } else { if(!intersectionAcc.isValueOn(nijk) && distAcc.probeValue(nijk, value) && value < 0.0) { maskAcc.setValueOn(nijk); } } } } } } } } template void PropagateSign::join(PropagateSign& rhs) { mSignMaskTree.merge(rhs.mSignMaskTree); } //////////////////////////////////////// // IntersectingVoxelSign /// @brief TBB body object that traversers all intersecting voxels (defined by the /// intersectingVoxelsGrid) and potentially flips their sign, by comparing the "closest point" /// directions of outside-marked and non-intersecting neighboring voxels template class IntersectingVoxelSign { public: typedef typename FloatTreeT::ValueType FloatValueT; typedef typename tree::ValueAccessor FloatAccessorT; typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; typedef typename tree::ValueAccessor IntAccessorT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef typename tree::ValueAccessor BoolAccessorT; typedef tree::LeafManager BoolLeafManager; IntersectingVoxelSign( const std::vector& pointList, const std::vector& polygonList, FloatTreeT& distTree, IntTreeT& indexTree, BoolTreeT& intersectionTree, BoolLeafManager& leafs); ~IntersectingVoxelSign() {} void run(bool threaded = true); IntersectingVoxelSign(const IntersectingVoxelSign &rhs); void operator()(const tbb::blocked_range&) const; private: void operator=(const IntersectingVoxelSign&) {} Vec3d getClosestPoint(const Coord& ijk, const Vec4I& prim) const; std::vector const * const mPointList; std::vector const * const mPolygonList; FloatTreeT& mDistTree; IntTreeT& mIndexTree; BoolTreeT& mIntersectionTree; BoolLeafManager& mLeafs; }; template void IntersectingVoxelSign::run(bool threaded) { if (threaded) tbb::parallel_for(mLeafs.getRange(), *this); else (*this)(mLeafs.getRange()); } template IntersectingVoxelSign::IntersectingVoxelSign( const std::vector& pointList, const std::vector& polygonList, FloatTreeT& distTree, IntTreeT& indexTree, BoolTreeT& intersectionTree, BoolLeafManager& leafs) : mPointList(&pointList) , mPolygonList(&polygonList) , mDistTree(distTree) , mIndexTree(indexTree) , mIntersectionTree(intersectionTree) , mLeafs(leafs) { } template IntersectingVoxelSign::IntersectingVoxelSign( const IntersectingVoxelSign &rhs) : mPointList(rhs.mPointList) , mPolygonList(rhs.mPolygonList) , mDistTree(rhs.mDistTree) , mIndexTree(rhs.mIndexTree) , mIntersectionTree(rhs.mIntersectionTree) , mLeafs(rhs.mLeafs) { } template void IntersectingVoxelSign::operator()( const tbb::blocked_range& range) const { Coord ijk, nijk; FloatAccessorT distAcc(mDistTree); BoolAccessorT maskAcc(mIntersectionTree); IntAccessorT idxAcc(mIndexTree); FloatValueT tmpValue; Vec3d cpt, center, dir1, dir2; typename BoolTreeT::LeafNodeType::ValueOnCIter iter; for (size_t n = range.begin(); n < range.end(); ++n) { iter = mLeafs.leaf(n).cbeginValueOn(); for (; iter; ++iter) { ijk = iter.getCoord(); FloatValueT value = distAcc.getValue(ijk); if (!(value < FloatValueT(0.0))) continue; center = Vec3d(ijk[0], ijk[1], ijk[2]); for (Int32 i = 0; i < 26; ++i) { nijk = ijk + util::COORD_OFFSETS[i]; if (!maskAcc.isValueOn(nijk) && distAcc.probeValue(nijk, tmpValue)) { if (tmpValue < FloatValueT(0.0)) continue; const Vec4I& prim = (*mPolygonList)[idxAcc.getValue(nijk)]; cpt = getClosestPoint(nijk, prim); dir1 = center - cpt; dir1.normalize(); dir2 = Vec3d(nijk[0], nijk[1], nijk[2]) - cpt; dir2.normalize(); if (dir2.dot(dir1) > 0.0) { distAcc.setValue(ijk, -value); break; } } } } } } template Vec3d IntersectingVoxelSign::getClosestPoint(const Coord& ijk, const Vec4I& prim) const { Vec3d voxelCenter(ijk[0], ijk[1], ijk[2]); // Evaluate first triangle const Vec3d a((*mPointList)[prim[0]]); const Vec3d b((*mPointList)[prim[1]]); const Vec3d c((*mPointList)[prim[2]]); Vec3d uvw; Vec3d cpt1 = closestPointOnTriangleToPoint(a, c, b, voxelCenter, uvw); // Evaluate second triangle if quad. if (prim[3] != util::INVALID_IDX) { Vec3d diff1 = voxelCenter - cpt1; const Vec3d d((*mPointList)[prim[3]]); Vec3d cpt2 = closestPointOnTriangleToPoint(a, d, c, voxelCenter, uvw); Vec3d diff2 = voxelCenter - cpt2; if (diff2.lengthSqr() < diff1.lengthSqr()) { return cpt2; } } return cpt1; } //////////////////////////////////////// // IntersectingVoxelCleaner /// @brief TBB body object that removes intersecting voxels that were set via /// voxelization of self-intersecting parts of a mesh template class IntersectingVoxelCleaner { public: typedef typename FloatTreeT::ValueType FloatValueT; typedef typename tree::ValueAccessor DistAccessorT; typedef typename FloatTreeT::LeafNodeType DistLeafT; typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; typedef typename tree::ValueAccessor IntAccessorT; typedef typename IntTreeT::LeafNodeType IntLeafT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef typename tree::ValueAccessor BoolAccessorT; typedef typename BoolTreeT::LeafNodeType BoolLeafT; typedef tree::LeafManager BoolLeafManager; IntersectingVoxelCleaner(FloatTreeT& distTree, IntTreeT& indexTree, BoolTreeT& intersectionTree, BoolLeafManager& leafs); ~IntersectingVoxelCleaner() {} void run(bool threaded = true); IntersectingVoxelCleaner(const IntersectingVoxelCleaner &rhs); void operator()(const tbb::blocked_range&) const; private: void operator=(const IntersectingVoxelCleaner&) {} FloatTreeT& mDistTree; IntTreeT& mIndexTree; BoolTreeT& mIntersectionTree; BoolLeafManager& mLeafs; }; template void IntersectingVoxelCleaner::run(bool threaded) { if (threaded) tbb::parallel_for(mLeafs.getRange(), *this); else (*this)(mLeafs.getRange()); mIntersectionTree.pruneInactive(); } template IntersectingVoxelCleaner::IntersectingVoxelCleaner( FloatTreeT& distTree, IntTreeT& indexTree, BoolTreeT& intersectionTree, BoolLeafManager& leafs) : mDistTree(distTree) , mIndexTree(indexTree) , mIntersectionTree(intersectionTree) , mLeafs(leafs) { } template IntersectingVoxelCleaner::IntersectingVoxelCleaner( const IntersectingVoxelCleaner& rhs) : mDistTree(rhs.mDistTree) , mIndexTree(rhs.mIndexTree) , mIntersectionTree(rhs.mIntersectionTree) , mLeafs(rhs.mLeafs) { } template void IntersectingVoxelCleaner::operator()( const tbb::blocked_range& range) const { Coord ijk, m_ijk; bool turnOff; FloatValueT value; Index offset; typename BoolLeafT::ValueOnCIter iter; IntAccessorT indexAcc(mIndexTree); DistAccessorT distAcc(mDistTree); BoolAccessorT maskAcc(mIntersectionTree); for (size_t n = range.begin(); n < range.end(); ++n) { BoolLeafT& maskLeaf = mLeafs.leaf(n); ijk = maskLeaf.origin(); DistLeafT * distLeaf = distAcc.probeLeaf(ijk); if (distLeaf) { iter = maskLeaf.cbeginValueOn(); for (; iter; ++iter) { offset = iter.pos(); if(distLeaf->getValue(offset) > 0.1) continue; ijk = iter.getCoord(); turnOff = true; for (Int32 m = 0; m < 26; ++m) { m_ijk = ijk + util::COORD_OFFSETS[m]; if (distAcc.probeValue(m_ijk, value)) { if (value > 0.1) { turnOff = false; break; } } } if (turnOff) { maskLeaf.setValueOff(offset); distLeaf->setValueOn(offset, -0.86602540378443861); } } } } } //////////////////////////////////////// // ShellVoxelCleaner /// @brief TBB body object that removes non-intersecting voxels that where set by rasterizing /// self-intersecting parts of the mesh. template class ShellVoxelCleaner { public: typedef typename FloatTreeT::ValueType FloatValueT; typedef typename tree::ValueAccessor DistAccessorT; typedef typename FloatTreeT::LeafNodeType DistLeafT; typedef tree::LeafManager DistArrayT; typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; typedef typename tree::ValueAccessor IntAccessorT; typedef typename IntTreeT::LeafNodeType IntLeafT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef typename tree::ValueAccessor BoolAccessorT; typedef typename BoolTreeT::LeafNodeType BoolLeafT; ShellVoxelCleaner(FloatTreeT& distTree, DistArrayT& leafs, IntTreeT& indexTree, BoolTreeT& intersectionTree); ~ShellVoxelCleaner() {} void run(bool threaded = true); ShellVoxelCleaner(const ShellVoxelCleaner &rhs); void operator()(const tbb::blocked_range&) const; private: void operator=(const ShellVoxelCleaner&) {} FloatTreeT& mDistTree; DistArrayT& mLeafs; IntTreeT& mIndexTree; BoolTreeT& mIntersectionTree; }; template void ShellVoxelCleaner::run(bool threaded) { if (threaded) tbb::parallel_for(mLeafs.getRange(), *this); else (*this)(mLeafs.getRange()); mDistTree.pruneInactive(); mIndexTree.pruneInactive(); } template ShellVoxelCleaner::ShellVoxelCleaner( FloatTreeT& distTree, DistArrayT& leafs, IntTreeT& indexTree, BoolTreeT& intersectionTree) : mDistTree(distTree) , mLeafs(leafs) , mIndexTree(indexTree) , mIntersectionTree(intersectionTree) { } template ShellVoxelCleaner::ShellVoxelCleaner( const ShellVoxelCleaner &rhs) : mDistTree(rhs.mDistTree) , mLeafs(rhs.mLeafs) , mIndexTree(rhs.mIndexTree) , mIntersectionTree(rhs.mIntersectionTree) { } template void ShellVoxelCleaner::operator()( const tbb::blocked_range& range) const { Coord ijk, m_ijk; bool turnOff; FloatValueT value; Index offset; typename DistLeafT::ValueOnCIter iter; const FloatValueT distBG = mDistTree.background(); const Int32 indexBG = mIntersectionTree.background(); IntAccessorT indexAcc(mIndexTree); DistAccessorT distAcc(mDistTree); BoolAccessorT maskAcc(mIntersectionTree); for (size_t n = range.begin(); n < range.end(); ++n) { DistLeafT& distLeaf = mLeafs.leaf(n); ijk = distLeaf.origin(); const BoolLeafT* maskLeaf = maskAcc.probeConstLeaf(ijk); IntLeafT& indexLeaf = *indexAcc.probeLeaf(ijk); iter = distLeaf.cbeginValueOn(); for (; iter; ++iter) { value = iter.getValue(); if(value > 0.0) continue; offset = iter.pos(); if (maskLeaf && maskLeaf->isValueOn(offset)) continue; ijk = iter.getCoord(); turnOff = true; for (Int32 m = 0; m < 18; ++m) { m_ijk = ijk + util::COORD_OFFSETS[m]; if (maskAcc.isValueOn(m_ijk)) { turnOff = false; break; } } if (turnOff) { distLeaf.setValueOff(offset, distBG); indexLeaf.setValueOff(offset, indexBG); } } } } //////////////////////////////////////// // ExpandNB /// @brief TBB body object to expand the level set narrow band /// @note The interior and exterior widths should be in world space units and squared. template class ExpandNB { public: typedef typename FloatTreeT::ValueType FloatValueT; typedef typename FloatTreeT::LeafNodeType FloatLeafT; typedef typename tree::ValueAccessor FloatAccessorT; typedef typename FloatTreeT::template ValueConverter::Type IntTreeT; typedef typename IntTreeT::LeafNodeType IntLeafT; typedef typename tree::ValueAccessor IntAccessorT; typedef typename FloatTreeT::template ValueConverter::Type BoolTreeT; typedef typename BoolTreeT::LeafNodeType BoolLeafT; typedef tree::LeafManager BoolLeafManager; typedef typename tree::ValueAccessor BoolAccessorT; ExpandNB(BoolLeafManager& leafs, FloatTreeT& distTree, IntTreeT& indexTree, BoolTreeT& maskTree, FloatValueT exteriorBandWidth, FloatValueT interiorBandWidth, FloatValueT voxelSize, const std::vector& pointList, const std::vector& polygonList); void run(bool threaded = true); void operator()(const tbb::blocked_range&); void join(ExpandNB&); ExpandNB(const ExpandNB&, tbb::split); ~ExpandNB() {} private: void operator=(const ExpandNB&) {} double evalVoxelDist(const Coord&, FloatAccessorT&, IntAccessorT&, BoolAccessorT&, std::vector&, Int32&) const; double evalVoxelDist(const Coord&, FloatLeafT&, IntLeafT&, BoolLeafT&, std::vector&, Int32&) const; double closestPrimDist(const Coord&, std::vector&, Int32&) const; BoolLeafManager& mMaskLeafs; FloatTreeT& mDistTree; IntTreeT& mIndexTree; BoolTreeT& mMaskTree; const FloatValueT mExteriorBandWidth, mInteriorBandWidth, mVoxelSize; const std::vector& mPointList; const std::vector& mPolygonList; FloatTreeT mNewDistTree; IntTreeT mNewIndexTree; BoolTreeT mNewMaskTree; }; template ExpandNB::ExpandNB( BoolLeafManager& leafs, FloatTreeT& distTree, IntTreeT& indexTree, BoolTreeT& maskTree, FloatValueT exteriorBandWidth, FloatValueT interiorBandWidth, FloatValueT voxelSize, const std::vector& pointList, const std::vector& polygonList) : mMaskLeafs(leafs) , mDistTree(distTree) , mIndexTree(indexTree) , mMaskTree(maskTree) , mExteriorBandWidth(exteriorBandWidth) , mInteriorBandWidth(interiorBandWidth) , mVoxelSize(voxelSize) , mPointList(pointList) , mPolygonList(polygonList) , mNewDistTree(std::numeric_limits::max()) , mNewIndexTree(Int32(util::INVALID_IDX)) , mNewMaskTree(false) { } template ExpandNB::ExpandNB(const ExpandNB& rhs, tbb::split) : mMaskLeafs(rhs.mMaskLeafs) , mDistTree(rhs.mDistTree) , mIndexTree(rhs.mIndexTree) , mMaskTree(rhs.mMaskTree) , mExteriorBandWidth(rhs.mExteriorBandWidth) , mInteriorBandWidth(rhs.mInteriorBandWidth) , mVoxelSize(rhs.mVoxelSize) , mPointList(rhs.mPointList) , mPolygonList(rhs.mPolygonList) , mNewDistTree(std::numeric_limits::max()) , mNewIndexTree(Int32(util::INVALID_IDX)) , mNewMaskTree(false) { } template void ExpandNB::run(bool threaded) { if (threaded) tbb::parallel_reduce(mMaskLeafs.getRange(), *this); else (*this)(mMaskLeafs.getRange()); mDistTree.merge(mNewDistTree); mIndexTree.merge(mNewIndexTree); mMaskTree.clear(); mMaskTree.merge(mNewMaskTree); } template void ExpandNB::operator()(const tbb::blocked_range& range) { Coord ijk; Int32 closestPrim = 0; Index pos = 0; FloatValueT distance; bool inside; FloatAccessorT newDistAcc(mNewDistTree); IntAccessorT newIndexAcc(mNewIndexTree); BoolAccessorT newMaskAcc(mNewMaskTree); FloatAccessorT distAcc(mDistTree); IntAccessorT indexAcc(mIndexTree); BoolAccessorT maskAcc(mMaskTree); CoordBBox bbox; std::vector primitives(18); for (size_t n = range.begin(); n < range.end(); ++n) { BoolLeafT& maskLeaf = mMaskLeafs.leaf(n); if (maskLeaf.isEmpty()) continue; ijk = maskLeaf.origin(); FloatLeafT* distLeafPt = distAcc.probeLeaf(ijk); if (!distLeafPt) { distLeafPt = new FloatLeafT(ijk, distAcc.getValue(ijk)); newDistAcc.addLeaf(distLeafPt); } IntLeafT* indexLeafPt = indexAcc.probeLeaf(ijk); if (!indexLeafPt) indexLeafPt = newIndexAcc.touchLeaf(ijk); bbox = maskLeaf.getNodeBoundingBox(); bbox.expand(-1); typename BoolLeafT::ValueOnIter iter = maskLeaf.beginValueOn(); for (; iter; ++iter) { ijk = iter.getCoord(); if (bbox.isInside(ijk)) { distance = evalVoxelDist(ijk, *distLeafPt, *indexLeafPt, maskLeaf, primitives, closestPrim); } else { distance = evalVoxelDist(ijk, distAcc, indexAcc, maskAcc, primitives, closestPrim); } pos = iter.pos(); inside = distLeafPt->getValue(pos) < FloatValueT(0.0); if (!inside && distance < mExteriorBandWidth) { distLeafPt->setValueOn(pos, distance); indexLeafPt->setValueOn(pos, closestPrim); } else if (inside && distance < mInteriorBandWidth) { distLeafPt->setValueOn(pos, -distance); indexLeafPt->setValueOn(pos, closestPrim); } else { continue; } for (Int32 i = 0; i < 6; ++i) { newMaskAcc.setValueOn(ijk + util::COORD_OFFSETS[i]); } } } } template double ExpandNB::evalVoxelDist( const Coord& ijk, FloatAccessorT& distAcc, IntAccessorT& indexAcc, BoolAccessorT& maskAcc, std::vector& prims, Int32& closestPrim) const { FloatValueT tmpDist, minDist = std::numeric_limits::max(); prims.clear(); // Collect primitive indices from active neighbors and min distance. Coord n_ijk; for (Int32 n = 0; n < 18; ++n) { n_ijk = ijk + util::COORD_OFFSETS[n]; if (!maskAcc.isValueOn(n_ijk) && distAcc.probeValue(n_ijk, tmpDist)) { prims.push_back(indexAcc.getValue(n_ijk)); tmpDist = std::abs(tmpDist); if (tmpDist < minDist) minDist = tmpDist; } } // Calc. this voxels distance to the closest primitive. tmpDist = FloatValueT(closestPrimDist(ijk, prims, closestPrim)); // Forces the gradient to be monotonic for non-manifold // polygonal models with self-intersections. return tmpDist > minDist ? tmpDist : minDist + mVoxelSize; } // Leaf specialized version. template double ExpandNB::evalVoxelDist( const Coord& ijk, FloatLeafT& distLeaf, IntLeafT& indexLeaf, BoolLeafT& maskLeaf, std::vector& prims, Int32& closestPrim) const { FloatValueT tmpDist, minDist = std::numeric_limits::max(); prims.clear(); Index pos; for (Int32 n = 0; n < 18; ++n) { pos = FloatLeafT::coordToOffset(ijk + util::COORD_OFFSETS[n]); if (!maskLeaf.isValueOn(pos) && distLeaf.probeValue(pos, tmpDist)) { prims.push_back(indexLeaf.getValue(pos)); tmpDist = std::abs(tmpDist); if (tmpDist < minDist) minDist = tmpDist; } } tmpDist = FloatValueT(closestPrimDist(ijk, prims, closestPrim)); return tmpDist > minDist ? tmpDist : minDist + mVoxelSize; } template double ExpandNB::closestPrimDist(const Coord& ijk, std::vector& prims, Int32& closestPrim) const { std::sort(prims.begin(), prims.end()); Int32 lastPrim = -1; Vec3d uvw, voxelCenter(ijk[0], ijk[1], ijk[2]); double primDist, tmpDist, dist = std::numeric_limits::max(); for (size_t n = 0, N = prims.size(); n < N; ++n) { if (prims[n] == lastPrim) continue; lastPrim = prims[n]; const Vec4I& verts = mPolygonList[lastPrim]; // Evaluate first triangle const Vec3d a(mPointList[verts[0]]); const Vec3d b(mPointList[verts[1]]); const Vec3d c(mPointList[verts[2]]); primDist = (voxelCenter - closestPointOnTriangleToPoint(a, c, b, voxelCenter, uvw)).lengthSqr(); // Split-up quad into a second triangle and calac distance. if (util::INVALID_IDX != verts[3]) { const Vec3d d(mPointList[verts[3]]); tmpDist = (voxelCenter - closestPointOnTriangleToPoint(a, d, c, voxelCenter, uvw)).lengthSqr(); if (tmpDist < primDist) primDist = tmpDist; } if (primDist < dist) { dist = primDist; closestPrim = lastPrim; } } return std::sqrt(dist) * double(mVoxelSize); } template void ExpandNB::join(ExpandNB& rhs) { mNewDistTree.merge(rhs.mNewDistTree); mNewIndexTree.merge(rhs.mNewIndexTree); mNewMaskTree.merge(rhs.mNewMaskTree); } //////////////////////////////////////// template struct SqrtAndScaleOp { SqrtAndScaleOp(ValueType voxelSize, bool unsignedDist = false) : mVoxelSize(voxelSize) , mUnsigned(unsignedDist) { } template void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const { ValueType w[2]; w[0] = mVoxelSize; w[1] = -mVoxelSize; typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); for (; iter; ++iter) { ValueType& val = const_cast(iter.getValue()); val = w[!mUnsigned && int(val < ValueType(0.0))] * std::sqrt(std::abs(val)); } } private: ValueType mVoxelSize; const bool mUnsigned; }; template struct VoxelSignOp { VoxelSignOp(ValueType exBandWidth, ValueType inBandWidth) : mExBandWidth(exBandWidth) , mInBandWidth(inBandWidth) { } template void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const { ValueType bgValues[2]; bgValues[0] = mExBandWidth; bgValues[1] = -mInBandWidth; typename LeafNodeType::ValueOffIter iter = leaf.beginValueOff(); for (; iter; ++iter) { ValueType& val = const_cast(iter.getValue()); val = bgValues[int(val < ValueType(0.0))]; } } private: ValueType mExBandWidth, mInBandWidth; }; template struct TrimOp { TrimOp(ValueType exBandWidth, ValueType inBandWidth) : mExBandWidth(exBandWidth) , mInBandWidth(inBandWidth) { } template void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const { typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); for (; iter; ++iter) { ValueType& val = const_cast(iter.getValue()); const bool inside = val < ValueType(0.0); if (inside && !(val > -mInBandWidth)) { val = -mInBandWidth; iter.setValueOff(); } else if (!inside && !(val < mExBandWidth)) { val = mExBandWidth; iter.setValueOff(); } } } private: ValueType mExBandWidth, mInBandWidth; }; template struct OffsetOp { OffsetOp(ValueType offset): mOffset(offset) {} void resetOffset(ValueType offset) { mOffset = offset; } template void operator()(LeafNodeType &leaf, size_t/*leafIndex*/) const { typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); for (; iter; ++iter) { ValueType& val = const_cast(iter.getValue()); val += mOffset; } } private: ValueType mOffset; }; template struct RenormOp { typedef math::BIAS_SCHEME Scheme; typedef typename Scheme::template ISStencil::StencilType Stencil; typedef tree::LeafManager LeafManagerType; typedef typename LeafManagerType::BufferType BufferType; RenormOp(GridType& grid, LeafManagerType& leafs, ValueType voxelSize, ValueType cfl = 1.0) : mGrid(grid) , mLeafs(leafs) , mVoxelSize(voxelSize) , mCFL(cfl) { } void resetCFL(ValueType cfl) { mCFL = cfl; } template void operator()(LeafNodeType &leaf, size_t leafIndex) const { const ValueType dt = mCFL * mVoxelSize, one(1.0), invDx = one / mVoxelSize; Stencil stencil(mGrid); BufferType& buffer = mLeafs.getBuffer(leafIndex, 1); typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); for (; iter; ++iter) { stencil.moveTo(iter); const ValueType normSqGradPhi = math::ISGradientNormSqrd::result(stencil); const ValueType phi0 = iter.getValue(); const ValueType diff = math::Sqrt(normSqGradPhi) * invDx - one; const ValueType S = phi0 / (math::Sqrt(math::Pow2(phi0) + normSqGradPhi)); buffer.setValue(iter.pos(), phi0 - dt * S * diff); } } private: GridType& mGrid; LeafManagerType& mLeafs; ValueType mVoxelSize, mCFL; }; template struct MinOp { typedef tree::LeafManager LeafManagerType; typedef typename LeafManagerType::BufferType BufferType; MinOp(LeafManagerType& leafs): mLeafs(leafs) {} template void operator()(LeafNodeType &leaf, size_t leafIndex) const { BufferType& buffer = mLeafs.getBuffer(leafIndex, 1); typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); for (; iter; ++iter) { ValueType& val = const_cast(iter.getValue()); val = std::min(val, buffer.getValue(iter.pos())); } } private: LeafManagerType& mLeafs; }; template struct MergeBufferOp { typedef tree::LeafManager LeafManagerType; typedef typename LeafManagerType::BufferType BufferType; MergeBufferOp(LeafManagerType& leafs, size_t bufferIndex = 1) : mLeafs(leafs) , mBufferIndex(bufferIndex) { } template void operator()(LeafNodeType &leaf, size_t leafIndex) const { BufferType& buffer = mLeafs.getBuffer(leafIndex, mBufferIndex); typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); Index offset; for (; iter; ++iter) { offset = iter.pos(); leaf.setValueOnly(offset, buffer.getValue(offset)); } } private: LeafManagerType& mLeafs; const size_t mBufferIndex; }; template struct LeafTopologyDiffOp { typedef typename tree::ValueAccessor AccessorT; typedef typename TreeType::LeafNodeType LeafNodeT; LeafTopologyDiffOp(TreeType& tree) : mAcc(tree) { } template void operator()(LeafNodeType &leaf, size_t) const { const LeafNodeT* rhsLeaf = mAcc.probeConstLeaf(leaf.origin()); if (rhsLeaf) leaf.topologyDifference(*rhsLeaf, false); } private: AccessorT mAcc; }; } // internal namespace //////////////////////////////////////// // MeshToVolume template MeshToVolume::MeshToVolume( openvdb::math::Transform::Ptr& transform, int conversionFlags, InterruptT *interrupter, int signSweeps) : mTransform(transform) , mConversionFlags(conversionFlags) , mSignSweeps(signSweeps) , mInterrupter(interrupter) { clear(); mSignSweeps = std::min(mSignSweeps, 1); } template void MeshToVolume::clear() { mDistGrid = FloatGridT::create(std::numeric_limits::max()); mIndexGrid = IntGridT::create(Int32(util::INVALID_IDX)); mIntersectingVoxelsGrid = BoolGridT::create(false); } template inline void MeshToVolume::convertToLevelSet( const std::vector& pointList, const std::vector& polygonList, FloatValueT exBandWidth, FloatValueT inBandWidth) { // The narrow band width is exclusive, the shortest valid distance has to be > 1 voxel exBandWidth = std::max(internal::Tolerance::minNarrowBandWidth(), exBandWidth); inBandWidth = std::max(internal::Tolerance::minNarrowBandWidth(), inBandWidth); const FloatValueT vs = mTransform->voxelSize()[0]; doConvert(pointList, polygonList, vs * exBandWidth, vs * inBandWidth); mDistGrid->setGridClass(GRID_LEVEL_SET); } template inline void MeshToVolume::convertToUnsignedDistanceField( const std::vector& pointList, const std::vector& polygonList, FloatValueT exBandWidth) { // The narrow band width is exclusive, the shortest valid distance has to be > 1 voxel exBandWidth = std::max(internal::Tolerance::minNarrowBandWidth(), exBandWidth); const FloatValueT vs = mTransform->voxelSize()[0]; doConvert(pointList, polygonList, vs * exBandWidth, 0.0, true); mDistGrid->setGridClass(GRID_UNKNOWN); } template void MeshToVolume::doConvert( const std::vector& pointList, const std::vector& polygonList, FloatValueT exBandWidth, FloatValueT inBandWidth, bool unsignedDistField) { mDistGrid->setTransform(mTransform); mIndexGrid->setTransform(mTransform); const bool rawData = OUTPUT_RAW_DATA & mConversionFlags; // The progress estimates given to the interrupter are based on the // observed average time for each stage and therefore not alway // accurate. The goal is to give some progression feedback to the user. if (wasInterrupted(1)) return; // Voxelize mesh { internal::MeshVoxelizer voxelizer(pointList, polygonList, mInterrupter); voxelizer.run(); if (wasInterrupted(18)) return; mDistGrid->tree().merge(voxelizer.sqrDistTree()); mIndexGrid->tree().merge(voxelizer.primIndexTree()); mIntersectingVoxelsGrid->tree().merge(voxelizer.intersectionTree()); } if (!unsignedDistField) { // Determine the inside/outside state for the narrow band of voxels. { // Slices up the volume and label the exterior contour of each slice in parallel. internal::ContourTracer trace( mDistGrid->tree(), mIntersectingVoxelsGrid->tree(), mInterrupter); for (int i = 0; i < mSignSweeps; ++i) { if (wasInterrupted(19)) return; trace.run(); if (wasInterrupted(24)) return; // Propagate sign information between the slices. BoolTreeT signMaskTree(false); { tree::LeafManager leafs(mDistGrid->tree()); internal::SignMask signMaskOp(leafs, mDistGrid->tree(), mIntersectingVoxelsGrid->tree(), mInterrupter); signMaskOp.run(); signMaskTree.merge(signMaskOp.signMaskTree()); } if (wasInterrupted(25)) return; while (true) { tree::LeafManager leafs(signMaskTree); if(leafs.leafCount() == 0) break; internal::PropagateSign sign(leafs, mDistGrid->tree(), mIntersectingVoxelsGrid->tree(), mInterrupter); sign.run(); signMaskTree.clear(); signMaskTree.merge(sign.signMaskTree()); } } } if (wasInterrupted(28)) return; { tree::LeafManager leafs(mIntersectingVoxelsGrid->tree()); // Determine the sign of the mesh intersecting voxels. internal::IntersectingVoxelSign sign(pointList, polygonList, mDistGrid->tree(), mIndexGrid->tree(), mIntersectingVoxelsGrid->tree(), leafs); sign.run(); if (wasInterrupted(34)) return; // Remove mesh intersecting voxels that where set by rasterizing // self-intersecting portions of the mesh. if (!rawData) { internal::IntersectingVoxelCleaner cleaner(mDistGrid->tree(), mIndexGrid->tree(), mIntersectingVoxelsGrid->tree(), leafs); cleaner.run(); } } // Remove shell voxels that where set by rasterizing // self-intersecting portions of the mesh. if (!rawData) { tree::LeafManager leafs(mDistGrid->tree()); internal::ShellVoxelCleaner cleaner(mDistGrid->tree(), leafs, mIndexGrid->tree(), mIntersectingVoxelsGrid->tree()); cleaner.run(); } if (wasInterrupted(38)) return; } else { // if unsigned dist. field inBandWidth = FloatValueT(0.0); } if (mDistGrid->activeVoxelCount() == 0) return; mIntersectingVoxelsGrid->clear(); const FloatValueT voxelSize(mTransform->voxelSize()[0]); { // Transform values (world space scaling etc.) tree::LeafManager leafs(mDistGrid->tree()); leafs.foreach(internal::SqrtAndScaleOp(voxelSize, unsignedDistField)); } if (wasInterrupted(40)) return; if (!unsignedDistField) { // Propagate sign information to inactive values. mDistGrid->tree().getRootNode().setBackground(exBandWidth, /*updateChildNodes=*/false); mDistGrid->tree().signedFloodFill(exBandWidth, -inBandWidth); } if (wasInterrupted(46)) return; // Narrow-band dilation const FloatValueT minWidth = voxelSize * 2.0; if (inBandWidth > minWidth || exBandWidth > minWidth) { // Create the initial voxel mask. BoolTreeT maskTree(false); maskTree.topologyUnion(mDistGrid->tree()); if (wasInterrupted(48)) return; internal::LeafTopologyDiffOp diffOp(mDistGrid->tree()); openvdb::tools::dilateVoxels(maskTree); unsigned maxIterations = std::numeric_limits::max(); float progress = 48, step = 0.0; // progress estimation.. double estimated = 2.0 * std::ceil((std::max(inBandWidth, exBandWidth) - minWidth) / voxelSize); if (estimated < double(maxIterations)) { maxIterations = unsigned(estimated); step = 42.0 / float(maxIterations); } unsigned count = 0; while (true) { if (wasInterrupted(int(progress))) return; tree::LeafManager leafs(maskTree); if (leafs.leafCount() == 0) break; leafs.foreach(diffOp); internal::ExpandNB expand( leafs, mDistGrid->tree(), mIndexGrid->tree(), maskTree, exBandWidth, inBandWidth, voxelSize, pointList, polygonList); expand.run(); if ((++count) >= maxIterations) break; progress += step; } } if (!bool(GENERATE_PRIM_INDEX_GRID & mConversionFlags)) mIndexGrid->clear(); if (wasInterrupted(80)) return; // Renormalize distances to smooth out bumps caused by self-intersecting // and overlapping portions of the mesh and renormalize the level set. if (!unsignedDistField && !rawData) { mDistGrid->tree().pruneLevelSet(); tree::LeafManager leafs(mDistGrid->tree(), 1); const FloatValueT offset = 0.8 * voxelSize; if (wasInterrupted(82)) return; internal::OffsetOp offsetOp(-offset); leafs.foreach(offsetOp); if (wasInterrupted(84)) return; leafs.foreach(internal::RenormOp(*mDistGrid, leafs, voxelSize)); leafs.foreach(internal::MinOp(leafs)); if (wasInterrupted(92)) return; offsetOp.resetOffset(offset - internal::Tolerance::epsilon()); leafs.foreach(offsetOp); } if (wasInterrupted(95)) return; const FloatValueT minTrimWidth = voxelSize * 4.0; if (inBandWidth < minTrimWidth || exBandWidth < minTrimWidth) { // If the narrow band was not expanded, we might need to trim off // some of the active voxels in order to respect the narrow band limits. // (The mesh voxelization step generates some extra 'shell' voxels) tree::LeafManager leafs(mDistGrid->tree()); leafs.foreach(internal::TrimOp( exBandWidth, unsignedDistField ? exBandWidth : inBandWidth)); } if (wasInterrupted(99)) return; mDistGrid->tree().pruneLevelSet(); mDistGrid->tree().signedFloodFill(exBandWidth, -inBandWidth); } //////////////////////////////////////// /// @internal This overload is enabled only for grids with a scalar, floating-point ValueType. template inline typename boost::enable_if, typename GridType::Ptr>::type doMeshConversion( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, const std::vector& quads, float exBandWidth, float inBandWidth, bool unsignedDistanceField = false) { std::vector indexSpacePoints(points.size()); { // Copy and transform (required for MeshToVolume) points to grid space. internal::PointTransform ptnXForm(points, indexSpacePoints, xform); ptnXForm.run(); } // Copy primitives std::vector primitives(triangles.size() + quads.size()); for (size_t n = 0, N = triangles.size(); n < N; ++n) { Vec4I& prim = primitives[n]; const Vec3I& triangle = triangles[n]; prim[0] = triangle[0]; prim[1] = triangle[1]; prim[2] = triangle[2]; prim[3] = util::INVALID_IDX; } for (size_t n = 0, N = quads.size(); n < N; ++n) { primitives[n + triangles.size()] = quads[n]; } typename GridType::ValueType exWidth(exBandWidth); typename GridType::ValueType inWidth(inBandWidth); math::Transform::Ptr transform = xform.copy(); MeshToVolume vol(transform); if (!unsignedDistanceField) { vol.convertToLevelSet(indexSpacePoints, primitives, exWidth, inWidth); } else { vol.convertToUnsignedDistanceField(indexSpacePoints, primitives, exWidth); } return vol.distGridPtr(); } /// @internal This overload is enabled only for grids that do not have a scalar, /// floating-point ValueType. template inline typename boost::disable_if, typename GridType::Ptr>::type doMeshConversion( const math::Transform& /*xform*/, const std::vector& /*points*/, const std::vector& /*triangles*/, const std::vector& /*quads*/, float /*exBandWidth*/, float /*inBandWidth*/, bool unsignedDistanceField = false) { OPENVDB_THROW(TypeError, "mesh to volume conversion is supported only for scalar, floating-point grids"); } //////////////////////////////////////// template inline typename GridType::Ptr meshToLevelSet( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, float halfWidth) { std::vector quads(0); return doMeshConversion(xform, points, triangles, quads, halfWidth, halfWidth); } template inline typename GridType::Ptr meshToLevelSet( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& quads, float halfWidth) { std::vector triangles(0); return doMeshConversion(xform, points, triangles, quads, halfWidth, halfWidth); } template inline typename GridType::Ptr meshToLevelSet( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, const std::vector& quads, float halfWidth) { return doMeshConversion(xform, points, triangles, quads, halfWidth, halfWidth); } template inline typename GridType::Ptr meshToSignedDistanceField( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, const std::vector& quads, float exBandWidth, float inBandWidth) { return doMeshConversion(xform, points, triangles, quads, exBandWidth, inBandWidth); } template inline typename GridType::Ptr meshToUnsignedDistanceField( const openvdb::math::Transform& xform, const std::vector& points, const std::vector& triangles, const std::vector& quads, float bandWidth) { return doMeshConversion(xform, points, triangles, quads, bandWidth, bandWidth, true); } //////////////////////////////////////////////////////////////////////////////// // Required by several of the tree nodes inline std::ostream& operator<<(std::ostream& ostr, const MeshToVoxelEdgeData::EdgeData& rhs) { ostr << "{[ " << rhs.mXPrim << ", " << rhs.mXDist << "]"; ostr << " [ " << rhs.mYPrim << ", " << rhs.mYDist << "]"; ostr << " [ " << rhs.mZPrim << ", " << rhs.mZDist << "]}"; return ostr; } // Required by math::Abs inline MeshToVoxelEdgeData::EdgeData Abs(const MeshToVoxelEdgeData::EdgeData& x) { return x; } //////////////////////////////////////// class MeshToVoxelEdgeData::GenEdgeData { public: GenEdgeData( const std::vector& pointList, const std::vector& polygonList); void run(bool threaded = true); GenEdgeData(GenEdgeData& rhs, tbb::split); inline void operator() (const tbb::blocked_range &range); inline void join(GenEdgeData& rhs); inline TreeType& tree() { return mTree; } private: void operator=(const GenEdgeData&) {} struct Primitive { Vec3d a, b, c, d; Int32 index; }; template inline void voxelize(const Primitive&); template inline bool evalPrimitive(const Coord&, const Primitive&); inline bool rayTriangleIntersection( const Vec3d& origin, const Vec3d& dir, const Vec3d& a, const Vec3d& b, const Vec3d& c, double& t); TreeType mTree; Accessor mAccessor; const std::vector& mPointList; const std::vector& mPolygonList; // Used internally for acceleration typedef TreeType::ValueConverter::Type IntTreeT; IntTreeT mLastPrimTree; tree::ValueAccessor mLastPrimAccessor; }; // class MeshToVoxelEdgeData::GenEdgeData inline MeshToVoxelEdgeData::GenEdgeData::GenEdgeData( const std::vector& pointList, const std::vector& polygonList) : mTree(EdgeData()) , mAccessor(mTree) , mPointList(pointList) , mPolygonList(polygonList) , mLastPrimTree(Int32(util::INVALID_IDX)) , mLastPrimAccessor(mLastPrimTree) { } inline MeshToVoxelEdgeData::GenEdgeData::GenEdgeData(GenEdgeData& rhs, tbb::split) : mTree(EdgeData()) , mAccessor(mTree) , mPointList(rhs.mPointList) , mPolygonList(rhs.mPolygonList) , mLastPrimTree(Int32(util::INVALID_IDX)) , mLastPrimAccessor(mLastPrimTree) { } inline void MeshToVoxelEdgeData::GenEdgeData::run(bool threaded) { if (threaded) { tbb::parallel_reduce(tbb::blocked_range(0, mPolygonList.size()), *this); } else { (*this)(tbb::blocked_range(0, mPolygonList.size())); } } inline void MeshToVoxelEdgeData::GenEdgeData::join(GenEdgeData& rhs) { typedef TreeType::RootNodeType RootNodeType; typedef RootNodeType::NodeChainType NodeChainType; BOOST_STATIC_ASSERT(boost::mpl::size::value > 1); typedef boost::mpl::at >::type InternalNodeType; Coord ijk; Index offset; rhs.mTree.clearAllAccessors(); TreeType::LeafIter leafIt = rhs.mTree.beginLeaf(); for ( ; leafIt; ++leafIt) { ijk = leafIt->origin(); TreeType::LeafNodeType* lhsLeafPt = mTree.probeLeaf(ijk); if (!lhsLeafPt) { mAccessor.addLeaf(rhs.mAccessor.probeLeaf(ijk)); InternalNodeType* node = rhs.mAccessor.getNode(); node->stealNode(ijk, EdgeData(), false); rhs.mAccessor.clear(); } else { TreeType::LeafNodeType::ValueOnCIter it = leafIt->cbeginValueOn(); for ( ; it; ++it) { offset = it.pos(); const EdgeData& rhsValue = it.getValue(); if (!lhsLeafPt->isValueOn(offset)) { lhsLeafPt->setValueOn(offset, rhsValue); } else { EdgeData& lhsValue = const_cast(lhsLeafPt->getValue(offset)); if (rhsValue.mXDist < lhsValue.mXDist) { lhsValue.mXDist = rhsValue.mXDist; lhsValue.mXPrim = rhsValue.mXPrim; } if (rhsValue.mYDist < lhsValue.mYDist) { lhsValue.mYDist = rhsValue.mYDist; lhsValue.mYPrim = rhsValue.mYPrim; } if (rhsValue.mZDist < lhsValue.mZDist) { lhsValue.mZDist = rhsValue.mZDist; lhsValue.mZPrim = rhsValue.mZPrim; } } } // end value iteration } } // end leaf iteration } inline void MeshToVoxelEdgeData::GenEdgeData::operator()(const tbb::blocked_range &range) { Primitive prim; for (size_t n = range.begin(); n < range.end(); ++n) { const Vec4I& verts = mPolygonList[n]; prim.index = Int32(n); prim.a = Vec3d(mPointList[verts[0]]); prim.b = Vec3d(mPointList[verts[1]]); prim.c = Vec3d(mPointList[verts[2]]); if (util::INVALID_IDX != verts[3]) { prim.d = Vec3d(mPointList[verts[3]]); voxelize(prim); } else { voxelize(prim); } } } template inline void MeshToVoxelEdgeData::GenEdgeData::voxelize(const Primitive& prim) { std::deque coordList; Coord ijk, nijk; ijk = util::nearestCoord(prim.a); coordList.push_back(ijk); evalPrimitive(ijk, prim); while (!coordList.empty()) { ijk = coordList.back(); coordList.pop_back(); for (Int32 i = 0; i < 26; ++i) { nijk = ijk + util::COORD_OFFSETS[i]; if (prim.index != mLastPrimAccessor.getValue(nijk)) { mLastPrimAccessor.setValue(nijk, prim.index); if(evalPrimitive(nijk, prim)) coordList.push_back(nijk); } } } } template inline bool MeshToVoxelEdgeData::GenEdgeData::evalPrimitive(const Coord& ijk, const Primitive& prim) { Vec3d uvw, org(ijk[0], ijk[1], ijk[2]); bool intersecting = false; double t; EdgeData edgeData; mAccessor.probeValue(ijk, edgeData); // Evaluate first triangle double dist = (org - closestPointOnTriangleToPoint(prim.a, prim.c, prim.b, org, uvw)).lengthSqr(); if (rayTriangleIntersection(org, Vec3d(1.0, 0.0, 0.0), prim.a, prim.c, prim.b, t)) { if (t < edgeData.mXDist) { edgeData.mXDist = t; edgeData.mXPrim = prim.index; intersecting = true; } } if (rayTriangleIntersection(org, Vec3d(0.0, 1.0, 0.0), prim.a, prim.c, prim.b, t)) { if (t < edgeData.mYDist) { edgeData.mYDist = t; edgeData.mYPrim = prim.index; intersecting = true; } } if (rayTriangleIntersection(org, Vec3d(0.0, 0.0, 1.0), prim.a, prim.c, prim.b, t)) { if (t < edgeData.mZDist) { edgeData.mZDist = t; edgeData.mZPrim = prim.index; intersecting = true; } } if (IsQuad) { // Split quad into a second triangle and calculate distance. double secondDist = (org - closestPointOnTriangleToPoint(prim.a, prim.d, prim.c, org, uvw)).lengthSqr(); if (secondDist < dist) dist = secondDist; if (rayTriangleIntersection(org, Vec3d(1.0, 0.0, 0.0), prim.a, prim.d, prim.c, t)) { if (t < edgeData.mXDist) { edgeData.mXDist = t; edgeData.mXPrim = prim.index; intersecting = true; } } if (rayTriangleIntersection(org, Vec3d(0.0, 1.0, 0.0), prim.a, prim.d, prim.c, t)) { if (t < edgeData.mYDist) { edgeData.mYDist = t; edgeData.mYPrim = prim.index; intersecting = true; } } if (rayTriangleIntersection(org, Vec3d(0.0, 0.0, 1.0), prim.a, prim.d, prim.c, t)) { if (t < edgeData.mZDist) { edgeData.mZDist = t; edgeData.mZPrim = prim.index; intersecting = true; } } } if (intersecting) mAccessor.setValue(ijk, edgeData); return (dist < 0.86602540378443861); } inline bool MeshToVoxelEdgeData::GenEdgeData::rayTriangleIntersection( const Vec3d& origin, const Vec3d& dir, const Vec3d& a, const Vec3d& b, const Vec3d& c, double& t) { // Check if ray is parallel with triangle Vec3d e1 = b - a; Vec3d e2 = c - a; Vec3d s1 = dir.cross(e2); double divisor = s1.dot(e1); if (!(std::abs(divisor) > 0.0)) return false; // Compute barycentric coordinates double inv_divisor = 1.0 / divisor; Vec3d d = origin - a; double b1 = d.dot(s1) * inv_divisor; if (b1 < 0.0 || b1 > 1.0) return false; Vec3d s2 = d.cross(e1); double b2 = dir.dot(s2) * inv_divisor; if (b2 < 0.0 || (b1 + b2) > 1.0) return false; // Compute distance to intersection point t = e2.dot(s2) * inv_divisor; return (t < 0.0) ? false : true; } //////////////////////////////////////// inline MeshToVoxelEdgeData::MeshToVoxelEdgeData() : mTree(EdgeData()) { } inline void MeshToVoxelEdgeData::convert( const std::vector& pointList, const std::vector& polygonList) { GenEdgeData converter(pointList, polygonList); converter.run(); mTree.clear(); mTree.merge(converter.tree()); } inline void MeshToVoxelEdgeData::getEdgeData( Accessor& acc, const Coord& ijk, std::vector& points, std::vector& primitives) { EdgeData data; Vec3d point; Coord coord = ijk; if (acc.probeValue(coord, data)) { if (data.mXPrim != util::INVALID_IDX) { point[0] = double(coord[0]) + data.mXDist; point[1] = double(coord[1]); point[2] = double(coord[2]); points.push_back(point); primitives.push_back(data.mXPrim); } if (data.mYPrim != util::INVALID_IDX) { point[0] = double(coord[0]); point[1] = double(coord[1]) + data.mYDist; point[2] = double(coord[2]); points.push_back(point); primitives.push_back(data.mYPrim); } if (data.mZPrim != util::INVALID_IDX) { point[0] = double(coord[0]); point[1] = double(coord[1]); point[2] = double(coord[2]) + data.mZDist; points.push_back(point); primitives.push_back(data.mZPrim); } } coord[0] += 1; if (acc.probeValue(coord, data)) { if (data.mYPrim != util::INVALID_IDX) { point[0] = double(coord[0]); point[1] = double(coord[1]) + data.mYDist; point[2] = double(coord[2]); points.push_back(point); primitives.push_back(data.mYPrim); } if (data.mZPrim != util::INVALID_IDX) { point[0] = double(coord[0]); point[1] = double(coord[1]); point[2] = double(coord[2]) + data.mZDist; points.push_back(point); primitives.push_back(data.mZPrim); } } coord[2] += 1; if (acc.probeValue(coord, data)) { if (data.mYPrim != util::INVALID_IDX) { point[0] = double(coord[0]); point[1] = double(coord[1]) + data.mYDist; point[2] = double(coord[2]); points.push_back(point); primitives.push_back(data.mYPrim); } } coord[0] -= 1; if (acc.probeValue(coord, data)) { if (data.mXPrim != util::INVALID_IDX) { point[0] = double(coord[0]) + data.mXDist; point[1] = double(coord[1]); point[2] = double(coord[2]); points.push_back(point); primitives.push_back(data.mXPrim); } if (data.mYPrim != util::INVALID_IDX) { point[0] = double(coord[0]); point[1] = double(coord[1]) + data.mYDist; point[2] = double(coord[2]); points.push_back(point); primitives.push_back(data.mYPrim); } } coord[1] += 1; if (acc.probeValue(coord, data)) { if (data.mXPrim != util::INVALID_IDX) { point[0] = double(coord[0]) + data.mXDist; point[1] = double(coord[1]); point[2] = double(coord[2]); points.push_back(point); primitives.push_back(data.mXPrim); } } coord[2] -= 1; if (acc.probeValue(coord, data)) { if (data.mXPrim != util::INVALID_IDX) { point[0] = double(coord[0]) + data.mXDist; point[1] = double(coord[1]); point[2] = double(coord[2]); points.push_back(point); primitives.push_back(data.mXPrim); } if (data.mZPrim != util::INVALID_IDX) { point[0] = double(coord[0]); point[1] = double(coord[1]); point[2] = double(coord[2]) + data.mZDist; points.push_back(point); primitives.push_back(data.mZPrim); } } coord[0] += 1; if (acc.probeValue(coord, data)) { if (data.mZPrim != util::INVALID_IDX) { point[0] = double(coord[0]); point[1] = double(coord[1]); point[2] = double(coord[2]) + data.mZDist; points.push_back(point); primitives.push_back(data.mZPrim); } } } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetFilter.h0000644000000000000000000005066712252453157015437 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file LevelSetFilter.h /// /// @brief Performs various types of level set deformations with /// interface tracking. These unrestricted deformations include /// surface smoothing (e.g., Laplacian flow), filtering (e.g., mean /// value) and morphological operations (e.g., morphological opening). /// All these operations can optionally be masked with another grid that /// acts as an alpha-mask. #ifndef OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED #include #include #include "LevelSetTracker.h" #include "Interpolation.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Filtering (e.g. diffusion) of narrow-band level sets. An /// optional scalar field can be used to produce a (smooth) alpha mask /// for the filtering. /// /// @note This class performs propper interface tracking which allows /// for unrestricted surface deformations template::Type, typename InterruptT = util::NullInterrupter> class LevelSetFilter : public LevelSetTracker { public: typedef LevelSetTracker BaseType; typedef GridT GridType; typedef MaskT MaskType; typedef typename GridType::TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename MaskType::ValueType AlphaType; typedef typename tree::LeafManager::LeafRange RangeType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); /// @brief Main constructor from a grid /// @param grid The level set to be filtered. /// @param interrupt Optional interrupter. LevelSetFilter(GridType& grid, InterruptT* interrupt = NULL) : BaseType(grid, interrupt) , mTask(0) , mMask(NULL) , mMinMask(0) , mMaxMask(1) , mInvertMask(false) { } /// @brief Shallow copy constructor called by tbb::parallel_for() /// threads during filtering. /// @param other The other LevelSetFilter from which to copy. LevelSetFilter(const LevelSetFilter& other) : BaseType(other) , mTask(other.mTask) , mMask(other.mMask) , mMinMask(other.mMinMask) , mMaxMask(other.mMaxMask) , mInvertMask(other.mInvertMask) { } /// @brief Destructor virtual ~LevelSetFilter() {}; /// @brief Used internally by tbb::parallel_for(). /// @param range The range over which to perform multi-threading. /// @warning Never call this method directly! void operator()(const RangeType& range) const { if (mTask) mTask(const_cast(this), range); else OPENVDB_THROW(ValueError, "task is undefined - call offset(), etc"); } /// @brief Return the minimum value of the mask to be used for the /// derivation of a smooth alpha value. AlphaType minMask() const { return mMinMask; } /// @brief Return the maximum value of the mask to be used for the /// derivation of a smooth alpha value. AlphaType maxMask() const { return mMaxMask; } /// @brief Define the range for the (optional) scalar mask. /// @param min Minimum value of the range. /// @param max Maximum value of the range. /// @details Mask values outside the range maps to alpha values of /// respectfully zero and one, and values inside the range maps /// smoothly to 0->1 (unless of course the mask is inverted). /// @throw ValueError if @a min is not smaller then @a max. void setMaskRange(AlphaType min, AlphaType max) { if (!(min < max)) OPENVDB_THROW(ValueError, "Invalid mask range (expects min < max)"); mMinMask = min; mMaxMask = max; } /// @brief Return true if the mask is inverted, i.e. min->max in the /// original mask maps to 1->0 in the inverted alpha mask. bool isMaskInverted() const { return mInvertMask; } /// @brief Invert the optional mask, i.e. min->max in the original /// mask maps to 1->0 in the inverted alpha mask. void invertMask(bool invert=true) { mInvertMask = invert; } /// @brief One iteration of mean-curvature flow of the level set. /// @param mask Optional alpha mask. void meanCurvature(const MaskType* mask = NULL); /// @brief One iteration of laplacian flow of the level set. /// @param mask Optional alpha mask. void laplacian(const MaskType* mask = NULL); /// @brief One iteration of a fast separable gaussian filter. /// @param width Width of the gaussian kernel in voxel units. /// @param mask Optional alpha mask. /// /// @note This is approximated as 4 iterations of a separable mean filter /// which typically leads an approximation that's better than 95%! void gaussian(int width = 1, const MaskType* mask = NULL); /// @brief Offset the level set by the specified (world) distance. /// @param offset Value of the offset. /// @param mask Optional alpha mask. void offset(ValueType offset, const MaskType* mask = NULL); /// @brief One iteration of median-value flow of the level set. /// @param width Width of the median-value kernel in voxel units. /// @param mask Optional alpha mask. /// /// @warning This filter is not separable and is hence relatively /// slow! void median(int width = 1, const MaskType* mask = NULL); /// @brief One iteration of mean-value flow of the level set. /// @param width Width of the mean-value kernel in voxel units. /// @param mask Optional alpha mask. /// /// @note This filter is separable so it's fast! void mean(int width = 1, const MaskType* mask = NULL); private: typedef typename TreeType::LeafNodeType LeafT; typedef typename LeafT::ValueOnIter VoxelIterT; typedef typename LeafT::ValueOnCIter VoxelCIterT; typedef typename tree::LeafManager::BufferType BufferT; typedef typename RangeType::Iterator LeafIterT; // Only two private member data typename boost::function mTask; const MaskType* mMask; AlphaType mMinMask, mMaxMask; bool mInvertMask; // Private cook method calling tbb::parallel_for void cook(bool swap) { const int n = BaseType::getGrainSize(); if (n>0) { tbb::parallel_for(BaseType::leafs().leafRange(n), *this); } else { (*this)(BaseType::leafs().leafRange()); } if (swap) BaseType::leafs().swapLeafBuffer(1, n==0); } // Private class to derive the normalized alpha mask struct AlphaMask { AlphaMask(const GridType& grid, const MaskType& mask, AlphaType min, AlphaType max, bool invert) : mSampler(mask, grid), mMin(min), mInvNorm(1/(max-min)), mInvert(invert) { assert(min < max); } inline bool operator()(const Coord& xyz, AlphaType& a, AlphaType& b) const { a = mSampler(xyz); const AlphaType t = (a-mMin)*mInvNorm; a = t > 0 ? t < 1 ? (3-2*t)*t*t : 1 : 0;//smooth mapping to 0->1 b = 1 - a; if (mInvert) std::swap(a,b); return a>0; } tools::DualGridSampler mSampler; const AlphaType mMin, mInvNorm; const bool mInvert; }; // Private driver method for mean and gaussian filtering void box(int width); template struct Avg { Avg(const GridT& grid, Int32 w) : acc(grid.tree()), width(w), frac(1/ValueType(2*w+1)) {} ValueType operator()(Coord xyz) { ValueType sum = zeroVal(); Int32& i = xyz[Axis], j = i + width; for (i -= width; i <= j; ++i) sum += acc.getValue(xyz); return sum*frac; } typename GridT::ConstAccessor acc; const Int32 width; const ValueType frac; }; // Private methods called by tbb::parallel_for threads template void doBox( const RangeType& r, Int32 w); void doBoxX(const RangeType& r, Int32 w) { this->doBox >(r,w); } void doBoxZ(const RangeType& r, Int32 w) { this->doBox >(r,w); } void doBoxY(const RangeType& r, Int32 w) { this->doBox >(r,w); } void doMedian(const RangeType&, int); void doMeanCurvature(const RangeType&); void doLaplacian(const RangeType&); void doOffset(const RangeType&, ValueType); }; // end of LevelSetFilter class //////////////////////////////////////// template inline void LevelSetFilter::median(int width, const MaskType* mask) { mMask = mask; BaseType::startInterrupter("Median-value flow of level set"); BaseType::leafs().rebuildAuxBuffers(1, BaseType::getGrainSize()==0); mTask = boost::bind(&LevelSetFilter::doMedian, _1, _2, std::max(1, width)); this->cook(true); BaseType::track(); BaseType::endInterrupter(); } template inline void LevelSetFilter::mean(int width, const MaskType* mask) { mMask = mask; BaseType::startInterrupter("Mean-value flow of level set"); this->box(width); BaseType::endInterrupter(); } template inline void LevelSetFilter::gaussian(int width, const MaskType* mask) { mMask = mask; BaseType::startInterrupter("Gaussian flow of level set"); for (int n=0; n<4; ++n) this->box(width); BaseType::endInterrupter(); } template inline void LevelSetFilter::box(int width) { BaseType::leafs().rebuildAuxBuffers(1, BaseType::getGrainSize()==0); width = std::max(1, width); mTask = boost::bind(&LevelSetFilter::doBoxX, _1, _2, width); this->cook(true); mTask = boost::bind(&LevelSetFilter::doBoxY, _1, _2, width); this->cook(true); mTask = boost::bind(&LevelSetFilter::doBoxZ, _1, _2, width); this->cook(true); BaseType::track(); } template inline void LevelSetFilter::meanCurvature(const MaskType* mask) { mMask = mask; BaseType::startInterrupter("Mean-curvature flow of level set"); BaseType::leafs().rebuildAuxBuffers(1, BaseType::getGrainSize()==0); mTask = boost::bind(&LevelSetFilter::doMeanCurvature, _1, _2); this->cook(true); BaseType::track(); BaseType::endInterrupter(); } template inline void LevelSetFilter::laplacian(const MaskType* mask) { mMask = mask; BaseType::startInterrupter("Laplacian flow of level set"); BaseType::leafs().rebuildAuxBuffers(1, BaseType::getGrainSize()==0); mTask = boost::bind(&LevelSetFilter::doLaplacian, _1, _2); this->cook(true); BaseType::track(); BaseType::endInterrupter(); } template inline void LevelSetFilter::offset(ValueType value, const MaskType* mask) { mMask = mask; BaseType::startInterrupter("Offsetting level set"); BaseType::leafs().removeAuxBuffers();// no auxiliary buffers required const ValueType CFL = ValueType(0.5) * BaseType::voxelSize(), offset = openvdb::math::Abs(value); ValueType dist = 0.0; while (offset-dist > ValueType(0.001)*CFL && BaseType::checkInterrupter()) { const ValueType delta = openvdb::math::Min(offset-dist, CFL); dist += delta; mTask = boost::bind(&LevelSetFilter::doOffset, _1, _2, copysign(delta, value)); this->cook(false); BaseType::track(); } BaseType::endInterrupter(); } ///////////////////////// PRIVATE METHODS ////////////////////// /// Performs parabolic mean-curvature diffusion template inline void LevelSetFilter::doMeanCurvature(const RangeType& range) { BaseType::checkInterrupter(); //const float CFL = 0.9f, dt = CFL * mDx * mDx / 6.0f; const ValueType dx = BaseType::voxelSize(), dt = math::Pow2(dx) / ValueType(3.0); math::CurvatureStencil stencil(BaseType::grid(), dx); if (mMask) { AlphaType a, b; AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { if (alpha(iter.getCoord(), a, b)) { stencil.moveTo(iter); const ValueType phi0 = *iter, phi1 = phi0 + dt*stencil.meanCurvatureNormGrad(); buffer.setValue(iter.pos(), b*phi0 + a*phi1); } } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); buffer.setValue(iter.pos(), *iter + dt*stencil.meanCurvatureNormGrad()); } } } } /// Performs laplacian diffusion. Note if the grids contains a true /// signed distance field (e.g. a solution to the Eikonal equation) /// Laplacian diffusions (e.g. geometric heat equation) is actually /// identical to mean curvature diffusion, yet less computationally /// expensive! In other words if you're performing renormalization /// anyway (e.g. rebuilding the narrow-band) you should consider /// performing laplacian diffusion over mean curvature flow! template inline void LevelSetFilter::doLaplacian(const RangeType& range) { BaseType::checkInterrupter(); //const float CFL = 0.9f, half_dt = CFL * mDx * mDx / 12.0f; const ValueType dx = BaseType::voxelSize(), dt = math::Pow2(dx) / ValueType(6.0); math::GradStencil stencil(BaseType::grid(), dx); if (mMask) { AlphaType a, b; AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { if (alpha(iter.getCoord(), a, b)) { stencil.moveTo(iter); const ValueType phi0 = *iter, phi1 = phi0 + dt*stencil.laplacian(); buffer.setValue(iter.pos(), b*phi0 + a*phi1); } } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); buffer.setValue(iter.pos(), *iter + dt*stencil.laplacian()); } } } } /// Offsets the values by a constant template inline void LevelSetFilter::doOffset(const RangeType& range, ValueType offset) { BaseType::checkInterrupter(); if (mMask) { AlphaType a, b; AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { if (alpha(iter.getCoord(), a, b)) iter.setValue(*iter + a*offset); } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { for (VoxelIterT iter = leafIter->beginValueOn(); iter; ++iter) { iter.setValue(*iter + offset); } } } } /// Performs simple but slow median-value diffusion template inline void LevelSetFilter::doMedian(const RangeType& range, int width) { BaseType::checkInterrupter(); typename math::DenseStencil stencil(BaseType::grid(), width);//creates local cache! if (mMask) { AlphaType a, b; AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { if (alpha(iter.getCoord(), a, b)) { stencil.moveTo(iter); buffer.setValue(iter.pos(), b*(*iter) + a*stencil.median()); } } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { stencil.moveTo(iter); buffer.setValue(iter.pos(), stencil.median()); } } } } /// One dimensional convolution of a separable box filter template template inline void LevelSetFilter::doBox(const RangeType& range, Int32 w) { BaseType::checkInterrupter(); AvgT avg(BaseType::grid(), w); if (mMask) { AlphaType a, b; AlphaMask alpha(BaseType::grid(), *mMask, mMinMask, mMaxMask, mInvertMask); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { const Coord xyz = iter.getCoord(); if (alpha(xyz, a, b)) buffer.setValue(iter.pos(), b*(*iter)+ a*avg(xyz)); } } } else { for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { BufferT& buffer = leafIter.buffer(1); for (VoxelCIterT iter = leafIter->cbeginValueOn(); iter; ++iter) { buffer.setValue(iter.pos(), avg(iter.getCoord())); } } } } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVELSETFILTER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/VolumeToSpheres.h0000644000000000000000000007237412252453157015651 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_TOOLS_VOLUME_TO_SPHERES_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_VOLUME_TO_SPHERES_HAS_BEEN_INCLUDED #include #include #include // for erodeVoxels() #include #include #include #include #include #include #include #include #include #include // std::numeric_limits ////////// namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Threaded method to fill a closed level set or fog volume /// with adaptively sized spheres. /// /// @param grid a scalar gird to fill with spheres. /// /// @param spheres a @c Vec4 array representing the spheres that returned by this /// method. The first three components specify the sphere center /// and the fourth is the radius. The spheres in this array are /// ordered by radius, biggest to smallest. /// /// @param maxSphereCount no more than this number of spheres are generated. /// /// @param overlapping toggle to allow spheres to overlap/intersect /// /// @param minRadius determines the smallest sphere size in voxel units. /// /// @param maxRadius determines the largest sphere size in voxel units. /// /// @param isovalue the crossing point of the volume values that is considered /// the surface. The zero default value works for signed distance /// fields while fog volumes require a larger positive value, /// 0.5 is a good initial guess. /// /// @param instanceCount how many interior points to consider for the sphere placement, /// increasing this count increases the chances of finding optimal /// sphere sizes. /// /// @param interrupter a pointer adhering to the util::NullInterrupter interface /// template inline void fillWithSpheres( const GridT& grid, std::vector& spheres, int maxSphereCount, bool overlapping = false, float minRadius = 1.0, float maxRadius = std::numeric_limits::max(), float isovalue = 0.0, int instanceCount = 10000, InterrupterT* interrupter = NULL); /// @brief @c fillWithSpheres method variant that automatically infers /// the util::NullInterrupter. template inline void fillWithSpheres( const GridT& grid, std::vector& spheres, int maxSphereCount, bool overlapping = false, float minRadius = 1.0, float maxRadius = std::numeric_limits::max(), float isovalue = 0.0, int instanceCount = 10000) { fillWithSpheres(grid, spheres, maxSphereCount, overlapping, minRadius, maxRadius, isovalue, instanceCount); } //////////////////////////////////////// /// @brief Accelerated closest surface point queries for narrow band level sets. /// Supports queries that originate at arbitrary worldspace locations, is /// not confined to the narrow band region of the input volume geometry. template class ClosestSurfacePoint { public: ClosestSurfacePoint(); /// @brief Extracts the surface points and constructs a spatial acceleration structure. /// /// @param grid a scalar gird, level set or fog volume. /// /// @param isovalue the crossing point of the volume values that is considered /// the surface. The zero default value works for signed distance /// fields while fog volumes require a larger positive value, /// 0.5 is a good initial guess. /// /// @param interrupter a pointer adhering to the util::NullInterrupter interface. /// template void initialize(const GridT& grid, float isovalue = 0.0, InterrupterT* interrupter = NULL); /// @brief @c initialize method variant that automatically infers /// the util::NullInterrupter. void initialize(const GridT& grid, float isovalue = 0.0); /// @brief Computes distance to closest surface. /// /// @param points search locations in world space. /// /// @param distances list of closest surface point distances, populated by this method. /// bool search(const std::vector& points, std::vector& distances); /// @brief Performs closest point searches. /// /// @param points search locations in world space to be replaced by their closest /// surface point. /// /// @param distances list of closest surface point distances, populated by this method. /// bool searchAndReplace(std::vector& points, std::vector& distances); private: typedef typename GridT::TreeType TreeT; typedef typename TreeT::template ValueConverter::Type IntTreeT; typedef typename IntTreeT::LeafNodeType IntLeafT; typedef std::pair IndexRange; bool mIsInitialized; std::vector mLeafBoundingSpheres, mNodeBoundingSpheres; std::vector mLeafRanges; std::vector mLeafNodes; PointList mSurfacePointList; size_t mPointListSize, mMaxNodeLeafs; float mMaxRadiusSqr; typename IntTreeT::Ptr mIdxTreePt; bool search(std::vector&, std::vector&, bool transformPoints); }; //////////////////////////////////////// // Internal utility methods namespace internal { struct PointAccessor { PointAccessor(std::vector& points) : mPoints(points) { } void add(const Vec3R &pos) { mPoints.push_back(pos); } private: std::vector& mPoints; }; template class LeafBS { public: LeafBS(std::vector& leafBoundingSpheres, const std::vector& leafNodes, const math::Transform& transform, const PointList& surfacePointList); void run(bool threaded = true); void operator()(const tbb::blocked_range&) const; private: std::vector& mLeafBoundingSpheres; const std::vector& mLeafNodes; const math::Transform& mTransform; const PointList& mSurfacePointList; }; template LeafBS::LeafBS( std::vector& leafBoundingSpheres, const std::vector& leafNodes, const math::Transform& transform, const PointList& surfacePointList) : mLeafBoundingSpheres(leafBoundingSpheres) , mLeafNodes(leafNodes) , mTransform(transform) , mSurfacePointList(surfacePointList) { } template void LeafBS::run(bool threaded) { if (threaded) { tbb::parallel_for(tbb::blocked_range(0, mLeafNodes.size()), *this); } else { (*this)(tbb::blocked_range(0, mLeafNodes.size())); } } template void LeafBS::operator()(const tbb::blocked_range& range) const { typename IntLeafT::ValueOnCIter iter; Vec3s avg; for (size_t n = range.begin(); n != range.end(); ++n) { avg[0] = 0.0; avg[1] = 0.0; avg[2] = 0.0; int count = 0; for (iter = mLeafNodes[n]->cbeginValueOn(); iter; ++iter) { avg += mSurfacePointList[iter.getValue()]; ++count; } if (count > 1) avg *= float(1.0 / double(count)); float maxDist = mTransform.voxelSize()[0]; maxDist *= maxDist; for (iter = mLeafNodes[n]->cbeginValueOn(); iter; ++iter) { float tmpDist = (mSurfacePointList[iter.getValue()] - avg).lengthSqr(); if (tmpDist > maxDist) maxDist = tmpDist; } Vec4R& sphere = mLeafBoundingSpheres[n]; sphere[0] = avg[0]; sphere[1] = avg[1]; sphere[2] = avg[2]; sphere[3] = maxDist; } } class NodeBS { public: typedef std::pair IndexRange; NodeBS(std::vector& nodeBoundingSpheres, const std::vector& leafRanges, const std::vector& leafBoundingSpheres); void run(bool threaded = true); void operator()(const tbb::blocked_range&) const; private: std::vector& mNodeBoundingSpheres; const std::vector& mLeafRanges; const std::vector& mLeafBoundingSpheres; }; inline NodeBS::NodeBS(std::vector& nodeBoundingSpheres, const std::vector& leafRanges, const std::vector& leafBoundingSpheres) : mNodeBoundingSpheres(nodeBoundingSpheres) , mLeafRanges(leafRanges) , mLeafBoundingSpheres(leafBoundingSpheres) { } inline void NodeBS::run(bool threaded) { if (threaded) { tbb::parallel_for(tbb::blocked_range(0, mLeafRanges.size()), *this); } else { (*this)(tbb::blocked_range(0, mLeafRanges.size())); } } inline void NodeBS::operator()(const tbb::blocked_range& range) const { Vec3s avg, pos; for (size_t n = range.begin(); n != range.end(); ++n) { avg[0] = 0.0; avg[1] = 0.0; avg[2] = 0.0; int count = mLeafRanges[n].second - mLeafRanges[n].first; for (size_t i = mLeafRanges[n].first; i < mLeafRanges[n].second; ++i) { avg[0] += mLeafBoundingSpheres[i][0]; avg[1] += mLeafBoundingSpheres[i][1]; avg[2] += mLeafBoundingSpheres[i][2]; } if (count > 1) avg *= float(1.0 / double(count)); float maxDist = 0.0; for (size_t i = mLeafRanges[n].first; i < mLeafRanges[n].second; ++i) { pos[0] = mLeafBoundingSpheres[i][0]; pos[1] = mLeafBoundingSpheres[i][1]; pos[2] = mLeafBoundingSpheres[i][2]; float tmpDist = (pos - avg).lengthSqr() + mLeafBoundingSpheres[i][3]; if (tmpDist > maxDist) maxDist = tmpDist; } Vec4R& sphere = mNodeBoundingSpheres[n]; sphere[0] = avg[0]; sphere[1] = avg[1]; sphere[2] = avg[2]; sphere[3] = maxDist; } } //////////////////////////////////////// template class ClosestPointDist { public: typedef std::pair IndexRange; ClosestPointDist( std::vector& instancePoints, std::vector& instanceDistances, const PointList& surfacePointList, const std::vector& leafNodes, const std::vector& leafRanges, const std::vector& leafBoundingSpheres, const std::vector& nodeBoundingSpheres, size_t maxNodeLeafs, bool transformPoints = false); void run(bool threaded = true); void operator()(const tbb::blocked_range&) const; private: void evalLeaf(size_t index, const IntLeafT& leaf) const; void evalNode(size_t pointIndex, size_t nodeIndex) const; std::vector& mInstancePoints; std::vector& mInstanceDistances; const PointList& mSurfacePointList; const std::vector& mLeafNodes; const std::vector& mLeafRanges; const std::vector& mLeafBoundingSpheres; const std::vector& mNodeBoundingSpheres; std::vector mLeafDistances, mNodeDistances; const bool mTransformPoints; size_t mClosestPointIndex; }; template ClosestPointDist::ClosestPointDist( std::vector& instancePoints, std::vector& instanceDistances, const PointList& surfacePointList, const std::vector& leafNodes, const std::vector& leafRanges, const std::vector& leafBoundingSpheres, const std::vector& nodeBoundingSpheres, size_t maxNodeLeafs, bool transformPoints) : mInstancePoints(instancePoints) , mInstanceDistances(instanceDistances) , mSurfacePointList(surfacePointList) , mLeafNodes(leafNodes) , mLeafRanges(leafRanges) , mLeafBoundingSpheres(leafBoundingSpheres) , mNodeBoundingSpheres(nodeBoundingSpheres) , mLeafDistances(maxNodeLeafs, 0.0) , mNodeDistances(leafRanges.size(), 0.0) , mTransformPoints(transformPoints) , mClosestPointIndex(0) { } template void ClosestPointDist::run(bool threaded) { if (threaded) { tbb::parallel_for(tbb::blocked_range(0, mInstancePoints.size()), *this); } else { (*this)(tbb::blocked_range(0, mInstancePoints.size())); } } template void ClosestPointDist::evalLeaf(size_t index, const IntLeafT& leaf) const { typename IntLeafT::ValueOnCIter iter; const Vec3s center = mInstancePoints[index]; size_t& closestPointIndex = const_cast(mClosestPointIndex); for (iter = leaf.cbeginValueOn(); iter; ++iter) { const Vec3s& point = mSurfacePointList[iter.getValue()]; float tmpDist = (point - center).lengthSqr(); if (tmpDist < mInstanceDistances[index]) { mInstanceDistances[index] = tmpDist; closestPointIndex = iter.getValue(); } } } template void ClosestPointDist::evalNode(size_t pointIndex, size_t nodeIndex) const { const Vec3R& pos = mInstancePoints[pointIndex]; float minDist = mInstanceDistances[pointIndex]; size_t minDistIdx = 0; Vec3R center; bool updatedDist = false; for (size_t i = 0, I = mLeafDistances.size(); i < I; ++i) { float& distToLeaf = const_cast(mLeafDistances[i]); distToLeaf = 0.0; } for (size_t i = mLeafRanges[nodeIndex].first, n = 0; i < mLeafRanges[nodeIndex].second; ++i, ++n) { float& distToLeaf = const_cast(mLeafDistances[n]); center[0] = mLeafBoundingSpheres[i][0]; center[1] = mLeafBoundingSpheres[i][1]; center[2] = mLeafBoundingSpheres[i][2]; distToLeaf = (pos - center).lengthSqr() - mLeafBoundingSpheres[i][3]; if (distToLeaf < minDist) { minDist = distToLeaf; minDistIdx = i; updatedDist = true; } } if (!updatedDist) return; evalLeaf(pointIndex, *mLeafNodes[minDistIdx]); for (size_t i = mLeafRanges[nodeIndex].first, n = 0; i < mLeafRanges[nodeIndex].second; ++i, ++n) { if (mLeafDistances[n] < mInstanceDistances[pointIndex] && i != minDistIdx) { evalLeaf(pointIndex, *mLeafNodes[i]); } } } template void ClosestPointDist::operator()(const tbb::blocked_range& range) const { Vec3R center; for (size_t n = range.begin(); n != range.end(); ++n) { const Vec3R& pos = mInstancePoints[n]; float minDist = mInstanceDistances[n]; size_t minDistIdx = 0; for (size_t i = 0, I = mNodeDistances.size(); i < I; ++i) { float& distToNode = const_cast(mNodeDistances[i]); center[0] = mNodeBoundingSpheres[i][0]; center[1] = mNodeBoundingSpheres[i][1]; center[2] = mNodeBoundingSpheres[i][2]; distToNode = (pos - center).lengthSqr() - mNodeBoundingSpheres[i][3]; if (distToNode < minDist) { minDist = distToNode; minDistIdx = i; } } evalNode(n, minDistIdx); for (size_t i = 0, I = mNodeDistances.size(); i < I; ++i) { if (mNodeDistances[i] < mInstanceDistances[n] && i != minDistIdx) { evalNode(n, i); } } mInstanceDistances[n] = std::sqrt(mInstanceDistances[n]); if (mTransformPoints) mInstancePoints[n] = mSurfacePointList[mClosestPointIndex]; } } class UpdatePoints { public: UpdatePoints( const Vec4s& sphere, const std::vector& points, std::vector& distances, std::vector& mask, bool overlapping); float radius() const { return mRadius; } int index() const { return mIndex; }; void run(bool threaded = true); UpdatePoints(UpdatePoints&, tbb::split); void operator()(const tbb::blocked_range& range); void join(const UpdatePoints& rhs) { if (rhs.mRadius > mRadius) { mRadius = rhs.mRadius; mIndex = rhs.mIndex; } } private: const Vec4s& mSphere; const std::vector& mPoints; std::vector& mDistances; std::vector& mMask; bool mOverlapping; float mRadius; int mIndex; }; inline UpdatePoints::UpdatePoints( const Vec4s& sphere, const std::vector& points, std::vector& distances, std::vector& mask, bool overlapping) : mSphere(sphere) , mPoints(points) , mDistances(distances) , mMask(mask) , mOverlapping(overlapping) , mRadius(0.0) , mIndex(0) { } inline UpdatePoints::UpdatePoints(UpdatePoints& rhs, tbb::split) : mSphere(rhs.mSphere) , mPoints(rhs.mPoints) , mDistances(rhs.mDistances) , mMask(rhs.mMask) , mOverlapping(rhs.mOverlapping) , mRadius(rhs.mRadius) , mIndex(rhs.mIndex) { } inline void UpdatePoints::run(bool threaded) { if (threaded) { tbb::parallel_reduce(tbb::blocked_range(0, mPoints.size()), *this); } else { (*this)(tbb::blocked_range(0, mPoints.size())); } } inline void UpdatePoints::operator()(const tbb::blocked_range& range) { Vec3s pos; for (size_t n = range.begin(); n != range.end(); ++n) { if (mMask[n]) continue; pos.x() = float(mPoints[n].x()) - mSphere[0]; pos.y() = float(mPoints[n].y()) - mSphere[1]; pos.z() = float(mPoints[n].z()) - mSphere[2]; float dist = pos.length(); if (dist < mSphere[3]) { mMask[n] = 1; continue; } if (!mOverlapping) { mDistances[n] = std::min(mDistances[n], (dist - mSphere[3])); } if (mDistances[n] > mRadius) { mRadius = mDistances[n]; mIndex = n; } } } } // namespace internal //////////////////////////////////////// template inline void fillWithSpheres( const GridT& grid, std::vector& spheres, int maxSphereCount, bool overlapping, float minRadius, float maxRadius, float isovalue, int instanceCount, InterrupterT* interrupter) { spheres.clear(); spheres.reserve(maxSphereCount); int instances = std::max(instanceCount, maxSphereCount); typedef typename GridT::TreeType TreeT; typedef typename GridT::ValueType ValueT; typedef typename TreeT::template ValueConverter::Type BoolTreeT; typedef typename TreeT::template ValueConverter::Type IntTreeT; typedef typename TreeT::template ValueConverter::Type Int16TreeT; typedef tree::LeafManager LeafManagerT; typedef tree::LeafManager IntLeafManagerT; typedef tree::LeafManager Int16LeafManagerT; typedef boost::mt11213b RandGen; RandGen mtRand(/*seed=*/0); const TreeT& tree = grid.tree(); const math::Transform& transform = grid.transform(); std::vector instancePoints; { // Scatter candidate sphere centroids (instancePoints) typename Grid::Ptr interiorMaskPtr; if (grid.getGridClass() == GRID_LEVEL_SET) { interiorMaskPtr = sdfInteriorMask(grid, ValueT(isovalue)); } else { interiorMaskPtr = typename Grid::Ptr(Grid::create(false)); interiorMaskPtr->setTransform(transform.copy()); interiorMaskPtr->tree().topologyUnion(tree); } if (interrupter && interrupter->wasInterrupted()) return; erodeVoxels(interiorMaskPtr->tree(), 3); instancePoints.reserve(instances); internal::PointAccessor ptnAcc(instancePoints); UniformPointScatter scatter(ptnAcc, instances, mtRand, interrupter); scatter(*interiorMaskPtr); } if (interrupter && interrupter->wasInterrupted()) return; std::vector instanceRadius; ClosestSurfacePoint csp; csp.initialize(grid, isovalue, interrupter); if (interrupter && interrupter->wasInterrupted()) return; if (!csp.search(instancePoints, instanceRadius)) return; std::vector instanceMask(instancePoints.size(), 0); float largestRadius = 0.0; int largestRadiusIdx = 0; for (size_t n = 0, N = instancePoints.size(); n < N; ++n) { if (instanceRadius[n] > largestRadius) { largestRadius = instanceRadius[n]; largestRadiusIdx = n; } } Vec3s pos; Vec4s sphere; minRadius *= transform.voxelSize()[0]; maxRadius *= transform.voxelSize()[0]; for (size_t s = 0, S = std::min(size_t(maxSphereCount), instancePoints.size()); s < S; ++s) { if (interrupter && interrupter->wasInterrupted()) return; largestRadius = std::min(maxRadius, largestRadius); if (s != 0 && largestRadius < minRadius) break; sphere[0] = float(instancePoints[largestRadiusIdx].x()); sphere[1] = float(instancePoints[largestRadiusIdx].y()); sphere[2] = float(instancePoints[largestRadiusIdx].z()); sphere[3] = largestRadius; spheres.push_back(sphere); instanceMask[largestRadiusIdx] = 1; internal::UpdatePoints op(sphere, instancePoints, instanceRadius, instanceMask, overlapping); op.run(); largestRadius = op.radius(); largestRadiusIdx = op.index(); } } //////////////////////////////////////// template ClosestSurfacePoint::ClosestSurfacePoint() : mIsInitialized(false) , mLeafBoundingSpheres(0) , mNodeBoundingSpheres(0) , mLeafRanges(0) , mLeafNodes(0) , mSurfacePointList() , mPointListSize(0) , mMaxNodeLeafs(0) , mMaxRadiusSqr(0.0) , mIdxTreePt() { } template void ClosestSurfacePoint::initialize(const GridT& grid, float isovalue) { initialize(grid, isovalue, NULL); } template template void ClosestSurfacePoint::initialize( const GridT& grid, float isovalue, InterrupterT* interrupter) { mIsInitialized = false; typedef typename TreeT::template ValueConverter::Type Int16TreeT; typedef tree::LeafManager LeafManagerT; typedef tree::LeafManager IntLeafManagerT; typedef tree::LeafManager Int16LeafManagerT; typedef typename GridT::ValueType ValueT; const TreeT& tree = grid.tree(); const math::Transform& transform = grid.transform(); { // Extract surface point cloud typename Int16TreeT::Ptr signTreePt; { LeafManagerT leafs(tree); internal::SignData signDataOp(tree, leafs, ValueT(isovalue)); signDataOp.run(); signTreePt = signDataOp.signTree(); mIdxTreePt = signDataOp.idxTree(); } if (interrupter && interrupter->wasInterrupted()) return; Int16LeafManagerT signLeafs(*signTreePt); std::vector regions(signLeafs.leafCount(), 0); signLeafs.foreach(internal::CountPoints(regions)); mPointListSize = 0; for (size_t tmp = 0, n = 0, N = regions.size(); n < N; ++n) { tmp = regions[n]; regions[n] = mPointListSize; mPointListSize += tmp; } if (mPointListSize == 0) return; mSurfacePointList.reset(new Vec3s[mPointListSize]); internal::GenPoints pointOp(signLeafs, tree, *mIdxTreePt, mSurfacePointList, regions, transform, isovalue); pointOp.run(); mIdxTreePt->topologyUnion(*signTreePt); } if (interrupter && interrupter->wasInterrupted()) return; // estimate max sphere radius (sqr dist) CoordBBox bbox = grid.evalActiveVoxelBoundingBox(); Vec3s dim = transform.indexToWorld(bbox.min()) - transform.indexToWorld(bbox.max()); dim[0] = std::abs(dim[0]); dim[1] = std::abs(dim[1]); dim[2] = std::abs(dim[2]); mMaxRadiusSqr = std::min(std::min(dim[0], dim[1]), dim[2]); mMaxRadiusSqr *= 0.51; mMaxRadiusSqr *= mMaxRadiusSqr; IntLeafManagerT idxLeafs(*mIdxTreePt); typedef typename IntTreeT::RootNodeType IntRootNodeT; typedef typename IntRootNodeT::NodeChainType IntNodeChainT; BOOST_STATIC_ASSERT(boost::mpl::size::value > 1); typedef typename boost::mpl::at >::type IntInternalNodeT; typename IntTreeT::NodeCIter nIt = mIdxTreePt->cbeginNode(); nIt.setMinDepth(IntTreeT::NodeCIter::LEAF_DEPTH - 1); nIt.setMaxDepth(IntTreeT::NodeCIter::LEAF_DEPTH - 1); std::vector internalNodes; const IntInternalNodeT* node = NULL; for (; nIt; ++nIt) { nIt.getNode(node); if (node) internalNodes.push_back(node); } std::vector().swap(mLeafRanges); mLeafRanges.resize(internalNodes.size()); std::vector().swap(mLeafNodes); mLeafNodes.reserve(idxLeafs.leafCount()); typename IntInternalNodeT::ChildOnCIter leafIt; mMaxNodeLeafs = 0; for (size_t n = 0, N = internalNodes.size(); n < N; ++n) { mLeafRanges[n].first = mLeafNodes.size(); size_t leafCount = 0; for (leafIt = internalNodes[n]->cbeginChildOn(); leafIt; ++leafIt) { mLeafNodes.push_back(&(*leafIt)); ++leafCount; } mMaxNodeLeafs = std::max(leafCount, mMaxNodeLeafs); mLeafRanges[n].second = mLeafNodes.size(); } std::vector().swap(mLeafBoundingSpheres); mLeafBoundingSpheres.resize(mLeafNodes.size()); internal::LeafBS leafBS(mLeafBoundingSpheres, mLeafNodes, transform, mSurfacePointList); leafBS.run(); std::vector().swap(mNodeBoundingSpheres); mNodeBoundingSpheres.resize(internalNodes.size()); internal::NodeBS nodeBS(mNodeBoundingSpheres, mLeafRanges, mLeafBoundingSpheres); nodeBS.run(); mIsInitialized = true; } template bool ClosestSurfacePoint::search(std::vector& points, std::vector& distances, bool transformPoints) { if (!mIsInitialized) return false; distances.clear(); distances.resize(points.size(), mMaxRadiusSqr); internal::ClosestPointDist cpd(points, distances, mSurfacePointList, mLeafNodes, mLeafRanges, mLeafBoundingSpheres, mNodeBoundingSpheres, mMaxNodeLeafs, transformPoints); cpd.run(); return true; } template bool ClosestSurfacePoint::search(const std::vector& points, std::vector& distances) { return search(const_cast& >(points), distances, false); } template bool ClosestSurfacePoint::searchAndReplace(std::vector& points, std::vector& distances) { return search(points, distances, true); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_VOLUME_TO_MESH_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/RayIntersector.h0000644000000000000000000005752112252453157015517 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// /// @file RayIntersector.h /// /// @author Ken Museth /// /// @brief Accelerated intersection of a ray with a narrow-band level /// set or a generic (e.g. density) volume. This will of course be /// useful for respectively surface and volume rendering. /// /// @details This file defines two main classes, /// LevelSetRayIntersector and VolumeRayIntersector, as well as the /// three support classes LevelSetHDDA, VolumeHDDA and LinearSearchImpl. /// The LevelSetRayIntersector is templated on the LinearSearchImpl class /// and calls instances of the LevelSetHDDA class. The reason to split /// level set ray intersection into three classes is twofold. First /// LevelSetRayIntersector defines the public API for client code and /// LinearSearchImpl defines the actual algorithm used for the /// ray level-set intersection. In other words this design will allow /// for the public API to be fixed while the intersection algorithm /// can change without resolving to (slow) virtual methods. Second, /// LevelSetHDDA, which implements a hierarchical Differential Digital /// Analyzer, relies on partial template specialization, so it has to /// be a standalone class (as opposed to a member class of /// LevelSetRayIntersector). The VolumeRayIntersector is conceptually /// much simpler then the LevelSetRayIntersector, and hence it only /// depends on VolumeHDDA that implements the hierarchical /// Differential Digital Analyzer. #ifndef OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { // Helper class that implements hierarchical Digital Differential Analyzers // specialized for ray intersections with level sets template struct LevelSetHDDA; /// Helper class that implements hierarchical Digital Differential Analysers /// specialized for ray intersections with density (vs level set surfaces) template struct VolumeHDDA; // Helper class that implements the actual search of the zero-crossing // of the level set along the direction of a ray. This particular // implementation uses iterative linear search. template class LinearSearchImpl; //////////////////////////////////////// LevelSetRayIntersector //////////////////////////////////////// /// @brief This class provides the public API for intersecting a ray /// with a narrow-band level set. /// /// @details It wraps an SearchImplT with a simple public API and /// performs the actual hierarchical tree node and voxel traversal. /// /// @warning Use the (default) copy-constructor to make sure each /// computational thread has their own instance of this class. This is /// important since the SearchImplT contains a ValueAccessor that is /// not thread-safe. However copying is very efficient. /// /// @see tools/RayTracer.h for examples of intended usage. /// /// @todo Add TrilinearSearchImpl, as an alternative to LinearSearchImpl, /// that performs analytical 3D trilinear intersection tests, i.e., solves /// cubic equations. This is slower but also more accurate than the 1D /// linear interpolation in LinearSearchImpl. template, int NodeLevel = GridT::TreeType::RootNodeType::ChildNodeType::LEVEL, typename RayT = math::Ray > class LevelSetRayIntersector { public: typedef GridT GridType; typedef RayT RayType; typedef typename RayT::RealType RealType; typedef typename RayT::Vec3T Vec3Type; typedef typename GridT::ValueType ValueT; typedef typename GridT::TreeType TreeT; BOOST_STATIC_ASSERT( NodeLevel >= -1 && NodeLevel < int(TreeT::DEPTH)-1); BOOST_STATIC_ASSERT(boost::is_floating_point::value); /// @brief Constructor /// @param grid level set grid to intersect rays against LevelSetRayIntersector(const GridT& grid): mTester(grid) { if (!grid.hasUniformVoxels() ) { OPENVDB_THROW(RuntimeError, "LevelSetRayIntersector only supports uniform voxels!"); } if (grid.getGridClass() != GRID_LEVEL_SET) { OPENVDB_THROW(RuntimeError, "LevelSetRayIntersector only supports level sets!" "\nUse Grid::setGridClass(openvdb::GRID_LEVEL_SET)"); } } /// @brief Return @c true if the index-space ray intersects the level set /// @param iRay ray represented in index space bool intersectsIS(const RayType& iRay) const { if (!mTester.setIndexRay(iRay)) return false;//missed bbox return LevelSetHDDA::test(mTester); } /// @brief Return @c true if the index-space ray intersects the level set. /// @param iRay ray represented in index space. /// @param xyz if an intersection was found it is assigned the /// intersection point in index space, otherwise it is unchanged. bool intersectsIS(const RayType& iRay, Vec3Type& xyz) const { if (!mTester.setIndexRay(iRay)) return false;//missed bbox if (!LevelSetHDDA::test(mTester)) return false;//missed level set mTester.getIndexPos(xyz); return true; } /// @brief Return @c true if the world-space ray intersects the level set. /// @param wRay ray represented in world space. bool intersectsWS(const RayType& wRay) const { if (!mTester.setWorldRay(wRay)) return false;//missed bbox return LevelSetHDDA::test(mTester); } /// @brief Return @c true if the world-space ray intersects the level set. /// @param wRay ray represented in world space. /// @param world if an intersection was found it is assigned the /// intersection point in world space, otherwise it is unchanged bool intersectsWS(const RayType& wRay, Vec3Type& world) const { if (!mTester.setWorldRay(wRay)) return false;//missed bbox if (!LevelSetHDDA::test(mTester)) return false;//missed level set mTester.getWorldPos(world); return true; } /// @brief Return @c true if the world-space ray intersects the level set. /// @param wRay ray represented in world space. /// @param world if an intersection was found it is assigned the /// intersection point in world space, otherwise it is unchanged. /// @param normal if an intersection was found it is assigned the normal /// of the level set surface in world space, otherwise it is unchanged. bool intersectsWS(const RayType& wRay, Vec3Type& world, Vec3Type& normal) const { if (!mTester.setWorldRay(wRay)) return false;//missed bbox if (!LevelSetHDDA::test(mTester)) return false;//missed level set mTester.getWorldPosAndNml(world, normal); return true; } private: mutable SearchImplT mTester; };// LevelSetRayIntersector //////////////////////////////////////// VolumeRayIntersector //////////////////////////////////////// /// @brief This class provides the public API for intersecting a ray /// with a generic (e.g. density) volume. /// @details Internally it performs the actual hierarchical tree node traversal. /// @warning Use the (default) copy-constructor to make sure each /// computational thread has their own instance of this class. This is /// important since it contains a ValueAccessor that is /// not thread-safe and a CoordBBox of the active voxels that should /// not be re-computed for each thread. However copying is very efficient. /// @par Example: /// @code /// // Create an instance for the master thread /// VolumeRayIntersector inter(grid); /// // For each additional thread use the copy contructor. This /// // amortizes the overhead of computing the bbox of the active voxels! /// VolumeRayIntersector inter2(inter); /// // Before each ray-traversal set the index ray. /// iter.setIndexRay(ray); /// // or world ray /// iter.setWorldRay(ray); /// // Now you can begin the ray-marching using consecutive calls to VolumeRayIntersector::march /// double t0=0, t1=0;// note the entry and exit times are with respect to the INDEX ray /// while ( int n = inter.march(t0, t1) ) {// perform line-integration between t0 and t1 /// if (n == 1) {//hit a tile so the value between t0 and t1 is constant /// } else {//n == 2 so we hit a leaf node and the value between t0 and t1 is unknown /// }} /// @endcode template > class VolumeRayIntersector { public: typedef GridT GridType; typedef RayT RayType; typedef typename RayT::RealType RealType; typedef typename GridT::TreeType TreeT; BOOST_STATIC_ASSERT( NodeLevel >= 0 && NodeLevel < int(TreeT::DEPTH)-1); /// @brief Constructor /// @param grid Generic grid to intersect rays against. /// @warning In the near future we will add support for grids with frustrum transforms. VolumeRayIntersector(const GridT& grid): mGrid(&grid), mAccessor(grid.tree()) { if (!grid.hasUniformVoxels() ) { OPENVDB_THROW(RuntimeError, "VolumeRayIntersector only supports uniform voxels!"); } if ( grid.empty() ) { OPENVDB_THROW(RuntimeError, "LinearSearchImpl does not supports empty grids"); } grid.tree().root().evalActiveBoundingBox(mBBox, /*visit individual voxels*/false); mBBox.max().offset(1);//padding so the bbox of a node becomes (origin,origin + node_dim) } /// @brief Return @c false if the index ray misses the bbox of the grid. /// @param iRay Ray represented in index space. /// @warning Call this method (or setWorldRay) before the ray /// traversal starts and use the return value to decide if further /// marching is required. inline bool setIndexRay(const RayT& iRay) { mRay = iRay; const bool hit = mRay.clip(mBBox); if (hit) mTmax = mRay.t1(); return hit; } /// @brief Return @c false if the world ray misses the bbox of the grid. /// @param wRay Ray represented in world space. /// @warning Call this method (or setIndexRay) before the ray /// traversal starts and use the return value to decide if further /// marching is required. /// @details Since hit times are computed with repect to the ray /// represented in index space of the current grid, it is /// recommended that either the client code uses getIndexPos to /// compute index position from hit times or alternatively keeps /// an instance of the index ray and instead uses setIndexRay to /// initialize the ray. inline bool setWorldRay(const RayT& wRay) { return this->setIndexRay(wRay.worldToIndex(*mGrid)); } /// @brief Return 0 if not hit was detected. A return value of 1 /// means it hit an active tile, and a return value of 2 means it /// hit a LeafNode. Only when a hit is detected are t0 and t1 /// updated with the corresponding entry and exit times along the /// INDEX ray! /// @param t0 If the return value > 0 this is the time of the first hit. /// @param t1 If the return value > 0 this is the time of the second hit. /// @warning t0 and t1 are computed with repect to the ray represented in /// index space of the current grid, not world space! inline int march(Real& t0, Real& t1) { const int n = mRay.test() ? VolumeHDDA::test(*this) : 0; if (n>0) { t0 = mRay.t0(); t1 = mRay.t1(); } mRay.setTimes(mRay.t1() + math::Delta::value(), mTmax); return n; } /// @brief Return the floating-point index position along the /// current index ray at the specified time. inline Vec3R getIndexPos(Real time) const { return mRay(time); } /// @brief Return the floating-point world position along the /// current index ray at the specified time. inline Vec3R getWorldPos(Real time) const { return mGrid->indexToWorld(mRay(time)); } private: inline void setRange(Real t0, Real t1) { mRay.setTimes(t0, t1); } /// @brief Return a const reference to the ray. inline const RayT& ray() const { return mRay; } /// @brief Return true if a node of the the specified type exists at ijk. template inline bool hasNode(const Coord& ijk) { return mAccessor.template probeConstNode(ijk) != NULL; } bool isValueOn(const Coord& ijk) { return mAccessor.isValueOn(ijk); } template friend struct VolumeHDDA; typedef typename GridT::ConstAccessor AccessorT; const GridT* mGrid; AccessorT mAccessor; RayT mRay; Real mTmax; math::CoordBBox mBBox; };// VolumeRayIntersector //////////////////////////////////////// LinearSearchImpl //////////////////////////////////////// /// @brief Implements linear iterative search for a zero-crossing of /// the level set along along the direction of the ray. /// /// @note Since this class is used internally in /// LevelSetRayIntersector (define above) and LevelSetHDDA (defined below) /// client code should never interact directly with its API. This also /// explains why we are not concerned with the fact that several of /// its methods are unsafe to call unless zero-crossings were /// already detected. /// /// @details It is approximate due to the limited number of iterations /// which can can be defined with a template parameter. However the default value /// has proven surprisingly accurate and fast. In fact more iterations /// are not guaranteed to give significantly better results. /// /// @warning Since this class internally stores a ValueAccessor it is NOT thread-safe, /// so make sure to give each thread its own instance. This of course also means that /// the cost of allocating an instance should (if possible) be amortized over /// as many ray intersections as possible. template class LinearSearchImpl { public: typedef math::Ray RayT; typedef typename GridT::ValueType ValueT; typedef typename GridT::ConstAccessor AccessorT; typedef math::BoxStencil StencilT; typedef typename StencilT::Vec3Type Vec3T; /// @brief Constructor from a grid. LinearSearchImpl(const GridT& grid) : mStencil(grid), mThreshold(2*grid.voxelSize()[0]) { if ( grid.empty() ) { OPENVDB_THROW(RuntimeError, "LinearSearchImpl does not supports empty grids"); } grid.tree().root().evalActiveBoundingBox(mBBox, /*visit individual voxels*/false); } /// @brief Return @c false the ray misses the bbox of the grid. /// @param iRay Ray represented in index space. /// @warning Call this method before the ray traversal starts. inline bool setIndexRay(const RayT& iRay) { mRay = iRay; return mRay.clip(mBBox);//did it hit the bbox } /// @brief Return @c false the ray misses the bbox of the grid. /// @param wRay Ray represented in world space. /// @warning Call this method before the ray traversal starts. inline bool setWorldRay(const RayT& wRay) { mRay = wRay.worldToIndex(mStencil.grid()); return mRay.clip(mBBox);//did it hit the bbox } /// @brief Get the intersection point in index space. /// @param xyz The position in index space of the intersection. inline void getIndexPos(Vec3d& xyz) const { xyz = mRay(mTime); } /// @brief Get the intersection point in world space. /// @param xyz The position in world space of the intersection. inline void getWorldPos(Vec3d& xyz) const { xyz = mStencil.grid().indexToWorld(mRay(mTime)); } /// @brief Get the intersection point and normal in world space /// @param xyz The position in world space of the intersection. /// @param nml The surface normal in world space of the intersection. inline void getWorldPosAndNml(Vec3d& xyz, Vec3d& nml) { this->getIndexPos(xyz); mStencil.moveTo(xyz); nml = mStencil.gradient(xyz); nml.normalize(); xyz = mStencil.grid().indexToWorld(xyz); } private: /// @brief Initiate the local voxel intersection test. /// @warning Make sure to call this method before the local voxel intersection test. inline void init(RealT t0) { mT[0] = t0; mV[0] = this->interpValue(t0); } inline void setRange(RealT t0, RealT t1) { mRay.setTimes(t0, t1); } /// @brief Return a const reference to the ray. inline const RayT& ray() const { return mRay; } /// @brief Return true if a node of the the specified type exists at ijk. template inline bool hasNode(const Coord& ijk) { return mStencil.accessor().template probeConstNode(ijk) != NULL; } /// @brief Return @c true if an intersection is detected. /// @param ijk Grid coordinate of the node origin or voxel being tested. /// @param time Time along the index ray being tested. /// @warning Only if and intersection is detected is it safe to /// call getIndexPos, getWorldPos and getWorldPosAndNml! inline bool operator()(const Coord& ijk, RealT time) { ValueT V; if (mStencil.accessor().probeValue(ijk, V) &&//inside narrow band? math::Abs(V)interpValue(time); if (math::ZeroCrossing(mV[0], mV[1])) { mTime = this->interpTime(); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN for (int n=0; Iterations>0 && ninterpValue(mTime); const int m = math::ZeroCrossing(mV[0], V) ? 1 : 0; mV[m] = V; mT[m] = mTime; mTime = this->interpTime(); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END return true; } mT[0] = mT[1]; mV[0] = mV[1]; } return false; } inline RealT interpTime() { assert(math::isApproxLarger(mT[1], mT[0], 1e-6)); return mT[0]+(mT[1]-mT[0])*mV[0]/(mV[0]-mV[1]); } inline RealT interpValue(RealT time) { const Vec3R pos = mRay(time); mStencil.moveTo(pos); return mStencil.interpolation(pos); } template friend struct LevelSetHDDA; RayT mRay; StencilT mStencil; RealT mTime; ValueT mV[2]; RealT mT[2]; ValueT mThreshold; math::CoordBBox mBBox; };// LinearSearchImpl //////////////////////////////////////// LevelSetHDDA //////////////////////////////////////// /// @brief Helper class that implements Hierarchical Digital Differential Analyzers /// and is specialized for ray intersections with level sets template struct LevelSetHDDA { typedef typename TreeT::RootNodeType::NodeChainType ChainT; typedef typename boost::mpl::at >::type NodeT; template static bool test(TesterT& tester) { math::DDA dda(tester.ray()); do { if (tester.template hasNode(dda.voxel())) { tester.setRange(dda.time(), dda.next()); if (LevelSetHDDA::test(tester)) return true; } } while(dda.step()); return false; } }; /// @brief Specialization of Hierarchical Digital Differential Analyzer /// class that intersects a ray against the voxels of a level set template struct LevelSetHDDA { template static bool test(TesterT& tester) { math::DDA dda(tester.ray()); tester.init(dda.time()); do { if (tester(dda.voxel(), dda.next())) return true; } while(dda.step()); return false; } }; //////////////////////////////////////// VolumeHDDA //////////////////////////////////////// /// Helper class that implements Hierarchical Digital Differential Analyzers /// for ray intersections against a generic volume. template struct VolumeHDDA { typedef typename TreeT::RootNodeType::NodeChainType ChainT; typedef typename boost::mpl::at >::type NodeT; template static int test(TesterT& tester) { math::DDA dda(tester.ray()); do { if (tester.template hasNode(dda.voxel())) {//child node tester.setRange(dda.time(), dda.next()); int hit = VolumeHDDA::test(tester); if (hit > 0) return hit; } else if (tester.isValueOn(dda.voxel())) {//active tile tester.setRange(dda.time(), dda.next()); return 1;//active tile } } while (dda.step()); return 0;//no hits } }; /// @brief Specialization of Hierarchical Digital Differential Analyzer /// class that intersects against the voxels of a generic volume. template struct VolumeHDDA { template static int test(TesterT&) { return 2; }//hit leaf so don't traverse voxels. }; } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetMeasure.h0000644000000000000000000004642212252453157015605 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file LevelSetMeasure.h #ifndef OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include //for Pi #include #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Return the surface area of a narrow-band level set. /// /// @param grid a scalar, floating-point grid with one or more disjoint, /// closed isosurfaces at the given @a isovalue /// @param useWorldSpace if true the area is computed in /// world space units, else in voxel units. /// /// @throw TypeError if @a grid is not scalar or not floating-point or not a level set. template inline Real levelSetArea(const GridType& grid, bool useWorldSpace = true); /// @brief Return the volume of a narrow-band level set surface. /// /// @param grid a scalar, floating-point grid with one or more disjoint, /// closed isosurfaces at the given @a isovalue /// @param useWorldSpace if true the volume is computed in /// world space units, else in voxel units. /// /// @throw TypeError if @a grid is not scalar or not floating-point or not a level set. template inline Real levelSetVolume(const GridType& grid, bool useWorldSpace = true); /// @brief Compute the surface area and volume of a narrow-band level set. /// /// @param grid a scalar, floating-point grid with one or more disjoint, /// closed isosurfaces at the given @a isovalue /// @param area surface area of the level set /// @param volume volume of the level set surface /// @param useWorldSpace if true the area and volume are computed in /// world space units, else in voxel units. /// /// @throw TypeError if @a grid is not scalar or not floating-point or not a level set. template inline void levelSetMeasure(const GridType& grid, Real& area, Real& volume, bool useWorldSpace = true); /// @brief Compute the surface area and volume of a narrow-band level set. /// /// @param grid a scalar, floating-point grid with one or more disjoint, /// closed isosurfaces at the given @a isovalue /// @param area surface area of the level set /// @param volume volume of the level set surface /// @param avgCurvature average mean curvature of the level set surface /// @param useWorldSpace if true the area, volume and curvature are computed in /// world space units, else in voxel units. /// /// @throw TypeError if @a grid is not scalar or not floating-point or not a level set. template inline void levelSetMeasure(const GridType& grid, Real& area, Real& volume, Real& avgCurvature, bool useWorldSpace = true); /// @brief Smeared-out and continuous Dirac Delta function. template class DiracDelta { public: DiracDelta(RealT eps) : mC(0.5/eps), mD(2*boost::math::constants::pi()*mC), mE(eps) {} inline RealT operator()(RealT phi) const { return math::Abs(phi) > mE ? 0 : mC*(1+cos(mD*phi)); } private: const RealT mC, mD, mE; }; /// @brief Multi-threaded computation of surface area, volume and /// average mean-curvature for narrow band level sets. /// /// @details To reduce the risk of round-off errors (primarily due to /// catastrophic cancellation) and guarantee determinism during /// multi-threading this class is implemented using parallel_for, and /// delayed reduction of a sorted list. template class LevelSetMeasure { public: typedef GridT GridType; typedef typename GridType::TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename tree::LeafManager ManagerType; typedef typename ManagerType::LeafRange RangeType; BOOST_STATIC_ASSERT(boost::is_floating_point::value); /// @brief Main constructor from a grid /// @param grid The level set to be measured. /// @param interrupt Optional interrupter. /// @throw RuntimeError if the grid is not a level set. LevelSetMeasure(const GridType& grid, InterruptT* interrupt = NULL); LevelSetMeasure(ManagerType& leafs, Real Dx, InterruptT* interrupt); /// @brief Re-initialize using the specified grid. void reinit(const GridType& grid); /// @brief Re-initialize using the specified LeafManager and voxelSize. void reinit(ManagerType& leafs, Real dx); /// @brief Destructor ~LevelSetMeasure() {} /// @return the grain-size used for multi-threading int getGrainSize() const { return mGrainSize; } /// @brief Set the grain-size used for multi-threading. /// @note A grainsize of 0 or less disables multi-threading! void setGrainSize(int grainsize) { mGrainSize = grainsize; } /// @brief Compute the surface area and volume of the level /// set. Use the last argument to specify the result in world or /// voxel units. /// @note This method is faster (about 3x) then the measure method /// below that also computes the average mean-curvature. void measure(Real& area, Real& volume, bool useWorldUnits = true); /// @brief Compute the surface area, volume, and average /// mean-curvatue of the level set. Use the last argument to /// specify the result in world or voxel units. /// @note This method is slower (about 3x) then the measure method /// above that only computes the area and volume. void measure(Real& area, Real& volume, Real& avgMeanCurvature, bool useWorldUnits = true); /// @brief Used internally by tbb::parallel_reduce(). /// @param range The range over which to perform multi-threading. /// @warning Never call this method directly! void operator()(const RangeType& range) const { if (mTask) mTask(const_cast(this), range); else OPENVDB_THROW(ValueError, "task is undefined"); } private: typedef typename GridT::ConstAccessor AccT; typedef typename TreeType::LeafNodeType LeafT; typedef typename LeafT::ValueOnCIter VoxelCIterT; typedef typename ManagerType::BufferType BufferT; typedef typename RangeType::Iterator LeafIterT; AccT mAcc; ManagerType* mLeafs; InterruptT* mInterrupter; double mDx; double* mArray; typename boost::function mTask; int mGrainSize; // @brief Return false if the process was interrupted bool checkInterrupter(); // Private methods called by tbb::parallel_reduce threads void measure2( const RangeType& ); // Private methods called by tbb::parallel_reduce threads void measure3( const RangeType& ); inline double reduce(double* first, double scale) { double* last = first + mLeafs->leafCount(); tbb::parallel_sort(first, last);//reduces catastrophic cancellation Real sum = 0.0; while(first != last) sum += *first++; return scale * sum; } }; // end of LevelSetMeasure class template inline LevelSetMeasure::LevelSetMeasure(const GridType& grid, InterruptT* interrupt) : mAcc(grid.tree()) , mLeafs(NULL) , mInterrupter(interrupt) , mDx(grid.voxelSize()[0]) , mArray(NULL) , mTask(0) , mGrainSize(1) { if (!grid.hasUniformVoxels()) { OPENVDB_THROW(RuntimeError, "The transform must have uniform scale for the LevelSetMeasure to function"); } if (grid.getGridClass() != GRID_LEVEL_SET) { OPENVDB_THROW(RuntimeError, "LevelSetMeasure only supports level sets;" " try setting the grid class to \"level set\""); } } template inline LevelSetMeasure::LevelSetMeasure( ManagerType& leafs, Real dx, InterruptT* interrupt) : mAcc(leafs.tree()) , mLeafs(&leafs) , mInterrupter(interrupt) , mDx(dx) , mArray(NULL) , mTask(0) , mGrainSize(1) { } template inline void LevelSetMeasure::reinit(const GridType& grid) { if (!grid.hasUniformVoxels()) { OPENVDB_THROW(RuntimeError, "The transform must have uniform scale for the LevelSetMeasure to function"); } if (grid.getGridClass() != GRID_LEVEL_SET) { OPENVDB_THROW(RuntimeError, "LevelSetMeasure only supports level sets;" " try setting the grid class to \"level set\""); } mLeafs = NULL; mAcc = grid.getConstAccessor(); mDx = grid.voxelSize()[0]; } template inline void LevelSetMeasure::reinit(ManagerType& leafs, Real dx) { mLeafs = &leafs; mAcc = AccT(leafs.tree()); mDx = dx; } //////////////////////////////////////// template inline void LevelSetMeasure::measure(Real& area, Real& volume, bool useWorldUnits) { if (mInterrupter) mInterrupter->start("Measuring level set"); mTask = boost::bind(&LevelSetMeasure::measure2, _1, _2); const bool newLeafs = mLeafs == NULL; if (newLeafs) mLeafs = new ManagerType(mAcc.tree()); const size_t leafCount = mLeafs->leafCount(); if (leafCount == 0) { area = volume = 0; return; } mArray = new double[2*leafCount]; if (mGrainSize>0) { tbb::parallel_for(mLeafs->leafRange(mGrainSize), *this); } else { (*this)(mLeafs->leafRange()); } const double dx = useWorldUnits ? mDx : 1.0; area = this->reduce(mArray, math::Pow2(dx)); volume = this->reduce(mArray + leafCount, math::Pow3(dx) / 3.0); if (newLeafs) { delete mLeafs; mLeafs = NULL; } delete [] mArray; if (mInterrupter) mInterrupter->end(); } template inline void LevelSetMeasure::measure(Real& area, Real& volume, Real& avgMeanCurvature, bool useWorldUnits) { if (mInterrupter) mInterrupter->start("Measuring level set"); mTask = boost::bind(&LevelSetMeasure::measure3, _1, _2); const bool newLeafs = mLeafs == NULL; if (newLeafs) mLeafs = new ManagerType(mAcc.tree()); const size_t leafCount = mLeafs->leafCount(); if (leafCount == 0) { area = volume = avgMeanCurvature = 0; return; } mArray = new double[3*leafCount]; if (mGrainSize>0) { tbb::parallel_for(mLeafs->leafRange(mGrainSize), *this); } else { (*this)(mLeafs->leafRange()); } const double dx = useWorldUnits ? mDx : 1.0; area = this->reduce(mArray, math::Pow2(dx)); volume = this->reduce(mArray + leafCount, math::Pow3(dx) / 3.0); avgMeanCurvature = this->reduce(mArray + 2*leafCount, dx/area); if (newLeafs) { delete mLeafs; mLeafs = NULL; } delete [] mArray; if (mInterrupter) mInterrupter->end(); } ///////////////////////// PRIVATE METHODS ////////////////////// template inline bool LevelSetMeasure::checkInterrupter() { if (util::wasInterrupted(mInterrupter)) { tbb::task::self().cancel_group_execution(); return false; } return true; } template inline void LevelSetMeasure::measure2(const RangeType& range) { typedef math::Vec3 Vec3T; typedef math::ISGradient Grad; this->checkInterrupter(); const Real invDx = 1.0/mDx; const DiracDelta DD(1.5); const size_t leafCount = mLeafs->leafCount(); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { Real sumA = 0, sumV = 0;//reduce risk of catastrophic cancellation for (VoxelCIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { const Real dd = DD(invDx * (*voxelIter)); if (dd > 0.0) { const Coord p = voxelIter.getCoord(); const Vec3T g = invDx*Grad::result(mAcc, p);//voxel units sumA += dd * g.dot(g); sumV += dd * (g[0]*p[0]+g[1]*p[1]+g[2]*p[2]); } } double* v = mArray + leafIter.pos(); *v = sumA; v += leafCount; *v = sumV; } } template inline void LevelSetMeasure::measure3(const RangeType& range) { typedef math::Vec3 Vec3T; typedef math::ISGradient Grad; typedef math::ISMeanCurvature Curv; this->checkInterrupter(); const Real invDx = 1.0/mDx; const DiracDelta DD(1.5); ValueType alpha, beta; const size_t leafCount = mLeafs->leafCount(); for (LeafIterT leafIter=range.begin(); leafIter; ++leafIter) { Real sumA = 0, sumV = 0, sumC = 0;//reduce risk of catastrophic cancellation for (VoxelCIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { const Real dd = DD(invDx * (*voxelIter)); if (dd > 0.0) { const Coord p = voxelIter.getCoord(); const Vec3T g = invDx*Grad::result(mAcc, p);//voxel units const Real dA = dd * g.dot(g); sumA += dA; sumV += dd * (g[0]*p[0]+g[1]*p[1]+g[2]*p[2]); Curv::result(mAcc, p, alpha, beta); sumC += dA * alpha/(2*math::Pow2(beta))*invDx; } } double* v = mArray + leafIter.pos(); *v = sumA; v += leafCount; *v = sumV; v += leafCount; *v = sumC; } } //////////////////////////////////////// template inline typename boost::enable_if, Real>::type doLevelSetArea(const GridT& grid, bool useWorldSpace) { Real area, volume; LevelSetMeasure m(grid); m.measure(area, volume, useWorldSpace); return area; } template inline typename boost::disable_if, Real>::type doLevelSetArea(const GridT&, bool) { OPENVDB_THROW(TypeError, "level set area is supported only for scalar, floating-point grids"); } template inline Real levelSetArea(const GridT& grid, bool useWorldSpace) { return doLevelSetArea(grid, useWorldSpace); } //////////////////////////////////////// template inline typename boost::enable_if, Real>::type doLevelSetVolume(const GridT& grid, bool useWorldSpace) { Real area, volume; LevelSetMeasure m(grid); m.measure(area, volume, useWorldSpace); return volume; } template inline typename boost::disable_if, Real>::type doLevelSetVolume(const GridT&, bool) { OPENVDB_THROW(TypeError, "level set volume is supported only for scalar, floating-point grids"); } template inline Real levelSetVolume(const GridT& grid, bool useWorldSpace) { return doLevelSetVolume(grid, useWorldSpace); } //////////////////////////////////////// template inline typename boost::enable_if >::type doLevelSetMeasure(const GridT& grid, Real& area, Real& volume, bool useWorldSpace) { LevelSetMeasure m(grid); m.measure(area, volume, useWorldSpace); } template inline typename boost::disable_if >::type doLevelSetMeasure(const GridT&, Real&, Real&, bool) { OPENVDB_THROW(TypeError, "level set measure is supported only for scalar, floating-point grids"); } template inline void levelSetMeasure(const GridT& grid, Real& area, Real& volume, bool useWorldSpace) { doLevelSetMeasure(grid, area, volume, useWorldSpace); } //////////////////////////////////////// template inline typename boost::enable_if >::type doLevelSetMeasure(const GridT& grid, Real& area, Real& volume, Real& avgCurvature, bool useWorldSpace) { LevelSetMeasure m(grid); m.measure(area, volume, avgCurvature, useWorldSpace); } template inline typename boost::disable_if >::type doLevelSetMeasure(const GridT&, Real&, Real&, Real&, bool) { OPENVDB_THROW(TypeError, "level set measure is supported only for scalar, floating-point grids"); } template inline void levelSetMeasure(const GridT& grid, Real& area, Real& volume, Real& avgCurvature, bool useWorldSpace) { doLevelSetMeasure(grid, area, volume, avgCurvature, useWorldSpace); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVELSETMEASURE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetMorph.h0000644000000000000000000006263412252453157015274 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// /// @file LevelSetMorph.h /// /// @brief Shape morphology of level sets. Morphing from a source /// narrow-band level sets to a target narrow-band level set. #ifndef OPENVDB_TOOLS_LEVEL_SET_MORPH_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVEL_SET_MORPH_HAS_BEEN_INCLUDED #include "LevelSetTracker.h" #include "Interpolation.h" // for BoxSampler, etc. #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// Below are two simple wrapper classes for advection velocity fields. /// DiscreteField wraps a velocity grid, and EnrightField is mostly /// intended for debugging (it's an analytical, divergence-free and /// periodic field). Both classes implement the interface required by /// the LevelSetMorphing class defined below, and any class with the /// same API should work with LevelSetMorphing. /// @brief Hyperbolic advection of narrow-band level sets in an /// external velocity field /// /// @details /// The @c InterruptType template argument below refers to any class /// with the following interface: /// @code /// class Interrupter { /// ... /// public: /// void start(const char* name = NULL)// called when computations begin /// void end() // called when computations end /// bool wasInterrupted(int percent=-1)// return true to break computation /// }; /// @endcode /// /// @note If no template argument is provided for this InterruptType, /// the util::NullInterrupter is used, which implies that all interrupter /// calls are no-ops (i.e., they incur no computational overhead). template class LevelSetMorphing { public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef LevelSetTracker TrackerT; typedef typename TrackerT::LeafRange LeafRange; typedef typename TrackerT::LeafType LeafType; typedef typename TrackerT::BufferType BufferType; typedef typename TrackerT::ValueType ScalarType; /// Main constructor LevelSetMorphing(GridT& sourceGrid, const GridT& targetGrid, InterruptT* interrupt = NULL): mTracker(sourceGrid, interrupt), mTarget(&targetGrid), mSpatialScheme(math::HJWENO5_BIAS), mTemporalScheme(math::TVD_RK2) {} virtual ~LevelSetMorphing() {}; /// Redefine the target level set void setTarget(const GridT& targetGrid) { mTarget = &targetGrid; } /// Return the spatial finite-difference scheme math::BiasedGradientScheme getSpatialScheme() const { return mSpatialScheme; } /// Set the spatial finite-difference scheme void setSpatialScheme(math::BiasedGradientScheme scheme) { mSpatialScheme = scheme; } /// Return the temporal integration scheme math::TemporalIntegrationScheme getTemporalScheme() const { return mTemporalScheme; } /// Set the temporal integration scheme void setTemporalScheme(math::TemporalIntegrationScheme scheme) { mTemporalScheme = scheme; } /// Return the spatial finite-difference scheme math::BiasedGradientScheme getTrackerSpatialScheme() const { return mTracker.getSpatialScheme(); } /// Set the spatial finite-difference scheme void setTrackerSpatialScheme(math::BiasedGradientScheme scheme) { mTracker.setSpatialScheme(scheme); } /// Return the temporal integration scheme math::TemporalIntegrationScheme getTrackerTemporalScheme() const { return mTracker.getTemporalScheme(); } /// Set the temporal integration scheme void setTrackerTemporalScheme(math::TemporalIntegrationScheme scheme) { mTracker.setTemporalScheme(scheme); } /// Return the number of normalizations performed per track or normalize call. int getNormCount() const { return mTracker.getNormCount(); } /// Set the number of normalizations performed per track or normalize call. void setNormCount(int n) { mTracker.setNormCount(n); } /// Return the grain size used for multithreading int getGrainSize() const { return mTracker.getGrainSize(); } /// @brief Set the grain size used for multithreading. /// @note A grain size of 0 or less disables multithreading! void setGrainSize(int grainsize) { mTracker.setGrainSize(grainsize); } /// @brief Advect the level set from its current time, @a time0, to its /// final time, @a time1. If @a time0 > @a time1, perform backward advection. /// /// @return the number of CFL iterations used to advect from @a time0 to @a time1 size_t advect(ScalarType time0, ScalarType time1); private: // This templated private class implements all the level set magic. template class LevelSetMorph { public: /// Main constructor LevelSetMorph(TrackerT& tracker, const GridT* target); /// Shallow copy constructor called by tbb::parallel_for() threads LevelSetMorph(const LevelSetMorph& other); /// Shallow copy constructor called by tbb::parallel_reduce() threads LevelSetMorph(LevelSetMorph& other, tbb::split); /// destructor virtual ~LevelSetMorph() {} /// Advect the level set from it's current time, time0, to it's final time, time1. /// @return number of CFL iterations size_t advect(ScalarType time0, ScalarType time1); /// Used internally by tbb::parallel_for() void operator()(const LeafRange& r) const { if (mTask) mTask(const_cast(this), r); else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); } /// Used internally by tbb::parallel_reduce() void operator()(const LeafRange& r) { if (mTask) mTask(this, r); else OPENVDB_THROW(ValueError, "task is undefined - don\'t call this method directly"); } /// This is only called by tbb::parallel_reduce() threads void join(const LevelSetMorph& other) { mMaxAbsS = math::Max(mMaxAbsS, other.mMaxAbsS); } private: typedef typename boost::function FuncType; TrackerT* mTracker; const GridT* mTarget; ScalarType mMinAbsS, mMaxAbsS; const MapT* mMap; FuncType mTask; /// Enum to define the type of multithreading enum ThreadingMode { PARALLEL_FOR, PARALLEL_REDUCE }; // for internal use // method calling tbb void cook(ThreadingMode mode, size_t swapBuffer = 0); /// Sample field and return the CFT time step typename GridT::ValueType sampleSpeed( ScalarType time0, ScalarType time1, Index speedBuffer); void sampleXformedSpeed(const LeafRange& r, Index speedBuffer); void sampleAlignedSpeed(const LeafRange& r, Index speedBuffer); // Forward Euler advection steps: Phi(result) = Phi(0) - dt * Speed(speed)*|Grad[Phi(0)]|; void euler1(const LeafRange& r, ScalarType dt, Index resultBuffer, Index speedBuffer); // Convex combination of Phi and a forward Euler advection steps: // Phi(result) = alpha * Phi(phi) + (1-alpha) * (Phi(0) - dt * Speed(speed)*|Grad[Phi(0)]|); void euler2(const LeafRange& r, ScalarType dt, ScalarType alpha, Index phiBuffer, Index resultBuffer, Index speedBuffer); }; // end of private LevelSetMorph class template size_t advect1(ScalarType time0, ScalarType time1); template size_t advect2(ScalarType time0, ScalarType time1); template size_t advect3(ScalarType time0, ScalarType time1); TrackerT mTracker; const GridT* mTarget; math::BiasedGradientScheme mSpatialScheme; math::TemporalIntegrationScheme mTemporalScheme; // disallow copy by assignment void operator=(const LevelSetMorphing& other) {} };//end of LevelSetMorphing template inline size_t LevelSetMorphing::advect(ScalarType time0, ScalarType time1) { switch (mSpatialScheme) { case math::FIRST_BIAS: return this->advect1(time0, time1); case math::SECOND_BIAS: return this->advect1(time0, time1); case math::THIRD_BIAS: return this->advect1(time0, time1); case math::WENO5_BIAS: return this->advect1(time0, time1); case math::HJWENO5_BIAS: return this->advect1(time0, time1); default: OPENVDB_THROW(ValueError, "Spatial difference scheme not supported!"); } return 0; } template template inline size_t LevelSetMorphing::advect1(ScalarType time0, ScalarType time1) { switch (mTemporalScheme) { case math::TVD_RK1: return this->advect2(time0, time1); case math::TVD_RK2: return this->advect2(time0, time1); case math::TVD_RK3: return this->advect2(time0, time1); default: OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); } return 0; } template template inline size_t LevelSetMorphing::advect2(ScalarType time0, ScalarType time1) { const math::Transform& trans = mTracker.grid().transform(); if (trans.mapType() == math::UniformScaleMap::mapType()) { return this->advect3(time0, time1); } else if (trans.mapType() == math::UniformScaleTranslateMap::mapType()) { return this->advect3( time0, time1); } else if (trans.mapType() == math::UnitaryMap::mapType()) { return this->advect3(time0, time1); } else if (trans.mapType() == math::TranslationMap::mapType()) { return this->advect3(time0, time1); } else { OPENVDB_THROW(ValueError, "MapType not supported!"); } return 0; } template template inline size_t LevelSetMorphing::advect3(ScalarType time0, ScalarType time1) { LevelSetMorph tmp(mTracker, mTarget); return tmp.advect(time0, time1); } /////////////////////////////////////////////////////////////////////// template template inline LevelSetMorphing:: LevelSetMorph:: LevelSetMorph(TrackerT& tracker, const GridT* target): mTracker(&tracker), mTarget(target), mMinAbsS(1e-6), mMap(tracker.grid().transform().template constMap().get()), mTask(0) { } template template inline LevelSetMorphing:: LevelSetMorph:: LevelSetMorph(const LevelSetMorph& other): mTracker(other.mTracker), mTarget(other.mTarget), mMinAbsS(other.mMinAbsS), mMaxAbsS(other.mMaxAbsS), mMap(other.mMap), mTask(other.mTask) { } template template inline LevelSetMorphing:: LevelSetMorph:: LevelSetMorph(LevelSetMorph& other, tbb::split): mTracker(other.mTracker), mTarget(other.mTarget), mMinAbsS(other.mMinAbsS), mMaxAbsS(other.mMaxAbsS), mMap(other.mMap), mTask(other.mTask) { } template template inline size_t LevelSetMorphing:: LevelSetMorph:: advect(ScalarType time0, ScalarType time1) { // Make sure we have enough temporal auxiliary buffers for the time // integration AS WELL AS an extra buffer with the speed function! static const Index auxBuffers = 1 + (TemporalScheme == math::TVD_RK3 ? 2 : 1); size_t countCFL = 0; while (time0 < time1 && mTracker->checkInterrupter()) { mTracker->leafs().rebuildAuxBuffers(auxBuffers); const ScalarType dt = this->sampleSpeed(time0, time1, auxBuffers); if ( math::isZero(dt) ) break;//V is essentially zero so terminate OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN //switch is resolved at compile-time switch(TemporalScheme) { case math::TVD_RK1: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * Speed(2) * |Grad[Phi(0)]| mTask = boost::bind(&LevelSetMorph::euler1, _1, _2, dt, /*result=*/1, /*speed*/2); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(PARALLEL_FOR, 1); break; case math::TVD_RK2: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * Speed(2) * |Grad[Phi(0)]| mTask = boost::bind(&LevelSetMorph::euler1, _1, _2, dt, /*result=*/1, /*speed*/2); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(PARALLEL_FOR, 1); // Convex combine explict Euler step: t2 = t0 + dt // Phi_t2(1) = 1/2 * Phi_t0(1) + 1/2 * (Phi_t1(0) - dt * Speed(2) * |Grad[Phi(0)]|) mTask = boost::bind(&LevelSetMorph::euler2, _1, _2, dt, ScalarType(0.5), /*phi=*/1, /*result=*/1, /*speed*/2); // Cook and swap buffer 0 and 1 such that Phi_t2(0) and Phi_t1(1) this->cook(PARALLEL_FOR, 1); break; case math::TVD_RK3: // Perform one explicit Euler step: t1 = t0 + dt // Phi_t1(1) = Phi_t0(0) - dt * Speed(3) * |Grad[Phi(0)]| mTask = boost::bind(&LevelSetMorph::euler1, _1, _2, dt, /*result=*/1, /*speed*/3); // Cook and swap buffer 0 and 1 such that Phi_t1(0) and Phi_t0(1) this->cook(PARALLEL_FOR, 1); // Convex combine explict Euler step: t2 = t0 + dt/2 // Phi_t2(2) = 3/4 * Phi_t0(1) + 1/4 * (Phi_t1(0) - dt * Speed(3) * |Grad[Phi(0)]|) mTask = boost::bind(&LevelSetMorph::euler2, _1, _2, dt, ScalarType(0.75), /*phi=*/1, /*result=*/2, /*speed*/3); // Cook and swap buffer 0 and 2 such that Phi_t2(0) and Phi_t1(2) this->cook(PARALLEL_FOR, 2); // Convex combine explict Euler step: t3 = t0 + dt // Phi_t3(2) = 1/3 * Phi_t0(1) + 2/3 * (Phi_t2(0) - dt * Speed(3) * |Grad[Phi(0)]|) mTask = boost::bind(&LevelSetMorph::euler2, _1, _2, dt, ScalarType(1.0/3.0), /*phi=*/1, /*result=*/2, /*speed*/3); // Cook and swap buffer 0 and 2 such that Phi_t3(0) and Phi_t2(2) this->cook(PARALLEL_FOR, 2); break; default: OPENVDB_THROW(ValueError, "Temporal integration scheme not supported!"); }//end of compile-time resolved switch OPENVDB_NO_UNREACHABLE_CODE_WARNING_END time0 += dt; ++countCFL; mTracker->leafs().removeAuxBuffers(); // Track the narrow band mTracker->track(); }//end wile-loop over time return countCFL;//number of CLF propagation steps } template template inline typename GridT::ValueType LevelSetMorphing:: LevelSetMorph:: sampleSpeed(ScalarType time0, ScalarType time1, Index speedBuffer) { mMaxAbsS = mMinAbsS; const size_t leafCount = mTracker->leafs().leafCount(); if (leafCount==0 || time0 >= time1) return ScalarType(0); if (mTarget->transform() == mTracker->grid().transform()) { mTask = boost::bind(&LevelSetMorph::sampleAlignedSpeed, _1, _2, speedBuffer); } else { mTask = boost::bind(&LevelSetMorph::sampleXformedSpeed, _1, _2, speedBuffer); } this->cook(PARALLEL_REDUCE); if (math::isApproxEqual(mMinAbsS, mMaxAbsS)) return ScalarType(0);//speed is essentially zero static const ScalarType CFL = (TemporalScheme == math::TVD_RK1 ? ScalarType(0.3) : TemporalScheme == math::TVD_RK2 ? ScalarType(0.9) : ScalarType(1.0))/math::Sqrt(ScalarType(3.0)); const ScalarType dt = math::Abs(time1 - time0), dx = mTracker->voxelSize(); return math::Min(dt, ScalarType(CFL*dx/mMaxAbsS)); } template template inline void LevelSetMorphing:: LevelSetMorph:: sampleXformedSpeed(const LeafRange& range, Index speedBuffer) { typedef typename LeafType::ValueOnCIter VoxelIterT; typedef tools::GridSampler SamplerT; const MapT& map = *mMap; mTracker->checkInterrupter(); SamplerT sampler(mTarget->getAccessor(), mTarget->transform()); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { BufferType& speed = leafIter.buffer(speedBuffer); for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { ScalarType& s = const_cast(speed.getValue(voxelIter.pos())); s -= sampler.wsSample(map.applyMap(voxelIter.getCoord().asVec3d())); mMaxAbsS = math::Max(mMaxAbsS, math::Abs(s)); } } } template template inline void LevelSetMorphing:: LevelSetMorph:: sampleAlignedSpeed(const LeafRange& range, Index speedBuffer) { typedef typename LeafType::ValueOnCIter VoxelIterT; mTracker->checkInterrupter(); typename GridT::ConstAccessor target = mTarget->getAccessor(); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { BufferType& speed = leafIter.buffer(speedBuffer); for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { ScalarType& s = const_cast(speed.getValue(voxelIter.pos())); s -= target.getValue(voxelIter.getCoord()); mMaxAbsS = math::Max(mMaxAbsS, math::Abs(s)); } } } template template inline void LevelSetMorphing:: LevelSetMorph:: cook(ThreadingMode mode, size_t swapBuffer) { mTracker->startInterrupter("Morphing level set"); const int grainSize = mTracker->getGrainSize(); const LeafRange range = mTracker->leafs().leafRange(grainSize); if (mTracker->getGrainSize()==0) { (*this)(range); } else if (mode == PARALLEL_FOR) { tbb::parallel_for(range, *this); } else if (mode == PARALLEL_REDUCE) { tbb::parallel_reduce(range, *this); } else { throw std::runtime_error("Undefined threading mode"); } mTracker->leafs().swapLeafBuffer(swapBuffer, grainSize == 0); mTracker->endInterrupter(); } // Forward Euler advection steps: // Phi(result) = Phi(0) - dt * Phi(speed) * |Grad[Phi(0)]| template template inline void LevelSetMorphing:: LevelSetMorph:: euler1(const LeafRange& range, ScalarType dt, Index resultBuffer, Index speedBuffer) { typedef math::BIAS_SCHEME Scheme; typedef typename Scheme::template ISStencil::StencilType Stencil; typedef typename LeafType::ValueOnCIter VoxelIterT; mTracker->checkInterrupter(); const MapT& map = *mMap; Stencil stencil(mTracker->grid()); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { BufferType& speed = leafIter.buffer(speedBuffer); BufferType& result = leafIter.buffer(resultBuffer); for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { const Index n = voxelIter.pos(); stencil.moveTo(voxelIter); const ScalarType G = math::GradientNormSqrd::result(map, stencil); result.setValue(n, *voxelIter - dt * speed.getValue(n) * G); } } } // Convex combination of Phi and a forward Euler advection steps: // Phi(result) = alpha * Phi(phi) + (1-alpha) * (Phi(0) - dt * Phi(speed) * |Grad[Phi(0)]|) template template inline void LevelSetMorphing:: LevelSetMorph:: euler2(const LeafRange& range, ScalarType dt, ScalarType alpha, Index phiBuffer, Index resultBuffer, Index speedBuffer) { typedef math::BIAS_SCHEME Scheme; typedef typename Scheme::template ISStencil::StencilType Stencil; typedef typename LeafType::ValueOnCIter VoxelIterT; mTracker->checkInterrupter(); const MapT& map = *mMap; const ScalarType beta = ScalarType(1.0) - alpha; Stencil stencil(mTracker->grid()); for (typename LeafRange::Iterator leafIter = range.begin(); leafIter; ++leafIter) { BufferType& speed = leafIter.buffer(speedBuffer); BufferType& result = leafIter.buffer(resultBuffer); BufferType& phi = leafIter.buffer(phiBuffer); for (VoxelIterT voxelIter = leafIter->cbeginValueOn(); voxelIter; ++voxelIter) { const Index n = voxelIter.pos(); stencil.moveTo(voxelIter); const ScalarType G = math::GradientNormSqrd::result(map, stencil); result.setValue(n, alpha * phi.getValue(n) + beta * (*voxelIter - dt * speed.getValue(n) * G)); } } } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVEL_SET_MORPH_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/ValueTransformer.h0000644000000000000000000006014212252453157016032 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file ValueTransformer.h /// /// @author Peter Cucka /// /// tools::foreach() and tools::transformValues() transform the values in a grid /// by iterating over the grid with a user-supplied iterator and applying a /// user-supplied functor at each step of the iteration. With tools::foreach(), /// the transformation is done in-place on the input grid, whereas with /// tools::transformValues(), transformed values are written to an output grid /// (which can, for example, have a different value type than the input grid). /// Both functions can optionally transform multiple values of the grid in parallel. /// /// tools::accumulate() can be used to accumulate the results of applying a functor /// at each step of a grid iteration. (The functor is responsible for storing and /// updating intermediate results.) When the iteration is done serially the behavior is /// the same as with tools::foreach(), but when multiple values are processed in parallel, /// an additional step is performed: when any two threads finish processing, /// @c op.join(otherOp) is called on one thread's functor to allow it to coalesce /// its intermediate result with the other thread's. /// /// Finally, tools::setValueOnMin(), tools::setValueOnMax(), tools::setValueOnSum() /// and tools::setValueOnMult() are wrappers around Tree::modifyValue() (or /// ValueAccessor::modifyValue()) for some commmon in-place operations. /// These are typically significantly faster than calling getValue() followed by setValue(). #ifndef OPENVDB_TOOLS_VALUETRANSFORMER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_VALUETRANSFORMER_HAS_BEEN_INCLUDED #include // for std::min(), std::max() #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// Iterate over a grid and at each step call @c op(iter). /// @param iter an iterator over a grid or its tree (@c Grid::ValueOnCIter, /// @c Tree::NodeIter, etc.) /// @param op a functor of the form void op(const IterT&), where @c IterT is /// the type of @a iter /// @param threaded if true, transform multiple values of the grid in parallel /// @param shareOp if true and @a threaded is true, all threads use the same functor; /// otherwise, each thread gets its own copy of the @e original functor /// /// @par Example: /// Multiply all values (both set and unset) of a scalar, floating-point grid by two. /// @code /// struct Local { /// static inline void op(const FloatGrid::ValueAllIter& iter) { /// iter.setValue(*iter * 2); /// } /// }; /// FloatGrid grid = ...; /// tools::foreach(grid.beginValueAll(), Local::op); /// @endcode /// /// @par Example: /// Rotate all active vectors of a vector grid by 45 degrees about the y axis. /// @code /// namespace { /// struct MatMul { /// math::Mat3s M; /// MatMul(const math::Mat3s& mat): M(mat) {} /// inline void operator()(const VectorGrid::ValueOnIter& iter) const { /// iter.setValue(M.transform(*iter)); /// } /// }; /// } /// { /// VectorGrid grid = ...; /// tools::foreach(grid.beginValueOn(), /// MatMul(math::rotation(math::Y, M_PI_4))); /// } /// @endcode /// /// @note For more complex operations that require finer control over threading, /// consider using @c tbb::parallel_for() or @c tbb::parallel_reduce() in conjunction /// with a tree::IteratorRange that wraps a grid or tree iterator. template inline void foreach(const IterT& iter, XformOp& op, bool threaded = true, bool shareOp = true); template inline void foreach(const IterT& iter, const XformOp& op, bool threaded = true, bool shareOp = true); /// Iterate over a grid and at each step call op(iter, accessor) to /// populate (via the accessor) the given output grid, whose @c ValueType /// need not be the same as the input grid's. /// @param inIter a non-const or (preferably) @c const iterator over an /// input grid or its tree (@c Grid::ValueOnCIter, @c Tree::NodeIter, etc.) /// @param outGrid an empty grid to be populated /// @param op a functor of the form /// void op(const InIterT&, OutGridT::ValueAccessor&), /// where @c InIterT is the type of @a inIter /// @param threaded if true, transform multiple values of the input grid in parallel /// @param shareOp if true and @a threaded is true, all threads use the same functor; /// otherwise, each thread gets its own copy of the @e original functor /// @param merge how to merge intermediate results from multiple threads (see Types.h) /// /// @par Example: /// Populate a scalar floating-point grid with the lengths of the vectors from all /// active voxels of a vector-valued input grid. /// @code /// struct Local { /// static void op( /// const Vec3fGrid::ValueOnCIter& iter, /// FloatGrid::ValueAccessor& accessor) /// { /// if (iter.isVoxelValue()) { // set a single voxel /// accessor.setValue(iter.getCoord(), iter->length()); /// } else { // fill an entire tile /// CoordBBox bbox; /// iter.getBoundingBox(bbox); /// accessor.getTree()->fill(bbox, iter->length()); /// } /// } /// }; /// Vec3fGrid inGrid = ...; /// FloatGrid outGrid; /// tools::transformValues(inGrid.cbeginValueOn(), outGrid, Local::op); /// @endcode /// /// @note For more complex operations that require finer control over threading, /// consider using @c tbb::parallel_for() or @c tbb::parallel_reduce() in conjunction /// with a tree::IteratorRange that wraps a grid or tree iterator. template inline void transformValues(const InIterT& inIter, OutGridT& outGrid, XformOp& op, bool threaded = true, bool shareOp = true, MergePolicy merge = MERGE_ACTIVE_STATES); #ifndef _MSC_VER template inline void transformValues(const InIterT& inIter, OutGridT& outGrid, const XformOp& op, bool threaded = true, bool shareOp = true, MergePolicy merge = MERGE_ACTIVE_STATES); #endif /// Iterate over a grid and at each step call @c op(iter). If threading is enabled, /// call @c op.join(otherOp) to accumulate intermediate results from pairs of threads. /// @param iter an iterator over a grid or its tree (@c Grid::ValueOnCIter, /// @c Tree::NodeIter, etc.) /// @param op a functor with a join method of the form void join(XformOp&) /// and a call method of the form void op(const IterT&), /// where @c IterT is the type of @a iter /// @param threaded if true, transform multiple values of the grid in parallel /// @note If @a threaded is true, each thread gets its own copy of the @e original functor. /// The order in which threads are joined is unspecified. /// @note If @a threaded is false, the join method is never called. /// /// @par Example: /// Compute the average of the active values of a scalar, floating-point grid /// using the math::Stats class. /// @code /// namespace { /// struct Average { /// math::Stats stats; /// /// // Accumulate voxel and tile values into this functor's Stats object. /// inline void operator()(const FloatGrid::ValueOnCIter& iter) { /// if (iter.isVoxelValue()) stats.add(*iter); /// else stats.add(*iter, iter.getVoxelCount()); /// } /// /// // Accumulate another functor's Stats object into this functor's. /// inline void join(Average& other) { stats.add(other.stats); } /// /// // Return the cumulative result. /// inline double average() const { return stats.mean(); } /// }; /// } /// { /// FloatGrid grid = ...; /// Average op; /// tools::accumulate(grid.cbeginValueOn(), op); /// double average = op.average(); /// } /// @endcode /// /// @note For more complex operations that require finer control over threading, /// consider using @c tbb::parallel_for() or @c tbb::parallel_reduce() in conjunction /// with a tree::IteratorRange that wraps a grid or tree iterator. template inline void accumulate(const IterT& iter, XformOp& op, bool threaded = true); /// @brief Set the value of the voxel at the given coordinates in @a tree to /// the minimum of its current value and @a value, and mark the voxel as active. /// @details This is typically significantly faster than calling getValue() /// followed by setValueOn(). /// @note @a TreeT can be either a Tree or a ValueAccessor. template inline void setValueOnMin(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value); /// @brief Set the value of the voxel at the given coordinates in @a tree to /// the maximum of its current value and @a value, and mark the voxel as active. /// @details This is typically significantly faster than calling getValue() /// followed by setValueOn(). /// @note @a TreeT can be either a Tree or a ValueAccessor. template inline void setValueOnMax(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value); /// @brief Set the value of the voxel at the given coordinates in @a tree to /// the sum of its current value and @a value, and mark the voxel as active. /// @details This is typically significantly faster than calling getValue() /// followed by setValueOn(). /// @note @a TreeT can be either a Tree or a ValueAccessor. template inline void setValueOnSum(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value); /// @brief Set the value of the voxel at the given coordinates in @a tree to /// the product of its current value and @a value, and mark the voxel as active. /// @details This is typically significantly faster than calling getValue() /// followed by setValueOn(). /// @note @a TreeT can be either a Tree or a ValueAccessor. template inline void setValueOnMult(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value); //////////////////////////////////////// namespace valxform { template struct MinOp { const ValueType val; MinOp(const ValueType& v): val(v) {} inline void operator()(ValueType& v) const { v = std::min(v, val); } }; template struct MaxOp { const ValueType val; MaxOp(const ValueType& v): val(v) {} inline void operator()(ValueType& v) const { v = std::max(v, val); } }; template struct SumOp { const ValueType val; SumOp(const ValueType& v): val(v) {} inline void operator()(ValueType& v) const { v += val; } }; template struct MultOp { const ValueType val; MultOp(const ValueType& v): val(v) {} inline void operator()(ValueType& v) const { v *= val; } }; } template inline void setValueOnMin(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value) { tree.modifyValue(xyz, valxform::MinOp(value)); } template inline void setValueOnMax(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value) { tree.modifyValue(xyz, valxform::MaxOp(value)); } template inline void setValueOnSum(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value) { tree.modifyValue(xyz, valxform::SumOp(value)); } template inline void setValueOnMult(TreeT& tree, const Coord& xyz, const typename TreeT::ValueType& value) { tree.modifyValue(xyz, valxform::MultOp(value)); } //////////////////////////////////////// namespace valxform { template class SharedOpApplier { public: typedef typename tree::IteratorRange IterRange; SharedOpApplier(const IterT& iter, OpT& op): mIter(iter), mOp(op) {} void process(bool threaded = true) { IterRange range(mIter); if (threaded) { tbb::parallel_for(range, *this); } else { (*this)(range); } } void operator()(IterRange& r) const { for ( ; r; ++r) mOp(r.iterator()); } private: IterT mIter; OpT& mOp; }; template class CopyableOpApplier { public: typedef typename tree::IteratorRange IterRange; CopyableOpApplier(const IterT& iter, const OpT& op): mIter(iter), mOp(op), mOrigOp(&op) {} // When splitting this task, give the subtask a copy of the original functor, // not of this task's functor, which might have been modified arbitrarily. CopyableOpApplier(const CopyableOpApplier& other): mIter(other.mIter), mOp(*other.mOrigOp), mOrigOp(other.mOrigOp) {} void process(bool threaded = true) { IterRange range(mIter); if (threaded) { tbb::parallel_for(range, *this); } else { (*this)(range); } } void operator()(IterRange& r) const { for ( ; r; ++r) mOp(r.iterator()); } private: IterT mIter; OpT mOp; // copy of original functor OpT const * const mOrigOp; // pointer to original functor }; } // namespace valxform template inline void foreach(const IterT& iter, XformOp& op, bool threaded, bool shared) { if (shared) { typename valxform::SharedOpApplier proc(iter, op); proc.process(threaded); } else { typedef typename valxform::CopyableOpApplier Processor; Processor proc(iter, op); proc.process(threaded); } } template inline void foreach(const IterT& iter, const XformOp& op, bool threaded, bool /*shared*/) { // Const ops are shared across threads, not copied. typename valxform::SharedOpApplier proc(iter, op); proc.process(threaded); } //////////////////////////////////////// namespace valxform { template class SharedOpTransformer { public: typedef typename InIterT::TreeT InTreeT; typedef typename tree::IteratorRange IterRange; typedef typename OutTreeT::ValueType OutValueT; SharedOpTransformer(const InIterT& inIter, OutTreeT& outTree, OpT& op, MergePolicy merge): mIsRoot(true), mInputIter(inIter), mInputTree(inIter.getTree()), mOutputTree(&outTree), mOp(op), mMergePolicy(merge) { if (static_cast(mInputTree) == static_cast(mOutputTree)) { OPENVDB_LOG_INFO("use tools::foreach(), not transformValues()," " to transform a grid in place"); } } /// Splitting constructor SharedOpTransformer(SharedOpTransformer& other, tbb::split): mIsRoot(false), mInputIter(other.mInputIter), mInputTree(other.mInputTree), mOutputTree(new OutTreeT(zeroVal())), mOp(other.mOp), mMergePolicy(other.mMergePolicy) {} ~SharedOpTransformer() { // Delete the output tree only if it was allocated locally // (the top-level output tree was supplied by the caller). if (!mIsRoot) { delete mOutputTree; mOutputTree = NULL; } } void process(bool threaded = true) { if (!mInputTree || !mOutputTree) return; IterRange range(mInputIter); // Independently transform elements in the iterator range, // either in parallel or serially. if (threaded) { tbb::parallel_reduce(range, *this); } else { (*this)(range); } } /// Transform each element in the given range. void operator()(IterRange& range) const { if (!mOutputTree) return; typename tree::ValueAccessor outAccessor(*mOutputTree); for ( ; range; ++range) { mOp(range.iterator(), outAccessor); } } void join(const SharedOpTransformer& other) { if (mOutputTree && other.mOutputTree) { mOutputTree->merge(*other.mOutputTree, mMergePolicy); } } private: bool mIsRoot; InIterT mInputIter; const InTreeT* mInputTree; OutTreeT* mOutputTree; OpT& mOp; MergePolicy mMergePolicy; }; // class SharedOpTransformer template class CopyableOpTransformer { public: typedef typename InIterT::TreeT InTreeT; typedef typename tree::IteratorRange IterRange; typedef typename OutTreeT::ValueType OutValueT; CopyableOpTransformer(const InIterT& inIter, OutTreeT& outTree, const OpT& op, MergePolicy merge): mIsRoot(true), mInputIter(inIter), mInputTree(inIter.getTree()), mOutputTree(&outTree), mOp(op), mOrigOp(&op), mMergePolicy(merge) { if (static_cast(mInputTree) == static_cast(mOutputTree)) { OPENVDB_LOG_INFO("use tools::foreach(), not transformValues()," " to transform a grid in place"); } } // When splitting this task, give the subtask a copy of the original functor, // not of this task's functor, which might have been modified arbitrarily. CopyableOpTransformer(CopyableOpTransformer& other, tbb::split): mIsRoot(false), mInputIter(other.mInputIter), mInputTree(other.mInputTree), mOutputTree(new OutTreeT(zeroVal())), mOp(*other.mOrigOp), mOrigOp(other.mOrigOp), mMergePolicy(other.mMergePolicy) {} ~CopyableOpTransformer() { // Delete the output tree only if it was allocated locally // (the top-level output tree was supplied by the caller). if (!mIsRoot) { delete mOutputTree; mOutputTree = NULL; } } void process(bool threaded = true) { if (!mInputTree || !mOutputTree) return; IterRange range(mInputIter); // Independently transform elements in the iterator range, // either in parallel or serially. if (threaded) { tbb::parallel_reduce(range, *this); } else { (*this)(range); } } /// Transform each element in the given range. void operator()(IterRange& range) { if (!mOutputTree) return; typename tree::ValueAccessor outAccessor(*mOutputTree); for ( ; range; ++range) { mOp(range.iterator(), outAccessor); } } void join(const CopyableOpTransformer& other) { if (mOutputTree && other.mOutputTree) { mOutputTree->merge(*other.mOutputTree, mMergePolicy); } } private: bool mIsRoot; InIterT mInputIter; const InTreeT* mInputTree; OutTreeT* mOutputTree; OpT mOp; // copy of original functor OpT const * const mOrigOp; // pointer to original functor MergePolicy mMergePolicy; }; // class CopyableOpTransformer } // namespace valxform //////////////////////////////////////// template inline void transformValues(const InIterT& inIter, OutGridT& outGrid, XformOp& op, bool threaded, bool shared, MergePolicy merge) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType OutTreeT; if (shared) { typedef typename valxform::SharedOpTransformer Processor; Processor proc(inIter, Adapter::tree(outGrid), op, merge); proc.process(threaded); } else { typedef typename valxform::CopyableOpTransformer Processor; Processor proc(inIter, Adapter::tree(outGrid), op, merge); proc.process(threaded); } } #ifndef _MSC_VER template inline void transformValues(const InIterT& inIter, OutGridT& outGrid, const XformOp& op, bool threaded, bool /*share*/, MergePolicy merge) { typedef TreeAdapter Adapter; typedef typename Adapter::TreeType OutTreeT; // Const ops are shared across threads, not copied. typedef typename valxform::SharedOpTransformer Processor; Processor proc(inIter, Adapter::tree(outGrid), op, merge); proc.process(threaded); } #endif //////////////////////////////////////// namespace valxform { template class OpAccumulator { public: typedef typename tree::IteratorRange IterRange; // The root task makes a const copy of the original functor (mOrigOp) // and keeps a pointer to the original functor (mOp), which it then modifies. // Each subtask keeps a const pointer to the root task's mOrigOp // and makes and then modifies a non-const copy (mOp) of it. OpAccumulator(const IterT& iter, OpT& op): mIsRoot(true), mIter(iter), mOp(&op), mOrigOp(new OpT(op)) {} // When splitting this task, give the subtask a copy of the original functor, // not of this task's functor, which might have been modified arbitrarily. OpAccumulator(OpAccumulator& other, tbb::split): mIsRoot(false), mIter(other.mIter), mOp(new OpT(*other.mOrigOp)), mOrigOp(other.mOrigOp) {} ~OpAccumulator() { if (mIsRoot) delete mOrigOp; else delete mOp; } void process(bool threaded = true) { IterRange range(mIter); if (threaded) { tbb::parallel_reduce(range, *this); } else { (*this)(range); } } void operator()(IterRange& r) { for ( ; r; ++r) (*mOp)(r.iterator()); } void join(OpAccumulator& other) { mOp->join(*other.mOp); } private: bool mIsRoot; IterT mIter; OpT* mOp; // pointer to original functor, which might get modified OpT const * const mOrigOp; // const copy of original functor }; // class OpAccumulator } // namespace valxform //////////////////////////////////////// template inline void accumulate(const IterT& iter, XformOp& op, bool threaded) { typename valxform::OpAccumulator proc(iter, op); proc.process(threaded); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_VALUETRANSFORMER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetFracture.h0000644000000000000000000003034312252453157015752 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file tools/LevelSetFracture.h /// /// @brief Divide volumes represented by level set grids into multiple, /// disjoint pieces by intersecting them with one or more "cutter" volumes, /// also represented by level sets. #ifndef OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED #include #include #include #include #include "Composite.h" // for csgIntersection() and csgDifference() #include "GridTransformer.h" // for resampleToMatch() #include "LevelSetUtil.h" // for MinMaxVoxel() #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Level set fracturing template class LevelSetFracture { public: typedef std::vector Vec3sList; typedef std::vector QuatsList; typedef std::list GridPtrList; typedef typename GridPtrList::iterator GridPtrListIter; /// @brief Default constructor /// /// @param interrupter optional interrupter object explicit LevelSetFracture(InterruptType* interrupter = NULL); /// @brief Divide volumes represented by level set grids into multiple, /// disjoint pieces by intersecting them with one or more "cutter" volumes, /// also represented by level sets. /// @details If desired, the process can be applied iteratively, so that /// fragments created with one cutter are subdivided by other cutters. /// /// @note The incoming @a grids and the @a cutter are required to have matching /// transforms and narrow band widths! /// /// @param grids list of grids to fracture. The residuals of the /// fractured grids will remain in this list /// @param cutter a level set grid to use as the cutter object /// @param segment toggle to split disjoint fragments into their own grids /// @param points optional list of world space points at which to instance the /// cutter object (if null, use the cutter's current position only) /// @param rotations optional list of custom rotations for each cutter instance /// @param cutterOverlap toggle to allow consecutive cutter instances to fracture /// previously generated fragments void fracture(GridPtrList& grids, const GridType& cutter, bool segment = false, const Vec3sList* points = NULL, const QuatsList* rotations = NULL, bool cutterOverlap = true); /// Return a list of new fragments, not including the residuals from the input grids. GridPtrList& fragments() { return mFragments; } /// Remove all elements from the fragment list. void clear() { mFragments.clear(); } private: // disallow copy by assignment void operator=(const LevelSetFracture&) {} bool wasInterrupted(int percent = -1) const { return mInterrupter && mInterrupter->wasInterrupted(percent); } bool isValidFragment(GridType&) const; void segmentFragments(GridPtrList&) const; void process(GridPtrList&, const GridType& cutter); InterruptType* mInterrupter; GridPtrList mFragments; }; //////////////////////////////////////// // Internal utility objects and implementation details namespace internal { /// @brief Segmentation scheme, splits disjoint fragments into separate grids. /// @note This is a temporary solution and it will be replaced soon. template inline std::vector segment(GridType& grid, InterruptType* interrupter = NULL) { typedef typename GridType::Ptr GridPtr; typedef typename GridType::TreeType TreeType; typedef typename TreeType::Ptr TreePtr; typedef typename TreeType::ValueType ValueType; std::vector segments; while (grid.activeVoxelCount() > 0) { if (interrupter && interrupter->wasInterrupted()) break; // Deep copy the grid's metadata (tree and transform are shared) GridPtr segment(new GridType(grid, ShallowCopy())); // Make the transform unique and insert an empty tree segment->setTransform(grid.transform().copy()); TreePtr tree(new TreeType(grid.background())); segment->setTree(tree); std::deque coordList; coordList.push_back(grid.tree().beginLeaf()->beginValueOn().getCoord()); Coord ijk, n_ijk; ValueType value; typename tree::ValueAccessor sourceAcc(grid.tree()); typename tree::ValueAccessor targetAcc(segment->tree()); while (!coordList.empty()) { if (interrupter && interrupter->wasInterrupted()) break; ijk = coordList.back(); coordList.pop_back(); if (!sourceAcc.probeValue(ijk, value)) continue; if (targetAcc.isValueOn(ijk)) continue; targetAcc.setValue(ijk, value); sourceAcc.setValueOff(ijk); for (int n = 0; n < 6; n++) { n_ijk = ijk + util::COORD_OFFSETS[n]; if (!targetAcc.isValueOn(n_ijk) && sourceAcc.isValueOn(n_ijk)) { coordList.push_back(n_ijk); } } } grid.tree().pruneInactive(); segment->tree().signedFloodFill(); segments.push_back(segment); } return segments; } } // namespace internal //////////////////////////////////////// template LevelSetFracture::LevelSetFracture(InterruptType* interrupter) : mInterrupter(interrupter) , mFragments() { } template void LevelSetFracture::fracture(GridPtrList& grids, const GridType& cutter, bool segmentation, const Vec3sList* points, const QuatsList* rotations, bool cutterOverlap) { // We can process all incoming grids with the same cutter instance, // this optimization is enabled by the requirement of having matching // transforms between all incoming grids and the cutter object. if (points && points->size() != 0) { math::Transform::Ptr originalCutterTransform = cutter.transform().copy(); GridType cutterGrid(cutter, ShallowCopy()); const bool hasInstanceRotations = points && rotations && points->size() == rotations->size(); // for each instance point.. for (size_t p = 0, P = points->size(); p < P; ++p) { int percent = int((float(p) / float(P)) * 100.0); if (wasInterrupted(percent)) break; GridType instCutterGrid; instCutterGrid.setTransform(originalCutterTransform->copy()); math::Transform::Ptr xform = originalCutterTransform->copy(); if (hasInstanceRotations) { const Vec3s& rot = (*rotations)[p].eulerAngles(math::XYZ_ROTATION); xform->preRotate(rot[0], math::X_AXIS); xform->preRotate(rot[1], math::Y_AXIS); xform->preRotate(rot[2], math::Z_AXIS); xform->postTranslate((*points)[p]); } else { xform->postTranslate((*points)[p]); } cutterGrid.setTransform(xform); if (wasInterrupted()) break; // Since there is no scaling, use the generic resampler instead of // the more expensive level set rebuild tool. if (mInterrupter != NULL) { doResampleToMatch(cutterGrid, instCutterGrid, *mInterrupter); } else { util::NullInterrupter interrupter; doResampleToMatch(cutterGrid, instCutterGrid, interrupter); } if (cutterOverlap && !mFragments.empty()) process(mFragments, instCutterGrid); process(grids, instCutterGrid); } } else { // use cutter in place if (cutterOverlap && !mFragments.empty()) process(mFragments, cutter); process(grids, cutter); } if (segmentation) { segmentFragments(mFragments); segmentFragments(grids); } } template bool LevelSetFracture::isValidFragment(GridType& grid) const { typedef typename GridType::TreeType TreeType; if (grid.activeVoxelCount() < 27) return false; // Check if valid level-set { tree::LeafManager leafs(grid.tree()); MinMaxVoxel minmax(leafs); minmax.runParallel(); if ((minmax.minVoxel() < 0) == (minmax.maxVoxel() < 0)) return false; } return true; } template void LevelSetFracture::segmentFragments(GridPtrList& grids) const { GridPtrList newFragments; for (GridPtrListIter it = grids.begin(); it != grids.end(); ++it) { if (wasInterrupted()) break; std::vector segments = internal::segment(*(*it), mInterrupter); for (size_t n = 0, N = segments.size(); n < N; ++n) { if (wasInterrupted()) break; if (isValidFragment(*segments[n])) { newFragments.push_back(segments[n]); } } } grids.swap(newFragments); } template void LevelSetFracture::process( GridPtrList& grids, const GridType& cutter) { typedef typename GridType::Ptr GridPtr; GridPtrList newFragments; for (GridPtrListIter it = grids.begin(); it != grids.end(); ++it) { if (wasInterrupted()) break; GridPtr grid = *it; // gen new fragment GridPtr fragment = grid->deepCopy(); csgIntersection(*fragment, *cutter.deepCopy()); if (wasInterrupted()) break; if (!isValidFragment(*fragment)) continue; // update residual GridPtr residual = grid->deepCopy(); csgDifference(*residual, *cutter.deepCopy()); if (wasInterrupted()) break; if (!isValidFragment(*residual)) continue; newFragments.push_back(fragment); grid->tree().clear(); grid->tree().merge(residual->tree()); } if (!newFragments.empty()) { mFragments.splice(mFragments.end(), newFragments); } } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVELSETFRACTURE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/LevelSetSphere.h0000644000000000000000000002213112252453157015421 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// /// @file LevelSetSphere.h /// /// @brief Generate a narrow-band level set of sphere. /// /// @note By definition a level set has a fixed narrow band width /// (the half width is defined by LEVEL_SET_HALF_WIDTH in Types.h), /// whereas an SDF can have a variable narrow band width. #ifndef OPENVDB_TOOLS_LEVELSETSPHERE_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_LEVELSETSPHERE_HAS_BEEN_INCLUDED #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Return a grid of type @c GridType containing a narrow-band level set /// representation of a sphere. /// /// @param radius radius of the sphere in world units /// @param center center of the sphere in world units /// @param voxelSize voxel size in world units /// @param halfWidth half the width of the narrow band, in voxel units /// @param interrupt a pointer adhering to the util::NullInterrupter interface /// /// @note @c GridType::ValueType must be a floating-point scalar. /// @note The leapfrog algorithm employed in this method is best suited /// for a single large sphere. For multiple small spheres consider /// using the faster algorithm in ParticlesToLevelSet.h template typename GridType::Ptr createLevelSetSphere(float radius, const openvdb::Vec3f& center, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupt = NULL); /// @brief Return a grid of type @c GridType containing a narrow-band level set /// representation of a sphere. /// /// @param radius radius of the sphere in world units /// @param center center of the sphere in world units /// @param voxelSize voxel size in world units /// @param halfWidth half the width of the narrow band, in voxel units /// /// @note @c GridType::ValueType must be a floating-point scalar. /// @note The leapfrog algorithm employed in this method is best suited /// for a single large sphere. For multiple small spheres consider /// using the faster algorithm in ParticlesToLevelSet.h template typename GridType::Ptr createLevelSetSphere(float radius, const openvdb::Vec3f& center, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH)) { return createLevelSetSphere(radius,center,voxelSize,halfWidth); } //////////////////////////////////////// /// @brief Generates a signed distance field (or narrow band level /// set) to a single sphere. /// /// @note The leapfrog algorithm employed in this class is best /// suited for a single large sphere. For multiple small spheres consider /// using the faster algorithm in tools/ParticlesToLevelSet.h template class LevelSetSphere { public: typedef typename GridT::ValueType ValueT; typedef typename math::Vec3 Vec3T; BOOST_STATIC_ASSERT(boost::is_floating_point::value); /// @brief Constructor /// /// @param radius radius of the sphere in world units /// @param center center of the sphere in world units /// @param interrupt pointer to optional interrupter. Use template /// argument util::NullInterrupter if no interruption is desired. /// /// @note If the radius of the sphere is smaller then /// 1.5*voxelSize, i.e. the sphere is smaller then the Nyquist /// frequency of the grid, it is ignored! LevelSetSphere(ValueT radius, const Vec3T ¢er, InterruptT* interrupt = NULL) : mRadius(radius), mCenter(center), mInterrupt(interrupt) { if (mRadius<=0) OPENVDB_THROW(ValueError, "radius must be positive"); } /// @return a narrow-band level set of the sphere /// /// @param voxelSize Size of voxels in world units /// @param halfWidth Half-width of narrow-band in voxel units typename GridT::Ptr getLevelSet(ValueT voxelSize, ValueT halfWidth) { mGrid = createLevelSet(voxelSize, halfWidth); this->rasterSphere(voxelSize, halfWidth); mGrid->setGridClass(GRID_LEVEL_SET); return mGrid; } private: void rasterSphere(ValueT dx, ValueT w) { if (!(dx>0.0f)) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (!(w>1)) OPENVDB_THROW(ValueError, "half-width must be larger than one"); // Define radius of sphere and narrow-band in voxel units const ValueT r0 = mRadius/dx, rmax = r0 + w; // Radius below the Nyquist frequency if (r0 < 1.5f) return; // Define center of sphere in voxel units const Vec3T c(mCenter[0]/dx, mCenter[1]/dx, mCenter[2]/dx); // Define index coordinates and their respective bounds openvdb::Coord ijk; int &i = ijk[0], &j = ijk[1], &k = ijk[2], m=1; const int imin=math::Floor(c[0]-rmax), imax=math::Ceil(c[0]+rmax); const int jmin=math::Floor(c[1]-rmax), jmax=math::Ceil(c[1]+rmax); const int kmin=math::Floor(c[2]-rmax), kmax=math::Ceil(c[2]+rmax); // Allocate an ValueAccessor for accelerated random access typename GridT::Accessor accessor = mGrid->getAccessor(); if (mInterrupt) mInterrupt->start("Generating level set of sphere"); // Compute signed distances to sphere using leapfrogging in k for ( i = imin; i <= imax; ++i ) { if (util::wasInterrupted(mInterrupt)) return; const float x2 = math::Pow2(i - c[0]); for ( j = jmin; j <= jmax; ++j ) { const float x2y2 = math::Pow2(j - c[1]) + x2; for (k=kmin; k<=kmax; k += m) { m = 1; /// Distance in voxel units to sphere const float v = math::Sqrt(x2y2 + math::Pow2(k-c[2]))-r0, d = math::Abs(v); if ( d < w ){ // inside narrow band accessor.setValue(ijk, dx*v);// distance in world units } else {// outside narrow band m += math::Floor(d-w);// leapfrog } }//end leapfrog over k }//end loop over j }//end loop over i // Define consistant signed distances outside the narrow-band mGrid->signedFloodFill(); if (mInterrupt) mInterrupt->end(); } const ValueT mRadius; const Vec3T mCenter; InterruptT* mInterrupt; typename GridT::Ptr mGrid; };// LevelSetSphere //////////////////////////////////////// template typename GridType::Ptr createLevelSetSphere(float radius, const openvdb::Vec3f& center, float voxelSize, float halfWidth, InterruptT* interrupt) { // GridType::ValueType is required to be a floating-point scalar. BOOST_STATIC_ASSERT(boost::is_floating_point::value); typedef typename GridType::ValueType ValueT; LevelSetSphere factory(ValueT(radius), center, interrupt); return factory.getLevelSet(ValueT(voxelSize), ValueT(halfWidth)); } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_LEVELSETSPHERE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/RayTracer.h0000644000000000000000000006645312252453157014442 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// /// @file RayTracer.h /// /// @author Ken Museth /// /// @brief Defines a simple, multithreaded level-set ray tracer, /// perspective and orthographic cameras (both designed to mimic a Houdini camera), /// a Film class and some rather naive shaders /// /// @note These classes are included mainly to illustrate how to ray-trace /// OpenVDB volumes. They are not intended for production-quality rendering. /// /// @todo Add a volume renderer #ifndef OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #ifdef OPENVDB_TOOLS_RAYTRACER_USE_EXR #include #include #include #include #include #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { // Forward declarations class BaseCamera; class BaseShader; /// @brief Ray-trace a volume. template inline void rayTrace(const GridT&, const BaseShader&, BaseCamera&, size_t pixelSamples = 1, unsigned int seed = 0, bool threaded = true); /// @brief Ray-trace a volume using a given ray intersector. template inline void rayTrace(const GridT&, const IntersectorT&, const BaseShader&, BaseCamera&, size_t pixelSamples = 1, unsigned int seed = 0, bool threaded = true); //////////////////////////////////////// RAYTRACER //////////////////////////////////////// /// @brief A (very) simple multithreaded ray tracer specifically for narrow-band level sets. /// @details Included primarily as a reference implementation. template > class LevelSetRayTracer { public: typedef GridT GridType; typedef typename IntersectorT::Vec3Type Vec3Type; typedef typename IntersectorT::RayType RayType; /// @brief Constructor based on an instance of the grid to be rendered. LevelSetRayTracer(const GridT& grid, const BaseShader& shader, BaseCamera& camera, size_t pixelSamples = 1, unsigned int seed = 0); /// @brief Constructor based on an instance of the intersector /// performing the ray-intersections. LevelSetRayTracer(const IntersectorT& inter, const BaseShader& shader, BaseCamera& camera, size_t pixelSamples = 1, unsigned int seed = 0); /// @brief Copy constructor LevelSetRayTracer(const LevelSetRayTracer& other); /// @brief Destructor ~LevelSetRayTracer(); /// @brief Set the level set grid to be ray-traced void setGrid(const GridT& grid); /// @brief Set the intersector that performs the actual /// intersection of the rays against the narrow-band level set. void setIntersector(const IntersectorT& inter); /// @brief Set the shader derived from the abstract BaseShader class. /// /// @note The shader is not assumed to be thread-safe so each /// thread will get it's only deep copy. For instance it could /// contains a ValueAccessor into another grid with auxiliary /// shading information. Thus, make sure it is relatively /// light-weight and efficient to copy (which is the case for ValueAccesors). void setShader(const BaseShader& shader); /// @brief Set the camera derived from the abstract BaseCamera class. void setCamera(BaseCamera& camera); /// @brief Set the number of pixel samples and the seed for /// jittered sub-rays. A value larger then one implies /// anti-aliasing by jittered super-sampling. /// @throw ValueError if pixelSamples is equal to zero. void setPixelSamples(size_t pixelSamples, unsigned int seed = 0); /// @brief Perform the actual (potentially multithreaded) ray-tracing. void render(bool threaded = true); OPENVDB_DEPRECATED void trace(bool threaded = true) { this->render(threaded); } /// @brief Public method required by tbb::parallel_for. /// @warning Never call it directly. void operator()(const tbb::blocked_range& range) const; private: const bool mIsMaster; double* mRand; IntersectorT mInter; boost::scoped_ptr mShader; BaseCamera* mCamera; size_t mSubPixels; };// LevelSetRayTracer //////////////////////////////////////// FILM //////////////////////////////////////// /// @brief A simple class that allows for concurrent writes to pixels in an image, /// background initialization of the image, and PPM or EXR file output. class Film { public: /// @brief Floating-point RGBA components in the range [0, 1]. /// @details This is our preferred representation for color processing. struct RGBA { typedef float ValueT; RGBA() : r(0), g(0), b(0), a(1) {} explicit RGBA(ValueT intensity) : r(intensity), g(intensity), b(intensity), a(1) {} RGBA(ValueT _r, ValueT _g, ValueT _b, ValueT _a=1.0f) : r(_r), g(_g), b(_b), a(_a) {} RGBA operator* (ValueT scale) const { return RGBA(r*scale, g*scale, b*scale);} RGBA operator+ (const RGBA& rhs) const { return RGBA(r+rhs.r, g+rhs.g, b+rhs.b);} RGBA operator* (const RGBA& rhs) const { return RGBA(r*rhs.r, g*rhs.g, b*rhs.b);} RGBA& operator+=(const RGBA& rhs) { r+=rhs.r; g+=rhs.g; b+=rhs.b, a+=rhs.a; return *this;} void over(const RGBA& rhs) { const float s = rhs.a*(1.0f-a); r = a*r+s*rhs.r; g = a*g+s*rhs.g; b = a*b+s*rhs.b; a = a + s; } ValueT r, g, b, a; }; Film(size_t width, size_t height) : mWidth(width), mHeight(height), mSize(width*height), mPixels(new RGBA[mSize]) { } Film(size_t width, size_t height, const RGBA& bg) : mWidth(width), mHeight(height), mSize(width*height), mPixels(new RGBA[mSize]) { this->fill(bg); } ~Film() { delete mPixels; } const RGBA& pixel(size_t w, size_t h) const { assert(w < mWidth); assert(h < mHeight); return mPixels[w + h*mWidth]; } RGBA& pixel(size_t w, size_t h) { assert(w < mWidth); assert(h < mHeight); return mPixels[w + h*mWidth]; } void fill(const RGBA& rgb=RGBA(0)) { for (size_t i=0; i(255.0f*(*p ).r); *q++ = static_cast(255.0f*(*p ).g); *q++ = static_cast(255.0f*(*p++).b); } std::ofstream os(name.c_str(), std::ios_base::binary); if (!os.is_open()) { std::cerr << "Error opening PPM file \"" << name << "\"" << std::endl; return; } os << "P6\n" << mWidth << " " << mHeight << "\n255\n"; os.write((const char *)&(*tmp), 3*mSize*sizeof(unsigned char)); delete [] tmp; } #ifdef OPENVDB_TOOLS_RAYTRACER_USE_EXR void saveEXR(const std::string& fileName, size_t compression = 2, size_t threads = 8) { std::string name(fileName + ".exr"); if (threads>0) Imf::setGlobalThreadCount(threads); Imf::Header header(mWidth, mHeight); if (compression==0) header.compression() = Imf::NO_COMPRESSION; if (compression==1) header.compression() = Imf::RLE_COMPRESSION; if (compression>=2) header.compression() = Imf::ZIP_COMPRESSION; header.channels().insert("R", Imf::Channel(Imf::FLOAT)); header.channels().insert("G", Imf::Channel(Imf::FLOAT)); header.channels().insert("B", Imf::Channel(Imf::FLOAT)); header.channels().insert("A", Imf::Channel(Imf::FLOAT)); Imf::FrameBuffer framebuffer; framebuffer.insert("R", Imf::Slice( Imf::FLOAT, (char *) &(mPixels[0].r), sizeof (RGBA), sizeof (RGBA) * mWidth)); framebuffer.insert("G", Imf::Slice( Imf::FLOAT, (char *) &(mPixels[0].g), sizeof (RGBA), sizeof (RGBA) * mWidth)); framebuffer.insert("B", Imf::Slice( Imf::FLOAT, (char *) &(mPixels[0].b), sizeof (RGBA), sizeof (RGBA) * mWidth)); framebuffer.insert("A", Imf::Slice( Imf::FLOAT, (char *) &(mPixels[0].a), sizeof (RGBA), sizeof (RGBA) * mWidth)); Imf::OutputFile file(name.c_str(), header); file.setFrameBuffer(framebuffer); file.writePixels(mHeight); } #endif size_t width() const { return mWidth; } size_t height() const { return mHeight; } size_t numPixels() const { return mSize; } const RGBA* pixels() const { return mPixels; } private: size_t mWidth, mHeight, mSize; RGBA* mPixels; };// Film //////////////////////////////////////// CAMERAS //////////////////////////////////////// /// Abstract base class for the perspective and orthographic cameras class BaseCamera { public: BaseCamera(Film& film, const Vec3R& rotation, const Vec3R& translation, double frameWidth, double nearPlane, double farPlane) : mFilm(&film) , mScaleWidth(frameWidth) , mScaleHeight(frameWidth*film.height()/double(film.width())) { assert(nearPlane > 0 && farPlane > nearPlane); mScreenToWorld.accumPostRotation(math::X_AXIS, rotation[0] * M_PI / 180.0); mScreenToWorld.accumPostRotation(math::Y_AXIS, rotation[1] * M_PI / 180.0); mScreenToWorld.accumPostRotation(math::Z_AXIS, rotation[2] * M_PI / 180.0); mScreenToWorld.accumPostTranslation(translation); this->initRay(nearPlane, farPlane); } virtual ~BaseCamera() {} Film::RGBA& pixel(size_t i, size_t j) { return mFilm->pixel(i, j); } size_t width() const { return mFilm->width(); } size_t height() const { return mFilm->height(); } /// Rotate the camera so its negative z-axis points at xyz and its /// y axis is in the plane of the xyz and up vectors. In other /// words the camera will look at xyz and use up as the /// horizontal direction. void lookAt(const Vec3R& xyz, const Vec3R& up = Vec3R(0.0, 1.0, 0.0)) { const Vec3R orig = mScreenToWorld.applyMap(Vec3R(0.0)); const Vec3R dir = orig - xyz; try { Mat4d xform = math::aim(dir, up); xform.postTranslate(orig); mScreenToWorld = math::AffineMap(xform); this->initRay(mRay.t0(), mRay.t1()); } catch (...) {} } Vec3R rasterToScreen(double i, double j, double z) const { return Vec3R( (2 * i / mFilm->width() - 1) * mScaleWidth, (1 - 2 * j / mFilm->height()) * mScaleHeight, z ); } /// @brief Return a Ray in world space given the pixel indices and /// optional offsets in the range [0, 1]. An offset of 0.5 corresponds /// to the center of the pixel. virtual math::Ray getRay( size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const = 0; protected: void initRay(double t0, double t1) { mRay.setTimes(t0, t1); mRay.setEye(mScreenToWorld.applyMap(Vec3R(0.0))); mRay.setDir(mScreenToWorld.applyJacobian(Vec3R(0.0, 0.0, -1.0))); } Film* mFilm; double mScaleWidth, mScaleHeight; math::Ray mRay; math::AffineMap mScreenToWorld; };// BaseCamera class PerspectiveCamera: public BaseCamera { public: /// @brief Constructor /// @param film film (i.e. image) defining the pixel resolution /// @param rotation rotation in degrees of the camera in world space /// (applied in x, y, z order) /// @param translation translation of the camera in world-space units, /// applied after rotation /// @param focalLength focal length of the camera in mm /// (the default of 50mm corresponds to Houdini's default camera) /// @param aperture width in mm of the frame, i.e., the visible field /// (the default 41.2136 mm corresponds to Houdini's default camera) /// @param nearPlane depth of the near clipping plane in world-space units /// @param farPlane depth of the far clipping plane in world-space units /// /// @details If no rotation or translation is provided, the camera is placed /// at (0,0,0) in world space and points in the direction of the negative z axis. PerspectiveCamera(Film& film, const Vec3R& rotation = Vec3R(0.0), const Vec3R& translation = Vec3R(0.0), double focalLength = 50.0, double aperture = 41.2136, double nearPlane = 1e-3, double farPlane = std::numeric_limits::max()) : BaseCamera(film, rotation, translation, 0.5*aperture/focalLength, nearPlane, farPlane) { } virtual ~PerspectiveCamera() {} /// @brief Return a Ray in world space given the pixel indices and /// optional offsets in the range [0,1]. An offset of 0.5 corresponds /// to the center of the pixel. virtual math::Ray getRay( size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const { math::Ray ray(mRay); Vec3R dir = BaseCamera::rasterToScreen(i + iOffset, j + jOffset, -1.0); dir = BaseCamera::mScreenToWorld.applyJacobian(dir); dir.normalize(); ray.scaleTimes(1.0/dir.dot(ray.dir())); ray.setDir(dir); return ray; } /// @brief Return the horizontal field of view in degrees given a /// focal lenth in mm and the specified aperture in mm. static double focalLengthToFieldOfView(double length, double aperture) { return 360.0 / M_PI * atan(aperture/(2.0*length)); } /// @brief Return the focal length in mm given a horizontal field of /// view in degrees and the specified aperture in mm. static double fieldOfViewToFocalLength(double fov, double aperture) { return aperture/(2.0*(tan(fov * M_PI / 360.0))); } };// PerspectiveCamera class OrthographicCamera: public BaseCamera { public: /// @brief Constructor /// @param film film (i.e. image) defining the pixel resolution /// @param rotation rotation in degrees of the camera in world space /// (applied in x, y, z order) /// @param translation translation of the camera in world-space units, /// applied after rotation /// @param frameWidth width in of the frame in world-space units /// @param nearPlane depth of the near clipping plane in world-space units /// @param farPlane depth of the far clipping plane in world-space units /// /// @details If no rotation or translation is provided, the camera is placed /// at (0,0,0) in world space and points in the direction of the negative z axis. OrthographicCamera(Film& film, const Vec3R& rotation = Vec3R(0.0), const Vec3R& translation = Vec3R(0.0), double frameWidth = 1.0, double nearPlane = 1e-3, double farPlane = std::numeric_limits::max()) : BaseCamera(film, rotation, translation, 0.5*frameWidth, nearPlane, farPlane) { } virtual ~OrthographicCamera() {} virtual math::Ray getRay( size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const { math::Ray ray(mRay); Vec3R eye = BaseCamera::rasterToScreen(i + iOffset, j + jOffset, 0.0); ray.setEye(BaseCamera::mScreenToWorld.applyMap(eye)); return ray; } };// OrthographicCamera //////////////////////////////////////// SHADERS //////////////////////////////////////// /// Abstract base class for the shaders class BaseShader { public: typedef math::Ray RayT; BaseShader() {} virtual ~BaseShader() {} /// @brief Defines the interface of the virtual function that returns a RGB color. /// @param xyz World position of the intersection point. /// @param nml Normal in world space at the intersection point. /// @param dir Direction of the ray in world space. virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R& nml, const Vec3R& dir) const = 0; /// @brief Deprecated, use the method above instead. OPENVDB_DEPRECATED Film::RGBA operator()(const Vec3R& xyz, const Vec3R& nml, const RayT& ray) const { return (*this)(xyz, nml, ray.dir()); } virtual BaseShader* copy() const = 0; }; /// Shader that produces a simple matte class MatteShader: public BaseShader { public: MatteShader(const Film::RGBA& c = Film::RGBA(1.0f)): mRGBA(c) {} virtual ~MatteShader() {} virtual Film::RGBA operator()(const Vec3R&, const Vec3R&, const Vec3R&) const { return mRGBA; } virtual BaseShader* copy() const { return new MatteShader(*this); } private: const Film::RGBA mRGBA; }; /// Color shader that treats the surface normal (x, y, z) as an RGB color class NormalShader: public BaseShader { public: NormalShader(const Film::RGBA& c = Film::RGBA(1.0f)) : mRGBA(c*0.5f) {} virtual ~NormalShader() {} virtual Film::RGBA operator()(const Vec3R&, const Vec3R& normal, const Vec3R&) const { return mRGBA*Film::RGBA(normal[0]+1.0f, normal[1]+1.0f, normal[2]+1.0f); } virtual BaseShader* copy() const { return new NormalShader(*this); } private: const Film::RGBA mRGBA; }; /// Color shader that treats position (x, y, z) as an RGB color in a /// cube defined from an axis-aligned bounding box in world space. class PositionShader: public BaseShader { public: PositionShader(const math::BBox& bbox, const Film::RGBA& c = Film::RGBA(1.0f)) : mMin(bbox.min()), mInvDim(1.0/bbox.extents()), mRGBA(c) {} virtual ~PositionShader() {} virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R&, const Vec3R&) const { const Vec3R rgb = (xyz - mMin)*mInvDim; return mRGBA*Film::RGBA(rgb[0], rgb[1], rgb[2]); } virtual BaseShader* copy() const { return new PositionShader(*this); } private: const Vec3R mMin, mInvDim; const Film::RGBA mRGBA; }; /// @brief Simple diffuse Lambertian surface shader /// @details Diffuse simply means the color is constant (e.g., white), and /// Lambertian implies that the (radiant) intensity is directly proportional /// to the cosine of the angle between the surface normal and the direction /// of the light source. class DiffuseShader: public BaseShader { public: DiffuseShader(const Film::RGBA& d = Film::RGBA(1.0f)): mRGBA(d) {} virtual Film::RGBA operator()(const Vec3R&, const Vec3R& normal, const Vec3R& rayDir) const { // We assume a single directional light source at the camera, // so the cosine of the angle between the surface normal and the // direction of the light source becomes the dot product of the // surface normal and inverse direction of the ray. We also ignore // negative dot products, corresponding to strict one-sided shading. //return mRGBA * math::Max(0.0, normal.dot(-rayDir)); // We take the abs of the dot product corresponding to having // light sources at +/- rayDir, i.e., two-sided shading. return mRGBA * math::Abs(normal.dot(rayDir)); } virtual BaseShader* copy() const { return new DiffuseShader(*this); } private: const Film::RGBA mRGBA; }; //////////////////////////////////////// RAYTRACER //////////////////////////////////////// template inline void rayTrace(const GridT& grid, const BaseShader& shader, BaseCamera& camera, size_t pixelSamples, unsigned int seed, bool threaded) { LevelSetRayTracer > tracer(grid, shader, camera, pixelSamples, seed); tracer.render(threaded); } template inline void rayTrace(const GridT&, const IntersectorT& inter, const BaseShader& shader, BaseCamera& camera, size_t pixelSamples, unsigned int seed, bool threaded) { LevelSetRayTracer tracer(inter, shader, camera, pixelSamples, seed); tracer.render(threaded); } //////////////////////////////////////// template inline LevelSetRayTracer:: LevelSetRayTracer(const GridT& grid, const BaseShader& shader, BaseCamera& camera, size_t pixelSamples, unsigned int seed) : mIsMaster(true), mRand(NULL), mInter(grid), mShader(shader.copy()), mCamera(&camera) { this->setPixelSamples(pixelSamples, seed); } template inline LevelSetRayTracer:: LevelSetRayTracer(const IntersectorT& inter, const BaseShader& shader, BaseCamera& camera, size_t pixelSamples, unsigned int seed) : mIsMaster(true), mRand(NULL), mInter(inter), mShader(shader.copy()), mCamera(&camera) { this->setPixelSamples(pixelSamples, seed); } template inline LevelSetRayTracer:: LevelSetRayTracer(const LevelSetRayTracer& other) : mIsMaster(false), mRand(other.mRand), mInter(other.mInter), mShader(other.mShader->copy()), mCamera(other.mCamera), mSubPixels(other.mSubPixels) { } template inline LevelSetRayTracer:: ~LevelSetRayTracer() { if (mIsMaster) delete [] mRand; } template inline void LevelSetRayTracer:: setGrid(const GridT& grid) { assert(mIsMaster); mInter = IntersectorT(grid); } template inline void LevelSetRayTracer:: setIntersector(const IntersectorT& inter) { assert(mIsMaster); mInter = inter; } template inline void LevelSetRayTracer:: setShader(const BaseShader& shader) { assert(mIsMaster); mShader.reset(shader.copy()); } template inline void LevelSetRayTracer:: setCamera(BaseCamera& camera) { assert(mIsMaster); mCamera = &camera; } template inline void LevelSetRayTracer:: setPixelSamples(size_t pixelSamples, unsigned int seed) { assert(mIsMaster); if (pixelSamples == 0) { OPENVDB_THROW(ValueError, "pixelSamples must be larger then zero!"); } mSubPixels = pixelSamples - 1; delete [] mRand; if (mSubPixels > 0) { mRand = new double[16]; math::Rand01 rand(seed);//offsets for anti-aliaing by jittered super-sampling for (size_t i=0; i<16; ++i) mRand[i] = rand(); } else { mRand = NULL; } } template inline void LevelSetRayTracer:: render(bool threaded) { tbb::blocked_range range(0, mCamera->height()); threaded ? tbb::parallel_for(range, *this) : (*this)(range); } template inline void LevelSetRayTracer:: operator()(const tbb::blocked_range& range) const { const BaseShader& shader = *mShader; Vec3Type xyz, nml; const float frac = 1.0f / (1.0f + mSubPixels); for (size_t j=range.begin(), n=0, je = range.end(); jwidth(); ipixel(i,j); RayType ray = mCamera->getRay(i, j);//primary ray Film::RGBA c = mInter.intersectsWS(ray, xyz, nml) ? shader(xyz, nml, ray.dir()) : bg; for (size_t k=0; kgetRay(i, j, mRand[n & 15], mRand[(n+1) & 15]); c += mInter.intersectsWS(ray, xyz, nml) ? shader(xyz, nml, ray.dir()) : bg; }//loop over sub-pixels bg = c*frac; }//loop over image height }//loop over image width } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/PointAdvect.h0000644000000000000000000004704212252453157014757 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth, D.J. Hill (openvdb port, added staggered grid support) /// @file PointAdvect.h /// /// @brief Class PointAdvect advects points (with position) in a static velocity field #ifndef OPENVDB_TOOLS_POINT_ADVECT_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_POINT_ADVECT_HAS_BEEN_INCLUDED #include #include // min #include // Vec3 types and version number #include // grid #include #include "Interpolation.h" // sampling #include #include // threading #include // threading #include // for cancel namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { /// Class that holds a Vec3 grid, to be interpreted as the closest point to a constraint /// surface. Supports a method to allow a point to be projected onto the closest point /// on the constraint surface. Uses Caching. template class ClosestPointProjector { public: typedef CptGridT CptGridType; typedef typename CptGridType::ConstAccessor CptAccessor; typedef typename CptGridType::ValueType CptValueType; ClosestPointProjector(): mCptIterations(0) { } ClosestPointProjector(const CptGridType& cptGrid, int n): mCptGrid(&cptGrid), mCptAccessor(cptGrid.getAccessor()), mCptIterations(n) { } ClosestPointProjector(const ClosestPointProjector &other): mCptGrid(other.mCptGrid), mCptAccessor(mCptGrid->getAccessor()), mCptIterations(other.mCptIterations) { } void setConstraintIterations(unsigned int cptIterations) { mCptIterations = cptIterations; } unsigned int numIterations() { return mCptIterations;}; // point constraint template inline void projectToConstraintSurface(LocationType& W) const { /// Entries in the CPT tree are the closest point to the constraint surface. /// The interpolation step in sample introduces error so that the result /// of a single sample may not lie exactly on the surface. The iterations /// in the loop exist to minimize this error. CptValueType result(W[0], W[1],W[2]); for (unsigned int i = 0; i < mCptIterations; ++i) { const Vec3R location = mCptGrid->worldToIndex(Vec3R(result[0], result[1], result[2])); BoxSampler::sample(mCptAccessor, location, result); } W[0] = result[0]; W[1] = result[1]; W[2] = result[2]; } private: const CptGridType* mCptGrid; // Closest-Point-Transform vector field CptAccessor mCptAccessor; unsigned int mCptIterations; };// end of ClosestPointProjector class /// Class to hold a Vec3 field interperated as a velocity field. /// Primarily exists to provide a method(s) that integrate a passive /// point forward in the velocity field for a single time-step (dt) template class VelocitySampler { public: typedef typename GridT::ConstAccessor VelAccessor; typedef typename GridT::ValueType VelValueType; VelocitySampler(const GridT& velGrid): mVelGrid(&velGrid), mVelAccessor(mVelGrid->getAccessor()) { } VelocitySampler(const VelocitySampler& other): mVelGrid(other.mVelGrid), mVelAccessor(mVelGrid->getAccessor()) { } ~VelocitySampler() { } /// Samples the velocity at position W onto result. Supports both /// staggered (i.e. MAC) and collocated velocity grids. template inline void sample(const LocationType& W, VelValueType& result) const { const Vec3R location = mVelGrid->worldToIndex(Vec3R(W[0], W[1], W[2])); /// Note this if-branch is optimized away at compile time if (StaggeredVelocity) { // the velocity Grid stores data in MAC-style staggered layout StaggeredBoxSampler::sample(mVelAccessor, location, result); } else { // the velocity Grid uses collocated data BoxSampler::sample(mVelAccessor, location, result); } } private: // holding the Grids for the transforms const GridT* mVelGrid; // Velocity vector field VelAccessor mVelAccessor; };// end of VelocitySampler class /// @brief Performs runge-kutta time integration of variable order in /// a static velocity field template class VelocityIntegrator { public: typedef typename GridT::ValueType VecType; typedef typename VecType::ValueType ElementType; VelocityIntegrator(const GridT& velGrid): mVelField(velGrid) { } // variable order Runge-Kutta time integration for a single time step template void rungeKutta(const float dt, LocationType& loc) { VecType P(loc[0],loc[1],loc[2]), V0, V1, V2, V3; BOOST_STATIC_ASSERT((Order < 5) && (Order > -1)); /// Note the if-braching below is optimized away at compile time if (Order == 0) { // do nothing return ; } else if (Order == 1) { mVelField.sample(P, V0); P = dt*V0; } else if (Order == 2) { mVelField.sample(P, V0); mVelField.sample(P + ElementType(0.5) * ElementType(dt) * V0, V1); P = dt*V1; } else if (Order == 3) { mVelField.sample(P, V0); mVelField.sample(P+ElementType(0.5)*ElementType(dt)*V0, V1); mVelField.sample(P+dt*(ElementType(2.0)*V1-V0), V2); P = dt*(V0 + ElementType(4.0)*V1 + V2)*ElementType(1.0/6.0); } else if (Order == 4) { mVelField.sample(P, V0); mVelField.sample(P+ElementType(0.5)*ElementType(dt)*V0, V1); mVelField.sample(P+ElementType(0.5)*ElementType(dt)*V1, V2); mVelField.sample(P+ dt*V2, V3); P = dt*(V0 + ElementType(2.0)*(V1 + V2) + V3)*ElementType(1.0/6.0); } loc += LocationType(P[0], P[1], P[2]); } private: VelocitySampler mVelField; };// end of VelocityIntegrator class //////////////////////////////////////// /// Performs passive or constrained advection of points in a velocity field /// represented by an OpenVDB grid and an optional closest-point-transform (CPT) /// represented in another OpenVDB grid. Note the CPT is assumed to be /// in world coordinates and NOT index coordinates! /// Supports both collocated velocity grids and staggered velocity grids /// /// The @c PointListT template argument refers to any class with the following /// interface (e.g., std::vector): /// @code /// class PointList { /// ... /// public: /// typedef internal_vector3_type value_type; // must support [] component access /// openvdb::Index size() const; // number of points in list /// value_type& operator[](int n); // world space position of nth point /// }; /// @endcode /// /// @note All methods (except size) are assumed to be thread-safe and /// the positions are returned as non-const references since the /// advection method needs to modify them! template, bool StaggeredVelocity = false, typename InterrupterType = util::NullInterrupter> class PointAdvect { public: typedef GridT GridType; typedef PointListT PointListType; typedef typename PointListT::value_type LocationType; typedef VelocityIntegrator VelocityFieldIntegrator; PointAdvect(const GridT& velGrid, InterrupterType* interrupter=NULL) : mVelGrid(&velGrid), mPoints(NULL), mIntegrationOrder(1), mThreaded(true), mInterrupter(interrupter) { } PointAdvect(const PointAdvect &other) : mVelGrid(other.mVelGrid), mPoints(other.mPoints), mDt(other.mDt), mAdvIterations(other.mAdvIterations), mIntegrationOrder(other.mIntegrationOrder), mThreaded(other.mThreaded), mInterrupter(other.mInterrupter) { } virtual ~PointAdvect() { } /// If the order of the integration is set to zero no advection is performed bool earlyOut() const { return (mIntegrationOrder==0);} /// get & set void setThreaded(bool threaded) { mThreaded = threaded; } bool getThreaded() { return mThreaded; } void setIntegrationOrder(unsigned int order) {mIntegrationOrder = order;} /// Constrained advection of a list of points over a time = dt * advIterations void advect(PointListT& points, float dt, unsigned int advIterations = 1) { if (this->earlyOut()) return; // nothing to do! mPoints = &points; mDt = dt; mAdvIterations = advIterations; if (mInterrupter) mInterrupter->start("Advecting points by OpenVDB velocity field: "); if (mThreaded) { tbb::parallel_for(tbb::blocked_range(0, mPoints->size()), *this); } else { (*this)(tbb::blocked_range(0, mPoints->size())); } if (mInterrupter) mInterrupter->end(); } /// Never call this method directly - it is use by TBB and has to be public! void operator() (const tbb::blocked_range &range) const { if (mInterrupter && mInterrupter->wasInterrupted()) { tbb::task::self().cancel_group_execution(); } VelocityFieldIntegrator velField(*mVelGrid); switch (mIntegrationOrder) { case 1: { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; // loop over number of time steps for (unsigned int i = 0; i < mAdvIterations; ++i) { velField.template rungeKutta<1>(mDt, X0); } } } break; case 2: { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; // loop over number of time steps for (unsigned int i = 0; i < mAdvIterations; ++i) { velField.template rungeKutta<2>(mDt, X0); } } } break; case 3: { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; // loop over number of time steps for (unsigned int i = 0; i < mAdvIterations; ++i) { velField.template rungeKutta<3>(mDt, X0); } } } break; case 4: { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; // loop over number of time steps for (unsigned int i = 0; i < mAdvIterations; ++i) { velField.template rungeKutta<4>(mDt, X0); } } } break; } } private: // the velocity field const GridType* mVelGrid; // vertex list of all the points PointListT* mPoints; // time integration parameters float mDt; // time step unsigned int mAdvIterations; // number of time steps unsigned int mIntegrationOrder; // operational parameters bool mThreaded; InterrupterType* mInterrupter; };//end of PointAdvect class template, bool StaggeredVelocity = false, typename CptGridType = GridT, typename InterrupterType = util::NullInterrupter> class ConstrainedPointAdvect { public: typedef GridT GridType; typedef typename PointListT::value_type LocationType; typedef VelocityIntegrator VelocityIntegratorType; typedef ClosestPointProjector ClosestPointProjectorType; typedef PointListT PointListType; ConstrainedPointAdvect(const GridType& velGrid, const GridType& cptGrid, int cptn, InterrupterType* interrupter = NULL): mVelGrid(&velGrid), mCptGrid(&cptGrid), mCptIter(cptn), mInterrupter(interrupter) { } ConstrainedPointAdvect(const ConstrainedPointAdvect& other): mVelGrid(other.mVelGrid), mCptGrid(other.mCptGrid), mCptIter(other.mCptIter), mPoints(other.mPoints), mDt(other.mDt), mAdvIterations(other.mAdvIterations), mIntegrationOrder(other.mIntegrationOrder), mThreaded(other.mThreaded), mInterrupter(other.mInterrupter) { } virtual ~ConstrainedPointAdvect(){} void setConstraintIterations(unsigned int cptIter) {mCptIter = cptIter;} void setIntegrationOrder(unsigned int order) {mIntegrationOrder = order;} void setThreaded(bool threaded) { mThreaded = threaded; } bool getThreaded() { return mThreaded; } /// Constrained Advection a list of points over a time = dt * advIterations void advect(PointListT& points, float dt, unsigned int advIterations = 1) { mPoints = &points; mDt = dt; if (mIntegrationOrder==0 && mCptIter == 0) { return; // nothing to do! } (mIntegrationOrder>0) ? mAdvIterations = advIterations : mAdvIterations = 1; if (mInterrupter) mInterrupter->start("Advecting points by OpenVDB velocity field: "); const int N = mPoints->size(); if (mThreaded) { tbb::parallel_for(tbb::blocked_range(0, N), *this); } else { (*this)(tbb::blocked_range(0, N)); } if (mInterrupter) mInterrupter->end(); } /// Never call this method directly - it is use by TBB and has to be public! void operator() (const tbb::blocked_range &range) const { if (mInterrupter && mInterrupter->wasInterrupted()) { tbb::task::self().cancel_group_execution(); } VelocityIntegratorType velField(*mVelGrid); ClosestPointProjectorType cptField(*mCptGrid, mCptIter); switch (mIntegrationOrder) { case 0://pure CPT projection { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; for (unsigned int i = 0; i < mAdvIterations; ++i) { cptField.projectToConstraintSurface(X0); } } } break; case 1://1'th order advection and CPT projection { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; for (unsigned int i = 0; i < mAdvIterations; ++i) { velField.template rungeKutta<1>(mDt, X0); cptField.projectToConstraintSurface(X0); } } } break; case 2://2'nd order advection and CPT projection { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; for (unsigned int i = 0; i < mAdvIterations; ++i) { velField.template rungeKutta<2>(mDt, X0); cptField.projectToConstraintSurface(X0); } } } break; case 3://3'rd order advection and CPT projection { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; for (unsigned int i = 0; i < mAdvIterations; ++i) { velField.template rungeKutta<3>(mDt, X0); cptField.projectToConstraintSurface(X0); } } } break; case 4://4'th order advection and CPT projection { for (size_t n = range.begin(); n != range.end(); ++n) { LocationType& X0 = (*mPoints)[n]; for (unsigned int i = 0; i < mAdvIterations; ++i) { velField.template rungeKutta<4>(mDt, X0); cptField.projectToConstraintSurface(X0); } } } break; } } private: const GridType* mVelGrid; // the velocity field const GridType* mCptGrid; int mCptIter; PointListT* mPoints; // vertex list of all the points // time integration parameters float mDt; // time step unsigned int mAdvIterations; // number of time steps unsigned int mIntegrationOrder; // order of Runge-Kutta integration // operational parameters bool mThreaded; InterrupterType* mInterrupter; };// end of ConstrainedPointAdvect class } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_POINT_ADVECT_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tools/Interpolation.h0000644000000000000000000006213512252453157015366 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Interpolation.h /// /// Sampler classes such as PointSampler and BoxSampler that are intended for use /// with tools::GridTransformer should operate in voxel space and must adhere to /// the interface described in the example below: /// @code /// struct MySampler /// { /// // Return a short name that can be used to identify this sampler /// // in error messages and elsewhere. /// const char* name() { return "mysampler"; } /// /// // Return the radius of the sampling kernel in voxels, not including /// // the center voxel. This is the number of voxels of padding that /// // are added to all sides of a volume as a result of resampling. /// int radius() { return 2; } /// /// // Return true if scaling by a factor smaller than 0.5 (along any axis) /// // should be handled via a mipmapping-like scheme of successive halvings /// // of a grid's resolution, until the remaining scale factor is /// // greater than or equal to 1/2. Set this to false only when high-quality /// // scaling is not required. /// bool mipmap() { return true; } /// /// // Specify if sampling at a location that is collocated with a grid point /// // is guaranteed to return the exact value at that grid point. /// // For most sampling kernels, this should be false. /// bool consistent() { return false; } /// /// // Sample the tree at the given coordinates and return the result in val. /// // Return true if the sampled value is active. /// template /// bool sample(const TreeT& tree, const Vec3R& coord, typename TreeT::ValueType& val); /// }; /// @endcode #ifndef OPENVDB_TOOLS_INTERPOLATION_HAS_BEEN_INCLUDED #define OPENVDB_TOOLS_INTERPOLATION_HAS_BEEN_INCLUDED #include #include #include // for OPENVDB_VERSION_NAME #include // for round() #include // for Transform #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tools { // The following samplers operate in voxel space. // When the samplers are applied to grids holding vector or other non-scalar data, // the data is assumed to be collocated. For example, using the BoxSampler on a grid // with ValueType Vec3f assumes that all three elements in a vector can be assigned // the same physical location. struct PointSampler { static const char* name() { return "point"; } static int radius() { return 0; } static bool mipmap() { return false; } static bool consistent() { return true; } /// @brief Sample @a inTree at the nearest neighbor to @a inCoord /// and store the result in @a result. /// @return @c true if the sampled value is active. template static bool sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result); }; struct BoxSampler { static const char* name() { return "box"; } static int radius() { return 1; } static bool mipmap() { return true; } static bool consistent() { return true; } /// @brief Trilinearly reconstruct @a inTree at @a inCoord /// and store the result in @a result. /// @return @c true if any one of the sampled values is active. template static bool sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result); /// @brief Trilinearly reconstruct @a inTree at @a inCoord. /// @return the reconstructed value template static typename TreeT::ValueType sample(const TreeT& inTree, const Vec3R& inCoord); private: template static inline ValueT trilinearInterpolation(ValueT (& data)[N][N][N], const Vec3R& uvw); }; struct QuadraticSampler { static const char* name() { return "quadratic"; } static int radius() { return 1; } static bool mipmap() { return true; } static bool consistent() { return false; } /// @brief Triquadratically reconstruct @a inTree at @a inCoord /// and store the result in @a result. /// @return @c true if any one of the sampled values is active. template static bool sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result); }; //////////////////////////////////////// // The following samplers operate in voxel space and are designed for Vec3 // staggered grid data (e.g., fluid simulations using the Marker-and-Cell approach // associate elements of the velocity vector with different physical locations: // the faces of a cube). struct StaggeredPointSampler { static const char* name() { return "point"; } static int radius() { return 0; } static bool mipmap() { return false; } static bool consistent() { return false; } /// @brief Sample @a inTree at the nearest neighbor to @a inCoord /// and store the result in @a result. /// @return true if the sampled value is active. template static bool sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result); }; struct StaggeredBoxSampler { static const char* name() { return "box"; } static int radius() { return 1; } static bool mipmap() { return true; } static bool consistent() { return false; } /// @brief Trilinearly reconstruct @a inTree at @a inCoord /// and store the result in @a result. /// @return true if any one of the sampled value is active. template static bool sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result); }; struct StaggeredQuadraticSampler { static const char* name() { return "quadratic"; } static int radius() { return 1; } static bool mipmap() { return true; } static bool consistent() { return false; } /// @brief Triquadratically reconstruct @a inTree at @a inCoord /// and store the result in @a result. /// @return true if any one of the sampled values is active. template static bool sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result); }; //////////////////////////////////////// /// @brief Class that provides the interface for continuous sampling /// of values in a tree. Consider using the GridSampler below instead. /// /// @details Since trees support only discrete voxel sampling, TreeSampler /// must be used to sample arbitrary continuous points in (world or /// index) space. /// /// @warning This implementation of the GridSampler stores a pointer /// to a Tree for value access. While this is thread-safe it is /// uncached and hence slow compared to using a /// ValueAccessor. Consequently is it normally advisable to use the /// template specialization below that employs a /// ValueAccessor. However case must be taken when dealing with /// multi-threading (see warning below). template class GridSampler { public: typedef boost::shared_ptr Ptr; typedef typename GridOrTreeType::ValueType ValueType; typedef typename TreeAdapter::GridType GridType; typedef typename TreeAdapter::TreeType TreeType; typedef typename TreeAdapter::AccessorType AccessorType; /// @param grid a grid to be sampled explicit GridSampler(const GridType& grid) : mTree(&(grid.tree())), mTransform(&(grid.transform())) {} /// @param tree a tree to be sampled, or a ValueAccessor for the tree /// @param transform is used when sampling world space locations. GridSampler(const TreeType& tree, const math::Transform& transform) : mTree(&tree), mTransform(&transform) {} const math::Transform& transform() const { return *mTransform; } /// @brief Sample a point in index space in the grid. /// @param x Fractional x-coordinate of point in index-coordinates of grid /// @param y Fractional y-coordinate of point in index-coordinates of grid /// @param z Fractional z-coordinate of point in index-coordinates of grid template ValueType sampleVoxel(const RealType& x, const RealType& y, const RealType& z) const { return this->isSample(Vec3d(x,y,z)); } /// @brief Sample value in integer index space /// @param i Integer x-coordinate in index space /// @param j Integer y-coordinate in index space /// @param k Integer x-coordinate in index space ValueType sampleVoxel(typename Coord::ValueType i, typename Coord::ValueType j, typename Coord::ValueType k) const { return this->isSample(Coord(i,j,k)); } /// @brief Sample value in integer index space /// @param ijk the location in index space ValueType isSample(const Coord& ijk) const { return mTree->getValue(ijk); } /// @brief Sample in fractional index space /// @param ispoint the location in index space ValueType isSample(const Vec3d& ispoint) const { ValueType result = zeroVal(); SamplerType::sample(*mTree, ispoint, result); return result; } /// @brief Sample in world space /// @param wspoint the location in world space ValueType wsSample(const Vec3d& wspoint) const { ValueType result = zeroVal(); SamplerType::sample(*mTree, mTransform->worldToIndex(wspoint), result); return result; } private: const TreeType* mTree; const math::Transform* mTransform; }; // class GridSampler /// @brief Specialization of GridSampler for construction from a ValueAccessor type /// /// @note This version should normally be favoured over the one above /// that takes a Grid or Tree. The reason is this version uses a /// ValueAccessor that performs fast (cached) access where the /// tree-based flavour performs slower (uncached) access. /// /// @warning Since this version stores a pointer to an (externally /// allocated) value accessor it is not threadsafe. Hence each thread /// should have it own instance of a GridSampler constructed from a /// local ValueAccessor. Alternatively the Grid/Tree-based GridSampler /// is threadsafe, but also slower. template class GridSampler, SamplerType> { public: typedef boost::shared_ptr Ptr; typedef typename TreeT::ValueType ValueType; typedef TreeT TreeType; typedef Grid GridType; typedef typename tree::ValueAccessor AccessorType; /// @param acc a ValueAccessor to be sampled /// @param transform is used when sampling world space locations. GridSampler(const AccessorType& acc, const math::Transform& transform) : mAccessor(&acc), mTransform(&transform) {} const math::Transform& transform() const { return *mTransform; } /// @brief Sample a point in index space in the grid. /// @param x Fractional x-coordinate of point in index-coordinates of grid /// @param y Fractional y-coordinate of point in index-coordinates of grid /// @param z Fractional z-coordinate of point in index-coordinates of grid template ValueType sampleVoxel(const RealType& x, const RealType& y, const RealType& z) const { return this->isSample(Vec3d(x,y,z)); } /// @brief Sample value in integer index space /// @param i Integer x-coordinate in index space /// @param j Integer y-coordinate in index space /// @param k Integer x-coordinate in index space ValueType sampleVoxel(typename Coord::ValueType i, typename Coord::ValueType j, typename Coord::ValueType k) const { return this->isSample(Coord(i,j,k)); } /// @brief Sample value in integer index space /// @param ijk the location in index space ValueType isSample(const Coord& ijk) const { return mAccessor->getValue(ijk); } /// @brief Sample in fractional index space /// @param ispoint the location in index space ValueType isSample(const Vec3d& ispoint) const { ValueType result = zeroVal(); SamplerType::sample(*mAccessor, ispoint, result); return result; } /// @brief Sample in world space /// @param wspoint the location in world space ValueType wsSample(const Vec3d& wspoint) const { ValueType result = zeroVal(); SamplerType::sample(*mAccessor, mTransform->worldToIndex(wspoint), result); return result; } private: const AccessorType* mAccessor;//not thread-safe! const math::Transform* mTransform; };//Specialization of GridSampler //////////////////////////////////////// /// @brief This is a simple convenience class that allows for sampling /// from a source grid into the index space of a target grid. At /// construction the source and target grids are checked for alignment /// which potentially renders interpolation unnecessary. Else /// interpolation is performed according to the templated Sampler type. /// /// @warning For performance reasons the check for alignment of the /// two grids is only performed at construction time! Also note that /// unless the grids are aligned, virtual methods are used to resolve /// the coordinate transformations, which is clearly not optimal. So /// consider resolving the Map types of the two grids for better /// performance. template class DualGridSampler { public: typedef typename SourceGridT::ValueType ValueType; DualGridSampler(const SourceGridT& source, const TargetGridT& target) : mTarget(&target), mSource(&source), mAccessor(source.tree()), mAligned(target.transform() == source.transform()) { } /// @brief Return the value of the source grid at the index /// coordinates, ijk, relative to the target grid. inline ValueType operator()(const Coord& ijk) const { return mAligned ? mAccessor.getValue(ijk) : SamplerT::sample(mAccessor, mSource->worldToIndex(mTarget->indexToWorld(ijk))); } private: const TargetGridT* mTarget; const SourceGridT* mSource; typename SourceGridT::ConstAccessor mAccessor; const bool mAligned; };// DualGridSampler //////////////////////////////////////// namespace local_util { inline Vec3i floorVec3(const Vec3R& v) { return Vec3i(int(std::floor(v(0))), int(std::floor(v(1))), int(std::floor(v(2)))); } inline Vec3i ceilVec3(const Vec3R& v) { return Vec3i(int(std::ceil(v(0))), int(std::ceil(v(1))), int(std::ceil(v(2)))); } inline Vec3i roundVec3(const Vec3R& v) { return Vec3i(int(::round(v(0))), int(::round(v(1))), int(::round(v(2)))); } } // namespace local_util //////////////////////////////////////// template inline bool PointSampler::sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { Vec3i inIdx = local_util::roundVec3(inCoord); return inTree.probeValue(Coord(inIdx), result); } //////////////////////////////////////// template inline ValueT BoxSampler::trilinearInterpolation(ValueT (& data)[N][N][N], const Vec3R& uvw) { // Trilinear interpolation: // The eight surrounding latice values are used to construct the result. \n // result(x,y,z) = // v000 (1-x)(1-y)(1-z) + v001 (1-x)(1-y)z + v010 (1-x)y(1-z) + v011 (1-x)yz // + v100 x(1-y)(1-z) + v101 x(1-y)z + v110 xy(1-z) + v111 xyz ValueT resultA, resultB; resultA = data[0][0][0] + ValueT((data[0][0][1] - data[0][0][0]) * uvw[2]); resultB = data[0][1][0] + ValueT((data[0][1][1] - data[0][1][0]) * uvw[2]); ValueT result1 = resultA + ValueT((resultB-resultA) * uvw[1]); resultA = data[1][0][0] + ValueT((data[1][0][1] - data[1][0][0]) * uvw[2]); resultB = data[1][1][0] + ValueT((data[1][1][1] - data[1][1][0]) * uvw[2]); ValueT result2 = resultA + ValueT((resultB - resultA) * uvw[1]); return result1 + ValueT(uvw[0] * (result2 - result1)); } template inline bool BoxSampler::sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { typedef typename TreeT::ValueType ValueT; Vec3i inIdx = local_util::floorVec3(inCoord); Vec3R uvw = inCoord - inIdx; // Retrieve the values of the eight voxels surrounding the // fractional source coordinates. ValueT data[2][2][2]; bool hasActiveValues = false; Coord ijk(inIdx); hasActiveValues |= inTree.probeValue(ijk, data[0][0][0]); // i, j, k ijk[2] += 1; hasActiveValues |= inTree.probeValue(ijk, data[0][0][1]); // i, j, k + 1 ijk[1] += 1; hasActiveValues |= inTree.probeValue(ijk, data[0][1][1]); // i, j+1, k + 1 ijk[2] = inIdx[2]; hasActiveValues |= inTree.probeValue(ijk, data[0][1][0]); // i, j+1, k ijk[0] += 1; ijk[1] = inIdx[1]; hasActiveValues |= inTree.probeValue(ijk, data[1][0][0]); // i+1, j, k ijk[2] += 1; hasActiveValues |= inTree.probeValue(ijk, data[1][0][1]); // i+1, j, k + 1 ijk[1] += 1; hasActiveValues |= inTree.probeValue(ijk, data[1][1][1]); // i+1, j+1, k + 1 ijk[2] = inIdx[2]; hasActiveValues |= inTree.probeValue(ijk, data[1][1][0]); // i+1, j+1, k result = trilinearInterpolation(data, uvw); return hasActiveValues; } template inline typename TreeT::ValueType BoxSampler::sample(const TreeT& inTree, const Vec3R& inCoord) { typedef typename TreeT::ValueType ValueT; Vec3i inIdx = local_util::floorVec3(inCoord); Vec3R uvw = inCoord - inIdx; // Retrieve the values of the eight voxels surrounding the // fractional source coordinates. ValueT data[2][2][2]; Coord ijk(inIdx); data[0][0][0] = inTree.getValue(ijk); // i, j, k ijk[2] += 1; data[0][0][1] = inTree.getValue(ijk); // i, j, k + 1 ijk[1] += 1; data[0][1][1] = inTree.getValue(ijk); // i, j+1, k + 1 ijk[2] = inIdx[2]; data[0][1][0] = inTree.getValue(ijk); // i, j+1, k ijk[0] += 1; ijk[1] = inIdx[1]; data[1][0][0] = inTree.getValue(ijk); // i+1, j, k ijk[2] += 1; data[1][0][1] = inTree.getValue(ijk); // i+1, j, k + 1 ijk[1] += 1; data[1][1][1] = inTree.getValue(ijk); // i+1, j+1, k + 1 ijk[2] = inIdx[2]; data[1][1][0] = inTree.getValue(ijk); // i+1, j+1, k return trilinearInterpolation(data, uvw); } //////////////////////////////////////// template inline bool QuadraticSampler::sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { typedef typename TreeT::ValueType ValueT; Vec3i inIdx = local_util::floorVec3(inCoord), inLoIdx = inIdx - Vec3i(1, 1, 1); Vec3R frac = inCoord - inIdx; // Retrieve the values of the 27 voxels surrounding the // fractional source coordinates. bool active = false; ValueT v[3][3][3]; for (int dx = 0, ix = inLoIdx.x(); dx < 3; ++dx, ++ix) { for (int dy = 0, iy = inLoIdx.y(); dy < 3; ++dy, ++iy) { for (int dz = 0, iz = inLoIdx.z(); dz < 3; ++dz, ++iz) { if (inTree.probeValue(Coord(ix, iy, iz), v[dx][dy][dz])) { active = true; } } } } /// @todo For vector types, interpolate over each component independently. ValueT vx[3]; for (int dx = 0; dx < 3; ++dx) { ValueT vy[3]; for (int dy = 0; dy < 3; ++dy) { // Fit a parabola to three contiguous samples in z // (at z=-1, z=0 and z=1), then evaluate the parabola at z', // where z' is the fractional part of inCoord.z, i.e., // inCoord.z - inIdx.z. The coefficients come from solving // // | (-1)^2 -1 1 || a | | v0 | // | 0 0 1 || b | = | v1 | // | 1^2 1 1 || c | | v2 | // // for a, b and c. const ValueT* vz = &v[dx][dy][0]; const ValueT az = static_cast(0.5 * (vz[0] + vz[2]) - vz[1]), bz = static_cast(0.5 * (vz[2] - vz[0])), cz = static_cast(vz[1]); vy[dy] = static_cast(frac.z() * (frac.z() * az + bz) + cz); } // Fit a parabola to three interpolated samples in y, then // evaluate the parabola at y', where y' is the fractional // part of inCoord.y. const ValueT ay = static_cast(0.5 * (vy[0] + vy[2]) - vy[1]), by = static_cast(0.5 * (vy[2] - vy[0])), cy = static_cast(vy[1]); vx[dx] = static_cast(frac.y() * (frac.y() * ay + by) + cy); } // Fit a parabola to three interpolated samples in x, then // evaluate the parabola at the fractional part of inCoord.x. const ValueT ax = static_cast(0.5 * (vx[0] + vx[2]) - vx[1]), bx = static_cast(0.5 * (vx[2] - vx[0])), cx = static_cast(vx[1]); result = static_cast(frac.x() * (frac.x() * ax + bx) + cx); return active; } //////////////////////////////////////// template inline bool StaggeredPointSampler::sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { typedef typename TreeT::ValueType ValueType; ValueType tempX, tempY, tempZ; bool active = false; active = PointSampler::sample(inTree, inCoord + Vec3R(0.5, 0, 0), tempX) || active; active = PointSampler::sample(inTree, inCoord + Vec3R(0, 0.5, 0), tempY) || active; active = PointSampler::sample(inTree, inCoord + Vec3R(0, 0, 0.5), tempZ) || active; result.x() = tempX.x(); result.y() = tempY.y(); result.z() = tempZ.z(); return active; } //////////////////////////////////////// template inline bool StaggeredBoxSampler::sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { typedef typename TreeT::ValueType ValueType; ValueType tempX, tempY, tempZ; tempX = tempY = tempZ = zeroVal(); bool active = false; active = BoxSampler::sample(inTree, inCoord + Vec3R(0.5, 0, 0), tempX) || active; active = BoxSampler::sample(inTree, inCoord + Vec3R(0, 0.5, 0), tempY) || active; active = BoxSampler::sample(inTree, inCoord + Vec3R(0, 0, 0.5), tempZ) || active; result.x() = tempX.x(); result.y() = tempY.y(); result.z() = tempZ.z(); return active; } //////////////////////////////////////// template inline bool StaggeredQuadraticSampler::sample(const TreeT& inTree, const Vec3R& inCoord, typename TreeT::ValueType& result) { typedef typename TreeT::ValueType ValueType; ValueType tempX, tempY, tempZ; bool active = false; active = QuadraticSampler::sample(inTree, inCoord + Vec3R(0.5, 0, 0), tempX) || active; active = QuadraticSampler::sample(inTree, inCoord + Vec3R(0, 0.5, 0), tempY) || active; active = QuadraticSampler::sample(inTree, inCoord + Vec3R(0, 0, 0.5), tempZ) || active; result.x() = tempX.x(); result.y() = tempY.y(); result.z() = tempZ.z(); return active; } } // namespace tools } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_INTERPOLATION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/INSTALL0000644000000000000000000002302712252452020012240 0ustar rootrootInstalling OpenVDB ================== Requirements ------------ - GNU GCC (gcc.gnu.org), version 4.1 or later or Intel ICC (software.intel.com), version 11.1 or later - GNU gmake (www.gnu.org/software/make/), version 3.81 or later - Boost (www.boost.org), version 1.42.0 or later (Linux: yum install boost-devel; OS X: port install boost +python26) - libz (zlib.net) (Linux: yum install zlib-devel) - OpenEXR (www.openexr.com), for the 16-bit float Half class in half.h - Intel Threading Building Blocks (threadingbuildingblocks.org), version 3.0 or later Other compilers or versions might work but have not been tested. Optional: - Doxygen 1.8 (www.stack.nl/~dimitri/doxygen/) - CppUnit (www.freedesktop.org/wiki/Software/cppunit), version 1.10 or later (Linux: yum install cppunit-devel) - Ghostscript (www.ghostscript.com), version 8.70 or later, for documentation in PDF format - pdfLaTeX (www.pdftex.org), version 1.21 or later, for documentation in PDF format - GLFW 2.7 (www.glfw.org), for the OpenVDB viewer - OpenGL 3.2 or later, for the OpenVDB viewer - Python 2.5, 2.6 or 2.7, for the Python module - NumPy (www.numpy.org), for the Python module - Epydoc (http://epydoc.sourceforge.net/), version 3.0 or later, for Python module documentation Other versions might work but have not been tested. Installation ------------ 1. Set values appropriate to your environment for the following variables at the top of the Makefile: INSTALL_DIR the directory into which to install libraries, executables and header files (e.g., /usr/local) BOOST_INCL_DIR the parent directory of the boost/ header directory (e.g., /usr/include) EXR_INCL_DIR the parent directory of the OpenEXR/ header directory EXR_LIB_DIR the directory containing libIlmImf, etc. EXR_LIB linker flags for libIlmImf, libIlmThread, libIex and libImath HALF_INCL_DIR the parent directory of the OpenEXR/ header directory (which contains half.h) HALF_LIB_DIR the directory containing libHalf.so and/or libHalf.a HALF_LIB linker flag(s) for the Half library (e.g., -lHalf) TBB_INCL_DIR the parent directory of the tbb/ header directory TBB_LIB_DIR the directory containing libtbb TBB_LIB linker flag(s) for the TBB library (e.g., -ltbb) CONCURRENT_MALLOC_LIB_DIR a directory containing a scalable, concurrent malloc replacement library such as jemalloc or TBB malloc (leave blank if no such library is available, but be aware that using standard malloc in concurrent code incurs a significant performance penalty) CONCURRENT_MALLOC_LIB linker flag(s) for the malloc replacement library (e.g., -ltbbmalloc_proxy -ltbbmalloc) CPPUNIT_INCL_DIR the parent directory of the cppunit/ header directory (leave blank if CppUnit is not available) CPPUNIT_LIB_DIR the directory containing libcppunit.so or libcppunit.a CPPUNIT_LIB linker flag(s) for the cppunit library (e.g., -lcppunit) GLFW_INCL_DIR the directory containing glfw.h (leave blank if GLFW is not available; GLFW is needed only for the command-line viewer tool) GLFW_LIB_DIR the directory containing libglfw GLFW_LIB linker flags for the GLFW library (e.g., -lglfw) PYTHON_VERSION the version of Python for which to build the OpenVDB module (e.g., 2.6) (leave blank if Python is unavailable) PYTHON_INCL_DIR the directory containing the Python.h header file (on OS X, this is usually /System/Library/Frameworks/ Python.framework/Versions/$(PYTHON_VERSION)/Headers) PYCONFIG_INCL_DIR the directory containing the pyconfig.h header file (usually but not always the same as PYTHON_INCL_DIR) PYTHON_LIB_DIR the directory containing the Python library (on OS X, this is usually /System/Library/Frameworks/ Python.framework/Versions/$(PYTHON_VERSION)/lib) PYTHON_LIB linker flags for the Python library (e.g., -lpython2.6) BOOST_PYTHON_LIB_DIR the directory containing the Boost.Python library BOOST_PYTHON_LIB linker flags for the Boost.Python library (e.g., -lboost_python-mt) NUMPY_INCL_DIR the directory containing the NumPy arrayobject.h header file (leave blank if NumPy is unavailable) (on OS X, this is usually /System/Library/Frameworks/ Python.framework/Versions/$(PYTHON_VERSION)/Extras/ lib/python/numpy/core/include/numpy) EPYDOC the path to the Epydoc executable (leave blank if Epydoc is unavailable) PYTHON_WRAP_ALL_GRID_TYPES if set to "no", expose only FloatGrid, BoolGrid and Vec3SGrid in Python, otherwise expose (most of) the standard grid types defined in openvdb.h DOXYGEN the path to the Doxygen executable (leave blank if Doxygen is unavailable) Note that if you plan to build the Houdini OpenVDB tools (distributed separately), you must build the OpenVDB library and the Houdini tools against compatible versions of the Boost, OpenEXR and TBB libraries. Fortunately, all three are included in the Houdini HDK, so by default several of the variables above reference the Houdini environment variables $HDSO, $HFS and $HT. Source the houdini_setup script provided with your Houdini installation to set those environment variables. To build the OpenVDB Python module, you will need local installations of Python, Boost.Python, and optionally NumPy. As of Houdini 12.5, the HDK includes versions 2.5 and 2.6 of Python as well as Boost.Python headers. Unfortunately, it includes neither the libboost_python library nor NumPy, so both Boost.Python and NumPy have to be built separately. Point the variables $(BOOST_PYTHON_LIB_DIR), $(BOOST_PYTHON_LIB) and $(NUMPY_INCL_DIR) to your local installations of those libraries. 2. From the top-level openvdb/ directory, type "make" (or "make -s" for less verbose output) to locally build the library and commands. The Makefile supports parallel builds (e.g. "make -j 8"). A default local build generates the following libraries and executables (but see the Makefile for additional targets and build options): openvdb/libopenvdb.so.2.1.0 the OpenVDB library openvdb/libopenvdb.so symlink to libopenvdb.so.2.1.0 openvdb/pyopenvdb.so the OpenVDB Python module (if Python and Boost.Python are available) openvdb/vdb_print command-line tool that prints info about OpenVDB .vdb files openvdb/vdb_render command-line tool that ray-traces OpenVDB volumes openvdb/vdb_test unit test runner for libopenvdb (if CppUnit is available) From the openvdb/ directory, type "make test" to run the unit tests and verify that the library is working correctly. (Alternatively, once the library has been installed (Step 5), run the unit test executable directly with "./vdb_test", or "./vdb_test -v" for more verbose output.) Type "make pytest" to run the Python module unit tests. 3. From the openvdb/ directory, type "make doc" (or "make -s doc") to generate HTML library documentation, then open the file openvdb/doc/html/index.html in a browser. Type "make pydoc" (or "make -s pydoc") to generate HTML Python module documentation, then open openvdb/doc/html/python/index.html in a browser. 4. Optionally (if OpenGL and GLFW are available), from the top-level openvdb/ directory, type "make vdb_view" (or "make -s vdb_view") to locally build the OpenVDB viewer tool. Then type "./vdb_view" for usage information. 5. From the openvdb/ directory, type "make install" (or "make -s install") to copy generated files into the directory tree rooted at $(INSTALL_DIR). This creates the following distribution: $(INSTALL_DIR)/ bin/ vdb_print vdb_render vdb_view include/ openvdb/ Exceptions.h ... openvdb.h tools/ tree/ ... version.h lib/ libopenvdb.so libopenvdb.so.2.1 libopenvdb.so.2.1.0 python/ include/ python$(PYTHON_VERSION)/ pyopenvdb.h lib/ python$(PYTHON_VERSION)/ pyopenvdb.so pyopenvdb.so.2.1 share/ doc/ openvdb/ html/ index.html ... python/ index.html ... EOF openvdb/math/0000755000000000000000000000000012252453157012150 5ustar rootrootopenvdb/math/BBox.h0000644000000000000000000003423512252453157013162 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_BBOX_HAS_BEEN_INCLUDED #define OPENVDB_MATH_BBOX_HAS_BEEN_INCLUDED #include "Math.h" // for math::isApproxEqual() and math::Tolerance() #include "Vec3.h" #include #include // for min/max #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief Axis-aligned bounding box template class BBox { public: typedef Vec3T Vec3Type; typedef Vec3T ValueType; typedef Vec3T VectorType; typedef typename Vec3Type::ValueType ElementType; /// @brief Default constructor creates an invalid BBox BBox(); /// @brief Constructor based on a minimum and maximum point. BBox(const Vec3T& xyzMin, const Vec3T& xyzMax); /// @brief Constructor based on a minimum and maximum point. /// If sorted is false the points will be sorted by x,y,z component. BBox(const Vec3T& xyzMin, const Vec3T& xyzMax, bool sorted); /// @brief Contruct a cubical BBox from a minimum coordinate and a /// single edge length. /// @note inclusive for integral ElementTypes BBox(const Vec3T& xyzMin, const ElementType& length); /// @brief Constructor based on a raw array of six points. If /// sorted is false the points will be sorted by x,y,z component. explicit BBox(const ElementType* xyz, bool sorted = true); /// @brief Copy constructor BBox(const BBox& other); /// @brief Sort the min/max by x,y,z component. void sort(); /// @brief Return a const reference to the minimum point of the BBox const Vec3T& min() const { return mMin; } /// @brief Return a const reference to the maximum point of the BBox const Vec3T& max() const { return mMax; } /// @brief Return a non-const reference to the minimum point of the BBox Vec3T& min() { return mMin; } /// @brief Return a non-const reference to the maximum point of the BBox Vec3T& max() { return mMax; } /// @brief Return true if the two BBox'es are identical bool operator==(const BBox& rhs) const; /// @brief Return true if the two BBox'es are not identical bool operator!=(const BBox& rhs) const { return !(*this == rhs); } /// @brief Return true if the BBox is empty, i.e. has no /// (positive) volume. bool empty() const; /// @brief Return true if the BBox has a (positive) volume. bool hasVolume() const { return !this->empty(); } /// @brief Return true if the BBox is valid, i.e. as a (positive) volume. operator bool() const { return !this->empty(); } /// @brief Return true if the all components of mMin <= mMax, /// i.e. the volume is not negative. /// @note For floating point values a tolerance is used for this test. bool isSorted() const; /// @brief Return the center point of the BBox Vec3d getCenter() const; /// @brief Returns the extents of the BBox, i.e. the length per axis /// for floating points values or number of grids per axis points /// integral values. /// @note inclusive for integral ElementTypes Vec3T extents() const; /// @brief Return the volume spanned by this BBox. ElementType volume() const { Vec3T e = this->extents(); return e[0] * e[1] * e[2]; } /// Return the index (0, 1 or 2) of the longest axis. size_t maxExtent() const { return MaxIndex(mMax - mMin); } /// Return the index (0, 1 or 2) of the shortest axis. size_t minExtent() const { return MinIndex(mMax - mMin); } /// Return @c true if point (x, y, z) is inside this bounding box. bool isInside(const Vec3T& xyz) const; /// Return @c true if the given bounding box is inside this bounding box. bool isInside(const BBox&) const; /// Return @c true if the given bounding box overlaps with this bounding box. bool hasOverlap(const BBox&) const; /// Pad this bounding box. void expand(ElementType padding); /// Expand this bounding box to enclose point (x, y, z). void expand(const Vec3T& xyz); /// Union this bounding box with the given bounding box. void expand(const BBox&); // @brief Union this bbox with the cubical bbox defined from xyzMin and // length /// @note inclusive for integral ElementTypes void expand(const Vec3T& xyzMin, const ElementType& length); /// Translate this bounding box by \f$(t_x, t_y, t_z)\f$. void translate(const Vec3T& t); /// Apply a map to this bounding box template BBox applyMap(const MapType& map) const; /// Apply the inverse of a map to this bounding box template BBox applyInverseMap(const MapType& map) const; /// Unserialize this bounding box from the given stream. void read(std::istream& is) { mMin.read(is); mMax.read(is); } /// Serialize this bounding box to the given stream. void write(std::ostream& os) const { mMin.write(os); mMax.write(os); } private: Vec3T mMin, mMax; }; // class BBox //////////////////////////////////////// template inline BBox::BBox(): mMin( std::numeric_limits::max()), mMax(-std::numeric_limits::max()) { } template inline BBox::BBox(const Vec3T& xyzMin, const Vec3T& xyzMax): mMin(xyzMin), mMax(xyzMax) { } template inline BBox::BBox(const Vec3T& xyzMin, const Vec3T& xyzMax, bool sorted): mMin(xyzMin), mMax(xyzMax) { if (!sorted) this->sort(); } template inline BBox::BBox(const Vec3T& xyzMin, const ElementType& length): mMin(xyzMin), mMax(xyzMin) { // min and max are inclusive for integral ElementType const ElementType size = boost::is_integral::value ? length-1 : length; mMax[0] += size; mMax[1] += size; mMax[2] += size; } template inline BBox::BBox(const ElementType* xyz, bool sorted): mMin(xyz[0], xyz[1], xyz[2]), mMax(xyz[3], xyz[4], xyz[5]) { if (!sorted) this->sort(); } template inline BBox::BBox(const BBox& other): mMin(other.mMin), mMax(other.mMax) { } //////////////////////////////////////// template inline bool BBox::empty() const { if (boost::is_integral::value) { // min and max are inclusive for integral ElementType return (mMin[0] > mMax[0] || mMin[1] > mMax[1] || mMin[2] > mMax[2]); } return mMin[0] >= mMax[0] || mMin[1] >= mMax[1] || mMin[2] >= mMax[2]; } template inline bool BBox::operator==(const BBox& rhs) const { if (boost::is_integral::value) { return mMin == rhs.min() && mMax == rhs.max(); } else { return math::isApproxEqual(mMin, rhs.min()) && math::isApproxEqual(mMax, rhs.max()); } } template inline void BBox::sort() { Vec3T tMin(mMin), tMax(mMax); for (size_t i = 0; i < 3; ++i) { mMin[i] = std::min(tMin[i], tMax[i]); mMax[i] = std::max(tMin[i], tMax[i]); } } template inline bool BBox::isSorted() const { if (boost::is_integral::value) { return (mMin[0] <= mMax[0] && mMin[1] <= mMax[1] && mMin[2] <= mMax[2]); } else { ElementType t = math::Tolerance::value(); return (mMin[0] < (mMax[0] + t) && mMin[1] < (mMax[1] + t) && mMin[2] < (mMax[2] + t)); } } template inline Vec3d BBox::getCenter() const { return (Vec3d(mMin.asPointer()) + Vec3d(mMax.asPointer())) * 0.5; } template inline Vec3T BBox::extents() const { if (boost::is_integral::value) { return (mMax - mMin) + Vec3T(1, 1, 1); } else { return (mMax - mMin); } } //////////////////////////////////////// template inline bool BBox::isInside(const Vec3T& xyz) const { if (boost::is_integral::value) { return xyz[0] >= mMin[0] && xyz[0] <= mMax[0] && xyz[1] >= mMin[1] && xyz[1] <= mMax[1] && xyz[2] >= mMin[2] && xyz[2] <= mMax[2]; } else { ElementType t = math::Tolerance::value(); return xyz[0] > (mMin[0]-t) && xyz[0] < (mMax[0]+t) && xyz[1] > (mMin[1]-t) && xyz[1] < (mMax[1]+t) && xyz[2] > (mMin[2]-t) && xyz[2] < (mMax[2]+t); } } template inline bool BBox::isInside(const BBox& b) const { if (boost::is_integral::value) { return b.min()[0] >= mMin[0] && b.max()[0] <= mMax[0] && b.min()[1] >= mMin[1] && b.max()[1] <= mMax[1] && b.min()[2] >= mMin[2] && b.max()[2] <= mMax[2]; } else { ElementType t = math::Tolerance::value(); return (b.min()[0]-t) > mMin[0] && (b.max()[0]+t) < mMax[0] && (b.min()[1]-t) > mMin[1] && (b.max()[1]+t) < mMax[1] && (b.min()[2]-t) > mMin[2] && (b.max()[2]+t) < mMax[2]; } } template inline bool BBox::hasOverlap(const BBox& b) const { if (boost::is_integral::value) { return mMax[0] >= b.min()[0] && mMin[0] <= b.max()[0] && mMax[1] >= b.min()[1] && mMin[1] <= b.max()[1] && mMax[2] >= b.min()[2] && mMin[2] <= b.max()[2]; } else { ElementType t = math::Tolerance::value(); return mMax[0] > (b.min()[0]-t) && mMin[0] < (b.max()[0]+t) && mMax[1] > (b.min()[1]-t) && mMin[1] < (b.max()[1]+t) && mMax[2] > (b.min()[2]-t) && mMin[2] < (b.max()[2]+t); } } //////////////////////////////////////// template inline void BBox::expand(ElementType dx) { dx = std::abs(dx); for (size_t i = 0; i < 3; ++i) { mMin[i] -= dx; mMax[i] += dx; } } template inline void BBox::expand(const Vec3T& xyz) { for (size_t i = 0; i < 3; ++i) { mMin[i] = std::min(mMin[i], xyz[i]); mMax[i] = std::max(mMax[i], xyz[i]); } } template inline void BBox::expand(const BBox& b) { for (size_t i = 0; i < 3; ++i) { mMin[i] = std::min(mMin[i], b.min()[i]); mMax[i] = std::max(mMax[i], b.max()[i]); } } template inline void BBox::expand(const Vec3T& xyzMin, const ElementType& length) { const ElementType size = boost::is_integral::value ? length-1 : length; for (size_t i = 0; i < 3; ++i) { mMin[i] = std::min(mMin[i], xyzMin[i]); mMax[i] = std::max(mMax[i], xyzMin[i] + size); } } template inline void BBox::translate(const Vec3T& dx) { mMin += dx; mMax += dx; } template template inline BBox BBox::applyMap(const MapType& map) const { typedef Vec3 Vec3R; BBox bbox; bbox.expand(map.applyMap(Vec3R(mMin[0], mMin[1], mMin[2]))); bbox.expand(map.applyMap(Vec3R(mMin[0], mMin[1], mMax[2]))); bbox.expand(map.applyMap(Vec3R(mMin[0], mMax[1], mMin[2]))); bbox.expand(map.applyMap(Vec3R(mMax[0], mMin[1], mMin[2]))); bbox.expand(map.applyMap(Vec3R(mMax[0], mMax[1], mMin[2]))); bbox.expand(map.applyMap(Vec3R(mMax[0], mMin[1], mMax[2]))); bbox.expand(map.applyMap(Vec3R(mMin[0], mMax[1], mMax[2]))); bbox.expand(map.applyMap(Vec3R(mMax[0], mMax[1], mMax[2]))); return bbox; } template template inline BBox BBox::applyInverseMap(const MapType& map) const { typedef Vec3 Vec3R; BBox bbox; bbox.expand(map.applyInverseMap(Vec3R(mMin[0], mMin[1], mMin[2]))); bbox.expand(map.applyInverseMap(Vec3R(mMin[0], mMin[1], mMax[2]))); bbox.expand(map.applyInverseMap(Vec3R(mMin[0], mMax[1], mMin[2]))); bbox.expand(map.applyInverseMap(Vec3R(mMax[0], mMin[1], mMin[2]))); bbox.expand(map.applyInverseMap(Vec3R(mMax[0], mMax[1], mMin[2]))); bbox.expand(map.applyInverseMap(Vec3R(mMax[0], mMin[1], mMax[2]))); bbox.expand(map.applyInverseMap(Vec3R(mMin[0], mMax[1], mMax[2]))); bbox.expand(map.applyInverseMap(Vec3R(mMax[0], mMax[1], mMax[2]))); return bbox; } //////////////////////////////////////// template inline std::ostream& operator<<(std::ostream& os, const BBox& b) { os << b.min() << " -> " << b.max(); return os; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_BBOX_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Stencils.h0000644000000000000000000023125612252453157014116 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @author Ken Museth /// @file Stencils.h #ifndef OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED #define OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED #include #include #include // for Pow2, needed by WENO and Gudonov #include // for Real #include // for Coord #include // for WENO5 and GudonovsNormSqrd namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// template class BaseStencil { public: typedef _GridType GridType; typedef typename GridType::TreeType TreeType; typedef typename GridType::ValueType ValueType; typedef std::vector BufferType; typedef typename BufferType::iterator IterType; typedef typename GridType::ConstAccessor AccessorType; /// @brief Initialize the stencil buffer with the values of voxel (i, j, k) /// and its neighbors. /// @param ijk Index coordinates of stencil center inline void moveTo(const Coord& ijk) { mCenter = ijk; mStencil[0] = mCache.getValue(ijk); static_cast(*this).init(mCenter); } /// @brief Initialize the stencil buffer with the values of voxel (i, j, k) /// and its neighbors. The method also takes a value of the center /// element of the stencil, assuming it is already known. /// @param ijk Index coordinates of stnecil center /// @param centerValue Value of the center element of the stencil inline void moveTo(const Coord& ijk, const ValueType& centerValue) { mCenter = ijk; mStencil[0] = centerValue; static_cast(*this).init(mCenter); } /// @brief Initialize the stencil buffer with the values of voxel /// (x, y, z) and its neighbors. /// /// @note This version is slightly faster than the one above, since /// the center voxel's value is read directly from the iterator. template inline void moveTo(const IterType& iter) { mCenter = iter.getCoord(); mStencil[0] = *iter; static_cast(*this).init(mCenter); } /// @brief Initialize the stencil buffer with the values of voxel (x, y, z) /// and its neighbors. /// @param xyz Floating point voxel coordinates of stencil center /// @details This method will check to see if it is necessary to /// update the stencil based on the cached index coordinates of /// the center point. inline void moveTo(const Vec3R& xyz) { Coord ijk = openvdb::Coord::floor(xyz); if (ijk != mCenter) this->moveTo(ijk); } /// @brief Return the value from the stencil buffer with linear /// offset pos. /// /// @note The default (@a pos = 0) corresponds to the first element /// which is typically the center point of the stencil. inline const ValueType& getValue(unsigned int pos = 0) const { assert(pos < mStencil.size()); return mStencil[pos]; } /// @brief Return the value at the specified location relative to the center of the stencil template inline const ValueType& getValue() const { return mStencil[static_cast(*this).template pos()]; } /// @brief Set the value at the specified location relative to the center of the stencil template inline void setValue(const ValueType& value) { mStencil[static_cast(*this).template pos()] = value; } /// @brief Return the size of the stencil buffer. inline int size() { return mStencil.size(); } /// @brief Return the median value of the current stencil. inline ValueType median() const { std::vector tmp(mStencil);//local copy assert(!tmp.empty()); size_t midpoint = (tmp.size() - 1) >> 1; // Partially sort the vector until the median value is at the midpoint. std::nth_element(tmp.begin(), tmp.begin() + midpoint, tmp.end()); return tmp[midpoint]; } /// @brief Return the mean value of the current stencil. inline ValueType mean() const { ValueType sum = 0.0; for (int n=0, s=mStencil.size(); n()) const { const bool less = this->getValue< 0, 0, 0>() < isoValue; return (less ^ (this->getValue<-1, 0, 0>() < isoValue)) || (less ^ (this->getValue< 1, 0, 0>() < isoValue)) || (less ^ (this->getValue< 0,-1, 0>() < isoValue)) || (less ^ (this->getValue< 0, 1, 0>() < isoValue)) || (less ^ (this->getValue< 0, 0,-1>() < isoValue)) || (less ^ (this->getValue< 0, 0, 1>() < isoValue)) ; } /// @brief Return a const reference to the grid from which this /// stencil was constructed. inline const GridType& grid() const { return *mGrid; } /// @brief Return a const reference to the ValueAccessor /// associated with this Stencil. inline const AccessorType& accessor() const { return mCache; } protected: // Constructor is protected to prevent direct instantiation. BaseStencil(const GridType& grid, int size): mGrid(&grid), mCache(grid.getConstAccessor()), mStencil(size), mCenter(Coord::max()) { } const GridType* mGrid; AccessorType mCache; BufferType mStencil; Coord mCenter; }; // class BaseStencil //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the seven point stencil template struct SevenPt {}; template<> struct SevenPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct SevenPt< 1, 0, 0> { enum { idx = 1 }; }; template<> struct SevenPt< 0, 1, 0> { enum { idx = 2 }; }; template<> struct SevenPt< 0, 0, 1> { enum { idx = 3 }; }; template<> struct SevenPt<-1, 0, 0> { enum { idx = 4 }; }; template<> struct SevenPt< 0,-1, 0> { enum { idx = 5 }; }; template<> struct SevenPt< 0, 0,-1> { enum { idx = 6 }; }; } template class SevenPointStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 7; SevenPointStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return SevenPt::idx; } private: inline void init(const Coord& ijk) { BaseType::template setValue<-1, 0, 0>(mCache.getValue(ijk.offsetBy(-1, 0, 0))); BaseType::template setValue< 1, 0, 0>(mCache.getValue(ijk.offsetBy( 1, 0, 0))); BaseType::template setValue< 0,-1, 0>(mCache.getValue(ijk.offsetBy( 0,-1, 0))); BaseType::template setValue< 0, 1, 0>(mCache.getValue(ijk.offsetBy( 0, 1, 0))); BaseType::template setValue< 0, 0,-1>(mCache.getValue(ijk.offsetBy( 0, 0,-1))); BaseType::template setValue< 0, 0, 1>(mCache.getValue(ijk.offsetBy( 0, 0, 1))); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; }; //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the eight point box stencil template struct BoxPt {}; template<> struct BoxPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct BoxPt< 0, 0, 1> { enum { idx = 1 }; }; template<> struct BoxPt< 0, 1, 1> { enum { idx = 2 }; }; template<> struct BoxPt< 0, 1, 0> { enum { idx = 3 }; }; template<> struct BoxPt< 1, 0, 0> { enum { idx = 4 }; }; template<> struct BoxPt< 1, 0, 1> { enum { idx = 5 }; }; template<> struct BoxPt< 1, 1, 1> { enum { idx = 6 }; }; template<> struct BoxPt< 1, 1, 0> { enum { idx = 7 }; }; } template class BoxStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 8; BoxStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return BoxPt::idx; } /// @brief Return true if the center of the stencil intersects the /// iso-contour specified by the isoValue inline bool intersects(const ValueType &isoValue = zeroVal()) const { const bool less = mStencil[0] < isoValue; return (less ^ (mStencil[1] < isoValue)) || (less ^ (mStencil[2] < isoValue)) || (less ^ (mStencil[3] < isoValue)) || (less ^ (mStencil[4] < isoValue)) || (less ^ (mStencil[5] < isoValue)) || (less ^ (mStencil[6] < isoValue)) || (less ^ (mStencil[7] < isoValue)) ; } /// @brief Return the trilinear interpolation at the normalized position. /// @param xyz Floating point coordinate position. /// @warning It is assumed that the stencil has already been moved /// to the relevant voxel position, e.g. using moveTo(xyz). /// @note Trilinear interpolation kernal reads as: /// v000 (1-u)(1-v)(1-w) + v001 (1-u)(1-v)w + v010 (1-u)v(1-w) + v011 (1-u)vw /// + v100 u(1-v)(1-w) + v101 u(1-v)w + v110 uv(1-w) + v111 uvw inline ValueType interpolation(const Vec3Type& xyz) const { const Real u = xyz[0] - BaseType::mCenter[0]; assert(u>=0 && u<=1); const Real v = xyz[1] - BaseType::mCenter[1]; assert(v>=0 && v<=1); const Real w = xyz[2] - BaseType::mCenter[2]; assert(w>=0 && w<=1); ValueType V = BaseType::template getValue<0,0,0>(); ValueType A = V + (BaseType::template getValue<0,0,1>() - V) * w; V = BaseType::template getValue< 0, 1, 0>(); ValueType B = V + (BaseType::template getValue<0,1,1>() - V) * w; ValueType C = A + (B - A) * v; V = BaseType::template getValue<1,0,0>(); A = V + (BaseType::template getValue<1,0,1>() - V) * w; V = BaseType::template getValue<1,1,0>(); B = V + (BaseType::template getValue<1,1,1>() - V) * w; ValueType D = A + (B - A) * v; return C + (D - C) * u; } /// @brief Return the gradient in world space of the trilinear interpolation kernel. /// @param xyz Floating point coordinate position. /// @warning It is assumed that the stencil has already been moved /// to the relevant voxel position, e.g. using moveTo(xyz). /// @note Computed as partial derivatives of the trilinear interpolation kernel: /// v000 (1-u)(1-v)(1-w) + v001 (1-u)(1-v)w + v010 (1-u)v(1-w) + v011 (1-u)vw /// + v100 u(1-v)(1-w) + v101 u(1-v)w + v110 uv(1-w) + v111 uvw inline Vec3Type gradient(const Vec3Type& xyz) const { const Real u = xyz[0] - BaseType::mCenter[0]; assert(u>=0 && u<=1); const Real v = xyz[1] - BaseType::mCenter[1]; assert(v>=0 && v<=1); const Real w = xyz[2] - BaseType::mCenter[2]; assert(w>=0 && w<=1); ValueType D[4]={BaseType::template getValue<0,0,1>()-BaseType::template getValue<0,0,0>(), BaseType::template getValue<0,1,1>()-BaseType::template getValue<0,1,0>(), BaseType::template getValue<1,0,1>()-BaseType::template getValue<1,0,0>(), BaseType::template getValue<1,1,1>()-BaseType::template getValue<1,1,0>()}; // Z component ValueType A = D[0] + (D[1]- D[0]) * v; ValueType B = D[2] + (D[3]- D[2]) * v; Vec3Type grad(zeroVal(), zeroVal(), A + (B - A) * u); D[0] = BaseType::template getValue<0,0,0>() + D[0] * w; D[1] = BaseType::template getValue<0,1,0>() + D[1] * w; D[2] = BaseType::template getValue<1,0,0>() + D[2] * w; D[3] = BaseType::template getValue<1,1,0>() + D[3] * w; // X component A = D[0] + (D[1] - D[0]) * v; B = D[2] + (D[3] - D[2]) * v; grad[0] = B - A; // Y component A = D[1] - D[0]; B = D[3] - D[2]; grad[1] = A + (B - A) * u; return BaseType::mGrid->transform().baseMap()->applyIJT(grad, xyz); } private: inline void init(const Coord& ijk) { BaseType::template setValue< 0, 0, 1>(mCache.getValue(ijk.offsetBy( 0, 0, 1))); BaseType::template setValue< 0, 1, 1>(mCache.getValue(ijk.offsetBy( 0, 1, 1))); BaseType::template setValue< 0, 1, 0>(mCache.getValue(ijk.offsetBy( 0, 1, 0))); BaseType::template setValue< 1, 0, 0>(mCache.getValue(ijk.offsetBy( 1, 0, 0))); BaseType::template setValue< 1, 0, 1>(mCache.getValue(ijk.offsetBy( 1, 0, 1))); BaseType::template setValue< 1, 1, 1>(mCache.getValue(ijk.offsetBy( 1, 1, 1))); BaseType::template setValue< 1, 1, 0>(mCache.getValue(ijk.offsetBy( 1, 1, 0))); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; }; //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the dense point stencil template struct DensePt {}; template<> struct DensePt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct DensePt< 1, 0, 0> { enum { idx = 1 }; }; template<> struct DensePt< 0, 1, 0> { enum { idx = 2 }; }; template<> struct DensePt< 0, 0, 1> { enum { idx = 3 }; }; template<> struct DensePt<-1, 0, 0> { enum { idx = 4 }; }; template<> struct DensePt< 0,-1, 0> { enum { idx = 5 }; }; template<> struct DensePt< 0, 0,-1> { enum { idx = 6 }; }; template<> struct DensePt<-1,-1, 0> { enum { idx = 7 }; }; template<> struct DensePt< 0,-1,-1> { enum { idx = 8 }; }; template<> struct DensePt<-1, 0,-1> { enum { idx = 9 }; }; template<> struct DensePt< 1,-1, 0> { enum { idx = 10 }; }; template<> struct DensePt< 0, 1,-1> { enum { idx = 11 }; }; template<> struct DensePt<-1, 0, 1> { enum { idx = 12 }; }; template<> struct DensePt<-1, 1, 0> { enum { idx = 13 }; }; template<> struct DensePt< 0,-1, 1> { enum { idx = 14 }; }; template<> struct DensePt< 1, 0,-1> { enum { idx = 15 }; }; template<> struct DensePt< 1, 1, 0> { enum { idx = 16 }; }; template<> struct DensePt< 0, 1, 1> { enum { idx = 17 }; }; template<> struct DensePt< 1, 0, 1> { enum { idx = 18 }; }; } template class SecondOrderDenseStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 19; SecondOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return DensePt::idx; } private: inline void init(const Coord& ijk) { mStencil[DensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[DensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[DensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[DensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[DensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[DensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[DensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, -1, 0)); mStencil[DensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, -1, 0)); mStencil[DensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); mStencil[DensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); mStencil[DensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, -1)); mStencil[DensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, -1)); mStencil[DensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); mStencil[DensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); mStencil[DensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, -1)); mStencil[DensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, -1)); mStencil[DensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 1)); mStencil[DensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; }; //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the dense point stencil template struct ThirteenPt {}; template<> struct ThirteenPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct ThirteenPt< 1, 0, 0> { enum { idx = 1 }; }; template<> struct ThirteenPt< 0, 1, 0> { enum { idx = 2 }; }; template<> struct ThirteenPt< 0, 0, 1> { enum { idx = 3 }; }; template<> struct ThirteenPt<-1, 0, 0> { enum { idx = 4 }; }; template<> struct ThirteenPt< 0,-1, 0> { enum { idx = 5 }; }; template<> struct ThirteenPt< 0, 0,-1> { enum { idx = 6 }; }; template<> struct ThirteenPt< 2, 0, 0> { enum { idx = 7 }; }; template<> struct ThirteenPt< 0, 2, 0> { enum { idx = 8 }; }; template<> struct ThirteenPt< 0, 0, 2> { enum { idx = 9 }; }; template<> struct ThirteenPt<-2, 0, 0> { enum { idx = 10 }; }; template<> struct ThirteenPt< 0,-2, 0> { enum { idx = 11 }; }; template<> struct ThirteenPt< 0, 0,-2> { enum { idx = 12 }; }; } template class ThirteenPointStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 13; ThirteenPointStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return ThirteenPt::idx; } private: inline void init(const Coord& ijk) { mStencil[ThirteenPt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[ThirteenPt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[ThirteenPt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[ThirteenPt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[ThirteenPt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[ThirteenPt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[ThirteenPt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[ThirteenPt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); mStencil[ThirteenPt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[ThirteenPt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[ThirteenPt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[ThirteenPt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; }; //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the 4th-order dense point stencil template struct FourthDensePt {}; template<> struct FourthDensePt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct FourthDensePt<-2, 2, 0> { enum { idx = 1 }; }; template<> struct FourthDensePt<-1, 2, 0> { enum { idx = 2 }; }; template<> struct FourthDensePt< 0, 2, 0> { enum { idx = 3 }; }; template<> struct FourthDensePt< 1, 2, 0> { enum { idx = 4 }; }; template<> struct FourthDensePt< 2, 2, 0> { enum { idx = 5 }; }; template<> struct FourthDensePt<-2, 1, 0> { enum { idx = 6 }; }; template<> struct FourthDensePt<-1, 1, 0> { enum { idx = 7 }; }; template<> struct FourthDensePt< 0, 1, 0> { enum { idx = 8 }; }; template<> struct FourthDensePt< 1, 1, 0> { enum { idx = 9 }; }; template<> struct FourthDensePt< 2, 1, 0> { enum { idx = 10 }; }; template<> struct FourthDensePt<-2, 0, 0> { enum { idx = 11 }; }; template<> struct FourthDensePt<-1, 0, 0> { enum { idx = 12 }; }; template<> struct FourthDensePt< 1, 0, 0> { enum { idx = 13 }; }; template<> struct FourthDensePt< 2, 0, 0> { enum { idx = 14 }; }; template<> struct FourthDensePt<-2,-1, 0> { enum { idx = 15 }; }; template<> struct FourthDensePt<-1,-1, 0> { enum { idx = 16 }; }; template<> struct FourthDensePt< 0,-1, 0> { enum { idx = 17 }; }; template<> struct FourthDensePt< 1,-1, 0> { enum { idx = 18 }; }; template<> struct FourthDensePt< 2,-1, 0> { enum { idx = 19 }; }; template<> struct FourthDensePt<-2,-2, 0> { enum { idx = 20 }; }; template<> struct FourthDensePt<-1,-2, 0> { enum { idx = 21 }; }; template<> struct FourthDensePt< 0,-2, 0> { enum { idx = 22 }; }; template<> struct FourthDensePt< 1,-2, 0> { enum { idx = 23 }; }; template<> struct FourthDensePt< 2,-2, 0> { enum { idx = 24 }; }; template<> struct FourthDensePt<-2, 0, 2> { enum { idx = 25 }; }; template<> struct FourthDensePt<-1, 0, 2> { enum { idx = 26 }; }; template<> struct FourthDensePt< 0, 0, 2> { enum { idx = 27 }; }; template<> struct FourthDensePt< 1, 0, 2> { enum { idx = 28 }; }; template<> struct FourthDensePt< 2, 0, 2> { enum { idx = 29 }; }; template<> struct FourthDensePt<-2, 0, 1> { enum { idx = 30 }; }; template<> struct FourthDensePt<-1, 0, 1> { enum { idx = 31 }; }; template<> struct FourthDensePt< 0, 0, 1> { enum { idx = 32 }; }; template<> struct FourthDensePt< 1, 0, 1> { enum { idx = 33 }; }; template<> struct FourthDensePt< 2, 0, 1> { enum { idx = 34 }; }; template<> struct FourthDensePt<-2, 0,-1> { enum { idx = 35 }; }; template<> struct FourthDensePt<-1, 0,-1> { enum { idx = 36 }; }; template<> struct FourthDensePt< 0, 0,-1> { enum { idx = 37 }; }; template<> struct FourthDensePt< 1, 0,-1> { enum { idx = 38 }; }; template<> struct FourthDensePt< 2, 0,-1> { enum { idx = 39 }; }; template<> struct FourthDensePt<-2, 0,-2> { enum { idx = 40 }; }; template<> struct FourthDensePt<-1, 0,-2> { enum { idx = 41 }; }; template<> struct FourthDensePt< 0, 0,-2> { enum { idx = 42 }; }; template<> struct FourthDensePt< 1, 0,-2> { enum { idx = 43 }; }; template<> struct FourthDensePt< 2, 0,-2> { enum { idx = 44 }; }; template<> struct FourthDensePt< 0,-2, 2> { enum { idx = 45 }; }; template<> struct FourthDensePt< 0,-1, 2> { enum { idx = 46 }; }; template<> struct FourthDensePt< 0, 1, 2> { enum { idx = 47 }; }; template<> struct FourthDensePt< 0, 2, 2> { enum { idx = 48 }; }; template<> struct FourthDensePt< 0,-2, 1> { enum { idx = 49 }; }; template<> struct FourthDensePt< 0,-1, 1> { enum { idx = 50 }; }; template<> struct FourthDensePt< 0, 1, 1> { enum { idx = 51 }; }; template<> struct FourthDensePt< 0, 2, 1> { enum { idx = 52 }; }; template<> struct FourthDensePt< 0,-2,-1> { enum { idx = 53 }; }; template<> struct FourthDensePt< 0,-1,-1> { enum { idx = 54 }; }; template<> struct FourthDensePt< 0, 1,-1> { enum { idx = 55 }; }; template<> struct FourthDensePt< 0, 2,-1> { enum { idx = 56 }; }; template<> struct FourthDensePt< 0,-2,-2> { enum { idx = 57 }; }; template<> struct FourthDensePt< 0,-1,-2> { enum { idx = 58 }; }; template<> struct FourthDensePt< 0, 1,-2> { enum { idx = 59 }; }; template<> struct FourthDensePt< 0, 2,-2> { enum { idx = 60 }; }; } template class FourthOrderDenseStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 61; FourthOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return FourthDensePt::idx; } private: inline void init(const Coord& ijk) { mStencil[FourthDensePt<-2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 2, 0)); mStencil[FourthDensePt<-1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 2, 0)); mStencil[FourthDensePt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[FourthDensePt< 1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 2, 0)); mStencil[FourthDensePt< 2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 2, 0)); mStencil[FourthDensePt<-2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 1, 0)); mStencil[FourthDensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); mStencil[FourthDensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[FourthDensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); mStencil[FourthDensePt< 2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 1, 0)); mStencil[FourthDensePt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[FourthDensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[FourthDensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[FourthDensePt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[FourthDensePt<-2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-1, 0)); mStencil[FourthDensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-1, 0)); mStencil[FourthDensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 0)); mStencil[FourthDensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-1, 0)); mStencil[FourthDensePt< 2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-1, 0)); mStencil[FourthDensePt<-2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-2, 0)); mStencil[FourthDensePt<-1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-2, 0)); mStencil[FourthDensePt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 0)); mStencil[FourthDensePt< 1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-2, 0)); mStencil[FourthDensePt< 2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-2, 0)); mStencil[FourthDensePt<-2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 2)); mStencil[FourthDensePt<-1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 2)); mStencil[FourthDensePt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[FourthDensePt< 1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 2)); mStencil[FourthDensePt< 2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 2)); mStencil[FourthDensePt<-2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 1)); mStencil[FourthDensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); mStencil[FourthDensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[FourthDensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); mStencil[FourthDensePt< 2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 1)); mStencil[FourthDensePt<-2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-1)); mStencil[FourthDensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-1)); mStencil[FourthDensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-1)); mStencil[FourthDensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-1)); mStencil[FourthDensePt< 2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-1)); mStencil[FourthDensePt<-2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-2)); mStencil[FourthDensePt<-1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-2)); mStencil[FourthDensePt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-2)); mStencil[FourthDensePt< 1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-2)); mStencil[FourthDensePt< 2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-2)); mStencil[FourthDensePt< 0,-2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 2)); mStencil[FourthDensePt< 0,-1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 2)); mStencil[FourthDensePt< 0, 1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 2)); mStencil[FourthDensePt< 0, 2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 2)); mStencil[FourthDensePt< 0,-2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 1)); mStencil[FourthDensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 1)); mStencil[FourthDensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); mStencil[FourthDensePt< 0, 2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 1)); mStencil[FourthDensePt< 0,-2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-1)); mStencil[FourthDensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-1)); mStencil[FourthDensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-1)); mStencil[FourthDensePt< 0, 2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-1)); mStencil[FourthDensePt< 0,-2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-2)); mStencil[FourthDensePt< 0,-1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-2)); mStencil[FourthDensePt< 0, 1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-2)); mStencil[FourthDensePt< 0, 2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-2)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; }; //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the dense point stencil template struct NineteenPt {}; template<> struct NineteenPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct NineteenPt< 1, 0, 0> { enum { idx = 1 }; }; template<> struct NineteenPt< 0, 1, 0> { enum { idx = 2 }; }; template<> struct NineteenPt< 0, 0, 1> { enum { idx = 3 }; }; template<> struct NineteenPt<-1, 0, 0> { enum { idx = 4 }; }; template<> struct NineteenPt< 0,-1, 0> { enum { idx = 5 }; }; template<> struct NineteenPt< 0, 0,-1> { enum { idx = 6 }; }; template<> struct NineteenPt< 2, 0, 0> { enum { idx = 7 }; }; template<> struct NineteenPt< 0, 2, 0> { enum { idx = 8 }; }; template<> struct NineteenPt< 0, 0, 2> { enum { idx = 9 }; }; template<> struct NineteenPt<-2, 0, 0> { enum { idx = 10 }; }; template<> struct NineteenPt< 0,-2, 0> { enum { idx = 11 }; }; template<> struct NineteenPt< 0, 0,-2> { enum { idx = 12 }; }; template<> struct NineteenPt< 3, 0, 0> { enum { idx = 13 }; }; template<> struct NineteenPt< 0, 3, 0> { enum { idx = 14 }; }; template<> struct NineteenPt< 0, 0, 3> { enum { idx = 15 }; }; template<> struct NineteenPt<-3, 0, 0> { enum { idx = 16 }; }; template<> struct NineteenPt< 0,-3, 0> { enum { idx = 17 }; }; template<> struct NineteenPt< 0, 0,-3> { enum { idx = 18 }; }; } template class NineteenPointStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 19; NineteenPointStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return NineteenPt::idx; } private: inline void init(const Coord& ijk) { mStencil[NineteenPt< 3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); mStencil[NineteenPt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[NineteenPt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[NineteenPt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[NineteenPt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[NineteenPt<-3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); mStencil[NineteenPt< 0, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); mStencil[NineteenPt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[NineteenPt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[NineteenPt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[NineteenPt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); mStencil[NineteenPt< 0,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, -3, 0)); mStencil[NineteenPt< 0, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); mStencil[NineteenPt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[NineteenPt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[NineteenPt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[NineteenPt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); mStencil[NineteenPt< 0, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, -3)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; }; //////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the 4th-order dense point stencil template struct SixthDensePt { }; template<> struct SixthDensePt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct SixthDensePt<-3, 3, 0> { enum { idx = 1 }; }; template<> struct SixthDensePt<-2, 3, 0> { enum { idx = 2 }; }; template<> struct SixthDensePt<-1, 3, 0> { enum { idx = 3 }; }; template<> struct SixthDensePt< 0, 3, 0> { enum { idx = 4 }; }; template<> struct SixthDensePt< 1, 3, 0> { enum { idx = 5 }; }; template<> struct SixthDensePt< 2, 3, 0> { enum { idx = 6 }; }; template<> struct SixthDensePt< 3, 3, 0> { enum { idx = 7 }; }; template<> struct SixthDensePt<-3, 2, 0> { enum { idx = 8 }; }; template<> struct SixthDensePt<-2, 2, 0> { enum { idx = 9 }; }; template<> struct SixthDensePt<-1, 2, 0> { enum { idx = 10 }; }; template<> struct SixthDensePt< 0, 2, 0> { enum { idx = 11 }; }; template<> struct SixthDensePt< 1, 2, 0> { enum { idx = 12 }; }; template<> struct SixthDensePt< 2, 2, 0> { enum { idx = 13 }; }; template<> struct SixthDensePt< 3, 2, 0> { enum { idx = 14 }; }; template<> struct SixthDensePt<-3, 1, 0> { enum { idx = 15 }; }; template<> struct SixthDensePt<-2, 1, 0> { enum { idx = 16 }; }; template<> struct SixthDensePt<-1, 1, 0> { enum { idx = 17 }; }; template<> struct SixthDensePt< 0, 1, 0> { enum { idx = 18 }; }; template<> struct SixthDensePt< 1, 1, 0> { enum { idx = 19 }; }; template<> struct SixthDensePt< 2, 1, 0> { enum { idx = 20 }; }; template<> struct SixthDensePt< 3, 1, 0> { enum { idx = 21 }; }; template<> struct SixthDensePt<-3, 0, 0> { enum { idx = 22 }; }; template<> struct SixthDensePt<-2, 0, 0> { enum { idx = 23 }; }; template<> struct SixthDensePt<-1, 0, 0> { enum { idx = 24 }; }; template<> struct SixthDensePt< 1, 0, 0> { enum { idx = 25 }; }; template<> struct SixthDensePt< 2, 0, 0> { enum { idx = 26 }; }; template<> struct SixthDensePt< 3, 0, 0> { enum { idx = 27 }; }; template<> struct SixthDensePt<-3,-1, 0> { enum { idx = 28 }; }; template<> struct SixthDensePt<-2,-1, 0> { enum { idx = 29 }; }; template<> struct SixthDensePt<-1,-1, 0> { enum { idx = 30 }; }; template<> struct SixthDensePt< 0,-1, 0> { enum { idx = 31 }; }; template<> struct SixthDensePt< 1,-1, 0> { enum { idx = 32 }; }; template<> struct SixthDensePt< 2,-1, 0> { enum { idx = 33 }; }; template<> struct SixthDensePt< 3,-1, 0> { enum { idx = 34 }; }; template<> struct SixthDensePt<-3,-2, 0> { enum { idx = 35 }; }; template<> struct SixthDensePt<-2,-2, 0> { enum { idx = 36 }; }; template<> struct SixthDensePt<-1,-2, 0> { enum { idx = 37 }; }; template<> struct SixthDensePt< 0,-2, 0> { enum { idx = 38 }; }; template<> struct SixthDensePt< 1,-2, 0> { enum { idx = 39 }; }; template<> struct SixthDensePt< 2,-2, 0> { enum { idx = 40 }; }; template<> struct SixthDensePt< 3,-2, 0> { enum { idx = 41 }; }; template<> struct SixthDensePt<-3,-3, 0> { enum { idx = 42 }; }; template<> struct SixthDensePt<-2,-3, 0> { enum { idx = 43 }; }; template<> struct SixthDensePt<-1,-3, 0> { enum { idx = 44 }; }; template<> struct SixthDensePt< 0,-3, 0> { enum { idx = 45 }; }; template<> struct SixthDensePt< 1,-3, 0> { enum { idx = 46 }; }; template<> struct SixthDensePt< 2,-3, 0> { enum { idx = 47 }; }; template<> struct SixthDensePt< 3,-3, 0> { enum { idx = 48 }; }; template<> struct SixthDensePt<-3, 0, 3> { enum { idx = 49 }; }; template<> struct SixthDensePt<-2, 0, 3> { enum { idx = 50 }; }; template<> struct SixthDensePt<-1, 0, 3> { enum { idx = 51 }; }; template<> struct SixthDensePt< 0, 0, 3> { enum { idx = 52 }; }; template<> struct SixthDensePt< 1, 0, 3> { enum { idx = 53 }; }; template<> struct SixthDensePt< 2, 0, 3> { enum { idx = 54 }; }; template<> struct SixthDensePt< 3, 0, 3> { enum { idx = 55 }; }; template<> struct SixthDensePt<-3, 0, 2> { enum { idx = 56 }; }; template<> struct SixthDensePt<-2, 0, 2> { enum { idx = 57 }; }; template<> struct SixthDensePt<-1, 0, 2> { enum { idx = 58 }; }; template<> struct SixthDensePt< 0, 0, 2> { enum { idx = 59 }; }; template<> struct SixthDensePt< 1, 0, 2> { enum { idx = 60 }; }; template<> struct SixthDensePt< 2, 0, 2> { enum { idx = 61 }; }; template<> struct SixthDensePt< 3, 0, 2> { enum { idx = 62 }; }; template<> struct SixthDensePt<-3, 0, 1> { enum { idx = 63 }; }; template<> struct SixthDensePt<-2, 0, 1> { enum { idx = 64 }; }; template<> struct SixthDensePt<-1, 0, 1> { enum { idx = 65 }; }; template<> struct SixthDensePt< 0, 0, 1> { enum { idx = 66 }; }; template<> struct SixthDensePt< 1, 0, 1> { enum { idx = 67 }; }; template<> struct SixthDensePt< 2, 0, 1> { enum { idx = 68 }; }; template<> struct SixthDensePt< 3, 0, 1> { enum { idx = 69 }; }; template<> struct SixthDensePt<-3, 0,-1> { enum { idx = 70 }; }; template<> struct SixthDensePt<-2, 0,-1> { enum { idx = 71 }; }; template<> struct SixthDensePt<-1, 0,-1> { enum { idx = 72 }; }; template<> struct SixthDensePt< 0, 0,-1> { enum { idx = 73 }; }; template<> struct SixthDensePt< 1, 0,-1> { enum { idx = 74 }; }; template<> struct SixthDensePt< 2, 0,-1> { enum { idx = 75 }; }; template<> struct SixthDensePt< 3, 0,-1> { enum { idx = 76 }; }; template<> struct SixthDensePt<-3, 0,-2> { enum { idx = 77 }; }; template<> struct SixthDensePt<-2, 0,-2> { enum { idx = 78 }; }; template<> struct SixthDensePt<-1, 0,-2> { enum { idx = 79 }; }; template<> struct SixthDensePt< 0, 0,-2> { enum { idx = 80 }; }; template<> struct SixthDensePt< 1, 0,-2> { enum { idx = 81 }; }; template<> struct SixthDensePt< 2, 0,-2> { enum { idx = 82 }; }; template<> struct SixthDensePt< 3, 0,-2> { enum { idx = 83 }; }; template<> struct SixthDensePt<-3, 0,-3> { enum { idx = 84 }; }; template<> struct SixthDensePt<-2, 0,-3> { enum { idx = 85 }; }; template<> struct SixthDensePt<-1, 0,-3> { enum { idx = 86 }; }; template<> struct SixthDensePt< 0, 0,-3> { enum { idx = 87 }; }; template<> struct SixthDensePt< 1, 0,-3> { enum { idx = 88 }; }; template<> struct SixthDensePt< 2, 0,-3> { enum { idx = 89 }; }; template<> struct SixthDensePt< 3, 0,-3> { enum { idx = 90 }; }; template<> struct SixthDensePt< 0,-3, 3> { enum { idx = 91 }; }; template<> struct SixthDensePt< 0,-2, 3> { enum { idx = 92 }; }; template<> struct SixthDensePt< 0,-1, 3> { enum { idx = 93 }; }; template<> struct SixthDensePt< 0, 1, 3> { enum { idx = 94 }; }; template<> struct SixthDensePt< 0, 2, 3> { enum { idx = 95 }; }; template<> struct SixthDensePt< 0, 3, 3> { enum { idx = 96 }; }; template<> struct SixthDensePt< 0,-3, 2> { enum { idx = 97 }; }; template<> struct SixthDensePt< 0,-2, 2> { enum { idx = 98 }; }; template<> struct SixthDensePt< 0,-1, 2> { enum { idx = 99 }; }; template<> struct SixthDensePt< 0, 1, 2> { enum { idx = 100 }; }; template<> struct SixthDensePt< 0, 2, 2> { enum { idx = 101 }; }; template<> struct SixthDensePt< 0, 3, 2> { enum { idx = 102 }; }; template<> struct SixthDensePt< 0,-3, 1> { enum { idx = 103 }; }; template<> struct SixthDensePt< 0,-2, 1> { enum { idx = 104 }; }; template<> struct SixthDensePt< 0,-1, 1> { enum { idx = 105 }; }; template<> struct SixthDensePt< 0, 1, 1> { enum { idx = 106 }; }; template<> struct SixthDensePt< 0, 2, 1> { enum { idx = 107 }; }; template<> struct SixthDensePt< 0, 3, 1> { enum { idx = 108 }; }; template<> struct SixthDensePt< 0,-3,-1> { enum { idx = 109 }; }; template<> struct SixthDensePt< 0,-2,-1> { enum { idx = 110 }; }; template<> struct SixthDensePt< 0,-1,-1> { enum { idx = 111 }; }; template<> struct SixthDensePt< 0, 1,-1> { enum { idx = 112 }; }; template<> struct SixthDensePt< 0, 2,-1> { enum { idx = 113 }; }; template<> struct SixthDensePt< 0, 3,-1> { enum { idx = 114 }; }; template<> struct SixthDensePt< 0,-3,-2> { enum { idx = 115 }; }; template<> struct SixthDensePt< 0,-2,-2> { enum { idx = 116 }; }; template<> struct SixthDensePt< 0,-1,-2> { enum { idx = 117 }; }; template<> struct SixthDensePt< 0, 1,-2> { enum { idx = 118 }; }; template<> struct SixthDensePt< 0, 2,-2> { enum { idx = 119 }; }; template<> struct SixthDensePt< 0, 3,-2> { enum { idx = 120 }; }; template<> struct SixthDensePt< 0,-3,-3> { enum { idx = 121 }; }; template<> struct SixthDensePt< 0,-2,-3> { enum { idx = 122 }; }; template<> struct SixthDensePt< 0,-1,-3> { enum { idx = 123 }; }; template<> struct SixthDensePt< 0, 1,-3> { enum { idx = 124 }; }; template<> struct SixthDensePt< 0, 2,-3> { enum { idx = 125 }; }; template<> struct SixthDensePt< 0, 3,-3> { enum { idx = 126 }; }; } template class SixthOrderDenseStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 127; SixthOrderDenseStencil(const GridType& grid): BaseType(grid, SIZE) {} /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return SixthDensePt::idx; } private: inline void init(const Coord& ijk) { mStencil[SixthDensePt<-3, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 3, 0)); mStencil[SixthDensePt<-2, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 3, 0)); mStencil[SixthDensePt<-1, 3, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 3, 0)); mStencil[SixthDensePt< 0, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); mStencil[SixthDensePt< 1, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 3, 0)); mStencil[SixthDensePt< 2, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 3, 0)); mStencil[SixthDensePt< 3, 3, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 3, 0)); mStencil[SixthDensePt<-3, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 2, 0)); mStencil[SixthDensePt<-2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 2, 0)); mStencil[SixthDensePt<-1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 2, 0)); mStencil[SixthDensePt< 0, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[SixthDensePt< 1, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 2, 0)); mStencil[SixthDensePt< 2, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 2, 0)); mStencil[SixthDensePt< 3, 2, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 2, 0)); mStencil[SixthDensePt<-3, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 1, 0)); mStencil[SixthDensePt<-2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 1, 0)); mStencil[SixthDensePt<-1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); mStencil[SixthDensePt< 0, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[SixthDensePt< 1, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); mStencil[SixthDensePt< 2, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 1, 0)); mStencil[SixthDensePt< 3, 1, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 1, 0)); mStencil[SixthDensePt<-3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); mStencil[SixthDensePt<-2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[SixthDensePt<-1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[SixthDensePt< 1, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[SixthDensePt< 2, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[SixthDensePt< 3, 0, 0>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); mStencil[SixthDensePt<-3,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-1, 0)); mStencil[SixthDensePt<-2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-1, 0)); mStencil[SixthDensePt<-1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-1, 0)); mStencil[SixthDensePt< 0,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 0)); mStencil[SixthDensePt< 1,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-1, 0)); mStencil[SixthDensePt< 2,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-1, 0)); mStencil[SixthDensePt< 3,-1, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-1, 0)); mStencil[SixthDensePt<-3,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-2, 0)); mStencil[SixthDensePt<-2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-2, 0)); mStencil[SixthDensePt<-1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-2, 0)); mStencil[SixthDensePt< 0,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 0)); mStencil[SixthDensePt< 1,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-2, 0)); mStencil[SixthDensePt< 2,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-2, 0)); mStencil[SixthDensePt< 3,-2, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-2, 0)); mStencil[SixthDensePt<-3,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-3,-3, 0)); mStencil[SixthDensePt<-2,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-2,-3, 0)); mStencil[SixthDensePt<-1,-3, 0>::idx] = mCache.getValue(ijk.offsetBy(-1,-3, 0)); mStencil[SixthDensePt< 0,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 0)); mStencil[SixthDensePt< 1,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 1,-3, 0)); mStencil[SixthDensePt< 2,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 2,-3, 0)); mStencil[SixthDensePt< 3,-3, 0>::idx] = mCache.getValue(ijk.offsetBy( 3,-3, 0)); mStencil[SixthDensePt<-3, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 3)); mStencil[SixthDensePt<-2, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 3)); mStencil[SixthDensePt<-1, 0, 3>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 3)); mStencil[SixthDensePt< 0, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); mStencil[SixthDensePt< 1, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 3)); mStencil[SixthDensePt< 2, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 3)); mStencil[SixthDensePt< 3, 0, 3>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 3)); mStencil[SixthDensePt<-3, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 2)); mStencil[SixthDensePt<-2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 2)); mStencil[SixthDensePt<-1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 2)); mStencil[SixthDensePt< 0, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[SixthDensePt< 1, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 2)); mStencil[SixthDensePt< 2, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 2)); mStencil[SixthDensePt< 3, 0, 2>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 2)); mStencil[SixthDensePt<-3, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-3, 0, 1)); mStencil[SixthDensePt<-2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0, 1)); mStencil[SixthDensePt<-1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); mStencil[SixthDensePt< 0, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[SixthDensePt< 1, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); mStencil[SixthDensePt< 2, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0, 1)); mStencil[SixthDensePt< 3, 0, 1>::idx] = mCache.getValue(ijk.offsetBy( 3, 0, 1)); mStencil[SixthDensePt<-3, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-1)); mStencil[SixthDensePt<-2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-1)); mStencil[SixthDensePt<-1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-1)); mStencil[SixthDensePt< 0, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-1)); mStencil[SixthDensePt< 1, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-1)); mStencil[SixthDensePt< 2, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-1)); mStencil[SixthDensePt< 3, 0,-1>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-1)); mStencil[SixthDensePt<-3, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-2)); mStencil[SixthDensePt<-2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-2)); mStencil[SixthDensePt<-1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-2)); mStencil[SixthDensePt< 0, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-2)); mStencil[SixthDensePt< 1, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-2)); mStencil[SixthDensePt< 2, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-2)); mStencil[SixthDensePt< 3, 0,-2>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-2)); mStencil[SixthDensePt<-3, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-3, 0,-3)); mStencil[SixthDensePt<-2, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-2, 0,-3)); mStencil[SixthDensePt<-1, 0,-3>::idx] = mCache.getValue(ijk.offsetBy(-1, 0,-3)); mStencil[SixthDensePt< 0, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 0,-3)); mStencil[SixthDensePt< 1, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 1, 0,-3)); mStencil[SixthDensePt< 2, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 2, 0,-3)); mStencil[SixthDensePt< 3, 0,-3>::idx] = mCache.getValue(ijk.offsetBy( 3, 0,-3)); mStencil[SixthDensePt< 0,-3, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 3)); mStencil[SixthDensePt< 0,-2, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 3)); mStencil[SixthDensePt< 0,-1, 3>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 3)); mStencil[SixthDensePt< 0, 1, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 3)); mStencil[SixthDensePt< 0, 2, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 3)); mStencil[SixthDensePt< 0, 3, 3>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 3)); mStencil[SixthDensePt< 0,-3, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 2)); mStencil[SixthDensePt< 0,-2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 2)); mStencil[SixthDensePt< 0,-1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 2)); mStencil[SixthDensePt< 0, 1, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 2)); mStencil[SixthDensePt< 0, 2, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 2)); mStencil[SixthDensePt< 0, 3, 2>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 2)); mStencil[SixthDensePt< 0,-3, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-3, 1)); mStencil[SixthDensePt< 0,-2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2, 1)); mStencil[SixthDensePt< 0,-1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1, 1)); mStencil[SixthDensePt< 0, 1, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); mStencil[SixthDensePt< 0, 2, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2, 1)); mStencil[SixthDensePt< 0, 3, 1>::idx] = mCache.getValue(ijk.offsetBy( 0, 3, 1)); mStencil[SixthDensePt< 0,-3,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-1)); mStencil[SixthDensePt< 0,-2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-1)); mStencil[SixthDensePt< 0,-1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-1)); mStencil[SixthDensePt< 0, 1,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-1)); mStencil[SixthDensePt< 0, 2,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-1)); mStencil[SixthDensePt< 0, 3,-1>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-1)); mStencil[SixthDensePt< 0,-3,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-2)); mStencil[SixthDensePt< 0,-2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-2)); mStencil[SixthDensePt< 0,-1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-2)); mStencil[SixthDensePt< 0, 1,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-2)); mStencil[SixthDensePt< 0, 2,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-2)); mStencil[SixthDensePt< 0, 3,-2>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-2)); mStencil[SixthDensePt< 0,-3,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-3,-3)); mStencil[SixthDensePt< 0,-2,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-2,-3)); mStencil[SixthDensePt< 0,-1,-3>::idx] = mCache.getValue(ijk.offsetBy( 0,-1,-3)); mStencil[SixthDensePt< 0, 1,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 1,-3)); mStencil[SixthDensePt< 0, 2,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 2,-3)); mStencil[SixthDensePt< 0, 3,-3>::idx] = mCache.getValue(ijk.offsetBy( 0, 3,-3)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; }; ////////////////////////////////////////////////////////////////////// /// This is a simple 7-point nearest neighbor stencil that supports /// gradient by second-order central differencing, first-order upwinding, /// Laplacian, closest-point transform and zero-crossing test. /// /// @note For optimal random access performance this class /// includes its own grid accessor. template class GradStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 7; GradStencil(const GridType& grid): BaseType(grid, SIZE), mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])), mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) { } GradStencil(const GridType& grid, Real dx): BaseType(grid, SIZE), mInv2Dx(ValueType(0.5 / dx)), mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) { } /// @brief Return the norm square of the single-sided upwind gradient /// (computed via Gudonov's scheme) at the previously buffered location. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType normSqGrad() const { return mInvDx2 * math::GudonovsNormSqrd(mStencil[0] > 0, mStencil[0] - mStencil[1], mStencil[2] - mStencil[0], mStencil[0] - mStencil[3], mStencil[4] - mStencil[0], mStencil[0] - mStencil[5], mStencil[6] - mStencil[0]); } /// @brief Return the gradient computed at the previously buffered /// location by second order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline Vec3Type gradient() const { return Vec3Type(mStencil[2] - mStencil[1], mStencil[4] - mStencil[3], mStencil[6] - mStencil[5])*mInv2Dx; } /// @brief Return the first-order upwind gradient corresponding to the direction V. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline Vec3Type gradient(const Vec3Type& V) const { return Vec3Type(V[0]>0 ? mStencil[0] - mStencil[1] : mStencil[2] - mStencil[0], V[1]>0 ? mStencil[0] - mStencil[3] : mStencil[4] - mStencil[0], V[2]>0 ? mStencil[0] - mStencil[5] : mStencil[6] - mStencil[0])*2*mInv2Dx; } /// Return the Laplacian computed at the previously buffered /// location by second-order central differencing. inline ValueType laplacian() const { return mInvDx2 * (mStencil[1] + mStencil[2] + mStencil[3] + mStencil[4] + mStencil[5] + mStencil[6] - 6*mStencil[0]); } /// Return @c true if the sign of the value at the center point of the stencil /// is different from the signs of any of its six nearest neighbors. inline bool zeroCrossing() const { const BufferType& v = mStencil; return (v[0]>0 ? (v[1]<0 || v[2]<0 || v[3]<0 || v[4]<0 || v[5]<0 || v[6]<0) : (v[1]>0 || v[2]>0 || v[3]>0 || v[4]>0 || v[5]>0 || v[6]>0)); } /// @brief Compute the closest-point transform to a level set. /// @return the closest point in index space to the surface /// from which the level set was derived. /// /// @note This method assumes that the grid represents a level set /// with distances in world units and a simple affine transfrom /// with uniform scaling. inline Vec3Type cpt() { const Coord& ijk = BaseType::getCenterCoord(); const ValueType d = ValueType(mStencil[0] * 0.5 * mInvDx2); // distance in voxels / (2dx^2) return Vec3Type(ijk[0] - d*(mStencil[2] - mStencil[1]), ijk[1] - d*(mStencil[4] - mStencil[3]), ijk[2] - d*(mStencil[6] - mStencil[5])); } private: inline void init(const Coord& ijk) { mStencil[1] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[2] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[3] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[4] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[5] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[6] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; const ValueType mInv2Dx, mInvDx2; }; // class GradStencil //////////////////////////////////////// /// @brief This is a special 19-point stencil that supports optimal fifth-order WENO /// upwinding, second-order central differencing, Laplacian, and zero-crossing test. /// /// @note For optimal random access performance this class /// includes its own grid accessor. template class WenoStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename BaseType::BufferType BufferType; typedef typename GridType::ValueType ValueType; typedef math::Vec3 Vec3Type; static const int SIZE = 19; WenoStencil(const GridType& grid): BaseType(grid, SIZE), mDx2(ValueType(math::Pow2(grid.voxelSize()[0]))), mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])), mInvDx2(ValueType(1.0 / mDx2)) { } WenoStencil(const GridType& grid, Real dx): BaseType(grid, SIZE), mDx2(ValueType(dx * dx)), mInv2Dx(ValueType(0.5 / dx)), mInvDx2(ValueType(1.0 / mDx2)) { } /// @brief Return the norm-square of the WENO upwind gradient (computed via /// WENO upwinding and Gudonov's scheme) at the previously buffered location. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType normSqGrad() const { const BufferType& v = mStencil; #ifdef DWA_OPENVDB // SSE optimized const simd::Float4 v1(v[2]-v[1], v[ 8]-v[ 7], v[14]-v[13], 0), v2(v[3]-v[2], v[ 9]-v[ 8], v[15]-v[14], 0), v3(v[0]-v[3], v[ 0]-v[ 9], v[ 0]-v[15], 0), v4(v[4]-v[0], v[10]-v[ 0], v[16]-v[ 0], 0), v5(v[5]-v[4], v[11]-v[10], v[17]-v[16], 0), v6(v[6]-v[5], v[12]-v[11], v[18]-v[17], 0), dP_m = math::WENO5(v1, v2, v3, v4, v5, mDx2), dP_p = math::WENO5(v6, v5, v4, v3, v2, mDx2); return mInvDx2 * math::GudonovsNormSqrd(mStencil[0] > 0, dP_m, dP_p); #else const Real dP_xm = math::WENO5(v[ 2]-v[ 1],v[ 3]-v[ 2],v[ 0]-v[ 3],v[ 4]-v[ 0],v[ 5]-v[ 4],mDx2), dP_xp = math::WENO5(v[ 6]-v[ 5],v[ 5]-v[ 4],v[ 4]-v[ 0],v[ 0]-v[ 3],v[ 3]-v[ 2],mDx2), dP_ym = math::WENO5(v[ 8]-v[ 7],v[ 9]-v[ 8],v[ 0]-v[ 9],v[10]-v[ 0],v[11]-v[10],mDx2), dP_yp = math::WENO5(v[12]-v[11],v[11]-v[10],v[10]-v[ 0],v[ 0]-v[ 9],v[ 9]-v[ 8],mDx2), dP_zm = math::WENO5(v[14]-v[13],v[15]-v[14],v[ 0]-v[15],v[16]-v[ 0],v[17]-v[16],mDx2), dP_zp = math::WENO5(v[18]-v[17],v[17]-v[16],v[16]-v[ 0],v[ 0]-v[15],v[15]-v[14],mDx2); return mInvDx2*math::GudonovsNormSqrd(v[0]>0,dP_xm,dP_xp,dP_ym,dP_yp,dP_zm,dP_zp); #endif } /// Return the optimal fifth-order upwind gradient corresponding to the /// direction V. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline Vec3Type gradient(const Vec3Type& V) const { const BufferType& v = mStencil; return 2*mInv2Dx * Vec3Type( V[0]>0 ? math::WENO5(v[ 2]-v[ 1],v[ 3]-v[ 2],v[ 0]-v[ 3], v[ 4]-v[ 0],v[ 5]-v[ 4],mDx2) : math::WENO5(v[ 6]-v[ 5],v[ 5]-v[ 4],v[ 4]-v[ 0], v[ 0]-v[ 3],v[ 3]-v[ 2],mDx2), V[1]>0 ? math::WENO5(v[ 8]-v[ 7],v[ 9]-v[ 8],v[ 0]-v[ 9], v[10]-v[ 0],v[11]-v[10],mDx2) : math::WENO5(v[12]-v[11],v[11]-v[10],v[10]-v[ 0], v[ 0]-v[ 9],v[ 9]-v[ 8],mDx2), V[2]>0 ? math::WENO5(v[14]-v[13],v[15]-v[14],v[ 0]-v[15], v[16]-v[ 0],v[17]-v[16],mDx2) : math::WENO5(v[18]-v[17],v[17]-v[16],v[16]-v[ 0], v[ 0]-v[15],v[15]-v[14],mDx2)); } /// Return the gradient computed at the previously buffered /// location by second-order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline Vec3Type gradient() const { return mInv2Dx * Vec3Type( mStencil[ 4] - mStencil[ 3], mStencil[10] - mStencil[ 9], mStencil[16] - mStencil[15]); } /// Return the Laplacian computed at the previously buffered /// location by second-order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType laplacian() const { return mInvDx2 * ( mStencil[ 3] + mStencil[ 4] + mStencil[ 9] + mStencil[10] + mStencil[15] + mStencil[16] - 6*mStencil[0]); } /// Return @c true if the sign of the value at the center point of the stencil /// differs from the sign of any of its six nearest neighbors inline bool zeroCrossing() const { const BufferType& v = mStencil; return (v[ 0]>0 ? (v[ 3]<0 || v[ 4]<0 || v[ 9]<0 || v[10]<0 || v[15]<0 || v[16]<0) : (v[ 3]>0 || v[ 4]>0 || v[ 9]>0 || v[10]>0 || v[15]>0 || v[16]>0)); } private: inline void init(const Coord& ijk) { mStencil[ 1] = mCache.getValue(ijk.offsetBy(-3, 0, 0)); mStencil[ 2] = mCache.getValue(ijk.offsetBy(-2, 0, 0)); mStencil[ 3] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[ 4] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[ 5] = mCache.getValue(ijk.offsetBy( 2, 0, 0)); mStencil[ 6] = mCache.getValue(ijk.offsetBy( 3, 0, 0)); mStencil[ 7] = mCache.getValue(ijk.offsetBy( 0, -3, 0)); mStencil[ 8] = mCache.getValue(ijk.offsetBy( 0, -2, 0)); mStencil[ 9] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[10] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[11] = mCache.getValue(ijk.offsetBy( 0, 2, 0)); mStencil[12] = mCache.getValue(ijk.offsetBy( 0, 3, 0)); mStencil[13] = mCache.getValue(ijk.offsetBy( 0, 0, -3)); mStencil[14] = mCache.getValue(ijk.offsetBy( 0, 0, -2)); mStencil[15] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[16] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[17] = mCache.getValue(ijk.offsetBy( 0, 0, 2)); mStencil[18] = mCache.getValue(ijk.offsetBy( 0, 0, 3)); } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; const ValueType mDx2, mInv2Dx, mInvDx2; }; // class WenoStencil ////////////////////////////////////////////////////////////////////// template class CurvatureStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename GridType::ValueType ValueType; static const int SIZE = 19; CurvatureStencil(const GridType& grid): BaseType(grid, SIZE), mInv2Dx(ValueType(0.5 / grid.voxelSize()[0])), mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) { } CurvatureStencil(const GridType& grid, Real dx): BaseType(grid, SIZE), mInv2Dx(ValueType(0.5 / dx)), mInvDx2(ValueType(4.0 * mInv2Dx * mInv2Dx)) { } /// @brief Return the mean curvature at the previously buffered location. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType meanCurvature() { Real alpha, beta; this->meanCurvature(alpha, beta); return ValueType(alpha*mInv2Dx/math::Pow3(beta)); } /// Return the mean curvature multiplied by the norm of the /// central-difference gradient. This method is very useful for /// mean-curvature flow of level sets! /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType meanCurvatureNormGrad() { Real alpha, beta; this->meanCurvature(alpha, beta); return ValueType(alpha*mInvDx2/(2*math::Pow2(beta))); } /// Return the Laplacian computed at the previously buffered /// location by second-order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline ValueType laplacian() const { return mInvDx2 * ( mStencil[1] + mStencil[2] + mStencil[3] + mStencil[4] + mStencil[5] + mStencil[6] - 6*mStencil[0]); } /// Return the gradient computed at the previously buffered /// location by second-order central differencing. /// /// @note This method should not be called until the stencil /// buffer has been populated via a call to moveTo(ijk). inline math::Vec3 gradient() { return math::Vec3( mStencil[2] - mStencil[1], mStencil[4] - mStencil[3], mStencil[6] - mStencil[5])*mInv2Dx; } private: inline void init(const Coord &ijk) { mStencil[ 1] = mCache.getValue(ijk.offsetBy(-1, 0, 0)); mStencil[ 2] = mCache.getValue(ijk.offsetBy( 1, 0, 0)); mStencil[ 3] = mCache.getValue(ijk.offsetBy( 0, -1, 0)); mStencil[ 4] = mCache.getValue(ijk.offsetBy( 0, 1, 0)); mStencil[ 5] = mCache.getValue(ijk.offsetBy( 0, 0, -1)); mStencil[ 6] = mCache.getValue(ijk.offsetBy( 0, 0, 1)); mStencil[ 7] = mCache.getValue(ijk.offsetBy(-1, -1, 0)); mStencil[ 8] = mCache.getValue(ijk.offsetBy( 1, -1, 0)); mStencil[ 9] = mCache.getValue(ijk.offsetBy(-1, 1, 0)); mStencil[10] = mCache.getValue(ijk.offsetBy( 1, 1, 0)); mStencil[11] = mCache.getValue(ijk.offsetBy(-1, 0, -1)); mStencil[12] = mCache.getValue(ijk.offsetBy( 1, 0, -1)); mStencil[13] = mCache.getValue(ijk.offsetBy(-1, 0, 1)); mStencil[14] = mCache.getValue(ijk.offsetBy( 1, 0, 1)); mStencil[15] = mCache.getValue(ijk.offsetBy( 0, -1, -1)); mStencil[16] = mCache.getValue(ijk.offsetBy( 0, 1, -1)); mStencil[17] = mCache.getValue(ijk.offsetBy( 0, -1, 1)); mStencil[18] = mCache.getValue(ijk.offsetBy( 0, 1, 1)); } inline void meanCurvature(Real& alpha, Real& beta) const { // For performance all finite differences are unscaled wrt dx const Real Half(0.5), Quarter(0.25), Dx = Half * (mStencil[2] - mStencil[1]), Dx2 = Dx * Dx, // * 1/dx Dy = Half * (mStencil[4] - mStencil[3]), Dy2 = Dy * Dy, // * 1/dx Dz = Half * (mStencil[6] - mStencil[5]), Dz2 = Dz * Dz, // * 1/dx Dxx = mStencil[2] - 2 * mStencil[0] + mStencil[1], // * 1/dx2 Dyy = mStencil[4] - 2 * mStencil[0] + mStencil[3], // * 1/dx2 Dzz = mStencil[6] - 2 * mStencil[0] + mStencil[5], // * 1/dx2 Dxy = Quarter * (mStencil[10] - mStencil[ 8] + mStencil[7] - mStencil[ 9]), // * 1/dx2 Dxz = Quarter * (mStencil[14] - mStencil[12] + mStencil[11] - mStencil[13]), // * 1/dx2 Dyz = Quarter * (mStencil[18] - mStencil[16] + mStencil[15] - mStencil[17]); // * 1/dx2 alpha = (Dx2*(Dyy+Dzz)+Dy2*(Dxx+Dzz)+Dz2*(Dxx+Dyy)-2*(Dx*(Dy*Dxy+Dz*Dxz)+Dy*Dz*Dyz)); beta = std::sqrt(Dx2 + Dy2 + Dz2); // * 1/dx } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; const ValueType mInv2Dx, mInvDx2; }; // class CurvatureStencil ////////////////////////////////////////////////////////////////////// /// @brief Dense stencil of a given width template class DenseStencil: public BaseStencil > { public: typedef BaseStencil > BaseType; typedef typename GridType::ValueType ValueType; DenseStencil(const GridType& grid, int halfWidth) : BaseType(grid, /*size=*/math::Pow3(2 * halfWidth + 1)), mHalfWidth(halfWidth) { assert(halfWidth>0); } inline const ValueType& getCenterValue() const { return mStencil[(mStencil.size()-1)>>1]; } /// @brief Initialize the stencil buffer with the values of voxel (x, y, z) /// and its neighbors. inline void moveTo(const Coord& ijk) { BaseType::mCenter = ijk; this->init(ijk); } /// @brief Initialize the stencil buffer with the values of voxel /// (x, y, z) and its neighbors. template inline void moveTo(const IterType& iter) { BaseType::mCenter = iter.getCoord(); this->init(BaseType::mCenter); } private: /// Initialize the stencil buffer centered at (i, j, k). /// @warning The center point is NOT at mStencil[0] for this DenseStencil! inline void init(const Coord& ijk) { int n = 0; for (Coord p=ijk.offsetBy(-mHalfWidth), q=ijk.offsetBy(mHalfWidth); p[0] <= q[0]; ++p[0]) { for (p[1] = ijk[1]-mHalfWidth; p[1] <= q[1]; ++p[1]) { for (p[2] = ijk[2]-mHalfWidth; p[2] <= q[2]; ++p[2]) { mStencil[n++] = mCache.getValue(p); } } } } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; const int mHalfWidth; }; } // end math namespace } // namespace OPENVDB_VERSION_NAME } // end openvdb namespace #endif // OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Transform.cc0000644000000000000000000003627312252453157014445 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Transform.h" #include "LegacyFrustum.h" #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// Transform::Transform(const MapBase::Ptr& map): mMap(boost::const_pointer_cast(map)) { // auto-convert to simplest type if (!mMap->isType() && mMap->isLinear()) { AffineMap::Ptr affine = mMap->getAffineMap(); mMap = simplify(affine); } } Transform::Transform(const Transform& other): mMap(boost::const_pointer_cast(other.baseMap())) { } //////////////////////////////////////// // Factory methods Transform::Ptr Transform::createLinearTransform(double voxelDim) { return Transform::Ptr(new Transform( MapBase::Ptr(new UniformScaleMap(voxelDim)))); } Transform::Ptr Transform::createLinearTransform(const Mat4R& m) { return Transform::Ptr(new Transform(MapBase::Ptr(new AffineMap(m)))); } Transform::Ptr Transform::createFrustumTransform(const BBoxd& bbox, double taper, double depth, double voxelDim) { return Transform::Ptr(new Transform( NonlinearFrustumMap(bbox, taper, depth).preScale(Vec3d(voxelDim, voxelDim, voxelDim)))); } //////////////////////////////////////// void Transform::read(std::istream& is) { // Read the type name. Name type = readString(is); if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_NEW_TRANSFORM) { // Handle old-style transforms. if (type == "LinearTransform") { // First read in the old transform's base class. Coord tmpMin, tmpMax; is.read(reinterpret_cast(&tmpMin), sizeof(Coord::ValueType) * 3); is.read(reinterpret_cast(&tmpMax), sizeof(Coord::ValueType) * 3); // Second read in the old linear transform Mat4d tmpLocalToWorld, tmpWorldToLocal, tmpVoxelToLocal, tmpLocalToVoxel; tmpLocalToWorld.read(is); tmpWorldToLocal.read(is); tmpVoxelToLocal.read(is); tmpLocalToVoxel.read(is); // Convert and simplify AffineMap::Ptr affineMap(new AffineMap(tmpVoxelToLocal*tmpLocalToWorld)); mMap = simplify(affineMap); } else if (type == "FrustumTransform") { internal::LegacyFrustum legacyFrustum(is); CoordBBox bb = legacyFrustum.getBBox(); BBoxd bbox(bb.min().asVec3d(), bb.max().asVec3d() /* -Vec3d(1,1,1) */ ); double taper = legacyFrustum.getTaper(); double depth = legacyFrustum.getDepth(); double nearPlaneWidth = legacyFrustum.getNearPlaneWidth(); double nearPlaneDist = legacyFrustum.getNearPlaneDist(); const Mat4d& camxform = legacyFrustum.getCamXForm(); // create the new frustum with these parameters Mat4d xform(Mat4d::identity()); xform.setToTranslation(Vec3d(0,0, -nearPlaneDist)); xform.preScale(Vec3d(nearPlaneWidth, nearPlaneWidth, -nearPlaneWidth)); // create the linear part of the frustum (the second map) Mat4d second = xform * camxform; // we might have precision problems, the constructor for the // affine map is not forgiving (so we fix here). const Vec4d col3 = second.col(3); const Vec4d ref(0, 0, 0, 1); if (ref.eq(col3) ) { second.setCol(3, ref); } MapBase::Ptr linearMap(simplify(AffineMap(second).getAffineMap())); // note that the depth is scaled on the nearPlaneSize. // the linearMap will uniformly scale the frustum to the correct size // and rotate to align with the camera mMap = MapBase::Ptr(new NonlinearFrustumMap( bbox, taper, depth/nearPlaneWidth, linearMap)); } else { OPENVDB_THROW(IoError, "Transforms of type " + type + " are no longer supported"); } } else { // Check if the map has been registered. if (!MapRegistry::isRegistered(type)) { OPENVDB_THROW(KeyError, "Map " << type << " is not registered"); } // Create the map of the type and then read it in. mMap = math::MapRegistry::createMap(type); mMap->read(is); } } void Transform::write(std::ostream& os) const { if (!mMap) OPENVDB_THROW(IoError, "Transform does not have a map"); // Write the type-name of the map. writeString(os, mMap->type()); mMap->write(os); } //////////////////////////////////////// bool Transform::isIdentity() const { if (mMap->isLinear()) { return mMap->getAffineMap()->isIdentity(); } else if ( mMap->isType() ) { NonlinearFrustumMap::Ptr frustum = boost::static_pointer_cast(mMap); return frustum->isIdentity(); } // unknown nonlinear map type return false; } //////////////////////////////////////// void Transform::preRotate(double radians, const Axis axis) { mMap = mMap->preRotate(radians, axis); } void Transform::preTranslate(const Vec3d& t) { mMap = mMap->preTranslate(t); } void Transform::preScale(const Vec3d& s) { mMap = mMap->preScale(s); } void Transform::preScale(double s) { const Vec3d vec(s,s,s); mMap = mMap->preScale(vec); } void Transform::preShear(double shear, Axis axis0, Axis axis1) { mMap = mMap->preShear(shear, axis0, axis1); } void Transform::preMult(const Mat4d& m) { if (mMap->isLinear()) { const Mat4d currentMat4 = mMap->getAffineMap()->getMat4(); const Mat4d newMat4 = m * currentMat4; AffineMap::Ptr affineMap( new AffineMap( newMat4) ); mMap = simplify(affineMap); } else if (mMap->isType() ) { NonlinearFrustumMap::Ptr currentFrustum = boost::static_pointer_cast(mMap); const Mat4d currentMat4 = currentFrustum->secondMap().getMat4(); const Mat4d newMat4 = m * currentMat4; AffineMap affine(newMat4); NonlinearFrustumMap::Ptr frustum( new NonlinearFrustumMap( currentFrustum->getBBox(), currentFrustum->getTaper(), currentFrustum->getDepth(), affine.copy() ) ); mMap = boost::static_pointer_cast( frustum ); } } void Transform::preMult(const Mat3d& m) { Mat4d mat4 = Mat4d::identity(); mat4.setMat3(m); preMult(mat4); } void Transform::postRotate(double radians, const Axis axis) { mMap = mMap->postRotate(radians, axis); } void Transform::postTranslate(const Vec3d& t) { mMap = mMap->postTranslate(t); } void Transform::postScale(const Vec3d& s) { mMap = mMap->postScale(s); } void Transform::postScale(double s) { const Vec3d vec(s,s,s); mMap = mMap->postScale(vec); } void Transform::postShear(double shear, Axis axis0, Axis axis1) { mMap = mMap->postShear(shear, axis0, axis1); } void Transform::postMult(const Mat4d& m) { if (mMap->isLinear()) { const Mat4d currentMat4 = mMap->getAffineMap()->getMat4(); const Mat4d newMat4 = currentMat4 * m; AffineMap::Ptr affineMap( new AffineMap( newMat4) ); mMap = simplify(affineMap); } else if (mMap->isType() ) { NonlinearFrustumMap::Ptr currentFrustum = boost::static_pointer_cast(mMap); const Mat4d currentMat4 = currentFrustum->secondMap().getMat4(); const Mat4d newMat4 = currentMat4 * m; AffineMap affine(newMat4); NonlinearFrustumMap::Ptr frustum( new NonlinearFrustumMap( currentFrustum->getBBox(), currentFrustum->getTaper(), currentFrustum->getDepth(), affine.copy() ) ); mMap = boost::static_pointer_cast( frustum ); } } void Transform::postMult(const Mat3d& m) { Mat4d mat4 = Mat4d::identity(); mat4.setMat3(m); postMult(mat4); } //////////////////////////////////////// // Utility methods void calculateBounds(const Transform& t, const Vec3d& minWS, const Vec3d& maxWS, Vec3d& minIS, Vec3d& maxIS) { /// the pre-image of the 8 corners of the box Vec3d corners[8]; corners[0] = minWS; corners[1] = Vec3d(maxWS(0), minWS(1), minWS(2)); corners[2] = Vec3d(maxWS(0), maxWS(1), minWS(2)); corners[3] = Vec3d(minWS(0), maxWS(1), minWS(2)); corners[4] = Vec3d(minWS(0), minWS(1), maxWS(2)); corners[5] = Vec3d(maxWS(0), minWS(1), maxWS(2)); corners[6] = maxWS; corners[7] = Vec3d(minWS(0), maxWS(1), maxWS(2)); Vec3d pre_image; minIS = t.worldToIndex(corners[0]); maxIS = minIS; for (int i = 1; i < 8; ++i) { pre_image = t.worldToIndex(corners[i]); for (int j = 0; j < 3; ++j) { minIS(j) = std::min(minIS(j), pre_image(j)); maxIS(j) = std::max(maxIS(j), pre_image(j)); } } } //////////////////////////////////////// bool Transform::operator==(const Transform& other) const { if (!this->voxelSize().eq(other.voxelSize())) return false; if (this->mapType() == other.mapType()) { return this->baseMap()->isEqual(*other.baseMap()); } if (this->isLinear() && other.isLinear()) { // promote both maps to mat4 form and compare return ( *(this->baseMap()->getAffineMap()) == *(other.baseMap()->getAffineMap()) ); } return this->baseMap()->isEqual(*other.baseMap()); } //////////////////////////////////////// void Transform::print(std::ostream& os, const std::string& indent) const { struct Local { // Print a Vec4d more compactly than Vec4d::str() does. static std::string rowAsString(const Vec4d& row) { std::ostringstream ostr; ostr << "[" << std::setprecision(3) << row[0] << ", " << row[1] << ", " << row[2] << ", " << row[3] << "] "; return ostr.str(); } }; // Write to a string stream so that I/O manipulators don't affect the output stream. std::ostringstream ostr; { Vec3d dim = this->voxelSize(); if (dim.eq(Vec3d(dim[0]))) { ostr << indent << std::left << "voxel size: " << std::setprecision(3) << dim[0]; } else { ostr << indent << std::left << "voxel dimensions: [" << std::setprecision(3) << dim[0] << ", " << dim[1] << ", " << dim[2] << "]"; } ostr << "\n"; } if (this->isLinear()) { openvdb::Mat4R v2w = this->baseMap()->getAffineMap()->getMat4(); ostr << indent << std::left << "index to world:\n"; for (int row = 0; row < 4; ++row) { ostr << indent << " " << std::left << Local::rowAsString(v2w[row]) << "\n"; } } else if (this->mapType() == NonlinearFrustumMap::mapType()) { const NonlinearFrustumMap& frustum = static_cast(*this->baseMap()); const openvdb::Mat4R linear = this->baseMap()->getAffineMap()->getMat4(); std::vector linearRow; size_t w = 0; for (int row = 0; row < 4; ++row) { std::string str = Local::rowAsString(linear[row]); w = std::max(w, str.size()); linearRow.push_back(str); } w = std::max(w, 30); // Print rows of the linear component matrix side-by-side with frustum parameters. ostr << indent << std::left << std::setw(w) << "linear:" << " frustum:\n"; ostr << indent << " " << std::left << std::setw(w) << linearRow[0] << " taper: " << frustum.getTaper() << "\n"; ostr << indent << " " << std::left << std::setw(w) << linearRow[1] << " depth: " << frustum.getDepth() << "\n"; std::ostringstream ostmp; ostmp << indent << " " << std::left << std::setw(w) << linearRow[2] << " bounds: " << frustum.getBBox(); if (ostmp.str().size() < 79) { ostr << ostmp.str() << "\n"; ostr << indent << " " << std::left << std::setw(w) << linearRow[3] << "\n"; } else { // If the frustum bounding box doesn't fit on one line, split it into two lines. ostr << indent << " " << std::left << std::setw(w) << linearRow[2] << " bounds: " << frustum.getBBox().min() << " ->\n"; ostr << indent << " " << std::left << std::setw(w) << linearRow[3] << " " << frustum.getBBox().max() << "\n"; } } else { /// @todo Handle other map types. } os << ostr.str(); } //////////////////////////////////////// std::ostream& operator<<(std::ostream& os, const Transform& t) { os << "Transform type: " << t.baseMap()->type() << std::endl; os << t.baseMap()->str() << std::endl; return os; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/QuantizedUnitVec.cc0000644000000000000000000000610612252453157015724 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "QuantizedUnitVec.h" #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// bool QuantizedUnitVec::sInitialized = false; float QuantizedUnitVec::sNormalizationWeights[MASK_SLOTS + 1]; // Declare this at file scope to ensure thread-safe initialization. tbb::mutex sInitMutex; //////////////////////////////////////// void QuantizedUnitVec::init() { tbb::mutex::scoped_lock(sInitMutex); if (!sInitialized) { OPENVDB_START_THREADSAFE_STATIC_WRITE sInitialized = true; uint16_t xbits, ybits; double x, y, z, w; for (uint16_t b = 0; b < 8192; ++b) { xbits = (b & MASK_XSLOT) >> 7; ybits = b & MASK_YSLOT; if ((xbits + ybits) > 126) { xbits = 127 - xbits; ybits = 127 - ybits; } x = double(xbits); y = double(ybits); z = double(126 - ybits - xbits); w = 1.0 / std::sqrt(x*x + y*y + z*z); sNormalizationWeights[b] = float(w); } OPENVDB_FINISH_THREADSAFE_STATIC_WRITE } } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Proximity.h0000644000000000000000000001323712252453157014333 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_PROXIMITY_HAS_BEEN_INCLUDED #define OPENVDB_MATH_PROXIMITY_HAS_BEEN_INCLUDED #include //#include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief Closest Point on Triangle to Point. Given a triangle @c abc and a /// point @c p, returns the point on @c abc closest to @c p and the /// corresponding barycentric coordinates. /// /// @note Algorithms from "Real-Time Collision Detection" pg 136 to 142 by Christer Ericson. /// The closest point is obtained by first determining which of the triangles /// Voronoi feature regions @c p is in and then computing the orthogonal projection /// of @c p onto the corresponding feature. /// /// @param a The triangle's first vertex point. /// @param b The triangle's second vertex point. /// @param c The triangle's third vertex point. /// @param p Point to compute the closest point on @c abc for. /// @param uvw Barycentric coordinates, computed and returned. OPENVDB_API Vec3d closestPointOnTriangleToPoint( const Vec3d& a, const Vec3d& b, const Vec3d& c, const Vec3d& p, Vec3d& uvw); /// @brief Closest Point on Line Segment to Point. Given segment @c ab and /// point @c p, returns the point on @c ab closest to @c p and @c t the /// parametric distance to @c b. /// /// @param a The segments's first vertex point. /// @param b The segments's second vertex point. /// @param p Point to compute the closest point on @c ab for. /// @param t Parametric distance to @c b. OPENVDB_API Vec3d closestPointOnSegmentToPoint( const Vec3d& a, const Vec3d& b, const Vec3d& p, double& t); //////////////////////////////////////// // DEPRECATED METHODS /// @brief Squared distance of a line segment p(t) = (1-t)*p0 + t*p1 to point. /// @return the closest point on the line segment as a function of t OPENVDB_API OPENVDB_DEPRECATED double sLineSeg3ToPointDistSqr(const Vec3d &p0, const Vec3d &p1, const Vec3d &point, double &t, double epsilon = 1e-10); /// @brief Slightly modified version of the algorithm described in "Geometric Tools for /// Computer Graphics" pg 376 to 382 by Schneider and Eberly. Extended to handle /// the case of a degenerate triangle. Also returns barycentric rather than /// (s,t) coordinates. /// /// Basic Idea (See book for details): /// /// Write the equation of the line as /// /// T(s,t) = v0 + s*(v1-v0) + t*(v2-v0) /// /// Minimize the quadratic function /// /// || T(s,t) - point || ^2 /// /// by solving for when the gradient is 0. This can be done without any /// square roots. /// /// If the resulting solution satisfies 0 <= s + t <= 1, then the solution lies /// on the interior of the triangle, and we are done (region 0). If it does /// not then the closest solution lies on a boundary and we have to solve for /// it by solving a 1D problem where we use one variable as free say "s" and /// set the other variable t = (1-s) /// /// @return the closest point on the triangle and barycentric coordinates. OPENVDB_API OPENVDB_DEPRECATED double sTri3ToPointDistSqr(const Vec3d &v0, const Vec3d &v1, const Vec3d &v2, const Vec3d &point, Vec2d &uv, double epsilon); /// @return the closest point on the triangle. static inline OPENVDB_DEPRECATED double triToPtnDistSqr(const Vec3d &v0, const Vec3d &v1, const Vec3d &v2, const Vec3d &point) { Vec3d cpt, uvw; cpt = closestPointOnTriangleToPoint(v0, v1, v2, point, uvw); return (cpt - point).lengthSqr(); } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_MESH_TO_VOLUME_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Coord.h0000644000000000000000000004212212252453157013370 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_COORD_HAS_BEEN_INCLUDED #define OPENVDB_MATH_COORD_HAS_BEEN_INCLUDED #include #include "Math.h" #include "Vec3.h" namespace tbb { class split; } // forward declaration namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief Signed (x, y, z) 32-bit integer coordinates class Coord { public: typedef int32_t Int32; typedef uint32_t Index32; typedef Vec3 Vec3i; typedef Vec3 Vec3I; typedef Int32 ValueType; typedef std::numeric_limits Limits; Coord() { mVec[0] = mVec[1] = mVec[2] = 0; } explicit Coord(Int32 xyz) { mVec[0] = mVec[1] = mVec[2] = xyz; } Coord(Int32 x, Int32 y, Int32 z) { mVec[0] = x; mVec[1] = y; mVec[2] = z; } explicit Coord(const Vec3i& v) { mVec[0] = v[0]; mVec[1] = v[1]; mVec[2] = v[2]; } explicit Coord(const Vec3I& v) { mVec[0] = Int32(v[0]); mVec[1] = Int32(v[1]); mVec[2] = Int32(v[2]); } explicit Coord(const Int32* v) { mVec[0] = v[0]; mVec[1] = v[1]; mVec[2] = v[2]; } /// @brief Return the smallest possible coordinate static const Coord& min() { static const Coord sMin(Limits::min()); return sMin; } /// @brief Return the largest possible coordinate static const Coord& max() { static const Coord sMax(Limits::max()); return sMax; } /// @brief Return @a xyz rounded to the closest integer coordinates /// (cell centered conversion). template static Coord round(const Vec3& xyz) { return Coord(Int32(Round(xyz[0])), Int32(Round(xyz[1])), Int32(Round(xyz[2]))); } /// @brief Return the largest integer coordinates that are not greater /// than @a xyz (node centered conversion). template static Coord floor(const Vec3& xyz) { return Coord(Int32(Floor(xyz[0])), Int32(Floor(xyz[1])), Int32(Floor(xyz[2]))); } /// @brief Return the largest integer coordinates that are not greater /// than @a xyz+1 (node centered conversion). template static Coord ceil(const Vec3& xyz) { return Coord(Int32(Ceil(xyz[0])), Int32(Ceil(xyz[1])), Int32(Ceil(xyz[2]))); } Coord& reset(Int32 x, Int32 y, Int32 z) { mVec[0] = x; mVec[1] = y; mVec[2] = z; this->dirty(); return *this; } Coord& reset(Int32 xyz) { return this->reset(xyz, xyz, xyz); } Coord& setX(Int32 x) { mVec[0] = x; dirty(); return *this; } Coord& setY(Int32 y) { mVec[1] = y; dirty(); return *this; } Coord& setZ(Int32 z) { mVec[2] = z; dirty(); return *this; } Coord& offset(Int32 dx, Int32 dy, Int32 dz) { mVec[0]+=dx; mVec[1]+=dy; mVec[2]+=dz; this->dirty(); return *this; } Coord& offset(Int32 n) { return this->offset(n, n, n); } Coord offsetBy(Int32 dx, Int32 dy, Int32 dz) const { return Coord(mVec[0] + dx, mVec[1] + dy, mVec[2] + dz); } Coord offsetBy(Int32 n) const { return offsetBy(n, n, n); } Coord& operator+=(const Coord& rhs) { mVec[0] += rhs[0]; mVec[1] += rhs[1]; mVec[2] += rhs[2]; return *this; } Coord& operator-=(const Coord& rhs) { mVec[0] -= rhs[0]; mVec[1] -= rhs[1]; mVec[2] -= rhs[2]; return *this; } Coord operator+(const Coord& rhs) const { return Coord(mVec[0] + rhs[0], mVec[1] + rhs[1], mVec[2] + rhs[2]); } Coord operator-(const Coord& rhs) const { return Coord(mVec[0] - rhs[0], mVec[1] - rhs[1], mVec[2] - rhs[2]); } Coord operator-() const { return Coord(-mVec[0], -mVec[1], -mVec[2]); } Coord operator>> (size_t n) const { return Coord(mVec[0]>>n, mVec[1]>>n, mVec[2]>>n); } Coord operator<< (size_t n) const { return Coord(mVec[0]<>=(size_t n) { mVec[0]>>=n; mVec[1]>>=n; mVec[2]>>=n; dirty(); return *this; } Coord operator& (Int32 n) const { return Coord(mVec[0] & n, mVec[1] & n, mVec[2] & n); } Coord operator| (Int32 n) const { return Coord(mVec[0] | n, mVec[1] | n, mVec[2] | n); } Coord& operator&= (Int32 n) { mVec[0]&=n; mVec[1]&=n; mVec[2]&=n; dirty(); return *this; } Coord& operator|= (Int32 n) { mVec[0]|=n; mVec[1]|=n; mVec[2]|=n; dirty(); return *this; } Int32 x() const { return mVec[0]; } Int32 y() const { return mVec[1]; } Int32 z() const { return mVec[2]; } Int32 operator[](size_t i) const { assert(i < 3); return mVec[i]; } Int32& x() { dirty(); return mVec[0]; } Int32& y() { dirty(); return mVec[1]; } Int32& z() { dirty(); return mVec[2]; } Int32& operator[](size_t i) { assert(i < 3); dirty(); return mVec[i]; } const Int32* asPointer() const { return mVec; } Int32* asPointer() { dirty(); return mVec; } Vec3d asVec3d() const { return Vec3d(double(mVec[0]), double(mVec[1]), double(mVec[2])); } Vec3s asVec3s() const { return Vec3s(float(mVec[0]), float(mVec[1]), float(mVec[2])); } Vec3i asVec3i() const { return Vec3i(mVec); } Vec3I asVec3I() const { return Vec3I(Index32(mVec[0]), Index32(mVec[1]), Index32(mVec[2])); } void asXYZ(Int32& x, Int32& y, Int32& z) const { x = mVec[0]; y = mVec[1]; z = mVec[2]; } bool operator==(const Coord& rhs) const { return (mVec[0] == rhs.mVec[0] && mVec[1] == rhs.mVec[1] && mVec[2] == rhs.mVec[2]); } bool operator!=(const Coord& rhs) const { return !(*this == rhs); } /// Lexicographic less than bool operator<(const Coord& rhs) const { return this->x() < rhs.x() ? true : this->x() > rhs.x() ? false : this->y() < rhs.y() ? true : this->y() > rhs.y() ? false : this->z() < rhs.z() ? true : false; } /// Lexicographic less than or equal to bool operator<=(const Coord& rhs) const { return this->x() < rhs.x() ? true : this->x() > rhs.x() ? false : this->y() < rhs.y() ? true : this->y() > rhs.y() ? false : this->z() <=rhs.z() ? true : false; } /// Lexicographic greater than bool operator>(const Coord& rhs) const { return !(*this <= rhs); } /// Lexicographic greater than or equal to bool operator>=(const Coord& rhs) const { return !(*this < rhs); } //HashType hash() { if (!mHash) { mHash = ...; } return mHash; } /// Perform a component-wise minimum with the other Coord. void minComponent(const Coord& other) { mVec[0] = std::min(mVec[0], other.mVec[0]); mVec[1] = std::min(mVec[1], other.mVec[1]); mVec[2] = std::min(mVec[2], other.mVec[2]); } /// Perform a component-wise maximum with the other Coord. void maxComponent(const Coord& other) { mVec[0] = std::max(mVec[0], other.mVec[0]); mVec[1] = std::max(mVec[1], other.mVec[1]); mVec[2] = std::max(mVec[2], other.mVec[2]); } /// Return the component-wise minimum of the two Coords. static inline Coord minComponent(const Coord& lhs, const Coord& rhs) { return Coord(std::min(lhs.x(), rhs.x()), std::min(lhs.y(), rhs.y()), std::min(lhs.z(), rhs.z())); } /// Return the component-wise maximum of the two Coords. static inline Coord maxComponent(const Coord& lhs, const Coord& rhs) { return Coord(std::max(lhs.x(), rhs.x()), std::max(lhs.y(), rhs.y()), std::max(lhs.z(), rhs.z())); } static inline bool lessThan(const Coord& a, const Coord& b) { return (a[0] < b[0] || a[1] < b[1] || a[2] < b[2]); } /// @brief Return the index (0, 1 or 2) with the smallest value. size_t minIndex() const { return MinIndex(mVec); } /// @brief Return the index (0, 1 or 2) with the largest value. size_t maxIndex() const { return MaxIndex(mVec); } void read(std::istream& is) { is.read(reinterpret_cast(mVec), sizeof(mVec)); } void write(std::ostream& os) const { os.write(reinterpret_cast(mVec), sizeof(mVec)); } private: //no-op for now void dirty() { /*mHash.clear();*/ } Int32 mVec[3]; //HashType mHash; }; // class Coord //////////////////////////////////////// /// @brief Axis-aligned bounding box of signed integer coordinates /// @note The range of the integer coordinates, [min, max], is inclusive. /// Thus, a bounding box with min = max is not empty but rather encloses /// a single coordinate. class CoordBBox { public: typedef uint64_t Index64; typedef Coord::ValueType ValueType; /// @brief The default constructor produces an empty bounding box. CoordBBox(): mMin(Coord::max()), mMax(Coord::min()) {} /// @brief Construct a bounding box with the given @a min and @a max bounds. CoordBBox(const Coord& min, const Coord& max): mMin(min), mMax(max) {} /// @brief Splitting constructor for use in TBB ranges /// @note The other bounding box is assumed to be divisible. CoordBBox(CoordBBox& other, const tbb::split&): mMin(other.mMin), mMax(other.mMax) { assert(this->is_divisible()); const size_t n = this->maxExtent(); mMax[n] = (mMin[n] + mMax[n]) >> 1; other.mMin[n] = mMax[n] + 1; } static CoordBBox createCube(const Coord& min, ValueType dim) { return CoordBBox(min, min.offsetBy(dim - 1)); } /// Return an "infinite" bounding box, as defined by the Coord value range. static CoordBBox inf() { return CoordBBox(Coord::min(), Coord::max()); } const Coord& min() const { return mMin; } const Coord& max() const { return mMax; } Coord& min() { return mMin; } Coord& max() { return mMax; } void reset() { mMin = Coord::max(); mMax = Coord::min(); } void reset(const Coord& min, const Coord& max) { mMin = min; mMax = max; } void resetToCube(const Coord& min, ValueType dim) { mMin = min; mMax = min.offsetBy(dim - 1); } /// @note The start coordinate is inclusive. Coord getStart() const { return mMin; } /// @note The end coordinate is exclusive. Coord getEnd() const { return mMax.offsetBy(1); } bool operator==(const CoordBBox& rhs) const { return mMin == rhs.mMin && mMax == rhs.mMax; } bool operator!=(const CoordBBox& rhs) const { return !(*this == rhs); } bool empty() const { return (mMin[0] > mMax[0] || mMin[1] > mMax[1] || mMin[2] > mMax[2]); } //@{ /// Return @c true if this bounding box is nonempty operator bool() const { return !this->empty(); } bool hasVolume() const { return !this->empty(); } //@} /// Return the floating-point position of the center of this bounding box. Vec3d getCenter() const { return 0.5 * Vec3d((mMin + mMax).asPointer()); } /// @brief Return the dimensions of the coordinates spanned by this bounding box. /// @note Since coordinates are inclusive, a bounding box with min = max /// has dimensions of (1, 1, 1). Coord dim() const { return mMax.offsetBy(1) - mMin; } /// @todo deprecate - use dim instead Coord extents() const { return this->dim(); } /// @brief Return the integer volume of coordinates spanned by this bounding box. /// @note Since coordinates are inclusive, a bounding box with min = max has volume one. Index64 volume() const { const Coord d = this->dim(); return Index64(d[0]) * Index64(d[1]) * Index64(d[2]); } /// Return @c true if this bounding box can be subdivided [mainly for use by TBB]. bool is_divisible() const { return mMin[0]dim().minIndex(); } /// @brief Return the index (0, 1 or 2) of the longest axis. size_t maxExtent() const { return this->dim().maxIndex(); } /// Return @c true if point (x, y, z) is inside this bounding box. bool isInside(const Coord& xyz) const { return !(Coord::lessThan(xyz,mMin) || Coord::lessThan(mMax,xyz)); } /// Return @c true if the given bounding box is inside this bounding box. bool isInside(const CoordBBox& b) const { return !(Coord::lessThan(b.mMin,mMin) || Coord::lessThan(mMax,b.mMax)); } /// Return @c true if the given bounding box overlaps with this bounding box. bool hasOverlap(const CoordBBox& b) const { return !(Coord::lessThan(mMax,b.mMin) || Coord::lessThan(b.mMax,mMin)); } /// Pad this bounding box with the specified padding. void expand(ValueType padding) { mMin.offset(-padding); mMax.offset( padding); } /// Expand this bounding box to enclose point (x, y, z). void expand(const Coord& xyz) { mMin.minComponent(xyz); mMax.maxComponent(xyz); } /// Union this bounding box with the given bounding box. void expand(const CoordBBox& bbox) { mMin.minComponent(bbox.min()); mMax.maxComponent(bbox.max()); } /// Intersect this bounding box with the given bounding box. void intersect(const CoordBBox& bbox) { mMin.maxComponent(bbox.min()); mMax.minComponent(bbox.max()); } /// @brief Union this bounding box with the cubical bounding box /// of the given size and with the given minimum coordinates. void expand(const Coord& min, Coord::ValueType dim) { mMin.minComponent(min); mMax.maxComponent(min.offsetBy(dim-1)); } /// Translate this bounding box by @f$(t_x, t_y, t_z)@f$. void translate(const Coord& t) { mMin += t; mMax += t; } /// Unserialize this bounding box from the given stream. void read(std::istream& is) { mMin.read(is); mMax.read(is); } /// Serialize this bounding box to the given stream. void write(std::ostream& os) const { mMin.write(os); mMax.write(os); } private: Coord mMin, mMax; }; // class CoordBBox //////////////////////////////////////// inline std::ostream& operator<<(std::ostream& os, const Coord& xyz) { os << xyz.asVec3i(); return os; } //@{ /// Allow a Coord to be added to or subtracted from a Vec3. template inline Vec3::type> operator+(const Vec3& v0, const Coord& v1) { Vec3::type> result(v0); result[0] += v1[0]; result[1] += v1[1]; result[2] += v1[2]; return result; } template inline Vec3::type> operator+(const Coord& v1, const Vec3& v0) { Vec3::type> result(v0); result[0] += v1[0]; result[1] += v1[1]; result[2] += v1[2]; return result; } //@} //@{ /// Allow a Coord to be subtracted from a Vec3. template inline Vec3::type> operator-(const Vec3& v0, const Coord& v1) { Vec3::type> result(v0); result[0] -= v1[0]; result[1] -= v1[1]; result[2] -= v1[2]; return result; } template inline Vec3::type> operator-(const Coord& v1, const Vec3& v0) { Vec3::type> result(v0); result[0] -= v1[0]; result[1] -= v1[1]; result[2] -= v1[2]; return -result; } //@} inline std::ostream& operator<<(std::ostream& os, const CoordBBox& b) { os << b.min() << " -> " << b.max(); return os; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_COORD_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Mat3.h0000644000000000000000000006406012252453157013133 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_MAT3_H_HAS_BEEN_INCLUDED #define OPENVDB_MATH_MAT3_H_HAS_BEEN_INCLUDED #include #include #include #include #include "Vec3.h" #include "Mat.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Vec3; template class Mat4; template class Quat; /// @class Mat3 Mat3.h /// @brief 3x3 matrix class. template class Mat3: public Mat<3, T> { public: /// Data type held by the matrix. typedef T value_type; typedef T ValueType; typedef Mat<3, T> MyBase; /// Trivial constructor, the matrix is NOT initialized Mat3() {} /// Constructor given the quaternion rotation, e.g. Mat3f m(q); /// The quaternion is normalized and used to construct the matrix Mat3(const Quat &q) { setToRotation(q); } /// Constructor given array of elements, the ordering is in row major form: /** @verbatim a b c d e f g h i @endverbatim */ template Mat3(Source a, Source b, Source c, Source d, Source e, Source f, Source g, Source h, Source i) { MyBase::mm[0] = a; MyBase::mm[1] = b; MyBase::mm[2] = c; MyBase::mm[3] = d; MyBase::mm[4] = e; MyBase::mm[5] = f; MyBase::mm[6] = g; MyBase::mm[7] = h; MyBase::mm[8] = i; } // constructor1Test /// Construct matrix given basis vectors (columns) template Mat3(const Vec3 &v1, const Vec3 &v2, const Vec3 &v3) { setBasis(v1, v2, v3); } /// Constructor given array of elements, the ordering is in row major form:\n /// a[0] a[1] a[2]\n /// a[3] a[4] a[5]\n /// a[6] a[7] a[8]\n template Mat3(Source *a) { MyBase::mm[0] = a[0]; MyBase::mm[1] = a[1]; MyBase::mm[2] = a[2]; MyBase::mm[3] = a[3]; MyBase::mm[4] = a[4]; MyBase::mm[5] = a[5]; MyBase::mm[6] = a[6]; MyBase::mm[7] = a[7]; MyBase::mm[8] = a[8]; } // constructor1Test /// Copy constructor Mat3(const Mat<3, T> &m) { for (int i=0; i<3; ++i) { for (int j=0; j<3; ++j) { MyBase::mm[i*3 + j] = m[i][j]; } } } /// Conversion constructor template explicit Mat3(const Mat3 &m) { for (int i=0; i<3; ++i) { for (int j=0; j<3; ++j) { MyBase::mm[i*3 + j] = m[i][j]; } } } /// Conversion from Mat4 (copies top left) explicit Mat3(const Mat4 &m) { for (int i=0; i<3; ++i) { for (int j=0; j<3; ++j) { MyBase::mm[i*3 + j] = m[i][j]; } } } /// Predefined constant for identity matrix static const Mat3& identity() { return sIdentity; } /// Predefined constant for zero matrix static const Mat3& zero() { return sZero; } /// Set ith row to vector v void setRow(int i, const Vec3 &v) { // assert(i>=0 && i<3); int i3 = i * 3; MyBase::mm[i3+0] = v[0]; MyBase::mm[i3+1] = v[1]; MyBase::mm[i3+2] = v[2]; } // rowColumnTest /// Get ith row, e.g. Vec3d v = m.row(1); Vec3 row(int i) const { // assert(i>=0 && i<3); return Vec3((*this)(i,0), (*this)(i,1), (*this)(i,2)); } // rowColumnTest /// Set jth column to vector v void setCol(int j, const Vec3& v) { // assert(j>=0 && j<3); MyBase::mm[0+j] = v[0]; MyBase::mm[3+j] = v[1]; MyBase::mm[6+j] = v[2]; } // rowColumnTest /// Get jth column, e.g. Vec3d v = m.col(0); Vec3 col(int j) const { // assert(j>=0 && j<3); return Vec3((*this)(0,j), (*this)(1,j), (*this)(2,j)); } // rowColumnTest // NB: The following two methods were changed to // work around a gccWS5 compiler issue related to strict // aliasing (see FX-475). //@{ /// Array style reference to ith row /// e.g. m[1][2] = 4; T* operator[](int i) { return &(MyBase::mm[i*3]); } const T* operator[](int i) const { return &(MyBase::mm[i*3]); } //@} T* asPointer() {return MyBase::mm;} const T* asPointer() const {return MyBase::mm;} /// Alternative indexed reference to the elements /// Note that the indices are row first and column second. /// e.g. m(0,0) = 1; T& operator()(int i, int j) { // assert(i>=0 && i<3); // assert(j>=0 && j<3); return MyBase::mm[3*i+j]; } // trivial /// Alternative indexed constant reference to the elements, /// Note that the indices are row first and column second. /// e.g. float f = m(1,0); T operator()(int i, int j) const { // assert(i>=0 && i<3); // assert(j>=0 && j<3); return MyBase::mm[3*i+j]; } // trivial /// Set the columns of "this" matrix to the vectors v1, v2, v3 void setBasis(const Vec3 &v1, const Vec3 &v2, const Vec3 &v3) { MyBase::mm[0] = v1[0]; MyBase::mm[1] = v1[1]; MyBase::mm[2] = v1[2]; MyBase::mm[3] = v2[0]; MyBase::mm[4] = v2[1]; MyBase::mm[5] = v2[2]; MyBase::mm[6] = v3[0]; MyBase::mm[7] = v3[1]; MyBase::mm[8] = v3[2]; } // setBasisTest /// Set diagonal and symmetric triangular components void setSymmetric(const Vec3 &vdiag, const Vec3 &vtri) { MyBase::mm[0] = vdiag[0]; MyBase::mm[1] = vtri[0]; MyBase::mm[2] = vtri[1]; MyBase::mm[3] = vtri[0]; MyBase::mm[4] = vdiag[1]; MyBase::mm[5] = vtri[2]; MyBase::mm[6] = vtri[1]; MyBase::mm[7] = vtri[2]; MyBase::mm[8] = vdiag[2]; } // setSymmetricTest /// Returns matrix with prescribed diagonal and symmetric triangular /// components static Mat3 symmetric(const Vec3 &vdiag, const Vec3 &vtri) { return Mat3( vdiag[0], vtri[0], vtri[1], vtri[0], vdiag[1], vtri[2], vtri[1], vtri[2], vdiag[2] ); } /// Set the matrix as cross product of the given vector void setSkew(const Vec3 &v) {*this = skew(v);} /// @brief Set this matrix to the rotation matrix specified by the quaternion /// @details The quaternion is normalized and used to construct the matrix. /// Note that the matrix is transposed to match post-multiplication semantics. void setToRotation(const Quat &q) {*this = rotation >(q);} /// @brief Set this matrix to the rotation specified by @a axis and @a angle /// @details The axis must be unit vector void setToRotation(const Vec3 &axis, T angle) {*this = rotation >(axis, angle);} /// Set this matrix to zero void setZero() { MyBase::mm[0] = 0; MyBase::mm[1] = 0; MyBase::mm[2] = 0; MyBase::mm[3] = 0; MyBase::mm[4] = 0; MyBase::mm[5] = 0; MyBase::mm[6] = 0; MyBase::mm[7] = 0; MyBase::mm[8] = 0; } // trivial /// Set "this" matrix to identity void setIdentity() { MyBase::mm[0] = 1; MyBase::mm[1] = 0; MyBase::mm[2] = 0; MyBase::mm[3] = 0; MyBase::mm[4] = 1; MyBase::mm[5] = 0; MyBase::mm[6] = 0; MyBase::mm[7] = 0; MyBase::mm[8] = 1; } // trivial /// Assignment operator template const Mat3& operator=(const Mat3 &m) { const Source *src = m.asPointer(); // don't suppress type conversion warnings std::copy(src, (src + this->numElements()), MyBase::mm); return *this; } // opEqualToTest /// Test if "this" is equivalent to m with tolerance of eps value bool eq(const Mat3 &m, T eps=1.0e-8) const { return (isApproxEqual(MyBase::mm[0],m.mm[0],eps) && isApproxEqual(MyBase::mm[1],m.mm[1],eps) && isApproxEqual(MyBase::mm[2],m.mm[2],eps) && isApproxEqual(MyBase::mm[3],m.mm[3],eps) && isApproxEqual(MyBase::mm[4],m.mm[4],eps) && isApproxEqual(MyBase::mm[5],m.mm[5],eps) && isApproxEqual(MyBase::mm[6],m.mm[6],eps) && isApproxEqual(MyBase::mm[7],m.mm[7],eps) && isApproxEqual(MyBase::mm[8],m.mm[8],eps)); } // trivial /// Negation operator, for e.g. m1 = -m2; Mat3 operator-() const { return Mat3( -MyBase::mm[0], -MyBase::mm[1], -MyBase::mm[2], -MyBase::mm[3], -MyBase::mm[4], -MyBase::mm[5], -MyBase::mm[6], -MyBase::mm[7], -MyBase::mm[8] ); } // trivial /// Multiplication operator, e.g. M = scalar * M; // friend Mat3 operator*(T scalar, const Mat3& m) { // return m*scalar; // } /// @brief Returns m, where \f$m_{i,j} *= scalar\f$ for \f$i, j \in [0, 2]\f$ template const Mat3& operator*=(S scalar) { MyBase::mm[0] *= scalar; MyBase::mm[1] *= scalar; MyBase::mm[2] *= scalar; MyBase::mm[3] *= scalar; MyBase::mm[4] *= scalar; MyBase::mm[5] *= scalar; MyBase::mm[6] *= scalar; MyBase::mm[7] *= scalar; MyBase::mm[8] *= scalar; return *this; } /// @brief Returns m0, where \f$m0_{i,j} += m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ template const Mat3 &operator+=(const Mat3 &m1) { const S *s = m1.asPointer(); MyBase::mm[0] += s[0]; MyBase::mm[1] += s[1]; MyBase::mm[2] += s[2]; MyBase::mm[3] += s[3]; MyBase::mm[4] += s[4]; MyBase::mm[5] += s[5]; MyBase::mm[6] += s[6]; MyBase::mm[7] += s[7]; MyBase::mm[8] += s[8]; return *this; } /// @brief Returns m0, where \f$m0_{i,j} -= m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ template const Mat3 &operator-=(const Mat3 &m1) { const S *s = m1.asPointer(); MyBase::mm[0] -= s[0]; MyBase::mm[1] -= s[1]; MyBase::mm[2] -= s[2]; MyBase::mm[3] -= s[3]; MyBase::mm[4] -= s[4]; MyBase::mm[5] -= s[5]; MyBase::mm[6] -= s[6]; MyBase::mm[7] -= s[7]; MyBase::mm[8] -= s[8]; return *this; } /// @brief Returns m0, where \f$m0_{i,j} *= m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ template const Mat3 &operator*=(const Mat3 &m1) { Mat3 m0(*this); const T* s0 = m0.asPointer(); const S* s1 = m1.asPointer(); MyBase::mm[0] = static_cast(s0[0] * s1[0] + s0[1] * s1[3] + s0[2] * s1[6]); MyBase::mm[1] = static_cast(s0[0] * s1[1] + s0[1] * s1[4] + s0[2] * s1[7]); MyBase::mm[2] = static_cast(s0[0] * s1[2] + s0[1] * s1[5] + s0[2] * s1[8]); MyBase::mm[3] = static_cast(s0[3] * s1[0] + s0[4] * s1[3] + s0[5] * s1[6]); MyBase::mm[4] = static_cast(s0[3] * s1[1] + s0[4] * s1[4] + s0[5] * s1[7]); MyBase::mm[5] = static_cast(s0[3] * s1[2] + s0[4] * s1[5] + s0[5] * s1[8]); MyBase::mm[6] = static_cast(s0[6] * s1[0] + s0[7] * s1[3] + s0[8] * s1[6]); MyBase::mm[7] = static_cast(s0[6] * s1[1] + s0[7] * s1[4] + s0[8] * s1[7]); MyBase::mm[8] = static_cast(s0[6] * s1[2] + s0[7] * s1[5] + s0[8] * s1[8]); return *this; } /// returns adjoint of m Mat3 adjoint() const { return Mat3( MyBase::mm[4] * MyBase::mm[8] - MyBase::mm[5] * MyBase::mm[7], MyBase::mm[2] * MyBase::mm[7] - MyBase::mm[1] * MyBase::mm[8], MyBase::mm[1] * MyBase::mm[5] - MyBase::mm[2] * MyBase::mm[4], MyBase::mm[5] * MyBase::mm[6] - MyBase::mm[3] * MyBase::mm[8], MyBase::mm[0] * MyBase::mm[8] - MyBase::mm[2] * MyBase::mm[6], MyBase::mm[2] * MyBase::mm[3] - MyBase::mm[0] * MyBase::mm[5], MyBase::mm[3] * MyBase::mm[7] - MyBase::mm[4] * MyBase::mm[6], MyBase::mm[1] * MyBase::mm[6] - MyBase::mm[0] * MyBase::mm[7], MyBase::mm[0] * MyBase::mm[4] - MyBase::mm[1] * MyBase::mm[3]); } // adjointTest /// returns transpose of this Mat3 transpose() const { return Mat3( MyBase::mm[0], MyBase::mm[3], MyBase::mm[6], MyBase::mm[1], MyBase::mm[4], MyBase::mm[7], MyBase::mm[2], MyBase::mm[5], MyBase::mm[8]); } // transposeTest /// returns inverse of this /// throws FailedOperationException if singular Mat3 inverse(T tolerance = 0) const { Mat3 inv(adjoint()); T det = inv.mm[0]*MyBase::mm[0] + inv.mm[1]*MyBase::mm[3] + inv.mm[2]*MyBase::mm[6]; // If the determinant is 0, m was singular and "this" will contain junk. if (isApproxEqual(det,0.0,tolerance)) { OPENVDB_THROW(ArithmeticError, "Inversion of singular 3x3 matrix"); } return inv * (T(1)/det); } // invertTest /// Determinant of matrix T det() const { T co00 = MyBase::mm[4]*MyBase::mm[8] - MyBase::mm[5]*MyBase::mm[7]; T co10 = MyBase::mm[5]*MyBase::mm[6] - MyBase::mm[3]*MyBase::mm[8]; T co20 = MyBase::mm[3]*MyBase::mm[7] - MyBase::mm[4]*MyBase::mm[6]; T d = MyBase::mm[0]*co00 + MyBase::mm[1]*co10 + MyBase::mm[2]*co20; return d; } // determinantTest /// Trace of matrix T trace() const { return MyBase::mm[0]+MyBase::mm[4]+MyBase::mm[8]; } /// This function snaps a specific axis to a specific direction, /// preserving scaling. It does this using minimum energy, thus /// posing a unique solution if basis & direction arent parralel. /// Direction need not be unit. Mat3 snapBasis(Axis axis, const Vec3 &direction) { return snapBasis(*this, axis, direction); } /// Return the transformed vector by "this" matrix. /// This function is equivalent to post-multiplying the matrix. template Vec3 transform(const Vec3 &v) const { return static_cast< Vec3 >(v * *this); } // xformVectorTest /// Return the transformed vector by transpose of "this" matrix. /// This function is equivalent to pre-multiplying the matrix. template Vec3 pretransform(const Vec3 &v) const { return static_cast< Vec3 >(*this * v); } // xformTVectorTest /// This function snaps a specific axis to a specific direction, /// preserving scaling. It does this using minimum energy, thus /// posing a unique solution if basis & direction arent parralel. /// Direction need not be unit. template Mat3 snappedBasis(Axis axis, const Vec3& direction) const { return snapBasis(*this, axis, direction); } private: static const Mat3 sIdentity; static const Mat3 sZero; }; // class Mat3 template const Mat3 Mat3::sIdentity = Mat3(1, 0, 0, 0, 1, 0, 0, 0, 1); template const Mat3 Mat3::sZero = Mat3(0, 0, 0, 0, 0, 0, 0, 0, 0); /// @relates Mat3 /// @brief Equality operator, does exact floating point comparisons template bool operator==(const Mat3 &m0, const Mat3 &m1) { const T0 *t0 = m0.asPointer(); const T1 *t1 = m1.asPointer(); for (int i=0; i<9; ++i) { if (!isExactlyEqual(t0[i], t1[i])) return false; } return true; } /// @relates Mat3 /// @brief Inequality operator, does exact floating point comparisons template bool operator!=(const Mat3 &m0, const Mat3 &m1) { return !(m0 == m1); } /// @relates Mat3 /// @brief Returns M, where \f$M_{i,j} = m_{i,j} * scalar\f$ for \f$i, j \in [0, 2]\f$ template Mat3::type> operator*(S scalar, const Mat3 &m) { return m*scalar; } /// @relates Mat3 /// @brief Returns M, where \f$M_{i,j} = m_{i,j} * scalar\f$ for \f$i, j \in [0, 2]\f$ template Mat3::type> operator*(const Mat3 &m, S scalar) { Mat3::type> result(m); result *= scalar; return result; } /// @relates Mat3 /// @brief Returns M, where \f$M_{i,j} = m0_{i,j} + m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ template Mat3::type> operator+(const Mat3 &m0, const Mat3 &m1) { Mat3::type> result(m0); result += m1; return result; } /// @relates Mat3 /// @brief Returns M, where \f$M_{i,j} = m0_{i,j} - m1_{i,j}\f$ for \f$i, j \in [0, 2]\f$ template Mat3::type> operator-(const Mat3 &m0, const Mat3 &m1) { Mat3::type> result(m0); result -= m1; return result; } /// @brief Matrix multiplication. /// /// Returns M, where /// \f$M_{ij} = \sum_{n=0}^2\left(m0_{nj} + m1_{in}\right)\f$ for \f$i, j \in [0, 2]\f$ template Mat3::type>operator*(const Mat3 &m0, const Mat3 &m1) { Mat3::type> result(m0); result *= m1; return result; } /// @relates Mat3 /// @brief Returns v, where \f$v_{i} = \sum_{n=0}^2 m_{i,n} * v_n\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator*(const Mat3 &_m, const Vec3 &_v) { MT const *m = _m.asPointer(); return Vec3::type>( _v[0]*m[0] + _v[1]*m[1] + _v[2]*m[2], _v[0]*m[3] + _v[1]*m[4] + _v[2]*m[5], _v[0]*m[6] + _v[1]*m[7] + _v[2]*m[8]); } /// @relates Mat3 /// @brief Returns v, where \f$v_{i} = \sum_{n=0}^2 m_{n,i} * v_n\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator*(const Vec3 &_v, const Mat3 &_m) { MT const *m = _m.asPointer(); return Vec3::type>( _v[0]*m[0] + _v[1]*m[3] + _v[2]*m[6], _v[0]*m[1] + _v[1]*m[4] + _v[2]*m[7], _v[0]*m[2] + _v[1]*m[5] + _v[2]*m[8]); } /// @relates Mat3 /// @brief Returns v, where \f$v_{i} = \sum_{n=0}^2 m_{i,n} * v_n\f$ for \f$i \in [0, 2]\f$ template inline Vec3 &operator *= (Vec3 &_v, const Mat3 &_m) { Vec3 mult = _v * _m; _v = mult; return _v; } /// this = outer product of v1, v2 /// e.g. M = Mat3f::outerproduct(v1,v2); template Mat3 outerProduct(const Vec3& v1, const Vec3& v2) { Mat3 m; m.setBasis(Vec3(v1[0]*v2[0], v1[1]*v2[0], v1[2]*v2[0]), Vec3(v1[0]*v2[1], v1[1]*v2[1], v1[2]*v2[1]), Vec3(v1[0]*v2[2], v1[1]*v2[2], v1[2]*v2[2])); return m; } // outerproductTest typedef Mat3 Mat3s; typedef Mat3 Mat3d; #if DWREAL_IS_DOUBLE == 1 typedef Mat3d Mat3f; #else typedef Mat3s Mat3f; #endif // DWREAL_IS_DOUBLE /// Interpolate the rotation between m1 and m2 using Mat::powSolve. /// Unlike slerp, translation is not treated independently. /// This results in smoother animation results. template Mat3 powLerp(const Mat3 &m1, const Mat3 &m2, T t) { Mat3 x = m1.inverse() * m2; powSolve(x, x, t); Mat3 m = m1 * x; return m; } namespace { template void pivot(int i, int j, Mat3& S, Vec3& D, Mat3& Q) { const int& n = Mat3::size; // should be 3 T temp; /// scratch variables used in pivoting double cotan_of_2_theta; double tan_of_theta; double cosin_of_theta; double sin_of_theta; double z; double Sij = S(i,j); double Sjj_minus_Sii = D[j] - D[i]; if (fabs(Sjj_minus_Sii) * (10*math::Tolerance::value()) > fabs(Sij)) { tan_of_theta = Sij / Sjj_minus_Sii; } else { /// pivot on Sij cotan_of_2_theta = 0.5*Sjj_minus_Sii / Sij ; if (cotan_of_2_theta < 0.) { tan_of_theta = -1./(sqrt(1. + cotan_of_2_theta*cotan_of_2_theta) - cotan_of_2_theta); } else { tan_of_theta = 1./(sqrt(1. + cotan_of_2_theta*cotan_of_2_theta) + cotan_of_2_theta); } } cosin_of_theta = 1./sqrt( 1. + tan_of_theta * tan_of_theta); sin_of_theta = cosin_of_theta * tan_of_theta; z = tan_of_theta * Sij; S(i,j) = 0; D[i] -= z; D[j] += z; for (int k = 0; k < i; ++k) { temp = S(k,i); S(k,i) = cosin_of_theta * temp - sin_of_theta * S(k,j); S(k,j)= sin_of_theta * temp + cosin_of_theta * S(k,j); } for (int k = i+1; k < j; ++k) { temp = S(i,k); S(i,k) = cosin_of_theta * temp - sin_of_theta * S(k,j); S(k,j) = sin_of_theta * temp + cosin_of_theta * S(k,j); } for (int k = j+1; k < n; ++k) { temp = S(i,k); S(i,k) = cosin_of_theta * temp - sin_of_theta * S(j,k); S(j,k) = sin_of_theta * temp + cosin_of_theta * S(j,k); } for (int k = 0; k < n; ++k) { temp = Q(k,i); Q(k,i) = cosin_of_theta * temp - sin_of_theta*Q(k,j); Q(k,j) = sin_of_theta * temp + cosin_of_theta*Q(k,j); } } } /// @brief Use Jacobi iterations to decompose a symmetric 3x3 matrix /// (diagonalize and compute eigenvectors) /// @details This is based on the "Efficient numerical diagonalization of Hermitian 3x3 matrices" /// Joachim Kopp. arXiv.org preprint: physics/0610206 /// with the addition of largest pivot template bool diagonalizeSymmetricMatrix(const Mat3& input, Mat3& Q, Vec3& D, unsigned int MAX_ITERATIONS=250) { /// use Givens rotation matrix to eliminate off-diagonal entries. /// initialize the rotation matrix as idenity Q = Mat3::identity(); int n = Mat3::size; // should be 3 /// temp matrix. Assumed to be symmetric Mat3 S(input); for (int i = 0; i < n; ++i) { D[i] = S(i,i); } unsigned int iterations(0); /// Just iterate over all the non-diagonal enteries /// using the largest as a pivot. do { /// check for absolute convergence /// are symmetric off diagonals all zero double er = 0; for (int i = 0; i < n; ++i) { for (int j = i+1; j < n; ++j) { er += fabs(S(i,j)); } } if (std::abs(er) < math::Tolerance::value()) { return true; } iterations++; T max_element = 0; int ip = 0; int jp = 0; /// loop over all the off-diagonals above the diagonal for (int i = 0; i < n; ++i) { for (int j = i+1; j < n; ++j){ if ( fabs(D[i]) * (10*math::Tolerance::value()) > fabs(S(i,j))) { /// value too small to pivot on S(i,j) = 0; } if (fabs(S(i,j)) > max_element) { max_element = fabs(S(i,j)); ip = i; jp = j; } } } pivot(ip, jp, S, D, Q); } while (iterations < MAX_ITERATIONS); return false; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_MAT3_H_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/FiniteDifference.h0000644000000000000000000026075212252453157015526 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file FiniteDifference.h #ifndef OPENVDB_MATH_FINITEDIFFERENCE_HAS_BEEN_INCLUDED #define OPENVDB_MATH_FINITEDIFFERENCE_HAS_BEEN_INCLUDED #include #include "Math.h" #include "Coord.h" #include "Vec3.h" #include #include #ifdef DWA_OPENVDB #include #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// /// @brief Different discrete schemes used in the first derivatives. // Add new items to the *end* of this list, and update NUM_DS_SCHEMES. enum DScheme { UNKNOWN_DS = -1, CD_2NDT = 0, // center difference, 2nd order, but the result must be divided by 2 CD_2ND, // center difference, 2nd order CD_4TH, // center difference, 4th order CD_6TH, // center difference, 6th order FD_1ST, // forward difference, 1st order FD_2ND, // forward difference, 2nd order FD_3RD, // forward difference, 3rd order BD_1ST, // backward difference, 1st order BD_2ND, // backward difference, 2nd order BD_3RD, // backward difference, 3rd order FD_WENO5, // forward difference, weno5 BD_WENO5, // backward difference, weno5 FD_HJWENO5, // forward differene, HJ-weno5 BD_HJWENO5 // backward difference, HJ-weno5 }; enum { NUM_DS_SCHEMES = BD_HJWENO5 + 1 }; inline std::string dsSchemeToString(DScheme dss) { std::string ret; switch (dss) { case UNKNOWN_DS: ret = "unknown_ds"; break; case CD_2NDT: ret = "cd_2ndt"; break; case CD_2ND: ret = "cd_2nd"; break; case CD_4TH: ret = "cd_4th"; break; case CD_6TH: ret = "cd_6th"; break; case FD_1ST: ret = "fd_1st"; break; case FD_2ND: ret = "fd_2nd"; break; case FD_3RD: ret = "fd_3rd"; break; case BD_1ST: ret = "bd_1st"; break; case BD_2ND: ret = "bd_2nd"; break; case BD_3RD: ret = "bd_3rd"; break; case FD_WENO5: ret = "fd_weno5"; break; case BD_WENO5: ret = "bd_weno5"; break; case FD_HJWENO5: ret = "fd_hjweno5"; break; case BD_HJWENO5: ret = "bd_hjweno5"; break; } return ret; } inline DScheme stringToDScheme(const std::string& s) { DScheme ret = UNKNOWN_DS; std::string str = s; boost::trim(str); boost::to_lower(str); if (str == dsSchemeToString(CD_2NDT)) { ret = CD_2NDT; } else if (str == dsSchemeToString(CD_2ND)) { ret = CD_2ND; } else if (str == dsSchemeToString(CD_4TH)) { ret = CD_4TH; } else if (str == dsSchemeToString(CD_6TH)) { ret = CD_6TH; } else if (str == dsSchemeToString(FD_1ST)) { ret = FD_1ST; } else if (str == dsSchemeToString(FD_2ND)) { ret = FD_2ND; } else if (str == dsSchemeToString(FD_3RD)) { ret = FD_3RD; } else if (str == dsSchemeToString(BD_1ST)) { ret = BD_1ST; } else if (str == dsSchemeToString(BD_2ND)) { ret = BD_2ND; } else if (str == dsSchemeToString(BD_3RD)) { ret = BD_3RD; } else if (str == dsSchemeToString(FD_WENO5)) { ret = FD_WENO5; } else if (str == dsSchemeToString(BD_WENO5)) { ret = BD_WENO5; } else if (str == dsSchemeToString(FD_HJWENO5)) { ret = FD_HJWENO5; } else if (str == dsSchemeToString(BD_HJWENO5)) { ret = BD_HJWENO5; } return ret; } inline std::string dsSchemeToMenuName(DScheme dss) { std::string ret; switch (dss) { case UNKNOWN_DS: ret = "Unknown DS scheme"; break; case CD_2NDT: ret = "Twice 2nd-order center difference"; break; case CD_2ND: ret = "2nd-order center difference"; break; case CD_4TH: ret = "4th-order center difference"; break; case CD_6TH: ret = "6th-order center difference"; break; case FD_1ST: ret = "1st-order forward difference"; break; case FD_2ND: ret = "2nd-order forward difference"; break; case FD_3RD: ret = "3rd-order forward difference"; break; case BD_1ST: ret = "1st-order backward difference"; break; case BD_2ND: ret = "2nd-order backward difference"; break; case BD_3RD: ret = "3rd-order backward difference"; break; case FD_WENO5: ret = "5th-order WENO forward difference"; break; case BD_WENO5: ret = "5th-order WENO backward difference"; break; case FD_HJWENO5: ret = "5th-order HJ-WENO forward difference"; break; case BD_HJWENO5: ret = "5th-order HJ-WENO backward difference"; break; } return ret; } //////////////////////////////////////// /// @brief Different discrete schemes used in the second derivatives. // Add new items to the *end* of this list, and update NUM_DD_SCHEMES. enum DDScheme { UNKNOWN_DD = -1, CD_SECOND = 0, // center difference, 2nd order CD_FOURTH, // center difference, 4th order CD_SIXTH // center difference, 6th order }; enum { NUM_DD_SCHEMES = CD_SIXTH + 1 }; //////////////////////////////////////// /// @brief Biased Gradients are limited to non-centered differences // Add new items to the *end* of this list, and update NUM_BIAS_SCHEMES. enum BiasedGradientScheme { UNKNOWN_BIAS = -1, FIRST_BIAS = 0, // uses FD_1ST & BD_1ST SECOND_BIAS, // uses FD_2ND & BD_2ND THIRD_BIAS, // uses FD_3RD & BD_3RD WENO5_BIAS, // uses WENO5 HJWENO5_BIAS // uses HJWENO5 }; enum { NUM_BIAS_SCHEMES = HJWENO5_BIAS + 1 }; inline std::string biasedGradientSchemeToString(BiasedGradientScheme bgs) { std::string ret; switch (bgs) { case UNKNOWN_BIAS: ret = "unknown_bias"; break; case FIRST_BIAS: ret = "first_bias"; break; case SECOND_BIAS: ret = "second_bias"; break; case THIRD_BIAS: ret = "third_bias"; break; case WENO5_BIAS: ret = "weno5_bias"; break; case HJWENO5_BIAS: ret = "hjweno5_bias"; break; } return ret; } inline BiasedGradientScheme stringToBiasedGradientScheme(const std::string& s) { BiasedGradientScheme ret = UNKNOWN_BIAS; std::string str = s; boost::trim(str); boost::to_lower(str); if (str == biasedGradientSchemeToString(FIRST_BIAS)) { ret = FIRST_BIAS; } else if (str == biasedGradientSchemeToString(SECOND_BIAS)) { ret = SECOND_BIAS; } else if (str == biasedGradientSchemeToString(THIRD_BIAS)) { ret = THIRD_BIAS; } else if (str == biasedGradientSchemeToString(WENO5_BIAS)) { ret = WENO5_BIAS; } else if (str == biasedGradientSchemeToString(HJWENO5_BIAS)) { ret = HJWENO5_BIAS; } return ret; } inline std::string biasedGradientSchemeToMenuName(BiasedGradientScheme bgs) { std::string ret; switch (bgs) { case UNKNOWN_BIAS: ret = "Unknown biased gradient"; break; case FIRST_BIAS: ret = "1st-order biased gradient"; break; case SECOND_BIAS: ret = "2nd-order biased gradient"; break; case THIRD_BIAS: ret = "3rd-order biased gradient"; break; case WENO5_BIAS: ret = "5th-order WENO biased gradient"; break; case HJWENO5_BIAS: ret = "5th-order HJ-WENO biased gradient"; break; } return ret; } //////////////////////////////////////// /// @brief Temporal integrations schemes // Add new items to the *end* of this list, and update NUM_TEMPORAL_SCHEMES. enum TemporalIntegrationScheme { UNKNOWN_TIS = -1, TVD_RK1,//same as explicit Euler integration TVD_RK2, TVD_RK3 }; enum { NUM_TEMPORAL_SCHEMES = TVD_RK3 + 1 }; inline std::string temporalIntegrationSchemeToString(TemporalIntegrationScheme tis) { std::string ret; switch (tis) { case UNKNOWN_TIS: ret = "unknown_tis"; break; case TVD_RK1: ret = "tvd_rk1"; break; case TVD_RK2: ret = "tvd_rk2"; break; case TVD_RK3: ret = "tvd_rk3"; break; } return ret; } inline TemporalIntegrationScheme stringToTemporalIntegrationScheme(const std::string& s) { TemporalIntegrationScheme ret = UNKNOWN_TIS; std::string str = s; boost::trim(str); boost::to_lower(str); if (str == temporalIntegrationSchemeToString(TVD_RK1)) { ret = TVD_RK1; } else if (str == temporalIntegrationSchemeToString(TVD_RK2)) { ret = TVD_RK2; } else if (str == temporalIntegrationSchemeToString(TVD_RK3)) { ret = TVD_RK3; } return ret; } inline std::string temporalIntegrationSchemeToMenuName(TemporalIntegrationScheme tis) { std::string ret; switch (tis) { case UNKNOWN_TIS: ret = "Unknown temporal integration"; break; case TVD_RK1: ret = "Forward Euler"; break; case TVD_RK2: ret = "2nd-order Runge-Kutta"; break; case TVD_RK3: ret = "3rd-order Runge-Kutta"; break; } return ret; } //@} /// @brief implimentation of nonimally fith-order finite-difference WENO. /// This function returns the numerical flux. See "High Order Finite Difference and /// Finite Volume WENO Schemes and Discontinuous Galerkin Methods for CFD" - Chi-Wang Shu /// ICASE Report No 2001-11 (page 6). Also see ICASE No 97-65 for a more complete reference /// (Shu, 1997) /// Given v1 = f(x-2dx), v2 = f(x-dx), v3 = f(x), v4 = f(x+dx), v5 = f(x+2dx), /// the returns and interpolated value f(x+dx/2) with the special property that /// ( f(x+dx/2) - f(x-dx/2) ) / dx = df/dx (x) + error, /// where the error is 5-order in smooth regions: O(dx) <= error <=O(dx^5) template inline ValueType WENO5(const ValueType& v1, const ValueType& v2, const ValueType& v3, const ValueType& v4, const ValueType& v5, float scale2 = 0.01) { static const double C=13.0/12.0; // Weno is formulated for non-dimensional equations, here the optional scale2 // is a reference value (squared) for the function being interpolated. For // example if 'v' is of order 1000, then scale2 = 10^6 is ok. But in practice // leave scale2 = 1. const double eps=1e-6*scale2; // {\tilde \omega_k} = \gamma_k / ( \beta_k + \epsilon)^2 in Shu's ICASE report) const double A1=0.1/math::Pow2(C*math::Pow2(v1-2*v2+v3)+0.25*math::Pow2(v1-4*v2+3.0*v3)+eps), A2=0.6/math::Pow2(C*math::Pow2(v2-2*v3+v4)+0.25*math::Pow2(v2-v4)+eps), A3=0.3/math::Pow2(C*math::Pow2(v3-2*v4+v5)+0.25*math::Pow2(3.0*v3-4*v4+v5)+eps); return ValueType(A1*(2.0*v1 - 7.0*v2 + 11.0*v3) + A2*(5.0*v3 - v2 + 2.0*v4) + A3*(2.0*v3 + 5.0*v4 - v5))/(6.0*(A1+A2+A3)); } template inline Real GudonovsNormSqrd(bool isOutside, Real dP_xm, Real dP_xp, Real dP_ym, Real dP_yp, Real dP_zm, Real dP_zp) { using math::Max; using math::Min; using math::Pow2; const Real zero(0); Real dPLen2; if (isOutside) { // outside dPLen2 = Max(Pow2(Max(dP_xm, zero)), Pow2(Min(dP_xp,zero))); // (dP/dx)2 dPLen2 += Max(Pow2(Max(dP_ym, zero)), Pow2(Min(dP_yp,zero))); // (dP/dy)2 dPLen2 += Max(Pow2(Max(dP_zm, zero)), Pow2(Min(dP_zp,zero))); // (dP/dz)2 } else { // inside dPLen2 = Max(Pow2(Min(dP_xm, zero)), Pow2(Max(dP_xp,zero))); // (dP/dx)2 dPLen2 += Max(Pow2(Min(dP_ym, zero)), Pow2(Max(dP_yp,zero))); // (dP/dy)2 dPLen2 += Max(Pow2(Min(dP_zm, zero)), Pow2(Max(dP_zp,zero))); // (dP/dz)2 } return dPLen2; // |\nabla\phi|^2 } template inline Real GudonovsNormSqrd(bool isOutside, const Vec3& gradient_m, const Vec3& gradient_p) { return GudonovsNormSqrd(isOutside, gradient_m[0], gradient_p[0], gradient_m[1], gradient_p[1], gradient_m[2], gradient_p[2]); } #ifdef DWA_OPENVDB inline simd::Float4 simdMin(const simd::Float4& a, const simd::Float4& b) { return simd::Float4(_mm_min_ps(a.base(), b.base())); } inline simd::Float4 simdMax(const simd::Float4& a, const simd::Float4& b) { return simd::Float4(_mm_max_ps(a.base(), b.base())); } inline float simdSum(const simd::Float4& v); inline simd::Float4 Pow2(const simd::Float4& v) { return v * v; } template<> inline simd::Float4 WENO5(const simd::Float4& v1, const simd::Float4& v2, const simd::Float4& v3, const simd::Float4& v4, const simd::Float4& v5, float scale2) { using math::Pow2; typedef simd::Float4 F4; const F4 C(13.0 / 12.0), eps(1e-6 * scale2), two(2.0), three(3.0), four(4.0), five(5.0), fourth(0.25), A1 = F4(0.1) / Pow2(C*Pow2(v1-two*v2+v3) + fourth*Pow2(v1-four*v2+three*v3) + eps), A2 = F4(0.6) / Pow2(C*Pow2(v2-two*v3+v4) + fourth*Pow2(v2-v4) + eps), A3 = F4(0.3) / Pow2(C*Pow2(v3-two*v4+v5) + fourth*Pow2(three*v3-four*v4+v5) + eps); return (A1 * (two * v1 - F4(7.0) * v2 + F4(11.0) * v3) + A2 * (five * v3 - v2 + two * v4) + A3 * (two * v3 + five * v4 - v5)) / (F4(6.0) * (A1 + A2 + A3)); } inline float simdSum(const simd::Float4& v) { // temp = { v3+v3, v2+v2, v1+v3, v0+v2 } __m128 temp = _mm_add_ps(v.base(), _mm_movehl_ps(v.base(), v.base())); // temp = { v3+v3, v2+v2, v1+v3, (v0+v2)+(v1+v3) } temp = _mm_add_ss(temp, _mm_shuffle_ps(temp, temp, 1)); return _mm_cvtss_f32(temp); } inline float GudonovsNormSqrd(bool isOutside, const simd::Float4& dP_m, const simd::Float4& dP_p) { const simd::Float4 zero(0.0); simd::Float4 v = isOutside ? simdMax(math::Pow2(simdMax(dP_m, zero)), math::Pow2(simdMin(dP_p, zero))) : simdMax(math::Pow2(simdMin(dP_m, zero)), math::Pow2(simdMax(dP_p, zero))); return simdSum(v);//should be v[0]+v[1]+v[2] } #endif template struct D1 { // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk); template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk); template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk); // stencil access version template static typename Stencil::ValueType inX(const Stencil& S); template static typename Stencil::ValueType inY(const Stencil& S); template static typename Stencil::ValueType inZ(const Stencil& S); }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xp1, const ValueType& xm1) { return xp1 - xm1; } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(1, 0, 0)), grid.getValue(ijk.offsetBy(-1, 0, 0))); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 1, 0)), grid.getValue(ijk.offsetBy( 0, -1, 0))); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 0, 1)), grid.getValue(ijk.offsetBy( 0, 0, -1))); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue< 1, 0, 0>(), S.template getValue<-1, 0, 0>()); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 1, 0>(), S.template getValue< 0,-1, 0>()); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0,-1>()); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xp1, const ValueType& xm1) { return (xp1 - xm1)*ValueType(0.5); } // random access template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(1, 0, 0)), grid.getValue(ijk.offsetBy(-1, 0, 0))); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 1, 0)), grid.getValue(ijk.offsetBy( 0, -1, 0))); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 0, 1)), grid.getValue(ijk.offsetBy( 0, 0, -1))); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { typedef typename Stencil::ValueType ValueType; return difference(S.template getValue< 1, 0, 0>(), S.template getValue<-1, 0, 0>()); } template static typename Stencil::ValueType inY(const Stencil& S) { typedef typename Stencil::ValueType ValueType; return difference(S.template getValue< 0, 1, 0>(), S.template getValue< 0,-1, 0>()); } template static typename Stencil::ValueType inZ(const Stencil& S) { typedef typename Stencil::ValueType ValueType; return difference(S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0,-1>()); } }; template<> struct D1 { // the difference opperator template static ValueType difference( const ValueType& xp2, const ValueType& xp1, const ValueType& xm1, const ValueType& xm2 ) { return ValueType(2./3.)*(xp1 - xm1) + ValueType(1./12.)*(xm2 - xp2) ; } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 2,0,0)), grid.getValue(ijk.offsetBy( 1,0,0)), grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk.offsetBy(-2,0,0)) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0, 2, 0)), grid.getValue(ijk.offsetBy( 0, 1, 0)), grid.getValue(ijk.offsetBy( 0,-1, 0)), grid.getValue(ijk.offsetBy( 0,-2, 0)) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0, 0, 2)), grid.getValue(ijk.offsetBy( 0, 0, 1)), grid.getValue(ijk.offsetBy( 0, 0,-1)), grid.getValue(ijk.offsetBy( 0, 0,-2)) ); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>() ); } }; template<> struct D1 { // the difference opperator template static ValueType difference( const ValueType& xp3, const ValueType& xp2, const ValueType& xp1, const ValueType& xm1, const ValueType& xm2, const ValueType& xm3 ) { return ValueType(3./4.)*(xp1 - xm1) - ValueType(0.15)*(xp2 - xm2) + ValueType(1./60.)*(xp3-xm3); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 3,0,0)), grid.getValue(ijk.offsetBy( 2,0,0)), grid.getValue(ijk.offsetBy( 1,0,0)), grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk.offsetBy(-2,0,0)), grid.getValue(ijk.offsetBy(-3,0,0))); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0, 3,0)), grid.getValue(ijk.offsetBy( 0, 2,0)), grid.getValue(ijk.offsetBy( 0, 1,0)), grid.getValue(ijk.offsetBy( 0,-1,0)), grid.getValue(ijk.offsetBy( 0,-2,0)), grid.getValue(ijk.offsetBy( 0,-3,0))); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0, 0, 3)), grid.getValue(ijk.offsetBy( 0, 0, 2)), grid.getValue(ijk.offsetBy( 0, 0, 1)), grid.getValue(ijk.offsetBy( 0, 0,-1)), grid.getValue(ijk.offsetBy( 0, 0,-2)), grid.getValue(ijk.offsetBy( 0, 0,-3))); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference(S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>(), S.template getValue<-3, 0, 0>()); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>(), S.template getValue< 0,-3, 0>()); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>(), S.template getValue< 0, 0,-3>()); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xp1, const ValueType& xp0) { return xp1 - xp0; } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(1, 0, 0)), grid.getValue(ijk)); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 1, 0)), grid.getValue(ijk)); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 0, 1)), grid.getValue(ijk)); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference(S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>()); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference(S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>()); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference(S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>()); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xp2, const ValueType& xp1, const ValueType& xp0) { return ValueType(2)*xp1 -(ValueType(0.5)*xp2 + ValueType(3./2.)*xp0); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(2,0,0)), grid.getValue(ijk.offsetBy(1,0,0)), grid.getValue(ijk)); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0,2,0)), grid.getValue(ijk.offsetBy(0,1,0)), grid.getValue(ijk)); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0,0,2)), grid.getValue(ijk.offsetBy(0,0,1)), grid.getValue(ijk)); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>() ); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xp3, const ValueType& xp2, const ValueType& xp1, const ValueType& xp0) { return ValueType(1./3.)*xp3 - ValueType(1.5)*xp2 + ValueType(3.)*xp1 - ValueType(11./6.)*xp0; } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(3,0,0)), grid.getValue(ijk.offsetBy(2,0,0)), grid.getValue(ijk.offsetBy(1,0,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0,3,0)), grid.getValue(ijk.offsetBy(0,2,0)), grid.getValue(ijk.offsetBy(0,1,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0,0,3)), grid.getValue(ijk.offsetBy(0,0,2)), grid.getValue(ijk.offsetBy(0,0,1)), grid.getValue(ijk) ); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference(S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference(S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>() ); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xm1, const ValueType& xm0) { return -D1::difference(xm1, xm0); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk)); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0,-1,0)), grid.getValue(ijk)); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 0,-1)), grid.getValue(ijk)); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference(S.template getValue<-1, 0, 0>(), S.template getValue< 0, 0, 0>()); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference(S.template getValue< 0,-1, 0>(), S.template getValue< 0, 0, 0>()); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference(S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0, 0>()); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xm2, const ValueType& xm1, const ValueType& xm0) { return -D1::difference(xm2, xm1, xm0); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(-2,0,0)), grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0,-2,0)), grid.getValue(ijk.offsetBy(0,-1,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0,0,-2)), grid.getValue(ijk.offsetBy(0,0,-1)), grid.getValue(ijk) ); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue<-2, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0,-2, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0,-2>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0, 0>() ); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xm3, const ValueType& xm2, const ValueType& xm1, const ValueType& xm0){ return -D1::difference(xm3, xm2, xm1, xm0); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(-3,0,0)), grid.getValue(ijk.offsetBy(-2,0,0)), grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0,-3,0)), grid.getValue(ijk.offsetBy( 0,-2,0)), grid.getValue(ijk.offsetBy( 0,-1,0)), grid.getValue(ijk) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0, 0,-3)), grid.getValue(ijk.offsetBy( 0, 0,-2)), grid.getValue(ijk.offsetBy( 0, 0,-1)), grid.getValue(ijk) ); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue<-3, 0, 0>(), S.template getValue<-2, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0,-3, 0>(), S.template getValue< 0,-2, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0, 0, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0,-3>(), S.template getValue< 0, 0,-2>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0, 0>() ); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xp3, const ValueType& xp2, const ValueType& xp1, const ValueType& xp0, const ValueType& xm1, const ValueType& xm2) { return WENO5(xp3, xp2, xp1, xp0, xm1) - WENO5(xp2, xp1, xp0, xm1, xm2); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(3,0,0)); V[1] = grid.getValue(ijk.offsetBy(2,0,0)); V[2] = grid.getValue(ijk.offsetBy(1,0,0)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(-1,0,0)); V[5] = grid.getValue(ijk.offsetBy(-2,0,0)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(0,3,0)); V[1] = grid.getValue(ijk.offsetBy(0,2,0)); V[2] = grid.getValue(ijk.offsetBy(0,1,0)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(0,-1,0)); V[5] = grid.getValue(ijk.offsetBy(0,-2,0)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(0,0,3)); V[1] = grid.getValue(ijk.offsetBy(0,0,2)); V[2] = grid.getValue(ijk.offsetBy(0,0,1)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(0,0,-1)); V[5] = grid.getValue(ijk.offsetBy(0,0,-2)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>() ); } }; template<> struct D1 { // the difference opperator template static ValueType difference(const ValueType& xp3, const ValueType& xp2, const ValueType& xp1, const ValueType& xp0, const ValueType& xm1, const ValueType& xm2) { return WENO5(xp3 - xp2, xp2 - xp1, xp1 - xp0, xp0-xm1, xm1-xm2); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(3,0,0)); V[1] = grid.getValue(ijk.offsetBy(2,0,0)); V[2] = grid.getValue(ijk.offsetBy(1,0,0)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(-1,0,0)); V[5] = grid.getValue(ijk.offsetBy(-2,0,0)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(0,3,0)); V[1] = grid.getValue(ijk.offsetBy(0,2,0)); V[2] = grid.getValue(ijk.offsetBy(0,1,0)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(0,-1,0)); V[5] = grid.getValue(ijk.offsetBy(0,-2,0)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(0,0,3)); V[1] = grid.getValue(ijk.offsetBy(0,0,2)); V[2] = grid.getValue(ijk.offsetBy(0,0,1)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(0,0,-1)); V[5] = grid.getValue(ijk.offsetBy(0,0,-2)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>() ); } }; template<> struct D1 { template static ValueType difference(const ValueType& xm3, const ValueType& xm2, const ValueType& xm1, const ValueType& xm0, const ValueType& xp1, const ValueType& xp2) { return -D1::difference(xm3, xm2, xm1, xm0, xp1, xp2); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(-3,0,0)); V[1] = grid.getValue(ijk.offsetBy(-2,0,0)); V[2] = grid.getValue(ijk.offsetBy(-1,0,0)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(1,0,0)); V[5] = grid.getValue(ijk.offsetBy(2,0,0)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(0,-3,0)); V[1] = grid.getValue(ijk.offsetBy(0,-2,0)); V[2] = grid.getValue(ijk.offsetBy(0,-1,0)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(0,1,0)); V[5] = grid.getValue(ijk.offsetBy(0,2,0)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(0,0,-3)); V[1] = grid.getValue(ijk.offsetBy(0,0,-2)); V[2] = grid.getValue(ijk.offsetBy(0,0,-1)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(0,0,1)); V[5] = grid.getValue(ijk.offsetBy(0,0,2)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { typedef typename Stencil::ValueType ValueType; ValueType V[6]; V[0] = S.template getValue<-3, 0, 0>(); V[1] = S.template getValue<-2, 0, 0>(); V[2] = S.template getValue<-1, 0, 0>(); V[3] = S.template getValue< 0, 0, 0>(); V[4] = S.template getValue< 1, 0, 0>(); V[5] = S.template getValue< 2, 0, 0>(); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Stencil::ValueType inY(const Stencil& S) { typedef typename Stencil::ValueType ValueType; ValueType V[6]; V[0] = S.template getValue< 0,-3, 0>(); V[1] = S.template getValue< 0,-2, 0>(); V[2] = S.template getValue< 0,-1, 0>(); V[3] = S.template getValue< 0, 0, 0>(); V[4] = S.template getValue< 0, 1, 0>(); V[5] = S.template getValue< 0, 2, 0>(); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Stencil::ValueType inZ(const Stencil& S) { typedef typename Stencil::ValueType ValueType; ValueType V[6]; V[0] = S.template getValue< 0, 0,-3>(); V[1] = S.template getValue< 0, 0,-2>(); V[2] = S.template getValue< 0, 0,-1>(); V[3] = S.template getValue< 0, 0, 0>(); V[4] = S.template getValue< 0, 0, 1>(); V[5] = S.template getValue< 0, 0, 2>(); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } }; template<> struct D1 { template static ValueType difference(const ValueType& xm3, const ValueType& xm2, const ValueType& xm1, const ValueType& xm0, const ValueType& xp1, const ValueType& xp2) { return -D1::difference(xm3, xm2, xm1, xm0, xp1, xp2); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(-3,0,0)); V[1] = grid.getValue(ijk.offsetBy(-2,0,0)); V[2] = grid.getValue(ijk.offsetBy(-1,0,0)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(1,0,0)); V[5] = grid.getValue(ijk.offsetBy(2,0,0)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(0,-3,0)); V[1] = grid.getValue(ijk.offsetBy(0,-2,0)); V[2] = grid.getValue(ijk.offsetBy(0,-1,0)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(0,1,0)); V[5] = grid.getValue(ijk.offsetBy(0,2,0)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType V[6]; V[0] = grid.getValue(ijk.offsetBy(0,0,-3)); V[1] = grid.getValue(ijk.offsetBy(0,0,-2)); V[2] = grid.getValue(ijk.offsetBy(0,0,-1)); V[3] = grid.getValue(ijk); V[4] = grid.getValue(ijk.offsetBy(0,0,1)); V[5] = grid.getValue(ijk.offsetBy(0,0,2)); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { typedef typename Stencil::ValueType ValueType; ValueType V[6]; V[0] = S.template getValue<-3, 0, 0>(); V[1] = S.template getValue<-2, 0, 0>(); V[2] = S.template getValue<-1, 0, 0>(); V[3] = S.template getValue< 0, 0, 0>(); V[4] = S.template getValue< 1, 0, 0>(); V[5] = S.template getValue< 2, 0, 0>(); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Stencil::ValueType inY(const Stencil& S) { typedef typename Stencil::ValueType ValueType; ValueType V[6]; V[0] = S.template getValue< 0,-3, 0>(); V[1] = S.template getValue< 0,-2, 0>(); V[2] = S.template getValue< 0,-1, 0>(); V[3] = S.template getValue< 0, 0, 0>(); V[4] = S.template getValue< 0, 1, 0>(); V[5] = S.template getValue< 0, 2, 0>(); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } template static typename Stencil::ValueType inZ(const Stencil& S) { typedef typename Stencil::ValueType ValueType; ValueType V[6]; V[0] = S.template getValue< 0, 0,-3>(); V[1] = S.template getValue< 0, 0,-2>(); V[2] = S.template getValue< 0, 0,-1>(); V[3] = S.template getValue< 0, 0, 0>(); V[4] = S.template getValue< 0, 0, 1>(); V[5] = S.template getValue< 0, 0, 2>(); return difference(V[0], V[1], V[2], V[3], V[4], V[5]); } }; template struct D1Vec { // random access version template static typename Accessor::ValueType::value_type inX(const Accessor& grid, const Coord& ijk, int n) { return D1::inX(grid, ijk)[n]; } template static typename Accessor::ValueType::value_type inY(const Accessor& grid, const Coord& ijk, int n) { return D1::inY(grid, ijk)[n]; } template static typename Accessor::ValueType::value_type inZ(const Accessor& grid, const Coord& ijk, int n) { return D1::inZ(grid, ijk)[n]; } // stencil access version template static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) { return D1::inX(S)[n]; } template static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) { return D1::inY(S)[n]; } template static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) { return D1::inZ(S)[n]; } }; template<> struct D1Vec { // random access version template static typename Accessor::ValueType::value_type inX(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy( 1, 0, 0))[n], grid.getValue(ijk.offsetBy(-1, 0, 0))[n] ); } template static typename Accessor::ValueType::value_type inY(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy(0, 1, 0))[n], grid.getValue(ijk.offsetBy(0,-1, 0))[n] ); } template static typename Accessor::ValueType::value_type inZ(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy(0, 0, 1))[n], grid.getValue(ijk.offsetBy(0, 0,-1))[n] ); } // stencil access version template static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) { return D1::difference( S.template getValue< 1, 0, 0>()[n], S.template getValue<-1, 0, 0>()[n] ); } template static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) { return D1::difference( S.template getValue< 0, 1, 0>()[n], S.template getValue< 0,-1, 0>()[n] ); } template static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) { return D1::difference( S.template getValue< 0, 0, 1>()[n], S.template getValue< 0, 0,-1>()[n] ); } }; template<> struct D1Vec { // random access version template static typename Accessor::ValueType::value_type inX(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy( 1, 0, 0))[n] , grid.getValue(ijk.offsetBy(-1, 0, 0))[n] ); } template static typename Accessor::ValueType::value_type inY(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy(0, 1, 0))[n] , grid.getValue(ijk.offsetBy(0,-1, 0))[n] ); } template static typename Accessor::ValueType::value_type inZ(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy(0, 0, 1))[n] , grid.getValue(ijk.offsetBy(0, 0,-1))[n] ); } // stencil access version template static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) { return D1::difference( S.template getValue< 1, 0, 0>()[n], S.template getValue<-1, 0, 0>()[n] ); } template static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) { return D1::difference( S.template getValue< 0, 1, 0>()[n], S.template getValue< 0,-1, 0>()[n] ); } template static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) { return D1::difference( S.template getValue< 0, 0, 1>()[n], S.template getValue< 0, 0,-1>()[n] ); } }; template<> struct D1Vec { // typedef typename Accessor::ValueType::value_type value_type; // random access version template static typename Accessor::ValueType::value_type inX(const Accessor& grid, const Coord& ijk, int n) { return D1::difference(grid.getValue(ijk.offsetBy(2, 0, 0))[n], grid.getValue(ijk.offsetBy( 1, 0, 0))[n], grid.getValue(ijk.offsetBy(-1,0, 0))[n], grid.getValue(ijk.offsetBy(-2, 0, 0))[n]); } template static typename Accessor::ValueType::value_type inY(const Accessor& grid, const Coord& ijk, int n) { return D1::difference(grid.getValue(ijk.offsetBy( 0, 2, 0))[n], grid.getValue(ijk.offsetBy( 0, 1, 0))[n], grid.getValue(ijk.offsetBy( 0,-1, 0))[n], grid.getValue(ijk.offsetBy( 0,-2, 0))[n]); } template static typename Accessor::ValueType::value_type inZ(const Accessor& grid, const Coord& ijk, int n) { return D1::difference(grid.getValue(ijk.offsetBy(0,0, 2))[n], grid.getValue(ijk.offsetBy( 0, 0, 1))[n], grid.getValue(ijk.offsetBy(0,0,-1))[n], grid.getValue(ijk.offsetBy( 0, 0,-2))[n]); } // stencil access version template static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) { return D1::difference(S.template getValue< 2, 0, 0>()[n], S.template getValue< 1, 0, 0>()[n], S.template getValue<-1, 0, 0>()[n], S.template getValue<-2, 0, 0>()[n] ); } template static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) { return D1::difference(S.template getValue< 0, 2, 0>()[n], S.template getValue< 0, 1, 0>()[n], S.template getValue< 0,-1, 0>()[n], S.template getValue< 0,-2, 0>()[n]); } template static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) { return D1::difference(S.template getValue< 0, 0, 2>()[n], S.template getValue< 0, 0, 1>()[n], S.template getValue< 0, 0,-1>()[n], S.template getValue< 0, 0,-2>()[n]); } }; template<> struct D1Vec { //typedef typename Accessor::ValueType::value_type::value_type ValueType; // random access version template static typename Accessor::ValueType::value_type inX(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy( 3, 0, 0))[n], grid.getValue(ijk.offsetBy( 2, 0, 0))[n], grid.getValue(ijk.offsetBy( 1, 0, 0))[n], grid.getValue(ijk.offsetBy(-1, 0, 0))[n], grid.getValue(ijk.offsetBy(-2, 0, 0))[n], grid.getValue(ijk.offsetBy(-3, 0, 0))[n] ); } template static typename Accessor::ValueType::value_type inY(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy( 0, 3, 0))[n], grid.getValue(ijk.offsetBy( 0, 2, 0))[n], grid.getValue(ijk.offsetBy( 0, 1, 0))[n], grid.getValue(ijk.offsetBy( 0,-1, 0))[n], grid.getValue(ijk.offsetBy( 0,-2, 0))[n], grid.getValue(ijk.offsetBy( 0,-3, 0))[n] ); } template static typename Accessor::ValueType::value_type inZ(const Accessor& grid, const Coord& ijk, int n) { return D1::difference( grid.getValue(ijk.offsetBy( 0, 0, 3))[n], grid.getValue(ijk.offsetBy( 0, 0, 2))[n], grid.getValue(ijk.offsetBy( 0, 0, 1))[n], grid.getValue(ijk.offsetBy( 0, 0,-1))[n], grid.getValue(ijk.offsetBy( 0, 0,-2))[n], grid.getValue(ijk.offsetBy( 0, 0,-3))[n] ); } // stencil access version template static typename Stencil::ValueType::value_type inX(const Stencil& S, int n) { return D1::difference( S.template getValue< 3, 0, 0>()[n], S.template getValue< 2, 0, 0>()[n], S.template getValue< 1, 0, 0>()[n], S.template getValue<-1, 0, 0>()[n], S.template getValue<-2, 0, 0>()[n], S.template getValue<-3, 0, 0>()[n] ); } template static typename Stencil::ValueType::value_type inY(const Stencil& S, int n) { return D1::difference( S.template getValue< 0, 3, 0>()[n], S.template getValue< 0, 2, 0>()[n], S.template getValue< 0, 1, 0>()[n], S.template getValue< 0,-1, 0>()[n], S.template getValue< 0,-2, 0>()[n], S.template getValue< 0,-3, 0>()[n] ); } template static typename Stencil::ValueType::value_type inZ(const Stencil& S, int n) { return D1::difference( S.template getValue< 0, 0, 3>()[n], S.template getValue< 0, 0, 2>()[n], S.template getValue< 0, 0, 1>()[n], S.template getValue< 0, 0,-1>()[n], S.template getValue< 0, 0,-2>()[n], S.template getValue< 0, 0,-3>()[n] ); } }; template struct D2 { template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk); template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk); template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk); // cross derivatives template static typename Accessor::ValueType inXandY(const Accessor& grid, const Coord& ijk); template static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk); template static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk); // stencil access version template static typename Stencil::ValueType inX(const Stencil& S); template static typename Stencil::ValueType inY(const Stencil& S); template static typename Stencil::ValueType inZ(const Stencil& S); // cross derivatives template static typename Stencil::ValueType inXandY(const Stencil& S); template static typename Stencil::ValueType inXandZ(const Stencil& S); template static typename Stencil::ValueType inYandZ(const Stencil& S); }; template<> struct D2 { // the difference opperator template static ValueType difference(const ValueType& xp1, const ValueType& xp0, const ValueType& xm1) { return xp1 + xm1 - ValueType(2)*xp0; } template static ValueType crossdifference(const ValueType& xpyp, const ValueType& xpym, const ValueType& xmyp, const ValueType& xmym) { return ValueType(0.25)*(xpyp + xmym - xpym - xmyp); } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 1,0,0)), grid.getValue(ijk), grid.getValue(ijk.offsetBy(-1,0,0)) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy(0, 1,0)), grid.getValue(ijk), grid.getValue(ijk.offsetBy(0,-1,0)) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference( grid.getValue(ijk.offsetBy( 0,0, 1)), grid.getValue(ijk), grid.getValue(ijk.offsetBy( 0,0,-1)) ); } // cross derivatives template static typename Accessor::ValueType inXandY(const Accessor& grid, const Coord& ijk) { return crossdifference(grid.getValue(ijk.offsetBy(1, 1,0)), grid.getValue(ijk.offsetBy( 1,-1,0)), grid.getValue(ijk.offsetBy(-1,1,0)), grid.getValue(ijk.offsetBy(-1,-1,0))); } template static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk) { return crossdifference(grid.getValue(ijk.offsetBy(1,0, 1)), grid.getValue(ijk.offsetBy(1, 0,-1)), grid.getValue(ijk.offsetBy(-1,0,1)), grid.getValue(ijk.offsetBy(-1,0,-1)) ); } template static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk) { return crossdifference(grid.getValue(ijk.offsetBy(0, 1,1)), grid.getValue(ijk.offsetBy(0, 1,-1)), grid.getValue(ijk.offsetBy(0,-1,1)), grid.getValue(ijk.offsetBy(0,-1,-1)) ); } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue<-1, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0,-1, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0, 0,-1>() ); } // cross derivatives template static typename Stencil::ValueType inXandY(const Stencil& S) { return crossdifference(S.template getValue< 1, 1, 0>(), S.template getValue< 1,-1, 0>(), S.template getValue<-1, 1, 0>(), S.template getValue<-1,-1, 0>() ); } template static typename Stencil::ValueType inXandZ(const Stencil& S) { return crossdifference(S.template getValue< 1, 0, 1>(), S.template getValue< 1, 0,-1>(), S.template getValue<-1, 0, 1>(), S.template getValue<-1, 0,-1>() ); } template static typename Stencil::ValueType inYandZ(const Stencil& S) { return crossdifference(S.template getValue< 0, 1, 1>(), S.template getValue< 0, 1,-1>(), S.template getValue< 0,-1, 1>(), S.template getValue< 0,-1,-1>() ); } }; template<> struct D2 { // the difference opperator template static ValueType difference(const ValueType& xp2, const ValueType& xp1, const ValueType& xp0, const ValueType& xm1, const ValueType& xm2) { return ValueType(-1./12.)*(xp2 + xm2) + ValueType(4./3.)*(xp1 + xm1) -ValueType(2.5)*xp0; } template static ValueType crossdifference(const ValueType& xp2yp2, const ValueType& xp2yp1, const ValueType& xp2ym1, const ValueType& xp2ym2, const ValueType& xp1yp2, const ValueType& xp1yp1, const ValueType& xp1ym1, const ValueType& xp1ym2, const ValueType& xm2yp2, const ValueType& xm2yp1, const ValueType& xm2ym1, const ValueType& xm2ym2, const ValueType& xm1yp2, const ValueType& xm1yp1, const ValueType& xm1ym1, const ValueType& xm1ym2 ) { ValueType tmp1 = ValueType(2./3.0)*(xp1yp1 - xm1yp1 - xp1ym1 + xm1ym1)- ValueType(1./12.)*(xp2yp1 - xm2yp1 - xp2ym1 + xm2ym1); ValueType tmp2 = ValueType(2./3.0)*(xp1yp2 - xm1yp2 - xp1ym2 + xm1ym2)- ValueType(1./12.)*(xp2yp2 - xm2yp2 - xp2ym2 + xm2ym2); return ValueType(2./3.)*tmp1 - ValueType(1./12.)*tmp2; } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(2,0,0)), grid.getValue(ijk.offsetBy( 1,0,0)), grid.getValue(ijk), grid.getValue(ijk.offsetBy(-1,0,0)), grid.getValue(ijk.offsetBy(-2, 0, 0))); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0, 2,0)), grid.getValue(ijk.offsetBy(0, 1,0)), grid.getValue(ijk), grid.getValue(ijk.offsetBy(0,-1,0)), grid.getValue(ijk.offsetBy(0,-2, 0))); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy(0,0, 2)), grid.getValue(ijk.offsetBy(0, 0,1)), grid.getValue(ijk), grid.getValue(ijk.offsetBy(0,0,-1)), grid.getValue(ijk.offsetBy(0,0,-2))); } // cross derivatives template static typename Accessor::ValueType inXandY(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typename Accessor::ValueType tmp1 = D1::inX(grid, ijk.offsetBy(0, 1, 0)) - D1::inX(grid, ijk.offsetBy(0,-1, 0)); typename Accessor::ValueType tmp2 = D1::inX(grid, ijk.offsetBy(0, 2, 0)) - D1::inX(grid, ijk.offsetBy(0,-2, 0)); return ValueType(2./3.)*tmp1 - ValueType(1./12.)*tmp2; } template static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typename Accessor::ValueType tmp1 = D1::inX(grid, ijk.offsetBy(0, 0, 1)) - D1::inX(grid, ijk.offsetBy(0, 0,-1)); typename Accessor::ValueType tmp2 = D1::inX(grid, ijk.offsetBy(0, 0, 2)) - D1::inX(grid, ijk.offsetBy(0, 0,-2)); return ValueType(2./3.)*tmp1 - ValueType(1./12.)*tmp2; } template static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typename Accessor::ValueType tmp1 = D1::inY(grid, ijk.offsetBy(0, 0, 1)) - D1::inY(grid, ijk.offsetBy(0, 0,-1)); typename Accessor::ValueType tmp2 = D1::inY(grid, ijk.offsetBy(0, 0, 2)) - D1::inY(grid, ijk.offsetBy(0, 0,-2)); return ValueType(2./3.)*tmp1 - ValueType(1./12.)*tmp2; } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference(S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference(S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference(S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>() ); } // cross derivatives template static typename Stencil::ValueType inXandY(const Stencil& S) { return crossdifference( S.template getValue< 2, 2, 0>(), S.template getValue< 2, 1, 0>(), S.template getValue< 2,-1, 0>(), S.template getValue< 2,-2, 0>(), S.template getValue< 1, 2, 0>(), S.template getValue< 1, 1, 0>(), S.template getValue< 1,-1, 0>(), S.template getValue< 1,-2, 0>(), S.template getValue<-2, 2, 0>(), S.template getValue<-2, 1, 0>(), S.template getValue<-2,-1, 0>(), S.template getValue<-2,-2, 0>(), S.template getValue<-1, 2, 0>(), S.template getValue<-1, 1, 0>(), S.template getValue<-1,-1, 0>(), S.template getValue<-1,-2, 0>() ); } template static typename Stencil::ValueType inXandZ(const Stencil& S) { return crossdifference( S.template getValue< 2, 0, 2>(), S.template getValue< 2, 0, 1>(), S.template getValue< 2, 0,-1>(), S.template getValue< 2, 0,-2>(), S.template getValue< 1, 0, 2>(), S.template getValue< 1, 0, 1>(), S.template getValue< 1, 0,-1>(), S.template getValue< 1, 0,-2>(), S.template getValue<-2, 0, 2>(), S.template getValue<-2, 0, 1>(), S.template getValue<-2, 0,-1>(), S.template getValue<-2, 0,-2>(), S.template getValue<-1, 0, 2>(), S.template getValue<-1, 0, 1>(), S.template getValue<-1, 0,-1>(), S.template getValue<-1, 0,-2>() ); } template static typename Stencil::ValueType inYandZ(const Stencil& S) { return crossdifference( S.template getValue< 0, 2, 2>(), S.template getValue< 0, 2, 1>(), S.template getValue< 0, 2,-1>(), S.template getValue< 0, 2,-2>(), S.template getValue< 0, 1, 2>(), S.template getValue< 0, 1, 1>(), S.template getValue< 0, 1,-1>(), S.template getValue< 0, 1,-2>(), S.template getValue< 0,-2, 2>(), S.template getValue< 0,-2, 1>(), S.template getValue< 0,-2,-1>(), S.template getValue< 0,-2,-2>(), S.template getValue< 0,-1, 2>(), S.template getValue< 0,-1, 1>(), S.template getValue< 0,-1,-1>(), S.template getValue< 0,-1,-2>() ); } }; template<> struct D2 { // the difference opperator template static ValueType difference(const ValueType& xp3, const ValueType& xp2, const ValueType& xp1, const ValueType& xp0, const ValueType& xm1, const ValueType& xm2, const ValueType& xm3) { return ValueType(1./90.)*(xp3 + xm3) - ValueType(3./20.)*(xp2 + xm2) + ValueType(1.5)*(xp1 + xm1) - ValueType(49./18.)*xp0; } template static ValueType crossdifference( const ValueType& xp1yp1,const ValueType& xm1yp1, const ValueType& xp1ym1,const ValueType& xm1ym1, const ValueType& xp2yp1,const ValueType& xm2yp1, const ValueType& xp2ym1,const ValueType& xm2ym1, const ValueType& xp3yp1,const ValueType& xm3yp1, const ValueType& xp3ym1,const ValueType& xm3ym1, const ValueType& xp1yp2,const ValueType& xm1yp2, const ValueType& xp1ym2,const ValueType& xm1ym2, const ValueType& xp2yp2,const ValueType& xm2yp2, const ValueType& xp2ym2,const ValueType& xm2ym2, const ValueType& xp3yp2,const ValueType& xm3yp2, const ValueType& xp3ym2,const ValueType& xm3ym2, const ValueType& xp1yp3,const ValueType& xm1yp3, const ValueType& xp1ym3,const ValueType& xm1ym3, const ValueType& xp2yp3,const ValueType& xm2yp3, const ValueType& xp2ym3,const ValueType& xm2ym3, const ValueType& xp3yp3,const ValueType& xm3yp3, const ValueType& xp3ym3,const ValueType& xm3ym3 ) { ValueType tmp1 = ValueType(0.7500)*(xp1yp1 - xm1yp1 - xp1ym1 + xm1ym1) - ValueType(0.1500)*(xp2yp1 - xm2yp1 - xp2ym1 + xm2ym1) + ValueType(1./60.)*(xp3yp1 - xm3yp1 - xp3ym1 + xm3ym1); ValueType tmp2 = ValueType(0.7500)*(xp1yp2 - xm1yp2 - xp1ym2 + xm1ym2) - ValueType(0.1500)*(xp2yp2 - xm2yp2 - xp2ym2 + xm2ym2) + ValueType(1./60.)*(xp3yp2 - xm3yp2 - xp3ym2 + xm3ym2); ValueType tmp3 = ValueType(0.7500)*(xp1yp3 - xm1yp3 - xp1ym3 + xm1ym3) - ValueType(0.1500)*(xp2yp3 - xm2yp3 - xp2ym3 + xm2ym3) + ValueType(1./60.)*(xp3yp3 - xm3yp3 - xp3ym3 + xm3ym3); return ValueType(0.75)*tmp1 - ValueType(0.15)*tmp2 + ValueType(1./60)*tmp3; } // random access version template static typename Accessor::ValueType inX(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy( 3, 0, 0)), grid.getValue(ijk.offsetBy( 2, 0, 0)), grid.getValue(ijk.offsetBy( 1, 0, 0)), grid.getValue(ijk), grid.getValue(ijk.offsetBy(-1, 0, 0)), grid.getValue(ijk.offsetBy(-2, 0, 0)), grid.getValue(ijk.offsetBy(-3, 0, 0)) ); } template static typename Accessor::ValueType inY(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy( 0, 3, 0)), grid.getValue(ijk.offsetBy( 0, 2, 0)), grid.getValue(ijk.offsetBy( 0, 1, 0)), grid.getValue(ijk), grid.getValue(ijk.offsetBy( 0,-1, 0)), grid.getValue(ijk.offsetBy( 0,-2, 0)), grid.getValue(ijk.offsetBy( 0,-3, 0)) ); } template static typename Accessor::ValueType inZ(const Accessor& grid, const Coord& ijk) { return difference(grid.getValue(ijk.offsetBy( 0, 0, 3)), grid.getValue(ijk.offsetBy( 0, 0, 2)), grid.getValue(ijk.offsetBy( 0, 0, 1)), grid.getValue(ijk), grid.getValue(ijk.offsetBy( 0, 0,-1)), grid.getValue(ijk.offsetBy( 0, 0,-2)), grid.getValue(ijk.offsetBy( 0, 0,-3)) ); } template static typename Accessor::ValueType inXandY(const Accessor& grid, const Coord& ijk) { typename Accessor::ValueType tmp1 = D1::inX(grid, ijk.offsetBy(0, 1, 0)) - D1::inX(grid, ijk.offsetBy(0,-1, 0)); typename Accessor::ValueType tmp2 = D1::inX(grid, ijk.offsetBy(0, 2, 0)) - D1::inX(grid, ijk.offsetBy(0,-2, 0)); typename Accessor::ValueType tmp3 = D1::inX(grid, ijk.offsetBy(0, 3, 0)) - D1::inX(grid, ijk.offsetBy(0,-3, 0)); return 0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3; } template static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk) { typename Accessor::ValueType tmp1 = D1::inX(grid, ijk.offsetBy(0, 0, 1)) - D1::inX(grid, ijk.offsetBy(0, 0,-1)); typename Accessor::ValueType tmp2 = D1::inX(grid, ijk.offsetBy(0, 0, 2)) - D1::inX(grid, ijk.offsetBy(0, 0,-2)); typename Accessor::ValueType tmp3 = D1::inX(grid, ijk.offsetBy(0, 0, 3)) - D1::inX(grid, ijk.offsetBy(0, 0,-3)); return 0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3; } template static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk) { typename Accessor::ValueType tmp1 = D1::inY(grid, ijk.offsetBy(0, 0, 1)) - D1::inY(grid, ijk.offsetBy(0, 0,-1)); typename Accessor::ValueType tmp2 = D1::inY(grid, ijk.offsetBy(0, 0, 2)) - D1::inY(grid, ijk.offsetBy(0, 0,-2)); typename Accessor::ValueType tmp3 = D1::inY(grid, ijk.offsetBy(0, 0, 3)) - D1::inY(grid, ijk.offsetBy(0, 0,-3)); return 0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3; } // stencil access version template static typename Stencil::ValueType inX(const Stencil& S) { return difference( S.template getValue< 3, 0, 0>(), S.template getValue< 2, 0, 0>(), S.template getValue< 1, 0, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue<-1, 0, 0>(), S.template getValue<-2, 0, 0>(), S.template getValue<-3, 0, 0>() ); } template static typename Stencil::ValueType inY(const Stencil& S) { return difference( S.template getValue< 0, 3, 0>(), S.template getValue< 0, 2, 0>(), S.template getValue< 0, 1, 0>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0,-1, 0>(), S.template getValue< 0,-2, 0>(), S.template getValue< 0,-3, 0>() ); } template static typename Stencil::ValueType inZ(const Stencil& S) { return difference( S.template getValue< 0, 0, 3>(), S.template getValue< 0, 0, 2>(), S.template getValue< 0, 0, 1>(), S.template getValue< 0, 0, 0>(), S.template getValue< 0, 0,-1>(), S.template getValue< 0, 0,-2>(), S.template getValue< 0, 0,-3>() ); } template static typename Stencil::ValueType inXandY(const Stencil& S) { return crossdifference( S.template getValue< 1, 1, 0>(), S.template getValue<-1, 1, 0>(), S.template getValue< 1,-1, 0>(), S.template getValue<-1,-1, 0>(), S.template getValue< 2, 1, 0>(), S.template getValue<-2, 1, 0>(), S.template getValue< 2,-1, 0>(), S.template getValue<-2,-1, 0>(), S.template getValue< 3, 1, 0>(), S.template getValue<-3, 1, 0>(), S.template getValue< 3,-1, 0>(), S.template getValue<-3,-1, 0>(), S.template getValue< 1, 2, 0>(), S.template getValue<-1, 2, 0>(), S.template getValue< 1,-2, 0>(), S.template getValue<-1,-2, 0>(), S.template getValue< 2, 2, 0>(), S.template getValue<-2, 2, 0>(), S.template getValue< 2,-2, 0>(), S.template getValue<-2,-2, 0>(), S.template getValue< 3, 2, 0>(), S.template getValue<-3, 2, 0>(), S.template getValue< 3,-2, 0>(), S.template getValue<-3,-2, 0>(), S.template getValue< 1, 3, 0>(), S.template getValue<-1, 3, 0>(), S.template getValue< 1,-3, 0>(), S.template getValue<-1,-3, 0>(), S.template getValue< 2, 3, 0>(), S.template getValue<-2, 3, 0>(), S.template getValue< 2,-3, 0>(), S.template getValue<-2,-3, 0>(), S.template getValue< 3, 3, 0>(), S.template getValue<-3, 3, 0>(), S.template getValue< 3,-3, 0>(), S.template getValue<-3,-3, 0>() ); } template static typename Stencil::ValueType inXandZ(const Stencil& S) { return crossdifference( S.template getValue< 1, 0, 1>(), S.template getValue<-1, 0, 1>(), S.template getValue< 1, 0,-1>(), S.template getValue<-1, 0,-1>(), S.template getValue< 2, 0, 1>(), S.template getValue<-2, 0, 1>(), S.template getValue< 2, 0,-1>(), S.template getValue<-2, 0,-1>(), S.template getValue< 3, 0, 1>(), S.template getValue<-3, 0, 1>(), S.template getValue< 3, 0,-1>(), S.template getValue<-3, 0,-1>(), S.template getValue< 1, 0, 2>(), S.template getValue<-1, 0, 2>(), S.template getValue< 1, 0,-2>(), S.template getValue<-1, 0,-2>(), S.template getValue< 2, 0, 2>(), S.template getValue<-2, 0, 2>(), S.template getValue< 2, 0,-2>(), S.template getValue<-2, 0,-2>(), S.template getValue< 3, 0, 2>(), S.template getValue<-3, 0, 2>(), S.template getValue< 3, 0,-2>(), S.template getValue<-3, 0,-2>(), S.template getValue< 1, 0, 3>(), S.template getValue<-1, 0, 3>(), S.template getValue< 1, 0,-3>(), S.template getValue<-1, 0,-3>(), S.template getValue< 2, 0, 3>(), S.template getValue<-2, 0, 3>(), S.template getValue< 2, 0,-3>(), S.template getValue<-2, 0,-3>(), S.template getValue< 3, 0, 3>(), S.template getValue<-3, 0, 3>(), S.template getValue< 3, 0,-3>(), S.template getValue<-3, 0,-3>() ); } template static typename Stencil::ValueType inYandZ(const Stencil& S) { return crossdifference( S.template getValue< 0, 1, 1>(), S.template getValue< 0,-1, 1>(), S.template getValue< 0, 1,-1>(), S.template getValue< 0,-1,-1>(), S.template getValue< 0, 2, 1>(), S.template getValue< 0,-2, 1>(), S.template getValue< 0, 2,-1>(), S.template getValue< 0,-2,-1>(), S.template getValue< 0, 3, 1>(), S.template getValue< 0,-3, 1>(), S.template getValue< 0, 3,-1>(), S.template getValue< 0,-3,-1>(), S.template getValue< 0, 1, 2>(), S.template getValue< 0,-1, 2>(), S.template getValue< 0, 1,-2>(), S.template getValue< 0,-1,-2>(), S.template getValue< 0, 2, 2>(), S.template getValue< 0,-2, 2>(), S.template getValue< 0, 2,-2>(), S.template getValue< 0,-2,-2>(), S.template getValue< 0, 3, 2>(), S.template getValue< 0,-3, 2>(), S.template getValue< 0, 3,-2>(), S.template getValue< 0,-3,-2>(), S.template getValue< 0, 1, 3>(), S.template getValue< 0,-1, 3>(), S.template getValue< 0, 1,-3>(), S.template getValue< 0,-1,-3>(), S.template getValue< 0, 2, 3>(), S.template getValue< 0,-2, 3>(), S.template getValue< 0, 2,-3>(), S.template getValue< 0,-2,-3>(), S.template getValue< 0, 3, 3>(), S.template getValue< 0,-3, 3>(), S.template getValue< 0, 3,-3>(), S.template getValue< 0,-3,-3>() ); } }; } // end math namespace } // namespace OPENVDB_VERSION_NAME } // end openvdb namespace #endif // OPENVDB_MATH_FINITEDIFFERENCE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Stats.h0000644000000000000000000002375412252453157013432 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Stats.h /// /// @brief Classes to compute statistics and histograms #ifndef OPENVDB_MATH_STATS_HAS_BEEN_INCLUDED #define OPENVDB_MATH_STATS_HAS_BEEN_INCLUDED #include // for ostringstream #include #include #include #include #include #include "Math.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief This class computes statistics (minimum value, maximum /// value, mean, variance and standard deviation) of a population /// of floating-point values. /// /// @details variance = Mean[ (X-Mean[X])^2 ] = Mean[X^2] - Mean[X]^2, /// standard deviation = sqrt(variance) /// /// @note This class employs incremental computation and double precision. class Stats { public: Stats(): mSize(0), mAvg(0.0), mAux(0.0), mMin(std::numeric_limits::max()), mMax(-mMin) {} /// Add a single sample. void add(double val) { mSize++; mMin = std::min(val, mMin); mMax = std::max(val, mMax); const double delta = val - mAvg; mAvg += delta/double(mSize); mAux += delta*(val - mAvg); } /// Add @a n samples with constant value @a val. void add(double val, uint64_t n) { mMin = std::min(val, mMin); mMax = std::max(val, mMax); const double denom = 1.0/double(mSize + n); const double delta = val - mAvg; mAvg += denom*delta*n; mAux += denom*delta*delta*mSize*n; mSize += n; } /// Add the samples from the other Stats instance. void add(const Stats& other) { if (other.mSize > 0) { mMin = std::min(mMin, other.mMin); mMax = std::max(mMax, other.mMax); const double denom = 1.0/double(mSize + other.mSize); const double delta = other.mAvg - mAvg; mAvg += denom*delta*other.mSize; mAux += other.mAux + denom*delta*delta*mSize*other.mSize; mSize += other.mSize; } } /// Return the size of the population, i.e., the total number of samples. inline uint64_t size() const { return mSize; } /// Return the minimum value. inline double min() const { return mMin; } /// Return the maximum value. inline double max() const { return mMax; } //@{ /// Return the arithmetic mean, i.e. average, value. inline double avg() const { return mAvg; } inline double mean() const { return mAvg; } //@} //@{ /// @brief Return the population variance. /// @note The unbiased sample variance = population variance * //num/(num-1) inline double var() const { return mSize<2 ? 0.0 : mAux/double(mSize); } inline double variance() const { return this->var(); } //@} //@{ /// @brief Return the standard deviation (=Sqrt(variance)) as /// defined from the (biased) population variance. inline double std() const { return sqrt(this->var()); } inline double stdDev() const { return this->std(); } //@} /// @brief Print statistics to the specified output stream. void print(const std::string &name= "", std::ostream &strm=std::cout, int precision=3) const { // Write to a temporary string stream so as not to affect the state // (precision, field width, etc.) of the output stream. std::ostringstream os; os << std::setprecision(precision) << std::setiosflags(std::ios::fixed); os << "Statistics "; if (!name.empty()) os << "for \"" << name << "\" "; if (mSize>0) { os << "with " << mSize << " samples:\n" << " Min=" << mMin << ", Max=" << mMax << ", Ave=" << mAvg << ", Std=" << this->stdDev() << ", Var=" << this->variance() << std::endl; } else { os << ": no samples were added." << std::endl; } strm << os.str(); } private: uint64_t mSize; double mAvg, mAux, mMin, mMax; }; // end Stats //////////////////////////////////////// /// @brief This class computes a histogram, with a fixed interval width, /// of a population of floating-point values. class Histogram { public: /// Construct with given minimum and maximum values and the given bin count. Histogram(double min, double max, size_t numBins = 10) : mSize(0), mMin(min), mMax(max+1e-10), mDelta(double(numBins)/(max-min)), mBins(numBins) { assert(numBins > 1); assert(mMax-mMin > 1e-10); for (size_t i=0; i 1); assert(mMax-mMin > 1e-10); for (size_t i=0; imMax) return false; mBins[size_t(mDelta*(val-mMin))] += n; mSize += n; return true; } /// @brief Add all the contributions from the other histogram, provided that /// it has the same configuration as this histogram. bool add(const Histogram& other) { if (!isApproxEqual(mMin, other.mMin) || !isApproxEqual(mMax, other.mMax) || mBins.size() != other.mBins.size()) return false; for (size_t i=0, e=mBins.size(); i!=e; ++i) mBins[i] += other.mBins[i]; mSize += other.mSize; return true; } /// Return the number of bins in this histogram. inline size_t numBins() const { return mBins.size(); } /// Return the lower bound of this histogram's value range. inline double min() const { return mMin; } /// Return the upper bound of this histogram's value range. inline double max() const { return mMax; } /// Return the minimum value in the nth bin. inline double min(int n) const { return mMin+n/mDelta; } /// Return the maximum value in the nth bin. inline double max(int n) const { return mMin+(n+1)/mDelta; } /// Return the number of samples in the nth bin. inline uint64_t count(int n) const { return mBins[n]; } /// Return the population size, i.e., the total number of samples. inline uint64_t size() const { return mSize; } /// Print the histogram to the specified output stream. void print(const std::string& name = "", std::ostream& strm = std::cout) const { // Write to a temporary string stream so as not to affect the state // (precision, field width, etc.) of the output stream. std::ostringstream os; os << std::setprecision(6) << std::setiosflags(std::ios::fixed) << std::endl; os << "Histogram "; if (!name.empty()) os << "for \"" << name << "\" "; if (mSize > 0) { os << "with " << mSize << " samples:\n"; os << "==============================================================\n"; os << "|| # | Min | Max | Frequency | % ||\n"; os << "==============================================================\n"; for (size_t i=0, e=mBins.size(); i!=e; ++i) { os << "|| " << std::setw(4) << i << " | " << std::setw(14) << this->min(i) << " | " << std::setw(14) << this->max(i) << " | " << std::setw(9) << mBins[i] << " | " << std::setw(3) << (100*mBins[i]/mSize) << " ||\n"; } os << "==============================================================\n"; } else { os << ": no samples were added." << std::endl; } strm << os.str(); } private: uint64_t mSize; double mMin, mMax, mDelta; std::vector mBins; }; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_STATS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Maps.cc0000644000000000000000000002145312252453157013364 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Maps.h" #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { namespace { typedef tbb::mutex Mutex; typedef Mutex::scoped_lock Lock; // Declare this at file scope to ensure thread-safe initialization. // NOTE: Do *NOT* move this into Maps.h or else we will need to pull in // Windows.h with things like 'rad2' defined! Mutex sInitMapRegistryMutex; } // unnamed namespace //////////////////////////////////////// MapRegistry* MapRegistry::mInstance = NULL; // Caller is responsible for calling this function serially. MapRegistry* MapRegistry::staticInstance() { if (mInstance == NULL) { OPENVDB_START_THREADSAFE_STATIC_WRITE mInstance = new MapRegistry(); OPENVDB_FINISH_THREADSAFE_STATIC_WRITE return mInstance; } return mInstance; } MapRegistry* MapRegistry::instance() { Lock lock(sInitMapRegistryMutex); return staticInstance(); } MapBase::Ptr MapRegistry::createMap(const Name& name) { Lock lock(sInitMapRegistryMutex); MapDictionary::const_iterator iter = staticInstance()->mMap.find(name); if (iter == staticInstance()->mMap.end()) { OPENVDB_THROW(LookupError, "Cannot create map of unregistered type " << name); } return (iter->second)(); } bool MapRegistry::isRegistered(const Name& name) { Lock lock(sInitMapRegistryMutex); return (staticInstance()->mMap.find(name) != staticInstance()->mMap.end()); } void MapRegistry::registerMap(const Name& name, MapBase::MapFactory factory) { Lock lock(sInitMapRegistryMutex); if (staticInstance()->mMap.find(name) != staticInstance()->mMap.end()) { OPENVDB_THROW(KeyError, "Map type " << name << " is already registered"); } staticInstance()->mMap[name] = factory; } void MapRegistry::unregisterMap(const Name& name) { Lock lock(sInitMapRegistryMutex); staticInstance()->mMap.erase(name); } void MapRegistry::clear() { Lock lock(sInitMapRegistryMutex); staticInstance()->mMap.clear(); } //////////////////////////////////////// // Utility methods for decomposition SymmetricMap::Ptr createSymmetricMap(const Mat3d& m) { // test that the mat3 is a rotation || reflection if (!isSymmetric(m)) { OPENVDB_THROW(ArithmeticError, "3x3 Matrix initializing symmetric map was not symmetric"); } Vec3d eigenValues; Mat3d Umatrix; bool converged = math::diagonalizeSymmetricMatrix(m, Umatrix, eigenValues); if (!converged) { OPENVDB_THROW(ArithmeticError, "Diagonalization of the symmetric matrix failed"); } UnitaryMap rotation(Umatrix); ScaleMap diagonal(eigenValues); CompoundMap first(rotation, diagonal); UnitaryMap rotationInv(Umatrix.transpose()); return SymmetricMap::Ptr( new SymmetricMap(first, rotationInv)); } PolarDecomposedMap::Ptr createPolarDecomposedMap(const Mat3d& m) { // Because our internal libary left-multiplies vectors against matrices // we are constructing M = Symmetric * Unitary instead of the more // standard M = Unitary * Symmetric Mat3d unitary, symmetric, mat3 = m.transpose(); // factor mat3 = U * S where U is unitary and S is symmetric bool gotPolar = math::polarDecomposition(mat3, unitary, symmetric); if (!gotPolar) { OPENVDB_THROW(ArithmeticError, "Polar decomposition of transform failed"); } // put the result in a polar map and then copy it into the output polar UnitaryMap unitary_map(unitary.transpose()); SymmetricMap::Ptr symmetric_map = createSymmetricMap(symmetric); return PolarDecomposedMap::Ptr(new PolarDecomposedMap(*symmetric_map, unitary_map)); } FullyDecomposedMap::Ptr createFullyDecomposedMap(const Mat4d& m) { if (!isAffine(m)) { OPENVDB_THROW(ArithmeticError, "4x4 Matrix initializing Decomposition map was not affine"); } TranslationMap translate(m.getTranslation()); PolarDecomposedMap::Ptr polar = createPolarDecomposedMap(m.getMat3()); UnitaryAndTranslationMap rotationAndTranslate(polar->secondMap(), translate); return FullyDecomposedMap::Ptr(new FullyDecomposedMap(polar->firstMap(), rotationAndTranslate)); } MapBase::Ptr simplify(AffineMap::Ptr affine) { if (affine->isScale()) { // can be simplified into a ScaleMap Vec3d scale = affine->applyMap(Vec3d(1,1,1)); if (isApproxEqual(scale[0], scale[1]) && isApproxEqual(scale[0], scale[2])) { return MapBase::Ptr(new UniformScaleMap(scale[0])); } else { return MapBase::Ptr(new ScaleMap(scale)); } } else if (affine->isScaleTranslate()) { // can be simplified into a ScaleTranslateMap Vec3d translate = affine->applyMap(Vec3d(0,0,0)); Vec3d scale = affine->applyMap(Vec3d(1,1,1)) - translate; if (isApproxEqual(scale[0], scale[1]) && isApproxEqual(scale[0], scale[2])) { return MapBase::Ptr(new UniformScaleTranslateMap(scale[0], translate)); } else { return MapBase::Ptr(new ScaleTranslateMap(scale, translate)); } } // could not simplify the general Affine map. return boost::static_pointer_cast(affine); } Mat4d approxInverse(const Mat4d& mat4d) { if (std::abs(mat4d.det()) >= 3 * math::Tolerance::value()) { try { Mat4d result = mat4d.inverse(); return result; } catch (ArithmeticError& ) { // Mat4 code couldn't invert. } } const Mat3d mat3 = mat4d.getMat3(); const Mat3d mat3T = mat3.transpose(); const Vec3d trans = mat4d.getTranslation(); // absolute tolerance used for the symmetric test. const double tol = 1.e-6; // only create the pseudoInverse for symmetric bool symmetric = true; for (int i = 0; i < 3; ++i ) { for (int j = 0; j < 3; ++j ) { if (!isApproxEqual(mat3[i][j], mat3T[i][j], tol)) { symmetric = false; } } } if (!symmetric) { // not symmetric, so just zero out the mat3 inverse and reverse the translation Mat4d result = Mat4d::zero(); result.setTranslation(-trans); result[3][3] = 1.f; return result; } else { // compute the pseudo inverse Mat3d eigenVectors; Vec3d eigenValues; diagonalizeSymmetricMatrix(mat3, eigenVectors, eigenValues); Mat3d d = Mat3d::identity(); for (int i = 0; i < 3; ++i ) { if (std::abs(eigenValues[i]) < 10.0 * math::Tolerance::value()) { d[i][i] = 0.f; } else { d[i][i] = 1.f/eigenValues[i]; } } // assemble the pseudo inverse Mat3d pseudoInv = eigenVectors * d * eigenVectors.transpose(); Vec3d invTrans = -trans * pseudoInv; Mat4d result = Mat4d::identity(); result.setMat3(pseudoInv); result.setTranslation(invTrans); return result; } } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Proximity.cc0000644000000000000000000003057212252453157014472 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Proximity.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { OPENVDB_API Vec3d closestPointOnTriangleToPoint( const Vec3d& a, const Vec3d& b, const Vec3d& c, const Vec3d& p, Vec3d& uvw) { uvw.setZero(); // degenerate triangle, singular if ((isApproxEqual(a, b) && isApproxEqual(a, c))) { uvw[0] = 1.0; return a; } Vec3d ab = b - a, ac = c - a, ap = p - a; double d1 = ab.dot(ap), d2 = ac.dot(ap); // degenerate triangle edges if (isApproxEqual(a, b)) { double t = 0.0; Vec3d cp = closestPointOnSegmentToPoint(a, c, p, t); uvw[0] = 1.0 - t; uvw[2] = t; return cp; } else if (isApproxEqual(a, c) || isApproxEqual(b, c)) { double t = 0.0; Vec3d cp = closestPointOnSegmentToPoint(a, b, p, t); uvw[0] = 1.0 - t; uvw[1] = t; return cp; } if (d1 <= 0.0 && d2 <= 0.0) { uvw[0] = 1.0; return a; // barycentric coordinates (1,0,0) } // Check if P in vertex region outside B Vec3d bp = p - b; double d3 = ab.dot(bp), d4 = ac.dot(bp); if (d3 >= 0.0 && d4 <= d3) { uvw[1] = 1.0; return b; // barycentric coordinates (0,1,0) } // Check if P in edge region of AB, if so return projection of P onto AB double vc = d1 * d4 - d3 * d2; if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) { uvw[1] = d1 / (d1 - d3); uvw[0] = 1.0 - uvw[1]; return a + uvw[1] * ab; // barycentric coordinates (1-v,v,0) } // Check if P in vertex region outside C Vec3d cp = p - c; double d5 = ab.dot(cp), d6 = ac.dot(cp); if (d6 >= 0.0 && d5 <= d6) { uvw[2] = 1.0; return c; // barycentric coordinates (0,0,1) } // Check if P in edge region of AC, if so return projection of P onto AC double vb = d5 * d2 - d1 * d6; if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) { uvw[2] = d2 / (d2 - d6); uvw[0] = 1.0 - uvw[2]; return a + uvw[2] * ac; // barycentric coordinates (1-w,0,w) } // Check if P in edge region of BC, if so return projection of P onto BC double va = d3*d6 - d5*d4; if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) { uvw[2] = (d4 - d3) / ((d4 - d3) + (d5 - d6)); uvw[1] = 1.0 - uvw[2]; return b + uvw[2] * (c - b); // barycentric coordinates (0,1-w,w) } // P inside face region. Compute Q through its barycentric coordinates (u,v,w) double denom = 1.0 / (va + vb + vc); uvw[2] = vc * denom; uvw[1] = vb * denom; uvw[0] = 1.0 - uvw[1] - uvw[2]; return a + ab*uvw[1] + ac*uvw[2]; // = u*a + v*b + w*c , u= va*denom = 1.0-v-w } OPENVDB_API Vec3d closestPointOnSegmentToPoint(const Vec3d& a, const Vec3d& b, const Vec3d& p, double& t) { Vec3d ab = b - a; t = (p - a).dot(ab); if (t <= 0.0) { // c projects outside the [a,b] interval, on the a side. t = 0.0; return a; } else { // always nonnegative since denom = ||ab||^2 double denom = ab.dot(ab); if (t >= denom) { // c projects outside the [a,b] interval, on the b side. t = 1.0; return b; } else { // c projects inside the [a,b] interval. t = t / denom; return a + (ab * t); } } } //////////////////////////////////////// // DEPRECATED METHODS double sLineSeg3ToPointDistSqr(const Vec3d &p0, const Vec3d &p1, const Vec3d &point, double &t, double epsilon) { Vec3d pDelta; Vec3d tDelta; double pDeltaDot; pDelta.sub(p1, p0); tDelta.sub(point, p0); // // Line is nearly a point check end points // pDeltaDot = pDelta.dot(pDelta); if (pDeltaDot < epsilon) { pDelta.sub(p1, point); if (pDelta.dot(pDelta) < tDelta.dot(tDelta)) { t = 1; return pDelta.dot(pDelta); } else { t = 0; return tDelta.dot(tDelta); } } t = tDelta.dot(pDelta) / pDeltaDot; if (t < 0) { t = 0; } else if (t > 1) { t = 1; tDelta.sub(point, p1); } else { tDelta -= t * pDelta; } return tDelta.dot(tDelta); } //////////////////////////////////////// double sTri3ToPointDistSqr(const Vec3d &v0, const Vec3d &v1, const Vec3d &v2, const Vec3d &point, Vec2d &uv, double) { Vec3d e0, e1; double distSqr; e0.sub(v1, v0); e1.sub(v2, v0); Vec3d delta = v0 - point; double a00 = e0.dot(e0); double a01 = e0.dot(e1); double a11 = e1.dot(e1); double b0 = delta.dot(e0); double b1 = delta.dot(e1); double c = delta.dot(delta); double det = fabs(a00*a11-a01*a01); /* DEPRECATED double aMax = (a00 > a11) ? a00 : a11; double epsilon2 = epsilon * epsilon; // // Triangle is degenerate. Use an absolute test for the length // of the edges and a relative test for area squared // if ((a00 <= epsilon2 && a11 <= epsilon2) || det <= epsilon * aMax * aMax) { double t; double minDistSqr; minDistSqr = sLineSeg3ToPointDistSqr(v0, v1, point, t, epsilon); uv[0] = 1.0 - t; uv[1] = t; distSqr = sLineSeg3ToPointDistSqr(v0, v2, point, t, epsilon); if (distSqr < minDistSqr) { minDistSqr = distSqr; uv[0] = 1.0 - t; uv[1] = 0; } distSqr = sLineSeg3ToPointDistSqr(v1, v2, point, t, epsilon); if (distSqr < minDistSqr) { minDistSqr = distSqr; uv[0] = 0; uv[1] = 1.0 - t; } return minDistSqr; }*/ double s = a01*b1-a11*b0; double t = a01*b0-a00*b1; if (s + t <= det ) { if (s < 0.0) { if (t < 0.0) { // region 4 if (b0 < 0.0) { t = 0.0; if (-b0 >= a00) { s = 1.0; distSqr = a00+2.0*b0+c; } else { s = -b0/a00; distSqr = b0*s+c; } } else { s = 0.0; if (b1 >= 0.0) { t = 0.0; distSqr = c; } else if (-b1 >= a11) { t = 1.0; distSqr = a11+2.0*b1+c; } else { t = -b1/a11; distSqr = b1*t+c; } } } else { // region 3 s = 0.0; if (b1 >= 0.0) { t = 0.0; distSqr = c; } else if (-b1 >= a11) { t = 1.0; distSqr = a11+2.0*b1+c; } else { t = -b1/a11; distSqr = b1*t+c; } } } else if (t < 0.0) { // region 5 t = 0.0; if (b0 >= 0.0) { s = 0.0; distSqr = c; } else if (-b0 >= a00) { s = 1.0; distSqr = a00+2.0*b0+c; } else { s = -b0/a00; distSqr = b0*s+c; } } else { // region 0 // minimum at interior point double fInvDet = 1.0/det; s *= fInvDet; t *= fInvDet; distSqr = s*(a00*s+a01*t+2.0*b0) + t*(a01*s+a11*t+2.0*b1)+c; } } else { double tmp0, tmp1, numer, denom; if (s < 0.0) { // region 2 tmp0 = a01 + b0; tmp1 = a11 + b1; if (tmp1 > tmp0) { numer = tmp1 - tmp0; denom = a00-2.0*a01+a11; if (numer >= denom) { s = 1.0; t = 0.0; distSqr = a00+2.0*b0+c; } else { s = numer/denom; t = 1.0 - s; distSqr = s*(a00*s+a01*t+2.0*b0) + t*(a01*s+a11*t+2.0*b1)+c; } } else { s = 0.0; if (tmp1 <= 0.0) { t = 1.0; distSqr = a11+2.0*b1+c; } else if (b1 >= 0.0) { t = 0.0; distSqr = c; } else { t = -b1/a11; distSqr = b1*t+c; } } } else if (t < 0.0) { // region 6 tmp0 = a01 + b1; tmp1 = a00 + b0; if (tmp1 > tmp0 ) { numer = tmp1 - tmp0; denom = a00-2.0*a01+a11; if (numer >= denom ) { t = 1.0; s = 0.0; distSqr = a11+2.0*b1+c; } else { t = numer/denom; s = 1.0 - t; distSqr = s*(a00*s+a01*t+2.0*b0) + t*(a01*s+a11*t+2.0*b1)+c; } } else { t = 0.0; if (tmp1 <= 0.0) { s = 1.0; distSqr = a00+2.0*b0+c; } else if (b0 >= 0.0) { s = 0.0; distSqr = c; } else { s = -b0/a00; distSqr = b0*s+c; } } } else { // region 1 numer = a11 + b1 - a01 - b0; if (numer <= 0.0) { s = 0.0; t = 1.0; distSqr = a11+2.0*b1+c; } else { denom = a00-2.0*a01+a11; if (numer >= denom ) { s = 1.0; t = 0.0; distSqr = a00+2.0*b0+c; } else { s = numer/denom; t = 1.0 - s; distSqr = s*(a00*s+a01*t+2.0*b0) + t*(a01*s+a11*t+2.0*b1)+c; } } } } // Convert s,t into barycentric coordinates uv[0] = 1.0 - s - t; uv[1] = s; return (distSqr < 0) ? 0.0 : distSqr; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/LegacyFrustum.h0000644000000000000000000001676212252453157015127 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file math/LegacyFrustum.h #ifndef OPENVDB_MATH_LEGACYFRUSTUM_HAS_BEEN_INCLUDED #define OPENVDB_MATH_LEGACYFRUSTUM_HAS_BEEN_INCLUDED #include #include // for Real typedef #include "Coord.h" #include "Mat4.h" #include "Vec3.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { namespace internal { /// @brief LegacyFrustum class used at DreamWorks for converting old vdb files. class LegacyFrustum { public: LegacyFrustum(std::istream& is) { // First read in the old transform's base class. // the "extents" Vec3i tmpMin, tmpMax; is.read(reinterpret_cast(&tmpMin), sizeof(Vec3i::ValueType) * 3); is.read(reinterpret_cast(&tmpMax), sizeof(Vec3i::ValueType) * 3); Coord tmpMinCoord(tmpMin); Coord tmpMaxCoord(tmpMax); // set the extents mExtents = CoordBBox(tmpMinCoord, tmpMaxCoord); // read the old-frustum class member data //Mat4d tmpW2C; Mat4d tmpW2C, tmpC2S, tmpS2C, tmpWorldToLocal; Mat4d tmpS2U, tmpXYLocalToUnit, tmpZLocalToUnit; Real tmpWindow[6]; Real tmpPadding; //Mat4d tmpXYUnitToLocal, tmpZUnitToLocal // read in each matrix. is.read(reinterpret_cast(&tmpW2C), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&mC2W), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&tmpC2S), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&tmpS2C), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&tmpWorldToLocal), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&mLocalToWorld), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&tmpWindow[0]), sizeof(Real)); is.read(reinterpret_cast(&tmpWindow[1]), sizeof(Real)); is.read(reinterpret_cast(&tmpWindow[2]), sizeof(Real)); is.read(reinterpret_cast(&tmpWindow[3]), sizeof(Real)); is.read(reinterpret_cast(&tmpWindow[4]), sizeof(Real)); is.read(reinterpret_cast(&tmpWindow[5]), sizeof(Real)); is.read(reinterpret_cast(&tmpPadding), sizeof(Real)); is.read(reinterpret_cast(&tmpS2U), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&mXYUnitToLocal), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&tmpXYLocalToUnit), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&mZUnitToLocal), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); is.read(reinterpret_cast(&tmpZLocalToUnit), sizeof(Mat4d::value_type) * Mat4d::size * Mat4d::size); mNearPlane = tmpWindow[4]; mFarPlane = tmpWindow[5]; // Look up the world space corners of the // frustum grid. mFrNearOrigin = unitToLocalFrustum(Vec3R(0,0,0)); mFrFarOrigin = unitToLocalFrustum(Vec3R(0,0,1)); Vec3d frNearXTip = unitToLocalFrustum(Vec3R(1,0,0)); Vec3d frNearYTip = unitToLocalFrustum(Vec3R(0,1,0)); mFrNearXBasis = frNearXTip - mFrNearOrigin; mFrNearYBasis = frNearYTip - mFrNearOrigin; Vec3R frFarXTip = unitToLocalFrustum(Vec3R(1,0,1)); Vec3R frFarYTip = unitToLocalFrustum(Vec3R(0,1,1)); mFrFarXBasis = frFarXTip - mFrFarOrigin; mFrFarYBasis = frFarYTip - mFrFarOrigin; } ~LegacyFrustum(){}; const Mat4d& getCamXForm() const {return mC2W; } double getDepth() const {return (mFarPlane - mNearPlane); } double getTaper() const { return getNearPlaneWidth() / getFarPlaneWidth(); } double getNearPlaneWidth() const { double nearPlaneWidth = (unitToWorld(Vec3d(0,0,0)) - unitToWorld(Vec3d(1,0,0))).length(); return nearPlaneWidth; } double getFarPlaneWidth() const { double farPlaneWidth = (unitToWorld(Vec3d(0,0,1)) - unitToWorld(Vec3d(1,0,1))).length(); return farPlaneWidth; } double getNearPlaneDist() const { return mNearPlane; } const CoordBBox& getBBox() const {return mExtents; } Vec3d unitToWorld(const Vec3d& in) const {return mLocalToWorld.transform( unitToLocal(in) ); } private: LegacyFrustum(){}; Vec3d unitToLocal(const Vec3d& U) const { // We first find the local space coordinates // of the unit point projected onto the near // and far planes of the frustum by using a // linear combination of the planes basis vectors Vec3d nearLS = ( U[0] * mFrNearXBasis ) + ( U[1] * mFrNearYBasis ) + mFrNearOrigin; Vec3d farLS = ( U[0] * mFrFarXBasis ) + ( U[1] * mFrFarYBasis ) + mFrFarOrigin; // then we lerp the two ws points in frustum z space return U[2] * farLS + ( 1.0 - U[2] ) * nearLS; } Vec3d unitToLocalFrustum(const Vec3d& u) const { Vec3d fzu = mZUnitToLocal.transformH(u); Vec3d fu = u; fu[2] = fzu.z(); return mXYUnitToLocal.transformH(fu); } private: Mat4d mC2W, mLocalToWorld, mXYUnitToLocal, mZUnitToLocal; CoordBBox mExtents; Vec3d mFrNearXBasis, mFrNearYBasis, mFrFarXBasis, mFrFarYBasis; Vec3d mFrNearOrigin, mFrFarOrigin; double mNearPlane, mFarPlane; }; } // namespace internal } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_LEGACYFRUSTUM_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Tuple.h0000644000000000000000000001572012252453157013417 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Tuple.h /// @author Ben Kwa #ifndef OPENVDB_MATH_TUPLE_HAS_BEEN_INCLUDED #define OPENVDB_MATH_TUPLE_HAS_BEEN_INCLUDED #include #include #include "Math.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @class Tuple "Tuple.h" /// A base class for homogenous tuple types template class Tuple { public: typedef T value_type; typedef T ValueType; static const int size = SIZE; /// Default ctor. Does nothing. Required because declaring a copy (or /// other) constructor means the default constructor gets left out. Tuple() {} /// Copy constructor. Used when the class signature matches exactly. inline Tuple(Tuple const &src) { for (int i = 0; i < SIZE; ++i) { mm[i] = src.mm[i]; } } /// Conversion constructor. Tuples with different value types and /// different sizes can be interconverted using this member. Converting /// from a larger tuple results in truncation; converting from a smaller /// tuple results in the extra data members being zeroed out. This /// function assumes that the integer 0 is convertible to the tuple's /// value type. template explicit Tuple(Tuple const &src) { static const int copyEnd = SIZE < src_size ? SIZE : src_size; for (int i = 0; i < copyEnd; ++i) { mm[i] = src[i]; } for (int i = copyEnd; i < SIZE; ++i) { mm[i] = 0; } } T operator[](int i) const { // we'd prefer to use size_t, but can't because gcc3.2 doesn't like // it - it conflicts with child class conversion operators to // pointer types. // assert(i >= 0 && i < SIZE); return mm[i]; } T& operator[](int i) { // see above for size_t vs int // assert(i >= 0 && i < SIZE); return mm[i]; } /// @name Compatibility /// These are mostly for backwards compability with functions that take /// old-style Vs (which are just arrays). //@{ /// Copies this tuple into an array of a compatible type template void toV(S *v) const { for (int i = 0; i < SIZE; ++i) { v[i] = mm[i]; } } /// Exposes the internal array. Be careful when using this function. value_type *asV() { return mm; } /// Exposes the internal array. Be careful when using this function. value_type const *asV() const { return mm; } //@} Compatibility /// @return string representation of Classname std::string str() const { std::ostringstream buffer; buffer << "["; // For each column for (unsigned j(0); j < SIZE; j++) { if (j) buffer << ", "; buffer << mm[j]; } buffer << "]"; return buffer.str(); } void write(std::ostream& os) const { os.write(reinterpret_cast(&mm), sizeof(T)*SIZE); } void read(std::istream& is) { is.read(reinterpret_cast(&mm), sizeof(T)*SIZE); } protected: T mm[SIZE]; }; //////////////////////////////////////// /// @return true if t0 < t1, comparing components in order of significance. template bool operator<(const Tuple& t0, const Tuple& t1) { for (size_t i = 0; i < SIZE-1; ++i) { if (!isExactlyEqual(t0[i], t1[i])) return t0[i] < t1[i]; } return t0[SIZE-1] < t1[SIZE-1]; } /// @return true if t0 > t1, comparing components in order of significance. template bool operator>(const Tuple& t0, const Tuple& t1) { for (size_t i = 0; i < SIZE-1; ++i) { if (!isExactlyEqual(t0[i], t1[i])) return t0[i] > t1[i]; } return t0[SIZE-1] > t1[SIZE-1]; } //////////////////////////////////////// /// Helper class to compute the absolute value of a Tuple template struct TupleAbs { static inline Tuple absVal(const Tuple& t) { Tuple result; for (size_t i = 0; i < SIZE; ++i) result[i] = ::fabs(t[i]); return result; } }; // Partial specialization for integer types, using abs() instead of fabs() template struct TupleAbs { static inline Tuple absVal(const Tuple& t) { Tuple result; for (size_t i = 0; i < SIZE; ++i) result[i] = ::abs(t[i]); return result; } }; /// @return the absolute value of the given Tuple. template Tuple Abs(const Tuple& t) { return TupleAbs::value>::absVal(t); } //////////////////////////////////////// /// Write a Tuple to an output stream template std::ostream& operator<<(std::ostream& ostr, const Tuple& classname) { ostr << classname.str(); return ostr; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_TUPLE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Vec4.h0000644000000000000000000004017612252453157013132 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_VEC4_HAS_BEEN_INCLUDED #define OPENVDB_MATH_VEC4_HAS_BEEN_INCLUDED #include #include #include "Math.h" #include "Tuple.h" #include "Vec3.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Mat3; template class Vec4: public Tuple<4, T> { public: typedef T value_type; typedef T ValueType; /// Trivial constructor, the vector is NOT initialized Vec4() {} /// Constructor with one argument, e.g. Vec4f v(0); explicit Vec4(T val) { this->mm[0] = this->mm[1] = this->mm[2] = this->mm[3] = val; } /// Constructor with three arguments, e.g. Vec4f v(1,2,3); Vec4(T x, T y, T z, T w) { this->mm[0] = x; this->mm[1] = y; this->mm[2] = z; this->mm[3] = w; } /// Constructor with array argument, e.g. float a[4]; Vec4f v(a); template Vec4(Source *a) { this->mm[0] = a[0]; this->mm[1] = a[1]; this->mm[2] = a[2]; this->mm[3] = a[3]; } /// Conversion constructor template explicit Vec4(const Tuple<4, Source> &v) { this->mm[0] = static_cast(v[0]); this->mm[1] = static_cast(v[1]); this->mm[2] = static_cast(v[2]); this->mm[3] = static_cast(v[3]); } /// Reference to the component, e.g. v.x() = 4.5f; T& x() { return this->mm[0]; } T& y() { return this->mm[1]; } T& z() { return this->mm[2]; } T& w() { return this->mm[3]; } /// Get the component, e.g. float f = v.y(); T x() const { return this->mm[0]; } T y() const { return this->mm[1]; } T z() const { return this->mm[2]; } T w() const { return this->mm[3]; } T* asPointer() { return this->mm; } const T* asPointer() const { return this->mm; } /// Alternative indexed reference to the elements T& operator()(int i) { return this->mm[i]; } /// Alternative indexed constant reference to the elements, T operator()(int i) const { return this->mm[i]; } /// Returns a Vec3 with the first three elements of the Vec4. Vec3 getVec3() const { return Vec3(this->mm[0], this->mm[1], this->mm[2]); } /// "this" vector gets initialized to [x, y, z, w], /// calling v.init(); has same effect as calling v = Vec4::zero(); const Vec4& init(T x=0, T y=0, T z=0, T w=0) { this->mm[0] = x; this->mm[1] = y; this->mm[2] = z; this->mm[3] = w; return *this; } /// Set "this" vector to zero const Vec4& setZero() { this->mm[0] = 0; this->mm[1] = 0; this->mm[2] = 0; this->mm[3] = 0; return *this; } /// Assignment operator template const Vec4& operator=(const Vec4 &v) { // note: don't static_cast because that suppresses warnings this->mm[0] = v[0]; this->mm[1] = v[1]; this->mm[2] = v[2]; this->mm[3] = v[3]; return *this; } /// Test if "this" vector is equivalent to vector v with tolerance /// of eps bool eq(const Vec4 &v, T eps=1.0e-8) const { return isApproxEqual(this->mm[0], v.mm[0], eps) && isApproxEqual(this->mm[1], v.mm[1], eps) && isApproxEqual(this->mm[2], v.mm[2], eps) && isApproxEqual(this->mm[3], v.mm[3], eps); } /// Negation operator, for e.g. v1 = -v2; Vec4 operator-() const { return Vec4( -this->mm[0], -this->mm[1], -this->mm[2], -this->mm[3]); } /// this = v1 + v2 /// "this", v1 and v2 need not be distinct objects, e.g. v.add(v1,v); template const Vec4& add(const Vec4 &v1, const Vec4 &v2) { this->mm[0] = v1[0] + v2[0]; this->mm[1] = v1[1] + v2[1]; this->mm[2] = v1[2] + v2[2]; this->mm[3] = v1[3] + v2[3]; return *this; } /// this = v1 - v2 /// "this", v1 and v2 need not be distinct objects, e.g. v.sub(v1,v); template const Vec4& sub(const Vec4 &v1, const Vec4 &v2) { this->mm[0] = v1[0] - v2[0]; this->mm[1] = v1[1] - v2[1]; this->mm[2] = v1[2] - v2[2]; this->mm[3] = v1[3] - v2[3]; return *this; } /// this = scalar*v, v need not be a distinct object from "this", /// e.g. v.scale(1.5,v1); template const Vec4& scale(T0 scale, const Vec4 &v) { this->mm[0] = scale * v[0]; this->mm[1] = scale * v[1]; this->mm[2] = scale * v[2]; this->mm[3] = scale * v[3]; return *this; } template const Vec4 &div(T0 scalar, const Vec4 &v) { this->mm[0] = v[0] / scalar; this->mm[1] = v[1] / scalar; this->mm[2] = v[2] / scalar; this->mm[3] = v[3] / scalar; return *this; } /// Dot product T dot(const Vec4 &v) const { return (this->mm[0]*v.mm[0] + this->mm[1]*v.mm[1] + this->mm[2]*v.mm[2] + this->mm[3]*v.mm[3]); } /// Length of the vector T length() const { return sqrt( this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1] + this->mm[2]*this->mm[2] + this->mm[3]*this->mm[3]); } /// Squared length of the vector, much faster than length() as it /// does not involve square root T lengthSqr() const { return (this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1] + this->mm[2]*this->mm[2] + this->mm[3]*this->mm[3]); } /// this = normalized this bool normalize(T eps=1.0e-8) { T d = length(); if (isApproxEqual(d, T(0), eps)) { return false; } *this *= (T(1) / d); return true; } /// return normalized this, throws if null vector Vec4 unit(T eps=0) const { T d; return unit(eps, d); } /// return normalized this and length, throws if null vector Vec4 unit(T eps, T& len) const { len = length(); if (isApproxEqual(len, T(0), eps)) { throw ArithmeticError("Normalizing null 4-vector"); } return *this / len; } /// Returns v, where \f$v_i *= scalar\f$ for \f$i \in [0, 3]\f$ template const Vec4 &operator*=(S scalar) { this->mm[0] *= scalar; this->mm[1] *= scalar; this->mm[2] *= scalar; this->mm[3] *= scalar; return *this; } /// Returns v0, where \f$v0_i *= v1_i\f$ for \f$i \in [0, 3]\f$ template const Vec4 &operator*=(const Vec4 &v1) { this->mm[0] *= v1[0]; this->mm[1] *= v1[1]; this->mm[2] *= v1[2]; this->mm[3] *= v1[3]; return *this; } /// Returns v, where \f$v_i /= scalar\f$ for \f$i \in [0, 3]\f$ template const Vec4 &operator/=(S scalar) { this->mm[0] /= scalar; this->mm[1] /= scalar; this->mm[2] /= scalar; this->mm[3] /= scalar; return *this; } /// Returns v0, where \f$v0_i /= v1_i\f$ for \f$i \in [0, 3]\f$ template const Vec4 &operator/=(const Vec4 &v1) { this->mm[0] /= v1[0]; this->mm[1] /= v1[1]; this->mm[2] /= v1[2]; this->mm[3] /= v1[3]; return *this; } /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 3]\f$ template const Vec4 &operator+=(S scalar) { this->mm[0] += scalar; this->mm[1] += scalar; this->mm[2] += scalar; this->mm[3] += scalar; return *this; } /// Returns v0, where \f$v0_i += v1_i\f$ for \f$i \in [0, 3]\f$ template const Vec4 &operator+=(const Vec4 &v1) { this->mm[0] += v1[0]; this->mm[1] += v1[1]; this->mm[2] += v1[2]; this->mm[3] += v1[3]; return *this; } /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 3]\f$ template const Vec4 &operator-=(S scalar) { this->mm[0] -= scalar; this->mm[1] -= scalar; this->mm[2] -= scalar; this->mm[3] -= scalar; return *this; } /// Returns v0, where \f$v0_i -= v1_i\f$ for \f$i \in [0, 3]\f$ template const Vec4 &operator-=(const Vec4 &v1) { this->mm[0] -= v1[0]; this->mm[1] -= v1[1]; this->mm[2] -= v1[2]; this->mm[3] -= v1[3]; return *this; } // Number of cols, rows, elements static unsigned numRows() { return 1; } static unsigned numColumns() { return 4; } static unsigned numElements() { return 4; } /// True if a Nan is present in vector bool isNan() const { return isnan(this->mm[0]) || isnan(this->mm[1]) || isnan(this->mm[2]) || isnan(this->mm[3]); } /// True if an Inf is present in vector bool isInfinite() const { return isinf(this->mm[0]) || isinf(this->mm[1]) || isinf(this->mm[2]) || isinf(this->mm[3]); } /// True if all no Nan or Inf values present bool isFinite() const { return finite(this->mm[0]) && finite(this->mm[1]) && finite(this->mm[2]) && finite(this->mm[3]); } /// Predefined constants, e.g. Vec4f v = Vec4f::xNegAxis(); static Vec4 zero() { return Vec4(0, 0, 0, 0); } static Vec4 origin() { return Vec4(0, 0, 0, 1); } }; /// Equality operator, does exact floating point comparisons template inline bool operator==(const Vec4 &v0, const Vec4 &v1) { return isExactlyEqual(v0[0], v1[0]) && isExactlyEqual(v0[1], v1[1]) && isExactlyEqual(v0[2], v1[2]) && isExactlyEqual(v0[3], v1[3]); } /// Inequality operator, does exact floating point comparisons template inline bool operator!=(const Vec4 &v0, const Vec4 &v1) { return !(v0==v1); } /// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator*(S scalar, const Vec4 &v) { return v*scalar; } /// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator*(const Vec4 &v, S scalar) { Vec4::type> result(v); result *= scalar; return result; } /// Returns V, where \f$V_i = v0_i * v1_i\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator*(const Vec4 &v0, const Vec4 &v1) { Vec4::type> result(v0[0]*v1[0], v0[1]*v1[1], v0[2]*v1[2], v0[3]*v1[3]); return result; } /// Returns V, where \f$V_i = scalar / v_i\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator/(S scalar, const Vec4 &v) { return Vec4::type>(scalar/v[0], scalar/v[1], scalar/v[2], scalar/v[3]); } /// Returns V, where \f$V_i = v_i / scalar\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator/(const Vec4 &v, S scalar) { Vec4::type> result(v); result /= scalar; return result; } /// Returns V, where \f$V_i = v0_i / v1_i\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator/(const Vec4 &v0, const Vec4 &v1) { Vec4::type> result(v0[0]/v1[0], v0[1]/v1[1], v0[2]/v1[2], v0[3]/v1[3]); return result; } /// Returns V, where \f$V_i = v0_i + v1_i\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator+(const Vec4 &v0, const Vec4 &v1) { Vec4::type> result(v0); result += v1; return result; } /// Returns V, where \f$V_i = v_i + scalar\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator+(const Vec4 &v, S scalar) { Vec4::type> result(v); result += scalar; return result; } /// Returns V, where \f$V_i = v0_i - v1_i\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator-(const Vec4 &v0, const Vec4 &v1) { Vec4::type> result(v0); result -= v1; return result; } /// Returns V, where \f$V_i = v_i - scalar\f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator-(const Vec4 &v, S scalar) { Vec4::type> result(v); result -= scalar; return result; } /// @remark We are switching to a more explicit name because the semantics /// are different from std::min/max. In that case, the function returns a /// reference to one of the objects based on a comparator. Here, we must /// fabricate a new object which might not match either of the inputs. /// Return component-wise minimum of the two vectors. template inline Vec4 minComponent(const Vec4 &v1, const Vec4 &v2) { return Vec4( std::min(v1.x(), v2.x()), std::min(v1.y(), v2.y()), std::min(v1.z(), v2.z()), std::min(v1.w(), v2.w())); } /// Return component-wise maximum of the two vectors. template inline Vec4 maxComponent(const Vec4 &v1, const Vec4 &v2) { return Vec4( std::max(v1.x(), v2.x()), std::max(v1.y(), v2.y()), std::max(v1.z(), v2.z()), std::max(v1.w(), v2.w())); } typedef Vec4 Vec4i; typedef Vec4 Vec4ui; typedef Vec4 Vec4s; typedef Vec4 Vec4d; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_VEC4_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Quat.h0000644000000000000000000004523512252453157013244 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_QUAT_H_HAS_BEEN_INCLUDED #define OPENVDB_MATH_QUAT_H_HAS_BEEN_INCLUDED #include #include #include "Mat.h" #include "Mat3.h" #include "Math.h" #include "Vec3.h" #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Quat; /// Linear interpolation between the two quaternions template Quat slerp(const Quat &q1, const Quat &q2, T t, T tolerance=0.00001) { T qdot, angle, sineAngle; qdot = q1.dot(q2); if (fabs(qdot) >= 1.0) { angle = 0; // not necessary but suppresses compiler warning sineAngle = 0; } else { angle = acos(qdot); sineAngle = sin(angle); } // // Denominator close to 0 corresponds to the case where the // two quaternions are close to the same rotation. In this // case linear interpolation is used but we normalize to // guarantee unit length // if (sineAngle <= tolerance) { T s = 1.0 - t; Quat qtemp(s * q1[0] + t * q2[0], s * q1[1] + t * q2[1], s * q1[2] + t * q2[2], s * q1[3] + t * q2[3]); // // Check the case where two close to antipodal quaternions were // blended resulting in a nearly zero result which can happen, // for example, if t is close to 0.5. In this case it is not safe // to project back onto the sphere. // double lengthSquared = qtemp.dot(qtemp); if (lengthSquared <= tolerance * tolerance) { qtemp = (t < 0.5) ? q1 : q2; } else { qtemp *= 1.0 / sqrt(lengthSquared); } return qtemp; } else { T sine = 1.0 / sineAngle; T a = sin((1.0 - t) * angle) * sine; T b = sin(t * angle) * sine; return Quat(a * q1[0] + b * q2[0], a * q1[1] + b * q2[1], a * q1[2] + b * q2[2], a * q1[3] + b * q2[3]); } } template class Quat { public: /// Trivial constructor, the quaternion is NOT initialized Quat() {} /// Constructor with four arguments, e.g. Quatf q(1,2,3,4); Quat(T x, T y, T z, T w) { mm[0] = x; mm[1] = y; mm[2] = z; mm[3] = w; } /// Constructor with array argument, e.g. float a[4]; Quatf q(a); Quat(T *a) { mm[0] = a[0]; mm[1] = a[1]; mm[2] = a[2]; mm[3] = a[3]; } /// Constructor given rotation as axis and angle, the axis must be /// unit vector Quat(const Vec3 &axis, T angle) { // assert( REL_EQ(axis.length(), 1.) ); T s = sin(angle*T(0.5)); mm[0] = axis.x() * s; mm[1] = axis.y() * s; mm[2] = axis.z() * s; mm[3] = cos(angle*T(0.5)); } /// Constructor given rotation as axis and angle Quat(math::Axis axis, T angle) { T s = sin(angle*T(0.5)); mm[0] = (axis==math::X_AXIS) * s; mm[1] = (axis==math::Y_AXIS) * s; mm[2] = (axis==math::Z_AXIS) * s; mm[3] = cos(angle*T(0.5)); } /// Constructor given a rotation matrix template Quat(const Mat3 &rot) { // verify that the matrix is really a rotation if(!isUnitary(rot)) { // unitary is reflection or rotation OPENVDB_THROW(ArithmeticError, "A non-rotation matrix can not be used to construct a quaternion"); } if (!isApproxEqual(rot.det(), (T1)1)) { // rule out reflection OPENVDB_THROW(ArithmeticError, "A reflection matrix can not be used to construct a quaternion"); } T trace = (T)rot.trace(); if (trace > 0) { T q_w = 0.5 * std::sqrt(trace+1); T factor = 0.25 / q_w; mm[0] = factor * (rot(1,2) - rot(2,1)); mm[1] = factor * (rot(2,0) - rot(0,2)); mm[2] = factor * (rot(0,1) - rot(1,0)); mm[3] = q_w; } else if (rot(0,0) > rot(1,1) && rot(0,0) > rot(2,2)) { T q_x = 0.5 * sqrt(rot(0,0)- rot(1,1)-rot(2,2)+1); T factor = 0.25 / q_x; mm[0] = q_x; mm[1] = factor * (rot(0,1) + rot(1,0)); mm[2] = factor * (rot(2,0) + rot(0,2)); mm[3] = factor * (rot(1,2) - rot(2,1)); } else if (rot(1,1) > rot(2,2)) { T q_y = 0.5 * sqrt(rot(1,1)-rot(0,0)-rot(2,2)+1); T factor = 0.25 / q_y; mm[0] = factor * (rot(0,1) + rot(1,0)); mm[1] = q_y; mm[2] = factor * (rot(1,2) + rot(2,1)); mm[3] = factor * (rot(2,0) - rot(0,2)); } else { T q_z = 0.5 * sqrt(rot(2,2)-rot(0,0)-rot(1,1)+1); T factor = 0.25 / q_z; mm[0] = factor * (rot(2,0) + rot(0,2)); mm[1] = factor * (rot(1,2) + rot(2,1)); mm[2] = q_z; mm[3] = factor * (rot(0,1) - rot(1,0)); } } /// Copy constructor Quat(const Quat &q) { mm[0] = q.mm[0]; mm[1] = q.mm[1]; mm[2] = q.mm[2]; mm[3] = q.mm[3]; } /// Reference to the component, e.g. q.x() = 4.5f; T& x() { return mm[0]; } T& y() { return mm[1]; } T& z() { return mm[2]; } T& w() { return mm[3]; } /// Get the component, e.g. float f = q.w(); T x() const { return mm[0]; } T y() const { return mm[1]; } T z() const { return mm[2]; } T w() const { return mm[3]; } // Number of elements static unsigned numElements() { return 4; } /// Array style reference to the components, e.g. q[3] = 1.34f; T& operator[](int i) { return mm[i]; } /// Array style constant reference to the components, e.g. float f = q[1]; T operator[](int i) const { return mm[i]; } /// Cast to T* operator T*() { return mm; } operator const T*() const { return mm; } /// Alternative indexed reference to the elements T& operator()(int i) { return mm[i]; } /// Alternative indexed constant reference to the elements, T operator()(int i) const { return mm[i]; } /// Return angle of rotation T angle() const { T sqrLength = mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2]; if ( sqrLength > 1.0e-8 ) { return T(2.0) * acos(mm[3]); } else { return T(0.0); } } /// Return axis of rotation Vec3 axis() const { T sqrLength = mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2]; if ( sqrLength > 1.0e-8 ) { T invLength = T(1)/sqrt(sqrLength); return Vec3( mm[0]*invLength, mm[1]*invLength, mm[2]*invLength ); } else { return Vec3(1,0,0); } } /// "this" quaternion gets initialized to [x, y, z, w] Quat& init(T x, T y, T z, T w) { mm[0] = x; mm[1] = y; mm[2] = z; mm[3] = w; return *this; } /// "this" quaternion gets initialized to identity, same as setIdentity() Quat& init() { return setIdentity(); } /// Set "this" quaternion to rotation specified by axis and angle, /// the axis must be unit vector Quat& setAxisAngle(const Vec3& axis, T angle) { T s = sin(angle*T(0.5)); mm[0] = axis.x() * s; mm[1] = axis.y() * s; mm[2] = axis.z() * s; mm[3] = cos(angle*T(0.5)); return *this; } // axisAngleTest /// Set "this" vector to zero Quat& setZero() { mm[0] = mm[1] = mm[2] = mm[3] = 0; return *this; } /// Set "this" vector to identity Quat& setIdentity() { mm[0] = mm[1] = mm[2] = 0; mm[3] = 1; return *this; } /// Returns vector of x,y,z rotational components Vec3 eulerAngles(RotationOrder rotationOrder) const { return math::eulerAngles(Mat3(*this), rotationOrder); } /// Assignment operator Quat& operator=(const Quat &q) { mm[0] = q.mm[0]; mm[1] = q.mm[1]; mm[2] = q.mm[2]; mm[3] = q.mm[3]; return *this; } /// Equality operator, does exact floating point comparisons bool operator==(const Quat &q) const { return (isExactlyEqual(mm[0],q.mm[0]) && isExactlyEqual(mm[1],q.mm[1]) && isExactlyEqual(mm[2],q.mm[2]) && isExactlyEqual(mm[3],q.mm[3]) ); } /// Test if "this" is equivalent to q with tolerance of eps value bool eq(const Quat &q, T eps=1.0e-7) const { return isApproxEqual(mm[0],q.mm[0],eps) && isApproxEqual(mm[1],q.mm[1],eps) && isApproxEqual(mm[2],q.mm[2],eps) && isApproxEqual(mm[3],q.mm[3],eps) ; } // trivial /// Add quaternion q to "this" quaternion, e.g. q += q1; Quat& operator+=(const Quat &q) { mm[0] += q.mm[0]; mm[1] += q.mm[1]; mm[2] += q.mm[2]; mm[3] += q.mm[3]; return *this; } /// Subtract quaternion q from "this" quaternion, e.g. q -= q1; Quat& operator-=(const Quat &q) { mm[0] -= q.mm[0]; mm[1] -= q.mm[1]; mm[2] -= q.mm[2]; mm[3] -= q.mm[3]; return *this; } /// Scale "this" quaternion by scalar, e.g. q *= scalar; Quat& operator*=(T scalar) { mm[0] *= scalar; mm[1] *= scalar; mm[2] *= scalar; mm[3] *= scalar; return *this; } /// Return (this+q), e.g. q = q1 + q2; Quat operator+(const Quat &q) const { return Quat(mm[0]+q.mm[0], mm[1]+q.mm[1], mm[2]+q.mm[2], mm[3]+q.mm[3]); } /// Return (this-q), e.g. q = q1 - q2; Quat operator-(const Quat &q) const { return Quat(mm[0]-q.mm[0], mm[1]-q.mm[1], mm[2]-q.mm[2], mm[3]-q.mm[3]); } /// Return (this*q), e.g. q = q1 * q2; Quat operator*(const Quat &q) const { Quat prod; prod.mm[0] = mm[3]*q.mm[0] + mm[0]*q.mm[3] + mm[1]*q.mm[2] - mm[2]*q.mm[1]; prod.mm[1] = mm[3]*q.mm[1] + mm[1]*q.mm[3] + mm[2]*q.mm[0] - mm[0]*q.mm[2]; prod.mm[2] = mm[3]*q.mm[2] + mm[2]*q.mm[3] + mm[0]*q.mm[1] - mm[1]*q.mm[0]; prod.mm[3] = mm[3]*q.mm[3] - mm[0]*q.mm[0] - mm[1]*q.mm[1] - mm[2]*q.mm[2]; return prod; } /// Assigns this to (this*q), e.g. q *= q1; Quat operator*=(const Quat &q) { *this = *this * q; return *this; } /// Return (this*scalar), e.g. q = q1 * scalar; Quat operator*(T scalar) const { return Quat(mm[0]*scalar, mm[1]*scalar, mm[2]*scalar, mm[3]*scalar); } /// Return (this/scalar), e.g. q = q1 / scalar; Quat operator/(T scalar) const { return Quat(mm[0]/scalar, mm[1]/scalar, mm[2]/scalar, mm[3]/scalar); } /// Negation operator, e.g. q = -q; Quat operator-() const { return Quat(-mm[0], -mm[1], -mm[2], -mm[3]); } /// this = q1 + q2 /// "this", q1 and q2 need not be distinct objects, e.g. q.add(q1,q); Quat& add(const Quat &q1, const Quat &q2) { mm[0] = q1.mm[0] + q2.mm[0]; mm[1] = q1.mm[1] + q2.mm[1]; mm[2] = q1.mm[2] + q2.mm[2]; mm[3] = q1.mm[3] + q2.mm[3]; return *this; } /// this = q1 - q2 /// "this", q1 and q2 need not be distinct objects, e.g. q.sub(q1,q); Quat& sub(const Quat &q1, const Quat &q2) { mm[0] = q1.mm[0] - q2.mm[0]; mm[1] = q1.mm[1] - q2.mm[1]; mm[2] = q1.mm[2] - q2.mm[2]; mm[3] = q1.mm[3] - q2.mm[3]; return *this; } /// this = q1 * q2 /// q1 and q2 must be distinct objects than "this", e.g. q.mult(q1,q2); Quat& mult(const Quat &q1, const Quat &q2) { mm[0] = q1.mm[3]*q2.mm[0] + q1.mm[0]*q2.mm[3] + q1.mm[1]*q2.mm[2] - q1.mm[2]*q2.mm[1]; mm[1] = q1.mm[3]*q2.mm[1] + q1.mm[1]*q2.mm[3] + q1.mm[2]*q2.mm[0] - q1.mm[0]*q2.mm[2]; mm[2] = q1.mm[3]*q2.mm[2] + q1.mm[2]*q2.mm[3] + q1.mm[0]*q2.mm[1] - q1.mm[1]*q2.mm[0]; mm[3] = q1.mm[3]*q2.mm[3] - q1.mm[0]*q2.mm[0] - q1.mm[1]*q2.mm[1] - q1.mm[2]*q2.mm[2]; return *this; } /// this = scalar*q, q need not be distinct object than "this", /// e.g. q.scale(1.5,q1); Quat& scale(T scale, const Quat &q) { mm[0] = scale * q.mm[0]; mm[1] = scale * q.mm[1]; mm[2] = scale * q.mm[2]; mm[3] = scale * q.mm[3]; return *this; } /// Dot product T dot(const Quat &q) const { return (mm[0]*q.mm[0] + mm[1]*q.mm[1] + mm[2]*q.mm[2] + mm[3]*q.mm[3]); } /// Return the quaternion rate corrsponding to the angular velocity omega /// and "this" current rotation Quat derivative(const Vec3& omega) const { return Quat( +w()*omega.x() -z()*omega.y() +y()*omega.z() , +z()*omega.x() +w()*omega.y() -x()*omega.z() , -y()*omega.x() +x()*omega.y() +w()*omega.z() , -x()*omega.x() -y()*omega.y() -z()*omega.z() ); } /// this = normalized this bool normalize(T eps =1.0e-8) { T d = sqrt(mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3]); if( isApproxEqual(d, T(0.0), eps) ) return false; *this *= ( T(1)/d ); return true; } /// this = normalized this Quat unit() const { T d = sqrt(mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3]); if( isExactlyEqual(d , T(0.0) ) ) OPENVDB_THROW(ArithmeticError, "Normalizing degenerate quaternion"); return *this / d; } /// returns inverse of this Quat inverse(T tolerance = 0) { T d = mm[0]*mm[0] + mm[1]*mm[1] + mm[2]*mm[2] + mm[3]*mm[3]; if( isApproxEqual(d, T(0.0), tolerance) ) OPENVDB_THROW(ArithmeticError, "Cannot invert degenerate quaternion"); Quat result = *this/-d; result.mm[3] = -result.mm[3]; return result; } /// Return the conjugate of "this", same as invert without /// unit quaternion test Quat conjugate() const { return Quat(-mm[0], -mm[1], -mm[2], mm[3]); } /// Return rotated vector by "this" quaternion Vec3 rotateVector(const Vec3 &v) const { Mat3 m(*this); return m.transform(v); } /// Predefined constants, e.g. Quat q = Quat::identity(); static Quat zero() { return Quat(0,0,0,0); } static Quat identity() { return Quat(0,0,0,1); } /// @return string representation of Classname std::string str() const { std::ostringstream buffer; buffer << "["; // For each column for (unsigned j(0); j < 4; j++) { if (j) buffer << ", "; buffer << mm[j]; } buffer << "]"; return buffer.str(); } /// Output to the stream, e.g. std::cout << q << std::endl; friend std::ostream& operator<<(std::ostream &stream, const Quat &q) { stream << q.str(); return stream; } friend Quat slerp<>(const Quat &q1, const Quat &q2, T t, T tolerance); void write(std::ostream& os) const { os.write((char*)&mm, sizeof(T)*4); } void read(std::istream& is) { is.read((char*)&mm, sizeof(T)*4); } protected: T mm[4]; }; /// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 3]\f$ template Quat operator*(S scalar, const Quat &q) { return q*scalar; } /// @brief Interpolate between m1 and m2. /// Converts to quaternion form and uses slerp /// m1 and m2 must be rotation matrices! template Mat3 slerp(const Mat3 &m1, const Mat3 &m2, T t) { typedef Mat3 MatType; Quat q1(m1); Quat q2(m2); if (q1.dot(q2) < 0) q2 *= -1; Quat qslerp = slerp(q1, q2, static_cast(t)); MatType m = rotation(qslerp); return m; } /// Interpolate between m1 and m4 by converting m1 ... m4 into /// quaternions and treating them as control points of a Bezier /// curve using slerp in place of lerp in the De Castlejeau evaluation /// algorithm. Just like a cubic Bezier curve, this will interpolate /// m1 at t = 0 and m4 at t = 1 but in general will not pass through /// m2 and m3. Unlike a standard Bezier curve this curve will not have /// the convex hull property. /// m1 ... m4 must be rotation matrices! template Mat3 bezLerp(const Mat3 &m1, const Mat3 &m2, const Mat3 &m3, const Mat3 &m4, T t) { Mat3 m00, m01, m02, m10, m11; m00 = slerp(m1, m2, t); m01 = slerp(m2, m3, t); m02 = slerp(m3, m4, t); m10 = slerp(m00, m01, t); m11 = slerp(m01, m02, t); return slerp(m10, m11, t); } typedef Quat Quats; typedef Quat Quatd; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif //OPENVDB_MATH_QUAT_H_HAS_BEEN_INCLUDED // --------------------------------------------------------------------------- // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Vec3.h0000644000000000000000000004472612252453157013136 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_VEC3_HAS_BEEN_INCLUDED #define OPENVDB_MATH_VEC3_HAS_BEEN_INCLUDED #include #include #include "Math.h" #include "Tuple.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Mat3; template class Vec3: public Tuple<3, T> { public: typedef T value_type; typedef T ValueType; /// Trivial constructor, the vector is NOT initialized Vec3() {} /// Constructor with one argument, e.g. Vec3f v(0); explicit Vec3(T val) { this->mm[0] = this->mm[1] = this->mm[2] = val; } /// Constructor with three arguments, e.g. Vec3d v(1,2,3); Vec3(T x, T y, T z) { this->mm[0] = x; this->mm[1] = y; this->mm[2] = z; } /// Constructor with array argument, e.g. double a[3]; Vec3d v(a); template Vec3(Source *a) { this->mm[0] = a[0]; this->mm[1] = a[1]; this->mm[2] = a[2]; } /// Conversion constructor template explicit Vec3(const Tuple<3, Source> &v) { this->mm[0] = static_cast(v[0]); this->mm[1] = static_cast(v[1]); this->mm[2] = static_cast(v[2]); } template Vec3(const Vec3& v) { this->mm[0] = static_cast(v[0]); this->mm[1] = static_cast(v[1]); this->mm[2] = static_cast(v[2]); } /// Reference to the component, e.g. v.x() = 4.5f; T& x() { return this->mm[0]; } T& y() { return this->mm[1]; } T& z() { return this->mm[2]; } /// Get the component, e.g. float f = v.y(); T x() const { return this->mm[0]; } T y() const { return this->mm[1]; } T z() const { return this->mm[2]; } T* asPointer() { return this->mm; } const T* asPointer() const { return this->mm; } /// Alternative indexed reference to the elements T& operator()(int i) { return this->mm[i]; } /// Alternative indexed constant reference to the elements, T operator()(int i) const { return this->mm[i]; } /// "this" vector gets initialized to [x, y, z], /// calling v.init(); has same effect as calling v = Vec3::zero(); const Vec3& init(T x=0, T y=0, T z=0) { this->mm[0] = x; this->mm[1] = y; this->mm[2] = z; return *this; } /// Set "this" vector to zero const Vec3& setZero() { this->mm[0] = 0; this->mm[1] = 0; this->mm[2] = 0; return *this; } /// Assignment operator template const Vec3& operator=(const Vec3 &v) { // note: don't static_cast because that suppresses warnings this->mm[0] = ValueType(v[0]); this->mm[1] = ValueType(v[1]); this->mm[2] = ValueType(v[2]); return *this; } /// Test if "this" vector is equivalent to vector v with tolerance of eps bool eq(const Vec3 &v, T eps = static_cast(1.0e-7)) const { return isRelOrApproxEqual(this->mm[0], v.mm[0], eps, eps) && isRelOrApproxEqual(this->mm[1], v.mm[1], eps, eps) && isRelOrApproxEqual(this->mm[2], v.mm[2], eps, eps); } /// Negation operator, for e.g. v1 = -v2; Vec3 operator-() const { return Vec3(-this->mm[0], -this->mm[1], -this->mm[2]); } /// this = v1 + v2 /// "this", v1 and v2 need not be distinct objects, e.g. v.add(v1,v); template const Vec3& add(const Vec3 &v1, const Vec3 &v2) { this->mm[0] = v1[0] + v2[0]; this->mm[1] = v1[1] + v2[1]; this->mm[2] = v1[2] + v2[2]; return *this; } /// this = v1 - v2 /// "this", v1 and v2 need not be distinct objects, e.g. v.sub(v1,v); template const Vec3& sub(const Vec3 &v1, const Vec3 &v2) { this->mm[0] = v1[0] - v2[0]; this->mm[1] = v1[1] - v2[1]; this->mm[2] = v1[2] - v2[2]; return *this; } /// this = scalar*v, v need not be a distinct object from "this", /// e.g. v.scale(1.5,v1); template const Vec3& scale(T0 scale, const Vec3 &v) { this->mm[0] = scale * v[0]; this->mm[1] = scale * v[1]; this->mm[2] = scale * v[2]; return *this; } template const Vec3 &div(T0 scale, const Vec3 &v) { this->mm[0] = v[0] / scale; this->mm[1] = v[1] / scale; this->mm[2] = v[2] / scale; return *this; } /// Dot product T dot(const Vec3 &v) const { return this->mm[0]*v.mm[0] + this->mm[1]*v.mm[1] + this->mm[2]*v.mm[2]; } /// Length of the vector T length() const { return static_cast(sqrt(double( this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1] + this->mm[2]*this->mm[2]))); } /// Squared length of the vector, much faster than length() as it /// does not involve square root T lengthSqr() const { return this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1] + this->mm[2]*this->mm[2]; } /// Return the cross product of "this" vector and v; Vec3 cross(const Vec3 &v) const { return Vec3(this->mm[1]*v.mm[2] - this->mm[2]*v.mm[1], this->mm[2]*v.mm[0] - this->mm[0]*v.mm[2], this->mm[0]*v.mm[1] - this->mm[1]*v.mm[0]); } /// this = v1 cross v2, v1 and v2 must be distinct objects than "this" const Vec3& cross(const Vec3 &v1, const Vec3 &v2) { // assert(this!=&v1); // assert(this!=&v2); this->mm[0] = v1.mm[1]*v2.mm[2] - v1.mm[2]*v2.mm[1]; this->mm[1] = v1.mm[2]*v2.mm[0] - v1.mm[0]*v2.mm[2]; this->mm[2] = v1.mm[0]*v2.mm[1] - v1.mm[1]*v2.mm[0]; return *this; } /// Returns v, where \f$v_i *= scalar\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator*=(S scalar) { this->mm[0] *= scalar; this->mm[1] *= scalar; this->mm[2] *= scalar; return *this; } /// Returns v0, where \f$v0_i *= v1_i\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator*=(const Vec3 &v1) { this->mm[0] *= v1[0]; this->mm[1] *= v1[1]; this->mm[2] *= v1[2]; return *this; } /// Returns v, where \f$v_i /= scalar\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator/=(S scalar) { this->mm[0] /= scalar; this->mm[1] /= scalar; this->mm[2] /= scalar; return *this; } /// Returns v0, where \f$v0_i /= v1_i\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator/=(const Vec3 &v1) { this->mm[0] /= v1[0]; this->mm[1] /= v1[1]; this->mm[2] /= v1[2]; return *this; } /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator+=(S scalar) { this->mm[0] += scalar; this->mm[1] += scalar; this->mm[2] += scalar; return *this; } /// Returns v0, where \f$v0_i += v1_i\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator+=(const Vec3 &v1) { this->mm[0] += v1[0]; this->mm[1] += v1[1]; this->mm[2] += v1[2]; return *this; } /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator-=(S scalar) { this->mm[0] -= scalar; this->mm[1] -= scalar; this->mm[2] -= scalar; return *this; } /// Returns v0, where \f$v0_i -= v1_i\f$ for \f$i \in [0, 2]\f$ template const Vec3 &operator-=(const Vec3 &v1) { this->mm[0] -= v1[0]; this->mm[1] -= v1[1]; this->mm[2] -= v1[2]; return *this; } /// this = normalized this bool normalize(T eps = T(1.0e-7)) { T d = length(); if (isApproxEqual(d, T(0), eps)) { return false; } *this *= (T(1) / d); return true; } /// return normalized this, throws if null vector Vec3 unit(T eps=0) const { T d; return unit(eps, d); } /// return normalized this and length, throws if null vector Vec3 unit(T eps, T& len) const { len = length(); if (isApproxEqual(len, T(0), eps)) { OPENVDB_THROW(ArithmeticError, "Normalizing null 3-vector"); } return *this / len; } // Number of cols, rows, elements static unsigned numRows() { return 1; } static unsigned numColumns() { return 3; } static unsigned numElements() { return 3; } /// Returns the scalar component of v in the direction of onto, onto need /// not be unit. e.g double c = Vec3d::component(v1,v2); T component(const Vec3 &onto, T eps=1.0e-7) const { T l = onto.length(); if (isApproxEqual(l, T(0), eps)) return 0; return dot(onto)*(T(1)/l); } /// Return the projection of v onto the vector, onto need not be unit /// e.g. Vec3d a = vprojection(n); Vec3 projection(const Vec3 &onto, T eps=1.0e-7) const { T l = onto.lengthSqr(); if (isApproxEqual(l, T(0), eps)) return Vec3::zero(); return onto*(dot(onto)*(T(1)/l)); } /// Return an arbitrary unit vector perpendicular to v /// Vector this must be a unit vector /// e.g. v = v.normalize(); Vec3d n = v.getArbPerpendicular(); Vec3 getArbPerpendicular() const { Vec3 u; T l; if ( fabs(this->mm[0]) >= fabs(this->mm[1]) ) { // v.x or v.z is the largest magnitude component, swap them l = this->mm[0]*this->mm[0] + this->mm[2]*this->mm[2]; l = static_cast(T(1)/sqrt(double(l))); u.mm[0] = -this->mm[2]*l; u.mm[1] = (T)0.0; u.mm[2] = +this->mm[0]*l; } else { // W.y or W.z is the largest magnitude component, swap them l = this->mm[1]*this->mm[1] + this->mm[2]*this->mm[2]; l = static_cast(T(1)/sqrt(double(l))); u.mm[0] = (T)0.0; u.mm[1] = +this->mm[2]*l; u.mm[2] = -this->mm[1]*l; } return u; } /// True if a Nan is present in vector bool isNan() const { return isnan(this->mm[0]) || isnan(this->mm[1]) || isnan(this->mm[2]); } /// True if an Inf is present in vector bool isInfinite() const { return isinf(this->mm[0]) || isinf(this->mm[1]) || isinf(this->mm[2]); } /// True if all no Nan or Inf values present bool isFinite() const { return finite(this->mm[0]) && finite(this->mm[1]) && finite(this->mm[2]); } /// Predefined constants, e.g. Vec3d v = Vec3d::xNegAxis(); static Vec3 zero() { return Vec3(0, 0, 0); } }; /// Equality operator, does exact floating point comparisons template inline bool operator==(const Vec3 &v0, const Vec3 &v1) { return isExactlyEqual(v0[0], v1[0]) && isExactlyEqual(v0[1], v1[1]) && isExactlyEqual(v0[2], v1[2]); } /// Inequality operator, does exact floating point comparisons template inline bool operator!=(const Vec3 &v0, const Vec3 &v1) { return !(v0==v1); } /// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator*(S scalar, const Vec3 &v) { return v*scalar; } /// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator*(const Vec3 &v, S scalar) { Vec3::type> result(v); result *= scalar; return result; } /// Returns V, where \f$V_i = v0_i * v1_i\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator*(const Vec3 &v0, const Vec3 &v1) { Vec3::type> result(v0[0] * v1[0], v0[1] * v1[1], v0[2] * v1[2]); return result; } /// Returns V, where \f$V_i = scalar / v_i\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator/(S scalar, const Vec3 &v) { return Vec3::type>(scalar/v[0], scalar/v[1], scalar/v[2]); } /// Returns V, where \f$V_i = v_i / scalar\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator/(const Vec3 &v, S scalar) { Vec3::type> result(v); result /= scalar; return result; } /// Returns V, where \f$V_i = v0_i / v1_i\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator/(const Vec3 &v0, const Vec3 &v1) { Vec3::type> result(v0[0] / v1[0], v0[1] / v1[1], v0[2] / v1[2]); return result; } /// Returns V, where \f$V_i = v0_i + v1_i\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator+(const Vec3 &v0, const Vec3 &v1) { Vec3::type> result(v0); result += v1; return result; } /// Returns V, where \f$V_i = v_i + scalar\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator+(const Vec3 &v, S scalar) { Vec3::type> result(v); result += scalar; return result; } /// Returns V, where \f$V_i = v0_i - v1_i\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator-(const Vec3 &v0, const Vec3 &v1) { Vec3::type> result(v0); result -= v1; return result; } /// Returns V, where \f$V_i = v_i - scalar\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator-(const Vec3 &v, S scalar) { Vec3::type> result(v); result -= scalar; return result; } /// Angle between two vectors, the result is between [0, pi], /// e.g. double a = Vec3d::angle(v1,v2); template inline T angle(const Vec3 &v1, const Vec3 &v2) { Vec3 c = v1.cross(v2); return static_cast(atan2(c.length(), v1.dot(v2))); } template inline bool isApproxEqual(const Vec3& a, const Vec3& b) { return a.eq(b); } template inline bool isApproxEqual(const Vec3& a, const Vec3& b, const Vec3& eps) { return isApproxEqual(a.x(), b.x(), eps.x()) && isApproxEqual(a.y(), b.y(), eps.y()) && isApproxEqual(a.z(), b.z(), eps.z()); } /// Orthonormalize vectors v1, v2 and v3 and store back the resulting /// basis e.g. Vec3d::orthonormalize(v1,v2,v3); template inline void orthonormalize(Vec3 &v1, Vec3 &v2, Vec3 &v3) { // If the input vectors are v0, v1, and v2, then the Gram-Schmidt // orthonormalization produces vectors u0, u1, and u2 as follows, // // u0 = v0/|v0| // u1 = (v1-(u0*v1)u0)/|v1-(u0*v1)u0| // u2 = (v2-(u0*v2)u0-(u1*v2)u1)/|v2-(u0*v2)u0-(u1*v2)u1| // // where |A| indicates length of vector A and A*B indicates dot // product of vectors A and B. // compute u0 v1.normalize(); // compute u1 T d0 = v1.dot(v2); v2 -= v1*d0; v2.normalize(); // compute u2 T d1 = v2.dot(v3); d0 = v1.dot(v3); v3 -= v1*d0 + v2*d1; v3.normalize(); } /// @remark We are switching to a more explicit name because the semantics /// are different from std::min/max. In that case, the function returns a /// reference to one of the objects based on a comparator. Here, we must /// fabricate a new object which might not match either of the inputs. /// Return component-wise minimum of the two vectors. template inline Vec3 minComponent(const Vec3 &v1, const Vec3 &v2) { return Vec3( std::min(v1.x(), v2.x()), std::min(v1.y(), v2.y()), std::min(v1.z(), v2.z())); } /// Return component-wise maximum of the two vectors. template inline Vec3 maxComponent(const Vec3 &v1, const Vec3 &v2) { return Vec3( std::max(v1.x(), v2.x()), std::max(v1.y(), v2.y()), std::max(v1.z(), v2.z())); } typedef Vec3 Vec3i; typedef Vec3 Vec3ui; typedef Vec3 Vec3s; typedef Vec3 Vec3d; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_VEC3_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Transform.h0000644000000000000000000002422012252453157014274 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_TRANSFORM_HAS_BEEN_INCLUDED #define OPENVDB_MATH_TRANSFORM_HAS_BEEN_INCLUDED #include "Maps.h" #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { // Forward declaration class Transform; // Utility methods /// @brief Calculate an axis-aligned bounding box in index space from an /// axis-aligned bounding box in world space. OPENVDB_API void calculateBounds(const Transform& t, const Vec3d& minWS, const Vec3d& maxWS, Vec3d& minIS, Vec3d& maxIS); /// @brief Calculate an axis-aligned bounding box in index space from a /// bounding sphere in world space. /// @todo void calculateBounds(const Transform& t, const Vec3d& center, const Real radius, /// Vec3d& minIS, Vec3d& maxIS); //////////////////////////////////////// /// @class Transform class OPENVDB_API Transform { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; Transform(): mMap(MapBase::Ptr(new ScaleMap())) {} Transform(const MapBase::Ptr&); Transform(const Transform&); ~Transform() {} Ptr copy() const { return Ptr(new Transform(mMap->copy())); } //@{ /// @brief Create and return a shared pointer to a new transform. static Transform::Ptr createLinearTransform(double voxelSize = 1.0); static Transform::Ptr createLinearTransform(const Mat4R&); static Transform::Ptr createFrustumTransform(const BBoxd&, double taper, double depth, double voxelSize = 1.0); //@} /// Return @c true if the transformation map is exclusively linear/affine. bool isLinear() const { return mMap->isLinear(); } /// Return @c true if the transform is equivalent to an idenity. bool isIdentity() const ; /// Return the transformation map's type-name Name mapType() const { return mMap->type(); } //@{ /// @brief Update the linear (affine) map by prepending or /// postfixing the appropriate operation. In the case of /// a frustum, the pre-operations apply to the linear part /// of the transform and not the entire transform, while the /// post-operations are allways applied last. void preRotate(double radians, const Axis axis = X_AXIS); void preTranslate(const Vec3d&); void preScale(const Vec3d&); void preScale(double); void preShear(double shear, Axis axis0, Axis axis1); void preMult(const Mat4d&); void preMult(const Mat3d&); void postRotate(double radians, const Axis axis = X_AXIS); void postTranslate(const Vec3d&); void postScale(const Vec3d&); void postScale(double); void postShear(double shear, Axis axis0, Axis axis1); void postMult(const Mat4d&); void postMult(const Mat3d&); //@} /// Return the size of a voxel using the linear component of the map. Vec3d voxelSize() const { return mMap->voxelSize(); } /// @brief Return the size of a voxel at position (x, y, z). /// @note Maps that have a nonlinear component (e.g., perspective and frustum maps) /// have position-dependent voxel sizes. Vec3d voxelSize(const Vec3d& xyz) const { return mMap->voxelSize(xyz); } /// Return the voxel volume of the linear component of the map. double voxelVolume() const { return mMap->determinant(); } /// Return the voxel volume at position (x, y, z). double voxelVolume(const Vec3d& xyz) const { return mMap->determinant(xyz); } /// Return true if the voxels in world space are uniformly sized cubes bool hasUniformScale() const { return mMap->hasUniformScale(); } //@{ /// @brief Apply this transformation to the given coordinates. Vec3d indexToWorld(const Vec3d& xyz) const { return mMap->applyMap(xyz); } Vec3d indexToWorld(const Coord& ijk) const { return mMap->applyMap(ijk.asVec3d()); } Vec3d worldToIndex(const Vec3d& xyz) const { return mMap->applyInverseMap(xyz); } Coord worldToIndexCellCentered(const Vec3d& xyz) const {return Coord::round(worldToIndex(xyz));} Coord worldToIndexNodeCentered(const Vec3d& xyz) const {return Coord::floor(worldToIndex(xyz));} //@} //@{ /// Return a base pointer to the transformation map. MapBase::ConstPtr baseMap() const { return mMap; } MapBase::Ptr baseMap() { return mMap; } //@} //@{ /// @brief Return the result of downcasting the base map pointer to a /// @c MapType pointer, or return a null pointer if the types are incompatible. template typename MapType::Ptr map(); template typename MapType::ConstPtr map() const; template typename MapType::ConstPtr constMap() const; //@} /// Unserialize this transform from the given stream. void read(std::istream&); /// Serialize this transform to the given stream. void write(std::ostream&) const; /// @brief Print a description of this transform. /// @param os a stream to which to write textual information /// @param indent a string with which to prefix each line of text void print(std::ostream& os = std::cout, const std::string& indent = "") const; bool operator==(const Transform& other) const; inline bool operator!=(const Transform& other) const { return !(*this == other); } private: MapBase::Ptr mMap; }; // class Transform OPENVDB_API std::ostream& operator<<(std::ostream&, const Transform&); //////////////////////////////////////// template inline typename MapType::Ptr Transform::map() { if (mMap->type() == MapType::mapType()) { return boost::static_pointer_cast(mMap); } return typename MapType::Ptr(); } template inline typename MapType::ConstPtr Transform::map() const { return boost::const_pointer_cast( const_cast(this)->map()); } template inline typename MapType::ConstPtr Transform::constMap() const { return map(); } //////////////////////////////////////// /// Helper function used internally by processTypedMap() template inline void doProcessTypedMap(Transform& transform, OpType& op) { ResolvedMapType& resolvedMap = *transform.map(); #ifdef _MSC_VER op.operator()(resolvedMap); #else op.template operator()(resolvedMap); #endif } /// Helper function used internally by processTypedMap() template inline void doProcessTypedMap(const Transform& transform, OpType& op) { const ResolvedMapType& resolvedMap = *transform.map(); #ifdef _MSC_VER op.operator()(resolvedMap); #else op.template operator()(resolvedMap); #endif } /// @brief Utility function that, given a generic map pointer, /// calls a functor on the fully-resoved map /// /// Usage: /// @code /// struct Foo { /// template /// void operator()(const MapT& map) const { blah } /// }; /// /// processTypedMap(myMap, Foo()); /// @endcode /// /// @return @c false if the grid type is unknown or unhandled. template bool processTypedMap(TransformType& transform, OpType& op) { using namespace openvdb; const Name mapType = transform.mapType(); if (mapType == UniformScaleMap::mapType()) { doProcessTypedMap(transform, op); } else if (mapType == UniformScaleTranslateMap::mapType()) { doProcessTypedMap(transform, op); } else if (mapType == ScaleMap::mapType()) { doProcessTypedMap(transform, op); } else if (mapType == ScaleTranslateMap::mapType()) { doProcessTypedMap(transform, op); } else if (mapType == UnitaryMap::mapType()) { doProcessTypedMap(transform, op); } else if (mapType == AffineMap::mapType()) { doProcessTypedMap(transform, op); } else if (mapType == TranslationMap::mapType()) { doProcessTypedMap(transform, op); } else if (mapType == NonlinearFrustumMap::mapType()) { doProcessTypedMap(transform, op); } else { return false; } return true; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_TRANSFORM_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Mat.h0000644000000000000000000007005212252453157013046 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Mat.h /// @author Joshua Schpok #ifndef OPENVDB_MATH_MAT_HAS_BEEN_INCLUDED #define OPENVDB_MATH_MAT_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include "Math.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @class Mat "Mat.h" /// A base class for square matrices. template class Mat { public: typedef T value_type; typedef T ValueType; enum SIZE_ { size = SIZE }; // Number of cols, rows, elements static unsigned numRows() { return SIZE; } static unsigned numColumns() { return SIZE; } static unsigned numElements() { return SIZE*SIZE; } /// Default ctor. Does nothing. Required because declaring a copy (or /// other) constructor means the default constructor gets left out. Mat() { } /// Copy constructor. Used when the class signature matches exactly. Mat(Mat const &src) { for (unsigned i(0); i < numElements(); ++i) { mm[i] = src.mm[i]; } } /// @return string representation of matrix /// Since output is multiline, optional indentation argument prefixes /// each newline with that much white space. It does not indent /// the first line, since you might be calling this inline: /// /// cout << "matrix: " << mat.str(7) /// /// matrix: [[1 2] /// [3 4]] std::string str(unsigned indentation = 0) const { std::string ret; std::string indent; // We add +1 since we're indenting one for the first '[' indent.append(indentation+1, ' '); ret.append("["); // For each row, for (unsigned i(0); i < SIZE; i++) { ret.append("["); // For each column for (unsigned j(0); j < SIZE; j++) { // Put a comma after everything except the last if (j) ret.append(", "); ret.append((boost::format("%1%") % mm[(i*SIZE)+j]).str()); } ret.append("]"); // At the end of every row (except the last)... if (i < SIZE-1 ) // ...suffix the row bracket with a comma, newline, and // advance indentation ret.append((boost::format(",\n%1%") % indent).str()); } ret.append("]"); return ret; } /// Write a Mat to an output stream friend std::ostream& operator<<( std::ostream& ostr, const Mat& m) { ostr << m.str(); return ostr; } void write(std::ostream& os) const { os.write(reinterpret_cast(&mm), sizeof(T)*SIZE*SIZE); } void read(std::istream& is) { is.read(reinterpret_cast(&mm), sizeof(T)*SIZE*SIZE); } protected: T mm[SIZE*SIZE]; }; template class Quat; template class Vec3; /// Returns rotation matrix specified by the quaternion /// The quaternion is normalized and used to construct the matrix /// Note that the matrix is transposed to match post-multiplication /// symantics. template MatType rotation(const Quat &q, typename MatType::value_type eps = 1.0e-8) { typedef typename MatType::value_type T; T qdot(q.dot(q)); T s(0); if (!isApproxEqual(qdot, T(0.0),eps)) { s = 2.0 / qdot; } T x = s*q.x(); T y = s*q.y(); T z = s*q.z(); T wx = x*q.w(); T wy = y*q.w(); T wz = z*q.w(); T xx = x*q.x(); T xy = y*q.x(); T xz = z*q.x(); T yy = y*q.y(); T yz = z*q.y(); T zz = z*q.z(); MatType r; r[0][0]=T(1) - (yy+zz); r[0][1]=xy + wz; r[0][2]=xz - wy; r[1][0]=xy - wz; r[1][1]=T(1) - (xx+zz); r[1][2]=yz + wx; r[2][0]=xz + wy; r[2][1]=yz - wx; r[2][2]=T(1) - (xx+yy); if(MatType::numColumns() == 4) padMat4(r); return r; } /// @brief Set the matrix to a rotation about the given axis. /// @param axis The axis (one of X, Y, Z) to rotate about. /// @param angle The rotation angle, in radians. template MatType rotation(Axis axis, typename MatType::value_type angle) { typedef typename MatType::value_type T; T c = static_cast(cos(angle)); T s = static_cast(sin(angle)); MatType result; result.setIdentity(); switch (axis) { case X_AXIS: result[1][1] = c; result[1][2] = s; result[2][1] = -s; result[2][2] = c; return result; case Y_AXIS: result[0][0] = c; result[0][2] = -s; result[2][0] = s; result[2][2] = c; return result; case Z_AXIS: result[0][0] = c; result[0][1] = s; result[1][0] = -s; result[1][1] = c; return result; default: throw ValueError("Unrecognized rotation axis"); } } /// @return matrix to the rotation specified by axis and angle /// @note The axis must be unit vector template MatType rotation( const Vec3 &_axis, typename MatType::value_type angle) { typedef typename MatType::value_type T; T txy, txz, tyz, sx, sy, sz; Vec3 axis(_axis.unit()); // compute trig properties of angle: T c(cos(double(angle))); T s(sin(double(angle))); T t(1 - c); MatType result; // handle diagonal elements result[0][0] = axis[0]*axis[0] * t + c; result[1][1] = axis[1]*axis[1] * t + c; result[2][2] = axis[2]*axis[2] * t + c; txy = axis[0]*axis[1] * t; sz = axis[2] * s; txz = axis[0]*axis[2] * t; sy = axis[1] * s; tyz = axis[1]*axis[2] * t; sx = axis[0] * s; // right handed space // Contribution from rotation about 'z' result[0][1] = txy + sz; result[1][0] = txy - sz; // Contribution from rotation about 'y' result[0][2] = txz - sy; result[2][0] = txz + sy; // Contribution from rotation about 'x' result[1][2] = tyz + sx; result[2][1] = tyz - sx; if(MatType::numColumns() == 4) padMat4(result); return MatType(result); } /// Return the euler angles composing this rotation matrix. Optional /// axes arguments describe in what order elementary rotations are /// applied. Note that in our convention, XYZ means Rz * Ry * Rx. /// Because we are using rows rather than columns to represent the /// local axes of a coordinate frame, the interpretation from a local /// reference point of view is to first rotate about the x axis, then /// about the newly rotated y axis, and finally by the new local z axis. /// From a fixed reference point of view, the interpretation is to /// rotate about the stationary world z, y, and x axes respectively. /// /// Irrespective of the euler angle convention, in the case of distinct /// axes, eulerAngles() returns the x, y, and z angles in the corresponding /// x, y, z components of the returned Vec3. For the XZX convention, the /// left X value is returned in Vec3.x, and the right X value in Vec3.y. /// For the ZXZ convention the left Z value is returned in Vec3.z and /// the right Z value in Vec3.y /// /// Examples of reconstructing r from its euler angle decomposition /// /// v = eulerAngles(r, ZYX_ROTATION); /// rx.setToRotation(Vec3d(1,0,0), v[0]); /// ry.setToRotation(Vec3d(0,1,0), v[1]); /// rz.setToRotation(Vec3d(0,0,1), v[2]); /// r = rx * ry * rz; /// /// v = eulerAngles(r, ZXZ_ROTATION); /// rz1.setToRotation(Vec3d(0,0,1), v[2]); /// rx.setToRotation (Vec3d(1,0,0), v[0]); /// rz2.setToRotation(Vec3d(0,0,1), v[1]); /// r = rz2 * rx * rz1; /// /// v = eulerAngles(r, XZX_ROTATION); /// rx1.setToRotation (Vec3d(1,0,0), v[0]); /// rx2.setToRotation (Vec3d(1,0,0), v[1]); /// rz.setToRotation (Vec3d(0,0,1), v[2]); /// r = rx2 * rz * rx1; /// template Vec3 eulerAngles( const MatType& mat, RotationOrder rotationOrder, typename MatType::value_type eps=1.0e-8) { typedef typename MatType::value_type ValueType; typedef Vec3 V; ValueType phi, theta, psi; switch(rotationOrder) { case XYZ_ROTATION: if (isApproxEqual(mat[2][0], ValueType(1.0), eps)) { theta = M_PI_2; phi = 0.5 * atan2(mat[1][2], mat[1][1]); psi = phi; } else if (isApproxEqual(mat[2][0], ValueType(-1.0), eps)) { theta = -M_PI_2; phi = 0.5 * atan2(mat[1][2], mat[1][1]); psi = -phi; } else { psi = atan2(-mat[1][0],mat[0][0]); phi = atan2(-mat[2][1],mat[2][2]); theta = atan2(mat[2][0], sqrt( mat[2][1]*mat[2][1] + mat[2][2]*mat[2][2])); } return V(phi, theta, psi); case ZXY_ROTATION: if (isApproxEqual(mat[1][2], ValueType(1.0), eps)) { theta = M_PI_2; phi = 0.5 * atan2(mat[0][1], mat[0][0]); psi = phi; } else if (isApproxEqual(mat[1][2], ValueType(-1.0), eps)) { theta = -M_PI/2; phi = 0.5 * atan2(mat[0][1],mat[2][1]); psi = -phi; } else { psi = atan2(-mat[0][2], mat[2][2]); phi = atan2(-mat[1][0], mat[1][1]); theta = atan2(mat[1][2], sqrt(mat[0][2] * mat[0][2] + mat[2][2] * mat[2][2])); } return V(theta, psi, phi); case YZX_ROTATION: if (isApproxEqual(mat[0][1], ValueType(1.0), eps)) { theta = M_PI_2; phi = 0.5 * atan2(mat[2][0], mat[2][2]); psi = phi; } else if (isApproxEqual(mat[0][1], ValueType(-1.0), eps)) { theta = -M_PI/2; phi = 0.5 * atan2(mat[2][0], mat[1][0]); psi = -phi; } else { psi = atan2(-mat[2][1], mat[1][1]); phi = atan2(-mat[0][2], mat[0][0]); theta = atan2(mat[0][1], sqrt(mat[0][0] * mat[0][0] + mat[0][2] * mat[0][2])); } return V(psi, phi, theta); case XZX_ROTATION: if (isApproxEqual(mat[0][0], ValueType(1.0), eps)) { theta = 0.0; phi = 0.5 * atan2(mat[1][2], mat[1][1]); psi = phi; } else if (isApproxEqual(mat[0][0], ValueType(-1.0), eps)) { theta = M_PI; psi = 0.5 * atan2(mat[2][1], -mat[1][1]); phi = - psi; } else { psi = atan2(mat[2][0], -mat[1][0]); phi = atan2(mat[0][2], mat[0][1]); theta = atan2(sqrt(mat[0][1] * mat[0][1] + mat[0][2] * mat[0][2]), mat[0][0]); } return V(phi, psi, theta); case ZXZ_ROTATION: if (isApproxEqual(mat[2][2], ValueType(1.0), eps)) { theta = 0.0; phi = 0.5 * atan2(mat[0][1], mat[0][0]); psi = phi; } else if (isApproxEqual(mat[2][2], ValueType(-1.0), eps)) { theta = M_PI; phi = 0.5 * atan2(mat[0][1], mat[0][0]); psi = -phi; } else { psi = atan2(mat[0][2], mat[1][2]); phi = atan2(mat[2][0], -mat[2][1]); theta = atan2(sqrt(mat[0][2] * mat[0][2] + mat[1][2] * mat[1][2]), mat[2][2]); } return V(theta, psi, phi); case YXZ_ROTATION: if (isApproxEqual(mat[2][1], ValueType(1.0), eps)) { theta = - M_PI_2; phi = 0.5 * atan2(-mat[1][0], mat[0][0]); psi = phi; } else if (isApproxEqual(mat[2][1], ValueType(-1.0), eps)) { theta = M_PI_2; phi = 0.5 * atan2(mat[1][0], mat[0][0]); psi = -phi; } else { psi = atan2(mat[0][1], mat[1][1]); phi = atan2(mat[2][0], mat[2][2]); theta = atan2(-mat[2][1], sqrt(mat[0][1] * mat[0][1] + mat[1][1] * mat[1][1])); } return V(theta, phi, psi); case ZYX_ROTATION: if (isApproxEqual(mat[0][2], ValueType(1.0), eps)) { theta = -M_PI_2; phi = 0.5 * atan2(-mat[1][0], mat[1][1]); psi = phi; } else if (isApproxEqual(mat[0][2], ValueType(-1.0), eps)) { theta = M_PI_2; phi = 0.5 * atan2(mat[2][1], mat[2][0]); psi = - phi; } else { psi = atan2(mat[1][2], mat[2][2]); phi = atan2(mat[0][1], mat[0][0]); theta = atan2(-mat[0][2], sqrt(mat[0][1] * mat[0][1] + mat[0][0] * mat[0][0])); } return V(psi, theta, phi); case XZY_ROTATION: if (isApproxEqual(mat[1][0], ValueType(-1.0), eps)) { theta = M_PI_2; psi = 0.5 * atan2(mat[2][1], mat[2][2]); phi = - psi; } else if (isApproxEqual(mat[1][0], ValueType(1.0), eps)) { theta = - M_PI_2; psi = 0.5 * atan2(- mat[2][1], mat[2][2]); phi = psi; } else { psi = atan2(mat[2][0], mat[0][0]); phi = atan2(mat[1][2], mat[1][1]); theta = atan2(- mat[1][0], sqrt(mat[1][1] * mat[1][1] + mat[1][2] * mat[1][2])); } return V(phi, psi, theta); } OPENVDB_THROW(NotImplementedError, "Euler extraction sequence not implemented"); } /// @brief Set the matrix to a rotation that maps v1 onto v2 about the cross /// product of v1 and v2. template MatType rotation( const Vec3& _v1, const Vec3& _v2, typename MatType::value_type eps=1.0e-8) { typedef typename MatType::value_type T; Vec3 v1(_v1); Vec3 v2(_v2); // Check if v1 and v2 are unit length if (!isApproxEqual(1.0, v1.dot(v1), eps)) { v1.normalize(); } if (!isApproxEqual(1.0, v2.dot(v2), eps)) { v2.normalize(); } Vec3 cross; cross.cross(v1, v2); if (isApproxEqual(cross[0], 0.0, eps) && isApproxEqual(cross[1], 0.0, eps) && isApproxEqual(cross[2], 0.0, eps)) { // Given two unit vectors v1 and v2 that are nearly parallel, build a // rotation matrix that maps v1 onto v2. First find which principal axis // p is closest to perpendicular to v1. Find a reflection that exchanges // v1 and p, and find a reflection that exchanges p2 and v2. The desired // rotation matrix is the composition of these two reflections. See the // paper "Efficiently Building a Matrix to Rotate One Vector to // Another" by Tomas Moller and John Hughes in Journal of Graphics // Tools Vol 4, No 4 for details. Vec3 u, v, p(0.0, 0.0, 0.0); double x = Abs(v1[0]); double y = Abs(v1[1]); double z = Abs(v1[2]); if (x < y) { if (z < x) { p[2] = 1; } else { p[0] = 1; } } else { if (z < y) { p[2] = 1; } else { p[1] = 1; } } u = p - v1; v = p - v2; double udot = u.dot(u); double vdot = v.dot(v); double a = -2 / udot; double b = -2 / vdot; double c = 4 * u.dot(v) / (udot * vdot); MatType result; result.setIdentity(); for (int j = 0; j < 3; j++) { for (int i = 0; i < 3; i++) result[i][j] = a * u[i] * u[j] + b * v[i] * v[j] + c * v[j] * u[i]; } result[0][0] += 1.0; result[1][1] += 1.0; result[2][2] += 1.0; if(MatType::numColumns() == 4) padMat4(result); return result; } else { double c = v1.dot(v2); double a = (1.0 - c) / cross.dot(cross); double a0 = a * cross[0]; double a1 = a * cross[1]; double a2 = a * cross[2]; double a01 = a0 * cross[1]; double a02 = a0 * cross[2]; double a12 = a1 * cross[2]; MatType r; r[0][0] = c + a0 * cross[0]; r[0][1] = a01 + cross[2]; r[0][2] = a02 - cross[1], r[1][0] = a01 - cross[2]; r[1][1] = c + a1 * cross[1]; r[1][2] = a12 + cross[0]; r[2][0] = a02 + cross[1]; r[2][1] = a12 - cross[0]; r[2][2] = c + a2 * cross[2]; if(MatType::numColumns() == 4) padMat4(r); return r; } } /// @return the matrix to a matrix that scales by v template MatType scale(const Vec3 &scaling) { // Gets identity, then sets top 3 diagonal // Inefficient by 3 sets. MatType result; result.setIdentity(); result[0][0] = scaling[0]; result[1][1] = scaling[1]; result[2][2] = scaling[2]; return result; } /// @return a Vec3 representing the lengths of the passed matrix's upper /// 3x3's rows. template Vec3 getScale(const MatType &mat) { typedef Vec3 V; return V( V(mat[0][0], mat[0][1], mat[0][2]).length(), V(mat[1][0], mat[1][1], mat[1][2]).length(), V(mat[2][0], mat[2][1], mat[2][2]).length()); } /// @return a copy of included matrix with its upper 3x3 rows normalized. /// This can be geometrically interpretted as a matrix with no scaling /// along its major axes. template MatType unit(const MatType &mat, typename MatType::value_type eps = 1.0e-8) { Vec3 dud; return unit(mat, eps, dud); } /// @return a copy of included matrix with its upper 3x3 rows normalized, /// and writes the length of each of these rows. /// This can be geometrically interpretted as a matrix with no scaling /// along its major axes, and the scaling in the input vector template MatType unit( const MatType &in, typename MatType::value_type eps, Vec3& scaling) { typedef typename MatType::value_type T; MatType result(in); for (int i(0); i < 3; i++) { try { const Vec3 u( Vec3(in[i][0], in[i][1], in[i][2]).unit(eps, scaling[i])); for (int j=0; j<3; j++) result[i][j] = u[j]; } catch (ArithmeticError&) { for (int j=0; j<3; j++) result[i][j] = 0; } } return result; } /// @brief Set the matrix to a shear along axis0 by a fraction of axis1. /// @param axis0 The fixed axis of the shear. /// @param axis1 The shear axis. /// @param shear The shear factor. template MatType shear(Axis axis0, Axis axis1, typename MatType::value_type shear) { int index0 = static_cast(axis0); int index1 = static_cast(axis1); MatType result; result.setIdentity(); if (axis0 == axis1) { result[index1][index0] = shear + 1; } else { result[index1][index0] = shear; } return result; } /// @return a matrix as the cross product of the given vector template MatType skew(const Vec3 &skew) { typedef typename MatType::value_type T; MatType r; r[0][0] = T(0); r[0][1] = skew.z(); r[0][2] = -skew.y(); r[1][0] = -skew.z(); r[1][1] = T(0); r[2][1] = skew.x(); r[2][0] = skew.y(); r[2][1] = -skew.x(); r[2][2] = T(0); if(MatType::numColumns() == 4) padMat4(r); return r; } /// Build an orientation matrix such that z points along direction, /// and y is along direction/vertical plane. template MatType aim(const Vec3& direction, const Vec3& vertical) { typedef typename MatType::value_type T; Vec3 forward(direction.unit()); Vec3 horizontal(vertical.unit().cross(forward).unit()); Vec3 up(forward.cross(horizontal).unit()); MatType r; r[0][0]=horizontal.x(); r[0][1]=horizontal.y(); r[0][2]=horizontal.z(); r[1][0]=up.x(); r[1][1]=up.y(); r[1][2]=up.z(); r[2][0]=forward.x(); r[2][1]=forward.y(); r[2][2]=forward.z(); if(MatType::numColumns() == 4) padMat4(r); return r; } /// Write 0's along Mat4's last row and column, and a 1 on its diagonal /// Useful initialization when we're initializing juse the 3x3 block template static MatType& padMat4(MatType& dest) { dest[0][3] = dest[1][3] = dest[2][3] = 0; dest[3][2] = dest[3][1] = dest[3][0] = 0; dest[3][3] = 1; return dest; } /// Solve for A=B*B, given A /// /// Denman-Beavers square root iteration template inline void sqrtSolve(const MatType &aA, MatType &aB, double aTol=0.01) { unsigned int iterations = (unsigned int)(log(aTol)/log(0.5)); MatType Y[2]; MatType Z[2]; MatType invY; MatType invZ; unsigned int current = 0; Y[0]=aA; Z[0] = MatType::identity(); unsigned int iteration; for (iteration=0; iteration inline void powSolve(const MatType &aA, MatType &aB, double aPower, double aTol=0.01) { unsigned int iterations = (unsigned int)(log(aTol)/log(0.5)); const bool inverted = ( aPower < 0.0 ); if (inverted) { aPower = -aPower; } unsigned int whole = (unsigned int)aPower; double fraction = aPower - whole; MatType R; R = MatType::identity(); MatType partial = aA; double contribution = 1.0; unsigned int iteration; for (iteration=0; iteration< iterations; iteration++) { sqrtSolve(partial, partial, aTol); contribution *= 0.5; if (fraction>=contribution) { R *= partial; fraction-=contribution; } } partial = aA; while (whole) { if (whole & 1) { R *= partial; } whole>>=1; if(whole) { partial*=partial; } } if (inverted) { aB = R.inverse(); } else { aB = R; } } template inline bool isIdentity(const MatType& m) { typedef typename MatType::ValueType value_type; return m.eq(MatType::identity()); } template inline bool isInvertible(const MatType& m) { typedef typename MatType::ValueType value_type; return !isApproxEqual(m.det(), (value_type)0); } /// Determine if a matrix is symmetric. /// This implicitly uses "isApproxEqual" to determine the equality template inline bool isSymmetric(const MatType& m) { return m.eq(m.transpose()); } /// Determine is a matrix is Unitary (i.e. rotation or reflection) template inline bool isUnitary(const MatType& m) { typedef typename MatType::ValueType value_type; if (!isApproxEqual(std::abs(m.det()), value_type(1.0))) return false; // check that the matrix transpose is the inverse MatType temp = m * m.transpose(); return temp.eq(MatType::identity()); } /// Determine if a matrix is diagonal template inline bool isDiagonal(const MatType& mat) { int n = MatType::size; typename MatType::ValueType temp(0); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (i != j) { temp+=std::abs(mat(i,j)); } } } return isApproxEqual(temp, typename MatType::ValueType(0.0)); } /// takes a n by n matrix and returns the L_Infinty norm template typename MatType::ValueType lInfinityNorm(const MatType& matrix) { int n = MatType::size; typename MatType::ValueType norm = 0; for( int j = 0; j typename MatType::ValueType lOneNorm(const MatType& matrix) { int n = MatType::size; typename MatType::ValueType norm = 0; for( int i = 0; i bool polarDecomposition(const MatType& input, MatType& unitary, MatType& positive_hermitian, unsigned int MAX_ITERATIONS=100) { unitary = input; MatType new_unitary(input); MatType unitary_inv; if (fabs(unitary.det()) < math::Tolerance::value()) return false; unsigned int iteration(0); typename MatType::ValueType linf_of_u; typename MatType::ValueType l1nm_of_u; typename MatType::ValueType linf_of_u_inv; typename MatType::ValueType l1nm_of_u_inv; typename MatType::ValueType l1_error = 100; double gamma; do { unitary_inv = unitary.inverse(); linf_of_u = lInfinityNorm(unitary); l1nm_of_u = lOneNorm(unitary); linf_of_u_inv = lInfinityNorm(unitary_inv); l1nm_of_u_inv = lOneNorm(unitary_inv); gamma = sqrt( sqrt( (l1nm_of_u_inv * linf_of_u_inv ) / (l1nm_of_u * linf_of_u) )); new_unitary = 0.5*(gamma * unitary + (1./gamma) * unitary_inv.transpose() ); l1_error = lInfinityNorm(unitary - new_unitary); unitary = new_unitary; /// this generally converges in less than ten iterations if (iteration > MAX_ITERATIONS) return false; iteration++; } while (l1_error > math::Tolerance::value()); positive_hermitian = unitary.transpose() * input; return true; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_MAT_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Hermite.cc0000644000000000000000000001054012252453157014054 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Hermite.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// // min and max on compressd data Hermite min(const Hermite& lhs, const Hermite& rhs) { Hermite ret; if(!lhs && !rhs) { if(lhs.isInside()) ret = lhs; else ret = rhs; return ret; } ret.setIsInside(lhs.isInside() || rhs.isInside()); if(lhs.isGreaterX(rhs)) ret.setX(rhs); else ret.setX(lhs); if(lhs.isGreaterY(rhs)) ret.setY(rhs); else ret.setY(lhs); if(lhs.isGreaterZ(rhs)) ret.setZ(rhs); else ret.setZ(lhs); return ret; } Hermite max(const Hermite& lhs, const Hermite& rhs) { Hermite ret; if(!lhs && !rhs) { if(!lhs.isInside()) ret = lhs; else ret = rhs; return ret; } ret.setIsInside(lhs.isInside() && rhs.isInside()); if(rhs.isGreaterX(lhs)) ret.setX(rhs); else ret.setX(lhs); if(rhs.isGreaterY(lhs)) ret.setY(rhs); else ret.setY(lhs); if(rhs.isGreaterZ(lhs)) ret.setZ(rhs); else ret.setZ(lhs); return ret; } //////////////////////////////////////// // constructors Hermite::Hermite(): mXNormal(0), mYNormal(0), mZNormal(0), mData(0) { } Hermite::Hermite(const Hermite& rhs): mXNormal(rhs.mXNormal), mYNormal(rhs.mYNormal), mZNormal(rhs.mZNormal), mData(rhs.mData) { } //////////////////////////////////////// // string representation std::string Hermite::str() const { std::ostringstream ss; ss << "{ " << (isInside() ? "inside" : "outside"); if(hasOffsetX()) ss << " |x " << getOffsetX() << " " << getNormalX(); if(hasOffsetY()) ss << " |y " << getOffsetY() << " " << getNormalY(); if(hasOffsetZ()) ss << " |z " << getOffsetZ() << " " << getNormalZ(); ss << " }"; return ss.str(); } //////////////////////////////////////// void Hermite::read(std::istream& is) { is.read(reinterpret_cast(&mXNormal), sizeof(uint16_t)); is.read(reinterpret_cast(&mYNormal), sizeof(uint16_t)); is.read(reinterpret_cast(&mZNormal), sizeof(uint16_t)); is.read(reinterpret_cast(&mData), sizeof(uint32_t)); } void Hermite::write(std::ostream& os) const { os.write(reinterpret_cast(&mXNormal), sizeof(uint16_t)); os.write(reinterpret_cast(&mYNormal), sizeof(uint16_t)); os.write(reinterpret_cast(&mZNormal), sizeof(uint16_t)); os.write(reinterpret_cast(&mData), sizeof(uint32_t)); } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Ray.h0000644000000000000000000004032712252453157013062 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Ray.h /// /// @author Ken Museth /// /// @brief A Ray class and a Digital Differential Analyzer specialized for VDB. #ifndef OPENVDB_MATH_RAY_HAS_BEEN_INCLUDED #define OPENVDB_MATH_RAY_HAS_BEEN_INCLUDED #include "Math.h" #include "Vec3.h" #include "Transform.h" #include // for std::ostream #include #include // for std::numeric_limits::max() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Ray { public: BOOST_STATIC_ASSERT(boost::is_floating_point::value); typedef RealT RealType; typedef Vec3 Vec3Type; typedef Vec3Type Vec3T; Ray(const Vec3Type& eye = Vec3Type(0,0,0), const Vec3Type& direction = Vec3Type(1,0,0), RealT t0 = math::Delta::value(), RealT t1 = std::numeric_limits::max()) : mEye(eye), mDir(direction), mInvDir(1/mDir), mT0(t0), mT1(t1) { } inline void setEye(const Vec3Type& eye) { mEye = eye; } inline void setDir(const Vec3Type& dir) { mDir = dir; mInvDir = 1/mDir; } inline void setMinTime(RealT t0) { assert(t0>0); mT0 = t0; } inline void setMaxTime(RealT t1) { assert(t1>0); mT1 = t1; } inline void setTimes(RealT t0, RealT t1) { assert(t0>0 && t1>0);mT0 = t0; mT1 = t1; } inline void scaleTimes(RealT scale) { assert(scale>0); mT0 *= scale; mT1 *= scale; } inline void reset(const Vec3Type& eye, const Vec3Type& direction, RealT t0 = 0, RealT t1 = std::numeric_limits::max()) { this->setEye(eye); this->setDir(direction); this->setTimes(t0, t1); } inline const Vec3T& eye() const {return mEye;} inline const Vec3T& dir() const {return mDir;} inline const Vec3T& invDir() const {return mInvDir;} inline RealT t0() const {return mT0;} inline RealT t1() const {return mT1;} /// @brief Return the position along the ray at the specified time. inline Vec3R operator()(RealT time) const { return mEye + mDir * time; } /// @brief Return the starting point of the ray. inline Vec3R start() const { return (*this)(mT0); } /// @brief Return the endpoint of the ray. inline Vec3R end() const { return (*this)(mT1); } /// @brief Return the midpoint of the ray. inline Vec3R mid() const { return (*this)(0.5*(mT0+mT1)); } /// @brief Return @c true if t0 is strictly less then t1. inline bool test() const { return (mT0 < mT1); } /// @brief Return @c true if @a time is within t0 and t1, both inclusive. inline bool test(RealT time) const { return (time>=mT0 && time<=mT1); } /// @brief Return a new Ray that is transformed with the specified map. /// @param map the map from which to construct the new Ray. /// @warning Assumes a linear map and a normalize direction. /// @details The requirement that the direction is normalized /// follows from the transformation of t0 and t1 - and that fact that /// we want applyMap and applyInverseMap to be inverse operations. template inline Ray applyMap(const MapType& map) const { assert(map.isLinear()); assert(math::isApproxEqual(mDir.length(), RealT(1))); const Vec3T eye = map.applyMap(mEye); const Vec3T dir = map.applyJacobian(mDir); const RealT length = dir.length(); return Ray(eye, dir/length, length*mT0, length*mT1); } /// @brief Return a new Ray that is transformed with the inverse of the specified map. /// @param map the map from which to construct the new Ray by inverse mapping. /// @warning Assumes a linear map and a normalize direction. /// @details The requirement that the direction is normalized /// follows from the transformation of t0 and t1 - and that fact that /// we want applyMap and applyInverseMap to be inverse operations. template inline Ray applyInverseMap(const MapType& map) const { assert(map.isLinear()); assert(math::isApproxEqual(mDir.length(), RealT(1))); const Vec3T eye = map.applyInverseMap(mEye); const Vec3T dir = map.applyInverseJacobian(mDir); const RealT length = dir.length(); return Ray(eye, dir/length, length*mT0, length*mT1); } /// @brief Return a new ray in world space, assuming the existing /// ray is represented in the index space of the specified grid. template inline Ray indexToWorld(const GridType& grid) const { return this->applyMap(*(grid.transform().baseMap())); } /// @brief Return a new ray in the index space of the specified /// grid, assuming the existing ray is represented in world space. template inline Ray worldToIndex(const GridType& grid) const { return this->applyInverseMap(*(grid.transform().baseMap())); } /// @brief Return true if this ray intersects the specified sphere. /// @param center The center of the sphere in the same space as this ray. /// @param radius The radius of the sphere in the same units as this ray. /// @param t0 The first intersection point if an intersection exists. /// @param t1 The second intersection point if an intersection exists. /// @note If the return value is true, i.e. a hit, and t0 = /// this->t0() or t1 == this->t1() only one true intersection exist. inline bool intersects(const Vec3T& center, RealT radius, RealT& t0, RealT& t1) const { const Vec3T origin = mEye - center; const RealT A = mDir.lengthSqr(); const RealT B = 2 * mDir.dot(origin); const RealT C = origin.lengthSqr() - radius * radius; const RealT D = B * B - 4 * A * C; if (D < 0) return false; const RealT Q = RealT(-0.5)*(B<0 ? (B + Sqrt(D)) : (B - Sqrt(D))); t0 = Q / A; t1 = C / Q; if (t0 > t1) std::swap(t0, t1); if (t0 < mT0) t0 = mT0; if (t1 > mT1) t1 = mT1; return t0 <= t1; } /// @brief Return true if this ray intersects the specified sphere. /// @param center The center of the sphere in the same space as this ray. /// @param radius The radius of the sphere in the same units as this ray. inline bool intersects(const Vec3T& center, RealT radius) const { RealT t0, t1; return this->intersects(center, radius, t0, t1)>0; } /// @brief Return true if this ray intersects the specified sphere. /// @note For intersection this ray is clipped to the two intersection points. /// @param center The center of the sphere in the same space as this ray. /// @param radius The radius of the sphere in the same units as this ray. inline bool clip(const Vec3T& center, RealT radius) { RealT t0, t1; const bool hit = this->intersects(center, radius, t0, t1); if (hit) { mT0 = t0; mT1 = t1; } return hit; } /// @brief Return true if the Ray intersects the specified /// axisaligned bounding box. /// @param bbox Axis-aligned bounding box in the same space as the Ray. /// @param t0 If an intersection is detected this is assigned /// the time for the first intersection point. /// @param t1 If an intersection is detected this is assigned /// the time for the second intersection point. template inline bool intersects(const BBoxT& bbox, RealT& t0, RealT& t1) const { t0 = mT0; t1 = mT1; for (size_t i = 0; i < 3; ++i) { RealT a = (bbox.min()[i] - mEye[i]) * mInvDir[i]; RealT b = (bbox.max()[i] - mEye[i]) * mInvDir[i]; if (a > b) std::swap(a, b); if (a > t0) t0 = a; if (b < t1) t1 = b; if (t0 > t1) return false; } return true; } /// @brief Return true if this ray intersects the specified bounding box. /// @param bbox Axis-aligned bounding box in the same space as this ray. template inline bool intersects(const BBoxT& bbox) const { RealT t0, t1; return this->intersects(bbox, t0, t1); } /// @brief Return true if this ray intersects the specified bounding box. /// @note For intersection this ray is clipped to the two intersection points. /// @param bbox Axis-aligned bounding box in the same space as this ray. template inline bool clip(const BBoxT& bbox) { RealT t0, t1; const bool hit = this->intersects(bbox, t0, t1); if (hit) { mT0 = t0; mT1 = t1; } return hit; } /// @brief Return true if the Ray intersects the plane specified /// by a normal and distance from the origin. /// @param normal Normal of the plane. /// @param distance Distance of the plane to the origin. /// @param t Time of intersection, if one exists. inline bool intersects(const Vec3T& normal, RealT distance, RealT& t) const { const RealT cosAngle = mDir.dot(normal); if (math::isApproxZero(cosAngle)) return false;//parallel t = (distance - mEye.dot(normal))/cosAngle; return this->test(t); } /// @brief Return true if the Ray intersects the plane specified /// by a normal and point. /// @param normal Normal of the plane. /// @param point Point in the plane. /// @param t Time of intersection, if one exists. inline bool intersects(const Vec3T& normal, const Vec3T& point, RealT& t) const { return this->intersects(normal, point.dot(normal), t); } private: Vec3T mEye, mDir, mInvDir; RealT mT0, mT1; }; // end of Ray class /// @brief Output streaming of the Ray class. /// @note Primarily intended for debugging. template inline std::ostream& operator<<(std::ostream& os, const Ray& r) { os << "eye=" << r.eye() << " dir=" << r.dir() << " 1/dir="< struct is_vec3d { static const bool value = false; }; template<> struct is_vec3d { static const bool value = true; }; template struct is_double { static const bool value = false; }; template<> struct is_double { static const bool value = true; }; /// @brief Adapter to associate a map with a world-space operator, /// giving it the same call signature as an index-space operator /// @todo For now, the operator's result type must be specified explicitly, /// but eventually it should be possible, via traits, to derive the result type /// from the operator type. template struct MapAdapter { MapAdapter(const MapType& m): map(m) {} template inline ResultType result(const AccessorType& grid, const Coord& ijk) { return OpType::result(map, grid, ijk); } template inline ResultType result(const StencilType& stencil) { return OpType::result(map, stencil); } const MapType map; }; /// Adapter for vector-valued index-space operators to return the vector magnitude template struct ISOpMagnitude { template static inline double result(const AccessorType& grid, const Coord& ijk) { return double(OpType::result(grid, ijk).length()); } template static inline double result(const StencilType& stencil) { return double(OpType::result(stencil).length()); } }; /// Adapter for vector-valued world-space operators to return the vector magnitude template struct OpMagnitude { template static inline double result(const MapT& map, const AccessorType& grid, const Coord& ijk) { return double(OpType::result(map, grid, ijk).length()); } template static inline double result(const MapT& map, const StencilType& stencil) { return double(OpType::result(map, stencil).length()); } }; namespace internal { // This additional layer is necessary for Visual C++ to compile. template struct ReturnValue { typedef typename T::ValueType ValueType; typedef math::Vec3 Vec3Type; }; } // namespace internal // ---- Operators defined in index space //@{ /// @brief Gradient operators defined in index space of various orders template struct ISGradient { // random access version template static Vec3 result(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typedef Vec3 Vec3Type; return Vec3Type( D1::inX(grid, ijk), D1::inY(grid, ijk), D1::inZ(grid, ijk) ); } // stencil access version template static Vec3 result(const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; typedef Vec3 Vec3Type; return Vec3Type( D1::inX(stencil), D1::inY(stencil), D1::inZ(stencil) ); } }; //@} /// struct that relates the BiasedGradientScheme to the /// forward and backward difference methods used, as well as to /// the correct stencil type for index space use template struct BIAS_SCHEME { static const DScheme FD = FD_1ST; static const DScheme BD = BD_1ST; template struct ISStencil { typedef SevenPointStencil StencilType; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_1ST; static const DScheme BD = BD_1ST; template struct ISStencil { typedef SevenPointStencil StencilType; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_2ND; static const DScheme BD = BD_2ND; template struct ISStencil { typedef ThirteenPointStencil StencilType; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_3RD; static const DScheme BD = BD_3RD; template struct ISStencil { typedef NineteenPointStencil StencilType; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_WENO5; static const DScheme BD = BD_WENO5; template struct ISStencil { typedef NineteenPointStencil StencilType; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_HJWENO5; static const DScheme BD = BD_HJWENO5; template struct ISStencil { typedef NineteenPointStencil StencilType; }; }; //@{ /// @brief Biased Gradient Operators, using upwinding defined by the @c Vec3Bias input template struct ISGradientBiased { static const DScheme FD = BIAS_SCHEME::FD; static const DScheme BD = BIAS_SCHEME::BD; // random access version template static Vec3 result(const Accessor& grid, const Coord& ijk, const Vec3Bias& V) { typedef typename Accessor::ValueType ValueType; typedef Vec3 Vec3Type; return Vec3Type(V[0]<0 ? D1::inX(grid,ijk) : D1::inX(grid,ijk), V[1]<0 ? D1::inY(grid,ijk) : D1::inY(grid,ijk), V[2]<0 ? D1::inZ(grid,ijk) : D1::inZ(grid,ijk) ); } // stencil access version template static Vec3 result(const StencilT& stencil, const Vec3Bias& V) { typedef typename StencilT::ValueType ValueType; typedef Vec3 Vec3Type; return Vec3Type(V[0]<0 ? D1::inX(stencil) : D1::inX(stencil), V[1]<0 ? D1::inY(stencil) : D1::inY(stencil), V[2]<0 ? D1::inZ(stencil) : D1::inZ(stencil) ); } }; template struct ISGradientNormSqrd { static const DScheme FD = BIAS_SCHEME::FD; static const DScheme BD = BIAS_SCHEME::BD; // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typedef math::Vec3 Vec3Type; Vec3Type up = ISGradient::result(grid, ijk); Vec3Type down = ISGradient::result(grid, ijk); return math::GudonovsNormSqrd(grid.getValue(ijk)>0, down, up); } // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; typedef math::Vec3 Vec3Type; Vec3Type up = ISGradient::result(stencil); Vec3Type down = ISGradient::result(stencil); return math::GudonovsNormSqrd(stencil.template getValue<0, 0, 0>()>0, down, up); } }; #ifdef DWA_OPENVDB // for SIMD - note will do the computations in float template<> struct ISGradientNormSqrd { // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typedef math::Vec3 Vec3Type; // SSE optimized const simd::Float4 v1(grid.getValue(ijk.offsetBy(-2, 0, 0)) - grid.getValue(ijk.offsetBy(-3, 0, 0)), grid.getValue(ijk.offsetBy( 0,-2, 0)) - grid.getValue(ijk.offsetBy( 0,-3, 0)), grid.getValue(ijk.offsetBy( 0, 0,-2)) - grid.getValue(ijk.offsetBy( 0, 0,-3)), 0), v2(grid.getValue(ijk.offsetBy(-1, 0, 0)) - grid.getValue(ijk.offsetBy(-2, 0, 0)), grid.getValue(ijk.offsetBy( 0,-1, 0)) - grid.getValue(ijk.offsetBy( 0,-2, 0)), grid.getValue(ijk.offsetBy( 0, 0,-1)) - grid.getValue(ijk.offsetBy( 0, 0,-2)), 0), v3(grid.getValue(ijk ) - grid.getValue(ijk.offsetBy(-1, 0, 0)), grid.getValue(ijk ) - grid.getValue(ijk.offsetBy( 0,-1, 0)), grid.getValue(ijk ) - grid.getValue(ijk.offsetBy( 0, 0,-1)), 0), v4(grid.getValue(ijk.offsetBy( 1, 0, 0)) - grid.getValue(ijk ), grid.getValue(ijk.offsetBy( 0, 1, 0)) - grid.getValue(ijk ), grid.getValue(ijk.offsetBy( 0, 0, 1)) - grid.getValue(ijk ), 0), v5(grid.getValue(ijk.offsetBy( 2, 0, 0)) - grid.getValue(ijk.offsetBy( 1, 0, 0)), grid.getValue(ijk.offsetBy( 0, 2, 0)) - grid.getValue(ijk.offsetBy( 0, 1, 0)), grid.getValue(ijk.offsetBy( 0, 0, 2)) - grid.getValue(ijk.offsetBy( 0, 0, 1)), 0), v6(grid.getValue(ijk.offsetBy( 3, 0, 0)) - grid.getValue(ijk.offsetBy( 2, 0, 0)), grid.getValue(ijk.offsetBy( 0, 3, 0)) - grid.getValue(ijk.offsetBy( 0, 2, 0)), grid.getValue(ijk.offsetBy( 0, 0, 3)) - grid.getValue(ijk.offsetBy( 0, 0, 2)), 0), down = math::WENO5(v1, v2, v3, v4, v5), up = math::WENO5(v6, v5, v4, v3, v2); return math::GudonovsNormSqrd(grid.getValue(ijk)>0, down, up); } // stencil access version template static typename StencilT::ValueType result(const StencilT& s) { typedef typename StencilT::ValueType ValueType; typedef math::Vec3 Vec3Type; // SSE optimized const simd::Float4 v1(s.template getValue<-2, 0, 0>() - s.template getValue<-3, 0, 0>(), s.template getValue< 0,-2, 0>() - s.template getValue< 0,-3, 0>(), s.template getValue< 0, 0,-2>() - s.template getValue< 0, 0,-3>(), 0), v2(s.template getValue<-1, 0, 0>() - s.template getValue<-2, 0, 0>(), s.template getValue< 0,-1, 0>() - s.template getValue< 0,-2, 0>(), s.template getValue< 0, 0,-1>() - s.template getValue< 0, 0,-2>(), 0), v3(s.template getValue< 0, 0, 0>() - s.template getValue<-1, 0, 0>(), s.template getValue< 0, 0, 0>() - s.template getValue< 0,-1, 0>(), s.template getValue< 0, 0, 0>() - s.template getValue< 0, 0,-1>(), 0), v4(s.template getValue< 1, 0, 0>() - s.template getValue< 0, 0, 0>(), s.template getValue< 0, 1, 0>() - s.template getValue< 0, 0, 0>(), s.template getValue< 0, 0, 1>() - s.template getValue< 0, 0, 0>(), 0), v5(s.template getValue< 2, 0, 0>() - s.template getValue< 1, 0, 0>(), s.template getValue< 0, 2, 0>() - s.template getValue< 0, 1, 0>(), s.template getValue< 0, 0, 2>() - s.template getValue< 0, 0, 1>(), 0), v6(s.template getValue< 3, 0, 0>() - s.template getValue< 2, 0, 0>(), s.template getValue< 0, 3, 0>() - s.template getValue< 0, 2, 0>(), s.template getValue< 0, 0, 3>() - s.template getValue< 0, 0, 2>(), 0), down = math::WENO5(v1, v2, v3, v4, v5), up = math::WENO5(v6, v5, v4, v3, v2); return math::GudonovsNormSqrd(s.template getValue<0, 0, 0>()>0, down, up); } }; #endif //DWA_OPENVDB // for SIMD - note will do the computations in float //@} //@{ /// @brief Laplacian defined in index space, using various center-difference stencils template struct ISLaplacian { // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk); // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil); }; template<> struct ISLaplacian { // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) { return grid.getValue(ijk.offsetBy(1,0,0)) + grid.getValue(ijk.offsetBy(-1, 0, 0)) + grid.getValue(ijk.offsetBy(0,1,0)) + grid.getValue(ijk.offsetBy(0, -1, 0)) + grid.getValue(ijk.offsetBy(0,0,1)) + grid.getValue(ijk.offsetBy(0, 0,-1)) - 6*grid.getValue(ijk); } // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil) { return stencil.template getValue< 1, 0, 0>() + stencil.template getValue<-1, 0, 0>() + stencil.template getValue< 0, 1, 0>() + stencil.template getValue< 0,-1, 0>() + stencil.template getValue< 0, 0, 1>() + stencil.template getValue< 0, 0,-1>() - 6*stencil.template getValue< 0, 0, 0>(); } }; template<> struct ISLaplacian { // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) { return (-1./12.)*( grid.getValue(ijk.offsetBy(2,0,0)) + grid.getValue(ijk.offsetBy(-2, 0, 0)) + grid.getValue(ijk.offsetBy(0,2,0)) + grid.getValue(ijk.offsetBy( 0,-2, 0)) + grid.getValue(ijk.offsetBy(0,0,2)) + grid.getValue(ijk.offsetBy( 0, 0,-2)) ) + (4./3.)*( grid.getValue(ijk.offsetBy(1,0,0)) + grid.getValue(ijk.offsetBy(-1, 0, 0)) + grid.getValue(ijk.offsetBy(0,1,0)) + grid.getValue(ijk.offsetBy( 0,-1, 0)) + grid.getValue(ijk.offsetBy(0,0,1)) + grid.getValue(ijk.offsetBy( 0, 0,-1)) ) - 7.5*grid.getValue(ijk); } // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil) { return (-1./12.)*( stencil.template getValue< 2, 0, 0>() + stencil.template getValue<-2, 0, 0>() + stencil.template getValue< 0, 2, 0>() + stencil.template getValue< 0,-2, 0>() + stencil.template getValue< 0, 0, 2>() + stencil.template getValue< 0, 0,-2>() ) + (4./3.)*( stencil.template getValue< 1, 0, 0>() + stencil.template getValue<-1, 0, 0>() + stencil.template getValue< 0, 1, 0>() + stencil.template getValue< 0,-1, 0>() + stencil.template getValue< 0, 0, 1>() + stencil.template getValue< 0, 0,-1>() ) - 7.5*stencil.template getValue< 0, 0, 0>(); } }; template<> struct ISLaplacian { // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) { return (1./90.)*( grid.getValue(ijk.offsetBy(3,0,0)) + grid.getValue(ijk.offsetBy(-3, 0, 0)) + grid.getValue(ijk.offsetBy(0,3,0)) + grid.getValue(ijk.offsetBy( 0,-3, 0)) + grid.getValue(ijk.offsetBy(0,0,3)) + grid.getValue(ijk.offsetBy( 0, 0,-3)) ) - (3./20.)*( grid.getValue(ijk.offsetBy(2,0,0)) + grid.getValue(ijk.offsetBy(-2, 0, 0)) + grid.getValue(ijk.offsetBy(0,2,0)) + grid.getValue(ijk.offsetBy( 0,-2, 0)) + grid.getValue(ijk.offsetBy(0,0,2)) + grid.getValue(ijk.offsetBy( 0, 0,-2)) ) + 1.5 *( grid.getValue(ijk.offsetBy(1,0,0)) + grid.getValue(ijk.offsetBy(-1, 0, 0)) + grid.getValue(ijk.offsetBy(0,1,0)) + grid.getValue(ijk.offsetBy( 0,-1, 0)) + grid.getValue(ijk.offsetBy(0,0,1)) + grid.getValue(ijk.offsetBy( 0, 0,-1)) ) - (3*49/18.)*grid.getValue(ijk); } // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil) { return (1./90.)*( stencil.template getValue< 3, 0, 0>() + stencil.template getValue<-3, 0, 0>() + stencil.template getValue< 0, 3, 0>() + stencil.template getValue< 0,-3, 0>() + stencil.template getValue< 0, 0, 3>() + stencil.template getValue< 0, 0,-3>() ) - (3./20.)*( stencil.template getValue< 2, 0, 0>() + stencil.template getValue<-2, 0, 0>() + stencil.template getValue< 0, 2, 0>() + stencil.template getValue< 0,-2, 0>() + stencil.template getValue< 0, 0, 2>() + stencil.template getValue< 0, 0,-2>() ) + 1.5 *( stencil.template getValue< 1, 0, 0>() + stencil.template getValue<-1, 0, 0>() + stencil.template getValue< 0, 1, 0>() + stencil.template getValue< 0,-1, 0>() + stencil.template getValue< 0, 0, 1>() + stencil.template getValue< 0, 0,-1>() ) - (3*49/18.)*stencil.template getValue< 0, 0, 0>(); } }; //@} //@{ /// Divergence operator defined in index space using various first derivative schemes template struct ISDivergence { // random access version template static typename Accessor::ValueType::value_type result(const Accessor& grid, const Coord& ijk) { return D1Vec::inX(grid, ijk, 0) + D1Vec::inY(grid, ijk, 1) + D1Vec::inZ(grid, ijk, 2); } // stencil access version template static typename StencilT::ValueType::value_type result(const StencilT& stencil) { return D1Vec::inX(stencil, 0) + D1Vec::inY(stencil, 1) + D1Vec::inZ(stencil, 2); } }; //@} //@{ /// Curl operator defined in index space using various first derivative schemes template struct ISCurl { // random access version template static typename Accessor::ValueType result(const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; return Vec3Type( D1Vec::inY(grid, ijk, 2) - //dw/dy - dv/dz D1Vec::inZ(grid, ijk, 1), D1Vec::inZ(grid, ijk, 0) - //du/dz - dw/dx D1Vec::inX(grid, ijk, 2), D1Vec::inX(grid, ijk, 1) - //dv/dx - du/dy D1Vec::inY(grid, ijk, 0) ); } // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; return Vec3Type( D1Vec::inY(stencil, 2) - //dw/dy - dv/dz D1Vec::inZ(stencil, 1), D1Vec::inZ(stencil, 0) - //du/dz - dw/dx D1Vec::inX(stencil, 2), D1Vec::inX(stencil, 1) - //dv/dx - du/dy D1Vec::inY(stencil, 0) ); } }; //@} //@{ /// Compute the mean curvature in index space template struct ISMeanCurvature { // random access version template static void result(const Accessor& grid, const Coord& ijk, typename Accessor::ValueType& alpha, typename Accessor::ValueType& beta) { typedef typename Accessor::ValueType ValueType; ValueType Dx = D1::inX(grid, ijk); ValueType Dy = D1::inY(grid, ijk); ValueType Dz = D1::inZ(grid, ijk); ValueType Dx2 = Dx*Dx; ValueType Dy2 = Dy*Dy; ValueType Dz2 = Dz*Dz; ValueType Dxx = D2::inX(grid, ijk); ValueType Dyy = D2::inY(grid, ijk); ValueType Dzz = D2::inZ(grid, ijk); ValueType Dxy = D2::inXandY(grid, ijk); ValueType Dyz = D2::inYandZ(grid, ijk); ValueType Dxz = D2::inXandZ(grid, ijk); // for return alpha = (Dx2*(Dyy+Dzz)+Dy2*(Dxx+Dzz)+Dz2*(Dxx+Dyy)-2*(Dx*(Dy*Dxy+Dz*Dxz)+Dy*Dz*Dyz)); beta = ValueType(std::sqrt(double(Dx2 + Dy2 + Dz2))); // * 1/dx } // stencil access version template static void result(const StencilT& stencil, typename StencilT::ValueType& alpha, typename StencilT::ValueType& beta) { typedef typename StencilT::ValueType ValueType; ValueType Dx = D1::inX(stencil); ValueType Dy = D1::inY(stencil); ValueType Dz = D1::inZ(stencil); ValueType Dx2 = Dx*Dx; ValueType Dy2 = Dy*Dy; ValueType Dz2 = Dz*Dz; ValueType Dxx = D2::inX(stencil); ValueType Dyy = D2::inY(stencil); ValueType Dzz = D2::inZ(stencil); ValueType Dxy = D2::inXandY(stencil); ValueType Dyz = D2::inYandZ(stencil); ValueType Dxz = D2::inXandZ(stencil); // for return alpha = (Dx2*(Dyy+Dzz)+Dy2*(Dxx+Dzz)+Dz2*(Dxx+Dyy)-2*(Dx*(Dy*Dxy+Dz*Dxz)+Dy*Dz*Dyz)); beta = ValueType(std::sqrt(double(Dx2 + Dy2 + Dz2))); // * 1/dx } }; //////////////////////////////////////////////////////// // --- Operators defined in the Range of a given map //@{ /// @brief Center difference gradient operators, defined with respect to /// the range-space of the @c map /// @note This will need to be divided by two in the case of CD_2NDT template struct Gradient { // random access version template static typename internal::ReturnValue::Vec3Type result(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3d iGradient( ISGradient::result(grid, ijk) ); return Vec3Type(map.applyIJT(iGradient, ijk.asVec3d())); } // stencil access version template static typename internal::ReturnValue::Vec3Type result(const MapType& map, const StencilT& stencil) { typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3d iGradient( ISGradient::result(stencil) ); return Vec3Type(map.applyIJT(iGradient, stencil.getCenterCoord().asVec3d())); } }; // Partial template specialization of Gradient // translation, any order template struct Gradient { // random access version template static typename internal::ReturnValue::Vec3Type result(const TranslationMap&, const Accessor& grid, const Coord& ijk) { return ISGradient::result(grid, ijk); } // stencil access version template static typename internal::ReturnValue::Vec3Type result(const TranslationMap&, const StencilT& stencil) { return ISGradient::result(stencil); } }; /// Full template specialization of Gradient /// uniform scale, 2nd order template<> struct Gradient { // random access version template static typename internal::ReturnValue::Vec3Type result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename internal::ReturnValue::ValueType ValueType; typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3Type iGradient( ISGradient::result(grid, ijk) ); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return iGradient * inv2dx; } // stencil access version template static typename internal::ReturnValue::Vec3Type result(const UniformScaleMap& map, const StencilT& stencil) { typedef typename internal::ReturnValue::ValueType ValueType; typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3Type iGradient( ISGradient::result(stencil) ); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return iGradient * inv2dx; } }; /// Full template specialization of Gradient /// uniform scale translate, 2nd order template<> struct Gradient { // random access version template static typename internal::ReturnValue::Vec3Type result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename internal::ReturnValue::ValueType ValueType; typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3Type iGradient( ISGradient::result(grid, ijk) ); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return iGradient * inv2dx; } // stencil access version template static typename internal::ReturnValue::Vec3Type result(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename internal::ReturnValue::ValueType ValueType; typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3Type iGradient( ISGradient::result(stencil) ); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return iGradient * inv2dx; } }; /// Full template specialization of Gradient /// scale, 2nd order template<> struct Gradient { // random access version template static typename internal::ReturnValue::Vec3Type result(const ScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename internal::ReturnValue::ValueType ValueType; typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3Type iGradient( ISGradient::result(grid, ijk) ); return Vec3Type(ValueType(iGradient[0] * map.getInvTwiceScale()[0]), ValueType(iGradient[1] * map.getInvTwiceScale()[1]), ValueType(iGradient[2] * map.getInvTwiceScale()[2]) ); } // stencil access version template static typename internal::ReturnValue::Vec3Type result(const ScaleMap& map, const StencilT& stencil) { typedef typename internal::ReturnValue::ValueType ValueType; typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3Type iGradient( ISGradient::result(stencil) ); return Vec3Type(ValueType(iGradient[0] * map.getInvTwiceScale()[0]), ValueType(iGradient[1] * map.getInvTwiceScale()[1]), ValueType(iGradient[2] * map.getInvTwiceScale()[2]) ); } }; /// Full template specialization of Gradient /// scale translate, 2nd order template<> struct Gradient { // random access version template static typename internal::ReturnValue::Vec3Type result(const ScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename internal::ReturnValue::ValueType ValueType; typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3Type iGradient( ISGradient::result(grid, ijk) ); return Vec3Type(ValueType(iGradient[0] * map.getInvTwiceScale()[0]), ValueType(iGradient[1] * map.getInvTwiceScale()[1]), ValueType(iGradient[2] * map.getInvTwiceScale()[2]) ); } // Stencil access version template static typename internal::ReturnValue::Vec3Type result(const ScaleTranslateMap& map, const StencilT& stencil) { typedef typename internal::ReturnValue::ValueType ValueType; typedef typename internal::ReturnValue::Vec3Type Vec3Type; Vec3Type iGradient( ISGradient::result(stencil) ); return Vec3Type(ValueType(iGradient[0] * map.getInvTwiceScale()[0]), ValueType(iGradient[1] * map.getInvTwiceScale()[1]), ValueType(iGradient[2] * map.getInvTwiceScale()[2]) ); } }; //@} //@{ /// @brief Biased gradient operators, defined with respect to the range-space of the map /// @note This will need to be divided by two in the case of CD_2NDT template struct GradientBiased { // random access version template static math::Vec3 result(const MapType& map, const Accessor& grid, const Coord& ijk, const Vec3& V) { typedef typename Accessor::ValueType ValueType; typedef math::Vec3 Vec3Type; Vec3d iGradient( ISGradientBiased::result(grid, ijk, V) ); return Vec3Type(map.applyIJT(iGradient, ijk.asVec3d())); } // stencil access version template static math::Vec3 result(const MapType& map, const StencilT& stencil, const Vec3& V) { typedef typename StencilT::ValueType ValueType; typedef math::Vec3 Vec3Type; Vec3d iGradient( ISGradientBiased::result(stencil, V) ); return Vec3Type(map.applyIJT(iGradient, stencil.getCenterCoord().asVec3d())); } }; //@} //////////////////////////////////////////////////////// // Computes |Grad[Phi]| using upwinding template struct GradientNormSqrd { static const DScheme FD = BIAS_SCHEME::FD; static const DScheme BD = BIAS_SCHEME::BD; // random access version template static typename Accessor::ValueType result(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typedef math::Vec3 Vec3Type; Vec3Type up = Gradient::result(map, grid, ijk); Vec3Type down = Gradient::result(map, grid, ijk); return math::GudonovsNormSqrd(grid.getValue(ijk)>0, down, up); } // stencil access version template static typename StencilT::ValueType result(const MapType& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; typedef math::Vec3 Vec3Type; Vec3Type up = Gradient::result(map, stencil); Vec3Type down = Gradient::result(map, stencil); return math::GudonovsNormSqrd(stencil.template getValue<0, 0, 0>()>0, down, up); } }; /// Partial template specialization of GradientNormSqrd template struct GradientNormSqrd { // random access version template static typename Accessor::ValueType result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return invdxdx * ISGradientNormSqrd::result(grid, ijk); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return invdxdx * ISGradientNormSqrd::result(stencil); } }; /// Partial template specialization of GradientNormSqrd template struct GradientNormSqrd { // random access version template static typename Accessor::ValueType result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return invdxdx * ISGradientNormSqrd::result(grid, ijk); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return invdxdx * ISGradientNormSqrd::result(stencil); } }; //@{ /// @brief Compute the divergence of a vector-valued grid using differencing /// of various orders, the result defined with respect to the range-space of the map. template struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div(0); for (int i=0; i < 3; i++) { Vec3d vec( D1Vec::inX(grid, ijk, i), D1Vec::inY(grid, ijk, i), D1Vec::inZ(grid, ijk, i) ); div += ValueType(map.applyIJT(vec, ijk.asVec3d())[i]); } return div; } // stencil access version template static typename StencilT::ValueType::value_type result(const MapType& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div(0); for (int i=0; i < 3; i++) { Vec3d vec( D1Vec::inX(stencil, i), D1Vec::inY(stencil, i), D1Vec::inZ(stencil, i) ); div += ValueType(map.applyIJT(vec, stencil.getCenterCoord().asVec3d())[i]); } return div; } }; /// Partial template specialization of Divergence /// translation, any scheme template struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const TranslationMap&, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(grid, ijk); return div; } // stencil access version template static typename StencilT::ValueType::value_type result(const TranslationMap&, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(stencil); return div; } }; /// Partial template specialization of Divergence /// uniform scale, any scheme template struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(grid, ijk); ValueType invdx = ValueType(map.getInvScale()[0]); return div * invdx; } // stencil access version template static typename StencilT::ValueType::value_type result(const UniformScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(stencil); ValueType invdx = ValueType(map.getInvScale()[0]); return div * invdx; } }; /// Partial template specialization of Divergence /// uniform scale and translation, any scheme template struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(grid, ijk); ValueType invdx = ValueType(map.getInvScale()[0]); return div * invdx; } // stencil access version template static typename StencilT::ValueType::value_type result(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(stencil); ValueType invdx = ValueType(map.getInvScale()[0]); return div * invdx; } }; /// Full template specialization of Divergence /// uniform scale 2nd order template<> struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(grid, ijk); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return div * inv2dx; } // stencil access version template static typename StencilT::ValueType::value_type result(const UniformScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(stencil); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return div * inv2dx; } }; /// Full template specialization of Divergence /// uniform scale translate 2nd order template<> struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(grid, ijk); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return div * inv2dx; } // stencil access version template static typename StencilT::ValueType::value_type result(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div(0); div =ISDivergence::result(stencil); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return div * inv2dx; } }; /// Partial template specialization of Divergence /// scale, any scheme template struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const ScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div = ValueType( D1Vec::inX(grid, ijk, 0) * (map.getInvScale()[0]) + D1Vec::inY(grid, ijk, 1) * (map.getInvScale()[1]) + D1Vec::inZ(grid, ijk, 2) * (map.getInvScale()[2])); return div; } // stencil access version template static typename StencilT::ValueType::value_type result(const ScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div(0); div = ValueType( D1Vec::inX(stencil, 0) * (map.getInvScale()[0]) + D1Vec::inY(stencil, 1) * (map.getInvScale()[1]) + D1Vec::inZ(stencil, 2) * (map.getInvScale()[2]) ); return div; } }; /// Partial template specialization of Divergence /// scale translate, any scheme template struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const ScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div = ValueType( D1Vec::inX(grid, ijk, 0) * (map.getInvScale()[0]) + D1Vec::inY(grid, ijk, 1) * (map.getInvScale()[1]) + D1Vec::inZ(grid, ijk, 2) * (map.getInvScale()[2])); return div; } // stencil access version template static typename StencilT::ValueType::value_type result(const ScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div(0); div = ValueType( D1Vec::inX(stencil, 0) * (map.getInvScale()[0]) + D1Vec::inY(stencil, 1) * (map.getInvScale()[1]) + D1Vec::inZ(stencil, 2) * (map.getInvScale()[2]) ); return div; } }; /// Full template specialization Divergence /// scale 2nd order template<> struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const ScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div = ValueType( D1Vec::inX(grid, ijk, 0) * (map.getInvTwiceScale()[0]) + D1Vec::inY(grid, ijk, 1) * (map.getInvTwiceScale()[1]) + D1Vec::inZ(grid, ijk, 2) * (map.getInvTwiceScale()[2]) ); return div; } // stencil access version template static typename StencilT::ValueType::value_type result(const ScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div = ValueType( D1Vec::inX(stencil, 0) * (map.getInvTwiceScale()[0]) + D1Vec::inY(stencil, 1) * (map.getInvTwiceScale()[1]) + D1Vec::inZ(stencil, 2) * (map.getInvTwiceScale()[2]) ); return div; } }; /// Full template specialization of Divergence /// scale and translate, 2nd order template<> struct Divergence { // random access version template static typename Accessor::ValueType::value_type result(const ScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Accessor::ValueType::value_type ValueType; ValueType div = ValueType( D1Vec::inX(grid, ijk, 0) * (map.getInvTwiceScale()[0]) + D1Vec::inY(grid, ijk, 1) * (map.getInvTwiceScale()[1]) + D1Vec::inZ(grid, ijk, 2) * (map.getInvTwiceScale()[2]) ); return div; } // stencil access version template static typename StencilT::ValueType::value_type result(const ScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename StencilT::ValueType::value_type ValueType; ValueType div = ValueType( D1Vec::inX(stencil, 0) * (map.getInvTwiceScale()[0]) + D1Vec::inY(stencil, 1) * (map.getInvTwiceScale()[1]) + D1Vec::inZ(stencil, 2) * (map.getInvTwiceScale()[2]) ); return div; } }; //@} //@{ /// @brief Compute the curl of a vector-valued grid using differencing /// of various orders in the space defined by the range of the map. template struct Curl { // random access version template static typename Accessor::ValueType result(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; Vec3Type mat[3]; for (int i = 0; i < 3; i++) { Vec3d vec( D1Vec::inX(grid, ijk, i), D1Vec::inY(grid, ijk, i), D1Vec::inZ(grid, ijk, i)); // dF_i/dx_j (x_1 = x, x_2 = y, x_3 = z) mat[i] = Vec3Type(map.applyIJT(vec, ijk.asVec3d())); } return Vec3Type(mat[2][1] - mat[1][2], // dF_3/dx_2 - dF_2/dx_3 mat[0][2] - mat[2][0], // dF_1/dx_3 - dF_3/dx_1 mat[1][0] - mat[0][1]); // dF_2/dx_1 - dF_1/dx_2 } // stencil access version template static typename StencilT::ValueType result(const MapType& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; Vec3Type mat[3]; for (int i = 0; i < 3; i++) { Vec3d vec( D1Vec::inX(stencil, i), D1Vec::inY(stencil, i), D1Vec::inZ(stencil, i)); // dF_i/dx_j (x_1 = x, x_2 = y, x_3 = z) mat[i] = Vec3Type(map.applyIJT(vec, stencil.getCenterCoord().asVec3d())); } return Vec3Type(mat[2][1] - mat[1][2], // dF_3/dx_2 - dF_2/dx_3 mat[0][2] - mat[2][0], // dF_1/dx_3 - dF_3/dx_1 mat[1][0] - mat[0][1]); // dF_2/dx_1 - dF_1/dx_2 } }; /// Partial template specialization of Curl template struct Curl { // random access version template static typename Accessor::ValueType result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Vec3Type::value_type ValueType; return ISCurl::result(grid, ijk) * ValueType(map.getInvScale()[0]); } // Stencil access version template static typename StencilT::ValueType result(const UniformScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename Vec3Type::value_type ValueType; return ISCurl::result(stencil) * ValueType(map.getInvScale()[0]); } }; /// Partial template specialization of Curl template struct Curl { // random access version template static typename Accessor::ValueType result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Vec3Type::value_type ValueType; return ISCurl::result(grid, ijk) * ValueType(map.getInvScale()[0]); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename Vec3Type::value_type ValueType; return ISCurl::result(stencil) * ValueType(map.getInvScale()[0]); } }; /// Full template specialization of Curl template<> struct Curl { // random access version template static typename Accessor::ValueType result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Vec3Type::value_type ValueType; return ISCurl::result(grid, ijk) * ValueType(map.getInvTwiceScale()[0]); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename Vec3Type::value_type ValueType; return ISCurl::result(stencil) * ValueType(map.getInvTwiceScale()[0]); } }; /// Full template specialization of Curl template<> struct Curl { // random access version template static typename Accessor::ValueType result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType Vec3Type; typedef typename Vec3Type::value_type ValueType; return ISCurl::result(grid, ijk) * ValueType(map.getInvTwiceScale()[0]); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType Vec3Type; typedef typename Vec3Type::value_type ValueType; return ISCurl::result(stencil) * ValueType(map.getInvTwiceScale()[0]); } }; //@} //@{ /// @brief Compute the Laplacian at a given location in a grid using finite differencing /// of various orders. The result is defined in the range of the map. template struct Laplacian { // random access version template static typename Accessor::ValueType result(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; // all the second derivatives in index space ValueType iddx = D2::inX(grid, ijk); ValueType iddy = D2::inY(grid, ijk); ValueType iddz = D2::inZ(grid, ijk); ValueType iddxy = D2::inXandY(grid, ijk); ValueType iddyz = D2::inYandZ(grid, ijk); ValueType iddxz = D2::inXandZ(grid, ijk); // second derivatives in index space Mat3d d2_is(iddx, iddxy, iddxz, iddxy, iddy, iddyz, iddxz, iddyz, iddz); Mat3d d2_rs; // to hold the second derivative matrix in range space if (is_linear::value) { d2_rs = map.applyIJC(d2_is); } else { // compute the first derivatives with 2nd order accuracy. Vec3d d1_is(D1::inX(grid, ijk), D1::inY(grid, ijk), D1::inZ(grid, ijk) ); d2_rs = map.applyIJC(d2_is, d1_is, ijk.asVec3d()); } // the trace of the second derivative (range space) matrix is laplacian return ValueType(d2_rs(0,0) + d2_rs(1,1) + d2_rs(2,2)); } // stencil access version template static typename StencilT::ValueType result(const MapType& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; // all the second derivatives in index space ValueType iddx = D2::inX(stencil); ValueType iddy = D2::inY(stencil); ValueType iddz = D2::inZ(stencil); ValueType iddxy = D2::inXandY(stencil); ValueType iddyz = D2::inYandZ(stencil); ValueType iddxz = D2::inXandZ(stencil); // second derivatives in index space Mat3d d2_is(iddx, iddxy, iddxz, iddxy, iddy, iddyz, iddxz, iddyz, iddz); Mat3d d2_rs; // to hold the second derivative matrix in range space if (is_linear::value) { d2_rs = map.applyIJC(d2_is); } else { // compute the first derivatives with 2nd order accuracy. Vec3d d1_is(D1::inX(stencil), D1::inY(stencil), D1::inZ(stencil) ); d2_rs = map.applyIJC(d2_is, d1_is, stencil.getCenterCoord().asVec3d()); } // the trace of the second derivative (range space) matrix is laplacian return ValueType(d2_rs(0,0) + d2_rs(1,1) + d2_rs(2,2)); } }; template struct Laplacian { // random access version template static typename Accessor::ValueType result(const TranslationMap&, const Accessor& grid, const Coord& ijk) { return ISLaplacian::result(grid, ijk); } // stencil access version template static typename StencilT::ValueType result(const TranslationMap&, const StencilT& stencil) { return ISLaplacian::result(stencil); } }; // The Laplacian is invariant to rotation or reflection. template struct Laplacian { // random access version template static typename Accessor::ValueType result(const UnitaryMap&, const Accessor& grid, const Coord& ijk) { return ISLaplacian::result(grid, ijk); } // stencil access version template static typename StencilT::ValueType result(const UnitaryMap&, const StencilT& stencil) { return ISLaplacian::result(stencil); } }; template struct Laplacian { // random access version template static typename Accessor::ValueType result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ISLaplacian::result(grid, ijk) * invdxdx; } // stencil access version template static typename StencilT::ValueType result(const UniformScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ISLaplacian::result(stencil) * invdxdx; } }; template struct Laplacian { // random access version template static typename Accessor::ValueType result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ISLaplacian::result(grid, ijk) * invdxdx; } // stencil access version template static typename StencilT::ValueType result(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ISLaplacian::result(stencil) * invdxdx; } }; template struct Laplacian { // random access version template static typename Accessor::ValueType result(const ScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; // compute the second derivatives in index space ValueType iddx = D2::inX(grid, ijk); ValueType iddy = D2::inY(grid, ijk); ValueType iddz = D2::inZ(grid, ijk); const Vec3d& invScaleSqr = map.getInvScaleSqr(); // scale them by the appropriate 1/dx^2, 1/dy^2, 1/dz^2 and sum return ValueType(iddx * invScaleSqr[0] + iddy * invScaleSqr[1] + iddz * invScaleSqr[2]); } // stencil access version template static typename StencilT::ValueType result(const ScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; // compute the second derivatives in index space ValueType iddx = D2::inX(stencil); ValueType iddy = D2::inY(stencil); ValueType iddz = D2::inZ(stencil); const Vec3d& invScaleSqr = map.getInvScaleSqr(); // scale them by the appropriate 1/dx^2, 1/dy^2, 1/dz^2 and sum return ValueType(iddx * invScaleSqr[0] + iddy * invScaleSqr[1] + iddz * invScaleSqr[2]); } }; template struct Laplacian { // random access version template static typename Accessor::ValueType result(const ScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; // compute the second derivatives in index space ValueType iddx = D2::inX(grid, ijk); ValueType iddy = D2::inY(grid, ijk); ValueType iddz = D2::inZ(grid, ijk); const Vec3d& invScaleSqr = map.getInvScaleSqr(); // scale them by the appropriate 1/dx^2, 1/dy^2, 1/dz^2 and sum return ValueType(iddx * invScaleSqr[0] + iddy * invScaleSqr[1] + iddz * invScaleSqr[2]); } // stencil access version template static typename StencilT::ValueType result(const ScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; // compute the second derivatives in index space ValueType iddx = D2::inX(stencil); ValueType iddy = D2::inY(stencil); ValueType iddz = D2::inZ(stencil); const Vec3d& invScaleSqr = map.getInvScaleSqr(); // scale them by the appropriate 1/dx^2, 1/dy^2, 1/dz^2 and sum return ValueType(iddx * invScaleSqr[0] + iddy * invScaleSqr[1] + iddz * invScaleSqr[2]); } }; /// @brief Compute the closest-point transform to a level set. /// @return the closest point to the surface from which the level set was derived, /// in the domain space of the map (e.g., voxel space). template struct CPT { // random access version template static math::Vec3 result(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typedef Vec3 Vec3Type; // current distance ValueType d = grid.getValue(ijk); // compute gradient in physical space where it is a unit normal // since the grid holds a distance level set. Vec3d vectorFromSurface(d*Gradient::result(map, grid, ijk)); if (is_linear::value) { Vec3d result = ijk.asVec3d() - map.applyInverseMap(vectorFromSurface); return Vec3Type(result); } else { Vec3d location = map.applyMap(ijk.asVec3d()); Vec3d result = map.applyInverseMap(location - vectorFromSurface); return Vec3Type(result); } } // stencil access version template static math::Vec3 result(const MapType& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; typedef Vec3 Vec3Type; // current distance ValueType d = stencil.template getValue<0, 0, 0>(); // compute gradient in physical space where it is a unit normal // since the grid holds a distance level set. Vec3d vectorFromSurface(d*Gradient::result(map, stencil)); if (is_linear::value) { Vec3d result = stencil.getCenterCoord().asVec3d() - map.applyInverseMap(vectorFromSurface); return Vec3Type(result); } else { Vec3d location = map.applyMap(stencil.getCenterCoord().asVec3d()); Vec3d result = map.applyInverseMap(location - vectorFromSurface); return Vec3Type(result); } } }; /// @brief Compute the closest-point transform to a level set. /// @return the closest point to the surface from which the level set was derived, /// in the range space of the map (e.g., in world space) template struct CPT_RANGE { // random access version template static Vec3 result(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; typedef Vec3 Vec3Type; // current distance ValueType d = grid.getValue(ijk); // compute gradient in physical space where it is a unit normal // since the grid holds a distance level set. Vec3Type vectorFromSurface = d*Gradient::result(map, grid, ijk); Vec3d result = map.applyMap(ijk.asVec3d()) - vectorFromSurface; return Vec3Type(result); } // stencil access version template static Vec3 result(const MapType& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; typedef Vec3 Vec3Type; // current distance ValueType d = stencil.template getValue<0, 0, 0>(); // compute gradient in physical space where it is a unit normal // since the grid holds a distance level set. Vec3Type vectorFromSurface = d*Gradient::result(map, stencil); Vec3d result = map.applyMap(stencil.getCenterCoord().asVec3d()) - vectorFromSurface; return Vec3Type(result); } }; /// @brief Compute the mean curvature. /// @return the mean curvature in two parts: @c alpha is the numerator in /// @f$\nabla \cdot (\nabla \phi / |\nabla \phi|)@f$, and @c beta is @f$|\nabla \phi|@f$. template struct MeanCurvature { // random access version template static void compute(const MapType& map, const Accessor& grid, const Coord& ijk, double& alpha, double& beta) { typedef typename Accessor::ValueType ValueType; typedef Vec3 Vec3Type; // compute the gradient in index space Vec3d d1_is(D1::inX(grid, ijk), D1::inY(grid, ijk), D1::inZ(grid, ijk) ); // all the second derivatives in index space ValueType iddx = D2::inX(grid, ijk); ValueType iddy = D2::inY(grid, ijk); ValueType iddz = D2::inZ(grid, ijk); ValueType iddxy = D2::inXandY(grid, ijk); ValueType iddyz = D2::inYandZ(grid, ijk); ValueType iddxz = D2::inXandZ(grid, ijk); // second derivatives in index space Mat3d d2_is(iddx, iddxy, iddxz, iddxy, iddy, iddyz, iddxz, iddyz, iddz); // convert to range space Mat3d d2_rs; Vec3d d1_rs; if (is_linear::value) { d2_rs = map.applyIJC(d2_is); d1_rs = map.applyIJT(d1_is); } else { d2_rs = map.applyIJC(d2_is, d1_is, ijk.asVec3d()); d1_rs = map.applyIJT(d1_is, ijk.asVec3d()); } // assemble the mean curvature double Dx2 = d1_rs(0)*d1_rs(0); double Dy2 = d1_rs(1)*d1_rs(1); double Dz2 = d1_rs(2)*d1_rs(2); // for return alpha = (Dx2*(d2_rs(1,1)+d2_rs(2,2))+Dy2*(d2_rs(0,0)+d2_rs(2,2)) +Dz2*(d2_rs(0,0)+d2_rs(1,1)) -2*(d1_rs(0)*(d1_rs(1)*d2_rs(0,1)+d1_rs(2)*d2_rs(0,2)) +d1_rs(1)*d1_rs(2)*d2_rs(1,2))); beta = std::sqrt(Dx2 + Dy2 + Dz2); // * 1/dx } template static typename Accessor::ValueType result(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; double alpha, beta; compute(map, grid, ijk, alpha, beta); return ValueType(alpha/(2. *math::Pow3(beta))); } template static typename Accessor::ValueType normGrad(const MapType& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; double alpha, beta; compute(map, grid, ijk, alpha, beta); return ValueType(alpha/(2. *math::Pow2(beta))); } // stencil access version template static void compute(const MapType& map, const StencilT& stencil, double& alpha, double& beta) { typedef typename StencilT::ValueType ValueType; typedef Vec3 Vec3Type; // compute the gradient in index space Vec3d d1_is(D1::inX(stencil), D1::inY(stencil), D1::inZ(stencil) ); // all the second derivatives in index space ValueType iddx = D2::inX(stencil); ValueType iddy = D2::inY(stencil); ValueType iddz = D2::inZ(stencil); ValueType iddxy = D2::inXandY(stencil); ValueType iddyz = D2::inYandZ(stencil); ValueType iddxz = D2::inXandZ(stencil); // second derivatives in index space Mat3d d2_is(iddx, iddxy, iddxz, iddxy, iddy, iddyz, iddxz, iddyz, iddz); // convert to range space Mat3d d2_rs; Vec3d d1_rs; if (is_linear::value) { d2_rs = map.applyIJC(d2_is); d1_rs = map.applyIJT(d1_is); } else { d2_rs = map.applyIJC(d2_is, d1_is, stencil.getCenterCoord().asVec3d()); d1_rs = map.applyIJT(d1_is, stencil.getCenterCoord().asVec3d()); } // assemble the mean curvature double Dx2 = d1_rs(0)*d1_rs(0); double Dy2 = d1_rs(1)*d1_rs(1); double Dz2 = d1_rs(2)*d1_rs(2); // for return alpha = (Dx2*(d2_rs(1,1)+d2_rs(2,2))+Dy2*(d2_rs(0,0)+d2_rs(2,2)) +Dz2*(d2_rs(0,0)+d2_rs(1,1)) -2*(d1_rs(0)*(d1_rs(1)*d2_rs(0,1)+d1_rs(2)*d2_rs(0,2)) +d1_rs(1)*d1_rs(2)*d2_rs(1,2))); beta = std::sqrt(Dx2 + Dy2 + Dz2); // * 1/dx } template static typename StencilT::ValueType result(const MapType& map, const StencilT stencil) { typedef typename StencilT::ValueType ValueType; double alpha, beta; compute(map, stencil, alpha, beta); return ValueType(alpha/(2*math::Pow3(beta))); } template static typename StencilT::ValueType normGrad(const MapType& map, const StencilT stencil) { typedef typename StencilT::ValueType ValueType; double alpha, beta; compute(map, stencil, alpha, beta); return ValueType(alpha/(2*math::Pow2(beta))); } }; template struct MeanCurvature { // random access version template static typename Accessor::ValueType result(const TranslationMap&, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(grid, ijk, alpha, beta); return ValueType(alpha /(2*math::Pow3(beta))); } template static typename Accessor::ValueType normGrad(const TranslationMap&, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(grid, ijk, alpha, beta); return ValueType(alpha/(2*math::Pow2(beta))); } // stencil access version template static typename StencilT::ValueType result(const TranslationMap&, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(stencil, alpha, beta); return ValueType(alpha /(2*math::Pow3(beta))); } template static typename StencilT::ValueType normGrad(const TranslationMap&, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(stencil, alpha, beta); return ValueType(alpha/(2*math::Pow2(beta))); } }; template struct MeanCurvature { // random access version template static typename Accessor::ValueType result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(grid, ijk, alpha, beta); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return ValueType(alpha*inv2dx/math::Pow3(beta)); } template static typename Accessor::ValueType normGrad(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(grid, ijk, alpha, beta); ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ValueType(alpha*invdxdx/(2*math::Pow2(beta))); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(stencil, alpha, beta); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return ValueType(alpha*inv2dx/math::Pow3(beta)); } template static typename StencilT::ValueType normGrad(const UniformScaleMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(stencil, alpha, beta); ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ValueType(alpha*invdxdx/(2*math::Pow2(beta))); } }; template struct MeanCurvature { // random access version template static typename Accessor::ValueType result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(grid, ijk, alpha, beta); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return ValueType(alpha*inv2dx/math::Pow3(beta)); } template static typename Accessor::ValueType normGrad(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { typedef typename Accessor::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(grid, ijk, alpha, beta); ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ValueType(alpha*invdxdx/(2*math::Pow2(beta))); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(stencil, alpha, beta); ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return ValueType(alpha*inv2dx/math::Pow3(beta)); } template static typename StencilT::ValueType normGrad(const UniformScaleTranslateMap& map, const StencilT& stencil) { typedef typename StencilT::ValueType ValueType; ValueType alpha, beta; ISMeanCurvature::result(stencil, alpha, beta); ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ValueType(alpha*invdxdx/(2*math::Pow2(beta))); } }; /// @brief A wrapper that holds a MapBase::ConstPtr and exposes a reduced set /// of functionality needed by the mathematical operators /// @details This may be used in some Map-templated code, when the overhead of /// actually resolving the @c Map type is large compared to the map work to be done. class GenericMap { public: template GenericMap(const GridType& g): mMap(g.transform().baseMap()) {} GenericMap(const Transform& t): mMap(t.baseMap()) {} GenericMap(MapBase::Ptr map): mMap(boost::const_pointer_cast(map)) {} GenericMap(MapBase::ConstPtr map): mMap(map) {} ~GenericMap() {} Vec3d applyMap(const Vec3d& in) const { return mMap->applyMap(in); } Vec3d applyInverseMap(const Vec3d& in) const { return mMap->applyInverseMap(in); } Vec3d applyIJT(const Vec3d& in) const { return mMap->applyIJT(in); } Vec3d applyIJT(const Vec3d& in, const Vec3d& pos) const { return mMap->applyIJT(in, pos); } Mat3d applyIJC(const Mat3d& m) const { return mMap->applyIJC(m); } Mat3d applyIJC(const Mat3d& m, const Vec3d& v, const Vec3d& pos) const { return mMap->applyIJC(m,v,pos); } double determinant() const { return mMap->determinant(); } double determinant(const Vec3d& in) const { return mMap->determinant(in); } Vec3d voxelSize() const { return mMap->voxelSize(); } Vec3d voxelSize(const Vec3d&v) const { return mMap->voxelSize(v); } private: MapBase::ConstPtr mMap; }; } // end math namespace } // namespace OPENVDB_VERSION_NAME } // end openvdb namespace #endif // OPENVDB_MATH_OPERATORS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Vec2.h0000644000000000000000000003647412252453157013136 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_VEC2_HAS_BEEN_INCLUDED #define OPENVDB_MATH_VEC2_HAS_BEEN_INCLUDED #include #include #include "Math.h" #include "Tuple.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Mat2; template class Vec2: public Tuple<2, T> { public: typedef T value_type; typedef T ValueType; /// Trivial constructor, the vector is NOT initialized Vec2() {} /// Constructor with one argument, e.g. Vec2f v(0); explicit Vec2(T val) { this->mm[0] = this->mm[1] = val; } /// Constructor with three arguments, e.g. Vec2f v(1,2,3); Vec2(T x, T y) { this->mm[0] = x; this->mm[1] = y; } /// Constructor with array argument, e.g. float a[2]; Vec2f v(a); template Vec2(Source *a) { this->mm[0] = a[0]; this->mm[1] = a[1]; } // trivial /// Conversion constructor template explicit Vec2(const Tuple<2, Source> &t) { this->mm[0] = static_cast(t[0]); this->mm[1] = static_cast(t[1]); } /// Reference to the component, e.g. v.x() = 4.5f; T& x() {return this->mm[0];} T& y() {return this->mm[1];} /// Get the component, e.g. float f = v.y(); T x() const {return this->mm[0];} T y() const {return this->mm[1];} /// Alternative indexed reference to the elements T& operator()(int i) {return this->mm[i];} /// Alternative indexed constant reference to the elements, T operator()(int i) const {return this->mm[i];} T* asPointer() {return this->mm;} const T* asPointer() const {return this->mm;} /// "this" vector gets initialized to [x, y, z], /// calling v.init(); has same effect as calling v = Vec2::zero(); const Vec2& init(T x=0, T y=0) { this->mm[0] = x; this->mm[1] = y; return *this; } /// Set "this" vector to zero const Vec2& setZero() { this->mm[0] = 0; this->mm[1] = 0; return *this; } /// Assignment operator template const Vec2& operator=(const Vec2 &v) { // note: don't static_cast because that suppresses warnings this->mm[0] = v[0]; this->mm[1] = v[1]; return *this; } /// Equality operator, does exact floating point comparisons bool operator==(const Vec2 &v) const { return (isExactlyEqual(this->mm[0], v.mm[0]) && isExactlyEqual(this->mm[1], v.mm[1])); } /// Inequality operator, does exact floating point comparisons bool operator!=(const Vec2 &v) const { return !(*this==v); } /// Test if "this" vector is equivalent to vector v with tolerance of eps bool eq(const Vec2 &v, T eps = static_cast(1.0e-7)) const { return isApproxEqual(this->mm[0], v.mm[0], eps) && isApproxEqual(this->mm[1], v.mm[1], eps); } // trivial /// Negation operator, for e.g. v1 = -v2; Vec2 operator-() const {return Vec2(-this->mm[0], -this->mm[1]);} /// this = v1 + v2 /// "this", v1 and v2 need not be distinct objects, e.g. v.add(v1,v); template const Vec2& add(const Vec2 &v1, const Vec2 &v2) { this->mm[0] = v1[0] + v2[0]; this->mm[1] = v1[1] + v2[1]; return *this; } /// this = v1 - v2 /// "this", v1 and v2 need not be distinct objects, e.g. v.sub(v1,v); template const Vec2& sub(const Vec2 &v1, const Vec2 &v2) { this->mm[0] = v1[0] - v2[0]; this->mm[1] = v1[1] - v2[1]; return *this; } /// this = scalar*v, v need not be a distinct object from "this", /// e.g. v.scale(1.5,v1); template const Vec2& scale(T0 scalar, const Vec2 &v) { this->mm[0] = scalar * v[0]; this->mm[1] = scalar * v[1]; return *this; } template const Vec2 &div(T0 scalar, const Vec2 &v) { this->mm[0] = v[0] / scalar; this->mm[1] = v[1] / scalar; return *this; } /// Dot product T dot(const Vec2 &v) const { return this->mm[0]*v[0] + this->mm[1]*v[1]; } // trivial /// Length of the vector T length() const { return static_cast(sqrt(double(this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1]))); } /// Squared length of the vector, much faster than length() as it /// does not involve square root T lengthSqr() const { return (this->mm[0]*this->mm[0] + this->mm[1]*this->mm[1]); } /// this = normalized this bool normalize(T eps=1.0e-8) { T d = length(); if (isApproxEqual(d, T(0), eps)) { return false; } *this *= (T(1) / d); return true; } /// return normalized this, throws if null vector Vec2 unit(T eps=0) const { T d; return unit(eps, d); } /// return normalized this and length, throws if null vector Vec2 unit(T eps, T& len) const { len = length(); if (isApproxEqual(len, T(0), eps)) { OPENVDB_THROW(ArithmeticError, "Normalizing null 2-vector"); } return *this / len; } /// Returns v, where \f$v_i *= scalar\f$ for \f$i \in [0, 1]\f$ template const Vec2 &operator*=(S scalar) { this->mm[0] *= scalar; this->mm[1] *= scalar; return *this; } /// Returns v0, where \f$v0_i *= v1_i\f$ for \f$i \in [0, 1]\f$ template const Vec2 &operator*=(const Vec2 &v1) { this->mm[0] *= v1[0]; this->mm[1] *= v1[1]; return *this; } /// Returns v, where \f$v_i /= scalar\f$ for \f$i \in [0, 1]\f$ template const Vec2 &operator/=(S scalar) { this->mm[0] /= scalar; this->mm[1] /= scalar; return *this; } /// Returns v0, where \f$v0_i /= v1_i\f$ for \f$i \in [0, 1]\f$ template const Vec2 &operator/=(const Vec2 &v1) { this->mm[0] /= v1[0]; this->mm[1] /= v1[1]; return *this; } /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 1]\f$ template const Vec2 &operator+=(S scalar) { this->mm[0] += scalar; this->mm[1] += scalar; return *this; } /// Returns v0, where \f$v0_i += v1_i\f$ for \f$i \in [0, 1]\f$ template const Vec2 &operator+=(const Vec2 &v1) { this->mm[0] += v1[0]; this->mm[1] += v1[1]; return *this; } /// Returns v, where \f$v_i += scalar\f$ for \f$i \in [0, 1]\f$ template const Vec2 &operator-=(S scalar) { this->mm[0] -= scalar; this->mm[1] -= scalar; return *this; } /// Returns v0, where \f$v0_i -= v1_i\f$ for \f$i \in [0, 1]\f$ template const Vec2 &operator-=(const Vec2 &v1) { this->mm[0] -= v1[0]; this->mm[1] -= v1[1]; return *this; } // Number of cols, rows, elements static unsigned numRows() { return 1; } static unsigned numColumns() { return 2; } static unsigned numElements() { return 2; } /// Returns the scalar component of v in the direction of onto, onto need /// not be unit. e.g float c = Vec2f::component(v1,v2); T component(const Vec2 &onto, T eps=1.0e-8) const { T l = onto.length(); if (isApproxEqual(l, T(0), eps)) return 0; return dot(onto)*(T(1)/l); } /// Return the projection of v onto the vector, onto need not be unit /// e.g. Vec2f v = Vec2f::projection(v,n); Vec2 projection(const Vec2 &onto, T eps=1.0e-8) const { T l = onto.lengthSqr(); if (isApproxEqual(l, T(0), eps)) return Vec2::zero(); return onto*(dot(onto)*(T(1)/l)); } /// Return an arbitrary unit vector perpendicular to v /// Vector v must be a unit vector /// e.g. v.normalize(); Vec2f n = Vec2f::getArbPerpendicular(v); Vec2 getArbPerpendicular() const { return Vec2(-this->mm[1], this->mm[0]); } /// True if a Nan is present in vector bool isNan() const { return isnan(this->mm[0]) || isnan(this->mm[1]); } /// True if an Inf is present in vector bool isInfinite() const { return isinf(this->mm[0]) || isinf(this->mm[1]); } /// True if all no Nan or Inf values present bool isFinite() const { return finite(this->mm[0]) && finite(this->mm[1]); } /// Predefined constants, e.g. Vec2f v = Vec2f::xNegAxis(); static Vec2 zero() { return Vec2(0, 0); } }; /// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator*(S scalar, const Vec2 &v) { return v * scalar; } /// Returns V, where \f$V_i = v_i * scalar\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator*(const Vec2 &v, S scalar) { Vec2::type> result(v); result *= scalar; return result; } /// Returns V, where \f$V_i = v0_i * v1_i\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator*(const Vec2 &v0, const Vec2 &v1) { Vec2::type> result(v0[0] * v1[0], v0[1] * v1[1]); return result; } /// Returns V, where \f$V_i = scalar / v_i\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator/(S scalar, const Vec2 &v) { return Vec2::type>(scalar/v[0], scalar/v[1]); } /// Returns V, where \f$V_i = v_i / scalar\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator/(const Vec2 &v, S scalar) { Vec2::type> result(v); result /= scalar; return result; } /// Returns V, where \f$V_i = v0_i / v1_i\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator/(const Vec2 &v0, const Vec2 &v1) { Vec2::type> result(v0[0] / v1[0], v0[1] / v1[1]); return result; } /// Returns V, where \f$V_i = v0_i + v1_i\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator+(const Vec2 &v0, const Vec2 &v1) { Vec2::type> result(v0); result += v1; return result; } /// Returns V, where \f$V_i = v_i + scalar\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator+(const Vec2 &v, S scalar) { Vec2::type> result(v); result += scalar; return result; } /// Returns V, where \f$V_i = v0_i - v1_i\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator-(const Vec2 &v0, const Vec2 &v1) { Vec2::type> result(v0); result -= v1; return result; } /// Returns V, where \f$V_i = v_i - scalar\f$ for \f$i \in [0, 1]\f$ template inline Vec2::type> operator-(const Vec2 &v, S scalar) { Vec2::type> result(v); result -= scalar; return result; } /// Angle between two vectors, the result is between [0, pi], /// e.g. float a = Vec2f::angle(v1,v2); template inline T angle(const Vec2 &v1, const Vec2 &v2) { T c = v1.dot(v2); return acos(c); } template inline bool isApproxEqual(const Vec2& a, const Vec2& b) { return a.eq(b); } template inline bool isApproxEqual(const Vec2& a, const Vec2& b, const Vec2& eps) { return isApproxEqual(a.x(), b.x(), eps.x()) && isApproxEqual(a.y(), b.y(), eps.y()); } /// Orthonormalize vectors v1 and v2 and store back the resulting basis /// e.g. Vec2f::orthonormalize(v1,v2); template inline void orthonormalize(Vec2 &v1, Vec2 &v2) { // If the input vectors are v0, v1, and v2, then the Gram-Schmidt // orthonormalization produces vectors u0, u1, and u2 as follows, // // u0 = v0/|v0| // u1 = (v1-(u0*v1)u0)/|v1-(u0*v1)u0| // // where |A| indicates length of vector A and A*B indicates dot // product of vectors A and B. // compute u0 v1.normalize(); // compute u1 T d0 = v1.dot(v2); v2 -= v1*d0; v2.normalize(); } /// \remark We are switching to a more explicit name because the semantics /// are different from std::min/max. In that case, the function returns a /// reference to one of the objects based on a comparator. Here, we must /// fabricate a new object which might not match either of the inputs. /// Return component-wise minimum of the two vectors. template inline Vec2 minComponent(const Vec2 &v1, const Vec2 &v2) { return Vec2( std::min(v1.x(), v2.x()), std::min(v1.y(), v2.y())); } /// Return component-wise maximum of the two vectors. template inline Vec2 maxComponent(const Vec2 &v1, const Vec2 &v2) { return Vec2( std::max(v1.x(), v2.x()), std::max(v1.y(), v2.y())); } typedef Vec2 Vec2i; typedef Vec2 Vec2ui; typedef Vec2 Vec2s; typedef Vec2 Vec2d; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_VEC2_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/QuantizedUnitVec.h0000644000000000000000000001252612252453157015571 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_QUANTIZED_UNIT_VEC_HAS_BEEN_INCLUDED #define OPENVDB_MATH_QUANTIZED_UNIT_VEC_HAS_BEEN_INCLUDED #include #include #include "Vec3.h" #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { // Bit compression method that effciently represents a unit vector using // 2 bytes i.e. 16 bits of data by only storing two quantized components. // Based on "Higher Accuracy Quantized Normals" article from GameDev.Net LLC, 2000 class OPENVDB_API QuantizedUnitVec { public: template static uint16_t pack(const Vec3& vec); static Vec3s unpack(const uint16_t data); static void flipSignBits(uint16_t&); private: QuantizedUnitVec() {} // threadsafe initialization function for the normalization weights. static void init(); // bit masks static const uint16_t MASK_SLOTS = 0x1FFF; // 0001111111111111 static const uint16_t MASK_XSLOT = 0x1F80; // 0001111110000000 static const uint16_t MASK_YSLOT = 0x007F; // 0000000001111111 static const uint16_t MASK_XSIGN = 0x8000; // 1000000000000000 static const uint16_t MASK_YSIGN = 0x4000; // 0100000000000000 static const uint16_t MASK_ZSIGN = 0x2000; // 0010000000000000 // initialization flag. static bool sInitialized; // normalization weights, 32 kilobytes. static float sNormalizationWeights[MASK_SLOTS + 1]; }; // class QuantizedUnitVec //////////////////////////////////////// template inline uint16_t QuantizedUnitVec::pack(const Vec3& vec) { uint16_t data = 0; T x(vec[0]), y(vec[1]), z(vec[2]); // The sign of the three components are first stored using // 3-bits and can then safely be discarded. if (x < T(0.0)) { data |= MASK_XSIGN; x = -x; } if (y < T(0.0)) { data |= MASK_YSIGN; y = -y; } if (z < T(0.0)) { data |= MASK_ZSIGN; z = -z; } // The z component is discarded and x & y are quantized in // the 0 to 126 range. T w = T(126.0) / (x + y + z); uint16_t xbits = uint16_t((x * w) + T(0.5)); uint16_t ybits = uint16_t((y * w) + T(0.5)); // The remaining 13 bits in our 16 bit word are dividied into a // 6-bit x-slot and a 7-bit y-slot. Both the xbits and the ybits // can still be represented using (2^7 - 1) quantization levels. // If the xbits requre more than 6-bits, store the complement. // (xbits + ybits < 127, thus if xbits > 63 => ybits <= 63) if(xbits > 63) { xbits = 127 - xbits; ybits = 127 - ybits; } // pack components into their respetive slot data |= xbits << 7; data |= ybits; return data; } inline Vec3s QuantizedUnitVec::unpack(const uint16_t data) { if (!sInitialized) init(); const float w = sNormalizationWeights[data & MASK_SLOTS]; uint16_t xbits = (data & MASK_XSLOT) >> 7; uint16_t ybits = data & MASK_YSLOT; // Check if the complement components where stored and revert. if ((xbits + ybits) > 126) { xbits = 127 - xbits; ybits = 127 - ybits; } Vec3s vec(float(xbits) * w, float(ybits) * w, float(126 - xbits - ybits) * w); if(data & MASK_XSIGN) vec[0] = -vec[0]; if(data & MASK_YSIGN) vec[1] = -vec[1]; if(data & MASK_ZSIGN) vec[2] = -vec[2]; return vec; } //////////////////////////////////////// inline void QuantizedUnitVec::flipSignBits(uint16_t& v) { v = (v & MASK_SLOTS) | (~v & ~MASK_SLOTS); } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_QUANTIZED_UNIT_VEC_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Mat4.h0000644000000000000000000012506612252453157013140 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_MAT4_H_HAS_BEEN_INCLUDED #define OPENVDB_MATH_MAT4_H_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include "Math.h" #include "Mat3.h" #include "Vec3.h" #include "Vec4.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Vec4; /// @class Mat4 Mat4.h /// @brief 4x4 -matrix class. template class Mat4: public Mat<4, T> { public: /// Data type held by the matrix. typedef T value_type; typedef T ValueType; typedef Mat<4, T> MyBase; /// Trivial constructor, the matrix is NOT initialized Mat4() {} /// Constructor given array of elements, the ordering is in row major form: /** @verbatim a[ 0] a[1] a[ 2] a[ 3] a[ 4] a[5] a[ 6] a[ 7] a[ 8] a[9] a[10] a[11] a[12] a[13] a[14] a[15] @endverbatim */ template Mat4(Source *a) { register int i; for (i = 0; i < 16; i++) { MyBase::mm[i] = a[i]; } } /// Constructor given array of elements, the ordering is in row major form: /** @verbatim a b c d e f g h i j k l m n o p @endverbatim */ template Mat4(Source a, Source b, Source c, Source d, Source e, Source f, Source g, Source h, Source i, Source j, Source k, Source l, Source m, Source n, Source o, Source p) { MyBase::mm[ 0] = a; MyBase::mm[ 1] = b; MyBase::mm[ 2] = c; MyBase::mm[ 3] = d; MyBase::mm[ 4] = e; MyBase::mm[ 5] = f; MyBase::mm[ 6] = g; MyBase::mm[ 7] = h; MyBase::mm[ 8] = i; MyBase::mm[ 9] = j; MyBase::mm[10] = k; MyBase::mm[11] = l; MyBase::mm[12] = m; MyBase::mm[13] = n; MyBase::mm[14] = o; MyBase::mm[15] = p; } /// Construct matrix given basis vectors (columns) template Mat4(const Vec4 &v1, const Vec4 &v2, const Vec4 &v3, const Vec4 &v4) { setBasis(v1, v2, v3, v4); } /// Copy constructor Mat4(const Mat<4, T> &m) { for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { MyBase::mm[i*4 + j] = m[i][j]; } } } /// Conversion constructor template explicit Mat4(const Mat4 &m) { const Source *src = m.asPointer(); for (int i=0; i<16; ++i) { MyBase::mm[i] = static_cast(src[i]); } } /// Predefined constant for identity matrix static const Mat4& identity() { return sIdentity; } /// Predefined constant for zero matrix static const Mat4& zero() { return sZero; } /// Set ith row to vector v void setRow(int i, const Vec4 &v) { // assert(i>=0 && i<4); int i4 = i * 4; MyBase::mm[i4+0] = v[0]; MyBase::mm[i4+1] = v[1]; MyBase::mm[i4+2] = v[2]; MyBase::mm[i4+3] = v[3]; } /// Get ith row, e.g. Vec4f v = m.row(1); Vec4 row(int i) const { // assert(i>=0 && i<3); return Vec4((*this)(i,0), (*this)(i,1), (*this)(i,2), (*this)(i,3)); } /// Set jth column to vector v void setCol(int j, const Vec4& v) { // assert(j>=0 && j<4); MyBase::mm[ 0+j] = v[0]; MyBase::mm[ 4+j] = v[1]; MyBase::mm[ 8+j] = v[2]; MyBase::mm[12+j] = v[3]; } /// Get jth column, e.g. Vec4f v = m.col(0); Vec4 col(int j) const { // assert(j>=0 && j<4); return Vec4((*this)(0,j), (*this)(1,j), (*this)(2,j), (*this)(3,j)); } //@{ /// Array style reference to ith row /// e.g. m[1][3] = 4; T* operator[](int i) { return &(MyBase::mm[i<<2]); } const T* operator[](int i) const { return &(MyBase::mm[i<<2]); } //@} /// Direct access to the internal data T* asPointer() {return MyBase::mm;} const T* asPointer() const {return MyBase::mm;} /// Alternative indexed reference to the elements /// Note that the indices are row first and column second. /// e.g. m(0,0) = 1; T& operator()(int i, int j) { // assert(i>=0 && i<4); // assert(j>=0 && j<4); return MyBase::mm[4*i+j]; } /// Alternative indexed constant reference to the elements, /// Note that the indices are row first and column second. /// e.g. float f = m(1,0); T operator()(int i, int j) const { // assert(i>=0 && i<4); // assert(j>=0 && j<4); return MyBase::mm[4*i+j]; } /// Set the columns of "this" matrix to the vectors v1, v2, v3, v4 void setBasis(const Vec4 &v1, const Vec4 &v2, const Vec4 &v3, const Vec4 &v4) { MyBase::mm[ 0] = v1[0]; MyBase::mm[ 1] = v1[1]; MyBase::mm[ 2] = v1[2]; MyBase::mm[ 3] = v1[3]; MyBase::mm[ 4] = v2[0]; MyBase::mm[ 5] = v2[1]; MyBase::mm[ 6] = v2[2]; MyBase::mm[ 7] = v2[3]; MyBase::mm[ 8] = v3[0]; MyBase::mm[ 9] = v3[1]; MyBase::mm[10] = v3[2]; MyBase::mm[11] = v3[3]; MyBase::mm[12] = v4[0]; MyBase::mm[13] = v4[1]; MyBase::mm[14] = v4[2]; MyBase::mm[15] = v4[3]; } // Set "this" matrix to zero void setZero() { MyBase::mm[ 0] = 0; MyBase::mm[ 1] = 0; MyBase::mm[ 2] = 0; MyBase::mm[ 3] = 0; MyBase::mm[ 4] = 0; MyBase::mm[ 5] = 0; MyBase::mm[ 6] = 0; MyBase::mm[ 7] = 0; MyBase::mm[ 8] = 0; MyBase::mm[ 9] = 0; MyBase::mm[10] = 0; MyBase::mm[11] = 0; MyBase::mm[12] = 0; MyBase::mm[13] = 0; MyBase::mm[14] = 0; MyBase::mm[15] = 0; } /// Set "this" matrix to identity void setIdentity() { MyBase::mm[ 0] = 1; MyBase::mm[ 1] = 0; MyBase::mm[ 2] = 0; MyBase::mm[ 3] = 0; MyBase::mm[ 4] = 0; MyBase::mm[ 5] = 1; MyBase::mm[ 6] = 0; MyBase::mm[ 7] = 0; MyBase::mm[ 8] = 0; MyBase::mm[ 9] = 0; MyBase::mm[10] = 1; MyBase::mm[11] = 0; MyBase::mm[12] = 0; MyBase::mm[13] = 0; MyBase::mm[14] = 0; MyBase::mm[15] = 1; } /// Set upper left to a Mat3 void setMat3(const Mat3 &m) { for (int i = 0; i < 3; i++) for (int j=0; j < 3; j++) MyBase::mm[i*4+j] = m[i][j]; } Mat3 getMat3() const { Mat3 m; for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) m[i][j] = MyBase::mm[i*4+j]; return m; } /// Return the translation component Vec3 getTranslation() const { return Vec3(MyBase::mm[12], MyBase::mm[13], MyBase::mm[14]); } void setTranslation(const Vec3 &t) { MyBase::mm[12] = t[0]; MyBase::mm[13] = t[1]; MyBase::mm[14] = t[2]; } /// Assignment operator template const Mat4& operator=(const Mat4 &m) { const Source *src = m.asPointer(); // don't suppress warnings when assigning from different numerical types std::copy(src, (src + this->numElements()), MyBase::mm); return *this; } /// Test if "this" is equivalent to m with tolerance of eps value bool eq(const Mat4 &m, T eps=1.0e-8) const { for (int i = 0; i < 16; i++) { if (!isApproxEqual(MyBase::mm[i], m.mm[i], eps)) return false; } return true; } /// Negation operator, for e.g. m1 = -m2; Mat4 operator-() const { return Mat4( -MyBase::mm[ 0], -MyBase::mm[ 1], -MyBase::mm[ 2], -MyBase::mm[ 3], -MyBase::mm[ 4], -MyBase::mm[ 5], -MyBase::mm[ 6], -MyBase::mm[ 7], -MyBase::mm[ 8], -MyBase::mm[ 9], -MyBase::mm[10], -MyBase::mm[11], -MyBase::mm[12], -MyBase::mm[13], -MyBase::mm[14], -MyBase::mm[15] ); } // trivial /// Return m, where \f$m_{i,j} *= scalar\f$ for \f$i, j \in [0, 3]\f$ template const Mat4& operator*=(S scalar) { MyBase::mm[ 0] *= scalar; MyBase::mm[ 1] *= scalar; MyBase::mm[ 2] *= scalar; MyBase::mm[ 3] *= scalar; MyBase::mm[ 4] *= scalar; MyBase::mm[ 5] *= scalar; MyBase::mm[ 6] *= scalar; MyBase::mm[ 7] *= scalar; MyBase::mm[ 8] *= scalar; MyBase::mm[ 9] *= scalar; MyBase::mm[10] *= scalar; MyBase::mm[11] *= scalar; MyBase::mm[12] *= scalar; MyBase::mm[13] *= scalar; MyBase::mm[14] *= scalar; MyBase::mm[15] *= scalar; return *this; } /// @brief Returns m0, where \f$m0_{i,j} += m1_{i,j}\f$ for \f$i, j \in [0, 3]\f$ template const Mat4 &operator+=(const Mat4 &m1) { const S* s = m1.asPointer(); MyBase::mm[ 0] += s[ 0]; MyBase::mm[ 1] += s[ 1]; MyBase::mm[ 2] += s[ 2]; MyBase::mm[ 3] += s[ 3]; MyBase::mm[ 4] += s[ 4]; MyBase::mm[ 5] += s[ 5]; MyBase::mm[ 6] += s[ 6]; MyBase::mm[ 7] += s[ 7]; MyBase::mm[ 8] += s[ 8]; MyBase::mm[ 9] += s[ 9]; MyBase::mm[10] += s[10]; MyBase::mm[11] += s[11]; MyBase::mm[12] += s[12]; MyBase::mm[13] += s[13]; MyBase::mm[14] += s[14]; MyBase::mm[15] += s[15]; return *this; } /// @brief Returns m0, where \f$m0_{i,j} -= m1_{i,j}\f$ for \f$i, j \in [0, 3]\f$ template const Mat4 &operator-=(const Mat4 &m1) { const S* s = m1.asPointer(); MyBase::mm[ 0] -= s[ 0]; MyBase::mm[ 1] -= s[ 1]; MyBase::mm[ 2] -= s[ 2]; MyBase::mm[ 3] -= s[ 3]; MyBase::mm[ 4] -= s[ 4]; MyBase::mm[ 5] -= s[ 5]; MyBase::mm[ 6] -= s[ 6]; MyBase::mm[ 7] -= s[ 7]; MyBase::mm[ 8] -= s[ 8]; MyBase::mm[ 9] -= s[ 9]; MyBase::mm[10] -= s[10]; MyBase::mm[11] -= s[11]; MyBase::mm[12] -= s[12]; MyBase::mm[13] -= s[13]; MyBase::mm[14] -= s[14]; MyBase::mm[15] -= s[15]; return *this; } /// Return m, where \f$m_{i,j} = \sum_{k} m0_{i,k}*m1_{k,j}\f$ for \f$i, j \in [0, 3]\f$ template const Mat4 &operator*=(const Mat4 &m1) { Mat4 m0(*this); const T* s0 = m0.asPointer(); const S* s1 = m1.asPointer(); for (int i = 0; i < 4; i++) { int i4 = 4 * i; MyBase::mm[i4+0] = static_cast(s0[i4+0] * s1[ 0] + s0[i4+1] * s1[ 4] + s0[i4+2] * s1[ 8] + s0[i4+3] * s1[12]); MyBase::mm[i4+1] = static_cast(s0[i4+0] * s1[ 1] + s0[i4+1] * s1[ 5] + s0[i4+2] * s1[ 9] + s0[i4+3] * s1[13]); MyBase::mm[i4+2] = static_cast(s0[i4+0] * s1[ 2] + s0[i4+1] * s1[ 6] + s0[i4+2] * s1[10] + s0[i4+3] * s1[14]); MyBase::mm[i4+3] = static_cast(s0[i4+0] * s1[ 3] + s0[i4+1] * s1[ 7] + s0[i4+2] * s1[11] + s0[i4+3] * s1[15]); } return *this; } /// @return transpose of this Mat4 transpose() const { return Mat4( MyBase::mm[ 0], MyBase::mm[ 4], MyBase::mm[ 8], MyBase::mm[12], MyBase::mm[ 1], MyBase::mm[ 5], MyBase::mm[ 9], MyBase::mm[13], MyBase::mm[ 2], MyBase::mm[ 6], MyBase::mm[10], MyBase::mm[14], MyBase::mm[ 3], MyBase::mm[ 7], MyBase::mm[11], MyBase::mm[15] ); } /// @return inverse of this /// @throw ArithmeticError if singular Mat4 inverse(T tolerance = 0) const { // // inv [ A | b ] = [ E | f ] A: 3x3, b: 3x1, c': 1x3 d: 1x1 // [ c' | d ] [ g' | h ] // // If A is invertible use // // E = A^-1 + p*h*r // p = A^-1 * b // f = -p * h // g' = -h * c' // h = 1 / (d - c'*p) // r' = c'*A^-1 // // Otherwise use gauss-jordan elimination // // // We create this alias to ourself so we can easily use own subscript // operator. const Mat4& m(*this); T m0011 = m[0][0] * m[1][1]; T m0012 = m[0][0] * m[1][2]; T m0110 = m[0][1] * m[1][0]; T m0210 = m[0][2] * m[1][0]; T m0120 = m[0][1] * m[2][0]; T m0220 = m[0][2] * m[2][0]; T detA = m0011 * m[2][2] - m0012 * m[2][1] - m0110 * m[2][2] + m0210 * m[2][1] + m0120 * m[1][2] - m0220 * m[1][1]; bool hasPerspective = (!isExactlyEqual(m[0][3], T(0.0)) || !isExactlyEqual(m[1][3], T(0.0)) || !isExactlyEqual(m[2][3], T(0.0)) || !isExactlyEqual(m[3][3], T(1.0))); T det; if (hasPerspective) { det = m[0][3] * det3(m, 1,2,3, 0,2,1) + m[1][3] * det3(m, 2,0,3, 0,2,1) + m[2][3] * det3(m, 3,0,1, 0,2,1) + m[3][3] * detA; } else { det = detA * m[3][3]; } Mat4 inv; bool invertible; if (isApproxEqual(det,T(0.0),tolerance)) { invertible = false; } else if (isApproxEqual(detA,T(0.0),T(1e-8))) { // det is too small to rely on inversion by subblocks invertible = m.invert(inv, tolerance); } else { invertible = true; detA = 1.0 / detA; // // Calculate A^-1 // inv[0][0] = detA * ( m[1][1] * m[2][2] - m[1][2] * m[2][1]); inv[0][1] = detA * (-m[0][1] * m[2][2] + m[0][2] * m[2][1]); inv[0][2] = detA * ( m[0][1] * m[1][2] - m[0][2] * m[1][1]); inv[1][0] = detA * (-m[1][0] * m[2][2] + m[1][2] * m[2][0]); inv[1][1] = detA * ( m[0][0] * m[2][2] - m0220); inv[1][2] = detA * ( m0210 - m0012); inv[2][0] = detA * ( m[1][0] * m[2][1] - m[1][1] * m[2][0]); inv[2][1] = detA * ( m0120 - m[0][0] * m[2][1]); inv[2][2] = detA * ( m0011 - m0110); if (hasPerspective) { // // Calculate r, p, and h // Vec3 r; r[0] = m[3][0] * inv[0][0] + m[3][1] * inv[1][0] + m[3][2] * inv[2][0]; r[1] = m[3][0] * inv[0][1] + m[3][1] * inv[1][1] + m[3][2] * inv[2][1]; r[2] = m[3][0] * inv[0][2] + m[3][1] * inv[1][2] + m[3][2] * inv[2][2]; Vec3 p; p[0] = inv[0][0] * m[0][3] + inv[0][1] * m[1][3] + inv[0][2] * m[2][3]; p[1] = inv[1][0] * m[0][3] + inv[1][1] * m[1][3] + inv[1][2] * m[2][3]; p[2] = inv[2][0] * m[0][3] + inv[2][1] * m[1][3] + inv[2][2] * m[2][3]; T h = m[3][3] - p.dot(Vec3(m[3][0],m[3][1],m[3][2])); if (isApproxEqual(h,T(0.0),tolerance)) { invertible = false; } else { h = 1.0 / h; // // Calculate h, g, and f // inv[3][3] = h; inv[3][0] = -h * r[0]; inv[3][1] = -h * r[1]; inv[3][2] = -h * r[2]; inv[0][3] = -h * p[0]; inv[1][3] = -h * p[1]; inv[2][3] = -h * p[2]; // // Calculate E // p *= h; inv[0][0] += p[0] * r[0]; inv[0][1] += p[0] * r[1]; inv[0][2] += p[0] * r[2]; inv[1][0] += p[1] * r[0]; inv[1][1] += p[1] * r[1]; inv[1][2] += p[1] * r[2]; inv[2][0] += p[2] * r[0]; inv[2][1] += p[2] * r[1]; inv[2][2] += p[2] * r[2]; } } else { // Equations are much simpler in the non-perspective case inv[3][0] = - (m[3][0] * inv[0][0] + m[3][1] * inv[1][0] + m[3][2] * inv[2][0]); inv[3][1] = - (m[3][0] * inv[0][1] + m[3][1] * inv[1][1] + m[3][2] * inv[2][1]); inv[3][2] = - (m[3][0] * inv[0][2] + m[3][1] * inv[1][2] + m[3][2] * inv[2][2]); inv[0][3] = 0.0; inv[1][3] = 0.0; inv[2][3] = 0.0; inv[3][3] = 1.0; } } if (!invertible) OPENVDB_THROW(ArithmeticError, "Inversion of singular 4x4 matrix"); return inv; } /// Determinant of matrix T det() const { const T *ap; Mat3 submat; T det; T *sp; int i, j, k, sign; det = 0; sign = 1; for (i = 0; i < 4; i++) { ap = &MyBase::mm[ 0]; sp = submat.asPointer(); for (j = 0; j < 4; j++) { for (k = 0; k < 4; k++) { if ((k != i) && (j != 0)) { *sp++ = *ap; } ap++; } } det += sign * MyBase::mm[i] * submat.det(); sign = -sign; } return det; } /// This function snaps a specific axis to a specific direction, /// preserving scaling. It does this using minimum energy, thus /// posing a unique solution if basis & direction arent parralel. /// Direction need not be unit. Mat4 snapBasis(Axis axis, const Vec3 &direction) {return snapBasis(*this, axis, direction);} /// Sets the matrix to a matrix that translates by v static Mat4 translation(const Vec3d& v) { return Mat4( T(1), T(0), T(0), T(0), T(0), T(1), T(0), T(0), T(0), T(0), T(1), T(0), T(v.x()), T(v.y()),T(v.z()), T(1)); } /// Sets the matrix to a matrix that translates by v template void setToTranslation(const Vec3& v) { MyBase::mm[ 0] = 1; MyBase::mm[ 1] = 0; MyBase::mm[ 2] = 0; MyBase::mm[ 3] = 0; MyBase::mm[ 4] = 0; MyBase::mm[ 5] = 1; MyBase::mm[ 6] = 0; MyBase::mm[ 7] = 0; MyBase::mm[ 8] = 0; MyBase::mm[ 9] = 0; MyBase::mm[10] = 1; MyBase::mm[11] = 0; MyBase::mm[12] = v.x(); MyBase::mm[13] = v.y(); MyBase::mm[14] = v.z(); MyBase::mm[15] = 1; } /// Left multiples by the specified translation, i.e. Trans * (*this) template void preTranslate(const Vec3& tr) { Vec3 tmp(tr.x(), tr.y(), tr.z()); Mat4 Tr = Mat4::translation(tmp); *this = Tr * (*this); } /// Right multiplies by the specified translation matrix, i.e. (*this) * Trans template void postTranslate(const Vec3& tr) { Vec3 tmp(tr.x(), tr.y(), tr.z()); Mat4 Tr = Mat4::translation(tmp); *this = (*this) * Tr; } /// Sets the matrix to a matrix that scales by v template void setToScale(const Vec3& v) { this->setIdentity(); MyBase::mm[ 0] = v.x(); MyBase::mm[ 5] = v.y(); MyBase::mm[10] = v.z(); } // Left multiples by the specified scale matrix, i.e. Sc * (*this) template void preScale(const Vec3& v) { MyBase::mm[ 0] *= v.x(); MyBase::mm[ 1] *= v.x(); MyBase::mm[ 2] *= v.x(); MyBase::mm[ 3] *= v.x(); MyBase::mm[ 4] *= v.y(); MyBase::mm[ 5] *= v.y(); MyBase::mm[ 6] *= v.y(); MyBase::mm[ 7] *= v.y(); MyBase::mm[ 8] *= v.z(); MyBase::mm[ 9] *= v.z(); MyBase::mm[10] *= v.z(); MyBase::mm[11] *= v.z(); } // Right multiples by the specified scale matrix, i.e. (*this) * Sc template void postScale(const Vec3& v) { MyBase::mm[ 0] *= v.x(); MyBase::mm[ 1] *= v.y(); MyBase::mm[ 2] *= v.z(); MyBase::mm[ 4] *= v.x(); MyBase::mm[ 5] *= v.y(); MyBase::mm[ 6] *= v.z(); MyBase::mm[ 8] *= v.x(); MyBase::mm[ 9] *= v.y(); MyBase::mm[10] *= v.z(); MyBase::mm[12] *= v.x(); MyBase::mm[13] *= v.y(); MyBase::mm[14] *= v.z(); } /// @brief Sets the matrix to a rotation about the given axis. /// @param axis The axis (one of X, Y, Z) to rotate about. /// @param angle The rotation angle, in radians. void setToRotation(Axis axis, T angle) {*this = rotation >(axis, angle);} /// @brief Sets the matrix to a rotation about an arbitrary axis /// @param axis The axis of rotation (cannot be zero-length) /// @param angle The rotation angle, in radians. void setToRotation(const Vec3& axis, T angle) {*this = rotation >(axis, angle);} /// @brief Sets the matrix to a rotation that maps v1 onto v2 about the cross /// product of v1 and v2. void setToRotation(const Vec3& v1, const Vec3& v2) {*this = rotation >(v1, v2);} /// @brief Left multiplies by a rotation clock-wiseabout the given axis into this matrix. /// @param axis The axis (one of X, Y, Z) of rotation. /// @param angle The clock-wise rotation angle, in radians. void preRotate(Axis axis, T angle) { T c = static_cast(cos(angle)); T s = -static_cast(sin(angle)); // the "-" makes it clockwise switch (axis) { case X_AXIS: { T a4, a5, a6, a7; a4 = c * MyBase::mm[ 4] - s * MyBase::mm[ 8]; a5 = c * MyBase::mm[ 5] - s * MyBase::mm[ 9]; a6 = c * MyBase::mm[ 6] - s * MyBase::mm[10]; a7 = c * MyBase::mm[ 7] - s * MyBase::mm[11]; MyBase::mm[ 8] = s * MyBase::mm[ 4] + c * MyBase::mm[ 8]; MyBase::mm[ 9] = s * MyBase::mm[ 5] + c * MyBase::mm[ 9]; MyBase::mm[10] = s * MyBase::mm[ 6] + c * MyBase::mm[10]; MyBase::mm[11] = s * MyBase::mm[ 7] + c * MyBase::mm[11]; MyBase::mm[ 4] = a4; MyBase::mm[ 5] = a5; MyBase::mm[ 6] = a6; MyBase::mm[ 7] = a7; } break; case Y_AXIS: { T a0, a1, a2, a3; a0 = c * MyBase::mm[ 0] + s * MyBase::mm[ 8]; a1 = c * MyBase::mm[ 1] + s * MyBase::mm[ 9]; a2 = c * MyBase::mm[ 2] + s * MyBase::mm[10]; a3 = c * MyBase::mm[ 3] + s * MyBase::mm[11]; MyBase::mm[ 8] = -s * MyBase::mm[ 0] + c * MyBase::mm[ 8]; MyBase::mm[ 9] = -s * MyBase::mm[ 1] + c * MyBase::mm[ 9]; MyBase::mm[10] = -s * MyBase::mm[ 2] + c * MyBase::mm[10]; MyBase::mm[11] = -s * MyBase::mm[ 3] + c * MyBase::mm[11]; MyBase::mm[ 0] = a0; MyBase::mm[ 1] = a1; MyBase::mm[ 2] = a2; MyBase::mm[ 3] = a3; } break; case Z_AXIS: { T a0, a1, a2, a3; a0 = c * MyBase::mm[ 0] - s * MyBase::mm[ 4]; a1 = c * MyBase::mm[ 1] - s * MyBase::mm[ 5]; a2 = c * MyBase::mm[ 2] - s * MyBase::mm[ 6]; a3 = c * MyBase::mm[ 3] - s * MyBase::mm[ 7]; MyBase::mm[ 4] = s * MyBase::mm[ 0] + c * MyBase::mm[ 4]; MyBase::mm[ 5] = s * MyBase::mm[ 1] + c * MyBase::mm[ 5]; MyBase::mm[ 6] = s * MyBase::mm[ 2] + c * MyBase::mm[ 6]; MyBase::mm[ 7] = s * MyBase::mm[ 3] + c * MyBase::mm[ 7]; MyBase::mm[ 0] = a0; MyBase::mm[ 1] = a1; MyBase::mm[ 2] = a2; MyBase::mm[ 3] = a3; } break; default: assert(axis==X_AXIS || axis==Y_AXIS || axis==Z_AXIS); } } /// @brief Right multiplies by a rotation clock-wiseabout the given axis into this matrix. /// @param axis The axis (one of X, Y, Z) of rotation. /// @param angle The clock-wise rotation angle, in radians. void postRotate(Axis axis, T angle) { T c = static_cast(cos(angle)); T s = -static_cast(sin(angle)); // the "-" makes it clockwise switch (axis) { case X_AXIS: { T a2, a6, a10, a14; a2 = c * MyBase::mm[ 2] - s * MyBase::mm[ 1]; a6 = c * MyBase::mm[ 6] - s * MyBase::mm[ 5]; a10 = c * MyBase::mm[10] - s * MyBase::mm[ 9]; a14 = c * MyBase::mm[14] - s * MyBase::mm[13]; MyBase::mm[ 1] = c * MyBase::mm[ 1] + s * MyBase::mm[ 2]; MyBase::mm[ 5] = c * MyBase::mm[ 5] + s * MyBase::mm[ 6]; MyBase::mm[ 9] = c * MyBase::mm[ 9] + s * MyBase::mm[10]; MyBase::mm[13] = c * MyBase::mm[13] + s * MyBase::mm[14]; MyBase::mm[ 2] = a2; MyBase::mm[ 6] = a6; MyBase::mm[10] = a10; MyBase::mm[14] = a14; } break; case Y_AXIS: { T a2, a6, a10, a14; a2 = c * MyBase::mm[ 2] + s * MyBase::mm[ 0]; a6 = c * MyBase::mm[ 6] + s * MyBase::mm[ 4]; a10 = c * MyBase::mm[10] + s * MyBase::mm[ 8]; a14 = c * MyBase::mm[14] + s * MyBase::mm[12]; MyBase::mm[ 0] = c * MyBase::mm[ 0] - s * MyBase::mm[ 2]; MyBase::mm[ 4] = c * MyBase::mm[ 4] - s * MyBase::mm[ 6]; MyBase::mm[ 8] = c * MyBase::mm[ 8] - s * MyBase::mm[10]; MyBase::mm[12] = c * MyBase::mm[12] - s * MyBase::mm[14]; MyBase::mm[ 2] = a2; MyBase::mm[ 6] = a6; MyBase::mm[10] = a10; MyBase::mm[14] = a14; } break; case Z_AXIS: { T a1, a5, a9, a13; a1 = c * MyBase::mm[ 1] - s * MyBase::mm[ 0]; a5 = c * MyBase::mm[ 5] - s * MyBase::mm[ 4]; a9 = c * MyBase::mm[ 9] - s * MyBase::mm[ 8]; a13 = c * MyBase::mm[13] - s * MyBase::mm[12]; MyBase::mm[ 0] = c * MyBase::mm[ 0] + s * MyBase::mm[ 1]; MyBase::mm[ 4] = c * MyBase::mm[ 4] + s * MyBase::mm[ 5]; MyBase::mm[ 8] = c * MyBase::mm[ 8] + s * MyBase::mm[ 9]; MyBase::mm[12] = c * MyBase::mm[12] + s * MyBase::mm[13]; MyBase::mm[ 1] = a1; MyBase::mm[ 5] = a5; MyBase::mm[ 9] = a9; MyBase::mm[13] = a13; } break; default: assert(axis==X_AXIS || axis==Y_AXIS || axis==Z_AXIS); } } /// @brief Sets the matrix to a shear along axis0 by a fraction of axis1. /// @param axis0 The fixed axis of the shear. /// @param axis1 The shear axis. /// @param shearby The shear factor. void setToShear(Axis axis0, Axis axis1, T shearby) { *this = shear >(axis0, axis1, shearby); } /// @brief Left multiplies a shearing transformation into the matrix. /// @see setToShear void preShear(Axis axis0, Axis axis1, T shear) { int index0 = static_cast(axis0); int index1 = static_cast(axis1); // to row "index1" add a multiple of the index0 row MyBase::mm[index1 * 4 + 0] += shear * MyBase::mm[index0 * 4 + 0]; MyBase::mm[index1 * 4 + 1] += shear * MyBase::mm[index0 * 4 + 1]; MyBase::mm[index1 * 4 + 2] += shear * MyBase::mm[index0 * 4 + 2]; MyBase::mm[index1 * 4 + 3] += shear * MyBase::mm[index0 * 4 + 3]; } /// @brief Right multiplies a shearing transformation into the matrix. /// @see setToShear void postShear(Axis axis0, Axis axis1, T shear) { int index0 = static_cast(axis0); int index1 = static_cast(axis1); // to collumn "index0" add a multiple of the index1 row MyBase::mm[index0 + 0] += shear * MyBase::mm[index1 + 0]; MyBase::mm[index0 + 4] += shear * MyBase::mm[index1 + 4]; MyBase::mm[index0 + 8] += shear * MyBase::mm[index1 + 8]; MyBase::mm[index0 + 12] += shear * MyBase::mm[index1 + 12]; } /// Transform a Vec4 by post-multiplication. template Vec4 transform(const Vec4 &v) const { return static_cast< Vec4 >(v * *this); } /// Transform a Vec3 by post-multiplication, without homogenous division. template Vec3 transform(const Vec3 &v) const { return static_cast< Vec3 >(v * *this); } /// Transform a Vec4 by pre-multiplication. template Vec4 pretransform(const Vec4 &v) const { return static_cast< Vec4 >(*this * v); } /// Transform a Vec3 by pre-multiplication, without homogenous division. template Vec3 pretransform(const Vec3 &v) const { return static_cast< Vec3 >(*this * v); } /// Transform a Vec3 by post-multiplication, doing homogenous divison. template Vec3 transformH(const Vec3 &p) const { T0 w; // w = p * (*this).col(3); w = p[0] * MyBase::mm[ 3] + p[1] * MyBase::mm[ 7] + p[2] * MyBase::mm[11] + MyBase::mm[15]; if ( !isExactlyEqual(w , 0.0) ) { return Vec3(static_cast((p[0] * MyBase::mm[ 0] + p[1] * MyBase::mm[ 4] + p[2] * MyBase::mm[ 8] + MyBase::mm[12]) / w), static_cast((p[0] * MyBase::mm[ 1] + p[1] * MyBase::mm[ 5] + p[2] * MyBase::mm[ 9] + MyBase::mm[13]) / w), static_cast((p[0] * MyBase::mm[ 2] + p[1] * MyBase::mm[ 6] + p[2] * MyBase::mm[10] + MyBase::mm[14]) / w)); } return Vec3(0, 0, 0); } /// Transform a Vec3 by pre-multiplication, doing homogenous division. template Vec3 pretransformH(const Vec3 &p) const { T0 w; // w = p * (*this).col(3); w = p[0] * MyBase::mm[12] + p[1] * MyBase::mm[13] + p[2] * MyBase::mm[14] + MyBase::mm[15]; if ( !isExactlyEqual(w , 0.0) ) { return Vec3(static_cast((p[0] * MyBase::mm[ 0] + p[1] * MyBase::mm[ 1] + p[2] * MyBase::mm[ 2] + MyBase::mm[ 3]) / w), static_cast((p[0] * MyBase::mm[ 4] + p[1] * MyBase::mm[ 5] + p[2] * MyBase::mm[ 6] + MyBase::mm[ 7]) / w), static_cast((p[0] * MyBase::mm[ 8] + p[1] * MyBase::mm[ 9] + p[2] * MyBase::mm[10] + MyBase::mm[11]) / w)); } return Vec3(0, 0, 0); } /// Transform a Vec3 by post-multiplication, without translation. template Vec3 transform3x3(const Vec3 &v) const { return Vec3( static_cast(v[0] * MyBase::mm[ 0] + v[1] * MyBase::mm[ 4] + v[2] * MyBase::mm[ 8]), static_cast(v[0] * MyBase::mm[ 1] + v[1] * MyBase::mm[ 5] + v[2] * MyBase::mm[ 9]), static_cast(v[0] * MyBase::mm[ 2] + v[1] * MyBase::mm[ 6] + v[2] * MyBase::mm[10])); } private: bool invert(Mat4 &inverse, T tolerance) const; T det2(const Mat4 &a, int i0, int i1, int j0, int j1) const { int i0row = i0 * 4; int i1row = i1 * 4; return a.mm[i0row+j0]*a.mm[i1row+j1] - a.mm[i0row+j1]*a.mm[i1row+j0]; } T det3(const Mat4 &a, int i0, int i1, int i2, int j0, int j1, int j2) const { int i0row = i0 * 4; return a.mm[i0row+j0]*det2(a, i1,i2, j1,j2) + a.mm[i0row+j1]*det2(a, i1,i2, j2,j0) + a.mm[i0row+j2]*det2(a, i1,i2, j0,j1); } static const Mat4 sIdentity; static const Mat4 sZero; }; // class Mat4 template const Mat4 Mat4::sIdentity = Mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); template const Mat4 Mat4::sZero = Mat4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); /// @relates Mat4 /// @brief Equality operator, does exact floating point comparisons template bool operator==(const Mat4 &m0, const Mat4 &m1) { const T0 *t0 = m0.asPointer(); const T1 *t1 = m1.asPointer(); for (int i=0; i<16; ++i) if (!isExactlyEqual(t0[i], t1[i])) return false; return true; } /// @relates Mat4 /// @brief Inequality operator, does exact floating point comparisons template bool operator!=(const Mat4 &m0, const Mat4 &m1) { return !(m0 == m1); } /// @relates Mat4 /// @brief Returns M, where \f$M_{i,j} = m_{i,j} * scalar\f$ for \f$i, j \in [0, 3]\f$ template Mat4::type> operator*(S scalar, const Mat4 &m) { return m*scalar; } /// @relates Mat4 /// @brief Returns M, where \f$M_{i,j} = m_{i,j} * scalar\f$ for \f$i, j \in [0, 3]\f$ template Mat4::type> operator*(const Mat4 &m, S scalar) { Mat4::type> result(m); result *= scalar; return result; } /// @relates Mat4 /// @brief Returns v, where \f$v_{i} = \sum_{n=0}^3 m_{i,n} * v_n \f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator*(const Mat4 &_m, const Vec4 &_v) { MT const *m = _m.asPointer(); return Vec4::type>( _v[0]*m[0] + _v[1]*m[1] + _v[2]*m[2] + _v[3]*m[3], _v[0]*m[4] + _v[1]*m[5] + _v[2]*m[6] + _v[3]*m[7], _v[0]*m[8] + _v[1]*m[9] + _v[2]*m[10] + _v[3]*m[11], _v[0]*m[12] + _v[1]*m[13] + _v[2]*m[14] + _v[3]*m[15]); } /// @relates Mat4 /// @brief Returns v, where \f$v_{i} = \sum_{n=0}^3 m_{n,i} * v_n \f$ for \f$i \in [0, 3]\f$ template inline Vec4::type> operator*(const Vec4 &_v, const Mat4 &_m) { MT const *m = _m.asPointer(); return Vec4::type>( _v[0]*m[0] + _v[1]*m[4] + _v[2]*m[8] + _v[3]*m[12], _v[0]*m[1] + _v[1]*m[5] + _v[2]*m[9] + _v[3]*m[13], _v[0]*m[2] + _v[1]*m[6] + _v[2]*m[10] + _v[3]*m[14], _v[0]*m[3] + _v[1]*m[7] + _v[2]*m[11] + _v[3]*m[15]); } /// @relates Mat4 /// @brief Returns v, where /// \f$v_{i} = \sum_{n=0}^3\left(m_{i,n} * v_n + m_{i,3}\right)\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator*(const Mat4 &_m, const Vec3 &_v) { MT const *m = _m.asPointer(); return Vec3::type>( _v[0]*m[0] + _v[1]*m[1] + _v[2]*m[2] + m[3], _v[0]*m[4] + _v[1]*m[5] + _v[2]*m[6] + m[7], _v[0]*m[8] + _v[1]*m[9] + _v[2]*m[10] + m[11]); } /// @relates Mat4 /// @brief Returns v, where /// \f$v_{i} = \sum_{n=0}^3\left(m_{n,i} * v_n + m_{3,i}\right)\f$ for \f$i \in [0, 2]\f$ template inline Vec3::type> operator*(const Vec3 &_v, const Mat4 &_m) { MT const *m = _m.asPointer(); return Vec3::type>( _v[0]*m[0] + _v[1]*m[4] + _v[2]*m[8] + m[12], _v[0]*m[1] + _v[1]*m[5] + _v[2]*m[9] + m[13], _v[0]*m[2] + _v[1]*m[6] + _v[2]*m[10] + m[14]); } /// @relates Mat4 /// @brief Returns M, where \f$M_{i,j} = m0_{i,j} + m1_{i,j}\f$ for \f$i, j \in [0, 3]\f$ template Mat4::type> operator+(const Mat4 &m0, const Mat4 &m1) { Mat4::type> result(m0); result += m1; return result; } /// @relates Mat4 /// @brief Returns M, where \f$M_{i,j} = m0_{i,j} - m1_{i,j}\f$ for \f$i, j \in [0, 3]\f$ template Mat4::type> operator-(const Mat4 &m0, const Mat4 &m1) { Mat4::type> result(m0); result -= m1; return result; } /// @relates Mat4 /// @brief Returns M, where /// \f$M_{ij} = \sum_{n=0}^3\left(m0_{nj} + m1_{in}\right)\f$ for \f$i, j \in [0, 3]\f$ template Mat4::type> operator*(const Mat4 &m0, const Mat4 &m1) { Mat4::type> result(m0); result *= m1; return result; } /// Transform a Vec3 by pre-multiplication, without translation. /// Presumes this matrix is inverse of coordinate transform /// Synonymous to "pretransform3x3" template Vec3 transformNormal(const Mat4 &m, const Vec3 &n) { return Vec3( static_cast(m[0][0]*n[0] + m[0][1]*n[1] + m[0][2]*n[2]), static_cast(m[1][0]*n[0] + m[1][1]*n[1] + m[1][2]*n[2]), static_cast(m[2][0]*n[0] + m[2][1]*n[1] + m[2][2]*n[2])); } /// Invert via gauss-jordan elimination. Modified from dreamworks internal mx library template bool Mat4::invert(Mat4 &inverse, T tolerance) const { Mat4 temp(*this); inverse.setIdentity(); // Forward elimination step double det = 1.0; for (int i = 0; i < 4; ++i) { int row = i; double max = fabs(temp[i][i]); for (int k = i+1; k < 4; ++k) { if (fabs(temp[k][i]) > max) { row = k; max = fabs(temp[k][i]); } } if (isExactlyEqual(max, 0.0)) return false; // must move pivot to row i if (row != i) { det = -det; for (int k = 0; k < 4; ++k) { std::swap(temp[row][k], temp[i][k]); std::swap(inverse[row][k], inverse[i][k]); } } double pivot = temp[i][i]; det *= pivot; // scale row i for (int k = 0; k < 4; ++k) { temp[i][k] /= pivot; inverse[i][k] /= pivot; } // eliminate in rows below i for (int j = i+1; j < 4; ++j) { double t = temp[j][i]; if (!isExactlyEqual(t, 0.0)) { // subtract scaled row i from row j for (int k = 0; k < 4; ++k) { temp[j][k] -= temp[i][k] * t; inverse[j][k] -= inverse[i][k] * t; } } } } // Backward elimination step for (int i = 3; i > 0; --i) { for (int j = 0; j < i; ++j) { double t = temp[j][i]; if (!isExactlyEqual(t, 0.0)) { for (int k = 0; k < 4; ++k) { inverse[j][k] -= inverse[i][k]*t; } } } } return det*det >= tolerance*tolerance; } template inline bool isAffine(const Mat4& m) { return (m.col(3) == Vec4(0, 0, 0, 1)); } template inline bool hasTranslation(const Mat4& m) { return (m.row(3) != Vec4(0, 0, 0, 1)); } typedef Mat4 Mat4s; typedef Mat4 Mat4d; #if DWREAL_IS_DOUBLE == 1 typedef Mat4d Mat4f; #else typedef Mat4s Mat4f; #endif // DWREAL_IS_DOUBLE } // namespace math template<> inline math::Mat4s zeroVal() { return math::Mat4s::identity(); } template<> inline math::Mat4d zeroVal() { return math::Mat4d::identity(); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_MAT4_H_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Math.h0000644000000000000000000006151112252453157013216 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Math.h /// @brief General-purpose arithmetic and comparison routines, most of which /// accept arbitrary value types (or at least arbitrary numeric value types) #ifndef OPENVDB_MATH_HAS_BEEN_INCLUDED #define OPENVDB_MATH_HAS_BEEN_INCLUDED #include #include // for std::max() #include // for floor(), ceil() and sqrt() #include // for pow(), fabs() etc #include // for srand(), abs(int) #include // for std::numeric_limits::max() #include #include #include #include // for boost::random::mt19937 #include #include #include // for BOOST_VERSION #include #include // Compile pragmas // Intel(r) compiler fires remark #1572: floating-point equality and inequality // comparisons are unrealiable when == or != is used with floating point operands. #if defined(__INTEL_COMPILER) #define OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN \ _Pragma("warning (push)") \ _Pragma("warning (disable:1572)") #define OPENVDB_NO_FP_EQUALITY_WARNING_END \ _Pragma("warning (pop)") #else // For GCC, #pragma GCC diagnostic ignored "-Wfloat-equal" // isn't working until gcc 4.2+, // Trying // #pragma GCC system_header // creates other problems, most notably "warning: will never be executed" // in from templates, unsure of how to work around. // If necessary, could use integer based comparisons for equality #define OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN #define OPENVDB_NO_FP_EQUALITY_WARNING_END #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { /// @brief Return the value of type T that corresponds to zero. /// @note A zeroVal() specialization must be defined for each @c ValueType T /// that cannot be constructed using the form @c T(0). For example, @c std::string(0) /// treats 0 as @c NULL and throws a @c std::logic_error. template inline T zeroVal() { return T(0); } /// Return the @c std::string value that corresponds to zero. template<> inline std::string zeroVal() { return ""; } /// Return the @c bool value that corresponds to zero. template<> inline bool zeroVal() { return false; } /// @todo These won't be needed if we eliminate StringGrids. //@{ /// @brief Needed to support the (zeroVal() + val) idiom /// when @c ValueType is @c std::string inline std::string operator+(const std::string& s, bool) { return s; } inline std::string operator+(const std::string& s, int) { return s; } inline std::string operator+(const std::string& s, float) { return s; } inline std::string operator+(const std::string& s, double) { return s; } //@} namespace math { /// @brief Return the unary negation of the given value. /// @note A negative() specialization must be defined for each ValueType T /// for which unary negation is not defined. template inline T negative(const T& val) { return T(-val); } /// Return the negation of the given boolean. template<> inline bool negative(const bool& val) { return !val; } /// Return the "negation" of the given string. template<> inline std::string negative(const std::string& val) { return val; } //@{ /// Tolerance for floating-point comparison template struct Tolerance { static T value() { return zeroVal(); } }; template<> struct Tolerance { static float value() { return 1e-8f; } }; template<> struct Tolerance { static double value() { return 1e-15; } }; //@} //@{ /// Delta for small floating-point offsets template struct Delta { static T value() { return zeroVal(); } }; template<> struct Delta { static float value() { return 1e-5f; } }; template<> struct Delta { static double value() { return 1e-9; } }; //@} // ==========> Random Values <================== /// Initialize the random number generator. /// @deprecated Use math::Random01 or Boost.Random instead. OPENVDB_DEPRECATED inline void randSeed(unsigned int seed) { srand(seed); } /// Return a random number in the interval [0,1] /// @deprecated Use math::Random01 or Boost.Random instead. OPENVDB_DEPRECATED inline double randUniform() { return (double)(rand() / (RAND_MAX + 1.0)); } /// @brief Simple generator of random numbers over the range [0, 1) /// @details Thread-safe as long as each thread has its own Rand01 instance template class Rand01 { private: EngineType mEngine; boost::uniform_01 mRand; public: typedef FloatType ValueType; /// @brief Initialize the generator. /// @param seed seed value for the random number generator Rand01(unsigned int seed): mEngine(static_cast(seed)) {} /// Return a uniformly distributed random number in the range [0, 1). FloatType operator()() { return mRand(mEngine); } }; typedef Rand01 Random01; /// @brief Simple random integer generator /// @details Thread-safe as long as each thread has its own RandInt instance template class RandInt { private: #if BOOST_VERSION >= 104700 typedef boost::random::uniform_int_distribution Distr; #else typedef boost::uniform_int Distr; #endif EngineType mEngine; Distr mRand; public: /// @brief Initialize the generator. /// @param seed seed value for the random number generator /// @param imin,imax generate integers that are uniformly distributed over [imin, imax] RandInt(unsigned int seed, int imin, int imax): mEngine(static_cast(seed)), mRand(std::min(imin, imax), std::max(imin, imax)) {} /// Change the range over which integers are distributed to [imin, imax]. void setRange(int imin, int imax) { mRand = Distr(std::min(imin, imax), std::max(imin, imax)); } /// Return a randomly-generated integer in the current range. int operator()() { return mRand(mEngine); } /// @brief Return a randomly-generated integer in the new range [imin, imax], /// without changing the current range. int operator()(int imin, int imax) { const int lo = std::min(imin, imax), hi = std::max(imin, imax); #if BOOST_VERSION >= 104700 return mRand(mEngine, Distr::param_type(lo, hi)); #else return Distr(lo, hi)(mEngine); #endif } }; typedef RandInt RandomInt; // ==========> Clamp <================== /// Return @a x clamped to [@a min, @a max] template inline Type Clamp(Type x, Type min, Type max) { assert(min min ? x < max ? x : max : min; } /// Return @a x clamped to [0, 1] template inline Type Clamp01(Type x) { return x > Type(0) ? x < Type(1) ? x : Type(1) : Type(0); } /// Return @c true if @a x is outside [0,1] template inline bool ClampTest01(Type &x) { if (x >= Type(0) && x <= Type(1)) return false; x = x < Type(0) ? Type(0) : Type(1); return true; } /// @brief Return 0 if @a x < @a min, 1 if @a x > @a max or else @f$(3-2t)t^2@f$, /// where @f$t = (x-min)/(max-min)@f$. template inline Type SmoothUnitStep(Type x, Type min, Type max) { assert(min < max); const Type t = (x-min)/(max-min); return t > 0 ? t < 1 ? (3-2*t)*t*t : Type(1) : Type(0); } // ==========> Absolute Value <================== //@{ /// Return the absolute value of the given quantity. inline int32_t Abs(int32_t i) { return abs(i); } inline int64_t Abs(int64_t i) { #ifdef _MSC_VER return (i < int64_t(0) ? -i : i); #else return abs(i); #endif } inline float Abs(float x) { return fabs(x); } inline double Abs(double x) { return fabs(x); } inline long double Abs(long double x) { return fabs(x); } inline uint32_t Abs(uint32_t i) { return i; } inline uint64_t Abs(uint64_t i) { return i; } // On OSX size_t and uint64_t are different types #if defined(__APPLE__) || defined(MACOSX) inline size_t Abs(size_t i) { return i; } #endif //@} //////////////////////////////////////// // ==========> Value Comparison <================== /// Return @c true if @a x is exactly equal to zero. template inline bool isZero(const Type& x) { OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN return x == zeroVal(); OPENVDB_NO_FP_EQUALITY_WARNING_END } /// @brief Return @c true if @a x is equal to zero to within /// the default floating-point comparison tolerance. template inline bool isApproxZero(const Type& x) { const Type tolerance = Type(zeroVal() + Tolerance::value()); return x < tolerance && x > -tolerance; } /// Return @c true if @a x is equal to zero to within the given tolerance. template inline bool isApproxZero(const Type& x, const Type& tolerance) { return x < tolerance && x > -tolerance; } /// Return @c true if @a x is less than zero. template inline bool isNegative(const Type& x) { return x < zeroVal(); } /// Return @c false, since @c bool values are never less than zero. template<> inline bool isNegative(const bool&) { return false; } /// @brief Return @c true if @a a is equal to @a b to within /// the default floating-point comparison tolerance. template inline bool isApproxEqual(const Type& a, const Type& b) { const Type tolerance = Type(zeroVal() + Tolerance::value()); return !(Abs(a - b) > tolerance); } /// Return @c true if @a a is equal to @a b to within the given tolerance. template inline bool isApproxEqual(const Type& a, const Type& b, const Type& tolerance) { return !(Abs(a - b) > tolerance); } #define OPENVDB_EXACT_IS_APPROX_EQUAL(T) \ template<> inline bool isApproxEqual(const T& a, const T& b) { return a == b; } \ template<> inline bool isApproxEqual(const T& a, const T& b, const T&) { return a == b; } \ /**/ OPENVDB_EXACT_IS_APPROX_EQUAL(bool) OPENVDB_EXACT_IS_APPROX_EQUAL(std::string) /// @brief Return @c true if @a a is larger than @a b to within /// the given tolerance, i.e., if @a b - @a a < @a tolerance. template inline bool isApproxLarger(const Type& a, const Type& b, const Type& tolerance) { return (b - a < tolerance); } /// @brief Return @c true if @a a is exactly equal to @a b. template inline bool isExactlyEqual(const T0& a, const T1& b) { OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN return a == b; OPENVDB_NO_FP_EQUALITY_WARNING_END } template inline bool isRelOrApproxEqual(const Type& a, const Type& b, const Type& absTol, const Type& relTol) { // First check to see if we are inside the absolute tolerance // Necessary for numbers close to 0 if (!(Abs(a - b) > absTol)) return true; // Next check to see if we are inside the relative tolerance // to handle large numbers that aren't within the abs tolerance // but could be the closest floating point representation double relError; if (Abs(b) > Abs(a)) { relError = Abs((a - b) / b); } else { relError = Abs((a - b) / a); } return (relError <= relTol); } template<> inline bool isRelOrApproxEqual(const bool& a, const bool& b, const bool&, const bool&) { return (a == b); } // Avoid strict aliasing issues by using type punning // http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html // Using "casting through a union(2)" inline int32_t floatToInt32(const float aFloatValue) { union FloatOrInt32 { float floatValue; int32_t int32Value; }; const FloatOrInt32* foi = reinterpret_cast(&aFloatValue); return foi->int32Value; } inline int64_t doubleToInt64(const double aDoubleValue) { union DoubleOrInt64 { double doubleValue; int64_t int64Value; }; const DoubleOrInt64* dol = reinterpret_cast(&aDoubleValue); return dol->int64Value; } // aUnitsInLastPlace is the allowed difference between the least significant digits // of the numbers' floating point representation // Please read refernce paper before trying to use isUlpsEqual // http://www.cygnus-software.com/papers/comparingFloats/comparingFloats.htm inline bool isUlpsEqual(const double aLeft, const double aRight, const int64_t aUnitsInLastPlace) { int64_t longLeft = doubleToInt64(aLeft); // Because of 2's complement, must restore lexicographical order if (longLeft < 0) { longLeft = INT64_C(0x8000000000000000) - longLeft; } int64_t longRight = doubleToInt64(aRight); // Because of 2's complement, must restore lexicographical order if (longRight < 0) { longRight = INT64_C(0x8000000000000000) - longRight; } int64_t difference = labs(longLeft - longRight); return (difference <= aUnitsInLastPlace); } inline bool isUlpsEqual(const float aLeft, const float aRight, const int32_t aUnitsInLastPlace) { int32_t intLeft = floatToInt32(aLeft); // Because of 2's complement, must restore lexicographical order if (intLeft < 0) { intLeft = 0x80000000 - intLeft; } int32_t intRight = floatToInt32(aRight); // Because of 2's complement, must restore lexicographical order if (intRight < 0) { intRight = 0x80000000 - intRight; } int32_t difference = abs(intLeft - intRight); return (difference <= aUnitsInLastPlace); } //////////////////////////////////////// // ==========> Pow <================== /// Return @f$ x^2 @f$. template inline Type Pow2(Type x) { return x*x; } /// Return @f$ x^3 @f$. template inline Type Pow3(Type x) { return x*x*x; } /// Return @f$ x^4 @f$. template inline Type Pow4(Type x) { return Pow2(Pow2(x)); } /// Return @f$ x^n @f$. template Type Pow(Type x, int n) { Type ans = 1; if (n < 0) { n = -n; x = Type(1)/x; } while (n--) ans *= x; return ans; } //@{ /// Return @f$ b^e @f$. inline float Pow(float b, float e) { assert( b >= 0.0f && "Pow(float,float): base is negative" ); return powf(b,e); } inline double Pow(double b, double e) { assert( b >= 0.0 && "Pow(double,double): base is negative" ); return pow(b,e); } //@} // ==========> Max <================== /// Return the maximum of two values template inline const Type& Max(const Type& a, const Type& b) { return std::max(a,b) ; } /// Return the maximum of three values template inline const Type& Max(const Type& a, const Type& b, const Type& c) { return std::max( std::max(a,b), c ) ; } /// Return the maximum of four values template inline const Type& Max(const Type& a, const Type& b, const Type& c, const Type& d) { return std::max(std::max(a,b), std::max(c,d)); } /// Return the maximum of five values template inline const Type& Max(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e) { return std::max(std::max(a,b), Max(c,d,e)); } /// Return the maximum of six values template inline const Type& Max(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e, const Type& f) { return std::max(Max(a,b,c), Max(d,e,f)); } /// Return the maximum of seven values template inline const Type& Max(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e, const Type& f, const Type& g) { return std::max(Max(a,b,c,d), Max(e,f,g)); } /// Return the maximum of eight values template inline const Type& Max(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e, const Type& f, const Type& g, const Type& h) { return std::max(Max(a,b,c,d), Max(e,f,g,h)); } // ==========> Min <================== /// Return the minimum of two values template inline const Type& Min(const Type& a, const Type& b) { return std::min(a, b); } /// Return the minimum of three values template inline const Type& Min(const Type& a, const Type& b, const Type& c) { return std::min(std::min(a, b), c); } /// Return the minimum of four values template inline const Type& Min(const Type& a, const Type& b, const Type& c, const Type& d) { return std::min(std::min(a, b), std::min(c, d)); } /// Return the minimum of five values template inline const Type& Min(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e) { return std::min(std::min(a,b), Min(c,d,e)); } /// Return the minimum of six values template inline const Type& Min(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e, const Type& f) { return std::min(Min(a,b,c), Min(d,e,f)); } /// Return the minimum of seven values template inline const Type& Min(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e, const Type& f, const Type& g) { return std::min(Min(a,b,c,d), Min(e,f,g)); } /// Return the minimum of eight values template inline const Type& Min(const Type& a, const Type& b, const Type& c, const Type& d, const Type& e, const Type& f, const Type& g, const Type& h) { return std::min(Min(a,b,c,d), Min(e,f,g,h)); } //////////////////////////////////////// /// Return the sign of the given value as an integer (either -1, 0 or 1). template inline int Sign(const Type &x) { return (zeroVal() < x) - (x < zeroVal()); } /// @brief Return @c true if @a a and @a b have different signs. /// @note Zero is considered a positive number. template inline bool SignChange(const Type& a, const Type& b) { return ( (a()) ^ (b()) ); } /// @brief Return @c true if the interval [@a a, @a b] includes zero, /// i.e., if either @a a or @a b is zero or if they have different signs. template inline bool ZeroCrossing(const Type& a, const Type& b) { return a * b <= zeroVal(); } //@{ /// Return the square root of a floating-point value. inline float Sqrt(float x) { return sqrtf(x); } inline double Sqrt(double x) { return sqrt(x); } inline long double Sqrt(long double x) { return sqrtl(x); } //@} //@{ /// Return the cube root of a floating-point value. inline float Cbrt(float x) { return boost::math::cbrt(x); } inline double Cbrt(double x) { return boost::math::cbrt(x); } inline long double Cbrt(long double x) { return boost::math::cbrt(x); } //@} //@{ /// Return the remainder of @a x / @a y. inline int Mod(int x, int y) { return (x % y); } inline float Mod(float x, float y) { return fmodf(x,y); } inline double Mod(double x, double y) { return fmod(x,y); } inline long double Mod(long double x, long double y) { return fmodl(x,y); } template inline Type Remainder(Type x, Type y) { return Mod(x,y); } //@} //@{ /// Return @a x rounded up to the nearest integer. inline float RoundUp(float x) { return ceilf(x); } inline double RoundUp(double x) { return ceil(x); } inline long double RoundUp(long double x) { return ceill(x); } //@} /// Return @a x rounded up to the nearest multiple of @a base. template inline Type RoundUp(Type x, Type base) { Type remainder = Remainder(x, base); return remainder ? x-remainder+base : x; } //@{ /// Return @a x rounded down to the nearest integer. inline float RoundDown(float x) { return floorf(x); } inline double RoundDown(double x) { return floor(x); } inline long double RoundDown(long double x) { return floorl(x); } template inline Type Round(Type x) { return RoundDown(x+0.5); } //@} /// Return @a x rounded down to the nearest multiple of @a base. template inline Type RoundDown(Type x, Type base) { Type remainder = Remainder(x, base); return remainder ? x-remainder : x; } /// Return the integer part of @a x. template inline Type IntegerPart(Type x) { return (x > 0 ? RoundDown(x) : RoundUp(x)); } /// Return the fractional part of @a x. template inline Type FractionalPart(Type x) { return Mod(x,Type(1)); } //@{ /// Return the floor of @a x. inline int Floor(float x) { return (int)RoundDown(x); } inline int Floor(double x) { return (int)RoundDown(x); } inline int Floor(long double x) { return (int)RoundDown(x); } //@} //@{ /// Return the ceiling of @a x. inline int Ceil(float x) { return (int)RoundUp(x); } inline int Ceil(double x) { return (int)RoundUp(x); } inline int Ceil(long double x) { return (int)RoundUp(x); } //@} /// Return @a x if it is greater in magnitude than @a delta. Otherwise, return zero. template inline Type Chop(Type x, Type delta) { return (Abs(x) < delta ? zeroVal() : x); } /// Return @a x truncated to the given number of decimal digits. template inline Type Truncate(Type x, unsigned int digits) { Type tenth = Pow(10,digits); return RoundDown(x*tenth+0.5)/tenth; } //////////////////////////////////////// /// Return the inverse of @a x. template inline Type Inv(Type x) { assert(x); return Type(1)/x; } enum Axis { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2 }; // enum values are consistent with their historical mx analogs. enum RotationOrder { XYZ_ROTATION = 0, XZY_ROTATION, YXZ_ROTATION, YZX_ROTATION, ZXY_ROTATION, ZYX_ROTATION, XZX_ROTATION, ZXZ_ROTATION }; template struct promote { typedef typename boost::numeric::conversion_traits::supertype type; }; /// @brief Return the index [0,1,2] of the smallest value in a 3D vector. /// @note This methods assumes operator[] exists and avoids branching. /// @details If two components of the input vector are equal and smaller then the /// third component, the largest index of the two is always returned. /// If all three vector components are equal the largest index, i.e. 2, is /// returned. In other words the return value corresponds to the largest index /// of the of the smallest vector components. template size_t MinIndex(const Vec3T& v) { static const size_t hashTable[8] = { 2, 1, 9, 1, 2, 9, 0, 0 };//9 is a dummy value const size_t hashKey = ((v[0] < v[1]) << 2) + ((v[0] < v[2]) << 1) + (v[1] < v[2]);// ?*4+?*2+?*1 return hashTable[hashKey]; } /// @brief Return the index [0,1,2] of the largest value in a 3D vector. /// @note This methods assumes operator[] exists and avoids branching. /// @details If two components of the input vector are equal and larger then the /// third component, the largest index of the two is always returned. /// If all three vector components are equal the largest index, i.e. 2, is /// returned. In other words the return value corresponds to the largest index /// of the of the largest vector components. template size_t MaxIndex(const Vec3T& v) { static const size_t hashTable[8] = { 2, 1, 9, 1, 2, 9, 0, 0 };//9 is a dummy value const size_t hashKey = ((v[0] > v[1]) << 2) + ((v[0] > v[2]) << 1) + (v[1] > v[2]);// ?*4+?*2+?*1 return hashTable[hashKey]; } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_MATH_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Maps.h0000644000000000000000000027642512252453157013241 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Maps.h #ifndef OPENVDB_MATH_MAPS_HAS_BEEN_INCLUDED #define OPENVDB_MATH_MAPS_HAS_BEEN_INCLUDED #include "Math.h" #include "Mat4.h" #include "Vec3.h" #include "BBox.h" #include "Coord.h" #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// /// Forward declarations of the different map types class MapBase; class ScaleMap; class TranslationMap; class ScaleTranslateMap; class UniformScaleMap; class UniformScaleTranslateMap; class AffineMap; class UnitaryMap; class NonlinearFrustumMap; template class CompoundMap; typedef CompoundMap UnitaryAndTranslationMap; typedef CompoundMap, UnitaryMap> SpectralDecomposedMap; typedef SpectralDecomposedMap SymmetricMap; typedef CompoundMap FullyDecomposedMap; typedef CompoundMap PolarDecomposedMap; //////////////////////////////////////// /// Map traits template struct is_linear { static const bool value = false; }; template<> struct is_linear { static const bool value = true; }; template<> struct is_linear { static const bool value = true; }; template<> struct is_linear { static const bool value = true; }; template<> struct is_linear { static const bool value = true; }; template<> struct is_linear { static const bool value = true; }; template<> struct is_linear { static const bool value = true; }; template<> struct is_linear { static const bool value = true; }; template struct is_linear > { static const bool value = is_linear::value && is_linear::value; }; template struct is_uniform_scale { static const bool value = false; }; template<> struct is_uniform_scale { static const bool value = true; }; template struct is_uniform_scale_translate { static const bool value = false; }; template<> struct is_uniform_scale_translate { static const bool value = true; }; template<> struct is_uniform_scale_translate { static const bool value = true; }; template struct is_scale { static const bool value = false; }; template<> struct is_scale { static const bool value = true; }; template struct is_scale_translate { static const bool value = false; }; template<> struct is_scale_translate { static const bool value = true; }; template struct is_uniform_diagonal_jacobian { static const bool value = is_uniform_scale::value || is_uniform_scale_translate::value; }; template struct is_diagonal_jacobian { static const bool value = is_scale::value || is_scale_translate::value; }; //////////////////////////////////////// /// Utility methods /// @brief Create a SymmetricMap from a symmetric matrix. /// Decomposes the map into Rotation Diagonal Rotation^T OPENVDB_API boost::shared_ptr createSymmetricMap(const Mat3d& m); /// @brief General decomposition of a Matrix into a Unitary (e.g. rotation) /// following a Symmetric (e.g. stretch & shear) OPENVDB_API boost::shared_ptr createFullyDecomposedMap(const Mat4d& m); /// @brief Decomposes a general linear into translation following polar decomposition. /// /// T U S where: /// /// T: Translation /// U: Unitary (rotation or reflection) /// S: Symmetric /// /// @note: the Symmetric is automatically decomposed into Q D Q^T, where /// Q is rotation and D is diagonal. OPENVDB_API boost::shared_ptr createPolarDecomposedMap(const Mat3d& m); /// @brief reduces an AffineMap to a ScaleMap or a ScaleTranslateMap when it can OPENVDB_API boost::shared_ptr simplify(boost::shared_ptr affine); /// @brief Returns the left pseudoInverse of the input matrix when the 3x3 part is symmetric /// otherwise it zeros the 3x3 and reverses the translation. OPENVDB_API Mat4d approxInverse(const Mat4d& mat); //////////////////////////////////////// /// @brief Abstract base class for maps class OPENVDB_API MapBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; typedef Ptr (*MapFactory)(); virtual ~MapBase(){} virtual boost::shared_ptr getAffineMap() const = 0; /// Return the name of this map's concrete type (e.g., @c "AffineMap"). virtual Name type() const = 0; /// Return @c true if this map is of concrete type @c MapT (e.g., AffineMap). template bool isType() const { return this->type() == MapT::mapType(); } /// Return @c true if this map is equal to the given map. virtual bool isEqual(const MapBase& other) const = 0; /// Return @c true if this map is linear. virtual bool isLinear() const = 0; /// Return @c true if the spacing between the image of latice is uniform in all directions virtual bool hasUniformScale() const = 0; virtual Vec3d applyMap(const Vec3d& in) const = 0; virtual Vec3d applyInverseMap(const Vec3d& in) const = 0; //@{ /// @brief Apply the Inverse Jacobian Transpose of this map to a vector. /// For a linear map this is equivalent to applying the transpose of /// inverse map excluding translation. virtual Vec3d applyIJT(const Vec3d& in) const = 0; virtual Vec3d applyIJT(const Vec3d& in, const Vec3d& domainPos) const = 0; //@} virtual Mat3d applyIJC(const Mat3d& m) const = 0; virtual Mat3d applyIJC(const Mat3d& m, const Vec3d& v, const Vec3d& domainPos) const = 0; virtual double determinant() const = 0; virtual double determinant(const Vec3d&) const = 0; //@{ /// @brief Method to return the local size of a voxel. /// When a location is specified as an argument, it is understood to be /// be in the domain of the map (i.e. index space) virtual Vec3d voxelSize() const = 0; virtual Vec3d voxelSize(const Vec3d&) const = 0; //@} virtual void read(std::istream&) = 0; virtual void write(std::ostream&) const = 0; virtual std::string str() const = 0; virtual MapBase::Ptr copy() const = 0; //@{ /// @brief Methods to update the map virtual MapBase::Ptr preRotate(double radians, Axis axis = X_AXIS) const = 0; virtual MapBase::Ptr preTranslate(const Vec3d&) const = 0; virtual MapBase::Ptr preScale(const Vec3d&) const = 0; virtual MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const = 0; virtual MapBase::Ptr postRotate(double radians, Axis axis = X_AXIS) const = 0; virtual MapBase::Ptr postTranslate(const Vec3d&) const = 0; virtual MapBase::Ptr postScale(const Vec3d&) const = 0; virtual MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const = 0; //@} //@{ /// @brief Apply the Jacobian of this map to a vector. /// For a linear map this is equivalent to applying the map excluding translation. /// @warning Houdini 12.5 uses an earlier version of OpenVDB, and maps created /// with that version lack a virtual table entry for this method. Do not call /// this method from Houdini 12.5. virtual Vec3d applyJacobian(const Vec3d& in) const = 0; virtual Vec3d applyJacobian(const Vec3d& in, const Vec3d& domainPos) const = 0; //@} //@{ /// @brief Apply the InverseJacobian of this map to a vector. /// For a linear map this is equivalent to applying the map inverse excluding translation. /// @warning Houdini 12.5 uses an earlier version of OpenVDB, and maps created /// with that version lack a virtual table entry for this method. Do not call /// this method from Houdini 12.5. virtual Vec3d applyInverseJacobian(const Vec3d& in) const = 0; virtual Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d& domainPos) const = 0; //@} //@{ /// @brief Apply the Jacobian transpose of this map to a vector. /// For a linear map this is equivalent to applying the transpose of the map /// excluding translation. /// @warning Houdini 12.5 uses an earlier version of OpenVDB, and maps created /// with that version lack a virtual table entry for this method. Do not call /// this method from Houdini 12.5. virtual Vec3d applyJT(const Vec3d& in) const = 0; virtual Vec3d applyJT(const Vec3d& in, const Vec3d& domainPos) const = 0; //@} /// @brief Return a new map representing the inverse of this map. /// @throw NotImplementedError if the map is a NonlinearFrustumMap. /// @warning Houdini 12.5 uses an earlier version of OpenVDB, and maps created /// with that version lack a virtual table entry for this method. Do not call /// this method from Houdini 12.5. virtual MapBase::Ptr inverseMap() const = 0; protected: MapBase() {} template static bool isEqualBase(const MapT& self, const MapBase& other) { return other.isType() && (self == *static_cast(&other)); } }; //////////////////////////////////////// /// @brief Threadsafe singleton object for accessing the map type-name dictionary. /// Associates a map type-name with a factory function. class OPENVDB_API MapRegistry { public: typedef std::map MapDictionary; static MapRegistry* instance(); /// Create a new map of the given (registered) type name. static MapBase::Ptr createMap(const Name&); /// Return @c true if the given map type name is registered. static bool isRegistered(const Name&); /// Register a map type along with a factory function. static void registerMap(const Name&, MapBase::MapFactory); /// Remove a map type from the registry. static void unregisterMap(const Name&); /// Clear the map type registry. static void clear(); private: MapRegistry() {} static MapRegistry* staticInstance(); static MapRegistry* mInstance; MapDictionary mMap; }; //////////////////////////////////////// /// @brief A general linear transform using homogeneous coordinates to perform /// rotation, scaling, shear and translation class OPENVDB_API AffineMap: public MapBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; AffineMap(): mMatrix(Mat4d::identity()), mMatrixInv(Mat4d::identity()), mJacobianInv(Mat3d::identity()), mDeterminant(1), mVoxelSize(Vec3d(1,1,1)), mIsDiagonal(true), mIsIdentity(true) // the default constructor for translation is zero { } AffineMap(const Mat3d& m) { Mat4d mat4(Mat4d::identity()); mat4.setMat3(m); mMatrix = mat4; updateAcceleration(); } AffineMap(const Mat4d& m): mMatrix(m) { if (!isAffine(m)) { OPENVDB_THROW(ArithmeticError, "Tried to initialize an affine transform from a non-affine 4x4 matrix"); } updateAcceleration(); } AffineMap(const AffineMap& other): MapBase(other), mMatrix(other.mMatrix), mMatrixInv(other.mMatrixInv), mJacobianInv(other.mJacobianInv), mDeterminant(other.mDeterminant), mVoxelSize(other.mVoxelSize), mIsDiagonal(other.mIsDiagonal), mIsIdentity(other.mIsIdentity) { } /// @brief constructor that merges the matrixes for two affine maps AffineMap(const AffineMap& first, const AffineMap& second): mMatrix(first.mMatrix * second.mMatrix) { updateAcceleration(); } ~AffineMap() {} /// Return a MapBase::Ptr to a new AffineMap static MapBase::Ptr create() { return MapBase::Ptr(new AffineMap()); } /// Return a MapBase::Ptr to a deep copy of this map MapBase::Ptr copy() const { return MapBase::Ptr(new AffineMap(*this)); } MapBase::Ptr inverseMap() const { return MapBase::Ptr(new AffineMap(mMatrixInv)); } static bool isRegistered() { return MapRegistry::isRegistered(AffineMap::mapType()); } static void registerMap() { MapRegistry::registerMap( AffineMap::mapType(), AffineMap::create); } Name type() const { return mapType(); } static Name mapType() { return Name("AffineMap"); } /// Return @c true (an AffineMap is always linear). bool isLinear() const { return true; } /// Return @c false ( test if this is unitary with translation ) bool hasUniformScale() const { Mat3d mat = mMatrix.getMat3(); const double det = mat.det(); if (isApproxEqual(det, double(0))) { return false; } else { mat *= (1.f / pow(std::abs(det),1./3.)); return isUnitary(mat); } } virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } bool operator==(const AffineMap& other) const { // the Mat.eq() is approximate if (!mMatrix.eq(other.mMatrix)) { return false; } if (!mMatrixInv.eq(other.mMatrixInv)) { return false; } return true; } bool operator!=(const AffineMap& other) const { return !(*this == other); } AffineMap& operator=(const AffineMap& other) { mMatrix = other.mMatrix; mMatrixInv = other.mMatrixInv; mJacobianInv = other.mJacobianInv; mDeterminant = other.mDeterminant; mVoxelSize = other.mVoxelSize; mIsDiagonal = other.mIsDiagonal; mIsIdentity = other.mIsIdentity; return *this; } /// Return the image of @c in under the map Vec3d applyMap(const Vec3d& in) const { return in * mMatrix; } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const {return in * mMatrixInv; } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const { return mMatrix.transform3x3(in); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const { return mMatrixInv.transform3x3(in); } /// Return the Jacobian Transpose of the map applied to @a in. /// This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const { const double* m = mMatrix.asPointer(); return Vec3d( m[ 0] * in[0] + m[ 1] * in[1] + m[ 2] * in[2], m[ 4] * in[0] + m[ 5] * in[1] + m[ 6] * in[2], m[ 8] * in[0] + m[ 9] * in[1] + m[10] * in[2] ); } /// Return the transpose of the inverse Jacobian of the map applied to @a in. Vec3d applyIJT(const Vec3d& in, const Vec3d&) const { return applyIJT(in); } /// Return the transpose of the inverse Jacobian of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const { return in * mJacobianInv; } /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& m) const { return mJacobianInv.transpose()* m * mJacobianInv; } Mat3d applyIJC(const Mat3d& in, const Vec3d& , const Vec3d& ) const { return applyIJC(in); } /// Return the determinant of the Jacobian, ignores argument double determinant(const Vec3d& ) const { return determinant(); } /// Return the determinant of the Jacobian double determinant() const { return mDeterminant; } //@{ /// @brief Return the lengths of the images of the segments /// (0,0,0)-(1,0,0), (0,0,0)-(0,1,0) and (0,0,0)-(0,0,1). Vec3d voxelSize() const { return mVoxelSize; } Vec3d voxelSize(const Vec3d&) const { return voxelSize(); } //@} /// Return @c true if the underlying matrix is approximately an identity bool isIdentity() const { return mIsIdentity; } /// Return @c true if the underylying matrix is diagonal bool isDiagonal() const { return mIsDiagonal; } /// Return @c true if the map is equivalent to a ScaleMap bool isScale() const { return isDiagonal(); } /// Return @c true if the map is equivalent to a ScaleTranslateMap bool isScaleTranslate() const { return math::isDiagonal(mMatrix.getMat3()); } // Methods that modify the existing affine map //@{ /// @brief Modify the existing affine map by pre-applying the given operation. void accumPreRotation(Axis axis, double radians) { mMatrix.preRotate(axis, radians); updateAcceleration(); } void accumPreScale(const Vec3d& v) { mMatrix.preScale(v); updateAcceleration(); } void accumPreTranslation(const Vec3d& v) { mMatrix.preTranslate(v); updateAcceleration(); } void accumPreShear(Axis axis0, Axis axis1, double shear) { mMatrix.preShear(axis0, axis1, shear); updateAcceleration(); } //@} //@{ /// @brief Modify the existing affine map by post-applying the given operation. void accumPostRotation(Axis axis, double radians) { mMatrix.postRotate(axis, radians); updateAcceleration(); } void accumPostScale(const Vec3d& v) { mMatrix.postScale(v); updateAcceleration(); } void accumPostTranslation(const Vec3d& v) { mMatrix.postTranslate(v); updateAcceleration(); } void accumPostShear(Axis axis0, Axis axis1, double shear) { mMatrix.postShear(axis0, axis1, shear); updateAcceleration(); } //@} /// read serialization void read(std::istream& is) { mMatrix.read(is); updateAcceleration(); } /// write serialization void write(std::ostream& os) const { mMatrix.write(os); } /// string serialization, useful for debugging std::string str() const { std::ostringstream buffer; buffer << " - mat4:\n" << mMatrix.str() << std::endl; buffer << " - voxel dimensions: " << mVoxelSize << std::endl; return buffer.str(); } /// on-demand decomposition of the affine map boost::shared_ptr createDecomposedMap() { return createFullyDecomposedMap(mMatrix); } /// Return AffineMap::Ptr to a deep copy of the current AffineMap AffineMap::Ptr getAffineMap() const { return AffineMap::Ptr(new AffineMap(*this)); } /// Return AffineMap::Ptr to the inverse of this map AffineMap::Ptr inverse() const { return AffineMap::Ptr(new AffineMap(mMatrixInv)); } //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the appropraite operation. MapBase::Ptr preRotate(double radians, Axis axis = X_AXIS) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr preTranslate(const Vec3d& t) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreTranslation(t); return boost::static_pointer_cast(affineMap); } MapBase::Ptr preScale(const Vec3d& s) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreScale(s); return boost::static_pointer_cast(affineMap); } MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreShear(axis0, axis1, shear); return simplify(affineMap); } //@} //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of postfixing the appropraite operation. MapBase::Ptr postRotate(double radians, Axis axis = X_AXIS) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr postTranslate(const Vec3d& t) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostTranslation(t); return boost::static_pointer_cast(affineMap); } MapBase::Ptr postScale(const Vec3d& s) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostScale(s); return boost::static_pointer_cast(affineMap); } MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostShear(axis0, axis1, shear); return simplify(affineMap); } //@} /// Return the matrix representation of this AffineMap Mat4d getMat4() const { return mMatrix;} const Mat4d& getConstMat4() const {return mMatrix;} const Mat3d& getConstJacobianInv() const {return mJacobianInv;} private: void updateAcceleration() { Mat3d mat3 = mMatrix.getMat3(); mDeterminant = mat3.det(); if (std::abs(mDeterminant) < (3.0 * math::Tolerance::value())) { OPENVDB_THROW(ArithmeticError, "Tried to initialize an affine transform from a nearly singular matrix"); } mMatrixInv = mMatrix.inverse(); mJacobianInv = mat3.inverse().transpose(); mIsDiagonal = math::isDiagonal(mMatrix); mIsIdentity = math::isIdentity(mMatrix); Vec3d pos = applyMap(Vec3d(0,0,0)); mVoxelSize(0) = (applyMap(Vec3d(1,0,0)) - pos).length(); mVoxelSize(1) = (applyMap(Vec3d(0,1,0)) - pos).length(); mVoxelSize(2) = (applyMap(Vec3d(0,0,1)) - pos).length(); } // the underlying matrix Mat4d mMatrix; // stored for acceleration Mat4d mMatrixInv; Mat3d mJacobianInv; double mDeterminant; Vec3d mVoxelSize; bool mIsDiagonal, mIsIdentity; }; // class AffineMap //////////////////////////////////////// /// @brief A specialized Affine transform that scales along the principal axis /// the scaling need not be uniform in the three-directions class OPENVDB_API ScaleMap: public MapBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; ScaleMap(): MapBase(), mScaleValues(Vec3d(1,1,1)), mVoxelSize(Vec3d(1,1,1)), mScaleValuesInverse(Vec3d(1,1,1)), mInvScaleSqr(1,1,1), mInvTwiceScale(0.5,0.5,0.5){} ScaleMap(const Vec3d& scale): MapBase(), mScaleValues(scale), mVoxelSize(Vec3d(std::abs(scale(0)),std::abs(scale(1)), std::abs(scale(2)))) { double determinant = scale[0]* scale[1] * scale[2]; if (std::abs(determinant) < 3.0 * math::Tolerance::value()) { OPENVDB_THROW(ArithmeticError, "Non-zero scale values required"); } mScaleValuesInverse = 1.0 / mScaleValues; mInvScaleSqr = mScaleValuesInverse * mScaleValuesInverse; mInvTwiceScale = mScaleValuesInverse / 2; } ScaleMap(const ScaleMap& other): MapBase(), mScaleValues(other.mScaleValues), mVoxelSize(other.mVoxelSize), mScaleValuesInverse(other.mScaleValuesInverse), mInvScaleSqr(other.mInvScaleSqr), mInvTwiceScale(other.mInvTwiceScale) { } ~ScaleMap() {} /// Return a MapBase::Ptr to a new ScaleMap static MapBase::Ptr create() { return MapBase::Ptr(new ScaleMap()); } /// Return a MapBase::Ptr to a deep copy of this map MapBase::Ptr copy() const { return MapBase::Ptr(new ScaleMap(*this)); } MapBase::Ptr inverseMap() const { return MapBase::Ptr(new ScaleMap(mScaleValuesInverse)); } static bool isRegistered() { return MapRegistry::isRegistered(ScaleMap::mapType()); } static void registerMap() { MapRegistry::registerMap( ScaleMap::mapType(), ScaleMap::create); } Name type() const { return mapType(); } static Name mapType() { return Name("ScaleMap"); } /// Return @c true (a ScaleMap is always linear). bool isLinear() const { return true; } /// Return @c true if the values have the same magitude (eg. -1, 1, -1 would be a rotation). bool hasUniformScale() const { bool value = isApproxEqual( std::abs(mScaleValues.x()), std::abs(mScaleValues.y()), double(5e-7)); value = value && isApproxEqual( std::abs(mScaleValues.x()), std::abs(mScaleValues.z()), double(5e-7)); return value; } /// Return the image of @c in under the map Vec3d applyMap(const Vec3d& in) const { return Vec3d( in.x() * mScaleValues.x(), in.y() * mScaleValues.y(), in.z() * mScaleValues.z()); } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const { return Vec3d( in.x() * mScaleValuesInverse.x(), in.y() * mScaleValuesInverse.y(), in.z() * mScaleValuesInverse.z()); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const { return applyMap(in); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const { return applyInverseMap(in); } /// Return the Jacobian Transpose of the map applied to @a in. /// This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const { return applyMap(in); } /// @brief Return the transpose of the inverse Jacobian of the map applied to @a in. /// @details Ignores second argument Vec3d applyIJT(const Vec3d& in, const Vec3d&) const { return applyIJT(in);} /// Return the transpose of the inverse Jacobian of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const { return applyInverseMap(in); } /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& in) const { Mat3d tmp; for (int i = 0; i < 3; i++) { tmp.setRow(i, in.row(i) * mScaleValuesInverse(i)); } for (int i = 0; i < 3; i++) { tmp.setCol(i, tmp.col(i) * mScaleValuesInverse(i)); } return tmp; } Mat3d applyIJC(const Mat3d& in, const Vec3d&, const Vec3d&) const { return applyIJC(in); } /// Return the product of the scale values, ignores argument double determinant(const Vec3d&) const { return determinant(); } /// Return the product of the scale values double determinant() const { return mScaleValues.x() * mScaleValues.y() * mScaleValues.z(); } /// Return the scale values that define the map const Vec3d& getScale() const {return mScaleValues;} /// Return the square of the scale. Used to optimize some finite difference calculations const Vec3d& getInvScaleSqr() const { return mInvScaleSqr; } /// Return 1/(2 scale). Used to optimize some finite difference calculations const Vec3d& getInvTwiceScale() const { return mInvTwiceScale; } /// Return 1/(scale) const Vec3d& getInvScale() const { return mScaleValuesInverse; } //@{ /// @brief Returns the lengths of the images /// of the segments /// \f$(0,0,0)-(1,0,0)\f$, \f$(0,0,0)-(0,1,0)\f$, \f$(0,0,0)-(0,0,1)\f$ /// this is equivalent to the absolute values of the scale values Vec3d voxelSize() const { return mVoxelSize; } Vec3d voxelSize(const Vec3d&) const { return voxelSize(); } //@} /// read serialization void read(std::istream& is) { mScaleValues.read(is); mVoxelSize.read(is); mScaleValuesInverse.read(is); mInvScaleSqr.read(is); mInvTwiceScale.read(is); } /// write serialization void write(std::ostream& os) const { mScaleValues.write(os); mVoxelSize.write(os); mScaleValuesInverse.write(os); mInvScaleSqr.write(os); mInvTwiceScale.write(os); } /// string serialization, useful for debuging std::string str() const { std::ostringstream buffer; buffer << " - scale: " << mScaleValues << std::endl; buffer << " - voxel dimensions: " << mVoxelSize << std::endl; return buffer.str(); } virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } bool operator==(const ScaleMap& other) const { // ::eq() uses a tolerance if (!mScaleValues.eq(other.mScaleValues)) { return false; } return true; } bool operator!=(const ScaleMap& other) const { return !(*this == other); } /// Return a AffineMap equivalent to this map AffineMap::Ptr getAffineMap() const { return AffineMap::Ptr(new AffineMap(math::scale(mScaleValues))); } //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the appropraite operation to the existing map MapBase::Ptr preRotate(double radians, Axis axis) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr preTranslate(const Vec3d& tr) const; MapBase::Ptr preScale(const Vec3d& v) const; MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreShear(axis0, axis1, shear); return simplify(affineMap); } //@} //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the appropraite operation to the existing map. MapBase::Ptr postRotate(double radians, Axis axis) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr postTranslate(const Vec3d& tr) const; MapBase::Ptr postScale(const Vec3d& v) const; MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostShear(axis0, axis1, shear); return simplify(affineMap); } //@} private: Vec3d mScaleValues, mVoxelSize, mScaleValuesInverse, mInvScaleSqr, mInvTwiceScale; }; // class ScaleMap /// @brief A specialized Affine transform that scales along the principal axis /// the scaling is uniform in the three-directions class OPENVDB_API UniformScaleMap: public ScaleMap { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; UniformScaleMap(): ScaleMap(Vec3d(1,1,1)) {} UniformScaleMap(double scale): ScaleMap(Vec3d(scale, scale, scale)) {} UniformScaleMap(const UniformScaleMap& other): ScaleMap(other) {} ~UniformScaleMap() {} /// Return a MapBase::Ptr to a new UniformScaleMap static MapBase::Ptr create() { return MapBase::Ptr(new UniformScaleMap()); } /// Return a MapBase::Ptr to a deep copy of this map MapBase::Ptr copy() const { return MapBase::Ptr(new UniformScaleMap(*this)); } MapBase::Ptr inverseMap() const { const Vec3d& invScale = getInvScale(); return MapBase::Ptr(new UniformScaleMap( invScale[0])); } static bool isRegistered() { return MapRegistry::isRegistered(UniformScaleMap::mapType()); } static void registerMap() { MapRegistry::registerMap( UniformScaleMap::mapType(), UniformScaleMap::create); } Name type() const { return mapType(); } static Name mapType() { return Name("UniformScaleMap"); } virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } bool operator==(const UniformScaleMap& other) const { return ScaleMap::operator==(other); } bool operator!=(const UniformScaleMap& other) const { return !(*this == other); } /// Return a MapBase::Ptr to a UniformScaleTraslateMap that is the result of /// pre-translation on this map MapBase::Ptr preTranslate(const Vec3d& tr) const; /// Return a MapBase::Ptr to a UniformScaleTraslateMap that is the result of /// post-translation on this map MapBase::Ptr postTranslate(const Vec3d& tr) const; }; // class UniformScaleMap //////////////////////////////////////// inline MapBase::Ptr ScaleMap::preScale(const Vec3d& v) const { const Vec3d new_scale(v * mScaleValues); if (isApproxEqual(new_scale[0],new_scale[1]) && isApproxEqual(new_scale[0],new_scale[2])) { return MapBase::Ptr(new UniformScaleMap(new_scale[0])); } else { return MapBase::Ptr(new ScaleMap(new_scale)); } } inline MapBase::Ptr ScaleMap::postScale(const Vec3d& v) const { // pre-post Scale are the same for a scale map return preScale(v); } /// @brief A specialized linear transform that performs a translation class OPENVDB_API TranslationMap: public MapBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; // default constructor is a translation by zero. TranslationMap(): MapBase(), mTranslation(Vec3d(0,0,0)) {} TranslationMap(const Vec3d& t): MapBase(), mTranslation(t) {} TranslationMap(const TranslationMap& other): MapBase(), mTranslation(other.mTranslation) {} ~TranslationMap() {} /// Return a MapBase::Ptr to a new TranslationMap static MapBase::Ptr create() { return MapBase::Ptr(new TranslationMap()); } /// Return a MapBase::Ptr to a deep copy of this map MapBase::Ptr copy() const { return MapBase::Ptr(new TranslationMap(*this)); } MapBase::Ptr inverseMap() const { return MapBase::Ptr(new TranslationMap(-mTranslation)); } static bool isRegistered() { return MapRegistry::isRegistered(TranslationMap::mapType()); } static void registerMap() { MapRegistry::registerMap( TranslationMap::mapType(), TranslationMap::create); } Name type() const { return mapType(); } static Name mapType() { return Name("TranslationMap"); } /// Return @c true (a TranslationMap is always linear). bool isLinear() const { return true; } /// Return @c false (by convention true) bool hasUniformScale() const { return true; } /// Return the image of @c in under the map Vec3d applyMap(const Vec3d& in) const { return in + mTranslation; } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const { return in - mTranslation; } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const { return in; } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const { return in; } /// Return the Jacobian Transpose of the map applied to @a in. /// This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const { return in; } /// @brief Return the transpose of the inverse Jacobian (Identity for TranslationMap) /// of the map applied to @c in, ignores second argument Vec3d applyIJT(const Vec3d& in, const Vec3d& ) const { return applyIJT(in);} /// @brief Return the transpose of the inverse Jacobian (Identity for TranslationMap) /// of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const {return in;} /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& mat) const {return mat;} Mat3d applyIJC(const Mat3d& mat, const Vec3d&, const Vec3d&) const { return applyIJC(mat); } /// Return @c 1 double determinant(const Vec3d& ) const { return determinant(); } /// Return @c 1 double determinant() const { return 1.0; } /// Return \f$ (1,1,1) \f$ Vec3d voxelSize() const { return Vec3d(1,1,1);} /// Return \f$ (1,1,1) \f$ Vec3d voxelSize(const Vec3d&) const { return voxelSize();} /// Return the translation vector const Vec3d& getTranslation() const { return mTranslation; } /// read serialization void read(std::istream& is) { mTranslation.read(is); } /// write serialization void write(std::ostream& os) const { mTranslation.write(os); } /// string serialization, useful for debuging std::string str() const { std::ostringstream buffer; buffer << " - translation: " << mTranslation << std::endl; return buffer.str(); } virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } bool operator==(const TranslationMap& other) const { // ::eq() uses a tolerance return mTranslation.eq(other.mTranslation); } bool operator!=(const TranslationMap& other) const { return !(*this == other); } /// Return AffineMap::Ptr to an AffineMap equivalent to *this AffineMap::Ptr getAffineMap() const { Mat4d matrix(Mat4d::identity()); matrix.setTranslation(mTranslation); AffineMap::Ptr affineMap(new AffineMap(matrix)); return affineMap; } //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the appropriate operation. MapBase::Ptr preRotate(double radians, Axis axis) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr preTranslate(const Vec3d& t) const { return MapBase::Ptr(new TranslationMap(t + mTranslation)); } MapBase::Ptr preScale(const Vec3d& v) const; MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreShear(axis0, axis1, shear); return simplify(affineMap); } //@} //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of postfixing the appropriate operation. MapBase::Ptr postRotate(double radians, Axis axis) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr postTranslate(const Vec3d& t) const { // post and pre are the same for this return MapBase::Ptr(new TranslationMap(t + mTranslation)); } MapBase::Ptr postScale(const Vec3d& v) const; MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostShear(axis0, axis1, shear); return simplify(affineMap); } //@} private: Vec3d mTranslation; }; // class TranslationMap //////////////////////////////////////// /// @brief A specialized Affine transform that scales along the principal axis /// the scaling need not be uniform in the three-directions, and then /// translates the result. class OPENVDB_API ScaleTranslateMap: public MapBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; ScaleTranslateMap(): MapBase(), mTranslation(Vec3d(0,0,0)), mScaleValues(Vec3d(1,1,1)), mVoxelSize(Vec3d(1,1,1)), mScaleValuesInverse(Vec3d(1,1,1)), mInvScaleSqr(1,1,1), mInvTwiceScale(0.5,0.5,0.5) { } ScaleTranslateMap(const Vec3d& scale, const Vec3d& translate): MapBase(), mTranslation(translate), mScaleValues(scale), mVoxelSize(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2))) { const double determinant = scale[0]* scale[1] * scale[2]; if (std::abs(determinant) < 3.0 * math::Tolerance::value()) { OPENVDB_THROW(ArithmeticError, "Non-zero scale values required"); } mScaleValuesInverse = 1.0 / mScaleValues; mInvScaleSqr = mScaleValuesInverse * mScaleValuesInverse; mInvTwiceScale = mScaleValuesInverse / 2; } ScaleTranslateMap(const ScaleMap& scale, const TranslationMap& translate): MapBase(), mTranslation(translate.getTranslation()), mScaleValues(scale.getScale()), mVoxelSize(std::abs(mScaleValues(0)), std::abs(mScaleValues(1)), std::abs(mScaleValues(2))), mScaleValuesInverse(1.0 / scale.getScale()) { mInvScaleSqr = mScaleValuesInverse * mScaleValuesInverse; mInvTwiceScale = mScaleValuesInverse / 2; } ScaleTranslateMap(const ScaleTranslateMap& other): MapBase(), mTranslation(other.mTranslation), mScaleValues(other.mScaleValues), mVoxelSize(other.mVoxelSize), mScaleValuesInverse(other.mScaleValuesInverse), mInvScaleSqr(other.mInvScaleSqr), mInvTwiceScale(other.mInvTwiceScale) {} ~ScaleTranslateMap() {} /// Return a MapBase::Ptr to a new ScaleTranslateMap static MapBase::Ptr create() { return MapBase::Ptr(new ScaleTranslateMap()); } /// Return a MapBase::Ptr to a deep copy of this map MapBase::Ptr copy() const { return MapBase::Ptr(new ScaleTranslateMap(*this)); } MapBase::Ptr inverseMap() const { return MapBase::Ptr(new ScaleTranslateMap( mScaleValuesInverse, -mScaleValuesInverse * mTranslation)); } static bool isRegistered() { return MapRegistry::isRegistered(ScaleTranslateMap::mapType()); } static void registerMap() { MapRegistry::registerMap( ScaleTranslateMap::mapType(), ScaleTranslateMap::create); } Name type() const { return mapType(); } static Name mapType() { return Name("ScaleTranslateMap"); } /// Return @c true (a ScaleTranslateMap is always linear). bool isLinear() const { return true; } /// @brief Return @c true if the scale values have the same magnitude /// (eg. -1, 1, -1 would be a rotation). bool hasUniformScale() const { bool value = isApproxEqual( std::abs(mScaleValues.x()), std::abs(mScaleValues.y()), double(5e-7)); value = value && isApproxEqual( std::abs(mScaleValues.x()), std::abs(mScaleValues.z()), double(5e-7)); return value; } /// Return the image of @c under the map Vec3d applyMap(const Vec3d& in) const { return Vec3d( in.x() * mScaleValues.x() + mTranslation.x(), in.y() * mScaleValues.y() + mTranslation.y(), in.z() * mScaleValues.z() + mTranslation.z()); } /// Return the pre-image of @c under the map Vec3d applyInverseMap(const Vec3d& in) const { return Vec3d( (in.x() - mTranslation.x() ) * mScaleValuesInverse.x(), (in.y() - mTranslation.y() ) * mScaleValuesInverse.y(), (in.z() - mTranslation.z() ) * mScaleValuesInverse.z()); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const { return in * mScaleValues; } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const { return in * mScaleValuesInverse; } /// Return the Jacobian Transpose of the map applied to @a in. /// This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const { return applyJacobian(in); } /// @brief Return the transpose of the inverse Jacobian of the map applied to @a in /// @details Ignores second argument Vec3d applyIJT(const Vec3d& in, const Vec3d& ) const { return applyIJT(in);} /// Return the transpose of the inverse Jacobian of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const { return Vec3d( in.x() * mScaleValuesInverse.x(), in.y() * mScaleValuesInverse.y(), in.z() * mScaleValuesInverse.z()); } /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& in) const { Mat3d tmp; for (int i=0; i<3; i++){ tmp.setRow(i, in.row(i)*mScaleValuesInverse(i)); } for (int i=0; i<3; i++){ tmp.setCol(i, tmp.col(i)*mScaleValuesInverse(i)); } return tmp; } Mat3d applyIJC(const Mat3d& in, const Vec3d&, const Vec3d& ) const { return applyIJC(in); } /// Return the product of the scale values, ignores argument double determinant(const Vec3d& ) const { return determinant(); } /// Return the product of the scale values double determinant() const { return mScaleValues.x()*mScaleValues.y()*mScaleValues.z(); } /// Return the absolute values of the scale values Vec3d voxelSize() const { return mVoxelSize;} /// Return the absolute values of the scale values, ignores ///argument Vec3d voxelSize(const Vec3d&) const { return voxelSize();} /// Returns the scale values const Vec3d& getScale() const { return mScaleValues; } /// Returns the translation const Vec3d& getTranslation() const { return mTranslation; } /// Return the square of the scale. Used to optimize some finite difference calculations const Vec3d& getInvScaleSqr() const {return mInvScaleSqr;} /// Return 1/(2 scale). Used to optimize some finite difference calculations const Vec3d& getInvTwiceScale() const {return mInvTwiceScale;} /// Return 1/(scale) const Vec3d& getInvScale() const {return mScaleValuesInverse; } /// read serialization void read(std::istream& is) { mTranslation.read(is); mScaleValues.read(is); mVoxelSize.read(is); mScaleValuesInverse.read(is); mInvScaleSqr.read(is); mInvTwiceScale.read(is); } /// write serialization void write(std::ostream& os) const { mTranslation.write(os); mScaleValues.write(os); mVoxelSize.write(os); mScaleValuesInverse.write(os); mInvScaleSqr.write(os); mInvTwiceScale.write(os); } /// string serialization, useful for debuging std::string str() const { std::ostringstream buffer; buffer << " - translation: " << mTranslation << std::endl; buffer << " - scale: " << mScaleValues << std::endl; buffer << " - voxel dimensions: " << mVoxelSize << std::endl; return buffer.str(); } virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } bool operator==(const ScaleTranslateMap& other) const { // ::eq() uses a tolerance if (!mScaleValues.eq(other.mScaleValues)) { return false; } if (!mTranslation.eq(other.mTranslation)) { return false; } return true; } bool operator!=(const ScaleTranslateMap& other) const { return !(*this == other); } /// Return AffineMap::Ptr to an AffineMap equivalent to *this AffineMap::Ptr getAffineMap() const { AffineMap::Ptr affineMap(new AffineMap(math::scale(mScaleValues))); affineMap->accumPostTranslation(mTranslation); return affineMap; } //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the appropraite operation. MapBase::Ptr preRotate(double radians, Axis axis) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr preTranslate(const Vec3d& t) const { const Vec3d& s = mScaleValues; const Vec3d scaled_trans( t.x() * s.x(), t.y() * s.y(), t.z() * s.z() ); return MapBase::Ptr( new ScaleTranslateMap(mScaleValues, mTranslation + scaled_trans)); } MapBase::Ptr preScale(const Vec3d& v) const; MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreShear(axis0, axis1, shear); return simplify(affineMap); } //@} //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of postfixing the appropraite operation. MapBase::Ptr postRotate(double radians, Axis axis) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr postTranslate(const Vec3d& t) const { return MapBase::Ptr( new ScaleTranslateMap(mScaleValues, mTranslation + t)); } MapBase::Ptr postScale(const Vec3d& v) const; MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostShear(axis0, axis1, shear); return simplify(affineMap); } //@} private: Vec3d mTranslation, mScaleValues, mVoxelSize, mScaleValuesInverse, mInvScaleSqr, mInvTwiceScale; }; // class ScaleTanslateMap inline MapBase::Ptr ScaleMap::postTranslate(const Vec3d& t) const { return MapBase::Ptr(new ScaleTranslateMap(mScaleValues, t)); } inline MapBase::Ptr ScaleMap::preTranslate(const Vec3d& t) const { const Vec3d& s = mScaleValues; const Vec3d scaled_trans( t.x() * s.x(), t.y() * s.y(), t.z() * s.z() ); return MapBase::Ptr(new ScaleTranslateMap(mScaleValues, scaled_trans)); } /// @brief A specialized Affine transform that uniformaly scales along the principal axis /// and then translates the result. class OPENVDB_API UniformScaleTranslateMap: public ScaleTranslateMap { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; UniformScaleTranslateMap():ScaleTranslateMap(Vec3d(1,1,1), Vec3d(0,0,0)) {} UniformScaleTranslateMap(double scale, const Vec3d& translate): ScaleTranslateMap(Vec3d(scale,scale,scale), translate) {} UniformScaleTranslateMap(const UniformScaleMap& scale, const TranslationMap& translate): ScaleTranslateMap(scale.getScale(), translate.getTranslation()) {} UniformScaleTranslateMap(const UniformScaleTranslateMap& other):ScaleTranslateMap(other) {} ~UniformScaleTranslateMap() {} /// Return a MapBase::Ptr to a new UniformScaleTranslateMap static MapBase::Ptr create() { return MapBase::Ptr(new UniformScaleTranslateMap()); } /// Return a MapBase::Ptr to a deep copy of this map MapBase::Ptr copy() const { return MapBase::Ptr(new UniformScaleTranslateMap(*this)); } MapBase::Ptr inverseMap() const { const Vec3d& scaleInv = getInvScale(); const Vec3d& trans = getTranslation(); return MapBase::Ptr(new UniformScaleTranslateMap(scaleInv[0], -scaleInv[0] * trans)); } static bool isRegistered() { return MapRegistry::isRegistered(UniformScaleTranslateMap::mapType()); } static void registerMap() { MapRegistry::registerMap( UniformScaleTranslateMap::mapType(), UniformScaleTranslateMap::create); } Name type() const { return mapType(); } static Name mapType() { return Name("UniformScaleTranslateMap"); } virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } bool operator==(const UniformScaleTranslateMap& other) const { return ScaleTranslateMap::operator==(other); } bool operator!=(const UniformScaleTranslateMap& other) const { return !(*this == other); } /// @brief Return a MapBase::Ptr to a UniformScaleTranslateMap that is /// the result of prepending translation on this map. MapBase::Ptr preTranslate(const Vec3d& t) const { const double scale = this->getScale().x(); const Vec3d new_trans = this->getTranslation() + scale * t; return MapBase::Ptr( new UniformScaleTranslateMap(scale, new_trans)); } /// @brief Return a MapBase::Ptr to a UniformScaleTranslateMap that is /// the result of postfixing translation on this map. MapBase::Ptr postTranslate(const Vec3d& t) const { const double scale = this->getScale().x(); return MapBase::Ptr( new UniformScaleTranslateMap(scale, this->getTranslation() + t)); } }; // class UniformScaleTanslateMap inline MapBase::Ptr UniformScaleMap::postTranslate(const Vec3d& t) const { const double scale = this->getScale().x(); return MapBase::Ptr(new UniformScaleTranslateMap(scale, t)); } inline MapBase::Ptr UniformScaleMap::preTranslate(const Vec3d& t) const { const double scale = this->getScale().x(); return MapBase::Ptr(new UniformScaleTranslateMap(scale, scale*t)); } inline MapBase::Ptr TranslationMap::preScale(const Vec3d& v) const { if (isApproxEqual(v[0],v[1]) && isApproxEqual(v[0],v[2])) { return MapBase::Ptr(new UniformScaleTranslateMap(v[0], mTranslation)); } else { return MapBase::Ptr(new ScaleTranslateMap(v, mTranslation)); } } inline MapBase::Ptr TranslationMap::postScale(const Vec3d& v) const { if (isApproxEqual(v[0],v[1]) && isApproxEqual(v[0],v[2])) { return MapBase::Ptr(new UniformScaleTranslateMap(v[0], v[0]*mTranslation)); } else { const Vec3d trans(mTranslation.x()*v.x(), mTranslation.y()*v.y(), mTranslation.z()*v.z()); return MapBase::Ptr(new ScaleTranslateMap(v, trans)); } } inline MapBase::Ptr ScaleTranslateMap::preScale(const Vec3d& v) const { const Vec3d new_scale( v * mScaleValues ); if (isApproxEqual(new_scale[0],new_scale[1]) && isApproxEqual(new_scale[0],new_scale[2])) { return MapBase::Ptr( new UniformScaleTranslateMap(new_scale[0], mTranslation)); } else { return MapBase::Ptr( new ScaleTranslateMap(new_scale, mTranslation)); } } inline MapBase::Ptr ScaleTranslateMap::postScale(const Vec3d& v) const { const Vec3d new_scale( v * mScaleValues ); const Vec3d new_trans( mTranslation.x()*v.x(), mTranslation.y()*v.y(), mTranslation.z()*v.z() ); if (isApproxEqual(new_scale[0],new_scale[1]) && isApproxEqual(new_scale[0],new_scale[2])) { return MapBase::Ptr( new UniformScaleTranslateMap(new_scale[0], new_trans)); } else { return MapBase::Ptr( new ScaleTranslateMap(new_scale, new_trans)); } } //////////////////////////////////////// /// @brief A specialized linear transform that performs a unitary maping /// i.e. rotation and or reflection. class OPENVDB_API UnitaryMap: public MapBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; /// default constructor makes an Idenity. UnitaryMap(): mAffineMap(Mat4d::identity()) { } UnitaryMap(const Vec3d& axis, double radians) { Mat3d matrix; matrix.setToRotation(axis, radians); mAffineMap = AffineMap(matrix); } UnitaryMap(Axis axis, double radians) { Mat4d matrix; matrix.setToRotation(axis, radians); mAffineMap = AffineMap(matrix); } UnitaryMap(const Mat3d& m) { // test that the mat3 is a rotation || reflection if (!isUnitary(m)) { OPENVDB_THROW(ArithmeticError, "Matrix initializing unitary map was not unitary"); } Mat4d matrix(Mat4d::identity()); matrix.setMat3(m); mAffineMap = AffineMap(matrix); } UnitaryMap(const Mat4d& m) { if (!isInvertible(m)) { OPENVDB_THROW(ArithmeticError, "4x4 Matrix initializing unitary map was not unitary: not invertible"); } if (!isAffine(m)) { OPENVDB_THROW(ArithmeticError, "4x4 Matrix initializing unitary map was not unitary: not affine"); } if (hasTranslation(m)) { OPENVDB_THROW(ArithmeticError, "4x4 Matrix initializing unitary map was not unitary: had translation"); } if (!isUnitary(m.getMat3())) { OPENVDB_THROW(ArithmeticError, "4x4 Matrix initializing unitary map was not unitary"); } mAffineMap = AffineMap(m); } UnitaryMap(const UnitaryMap& other): MapBase(other), mAffineMap(other.mAffineMap) { } UnitaryMap(const UnitaryMap& first, const UnitaryMap& second): mAffineMap(*(first.getAffineMap()), *(second.getAffineMap())) { } ~UnitaryMap() {} /// Return a MapBase::Ptr to a new UnitaryMap static MapBase::Ptr create() { return MapBase::Ptr(new UnitaryMap()); } /// Returns a MapBase::Ptr to a deep copy of *this MapBase::Ptr copy() const { return MapBase::Ptr(new UnitaryMap(*this)); } MapBase::Ptr inverseMap() const { return MapBase::Ptr(new UnitaryMap(mAffineMap.getMat4().inverse())); } static bool isRegistered() { return MapRegistry::isRegistered(UnitaryMap::mapType()); } static void registerMap() { MapRegistry::registerMap( UnitaryMap::mapType(), UnitaryMap::create); } /// Return @c UnitaryMap Name type() const { return mapType(); } /// Return @c UnitaryMap static Name mapType() { return Name("UnitaryMap"); } /// Return @c true (a UnitaryMap is always linear). bool isLinear() const { return true; } /// Return @c false (by convention true) bool hasUniformScale() const { return true; } virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } bool operator==(const UnitaryMap& other) const { // compare underlying linear map. if (mAffineMap!=other.mAffineMap) return false; return true; } bool operator!=(const UnitaryMap& other) const { return !(*this == other); } /// Return the image of @c in under the map Vec3d applyMap(const Vec3d& in) const { return mAffineMap.applyMap(in); } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const { return mAffineMap.applyInverseMap(in); } Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const { return mAffineMap.applyJacobian(in); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d&) const { return applyInverseJacobian(in); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const { return mAffineMap.applyInverseJacobian(in); } /// Return the Jacobian Transpose of the map applied to @a in. /// This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const { // The transpose of the unitary map is its inverse return applyInverseMap(in); } /// @brief Return the transpose of the inverse Jacobian of the map applied to @a in /// @details Ignores second argument Vec3d applyIJT(const Vec3d& in, const Vec3d& ) const { return applyIJT(in);} /// Return the transpose of the inverse Jacobian of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const { return mAffineMap.applyIJT(in); } /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& in) const { return mAffineMap.applyIJC(in); } Mat3d applyIJC(const Mat3d& in, const Vec3d&, const Vec3d& ) const { return applyIJC(in); } /// Return the determinant of the Jacobian, ignores argument double determinant(const Vec3d& ) const { return determinant(); } /// Return the determinant of the Jacobian double determinant() const { return mAffineMap.determinant(); } /// @brief Returns the lengths of the images /// of the segments /// \f$(0,0,0)-(1,0,0)\f$, \f$(0,0,0)-(0,1,0)\f$, /// \f$(0,0,0)-(0,0,1)\f$ Vec3d voxelSize() const { return mAffineMap.voxelSize();} Vec3d voxelSize(const Vec3d&) const { return voxelSize();} /// read serialization void read(std::istream& is) { mAffineMap.read(is); } /// write serialization void write(std::ostream& os) const { mAffineMap.write(os); } /// string serialization, useful for debuging std::string str() const { std::ostringstream buffer; buffer << mAffineMap.str(); return buffer.str(); } /// Return AffineMap::Ptr to an AffineMap equivalent to *this AffineMap::Ptr getAffineMap() const { return AffineMap::Ptr(new AffineMap(mAffineMap)); } //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the appropraite operation. MapBase::Ptr preRotate(double radians, Axis axis) const { UnitaryMap first(axis, radians); UnitaryMap::Ptr unitaryMap(new UnitaryMap(first, *this)); return boost::static_pointer_cast(unitaryMap); } MapBase::Ptr preTranslate(const Vec3d& t) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreTranslation(t); return simplify(affineMap); } MapBase::Ptr preScale(const Vec3d& v) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreScale(v); return simplify(affineMap); } MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreShear(axis0, axis1, shear); return simplify(affineMap); } //@} //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of postfixing the appropraite operation. MapBase::Ptr postRotate(double radians, Axis axis) const { UnitaryMap second(axis, radians); UnitaryMap::Ptr unitaryMap(new UnitaryMap(*this, second)); return boost::static_pointer_cast(unitaryMap); } MapBase::Ptr postTranslate(const Vec3d& t) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostTranslation(t); return simplify(affineMap); } MapBase::Ptr postScale(const Vec3d& v) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostScale(v); return simplify(affineMap); } MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostShear(axis0, axis1, shear); return simplify(affineMap); } //@} private: AffineMap mAffineMap; }; // class UnitaryMap //////////////////////////////////////// /// @brief This map is composed of three steps. /// Frist it will take a box of size (Lx X Ly X Lz) defined by an member data bounding box /// and map it into a frustum with near plane (1 X Ly/Lx) and precribed depth /// Then this frustum is transformed by an internal second map: most often a uniform scale, /// but other affects can be achieved by accumulating translation, shear and rotation: these /// are all applied to the second map class OPENVDB_API NonlinearFrustumMap: public MapBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; NonlinearFrustumMap(): MapBase(), mBBox(Vec3d(0), Vec3d(1)), mTaper(1), mDepth(1) { init(); } /// @brief Constructor that takes an index-space bounding box /// to be mapped into a frustum with a given @a depth and @a taper /// (defined as ratio of nearplane/farplane). NonlinearFrustumMap(const BBoxd& bb, double taper, double depth): MapBase(),mBBox(bb), mTaper(taper), mDepth(depth) { init(); } /// @brief Constructor that takes an index-space bounding box /// to be mapped into a frustum with a given @a depth and @a taper /// (defined as ratio of nearplane/farplane). /// @details This frustum is further modifed by the @a secondMap, /// intended to be a simple translation and rotation and uniform scale NonlinearFrustumMap(const BBoxd& bb, double taper, double depth, const MapBase::Ptr& secondMap): mBBox(bb), mTaper(taper), mDepth(depth) { if (!secondMap->isLinear() ) { OPENVDB_THROW(ArithmeticError, "The second map in the Frustum transfrom must be linear"); } mSecondMap = *( secondMap->getAffineMap() ); init(); } NonlinearFrustumMap(const NonlinearFrustumMap& other): MapBase(), mBBox(other.mBBox), mTaper(other.mTaper), mDepth(other.mDepth), mSecondMap(other.mSecondMap), mHasSimpleAffine(other.mHasSimpleAffine) { init(); } /// @brief Constructor from a camera frustum /// /// @param position the tip of the frustum (i.e., the camera's position). /// @param direction a vector pointing from @a position toward the near plane. /// @param up a non-unit vector describing the direction and extent of /// the frustum's intersection on the near plane. Together, /// @a up must be orthogonal to @a direction. /// @param aspect the aspect ratio of the frustum intersection with near plane /// defined as width / height /// @param z_near,depth the distance from @a position along @a direction to the /// near and far planes of the frustum. /// @param x_count the number of voxels, aligned with @a left, /// across the face of the frustum /// @param z_count the number of voxels, aligned with @a direction, /// between the near and far planes NonlinearFrustumMap(const Vec3d& position, const Vec3d& direction, const Vec3d& up, double aspect /* width / height */, double z_near, double depth, Coord::ValueType x_count, Coord::ValueType z_count) { /// @todo check that depth > 0 /// @todo check up.length > 0 /// @todo check that direction dot up = 0 if (!(depth > 0)) { OPENVDB_THROW(ArithmeticError, "The frustum depth must be non-zero and positive"); } if (!(up.length() > 0)) { OPENVDB_THROW(ArithmeticError, "The frustum height must be non-zero and positive"); } if (!(aspect > 0)) { OPENVDB_THROW(ArithmeticError, "The frustum aspect ratio must be non-zero and positive"); } if (!(isApproxEqual(up.dot(direction), 0.))) { OPENVDB_THROW(ArithmeticError, "The frustum up orientation must be perpendicular to into-frustum direction"); } double near_plane_height = 2 * up.length(); double near_plane_width = aspect * near_plane_height; Coord::ValueType y_count = static_cast(Round(x_count / aspect)); mBBox = BBoxd(Vec3d(0,0,0), Vec3d(x_count, y_count, z_count)); mDepth = depth / near_plane_width; // depth non-dimensionalized on width double gamma = near_plane_width / z_near; mTaper = 1./(mDepth*gamma + 1.); Vec3d direction_unit = direction; direction_unit.normalize(); Mat4d r1(Mat4d::identity()); r1.setToRotation(/*from*/Vec3d(0,0,1), /*to */direction_unit); Mat4d r2(Mat4d::identity()); Vec3d temp = r1.inverse().transform(up); r2.setToRotation(/*from*/Vec3d(0,1,0), /*to*/temp ); Mat4d scale = math::scale( Vec3d(near_plane_width, near_plane_width, near_plane_width)); // move the near plane to origin, rotate to align with axis, and scale down // T_inv * R1_inv * R2_inv * scale_inv Mat4d mat = scale * r2 * r1; mat.setTranslation(position + z_near*direction_unit); mSecondMap = AffineMap(mat); init(); } ~NonlinearFrustumMap(){} /// Return a MapBase::Ptr to a new NonlinearFrustumMap static MapBase::Ptr create() { return MapBase::Ptr(new NonlinearFrustumMap()); } /// Return a MapBase::Ptr to a deep copy of this map MapBase::Ptr copy() const { return MapBase::Ptr(new NonlinearFrustumMap(*this)); } /// @brief Not implemented, since there is currently no map type that can /// represent the inverse of a frustum /// @throw NotImplementedError MapBase::Ptr inverseMap() const { OPENVDB_THROW(NotImplementedError, "inverseMap() is not implemented for NonlinearFrustumMap"); } static bool isRegistered() { return MapRegistry::isRegistered(NonlinearFrustumMap::mapType()); } static void registerMap() { MapRegistry::registerMap( NonlinearFrustumMap::mapType(), NonlinearFrustumMap::create); } /// Return @c NonlinearFrustumMap Name type() const { return mapType(); } /// Return @c NonlinearFrustumMap static Name mapType() { return Name("NonlinearFrustumMap"); } /// Return @c false (a NonlinearFrustumMap is never linear). bool isLinear() const { return false; } /// Return @c false (by convention false) bool hasUniformScale() const { return false; } /// Return @c true if the map is equivalent to an identity bool isIdentity() const { // The frustum can only be consistent with a linear map if the taper value is 1 if (!isApproxEqual(mTaper, double(1)) ) return false; // There are various ways an identity can decomposed between the two parts of the // map. Best to just check that the principle vectors are stationary. const Vec3d e1(1,0,0); if (!applyMap(e1).eq(e1)) return false; const Vec3d e2(0,1,0); if (!applyMap(e2).eq(e2)) return false; const Vec3d e3(0,0,1); if (!applyMap(e3).eq(e3)) return false; return true; } virtual bool isEqual(const MapBase& other) const { return isEqualBase(*this, other); } bool operator==(const NonlinearFrustumMap& other) const { if (mBBox!=other.mBBox) return false; if (!isApproxEqual(mTaper, other.mTaper)) return false; if (!isApproxEqual(mDepth, other.mDepth)) return false; // Two linear transforms are equivalent iff they have the same translation // and have the same affects on orthongal spanning basis check translation Vec3d e(0,0,0); if (!mSecondMap.applyMap(e).eq(other.mSecondMap.applyMap(e))) return false; /// check spanning vectors e(0) = 1; if (!mSecondMap.applyMap(e).eq(other.mSecondMap.applyMap(e))) return false; e(0) = 0; e(1) = 1; if (!mSecondMap.applyMap(e).eq(other.mSecondMap.applyMap(e))) return false; e(1) = 0; e(2) = 1; if (!mSecondMap.applyMap(e).eq(other.mSecondMap.applyMap(e))) return false; return true; } bool operator!=(const NonlinearFrustumMap& other) const { return !(*this == other); } /// Return the image of @c in under the map Vec3d applyMap(const Vec3d& in) const { return mSecondMap.applyMap(applyFrustumMap(in)); } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const { return applyFrustumInverseMap(mSecondMap.applyInverseMap(in)); } /// Return the Jacobian of the linear second map applied to @c in Vec3d applyJacobian(const Vec3d& in) const { return mSecondMap.applyJacobian(in); } /// Return the Jacobian defined at @c isloc applied to @c in Vec3d applyJacobian(const Vec3d& in, const Vec3d& isloc) const { // Move the center of the x-face of the bbox // to the origin in index space. Vec3d centered(isloc); centered = centered - mBBox.min(); centered.x() -= mXo; centered.y() -= mYo; // scale the z-direction on depth / K count const double zprime = centered.z()*mDepthOnLz; const double scale = (mGamma * zprime + 1.) / mLx; const double scale2 = mGamma * mDepthOnLz / mLx; const Vec3d tmp(scale * in.x() + scale2 * centered.x()* in.z(), scale * in.y() + scale2 * centered.y()* in.z(), mDepthOnLz * in.z()); return mSecondMap.applyJacobian(tmp); } /// Return the Inverse Jacobian of the map applied to @a in. (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const { return mSecondMap.applyInverseJacobian(in); } /// Return the Inverse Jacobian defined at @c isloc of the map applied to @a in. Vec3d applyInverseJacobian(const Vec3d& in, const Vec3d& isloc) const { // Move the center of the x-face of the bbox // to the origin in index space. Vec3d centered(isloc); centered = centered - mBBox.min(); centered.x() -= mXo; centered.y() -= mYo; // scale the z-direction on depth / K count const double zprime = centered.z()*mDepthOnLz; const double scale = (mGamma * zprime + 1.) / mLx; const double scale2 = mGamma * mDepthOnLz / mLx; Vec3d out = mSecondMap.applyInverseJacobian(in); out.x() = (out.x() - scale2 * centered.x() * out.z() / mDepthOnLz) / scale; out.y() = (out.y() - scale2 * centered.y() * out.z() / mDepthOnLz) / scale; out.z() = out.z() / mDepthOnLz; return out; } /// Return the Jacobian Transpose of the map applied to vector @c in at @c indexloc. /// This tranforms range-space gradients to domain-space gradients. /// Vec3d applyJT(const Vec3d& in, const Vec3d& isloc) const { const Vec3d tmp = mSecondMap.applyJT(in); // Move the center of the x-face of the bbox // to the origin in index space. Vec3d centered(isloc); centered = centered - mBBox.min(); centered.x() -= mXo; centered.y() -= mYo; // scale the z-direction on depth / K count const double zprime = centered.z()*mDepthOnLz; const double scale = (mGamma * zprime + 1.) / mLx; const double scale2 = mGamma * mDepthOnLz / mLx; return Vec3d(scale * tmp.x(), scale * tmp.y(), scale2 * centered.x()* tmp.x() + scale2 * centered.y()* tmp.y() + mDepthOnLz * tmp.z()); } /// Return the Jacobian Transpose of the second map applied to @c in. Vec3d applyJT(const Vec3d& in) const { return mSecondMap.applyJT(in); } /// Return the transpose of the inverse Jacobian of the linear second map applied to @c in Vec3d applyIJT(const Vec3d& in) const { return mSecondMap.applyIJT(in); } // the Jacobian of the nonlinear part of the transform is a sparse matrix // Jacobian^(-T) = // // (Lx)( 1/s 0 0 ) // ( 0 1/s 0 ) // ( -(x-xo)g/(sLx) -(y-yo)g/(sLx) Lz/(Depth Lx) ) /// Return the transpose of the inverse Jacobain (at @c locW applied to @c in. /// @c ijk is the location in the pre-image space (e.g. index space) Vec3d applyIJT(const Vec3d& d1_is, const Vec3d& ijk) const { const Vec3d loc = applyFrustumMap(ijk); const double s = mGamma * loc.z() + 1.; // verify that we aren't at the singularity if (isApproxEqual(s, 0.)) { OPENVDB_THROW(ArithmeticError, "Tried to evaluate the frustum transform" " at the singular focal point (e.g. camera)"); } const double sinv = 1.0/s; // 1/(z*gamma + 1) const double pt0 = mLx * sinv; // Lx / (z*gamma +1) const double pt1 = mGamma * pt0; // gamma * Lx / ( z*gamma +1) const double pt2 = pt1 * sinv; // gamma * Lx / ( z*gamma +1)**2 const Mat3d& jacinv = mSecondMap.getConstJacobianInv(); // compute \frac{\partial E_i}{\partial x_j} Mat3d gradE(Mat3d::zero()); for (int j = 0; j < 3; ++j ) { gradE(0,j) = pt0 * jacinv(0,j) - pt2 * loc.x()*jacinv(2,j); gradE(1,j) = pt0 * jacinv(1,j) - pt2 * loc.y()*jacinv(2,j); gradE(2,j) = (1./mDepthOnLz) * jacinv(2,j); } Vec3d result; for (int i = 0; i < 3; ++i) { result(0) = d1_is(0) * gradE(0,i) + d1_is(1) * gradE(1,i) + d1_is(2) * gradE(2,i); } return result; } /// Return the Jacobian Curvature for the linear second map Mat3d applyIJC(const Mat3d& in) const { return mSecondMap.applyIJC(in); } /// Return the Jacobian Curvature: all the second derivatives in range space /// @param d2_is second derivative matrix computed in index space /// @param d1_is gradient computed in index space /// @param ijk the index space location where the result is computed Mat3d applyIJC(const Mat3d& d2_is, const Vec3d& d1_is, const Vec3d& ijk) const { const Vec3d loc = applyFrustumMap(ijk); const double s = mGamma * loc.z() + 1.; // verify that we aren't at the singularity if (isApproxEqual(s, 0.)) { OPENVDB_THROW(ArithmeticError, "Tried to evaluate the frustum transform" " at the singular focal point (e.g. camera)"); } // precompute const double sinv = 1.0/s; // 1/(z*gamma + 1) const double pt0 = mLx * sinv; // Lx / (z*gamma +1) const double pt1 = mGamma * pt0; // gamma * Lx / ( z*gamma +1) const double pt2 = pt1 * sinv; // gamma * Lx / ( z*gamma +1)**2 const double pt3 = pt2 * sinv; // gamma * Lx / ( z*gamma +1)**3 const Mat3d& jacinv = mSecondMap.getConstJacobianInv(); // compute \frac{\partial^2 E_i}{\partial x_j \partial x_k} Mat3d matE0(Mat3d::zero()); Mat3d matE1(Mat3d::zero()); // matE2 = 0 for(int j = 0; j < 3; j++) { for (int k = 0; k < 3; k++) { const double pt4 = 2. * jacinv(2,j) * jacinv(2,k) * pt3; matE0(j,k) = -(jacinv(0,j) * jacinv(2,k) + jacinv(2,j) * jacinv(0,k)) * pt2 + pt4 * loc.x(); matE1(j,k) = -(jacinv(1,j) * jacinv(2,k) + jacinv(2,j) * jacinv(1,k)) * pt2 + pt4 * loc.y(); } } // compute \frac{\partial E_i}{\partial x_j} Mat3d gradE(Mat3d::zero()); for (int j = 0; j < 3; ++j ) { gradE(0,j) = pt0 * jacinv(0,j) - pt2 * loc.x()*jacinv(2,j); gradE(1,j) = pt0 * jacinv(1,j) - pt2 * loc.y()*jacinv(2,j); gradE(2,j) = (1./mDepthOnLz) * jacinv(2,j); } Mat3d result(Mat3d::zero()); // compute \fac{\partial E_j}{\partial x_m} \fac{\partial E_i}{\partial x_n} // \frac{\partial^2 input}{\partial E_i \partial E_j} for (int m = 0; m < 3; ++m ) { for ( int n = 0; n < 3; ++n) { for (int i = 0; i < 3; ++i ) { for (int j = 0; j < 3; ++j) { result(m, n) += gradE(j, m) * gradE(i, n) * d2_is(i, j); } } } } for (int m = 0; m < 3; ++m ) { for ( int n = 0; n < 3; ++n) { result(m, n) += matE0(m, n) * d1_is(0) + matE1(m, n) * d1_is(1);// + matE2(m, n) * d1_is(2); } } return result; } /// Return the determinant of the Jacobian of linear second map double determinant() const {return mSecondMap.determinant();} // no implementation /// Return the determinate of the Jacobian evaluated at @c loc /// @c loc is a location in the pre-image space (e.g., index space) double determinant(const Vec3d& loc) const { double s = mGamma * loc.z() + 1.0; double frustum_determinant = s * s * mDepthOnLzLxLx; return mSecondMap.determinant() * frustum_determinant; } /// Return the size of a voxel at the center of the near plane Vec3d voxelSize() const { const Vec3d loc( 0.5*(mBBox.min().x() + mBBox.max().x()), 0.5*(mBBox.min().y() + mBBox.max().y()), mBBox.min().z()); return voxelSize(loc); } /// @brief Returns the lengths of the images of the three segments /// from @a loc to @a loc + (1,0,0), from @a loc to @a loc + (0,1,0) /// and from @a loc to @a loc + (0,0,1) /// @param loc a location in the pre-image space (e.g., index space) Vec3d voxelSize(const Vec3d& loc) const { Vec3d out, pos = applyMap(loc); out(0) = (applyMap(loc + Vec3d(1,0,0)) - pos).length(); out(1) = (applyMap(loc + Vec3d(0,1,0)) - pos).length(); out(2) = (applyMap(loc + Vec3d(0,0,1)) - pos).length(); return out; } AffineMap::Ptr getAffineMap() const { return mSecondMap.getAffineMap(); } /// set the taper value, the ratio of nearplane width / far plane width void setTaper(double t) { mTaper = t; init();} /// Return the taper value. double getTaper() const { return mTaper; } /// set the frustum depth: distance between near and far plane = frustm depth * frustm x-width void setDepth(double d) { mDepth = d; init();} /// Return the unscaled frustm depth double getDepth() const { return mDepth; } // gamma a non-dimensional number: nearplane x-width / camera to near plane distance double getGamma() const { return mGamma; } /// Return the bounding box that defines the frustum in pre-image space const BBoxd& getBBox() const { return mBBox; } /// Return MapBase::Ptr& to the second map const AffineMap& secondMap() const { return mSecondMap; } /// Return @c true if the the bounding box in index space that defines the region that /// is maped into the frustum is non-zero, otherwise @c false bool isValid() const { return !mBBox.empty();} /// Return @c true if the second map is a uniform scale, Rotation and translation bool hasSimpleAffine() const { return mHasSimpleAffine; } /// read serialization void read(std::istream& is) { // for backward compatibility with earlier version if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_FLOAT_FRUSTUM_BBOX ) { CoordBBox bb; bb.read(is); mBBox = BBoxd(bb.min().asVec3d(), bb.max().asVec3d()); } else { mBBox.read(is); } is.read(reinterpret_cast(&mTaper), sizeof(double)); is.read(reinterpret_cast(&mDepth), sizeof(double)); // Read the second maps type. Name type = readString(is); // Check if the map has been registered. if(!MapRegistry::isRegistered(type)) { OPENVDB_THROW(KeyError, "Map " << type << " is not registered"); } // Create the second map of the type and then read it in. MapBase::Ptr proxy = math::MapRegistry::createMap(type); proxy->read(is); mSecondMap = *(proxy->getAffineMap()); init(); } /// write serialization void write(std::ostream& os) const { mBBox.write(os); os.write(reinterpret_cast(&mTaper), sizeof(double)); os.write(reinterpret_cast(&mDepth), sizeof(double)); writeString(os, mSecondMap.type()); mSecondMap.write(os); } /// string serialization, useful for debuging std::string str() const { std::ostringstream buffer; buffer << " - taper: " << mTaper << std::endl; buffer << " - depth: " << mDepth << std::endl; buffer << " SecondMap: "<< mSecondMap.type() << std::endl; buffer << mSecondMap.str() << std::endl; return buffer.str(); } //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the appropriate operation to the linear part of this map MapBase::Ptr preRotate(double radians, Axis axis = X_AXIS) const { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preRotate(radians, axis))); } MapBase::Ptr preTranslate(const Vec3d& t) const { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preTranslate(t))); } MapBase::Ptr preScale(const Vec3d& s) const { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preScale(s))); } MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const { return MapBase::Ptr(new NonlinearFrustumMap( mBBox, mTaper, mDepth, mSecondMap.preShear(shear, axis0, axis1))); } //@} //@{ /// @brief Return a MapBase::Ptr to a new map that is the result /// of postfixing the appropiate operation to the linear part of this map. MapBase::Ptr postRotate(double radians, Axis axis = X_AXIS) const { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postRotate(radians, axis))); } MapBase::Ptr postTranslate(const Vec3d& t) const { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postTranslate(t))); } MapBase::Ptr postScale(const Vec3d& s) const { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postScale(s))); } MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const { return MapBase::Ptr(new NonlinearFrustumMap( mBBox, mTaper, mDepth, mSecondMap.postShear(shear, axis0, axis1))); } //@} private: void init() { // set up as a frustum mLx = mBBox.extents().x(); mLy = mBBox.extents().y(); mLz = mBBox.extents().z(); if (isApproxEqual(mLx,0.) || isApproxEqual(mLy,0.) || isApproxEqual(mLz,0.) ) { OPENVDB_THROW(ArithmeticError, "The index space bounding box" " must have at least two index points in each direction."); } mXo = 0.5* mLx; mYo = 0.5* mLy; // mDepth is non-dimensionalized on near mGamma = (1./mTaper - 1) / mDepth; mDepthOnLz = mDepth/mLz; mDepthOnLzLxLx = mDepthOnLz/(mLx * mLx); /// test for shear and non-uniform scale mHasSimpleAffine = true; Vec3d tmp = mSecondMap.voxelSize(); /// false if there is non-uniform scale if (!isApproxEqual(tmp(0), tmp(1))) { mHasSimpleAffine = false; return; } if (!isApproxEqual(tmp(0), tmp(2))) { mHasSimpleAffine = false; return; } Vec3d trans = mSecondMap.applyMap(Vec3d(0,0,0)); /// look for shear Vec3d tmp1 = mSecondMap.applyMap(Vec3d(1,0,0)) - trans; Vec3d tmp2 = mSecondMap.applyMap(Vec3d(0,1,0)) - trans; Vec3d tmp3 = mSecondMap.applyMap(Vec3d(0,0,1)) - trans; /// false if there is shear if (!isApproxEqual(tmp1.dot(tmp2), 0., 1.e-7)) { mHasSimpleAffine = false; return; } if (!isApproxEqual(tmp2.dot(tmp3), 0., 1.e-7)) { mHasSimpleAffine = false; return; } if (!isApproxEqual(tmp3.dot(tmp1), 0., 1.e-7)) { mHasSimpleAffine = false; return; } } Vec3d applyFrustumMap(const Vec3d& in) const { // Move the center of the x-face of the bbox // to the origin in index space. Vec3d out(in); out = out - mBBox.min(); out.x() -= mXo; out.y() -= mYo; // scale the z-direction on depth / K count out.z() *= mDepthOnLz; double scale = (mGamma * out.z() + 1.)/ mLx; // scale the x-y on the length I count and apply tapper out.x() *= scale ; out.y() *= scale ; return out; } Vec3d applyFrustumInverseMap(const Vec3d& in) const { // invert taper and resize: scale = 1/( (z+1)/2 (mt-1) + 1) Vec3d out(in); double invScale = mLx / (mGamma * out.z() + 1.); out.x() *= invScale; out.y() *= invScale; out.x() += mXo; out.y() += mYo; out.z() /= mDepthOnLz; // move back out = out + mBBox.min(); return out; } // bounding box in index space used in Frustum transforms. BBoxd mBBox; // taper value used in constructing Frustums. double mTaper; double mDepth; // defines the second map AffineMap mSecondMap; // these are derived from the above. double mLx, mLy, mLz; double mXo, mYo, mGamma, mDepthOnLz, mDepthOnLzLxLx; // true: if the mSecondMap is linear and has no shear, and has no non-uniform scale bool mHasSimpleAffine; }; // class NonlinearFrustumMap //////////////////////////////////////// /// @brief Creates the composition of two maps, each of which could be a composition. /// In the case that each component of the composition classified as linear an /// acceleration AffineMap is stored. template class CompoundMap { public: typedef CompoundMap MyType; typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; CompoundMap() { updateAffineMatrix(); } CompoundMap(const FirstMapType& f, const SecondMapType& s): mFirstMap(f), mSecondMap(s) { updateAffineMatrix(); } CompoundMap(const MyType& other): mFirstMap(other.mFirstMap), mSecondMap(other.mSecondMap), mAffineMap(other.mAffineMap) {} Name type() const { return mapType(); } static Name mapType() { return (FirstMapType::mapType() + Name(":") + SecondMapType::mapType()); } bool operator==(const MyType& other) const { if (mFirstMap != other.mFirstMap) return false; if (mSecondMap != other.mSecondMap) return false; if (mAffineMap != other.mAffineMap) return false; return true; } bool operator!=(const MyType& other) const { return !(*this == other); } MyType& operator=(const MyType& other) { mFirstMap = other.mFirstMap; mSecondMap = other.mSecondMap; mAffineMap = other.mAffineMap; return *this; } bool isIdentity() const { if (is_linear::value) { return mAffineMap.isIdentity(); } else { return mFirstMap.isIdentity()&&mSecondMap.isIdentity(); } } bool isDiagonal() const { if (is_linear::value) { return mAffineMap.isDiagonal(); } else { return mFirstMap.isDiagonal()&&mSecondMap.isDiagonal(); } } AffineMap::Ptr getAffineMap() const { if (is_linear::value) { AffineMap::Ptr affine(new AffineMap(mAffineMap)); return affine; } else { OPENVDB_THROW(ArithmeticError, "Constant affine matrix representation not possible for this nonlinear map"); } } // direct decompotion const FirstMapType& firstMap() const { return mFirstMap; } const SecondMapType& secondMap() const {return mSecondMap; } void setFirstMap(const FirstMapType& first) { mFirstMap = first; updateAffineMatrix(); } void setSecondMap(const SecondMapType& second) { mSecondMap = second; updateAffineMatrix(); } void read(std::istream& is) { mAffineMap.read(is); mFirstMap.read(is); mSecondMap.read(is); } void write(std::ostream& os) const { mAffineMap.write(os); mFirstMap.write(os); mSecondMap.write(os); } private: void updateAffineMatrix() { if (is_linear::value) { // both maps need to be linear, these methods are only defined for linear maps AffineMap::Ptr first = mFirstMap.getAffineMap(); AffineMap::Ptr second= mSecondMap.getAffineMap(); mAffineMap = AffineMap(*first, *second); } } FirstMapType mFirstMap; SecondMapType mSecondMap; // used for acceleration AffineMap mAffineMap; }; // class CompoundMap } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_MAPS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/math/Hermite.h0000644000000000000000000003130212252453157013715 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_MATH_HERMITE_HAS_BEEN_INCLUDED #define OPENVDB_MATH_HERMITE_HAS_BEEN_INCLUDED #include #include #include "QuantizedUnitVec.h" #include "Math.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { // Forward declaration class Hermite; //////////////////////////////////////// // Utility methods //@{ /// min and max operations done directly on the compressed data. OPENVDB_API Hermite min(const Hermite&, const Hermite&); OPENVDB_API Hermite max(const Hermite&, const Hermite&); //@} //////////////////////////////////////// /// @brief Quantized Hermite data object that stores compressed intersection /// information (offsets and normlas) for the up-wind edges of a voxel. (Size 10 bytes) class OPENVDB_API Hermite { public: Hermite(); Hermite(const Hermite&); const Hermite& operator=(const Hermite&); /// clears all intersection data void clear(); /// @return true if this Hermite objet has any edge intersection data. operator bool() const; /// equality operator inline bool operator==(const Hermite&) const; /// inequality operator bool operator!=(const Hermite& rhs) const { return !(*this == rhs); } /// unary negation operator, flips inside/outside state and normals. Hermite operator-() const; //@{ /// @brief methods to compress and store edge data. /// @note @c offset is expected to be in the [0 to 1) range. template void setX(T offset, const Vec3&); template void setY(T offset, const Vec3&); template void setZ(T offset, const Vec3&); //@} /// @return true if the current Hermite object is classified // as being inside a contour. bool isInside() const { return MASK_SIGN & mData; } /// Set the inside/outside state to reflect if this Hermite object /// is located at a point in space that is inside/outside a contour. void setIsInside(bool); //@{ /// @return true if this Hermite object has intersection data /// for the corresponding edge. bool hasOffsetX() const { return mXNormal; }; bool hasOffsetY() const { return mYNormal; } bool hasOffsetZ() const { return MASK_ZFLAG & mData; } //@} //@{ /// Edge offset greater-than comparisson operators /// @note is @c this offset > than @c other offset bool isGreaterX(const Hermite& other) const; bool isGreaterY(const Hermite& other) const; bool isGreaterZ(const Hermite& other) const; //@} //@{ /// Edge offset less-than comparisson operators /// @note is @c this offset < than @c other offset bool isLessX(const Hermite& other) const; bool isLessY(const Hermite& other) const; bool isLessZ(const Hermite& other) const; //@} //@{ /// @return uncompressed edge intersection offsets float getOffsetX() const; float getOffsetY() const; float getOffsetZ() const; //@} //@{ /// @return uncompressed edge intersection normals Vec3s getNormalX() const { return QuantizedUnitVec::unpack(mXNormal); } Vec3s getNormalY() const { return QuantizedUnitVec::unpack(mYNormal); } Vec3s getNormalZ() const { return QuantizedUnitVec::unpack(mZNormal); } //@} //@{ /// copy edge data from other Hermite object /// @note copies data in the compressed form void setX(const Hermite&); void setY(const Hermite&); void setZ(const Hermite&); //@} /// String representation. std::string str() const; /// Unserialize this transform from the given stream. void read(std::istream&); /// Serialize this transform to the given stream. void write(std::ostream&) const; //@{ /// Operators required by OpenVDB. /// @note These methods don't perform meaningful operations on Hermite data. bool operator< (const Hermite&) const { return false; }; bool operator> (const Hermite&) const { return false; }; template Hermite operator+(const T&) const { return *this; } template Hermite operator-(const T&) const { return *this; } //@} private: /// Helper function that quantizes a [0, 1) offset using 10-bits. template static uint32_t quantizeOffset(T offset); /// Helper function that returns (signed) compressed-offsets, /// used by comparisson operators. static void getSignedOffsets(const Hermite& lhs, const Hermite& rhs, const uint32_t bitmask, int& lhsV, int& rhsV); // Bits masks // 10000000000000000000000000000000 static const uint32_t MASK_SIGN = 0x80000000; // 01000000000000000000000000000000 static const uint32_t MASK_ZFLAG = 0x40000000; // 00111111111100000000000000000000 static const uint32_t MASK_XSLOT = 0x3FF00000; // 00000000000011111111110000000000 static const uint32_t MASK_YSLOT = 0x000FFC00; // 00000000000000000000001111111111 static const uint32_t MASK_ZSLOT = 0x000003FF; // 00111111111111111111111111111111 static const uint32_t MASK_SLOTS = 0x3FFFFFFF; uint16_t mXNormal, mYNormal, mZNormal; uint32_t mData; }; // class Hermite //////////////////////////////////////// // output-stream insertion operator inline std::ostream& operator<<(std::ostream& ostr, const Hermite& rhs) { ostr << rhs.str(); return ostr; } //////////////////////////////////////// // construction and assignment inline const Hermite& Hermite::operator=(const Hermite& rhs) { mData = rhs.mData; mXNormal = rhs.mXNormal; mYNormal = rhs.mYNormal; mZNormal = rhs.mZNormal; return *this; } inline void Hermite::clear() { mXNormal = 0; mYNormal = 0; mZNormal = 0; mData = 0; } //////////////////////////////////////// // bool operator and equality inline Hermite::operator bool() const { if (0 != (mXNormal | mYNormal)) return true; return hasOffsetZ(); } inline bool Hermite::operator==(const Hermite& rhs) const { if(mXNormal != rhs.mXNormal) return false; if(mYNormal != rhs.mYNormal) return false; if(mZNormal != rhs.mZNormal) return false; return mData == rhs.mData; } //////////////////////////////////////// // unary negation operator inline Hermite Hermite::operator-() const { Hermite ret(*this); QuantizedUnitVec::flipSignBits(ret.mXNormal); QuantizedUnitVec::flipSignBits(ret.mYNormal); QuantizedUnitVec::flipSignBits(ret.mZNormal); ret.mData = (~MASK_SIGN & ret.mData) | (MASK_SIGN & ~ret.mData); return ret; } //////////////////////////////////////// // Helper funcions template inline uint32_t Hermite::quantizeOffset(T offset) { // the offset is expected to be normalized [0 to 1) assert(offset < 1.0); assert(offset > -1.0e-8); // quantize the offset using 10-bits. (higher bits are masked out) return uint32_t(1023 * offset) & MASK_ZSLOT; } inline void Hermite::getSignedOffsets(const Hermite& lhs, const Hermite& rhs, const uint32_t bitmask, int& lhsV, int& rhsV) { lhsV = bitmask & lhs.mData; rhsV = bitmask & rhs.mData; if(lhs.isInside()) lhsV = -lhsV; if(rhs.isInside()) rhsV = -rhsV; } //////////////////////////////////////// // compress and set edge data template inline void Hermite::setX(T offset, const Vec3& n) { mData &= ~MASK_XSLOT; // clear xslot mData |= quantizeOffset(offset) << 20; mXNormal = QuantizedUnitVec::pack(n); } template inline void Hermite::setY(T offset, const Vec3& n) { mData &= ~MASK_YSLOT; // clear yslot mData |= quantizeOffset(offset) << 10; mYNormal = QuantizedUnitVec::pack(n); } template inline void Hermite::setZ(T offset, const Vec3& n) { mData &= ~MASK_ZSLOT; // clear zslot mData |= MASK_ZFLAG | quantizeOffset(offset); mZNormal = QuantizedUnitVec::pack(n); } //////////////////////////////////////// // change inside/outside state inline void Hermite::setIsInside(bool isInside) { mData &= ~MASK_SIGN; // clear sign-bit mData |= uint32_t(isInside) * MASK_SIGN; } //////////////////////////////////////// // Uncompress and return the edge intersection-offsets // 0.000977517 = 1.0 / 1023 inline float Hermite::getOffsetX() const { return float((mData >> 20) & MASK_ZSLOT) * 0.000977517; } inline float Hermite::getOffsetY() const { return float((mData >> 10) & MASK_ZSLOT) * 0.000977517; } inline float Hermite::getOffsetZ() const { return float(mData & MASK_ZSLOT) * 0.000977517; } //////////////////////////////////////// // copy compressed edge data from other object inline void Hermite::setX(const Hermite& rhs) { mData &= ~MASK_XSLOT; // clear xslot mData |= MASK_XSLOT & rhs.mData; // copy xbits from rhs mXNormal = rhs.mXNormal; // copy compressed normal // Flip the copied normal if the rhs object has // a different inside/outside state. if(hasOffsetX() && isInside() != rhs.isInside()) QuantizedUnitVec::flipSignBits(mXNormal); } inline void Hermite::setY(const Hermite& rhs) { mData &= ~MASK_YSLOT; mData |= MASK_YSLOT & rhs.mData; mYNormal = rhs.mYNormal; if(hasOffsetY() && isInside() != rhs.isInside()) QuantizedUnitVec::flipSignBits(mYNormal); } inline void Hermite::setZ(const Hermite& rhs) { mData &= ~MASK_ZSLOT; mData |= (MASK_ZFLAG | MASK_ZSLOT) & rhs.mData; mZNormal = rhs.mZNormal; if(hasOffsetZ() && isInside() != rhs.isInside()) QuantizedUnitVec::flipSignBits(mZNormal); } //////////////////////////////////////// // edge comparison operators inline bool Hermite::isGreaterX(const Hermite& rhs) const { int lhsV, rhsV; getSignedOffsets(*this, rhs, MASK_XSLOT, lhsV, rhsV); return lhsV > rhsV; } inline bool Hermite::isGreaterY(const Hermite& rhs) const { int lhsV, rhsV; getSignedOffsets(*this, rhs, MASK_YSLOT, lhsV, rhsV); return lhsV > rhsV; } inline bool Hermite::isGreaterZ(const Hermite& rhs) const { int lhsV, rhsV; getSignedOffsets(*this, rhs, MASK_ZSLOT, lhsV, rhsV); return lhsV > rhsV; } inline bool Hermite::isLessX(const Hermite& rhs) const { int lhsV, rhsV; getSignedOffsets(*this, rhs, MASK_XSLOT, lhsV, rhsV); return lhsV < rhsV; } inline bool Hermite::isLessY(const Hermite& rhs) const { int lhsV, rhsV; getSignedOffsets(*this, rhs, MASK_YSLOT, lhsV, rhsV); return lhsV < rhsV; } inline bool Hermite::isLessZ(const Hermite& rhs) const { int lhsV, rhsV; getSignedOffsets(*this, rhs, MASK_ZSLOT, lhsV, rhsV); return lhsV < rhsV; } //////////////////////////////////////// inline bool isApproxEqual(const Hermite& lhs, const Hermite& rhs) { return lhs == rhs; } inline bool isApproxEqual(const Hermite& lhs, const Hermite& rhs, const Hermite& /*tolerance*/) { return isApproxEqual(lhs, rhs); } } // namespace math //////////////////////////////////////// template<> inline math::Hermite zeroVal() { return math::Hermite(); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_HERMITE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/openvdb.cc0000644000000000000000000001022512252453157013163 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "openvdb.h" #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { typedef tbb::mutex Mutex; typedef Mutex::scoped_lock Lock; // Declare this at file scope to ensure thread-safe initialization. Mutex sInitMutex; bool sIsInitialized = false; void initialize() { Lock lock(sInitMutex); if (sIsInitialized) return; // Register metadata. Metadata::clearRegistry(); BoolMetadata::registerType(); DoubleMetadata::registerType(); FloatMetadata::registerType(); Int32Metadata::registerType(); Int64Metadata::registerType(); StringMetadata::registerType(); Vec2IMetadata::registerType(); Vec2SMetadata::registerType(); Vec2DMetadata::registerType(); Vec3IMetadata::registerType(); Vec3SMetadata::registerType(); Vec3DMetadata::registerType(); Mat4SMetadata::registerType(); Mat4DMetadata::registerType(); // Register maps math::MapRegistry::clear(); math::AffineMap::registerMap(); math::UnitaryMap::registerMap(); math::ScaleMap::registerMap(); math::UniformScaleMap::registerMap(); math::TranslationMap::registerMap(); math::ScaleTranslateMap::registerMap(); math::UniformScaleTranslateMap::registerMap(); math::NonlinearFrustumMap::registerMap(); // Register common grid types. GridBase::clearRegistry(); BoolGrid::registerGrid(); FloatGrid::registerGrid(); DoubleGrid::registerGrid(); Int32Grid::registerGrid(); Int64Grid::registerGrid(); HermiteGrid::registerGrid(); StringGrid::registerGrid(); Vec3IGrid::registerGrid(); Vec3SGrid::registerGrid(); Vec3DGrid::registerGrid(); #ifdef __ICC // Disable ICC "assignment to statically allocated variable" warning. // This assignment is mutex-protected and therefore thread-safe. __pragma(warning(disable:1711)) #endif sIsInitialized = true; #ifdef __ICC __pragma(warning(default:1711)) #endif } void uninitialize() { Lock lock(sInitMutex); #ifdef __ICC // Disable ICC "assignment to statically allocated variable" warning. // This assignment is mutex-protected and therefore thread-safe. __pragma(warning(disable:1711)) #endif sIsInitialized = false; #ifdef __ICC __pragma(warning(default:1711)) #endif Metadata::clearRegistry(); GridBase::clearRegistry(); math::MapRegistry::clear(); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/COPYRIGHT0000644000000000000000000000255012252452020012500 0ustar rootrootCopyright (c) 2012-2013 DreamWorks Animation LLC All rights reserved. This software is distributed under the Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) Redistributions of source code must retain the above copyright and license notice and the following restrictions and disclaimer. * Neither the name of DreamWorks Animation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. openvdb/Grid.cc0000644000000000000000000003057112252453157012421 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Grid.h" #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { /// @note For Houdini compatibility, boolean-valued metadata names /// should begin with "is_". const char * const GridBase::META_GRID_CLASS = "class", * const GridBase::META_GRID_CREATOR = "creator", * const GridBase::META_GRID_NAME = "name", * const GridBase::META_SAVE_HALF_FLOAT = "is_saved_as_half_float", * const GridBase::META_IS_LOCAL_SPACE = "is_local_space", * const GridBase::META_VECTOR_TYPE = "vector_type", * const GridBase::META_FILE_BBOX_MIN = "file_bbox_min", * const GridBase::META_FILE_BBOX_MAX = "file_bbox_max", * const GridBase::META_FILE_COMPRESSION = "file_compression", * const GridBase::META_FILE_MEM_BYTES = "file_mem_bytes", * const GridBase::META_FILE_VOXEL_COUNT = "file_voxel_count"; namespace { /// @todo Remove (deprecated in favor of META_SAVE_HALF_FLOAT) const char *SAVE_FLOAT_AS_HALF = "write as 16-bit float"; } //////////////////////////////////////// namespace { typedef std::map GridFactoryMap; typedef GridFactoryMap::const_iterator GridFactoryMapCIter; typedef tbb::mutex Mutex; typedef Mutex::scoped_lock Lock; struct LockedGridRegistry { LockedGridRegistry() {} ~LockedGridRegistry() {} Mutex mMutex; GridFactoryMap mMap; }; // Declare this at file scope to ensure thread-safe initialization. Mutex sInitGridRegistryMutex; // Global function for accessing the registry LockedGridRegistry* getGridRegistry() { Lock lock(sInitGridRegistryMutex); static LockedGridRegistry* registry = NULL; if (registry == NULL) { #ifdef __ICC // Disable ICC "assignment to statically allocated variable" warning. // This assignment is mutex-protected and therefore thread-safe. __pragma(warning(disable:1711)) #endif registry = new LockedGridRegistry(); #ifdef __ICC __pragma(warning(default:1711)) #endif } return registry; } } // unnamed namespace bool GridBase::isRegistered(const Name& name) { LockedGridRegistry* registry = getGridRegistry(); Lock lock(registry->mMutex); return (registry->mMap.find(name) != registry->mMap.end()); } void GridBase::registerGrid(const Name& name, GridFactory factory) { LockedGridRegistry* registry = getGridRegistry(); Lock lock(registry->mMutex); if (registry->mMap.find(name) != registry->mMap.end()) { OPENVDB_THROW(KeyError, "Grid type " << name << " is already registered"); } registry->mMap[name] = factory; } void GridBase::unregisterGrid(const Name& name) { LockedGridRegistry* registry = getGridRegistry(); Lock lock(registry->mMutex); registry->mMap.erase(name); } GridBase::Ptr GridBase::createGrid(const Name& name) { LockedGridRegistry* registry = getGridRegistry(); Lock lock(registry->mMutex); GridFactoryMapCIter iter = registry->mMap.find(name); if (iter == registry->mMap.end()) { OPENVDB_THROW(LookupError, "Cannot create grid of unregistered type " << name); } return (iter->second)(); } void GridBase::clearRegistry() { LockedGridRegistry* registry = getGridRegistry(); Lock lock(registry->mMutex); registry->mMap.clear(); } //////////////////////////////////////// GridClass GridBase::stringToGridClass(const std::string& s) { GridClass ret = GRID_UNKNOWN; std::string str = s; boost::trim(str); boost::to_lower(str); if (str == gridClassToString(GRID_LEVEL_SET)) { ret = GRID_LEVEL_SET; } else if (str == gridClassToString(GRID_FOG_VOLUME)) { ret = GRID_FOG_VOLUME; } else if (str == gridClassToString(GRID_STAGGERED)) { ret = GRID_STAGGERED; } return ret; } std::string GridBase::gridClassToString(GridClass cls) { std::string ret; switch (cls) { case GRID_UNKNOWN: ret = "unknown"; break; case GRID_LEVEL_SET: ret = "level set"; break; case GRID_FOG_VOLUME: ret = "fog volume"; break; case GRID_STAGGERED: ret = "staggered"; break; } return ret; } std::string GridBase::gridClassToMenuName(GridClass cls) { std::string ret; switch (cls) { case GRID_UNKNOWN: ret = "Other"; break; case GRID_LEVEL_SET: ret = "Level Set"; break; case GRID_FOG_VOLUME: ret = "Fog Volume"; break; case GRID_STAGGERED: ret = "Staggered Vector Field"; break; } return ret; } GridClass GridBase::getGridClass() const { GridClass cls = GRID_UNKNOWN; if (StringMetadata::ConstPtr s = this->getMetadata(META_GRID_CLASS)) { cls = stringToGridClass(s->value()); } return cls; } void GridBase::setGridClass(GridClass cls) { this->insertMeta(META_GRID_CLASS, StringMetadata(gridClassToString(cls))); } void GridBase::clearGridClass() { this->removeMeta(META_GRID_CLASS); } //////////////////////////////////////// VecType GridBase::stringToVecType(const std::string& s) { VecType ret = VEC_INVARIANT; std::string str = s; boost::trim(str); boost::to_lower(str); if (str == vecTypeToString(VEC_COVARIANT)) { ret = VEC_COVARIANT; } else if (str == vecTypeToString(VEC_COVARIANT_NORMALIZE)) { ret = VEC_COVARIANT_NORMALIZE; } else if (str == vecTypeToString(VEC_CONTRAVARIANT_RELATIVE)) { ret = VEC_CONTRAVARIANT_RELATIVE; } else if (str == vecTypeToString(VEC_CONTRAVARIANT_ABSOLUTE)) { ret = VEC_CONTRAVARIANT_ABSOLUTE; } return ret; } std::string GridBase::vecTypeToString(VecType typ) { std::string ret; switch (typ) { case VEC_INVARIANT: ret = "invariant"; break; case VEC_COVARIANT: ret = "covariant"; break; case VEC_COVARIANT_NORMALIZE: ret = "covariant normalize"; break; case VEC_CONTRAVARIANT_RELATIVE: ret = "contravariant relative"; break; case VEC_CONTRAVARIANT_ABSOLUTE: ret = "contravariant absolute"; break; } return ret; } std::string GridBase::vecTypeExamples(VecType typ) { std::string ret; switch (typ) { case VEC_INVARIANT: ret = "Tuple/Color/UVW"; break; case VEC_COVARIANT: ret = "Gradient/Normal"; break; case VEC_COVARIANT_NORMALIZE: ret = "Unit Normal"; break; case VEC_CONTRAVARIANT_RELATIVE: ret = "Displacement/Velocity/Acceleration"; break; case VEC_CONTRAVARIANT_ABSOLUTE: ret = "Position"; break; } return ret; } std::string GridBase::vecTypeDescription(VecType typ) { std::string ret; switch (typ) { case VEC_INVARIANT: ret = "Does not transform"; break; case VEC_COVARIANT: ret = "Apply the inverse-transpose transform matrix but ignore translation"; break; case VEC_COVARIANT_NORMALIZE: ret = "Apply the inverse-transpose transform matrix but ignore translation" " and renormalize vectors"; break; case VEC_CONTRAVARIANT_RELATIVE: ret = "Apply the forward transform matrix but ignore translation"; break; case VEC_CONTRAVARIANT_ABSOLUTE: ret = "Apply the forward transform matrix, including translation"; break; } return ret; } VecType GridBase::getVectorType() const { VecType typ = VEC_INVARIANT; if (StringMetadata::ConstPtr s = this->getMetadata(META_VECTOR_TYPE)) { typ = stringToVecType(s->value()); } return typ; } void GridBase::setVectorType(VecType typ) { this->insertMeta(META_VECTOR_TYPE, StringMetadata(vecTypeToString(typ))); } void GridBase::clearVectorType() { this->removeMeta(META_VECTOR_TYPE); } //////////////////////////////////////// std::string GridBase::getName() const { if (Metadata::ConstPtr meta = (*this)[META_GRID_NAME]) return meta->str(); return ""; } void GridBase::setName(const std::string& name) { this->removeMeta(META_GRID_NAME); this->insertMeta(META_GRID_NAME, StringMetadata(name)); } //////////////////////////////////////// std::string GridBase::getCreator() const { if (Metadata::ConstPtr meta = (*this)[META_GRID_CREATOR]) return meta->str(); return ""; } void GridBase::setCreator(const std::string& creator) { this->removeMeta(META_GRID_CREATOR); this->insertMeta(META_GRID_CREATOR, StringMetadata(creator)); } //////////////////////////////////////// bool GridBase::saveFloatAsHalf() const { bool saveAsHalf = false; if (Metadata::ConstPtr meta = (*this)[META_SAVE_HALF_FLOAT]) { saveAsHalf = meta->asBool(); } else if ((*this)[SAVE_FLOAT_AS_HALF]) { // Old behavior: saveAsHalf is true if metadata named // SAVE_FLOAT_AS_HALF exists, regardless of its value. saveAsHalf = true; } return saveAsHalf; } void GridBase::setSaveFloatAsHalf(bool saveAsHalf) { this->removeMeta(META_SAVE_HALF_FLOAT); this->insertMeta(META_SAVE_HALF_FLOAT, BoolMetadata(saveAsHalf)); // Remove the old, deprecated metadata. this->removeMeta(SAVE_FLOAT_AS_HALF); } //////////////////////////////////////// bool GridBase::isInWorldSpace() const { bool local = false; if (Metadata::ConstPtr meta = (*this)[META_IS_LOCAL_SPACE]) { local = meta->asBool(); } return !local; } void GridBase::setIsInWorldSpace(bool world) { this->removeMeta(META_IS_LOCAL_SPACE); this->insertMeta(META_IS_LOCAL_SPACE, BoolMetadata(!world)); } //////////////////////////////////////// void GridBase::addStatsMetadata() { const CoordBBox bbox = this->evalActiveVoxelBoundingBox(); this->removeMeta(META_FILE_BBOX_MIN); this->removeMeta(META_FILE_BBOX_MAX); this->removeMeta(META_FILE_MEM_BYTES); this->removeMeta(META_FILE_VOXEL_COUNT); this->insertMeta(META_FILE_BBOX_MIN, Vec3IMetadata(bbox.min().asVec3i())); this->insertMeta(META_FILE_BBOX_MAX, Vec3IMetadata(bbox.max().asVec3i())); this->insertMeta(META_FILE_MEM_BYTES, Int64Metadata(this->memUsage())); this->insertMeta(META_FILE_VOXEL_COUNT, Int64Metadata(this->activeVoxelCount())); } MetaMap::Ptr GridBase::getStatsMetadata() const { static const char* const sFields[] = { META_FILE_BBOX_MIN, META_FILE_BBOX_MAX, META_FILE_MEM_BYTES, META_FILE_VOXEL_COUNT, NULL }; /// @todo Check that the fields are of the correct type? MetaMap::Ptr ret(new MetaMap); for (int i = 0; sFields[i] != NULL; ++i) { if (Metadata::ConstPtr m = (*this)[sFields[i]]) { ret->insertMeta(sFields[i], *m); } } return ret; } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Metadata.h0000644000000000000000000000373512252453157013120 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_METADATA_HAS_BEEN_INCLUDED #define OPENVDB_METADATA_HAS_BEEN_INCLUDED #include #include #endif // OPENVDB_METADATA_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/0000755000000000000000000000000012252453157011626 5ustar rootrootopenvdb/io/Compression.cc0000644000000000000000000001273512252453157014446 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Compression.h" #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { std::string compressionToString(uint32_t flags) { if (flags == COMPRESS_NONE) return "none"; std::vector words; if (flags & COMPRESS_ZIP) words.push_back("zipped"); if (flags & COMPRESS_ACTIVE_MASK) { words.push_back("active values"); } return boost::join(words, " "); } //////////////////////////////////////// namespace { const int ZIP_COMPRESSION_LEVEL = Z_DEFAULT_COMPRESSION; ///< @todo use Z_BEST_SPEED? } void zipToStream(std::ostream& os, const char* data, size_t numBytes) { // Get an upper bound on the size of the compressed data. uLongf numZippedBytes = compressBound(numBytes); // Compress the data. boost::shared_array zippedData(new Bytef[numZippedBytes]); int status = compress2( /*dest=*/zippedData.get(), &numZippedBytes, /*src=*/reinterpret_cast(data), numBytes, /*level=*/ZIP_COMPRESSION_LEVEL); if (status != Z_OK) { std::string errDescr; if (const char* s = zError(status)) errDescr = s; if (!errDescr.empty()) errDescr = " (" + errDescr + ")"; OPENVDB_LOG_DEBUG("zlib compress2() returned error code " << status << errDescr); } if (status == Z_OK && numZippedBytes < numBytes) { // Write the size of the compressed data. Int64 outZippedBytes = numZippedBytes; os.write(reinterpret_cast(&outZippedBytes), 8); // Write the compressed data. os.write(reinterpret_cast(zippedData.get()), outZippedBytes); } else { // Write the size of the uncompressed data. Int64 negBytes = -numBytes; os.write(reinterpret_cast(&negBytes), 8); // Write the uncompressed data. os.write(reinterpret_cast(data), numBytes); } } void unzipFromStream(std::istream& is, char* data, size_t numBytes) { // Read the size of the compressed data. // A negative size indicates uncompressed data. Int64 numZippedBytes; is.read(reinterpret_cast(&numZippedBytes), 8); if (numZippedBytes <= 0) { // Read the uncompressed data. is.read(data, -numZippedBytes); if (size_t(-numZippedBytes) != numBytes) { OPENVDB_THROW(RuntimeError, "Expected to read a " << numBytes << "-byte chunk, got a " << -numZippedBytes << "-byte chunk"); } } else { // Read the compressed data. boost::shared_array zippedData(new Bytef[numZippedBytes]); is.read(reinterpret_cast(zippedData.get()), numZippedBytes); // Uncompress the data. uLongf numUnzippedBytes = numBytes; int status = uncompress( /*dest=*/reinterpret_cast(data), &numUnzippedBytes, /*src=*/zippedData.get(), static_cast(numZippedBytes)); if (status != Z_OK) { std::string errDescr; if (const char* s = zError(status)) errDescr = s; if (!errDescr.empty()) errDescr = " (" + errDescr + ")"; OPENVDB_LOG_DEBUG("zlib uncompress() returned error code " << status << errDescr); } if (numUnzippedBytes != numBytes) { OPENVDB_THROW(RuntimeError, "Expected to decompress " << numBytes << " byte" << (numBytes == 1 ? "" : "s") << ", got " << numZippedBytes << " byte" << (numZippedBytes == 1 ? "" : "s")); } } } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Archive.h0000644000000000000000000003062712252453157013370 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_IO_ARCHIVE_HAS_BEEN_INCLUDED #define OPENVDB_IO_ARCHIVE_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include // for VersionId #include "Compression.h" // for COMPRESS_ZIP, etc. class TestFile; namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { class GridDescriptor; /// Return the file format version number associated with the given input stream. /// @sa File::setFormatVersion() OPENVDB_API uint32_t getFormatVersion(std::istream&); /// Return the library version number associated with the given input stream. /// @sa File::setLibraryVersion() OPENVDB_API VersionId getLibraryVersion(std::istream&); /// Return a string of the form "./", giving the library /// and file format version numbers associated with the given input stream. OPENVDB_API std::string getVersion(std::istream&); /// Associate the current file format and library version numbers with the given input stream. OPENVDB_API void setCurrentVersion(std::istream&); /// @brief Associate specific file format and library version numbers with the given stream. /// @details This is typically called immediately after reading a header that contains /// the version numbers. Data read subsequently can then be interpreted appropriately. OPENVDB_API void setVersion(std::ios_base&, const VersionId& libraryVersion, uint32_t fileVersion); /// Return @c true if grid statistics (active voxel count and bounding box, etc.) /// should be computed and stored as grid metadata on output to the given stream. OPENVDB_API bool getWriteGridStatsMetadata(std::ostream&); /// @brief Return a bitwise OR of compression option flags (COMPRESS_ZIP, /// COMPRESS_ACTIVE_MASK, etc.) specifying whether and how input data is compressed /// or output data should be compressed. OPENVDB_API uint32_t getDataCompression(std::ios_base&); /// @brief Associate with the given stream a bitwise OR of compression option flags /// (COMPRESS_ZIP, COMPRESS_ACTIVE_MASK, etc.) specifying whether and how input data /// is compressed or output data should be compressed. OPENVDB_API void setDataCompression(std::ios_base&, uint32_t compressionFlags); /// @brief Return the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) of the grid /// currently being read from or written to the given stream. OPENVDB_API uint32_t getGridClass(std::ios_base&); /// @brief Associate with the given stream the class (GRID_LEVEL_SET, GRID_UNKNOWN, etc.) /// of the grid currently being read or written. OPENVDB_API void setGridClass(std::ios_base&, uint32_t); /// @brief Return a pointer to the background value of the grid /// currently being read from or written to the given stream. OPENVDB_API const void* getGridBackgroundValuePtr(std::ios_base&); /// @brief Specify (a pointer to) the background value of the grid /// currently being read from or written to the given stream. /// @note The pointer must remain valid until the entire grid has been read or written. OPENVDB_API void setGridBackgroundValuePtr(std::ios_base&, const void* background); //////////////////////////////////////// /// Grid serializer/unserializer class OPENVDB_API Archive { public: static const uint32_t DEFAULT_COMPRESSION_FLAGS; Archive(); virtual ~Archive(); /// @brief Return a copy of this archive. virtual boost::shared_ptr copy() const; /// @brief Return the UUID that was most recently written (or read, /// if no UUID has been written yet). std::string getUniqueTag() const; /// @brief Return @c true if the given UUID matches this archive's UUID. bool isIdentical(const std::string& uuidStr) const; /// @brief Return the file format version number of the input stream. uint32_t fileVersion() const { return mFileVersion; } /// @brief Return the (major, minor) version number of the library that was /// used to write the input stream. VersionId libraryVersion() const { return mLibraryVersion; } /// @brief Return a string of the form "./", giving the /// library and file format version numbers associated with the input stream. std::string version() const; /// @brief Return @c true if trees shared by multiple grids are written out /// only once, @c false if they are written out once per grid. bool isInstancingEnabled() const { return mEnableInstancing; } /// @brief Specify whether trees shared by multiple grids should be /// written out only once (@c true) or once per grid (@c false). /// @note Instancing is enabled by default. void setInstancingEnabled(bool b) { mEnableInstancing = b; } /// Return @c true if the data stream is Zip-compressed. bool isCompressionEnabled() const; /// @brief Specify whether the data stream should be Zip-compressed. /// @details Enabling Zip compression makes I/O slower, but saves space. /// Disable it only if raw I/O speed is a concern. void setCompressionEnabled(bool); /// Return a bit mask specifying compression options for the data stream. uint32_t compressionFlags() const { return mCompression; } /// @brief Specify whether and how the data stream should be compressed. /// [Mainly for internal use] /// @param c bitwise OR (e.g., COMPRESS_ZIP | COMPRESS_ACTIVE_MASK) of /// compression option flags (see Compression.h for the available flags) /// @note Not all combinations of compression options are supported. void setCompressionFlags(uint32_t c) { mCompression = c; } /// @brief Return @c true if grid statistics (active voxel count and /// bounding box, etc.) are computed and written as grid metadata. bool isGridStatsMetadataEnabled() const { return mEnableGridStats; } /// @brief Specify whether grid statistics (active voxel count and /// bounding box, etc.) should be computed and written as grid metadata. void setGridStatsMetadataEnabled(bool b) { mEnableGridStats = b; } /// @brief Write the grids in the given container to this archive's output stream. virtual void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const {} protected: /// @brief Return @c true if the input stream contains grid offsets /// that allow for random access or partial reading. bool inputHasGridOffsets() const { return mInputHasGridOffsets; } void setInputHasGridOffsets(bool b) { mInputHasGridOffsets = b; } /// @brief Tag the given input stream with the input file format version number. /// /// The tag can be retrieved with getFormatVersion(). /// @sa getFormatVersion() void setFormatVersion(std::istream&); /// @brief Tag the given input stream with the version number of /// the library with which the input stream was created. /// /// The tag can be retrieved with getLibraryVersion(). /// @sa getLibraryVersion() void setLibraryVersion(std::istream&); /// @brief Tag the given input stream with flags indicating whether /// the input stream contains compressed data and how it is compressed. void setDataCompression(std::istream&); /// @brief Tag an output stream with flags specifying only those /// compression options that are applicable to the given grid. void setGridCompression(std::ostream&, const GridBase&) const; /// @brief Read in the compression flags for a grid and /// tag the given input stream with those flags. static void readGridCompression(std::istream&); /// @brief Tag the given output stream with a flag indicating whether /// to compute and write grid statistics metadata. void setWriteGridStatsMetadata(std::ostream&); /// Read in and return the number of grids on the input stream. static int32_t readGridCount(std::istream&); /// Populate the given grid from the input stream. static void readGrid(GridBase::Ptr, const GridDescriptor&, std::istream&); typedef std::map NamedGridMap; /// @brief If the grid represented by the given grid descriptor /// is an instance, connect it with its instance parent. void connectInstance(const GridDescriptor&, const NamedGridMap&) const; /// Write the given grid descriptor and grid to an output stream /// and update the GridDescriptor offsets. /// @param seekable if true, the output stream supports seek operations void writeGrid(GridDescriptor&, GridBase::ConstPtr, std::ostream&, bool seekable) const; /// Write the given grid descriptor and grid metadata to an output stream /// and update the GridDescriptor offsets, but don't write the grid's tree, /// since it is shared with another grid. /// @param seekable if true, the output stream supports seek operations void writeGridInstance(GridDescriptor&, GridBase::ConstPtr, std::ostream&, bool seekable) const; /// @brief Read the magic number, version numbers, UUID, etc. from the given input stream. /// @return @c true if the input UUID differs from the previously-read UUID. bool readHeader(std::istream&); /// @brief Write the magic number, version numbers, UUID, etc. to the given output stream. /// @param seekable if true, the output stream supports seek operations /// @todo This method should not be const since it actually redefines the UUID! void writeHeader(std::ostream&, bool seekable) const; //@{ /// Write the given grids to an output stream. void write(std::ostream&, const GridPtrVec&, bool seekable, const MetaMap& = MetaMap()) const; void write(std::ostream&, const GridCPtrVec&, bool seekable, const MetaMap& = MetaMap()) const; //@} private: friend class ::TestFile; /// The version of the file that was read uint32_t mFileVersion; /// The version of the library that was used to create the file that was read VersionId mLibraryVersion; /// 16-byte (128-bit) UUID mutable boost::uuids::uuid mUuid;// needs to be mutable since writeHeader is const! /// Flag indicating whether the input stream contains grid offsets /// and therefore supports partial reading bool mInputHasGridOffsets; /// Flag indicating whether a tree shared by multiple grids should be /// written out only once (true) or once per grid (false) bool mEnableInstancing; /// Flags indicating whether and how the data stream is compressed uint32_t mCompression; /// Flag indicating whether grid statistics metadata should be written bool mEnableGridStats; }; // class Archive } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_ARCHIVE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/GridDescriptor.h0000644000000000000000000001314512252453157014727 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_IO_GRIDDESCRIPTOR_HAS_BEEN_INCLUDED #define OPENVDB_IO_GRIDDESCRIPTOR_HAS_BEEN_INCLUDED #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { /// This structure stores useful information that describes a grid on disk. /// It can be used to retrieve I/O information about the grid such as /// offsets into the file where the grid is located, its type, etc. class OPENVDB_API GridDescriptor { public: GridDescriptor(); GridDescriptor(const Name& name, const Name& gridType, bool saveFloatAsHalf = false); ~GridDescriptor(); const Name& gridType() const { return mGridType; } const Name& gridName() const { return mGridName; } const Name& uniqueName() const { return mUniqueName; } const Name& instanceParentName() const { return mInstanceParentName; } void setInstanceParentName(const Name& name) { mInstanceParentName = name; } bool isInstance() const { return !mInstanceParentName.empty(); } bool saveFloatAsHalf() const { return mSaveFloatAsHalf; } void setGridPos(boost::int64_t pos) { mGridPos = pos; } boost::int64_t getGridPos() const { return mGridPos; } void setBlockPos(boost::int64_t pos) { mBlockPos = pos; } boost::int64_t getBlockPos() const { return mBlockPos; } void setEndPos(boost::int64_t pos) { mEndPos = pos; } boost::int64_t getEndPos() const { return mEndPos; } // These methods seek to the right position in the given stream. void seekToGrid(std::istream&) const; void seekToBlocks(std::istream&) const; void seekToEnd(std::istream&) const; void seekToGrid(std::ostream&) const; void seekToBlocks(std::ostream&) const; void seekToEnd(std::ostream&) const; /// @brief Write out this descriptor's header information (all data except for /// stream offsets). void writeHeader(std::ostream&) const; /// @brief Since positions into the stream are known at a later time, they are /// written out separately. void writeStreamPos(std::ostream&) const; /// @brief Read a grid descriptor from the given stream. /// @return an empty grid of the type specified by the grid descriptor. GridBase::Ptr read(std::istream&); /// @brief Append the number @a n to the given name (separated by an ASCII /// "record separator" character) and return the resulting name. static Name addSuffix(const Name&, int n); /// @brief Strip from the given name any suffix that is separated by an ASCII /// "record separator" character and return the resulting name. static Name stripSuffix(const Name&); /// @brief Given a name with suffix N, return "name[N]", otherwise just return "name". /// Use this to produce a human-readable string from a descriptor's unique name. static std::string nameAsString(const Name&); /// @brief Given a string of the form "name[N]", return "name" with the suffix N /// separated by an ASCII "record separator" character). Otherwise just return /// the string as is. static Name stringAsUniqueName(const std::string&); private: /// Name of the grid Name mGridName; /// Unique name for this descriptor Name mUniqueName; /// If nonempty, the name of another grid that shares this grid's tree Name mInstanceParentName; /// The type of the grid Name mGridType; /// Are floats quantized to 16 bits on disk? bool mSaveFloatAsHalf; /// Location in the stream where the grid data is stored boost::int64_t mGridPos; /// Location in the stream where the grid blocks are stored boost::int64_t mBlockPos; /// Location in the stream where the next grid descriptor begins boost::int64_t mEndPos; }; } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_GRIDDESCRIPTOR_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/File.h0000644000000000000000000002206612252453157012664 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file File.h #ifndef OPENVDB_IO_FILE_HAS_BEEN_INCLUDED #define OPENVDB_IO_FILE_HAS_BEEN_INCLUDED #include #include #include #include #include "Archive.h" #include "GridDescriptor.h" class TestFile; class TestStream; namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { /// Grid archive associated with a file on disk class OPENVDB_API File: public Archive { public: typedef std::multimap NameMap; typedef NameMap::const_iterator NameMapCIter; explicit File(const std::string& filename); virtual ~File(); /// @brief Copy constructor /// @details The copy will be closed and will not reference the same /// file descriptor as the original. File(const File& other); /// @brief Assignment /// @details After assignment, this File will be closed and will not /// reference the same file descriptor as the source File. File& operator=(const File& other); /// @brief Return a copy of this archive. /// @details The copy will be closed and will not reference the same /// file descriptor as the original. virtual boost::shared_ptr copy() const; /// @brief Return the name of the file with which this archive is associated. /// @details The file does not necessarily exist on disk yet. const std::string& filename() const { return mFilename; } /// Open the file, read the file header and the file-level metadata, and /// populate the grid descriptors, but do not load any grids into memory. /// @throw IoError if the file is not a valid VDB file. /// @return @c true if the file's UUID has changed since it was last read. bool open(); /// Return @c true if the file has been opened for reading. bool isOpen() const { return mIsOpen; } /// Close the file once we are done reading from it. void close(); /// Return @c true if a grid of the given name exists in this file. bool hasGrid(const Name&) const; /// Return (in a newly created MetaMap) the file-level metadata. MetaMap::Ptr getMetadata() const; /// Read the entire contents of the file and return a list of grid pointers. GridPtrVecPtr getGrids() const; /// @brief Read just the grid metadata and transforms from the file and return a list /// of pointers to grids that are empty except for their metadata and transforms. /// @throw IoError if this file is not open for reading. GridPtrVecPtr readAllGridMetadata(); /// @brief Read a grid's metadata and transform only. /// @return A pointer to a grid that is empty except for its metadata and transform. /// @throw IoError if this file is not open for reading. /// @throw KeyError if no grid with the given name exists in this file. GridBase::Ptr readGridMetadata(const Name&); /// @brief Read a grid's metadata, topology, transform, etc., but not /// any of its leaf node data blocks. /// @return the grid pointer to the partially loaded grid. /// @note This returns a @c const pointer, so that the grid can't be /// changed before its data blocks have been loaded. A non-const /// pointer is only returned when readGrid() is called. GridBase::ConstPtr readGridPartial(const Name&); /// Read an entire grid, including all of its data blocks. GridBase::Ptr readGrid(const Name&); /// @todo GridPtrVec readAllGridsPartial(const Name&) /// @todo GridPtrVec readAllGrids(const Name&) /// @brief Write the grids in the given container to the file whose name /// was given in the constructor. virtual void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const; /// @brief Write the grids in the given container to the file whose name /// was given in the constructor. template void write(const GridPtrContainerT&, const MetaMap& = MetaMap()) const; /// A const iterator that iterates over all names in the file. This is only /// valid once the file has been opened. class NameIterator { public: NameIterator(const NameMapCIter& iter): mIter(iter) {} ~NameIterator() {} NameIterator& operator++() { mIter++; return *this; } bool operator==(const NameIterator& iter) const { return mIter == iter.mIter; } bool operator!=(const NameIterator& iter) const { return mIter != iter.mIter; } Name operator*() const { return this->gridName(); } Name gridName() const { return GridDescriptor::nameAsString(mIter->second.uniqueName()); } private: NameMapCIter mIter; }; /// @return a NameIterator to iterate over all grid names in the file. NameIterator beginName() const; /// @return the ending iterator for all grid names in the file. NameIterator endName() const; private: /// Resets the input stream to the beginning. void resetInStream() const { mInStream.seekg(0, std::ios::beg); } /// Read in all grid descriptors that are stored in the given stream. void readGridDescriptors(std::istream&); /// @brief Return an iterator to the descriptor for the grid with the given name. /// If the name is non-unique, return an iterator to the first matching descriptor. NameMapCIter findDescriptor(const Name&) const; /// Return a newly created, empty grid of the type specified by the given grid descriptor. GridBase::Ptr createGrid(const GridDescriptor&) const; /// Read in and return the partially-populated grid specified by the given grid descriptor. GridBase::ConstPtr readGridPartial(const GridDescriptor&, bool readTopology) const; /// Read in and return the grid specified by the given grid descriptor. GridBase::Ptr readGrid(const GridDescriptor&) const; /// Partially populate the given grid by reading its metadata and transform and, /// if the grid is not an instance, its tree structure, but not the tree's leaf nodes. void readGridPartial(GridBase::Ptr, std::istream&, bool isInstance, bool readTopology) const; void writeGrids(const GridCPtrVec&, const MetaMap&) const; friend class ::TestFile; friend class ::TestStream; std::string mFilename; /// The file-level metadata MetaMap::Ptr mMeta; /// The file stream that is open for reading mutable std::ifstream mInStream; /// Flag indicating if we have read in the global information (header, /// metadata, and grid descriptors) for this VDB file bool mIsOpen; /// Grid descriptors for all grids stored in the file, indexed by grid name NameMap mGridDescriptors; /// All grids, indexed by unique name (used only when mHasGridOffsets is false) Archive::NamedGridMap mNamedGrids; /// All grids stored in the file (used only when mHasGridOffsets is false) GridPtrVecPtr mGrids; }; //////////////////////////////////////// inline void File::write(const GridCPtrVec& grids, const MetaMap& metadata) const { this->writeGrids(grids, metadata); } template inline void File::write(const GridPtrContainerT& container, const MetaMap& metadata) const { GridCPtrVec grids; std::copy(container.begin(), container.end(), std::back_inserter(grids)); this->writeGrids(grids, metadata); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_FILE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Queue.cc0000644000000000000000000002352612252453157013231 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Queue.cc /// @author Peter Cucka #include "Queue.h" #include "File.h" #include "Stream.h" #include #include #include #include #include #include #include #include // for tbb::this_tbb_thread::sleep() #include #include // for std::max() #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { namespace { typedef tbb::mutex Mutex; typedef Mutex::scoped_lock Lock; // Abstract base class for queuable TBB tasks that adds a task completion callback class Task: public tbb::task { public: Task(Queue::Id id): mId(id) {} virtual ~Task() {} Queue::Id id() const { return mId; } void setNotifier(Queue::Notifier& notifier) { mNotify = notifier; } protected: void notify(Queue::Status status) { if (mNotify) mNotify(this->id(), status); } private: Queue::Id mId; Queue::Notifier mNotify; }; // Queuable TBB task that writes one or more grids to a .vdb file or an output stream class OutputTask: public Task { public: OutputTask(Queue::Id id, const GridCPtrVec& grids, const Archive& archive, const MetaMap& metadata) : Task(id) , mGrids(grids) , mArchive(archive.copy()) , mMetadata(metadata) {} virtual tbb::task* execute() { Queue::Status status = Queue::FAILED; try { mArchive->write(mGrids, mMetadata); status = Queue::SUCCEEDED; } catch (std::exception& e) { if (const char* msg = e.what()) { OPENVDB_LOG_ERROR(msg); } } catch (...) { } this->notify(status); return NULL; // no successor to this task } private: GridCPtrVec mGrids; boost::shared_ptr mArchive; MetaMap mMetadata; }; } // unnamed namespace //////////////////////////////////////// // Private implementation details of a Queue struct Queue::Impl { typedef std::map NotifierMap; /// @todo Provide more information than just "succeeded" or "failed"? typedef tbb::concurrent_hash_map StatusMap; Impl() : mTimeout(Queue::DEFAULT_TIMEOUT) , mCapacity(Queue::DEFAULT_CAPACITY) , mNextId(1) , mNextNotifierId(1) { mNumTasks = 0; // note: must explicitly zero-initialize atomics } ~Impl() {} // Disallow copying of instances of this class. Impl(const Impl&); Impl& operator=(const Impl&); // This method might be called from multiple threads. void setStatus(Queue::Id id, Queue::Status status) { StatusMap::accessor acc; mStatus.insert(acc, id); acc->second = status; } // This method might be called from multiple threads. void setStatusWithNotification(Queue::Id id, Queue::Status status) { const bool completed = (status == SUCCEEDED || status == FAILED); // Update the task's entry in the status map with the new status. this->setStatus(id, status); // If the client registered any callbacks, call them now. bool didNotify = false; { // tbb::concurrent_hash_map does not support concurrent iteration // (i.e., iteration concurrent with insertion or deletion), // so we use a mutex-protected STL map instead. But if a callback // invokes a notifier method such as removeNotifier() on this queue, // the result will be a deadlock. /// @todo Is it worth trying to avoid such deadlocks? Lock lock(mNotifierMutex); if (!mNotifiers.empty()) { didNotify = true; for (NotifierMap::const_iterator it = mNotifiers.begin(); it != mNotifiers.end(); ++it) { it->second(id, status); } } } // If the task completed and callbacks were called, remove // the task's entry from the status map. if (completed) { if (didNotify) { StatusMap::accessor acc; if (mStatus.find(acc, id)) { mStatus.erase(acc); } } --mNumTasks; } } bool canEnqueue() const { return mNumTasks < Int64(mCapacity); } void enqueue(Task& task) { tbb::tick_count start = tbb::tick_count::now(); while (!canEnqueue()) { tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(0.5/*sec*/)); if ((tbb::tick_count::now() - start).seconds() > double(mTimeout)) { OPENVDB_THROW(RuntimeError, "unable to queue I/O task; " << mTimeout << "-second time limit expired"); } } Queue::Notifier notify = boost::bind(&Impl::setStatusWithNotification, this, _1, _2); task.setNotifier(notify); this->setStatus(task.id(), Queue::PENDING); tbb::task::enqueue(task); ++mNumTasks; } Index32 mTimeout; Index32 mCapacity; tbb::atomic mNumTasks; Index32 mNextId; StatusMap mStatus; NotifierMap mNotifiers; Index32 mNextNotifierId; Mutex mNotifierMutex; }; //////////////////////////////////////// Queue::Queue(Index32 capacity): mImpl(new Impl) { mImpl->mCapacity = capacity; } Queue::~Queue() { // Wait for all queued tasks to complete (successfully or unsuccessfully). /// @todo Allow the queue to be destroyed while there are uncompleted tasks /// (e.g., by keeping a static registry of queues that also dispatches /// or blocks notifications)? while (mImpl->mNumTasks > 0) { tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(0.5/*sec*/)); } } //////////////////////////////////////// bool Queue::empty() const { return (mImpl->mNumTasks == 0); } Index32 Queue::size() const { return Index32(std::max(0, mImpl->mNumTasks)); } Index32 Queue::capacity() const { return mImpl->mCapacity; } void Queue::setCapacity(Index32 n) { mImpl->mCapacity = std::max(1, n); } /// @todo void Queue::setCapacity(Index64 bytes); /// @todo Provide a way to limit the number of tasks in flight /// (e.g., by enqueueing tbb::tasks that pop Tasks off a concurrent_queue)? /// @todo Remove any tasks from the queue that are not currently executing. //void clear() const; Index32 Queue::timeout() const { return mImpl->mTimeout; } void Queue::setTimeout(Index32 sec) { mImpl->mTimeout = sec; } //////////////////////////////////////// Queue::Status Queue::status(Id id) const { Impl::StatusMap::const_accessor acc; if (mImpl->mStatus.find(acc, id)) { const Status status = acc->second; if (status == SUCCEEDED || status == FAILED) { mImpl->mStatus.erase(acc); } return status; } return UNKNOWN; } Queue::Id Queue::addNotifier(Notifier notify) { Lock lock(mImpl->mNotifierMutex); Queue::Id id = mImpl->mNextNotifierId++; mImpl->mNotifiers[id] = notify; return id; } void Queue::removeNotifier(Id id) { Lock lock(mImpl->mNotifierMutex); Impl::NotifierMap::iterator it = mImpl->mNotifiers.find(id); if (it != mImpl->mNotifiers.end()) { mImpl->mNotifiers.erase(it); } } void Queue::clearNotifiers() { Lock lock(mImpl->mNotifierMutex); mImpl->mNotifiers.clear(); } //////////////////////////////////////// Queue::Id Queue::writeGrid(GridBase::ConstPtr grid, const Archive& archive, const MetaMap& metadata) { return writeGridVec(GridCPtrVec(1, grid), archive, metadata); } Queue::Id Queue::writeGridVec(const GridCPtrVec& grids, const Archive& archive, const MetaMap& metadata) { // From the "GUI Thread" chapter in the TBB Design Patterns guide OutputTask* task = new(tbb::task::allocate_root()) OutputTask(mImpl->mNextId++, grids, archive, metadata); mImpl->enqueue(*task); return task->id(); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Compression.h0000644000000000000000000006021412252453157014303 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED #define OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED #include #include // for negative() #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { /// @brief OR-able bit flags for compression options on input and output streams /// @details ///
///
COMPRESS_NONE ///
On write, don't compress data.
/// On read, the input stream contains uncompressed data. /// ///
COMPRESS_ZIP ///
When writing grids other than level sets or fog volumes, apply ZIP compression /// to internal and leaf node value buffers.
/// On read of grids other than level sets or fog volumes, the value buffers /// of internal and leaf nodes are ZIP-compressed. /// ///
COMPRESS_ACTIVE_MASK ///
When writing a grid of any class, don't output a node's inactive values /// if it has two or fewer distinct values. Instead, output minimal information /// to permit the lossless reconstruction of inactive values.
/// On read, nodes might have been stored without inactive values. /// Where necessary, reconstruct inactive values from available information. ///
enum { COMPRESS_NONE = 0, COMPRESS_ZIP = 0x1, COMPRESS_ACTIVE_MASK = 0x2 }; /// Return a string describing the given compression flags. OPENVDB_API std::string compressionToString(uint32_t flags); //////////////////////////////////////// /// @internal Per-node indicator byte that specifies what additional metadata /// is stored to permit reconstruction of inactive values enum { /*0*/ NO_MASK_OR_INACTIVE_VALS, // no inactive vals, or all inactive vals are +background /*1*/ NO_MASK_AND_MINUS_BG, // all inactive vals are -background /*2*/ NO_MASK_AND_ONE_INACTIVE_VAL, // all inactive vals have the same non-background val /*3*/ MASK_AND_NO_INACTIVE_VALS, // mask selects between -background and +background /*4*/ MASK_AND_ONE_INACTIVE_VAL, // mask selects between backgd and one other inactive val /*5*/ MASK_AND_TWO_INACTIVE_VALS // mask selects between two non-background inactive vals }; //////////////////////////////////////// /// @brief RealToHalf and its specializations define a mapping from /// floating-point data types to analogous half float types. template struct RealToHalf { enum { isReal = false }; // unless otherwise specified, type T is not a floating-point type typedef T HalfT; // type T's half float analogue is T itself }; template<> struct RealToHalf { enum { isReal = true }; typedef half HalfT; }; template<> struct RealToHalf { enum { isReal = true }; typedef half HalfT; }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec2H HalfT; }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec2H HalfT; }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec3H HalfT; }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec3H HalfT; }; /// Return the given value truncated to 16-bit float precision. template inline T truncateRealToHalf(const T& val) { return T(typename RealToHalf::HalfT(val)); } //////////////////////////////////////// OPENVDB_API void zipToStream(std::ostream&, const char* data, size_t numBytes); OPENVDB_API void unzipFromStream(std::istream&, char* data, size_t numBytes); /// @brief Read data from a stream. /// @param is the input stream /// @param data the contiguous array of data to read in /// @param count the number of elements to read in /// @param compressed if @c true, assume the data is ZIP compressed and uncompress it /// This default implementation is instantiated only for types whose size /// can be determined by the sizeof() operator. template inline void readData(std::istream& is, T* data, Index count, bool compressed) { if (compressed) { unzipFromStream(is, reinterpret_cast(data), sizeof(T) * count); } else { is.read(reinterpret_cast(data), sizeof(T) * count); } } /// Specialization for std::string input template<> inline void readData(std::istream& is, std::string* data, Index count, bool /*compressed*/) { for (Index i = 0; i < count; ++i) { size_t len = 0; is >> len; //data[i].resize(len); //is.read(&(data[i][0]), len); std::string buffer(len+1, ' '); is.read(&buffer[0], len+1 ); data[i].assign(buffer, 0, len); } } /// HalfReader wraps a static function, read(), that is analogous to readData(), above, /// except that it is partially specialized for floating-point types in order to promote /// 16-bit half float values to full float. A wrapper class is required because /// only classes, not functions, can be partially specialized. template struct HalfReader; /// Partial specialization for non-floating-point types (no half to float promotion) template struct HalfReader { static inline void read(std::istream& is, T* data, Index count, bool compressed) { readData(is, data, count, compressed); } }; /// Partial specialization for floating-point types template struct HalfReader { typedef typename RealToHalf::HalfT HalfT; static inline void read(std::istream& is, T* data, Index count, bool compressed) { if (count < 1) return; std::vector halfData(count); // temp buffer into which to read half float values readData(is, reinterpret_cast(&halfData[0]), count, compressed); // Copy half float values from the temporary buffer to the full float output array. std::copy(halfData.begin(), halfData.end(), data); } }; /// Write data to a stream. /// @param os the output stream /// @param data the contiguous array of data to write /// @param count the number of elements to write out /// @param compress if @c true, apply ZIP compression to the data /// This default implementation is instantiated only for types whose size /// can be determined by the sizeof() operator. template inline void writeData(std::ostream &os, const T *data, Index count, bool compress) { if (compress) { zipToStream(os, reinterpret_cast(data), sizeof(T) * count); } else { os.write(reinterpret_cast(data), sizeof(T) * count); } } /// Specialization for std::string output /// @todo Add compression template<> inline void writeData(std::ostream& os, const std::string* data, Index count, bool /*compress*/) { for (Index i = 0; i < count; ++i) { const size_t len = data[i].size(); os << len; os.write(data[i].c_str(), len+1); //os.write(&(data[i][0]), len ); } } /// HalfWriter wraps a static function, write(), that is analogous to writeData(), above, /// except that it is partially specialized for floating-point types in order to quantize /// floating-point values to 16-bit half float. A wrapper class is required because /// only classes, not functions, can be partially specialized. template struct HalfWriter; /// Partial specialization for non-floating-point types (no float to half quantization) template struct HalfWriter { static inline void write(std::ostream& os, const T* data, Index count, bool compress) { writeData(os, data, count, compress); } }; /// Partial specialization for floating-point types template struct HalfWriter { typedef typename RealToHalf::HalfT HalfT; static inline void write(std::ostream& os, const T* data, Index count, bool compress) { if (count < 1) return; // Convert full float values to half float, then output the half float array. std::vector halfData(count); std::copy(data, data + count, halfData.begin()); writeData(os, reinterpret_cast(&halfData[0]), count, compress); } }; #ifdef _MSC_VER /// Specialization to avoid double to float warnings in MSVC template<> struct HalfWriter { typedef RealToHalf::HalfT HalfT; static inline void write(std::ostream& os, const double* data, Index count, bool compress) { if (count < 1) return; // Convert full float values to half float, then output the half float array. std::vector halfData(count); for (Index i = 0; i < count; ++i) halfData[i] = float(data[i]); writeData(os, reinterpret_cast(&halfData[0]), count, compress); } }; #endif // _MSC_VER //////////////////////////////////////// /// Populate the given buffer with @a destCount values of type @c ValueT /// read from the given stream, taking into account that the stream might /// have been compressed via one of several supported schemes. /// [Mainly for internal use] /// @param is a stream from which to read data (possibly compressed, /// depending on the stream's compression settings) /// @param destBuf a buffer into which to read values of type @c ValueT /// @param destCount the number of values to be stored in the buffer /// @param valueMask a bitmask (typically, a node's value mask) indicating /// which positions in the buffer correspond to active values /// @param fromHalf if true, read 16-bit half floats from the input stream /// and convert them to full floats template inline void readCompressedValues(std::istream& is, ValueT* destBuf, Index destCount, const MaskT& valueMask, bool fromHalf) { // Get the stream's compression settings. const uint32_t compression = getDataCompression(is); const bool zipped = compression & COMPRESS_ZIP, maskCompressed = compression & COMPRESS_ACTIVE_MASK; int8_t metadata = NO_MASK_OR_INACTIVE_VALS; if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { // Read the flag that specifies what, if any, additional metadata // (selection mask and/or inactive value(s)) is saved. is.read(reinterpret_cast(&metadata), /*bytes=*/1); } ValueT background = zeroVal(); if (const void* bgPtr = getGridBackgroundValuePtr(is)) { background = *static_cast(bgPtr); } ValueT inactiveVal1 = background; ValueT inactiveVal0 = ((metadata == NO_MASK_OR_INACTIVE_VALS) ? background : math::negative(background)); if (metadata != NO_MASK_OR_INACTIVE_VALS && metadata != NO_MASK_AND_MINUS_BG && metadata != MASK_AND_NO_INACTIVE_VALS) { // Read one of at most two distinct inactive values. is.read(reinterpret_cast(&inactiveVal0), sizeof(ValueT)); if (metadata == MASK_AND_TWO_INACTIVE_VALS) { // Read the second of two distinct inactive values. is.read(reinterpret_cast(&inactiveVal1), sizeof(ValueT)); } } MaskT selectionMask; if (metadata != NO_MASK_OR_INACTIVE_VALS && metadata != NO_MASK_AND_MINUS_BG && metadata != NO_MASK_AND_ONE_INACTIVE_VAL) { // For use in mask compression (only), read the bitmask that selects // between two distinct inactive values. selectionMask.load(is); } ValueT* tempBuf = destBuf; boost::scoped_array scopedTempBuf; Index tempCount = destCount; if (maskCompressed && getFormatVersion(is) >= OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { tempCount = valueMask.countOn(); if (tempCount != destCount) { // If this node has inactive voxels, allocate a temporary buffer // into which to read just the active values. scopedTempBuf.reset(new ValueT[tempCount]); tempBuf = scopedTempBuf.get(); } } // Read in the buffer. if (fromHalf) { HalfReader::isReal, ValueT>::read(is, tempBuf, tempCount, zipped); } else { readData(is, tempBuf, tempCount, zipped); } // If mask compression is enabled and the number of active values read into // the temp buffer is smaller than the size of the destination buffer, // then there are missing (inactive) values. if (maskCompressed && tempCount != destCount) { // Restore inactive values, using the background value and, if available, // the inside/outside mask. (For fog volumes, the destination buffer is assumed // to be initialized to background value zero, so inactive values can be ignored.) for (Index destIdx = 0, tempIdx = 0; destIdx < MaskT::SIZE; ++destIdx) { if (valueMask.isOn(destIdx)) { // Copy a saved active value into this node's buffer. destBuf[destIdx] = tempBuf[tempIdx]; ++tempIdx; } else { // Reconstruct an unsaved inactive value and copy it into this node's buffer. destBuf[destIdx] = (selectionMask.isOn(destIdx) ? inactiveVal1 : inactiveVal0); } } } } /// Write @a srcCount values of type @c ValueT to the given stream, optionally /// after compressing the values via one of several supported schemes. /// [Mainly for internal use] /// @param os a stream to which to write data (possibly compressed, depending /// on the stream's compression settings) /// @param srcBuf a buffer containing values of type @c ValueT to be written /// @param srcCount the number of values stored in the buffer /// @param valueMask a bitmask (typically, a node's value mask) indicating /// which positions in the buffer correspond to active values /// @param childMask a bitmask (typically, a node's child mask) indicating /// which positions in the buffer correspond to child node pointers /// @param toHalf if true, convert floating-point values to 16-bit half floats template inline void writeCompressedValues(std::ostream& os, ValueT* srcBuf, Index srcCount, const MaskT& valueMask, const MaskT& childMask, bool toHalf) { struct Local { // Comparison function for values static inline bool eq(const ValueT& a, const ValueT& b) { return math::isExactlyEqual(a, b); } }; // Get the stream's compression settings. const uint32_t compress = getDataCompression(os); const bool zip = compress & COMPRESS_ZIP, maskCompress = compress & COMPRESS_ACTIVE_MASK; Index tempCount = srcCount; ValueT* tempBuf = srcBuf; boost::scoped_array scopedTempBuf; int8_t metadata = NO_MASK_OR_INACTIVE_VALS; if (!maskCompress) { os.write(reinterpret_cast(&metadata), /*bytes=*/1); } else { // A valid level set's inactive values are either +background (outside) // or -background (inside), and a fog volume's inactive values are all zero. // Rather than write out all of these values, we can store just the active values // (given that the value mask specifies their positions) and, if necessary, // an inside/outside bitmask. const ValueT zero = zeroVal(); ValueT background = zero; if (const void* bgPtr = getGridBackgroundValuePtr(os)) { background = *static_cast(bgPtr); } /// @todo Consider all values, not just inactive values? ValueT inactiveVal[2] = { background, background }; int numUniqueInactiveVals = 0; for (typename MaskT::OffIterator it = valueMask.beginOff(); numUniqueInactiveVals < 3 && it; ++it) { const Index32 idx = it.pos(); // Skip inactive values that are actually child node pointers. if (childMask.isOn(idx)) continue; const ValueT& val = srcBuf[idx]; const bool unique = !( (numUniqueInactiveVals > 0 && Local::eq(val, inactiveVal[0])) || (numUniqueInactiveVals > 1 && Local::eq(val, inactiveVal[1])) ); if (unique) { if (numUniqueInactiveVals < 2) inactiveVal[numUniqueInactiveVals] = val; ++numUniqueInactiveVals; } } metadata = NO_MASK_OR_INACTIVE_VALS; if (numUniqueInactiveVals == 1) { if (!Local::eq(inactiveVal[0], background)) { if (Local::eq(inactiveVal[0], math::negative(background))) { metadata = NO_MASK_AND_MINUS_BG; } else { metadata = NO_MASK_AND_ONE_INACTIVE_VAL; } } } else if (numUniqueInactiveVals == 2) { metadata = NO_MASK_OR_INACTIVE_VALS; if (!Local::eq(inactiveVal[0], background) && !Local::eq(inactiveVal[1], background)) { // If neither inactive value is equal to the background, both values // need to be saved, along with a mask that selects between them. metadata = MASK_AND_TWO_INACTIVE_VALS; } else if (Local::eq(inactiveVal[1], background)) { if (Local::eq(inactiveVal[0], math::negative(background))) { // If the second inactive value is equal to the background and // the first is equal to -background, neither value needs to be saved, // but save a mask that selects between -background and +background. metadata = MASK_AND_NO_INACTIVE_VALS; } else { // If the second inactive value is equal to the background, only // the first value needs to be saved, along with a mask that selects // between it and the background. metadata = MASK_AND_ONE_INACTIVE_VAL; } } else if (Local::eq(inactiveVal[0], background)) { if (Local::eq(inactiveVal[1], math::negative(background))) { // If the first inactive value is equal to the background and // the second is equal to -background, neither value needs to be saved, // but save a mask that selects between -background and +background. metadata = MASK_AND_NO_INACTIVE_VALS; std::swap(inactiveVal[0], inactiveVal[1]); } else { // If the first inactive value is equal to the background, swap it // with the second value and save only that value, along with a mask // that selects between it and the background. std::swap(inactiveVal[0], inactiveVal[1]); metadata = MASK_AND_ONE_INACTIVE_VAL; } } } os.write(reinterpret_cast(&metadata), /*bytes=*/1); if (metadata != NO_MASK_OR_INACTIVE_VALS && metadata != NO_MASK_AND_MINUS_BG && metadata != MASK_AND_NO_INACTIVE_VALS) { if (!toHalf) { // Write one of at most two distinct inactive values. os.write(reinterpret_cast(&inactiveVal[0]), sizeof(ValueT)); if (metadata == MASK_AND_TWO_INACTIVE_VALS) { // Write the second of two distinct inactive values. os.write(reinterpret_cast(&inactiveVal[1]), sizeof(ValueT)); } } else { // Write one of at most two distinct inactive values. ValueT truncatedVal = truncateRealToHalf(inactiveVal[0]); os.write(reinterpret_cast(&truncatedVal), sizeof(ValueT)); if (metadata == MASK_AND_TWO_INACTIVE_VALS) { // Write the second of two distinct inactive values. truncatedVal = truncateRealToHalf(inactiveVal[1]); os.write(reinterpret_cast(&truncatedVal), sizeof(ValueT)); } } } if (metadata == NO_MASK_OR_INACTIVE_VALS && numUniqueInactiveVals > 2) { // If there are more than two unique inactive values, the entire input buffer // needs to be saved (both active and inactive values). /// @todo Save the selection mask as long as most of the inactive values /// are one of two values? } else { // Create a new array to hold just the active values. scopedTempBuf.reset(new ValueT[srcCount]); tempBuf = scopedTempBuf.get(); if (metadata == NO_MASK_OR_INACTIVE_VALS || metadata == NO_MASK_AND_MINUS_BG || metadata == NO_MASK_AND_ONE_INACTIVE_VAL) { // Copy active values to the contiguous array. tempCount = 0; for (typename MaskT::OnIterator it = valueMask.beginOn(); it; ++it, ++tempCount) { tempBuf[tempCount] = srcBuf[it.pos()]; } } else { // Copy active values to a new, contiguous array and populate a bitmask // that selects between two distinct inactive values. MaskT selectionMask; tempCount = 0; for (Index srcIdx = 0; srcIdx < srcCount; ++srcIdx) { if (valueMask.isOn(srcIdx)) { // active value tempBuf[tempCount] = srcBuf[srcIdx]; ++tempCount; } else { // inactive value if (Local::eq(srcBuf[srcIdx], inactiveVal[1])) { selectionMask.setOn(srcIdx); // inactive value 1 } // else inactive value 0 } } assert(tempCount == valueMask.countOn()); // Write out the mask that selects between two inactive values. selectionMask.save(os); } } } // Write out the buffer. if (toHalf) { HalfWriter::isReal, ValueT>::write(os, tempBuf, tempCount, zip); } else { writeData(os, tempBuf, tempCount, zip); } } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Stream.cc0000644000000000000000000001075012252453157013373 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Stream.h" #include #include #include #include #include "GridDescriptor.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { Stream::Stream(std::istream& is): mOutputStream(NULL) { if (!is) return; readHeader(is); // Tag the input stream with the library and file format version numbers // and the compression options specified in the header. io::setVersion(is, libraryVersion(), fileVersion()); io::setDataCompression(is, compressionFlags()); // Read in the VDB metadata. mMeta.reset(new MetaMap); mMeta->readMeta(is); // Read in the number of grids. const boost::int32_t gridCount = readGridCount(is); // Read in all grids and insert them into mGrids. mGrids.reset(new GridPtrVec); std::vector descriptors; descriptors.reserve(gridCount); Archive::NamedGridMap namedGrids; for (boost::int32_t i = 0; i < gridCount; ++i) { GridDescriptor gd; gd.read(is); descriptors.push_back(gd); GridBase::Ptr grid = readGrid(gd, is); mGrids->push_back(grid); namedGrids[gd.uniqueName()] = grid; } // Connect instances (grids that share trees with other grids). for (size_t i = 0, N = descriptors.size(); i < N; ++i) { Archive::connectInstance(descriptors[i], namedGrids); } } Stream::Stream(): mOutputStream(NULL) { } Stream::Stream(std::ostream& os): mOutputStream(&os) { } Stream::~Stream() { } boost::shared_ptr Stream::copy() const { return boost::shared_ptr(new Stream(*this)); } //////////////////////////////////////// GridBase::Ptr Stream::readGrid(const GridDescriptor& gd, std::istream& is) const { GridBase::Ptr grid; if (!GridBase::isRegistered(gd.gridType())) { OPENVDB_THROW(TypeError, "can't read grid \"" << GridDescriptor::nameAsString(gd.uniqueName()) << "\" from input stream because grid type " << gd.gridType() << " is unknown"); } else { grid = GridBase::createGrid(gd.gridType()); if (grid) grid->setSaveFloatAsHalf(gd.saveFloatAsHalf()); Archive::readGrid(grid, gd, is); } return grid; } void Stream::writeGrids(std::ostream& os, const GridCPtrVec& grids, const MetaMap& metadata) const { Archive::write(os, grids, /*seekable=*/false, metadata); } //////////////////////////////////////// MetaMap::Ptr Stream::getMetadata() const { // Return a deep copy of the file-level metadata, which was read // when this object was constructed. return MetaMap::Ptr(new MetaMap(*mMeta)); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/GridDescriptor.cc0000644000000000000000000001457612252453157015076 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "GridDescriptor.h" #include #include // for boost::ends_with() #include // for boost::erase_last() #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { namespace { // In order not to break backward compatibility with existing VDB files, // grids stored using 16-bit half floats are flagged by adding the following // suffix to the grid's type name on output. The suffix is removed on input // and the grid's "save float as half" flag set accordingly. const char* HALF_FLOAT_TYPENAME_SUFFIX = "_HalfFloat"; const char* SEP = "\x1e"; // ASCII "record separator" } GridDescriptor::GridDescriptor(): mSaveFloatAsHalf(false), mGridPos(0), mBlockPos(0), mEndPos(0) { } GridDescriptor::GridDescriptor(const Name &name, const Name &type, bool half): mGridName(stripSuffix(name)), mUniqueName(name), mGridType(type), mSaveFloatAsHalf(half), mGridPos(0), mBlockPos(0), mEndPos(0) { } GridDescriptor::~GridDescriptor() { } void GridDescriptor::writeHeader(std::ostream &os) const { writeString(os, mUniqueName); Name gridType = mGridType; if (mSaveFloatAsHalf) gridType += HALF_FLOAT_TYPENAME_SUFFIX; writeString(os, gridType); writeString(os, mInstanceParentName); } void GridDescriptor::writeStreamPos(std::ostream &os) const { os.write(reinterpret_cast(&mGridPos), sizeof(boost::int64_t)); os.write(reinterpret_cast(&mBlockPos), sizeof(boost::int64_t)); os.write(reinterpret_cast(&mEndPos), sizeof(boost::int64_t)); } GridBase::Ptr GridDescriptor::read(std::istream &is) { // Read in the name. mUniqueName = readString(is); mGridName = stripSuffix(mUniqueName); // Read in the grid type. mGridType = readString(is); if (boost::ends_with(mGridType, HALF_FLOAT_TYPENAME_SUFFIX)) { mSaveFloatAsHalf = true; boost::erase_last(mGridType, HALF_FLOAT_TYPENAME_SUFFIX); } if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { mInstanceParentName = readString(is); } // Create the grid of the type if it has been registered. if (!GridBase::isRegistered(mGridType)) { OPENVDB_THROW(LookupError, "Cannot read grid." << " Grid type " << mGridType << " is not registered."); } // else GridBase::Ptr grid = GridBase::createGrid(mGridType); if (grid) grid->setSaveFloatAsHalf(mSaveFloatAsHalf); // Read in the offsets. is.read(reinterpret_cast(&mGridPos), sizeof(boost::int64_t)); is.read(reinterpret_cast(&mBlockPos), sizeof(boost::int64_t)); is.read(reinterpret_cast(&mEndPos), sizeof(boost::int64_t)); return grid; } void GridDescriptor::seekToGrid(std::istream &is) const { is.seekg(mGridPos, std::ios_base::beg); } void GridDescriptor::seekToBlocks(std::istream &is) const { is.seekg(mBlockPos, std::ios_base::beg); } void GridDescriptor::seekToEnd(std::istream &is) const { is.seekg(mEndPos, std::ios_base::beg); } void GridDescriptor::seekToGrid(std::ostream &os) const { os.seekp(mGridPos, std::ios_base::beg); } void GridDescriptor::seekToBlocks(std::ostream &os) const { os.seekp(mBlockPos, std::ios_base::beg); } void GridDescriptor::seekToEnd(std::ostream &os) const { os.seekp(mEndPos, std::ios_base::beg); } //////////////////////////////////////// // static Name GridDescriptor::addSuffix(const Name& name, int n) { std::ostringstream ostr; ostr << name << SEP << n; return ostr.str(); } // static Name GridDescriptor::stripSuffix(const Name& name) { return name.substr(0, name.find(SEP)); } // static std::string GridDescriptor::nameAsString(const Name& name) { std::string::size_type pos = name.find(SEP); if (pos == std::string::npos) return name; return name.substr(0, pos) + "[" + name.substr(pos + 1) + "]"; } //static Name GridDescriptor::stringAsUniqueName(const std::string& s) { Name ret = s; if (!ret.empty() && *ret.rbegin() == ']') { // found trailing ']' std::string::size_type pos = ret.find("["); // Replace "[N]" with SEP "N". if (pos != std::string::npos) { if (ret.substr(pos) == "[0]") { // "name[0]" is equivalent to "name". ret.erase(pos); } else { ret.resize(ret.size() - 1); // drop trailing ']' ret.replace(ret.find("["), 1, SEP); } } } return ret; } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Queue.h0000644000000000000000000002464112252453157013072 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Queue.h /// @author Peter Cucka #ifndef OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED #define OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED #include #include #include #include #include // for std::copy #include // for std::back_inserter namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { class Archive; /// @brief Queue for asynchronous output of grids to files or streams /// /// @warning The queue holds shared pointers to grids. It is not safe /// to modify a grid that has been placed in the queue. Instead, /// make a deep copy of the grid (Grid::deepCopy()). /// /// @par Example: /// @code /// #include /// #include /// #include /// #include /// /// using openvdb::io::Queue; /// /// struct MyNotifier /// { /// // Use a concurrent container, because queue callback functions /// // must be thread-safe. /// typedef tbb::concurrent_hash_map FilenameMap; /// FilenameMap filenames; /// /// // Callback function that prints the status of a completed task. /// void callback(Queue::Id id, Queue::Status status) /// { /// const bool ok = (status == Queue::SUCCEEDED); /// FilenameMap::accessor acc; /// if (filenames.find(acc, id)) { /// std::cout << (ok ? "wrote " : "failed to write ") /// << acc->second << std::endl; /// filenames.erase(acc); /// } /// } /// }; /// /// int main() /// { /// // Construct an object to receive notifications from the queue. /// // The object's lifetime must exceed the queue's. /// MyNotifier notifier; /// /// Queue queue; /// /// // Register the callback() method of the MyNotifier object /// // to receive notifications of completed tasks. /// queue.addNotifier(boost::bind(&MyNotifier::callback, ¬ifier, _1, _2)); /// /// // Queue grids for output (e.g., for each step of a simulation). /// for (int step = 1; step <= 10; ++step) { /// openvdb::FloatGrid::Ptr grid = ...; /// /// std::ostringstream os; /// os << "mygrid." << step << ".vdb"; /// const std::string filename = os.str(); /// /// Queue::Id id = queue.writeGrid(grid, openvdb::io::File(filename)); /// /// // Associate the filename with the ID of the queued task. /// MyNotifier::FilenameMap::accessor acc; /// notifier.filenames.insert(acc, id); /// acc->second = filename; /// } /// } /// @endcode /// Output: /// @code /// wrote mygrid.1.vdb /// wrote mygrid.2.vdb /// wrote mygrid.4.vdb /// wrote mygrid.3.vdb /// ... /// wrote mygrid.10.vdb /// @endcode /// Note that tasks do not necessarily complete in the order in which they were queued. class OPENVDB_API Queue { public: /// Default maximum queue length (see setCapacity()) static const Index32 DEFAULT_CAPACITY = 100; /// @brief Default maximum time in seconds to wait to queue a task /// when the queue is full (see setTimeout()) static const Index32 DEFAULT_TIMEOUT = 120; // seconds /// ID number of a queued task or of a registered notification callback typedef Index32 Id; /// Status of a queued task enum Status { UNKNOWN, PENDING, SUCCEEDED, FAILED }; /// Construct a queue with the given capacity. explicit Queue(Index32 capacity = DEFAULT_CAPACITY); /// Block until all queued tasks complete (successfully or unsuccessfully). ~Queue(); /// @brief Return @c true if the queue is empty. bool empty() const; /// @brief Return the number of tasks currently in the queue. Index32 size() const; /// @brief Return the maximum number of tasks allowed in the queue. /// @details Once the queue has reached its maximum size, adding /// a new task will block until an existing task has executed. Index32 capacity() const; /// Set the maximum number of tasks allowed in the queue. void setCapacity(Index32); /// Return the maximum number of seconds to wait to queue a task when the queue is full. Index32 timeout() const; /// Set the maximum number of seconds to wait to queue a task when the queue is full. void setTimeout(Index32 seconds = DEFAULT_TIMEOUT); /// @brief Return the status of the task with the given ID. /// @note Querying the status of a task that has already completed /// (whether successfully or not) removes the task from the status registry. /// Subsequent queries of its status will return UNKNOWN. Status status(Id) const; typedef boost::function Notifier; /// @brief Register a function that will be called with a task's ID /// and status when that task completes, whether successfully or not. /// @return an ID that can be passed to removeNotifier() to deregister the function /// @details When multiple notifiers are registered, they are called /// in the order in which they were registered. /// @warning Notifiers are called from worker threads, so they must be thread-safe /// and their lifetimes must exceed that of the queue. They must also not call, /// directly or indirectly, addNotifier(), removeNotifier() or clearNotifiers(), /// as that can result in a deadlock. Id addNotifier(Notifier); /// Deregister the notifier with the given ID. void removeNotifier(Id); /// Deregister all notifiers. void clearNotifiers(); /// @brief Queue a single grid for output to a file or stream. /// @param grid the grid to be serialized /// @param archive the io::File or io::Stream to which to output the grid /// @param fileMetadata optional file-level metadata /// @return an ID with which the status of the queued task can be queried /// @throw RuntimeError if the task cannot be queued within the time limit /// (see setTimeout()) because the queue is full /// @par Example: /// @code /// openvdb::FloatGrid::Ptr grid = ...; /// /// openvdb::io::Queue queue; /// /// // Write the grid to the file mygrid.vdb. /// queue.writeGrid(grid, openvdb::io::File("mygrid.vdb")); /// /// // Stream the grid to a binary string. /// std::ostringstream ostr(std::ios_base::binary); /// queue.writeGrid(grid, openvdb::io::Stream(ostr)); /// @endcode Id writeGrid(GridBase::ConstPtr grid, const Archive& archive, const MetaMap& fileMetadata = MetaMap()); /// @brief Queue a container of grids for output to a file. /// @param grids any iterable container of grid pointers /// (e.g., a GridPtrVec or GridPtrSet) /// @param archive the io::File or io::Stream to which to output the grids /// @param fileMetadata optional file-level metadata /// @return an ID with which the status of the queued task can be queried /// @throw RuntimeError if the task cannot be queued within the time limit /// (see setTimeout()) because the queue is full /// @par Example: /// @code /// openvdb::FloatGrid::Ptr floatGrid = ...; /// openvdb::BoolGrid::Ptr boolGrid = ...; /// openvdb::GridPtrVec grids; /// grids.push_back(floatGrid); /// grids.push_back(boolGrid); /// /// openvdb::io::Queue queue; /// /// // Write the grids to the file mygrid.vdb. /// queue.write(grids, openvdb::io::File("mygrid.vdb")); /// /// // Stream the grids to a (binary) string. /// std::ostringstream ostr(std::ios_base::binary); /// queue.write(grids, openvdb::io::Stream(ostr)); /// @endcode template Id write(const GridPtrContainer& grids, const Archive& archive, const MetaMap& fileMetadata = MetaMap()); private: // Disallow copying of instances of this class. Queue(const Queue&); Queue& operator=(const Queue&); Id writeGridVec(const GridCPtrVec&, const Archive&, const MetaMap&); class Impl; boost::shared_ptr mImpl; }; // class Queue template inline Queue::Id Queue::write(const GridPtrContainer& container, const Archive& archive, const MetaMap& metadata) { GridCPtrVec grids; std::copy(container.begin(), container.end(), std::back_inserter(grids)); return this->writeGridVec(grids, archive, metadata); } // Specialization for vectors of const Grid pointers; no copying necessary template<> inline Queue::Id Queue::write(const GridCPtrVec& grids, const Archive& archive, const MetaMap& metadata) { return this->writeGridVec(grids, archive, metadata); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_QUEUE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Archive.cc0000644000000000000000000005720612252453157013530 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include "Archive.h" #include // for std::find_if() #include // for std::memcpy() #include #include #include #include #include #include #include #include #include "GridDescriptor.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { // Indices into a stream's internal extensible array of values used by readers and writers struct StreamState { static const long MAGIC_NUMBER; StreamState(); ~StreamState(); int magicNumber; int fileVersion; int libraryMajorVersion; int libraryMinorVersion; int dataCompression; int writeGridStatsMetadata; int gridBackground; int gridClass; } sStreamState; const long StreamState::MAGIC_NUMBER = long((uint64_t(OPENVDB_MAGIC) << 32) | (uint64_t(OPENVDB_MAGIC))); const uint32_t Archive::DEFAULT_COMPRESSION_FLAGS = (COMPRESS_ZIP | COMPRESS_ACTIVE_MASK); //////////////////////////////////////// StreamState::StreamState(): magicNumber(std::ios_base::xalloc()) { // Having reserved an entry (the one at index magicNumber) in the extensible array // associated with every stream, store a magic number at that location in the // array belonging to the cout stream. std::cout.iword(magicNumber) = MAGIC_NUMBER; std::cout.pword(magicNumber) = this; // Search for a lower-numbered entry in cout's array that already contains the magic number. /// @todo This assumes that the indices returned by xalloc() increase monotonically. int existingArray = -1; for (int i = 0; i < magicNumber; ++i) { if (std::cout.iword(i) == MAGIC_NUMBER) { existingArray = i; break; } } if (existingArray >= 0 && std::cout.pword(existingArray) != NULL) { // If a lower-numbered entry was found to contain the magic number, // a coexisting version of this library must have registered it. // In that case, the corresponding pointer should point to an existing // StreamState struct. Copy the other array indices from that StreamState // into this one, so as to share state with the other library. const StreamState& other = *static_cast(std::cout.pword(existingArray)); fileVersion = other.fileVersion; libraryMajorVersion = other.libraryMajorVersion; libraryMinorVersion = other.libraryMinorVersion; dataCompression = other.dataCompression; writeGridStatsMetadata = other.writeGridStatsMetadata; gridBackground = other.gridBackground; gridClass = other.gridClass; } else { // Reserve storage for per-stream file format and library version numbers // and other values of use to readers and writers. Each of the following // values is an index into the extensible arrays associated with all streams. // The indices are common to all streams, but the values stored at those indices // are unique to each stream. fileVersion = std::ios_base::xalloc(); libraryMajorVersion = std::ios_base::xalloc(); libraryMinorVersion = std::ios_base::xalloc(); dataCompression = std::ios_base::xalloc(); writeGridStatsMetadata = std::ios_base::xalloc(); gridBackground = std::ios_base::xalloc(); gridClass = std::ios_base::xalloc(); } } StreamState::~StreamState() { // Ensure that this StreamState struct can no longer be accessed. std::cout.iword(magicNumber) = 0; std::cout.pword(magicNumber) = NULL; } //////////////////////////////////////// Archive::Archive(): mFileVersion(OPENVDB_FILE_VERSION), mUuid(boost::uuids::nil_uuid()), mInputHasGridOffsets(false), mEnableInstancing(true), mCompression(DEFAULT_COMPRESSION_FLAGS), mEnableGridStats(true) { mLibraryVersion.first = OPENVDB_LIBRARY_MAJOR_VERSION; mLibraryVersion.second = OPENVDB_LIBRARY_MINOR_VERSION; } Archive::~Archive() { } boost::shared_ptr Archive::copy() const { return boost::shared_ptr(new Archive(*this)); } //////////////////////////////////////// std::string Archive::getUniqueTag() const { /// @todo Once versions of Boost < 1.44.0 are no longer in use, /// this can be replaced with "return boost::uuids::to_string(mUuid);". std::ostringstream ostr; ostr << mUuid; return ostr.str(); } bool Archive::isIdentical(const std::string& uuidStr) const { return uuidStr == getUniqueTag(); } //////////////////////////////////////// uint32_t getFormatVersion(std::istream& is) { return static_cast(is.iword(sStreamState.fileVersion)); } void Archive::setFormatVersion(std::istream& is) { is.iword(sStreamState.fileVersion) = mFileVersion; } VersionId getLibraryVersion(std::istream& is) { VersionId version; version.first = static_cast(is.iword(sStreamState.libraryMajorVersion)); version.second = static_cast(is.iword(sStreamState.libraryMinorVersion)); return version; } void Archive::setLibraryVersion(std::istream& is) { is.iword(sStreamState.libraryMajorVersion) = mLibraryVersion.first; is.iword(sStreamState.libraryMinorVersion) = mLibraryVersion.second; } std::string getVersion(std::istream& is) { VersionId version = getLibraryVersion(is); std::ostringstream ostr; ostr << version.first << "." << version.second << "/" << getFormatVersion(is); return ostr.str(); } void setCurrentVersion(std::istream& is) { is.iword(sStreamState.fileVersion) = OPENVDB_FILE_VERSION; is.iword(sStreamState.libraryMajorVersion) = OPENVDB_LIBRARY_MAJOR_VERSION; is.iword(sStreamState.libraryMinorVersion) = OPENVDB_LIBRARY_MINOR_VERSION; } void setVersion(std::ios_base& strm, const VersionId& libraryVersion, uint32_t fileVersion) { strm.iword(sStreamState.fileVersion) = fileVersion; strm.iword(sStreamState.libraryMajorVersion) = libraryVersion.first; strm.iword(sStreamState.libraryMinorVersion) = libraryVersion.second; } std::string Archive::version() const { std::ostringstream ostr; ostr << mLibraryVersion.first << "." << mLibraryVersion.second << "/" << mFileVersion; return ostr.str(); } //////////////////////////////////////// uint32_t getDataCompression(std::ios_base& strm) { return uint32_t(strm.iword(sStreamState.dataCompression)); } void setDataCompression(std::ios_base& strm, uint32_t compression) { strm.iword(sStreamState.dataCompression) = compression; } void Archive::setDataCompression(std::istream& is) { io::setDataCompression(is, mCompression); } bool Archive::isCompressionEnabled() const { return (mCompression & COMPRESS_ZIP); } void Archive::setCompressionEnabled(bool b) { if (b) mCompression |= COMPRESS_ZIP; else mCompression &= ~COMPRESS_ZIP; } void Archive::setGridCompression(std::ostream& os, const GridBase& grid) const { // Start with the options that are enabled globally for this archive. uint32_t compression = compressionFlags(); // Disable options that are inappropriate for the given grid. switch (grid.getGridClass()) { case GRID_LEVEL_SET: case GRID_FOG_VOLUME: // Zip compression is not used on level sets or fog volumes. compression = compression & ~COMPRESS_ZIP; break; default: break; } io::setDataCompression(os, compression); os.write(reinterpret_cast(&compression), sizeof(uint32_t)); } void Archive::readGridCompression(std::istream& is) { if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { uint32_t compression = COMPRESS_NONE; is.read(reinterpret_cast(&compression), sizeof(uint32_t)); io::setDataCompression(is, compression); } } //////////////////////////////////////// bool getWriteGridStatsMetadata(std::ostream& os) { return os.iword(sStreamState.writeGridStatsMetadata) != 0; } void Archive::setWriteGridStatsMetadata(std::ostream& os) { os.iword(sStreamState.writeGridStatsMetadata) = mEnableGridStats; } //////////////////////////////////////// uint32_t getGridClass(std::ios_base& strm) { const uint32_t val = strm.iword(sStreamState.gridClass); if (val >= NUM_GRID_CLASSES) return GRID_UNKNOWN; return val; } void setGridClass(std::ios_base& strm, uint32_t cls) { strm.iword(sStreamState.gridClass) = long(cls); } const void* getGridBackgroundValuePtr(std::ios_base& strm) { return strm.pword(sStreamState.gridBackground); } void setGridBackgroundValuePtr(std::ios_base& strm, const void* background) { strm.pword(sStreamState.gridBackground) = const_cast(background); } //////////////////////////////////////// bool Archive::readHeader(std::istream& is) { // 1) Read the magic number for VDB. int64_t magic; is.read(reinterpret_cast(&magic), sizeof(int64_t)); if (magic != OPENVDB_MAGIC) { OPENVDB_THROW(IoError, "not a VDB file"); } // 2) Read the file format version number. is.read(reinterpret_cast(&mFileVersion), sizeof(uint32_t)); if (mFileVersion > OPENVDB_FILE_VERSION) { OPENVDB_LOG_WARN("unsupported VDB file format (expected version " << OPENVDB_FILE_VERSION << " or earlier, got version " << mFileVersion << ")"); } else if (mFileVersion < 211) { // Versions prior to 211 stored separate major, minor and patch numbers. uint32_t version; is.read(reinterpret_cast(&version), sizeof(uint32_t)); mFileVersion = 100 * mFileVersion + 10 * version; is.read(reinterpret_cast(&version), sizeof(uint32_t)); mFileVersion += version; } // 3) Read the library version numbers (not stored prior to file format version 211). mLibraryVersion.first = mLibraryVersion.second = 0; if (mFileVersion >= 211) { uint32_t version; is.read(reinterpret_cast(&version), sizeof(uint32_t)); mLibraryVersion.first = version; // major version is.read(reinterpret_cast(&version), sizeof(uint32_t)); mLibraryVersion.second = version; // minor version } // 4) Read the flag indicating whether the stream supports partial reading. // (Versions prior to 212 have no flag because they always supported partial reading.) mInputHasGridOffsets = true; if (mFileVersion >= 212) { char hasGridOffsets; is.read(&hasGridOffsets, sizeof(char)); mInputHasGridOffsets = hasGridOffsets; } // 5) Read the flag that indicates whether data is compressed. // (From version 222 on, compression information is stored per grid.) mCompression = DEFAULT_COMPRESSION_FLAGS; if (mFileVersion >= OPENVDB_FILE_VERSION_SELECTIVE_COMPRESSION && mFileVersion < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { char isCompressed; is.read(&isCompressed, sizeof(char)); mCompression = (isCompressed != 0 ? COMPRESS_ZIP : COMPRESS_NONE); } // 6) Read the 16-byte (128-bit) uuid. boost::uuids::uuid oldUuid = mUuid; if (mFileVersion >= OPENVDB_FILE_VERSION_BOOST_UUID) { // UUID is stored as an ASCII string. is >> mUuid; } else { // Older versions stored the UUID as a byte string. char uuidBytes[16]; is.read(uuidBytes, 16); std::memcpy(&mUuid.data[0], uuidBytes, std::min(16, mUuid.size())); } return oldUuid != mUuid; // true if UUID in input stream differs from old UUID } void Archive::writeHeader(std::ostream& os, bool seekable) const { using boost::uint32_t; using boost::int64_t; // 1) Write the magic number for VDB. int64_t magic = OPENVDB_MAGIC; os.write(reinterpret_cast(&magic), sizeof(int64_t)); // 2) Write the file format version number. uint32_t version = OPENVDB_FILE_VERSION; os.write(reinterpret_cast(&version), sizeof(uint32_t)); // 3) Write the library version numbers. version = OPENVDB_LIBRARY_MAJOR_VERSION; os.write(reinterpret_cast(&version), sizeof(uint32_t)); version = OPENVDB_LIBRARY_MINOR_VERSION; os.write(reinterpret_cast(&version), sizeof(uint32_t)); // 4) Write a flag indicating that this stream contains no grid offsets. char hasGridOffsets = seekable; os.write(&hasGridOffsets, sizeof(char)); // 5) Write a flag indicating that this stream contains compressed leaf data. // (Omitted as of version 222) // 6) Generate a new random 16-byte (128-bit) uuid and write it to the stream. boost::mt19937 ran; ran.seed(time(NULL)); boost::uuids::basic_random_generator gen(&ran); mUuid = gen(); // mUuid is mutable os << mUuid; } //////////////////////////////////////// int32_t Archive::readGridCount(std::istream& is) { int32_t gridCount = 0; is.read(reinterpret_cast(&gridCount), sizeof(int32_t)); return gridCount; } //////////////////////////////////////// void Archive::connectInstance(const GridDescriptor& gd, const NamedGridMap& grids) const { if (!gd.isInstance() || grids.empty()) return; NamedGridMap::const_iterator it = grids.find(gd.uniqueName()); if (it == grids.end()) return; GridBase::Ptr grid = it->second; if (!grid) return; it = grids.find(gd.instanceParentName()); if (it != grids.end()) { GridBase::Ptr parent = it->second; if (mEnableInstancing) { // Share the instance parent's tree. grid->setTree(parent->baseTreePtr()); } else { // Copy the instance parent's tree. grid->setTree(parent->baseTree().copy()); } } else { OPENVDB_THROW(KeyError, "missing instance parent \"" << GridDescriptor::nameAsString(gd.instanceParentName()) << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName())); } } //////////////////////////////////////// void Archive::readGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is) { // Read the compression settings for this grid and tag the stream with them // so that downstream functions can reference them. readGridCompression(is); io::setGridClass(is, GRID_UNKNOWN); io::setGridBackgroundValuePtr(is, NULL); grid->readMeta(is); // Add a description of the compression settings to the grid as metadata. /// @todo Would this be useful? //const uint32_t compression = getDataCompression(is); //grid->insertMeta(GridBase::META_FILE_COMPRESSION, // StringMetadata(compressionToString(compression))); const GridClass gridClass = grid->getGridClass(); io::setGridClass(is, gridClass); if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { grid->readTransform(is); if (!gd.isInstance()) { grid->readTopology(is); grid->readBuffers(is); } } else { grid->readTopology(is); grid->readTransform(is); grid->readBuffers(is); } if (getFormatVersion(is) < OPENVDB_FILE_VERSION_NO_GRIDMAP) { // Older versions of the library didn't store grid names as metadata, // so when reading older files, copy the grid name from the descriptor // to the grid's metadata. if (grid->getName().empty()) { grid->setName(gd.gridName()); } } } void Archive::write(std::ostream& os, const GridPtrVec& grids, bool seekable, const MetaMap& metadata) const { this->write(os, GridCPtrVec(grids.begin(), grids.end()), seekable, metadata); } void Archive::write(std::ostream& os, const GridCPtrVec& grids, bool seekable, const MetaMap& metadata) const { // Set stream flags so that downstream functions can reference them. io::setDataCompression(os, compressionFlags()); os.iword(sStreamState.writeGridStatsMetadata) = isGridStatsMetadataEnabled(); this->writeHeader(os, seekable); metadata.writeMeta(os); // Write the number of non-null grids. int32_t gridCount = 0; for (GridCPtrVecCIter i = grids.begin(), e = grids.end(); i != e; ++i) { if (*i) ++gridCount; } os.write(reinterpret_cast(&gridCount), sizeof(int32_t)); typedef std::map TreeMap; typedef TreeMap::iterator TreeMapIter; TreeMap treeMap; std::set uniqueNames; // Write out the non-null grids. for (GridCPtrVecCIter i = grids.begin(), e = grids.end(); i != e; ++i) { if (const GridBase::ConstPtr& grid = *i) { // Ensure that the grid's descriptor has a unique grid name, by appending // a number to it if a grid with the same name was already written. // Always add a number if the grid name is empty, so that the grid can be // properly identified as an instance parent, if necessary. std::string name = grid->getName(); for (int n = 1; name.empty() || uniqueNames.find(name) != uniqueNames.end(); ++n) { name = GridDescriptor::addSuffix(grid->getName(), n); } uniqueNames.insert(name); // Create a grid descriptor. GridDescriptor gd(name, grid->type(), grid->saveFloatAsHalf()); // Check if this grid's tree is shared with a grid that has already been written. const TreeBase* treePtr = &(grid->baseTree()); TreeMapIter mapIter = treeMap.find(treePtr); bool isInstance = ((mapIter != treeMap.end()) && (mapIter->second.saveFloatAsHalf() == gd.saveFloatAsHalf())); if (mEnableInstancing && isInstance) { // This grid's tree is shared with another grid that has already been written. // Get the name of the other grid. gd.setInstanceParentName(mapIter->second.uniqueName()); // Write out this grid's descriptor and metadata, but not its tree. writeGridInstance(gd, grid, os, seekable); OPENVDB_LOG_DEBUG_RUNTIME("io::Archive::write(): " << GridDescriptor::nameAsString(gd.uniqueName()) << " (" << std::hex << treePtr << std::dec << ")" << " is an instance of " << GridDescriptor::nameAsString(gd.instanceParentName())); } else { // Write out the grid descriptor and its associated grid. writeGrid(gd, grid, os, seekable); // Record the grid's tree pointer so that the tree doesn't get written // more than once. treeMap[treePtr] = gd; } } // Some compression options (e.g., mask compression) are set per grid. // Restore the original settings before writing the next grid. io::setDataCompression(os, compressionFlags()); } } void Archive::writeGrid(GridDescriptor& gd, GridBase::ConstPtr grid, std::ostream& os, bool seekable) const { // Write out the Descriptor's header information (grid name and type) gd.writeHeader(os); // Save the curent stream position as postion to where the offsets for // this GridDescriptor will be written to. int64_t offsetPos = (seekable ? int64_t(os.tellp()) : 0); // Write out the offset information. At this point it will be incorrect. // But we need to write it out to move the stream head forward. gd.writeStreamPos(os); // Now we know the starting grid storage position. if (seekable) gd.setGridPos(os.tellp()); // Save the compression settings for this grid. setGridCompression(os, *grid); // Save the grid's metadata and transform. if (!getWriteGridStatsMetadata(os)) { grid->writeMeta(os); } else { // Compute and add grid statistics metadata. GridBase::Ptr copyOfGrid = grid->copyGrid(); // shallow copy copyOfGrid->addStatsMetadata(); copyOfGrid->writeMeta(os); } grid->writeTransform(os); // Save the grid's structure. grid->writeTopology(os); // Now we know the grid block storage position. if (seekable) gd.setBlockPos(os.tellp()); // Save out the data blocks of the grid. grid->writeBuffers(os); // Now we know the end position of this grid. if (seekable) gd.setEndPos(os.tellp()); if (seekable) { // Now, go back to where the Descriptor's offset information is written // and write the offsets again. os.seekp(offsetPos, std::ios_base::beg); gd.writeStreamPos(os); // Now seek back to the end. gd.seekToEnd(os); } } void Archive::writeGridInstance(GridDescriptor& gd, GridBase::ConstPtr grid, std::ostream& os, bool seekable) const { // Write out the Descriptor's header information (grid name, type // and instance parent name). gd.writeHeader(os); // Save the curent stream position as postion to where the offsets for // this GridDescriptor will be written to. int64_t offsetPos = (seekable ? int64_t(os.tellp()) : 0); // Write out the offset information. At this point it will be incorrect. // But we need to write it out to move the stream head forward. gd.writeStreamPos(os); // Now we know the starting grid storage position. if (seekable) gd.setGridPos(os.tellp()); // Save the compression settings for this grid. setGridCompression(os, *grid); // Save the grid's metadata and transform. grid->writeMeta(os); grid->writeTransform(os); // Now we know the end position of this grid. if (seekable) gd.setEndPos(os.tellp()); if (seekable) { // Now, go back to where the Descriptor's offset information is written // and write the offsets again. os.seekp(offsetPos, std::ios_base::beg); gd.writeStreamPos(os); // Now seek back to the end. gd.seekToEnd(os); } } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/File.cc0000644000000000000000000004543512252453157013027 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file File.cc #include "File.h" #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { File::File(const std::string& filename): mFilename(filename), mIsOpen(false) { setInputHasGridOffsets(true); } File::~File() { } File::File(const File& other) : Archive(other) , mFilename(other.mFilename) , mMeta(other.mMeta) , mIsOpen(false) // don't want two file objects reading from the same stream , mGridDescriptors(other.mGridDescriptors) , mNamedGrids(other.mNamedGrids) , mGrids(other.mGrids) { } File& File::operator=(const File& other) { if (&other != this) { Archive::operator=(other); mFilename = other.mFilename; mMeta = other.mMeta; mIsOpen = false; // don't want two file objects reading from the same stream mGridDescriptors = other.mGridDescriptors; mNamedGrids = other.mNamedGrids; mGrids = other.mGrids; } return *this; } boost::shared_ptr File::copy() const { return boost::shared_ptr(new File(*this)); } //////////////////////////////////////// bool File::open() { #if defined(_MSC_VER) // The original C++ standard library specified that open() only _sets_ // the fail bit upon an error. It does not clear any bits upon success. // This was later addressed by the Library Working Group (LWG) for DR #409 // and implemented by gcc 4.0. Visual Studio 2008 however is one of those // which has not caught up. // See: http://gcc.gnu.org/onlinedocs/libstdc++/ext/lwg-defects.html#22 mInStream.clear(); #endif // Open the file. mInStream.open(mFilename.c_str(), std::ios_base::in | std::ios_base::binary); if (mInStream.fail()) { OPENVDB_THROW(IoError, "could not open file " << mFilename); } // Read in the file header. bool newFile = false; try { newFile = Archive::readHeader(mInStream); } catch (IoError& e) { mInStream.close(); if (e.what() && std::string("not a VDB file") == e.what()) { // Rethrow, adding the filename. OPENVDB_THROW(IoError, mFilename << " is not a VDB file"); } throw; } // Tag the input stream with the file format and library version numbers. Archive::setFormatVersion(mInStream); Archive::setLibraryVersion(mInStream); Archive::setDataCompression(mInStream); // Read in the VDB metadata. mMeta = MetaMap::Ptr(new MetaMap); mMeta->readMeta(mInStream); if (!inputHasGridOffsets()) { OPENVDB_LOG_WARN("file " << mFilename << " does not support partial reading"); mGrids.reset(new GridPtrVec); mNamedGrids.clear(); // Stream in the entire contents of the file and append all grids to mGrids. const boost::int32_t gridCount = readGridCount(mInStream); for (boost::int32_t i = 0; i < gridCount; ++i) { GridDescriptor gd; gd.read(mInStream); GridBase::Ptr grid = createGrid(gd); Archive::readGrid(grid, gd, mInStream); mGridDescriptors.insert(std::make_pair(gd.gridName(), gd)); mGrids->push_back(grid); mNamedGrids[gd.uniqueName()] = grid; } // Connect instances (grids that share trees with other grids). for (NameMapCIter it = mGridDescriptors.begin(); it != mGridDescriptors.end(); ++it) { Archive::connectInstance(it->second, mNamedGrids); } } else { // Read in just the grid descriptors. readGridDescriptors(mInStream); } mIsOpen = true; return newFile; // true if file is not identical to opened file } void File::close() { // Close the stream. if (mInStream.is_open()) { mInStream.close(); } // Reset all data. mMeta.reset(); mGridDescriptors.clear(); mGrids.reset(); mNamedGrids.clear(); mIsOpen = false; setInputHasGridOffsets(true); } //////////////////////////////////////// bool File::hasGrid(const Name& name) const { if (!isOpen()) { OPENVDB_THROW(IoError, mFilename << " is not open for reading"); } return (findDescriptor(name) != mGridDescriptors.end()); } MetaMap::Ptr File::getMetadata() const { if (!isOpen()) { OPENVDB_THROW(IoError, mFilename << " is not open for reading"); } // Return a deep copy of the file-level metadata, which was read // when the file was opened. return MetaMap::Ptr(new MetaMap(*mMeta)); } GridPtrVecPtr File::getGrids() const { if (!isOpen()) { OPENVDB_THROW(IoError, mFilename << " is not open for reading"); } GridPtrVecPtr ret; if (!inputHasGridOffsets()) { // If the input file doesn't have grid offsets, then all of the grids // have already been streamed in and stored in mGrids. ret = mGrids; } else { ret.reset(new GridPtrVec); Archive::NamedGridMap namedGrids; // Read all grids represented by the GridDescriptors. for (NameMapCIter i = mGridDescriptors.begin(), e = mGridDescriptors.end(); i != e; ++i) { const GridDescriptor& gd = i->second; GridBase::Ptr grid = readGrid(gd); ret->push_back(grid); namedGrids[gd.uniqueName()] = grid; } // Connect instances (grids that share trees with other grids). for (NameMapCIter i = mGridDescriptors.begin(), e = mGridDescriptors.end(); i != e; ++i) { Archive::connectInstance(i->second, namedGrids); } } return ret; } //////////////////////////////////////// GridPtrVecPtr File::readAllGridMetadata() { if (!isOpen()) { OPENVDB_THROW(IoError, mFilename << " is not open for reading"); } GridPtrVecPtr ret(new GridPtrVec); if (!inputHasGridOffsets()) { // If the input file doesn't have grid offsets, then all of the grids // have already been streamed in and stored in mGrids. for (size_t i = 0, N = mGrids->size(); i < N; ++i) { // Return copies of the grids, but with empty trees. ret->push_back((*mGrids)[i]->copyGrid(/*treePolicy=*/CP_NEW)); } } else { // Read just the metadata and transforms for all grids. for (NameMapCIter i = mGridDescriptors.begin(), e = mGridDescriptors.end(); i != e; ++i) { const GridDescriptor& gd = i->second; GridBase::ConstPtr grid = readGridPartial(gd, /*readTopology=*/false); // Return copies of the grids, but with empty trees. // (As of 0.98.0, at least, it would suffice to just const cast // the grid pointers returned by readGridPartial(), but shallow // copying the grids helps to ensure future compatibility.) ret->push_back(grid->copyGrid(/*treePolicy=*/CP_NEW)); } } return ret; } GridBase::Ptr File::readGridMetadata(const Name& name) { if (!isOpen()) { OPENVDB_THROW(IoError, mFilename << " is not open for reading."); } GridBase::ConstPtr ret; if (!inputHasGridOffsets()) { // Retrieve the grid from mGrids, which should already contain // the entire contents of the file. ret = readGrid(name); } else { NameMapCIter it = findDescriptor(name); if (it == mGridDescriptors.end()) { OPENVDB_THROW(KeyError, mFilename << " has no grid named \"" << name << "\""); } // Seek to and read in the grid from the file. const GridDescriptor& gd = it->second; ret = readGridPartial(gd, /*readTopology=*/false); } return ret->copyGrid(/*treePolicy=*/CP_NEW); } //////////////////////////////////////// GridBase::ConstPtr File::readGridPartial(const Name& name) { if (!isOpen()) { OPENVDB_THROW(IoError, mFilename << " is not open for reading."); } GridBase::ConstPtr ret; if (!inputHasGridOffsets()) { // Retrieve the grid from mGrids, which should already contain // the entire contents of the file. if (GridBase::Ptr grid = readGrid(name)) { ret = boost::const_pointer_cast(grid); } } else { NameMapCIter it = findDescriptor(name); if (it == mGridDescriptors.end()) { OPENVDB_THROW(KeyError, mFilename << " has no grid named \"" << name << "\""); } // Seek to and read in the grid from the file. const GridDescriptor& gd = it->second; ret = readGridPartial(gd, /*readTopology=*/true); if (gd.isInstance()) { NameMapCIter parentIt = findDescriptor(GridDescriptor::nameAsString(gd.instanceParentName())); if (parentIt == mGridDescriptors.end()) { OPENVDB_THROW(KeyError, "missing instance parent \"" << GridDescriptor::nameAsString(gd.instanceParentName()) << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName()) << " in file " << mFilename); } if (GridBase::ConstPtr parent = readGridPartial(parentIt->second, /*readTopology=*/true)) { if (Archive::isInstancingEnabled()) { // Share the instance parent's tree. boost::const_pointer_cast(ret)->setTree( boost::const_pointer_cast(parent)->baseTreePtr()); } else { // Copy the instance parent's tree. boost::const_pointer_cast(ret)->setTree( parent->baseTree().copy()); } } } } return ret; } GridBase::Ptr File::readGrid(const Name& name) { if (!isOpen()) { OPENVDB_THROW(IoError, mFilename << " is not open for reading."); } GridBase::Ptr ret; if (!inputHasGridOffsets()) { // Retrieve the grid from mNamedGrids, which should already contain // the entire contents of the file. // Search by unique name. Archive::NamedGridMap::const_iterator it = mNamedGrids.find(GridDescriptor::stringAsUniqueName(name)); // If not found, search by grid name. if (it == mNamedGrids.end()) it = mNamedGrids.find(name); if (it == mNamedGrids.end()) { OPENVDB_THROW(KeyError, mFilename << " has no grid named \"" << name << "\""); } ret = it->second; } else { NameMapCIter it = findDescriptor(name); if (it == mGridDescriptors.end()) { OPENVDB_THROW(KeyError, mFilename << " has no grid named \"" << name << "\""); } // Seek to and read in the grid from the file. const GridDescriptor& gd = it->second; ret = readGrid(gd); if (gd.isInstance()) { NameMapCIter parentIt = findDescriptor(GridDescriptor::nameAsString(gd.instanceParentName())); if (parentIt == mGridDescriptors.end()) { OPENVDB_THROW(KeyError, "missing instance parent \"" << GridDescriptor::nameAsString(gd.instanceParentName()) << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName()) << " in file " << mFilename); } if (GridBase::Ptr parent = readGrid(parentIt->second)) { if (Archive::isInstancingEnabled()) { // Share the instance parent's tree. ret->setTree(parent->baseTreePtr()); } else { // Copy the instance parent's tree. ret->setTree(parent->baseTree().copy()); } } } } return ret; } //////////////////////////////////////// void File::writeGrids(const GridCPtrVec& grids, const MetaMap& metadata) const { if (isOpen()) { OPENVDB_THROW(IoError, mFilename << " cannot be written because it is open for reading"); } // Create a file stream and write it out. std::ofstream file; file.open(mFilename.c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); if (file.fail()) { OPENVDB_THROW(IoError, "could not open " << mFilename << " for writing"); } // Write out the vdb. Archive::write(file, grids, /*seekable=*/true, metadata); file.close(); } //////////////////////////////////////// void File::readGridDescriptors(std::istream& is) { // This method should not be called for files that don't contain grid offsets. assert(inputHasGridOffsets()); mGridDescriptors.clear(); for (boost::int32_t i = 0, N = readGridCount(is); i < N; ++i) { // Read the grid descriptor. GridDescriptor gd; gd.read(is); // Add the descriptor to the dictionary. mGridDescriptors.insert(std::make_pair(gd.gridName(), gd)); // Skip forward to the next descriptor. gd.seekToEnd(is); } } //////////////////////////////////////// File::NameMapCIter File::findDescriptor(const Name& name) const { const Name uniqueName = GridDescriptor::stringAsUniqueName(name); // Find all descriptors with the given grid name. std::pair range = mGridDescriptors.equal_range(name); if (range.first == range.second) { // If no descriptors were found with the given grid name, the name might have // a suffix ("name[N]"). In that case, remove the "[N]" suffix and search again. range = mGridDescriptors.equal_range(GridDescriptor::stripSuffix(uniqueName)); } const size_t count = size_t(std::distance(range.first, range.second)); if (count > 1 && name == uniqueName) { OPENVDB_LOG_WARN(mFilename << " has more than one grid named \"" << name << "\""); } NameMapCIter ret = mGridDescriptors.end(); if (count > 0) { if (name == uniqueName) { // If the given grid name is unique or if no "[N]" index was given, // use the first matching descriptor. ret = range.first; } else { // If the given grid name has a "[N]" index, find the descriptor // with a matching unique name. for (NameMapCIter it = range.first; it != range.second; ++it) { const Name candidateName = it->second.uniqueName(); if (candidateName == uniqueName || candidateName == name) { ret = it; break; } } } } return ret; } //////////////////////////////////////// GridBase::Ptr File::createGrid(const GridDescriptor& gd) const { // Create the grid. if (!GridBase::isRegistered(gd.gridType())) { OPENVDB_THROW(KeyError, "Cannot read grid " << GridDescriptor::nameAsString(gd.uniqueName()) << " from " << mFilename << ": grid type " << gd.gridType() << " is not registered"); } GridBase::Ptr grid = GridBase::createGrid(gd.gridType()); if (grid) grid->setSaveFloatAsHalf(gd.saveFloatAsHalf()); return grid; } GridBase::ConstPtr File::readGridPartial(const GridDescriptor& gd, bool readTopology) const { // This method should not be called for files that don't contain grid offsets. assert(inputHasGridOffsets()); GridBase::Ptr grid = createGrid(gd); // Seek to grid. gd.seekToGrid(mInStream); // Read the grid partially. readGridPartial(grid, mInStream, gd.isInstance(), readTopology); // Promote to a const grid. GridBase::ConstPtr constGrid = grid; return constGrid; } GridBase::Ptr File::readGrid(const GridDescriptor& gd) const { // This method should not be called for files that don't contain grid offsets. assert(inputHasGridOffsets()); GridBase::Ptr grid = createGrid(gd); // Seek to where the grid is. gd.seekToGrid(mInStream); // Read in the grid. Archive::readGrid(grid, gd, mInStream); return grid; } void File::readGridPartial(GridBase::Ptr grid, std::istream& is, bool isInstance, bool readTopology) const { // This method should not be called for files that don't contain grid offsets. assert(inputHasGridOffsets()); // This code needs to stay in sync with io::Archive::readGrid(), in terms of // the order of operations. readGridCompression(is); grid->readMeta(is); if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_GRID_INSTANCING) { grid->readTransform(is); if (!isInstance && readTopology) { grid->readTopology(is); } } else { if (readTopology) { grid->readTopology(is); grid->readTransform(is); } } } //////////////////////////////////////// File::NameIterator File::beginName() const { if (!isOpen()) { OPENVDB_THROW(IoError, mFilename << " is not open for reading"); } return File::NameIterator(mGridDescriptors.begin()); } File::NameIterator File::endName() const { return File::NameIterator(mGridDescriptors.end()); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/io/Stream.h0000644000000000000000000001235512252453157013240 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED #define OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED #include #include "Archive.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { class GridDescriptor; /// Grid archive associated with arbitrary input and output streams (not necessarily files) class OPENVDB_API Stream: public Archive { public: /// Read grids from an input stream. explicit Stream(std::istream&); /// Construct an archive for stream output. Stream(); /// Construct an archive for output to the given stream. explicit Stream(std::ostream&); virtual ~Stream(); /// @brief Return a copy of this archive. virtual boost::shared_ptr copy() const; /// Return the file-level metadata in a newly created MetaMap. MetaMap::Ptr getMetadata() const; /// Return pointers to the grids that were read from the input stream. GridPtrVecPtr getGrids() { return mGrids; } /// @brief Write the grids in the given container to this archive's output stream. /// @throw ValueError if this archive was constructed without specifying an output stream. virtual void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const; /// @brief Write the grids in the given container to this archive's output stream. /// @throw ValueError if this archive was constructed without specifying an output stream. template void write(const GridPtrContainerT&, const MetaMap& = MetaMap()) const; /// @brief Write the grids in the given container to an output stream. /// @deprecated Use Stream(os).write(grids) instead. template OPENVDB_DEPRECATED void write(std::ostream&, const GridPtrContainerT&, const MetaMap& = MetaMap()) const; private: /// Create a new grid of the type specified by the given descriptor, /// then populate the grid from the given input stream. /// @return the newly created grid. GridBase::Ptr readGrid(const GridDescriptor&, std::istream&) const; void writeGrids(std::ostream&, const GridCPtrVec&, const MetaMap&) const; MetaMap::Ptr mMeta; GridPtrVecPtr mGrids; std::ostream* mOutputStream; }; //////////////////////////////////////// inline void Stream::write(const GridCPtrVec& grids, const MetaMap& metadata) const { if (mOutputStream == NULL) { OPENVDB_THROW(ValueError, "no output stream was specified"); } this->writeGrids(*mOutputStream, grids, metadata); } template inline void Stream::write(const GridPtrContainerT& container, const MetaMap& metadata) const { if (mOutputStream == NULL) { OPENVDB_THROW(ValueError, "no output stream was specified"); } GridCPtrVec grids; std::copy(container.begin(), container.end(), std::back_inserter(grids)); this->writeGrids(*mOutputStream, grids, metadata); } template inline void Stream::write(std::ostream& os, const GridPtrContainerT& container, const MetaMap& metadata) const { GridCPtrVec grids; std::copy(container.begin(), container.end(), std::back_inserter(grids)); this->writeGrids(os, grids, metadata); } template<> inline void Stream::write(std::ostream& os, const GridCPtrVec& grids, const MetaMap& metadata) const { this->writeGrids(os, grids, metadata); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/README0000644000000000000000000000116012252452020012061 0ustar rootroot======================================================================== OpenVDB ======================================================================== The OpenVDB library comprises a hierarchical data structure and a suite of tools for the efficient manipulation of sparse, possibly time-varying, volumetric data discretized on a three-dimensional grid. For instructions on library installation and dependencies see INSTALL For documentation of the library and code examples see: www.openvdb.org/documentation/doxygen For more details visit the project's home page: www.openvdb.org openvdb/CHANGES0000644000000000000000000012355312252452020012207 0ustar rootrootOpenVDB Version History ======================= Version 2.1.0 - December 12, 2013 - Added a small number of Maya nodes, primarily for conversion of geometry to and from OpenVDB volumes and for visualization of volumes. - Added an initial implementation of level set morphing (with improvements to follow soon). - Added tools::LevelSetMeasure, which efficiently computes the surface area, volume and average mean-curvature of narrow-band level sets, in both world and voxel units. Those quantities are now exposed as intrinsic attributes on the Houdini VDB primitive and can be queried using the native Measure SOP. - tools::Dense now supports the XYZ memory layout used by Houdini and Maya in addition to the ZYX layout used in OpenVDB trees. - Improved the performance of masking in the level set filter tool and added inversion and scaling of the mask input, so that any scalar-valued volume can be used as a mask, not just volumes with a [0, 1] range. - Added optional masking to the non-level-set filters, to the grid operators (CPT, curl, divergence, gradient, Laplacian, mean curvature, magnitude, and normalize) and to the Analysis and Filter SOPs. - Added more narrow band controls to the Rebuild Level Set SOP. - Improved the accuracy of the level set rebuild tool. - Added tools::activate() and tools::deactivate(), which set the active states of tiles and voxels whose values are equal to or approximately equal to a given value, and added a Deactivate Background Voxels toggle to the Combine SOP. - Added math::BBox::applyMap() and math::BBox::applyInverseMap(), which allow for transformation of axis-aligned bounding boxes. - Added a position shader to the level set ray-tracer (primarily for debugging purposes). - Added an io::Queue class that manages a concurrent queue for asynchronous serialization of grids to files or streams. - Fixed a bug in io::Archive whereby writing unnamed, instanced grids (i.e., grids sharing a tree) to a file rendered the file unreadable. - Fixed a bug in the volume to mesh converter that caused it to generate invalid polygons when the zero crossing lay between active and inactive regions. - Fixed a bug in the point scatter tool (and the Scatter SOP) whereby the last voxel always remained empty. - Fixed a bug in the Read SOP that caused grids with the same name to be renamed with a numeric suffix (e.g., "grid[1]", "grid[2]", etc.). - Fixed some unit test failures on 64-bit Itanium machines. API changes: - The Filter tool is now templated on a mask grid, and threading is controlled using a grain size, for consistency with most of the other level set tools. - The level set filter tool is now templated on a mask grid. - All shaders now take a ray direction instead of a ray. Version 2.0.0 - October 31, 2013 - Added a Python module with functions for basic manipulation of grids (but no tools, yet). - Added ray intersector tools for efficient, hierarchical intersection of rays with level-set and generic volumes. - Added a Ray class and a hierarchical Digital Differential Analyzer for fast ray traversal. - Added a fully multithreaded level set ray tracer and camera classes that mimic Houdini's cameras. - Added a simple, command-line renderer (currently for level sets only). - Implemented a new meshing scheme that produces topologically robust two-manifold meshes and is twice as fast as the previous scheme. - Implemented a new, topologically robust (producing two-manifold meshes) level-set-based seamless fracture scheme. The new scheme eliminates visible scarring seen in the previous implementation by subdividing internal, nonplanar quads near fracture seams. In addition, fracture seam points are now tagged, allowing them to be used to drive pre-fracture dynamics such as local surface buckling. - Improved the performance of Tree::evalActiveVoxelBoundingBox() and Tree::activeVoxelCount(), and significantly improved the performance of Tree::evalLeafBoundingBox() (by about 30x). - Added a tool (and a Houdini SOP) that fills a volume with adaptively-sized overlapping or non-overlapping spheres. - Added a Ray SOP that can be used to perform geometry projections using level-set ray intersections or closest-point queries. - Added a tool that performs accelerated closest surface point queries from arbitrary points in world space to narrow-band level sets. - Increased the speed of masked level set filtering by 20% for the most common cases. - Added math::BoxStencil, with support for trilinear interpolation and gradient computation. - Added Tree::topologyIntersection(), which intersects a tree's active values with those of another tree, and Tree::topologyDifference(), which performs topological subtraction of one tree's active values from another's. In both cases, the ValueTypes of the two trees need not be the same. - Added Tree::activeTileCount(), which returns the number of active tiles in a tree. - Added math::MinIndex() and math::MaxIndex(), which find the minimum and maximum components of a vector without any branching. - Added math::BBox::minExtent(), which returns a bounding box's shortest axis. - The default math::BBox constructor now generates an invalid bounding box rather than an empty bounding box positioned at the origin. The new behavior is consistent with math::CoordBBox. [Thanks to Rick Hankins for suggesting this fix.] - Added CoordBBox::reset(), which resets a bounding box to its initial, invalid state. - Fixed a bug in the default ScaleMap constructor that left some data used in the inverse uninitialized. - Added MapBase::applyJT(), which applies the Jacobian transpose to a vector (the Jacobian transpose takes a range-space vector to a domain-space vector, e.g., world to index), and added MapBase::inverseMap(), which returns a new map representing the inverse of the original map (except for NonlinearFrustumMap, which does not currently have a defined inverse map). Note: Houdini 12.5 uses an earlier version of OpenVDB, and maps created with that version lack virtual table entries for these new methods, so do not call these methods from Houdini 12.5. - Reimplemented math::RandomInt using Boost.Random instead of rand() (which is not thread-safe), and deprecated math::randUniform() and added math::Random01 to replace it. - Modified tools::copyFromDense() and tools::copyToDense() to allow for implicit type conversion (e.g., between a Dense and a FloatTree) and fixed several bugs in tools::CopyFromDense. - Fixed bugs in math::Stats and math::Histogram that could produce NaNs or other incorrect behavior if certain methods were called on populations of size zero. - Renamed tolerance to math::Tolerance and negative() to math::negative() and removed math::toleranceValue(). - Implemented a closest point on line segment algorithm, math::closestPointOnSegmentToPoint(). - Fixed meshing issues relating to masking and automatic partitioning. - Grid::merge() and Tree::merge() now accept an optional MergePolicy argument that specifies one of three new merging schemes. (The old merging scheme, which is no longer available, used logic for each tree level that was inconsistent with the other levels and that could result in active tiles being replaced with nodes having only inactive values.) - Renamed LeafNode::coord2offset(), LeafNode::offset2coord() and LeafNode::offset2globalCoord() to coordToOffset(), offsetToLocalCoord() and offsetToGlobalCoord(), respectively, and likewise for InternalNode. [Thanks to Rick Hankins for suggesting this change.] - Replaced Tree methods setValueOnMin(), setValueOnMax() and setValueOnSum() with tools::setValueOnMin(), tools::setValueOnMax() and tools::setValueOnSum() (and a new tools::setValueOnMult()) and added Tree::modifyValue() and Tree::modifyValueAndActiveState(), which modify voxel values in place via user-supplied functors. Similarly, replaced ValueAccessor::setValueOnSum() with ValueAccessor::modifyValue() and ValueAccessor::modifyValueAndActiveState(), and added a modifyValue() method to all value iterators. - Removed LeafNode::addValue() and LeafNode::scaleValue(). - Added convenience classes Tree3 and Tree5 for custom tree configurations. - Added an option to the From Particles SOP to generate an alpha mask, which can be used to constrain level set filtering so as to preserve surface details. - The mesh to volume converter now handles point-degenerate polygons. - Fixed a bug in the Level Set Smooth, Level Set Renormalize and Level Set Offset SOPs that caused the group name to be ignored. - Fixed various OS X and Windows build issues. [Contributions from SESI and DD] Version 1.2.0 - June 28, 2013 - Level set filters now accept an optional alpha mask grid. - Implemented sharp feature extraction for level set surfacing. This enhances the quality of the output mesh and reduces aliasing artifacts. - Added masking options to the meshing tools, as well as a spatial multiplier for the adaptivity threshold, automatic partitioning, and the ability to preserve edges and corners when mesh adaptivity is applied. - The mesh to volume attribute transfer scheme now takes surface orientation into account, which improves accuracy in proximity to edges and corners. - Added a foreach() method to tools::LeafManager that, like tools::foreach(), applies a user-supplied functor to each leaf node in parallel. - Rewrote the particle to level set converter, simplifying the API, improving performance (especially when particles have a fixed radius), adding the capability to transfer arbitrary point attributes, and fixing a velocity trail bug. - Added utility methods Sign(), SignChange(), isApproxZero(), Cbrt() and ZeroCrossing() to math/Math.h. - Added a probeNode() method to the value accessor and to tree nodes that returns a pointer to the node that contains a given voxel. - Deprecated LeafNode::addValue() and LeafNode::scaleValue(). - Doubled the speed of the mesh to volume converter (which also improves the performance of the fracture and level set rebuild tools) and improved its inside/outside voxel classification near edges and corners. - tools::GridSampler now accepts either a grid, a tree or a value accessor, and it offers faster index-based access methods and much better performance in cases where many instances are allocated. - Extended tools::Dense to make it more compatible with existing tools. - Fixed a crash in io::Archive whenever the library was unloaded from memory and then reloaded. [Contributed by Ollie Harding] - Fixed a bug in GU_PrimVDB::buildFromPrimVolume(), seen during the conversion from Houdini volumes to OpenVDB grids, that could cause signed flood fill to be applied to non-level set grids, resulting in active tiles with incorrect values. - Added a Prune SOP with several pruning schemes. Version 1.1.1 - May 10 2013 - Added a simple dense grid class and tools to copy data from dense voxel arrays into OpenVDB grids and vice-versa. - Starting with Houdini 12.5.396, plugins built with this version of OpenVDB can coexist with native Houdini OpenVDB nodes. - The level set fracture tool now smooths seam line edges during mesh extraction, eliminating staircase artifacts. - Significantly improved the performance of the util::leafTopologyIntersection() and util::leafTopologyDifference() utilities and added a LeafNode::topologyDifference() method. - Added convenience functions that provide simplified interfaces to the mesh to volume and volume to mesh converters. - Added a tools::accumulate() function that is similar to tools::foreach() but can be used to accumulate the results of computations over the values of a grid. - Added tools::statistics(), tools::opStatistics() and tools::histogram(), which efficiently compute statistics (mean, variance, etc.) and histograms of grid values (using math::Stats and math::Histogram). - Modified CoordBBox to adhere to TBB's splittable type requirements, so that, for example, a CoordBBox can be used as a blocked iteration range. - Added Tree::addTile(), Tree::addLeaf() and Tree::stealNode(), for fine control over tree construction. - Addressed a numerical stability issue when performing Gaussian filtering of level set grids. - Changed the return type of CoordBBox::volume() to reduce the risk of overflow. - When the input mesh is self-intersecting, the mesh to volume converter now produces a level set with a monotonic gradient field. - Fixed a threading bug in the mesh to volume converter that caused it to produce different results for the same input. - Fixed a bug in the particle to level set converter that prevented particles with zero velocity from being rasterized in Trail mode. - Added an optional input to the Create SOP into which to merge newly-created grids. - Fixed a bug in the Resample SOP that caused it to produce incorrect narrow-band widths when resampling level set grids. - Fixed a bug in the To Polygons SOP that caused intermittent crashes when the optional reference input was connected. - Fixed a bug in the Advect Level Set SOP that caused a crash when the velocity input was connected but empty. - The Scatter and Sample Point SOPs now warn instead of erroring when given empty grids. - Fixed a crash in vdb_view when stepping through multiple grids after changing render modes. - vdb_view can now render fog volumes and vector fields, and it now features interactively adjustable clipping planes that enable one to view the interior of a volume. Version 1.1.0 - April 4 2013 - The resampleToMatch() tool, the Resample SOP and the Combine SOP now use level set rebuild to correctly and safely resample level sets. Previously, scaling a level set would invalidate the signed distance field, leading to holes and other artifacts. - Added a mask-based topological erosion tool, and rewrote and simplified the dilation tool. - The LevelSetAdvection tool can now advect forward or backward in time. - Tree::pruneLevelSet() now replaces each pruned node with a tile having the inside or outside background value, instead of arbitrarily selecting one of the node's tile or voxel values. - When a grid is saved to a file with saveFloatAsHalf() set to true, the grid's background value is now also quantized to 16 bits. (Not quantizing the background value caused a mismatch with the values of background tiles.) - As with tools::foreach(), it is now possible to specify whether functors passed to tools::transformValues() should be shared across threads. - tree::LeafManager can now be instantiated with a const tree, although buffer swapping with const trees is disabled. - Added a Grid::signedFloodFill() overload that allows one to specify inside and outside values. - Fixed a bug in Grid::setBackground() so that now only the values of inactive voxels change. - Fixed Grid::topologyUnion() so that it actually unions tree topology, instead of just the active states of tiles and voxels. The previous behavior broke multithreaded code that relied on input and output grids having compatible tree topology. - math::Transform now includes an isIdentity() predicate and methods to pre- and postmultiply by a matrix. - Modified the node mask classes to permit octree-like tree configurations (i.e., with a branching factor of two) and to use 64-bit operations instead of 32-bit operations. - Implemented a new, more efficient closest point on triangle algorithm. - Implemented a new vertex normal scheme in the volume to mesh converter, and resolved some overlapping polygon issues. - The volume to mesh converter now meshes not just active voxels but also active tiles. - Fixed a bug in the mesh to volume converter that caused unsigned distance field conversion to produce empty grids. - Fixed a bug in the level set fracture tool whereby the cutter overlap toggle was ignored. - Fixed an infinite loop bug in vdb_view. - Updated vdb_view to use the faster and less memory-intensive OpenVDB volume to mesh converter instead of marching cubes, and rewrote the shader to be OpenGL 3.2 and GLSL 1.2 compatible. - Given multiple input files or a file containing multiple grids, vdb_view now displays one grid at a time. The left and right arrow keys cycle between grids. - The To Polygons SOP now has an option to associate the input grid's name with each output polygon. Version 1.0.0 - March 14 2013 - tools::levelSetRebuild() now throws an exception when given a non-scalar or non-floating-point grid. - The tools in tools/GridOperators.h are now interruptible, as is the Analysis SOP. - Added a leaf node iterator and a TBB-compatible range class to the LeafManager. - Modified the VolumeToMesh tool to handle surface topology issues around fracture seam lines. - Modified the Makefile to allow vdb_view to compile on OS X systems (provided that GLFW is available). - Fixed a bug in the Create SOP that resulted in "invalid parameter name" warnings. - The Combine SOP now optionally resamples the A grid into the B grid's index space (or vice-versa) if the A and B transforms differ. - The Vector Split and Vector Merge SOPs now skip inactive voxels by default, but they can optionally be made to include inactive voxels, as they did before. - The LevelSetFracture tool now supports custom rotations for each cutter instance, and the Fracture SOP now uses quaternions to generate uniformly-distributed random rotations. Version 0.104.0 - February 15 2013 - Added a tool and a SOP to rebuild a level set from any scalar volume. - .vdb files are now saved using a mask-based compression scheme that is an order of magnitude faster than Zip and produces comparable file sizes for level set and fog volume grids. (Zip compression is still enabled by default for other classes of grids). - The Filter and LevelSetFilter tools now include a Gaussian filter, and mean (box) filtering is now 10-50x faster. - The isosurface meshing tool is now more robust (to level sets with one voxel wide narrow bands, for example). - Mesh to volume conversion is on average 1.5x faster and up to 5.5x faster for high-resolution meshes where the polygon/voxel size ratio is small. - Added createLevelSet() and createLevelSetSphere() factory functions for level set grids. - tree::ValueAccessor is now faster for trees of height 2, 3 and 4 (the latter is the default), and it now allows one to specify, via a template argument, the number of node levels to be cached, which can also improve performance in special cases. - Added a toggle to tools::foreach() to specify whether or not the functor should be shared across threads. - Added Mat4s and Mat4d metadata types. - Added explicit pre- and postmultiplication methods to the Transform, Map and Mat4 classes and deprecated the old accumulation methods. - Modified NonlinearFrustumMap to be more compatible with Houdini's frustum transform. - Fixed a GridTransformer bug that caused it to translate the output grid incorrectly in some cases. - Fixed a bug in the tree-level LeafIterator that resulted in intermittent crashes in tools::dilateVoxels(). - The Hermite data type and Hermite grids are no longer supported. - Added tools/GridOperators.h, which includes new, cleaner implementations of the Cpt, Curl, Divergence, Gradient, Laplacian, Magnitude, MeanCurvature and Normalize tools. - Interrupt support has been improved in several tools, including tools::ParticlesToLevelSet. - Simplified the API of the Stencil class and added an intersects() method to test for intersection with a specified isovalue. - Renamed voxelDimensions to voxelSize in transform classes and elsewhere. - Deprecated houdini_utils::ParmFactory::setChoiceList() in favor of houdini_utils::ParmFactory::setChoiceListItems(), which requires a list of token, label string pairs. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Fixed a bug in houdini_utils::getNodeChain() that caused the Offset Level Set, Smooth Level Set and Renormalize Level Set SOPs to ignore frame changes. [Contributed by SESI] - The From Particles SOP now provides the option to write into an existing grid. - Added a SOP to edit grid metadata. - The Fracture SOP now supports multiple cutter objects. - Added a To Polygons SOP that complements the Fracture SOP and allows for elimination of seam lines, generation of correct vertex normals and grouping of polygons when surfacing fracture fragments, using the original level set or mesh as a reference. Version 0.103.1 - January 15 2013 - tree::ValueAccessor read operations are now faster for four-level trees. (Preliminary benchmark tests suggest a 30-40% improvement.) - For vector-valued grids, tools::compMin() and tools::compMax() now compare vector magnitudes instead of individual components. - Migrated grid sampling code to a new file, Interpolation.h, and deprecated old files and classes. - Added a level-set fracture tool and a Fracture SOP. - Added tools::sdfInteriorMask(), which creates a mask of the interior region of a level set grid. - Fixed a bug in the mesh to volume converter that produced unexpected nonzero values for voxels at the intersection of two polygons, and another bug that produced narrow-band widths that didn't respect the background value when the half-band width was less than three voxels. - houdini_utils::ParmFactory can now correctly generate ramp multi-parms. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - The Convert SOP can now convert between signed distance fields and fog volumes and from volumes to meshes. [Contributed by SESI] - For level sets, the From Mesh and From Particles SOPs now match the reference grid's narrow-band width. - The Scatter SOP can now optionally scatter points in the interior of a level set. Version 0.103.0 - December 21 2012 - The mesh to volume converter is now 60% faster at generating level sets with wide bands, and the From Mesh SOP is now interruptible. - Fixed a threading bug in the recently-added compReplace() tool that caused it to produce incorrect output. - Added a probeConstLeaf() method to the Tree, ValueAccessor and node classes. - The Houdini VDB primitive doesn't create a "name" attribute unnecessarily (i.e., if its grid's name is empty), but it now correctly allows the name to be changed to the empty string. - Fixed a crash in the Vector Merge SOP when fewer than three grids were merged. - The From Particles SOP now features a "maximum half-width" parameter to help avoid runaway computations. Version 0.102.0 - December 13 2012 - Added tools::compReplace(), which copies the active values of one grid into another, and added a "Replace A With Active B" mode to the Combine SOP. - Grid::signedFloodFill() no longer enters an infinite loop when filling an empty grid. - Fixed a bug in the particle to level set converter that sometimes produced level sets with holes, and fixed a bug in the SOP that could result in random output. - Fixed an issue in the frustum preview feature of the Create SOP whereby rendering very large frustums could cause high CPU usage. - Added streamline support to the constrained advection scheme in the Advect Points SOP. - Added an Advect Level Set SOP. Version 0.101.1 - December 11 2012 (DWA internal release) - Partially reverted the Houdini VDB primitive's grid accessor methods to their pre-0.98.0 behavior. A primitive's grid can once again be accessed by shared pointer, but now also by reference. Accessor methods for grid metadata have also been added, and the primitive now ensures that metadata and transforms are never shared. - Fixed an intermittent crash in the From Particles SOP. Version 0.101.0 - December 6 2012 (DWA internal release) - Partially reverted the Grid's tree and transform accessor methods to their pre-0.98.0 behavior, eliminating copy-on-write but preserving their return-by-reference semantics. These methods are now supplemented with a suite of shared pointer accessors. - Restructured the mesh to volume converter for a 40% speedup and to be more robust to non-manifold geometry, to better preserve sharp features, to support arbitrary tree configurations and to respect narrow-band limits. - Added a getNodeBoundingBox() method to RootNode, InternalNode and LeafNode that returns the index space spanned by a node. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Renamed the Reshape Level Set SOP to Offset Level Set. - Fixed a crash in the Convert SOP and added support for conversion of empty grids. Version 0.100.0 - November 30 2012 (DWA internal release) - Greatly improved the performance of the level set to fog volume converter. - Improved the performance of the median filter and of level set CSG operations. - Reintroduced Tree::pruneLevelSet(), a specialized pruneInactive() for level-set grids. - Added utilities to the houdini_utils library to facilitate the collection of a chain of adjacent nodes of a particular type so that they can be cooked in a single step. (For example, adjacent xform SOPs could be collapsed by composing their transformation matrices into a single matrix.) - Added pruning and flood-filling options to the Convert SOP. - Reimplemented the Filter SOP, omitting level-set-specific filters and adding node chaining (to reduce memory usage when applying several filters in sequence). - Added a toggle to the Read SOP to read grid metadata and transforms only. - Changed the attribute transfer scheme on the From Mesh and From Particles SOPs to allow for custom grid names and vector type metadata. Version 0.99.0 - November 21 2012 - Added Grid methods that return non-const Tree and Transform references without triggering deep copies, as well as const methods that return const shared pointers. - Added Grid methods to populate a grid's metadata with statistics like the active voxel count, and to retrieve that metadata. By default, statistics are now computed and added to grids whenever they are written to .vdb files. - Added io::File::readGridMetadata() and io::File::readAllGridMetadata() methods to read just the grid metadata and transforms from a .vdb file. - Fixed numerical precision issues in the csgUnion, csgIntersection and csgDifference tools, and added toggles to optionally disable postprocess pruning. - Fixed an issue in vdb_view with the ordering of GL vertex buffer calls. [Contributed by Bill Katz] - Fixed an intermittent crash in the ParticlesToLevelSet tool, as well as a race condition that could cause data corruption. - The ParticlesToLevelSet tool and From Particles SOP can now transfer arbitrary point attribute values from the input particles to output voxels. - Fixed a bug in the Convert SOP whereby the names of primitives were lost during conversion, and another bug that resulted in an arithmetic error when converting an empty grid. - Fixed a bug in the Combine SOP that caused the Operation selection to be lost. Version 0.98.0 - November 16 2012 - Tree and Transform objects (and Grid objects in the context of Houdini SOPs) are now passed and accessed primarily by reference rather than by shared pointer. See the online documentation for details about this important API change. [Contributed by SESI] - Reimplemented CoordBBox to address several off-by-one bugs related to bounding box dimensions. - Fixed an off-by-one bug in Grid::evalActiveVoxelBoundingBox(). - Introduced the LeafManager class, which will eventually replace the LeafArray class. LeafManager supports dynamic buffers stored as a structure of arrays (SOA), unlike LeafArray, which supports only static buffers stored as an array of structures (AOS). - Improved the performance of the LevelSetFilter and LevelSetTracker tools by rewriting them to use the new LeafManager class. - Added Tree and ValueAccessor setValueOnly() methods, which change the value of a voxel without changing its active state, and Tree and ValueAccessor probeLeaf() methods that return the leaf node that contains a given voxel (unless the voxel is represented by a tile). - Added a LevelSetAdvection tool that propagates and tracks narrow-band level sets. - Introduced a new GridSampler class that supports world-space (or index-space) sampling of grid values. - Changed the interpretation of the NonlinearFrustumMap's taper parameter to be the ratio of the near and far plane depths. - Added a ParmFactory::setChoiceList() overload that accepts (token, label) string pairs, and a setDefault() overload that accepts an STL string. - Fixed a crash in the Combine SOP in Copy B mode. - Split the Level Set Filter SOP into three separate SOPs, Level Set Smooth, Level Set Reshape and Level Set Renormalize. When two or more of these nodes are connected in sequence, they interact to reduce memory usage: the last node in the sequence performs all of the operations in one step. - The Advect Points SOP can now output polyline streamlines that trace the paths of the points. - Added an option to the Analysis SOP to specify names for output grids. - Added camera-derived frustum transform support to the Create SOP. Version 0.97.0 - October 18 2012 - Added a narrow-band level set interface tracking tool (up to fifth-order in space but currently only first-order in time, with higher temporal orders to be added soon). - Added a level set filter tool to perform unrestricted surface smoothing (e.g., Laplacian flow), filtering (e.g., mean value) and morphological operations (e.g., morphological opening). - Added adaptivity to the level set meshing tool for faster mesh extraction with fewer polygons, without postprocessing. - Added a ValueAccessor::touchLeaf() method that creates (if necessary) and returns the leaf node containing a given voxel. It can be used to preallocate leaf nodes over which to run parallel algorithms. - Fixed a bug in Grid::merge() whereby active tiles were sometimes lost. - Added LeafManager, which is similar to LeafArray but supports a dynamic buffer count and allocates buffers more efficiently. Useful for temporal integration (e.g., for level set propagation and interface tracking), LeafManager is meant to replace LeafArray, which will be deprecated in the next release. - Added a LeafNode::fill() method to efficiently populate leaf nodes with constant values. - Added a Tree::visitActiveBBox() method that applies a functor to the bounding boxes of all active tiles and leaf nodes and that can be used to improve the performance of ray intersection tests, rendering of bounding boxes, etc. - Added a Tree::voxelizeActiveTiles() method to densify active tiles. While convenient and fast, this can produce large dense grids, so use it with caution. - Repackaged Tree::pruneLevelSet() as a Tree::pruneOp()-compatible functor. LevelSetPrune is a specialized pruneInactive() for level-set grids and is used in interface tracking. - Added a GridBase::pruneGrid() method. - Added a Grid:hasUniformVoxels() method. - Renamed tools::dilate() to tools::dilateVoxels() and improved its performance. The new name reflects the fact that the current implementation ignores active tiles. - Added a tools::resampleToMatch() function that resamples an input grid into an output grid with a different transform such that, after resampling, the input and output grids coincide, but the output grid's transform is preserved. - Significantly improved the performance of depth-bounded value iterators (ValueOnIter, ValueAllIter, etc.) when the depth bound excludes leaf nodes. - Exposed the value buffers inside leaf nodes with LeafNode::buffer(). This allows for very fast access (const and non-const) to voxel values using linear array offsets instead of (i,j,k) coordinates. - In openvdb_houdini/UT_VDBTools.h, added operators for use with processTypedGrid() that resample grids in several different ways. - Added a policy mechanism to houdini_utils::OpFactory that allows for customization of operator names, icons, and Help URLs. - Renamed many of the Houdini SOPs to make the names more consistent. - Added an Advect Points SOP. - Added a Level Set Filter SOP that allows for unrestricted surface deformations, unlike the older Filter SOP, which restricts surface motion to the initial narrow band. - Added staggered vector sampling to the Sample Points SOP. - Added a minimum radius threshold to the particle voxelization tool and SOP. - Merged the Composite and CSG SOPs into a single Combine SOP. - Added a tool and a SOP to efficiently generate narrow-band level set representations of spheres. - In the Visualize SOP, improved the performance of tree topology generation, which is now enabled by default. Version 0.96.0 - September 24 2012 - Fixed a memory corruption bug in the mesh voxelizer tool. - Temporarily removed the optional clipping feature from the level set mesher. - Added "Staggered Vector Field" to the list of grid classes in the Create SOP. Version 0.95.0 - September 20 2012 - Added a quad meshing tool for higher-quality level set meshing and updated the Visualizer SOP to use it. - Fixed a precision error in the mesh voxelizer and improved the quality of inside/outside voxel classification. Output grids are now also tagged as either level sets or fog volumes. - Modified the GridResampler to use the signed flood fill optimization only on grids that are tagged as level sets. - Added a quaternion class to the math library and a method to return the trace of a Mat3. - Fixed a bug in the ValueAccessor copy constructor that caused the copy to reference the original. - Fixed a bug in RootNode::setActiveState() that caused a crash when marking a (virtual) background voxel as inactive. - Added a Tree::pruneLevelSet() method that is similar to but faster than pruneInactive() for level set grids. - Added fast leaf node voxel access methods that index by linear offset (as returned by ValueIter::pos()) instead of by (i, j, k) coordinates. - Added a Tree::touchLeaf() method that can be used to preallocate a static tree topology over which to safely perform multithreaded processing. - Added a grain size argument to the LeafArray class for finer control of parallelism. - Modified the Makefile to make it easier to omit the doc, vdb_test and vdb_view targets. - Added utility functions (in houdini/UT_VDBUtils.h) to convert between Houdini and OpenVDB matrix and vector types. [Contributed by SESI] - Added accessors to GEO_PrimVDB that make it easier to directly access voxel data and that are used by the HScript volume expression functions in Houdini 12.5. [Contributed by SESI] - As of Houdini 12.1.77, the native transform SOP operates on OpenVDB primitives. [Contributed by SESI] - Added a Convert SOP that converts OpenVDB grids to Houdini volumes and vice-versa. Version 0.94.1 - September 7 2012 - Fixed bugs in RootNode and InternalNode setValue*() and fill() methods that could cause neighboring voxels to become inactive. - Fixed a bug in Tree::hasSameTopology() that caused false positives when only active states and not values differed. - Added a Tree::hasActiveTiles() method. - For better cross-platform consistency, substituted bitwise AND operations for right shifts in the ValueAccessor hash key computation. - vdb_view no longer aborts when asked to surface a vector-valued grid--but it still doesn't render the surface. - Made various changes for Visual C++ compatibility. [Contributed by SESI] - Added an option to the MeshVoxelizer SOP to convert both open and closed surfaces to unsigned distance fields. - The Filter SOP now allows multiple filters to be applied in user-specified order. Version 0.94.0 - August 30 2012 - Added a method to union just the active states of voxels from one grid with those of another grid of a possibly different type. - Fixed an incorrect scale factor in the Laplacian diffusion filter. - Fixed a bug in Tree::merge that could leave a tree with invalid value accessors. - Added TreeValueIteratorBase::setActiveState() and deprecated setValueOn(). - Removed tools/FastSweeping.h. It will be replaced with a much more efficient implementation in the near future. - ZIP compression of .vdb files is now optional, but enabled by default. [Contributed by SESI] - Made various changes for Clang and Visual C++ compatibility. [Contributed by SESI] - The MeshVoxelizer SOP can now transfer arbitrary point and primitive attribute values from the input mesh to output voxels. Version 0.93.0 - August 24 2012 - Renamed symbols in math/Operators.h to avoid ambiguities that GCC 4.4 reports as errors. - Simplified the API for the stencil version of the closest-point transform operator. - Added logic to io::Archive::readGrid() to set the grid name metadata from the descriptor if the metadata doesn't already exist. - Added guards to prevent nesting of openvdb_houdini::Interrupter::start() and end() calls. Version 0.92.0 - August 23 2012 - Added a Laplacian diffusion filter. - Fixed a bug in the initialization of the sparse contour tracer that caused mesh-to-volume conversion to fail in certain cases. - Fixed a bug in the curvature stencil that caused mean curvature filtering to produce wrong results. - Increased the speed of the GridTransformer by as much as 20% for fog volumes. - Added optional pruning to the Resample SOP. - Modified the PointSample SOP to allow it to work with ungrouped, anonymous grids. - Fixed a crash in the LevelSetNoise SOP. Version 0.91.0 - August 16 2012 - tools::GridTransformer and tools::GridResampler now correctly (but not yet efficiently) process tiles in sparse grids. - Added an optional CopyPolicy argument to GridBase::copyGrid() and to Grid::copy() that specifies whether and how the grid's tree should be copied. - Added a GridBase::newTree() method that replaces a grid's tree with a new, empty tree of the correct type. - Fixed a crash in Tree::setValueOff() when the new value was equal to the background value. - Fixed bugs in Tree::prune() that could result in output tiles with incorrect active states. - Added librt to the link dependencies to address build failures on Ubuntu systems. - Made various small changes to the Makefile and the source code that should help with Mac OS X compatibility. - The Composite and Resample SOPs now correctly copy the input grid's metadata to the output grid. Version 0.90.1 - August 7 2012 - Fixed a bug in the BBox::getCenter() method. - Added missing header files to various files. - io::File::NameIterator::gridName() now returns a unique name of the form "name[1]", "name[2]", etc. if a file contains multiple grids with the same name. - Fixed a bug in the Writer SOP that caused grid names to be discarded. - The Resample SOP now correctly sets the background value of the output grid. Version 0.90.0 - August 3 2012 (initial public release) - Added a basic GL viewer for OpenVDB files. - Greatly improved the performance of two commonly-used Tree methods, evalActiveVoxelBoundingBox() and memUsage(). - Eliminated the GridMap class. File I/O now uses STL containers of grid pointers instead. - Refactored stencil-based tools (Gradient, Laplacian, etc.) and rewrote some of them for generality and better performance. Most now behave correctly for grids with nonlinear index-to-world transforms. - Added a library of index-space finite difference operators. - Added a "Hermite" grid type that compactly stores each voxel's upwind normals and can be used to convert volumes to and from polygonal meshes. - Added a tool (and a Houdini SOP) to scatter points randomly throughout a volume. openvdb/Exceptions.h0000644000000000000000000000715112252453157013515 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_EXCEPTIONS_HAS_BEEN_INCLUDED #define OPENVDB_EXCEPTIONS_HAS_BEEN_INCLUDED #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { class OPENVDB_API Exception: public std::exception { public: virtual const char* what() const throw() { try { return mMessage.c_str(); } catch (...) {}; return NULL; } virtual ~Exception() throw() {} protected: Exception() throw() {} explicit Exception(const char* eType, const std::string* const msg = NULL) throw() { try { if (eType) mMessage = eType; if (msg) mMessage += ": " + (*msg); } catch (...) {} } private: std::string mMessage; }; #define OPENVDB_EXCEPTION(_classname) \ class OPENVDB_API _classname: public Exception \ { \ public: \ _classname() throw() : Exception( #_classname ) {} \ explicit _classname(const std::string &msg) throw() : Exception( #_classname , &msg) {} \ } OPENVDB_EXCEPTION(ArithmeticError); OPENVDB_EXCEPTION(IllegalValueException); OPENVDB_EXCEPTION(IndexError); OPENVDB_EXCEPTION(IoError); OPENVDB_EXCEPTION(KeyError); OPENVDB_EXCEPTION(LookupError); OPENVDB_EXCEPTION(NotImplementedError); OPENVDB_EXCEPTION(ReferenceError); OPENVDB_EXCEPTION(RuntimeError); OPENVDB_EXCEPTION(TypeError); OPENVDB_EXCEPTION(ValueError); #undef OPENVDB_EXCEPTION } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #define OPENVDB_THROW(exception, message) \ { \ std::string _openvdb_throw_msg; \ try { \ std::ostringstream _openvdb_throw_os; \ _openvdb_throw_os << message; \ _openvdb_throw_msg = _openvdb_throw_os.str(); \ } catch (...) {} \ throw exception(_openvdb_throw_msg); \ } // OPENVDB_THROW #endif // OPENVDB_EXCEPTIONS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Grid.h0000644000000000000000000013555112252453157012267 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_GRID_HAS_BEEN_INCLUDED #define OPENVDB_GRID_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { typedef tree::TreeBase TreeBase; template class Grid; // forward declaration /// @brief Create a new grid of type @c GridType with a given background value. /// /// @note Calling createGrid(background) is equivalent to calling /// GridType::create(background). template inline typename GridType::Ptr createGrid(const typename GridType::ValueType& background); /// @brief Create a new grid of type @c GridType with background value zero. /// /// @note Calling createGrid() is equivalent to calling GridType::create(). template inline typename GridType::Ptr createGrid(); /// @brief Create a new grid of the appropriate type that wraps the given tree. /// /// @note This function can be called without specifying the template argument, /// i.e., as createGrid(tree). template inline typename Grid::Ptr createGrid(TreePtrType); /// @brief Create a new grid of type @c GridType classified as a "Level Set", /// i.e., a narrow-band level set. /// /// @note @c GridType::ValueType must be a floating-point scalar. /// /// @param voxelSize the size of a voxel in world units /// @param halfWidth the half width of the narrow band in voxel units /// /// @details The voxel size and the narrow band half width define the grid's /// background value as halfWidth*voxelWidth. The transform is linear /// with a uniform scaling only corresponding to the specified voxel size. /// /// @note It is generally advisable to specify a half-width of the narrow band /// that is larger than one voxel unit, otherwise zero crossings are not guaranteed. template typename GridType::Ptr createLevelSet( Real voxelSize = 1.0, Real halfWidth = LEVEL_SET_HALF_WIDTH); //////////////////////////////////////// /// @brief Abstract base class for typed grids class OPENVDB_API GridBase: public MetaMap { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; typedef Ptr (*GridFactory)(); virtual ~GridBase() {} /// @brief Return a new grid of the same type as this grid and whose /// metadata and transform are deep copies of this grid's. virtual GridBase::Ptr copyGrid(CopyPolicy treePolicy = CP_SHARE) const = 0; /// Return a new grid whose metadata, transform and tree are deep copies of this grid's. virtual GridBase::Ptr deepCopyGrid() const = 0; // // Registry methods // /// Create a new grid of the given (registered) type. static Ptr createGrid(const Name& type); /// Return @c true if the given grid type name is registered. static bool isRegistered(const Name &type); /// Clear the grid type registry. static void clearRegistry(); // // Grid type methods // /// Return the name of this grid's type. virtual Name type() const = 0; /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d"). virtual Name valueType() const = 0; /// Return @c true if this grid is of the same type as the template parameter. template bool isType() const { return (this->type() == GridType::gridType()); } //@{ /// @brief Return the result of downcasting a GridBase pointer to a Grid pointer /// of the specified type, or return a null pointer if the types are incompatible. template static typename GridType::Ptr grid(const GridBase::Ptr&); template static typename GridType::ConstPtr grid(const GridBase::ConstPtr&); template static typename GridType::ConstPtr constGrid(const GridBase::Ptr&); template static typename GridType::ConstPtr constGrid(const GridBase::ConstPtr&); //@} //@{ /// @brief Return a pointer to this grid's tree, which might be /// shared with other grids. The pointer is guaranteed to be non-null. TreeBase::Ptr baseTreePtr(); TreeBase::ConstPtr baseTreePtr() const { return this->constBaseTreePtr(); } virtual TreeBase::ConstPtr constBaseTreePtr() const = 0; //@} //@{ /// @brief Return a reference to this grid's tree, which might be /// shared with other grids. /// @note Calling setTree() on this grid invalidates all references /// previously returned by this method. TreeBase& baseTree() { return const_cast(this->constBaseTree()); } const TreeBase& baseTree() const { return this->constBaseTree(); } const TreeBase& constBaseTree() const { return *(this->constBaseTreePtr()); } //@} /// @brief Associate the given tree with this grid, in place of its existing tree. /// @throw ValueError if the tree pointer is null /// @throw TypeError if the tree is not of the appropriate type /// @note Invalidates all references previously returned by baseTree() /// or constBaseTree(). virtual void setTree(TreeBase::Ptr) = 0; /// Set a new tree with the same background value as the previous tree. virtual void newTree() = 0; /// Return @c true if this grid contains only background voxels. virtual bool empty() const = 0; /// Empty this grid, setting all voxels to the background. virtual void clear() = 0; /// @brief Reduce the memory footprint of this grid by increasing its sparseness /// either losslessly (@a tolerance = 0) or lossily (@a tolerance > 0). /// @details With @a tolerance > 0, sparsify regions where voxels have the same /// active state and have values that differ by no more than the tolerance /// (converted to this grid's value type). virtual void pruneGrid(float tolerance = 0.0) = 0; // // Metadata // /// Return this grid's user-specified name. std::string getName() const; /// Specify a name for this grid. void setName(const std::string&); /// Return the user-specified description of this grid's creator. std::string getCreator() const; /// Provide a description of this grid's creator. void setCreator(const std::string&); /// @brief Return @c true if this grid should be written out with floating-point /// voxel values (including components of vectors) quantized to 16 bits. bool saveFloatAsHalf() const; void setSaveFloatAsHalf(bool); /// Return the class of volumetric data (level set, fog volume, etc.) stored in this grid. GridClass getGridClass() const; /// Specify the class of volumetric data (level set, fog volume, etc.) stored in this grid. void setGridClass(GridClass); /// Remove the setting specifying the class of this grid's volumetric data. void clearGridClass(); /// Return the metadata string value for the given class of volumetric data. static std::string gridClassToString(GridClass); /// Return a formatted string version of the grid class. static std::string gridClassToMenuName(GridClass); /// @brief Return the class of volumetric data specified by the given string. /// @details If the string is not one of the ones returned by gridClassToString(), /// return @c GRID_UNKNOWN. static GridClass stringToGridClass(const std::string&); /// @brief Return the type of vector data (invariant, covariant, etc.) stored /// in this grid, assuming that this grid contains a vector-valued tree. VecType getVectorType() const; /// @brief Specify the type of vector data (invariant, covariant, etc.) stored /// in this grid, assuming that this grid contains a vector-valued tree. void setVectorType(VecType); /// Remove the setting specifying the type of vector data stored in this grid. void clearVectorType(); /// Return the metadata string value for the given type of vector data. static std::string vecTypeToString(VecType); /// Return a string listing examples of the given type of vector data /// (e.g., "Gradient/Normal", given VEC_COVARIANT). static std::string vecTypeExamples(VecType); /// @brief Return a string describing how the given type of vector data is affected /// by transformations (e.g., "Does not transform", given VEC_INVARIANT). static std::string vecTypeDescription(VecType); static VecType stringToVecType(const std::string&); /// Return @c true if this grid's voxel values are in world space and should be /// affected by transformations, @c false if they are in local space and should /// not be affected by transformations. bool isInWorldSpace() const; /// Specify whether this grid's voxel values are in world space or in local space. void setIsInWorldSpace(bool); // Standard metadata field names // (These fields should normally not be accessed directly, but rather // via the accessor methods above, when available.) // Note: Visual C++ requires these declarations to be separate statements. static const char* const META_GRID_CLASS; static const char* const META_GRID_CREATOR; static const char* const META_GRID_NAME; static const char* const META_SAVE_HALF_FLOAT; static const char* const META_IS_LOCAL_SPACE; static const char* const META_VECTOR_TYPE; static const char* const META_FILE_BBOX_MIN; static const char* const META_FILE_BBOX_MAX; static const char* const META_FILE_COMPRESSION; static const char* const META_FILE_MEM_BYTES; static const char* const META_FILE_VOXEL_COUNT; // // Statistics // /// Return the number of active voxels. virtual Index64 activeVoxelCount() const = 0; /// Return the axis-aligned bounding box of all active voxels. If /// the grid is empty a default bbox is returned. virtual CoordBBox evalActiveVoxelBoundingBox() const = 0; /// Return the dimensions of the axis-aligned bounding box of all active voxels. virtual Coord evalActiveVoxelDim() const = 0; /// Return the number of bytes of memory used by this grid. virtual Index64 memUsage() const = 0; /// @brief Add metadata to this grid comprising the current values /// of statistics like the active voxel count and bounding box. /// @note This metadata is not automatically kept up-to-date with /// changes to this grid. void addStatsMetadata(); /// @brief Return a new MetaMap containing just the metadata that /// was added to this grid with addStatsMetadata(). /// @details If addStatsMetadata() was never called on this grid, /// return an empty MetaMap. MetaMap::Ptr getStatsMetadata() const; // // Transform methods // //@{ /// @brief Return a pointer to this grid's transform, which might be /// shared with other grids. math::Transform::Ptr transformPtr() { return mTransform; } math::Transform::ConstPtr transformPtr() const { return mTransform; } math::Transform::ConstPtr constTransformPtr() const { return mTransform; } //@} //@{ /// @brief Return a reference to this grid's transform, which might be /// shared with other grids. /// @note Calling setTransform() on this grid invalidates all references /// previously returned by this method. math::Transform& transform() { return *mTransform; } const math::Transform& transform() const { return *mTransform; } const math::Transform& constTransform() const { return *mTransform; } //@} /// @brief Associate the given transform with this grid, in place of /// its existing transform. /// @throw ValueError if the transform pointer is null /// @note Invalidates all references previously returned by transform() /// or constTransform(). void setTransform(math::Transform::Ptr); /// Return the size of this grid's voxels. Vec3d voxelSize() const { return transform().voxelSize(); } /// @brief Return the size of this grid's voxel at position (x, y, z). /// @note Frustum and perspective transforms have position-dependent voxel size. Vec3d voxelSize(const Vec3d& xyz) const { return transform().voxelSize(xyz); } /// Return true if the voxels in world space are uniformly sized cubes bool hasUniformVoxels() const { return mTransform->hasUniformScale(); } //@{ /// Apply this grid's transform to the given coordinates. Vec3d indexToWorld(const Vec3d& xyz) const { return transform().indexToWorld(xyz); } Vec3d indexToWorld(const Coord& ijk) const { return transform().indexToWorld(ijk); } //@} /// Apply the inverse of this grid's transform to the given coordinates. Vec3d worldToIndex(const Vec3d& xyz) const { return transform().worldToIndex(xyz); } // // I/O methods // /// @brief Read the grid topology from a stream. /// This will read only the grid structure, not the actual data buffers. virtual void readTopology(std::istream&) = 0; /// @brief Write the grid topology to a stream. /// This will write only the grid structure, not the actual data buffers. virtual void writeTopology(std::ostream&) const = 0; /// Read all data buffers for this grid. virtual void readBuffers(std::istream&) = 0; /// Write out all data buffers for this grid. virtual void writeBuffers(std::ostream&) const = 0; /// Read in the transform for this grid. void readTransform(std::istream& is) { transform().read(is); } /// Write out the transform for this grid. void writeTransform(std::ostream& os) const { transform().write(os); } /// Output a human-readable description of this grid. virtual void print(std::ostream& = std::cout, int verboseLevel = 1) const = 0; protected: /// @brief Initialize with an identity linear transform. GridBase(): mTransform(math::Transform::createLinearTransform()) {} /// @brief Deep copy another grid's metadata and transform. GridBase(const GridBase& other): MetaMap(other), mTransform(other.mTransform->copy()) {} /// @brief Copy another grid's metadata but share its transform. GridBase(const GridBase& other, ShallowCopy): MetaMap(other), mTransform(other.mTransform) {} /// Register a grid type along with a factory function. static void registerGrid(const Name& type, GridFactory); /// Remove a grid type from the registry. static void unregisterGrid(const Name& type); private: math::Transform::Ptr mTransform; }; // class GridBase //////////////////////////////////////// typedef std::vector GridPtrVec; typedef GridPtrVec::iterator GridPtrVecIter; typedef GridPtrVec::const_iterator GridPtrVecCIter; typedef boost::shared_ptr GridPtrVecPtr; typedef std::vector GridCPtrVec; typedef GridCPtrVec::iterator GridCPtrVecIter; typedef GridCPtrVec::const_iterator GridCPtrVecCIter; typedef boost::shared_ptr GridCPtrVecPtr; typedef std::set GridPtrSet; typedef GridPtrSet::iterator GridPtrSetIter; typedef GridPtrSet::const_iterator GridPtrSetCIter; typedef boost::shared_ptr GridPtrSetPtr; typedef std::set GridCPtrSet; typedef GridCPtrSet::iterator GridCPtrSetIter; typedef GridCPtrSet::const_iterator GridCPtrSetCIter; typedef boost::shared_ptr GridCPtrSetPtr; /// @brief Predicate functor that returns @c true for grids that have a specified name struct OPENVDB_API GridNamePred { GridNamePred(const Name& name): name(name) {} bool operator()(const GridBase::ConstPtr& g) const { return g && g->getName() == name; } Name name; }; /// Return the first grid in the given container whose name is @a name. template inline typename GridPtrContainerT::value_type findGridByName(const GridPtrContainerT& container, const Name& name) { typedef typename GridPtrContainerT::value_type GridPtrT; typename GridPtrContainerT::const_iterator it = std::find_if(container.begin(), container.end(), GridNamePred(name)); return (it == container.end() ? GridPtrT() : *it); } /// Return the first grid in the given map whose name is @a name. template inline GridPtrT findGridByName(const std::map& container, const Name& name) { typedef std::map GridPtrMapT; for (typename GridPtrMapT::const_iterator it = container.begin(), end = container.end(); it != end; ++it) { const GridPtrT& grid = it->second; if (grid && grid->getName() == name) return grid; } return GridPtrT(); } //@} //////////////////////////////////////// /// @brief Container class that associates a tree with a transform and metadata template class Grid: public GridBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; typedef _TreeType TreeType; typedef typename _TreeType::Ptr TreePtrType; typedef typename _TreeType::ConstPtr ConstTreePtrType; typedef typename _TreeType::ValueType ValueType; typedef typename tree::ValueAccessor<_TreeType> Accessor; typedef typename tree::ValueAccessor ConstAccessor; typedef typename _TreeType::ValueOnIter ValueOnIter; typedef typename _TreeType::ValueOnCIter ValueOnCIter; typedef typename _TreeType::ValueOffIter ValueOffIter; typedef typename _TreeType::ValueOffCIter ValueOffCIter; typedef typename _TreeType::ValueAllIter ValueAllIter; typedef typename _TreeType::ValueAllCIter ValueAllCIter; /// @brief ValueConverter::Type is the type of a grid having the same /// hierarchy as this grid but a different value type, T. /// /// For example, FloatGrid::ValueConverter::Type is equivalent to DoubleGrid. /// @note If the source grid type is a template argument, it might be necessary /// to write "typename SourceGrid::template ValueConverter::Type". template struct ValueConverter { typedef Grid::Type> Type; }; /// Return a new grid with the given background value. static Ptr create(const ValueType& background); /// Return a new grid with background value zero. static Ptr create(); /// @brief Return a new grid that contains the given tree. /// @throw ValueError if the tree pointer is null static Ptr create(TreePtrType); /// @brief Return a new, empty grid with the same transform and metadata as the /// given grid and with background value zero. static Ptr create(const GridBase& other); /// Construct a new grid with background value zero. Grid(); /// Construct a new grid with the given background value. explicit Grid(const ValueType& background); /// @brief Construct a new grid that shares the given tree and associates with it /// an identity linear transform. /// @throw ValueError if the tree pointer is null explicit Grid(TreePtrType); /// Deep copy another grid's metadata, transform and tree. Grid(const Grid&); /// Deep copy another grid's metadata, but share its tree and transform. Grid(const Grid&, ShallowCopy); /// @brief Deep copy another grid's metadata and transform, but construct a new tree /// with background value zero. Grid(const GridBase&); virtual ~Grid() {} //@{ /// @brief Return a new grid of the same type as this grid and whose /// metadata and transform are deep copies of this grid's. /// @details If @a treePolicy is @c CP_NEW, give the new grid a new, empty tree; /// if @c CP_SHARE, the new grid shares this grid's tree and transform; /// if @c CP_COPY, the new grid's tree is a deep copy of this grid's tree and transform Ptr copy(CopyPolicy treePolicy = CP_SHARE) const; virtual GridBase::Ptr copyGrid(CopyPolicy treePolicy = CP_SHARE) const; //@} //@{ /// Return a new grid whose metadata, transform and tree are deep copies of this grid's. Ptr deepCopy() const { return Ptr(new Grid(*this)); } virtual GridBase::Ptr deepCopyGrid() const { return this->deepCopy(); } //@} /// Return the name of this grid's type. virtual Name type() const { return this->gridType(); } /// Return the name of this type of grid. static Name gridType() { return TreeType::treeType(); } // // Voxel access methods // /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d"). virtual Name valueType() const { return tree().valueType(); } /// Return this grid's background value. const ValueType& background() const { return mTree->background(); } /// Replace this grid's background value. void setBackground(const ValueType& val) { tree().setBackground(val); } /// Return @c true if this grid contains only inactive background voxels. virtual bool empty() const { return tree().empty(); } /// Empty this grid, so that all voxels become inactive background voxels. virtual void clear() { tree().clear(); } /// Return an accessor that provides random read and write access to this grid's voxels. Accessor getAccessor() { return Accessor(tree()); } //@{ /// Return an accessor that provides random read-only access to this grid's voxels. ConstAccessor getAccessor() const { return ConstAccessor(tree()); } ConstAccessor getConstAccessor() const { return ConstAccessor(tree()); } //@} //@{ /// Return an iterator over all of this grid's active values (tile and voxel). ValueOnIter beginValueOn() { return tree().beginValueOn(); } ValueOnCIter beginValueOn() const { return tree().cbeginValueOn(); } ValueOnCIter cbeginValueOn() const { return tree().cbeginValueOn(); } //@} //@{ /// Return an iterator over all of this grid's inactive values (tile and voxel). ValueOffIter beginValueOff() { return tree().beginValueOff(); } ValueOffCIter beginValueOff() const { return tree().cbeginValueOff(); } ValueOffCIter cbeginValueOff() const { return tree().cbeginValueOff(); } //@} //@{ /// Return an iterator over all of this grid's values (tile and voxel). ValueAllIter beginValueAll() { return tree().beginValueAll(); } ValueAllCIter beginValueAll() const { return tree().cbeginValueAll(); } ValueAllCIter cbeginValueAll() const { return tree().cbeginValueAll(); } //@} /// Return the minimum and maximum active values in this grid. void evalMinMax(ValueType& minVal, ValueType& maxVal) const; /// @brief Set all voxels within a given axis-aligned box to a constant value. /// @param bbox inclusive coordinates of opposite corners of an axis-aligned box /// @param value the value to which to set voxels within the box /// @param active if true, mark voxels within the box as active, /// otherwise mark them as inactive /// @note This operation generates a sparse, but not always optimally sparse, /// representation of the filled box. Follow fill operations with a prune() /// operation for optimal sparseness. void fill(const CoordBBox& bbox, const ValueType& value, bool active = true); /// @brief Set the values of all inactive voxels and tiles of a narrow-band /// level set from the signs of the active voxels, setting outside values to /// +background and inside values to -background. /// /// @note This operation should only be used on closed, narrow-band level sets! void signedFloodFill() { tree().signedFloodFill(); } /// @brief Set the values of all inactive voxels and tiles of a narrow-band /// level set from the signs of the active voxels, setting outside values to /// @a outside and inside values to @a inside. /// @details Also, set this grid's background value to @a outside. /// /// @note This operation should only be used on closed, narrow-band level sets! /// Also, @a inside should be negative, and @a outside should be larger than @a inside. void signedFloodFill(const ValueType& outside, const ValueType& inside); /// @brief Reduce the memory footprint of this grid by increasing its sparseness /// either losslessly (@a tolerance = 0) or lossily (@a tolerance > 0). /// @details With @a tolerance > 0, sparsify regions where voxels have the same /// active state and have values that differ by no more than the tolerance. void prune(const ValueType& tolerance = zeroVal()) { tree().prune(tolerance); } /// Reduce the memory footprint of this grid by increasing its sparseness. virtual void pruneGrid(float tolerance = 0.0); /// @brief Efficiently merge another grid into this grid using one of several schemes. /// @details This operation is primarily intended to combine grids that are mostly /// non-overlapping (for example, intermediate grids from computations that are /// parallelized across disjoint regions of space). /// @warning This operation always empties the other grid. void merge(Grid& other, MergePolicy policy = MERGE_ACTIVE_STATES); /// @brief Union this grid's set of active values with the active values /// of the other grid, whose value type may be different. /// @details The resulting state of a value is active if the corresponding value /// was already active OR if it is active in the other grid. Also, a resulting /// value maps to a voxel if the corresponding value already mapped to a voxel /// OR if it is a voxel in the other grid. Thus, a resulting value can only /// map to a tile if the corresponding value already mapped to a tile /// AND if it is a tile value in other grid. /// /// @note This operation modifies only active states, not values. /// Specifically, active tiles and voxels in this grid are not changed, and /// tiles or voxels that were inactive in this grid but active in the other grid /// are marked as active in this grid but left with their original values. template void topologyUnion(const Grid& other) { tree().topologyUnion(other.tree()); } /// @brief Intersects this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. /// @details The resulting state of a value is active only if the corresponding /// value was already active AND if it is active in the other tree. Also, a /// resulting value maps to a voxel if the corresponding value /// already mapped to an active voxel in either of the two grids /// and it maps to an active tile or voxel in the other grid. /// /// @note This operation can delete branches in this grid if they /// overlap with inactive tiles in the other grid. Likewise active /// voxels can be turned into unactive voxels resulting in leaf /// nodes with no active values. Thus, it is recommended to /// subsequently call prune. template void topologyIntersection(const Grid& other) { tree().topologyIntersection(other.tree()); } /// @todo topologyDifference // // Statistics // /// Return the number of active voxels. virtual Index64 activeVoxelCount() const { return tree().activeVoxelCount(); } /// Return the axis-aligned bounding box of all active voxels. virtual CoordBBox evalActiveVoxelBoundingBox() const; /// Return the dimensions of the axis-aligned bounding box of all active voxels. virtual Coord evalActiveVoxelDim() const; /// Return the number of bytes of memory used by this grid. /// @todo Add transform().memUsage() virtual Index64 memUsage() const { return tree().memUsage(); } // // Tree methods // //@{ /// @brief Return a pointer to this grid's tree, which might be /// shared with other grids. The pointer is guaranteed to be non-null. TreePtrType treePtr() { return mTree; } ConstTreePtrType treePtr() const { return mTree; } ConstTreePtrType constTreePtr() const { return mTree; } virtual TreeBase::ConstPtr constBaseTreePtr() const { return mTree; } //@} //@{ /// @brief Return a reference to this grid's tree, which might be /// shared with other grids. /// @note Calling setTree() on this grid invalidates all references /// previously returned by this method. TreeType& tree() { return *mTree; } const TreeType& tree() const { return *mTree; } const TreeType& constTree() const { return *mTree; } //@} /// @brief Associate the given tree with this grid, in place of its existing tree. /// @throw ValueError if the tree pointer is null /// @throw TypeError if the tree is not of type TreeType /// @note Invalidates all references previously returned by baseTree(), /// constBaseTree(), tree() or constTree(). virtual void setTree(TreeBase::Ptr); /// @brief Associate a new, empty tree with this grid, in place of its existing tree. /// @note The new tree has the same background value as the existing tree. virtual void newTree(); // // I/O methods // /// @brief Read the grid topology from a stream. /// This will read only the grid structure, not the actual data buffers. virtual void readTopology(std::istream&); /// @brief Write the grid topology to a stream. /// This will write only the grid structure, not the actual data buffers. virtual void writeTopology(std::ostream&) const; /// Read all data buffers for this grid. virtual void readBuffers(std::istream&); /// Write out all data buffers for this grid. virtual void writeBuffers(std::ostream&) const; /// Output a human-readable description of this grid. virtual void print(std::ostream& = std::cout, int verboseLevel = 1) const; // // Registry methods // /// Return @c true if this grid type is registered. static bool isRegistered() { return GridBase::isRegistered(Grid::gridType()); } /// Register this grid type along with a factory function. static void registerGrid() { GridBase::registerGrid(Grid::gridType(), Grid::factory); } /// Remove this grid type from the registry. static void unregisterGrid() { GridBase::unregisterGrid(Grid::gridType()); } private: /// Disallow assignment, since it wouldn't be obvious whether the copy is deep or shallow. Grid& operator=(const Grid& other); /// Helper function for use with registerGrid() static GridBase::Ptr factory() { return Grid::create(); } TreePtrType mTree; }; // class Grid //////////////////////////////////////// /// @brief Cast a generic grid pointer to a pointer to a grid of a concrete class. /// /// Return a null pointer if the input pointer is null or if it /// points to a grid that is not of type @c GridType. /// /// @note Calling gridPtrCast(grid) is equivalent to calling /// GridBase::grid(grid). template inline typename GridType::Ptr gridPtrCast(const GridBase::Ptr& grid) { return GridBase::grid(grid); } /// @brief Cast a generic const grid pointer to a const pointer to a grid /// of a concrete class. /// /// Return a null pointer if the input pointer is null or if it /// points to a grid that is not of type @c GridType. /// /// @note Calling gridConstPtrCast(grid) is equivalent to calling /// GridBase::constGrid(grid). template inline typename GridType::ConstPtr gridConstPtrCast(const GridBase::ConstPtr& grid) { return GridBase::constGrid(grid); } //////////////////////////////////////// /// @{ /// @brief Return a pointer to a deep copy of the given grid, provided that /// the grid's concrete type is @c GridType. /// /// Return a null pointer if the input pointer is null or if it /// points to a grid that is not of type @c GridType. template inline typename GridType::Ptr deepCopyTypedGrid(const GridBase::ConstPtr& grid) { if (!grid || !grid->isType()) return typename GridType::Ptr(); return gridPtrCast(grid->deepCopyGrid()); } template inline typename GridType::Ptr deepCopyTypedGrid(const GridBase& grid) { if (!grid.isType()) return typename GridType::Ptr(); return gridPtrCast(grid.deepCopyGrid()); } /// @} //////////////////////////////////////// //@{ /// @brief This adapter allows code that is templated on a Tree type to /// accept either a Tree type or a Grid type. template struct TreeAdapter { typedef _TreeType TreeType; typedef typename boost::remove_const::type NonConstTreeType; typedef typename TreeType::Ptr TreePtrType; typedef typename TreeType::ConstPtr ConstTreePtrType; typedef typename NonConstTreeType::Ptr NonConstTreePtrType; typedef Grid GridType; typedef Grid NonConstGridType; typedef typename GridType::Ptr GridPtrType; typedef typename NonConstGridType::Ptr NonConstGridPtrType; typedef typename GridType::ConstPtr ConstGridPtrType; typedef typename TreeType::ValueType ValueType; typedef typename tree::ValueAccessor AccessorType; typedef typename tree::ValueAccessor ConstAccessorType; typedef typename tree::ValueAccessor NonConstAccessorType; static TreeType& tree(TreeType& t) { return t; } static TreeType& tree(GridType& g) { return g.tree(); } static const TreeType& tree(const TreeType& t) { return t; } static const TreeType& tree(const GridType& g) { return g.tree(); } static const TreeType& constTree(TreeType& t) { return t; } static const TreeType& constTree(GridType& g) { return g.constTree(); } static const TreeType& constTree(const TreeType& t) { return t; } static const TreeType& constTree(const GridType& g) { return g.constTree(); } }; /// Partial specialization for Grid types template struct TreeAdapter > { typedef _TreeType TreeType; typedef typename boost::remove_const::type NonConstTreeType; typedef typename TreeType::Ptr TreePtrType; typedef typename TreeType::ConstPtr ConstTreePtrType; typedef typename NonConstTreeType::Ptr NonConstTreePtrType; typedef Grid GridType; typedef Grid NonConstGridType; typedef typename GridType::Ptr GridPtrType; typedef typename NonConstGridType::Ptr NonConstGridPtrType; typedef typename GridType::ConstPtr ConstGridPtrType; typedef typename TreeType::ValueType ValueType; typedef typename tree::ValueAccessor AccessorType; typedef typename tree::ValueAccessor ConstAccessorType; typedef typename tree::ValueAccessor NonConstAccessorType; static TreeType& tree(TreeType& t) { return t; } static TreeType& tree(GridType& g) { return g.tree(); } static const TreeType& tree(const TreeType& t) { return t; } static const TreeType& tree(const GridType& g) { return g.tree(); } static const TreeType& constTree(TreeType& t) { return t; } static const TreeType& constTree(GridType& g) { return g.constTree(); } static const TreeType& constTree(const TreeType& t) { return t; } static const TreeType& constTree(const GridType& g) { return g.constTree(); } }; /// Partial specialization for ValueAccessor types template struct TreeAdapter > { typedef _TreeType TreeType; typedef typename boost::remove_const::type NonConstTreeType; typedef typename TreeType::Ptr TreePtrType; typedef typename TreeType::ConstPtr ConstTreePtrType; typedef typename NonConstTreeType::Ptr NonConstTreePtrType; typedef Grid GridType; typedef Grid NonConstGridType; typedef typename GridType::Ptr GridPtrType; typedef typename NonConstGridType::Ptr NonConstGridPtrType; typedef typename GridType::ConstPtr ConstGridPtrType; typedef typename TreeType::ValueType ValueType; typedef typename tree::ValueAccessor AccessorType; typedef typename tree::ValueAccessor ConstAccessorType; typedef typename tree::ValueAccessor NonConstAccessorType; static TreeType& tree(TreeType& t) { return t; } static TreeType& tree(GridType& g) { return g.tree(); } static TreeType& tree(AccessorType& a) { return a.tree(); } static const TreeType& tree(const TreeType& t) { return t; } static const TreeType& tree(const GridType& g) { return g.tree(); } static const TreeType& tree(const AccessorType& a) { return a.tree(); } static const TreeType& constTree(TreeType& t) { return t; } static const TreeType& constTree(GridType& g) { return g.constTree(); } static const TreeType& constTree(const TreeType& t) { return t; } static const TreeType& constTree(const GridType& g) { return g.constTree(); } }; //@} //////////////////////////////////////// template inline typename GridType::Ptr GridBase::grid(const GridBase::Ptr& grid) { // The string comparison on type names is slower than a dynamic_pointer_cast, but // it is safer when pointers cross dso boundaries, as they do in many Houdini nodes. if (grid && grid->type() == GridType::gridType()) { return boost::static_pointer_cast(grid); } return typename GridType::Ptr(); } template inline typename GridType::ConstPtr GridBase::grid(const GridBase::ConstPtr& grid) { return boost::const_pointer_cast( GridBase::grid(boost::const_pointer_cast(grid))); } template inline typename GridType::ConstPtr GridBase::constGrid(const GridBase::Ptr& grid) { return boost::const_pointer_cast(GridBase::grid(grid)); } template inline typename GridType::ConstPtr GridBase::constGrid(const GridBase::ConstPtr& grid) { return boost::const_pointer_cast( GridBase::grid(boost::const_pointer_cast(grid))); } inline TreeBase::Ptr GridBase::baseTreePtr() { return boost::const_pointer_cast(this->constBaseTreePtr()); } inline void GridBase::setTransform(math::Transform::Ptr xform) { if (!xform) OPENVDB_THROW(ValueError, "Transform pointer is null"); mTransform = xform; } //////////////////////////////////////// template inline Grid::Grid(): mTree(new TreeType) { } template inline Grid::Grid(const ValueType &background): mTree(new TreeType(background)) { } template inline Grid::Grid(TreePtrType tree): mTree(tree) { if (!tree) OPENVDB_THROW(ValueError, "Tree pointer is null"); } template inline Grid::Grid(const Grid& other): GridBase(other), mTree(boost::static_pointer_cast(other.mTree->copy())) { } template inline Grid::Grid(const Grid& other, ShallowCopy): GridBase(other, ShallowCopy()), mTree(other.mTree) { } template inline Grid::Grid(const GridBase& other): GridBase(other), mTree(new TreeType) { } //static template inline typename Grid::Ptr Grid::create() { return Grid::create(zeroVal()); } //static template inline typename Grid::Ptr Grid::create(const ValueType& background) { return Ptr(new Grid(background)); } //static template inline typename Grid::Ptr Grid::create(TreePtrType tree) { return Ptr(new Grid(tree)); } //static template inline typename Grid::Ptr Grid::create(const GridBase& other) { return Ptr(new Grid(other)); } //////////////////////////////////////// template inline typename Grid::Ptr Grid::copy(CopyPolicy treePolicy) const { Ptr ret; switch (treePolicy) { case CP_NEW: ret.reset(new Grid(*this, ShallowCopy())); ret->newTree(); break; case CP_COPY: ret.reset(new Grid(*this)); break; case CP_SHARE: ret.reset(new Grid(*this, ShallowCopy())); break; } return ret; } template inline GridBase::Ptr Grid::copyGrid(CopyPolicy treePolicy) const { return this->copy(treePolicy); } //////////////////////////////////////// template inline void Grid::setTree(TreeBase::Ptr tree) { if (!tree) OPENVDB_THROW(ValueError, "Tree pointer is null"); if (tree->type() != TreeType::treeType()) { OPENVDB_THROW(TypeError, "Cannot assign a tree of type " + tree->type() + " to a grid of type " + this->type()); } mTree = boost::static_pointer_cast(tree); } template inline void Grid::newTree() { mTree.reset(new TreeType(this->background())); } template inline void Grid::fill(const CoordBBox& bbox, const ValueType& value, bool active) { tree().fill(bbox, value, active); } template inline void Grid::signedFloodFill(const ValueType& outside, const ValueType& inside) { tree().signedFloodFill(outside, inside); } template inline void Grid::pruneGrid(float tolerance) { this->prune(ValueType(zeroVal() + tolerance)); } template inline void Grid::merge(Grid& other, MergePolicy policy) { tree().merge(other.tree(), policy); } template inline void Grid::evalMinMax(ValueType& minVal, ValueType& maxVal) const { tree().evalMinMax(minVal, maxVal); } template inline CoordBBox Grid::evalActiveVoxelBoundingBox() const { CoordBBox bbox; tree().evalActiveVoxelBoundingBox(bbox); return bbox; } template inline Coord Grid::evalActiveVoxelDim() const { Coord dim; const bool nonempty = tree().evalActiveVoxelDim(dim); return (nonempty ? dim : Coord()); } //////////////////////////////////////// /// @internal Consider using the stream tagging mechanism (see io::Archive) /// to specify the float precision, but note that the setting is per-grid. template inline void Grid::readTopology(std::istream& is) { tree().readTopology(is, saveFloatAsHalf()); } template inline void Grid::writeTopology(std::ostream& os) const { tree().writeTopology(os, saveFloatAsHalf()); } template inline void Grid::readBuffers(std::istream& is) { tree().readBuffers(is, saveFloatAsHalf()); } template inline void Grid::writeBuffers(std::ostream& os) const { tree().writeBuffers(os, saveFloatAsHalf()); } template inline void Grid::print(std::ostream& os, int verboseLevel) const { tree().print(os, verboseLevel); if (metaCount() > 0) { os << "Additional metadata:" << std::endl; for (ConstMetaIterator it = beginMeta(), end = endMeta(); it != end; ++it) { os << " " << it->first; if (it->second) { const std::string value = it->second->str(); if (!value.empty()) os << ": " << value; } os << "\n"; } } os << "Transform:" << std::endl; transform().print(os, /*indent=*/" "); os << std::endl; } //////////////////////////////////////// template inline typename GridType::Ptr createGrid(const typename GridType::ValueType& background) { return GridType::create(background); } template inline typename GridType::Ptr createGrid() { return GridType::create(); } template inline typename Grid::Ptr createGrid(TreePtrType tree) { typedef typename TreePtrType::element_type TreeType; return Grid::create(tree); } template typename GridType::Ptr createLevelSet(Real voxelSize, Real halfWidth) { typedef typename GridType::ValueType ValueType; // GridType::ValueType is required to be a floating-point scalar. BOOST_STATIC_ASSERT(boost::is_floating_point::value); typename GridType::Ptr grid = GridType::create( /*background=*/static_cast(voxelSize * halfWidth)); grid->setTransform(math::Transform::createLinearTransform(voxelSize)); grid->setGridClass(GRID_LEVEL_SET); return grid; } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_GRID_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/doxygen-config0000644000000000000000000014402012252452020014047 0ustar rootroot# Doxyfile 1.4.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = OpenVDB # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 2.1.0 ALIASES += vdbnamespace="openvdb::v2_1_0" PREDEFINED = OPENVDB_VERSION_NAME=v2_1_0 PREDEFINED += __declspec(x):= __attribute__(x):= #EXPAND_AS_DEFINED = \ # OPENVDB_API \ # OPENVDB_HOUDINI_API \ # OPENVDB_EXPORT \ # OPENVDB_IMPORT \ # OPENVDB_DEPRECATED # OPENVDB_STATIC_SPECIALIZATION # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = obj/doc # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, # Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, # Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, # Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, # Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = YES # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like the Qt-style comments (thus requiring an # explicit @brief command for a brief description. JAVADOC_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES += ijk="@f$(i,j,k)@f$" ALIASES += xyz="@f$(x,y,z)@f$" ALIASES += const="const" # Use this command to create a link to an OpenVDB class, function, etc. # Usage is "@vdblink:: @endlink", where is a fully # namespace-qualified symbol minus the openvdb and version number namespaces # and is the text of the link. # Example: @vdblink::tree::RootNode root node@endlink ALIASES += vdblink="@link @vdbnamespace" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for Java. # For instance, namespaces will be presented as packages, qualified scopes # will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to # include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = YES # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = YES # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = YES # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = NO # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from the # version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = . \ io \ math \ metadata \ python/pyopenvdb.h \ tools \ tree \ util \ doc/doc.txt \ doc/faq.txt \ doc/math.txt \ doc/changes.txt \ doc/codingstyle.txt \ doc/api_0_98_0.txt \ doc/examplecode.txt \ doc/python.txt # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py FILE_PATTERNS = *.h # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES (the default) # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES (the default) # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compressed HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # If the GENERATE_TREEVIEW tag is set to YES, a side panel will be # generated containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. #PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = NO # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a call dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable call graphs for selected # functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will # generate a caller dependency graph for every global function or class method. # Note that enabling this option will significantly increase the time of a run. # So in most cases it will be better to enable caller graphs for selected # functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that a graph may be further truncated if the graph's # image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH # and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), # the graph is not depth-constrained. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, which results in a white background. # Warning: Depending on the platform used, enabling this option may lead to # badly anti-aliased labels on the edges of a graph (i.e. they become hard to # read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO openvdb/tree/0000755000000000000000000000000012252453157012156 5ustar rootrootopenvdb/tree/Util.h0000644000000000000000000001067212252453157013252 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file tree/Util.h #ifndef OPENVDB_TREE_UTIL_HAS_BEEN_INCLUDED #define OPENVDB_TREE_UTIL_HAS_BEEN_INCLUDED #include // for isNegative and negative #include // for Index typedef namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// @brief Helper class for use with Tree::pruneOp() to replace constant branches /// (to within the provided tolerance) with more memory-efficient tiles template struct TolerancePrune { TolerancePrune(const ValueType& tol): tolerance(tol) {} template bool operator()(ChildType& child) { return (ChildType::LEVEL < TerminationLevel) ? false : this->isConstant(child); } template bool isConstant(ChildType& child) { child.pruneOp(*this); return child.isConstant(value, state, tolerance); } bool state; ValueType value; const ValueType tolerance; }; /// @brief Helper class for use with Tree::pruneOp() to replace inactive branches /// with more memory-efficient inactive tiles with the provided value /// @details This is more specialized but faster than a TolerancePrune. template struct InactivePrune { InactivePrune(const ValueType& val): value(val) {} template bool operator()(ChildType& child) const { child.pruneOp(*this); return child.isInactive(); } static const bool state = false; const ValueType value; }; /// @brief Helper class for use with Tree::pruneOp() to prune any branches /// whose values are all inactive and replace each with an inactive tile /// whose value is equal in magnitude to the background value and whose sign /// is equal to that of the first value encountered in the (inactive) child /// /// @details This operation is faster than a TolerancePrune and useful for /// narrow-band level set applications where inactive values are limited /// to either the inside or the outside value. template struct LevelSetPrune { LevelSetPrune(const ValueType& background): outside(background) {} template bool operator()(ChildType& child) { child.pruneOp(*this); if (!child.isInactive()) return false; value = math::isNegative(child.getFirstValue()) ? math::negative(outside) : outside; return true; } static const bool state = false; const ValueType outside; ValueType value; }; } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/LeafNodeBool.h0000644000000000000000000016657412252453157014643 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_TREE_LEAFNODEBOOL_HAS_BEEN_INCLUDED #define OPENVDB_TREE_LEAFNODEBOOL_HAS_BEEN_INCLUDED #include #include #include #include #include #include // for io::readData(), etc. #include #include "LeafNode.h" #include "Iterator.h" #include "Util.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// @brief LeafNode specialization for values of type bool that stores both /// the active states and the values of (2^Log2Dim)^3 voxels as bit masks template class LeafNode { public: typedef LeafNode LeafNodeType; typedef boost::shared_ptr Ptr; typedef bool ValueType; typedef util::NodeMask NodeMaskType; // These static declarations must be on separate lines to avoid VC9 compiler errors. static const Index LOG2DIM = Log2Dim; // needed by parent nodes static const Index TOTAL = Log2Dim; // needed by parent nodes static const Index DIM = 1 << TOTAL; // dimension along one coordinate direction static const Index NUM_VALUES = 1 << 3 * Log2Dim; static const Index NUM_VOXELS = NUM_VALUES; // total number of voxels represented by this node static const Index SIZE = NUM_VALUES; static const Index LEVEL = 0; // level 0 = leaf /// @brief ValueConverter::Type is the type of a LeafNode having the same /// dimensions as this node but a different value type, T. template struct ValueConverter { typedef LeafNode Type; }; class Buffer { public: Buffer() {} Buffer(bool on) : mData(on) {} Buffer(const NodeMaskType& other): mData(other) {} Buffer(const Buffer& other): mData(other.mData) {} ~Buffer() {} void fill(bool val) { mData.set(val); } Buffer& operator=(const Buffer& b) { if (&b != this) { mData = b.mData; } return *this; } const bool& getValue(Index i) const { assert(i < SIZE); return mData.isOn(i) ? LeafNode::sOn : LeafNode::sOff; } const bool& operator[](Index i) const { return this->getValue(i); } bool operator==(const Buffer& other) const { return mData == other.mData; } bool operator!=(const Buffer& other) const { return mData != other.mData; } void setValue(Index i, bool val) { assert(i < SIZE); mData.set(i, val); } void swap(Buffer& other) { if (&other != this) std::swap(mData, other.mData); } Index memUsage() const { return mData.memUsage(); } static Index size() { return SIZE; } private: friend class ::TestLeaf; // Allow the parent LeafNode to access this Buffer's bit mask. friend class LeafNode; NodeMaskType mData; }; // class Buffer /// Default constructor LeafNode(); /// Constructor /// @param xyz the coordinates of a voxel that lies within the node /// @param value the initial value for all of this node's voxels /// @param active the active state to which to initialize all voxels explicit LeafNode(const Coord& xyz, bool value = false, bool active = false); /// Deep copy constructor LeafNode(const LeafNode&); /// Topology copy constructor template LeafNode(const LeafNode& other, TopologyCopy); /// Topology copy constructor /// @note This variant exists mainly to enable template instantiation. template LeafNode(const LeafNode& other, bool offValue, bool onValue, TopologyCopy); template LeafNode(const LeafNode& other, bool background, TopologyCopy); /// Destructor ~LeafNode(); // // Statistics // /// Return log2 of the size of the buffer storage. static Index log2dim() { return Log2Dim; } /// Return the number of voxels in each dimension. static Index dim() { return DIM; } static Index size() { return SIZE; } static Index numValues() { return SIZE; } static Index getLevel() { return LEVEL; } static void getNodeLog2Dims(std::vector& dims) { dims.push_back(Log2Dim); } static Index getChildDim() { return 1; } static Index32 leafCount() { return 1; } static Index32 nonLeafCount() { return 0; } /// Return the number of active voxels. Index64 onVoxelCount() const { return mValueMask.countOn(); } /// Return the number of inactive voxels. Index64 offVoxelCount() const { return mValueMask.countOff(); } Index64 onLeafVoxelCount() const { return onVoxelCount(); } Index64 offLeafVoxelCount() const { return offVoxelCount(); } static Index64 onTileCount() { return 0; } static Index64 offTileCount() { return 0; } /// Return @c true if this node has no active voxels. bool isEmpty() const { return mValueMask.isOff(); } /// Return @c true if this node only contains active voxels. bool isDense() const { return mValueMask.isOn(); } /// Return the memory in bytes occupied by this node. Index64 memUsage() const; /// Expand the given bounding box so that it includes this leaf node's active voxels. /// If visitVoxels is false this LeafNode will be approximated as dense, i.e. with all /// voxels active. Else the individual active voxels are visited to produce a tight bbox. void evalActiveBoundingBox(CoordBBox&, bool visitVoxels = true) const; OPENVDB_DEPRECATED void evalActiveVoxelBoundingBox(CoordBBox& bbox) const; /// @brief Return the bounding box of this node, i.e., the full index space /// spanned by this leaf node. CoordBBox getNodeBoundingBox() const { return CoordBBox::createCube(mOrigin, DIM); } /// Set the grid index coordinates of this node's local origin. void setOrigin(const Coord& origin) { mOrigin = origin; } /// @brief Return the grid index coordinates of this node's local origin. /// @deprecated Use origin() instead. OPENVDB_DEPRECATED const Coord& getOrigin() const { return mOrigin; } //@{ /// Return the grid index coordinates of this node's local origin. const Coord& origin() const { return mOrigin; } void getOrigin(Coord& origin) const { origin = mOrigin; } void getOrigin(Int32& x, Int32& y, Int32& z) const { mOrigin.asXYZ(x, y, z); } //@} /// Return the linear table offset of the given global or local coordinates. static Index coordToOffset(const Coord& xyz); /// @brief Return the local coordinates for a linear table offset, /// where offset 0 has coordinates (0, 0, 0). static Coord offsetToLocalCoord(Index n); /// Return the global coordinates for a linear table offset. Coord offsetToGlobalCoord(Index n) const; /// Return a string representation of this node. std::string str() const; /// @brief Return @c true if the given node (which may have a different @c ValueType /// than this node) has the same active value topology as this node. template bool hasSameTopology(const LeafNode* other) const; /// Check for buffer equivalence by value. bool operator==(const LeafNode&) const; bool operator!=(const LeafNode&) const; // // Buffer management // /// @brief Exchange this node's data buffer with the given data buffer /// without changing the active states of the values. void swap(Buffer& other) { mBuffer.swap(other); } const Buffer& buffer() const { return mBuffer; } Buffer& buffer() { return mBuffer; } // // I/O methods // /// Read in just the topology. void readTopology(std::istream&, bool fromHalf = false); /// Write out just the topology. void writeTopology(std::ostream&, bool toHalf = false) const; /// Read in the topology and the origin. void readBuffers(std::istream&, bool fromHalf = false); /// Write out the topology and the origin. void writeBuffers(std::ostream&, bool toHalf = false) const; // // Accessor methods // /// Return the value of the voxel at the given coordinates. const bool& getValue(const Coord& xyz) const; /// Return the value of the voxel at the given offset. const bool& getValue(Index offset) const; /// @brief Return @c true if the voxel at the given coordinates is active. /// @param xyz the coordinates of the voxel to be probed /// @param[out] val the value of the voxel at the given coordinates bool probeValue(const Coord& xyz, bool& val) const; /// Return the level (0) at which leaf node values reside. static Index getValueLevel(const Coord&) { return LEVEL; } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on); /// Set the active state of the voxel at the given offset but don't change its value. void setActiveState(Index offset, bool on) { assert(offsetcoordToOffset(xyz)); } /// Mark the voxel at the given offset as inactive but don't change its value. void setValueOff(Index offset) { assert(offset < SIZE); mValueMask.setOff(offset); } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, bool val); /// Set the value of the voxel at the given offset and mark the voxel as inactive. void setValueOff(Index offset, bool val); /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz) { mValueMask.setOn(this->coordToOffset(xyz)); } /// Mark the voxel at the given offset as active but don't change its value. void setValueOn(Index offset) { assert(offset < SIZE); mValueMask.setOn(offset); } /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValueOn(const Coord& xyz, bool val); /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, bool val) { this->setValueOn(xyz, val); }; /// Set the value of the voxel at the given offset and mark the voxel as active. void setValueOn(Index offset, bool val); /// @brief Apply a functor to the value of the voxel at the given offset /// and mark the voxel as active. template void modifyValue(Index offset, const ModifyOp& op); /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. template void modifyValue(const Coord& xyz, const ModifyOp& op); /// Apply a functor to the voxel at the given coordinates. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); /// Mark all voxels as active but don't change their values. void setValuesOn() { mValueMask.setOn(); } /// Mark all voxels as inactive but don't change their values. void setValuesOff() { mValueMask.setOff(); } /// Return @c true if the voxel at the given coordinates is active. bool isValueOn(const Coord& xyz) const { return mValueMask.isOn(this->coordToOffset(xyz)); } /// Return @c true if the voxel at the given offset is active. bool isValueOn(Index offset) const { assert(offset < SIZE); return mValueMask.isOn(offset); } /// Return @c false since leaf nodes never contain tiles. static bool hasActiveTiles() { return false; } /// Set all voxels within an axis-aligned box to the specified value and active state. void fill(const CoordBBox& bbox, bool value, bool active = true); /// Set all voxels to the specified value but don't change their active states. void fill(const bool& value); /// Set all voxels to the specified value and active state. void fill(const bool& value, bool active); /// @brief Copy into a dense grid the values of the voxels that lie within /// a given bounding box. /// /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid /// @param dense dense grid with a stride in @e z of one (see tools::Dense /// in tools/Dense.h for the required API) /// /// @note @a bbox is assumed to be identical to or contained in the coordinate domains /// of both the dense grid and this node, i.e., no bounds checking is performed. /// @note Consider using tools::CopyToDense in tools/Dense.h /// instead of calling this method directly. template void copyToDense(const CoordBBox& bbox, DenseT& dense) const; /// @brief Copy from a dense grid into this node the values of the voxels /// that lie within a given bounding box. /// @details Only values that are different (by more than the given tolerance) /// from the background value will be active. Other values are inactive /// and truncated to the background value. /// /// @param bbox inclusive bounding box of the voxels to be copied into this node /// @param dense dense grid with a stride in @e z of one (see tools::Dense /// in tools/Dense.h for the required API) /// @param background background value of the tree that this node belongs to /// @param tolerance tolerance within which a value equals the background value /// /// @note @a bbox is assumed to be identical to or contained in the coordinate domains /// of both the dense grid and this node, i.e., no bounds checking is performed. /// @note Consider using tools::CopyFromDense in tools/Dense.h /// instead of calling this method directly. template void copyFromDense(const CoordBBox& bbox, const DenseT& dense, bool background, bool tolerance); /// @brief Return the value of the voxel at the given coordinates. /// @note Used internally by ValueAccessor. template const bool& getValueAndCache(const Coord& xyz, AccessorT&) const {return this->getValue(xyz);} /// @brief Return @c true if the voxel at the given coordinates is active. /// @note Used internally by ValueAccessor. template bool isValueOnAndCache(const Coord& xyz, AccessorT&) const { return this->isValueOn(xyz); } /// @brief Change the value of the voxel at the given coordinates and mark it as active. /// @note Used internally by ValueAccessor. template void setValueAndCache(const Coord& xyz, bool val, AccessorT&) { this->setValueOn(xyz, val); } /// @brief Change the value of the voxel at the given coordinates /// but preserve its state. /// @note Used internally by ValueAccessor. template void setValueOnlyAndCache(const Coord& xyz, bool val, AccessorT&) {this->setValueOnly(xyz,val);} /// @brief Change the value of the voxel at the given coordinates and mark it as inactive. /// @note Used internally by ValueAccessor. template void setValueOffAndCache(const Coord& xyz, bool value, AccessorT&) { this->setValueOff(xyz, value); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @note Used internally by ValueAccessor. template void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) { this->modifyValue(xyz, op); } /// Apply a functor to the voxel at the given coordinates. /// @note Used internally by ValueAccessor. template void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) { this->modifyValueAndActiveState(xyz, op); } /// @brief Set the active state of the voxel at the given coordinates /// without changing its value. /// @note Used internally by ValueAccessor. template void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&) { this->setActiveState(xyz, on); } /// @brief Return @c true if the voxel at the given coordinates is active /// and return the voxel value in @a val. /// @note Used internally by ValueAccessor. template bool probeValueAndCache(const Coord& xyz, bool& val, AccessorT&) const { return this->probeValue(xyz, val); } /// @brief Return the LEVEL (=0) at which leaf node values reside. /// @note Used internally by ValueAccessor. template static Index getValueLevelAndCache(const Coord&, AccessorT&) { return LEVEL; } /// @brief Return a const reference to the first entry in the buffer. /// @note Since it's actually a reference to a static data member /// it should not be converted to a non-const pointer! const bool& getFirstValue() const { if (mValueMask.isOn(0)) return sOn; else return sOff; } /// @brief Return a const reference to the last entry in the buffer. /// @note Since it's actually a reference to a static data member /// it should not be converted to a non-const pointer! const bool& getLastValue() const { if (mValueMask.isOn(SIZE-1)) return sOn; else return sOff; } /// Return @c true if all of this node's voxels have the same active state /// and are equal to within the given tolerance, and return the value in /// @a constValue and the active state in @a state. bool isConstant(bool& constValue, bool& state, bool tolerance = 0) const; /// Return @c true if all of this node's values are inactive. bool isInactive() const { return mValueMask.isOff(); } void resetBackground(bool oldBackground, bool newBackground); void negate() { mBuffer.mData.toggle(); } template void merge(const LeafNode& other, bool bg = false, bool otherBG = false); template void merge(bool tileValue, bool tileActive); void voxelizeActiveTiles() {}; /// @brief Union this node's set of active values with the active values /// of the other node, whose @c ValueType may be different. So a /// resulting voxel will be active if either of the original voxels /// were active. /// /// @note This operation modifies only active states, not values. template void topologyUnion(const LeafNode& other); /// @brief Intersect this node's set of active values with the active values /// of the other node, whose @c ValueType may be different. So a /// resulting voxel will be active only if both of the original voxels /// were active. /// /// @details The last dummy argument is required to match the signature /// for InternalNode::topologyIntersection. /// /// @note This operation modifies only active states, not /// values. Also note that this operation can result in all voxels /// being inactive so consider subsequnetly calling prune. template void topologyIntersection(const LeafNode& other, const bool&); /// @brief Difference this node's set of active values with the active values /// of the other node, whose @c ValueType may be different. So a /// resulting voxel will be active only if the original voxel is /// active in this LeafNode and inactive in the other LeafNode. /// /// @details The last dummy argument is required to match the signature /// for InternalNode::topologyDifference. /// /// @note This operation modifies only active states, not /// values. Also note that this operation can result in all voxels /// being inactive so consider subsequnetly calling prune. template void topologyDifference(const LeafNode& other, const bool&); template void combine(const LeafNode& other, CombineOp& op); template void combine(bool, bool valueIsActive, CombineOp& op); template void combine2(const LeafNode& other, bool, bool valueIsActive, CombineOp&); template void combine2(bool, const LeafNode& other, bool valueIsActive, CombineOp&); template void combine2(const LeafNode& b0, const LeafNode& b1, CombineOp&); /// @brief Calls the templated functor BBoxOp with bounding box information. /// An additional level argument is provided to the callback. /// /// @note The bounding boxes are guarenteed to be non-overlapping. template void visitActiveBBox(BBoxOp&) const; template void visit(VisitorOp&); template void visit(VisitorOp&) const; template void visit2Node(OtherLeafNodeType& other, VisitorOp&); template void visit2Node(OtherLeafNodeType& other, VisitorOp&) const; template void visit2(IterT& otherIter, VisitorOp&, bool otherIsLHS = false); template void visit2(IterT& otherIter, VisitorOp&, bool otherIsLHS = false) const; //@{ /// This function exists only to enable template instantiation. void signedFloodFill(bool) {} /// This function exists only to enable template instantiation. void signedFloodFill(bool, bool) {} template void pruneOp(PruneOp&) {} void prune(const ValueType& /*tolerance*/ = zeroVal()) {} void pruneInactive(const ValueType&) {} void addLeaf(LeafNode*) {} template void addLeafAndCache(LeafNode*, AccessorT&) {} template NodeT* stealNode(const Coord&, const ValueType&, bool) { return NULL; } template NodeT* probeNode(const Coord&) { return NULL; } template const NodeT* probeConstNode(const Coord&) const { return NULL; } //@} void addTile(Index level, const Coord&, bool val, bool active); void addTile(Index offset, bool val, bool active); template void addTileAndCache(Index level, const Coord&, bool val, bool active, AccessorT&); //@{ /// @brief Return a pointer to this node. LeafNode* touchLeaf(const Coord&) { return this; } template LeafNode* touchLeafAndCache(const Coord&, AccessorT&) { return this; } LeafNode* probeLeaf(const Coord&) { return this; } template LeafNode* probeLeafAndCache(const Coord&, AccessorT&) { return this; } template NodeT* probeNodeAndCache(const Coord&, AccessorT&) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (!(boost::is_same::value)) return NULL; return reinterpret_cast(this); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //@} //@{ /// @brief Return a @const pointer to this node. const LeafNode* probeLeaf(const Coord&) const { return this; } template const LeafNode* probeLeafAndCache(const Coord&, AccessorT&) const { return this; } const LeafNode* probeConstLeaf(const Coord&) const { return this; } template const LeafNode* probeConstLeafAndCache(const Coord&, AccessorT&) const { return this; } template const NodeT* probeConstNodeAndCache(const Coord&, AccessorT&) const { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (!(boost::is_same::value)) return NULL; return reinterpret_cast(this); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //@} // // Iterators // protected: typedef typename NodeMaskType::OnIterator MaskOnIter; typedef typename NodeMaskType::OffIterator MaskOffIter; typedef typename NodeMaskType::DenseIterator MaskDenseIter; template struct ValueIter: // Derives from SparseIteratorBase, but can also be used as a dense iterator, // if MaskIterT is a dense mask iterator type. public SparseIteratorBase, NodeT, ValueT> { typedef SparseIteratorBase BaseT; ValueIter() {} ValueIter(const MaskIterT& iter, NodeT* parent): BaseT(iter, parent) {} const bool& getItem(Index pos) const { return this->parent().getValue(pos); } const bool& getValue() const { return this->getItem(this->pos()); } // Note: setItem() can't be called on const iterators. void setItem(Index pos, bool value) const { this->parent().setValueOnly(pos, value); } // Note: setValue() can't be called on const iterators. void setValue(bool value) const { this->setItem(this->pos(), value); } // Note: modifyItem() can't be called on const iterators. template void modifyItem(Index n, const ModifyOp& op) const { this->parent().modifyValue(n, op); } // Note: modifyValue() can't be called on const iterators. template void modifyValue(const ModifyOp& op) const { this->modifyItem(this->pos(), op); } }; /// Leaf nodes have no children, so their child iterators have no get/set accessors. template struct ChildIter: public SparseIteratorBase, NodeT, bool> { ChildIter() {} ChildIter(const MaskIterT& iter, NodeT* parent): SparseIteratorBase< MaskIterT, ChildIter, NodeT, bool>(iter, parent) {} }; template struct DenseIter: public DenseIteratorBase< MaskDenseIter, DenseIter, NodeT, /*ChildT=*/void, ValueT> { typedef DenseIteratorBase BaseT; typedef typename BaseT::NonConstValueType NonConstValueT; DenseIter() {} DenseIter(const MaskDenseIter& iter, NodeT* parent): BaseT(iter, parent) {} bool getItem(Index pos, void*& child, NonConstValueT& value) const { value = this->parent().getValue(pos); child = NULL; return false; // no child } // Note: setItem() can't be called on const iterators. //void setItem(Index pos, void* child) const {} // Note: unsetItem() can't be called on const iterators. void unsetItem(Index pos, const ValueT& val) const {this->parent().setValueOnly(pos, val);} }; public: typedef ValueIter ValueOnIter; typedef ValueIter ValueOnCIter; typedef ValueIter ValueOffIter; typedef ValueIter ValueOffCIter; typedef ValueIter ValueAllIter; typedef ValueIter ValueAllCIter; typedef ChildIter ChildOnIter; typedef ChildIter ChildOnCIter; typedef ChildIter ChildOffIter; typedef ChildIter ChildOffCIter; typedef DenseIter ChildAllIter; typedef DenseIter ChildAllCIter; ValueOnCIter cbeginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } ValueOnCIter beginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } ValueOnIter beginValueOn() { return ValueOnIter(mValueMask.beginOn(), this); } ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } ValueOffCIter beginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } ValueOffIter beginValueOff() { return ValueOffIter(mValueMask.beginOff(), this); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mValueMask.beginDense(), this); } ValueAllCIter beginValueAll() const { return ValueAllCIter(mValueMask.beginDense(), this); } ValueAllIter beginValueAll() { return ValueAllIter(mValueMask.beginDense(), this); } ValueOnCIter cendValueOn() const { return ValueOnCIter(mValueMask.endOn(), this); } ValueOnCIter endValueOn() const { return ValueOnCIter(mValueMask.endOn(), this); } ValueOnIter endValueOn() { return ValueOnIter(mValueMask.endOn(), this); } ValueOffCIter cendValueOff() const { return ValueOffCIter(mValueMask.endOff(), this); } ValueOffCIter endValueOff() const { return ValueOffCIter(mValueMask.endOff(), this); } ValueOffIter endValueOff() { return ValueOffIter(mValueMask.endOff(), this); } ValueAllCIter cendValueAll() const { return ValueAllCIter(mValueMask.endDense(), this); } ValueAllCIter endValueAll() const { return ValueAllCIter(mValueMask.endDense(), this); } ValueAllIter endValueAll() { return ValueAllIter(mValueMask.endDense(), this); } // Note that [c]beginChildOn() and [c]beginChildOff() actually return end iterators, // because leaf nodes have no children. ChildOnCIter cbeginChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } ChildOnCIter beginChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } ChildOnIter beginChildOn() { return ChildOnIter(mValueMask.endOn(), this); } ChildOffCIter cbeginChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } ChildOffCIter beginChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } ChildOffIter beginChildOff() { return ChildOffIter(mValueMask.endOff(), this); } ChildAllCIter cbeginChildAll() const { return ChildAllCIter(mValueMask.beginDense(), this); } ChildAllCIter beginChildAll() const { return ChildAllCIter(mValueMask.beginDense(), this); } ChildAllIter beginChildAll() { return ChildAllIter(mValueMask.beginDense(), this); } ChildOnCIter cendChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } ChildOnCIter endChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } ChildOnIter endChildOn() { return ChildOnIter(mValueMask.endOn(), this); } ChildOffCIter cendChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } ChildOffCIter endChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } ChildOffIter endChildOff() { return ChildOffIter(mValueMask.endOff(), this); } ChildAllCIter cendChildAll() const { return ChildAllCIter(mValueMask.endDense(), this); } ChildAllCIter endChildAll() const { return ChildAllCIter(mValueMask.endDense(), this); } ChildAllIter endChildAll() { return ChildAllIter(mValueMask.endDense(), this); } // // Mask accessors // bool isValueMaskOn(Index n) const { return mValueMask.isOn(n); } bool isValueMaskOn() const { return mValueMask.isOn(); } bool isValueMaskOff(Index n) const { return mValueMask.isOff(n); } bool isValueMaskOff() const { return mValueMask.isOff(); } const NodeMaskType& getValueMask() const { return mValueMask; } NodeMaskType& getValueMask() { return mValueMask; } void setValueMask(const NodeMaskType& mask) { mValueMask = mask; } bool isChildMaskOn(Index) const { return false; } // leaf nodes have no children bool isChildMaskOff(Index) const { return true; } bool isChildMaskOff() const { return true; } protected: void setValueMask(Index n, bool on) { mValueMask.set(n, on); } void setValueMaskOn(Index n) { mValueMask.setOn(n); } void setValueMaskOff(Index n) { mValueMask.setOff(n); } /// Compute the origin of the leaf node that contains the voxel with the given coordinates. static void evalNodeOrigin(Coord& xyz) { xyz &= ~(DIM - 1); } template static inline void doVisit(NodeT&, VisitorOp&); template static inline void doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp&); template static inline void doVisit2(NodeT& self, OtherChildAllIterT&, VisitorOp&, bool otherIsLHS); /// Bitmask that determines which voxels are active NodeMaskType mValueMask; /// Bitmask representing the values of voxels Buffer mBuffer; /// Global grid index coordinates (x,y,z) of the local origin of this node Coord mOrigin; // These static declarations must be on separate lines to avoid VC9 compiler errors. static const bool sOn; static const bool sOff; private: /// @brief During topology-only construction, access is needed /// to protected/private members of other template instances. template friend class LeafNode; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; //@{ /// Allow iterators to call mask accessor methods (see below). /// @todo Make mask accessors public? friend class IteratorBase; friend class IteratorBase; friend class IteratorBase; //@} }; // class LeafNode /// @internal For consistency with other nodes and with iterators, methods like /// LeafNode::getValue() return a reference to a value. Since it's not possible /// to return a reference to a bit in a node mask, we return a reference to one /// of the following static values instead. template const bool LeafNode::sOn = true; template const bool LeafNode::sOff = false; //////////////////////////////////////// template inline LeafNode::LeafNode(): mOrigin(0, 0, 0) { } template inline LeafNode::LeafNode(const Coord& xyz, bool value, bool active): mValueMask(active), mBuffer(value), mOrigin(xyz & (~(DIM - 1))) { } template template inline LeafNode::LeafNode(const LeafNode& other, TopologyCopy): mValueMask(other.getValueMask()), mBuffer(other.getValueMask()), // value = active state mOrigin(other.origin()) { } template template inline LeafNode::LeafNode(const LeafNode& other, bool offValue, bool onValue, TopologyCopy): mValueMask(other.getValueMask()), mBuffer(other.getValueMask()), mOrigin(other.origin()) { if (offValue) { if (!onValue) mBuffer.mData.toggle(); else mBuffer.mData.setOn(); } } template template inline LeafNode::LeafNode(const LeafNode& other, bool background, TopologyCopy): mValueMask(other.getValueMask()), mBuffer(background), mOrigin(other.origin()) { } template inline LeafNode::LeafNode(const LeafNode &other): mValueMask(other.mValueMask), mBuffer(other.mBuffer), mOrigin(other.mOrigin) { } template inline LeafNode::~LeafNode() { } //////////////////////////////////////// template inline Index64 LeafNode::memUsage() const { return sizeof(mOrigin) + mValueMask.memUsage() + mBuffer.memUsage(); } template inline void LeafNode::evalActiveVoxelBoundingBox(CoordBBox& bbox) const { const CoordBBox this_bbox = this->getNodeBoundingBox(); if (bbox.isInside(this_bbox)) { // nothing to do } else if (this->isDense()) { bbox.expand(this_bbox); } else { for (ValueOnCIter iter=this->cbeginValueOn(); iter; ++iter) bbox.expand(iter.getCoord()); } } template inline void LeafNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const { CoordBBox this_bbox = this->getNodeBoundingBox(); if (bbox.isInside(this_bbox)) return;//this LeafNode is already enclosed in the bbox if (ValueOnCIter iter = this->cbeginValueOn()) {//any active values? if (visitVoxels) {//use voxel granularity? this_bbox.reset(); for(; iter; ++iter) this_bbox.expand(this->offsetToLocalCoord(iter.pos())); this_bbox.translate(this->origin()); } bbox.expand(this_bbox); } } template template inline bool LeafNode::hasSameTopology(const LeafNode* other) const { assert(other); return (Log2Dim == OtherLog2Dim && mValueMask == other->getValueMask()); } template inline std::string LeafNode::str() const { std::ostringstream ostr; ostr << "LeafNode @" << mOrigin << ": "; for (Index32 n = 0; n < SIZE; ++n) ostr << (mValueMask.isOn(n) ? '#' : '.'); return ostr.str(); } //////////////////////////////////////// template inline Index LeafNode::coordToOffset(const Coord& xyz) { assert ((xyz[0] & (DIM-1u)) < DIM && (xyz[1] & (DIM-1u)) < DIM && (xyz[2] & (DIM-1u)) < DIM); return ((xyz[0] & (DIM-1u)) << 2*Log2Dim) + ((xyz[1] & (DIM-1u)) << Log2Dim) + (xyz[2] & (DIM-1u)); } template inline Coord LeafNode::offsetToLocalCoord(Index n) { assert(n < (1 << 3*Log2Dim)); Coord xyz; xyz.setX(n >> 2*Log2Dim); n &= ((1 << 2*Log2Dim) - 1); xyz.setY(n >> Log2Dim); xyz.setZ(n & ((1 << Log2Dim) - 1)); return xyz; } template inline Coord LeafNode::offsetToGlobalCoord(Index n) const { return (this->offsetToLocalCoord(n) + this->origin()); } //////////////////////////////////////// template inline void LeafNode::readTopology(std::istream& is, bool /*fromHalf*/) { mValueMask.load(is); } template inline void LeafNode::writeTopology(std::ostream& os, bool /*toHalf*/) const { mValueMask.save(os); } template inline void LeafNode::readBuffers(std::istream& is, bool /*fromHalf*/) { // Read in the value mask. mValueMask.load(is); // Read in the origin. is.read(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); if (io::getFormatVersion(is) >= OPENVDB_FILE_VERSION_BOOL_LEAF_OPTIMIZATION) { // Read in the mask for the voxel values. mBuffer.mData.load(is); } else { // Older files stored one or more bool arrays. // Read in the number of buffers, which should now always be one. int8_t numBuffers = 0; is.read(reinterpret_cast(&numBuffers), sizeof(int8_t)); // Read in the buffer. // (Note: prior to the bool leaf optimization, buffers were always compressed.) boost::shared_array buf(new bool[SIZE]); io::readData(is, buf.get(), SIZE, /*isCompressed=*/true); // Transfer values to mBuffer. mBuffer.mData.setOff(); for (Index i = 0; i < SIZE; ++i) { if (buf[i]) mBuffer.mData.setOn(i); } if (numBuffers > 1) { // Read in and discard auxiliary buffers that were created with // earlier versions of the library. for (int i = 1; i < numBuffers; ++i) { io::readData(is, buf.get(), SIZE, /*isCompressed=*/true); } } } } template inline void LeafNode::writeBuffers(std::ostream& os, bool /*toHalf*/) const { // Write out the value mask. mValueMask.save(os); // Write out the origin. os.write(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); // Write out the voxel values. mBuffer.mData.save(os); } //////////////////////////////////////// template inline bool LeafNode::operator==(const LeafNode& other) const { return (mValueMask == other.mValueMask && mBuffer.mData == other.mBuffer.mData); } template inline bool LeafNode::operator!=(const LeafNode& other) const { return !(this->operator==(other)); } //////////////////////////////////////// template inline bool LeafNode::isConstant(bool& constValue, bool& state, bool tolerance) const { state = mValueMask.isOn(); if (!(state || mValueMask.isOff())) return false; // Note: if tolerance is true (i.e., 1), then all boolean values compare equal. if (!tolerance && !(mBuffer.mData.isOn() || mBuffer.mData.isOff())) return false; state = mValueMask.isOn(); constValue = mBuffer.mData.isOn(); return true; } //////////////////////////////////////// template inline void LeafNode::addTile(Index level, const Coord& xyz, bool val, bool active) { assert(level == 0); this->addTile(this->coordToOffset(xyz), val, active); } template inline void LeafNode::addTile(Index offset, bool val, bool active) { assert(offset < SIZE); setValueOnly(offset, val); setActiveState(offset, active); } template template inline void LeafNode::addTileAndCache(Index level, const Coord& xyz, bool val, bool active, AccessorT&) { this->addTile(level, xyz, val, active); } //////////////////////////////////////// template inline const bool& LeafNode::getValue(const Coord& xyz) const { // This *CANNOT* use operator ? because Visual C++ if (mBuffer.mData.isOn(this->coordToOffset(xyz))) return sOn; else return sOff; } template inline const bool& LeafNode::getValue(Index offset) const { assert(offset < SIZE); // This *CANNOT* use operator ? for Windows if (mBuffer.mData.isOn(offset)) return sOn; else return sOff; } template inline bool LeafNode::probeValue(const Coord& xyz, bool& val) const { const Index offset = this->coordToOffset(xyz); val = mBuffer.mData.isOn(offset); return mValueMask.isOn(offset); } template inline void LeafNode::setValueOn(const Coord& xyz, bool val) { this->setValueOn(this->coordToOffset(xyz), val); } template inline void LeafNode::setValueOn(Index offset, bool val) { assert(offset < SIZE); mValueMask.setOn(offset); mBuffer.mData.set(offset, val); } template inline void LeafNode::setValueOnly(const Coord& xyz, bool val) { this->setValueOnly(this->coordToOffset(xyz), val); } template inline void LeafNode::setActiveState(const Coord& xyz, bool on) { mValueMask.set(this->coordToOffset(xyz), on); } template inline void LeafNode::setValueOff(const Coord& xyz, bool val) { this->setValueOff(this->coordToOffset(xyz), val); } template inline void LeafNode::setValueOff(Index offset, bool val) { assert(offset < SIZE); mValueMask.setOff(offset); mBuffer.mData.set(offset, val); } template template inline void LeafNode::modifyValue(Index offset, const ModifyOp& op) { bool val = mBuffer.mData.isOn(offset); op(val); mBuffer.mData.set(offset, val); mValueMask.setOn(offset); } template template inline void LeafNode::modifyValue(const Coord& xyz, const ModifyOp& op) { this->modifyValue(this->coordToOffset(xyz), op); } template template inline void LeafNode::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { const Index offset = this->coordToOffset(xyz); bool val = mBuffer.mData.isOn(offset), state = mValueMask.isOn(offset); op(val, state); mBuffer.mData.set(offset, val); mValueMask.set(offset, state); } //////////////////////////////////////// template inline void LeafNode::resetBackground(bool oldBackground, bool newBackground) { if (newBackground != oldBackground) { // Flip mBuffer's background bits and zero its foreground bits. NodeMaskType bgMask = !(mBuffer.mData | mValueMask); // Overwrite mBuffer's background bits, leaving its foreground bits intact. mBuffer.mData = (mBuffer.mData & mValueMask) | bgMask; } } //////////////////////////////////////// template template inline void LeafNode::merge(const LeafNode& other, bool /*bg*/, bool /*otherBG*/) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy == MERGE_NODES) return; for (typename NodeMaskType::OnIterator iter = other.mValueMask.beginOn(); iter; ++iter) { const Index n = iter.pos(); if (mValueMask.isOff(n)) { mBuffer.mData.set(n, other.mBuffer.mData.isOn(n)); mValueMask.setOn(n); } } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline void LeafNode::merge(bool tileValue, bool tileActive) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy != MERGE_ACTIVE_STATES_AND_NODES) return; if (!tileActive) return; // Replace all inactive values with the active tile value. if (tileValue) mBuffer.mData |= !mValueMask; // -0=>1, +0=>0, -1=>1, +1=>1 (-,+ = off,on) else mBuffer.mData &= mValueMask; // -0=>0, +0=>0, -1=>0, +1=>1 mValueMask.setOn(); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template template inline void LeafNode::topologyUnion(const LeafNode& other) { mValueMask |= other.getValueMask(); } template template inline void LeafNode::topologyIntersection(const LeafNode& other, const bool&) { mValueMask &= other.getValueMask(); } template template inline void LeafNode::topologyDifference(const LeafNode& other, const bool&) { mValueMask &= !other.getValueMask(); } //////////////////////////////////////// template inline void LeafNode::fill(const CoordBBox& bbox, bool value, bool active) { for (Int32 x = bbox.min().x(); x <= bbox.max().x(); ++x) { const Index offsetX = (x & (DIM-1u))<<2*Log2Dim; for (Int32 y = bbox.min().y(); y <= bbox.max().y(); ++y) { const Index offsetXY = offsetX + ((y & (DIM-1u))<< Log2Dim); for (Int32 z = bbox.min().z(); z <= bbox.max().z(); ++z) { const Index offset = offsetXY + (z & (DIM-1u)); mValueMask.set(offset, active); mBuffer.mData.set(offset, value); } } } } template inline void LeafNode::fill(const bool& value) { mBuffer.fill(value); } template inline void LeafNode::fill(const bool& value, bool active) { mBuffer.fill(value); mValueMask.set(active); } //////////////////////////////////////// template template inline void LeafNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const { typedef typename DenseT::ValueType DenseValueType; const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); const Coord& min = dense.bbox().min(); DenseValueType* t0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // target array const Int32 n0 = bbox.min()[2] & (DIM-1u); for (Int32 x = bbox.min()[0], ex = bbox.max()[0] + 1; x < ex; ++x) { DenseValueType* t1 = t0 + xStride * (x - min[0]); const Int32 n1 = n0 + ((x & (DIM-1u)) << 2*LOG2DIM); for (Int32 y = bbox.min()[1], ey = bbox.max()[1] + 1; y < ey; ++y) { DenseValueType* t2 = t1 + yStride * (y - min[1]); Int32 n2 = n1 + ((y & (DIM-1u)) << LOG2DIM); for (Int32 z = bbox.min()[2], ez = bbox.max()[2] + 1; z < ez; ++z, t2 += zStride) { *t2 = DenseValueType(mBuffer.mData.isOn(n2++)); } } } } template template inline void LeafNode::copyFromDense(const CoordBBox& bbox, const DenseT& dense, bool background, bool tolerance) { typedef typename DenseT::ValueType DenseValueType; const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); const Coord& min = dense.bbox().min(); const DenseValueType* s0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // source const Int32 n0 = bbox.min()[2] & (DIM-1u); for (Int32 x = bbox.min()[0], ex = bbox.max()[0] + 1; x < ex; ++x) { const DenseValueType* s1 = s0 + xStride * (x - min[0]); const Int32 n1 = n0 + ((x & (DIM-1u)) << 2*LOG2DIM); for (Int32 y = bbox.min()[1], ey = bbox.max()[1] + 1; y < ey; ++y) { const DenseValueType* s2 = s1 + yStride * (y - min[1]); Int32 n2 = n1 + ((y & (DIM-1u)) << LOG2DIM); for (Int32 z = bbox.min()[2], ez = bbox.max()[2]+1; z < ez; ++z, ++n2, s2 += zStride) { // Note: if tolerance is true (i.e., 1), then all boolean values compare equal. if (tolerance || background == bool(*s2)) { mValueMask.setOff(n2); mBuffer.mData.set(n2, background); } else { mValueMask.setOn(n2); mBuffer.mData.set(n2, bool(*s2)); } } } } } //////////////////////////////////////// template template inline void LeafNode::combine(const LeafNode& other, CombineOp& op) { CombineArgs args; for (Index i = 0; i < SIZE; ++i) { bool result = false, aVal = mBuffer.mData.isOn(i), bVal = other.mBuffer.mData.isOn(i); op(args.setARef(aVal) .setAIsActive(mValueMask.isOn(i)) .setBRef(bVal) .setBIsActive(other.mValueMask.isOn(i)) .setResultRef(result)); mValueMask.set(i, args.resultIsActive()); mBuffer.mData.set(i, result); } } template template inline void LeafNode::combine(bool value, bool valueIsActive, CombineOp& op) { CombineArgs args; args.setBRef(value).setBIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { bool result = false, aVal = mBuffer.mData.isOn(i); op(args.setARef(aVal) .setAIsActive(mValueMask.isOn(i)) .setResultRef(result)); mValueMask.set(i, args.resultIsActive()); mBuffer.mData.set(i, result); } } //////////////////////////////////////// template template inline void LeafNode::combine2(const LeafNode& other, bool value, bool valueIsActive, CombineOp& op) { CombineArgs args; args.setBRef(value).setBIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { bool result = false, aVal = other.mBuffer.mData.isOn(i); op(args.setARef(aVal) .setAIsActive(other.mValueMask.isOn(i)) .setResultRef(result)); mValueMask.set(i, args.resultIsActive()); mBuffer.mData.set(i, result); } } template template inline void LeafNode::combine2(bool value, const LeafNode& other, bool valueIsActive, CombineOp& op) { CombineArgs args; args.setARef(value).setAIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { bool result = false, bVal = other.mBuffer.mData.isOn(i); op(args.setBRef(bVal) .setBIsActive(other.mValueMask.isOn(i)) .setResultRef(result)); mValueMask.set(i, args.resultIsActive()); mBuffer.mData.set(i, result); } } template template inline void LeafNode::combine2(const LeafNode& b0, const LeafNode& b1, CombineOp& op) { CombineArgs args; for (Index i = 0; i < SIZE; ++i) { // Default behavior: output voxel is active if either input voxel is active. mValueMask.set(i, b0.mValueMask.isOn(i) || b1.mValueMask.isOn(i)); bool result = false, b0Val = b0.mBuffer.mData.isOn(i), b1Val = b1.mBuffer.mData.isOn(i); op(args.setARef(b0Val) .setAIsActive(b0.mValueMask.isOn(i)) .setBRef(b1Val) .setBIsActive(b1.mValueMask.isOn(i)) .setResultRef(result)); mValueMask.set(i, args.resultIsActive()); mBuffer.mData.set(i, result); } } //////////////////////////////////////// template template inline void LeafNode::visitActiveBBox(BBoxOp& op) const { if (op.template descent()) { for (ValueOnCIter i=this->cbeginValueOn(); i; ++i) { #ifdef _MSC_VER op.operator()(CoordBBox(i.getCoord(),1)); #else op.template operator()(CoordBBox(i.getCoord(),1)); #endif } } else { #ifdef _MSC_VER op.operator()(this->getNodeBoundingBox()); #else op.template operator()(this->getNodeBoundingBox()); #endif } } template template inline void LeafNode::visit(VisitorOp& op) { doVisit(*this, op); } template template inline void LeafNode::visit(VisitorOp& op) const { doVisit(*this, op); } template template inline void LeafNode::doVisit(NodeT& self, VisitorOp& op) { for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { op(iter); } } //////////////////////////////////////// template template inline void LeafNode::visit2Node(OtherLeafNodeType& other, VisitorOp& op) { doVisit2Node(*this, other, op); } template template inline void LeafNode::visit2Node(OtherLeafNodeType& other, VisitorOp& op) const { doVisit2Node(*this, other, op); } template template< typename NodeT, typename OtherNodeT, typename VisitorOp, typename ChildAllIterT, typename OtherChildAllIterT> inline void LeafNode::doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp& op) { // Allow the two nodes to have different ValueTypes, but not different dimensions. BOOST_STATIC_ASSERT(OtherNodeT::SIZE == NodeT::SIZE); BOOST_STATIC_ASSERT(OtherNodeT::LEVEL == NodeT::LEVEL); ChildAllIterT iter = self.beginChildAll(); OtherChildAllIterT otherIter = other.beginChildAll(); for ( ; iter && otherIter; ++iter, ++otherIter) { op(iter, otherIter); } } //////////////////////////////////////// template template inline void LeafNode::visit2(IterT& otherIter, VisitorOp& op, bool otherIsLHS) { doVisit2(*this, otherIter, op, otherIsLHS); } template template inline void LeafNode::visit2(IterT& otherIter, VisitorOp& op, bool otherIsLHS) const { doVisit2(*this, otherIter, op, otherIsLHS); } template template< typename NodeT, typename VisitorOp, typename ChildAllIterT, typename OtherChildAllIterT> inline void LeafNode::doVisit2(NodeT& self, OtherChildAllIterT& otherIter, VisitorOp& op, bool otherIsLHS) { if (!otherIter) return; if (otherIsLHS) { for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { op(otherIter, iter); } } else { for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { op(iter, otherIter); } } } } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_LEAFNODEBOOL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/Tree.h0000644000000000000000000023651312252453157013240 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file tree/Tree.h #ifndef OPENVDB_TREE_TREE_HAS_BEEN_INCLUDED #define OPENVDB_TREE_TREE_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "RootNode.h" #include "InternalNode.h" #include "LeafNode.h" #include "TreeIterator.h" #include "ValueAccessor.h" #include "Util.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// @brief Base class for typed trees class OPENVDB_API TreeBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; TreeBase() {} virtual ~TreeBase() {} /// Return the name of this tree's type. virtual const Name& type() const = 0; /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d"). virtual Name valueType() const = 0; /// Return a pointer to a deep copy of this tree virtual TreeBase::Ptr copy() const = 0; // // Tree methods // /// @brief Return this tree's background value wrapped as metadata. /// @note Query the metadata object for the value's type. virtual Metadata::Ptr getBackgroundValue() const { return Metadata::Ptr(); } /// @brief Return in @a bbox the axis-aligned bounding box of all /// leaf nodes and active tiles. /// @details This is faster then calling evalActiveVoxelBoundingBox, /// which visits the individual active voxels, and hence /// evalLeafBoundingBox produces a less tight, i.e. approximate, bbox. /// @return @c false if the bounding box is empty (in which case /// the bbox is set to its default value). virtual bool evalLeafBoundingBox(CoordBBox& bbox) const = 0; /// @brief Return in @a dim the dimensions of the axis-aligned bounding box /// of all leaf nodes. /// @return @c false if the bounding box is empty. virtual bool evalLeafDim(Coord& dim) const = 0; /// @brief Return in @a bbox the axis-aligned bounding box of all /// active voxels and tiles. /// @details This method produces a more accurate, i.e. tighter, /// bounding box than evalLeafBoundingBox which is approximate but /// faster. /// @return @c false if the bounding box is empty (in which case /// the bbox is set to its default value). virtual bool evalActiveVoxelBoundingBox(CoordBBox& bbox) const = 0; /// @brief Return in @a dim the dimensions of the axis-aligned bounding box of all /// active voxels. This is a tighter bounding box than the leaf node bounding box. /// @return @c false if the bounding box is empty. virtual bool evalActiveVoxelDim(Coord& dim) const = 0; virtual void getIndexRange(CoordBBox& bbox) const = 0; // // Statistics // /// @brief Return the depth of this tree. /// /// A tree with only a root node and leaf nodes has depth 2, for example. virtual Index treeDepth() const = 0; /// Return the number of leaf nodes. virtual Index32 leafCount() const = 0; /// Return the number of non-leaf nodes. virtual Index32 nonLeafCount() const = 0; /// Return the number of active voxels stored in leaf nodes. virtual Index64 activeLeafVoxelCount() const = 0; /// Return the number of inactive voxels stored in leaf nodes. virtual Index64 inactiveLeafVoxelCount() const = 0; /// Return the total number of active voxels. virtual Index64 activeVoxelCount() const = 0; /// Return the number of inactive voxels within the bounding box of all active voxels. virtual Index64 inactiveVoxelCount() const = 0; /// Return the total amount of memory in bytes occupied by this tree. virtual Index64 memUsage() const { return 0; } // // I/O methods // /// @brief Read the tree topology from a stream. /// /// This will read the tree structure and tile values, but not voxel data. virtual void readTopology(std::istream&, bool saveFloatAsHalf = false); /// @brief Write the tree topology to a stream. /// /// This will write the tree structure and tile values, but not voxel data. virtual void writeTopology(std::ostream&, bool saveFloatAsHalf = false) const; /// Read all data buffers for this tree. virtual void readBuffers(std::istream&, bool saveFloatAsHalf = false) = 0; /// Write out all the data buffers for this tree. virtual void writeBuffers(std::ostream&, bool saveFloatAsHalf = false) const = 0; /// @brief Print statistics, memory usage and other information about this tree. /// @param os a stream to which to write textual information /// @param verboseLevel 1: print tree configuration only; 2: include node and /// voxel statistics; 3: include memory usage virtual void print(std::ostream& os = std::cout, int verboseLevel = 1) const; private: // Disallow copying of instances of this class. //TreeBase(const TreeBase& other); TreeBase& operator=(const TreeBase& other); }; //////////////////////////////////////// template class Tree: public TreeBase { public: typedef boost::shared_ptr Ptr; typedef boost::shared_ptr ConstPtr; typedef _RootNodeType RootNodeType; typedef typename RootNodeType::ValueType ValueType; typedef typename RootNodeType::LeafNodeType LeafNodeType; static const Index DEPTH = RootNodeType::LEVEL + 1; /// @brief ValueConverter::Type is the type of a tree having the same /// hierarchy as this tree but a different value type, T. /// /// For example, FloatTree::ValueConverter::Type is equivalent to DoubleTree. /// @note If the source tree type is a template argument, it might be necessary /// to write "typename SourceTree::template ValueConverter::Type". template struct ValueConverter { typedef Tree::Type> Type; }; Tree(){} /// Deep copy constructor Tree(const Tree& other): TreeBase(other), mRoot(other.mRoot) { } /// @brief Topology copy constructor from a tree of a different type /// /// Copy the structure, i.e., the active states of tiles and voxels, of another /// tree of a possibly different type, but don't copy any tile or voxel values. /// Instead, initialize tiles and voxels with the given active and inactive values. /// @param other a tree having (possibly) a different ValueType /// @param inactiveValue background value for this tree, and the value to which /// all inactive tiles and voxels are initialized /// @param activeValue value to which active tiles and voxels are initialized template Tree(const OtherTreeType& other, const ValueType& inactiveValue, const ValueType& activeValue, TopologyCopy): TreeBase(other), mRoot(other.getRootNode(), inactiveValue, activeValue, TopologyCopy()) { } /// @brief Topology copy constructor from a tree of a different type /// /// @note This topology copy constructor is generally faster than /// the one that takes both a foreground and a background value. /// /// Copy the structure, i.e., the active states of tiles and voxels, of another /// tree of a possibly different type, but don't copy any tile or voxel values. /// Instead, initialize tiles and voxels with the given background value. /// @param other a tree having (possibly) a different ValueType /// @param background the value to which tiles and voxels are initialized template Tree(const OtherTreeType& other, const ValueType& background, TopologyCopy): TreeBase(other), mRoot(other.getRootNode(), background, TopologyCopy()) { } /// Empty tree constructor Tree(const ValueType& background): mRoot(background) {} virtual ~Tree() { releaseAllAccessors(); } /// Return a pointer to a deep copy of this tree virtual TreeBase::Ptr copy() const { return TreeBase::Ptr(new Tree(*this)); } /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d") virtual Name valueType() const { return typeNameAsString(); } /// Return the name of this type of tree. static const Name& treeType(); /// Return the name of this type of tree. virtual const Name& type() const { return this->treeType(); } bool operator==(const Tree&) const { OPENVDB_THROW(NotImplementedError, ""); } bool operator!=(const Tree&) const { OPENVDB_THROW(NotImplementedError, ""); } //@{ /// Return this tree's root node. RootNodeType& root() { return mRoot; } const RootNodeType& root() const { return mRoot; } // Deprecate the methods below RootNodeType& getRootNode() { return mRoot; } const RootNodeType& getRootNode() const { return mRoot; } //@} // // Tree methods // /// @brief Return @c true if the given tree has the same node and active value /// topology as this tree, whether or not it has the same @c ValueType. template bool hasSameTopology(const Tree& other) const; virtual bool evalLeafBoundingBox(CoordBBox& bbox) const; virtual bool evalActiveVoxelBoundingBox(CoordBBox& bbox) const; virtual bool evalActiveVoxelDim(Coord& dim) const; virtual bool evalLeafDim(Coord& dim) const; /// @brief Traverse the type hierarchy of nodes, and return, in @a dims, a list /// of the Log2Dims of nodes in order from RootNode to LeafNode. /// @note Because RootNodes are resizable, the RootNode Log2Dim is 0 for all trees. static void getNodeLog2Dims(std::vector& dims); // // I/O methods // /// @brief Read the tree topology from a stream. /// /// This will read the tree structure and tile values, but not voxel data. virtual void readTopology(std::istream&, bool saveFloatAsHalf = false); /// @brief Write the tree topology to a stream. /// /// This will write the tree structure and tile values, but not voxel data. virtual void writeTopology(std::ostream&, bool saveFloatAsHalf = false) const; /// Read all data buffers for this tree. virtual void readBuffers(std::istream&, bool saveFloatAsHalf = false); /// Write out all data buffers for this tree. virtual void writeBuffers(std::ostream&, bool saveFloatAsHalf = false) const; virtual void print(std::ostream& os = std::cout, int verboseLevel = 1) const; // // Statistics // /// @brief Return the depth of this tree. /// /// A tree with only a root node and leaf nodes has depth 2, for example. virtual Index treeDepth() const { return DEPTH; } /// Return the number of leaf nodes. virtual Index32 leafCount() const { return mRoot.leafCount(); } /// Return the number of non-leaf nodes. virtual Index32 nonLeafCount() const { return mRoot.nonLeafCount(); } /// Return the number of active voxels stored in leaf nodes. virtual Index64 activeLeafVoxelCount() const { return mRoot.onLeafVoxelCount(); } /// Return the number of inactive voxels stored in leaf nodes. virtual Index64 inactiveLeafVoxelCount() const { return mRoot.offLeafVoxelCount(); } /// Return the total number of active voxels. virtual Index64 activeVoxelCount() const { return mRoot.onVoxelCount(); } /// Return the number of inactive voxels within the bounding box of all active voxels. virtual Index64 inactiveVoxelCount() const; /// Return the total number of active tiles. /// @note This method is not virtual so as to not change the ABI. Index64 activeTileCount() const { return mRoot.onTileCount(); } /// Return the minimum and maximum active values in this tree. void evalMinMax(ValueType &min, ValueType &max) const; virtual Index64 memUsage() const { return sizeof(*this) + mRoot.memUsage(); } // // Voxel access methods (using signed indexing) // /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const; /// @brief Return the value of the voxel at the given coordinates /// and update the given accessor's node cache. template const ValueType& getValue(const Coord& xyz, AccessT&) const; /// @brief Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides. /// @details If (x, y, z) isn't explicitly represented in the tree (i.e., it is /// implicitly a background voxel), return -1. int getValueDepth(const Coord& xyz) const; /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on); /// Set the value of the voxel at the given coordinates but don't change its active state. void setValueOnly(const Coord& xyz, const ValueType& value); /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz); /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValueOn(const Coord& xyz, const ValueType& value); /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value); /// @brief Set the value of the voxel at the given coordinates, mark the voxel as active, /// and update the given accessor's node cache. template void setValue(const Coord& xyz, const ValueType& value, AccessT&); /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz); /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value); /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details Provided that the functor can be inlined, this is typically /// significantly faster than calling getValue() followed by setValueOn(). /// @param xyz the coordinates of a voxel whose value is to be modified /// @param op a functor of the form void op(ValueType&) const that modifies /// its argument in place /// @par Example: /// @code /// Coord xyz(1, 0, -2); /// // Multiply the value of a voxel by a constant and mark the voxel as active. /// floatTree.modifyValue(xyz, [](float& f) { f *= 0.25; }); // C++11 /// // Set the value of a voxel to the maximum of its current value and 0.25, /// // and mark the voxel as active. /// floatTree.modifyValue(xyz, [](float& f) { f = std::max(f, 0.25f); }); // C++11 /// @endcode /// @note The functor is not guaranteed to be called only once. /// @see tools::foreach() template void modifyValue(const Coord& xyz, const ModifyOp& op); /// @brief Apply a functor to the voxel at the given coordinates. /// @details Provided that the functor can be inlined, this is typically /// significantly faster than calling getValue() followed by setValue(). /// @param xyz the coordinates of a voxel to be modified /// @param op a functor of the form void op(ValueType&, bool&) const that /// modifies its arguments, a voxel's value and active state, in place /// @par Example: /// @code /// Coord xyz(1, 0, -2); /// // Multiply the value of a voxel by a constant and mark the voxel as inactive. /// floatTree.modifyValueAndActiveState(xyz, /// [](float& f, bool& b) { f *= 0.25; b = false; }); // C++11 /// // Set the value of a voxel to the maximum of its current value and 0.25, /// // but don't change the voxel's active state. /// floatTree.modifyValueAndActiveState(xyz, /// [](float& f, bool&) { f = std::max(f, 0.25f); }); // C++11 /// @endcode /// @note The functor is not guaranteed to be called only once. /// @see tools::foreach() template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); /// @brief Get the value of the voxel at the given coordinates. /// @return @c true if the value is active. bool probeValue(const Coord& xyz, ValueType& value) const; /// Return @c true if the value at the given coordinates is active. bool isValueOn(const Coord& xyz) const { return mRoot.isValueOn(xyz); } /// Return @c true if the value at the given coordinates is inactive. bool isValueOff(const Coord& xyz) const { return !this->isValueOn(xyz); } /// Return @c true if this tree has any active tiles. bool hasActiveTiles() const { return mRoot.hasActiveTiles(); } /// @brief Set all voxels within a given axis-aligned box to a constant value. /// If necessary, subdivide tiles that intersect the box. /// @param bbox inclusive coordinates of opposite corners of an axis-aligned box /// @param value the value to which to set voxels within the box /// @param active if true, mark voxels within the box as active, /// otherwise mark them as inactive /// @note This operation generates a sparse, but not always optimally sparse, /// representation of the filled box. Follow fill operations with a prune() /// operation for optimal sparseness. void fill(const CoordBBox& bbox, const ValueType& value, bool active = true); /// Call the @c PruneOp functor for each non-root node in the tree. /// If the functor returns @c true, prune the node and replace it with a tile. /// /// This method is used to implement all of the various pruning algorithms /// (prune(), pruneInactive(), etc.). It should rarely be called directly. /// @see openvdb/tree/Util.h for the definition of the @c PruneOp functor template void pruneOp(PruneOp&); /// @brief Reduce the memory footprint of this tree by replacing with tiles /// any nodes whose values are all the same (optionally to within a tolerance) /// and have the same active state. void prune(const ValueType& tolerance = zeroVal()); /// @brief Reduce the memory footprint of this tree by replacing with /// tiles of the given value any nodes whose values are all inactive. void pruneInactive(const ValueType&); /// @brief Reduce the memory footprint of this tree by replacing with /// background tiles any nodes whose values are all inactive. void pruneInactive(); /// @brief Reduce the memory footprint of this tree by replacing nodes /// whose values are all inactive with inactive tiles having a value equal to /// the first value encountered in the (inactive) child. /// @details This method is faster than tolerance-based prune and /// useful for narrow-band level set applications where inactive /// values are limited to either an inside or an outside value. void pruneLevelSet(); /// @brief Add the given leaf node to this tree, creating a new branch if necessary. /// If a leaf node with the same origin already exists, replace it. void addLeaf(LeafNodeType& leaf) { mRoot.addLeaf(&leaf); } /// @brief Add a tile containing voxel (x, y, z) at the specified tree level, /// creating a new branch if necessary. Delete any existing lower-level nodes /// that contain (x, y, z). /// @note @a level must be less than this tree's depth. void addTile(Index level, const Coord& xyz, const ValueType& value, bool active); /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z) /// and replace it with a tile of the specified value and state. /// If no such node exists, leave the tree unchanged and return @c NULL. /// @note The caller takes ownership of the node and is responsible for deleting it. template NodeT* stealNode(const Coord& xyz, const ValueType& value, bool active); /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). /// If no such node exists, create one that preserves the values and /// active states of all voxels. /// @details Use this method to preallocate a static tree topology over which to /// safely perform multithreaded processing. LeafNodeType* touchLeaf(const Coord& xyz); //@{ /// @brief Return a pointer to the node of type @c NodeType that contains /// voxel (x, y, z). If no such node exists, return NULL. template NodeType* probeNode(const Coord& xyz); template const NodeType* probeConstNode(const Coord& xyz) const; template const NodeType* probeNode(const Coord& xyz) const; //@} //@{ /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). /// If no such node exists, return NULL. LeafNodeType* probeLeaf(const Coord& xyz); const LeafNodeType* probeConstLeaf(const Coord& xyz) const; const LeafNodeType* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } //@} // // Aux methods // /// @brief Return @c true if this tree contains no nodes other than /// the root node and no tiles other than background tiles. bool empty() const { return mRoot.empty(); } /// Remove all tiles from this tree and all nodes other than the root node. void clear() { this->clearAllAccessors(); mRoot.clear(); } /// Clear all registered accessors. void clearAllAccessors(); //@{ /// @brief Register an accessor for this tree. Registered accessors are /// automatically cleared whenever one of this tree's nodes is deleted. void attachAccessor(ValueAccessorBase&) const; void attachAccessor(ValueAccessorBase&) const; //@} //@{ /// Deregister an accessor so that it is no longer automatically cleared. void releaseAccessor(ValueAccessorBase&) const; void releaseAccessor(ValueAccessorBase&) const; //@} /// @brief Return this tree's background value wrapped as metadata. /// @note Query the metadata object for the value's type. virtual Metadata::Ptr getBackgroundValue() const; /// Return this tree's background value. const ValueType& background() const { return mRoot.background(); } /// Replace this tree's background value. void setBackground(const ValueType& background) { mRoot.setBackground(background); } /// Min and max are both inclusive. virtual void getIndexRange(CoordBBox& bbox) const { mRoot.getIndexRange(bbox); } /// @brief Set the values of all inactive voxels and tiles of a narrow-band /// level set from the signs of the active voxels, setting outside values to /// +background and inside values to -background. /// @warning This method should only be used on closed, narrow-band level sets. void signedFloodFill() { mRoot.signedFloodFill(); } /// @brief Set the values of all inactive voxels and tiles of a narrow-band /// level set from the signs of the active voxels, setting exterior values to /// @a outside and interior values to @a inside. Set the background value /// of this tree to @a outside. /// @warning This method should only be used on closed, narrow-band level sets. void signedFloodFill(const ValueType& outside, const ValueType& inside); /// Densify active tiles, i.e., replace them with leaf-level active voxels. void voxelizeActiveTiles(); /// @brief Efficiently merge another tree into this tree using one of several schemes. /// @details This operation is primarily intended to combine trees that are mostly /// non-overlapping (for example, intermediate trees from computations that are /// parallelized across disjoint regions of space). /// @note This operation is not guaranteed to produce an optimally sparse tree. /// Follow merge() with prune() for optimal sparseness. /// @warning This operation always empties the other tree. void merge(Tree& other, MergePolicy = MERGE_ACTIVE_STATES); /// @brief Union this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. /// @details The resulting state of a value is active if the corresponding value /// was already active OR if it is active in the other tree. Also, a resulting /// value maps to a voxel if the corresponding value already mapped to a voxel /// OR if it is a voxel in the other tree. Thus, a resulting value can only /// map to a tile if the corresponding value already mapped to a tile /// AND if it is a tile value in other tree. /// /// @note This operation modifies only active states, not values. /// Specifically, active tiles and voxels in this tree are not changed, and /// tiles or voxels that were inactive in this tree but active in the other tree /// are marked as active in this tree but left with their original values. template void topologyUnion(const Tree& other); /// @brief Intersects this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. /// @details The resulting state of a value is active only if the corresponding /// value was already active AND if it is active in the other tree. Also, a /// resulting value maps to a voxel if the corresponding value /// already mapped to an active voxel in either of the two grids /// and it maps to an active tile or voxel in the other grid. /// /// @note This operation can delete branches in this grid if they /// overlap with inactive tiles in the other grid. Likewise active /// voxels can be turned into unactive voxels resulting in leaf /// nodes with no active values. Thus, it is recommended to /// subsequently call prune. template void topologyIntersection(const Tree& other); /// @brief Difference this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. So a /// resulting voxel will be active only if the original voxel is /// active in this tree and inactive in the other tree. /// /// @note This operation can delete branches in this grid if they /// overlap with active tiles in the other grid. Likewise active /// voxels can be turned into inactive voxels resulting in leaf /// nodes with no active values. Thus, it is recommended to /// subsequently call prune. template void topologyDifference(const Tree& other); /*! For a given function @c f, use sparse traversal to compute f(this, other) * over all corresponding pairs of values (tile or voxel) of this tree and the other tree * and store the result in this tree. * This method is typically more space-efficient than the two-tree combine2(), * since it moves rather than copies nodes from the other tree into this tree. * @note This operation always empties the other tree. * @param other a tree of the same type as this tree * @param op a functor of the form void op(const T& a, const T& b, T& result), * where @c T is this tree's @c ValueType, that computes * result = f(a, b) * @param prune if true, prune the resulting tree one branch at a time (this is usually * more space-efficient than pruning the entire tree in one pass) * * @par Example: * Compute the per-voxel difference between two floating-point trees, * @c aTree and @c bTree, and store the result in @c aTree (leaving @c bTree empty). * @code * { * struct Local { * static inline void diff(const float& a, const float& b, float& result) { * result = a - b; * } * }; * aTree.combine(bTree, Local::diff); * } * @endcode * * @par Example: * Compute f * a + (1 - f) * b over all voxels of two floating-point trees, * @c aTree and @c bTree, and store the result in @c aTree (leaving @c bTree empty). * @code * namespace { * 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; * }; * } * { * aTree.combine(bTree, Blend(0.25)); // 0.25 * a + 0.75 * b * } * @endcode */ template void combine(Tree& other, CombineOp& op, bool prune = false); #ifndef _MSC_VER template void combine(Tree& other, const CombineOp& op, bool prune = false); #endif /*! Like combine(), but with * @param other a tree of the same type as this tree * @param op a functor of the form void op(CombineArgs& args) that * computes args.setResult(f(args.a(), args.b())) and, optionally, * args.setResultIsActive(g(args.aIsActive(), args.bIsActive())) * for some functions @c f and @c g * @param prune if true, prune the resulting tree one branch at a time (this is usually * more space-efficient than pruning the entire tree in one pass) * * This variant passes not only the @em a and @em b values but also the active states * of the @em a and @em b values to the functor, which may then return, by calling * @c args.setResultIsActive(), a computed active state for the result value. * By default, the result is active if either the @em a or the @em b value is active. * * @see openvdb/Types.h for the definition of the CombineArgs struct. * * @par Example: * Replace voxel values in floating-point @c aTree with corresponding values * from floating-point @c bTree (leaving @c bTree empty) wherever the @c bTree * values are larger. Also, preserve the active states of any transferred values. * @code * { * struct Local { * static inline void max(CombineArgs& args) { * if (args.b() > args.a()) { * // Transfer the B value and its active state. * args.setResult(args.b()); * args.setResultIsActive(args.bIsActive()); * } else { * // Preserve the A value and its active state. * args.setResult(args.a()); * args.setResultIsActive(args.aIsActive()); * } * } * }; * aTree.combineExtended(bTree, Local::max); * } * @endcode */ template void combineExtended(Tree& other, ExtendedCombineOp& op, bool prune = false); #ifndef _MSC_VER template void combineExtended(Tree& other, const ExtendedCombineOp& op, bool prune = false); #endif /*! For a given function @c f, use sparse traversal to compute f(a, b) over all * corresponding pairs of values (tile or voxel) of trees A and B and store the result * in this tree. * @param a,b two trees of the same type * @param op a functor of the form void op(const T& a, const T& b, T& result), * where @c T is this tree's @c ValueType, that computes * result = f(a, b) * @param prune if true, prune the resulting tree one branch at a time (this is usually * more space-efficient than pruning the entire tree in one pass) * * @par Example: * Compute the per-voxel difference between two floating-point trees, * @c aTree and @c bTree, and store the result in a third tree. * @code * { * struct Local { * static inline void diff(const float& a, const float& b, float& result) { * result = a - b; * } * }; * FloatTree resultTree; * resultTree.combine2(aTree, bTree, Local::diff); * } * @endcode */ template void combine2(const Tree& a, const Tree& b, CombineOp& op, bool prune = false); #ifndef _MSC_VER template void combine2(const Tree& a, const Tree& b, const CombineOp& op, bool prune = false); #endif /*! Like combine2(), but with * @param a,b two trees of the same type * @param op a functor of the form void op(CombineArgs& args) that * computes args.setResult(f(args.a(), args.b())) and, optionally, * args.setResultIsActive(g(args.aIsActive(), args.bIsActive())) * for some functions @c f and @c g * @param prune if true, prune the resulting tree one branch at a time (this is usually * more space-efficient than pruning the entire tree in one pass) * This variant passes not only the @em a and @em b values but also the active states * of the @em a and @em b values to the functor, which may then return, by calling * args.setResultIsActive(), a computed active state for the result value. * By default, the result is active if either the @em a or the @em b value is active. * * @see openvdb/Types.h for the definition of the CombineArgs struct. * * @par Example: * Compute the per-voxel maximum values of two floating-point trees, @c aTree * and @c bTree, and store the result in a third tree. Set the active state * of each output value to that of the larger of the two input values. * @code * { * struct Local { * static inline void max(CombineArgs& args) { * if (args.b() > args.a()) { * // Transfer the B value and its active state. * args.setResult(args.b()); * args.setResultIsActive(args.bIsActive()); * } else { * // Preserve the A value and its active state. * args.setResult(args.a()); * args.setResultIsActive(args.aIsActive()); * } * } * }; * FloatTree resultTree; * resultTree.combine2Extended(aTree, bTree, Local::max); * } * @endcode */ template void combine2Extended(const Tree& a, const Tree& b, ExtendedCombineOp& op, bool prune = false); #ifndef _MSC_VER template void combine2Extended(const Tree& a, const Tree& b, const ExtendedCombineOp&, bool prune = false); #endif /*! For a given function use sparse traversal to call it with * bounding box information for all active tiles and leaf nodes * or active voxels in the tree. * * @note The bounding boxes are guarenteed to be non-overlapping. * @param op a template functor of the form * template void op(const * CoordBBox& bbox), where bbox * defines the bbox of an active tile if LEVEL>0, * and else a LeafNode or active voxel. The functor * must also provide a template method of the form * template bool descent() * that returns false if no bboxes * are to be derived below the templated tree level. In * such cases of early tree termination a bbox is * instead derived from each terminating child node. * * * @par Example: * Render all active tiles and leaf nodes in a tree. Note in * this example descent returns false if LEVEL==0 which means * the functor will never descent to the active voxels. In * other words the smallest BBoxes correspond to LeafNodes or * active tiles at LEVEL=1! * @code * { * struct RenderTilesAndLeafs { * template * inline bool descent() { return LEVEL>0; }//only descent to leaf nodes * //inline bool descent() { return true; }//use this to decent to voxels * * template * inline void operator()(const CoordBBox &bbox) { * if (LEVEL>0) { * // code to render active tile * } else { * // code to render leaf node * } * } * }; * RenderTilesAndLeafs op; * aTree.visitActiveBBox(op); * } * @endcode * @see openvdb/unittest/TestTree.cc for another example. */ template void visitActiveBBox(BBoxOp& op) const { mRoot.visitActiveBBox(op); } /*! Traverse this tree in depth-first order, and at each node call the given functor * with a @c DenseIterator (see Iterator.h) that points to either a child node or a * tile value. If the iterator points to a child node and the functor returns true, * do not descend to the child node; instead, continue the traversal at the next * iterator position. * @param op a functor of the form template bool op(IterT&), * where @c IterT is either a RootNode::ChildAllIter, * an InternalNode::ChildAllIter or a LeafNode::ChildAllIter * * @note There is no iterator that points to a RootNode, so to visit the root node, * retrieve the @c parent() of a RootNode::ChildAllIter. * * @par Example: * Print information about the nodes and tiles of a tree, but not individual voxels. * @code * namespace { * template * struct PrintTreeVisitor * { * typedef typename TreeT::RootNodeType RootT; * bool visitedRoot; * * PrintTreeVisitor(): visitedRoot(false) {} * * template * inline bool operator()(IterT& iter) * { * if (!visitedRoot && iter.parent().getLevel() == RootT::LEVEL) { * visitedRoot = true; * std::cout << "Level-" << RootT::LEVEL << " node" << std::endl; * } * typename IterT::NonConstValueType value; * typename IterT::ChildNodeType* child = iter.probeChild(value); * if (child == NULL) { * std::cout << "Tile with value " << value << std::endl; * return true; // no child to visit, so stop descending * } * std::cout << "Level-" << child->getLevel() << " node" << std::endl; * return (child->getLevel() == 0); // don't visit leaf nodes * } * * // The generic method, above, calls iter.probeChild(), which is not defined * // for LeafNode::ChildAllIter. These overloads ensure that the generic * // method template doesn't get instantiated for LeafNode iterators. * bool operator()(typename TreeT::LeafNodeType::ChildAllIter&) { return true; } * bool operator()(typename TreeT::LeafNodeType::ChildAllCIter&) { return true; } * }; * } * { * PrintTreeVisitor visitor; * tree.visit(visitor); * } * @endcode */ template void visit(VisitorOp& op); template void visit(const VisitorOp& op); /// Like visit(), but using @c const iterators, i.e., with /// @param op a functor of the form template bool op(IterT&), /// where @c IterT is either a RootNode::ChildAllCIter, /// an InternalNode::ChildAllCIter or a LeafNode::ChildAllCIter template void visit(VisitorOp& op) const; template void visit(const VisitorOp& op) const; /*! Traverse this tree and another tree in depth-first order, and for corresponding * subregions of index space call the given functor with two @c DenseIterators * (see Iterator.h), each of which points to either a child node or a tile value * of this tree and the other tree. If the A iterator points to a child node * and the functor returns a nonzero value with bit 0 set (e.g., 1), do not descend * to the child node; instead, continue the traversal at the next A iterator position. * Similarly, if the B iterator points to a child node and the functor returns a value * with bit 1 set (e.g., 2), continue the traversal at the next B iterator position. * @note The other tree must have the same index space and fan-out factors as * this tree, but it may have a different @c ValueType and a different topology. * @param other a tree of the same type as this tree * @param op a functor of the form * template int op(AIterT&, BIterT&), * where @c AIterT and @c BIterT are any combination of a * RootNode::ChildAllIter, an InternalNode::ChildAllIter or a * LeafNode::ChildAllIter with an @c OtherTreeType::RootNode::ChildAllIter, * an @c OtherTreeType::InternalNode::ChildAllIter * or an @c OtherTreeType::LeafNode::ChildAllIter * * @par Example: * Given two trees of the same type, @c aTree and @c bTree, replace leaf nodes of * @c aTree with corresponding leaf nodes of @c bTree, leaving @c bTree partially empty. * @code * namespace { * template * inline int stealLeafNodes(AIterT& aIter, BIterT& bIter) * { * typename AIterT::NonConstValueType aValue; * typename AIterT::ChildNodeType* aChild = aIter.probeChild(aValue); * typename BIterT::NonConstValueType bValue; * typename BIterT::ChildNodeType* bChild = bIter.probeChild(bValue); * * const Index aLevel = aChild->getLevel(), bLevel = bChild->getLevel(); * if (aChild && bChild && aLevel == 0 && bLevel == 0) { // both are leaf nodes * aIter.setChild(bChild); // give B's child to A * bIter.setValue(bValue); // replace B's child with a constant tile value * } * // Don't iterate over leaf node voxels of either A or B. * int skipBranch = (aLevel == 0) ? 1 : 0; * if (bLevel == 0) skipBranch = skipBranch | 2; * return skipBranch; * } * } * { * aTree.visit2(bTree, stealLeafNodes); * } * @endcode */ template void visit2(OtherTreeType& other, VisitorOp& op); template void visit2(OtherTreeType& other, const VisitorOp& op); /// Like visit2(), but using @c const iterators, i.e., with /// @param other a tree of the same type as this tree /// @param op a functor of the form /// template int op(AIterT&, BIterT&), /// where @c AIterT and @c BIterT are any combination of a /// RootNode::ChildAllCIter, an InternalNode::ChildAllCIter /// or a LeafNode::ChildAllCIter with an /// @c OtherTreeType::RootNode::ChildAllCIter, /// an @c OtherTreeType::InternalNode::ChildAllCIter /// or an @c OtherTreeType::LeafNode::ChildAllCIter template void visit2(OtherTreeType& other, VisitorOp& op) const; template void visit2(OtherTreeType& other, const VisitorOp& op) const; // // Iteration // //@{ /// Return an iterator over children of the root node. typename RootNodeType::ChildOnCIter beginRootChildren() const { return mRoot.cbeginChildOn(); } typename RootNodeType::ChildOnCIter cbeginRootChildren() const { return mRoot.cbeginChildOn(); } typename RootNodeType::ChildOnIter beginRootChildren() { return mRoot.beginChildOn(); } //@} //@{ /// Return an iterator over non-child entries of the root node's table. typename RootNodeType::ChildOffCIter beginRootTiles() const { return mRoot.cbeginChildOff(); } typename RootNodeType::ChildOffCIter cbeginRootTiles() const { return mRoot.cbeginChildOff(); } typename RootNodeType::ChildOffIter beginRootTiles() { return mRoot.beginChildOff(); } //@} //@{ /// Return an iterator over all entries of the root node's table. typename RootNodeType::ChildAllCIter beginRootDense() const { return mRoot.cbeginChildAll(); } typename RootNodeType::ChildAllCIter cbeginRootDense() const { return mRoot.cbeginChildAll(); } typename RootNodeType::ChildAllIter beginRootDense() { return mRoot.beginChildAll(); } //@} //@{ /// Iterator over all nodes in this tree typedef NodeIteratorBase NodeIter; typedef NodeIteratorBase NodeCIter; //@} //@{ /// Iterator over all leaf nodes in this tree typedef LeafIteratorBase LeafIter; typedef LeafIteratorBase LeafCIter; //@} //@{ /// Return an iterator over all nodes in this tree. NodeIter beginNode() { return NodeIter(*this); } NodeCIter beginNode() const { return NodeCIter(*this); } NodeCIter cbeginNode() const { return NodeCIter(*this); } //@} //@{ /// Return an iterator over all leaf nodes in this tree. LeafIter beginLeaf() { return LeafIter(*this); } LeafCIter beginLeaf() const { return LeafCIter(*this); } LeafCIter cbeginLeaf() const { return LeafCIter(*this); } //@} typedef TreeValueIteratorBase ValueAllIter; typedef TreeValueIteratorBase ValueAllCIter; typedef TreeValueIteratorBase ValueOnIter; typedef TreeValueIteratorBase ValueOnCIter; typedef TreeValueIteratorBase ValueOffIter; typedef TreeValueIteratorBase ValueOffCIter; //@{ /// Return an iterator over all values (tile and voxel) across all nodes. ValueAllIter beginValueAll() { return ValueAllIter(*this); } ValueAllCIter beginValueAll() const { return ValueAllCIter(*this); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(*this); } //@} //@{ /// Return an iterator over active values (tile and voxel) across all nodes. ValueOnIter beginValueOn() { return ValueOnIter(*this); } ValueOnCIter beginValueOn() const { return ValueOnCIter(*this); } ValueOnCIter cbeginValueOn() const { return ValueOnCIter(*this); } //@} //@{ /// Return an iterator over inactive values (tile and voxel) across all nodes. ValueOffIter beginValueOff() { return ValueOffIter(*this); } ValueOffCIter beginValueOff() const { return ValueOffCIter(*this); } ValueOffCIter cbeginValueOff() const { return ValueOffCIter(*this); } //@} /// @brief Return an iterator of type @c IterT (for example, begin() is /// equivalent to beginValueOn()). template IterT begin(); /// @brief Return a const iterator of type CIterT (for example, cbegin() /// is equivalent to cbeginValueOn()). template CIterT cbegin() const; protected: typedef tbb::concurrent_hash_map*, bool> AccessorRegistry; typedef tbb::concurrent_hash_map*, bool> ConstAccessorRegistry; // Disallow assignment of instances of this class. Tree& operator=(const Tree&); /// @brief Notify all registered accessors, by calling ValueAccessor::release(), /// that this tree is about to be deleted. void releaseAllAccessors(); // // Data members // RootNodeType mRoot; // root node of the tree mutable AccessorRegistry mAccessorRegistry; mutable ConstAccessorRegistry mConstAccessorRegistry; }; // end of Tree class /// @brief Tree3::Type is the type of a three-level tree /// (Root, Internal, Leaf) with value type T and /// internal and leaf node log dimensions N1 and N2, respectively. /// @note This is NOT the standard tree configuration (Tree4 is). template struct Tree3 { typedef Tree, N1> > > Type; }; /// @brief Tree4::Type is the type of a four-level tree /// (Root, Internal, Internal, Leaf) with value type T and /// internal and leaf node log dimensions N1, N2 and N3, respectively. /// @note This is the standard tree configuration. template struct Tree4 { typedef Tree, N2>, N1> > > Type; }; /// @brief Tree5::Type is the type of a five-level tree /// (Root, Internal, Internal, Internal, Leaf) with value type T and /// internal and leaf node log dimensions N1, N2, N3 and N4, respectively. /// @note This is NOT the standard tree configuration (Tree4 is). template struct Tree5 { typedef Tree, N3>, N2>, N1> > > Type; }; //////////////////////////////////////// inline void TreeBase::readTopology(std::istream& is, bool /*saveFloatAsHalf*/) { int32_t bufferCount; is.read(reinterpret_cast(&bufferCount), sizeof(int32_t)); if (bufferCount != 1) OPENVDB_LOG_WARN("multi-buffer trees are no longer supported"); } inline void TreeBase::writeTopology(std::ostream& os, bool /*saveFloatAsHalf*/) const { int32_t bufferCount = 1; os.write(reinterpret_cast(&bufferCount), sizeof(int32_t)); } inline void TreeBase::print(std::ostream& os, int /*verboseLevel*/) const { os << " Tree Type: " << type() << " Active Voxel Count: " << activeVoxelCount() << std::endl << " Inactive Voxel Count: " << inactiveVoxelCount() << std::endl << " Leaf Node Count: " << leafCount() << std::endl << " Non-leaf Node Count: " << nonLeafCount() << std::endl; } //////////////////////////////////////// // // Type traits for tree iterators // /// @brief TreeIterTraits provides, for all tree iterators, a begin(tree) function /// that returns an iterator over a tree of arbitrary type. template struct TreeIterTraits; template struct TreeIterTraits { static typename TreeT::RootNodeType::ChildOnIter begin(TreeT& tree) { return tree.beginRootChildren(); } }; template struct TreeIterTraits { static typename TreeT::RootNodeType::ChildOnCIter begin(const TreeT& tree) { return tree.cbeginRootChildren(); } }; template struct TreeIterTraits { static typename TreeT::RootNodeType::ChildOffIter begin(TreeT& tree) { return tree.beginRootTiles(); } }; template struct TreeIterTraits { static typename TreeT::RootNodeType::ChildOffCIter begin(const TreeT& tree) { return tree.cbeginRootTiles(); } }; template struct TreeIterTraits { static typename TreeT::RootNodeType::ChildAllIter begin(TreeT& tree) { return tree.beginRootDense(); } }; template struct TreeIterTraits { static typename TreeT::RootNodeType::ChildAllCIter begin(const TreeT& tree) { return tree.cbeginRootDense(); } }; template struct TreeIterTraits { static typename TreeT::NodeIter begin(TreeT& tree) { return tree.beginNode(); } }; template struct TreeIterTraits { static typename TreeT::NodeCIter begin(const TreeT& tree) { return tree.cbeginNode(); } }; template struct TreeIterTraits { static typename TreeT::LeafIter begin(TreeT& tree) { return tree.beginLeaf(); } }; template struct TreeIterTraits { static typename TreeT::LeafCIter begin(const TreeT& tree) { return tree.cbeginLeaf(); } }; template struct TreeIterTraits { static typename TreeT::ValueOnIter begin(TreeT& tree) { return tree.beginValueOn(); } }; template struct TreeIterTraits { static typename TreeT::ValueOnCIter begin(const TreeT& tree) { return tree.cbeginValueOn(); } }; template struct TreeIterTraits { static typename TreeT::ValueOffIter begin(TreeT& tree) { return tree.beginValueOff(); } }; template struct TreeIterTraits { static typename TreeT::ValueOffCIter begin(const TreeT& tree) { return tree.cbeginValueOff(); } }; template struct TreeIterTraits { static typename TreeT::ValueAllIter begin(TreeT& tree) { return tree.beginValueAll(); } }; template struct TreeIterTraits { static typename TreeT::ValueAllCIter begin(const TreeT& tree) { return tree.cbeginValueAll(); } }; template template inline IterT Tree::begin() { return TreeIterTraits::begin(*this); } template template inline IterT Tree::cbegin() const { return TreeIterTraits::begin(*this); } //////////////////////////////////////// template void Tree::readTopology(std::istream& is, bool saveFloatAsHalf) { this->clearAllAccessors(); TreeBase::readTopology(is, saveFloatAsHalf); mRoot.readTopology(is, saveFloatAsHalf); } template void Tree::writeTopology(std::ostream& os, bool saveFloatAsHalf) const { TreeBase::writeTopology(os, saveFloatAsHalf); mRoot.writeTopology(os, saveFloatAsHalf); } template inline void Tree::readBuffers(std::istream &is, bool saveFloatAsHalf) { this->clearAllAccessors(); mRoot.readBuffers(is, saveFloatAsHalf); } template inline void Tree::writeBuffers(std::ostream &os, bool saveFloatAsHalf) const { mRoot.writeBuffers(os, saveFloatAsHalf); } //////////////////////////////////////// template inline void Tree::attachAccessor(ValueAccessorBase& accessor) const { typename AccessorRegistry::accessor a; mAccessorRegistry.insert(a, &accessor); } template inline void Tree::attachAccessor(ValueAccessorBase& accessor) const { typename ConstAccessorRegistry::accessor a; mConstAccessorRegistry.insert(a, &accessor); } template inline void Tree::releaseAccessor(ValueAccessorBase& accessor) const { mAccessorRegistry.erase(&accessor); } template inline void Tree::releaseAccessor(ValueAccessorBase& accessor) const { mConstAccessorRegistry.erase(&accessor); } template inline void Tree::clearAllAccessors() { for (typename AccessorRegistry::iterator it = mAccessorRegistry.begin(); it != mAccessorRegistry.end(); ++it) { if (it->first) it->first->clear(); } for (typename ConstAccessorRegistry::iterator it = mConstAccessorRegistry.begin(); it != mConstAccessorRegistry.end(); ++it) { if (it->first) it->first->clear(); } } template inline void Tree::releaseAllAccessors() { mAccessorRegistry.erase(NULL); for (typename AccessorRegistry::iterator it = mAccessorRegistry.begin(); it != mAccessorRegistry.end(); ++it) { it->first->release(); } mAccessorRegistry.clear(); mAccessorRegistry.erase(NULL); for (typename ConstAccessorRegistry::iterator it = mConstAccessorRegistry.begin(); it != mConstAccessorRegistry.end(); ++it) { it->first->release(); } mConstAccessorRegistry.clear(); } //////////////////////////////////////// template inline const typename RootNodeType::ValueType& Tree::getValue(const Coord& xyz) const { return mRoot.getValue(xyz); } template template inline const typename RootNodeType::ValueType& Tree::getValue(const Coord& xyz, AccessT& accessor) const { return accessor.getValue(xyz); } template inline int Tree::getValueDepth(const Coord& xyz) const { return mRoot.getValueDepth(xyz); } template inline void Tree::setValueOff(const Coord& xyz) { mRoot.setValueOff(xyz); } template inline void Tree::setValueOff(const Coord& xyz, const ValueType& value) { mRoot.setValueOff(xyz, value); } template inline void Tree::setActiveState(const Coord& xyz, bool on) { mRoot.setActiveState(xyz, on); } template inline void Tree::setValue(const Coord& xyz, const ValueType& value) { mRoot.setValueOn(xyz, value); } template inline void Tree::setValueOnly(const Coord& xyz, const ValueType& value) { mRoot.setValueOnly(xyz, value); } template template inline void Tree::setValue(const Coord& xyz, const ValueType& value, AccessT& accessor) { accessor.setValue(xyz, value); } template inline void Tree::setValueOn(const Coord& xyz) { mRoot.setActiveState(xyz, true); } template inline void Tree::setValueOn(const Coord& xyz, const ValueType& value) { mRoot.setValueOn(xyz, value); } template template inline void Tree::modifyValue(const Coord& xyz, const ModifyOp& op) { mRoot.modifyValue(xyz, op); } template template inline void Tree::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { mRoot.modifyValueAndActiveState(xyz, op); } template inline bool Tree::probeValue(const Coord& xyz, ValueType& value) const { return mRoot.probeValue(xyz, value); } //////////////////////////////////////// template template inline void Tree::pruneOp(PruneOp& op) { this->clearAllAccessors(); mRoot.pruneOp(op); } template inline void Tree::prune(const ValueType& tolerance) { TolerancePrune op(tolerance); this->pruneOp(op); } template inline void Tree::pruneInactive(const ValueType& bg) { InactivePrune op(bg); this->pruneOp(op); } template inline void Tree::pruneInactive() { this->pruneInactive(this->background()); } template inline void Tree::pruneLevelSet() { LevelSetPrune op(this->background()); this->pruneOp(op); } template inline void Tree::addTile(Index level, const Coord& xyz, const ValueType& value, bool active) { mRoot.addTile(level, xyz, value, active); } template template inline NodeT* Tree::stealNode(const Coord& xyz, const ValueType& value, bool active) { this->clearAllAccessors(); return mRoot.template stealNode(xyz, value, active); } template inline typename RootNodeType::LeafNodeType* Tree::touchLeaf(const Coord& xyz) { return mRoot.touchLeaf(xyz); } template inline typename RootNodeType::LeafNodeType* Tree::probeLeaf(const Coord& xyz) { return mRoot.probeLeaf(xyz); } template inline const typename RootNodeType::LeafNodeType* Tree::probeConstLeaf(const Coord& xyz) const { return mRoot.probeConstLeaf(xyz); } template template inline NodeType* Tree::probeNode(const Coord& xyz) { return mRoot.template probeNode(xyz); } template template inline const NodeType* Tree::probeNode(const Coord& xyz) const { return this->template probeConstNode(xyz); } template template inline const NodeType* Tree::probeConstNode(const Coord& xyz) const { return mRoot.template probeConstNode(xyz); } //////////////////////////////////////// template inline void Tree::fill(const CoordBBox& bbox, const ValueType& value, bool active) { this->clearAllAccessors(); return mRoot.fill(bbox, value, active); } template inline void Tree::signedFloodFill(const ValueType& outside, const ValueType& inside) { mRoot.signedFloodFill(outside, inside); } template Metadata::Ptr Tree::getBackgroundValue() const { Metadata::Ptr result; if (Metadata::isRegisteredType(valueType())) { typedef TypedMetadata MetadataT; result = Metadata::createMetadata(valueType()); if (MetadataT* m = dynamic_cast(result.get())) { m->value() = mRoot.background(); } } return result; } //////////////////////////////////////// template inline void Tree::voxelizeActiveTiles() { this->clearAllAccessors(); mRoot.voxelizeActiveTiles(); } template inline void Tree::merge(Tree& other, MergePolicy policy) { this->clearAllAccessors(); other.clearAllAccessors(); switch (policy) { case MERGE_ACTIVE_STATES: mRoot.template merge(other.mRoot); break; case MERGE_NODES: mRoot.template merge(other.mRoot); break; case MERGE_ACTIVE_STATES_AND_NODES: mRoot.template merge(other.mRoot); break; } } template template inline void Tree::topologyUnion(const Tree& other) { this->clearAllAccessors(); mRoot.topologyUnion(other.getRootNode()); } template template inline void Tree::topologyIntersection(const Tree& other) { this->clearAllAccessors(); mRoot.topologyIntersection(other.getRootNode()); } template template inline void Tree::topologyDifference(const Tree& other) { this->clearAllAccessors(); mRoot.topologyDifference(other.getRootNode()); } //////////////////////////////////////// /// @brief Helper class to adapt a three-argument (a, b, result) CombineOp functor /// into a single-argument functor that accepts a CombineArgs struct template struct CombineOpAdapter { CombineOpAdapter(CombineOp& op): op(op) {} void operator()(CombineArgs& args) const { op(args.a(), args.b(), args.result()); } CombineOp& op; }; template template inline void Tree::combine(Tree& other, CombineOp& op, bool prune) { CombineOpAdapter extendedOp(op); this->combineExtended(other, extendedOp, prune); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like this: aTree.combine(bTree, MyCombineOp(...)). #ifndef _MSC_VER template template inline void Tree::combine(Tree& other, const CombineOp& op, bool prune) { CombineOpAdapter extendedOp(op); this->combineExtended(other, extendedOp, prune); } #endif template template inline void Tree::combineExtended(Tree& other, ExtendedCombineOp& op, bool prune) { this->clearAllAccessors(); mRoot.combine(other.getRootNode(), op, prune); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like this: aTree.combineExtended(bTree, MyCombineOp(...)). #ifndef _MSC_VER template template inline void Tree::combineExtended(Tree& other, const ExtendedCombineOp& op, bool prune) { this->clearAllAccessors(); mRoot.template combine(other.mRoot, op, prune); } #endif template template inline void Tree::combine2(const Tree& a, const Tree& b, CombineOp& op, bool prune) { CombineOpAdapter extendedOp(op); this->combine2Extended(a, b, extendedOp, prune); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like this: tree.combine2(aTree, bTree, MyCombineOp(...)). #ifndef _MSC_VER template template inline void Tree::combine2(const Tree& a, const Tree& b, const CombineOp& op, bool prune) { CombineOpAdapter extendedOp(op); this->combine2Extended(a, b, extendedOp, prune); } #endif template template inline void Tree::combine2Extended(const Tree& a, const Tree& b, ExtendedCombineOp& op, bool prune) { this->clearAllAccessors(); mRoot.combine2(a.mRoot, b.mRoot, op, prune); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like this: tree.combine2Extended(aTree, bTree, MyCombineOp(...)). #ifndef _MSC_VER template template inline void Tree::combine2Extended(const Tree& a, const Tree& b, const ExtendedCombineOp& op, bool prune) { this->clearAllAccessors(); mRoot.template combine2(a.mRoot, b.mRoot, op, prune); } #endif //////////////////////////////////////// template template inline void Tree::visit(VisitorOp& op) { this->clearAllAccessors(); mRoot.template visit(op); } template template inline void Tree::visit(VisitorOp& op) const { mRoot.template visit(op); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like this: tree.visit(MyVisitorOp(...)). template template inline void Tree::visit(const VisitorOp& op) { this->clearAllAccessors(); mRoot.template visit(op); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like this: tree.visit(MyVisitorOp(...)). template template inline void Tree::visit(const VisitorOp& op) const { mRoot.template visit(op); } //////////////////////////////////////// template template inline void Tree::visit2(OtherTreeType& other, VisitorOp& op) { this->clearAllAccessors(); typedef typename OtherTreeType::RootNodeType OtherRootNodeType; mRoot.template visit2(other.getRootNode(), op); } template template inline void Tree::visit2(OtherTreeType& other, VisitorOp& op) const { typedef typename OtherTreeType::RootNodeType OtherRootNodeType; mRoot.template visit2(other.getRootNode(), op); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like this: aTree.visit2(bTree, MyVisitorOp(...)). template template inline void Tree::visit2(OtherTreeType& other, const VisitorOp& op) { this->clearAllAccessors(); typedef typename OtherTreeType::RootNodeType OtherRootNodeType; mRoot.template visit2(other.getRootNode(), op); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like this: aTree.visit2(bTree, MyVisitorOp(...)). template template inline void Tree::visit2(OtherTreeType& other, const VisitorOp& op) const { typedef typename OtherTreeType::RootNodeType OtherRootNodeType; mRoot.template visit2(other.getRootNode(), op); } //////////////////////////////////////// template inline const Name& Tree::treeType() { static tbb::atomic sTypeName; if (sTypeName == NULL) { std::vector dims; Tree::getNodeLog2Dims(dims); std::ostringstream ostr; ostr << "Tree_" << typeNameAsString(); for (size_t i = 1, N = dims.size(); i < N; ++i) { // start from 1 to skip the RootNode ostr << "_" << dims[i]; } Name* s = new Name(ostr.str()); if (sTypeName.compare_and_swap(s, NULL) != NULL) delete s; } return *sTypeName; } template template inline bool Tree::hasSameTopology(const Tree& other) const { return mRoot.hasSameTopology(other.getRootNode()); } template Index64 Tree::inactiveVoxelCount() const { Coord dim(0, 0, 0); this->evalActiveVoxelDim(dim); const Index64 totalVoxels = dim.x() * dim.y() * dim.z(), activeVoxels = this->activeVoxelCount(); assert(totalVoxels >= activeVoxels); return totalVoxels - activeVoxels; } template inline bool Tree::evalLeafBoundingBox(CoordBBox& bbox) const { bbox.reset(); // default invalid bbox if (this->empty()) return false; // empty mRoot.evalActiveBoundingBox(bbox, false); return true;// not empty } template inline bool Tree::evalActiveVoxelBoundingBox(CoordBBox& bbox) const { bbox.reset(); // default invalid bbox if (this->empty()) return false; // empty mRoot.evalActiveBoundingBox(bbox, true); return true;// not empty } template inline bool Tree::evalActiveVoxelDim(Coord& dim) const { CoordBBox bbox; bool notEmpty = this->evalActiveVoxelBoundingBox(bbox); dim = bbox.extents(); return notEmpty; } template inline bool Tree::evalLeafDim(Coord& dim) const { CoordBBox bbox; bool notEmpty = this->evalLeafBoundingBox(bbox); dim = bbox.extents(); return notEmpty; } template inline void Tree::evalMinMax(ValueType& minVal, ValueType& maxVal) const { minVal = maxVal = zeroVal(); if (ValueOnCIter iter = this->cbeginValueOn()) { minVal = maxVal = *iter; for (++iter; iter; ++iter) { const ValueType& val = *iter; if (val < minVal) minVal = val; if (val > maxVal) maxVal = val; } } } template inline void Tree::getNodeLog2Dims(std::vector& dims) { dims.clear(); RootNodeType::getNodeLog2Dims(dims); } template inline void Tree::print(std::ostream& os, int verboseLevel) const { if (verboseLevel <= 0) return; struct OnExit { std::ostream& os; std::streamsize savedPrecision; OnExit(std::ostream& os): os(os), savedPrecision(os.precision()) {} ~OnExit() { os.precision(savedPrecision); } }; OnExit restorePrecision(os); std::vector dims; Tree::getNodeLog2Dims(dims); std::vector nodeCount; os << "Information about Tree:\n" << " Type: " << this->type() << "\n"; os << " Configuration:\n"; if (verboseLevel <= 1) { // Print node types and sizes. os << " Root(" << mRoot.getTableSize() << ")"; if (dims.size() > 1) { for (size_t i = 1, N = dims.size() - 1; i < N; ++i) { os << ", Internal(" << (1 << dims[i]) << "^3)"; } os << ", Leaf(" << (1 << *dims.rbegin()) << "^3)\n"; } } else { // Print node types, counts and sizes. nodeCount.resize(dims.size()); for (NodeCIter it = cbeginNode(); it; ++it) { ++(nodeCount[it.getDepth()]); } os << " Root(1 x " << mRoot.getTableSize() << ")"; if (dims.size() > 1) { for (size_t i = 1, N = dims.size() - 1; i < N; ++i) { os << ", Internal(" << util::formattedInt(nodeCount[i]); os << " x " << (1 << dims[i]) << "^3)"; } os << ", Leaf(" << util::formattedInt(*nodeCount.rbegin()); os << " x " << (1 << *dims.rbegin()) << "^3)\n"; } } os << " Background value: " << mRoot.background() << "\n"; if (verboseLevel == 1) return; // The following is tree information that is expensive to extract. if (nodeCount.empty()) { nodeCount.resize(dims.size()); for (NodeCIter it = cbeginNode(); it; ++it) { ++(nodeCount[it.getDepth()]); } } // Statistics of topology and values ValueType minVal, maxVal; this->evalMinMax(minVal, maxVal); os << " Min value: " << minVal << "\n"; os << " Max value: " << maxVal << "\n"; const uint64_t leafCount = *nodeCount.rbegin(), numActiveVoxels = this->activeVoxelCount(), numActiveLeafVoxels = this->activeLeafVoxelCount(); os << " Number of active voxels: " << util::formattedInt(numActiveVoxels) << "\n"; Coord dim(0, 0, 0); uint64_t totalVoxels = 0; if (numActiveVoxels) { // nonempty CoordBBox bbox; this->evalActiveVoxelBoundingBox(bbox); dim = bbox.extents(); totalVoxels = dim.x() * uint64_t(dim.y()) * dim.z(); os << " Bounding box of active voxels: " << bbox << "\n"; os << " Dimensions of active voxels: " << dim[0] << " x " << dim[1] << " x " << dim[2] << "\n"; const double activeRatio = (100.0 * numActiveVoxels) / totalVoxels; os << " Percentage of active voxels: " << std::setprecision(3) << activeRatio << "%\n"; if (leafCount>0) { const double fillRatio = (100.0 * numActiveLeafVoxels) / (leafCount * LeafNodeType::NUM_VOXELS); os << " Average leaf node fill ratio: " << fillRatio << "%\n"; } } else { os << " Tree is empty!\n"; } os << std::flush; if (verboseLevel == 2) return; // Memory footprint in bytes const uint64_t actualMem = this->memUsage(), denseMem = sizeof(ValueType) * totalVoxels, voxelsMem = sizeof(ValueType) * numActiveLeafVoxels; ///< @todo not accurate for BoolTree (and probably should count tile values) os << "Memory footprint:\n"; util::printBytes(os, actualMem, " Actual footprint: "); util::printBytes(os, voxelsMem, " Voxel footprint: "); if (numActiveVoxels) { util::printBytes(os, denseMem, " Dense* footprint: "); os << " Actual footprint is " << (100.0 * actualMem / denseMem) << "% of dense* footprint\n"; os << " Leaf voxel footprint is " << (100.0 * voxelsMem / actualMem) << "% of actual footprint\n"; os << " *Dense refers to the smallest equivalent non-sparse volume" << std::endl; } } } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_TREE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/ValueAccessor.h0000644000000000000000000030405712252453157015077 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file ValueAccessor.h /// /// When traversing a grid in a spatially coherent pattern (e.g., iterating /// over neighboring voxels), request a @c ValueAccessor from the grid /// (with Grid::getAccessor()) and use the accessor's @c getValue() and /// @c setValue() methods. These will typically be significantly faster /// than accessing voxels directly in the grid's tree. /// /// @par Example: /// /// @code /// FloatGrid grid; /// FloatGrid::Accessor acc = grid.getAccessor(); /// // First access is slow: /// acc.setValue(Coord(0, 0, 0), 100); /// // Subsequent nearby accesses are fast, since the accessor now holds pointers /// // to nodes that contain (0, 0, 0) along the path from the root of the grid's /// // tree to the leaf: /// acc.setValue(Coord(0, 0, 1), 100); /// acc.getValue(Coord(0, 2, 0), 100); /// // Slow, because the accessor must be repopulated: /// acc.getValue(Coord(-1, -1, -1)); /// // Fast: /// acc.getValue(Coord(-1, -1, -2)); /// acc.setValue(Coord(-1, -2, 0), -100); /// @endcode #ifndef OPENVDB_TREE_VALUEACCESSOR_HAS_BEEN_INCLUDED #define OPENVDB_TREE_VALUEACCESSOR_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { // Forward declarations of local classes that are not intended for general use template class ValueAccessor0; template class ValueAccessor1; template class ValueAccessor2; template class ValueAccessor3; template class CacheItem; /// @brief This base class for ValueAccessors manages registration of an accessor /// with a tree so that the tree can automatically clear the accessor whenever /// one of its nodes is deleted. /// @internal A base class is needed because ValueAccessor is templated on both /// a Tree type and a mutex type. The various instantiations of the template /// are distinct, unrelated types, so they can't easily be stored in a container /// such as the Tree's CacheRegistry. This base class, in contrast, is templated /// only on the Tree type, so for any given Tree, only two distinct instantiations /// are possible, ValueAccessorBase and ValueAccessorBase. template class ValueAccessorBase { public: static const bool IsConstTree = boost::is_const::value; ValueAccessorBase(TreeType& tree): mTree(&tree) { tree.attachAccessor(*this); } virtual ~ValueAccessorBase() { if (mTree) mTree->releaseAccessor(*this); } /// @brief Return a pointer to the tree associated with this accessor. /// @details The pointer will be null only if the tree from which this accessor /// was constructed was subsequently deleted (which generally leaves the /// accessor in an unsafe state). TreeType* getTree() const { return mTree; } /// Return a reference to the tree associated with this accessor. TreeType& tree() const { assert(mTree); return *mTree; } ValueAccessorBase(const ValueAccessorBase& other): mTree(other.mTree) { if (mTree) mTree->attachAccessor(*this); } ValueAccessorBase& operator=(const ValueAccessorBase& other) { if (&other != this) { if (mTree) mTree->releaseAccessor(*this); mTree = other.mTree; if (mTree) mTree->attachAccessor(*this); } return *this; } virtual void clear() = 0; protected: // Allow trees to deregister themselves. template friend class Tree; virtual void release() { mTree = NULL; } TreeType* mTree; }; // class ValueAccessorBase //////////////////////////////////////// /// When traversing a grid in a spatially coherent pattern (e.g., iterating /// over neighboring voxels), request a @c ValueAccessor from the grid /// (with Grid::getAccessor()) and use the accessor's @c getValue() and /// @c setValue() methods. These will typically be significantly faster /// than accessing voxels directly in the grid's tree. /// /// A ValueAccessor caches pointers to tree nodes along the path to a voxel (x, y, z). /// A subsequent access to voxel (x', y', z') starts from the cached leaf node and /// moves up until a cached node that encloses (x', y', z') is found, then traverses /// down the tree from that node to a leaf, updating the cache with the new path. /// This leads to significant acceleration of spatially-coherent accesses. /// /// @param _TreeType the type of the tree to be accessed [required] /// @param CacheLevels the number of nodes to be cached, starting from the leaf level /// and not including the root (i.e., CacheLevels < DEPTH), /// and defaulting to all non-root nodes /// @param MutexType the type of mutex to use (see note) /// /// @note If @c MutexType is a TBB-compatible mutex, then multiple threads may /// safely access a single, shared accessor. However, it is highly recommended /// that, instead, each thread be assigned its own, non-mutex-protected accessor. template class ValueAccessor: public ValueAccessorBase<_TreeType> { public: BOOST_STATIC_ASSERT(CacheLevels < _TreeType::DEPTH); typedef _TreeType TreeType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef typename RootNodeT::ValueType ValueType; typedef ValueAccessorBase BaseT; typedef typename MutexType::scoped_lock LockT; using BaseT::IsConstTree; ValueAccessor(TreeType& tree): BaseT(tree), mCache(*this) { mCache.insert(Coord(), &tree.root()); } ValueAccessor(const ValueAccessor& other): BaseT(other), mCache(*this, other.mCache) {} ValueAccessor& operator=(const ValueAccessor& other) { if (&other != this) { this->BaseT::operator=(other); mCache.copy(*this, other.mCache); } return *this; } virtual ~ValueAccessor() {} /// Return the number of cache levels employed by this accessor. static Index numCacheLevels() { return CacheLevels; } /// Return @c true if nodes along the path to the given voxel have been cached. bool isCached(const Coord& xyz) const { LockT lock(mMutex); return mCache.isCached(xyz); } /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const { LockT lock(mMutex); return mCache.getValue(xyz); } /// Return the active state of the voxel at the given coordinates. bool isValueOn(const Coord& xyz) const { LockT lock(mMutex); return mCache.isValueOn(xyz); } /// Return the active state of the voxel as well as its value bool probeValue(const Coord& xyz, ValueType& value) const { LockT lock(mMutex); return mCache.probeValue(xyz,value); } /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is /// implicitly a background voxel). int getValueDepth(const Coord& xyz) const { LockT lock(mMutex); return mCache.getValueDepth(xyz); } /// Return @c true if the value of voxel (x, y, z) resides at the leaf level /// of the tree, i.e., if it is not a tile value. bool isVoxel(const Coord& xyz) const { LockT lock(mMutex); return mCache.isVoxel(xyz); } //@{ /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value) { LockT lock(mMutex); mCache.setValue(xyz, value); } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } //@} /// Set the value of the voxel at the given coordinate but don't change its active state. void setValueOnly(const Coord& xyz, const ValueType& value) { LockT lock(mMutex); mCache.setValueOnly(xyz, value); } /// Set the value of the voxel at the given coordinates and mark the voxel /// as active. [Experimental] void newSetValue(const Coord& xyz, const ValueType& value) { LockT lock(mMutex); mCache.newSetValue(xyz, value); } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value) { LockT lock(mMutex); mCache.setValueOff(xyz, value); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details See Tree::modifyValue() for details. template void modifyValue(const Coord& xyz, const ModifyOp& op) { LockT lock(mMutex); mCache.modifyValue(xyz, op); } /// @brief Apply a functor to the voxel at the given coordinates. /// @details See Tree::modifyValueAndActiveState() for details. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { LockT lock(mMutex); mCache.modifyValueAndActiveState(xyz, op); } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on = true) { LockT lock(mMutex); mCache.setActiveState(xyz, on); } /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } /// Return the cached node of type @a NodeType. [Mainly for internal use] template NodeType* getNode() { LockT lock(mMutex); NodeType* node = NULL; mCache.getNode(node); return node; } /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). [Mainly for internal use] template void insertNode(const Coord& xyz, NodeType& node) { LockT lock(mMutex); mCache.insert(xyz, &node); } /// If a node of the given type exists in the cache, remove it, so that /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in /// that node. [Mainly for internal use] template void eraseNode() { LockT lock(mMutex); NodeType* node = NULL; mCache.erase(node); } /// @brief Add the specified leaf to this tree, possibly creating a child branch /// in the process. If the leaf node already exists, replace it. void addLeaf(LeafNodeT* leaf) { LockT lock(mMutex); mCache.addLeaf(leaf); } /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), /// possibly deleting existing nodes or creating new nodes in the process. void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { LockT lock(mMutex); mCache.addTile(level, xyz, value, state); } /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). /// If no such node exists, create one, but preserve the values and /// active states of all voxels. /// @details Use this method to preallocate a static tree topology /// over which to safely perform multithreaded processing. LeafNodeT* touchLeaf(const Coord& xyz) { LockT lock(mMutex); return mCache.touchLeaf(xyz); } //@{ /// @brief Return a pointer to the node of the specified type that contains /// voxel (x, y, z), or NULL if no such node exists. template NodeT* probeNode(const Coord& xyz) { LockT lock(mMutex); return mCache.template probeNode(xyz); } template const NodeT* probeConstNode(const Coord& xyz) const { LockT lock(mMutex); return mCache.template probeConstNode(xyz); } template const NodeT* probeNode(const Coord& xyz) const { return this->template probeConstNode(xyz); } //@} //@{ /// @brief Return a pointer to the leaf node that contains voxel (x, y, z), /// or NULL if no such node exists. LeafNodeT* probeLeaf(const Coord& xyz) { LockT lock(mMutex); return mCache.probeLeaf(xyz); } const LeafNodeT* probeConstLeaf(const Coord& xyz) const { LockT lock(mMutex); return mCache.probeConstLeaf(xyz); } const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } //@} /// Remove all nodes from this cache, then reinsert the root node. virtual void clear() { LockT lock(mMutex); mCache.clear(); if (this->mTree) mCache.insert(Coord(), &(this->mTree->root())); } private: // Allow nodes to insert themselves into the cache. template friend class RootNode; template friend class InternalNode; template friend class LeafNode; // Allow trees to deregister themselves. template friend class Tree; /// Prevent this accessor from calling Tree::releaseCache() on a tree that /// no longer exists. (Called by mTree when it is destroyed.) virtual void release() { LockT lock(mMutex); this->BaseT::release(); mCache.clear(); } /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). /// @note This operation is not mutex-protected and is intended to be called /// only by nodes and only in the context of a getValue() or setValue() call. template void insert(const Coord& xyz, NodeType* node) { mCache.insert(xyz, node); } // Define a list of all tree node types from LeafNode to RootNode typedef typename RootNodeT::NodeChainType InvTreeT; // Remove all tree node types that are excluded from the cache typedef typename boost::mpl::begin::type BeginT; typedef typename boost::mpl::advance >::type FirstT; typedef typename boost::mpl::find::type LastT; typedef typename boost::mpl::erase::type SubtreeT; typedef CacheItem::value==1> CacheItemT; // Private member data mutable CacheItemT mCache; mutable MutexType mMutex; }; // class ValueAccessor /// @brief Template specialization of the ValueAccessor with no mutex and no cache levels /// @details This specialization is provided mainly for benchmarking. /// Accessors with caching will almost always be faster. template struct ValueAccessor: public ValueAccessor0 { ValueAccessor(TreeType& tree): ValueAccessor0(tree) {} ValueAccessor(const ValueAccessor& other): ValueAccessor0(other) {} virtual ~ValueAccessor() {} }; /// Template specialization of the ValueAccessor with no mutex and one cache level template struct ValueAccessor: public ValueAccessor1 { ValueAccessor(TreeType& tree): ValueAccessor1(tree) {} ValueAccessor(const ValueAccessor& other): ValueAccessor1(other) {} virtual ~ValueAccessor() {} }; /// Template specialization of the ValueAccessor with no mutex and two cache levels template struct ValueAccessor: public ValueAccessor2 { ValueAccessor(TreeType& tree): ValueAccessor2(tree) {} ValueAccessor(const ValueAccessor& other): ValueAccessor2(other) {} virtual ~ValueAccessor() {} }; /// Template specialization of the ValueAccessor with no mutex and three cache levels template struct ValueAccessor: public ValueAccessor3 { ValueAccessor(TreeType& tree): ValueAccessor3(tree) {} ValueAccessor(const ValueAccessor& other): ValueAccessor3(other) {} virtual ~ValueAccessor() {} }; //////////////////////////////////////// /// @brief This accessor is thread-safe (at the cost of speed) for both reading and /// writing to a tree. That is, multiple threads may safely access a single, /// shared ValueAccessorRW. /// /// @warning Since the mutex-locking employed by the ValueAccessorRW /// can seriously impair performance of multithreaded applications, it /// is recommended that, instead, each thread be assigned its own /// (non-mutex protected) accessor. template struct ValueAccessorRW: public ValueAccessor { ValueAccessorRW(TreeType& tree) : ValueAccessor(tree) { } }; //////////////////////////////////////// // // The classes below are for internal use and should rarely be used directly. // // An element of a compile-time linked list of node pointers, ordered from LeafNode to RootNode template class CacheItem { public: typedef typename boost::mpl::front::type NodeType; typedef typename NodeType::ValueType ValueType; typedef typename NodeType::LeafNodeType LeafNodeType; typedef std::numeric_limits CoordLimits; CacheItem(TreeCacheT& parent): mParent(&parent), mHash(CoordLimits::max()), mNode(NULL), mNext(parent) { } //@{ /// Copy another CacheItem's node pointers and hash keys, but not its parent pointer. CacheItem(TreeCacheT& parent, const CacheItem& other): mParent(&parent), mHash(other.mHash), mNode(other.mNode), mNext(parent, other.mNext) { } CacheItem& copy(TreeCacheT& parent, const CacheItem& other) { mParent = &parent; mHash = other.mHash; mNode = other.mNode; mNext.copy(parent, other.mNext); return *this; } //@} bool isCached(const Coord& xyz) const { return (this->isHashed(xyz) || mNext.isCached(xyz)); } /// Cache the given node at this level. void insert(const Coord& xyz, const NodeType* node) { mHash = (node != NULL) ? xyz & ~(NodeType::DIM-1) : Coord::max(); mNode = node; } /// Forward the given node to another level of the cache. template void insert(const Coord& xyz, const OtherNodeType* node) { mNext.insert(xyz, node); } /// Erase the node at this level. void erase(const NodeType*) { mHash = Coord::max(); mNode = NULL; } /// Erase the node at another level of the cache. template void erase(const OtherNodeType* node) { mNext.erase(node); } /// Erase the nodes at this and lower levels of the cache. void clear() { mHash = Coord::max(); mNode = NULL; mNext.clear(); } /// Return the cached node (if any) at this level. void getNode(const NodeType*& node) const { node = mNode; } void getNode(const NodeType*& node) { node = mNode; } void getNode(NodeType*& node) { // This combination of a static assertion and a const_cast might not be elegant, // but it is a lot simpler than specializing TreeCache for const Trees. BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); node = const_cast(mNode); } /// Forward the request to another level of the cache. template void getNode(OtherNodeType*& node) { mNext.getNode(node); } /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) { if (this->isHashed(xyz)) { assert(mNode); return mNode->getValueAndCache(xyz, *mParent); } return mNext.getValue(xyz); } void addLeaf(LeafNodeType* leaf) { BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); if (NodeType::LEVEL == 0) return; if (this->isHashed(leaf->origin())) { assert(mNode); return const_cast(mNode)->addLeafAndCache(leaf, *mParent); } mNext.addLeaf(leaf); } void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); if (NodeType::LEVEL < level) return; if (this->isHashed(xyz)) { assert(mNode); return const_cast(mNode)->addTileAndCache( level, xyz, value, state, *mParent); } mNext.addTile(level, xyz, value, state); } LeafNodeType* touchLeaf(const Coord& xyz) { BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode); return const_cast(mNode)->touchLeafAndCache(xyz, *mParent); } return mNext.touchLeaf(xyz); } LeafNodeType* probeLeaf(const Coord& xyz) { BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode); return const_cast(mNode)->probeLeafAndCache(xyz, *mParent); } return mNext.probeLeaf(xyz); } const LeafNodeType* probeConstLeaf(const Coord& xyz) { if (this->isHashed(xyz)) { assert(mNode); return mNode->probeConstLeafAndCache(xyz, *mParent); } return mNext.probeConstLeaf(xyz); } template NodeT* probeNode(const Coord& xyz) { BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (this->isHashed(xyz)) { if ((boost::is_same::value)) { assert(mNode); return reinterpret_cast(const_cast(mNode)); } return const_cast(mNode)->template probeNodeAndCache(xyz, *mParent); } return mNext.template probeNode(xyz); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template const NodeT* probeConstNode(const Coord& xyz) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (this->isHashed(xyz)) { if ((boost::is_same::value)) { assert(mNode); return reinterpret_cast(mNode); } return mNode->template probeConstNodeAndCache(xyz, *mParent); } return mNext.template probeConstNode(xyz); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// Return the active state of the voxel at the given coordinates. bool isValueOn(const Coord& xyz) { if (this->isHashed(xyz)) { assert(mNode); return mNode->isValueOnAndCache(xyz, *mParent); } return mNext.isValueOn(xyz); } /// Return the active state and value of the voxel at the given coordinates. bool probeValue(const Coord& xyz, ValueType& value) { if (this->isHashed(xyz)) { assert(mNode); return mNode->probeValueAndCache(xyz, value, *mParent); } return mNext.probeValue(xyz, value); } int getValueDepth(const Coord& xyz) { if (this->isHashed(xyz)) { assert(mNode); return static_cast(TreeCacheT::RootNodeT::LEVEL) - static_cast(mNode->getValueLevelAndCache(xyz, *mParent)); } else { return mNext.getValueDepth(xyz); } } bool isVoxel(const Coord& xyz) { if (this->isHashed(xyz)) { assert(mNode); return mNode->getValueLevelAndCache(xyz, *mParent)==0; } else { return mNext.isVoxel(xyz); } } /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value) { if (this->isHashed(xyz)) { assert(mNode); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mNode)->setValueAndCache(xyz, value, *mParent); } else { mNext.setValue(xyz, value); } } void setValueOnly(const Coord& xyz, const ValueType& value) { if (this->isHashed(xyz)) { assert(mNode); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mNode)->setValueOnlyAndCache(xyz, value, *mParent); } else { mNext.setValueOnly(xyz, value); } } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details See Tree::modifyValue() for details. template void modifyValue(const Coord& xyz, const ModifyOp& op) { if (this->isHashed(xyz)) { assert(mNode); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mNode)->modifyValueAndCache(xyz, op, *mParent); } else { mNext.modifyValue(xyz, op); } } /// @brief Apply a functor to the voxel at the given coordinates. /// @details See Tree::modifyValueAndActiveState() for details. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { if (this->isHashed(xyz)) { assert(mNode); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mNode)->modifyValueAndActiveStateAndCache(xyz, op, *mParent); } else { mNext.modifyValueAndActiveState(xyz, op); } } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value) { if (this->isHashed(xyz)) { assert(mNode); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mNode)->setValueOffAndCache(xyz, value, *mParent); } else { mNext.setValueOff(xyz, value); } } /// Set the active state of the voxel at the given coordinates. void setActiveState(const Coord& xyz, bool on) { if (this->isHashed(xyz)) { assert(mNode); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mNode)->setActiveStateAndCache(xyz, on, *mParent); } else { mNext.setActiveState(xyz, on); } } private: CacheItem(const CacheItem&); CacheItem& operator=(const CacheItem&); bool isHashed(const Coord& xyz) const { return (xyz[0] & ~Coord::ValueType(NodeType::DIM-1)) == mHash[0] && (xyz[1] & ~Coord::ValueType(NodeType::DIM-1)) == mHash[1] && (xyz[2] & ~Coord::ValueType(NodeType::DIM-1)) == mHash[2]; } TreeCacheT* mParent; Coord mHash; const NodeType* mNode; typedef typename boost::mpl::pop_front::type RestT; // NodeVecT minus its first item CacheItem::value == 1> mNext; };// end of CacheItem /// The tail of a compile-time list of cached node pointers, ordered from LeafNode to RootNode template class CacheItem { public: typedef typename boost::mpl::front::type RootNodeType; typedef typename RootNodeType::ValueType ValueType; typedef typename RootNodeType::LeafNodeType LeafNodeType; CacheItem(TreeCacheT& parent): mParent(&parent), mRoot(NULL) {} CacheItem(TreeCacheT& parent, const CacheItem& other): mParent(&parent), mRoot(other.mRoot) {} CacheItem& copy(TreeCacheT& parent, const CacheItem& other) { mParent = &parent; mRoot = other.mRoot; return *this; } bool isCached(const Coord& xyz) const { return this->isHashed(xyz); } void insert(const Coord&, const RootNodeType* root) { mRoot = root; } // Needed for node types that are not cached template void insert(const Coord&, const OtherNodeType*) {} void erase(const RootNodeType*) { mRoot = NULL; } void clear() { mRoot = NULL; } void getNode(RootNodeType*& node) { BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); node = const_cast(mRoot); } void getNode(const RootNodeType*& node) const { node = mRoot; } void addLeaf(LeafNodeType* leaf) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mRoot)->addLeafAndCache(leaf, *mParent); } void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mRoot)->addTileAndCache(level, xyz, value, state, *mParent); } LeafNodeType* touchLeaf(const Coord& xyz) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); return const_cast(mRoot)->touchLeafAndCache(xyz, *mParent); } LeafNodeType* probeLeaf(const Coord& xyz) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); return const_cast(mRoot)->probeLeafAndCache(xyz, *mParent); } const LeafNodeType* probeConstLeaf(const Coord& xyz) { assert(mRoot); return mRoot->probeConstLeafAndCache(xyz, *mParent); } template NodeType* probeNode(const Coord& xyz) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); return const_cast(mRoot)->template probeNodeAndCache(xyz, *mParent); } template const NodeType* probeConstNode(const Coord& xyz) { assert(mRoot); return mRoot->template probeConstNodeAndCache(xyz, *mParent); } int getValueDepth(const Coord& xyz) { assert(mRoot); return mRoot->getValueDepthAndCache(xyz, *mParent); } bool isValueOn(const Coord& xyz) { assert(mRoot); return mRoot->isValueOnAndCache(xyz, *mParent); } bool probeValue(const Coord& xyz, ValueType& value) { assert(mRoot); return mRoot->probeValueAndCache(xyz, value, *mParent); } bool isVoxel(const Coord& xyz) { assert(mRoot); return mRoot->getValueDepthAndCache(xyz, *mParent) == static_cast(RootNodeType::LEVEL); } const ValueType& getValue(const Coord& xyz) { assert(mRoot); return mRoot->getValueAndCache(xyz, *mParent); } void setValue(const Coord& xyz, const ValueType& value) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mRoot)->setValueAndCache(xyz, value, *mParent); } void setValueOnly(const Coord& xyz, const ValueType& value) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mRoot)->setValueOnlyAndCache(xyz, value, *mParent); } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } template void modifyValue(const Coord& xyz, const ModifyOp& op) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mRoot)->modifyValueAndCache(xyz, op, *mParent); } template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mRoot)->modifyValueAndActiveStateAndCache(xyz, op, *mParent); } void setValueOff(const Coord& xyz, const ValueType& value) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mRoot)->setValueOffAndCache(xyz, value, *mParent); } void setActiveState(const Coord& xyz, bool on) { assert(mRoot); BOOST_STATIC_ASSERT(!TreeCacheT::IsConstTree); const_cast(mRoot)->setActiveStateAndCache(xyz, on, *mParent); } private: CacheItem(const CacheItem&); CacheItem& operator=(const CacheItem&); bool isHashed(const Coord&) const { return false; } TreeCacheT* mParent; const RootNodeType* mRoot; };// end of CacheItem specialized for RootNode //////////////////////////////////////// /// @brief ValueAccessor with no mutex and no node caching. /// @details This specialization is provided mainly for benchmarking. /// Accessors with caching will almost always be faster. template class ValueAccessor0: public ValueAccessorBase<_TreeType> { public: typedef _TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef ValueAccessorBase BaseT; ValueAccessor0(TreeType& tree): BaseT(tree) {} ValueAccessor0(const ValueAccessor0& other): BaseT(other) {} /// Return the number of cache levels employed by this accessor. static Index numCacheLevels() { return 0; } ValueAccessor0& operator=(const ValueAccessor0& other) { if (&other != this) this->BaseT::operator=(other); return *this; } virtual ~ValueAccessor0() {} /// Return @c true if nodes along the path to the given voxel have been cached. bool isCached(const Coord&) const { return false; } /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->getValue(xyz); } /// Return the active state of the voxel at the given coordinates. bool isValueOn(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->isValueOn(xyz); } /// Return the active state and, in @a value, the value of the voxel at the given coordinates. bool probeValue(const Coord& xyz, ValueType& value) const { assert(BaseT::mTree); return BaseT::mTree->probeValue(xyz, value); } /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is /// implicitly a background voxel). int getValueDepth(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->getValueDepth(xyz); } /// Return @c true if the value of voxel (x, y, z) resides at the leaf level /// of the tree, i.e., if it is not a tile value. bool isVoxel(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->getValueDepth(xyz) == static_cast(RootNodeT::LEVEL); } //@{ /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->setValue(xyz, value); } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } //@} /// Set the value of the voxel at the given coordinate but don't change its active state. void setValueOnly(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->setValueOnly(xyz, value); } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->root().setValueOff(xyz, value); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details See Tree::modifyValue() for details. template void modifyValue(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->modifyValue(xyz, op); } /// @brief Apply a functor to the voxel at the given coordinates. /// @details See Tree::modifyValueAndActiveState() for details. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->modifyValueAndActiveState(xyz, op); } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on = true) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->setActiveState(xyz, on); } /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } /// Return the cached node of type @a NodeType. [Mainly for internal use] template NodeT* getNode() { return NULL; } /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). [Mainly for internal use] template void insertNode(const Coord&, NodeT&) {} /// @brief Add the specified leaf to this tree, possibly creating a child branch /// in the process. If the leaf node already exists, replace it. void addLeaf(LeafNodeT* leaf) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->root().addLeaf(leaf); } /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), /// possibly deleting existing nodes or creating new nodes in the process. void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->root().addTile(level, xyz, value, state); } /// If a node of the given type exists in the cache, remove it, so that /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in /// that node. [Mainly for internal use] template void eraseNode() {} LeafNodeT* touchLeaf(const Coord& xyz) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); return BaseT::mTree->touchLeaf(xyz); } template NodeT* probeNode(const Coord& xyz) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); return BaseT::mTree->template probeNode(xyz); } template const NodeT* probeConstNode(const Coord& xyz) const { assert(BaseT::mTree); return BaseT::mTree->template probeConstNode(xyz); } LeafNodeT* probeLeaf(const Coord& xyz) { return this->template probeNode(xyz); } const LeafNodeT* probeConstLeaf(const Coord& xyz) const { return this->template probeConstNode(xyz); } const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } /// Remove all nodes from this cache, then reinsert the root node. virtual void clear() {} private: // Allow trees to deregister themselves. template friend class Tree; /// Prevent this accessor from calling Tree::releaseCache() on a tree that /// no longer exists. (Called by mTree when it is destroyed.) virtual void release() { this->BaseT::release(); } }; // ValueAccessor0 /// @brief Value accessor with one level of node caching. /// @details The node cache level is specified by L0 with the default value 0 /// (defined in the forward declaration) corresponding to a LeafNode. /// /// @note This class is for experts only and should rarely be used /// directly. Instead use ValueAccessor with its default template arguments. template class ValueAccessor1 : public ValueAccessorBase<_TreeType> { public: BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 2); BOOST_STATIC_ASSERT( L0 < _TreeType::RootNodeType::LEVEL ); typedef _TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef ValueAccessorBase BaseT; typedef typename RootNodeT::NodeChainType InvTreeT; typedef typename boost::mpl::at >::type NodeT0; /// Constructor from a tree ValueAccessor1(TreeType& tree) : BaseT(tree), mKey0(Coord::max()), mNode0(NULL) { } /// Copy constructor ValueAccessor1(const ValueAccessor1& other) : BaseT(other) { this->copy(other); } /// Return the number of cache levels employed by this ValueAccessor static Index numCacheLevels() { return 1; } /// Asignment operator ValueAccessor1& operator=(const ValueAccessor1& other) { if (&other != this) { this->BaseT::operator=(other); this->copy(other); } return *this; } /// Virtual destructor virtual ~ValueAccessor1() {} /// Return @c true if any of the nodes along the path to the given /// voxel have been cached. bool isCached(const Coord& xyz) const { assert(BaseT::mTree); return this->isHashed(xyz); } /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed(xyz)) { assert(mNode0); return mNode0->getValueAndCache(xyz, this->self()); } return BaseT::mTree->root().getValueAndCache(xyz, this->self()); } /// Return the active state of the voxel at the given coordinates. bool isValueOn(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed(xyz)) { assert(mNode0); return mNode0->isValueOnAndCache(xyz, this->self()); } return BaseT::mTree->root().isValueOnAndCache(xyz, this->self()); } /// Return the active state of the voxel as well as its value bool probeValue(const Coord& xyz, ValueType& value) const { assert(BaseT::mTree); if (this->isHashed(xyz)) { assert(mNode0); return mNode0->probeValueAndCache(xyz, value, this->self()); } return BaseT::mTree->root().probeValueAndCache(xyz, value, this->self()); } /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is /// implicitly a background voxel). int getValueDepth(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed(xyz)) { assert(mNode0); return RootNodeT::LEVEL - mNode0->getValueLevelAndCache(xyz, this->self()); } return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()); } /// Return @c true if the value of voxel (x, y, z) resides at the leaf level /// of the tree, i.e., if it is not a tile value. bool isVoxel(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed(xyz)) { assert(mNode0); return mNode0->getValueLevelAndCache(xyz, this->self()) == 0; } return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()) == static_cast(RootNodeT::LEVEL); } //@{ /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode0); const_cast(mNode0)->setValueAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueAndCache(xyz, value, *this); } } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } //@} /// Set the value of the voxel at the given coordinate but preserves its active state. void setValueOnly(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode0); const_cast(mNode0)->setValueOnlyAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueOnlyAndCache(xyz, value, *this); } } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode0); const_cast(mNode0)->setValueOffAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueOffAndCache(xyz, value, *this); } } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details See Tree::modifyValue() for details. template void modifyValue(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode0); const_cast(mNode0)->modifyValueAndCache(xyz, op, *this); } else { BaseT::mTree->root().modifyValueAndCache(xyz, op, *this); } } /// @brief Apply a functor to the voxel at the given coordinates. /// @details See Tree::modifyValueAndActiveState() for details. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode0); const_cast(mNode0)->modifyValueAndActiveStateAndCache(xyz, op, *this); } else { BaseT::mTree->root().modifyValueAndActiveStateAndCache(xyz, op, *this); } } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on = true) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode0); const_cast(mNode0)->setActiveStateAndCache(xyz, on, *this); } else { BaseT::mTree->root().setActiveStateAndCache(xyz, on, *this); } } /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } /// Return the cached node of type @a NodeType. [Mainly for internal use] template NodeT* getNode() { const NodeT* node = NULL; this->getNode(node); return const_cast(node); } /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). [Mainly for internal use] template void insertNode(const Coord& xyz, NodeT& node) { this->insert(xyz, &node); } /// If a node of the given type exists in the cache, remove it, so that /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in /// that node. [Mainly for internal use] template void eraseNode() { const NodeT* node = NULL; this->eraseNode(node); } /// @brief Add the specified leaf to this tree, possibly creating a child branch /// in the process. If the leaf node already exists, replace it. void addLeaf(LeafNodeT* leaf) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->root().addLeaf(leaf); } /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), /// possibly deleting existing nodes or creating new nodes in the process. void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); BaseT::mTree->root().addTile(level, xyz, value, state); } /// @brief @return the leaf node that contains voxel (x, y, z) and /// if it doesn't exist, create it, but preserve the values and /// active states of all voxels. /// /// Use this method to preallocate a static tree topology over which to /// safely perform multithreaded processing. LeafNodeT* touchLeaf(const Coord& xyz) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed(xyz)) { assert(mNode0); return const_cast(mNode0)->touchLeafAndCache(xyz, *this); } return BaseT::mTree->root().touchLeafAndCache(xyz, *this); } /// @brief @return a pointer to the node of the specified type that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. template NodeT* probeNode(const Coord& xyz) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if ((boost::is_same::value)) { if (this->isHashed(xyz)) { assert(mNode0); return reinterpret_cast(const_cast(mNode0)); } return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); } return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } LeafNodeT* probeLeaf(const Coord& xyz) { return this->template probeNode(xyz); } /// @brief @return a const pointer to the nodeof the specified type that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. template const NodeT* probeConstNode(const Coord& xyz) const { assert(BaseT::mTree); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if ((boost::is_same::value)) { if (this->isHashed(xyz)) { assert(mNode0); return reinterpret_cast(mNode0); } return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); } return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } const LeafNodeT* probeConstLeaf(const Coord& xyz) const { return this->template probeConstNode(xyz); } const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } /// Remove all the cached nodes and invalidate the corresponding hash-keys. virtual void clear() { mKey0 = Coord::max(); mNode0 = NULL; } private: // Allow nodes to insert themselves into the cache. template friend class RootNode; template friend class InternalNode; template friend class LeafNode; // Allow trees to deregister themselves. template friend class Tree; // This private method is merely for convenience. inline ValueAccessor1& self() const { return const_cast(*this); } void getNode(const NodeT0*& node) { node = mNode0; } void getNode(const RootNodeT*& node) { node = (BaseT::mTree ? &BaseT::mTree->root() : NULL); } template void getNode(const OtherNodeType*& node) { node = NULL; } void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = NULL; } template void eraseNode(const OtherNodeType*) {} /// Private copy method inline void copy(const ValueAccessor1& other) { mKey0 = other.mKey0; mNode0 = other.mNode0; } /// Prevent this accessor from calling Tree::releaseCache() on a tree that /// no longer exists. (Called by mTree when it is destroyed.) virtual void release() { this->BaseT::release(); this->clear(); } /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). /// @note This operation is not mutex-protected and is intended to be called /// only by nodes and only in the context of a getValue() or setValue() call. inline void insert(const Coord& xyz, const NodeT0* node) { assert(node); mKey0 = xyz & ~(NodeT0::DIM-1); mNode0 = node; } /// No-op in case a tree traversal attemps to insert a node that /// is not cached by the ValueAccessor template inline void insert(const Coord&, const OtherNodeType*) {} inline bool isHashed(const Coord& xyz) const { return (xyz[0] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[0] && (xyz[1] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[1] && (xyz[2] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[2]; } mutable Coord mKey0; mutable const NodeT0* mNode0; }; // ValueAccessor1 /// @brief Value accessor with two levels of node caching. /// @details The node cache levels are specified by L0 and L1 /// with the default values 0 and 1 (defined in the forward declaration) /// corresponding to a LeafNode and its parent InternalNode. /// /// @note This class is for experts only and should rarely be used directly. /// Instead use ValueAccessor with its default template arguments. template class ValueAccessor2 : public ValueAccessorBase<_TreeType> { public: BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 3); BOOST_STATIC_ASSERT( L0 < L1 && L1 < _TreeType::RootNodeType::LEVEL ); typedef _TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef ValueAccessorBase BaseT; typedef typename RootNodeT::NodeChainType InvTreeT; typedef typename boost::mpl::at >::type NodeT0; typedef typename boost::mpl::at >::type NodeT1; /// Constructor from a tree ValueAccessor2(TreeType& tree) : BaseT(tree), mKey0(Coord::max()), mNode0(NULL), mKey1(Coord::max()), mNode1(NULL) {} /// Copy constructor ValueAccessor2(const ValueAccessor2& other) : BaseT(other) { this->copy(other); } /// Return the number of cache levels employed by this ValueAccessor static Index numCacheLevels() { return 2; } /// Asignment operator ValueAccessor2& operator=(const ValueAccessor2& other) { if (&other != this) { this->BaseT::operator=(other); this->copy(other); } return *this; } /// Virtual destructor virtual ~ValueAccessor2() {} /// Return @c true if any of the nodes along the path to the given /// voxel have been cached. bool isCached(const Coord& xyz) const { assert(BaseT::mTree); return this->isHashed1(xyz) || this->isHashed0(xyz); } /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return mNode0->getValueAndCache(xyz, this->self()); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->getValueAndCache(xyz, this->self()); } return BaseT::mTree->root().getValueAndCache(xyz, this->self()); } /// Return the active state of the voxel at the given coordinates. bool isValueOn(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return mNode0->isValueOnAndCache(xyz, this->self()); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->isValueOnAndCache(xyz, this->self()); } return BaseT::mTree->root().isValueOnAndCache(xyz, this->self()); } /// Return the active state of the voxel as well as its value bool probeValue(const Coord& xyz, ValueType& value) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return mNode0->probeValueAndCache(xyz, value, this->self()); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->probeValueAndCache(xyz, value, this->self()); } return BaseT::mTree->root().probeValueAndCache(xyz, value, this->self()); } /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is /// implicitly a background voxel). int getValueDepth(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return RootNodeT::LEVEL - mNode0->getValueLevelAndCache(xyz, this->self()); } else if (this->isHashed1(xyz)) { assert(mNode1); return RootNodeT::LEVEL - mNode1->getValueLevelAndCache(xyz, this->self()); } return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()); } /// Return @c true if the value of voxel (x, y, z) resides at the leaf level /// of the tree, i.e., if it is not a tile value. bool isVoxel(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return mNode0->getValueLevelAndCache(xyz, this->self())==0; } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->getValueLevelAndCache(xyz, this->self())==0; } return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()) == static_cast(RootNodeT::LEVEL); } //@{ /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->setValueAndCache(xyz, value, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->setValueAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueAndCache(xyz, value, *this); } } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } //@} /// Set the value of the voxel at the given coordinate but preserves its active state. void setValueOnly(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->setValueOnlyAndCache(xyz, value, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->setValueOnlyAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueOnlyAndCache(xyz, value, *this); } } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->setValueOffAndCache(xyz, value, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->setValueOffAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueOffAndCache(xyz, value, *this); } } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details See Tree::modifyValue() for details. template void modifyValue(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->modifyValueAndCache(xyz, op, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->modifyValueAndCache(xyz, op, *this); } else { BaseT::mTree->root().modifyValueAndCache(xyz, op, *this); } } /// @brief Apply a functor to the voxel at the given coordinates. /// @details See Tree::modifyValueAndActiveState() for details. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->modifyValueAndActiveStateAndCache(xyz, op, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->modifyValueAndActiveStateAndCache(xyz, op, *this); } else { BaseT::mTree->root().modifyValueAndActiveStateAndCache(xyz, op, *this); } } /// Set the active state of the voxel at the given coordinates without changing its value. void setActiveState(const Coord& xyz, bool on = true) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->setActiveStateAndCache(xyz, on, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->setActiveStateAndCache(xyz, on, *this); } else { BaseT::mTree->root().setActiveStateAndCache(xyz, on, *this); } } /// Mark the voxel at the given coordinates as active without changing its value. void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } /// Mark the voxel at the given coordinates as inactive without changing its value. void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } /// Return the cached node of type @a NodeType. [Mainly for internal use] template NodeT* getNode() { const NodeT* node = NULL; this->getNode(node); return const_cast(node); } /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). [Mainly for internal use] template void insertNode(const Coord& xyz, NodeT& node) { this->insert(xyz, &node); } /// If a node of the given type exists in the cache, remove it, so that /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in /// that node. [Mainly for internal use] template void eraseNode() { const NodeT* node = NULL; this->eraseNode(node); } /// @brief Add the specified leaf to this tree, possibly creating a child branch /// in the process. If the leaf node already exists, replace it. void addLeaf(LeafNodeT* leaf) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed1(leaf->origin())) { assert(mNode1); return const_cast(mNode1)->addLeafAndCache(leaf, *this); } BaseT::mTree->root().addLeafAndCache(leaf, *this); } /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), /// possibly deleting existing nodes or creating new nodes in the process. void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed1(xyz)) { assert(mNode1); return const_cast(mNode1)->addTileAndCache(level, xyz, value, state, *this); } BaseT::mTree->root().addTileAndCache(level, xyz, value, state, *this); } /// @brief @return the leaf node that contains voxel (x, y, z) and /// if it doesn't exist, create it, but preserve the values and /// active states of all voxels. /// /// Use this method to preallocate a static tree topology over which to /// safely perform multithreaded processing. LeafNodeT* touchLeaf(const Coord& xyz) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); return const_cast(mNode0)->touchLeafAndCache(xyz, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); return const_cast(mNode1)->touchLeafAndCache(xyz, *this); } return BaseT::mTree->root().touchLeafAndCache(xyz, *this); } /// @brief @return a pointer to the node of the specified type that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. template NodeT* probeNode(const Coord& xyz) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if ((boost::is_same::value)) { if (this->isHashed0(xyz)) { assert(mNode0); return reinterpret_cast(const_cast(mNode0)); } else if (this->isHashed1(xyz)) { assert(mNode1); return const_cast(mNode1)->template probeNodeAndCache(xyz, *this); } return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); } else if ((boost::is_same::value)) { if (this->isHashed1(xyz)) { assert(mNode1); return reinterpret_cast(const_cast(mNode1)); } return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); } return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// @brief @return a pointer to the leaf node that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. LeafNodeT* probeLeaf(const Coord& xyz) { return this->template probeNode(xyz); } /// @brief @return a const pointer to the node of the specified type that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. template const NodeT* probeConstLeaf(const Coord& xyz) const { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if ((boost::is_same::value)) { if (this->isHashed0(xyz)) { assert(mNode0); return reinterpret_cast(mNode0); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->template probeConstNodeAndCache(xyz, this->self()); } return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); } else if ((boost::is_same::value)) { if (this->isHashed1(xyz)) { assert(mNode1); return reinterpret_cast(mNode1); } return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); } return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// @brief @return a const pointer to the leaf node that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. const LeafNodeT* probeConstLeaf(const Coord& xyz) const { return this->template probeConstNode(xyz); } const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } /// @brief @return a const pointer to the node of the specified type that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. template const NodeT* probeConstNode(const Coord& xyz) const { assert(BaseT::mTree); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if ((boost::is_same::value)) { if (this->isHashed0(xyz)) { assert(mNode0); return reinterpret_cast(mNode0); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->template probeConstNodeAndCache(xyz, this->self()); } return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); } else if ((boost::is_same::value)) { if (this->isHashed1(xyz)) { assert(mNode1); return reinterpret_cast(mNode1); } return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); } return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// Remove all the cached nodes and invalidate the corresponding hash-keys. virtual void clear() { mKey0 = Coord::max(); mNode0 = NULL; mKey1 = Coord::max(); mNode1 = NULL; } private: // Allow nodes to insert themselves into the cache. template friend class RootNode; template friend class InternalNode; template friend class LeafNode; // Allow trees to deregister themselves. template friend class Tree; // This private method is merely for convenience. inline ValueAccessor2& self() const { return const_cast(*this); } void getNode(const NodeT0*& node) { node = mNode0; } void getNode(const NodeT1*& node) { node = mNode1; } void getNode(const RootNodeT*& node) { node = (BaseT::mTree ? &BaseT::mTree->root() : NULL); } template void getNode(const OtherNodeType*& node) { node = NULL; } void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = NULL; } void eraseNode(const NodeT1*) { mKey1 = Coord::max(); mNode1 = NULL; } template void eraseNode(const OtherNodeType*) {} /// Private copy method inline void copy(const ValueAccessor2& other) { mKey0 = other.mKey0; mNode0 = other.mNode0; mKey1 = other.mKey1; mNode1 = other.mNode1; } /// Prevent this accessor from calling Tree::releaseCache() on a tree that /// no longer exists. (Called by mTree when it is destroyed.) virtual void release() { this->BaseT::release(); this->clear(); } /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). /// @note This operation is not mutex-protected and is intended to be called /// only by nodes and only in the context of a getValue() or setValue() call. inline void insert(const Coord& xyz, const NodeT0* node) { assert(node); mKey0 = xyz & ~(NodeT0::DIM-1); mNode0 = node; } inline void insert(const Coord& xyz, const NodeT1* node) { assert(node); mKey1 = xyz & ~(NodeT1::DIM-1); mNode1 = node; } /// No-op in case a tree traversal attemps to insert a node that /// is not cached by the ValueAccessor template inline void insert(const Coord&, const NodeT*) {} inline bool isHashed0(const Coord& xyz) const { return (xyz[0] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[0] && (xyz[1] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[1] && (xyz[2] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[2]; } inline bool isHashed1(const Coord& xyz) const { return (xyz[0] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[0] && (xyz[1] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[1] && (xyz[2] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[2]; } mutable Coord mKey0; mutable const NodeT0* mNode0; mutable Coord mKey1; mutable const NodeT1* mNode1; }; // ValueAccessor2 /// @brief Value accessor with three levels of node caching. /// @details The node cache levels are specified by L0, L1, and L2 /// with the default values 0, 1 and 2 (defined in the forward declaration) /// corresponding to a LeafNode, its parent InternalNode, and its parent InternalNode. /// Since the default configuration of all typed trees and grids, e.g., /// FloatTree or FloatGrid, has a depth of four, this value accessor is the one /// used by default. /// /// @note This class is for experts only and should rarely be used /// directly. Instead use ValueAccessor with its default template arguments template class ValueAccessor3 : public ValueAccessorBase<_TreeType> { public: BOOST_STATIC_ASSERT(_TreeType::DEPTH >= 4); BOOST_STATIC_ASSERT(L0 < L1 && L1 < L2 && L2 < _TreeType::RootNodeType::LEVEL); typedef _TreeType TreeType; typedef typename TreeType::ValueType ValueType; typedef typename TreeType::RootNodeType RootNodeT; typedef typename TreeType::LeafNodeType LeafNodeT; typedef ValueAccessorBase BaseT; typedef typename RootNodeT::NodeChainType InvTreeT; typedef typename boost::mpl::at >::type NodeT0; typedef typename boost::mpl::at >::type NodeT1; typedef typename boost::mpl::at >::type NodeT2; /// Constructor from a tree ValueAccessor3(TreeType& tree) : BaseT(tree), mKey0(Coord::max()), mNode0(NULL), mKey1(Coord::max()), mNode1(NULL), mKey2(Coord::max()), mNode2(NULL) {} /// Copy constructor ValueAccessor3(const ValueAccessor3& other) : BaseT(other) { this->copy(other); } /// Asignment operator ValueAccessor3& operator=(const ValueAccessor3& other) { if (&other != this) { this->BaseT::operator=(other); this->copy(other); } return *this; } /// Return the number of cache levels employed by this ValueAccessor static Index numCacheLevels() { return 3; } /// Virtual destructor virtual ~ValueAccessor3() {} /// Return @c true if any of the nodes along the path to the given /// voxel have been cached. bool isCached(const Coord& xyz) const { assert(BaseT::mTree); return this->isHashed2(xyz) || this->isHashed1(xyz) || this->isHashed0(xyz); } /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return mNode0->getValueAndCache(xyz, this->self()); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->getValueAndCache(xyz, this->self()); } else if (this->isHashed2(xyz)) { assert(mNode2); return mNode2->getValueAndCache(xyz, this->self()); } return BaseT::mTree->root().getValueAndCache(xyz, this->self()); } /// Return the active state of the voxel at the given coordinates. bool isValueOn(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return mNode0->isValueOnAndCache(xyz, this->self()); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->isValueOnAndCache(xyz, this->self()); } else if (this->isHashed2(xyz)) { assert(mNode2); return mNode2->isValueOnAndCache(xyz, this->self()); } return BaseT::mTree->root().isValueOnAndCache(xyz, this->self()); } /// Return the active state of the voxel as well as its value bool probeValue(const Coord& xyz, ValueType& value) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return mNode0->probeValueAndCache(xyz, value, this->self()); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->probeValueAndCache(xyz, value, this->self()); } else if (this->isHashed2(xyz)) { assert(mNode2); return mNode2->probeValueAndCache(xyz, value, this->self()); } return BaseT::mTree->root().probeValueAndCache(xyz, value, this->self()); } /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides, /// or -1 if (x, y, z) isn't explicitly represented in the tree (i.e., if it is /// implicitly a background voxel). int getValueDepth(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return RootNodeT::LEVEL - mNode0->getValueLevelAndCache(xyz, this->self()); } else if (this->isHashed1(xyz)) { assert(mNode1); return RootNodeT::LEVEL - mNode1->getValueLevelAndCache(xyz, this->self()); } else if (this->isHashed2(xyz)) { assert(mNode2); return RootNodeT::LEVEL - mNode2->getValueLevelAndCache(xyz, this->self()); } return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()); } /// Return @c true if the value of voxel (x, y, z) resides at the leaf level /// of the tree, i.e., if it is not a tile value. bool isVoxel(const Coord& xyz) const { assert(BaseT::mTree); if (this->isHashed0(xyz)) { assert(mNode0); return mNode0->getValueLevelAndCache(xyz, this->self())==0; } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->getValueLevelAndCache(xyz, this->self())==0; } else if (this->isHashed2(xyz)) { assert(mNode2); return mNode2->getValueLevelAndCache(xyz, this->self())==0; } return BaseT::mTree->root().getValueDepthAndCache(xyz, this->self()) == static_cast(RootNodeT::LEVEL); } //@{ /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->setValueAndCache(xyz, value, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->setValueAndCache(xyz, value, *this); } else if (this->isHashed2(xyz)) { assert(mNode2); const_cast(mNode2)->setValueAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueAndCache(xyz, value, *this); } } void setValueOn(const Coord& xyz, const ValueType& value) { this->setValue(xyz, value); } //@} /// Set the value of the voxel at the given coordinate but preserves its active state. void setValueOnly(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->setValueOnlyAndCache(xyz, value, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->setValueOnlyAndCache(xyz, value, *this); } else if (this->isHashed2(xyz)) { assert(mNode2); const_cast(mNode2)->setValueOnlyAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueOnlyAndCache(xyz, value, *this); } } /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->setValueOffAndCache(xyz, value, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->setValueOffAndCache(xyz, value, *this); } else if (this->isHashed2(xyz)) { assert(mNode2); const_cast(mNode2)->setValueOffAndCache(xyz, value, *this); } else { BaseT::mTree->root().setValueOffAndCache(xyz, value, *this); } } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @details See Tree::modifyValue() for details. template void modifyValue(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->modifyValueAndCache(xyz, op, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->modifyValueAndCache(xyz, op, *this); } else if (this->isHashed2(xyz)) { assert(mNode2); const_cast(mNode2)->modifyValueAndCache(xyz, op, *this); } else { BaseT::mTree->root().modifyValueAndCache(xyz, op, *this); } } /// @brief Apply a functor to the voxel at the given coordinates. /// @details See Tree::modifyValueAndActiveState() for details. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->modifyValueAndActiveStateAndCache(xyz, op, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->modifyValueAndActiveStateAndCache(xyz, op, *this); } else if (this->isHashed2(xyz)) { assert(mNode2); const_cast(mNode2)->modifyValueAndActiveStateAndCache(xyz, op, *this); } else { BaseT::mTree->root().modifyValueAndActiveStateAndCache(xyz, op, *this); } } /// Set the active state of the voxel at the given coordinates without changing its value. void setActiveState(const Coord& xyz, bool on = true) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); const_cast(mNode0)->setActiveStateAndCache(xyz, on, *this); } else if (this->isHashed1(xyz)) { assert(mNode1); const_cast(mNode1)->setActiveStateAndCache(xyz, on, *this); } else if (this->isHashed2(xyz)) { assert(mNode2); const_cast(mNode2)->setActiveStateAndCache(xyz, on, *this); } else { BaseT::mTree->root().setActiveStateAndCache(xyz, on, *this); } } /// Mark the voxel at the given coordinates as active without changing its value. void setValueOn(const Coord& xyz) { this->setActiveState(xyz, true); } /// Mark the voxel at the given coordinates as inactive without changing its value. void setValueOff(const Coord& xyz) { this->setActiveState(xyz, false); } /// Return the cached node of type @a NodeType. [Mainly for internal use] template NodeT* getNode() { const NodeT* node = NULL; this->getNode(node); return const_cast(node); } /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). [Mainly for internal use] template void insertNode(const Coord& xyz, NodeT& node) { this->insert(xyz, &node); } /// If a node of the given type exists in the cache, remove it, so that /// isCached(xyz) returns @c false for any voxel (x, y, z) contained in /// that node. [Mainly for internal use] template void eraseNode() { const NodeT* node = NULL; this->eraseNode(node); } /// @brief Add the specified leaf to this tree, possibly creating a child branch /// in the process. If the leaf node already exists, replace it. void addLeaf(LeafNodeT* leaf) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed1(leaf->origin())) { assert(mNode1); return const_cast(mNode1)->addLeafAndCache(leaf, *this); } else if (this->isHashed2(leaf->origin())) { assert(mNode2); return const_cast(mNode2)->addLeafAndCache(leaf, *this); } BaseT::mTree->root().addLeafAndCache(leaf, *this); } /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), /// possibly deleting existing nodes or creating new nodes in the process. void addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed1(xyz)) { assert(mNode1); return const_cast(mNode1)->addTileAndCache(level, xyz, value, state, *this); } if (this->isHashed2(xyz)) { assert(mNode2); return const_cast(mNode2)->addTileAndCache(level, xyz, value, state, *this); } BaseT::mTree->root().addTileAndCache(level, xyz, value, state, *this); } /// @brief @return the leaf node that contains voxel (x, y, z) and /// if it doesn't exist, create it, but preserve the values and /// active states of all voxels. /// /// Use this method to preallocate a static tree topology over which to /// safely perform multithreaded processing. LeafNodeT* touchLeaf(const Coord& xyz) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); if (this->isHashed0(xyz)) { assert(mNode0); return const_cast(mNode0); } else if (this->isHashed1(xyz)) { assert(mNode1); return const_cast(mNode1)->touchLeafAndCache(xyz, *this); } else if (this->isHashed2(xyz)) { assert(mNode2); return const_cast(mNode2)->touchLeafAndCache(xyz, *this); } return BaseT::mTree->root().touchLeafAndCache(xyz, *this); } /// @brief @return a pointer to the node of the specified type that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. template NodeT* probeNode(const Coord& xyz) { assert(BaseT::mTree); BOOST_STATIC_ASSERT(!BaseT::IsConstTree); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if ((boost::is_same::value)) { if (this->isHashed0(xyz)) { assert(mNode0); return reinterpret_cast(const_cast(mNode0)); } else if (this->isHashed1(xyz)) { assert(mNode1); return const_cast(mNode1)->template probeNodeAndCache(xyz, *this); } else if (this->isHashed2(xyz)) { assert(mNode2); return const_cast(mNode2)->template probeNodeAndCache(xyz, *this); } return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); } else if ((boost::is_same::value)) { if (this->isHashed1(xyz)) { assert(mNode1); return reinterpret_cast(const_cast(mNode1)); } else if (this->isHashed2(xyz)) { assert(mNode2); return const_cast(mNode2)->template probeNodeAndCache(xyz, *this); } return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); } else if ((boost::is_same::value)) { if (this->isHashed2(xyz)) { assert(mNode2); return reinterpret_cast(const_cast(mNode2)); } return BaseT::mTree->root().template probeNodeAndCache(xyz, *this); } return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// @brief @return a pointer to the leaf node that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. LeafNodeT* probeLeaf(const Coord& xyz) { return this->template probeNode(xyz); } /// @brief @return a const pointer to the node of the specified type that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. template const NodeT* probeConstNode(const Coord& xyz) const { assert(BaseT::mTree); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if ((boost::is_same::value)) { if (this->isHashed0(xyz)) { assert(mNode0); return reinterpret_cast(mNode0); } else if (this->isHashed1(xyz)) { assert(mNode1); return mNode1->template probeConstNodeAndCache(xyz, this->self()); } else if (this->isHashed2(xyz)) { assert(mNode2); return mNode2->template probeConstNodeAndCache(xyz, this->self()); } return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); } else if ((boost::is_same::value)) { if (this->isHashed1(xyz)) { assert(mNode1); return reinterpret_cast(mNode1); } else if (this->isHashed2(xyz)) { assert(mNode2); return mNode2->template probeConstNodeAndCache(xyz, this->self()); } return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); } else if ((boost::is_same::value)) { if (this->isHashed2(xyz)) { assert(mNode2); return reinterpret_cast(mNode2); } return BaseT::mTree->root().template probeConstNodeAndCache(xyz, this->self()); } return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// @brief @return a const pointer to the leaf node that contains /// voxel (x, y, z) and if it doesn't exist, return NULL. const LeafNodeT* probeConstLeaf(const Coord& xyz) const { return this->template probeConstNode(xyz); } const LeafNodeT* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } /// Remove all the cached nodes and invalidate the corresponding hash-keys. virtual void clear() { mKey0 = Coord::max(); mNode0 = NULL; mKey1 = Coord::max(); mNode1 = NULL; mKey2 = Coord::max(); mNode2 = NULL; } private: // Allow nodes to insert themselves into the cache. template friend class RootNode; template friend class InternalNode; template friend class LeafNode; // Allow trees to deregister themselves. template friend class Tree; // This private method is merely for convenience. inline ValueAccessor3& self() const { return const_cast(*this); } /// Private copy method inline void copy(const ValueAccessor3& other) { mKey0 = other.mKey0; mNode0 = other.mNode0; mKey1 = other.mKey1; mNode1 = other.mNode1; mKey2 = other.mKey2; mNode2 = other.mNode2; } /// Prevent this accessor from calling Tree::releaseCache() on a tree that /// no longer exists. (Called by mTree when it is destroyed.) virtual void release() { this->BaseT::release(); this->clear(); } void getNode(const NodeT0*& node) { node = mNode0; } void getNode(const NodeT1*& node) { node = mNode1; } void getNode(const NodeT2*& node) { node = mNode2; } void getNode(const RootNodeT*& node) { node = (BaseT::mTree ? &BaseT::mTree->root() : NULL); } template void getNode(const OtherNodeType*& node) { node = NULL; } void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = NULL; } void eraseNode(const NodeT1*) { mKey1 = Coord::max(); mNode1 = NULL; } void eraseNode(const NodeT2*) { mKey2 = Coord::max(); mNode2 = NULL; } template void eraseNode(const OtherNodeType*) {} /// Cache the given node, which should lie along the path from the root node to /// the node containing voxel (x, y, z). /// @note This operation is not mutex-protected and is intended to be called /// only by nodes and only in the context of a getValue() or setValue() call. inline void insert(const Coord& xyz, const NodeT0* node) { assert(node); mKey0 = xyz & ~(NodeT0::DIM-1); mNode0 = node; } inline void insert(const Coord& xyz, const NodeT1* node) { assert(node); mKey1 = xyz & ~(NodeT1::DIM-1); mNode1 = node; } inline void insert(const Coord& xyz, const NodeT2* node) { assert(node); mKey2 = xyz & ~(NodeT2::DIM-1); mNode2 = node; } /// No-op in case a tree traversal attemps to insert a node that /// is not cached by the ValueAccessor template inline void insert(const Coord&, const OtherNodeType*) { } inline bool isHashed0(const Coord& xyz) const { return (xyz[0] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[0] && (xyz[1] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[1] && (xyz[2] & ~Coord::ValueType(NodeT0::DIM-1)) == mKey0[2]; } inline bool isHashed1(const Coord& xyz) const { return (xyz[0] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[0] && (xyz[1] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[1] && (xyz[2] & ~Coord::ValueType(NodeT1::DIM-1)) == mKey1[2]; } inline bool isHashed2(const Coord& xyz) const { return (xyz[0] & ~Coord::ValueType(NodeT2::DIM-1)) == mKey2[0] && (xyz[1] & ~Coord::ValueType(NodeT2::DIM-1)) == mKey2[1] && (xyz[2] & ~Coord::ValueType(NodeT2::DIM-1)) == mKey2[2]; } mutable Coord mKey0; mutable const NodeT0* mNode0; mutable Coord mKey1; mutable const NodeT1* mNode1; mutable Coord mKey2; mutable const NodeT2* mNode2; }; // ValueAccessor3 } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_VALUEACCESSOR_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/LeafNode.h0000644000000000000000000020467712252453157014024 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_TREE_LEAFNODE_HAS_BEEN_INCLUDED #define OPENVDB_TREE_LEAFNODE_HAS_BEEN_INCLUDED #include #include // for std::swap #include // for std::memcpy() #include #include #include #include #include #include #include #include // for io::readData(), etc. #include "Iterator.h" #include "Util.h" class TestLeaf; template class TestLeafIO; namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// @brief Templated block class to hold specific data types and a fixed /// number of values determined by Log2Dim. The actual coordinate /// dimension of the block is 2^Log2Dim, i.e. Log2Dim=3 corresponds to /// a LeafNode that spans a 8^3 block. template class LeafNode { public: typedef T ValueType; typedef LeafNode LeafNodeType; typedef boost::shared_ptr Ptr; typedef util::NodeMask NodeMaskType; static const Index LOG2DIM = Log2Dim, // needed by parent nodes TOTAL = Log2Dim, // needed by parent nodes DIM = 1 << TOTAL, // dimension along one coordinate direction NUM_VALUES = 1 << 3 * Log2Dim, NUM_VOXELS = NUM_VALUES, // total number of voxels represented by this node SIZE = NUM_VALUES, LEVEL = 0; // level 0 = leaf /// @brief ValueConverter::Type is the type of a LeafNode having the same /// child hierarchy and dimensions as this node but a different value type, T. template struct ValueConverter { typedef LeafNode Type; }; /// @brief Stores the actual values in the LeafNode. Its dimension /// it fixed to 2^(3*Log2Dim) class Buffer { public: /// @brief Empty default constructor Buffer(): mData(new ValueType[SIZE]) {} /// @brief Constructs a buffer populated with the specified value Buffer(const ValueType& val) : mData(new ValueType[SIZE]) { this->fill(val); } /// @brief Copy constructor Buffer(const Buffer& other) : mData(new ValueType[SIZE]) { *this = other; } /// @brief Destructor ~Buffer() { delete [] mData; } /// @brief Populates the buffer with a constant value void fill(const ValueType& val) { ValueType* target = mData; Index n = SIZE; while (n--) *target++ = val; } /// Return a const reference to the i'th element of the Buffer const ValueType& getValue(Index i) const { assert(i < SIZE); return mData[i]; } /// Return a const reference to the i'th element of the Buffer const ValueType& operator[](Index i) const { return this->getValue(i); } /// Set the i'th value of the Buffer to the specified value void setValue(Index i, const ValueType& val) { assert(i < SIZE); mData[i] = val; } /// Assigns the values in the other Buffer to this Buffer Buffer& operator=(const Buffer& other) { ValueType* target = mData; const ValueType* source = other.mData; Index n = SIZE; while (n--) *target++ = *source++; return *this; } /// Return true if the values in the other buffer exactly /// equates the values in this buffer bool operator==(const Buffer& other) const { const ValueType* target = mData; const ValueType* source = other.mData; Index n = SIZE; while (n && math::isExactlyEqual(*target++, *source++)) --n; return n == 0; } /// Return true if any of the values in the other buffer do /// not exactly equate the values in this buffer bool operator!=(const Buffer& other) const { return !(other == *this); } /// Replace the values in this Buffer with the values in the other Buffer void swap(Buffer& other) { ValueType* tmp = mData; mData = other.mData; other.mData = tmp; } /// Return the memory-footprint of this Buffer in units of bytes static Index memUsage() { return sizeof(ValueType*) + SIZE * sizeof(ValueType); } /// Return the number of values represented in this Buffer static Index size() { return SIZE; } private: /// This direct access method is private since it makes /// assumptions about the implementations of the memory layout. ValueType& operator[](Index i) { assert(i < SIZE); return mData[i]; } friend class ::TestLeaf; // Allow the parent LeafNode to access this Buffer's data pointer. friend class LeafNode; ValueType* mData; }; // class Buffer /// Default constructor LeafNode(); /// @brief Constructor /// @param coords the grid index coordinates of a voxel /// @param value a value with which to fill the buffer /// @param active the active state to which to initialize all voxels explicit LeafNode(const Coord& coords, const ValueType& value = zeroVal(), bool active = false); /// Deep copy constructor LeafNode(const LeafNode&); /// Topology copy constructor template LeafNode(const LeafNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy); /// Topology copy constructor template LeafNode(const LeafNode& other, const ValueType& background, TopologyCopy); /// Destructor. ~LeafNode(); // // Statistics // /// Return log2 of the dimension of this LeafNode, e.g. 3 if dimensions are 8^3 static Index log2dim() { return Log2Dim; } /// Return the number of voxels in each coordinate dimension. static Index dim() { return DIM; } /// Return the total number of voxels represented by this LeafNode static Index size() { return SIZE; } /// Return the total number of voxels represented by this LeafNode static Index numValues() { return SIZE; } /// Return the level of this node, which by definition is zero for LeafNodes static Index getLevel() { return LEVEL; } /// Append the Log2Dim of this LeafNode to the specified vector static void getNodeLog2Dims(std::vector& dims) { dims.push_back(Log2Dim); } /// Return the dimension of child nodes of this LeafNode, which is one for voxels. static Index getChildDim() { return 1; } /// Return the leaf count for this node, which is one. static Index32 leafCount() { return 1; } /// Return the non-leaf count for this node, which is zero. static Index32 nonLeafCount() { return 0; } /// Return the number of voxels marked On. Index64 onVoxelCount() const { return mValueMask.countOn(); } /// Return the number of voxels marked Off. Index64 offVoxelCount() const { return mValueMask.countOff(); } Index64 onLeafVoxelCount() const { return onVoxelCount(); } Index64 offLeafVoxelCount() const { return offVoxelCount(); } static Index64 onTileCount() { return 0; } static Index64 offTileCount() { return 0; } /// Return @c true if this node has no active voxels. bool isEmpty() const { return mValueMask.isOff(); } /// Return @c true if this node contains only active voxels. bool isDense() const { return mValueMask.isOn(); } /// Return the memory in bytes occupied by this node. Index64 memUsage() const; /// Expand the given bounding box so that it includes this leaf node's active voxels. /// If visitVoxels is false this LeafNode will be approximated as dense, i.e. with all /// voxels active. Else the individual active voxels are visited to produce a tight bbox. void evalActiveBoundingBox(CoordBBox&, bool visitVoxels = true) const; OPENVDB_DEPRECATED void evalActiveVoxelBoundingBox(CoordBBox&) const; /// @brief Return the bounding box of this node, i.e., the full index space /// spanned by this leaf node. CoordBBox getNodeBoundingBox() const { return CoordBBox::createCube(mOrigin, DIM); } /// Set the grid index coordinates of this node's local origin. void setOrigin(const Coord& origin) { mOrigin = origin; } /// @brief Return the grid index coordinates of this node's local origin. /// @deprecated Use origin() instead. OPENVDB_DEPRECATED const Coord& getOrigin() const { return mOrigin; } //@{ /// Return the grid index coordinates of this node's local origin. const Coord& origin() const { return mOrigin; } void getOrigin(Coord& origin) const { origin = mOrigin; } void getOrigin(Int32& x, Int32& y, Int32& z) const { mOrigin.asXYZ(x, y, z); } //@} /// Return the linear table offset of the given global or local coordinates. static Index coordToOffset(const Coord& xyz); /// @brief Return the local coordinates for a linear table offset, /// where offset 0 has coordinates (0, 0, 0). static Coord offsetToLocalCoord(Index n); OPENVDB_DEPRECATED static void offsetToLocalCoord(Index n, Coord& xyz); /// Return the global coordinates for a linear table offset. Coord offsetToGlobalCoord(Index n) const; /// Return a string representation of this node. std::string str() const; /// @brief Return @c true if the given node (which may have a different @c ValueType /// than this node) has the same active value topology as this node. template bool hasSameTopology(const LeafNode* other) const; /// Check for buffer, state and origin equivalence. bool operator==(const LeafNode& other) const; bool operator!=(const LeafNode& other) const { return !(other == *this); } protected: typedef typename NodeMaskType::OnIterator MaskOnIterator; typedef typename NodeMaskType::OffIterator MaskOffIterator; typedef typename NodeMaskType::DenseIterator MaskDenseIterator; // Type tags to disambiguate template instantiations struct ValueOn {}; struct ValueOff {}; struct ValueAll {}; struct ChildOn {}; struct ChildOff {}; struct ChildAll {}; template struct ValueIter: // Derives from SparseIteratorBase, but can also be used as a dense iterator, // if MaskIterT is a dense mask iterator type. public SparseIteratorBase< MaskIterT, ValueIter, NodeT, ValueT> { typedef SparseIteratorBase BaseT; ValueIter() {} ValueIter(const MaskIterT& iter, NodeT* parent): BaseT(iter, parent) {} ValueT& getItem(Index pos) const { return this->parent().getValue(pos); } ValueT& getValue() const { return this->parent().getValue(this->pos()); } // Note: setItem() can't be called on const iterators. void setItem(Index pos, const ValueT& value) const { this->parent().setValueOnly(pos, value); } // Note: setValue() can't be called on const iterators. void setValue(const ValueT& value) const { this->parent().setValueOnly(this->pos(), value); } // Note: modifyItem() can't be called on const iterators. template void modifyItem(Index n, const ModifyOp& op) const { this->parent().modifyValue(n, op); } // Note: modifyValue() can't be called on const iterators. template void modifyValue(const ModifyOp& op) const { this->parent().modifyValue(this->pos(), op); } }; /// Leaf nodes have no children, so their child iterators have no get/set accessors. template struct ChildIter: public SparseIteratorBase, NodeT, ValueType> { ChildIter() {} ChildIter(const MaskIterT& iter, NodeT* parent): SparseIteratorBase< MaskIterT, ChildIter, NodeT, ValueType>(iter, parent) {} }; template struct DenseIter: public DenseIteratorBase< MaskDenseIterator, DenseIter, NodeT, /*ChildT=*/void, ValueT> { typedef DenseIteratorBase BaseT; typedef typename BaseT::NonConstValueType NonConstValueT; DenseIter() {} DenseIter(const MaskDenseIterator& iter, NodeT* parent): BaseT(iter, parent) {} bool getItem(Index pos, void*& child, NonConstValueT& value) const { value = this->parent().getValue(pos); child = NULL; return false; // no child } // Note: setItem() can't be called on const iterators. //void setItem(Index pos, void* child) const {} // Note: unsetItem() can't be called on const iterators. void unsetItem(Index pos, const ValueT& value) const { this->parent().setValueOnly(pos, value); } }; public: typedef ValueIter ValueOnIter; typedef ValueIter ValueOnCIter; typedef ValueIter ValueOffIter; typedef ValueIter ValueOffCIter; typedef ValueIter ValueAllIter; typedef ValueIter ValueAllCIter; typedef ChildIter ChildOnIter; typedef ChildIter ChildOnCIter; typedef ChildIter ChildOffIter; typedef ChildIter ChildOffCIter; typedef DenseIter ChildAllIter; typedef DenseIter ChildAllCIter; ValueOnCIter cbeginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } ValueOnCIter beginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } ValueOnIter beginValueOn() { return ValueOnIter(mValueMask.beginOn(), this); } ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } ValueOffCIter beginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } ValueOffIter beginValueOff() { return ValueOffIter(mValueMask.beginOff(), this); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mValueMask.beginDense(), this); } ValueAllCIter beginValueAll() const { return ValueAllCIter(mValueMask.beginDense(), this); } ValueAllIter beginValueAll() { return ValueAllIter(mValueMask.beginDense(), this); } ValueOnCIter cendValueOn() const { return ValueOnCIter(mValueMask.endOn(), this); } ValueOnCIter endValueOn() const { return ValueOnCIter(mValueMask.endOn(), this); } ValueOnIter endValueOn() { return ValueOnIter(mValueMask.endOn(), this); } ValueOffCIter cendValueOff() const { return ValueOffCIter(mValueMask.endOff(), this); } ValueOffCIter endValueOff() const { return ValueOffCIter(mValueMask.endOff(), this); } ValueOffIter endValueOff() { return ValueOffIter(mValueMask.endOff(), this); } ValueAllCIter cendValueAll() const { return ValueAllCIter(mValueMask.endDense(), this); } ValueAllCIter endValueAll() const { return ValueAllCIter(mValueMask.endDense(), this); } ValueAllIter endValueAll() { return ValueAllIter(mValueMask.endDense(), this); } // Note that [c]beginChildOn() and [c]beginChildOff() actually return end iterators, // because leaf nodes have no children. ChildOnCIter cbeginChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } ChildOnCIter beginChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } ChildOnIter beginChildOn() { return ChildOnIter(mValueMask.endOn(), this); } ChildOffCIter cbeginChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } ChildOffCIter beginChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } ChildOffIter beginChildOff() { return ChildOffIter(mValueMask.endOff(), this); } ChildAllCIter cbeginChildAll() const { return ChildAllCIter(mValueMask.beginDense(), this); } ChildAllCIter beginChildAll() const { return ChildAllCIter(mValueMask.beginDense(), this); } ChildAllIter beginChildAll() { return ChildAllIter(mValueMask.beginDense(), this); } ChildOnCIter cendChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } ChildOnCIter endChildOn() const { return ChildOnCIter(mValueMask.endOn(), this); } ChildOnIter endChildOn() { return ChildOnIter(mValueMask.endOn(), this); } ChildOffCIter cendChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } ChildOffCIter endChildOff() const { return ChildOffCIter(mValueMask.endOff(), this); } ChildOffIter endChildOff() { return ChildOffIter(mValueMask.endOff(), this); } ChildAllCIter cendChildAll() const { return ChildAllCIter(mValueMask.endDense(), this); } ChildAllCIter endChildAll() const { return ChildAllCIter(mValueMask.endDense(), this); } ChildAllIter endChildAll() { return ChildAllIter(mValueMask.endDense(), this); } // // Buffer management // /// @brief Exchange this node's data buffer with the given data buffer /// without changing the active states of the values. void swap(Buffer& other) { mBuffer.swap(other); } const Buffer& buffer() const { return mBuffer; } Buffer& buffer() { return mBuffer; } // // I/O methods // /// @brief Read in just the topology. /// @param is the stream from which to read /// @param fromHalf if true, floating-point input values are assumed to be 16-bit void readTopology(std::istream& is, bool fromHalf = false); /// @brief Write out just the topology. /// @param os the stream to which to write /// @param toHalf if true, output floating-point values as 16-bit half floats void writeTopology(std::ostream& os, bool toHalf = false) const; /// @brief Read buffers from a stream. /// @param is the stream from which to read /// @param fromHalf if true, floating-point input values are assumed to be 16-bit void readBuffers(std::istream& is, bool fromHalf = false); /// @brief Write buffers to a stream. /// @param os the stream to which to write /// @param toHalf if true, output floating-point values as 16-bit half floats void writeBuffers(std::ostream& os, bool toHalf = false) const; size_t streamingSize(bool toHalf = false) const; // // Accessor methods // /// Return the value of the voxel at the given coordinates. const ValueType& getValue(const Coord& xyz) const; /// Return the value of the voxel at the given linear offset. const ValueType& getValue(Index offset) const; /// @brief Return @c true if the voxel at the given coordinates is active. /// @param xyz the coordinates of the voxel to be probed /// @param[out] val the value of the voxel at the given coordinates bool probeValue(const Coord& xyz, ValueType& val) const; /// @brief Return @c true if the voxel at the given offset is active. /// @param offset the linear offset of the voxel to be probed /// @param[out] val the value of the voxel at the given coordinates bool probeValue(Index offset, ValueType& val) const; /// Return the level (i.e., 0) at which leaf node values reside. static Index getValueLevel(const Coord&) { return LEVEL; } /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on); /// Set the active state of the voxel at the given offset but don't change its value. void setActiveState(Index offset, bool on) { assert(offsetsetValueOn(LeafNode::coordToOffset(xyz), val); } /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValue(const Coord& xyz, const ValueType& val) { this->setValueOn(xyz, val); }; /// Set the value of the voxel at the given offset and mark the voxel as active. void setValueOn(Index offset, const ValueType& val) { mBuffer[offset] = val; mValueMask.setOn(offset); } /// @brief Apply a functor to the value of the voxel at the given offset /// and mark the voxel as active. template void modifyValue(Index offset, const ModifyOp& op) { op(mBuffer[offset]); mValueMask.setOn(offset); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. template void modifyValue(const Coord& xyz, const ModifyOp& op) { this->modifyValue(this->coordToOffset(xyz), op); } /// Apply a functor to the voxel at the given coordinates. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { const Index offset = this->coordToOffset(xyz); bool state = mValueMask.isOn(offset); op(mBuffer[offset], state); mValueMask.set(offset, state); } /// Mark all voxels as active but don't change their values. void setValuesOn() { mValueMask.setOn(); } /// Mark all voxels as inactive but don't change their values. void setValuesOff() { mValueMask.setOff(); } /// Return @c true if the voxel at the given coordinates is active. bool isValueOn(const Coord& xyz) const {return this->isValueOn(LeafNode::coordToOffset(xyz));} /// Return @c true if the voxel at the given offset is active. bool isValueOn(Index offset) const { return mValueMask.isOn(offset); } /// Return @c false since leaf nodes never contain tiles. static bool hasActiveTiles() { return false; } /// Set all voxels within an axis-aligned box to the specified value and active state. void fill(const CoordBBox& bbox, const ValueType&, bool active = true); /// Set all voxels to the specified value but don't change their active states. void fill(const ValueType& value); /// Set all voxels to the specified value and active state. void fill(const ValueType& value, bool active); /// @brief Copy into a dense grid the values of the voxels that lie within /// a given bounding box. /// /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid /// @param dense dense grid with a stride in @e z of one (see tools::Dense /// in tools/Dense.h for the required API) /// /// @note @a bbox is assumed to be identical to or contained in the coordinate domains /// of both the dense grid and this node, i.e., no bounds checking is performed. /// @note Consider using tools::CopyToDense in tools/Dense.h /// instead of calling this method directly. template void copyToDense(const CoordBBox& bbox, DenseT& dense) const; /// @brief Copy from a dense grid into this node the values of the voxels /// that lie within a given bounding box. /// @details Only values that are different (by more than the given tolerance) /// from the background value will be active. Other values are inactive /// and truncated to the background value. /// /// @param bbox inclusive bounding box of the voxels to be copied into this node /// @param dense dense grid with a stride in @e z of one (see tools::Dense /// in tools/Dense.h for the required API) /// @param background background value of the tree that this node belongs to /// @param tolerance tolerance within which a value equals the background value /// /// @note @a bbox is assumed to be identical to or contained in the coordinate domains /// of both the dense grid and this node, i.e., no bounds checking is performed. /// @note Consider using tools::CopyFromDense in tools/Dense.h /// instead of calling this method directly. template void copyFromDense(const CoordBBox& bbox, const DenseT& dense, const ValueType& background, const ValueType& tolerance); /// @brief Return the value of the voxel at the given coordinates. /// @note Used internally by ValueAccessor. template const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const { return this->getValue(xyz); } /// @brief Return @c true if the voxel at the given coordinates is active. /// @note Used internally by ValueAccessor. template bool isValueOnAndCache(const Coord& xyz, AccessorT&) const { return this->isValueOn(xyz); } /// @brief Change the value of the voxel at the given coordinates and mark it as active. /// @note Used internally by ValueAccessor. template void setValueAndCache(const Coord& xyz, const ValueType& val, AccessorT&) { this->setValueOn(xyz, val); } /// @brief Change the value of the voxel at the given coordinates /// but preserve its state. /// @note Used internally by ValueAccessor. template void setValueOnlyAndCache(const Coord& xyz, const ValueType& val, AccessorT&) { this->setValueOnly(xyz, val); } /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// @note Used internally by ValueAccessor. template void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) { this->modifyValue(xyz, op); } /// Apply a functor to the voxel at the given coordinates. /// @note Used internally by ValueAccessor. template void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&) { this->modifyValueAndActiveState(xyz, op); } /// @brief Change the value of the voxel at the given coordinates and mark it as inactive. /// @note Used internally by ValueAccessor. template void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&) { this->setValueOff(xyz, value); } /// @brief Set the active state of the voxel at the given coordinates /// without changing its value. /// @note Used internally by ValueAccessor. template void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&) { this->setActiveState(xyz, on); } /// @brief Return @c true if the voxel at the given coordinates is active /// and return the voxel value in @a val. /// @note Used internally by ValueAccessor. template bool probeValueAndCache(const Coord& xyz, ValueType& val, AccessorT&) const { return this->probeValue(xyz, val); } /// @brief Return the value of the voxel at the given coordinates and return /// its active state and level (i.e., 0) in @a state and @a level. /// @note Used internally by ValueAccessor. template const ValueType& getValue(const Coord& xyz, bool& state, int& level, AccessorT&) const { const Index offset = this->coordToOffset(xyz); state = mValueMask.isOn(offset); level = LEVEL; return mBuffer[offset]; } /// @brief Return the LEVEL (=0) at which leaf node values reside. /// @note Used internally by ValueAccessor (note last argument is a dummy). template static Index getValueLevelAndCache(const Coord&, AccessorT&) { return LEVEL; } /// @brief Return a const reference to the first value in the buffer. /// @note Though it is potentially risky you can convert this /// to a non-const pointer by means of const_case&. const ValueType& getFirstValue() const { return mBuffer[0]; } /// Return a const reference to the last value in the buffer. const ValueType& getLastValue() const { return mBuffer[SIZE - 1]; } /// @brief Replace inactive occurrences of @a oldBackground with @a newBackground, /// and inactive occurrences of @a -oldBackground with @a -newBackground. void resetBackground(const ValueType& oldBackground, const ValueType& newBackground); /// @brief Overwrite each inactive value in this node and in any child nodes with /// a new value whose magnitude is equal to the specified background value and whose /// sign is consistent with that of the lexicographically closest active value. /// @details This is primarily useful for propagating the sign from the (active) voxels /// in a narrow-band level set to the inactive voxels outside the narrow band. void signedFloodFill(const ValueType& background); /// @brief Overwrite each inactive value in this node and in any child nodes with /// either @a outside or @a inside, depending on the sign of the lexicographically /// closest active value. /// @details Specifically, an inactive value is set to @a outside if the closest active /// value in the lexicographic direction is positive, otherwise it is set to @a inside. void signedFloodFill(const ValueType& outside, const ValueType& inside); void negate(); void voxelizeActiveTiles() {}; template void merge(const LeafNode&); template void merge(const ValueType& tileValue, bool tileActive); template void merge(const LeafNode& other, const ValueType& /*bg*/, const ValueType& /*otherBG*/); /// @brief Union this node's set of active values with the active values /// of the other node, whose @c ValueType may be different. So a /// resulting voxel will be active if either of the original voxels /// were active. /// /// @note This operation modifies only active states, not values. template void topologyUnion(const LeafNode& other); /// @brief Intersect this node's set of active values with the active values /// of the other node, whose @c ValueType may be different. So a /// resulting voxel will be active only if both of the original voxels /// were active. /// /// @details The last dummy argument is required to match the signature /// for InternalNode::topologyIntersection. /// /// @note This operation modifies only active states, not /// values. Also note that this operation can result in all voxels /// being inactive so consider subsequnetly calling prune. template void topologyIntersection(const LeafNode& other, const ValueType&); /// @brief Difference this node's set of active values with the active values /// of the other node, whose @c ValueType may be different. So a /// resulting voxel will be active only if the original voxel is /// active in this LeafNode and inactive in the other LeafNode. /// /// @details The last dummy argument is required to match the signature /// for InternalNode::topologyDifference. /// /// @note This operation modifies only active states, not /// values. Also note that this operation can result in all voxels /// being inactive so consider subsequnetly calling prune. template void topologyDifference(const LeafNode& other, const ValueType&); template void combine(const LeafNode& other, CombineOp& op); template void combine(const ValueType& value, bool valueIsActive, CombineOp& op); template void combine2(const LeafNode& other, const ValueType&, bool valueIsActive, CombineOp&); template void combine2(const ValueType&, const LeafNode& other, bool valueIsActive, CombineOp&); template void combine2(const LeafNode& b0, const LeafNode& b1, CombineOp&); /// @brief Calls the templated functor BBoxOp with bounding box /// information. An additional level argument is provided to the /// callback. /// /// @note The bounding boxes are guarenteed to be non-overlapping. template void visitActiveBBox(BBoxOp&) const; template void visit(VisitorOp&); template void visit(VisitorOp&) const; template void visit2Node(OtherLeafNodeType& other, VisitorOp&); template void visit2Node(OtherLeafNodeType& other, VisitorOp&) const; template void visit2(IterT& otherIter, VisitorOp&, bool otherIsLHS = false); template void visit2(IterT& otherIter, VisitorOp&, bool otherIsLHS = false) const; //@{ /// This function exists only to enable template instantiation. template void pruneOp(PruneOp&) {} void prune(const ValueType& /*tolerance*/ = zeroVal()) {} void pruneInactive(const ValueType&) {} void addLeaf(LeafNode*) {} template void addLeafAndCache(LeafNode*, AccessorT&) {} template NodeT* stealNode(const Coord&, const ValueType&, bool) { return NULL; } template NodeT* probeNode(const Coord&) { return NULL; } template const NodeT* probeConstNode(const Coord&) const { return NULL; } //@} void addTile(Index level, const Coord&, const ValueType&, bool); void addTile(Index offset, const ValueType&, bool); template void addTileAndCache(Index, const Coord&, const ValueType&, bool, AccessorT&); //@{ /// @brief Return a pointer to this node. LeafNode* touchLeaf(const Coord&) { return this; } template LeafNode* touchLeafAndCache(const Coord&, AccessorT&) { return this; } template NodeT* probeNodeAndCache(const Coord&, AccessorT&) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (!(boost::is_same::value)) return NULL; return reinterpret_cast(this); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } LeafNode* probeLeaf(const Coord&) { return this; } template LeafNode* probeLeafAndCache(const Coord&, AccessorT&) { return this; } //@} //@{ /// @brief Return a @const pointer to this node. const LeafNode* probeConstLeaf(const Coord&) const { return this; } template const LeafNode* probeConstLeafAndCache(const Coord&, AccessorT&) const { return this; } template const LeafNode* probeLeafAndCache(const Coord&, AccessorT&) const { return this; } const LeafNode* probeLeaf(const Coord&) const { return this; } template const NodeT* probeConstNodeAndCache(const Coord&, AccessorT&) const { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (!(boost::is_same::value)) return NULL; return reinterpret_cast(this); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //@} /// Return @c true if all of this node's values have the same active state /// and are equal to within the given tolerance, and return the value in @a constValue /// and the active state in @a state. bool isConstant(ValueType& constValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// Return @c true if all of this node's values are inactive. bool isInactive() const { return mValueMask.isOff(); } protected: friend class ::TestLeaf; template friend class ::TestLeafIO; // During topology-only construction, access is needed // to protected/private members of other template instances. template friend class LeafNode; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; friend struct ValueIter; // Allow iterators to call mask accessor methods (see below). /// @todo Make mask accessors public? friend class IteratorBase; friend class IteratorBase; friend class IteratorBase; /// Buffer containing the actual data values Buffer mBuffer; /// Bitmask that determines which voxels are active NodeMaskType mValueMask; /// Global grid index coordinates (x,y,z) of the local origin of this node Coord mOrigin; // Mask accessors public: bool isValueMaskOn(Index n) const { return mValueMask.isOn(n); } bool isValueMaskOn() const { return mValueMask.isOn(); } bool isValueMaskOff(Index n) const { return mValueMask.isOff(n); } bool isValueMaskOff() const { return mValueMask.isOff(); } const NodeMaskType& getValueMask() const { return mValueMask; } NodeMaskType& getValueMask() { return mValueMask; } void setValueMask(const NodeMaskType& mask) { mValueMask = mask; } bool isChildMaskOn(Index) const { return false; } // leaf nodes have no children bool isChildMaskOff(Index) const { return true; } bool isChildMaskOff() const { return true; } protected: void setValueMask(Index n, bool on) { mValueMask.set(n, on); } void setValueMaskOn(Index n) { mValueMask.setOn(n); } void setValueMaskOff(Index n) { mValueMask.setOff(n); } /// Compute the origin of the leaf node that contains the voxel with the given coordinates. static void evalNodeOrigin(Coord& xyz) { xyz &= ~(DIM - 1); } template static inline void doVisit(NodeT&, VisitorOp&); template static inline void doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp&); template static inline void doVisit2(NodeT& self, OtherChildAllIterT&, VisitorOp&, bool otherIsLHS); }; // end of LeafNode class //////////////////////////////////////// template inline LeafNode::LeafNode(): mValueMask(),//default is off! mOrigin(0, 0, 0) { } template inline LeafNode::LeafNode(const Coord& xyz, const ValueType& val, bool active): mBuffer(val), mValueMask(active), mOrigin(xyz & (~(DIM - 1))) { } template template inline LeafNode::LeafNode(const LeafNode& other, const ValueType& background, TopologyCopy): mBuffer(background), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { } template template inline LeafNode::LeafNode(const LeafNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy): mValueMask(other.mValueMask), mOrigin(other.mOrigin) { for (Index i = 0; i < SIZE; ++i) { mBuffer[i] = (mValueMask.isOn(i) ? onValue : offValue); } } template inline LeafNode::LeafNode(const LeafNode &other): mBuffer(other.mBuffer), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { } template inline LeafNode::~LeafNode() { } template inline std::string LeafNode::str() const { std::ostringstream ostr; ostr << "LeafNode @" << mOrigin << ": " << mBuffer; return ostr.str(); } //////////////////////////////////////// template inline Index LeafNode::coordToOffset(const Coord& xyz) { assert ((xyz[0] & (DIM-1u)) < DIM && (xyz[1] & (DIM-1u)) < DIM && (xyz[2] & (DIM-1u)) < DIM); return ((xyz[0] & (DIM-1u)) << 2*Log2Dim) + ((xyz[1] & (DIM-1u)) << Log2Dim) + (xyz[2] & (DIM-1u)); } template inline Coord LeafNode::offsetToLocalCoord(Index n) { assert(n<(1<< 3*Log2Dim)); Coord xyz; xyz.setX(n >> 2*Log2Dim); n &= ((1<<2*Log2Dim)-1); xyz.setY(n >> Log2Dim); xyz.setZ(n & ((1< inline void LeafNode::offsetToLocalCoord(Index n, Coord &xyz) { assert(n<(1<< 3*Log2Dim)); xyz.setX(n >> 2*Log2Dim); n &= ((1<<2*Log2Dim)-1); xyz.setY(n >> Log2Dim); xyz.setZ(n & ((1< inline Coord LeafNode::offsetToGlobalCoord(Index n) const { return (this->offsetToLocalCoord(n) + this->origin()); } //////////////////////////////////////// template inline const ValueT& LeafNode::getValue(const Coord& xyz) const { return this->getValue(LeafNode::coordToOffset(xyz)); } template inline const ValueT& LeafNode::getValue(Index offset) const { assert(offset < SIZE); return mBuffer[offset]; } template inline bool LeafNode::probeValue(const Coord& xyz, ValueType& val) const { return this->probeValue(LeafNode::coordToOffset(xyz), val); } template inline bool LeafNode::probeValue(Index offset, ValueType& val) const { assert(offset < SIZE); val = mBuffer[offset]; return mValueMask.isOn(offset); } template inline void LeafNode::setValueOff(const Coord& xyz, const ValueType& val) { this->setValueOff(LeafNode::coordToOffset(xyz), val); } template inline void LeafNode::setValueOff(Index offset, const ValueType& val) { assert(offset < SIZE); mBuffer[offset] = val; mValueMask.setOff(offset); } template inline void LeafNode::setActiveState(const Coord& xyz, bool on) { mValueMask.set(this->coordToOffset(xyz), on); } template inline void LeafNode::setValueOnly(const Coord& xyz, const ValueType& val) { this->setValueOnly(LeafNode::coordToOffset(xyz), val); } template inline void LeafNode::setValueOnly(Index offset, const ValueType& val) { assert(offset inline void LeafNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) { for (Int32 x = bbox.min().x(); x <= bbox.max().x(); ++x) { const Index offsetX = (x & (DIM-1u)) << 2*Log2Dim; for (Int32 y = bbox.min().y(); y <= bbox.max().y(); ++y) { const Index offsetXY = offsetX + ((y & (DIM-1u)) << Log2Dim); for (Int32 z = bbox.min().z(); z <= bbox.max().z(); ++z) { const Index offset = offsetXY + (z & (DIM-1u)); mBuffer[offset] = value; mValueMask.set(offset, active); } } } } template inline void LeafNode::fill(const ValueType& value) { mBuffer.fill(value); } template inline void LeafNode::fill(const ValueType& value, bool active) { mBuffer.fill(value); mValueMask.set(active); } //////////////////////////////////////// template template inline void LeafNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const { typedef typename DenseT::ValueType DenseValueType; const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); const Coord& min = dense.bbox().min(); DenseValueType* t0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // target array const T* s0 = &mBuffer[bbox.min()[2] & (DIM-1u)]; // source array for (Int32 x = bbox.min()[0], ex = bbox.max()[0] + 1; x < ex; ++x) { DenseValueType* t1 = t0 + xStride * (x - min[0]); const T* s1 = s0 + ((x & (DIM-1u)) << 2*Log2Dim); for (Int32 y = bbox.min()[1], ey = bbox.max()[1] + 1; y < ey; ++y) { DenseValueType* t2 = t1 + yStride * (y - min[1]); const T* s2 = s1 + ((y & (DIM-1u)) << Log2Dim); for (Int32 z = bbox.min()[2], ez = bbox.max()[2] + 1; z < ez; ++z, t2 += zStride) { *t2 = DenseValueType(*s2++); } } } } template template inline void LeafNode::copyFromDense(const CoordBBox& bbox, const DenseT& dense, const ValueType& background, const ValueType& tolerance) { typedef typename DenseT::ValueType DenseValueType; const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); const Coord& min = dense.bbox().min(); const DenseValueType* s0 = dense.data() + zStride * (bbox.min()[2] - min[2]); // source const Int32 n0 = bbox.min()[2] & (DIM-1u); for (Int32 x = bbox.min()[0], ex = bbox.max()[0]+1; x < ex; ++x) { const DenseValueType* s1 = s0 + xStride * (x - min[0]); const Int32 n1 = n0 + ((x & (DIM-1u)) << 2*LOG2DIM); for (Int32 y = bbox.min()[1], ey = bbox.max()[1]+1; y < ey; ++y) { const DenseValueType* s2 = s1 + yStride * (y - min[1]); Int32 n2 = n1 + ((y & (DIM-1u)) << LOG2DIM); for (Int32 z = bbox.min()[2], ez = bbox.max()[2]+1; z < ez; ++z, ++n2, s2 += zStride) { if (math::isApproxEqual(background, ValueType(*s2), tolerance)) { mValueMask.setOff(n2); mBuffer[n2] = background; } else { mValueMask.setOn(n2); mBuffer[n2] = ValueType(*s2); } } } } } //////////////////////////////////////// template inline void LeafNode::readTopology(std::istream& is, bool /*fromHalf*/) { mValueMask.load(is); } template inline void LeafNode::writeTopology(std::ostream& os, bool /*toHalf*/) const { mValueMask.save(os); } //////////////////////////////////////// template inline void LeafNode::readBuffers(std::istream& is, bool fromHalf) { // Read in the value mask. mValueMask.load(is); int8_t numBuffers = 1; if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { // Read in the origin. is.read(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); // Read in the number of buffers, which should now always be one. is.read(reinterpret_cast(&numBuffers), sizeof(int8_t)); } io::readCompressedValues(is, mBuffer.mData, SIZE, mValueMask, fromHalf); if (numBuffers > 1) { // Read in and discard auxiliary buffers that were created with earlier // versions of the library. (Auxiliary buffers are not mask compressed.) const bool zipped = io::getDataCompression(is) & io::COMPRESS_ZIP; Buffer temp; for (int i = 1; i < numBuffers; ++i) { if (fromHalf) { io::HalfReader::isReal, T>::read(is, temp.mData, SIZE, zipped); } else { io::readData(is, temp.mData, SIZE, zipped); } } } } template inline void LeafNode::writeBuffers(std::ostream& os, bool toHalf) const { // Write out the value mask. mValueMask.save(os); io::writeCompressedValues(os, mBuffer.mData, SIZE, mValueMask, /*childMask=*/NodeMaskType(), toHalf); } //////////////////////////////////////// template inline bool LeafNode::operator==(const LeafNode& other) const { return mOrigin == other.mOrigin && mValueMask == other.mValueMask && mBuffer == other.mBuffer; } template inline Index64 LeafNode::memUsage() const { return mBuffer.memUsage() + sizeof(mOrigin) + mValueMask.memUsage(); } template inline void LeafNode::evalActiveVoxelBoundingBox(CoordBBox& bbox) const { CoordBBox this_bbox = this->getNodeBoundingBox(); if (bbox.isInside(this_bbox)) return;//this LeafNode is already enclosed in the bbox if (ValueOnCIter iter = this->cbeginValueOn()) {//any active values? this_bbox.reset(); for(; iter; ++iter) this_bbox.expand(this->offsetToLocalCoord(iter.pos())); this_bbox.translate(this->origin()); bbox.expand(this_bbox); } } template inline void LeafNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const { CoordBBox this_bbox = this->getNodeBoundingBox(); if (bbox.isInside(this_bbox)) return;//this LeafNode is already enclosed in the bbox if (ValueOnCIter iter = this->cbeginValueOn()) {//any active values? if (visitVoxels) {//use voxel granularity? this_bbox.reset(); for(; iter; ++iter) this_bbox.expand(this->offsetToLocalCoord(iter.pos())); this_bbox.translate(this->origin()); } bbox.expand(this_bbox); } } template template inline bool LeafNode::hasSameTopology(const LeafNode* other) const { assert(other); return (Log2Dim == OtherLog2Dim && mValueMask == other->getValueMask()); } template inline bool LeafNode::isConstant(ValueType& constValue, bool& state, const ValueType& tolerance) const { state = mValueMask.isOn(); if (!(state || mValueMask.isOff())) return false; bool allEqual = true; const T value = mBuffer[0]; for (Index i = 1; allEqual && i < SIZE; ++i) { /// @todo Alternatively, allEqual = !((maxVal - minVal) > (2 * tolerance)) allEqual = math::isApproxEqual(mBuffer[i], value, tolerance); } if (allEqual) constValue = value; ///< @todo return average/median value? return allEqual; } //////////////////////////////////////// template inline void LeafNode::addTile(Index level, const Coord& xyz, const ValueType& val, bool active) { assert(level == 0); this->addTile(this->coordToOffset(xyz), val, active); } template inline void LeafNode::addTile(Index offset, const ValueType& val, bool active) { assert(offset < SIZE); setValueOnly(offset, val); setActiveState(offset, active); } template template inline void LeafNode::addTileAndCache(Index level, const Coord& xyz, const ValueType& val, bool active, AccessorT&) { this->addTile(level, xyz, val, active); } //////////////////////////////////////// template inline void LeafNode::signedFloodFill(const ValueType& background) { this->signedFloodFill(background, math::negative(background)); } template inline void LeafNode::signedFloodFill(const ValueType& outsideValue, const ValueType& insideValue) { const Index first = mValueMask.findFirstOn(); if (first < SIZE) { bool xInside = math::isNegative(mBuffer[first]), yInside = xInside, zInside = xInside; for (Index x = 0; x != (1 << Log2Dim); ++x) { const Index x00 = x << (2 * Log2Dim); if (mValueMask.isOn(x00)) { xInside = math::isNegative(mBuffer[x00]); // element(x, 0, 0) } yInside = xInside; for (Index y = 0; y != (1 << Log2Dim); ++y) { const Index xy0 = x00 + (y << Log2Dim); if (mValueMask.isOn(xy0)) { yInside = math::isNegative(mBuffer[xy0]); // element(x, y, 0) } zInside = yInside; for (Index z = 0; z != (1 << Log2Dim); ++z) { const Index xyz = xy0 + z; // element(x, y, z) if (mValueMask.isOn(xyz)) { zInside = math::isNegative(mBuffer[xyz]); } else { mBuffer[xyz] = zInside ? insideValue : outsideValue; } } } } } else {// if no active voxels exist simply use the sign of the first value mBuffer.fill(math::isNegative(mBuffer[0]) ? insideValue : outsideValue); } } template inline void LeafNode::resetBackground(const ValueType& oldBackground, const ValueType& newBackground) { typename NodeMaskType::OffIterator iter; // For all inactive values... for (iter = this->mValueMask.beginOff(); iter; ++iter) { ValueType &inactiveValue = mBuffer[iter.pos()]; if (math::isApproxEqual(inactiveValue, oldBackground)) { inactiveValue = newBackground; } else if (math::isApproxEqual(inactiveValue, math::negative(oldBackground))) { inactiveValue = math::negative(newBackground); } } } template template inline void LeafNode::merge(const LeafNode& other) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy == MERGE_NODES) return; typename NodeMaskType::OnIterator iter = other.mValueMask.beginOn(); for (; iter; ++iter) { const Index n = iter.pos(); if (mValueMask.isOff(n)) { mBuffer[n] = other.mBuffer[n]; mValueMask.setOn(n); } } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline void LeafNode::merge(const LeafNode& other, const ValueType& /*bg*/, const ValueType& /*otherBG*/) { this->template merge(other); } template template inline void LeafNode::merge(const ValueType& tileValue, bool tileActive) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy != MERGE_ACTIVE_STATES_AND_NODES) return; if (!tileActive) return; // Replace all inactive values with the active tile value. for (typename NodeMaskType::OffIterator iter = mValueMask.beginOff(); iter; ++iter) { const Index n = iter.pos(); mBuffer[n] = tileValue; mValueMask.setOn(n); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline void LeafNode::topologyUnion(const LeafNode& other) { mValueMask |= other.getValueMask(); } template template inline void LeafNode::topologyIntersection(const LeafNode& other, const ValueType&) { mValueMask &= other.getValueMask(); } template template inline void LeafNode::topologyDifference(const LeafNode& other, const ValueType&) { mValueMask &= !other.getValueMask(); } template inline void LeafNode::negate() { for (Index i = 0; i < SIZE; ++i) { mBuffer[i] = -mBuffer[i]; } } //////////////////////////////////////// template template inline void LeafNode::combine(const LeafNode& other, CombineOp& op) { CombineArgs args; for (Index i = 0; i < SIZE; ++i) { op(args.setARef(mBuffer[i]) .setAIsActive(mValueMask.isOn(i)) .setBRef(other.mBuffer[i]) .setBIsActive(other.mValueMask.isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } template template inline void LeafNode::combine(const ValueType& value, bool valueIsActive, CombineOp& op) { CombineArgs args; args.setBRef(value).setBIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { op(args.setARef(mBuffer[i]) .setAIsActive(mValueMask.isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } //////////////////////////////////////// template template inline void LeafNode::combine2(const LeafNode& other, const ValueType& value, bool valueIsActive, CombineOp& op) { CombineArgs args; args.setBRef(value).setBIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { op(args.setARef(other.mBuffer[i]) .setAIsActive(other.mValueMask.isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } template template inline void LeafNode::combine2(const ValueType& value, const LeafNode& other, bool valueIsActive, CombineOp& op) { CombineArgs args; args.setARef(value).setAIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { op(args.setBRef(other.mBuffer[i]) .setBIsActive(other.mValueMask.isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } template template inline void LeafNode::combine2(const LeafNode& b0, const LeafNode& b1, CombineOp& op) { CombineArgs args; for (Index i = 0; i < SIZE; ++i) { mValueMask.set(i, b0.mValueMask.isOn(i) || b1.mValueMask.isOn(i)); op(args.setARef(b0.mBuffer[i]) .setAIsActive(b0.mValueMask.isOn(i)) .setBRef(b1.mBuffer[i]) .setBIsActive(b1.mValueMask.isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } //////////////////////////////////////// template template inline void LeafNode::visitActiveBBox(BBoxOp& op) const { if (op.template descent()) { for (ValueOnCIter i=this->cbeginValueOn(); i; ++i) { #ifdef _MSC_VER op.operator()(CoordBBox::createCube(i.getCoord(), 1)); #else op.template operator()(CoordBBox::createCube(i.getCoord(), 1)); #endif } } else { #ifdef _MSC_VER op.operator()(this->getNodeBoundingBox()); #else op.template operator()(this->getNodeBoundingBox()); #endif } } template template inline void LeafNode::visit(VisitorOp& op) { doVisit(*this, op); } template template inline void LeafNode::visit(VisitorOp& op) const { doVisit(*this, op); } template template inline void LeafNode::doVisit(NodeT& self, VisitorOp& op) { for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { op(iter); } } //////////////////////////////////////// template template inline void LeafNode::visit2Node(OtherLeafNodeType& other, VisitorOp& op) { doVisit2Node(*this, other, op); } template template inline void LeafNode::visit2Node(OtherLeafNodeType& other, VisitorOp& op) const { doVisit2Node(*this, other, op); } template template< typename NodeT, typename OtherNodeT, typename VisitorOp, typename ChildAllIterT, typename OtherChildAllIterT> inline void LeafNode::doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp& op) { // Allow the two nodes to have different ValueTypes, but not different dimensions. BOOST_STATIC_ASSERT(OtherNodeT::SIZE == NodeT::SIZE); BOOST_STATIC_ASSERT(OtherNodeT::LEVEL == NodeT::LEVEL); ChildAllIterT iter = self.beginChildAll(); OtherChildAllIterT otherIter = other.beginChildAll(); for ( ; iter && otherIter; ++iter, ++otherIter) { op(iter, otherIter); } } //////////////////////////////////////// template template inline void LeafNode::visit2(IterT& otherIter, VisitorOp& op, bool otherIsLHS) { doVisit2( *this, otherIter, op, otherIsLHS); } template template inline void LeafNode::visit2(IterT& otherIter, VisitorOp& op, bool otherIsLHS) const { doVisit2( *this, otherIter, op, otherIsLHS); } template template< typename NodeT, typename VisitorOp, typename ChildAllIterT, typename OtherChildAllIterT> inline void LeafNode::doVisit2(NodeT& self, OtherChildAllIterT& otherIter, VisitorOp& op, bool otherIsLHS) { if (!otherIter) return; if (otherIsLHS) { for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { op(otherIter, iter); } } else { for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { op(iter, otherIter); } } } //////////////////////////////////////// template inline std::ostream& operator<<(std::ostream& os, const typename LeafNode::Buffer& buf) { for (Index32 i = 0, N = buf.size(); i < N; ++i) os << buf.mData[i] << ", "; return os; } } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb //////////////////////////////////////// // Specialization for LeafNodes of type bool #include "LeafNodeBool.h" #endif // OPENVDB_TREE_LEAFNODE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/TreeIterator.h0000644000000000000000000014650712252453157014755 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file TreeIterator.h #ifndef OPENVDB_TREE_TREEITERATOR_HAS_BEEN_INCLUDED #define OPENVDB_TREE_TREEITERATOR_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include #include #include #include #include // Prior to 0.96.1, depth-bounded value iterators always descended to the leaf level // and iterated past leaf nodes. Now, they never descend past the maximum depth. // Comment out the following line to restore the older, less-efficient behavior: #define ENABLE_TREE_VALUE_DEPTH_BOUND_OPTIMIZATION namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// CopyConstness::Type is either const T2 or T2 with no const qualifier, /// depending on whether T1 is const. For example, /// - CopyConstness::Type is int /// - CopyConstness::Type is int /// - CopyConstness::Type is const int /// - CopyConstness::Type is const int template struct CopyConstness { typedef typename boost::remove_const::type Type; }; template struct CopyConstness { typedef const ToType Type; }; //////////////////////////////////////// namespace iter { template struct InvertedTree { typedef typename InvertedTree::Type SubtreeT; typedef typename boost::mpl::push_back::type Type; }; template struct InvertedTree { typedef typename boost::mpl::vector::type Type; }; } // namespace iter //////////////////////////////////////// /// IterTraits provides the following for iterators of the standard types, /// i.e., for {Child,Value}{On,Off,All}{Iter,CIter}: /// - a NodeConverter template to convert an iterator for one type of node /// to an iterator of the same type for another type of node; for example, /// IterTraits::NodeConverter::Type /// is synonymous with LeafNode::ValueOnIter. /// - a begin(node) function that returns a begin iterator for a node of arbitrary type; /// for example, IterTraits::begin(leaf) returns /// leaf.beginValueOn() /// - a getChild() function that returns a pointer to the child node to which the iterator /// is currently pointing (always NULL if the iterator is a Value iterator) template struct IterTraits { template static ChildT* getChild(const IterT&) { return NULL; } }; template struct IterTraits { typedef typename NodeT::ChildOnIter IterT; static IterT begin(NodeT& node) { return node.beginChildOn(); } template static ChildT* getChild(const IterT& iter) { return &iter.getValue(); } template struct NodeConverter { typedef typename OtherNodeT::ChildOnIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildOnCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginChildOn(); } template static const ChildT* getChild(const IterT& iter) { return &iter.getValue(); } template struct NodeConverter { typedef typename OtherNodeT::ChildOnCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildOffIter IterT; static IterT begin(NodeT& node) { return node.beginChildOff(); } template struct NodeConverter { typedef typename OtherNodeT::ChildOffIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildOffCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginChildOff(); } template struct NodeConverter { typedef typename OtherNodeT::ChildOffCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildAllIter IterT; static IterT begin(NodeT& node) { return node.beginChildAll(); } template static ChildT* getChild(const IterT& iter) { typename IterT::NonConstValueType val; return iter.probeChild(val); } template struct NodeConverter { typedef typename OtherNodeT::ChildAllIter Type; }; }; template struct IterTraits { typedef typename NodeT::ChildAllCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginChildAll(); } template static ChildT* getChild(const IterT& iter) { typename IterT::NonConstValueType val; return iter.probeChild(val); } template struct NodeConverter { typedef typename OtherNodeT::ChildAllCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueOnIter IterT; static IterT begin(NodeT& node) { return node.beginValueOn(); } template struct NodeConverter { typedef typename OtherNodeT::ValueOnIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueOnCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginValueOn(); } template struct NodeConverter { typedef typename OtherNodeT::ValueOnCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueOffIter IterT; static IterT begin(NodeT& node) { return node.beginValueOff(); } template struct NodeConverter { typedef typename OtherNodeT::ValueOffIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueOffCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginValueOff(); } template struct NodeConverter { typedef typename OtherNodeT::ValueOffCIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueAllIter IterT; static IterT begin(NodeT& node) { return node.beginValueAll(); } template struct NodeConverter { typedef typename OtherNodeT::ValueAllIter Type; }; }; template struct IterTraits { typedef typename NodeT::ValueAllCIter IterT; static IterT begin(const NodeT& node) { return node.cbeginValueAll(); } template struct NodeConverter { typedef typename OtherNodeT::ValueAllCIter Type; }; }; //////////////////////////////////////// /// @brief An IterListItem is an element of a compile-time linked list of iterators /// to nodes of different types. /// /// The list is constructed by traversing the template hierarchy of a Tree in reverse order, /// so typically the elements will be a LeafNode iterator of some type (e.g., ValueOnCIter), /// followed by one or more InternalNode iterators of the same type, followed by a RootNode /// iterator of the same type. /// /// The length of the list is fixed at compile time, and because it is implemented using /// nested, templated classes, much of the list traversal logic can be optimized away. template class IterListItem { public: /// The type of iterator stored in the previous list item typedef typename PrevItemT::IterT PrevIterT; /// The type of node (non-const) whose iterator is stored in this list item typedef typename boost::mpl::front::type _NodeT; /// The type of iterator stored in this list item (e.g., InternalNode::ValueOnCIter) typedef typename IterTraits::template NodeConverter<_NodeT>::Type IterT; /// The type of node (const or non-const) over which IterT iterates (e.g., const RootNode<...>) typedef typename IterT::NodeType NodeT; /// The type of the node with const qualifiers removed ("Non-Const") typedef typename IterT::NonConstNodeType NCNodeT; /// The type of value (with const qualifiers removed) to which the iterator points typedef typename IterT::NonConstValueType NCValueT; /// NodeT's child node type, with the same constness (e.g., const InternalNode<...>) typedef typename CopyConstness::Type ChildT; /// NodeT's child node type with const qualifiers removed typedef typename CopyConstness::Type NCChildT; typedef IterTraits ITraits; /// NodeT's level in its tree (0 = LeafNode) static const Index Level = _Level; IterListItem(PrevItemT* prev): mNext(this), mPrev(prev) {} IterListItem(const IterListItem& other): mIter(other.mIter), mNext(other.mNext), mPrev(NULL) {} IterListItem& operator=(const IterListItem& other) { if (&other != this) { mIter = other.mIter; mNext = other.mNext; mPrev = NULL; ///< @note external call to updateBackPointers() required } return *this; } void updateBackPointers(PrevItemT* prev) { mPrev = prev; mNext.updateBackPointers(this); } void setIter(const IterT& iter) { mIter = iter; } template void setIter(const OtherIterT& iter) { mNext.setIter(iter); } /// Return the node over which this list element's iterator iterates. void getNode(Index lvl, NodeT*& node) const { node = (lvl <= Level) ? mIter.getParentNode() : NULL; } /// Return the node over which one of the following list elements' iterator iterates. template void getNode(Index lvl, OtherNodeT*& node) const { mNext.getNode(lvl, node); } /// @brief Initialize the iterator for level @a lvl of the tree with the node /// over which the corresponding iterator of @a otherListItem is iterating. /// /// For example, if @a otherListItem contains a LeafNode::ValueOnIter, /// initialize this list's leaf iterator with the same LeafNode. template void initLevel(Index lvl, OtherIterListItemT& otherListItem) { if (lvl == Level) { const NodeT* node = NULL; otherListItem.getNode(lvl, node); mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); } else { // Forward to one of the following list elements. mNext.initLevel(lvl, otherListItem); } } /// Return The table offset of the iterator at level @a lvl of the tree. Index pos(Index lvl) const { return (lvl == Level) ? mIter.pos() : mNext.pos(lvl); } /// Return @c true if the iterator at level @a lvl of the tree has not yet reached its end. bool test(Index lvl) const { return (lvl == Level) ? mIter.test() : mNext.test(lvl); } /// Increment the iterator at level @a lvl of the tree. bool next(Index lvl) { return (lvl == Level) ? mIter.next() : mNext.next(lvl); } /// @brief If the iterator at level @a lvl of the tree points to a child node, /// initialize the next iterator in this list with that child node. bool down(Index lvl) { if (lvl == Level && mPrev != NULL && mIter) { if (ChildT* child = ITraits::template getChild(mIter)) { mPrev->setIter(PrevItemT::ITraits::begin(*child)); return true; } } return (lvl > Level) ? mNext.down(lvl) : false; } /// @brief Return the global coordinates of the voxel or tile to which the iterator /// at level @a lvl of the tree is currently pointing. Coord getCoord(Index lvl) const { return (lvl == Level) ? mIter.getCoord() : mNext.getCoord(lvl); } Index getChildDim(Index lvl) const { return (lvl == Level) ? NodeT::getChildDim() : mNext.getChildDim(lvl); } /// Return the number of (virtual) voxels spanned by a tile value or child node Index64 getVoxelCount(Index lvl) const { return (lvl == Level) ? ChildT::NUM_VOXELS : mNext.getVoxelCount(lvl); } /// Return @c true if the iterator at level @a lvl of the tree points to an active value. bool isValueOn(Index lvl) const { return (lvl == Level) ? mIter.isValueOn() : mNext.isValueOn(lvl); } /// Return the value to which the iterator at level @a lvl of the tree points. const NCValueT& getValue(Index lvl) const { if (lvl == Level) return mIter.getValue(); return mNext.getValue(lvl); } /// @brief Set the value (to @a val) to which the iterator at level @a lvl /// of the tree points and mark the value as active. /// @note Not valid when @c IterT is a const iterator type void setValue(Index lvl, const NCValueT& val) const { if (lvl == Level) mIter.setValue(val); else mNext.setValue(lvl, val); } /// @brief Set the value (to @a val) to which the iterator at level @a lvl of the tree /// points and mark the value as active if @a on is @c true, or inactive otherwise. /// @note Not valid when @c IterT is a const iterator type void setValueOn(Index lvl, bool on = true) const { if (lvl == Level) mIter.setValueOn(on); else mNext.setValueOn(lvl, on); } /// @brief Mark the value to which the iterator at level @a lvl of the tree points /// as inactive. /// @note Not valid when @c IterT is a const iterator type void setValueOff(Index lvl) const { if (lvl == Level) mIter.setValueOff(); else mNext.setValueOff(lvl); } /// @brief Apply a functor to the item to which this iterator is pointing. /// @note Not valid when @c IterT is a const iterator type template void modifyValue(Index lvl, const ModifyOp& op) const { if (lvl == Level) mIter.modifyValue(op); else mNext.modifyValue(lvl, op); } private: typedef typename boost::mpl::pop_front::type RestT; // NodeVecT minus its first item typedef IterListItem NextItem; IterT mIter; NextItem mNext; PrevItemT* mPrev; }; /// The initial element of a compile-time linked list of iterators to nodes of different types template class IterListItem { public: /// The type of iterator stored in the previous list item typedef typename PrevItemT::IterT PrevIterT; /// The type of node (non-const) whose iterator is stored in this list item typedef typename boost::mpl::front::type _NodeT; /// The type of iterator stored in this list item (e.g., InternalNode::ValueOnCIter) typedef typename IterTraits::template NodeConverter<_NodeT>::Type IterT; /// The type of node (const or non-const) over which IterT iterates (e.g., const RootNode<...>) typedef typename IterT::NodeType NodeT; /// The type of the node with const qualifiers removed ("Non-Const") typedef typename IterT::NonConstNodeType NCNodeT; /// The type of value (with const qualifiers removed) to which the iterator points typedef typename IterT::NonConstValueType NCValueT; typedef IterTraits ITraits; /// NodeT's level in its tree (0 = LeafNode) static const Index Level = 0; IterListItem(PrevItemT*): mNext(this), mPrev(NULL) {} IterListItem(const IterListItem& other): mIter(other.mIter), mNext(other.mNext), mPrev(NULL) {} IterListItem& operator=(const IterListItem& other) { if (&other != this) { mIter = other.mIter; mNext = other.mNext; mPrev = NULL; } return *this; } void updateBackPointers(PrevItemT* = NULL) { mPrev = NULL; mNext.updateBackPointers(this); } void setIter(const IterT& iter) { mIter = iter; } template void setIter(const OtherIterT& iter) { mNext.setIter(iter); } void getNode(Index lvl, NodeT*& node) const { node = (lvl == 0) ? mIter.getParentNode() : NULL; } template void getNode(Index lvl, OtherNodeT*& node) const { mNext.getNode(lvl, node); } template void initLevel(Index lvl, OtherIterListItemT& otherListItem) { if (lvl == 0) { const NodeT* node = NULL; otherListItem.getNode(lvl, node); mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); } else { mNext.initLevel(lvl, otherListItem); } } Index pos(Index lvl) const { return (lvl == 0) ? mIter.pos() : mNext.pos(lvl); } bool test(Index lvl) const { return (lvl == 0) ? mIter.test() : mNext.test(lvl); } bool next(Index lvl) { return (lvl == 0) ? mIter.next() : mNext.next(lvl); } bool down(Index lvl) { return (lvl == 0) ? false : mNext.down(lvl); } Coord getCoord(Index lvl) const { return (lvl == 0) ? mIter.getCoord() : mNext.getCoord(lvl); } Index getChildDim(Index lvl) const { return (lvl == 0) ? NodeT::getChildDim() : mNext.getChildDim(lvl); } Index64 getVoxelCount(Index lvl) const { return (lvl == 0) ? 1 : mNext.getVoxelCount(lvl); } bool isValueOn(Index lvl) const { return (lvl == 0) ? mIter.isValueOn() : mNext.isValueOn(lvl); } const NCValueT& getValue(Index lvl) const { if (lvl == 0) return mIter.getValue(); return mNext.getValue(lvl); } void setValue(Index lvl, const NCValueT& val) const { if (lvl == 0) mIter.setValue(val); else mNext.setValue(lvl, val); } void setValueOn(Index lvl, bool on = true) const { if (lvl == 0) mIter.setValueOn(on); else mNext.setValueOn(lvl, on); } void setValueOff(Index lvl) const { if (lvl == 0) mIter.setValueOff(); else mNext.setValueOff(lvl); } template void modifyValue(Index lvl, const ModifyOp& op) const { if (lvl == 0) mIter.modifyValue(op); else mNext.modifyValue(lvl, op); } private: typedef typename boost::mpl::pop_front::type RestT; // NodeVecT minus its first item typedef IterListItem NextItem; IterT mIter; NextItem mNext; PrevItemT* mPrev; }; /// The final element of a compile-time linked list of iterators to nodes of different types template class IterListItem { public: typedef typename boost::mpl::front::type _NodeT; /// The type of iterator stored in the previous list item typedef typename PrevItemT::IterT PrevIterT; /// The type of iterator stored in this list item (e.g., RootNode::ValueOnCIter) typedef typename IterTraits::template NodeConverter<_NodeT>::Type IterT; /// The type of node over which IterT iterates (e.g., const RootNode<...>) typedef typename IterT::NodeType NodeT; /// The type of the node with const qualifiers removed ("Non-Const") typedef typename IterT::NonConstNodeType NCNodeT; /// The type of value (with const qualifiers removed) to which the iterator points typedef typename IterT::NonConstValueType NCValueT; /// NodeT's child node type, with the same constness (e.g., const InternalNode<...>) typedef typename CopyConstness::Type ChildT; /// NodeT's child node type with const qualifiers removed typedef typename CopyConstness::Type NCChildT; typedef IterTraits ITraits; /// NodeT's level in its tree (0 = LeafNode) static const Index Level = _Level; IterListItem(PrevItemT* prev): mPrev(prev) {} IterListItem(const IterListItem& other): mIter(other.mIter), mPrev(NULL) {} IterListItem& operator=(const IterListItem& other) { if (&other != this) { mIter = other.mIter; mPrev = NULL; ///< @note external call to updateBackPointers() required } return *this; } void updateBackPointers(PrevItemT* prev) { mPrev = prev; } // The following method specializations differ from the default template // implementations mainly in that they don't forward. void setIter(const IterT& iter) { mIter = iter; } void getNode(Index lvl, NodeT*& node) const { node = (lvl <= Level) ? mIter.getParentNode() : NULL; } template void initLevel(Index lvl, OtherIterListItemT& otherListItem) { if (lvl == Level) { const NodeT* node = NULL; otherListItem.getNode(lvl, node); mIter = (node == NULL) ? IterT() : ITraits::begin(*const_cast(node)); } } Index pos(Index lvl) const { return (lvl == Level) ? mIter.pos() : Index(-1); } bool test(Index lvl) const { return (lvl == Level) ? mIter.test() : false; } bool next(Index lvl) { return (lvl == Level) ? mIter.next() : false; } bool down(Index lvl) { if (lvl == Level && mPrev != NULL && mIter) { if (ChildT* child = ITraits::template getChild(mIter)) { mPrev->setIter(PrevItemT::ITraits::begin(*child)); return true; } } return false; } Coord getCoord(Index lvl) const { return (lvl == Level) ? mIter.getCoord() : Coord(); } Index getChildDim(Index lvl) const { return (lvl == Level) ? NodeT::getChildDim() : 0; } Index64 getVoxelCount(Index lvl) const { return (lvl == Level) ? ChildT::NUM_VOXELS : 0; } bool isValueOn(Index lvl) const { return (lvl == Level) ? mIter.isValueOn() : false; } const NCValueT& getValue(Index lvl) const { assert(lvl == Level); (void)lvl; // avoid unused variable warning in optimized builds return mIter.getValue(); } void setValue(Index lvl, const NCValueT& val) const { if (lvl == Level) mIter.setValue(val); } void setValueOn(Index lvl, bool on = true) const { if (lvl == Level) mIter.setValueOn(on); } void setValueOff(Index lvl) const { if (lvl == Level) mIter.setValueOff(); } template void modifyValue(Index lvl, const ModifyOp& op) const { if (lvl == Level) mIter.modifyValue(op); } private: IterT mIter; PrevItemT* mPrev; }; //////////////////////////////////////// //#define DEBUG_TREE_VALUE_ITERATOR /// @brief Base class for tree-traversal iterators over tile and voxel values template class TreeValueIteratorBase { public: typedef _TreeT TreeT; typedef typename ValueIterT::NodeType NodeT; typedef typename ValueIterT::NonConstValueType ValueT; typedef typename NodeT::ChildOnCIter ChildOnIterT; static const Index ROOT_LEVEL = NodeT::LEVEL; BOOST_STATIC_ASSERT(ValueIterT::NodeType::LEVEL == ROOT_LEVEL); static const Index LEAF_LEVEL = 0, ROOT_DEPTH = 0, LEAF_DEPTH = ROOT_LEVEL; TreeValueIteratorBase(TreeT&); TreeValueIteratorBase(const TreeValueIteratorBase& other); TreeValueIteratorBase& operator=(const TreeValueIteratorBase& other); /// Specify the depth of the highest level of the tree to which to ascend (depth 0 = root). void setMinDepth(Index minDepth); /// Return the depth of the highest level of the tree to which this iterator ascends. Index getMinDepth() const { return ROOT_LEVEL - Index(mMaxLevel); } /// Specify the depth of the lowest level of the tree to which to descend (depth 0 = root). void setMaxDepth(Index maxDepth); /// Return the depth of the lowest level of the tree to which this iterator ascends. Index getMaxDepth() const { return ROOT_LEVEL - Index(mMinLevel); } //@{ /// Return @c true if this iterator is not yet exhausted. bool test() const { return mValueIterList.test(mLevel); } operator bool() const { return this->test(); } //@} /// @brief Advance to the next tile or voxel value. /// Return @c true if this iterator is not yet exhausted. bool next(); /// Advance to the next tile or voxel value. TreeValueIteratorBase& operator++() { this->next(); return *this; } /// @brief Return the level in the tree (0 = leaf) of the node to which /// this iterator is currently pointing. Index getLevel() const { return mLevel; } /// @brief Return the depth in the tree (0 = root) of the node to which /// this iterator is currently pointing. Index getDepth() const { return ROOT_LEVEL - mLevel; } static Index getLeafDepth() { return LEAF_DEPTH; } /// @brief Return in @a node a pointer to the node over which this iterator is /// currently iterating or one of that node's parents, as determined by @a NodeType. /// @return a null pointer if @a NodeType specifies a node at a lower level /// of the tree than that given by getLevel(). template void getNode(NodeType*& node) const { mValueIterList.getNode(mLevel, node); } /// @brief Return the global coordinates of the voxel or tile to which /// this iterator is currently pointing. Coord getCoord() const { return mValueIterList.getCoord(mLevel); } /// @brief Return in @a bbox the axis-aligned bounding box of /// the voxel or tile to which this iterator is currently pointing. /// @return false if the bounding box is empty. bool getBoundingBox(CoordBBox&) const; /// @brief Return the axis-aligned bounding box of the voxel or tile to which /// this iterator is currently pointing. CoordBBox getBoundingBox() const { CoordBBox b; this->getBoundingBox(b); return b; } /// Return the number of (virtual) voxels corresponding to the value Index64 getVoxelCount() const { return mValueIterList.getVoxelCount(mLevel);} /// Return @c true if this iterator is currently pointing to a (non-leaf) tile value. bool isTileValue() const { return mLevel != 0 && this->test(); } /// Return @c true if this iterator is currently pointing to a (leaf) voxel value. bool isVoxelValue() const { return mLevel == 0 && this->test(); } /// Return @c true if the value to which this iterator is currently pointing is active. bool isValueOn() const { return mValueIterList.isValueOn(mLevel); } //@{ /// Return the tile or voxel value to which this iterator is currently pointing. const ValueT& getValue() const { return mValueIterList.getValue(mLevel); } const ValueT& operator*() const { return this->getValue(); } const ValueT* operator->() const { return &(this->operator*()); } //@} /// @brief Change the tile or voxel value to which this iterator is currently pointing /// and mark it as active. void setValue(const ValueT& val) const { mValueIterList.setValue(mLevel, val); } /// @brief Change the active/inactive state of the tile or voxel value to which /// this iterator is currently pointing. void setActiveState(bool on) const { mValueIterList.setValueOn(mLevel, on); } /// Mark the tile or voxel value to which this iterator is currently pointing as inactive. void setValueOff() const { mValueIterList.setValueOff(mLevel); } /// @brief Apply a functor to the item to which this iterator is pointing. /// (Not valid for const iterators.) /// @param op a functor of the form void op(ValueType&) const that modifies /// its argument in place /// @see Tree::modifyValue() template void modifyValue(const ModifyOp& op) const { mValueIterList.modifyValue(mLevel, op); } /// Return a pointer to the tree over which this iterator is iterating. TreeT* getTree() const { return mTree; } /// Return a string (for debugging, mainly) describing this iterator's current state. std::string summary() const; private: bool advance(bool dontIncrement = false); typedef typename iter::InvertedTree::Type InvTreeT; struct PrevChildItem { typedef ChildOnIterT IterT; }; struct PrevValueItem { typedef ValueIterT IterT; }; IterListItem mChildIterList; IterListItem mValueIterList; Index mLevel; int mMinLevel, mMaxLevel; TreeT* mTree; }; // class TreeValueIteratorBase template inline TreeValueIteratorBase::TreeValueIteratorBase(TreeT& tree): mChildIterList(NULL), mValueIterList(NULL), mLevel(ROOT_LEVEL), mMinLevel(int(LEAF_LEVEL)), mMaxLevel(int(ROOT_LEVEL)), mTree(&tree) { mChildIterList.setIter(IterTraits::begin(tree.getRootNode())); mValueIterList.setIter(IterTraits::begin(tree.getRootNode())); this->advance(/*dontIncrement=*/true); } template inline TreeValueIteratorBase::TreeValueIteratorBase(const TreeValueIteratorBase& other): mChildIterList(other.mChildIterList), mValueIterList(other.mValueIterList), mLevel(other.mLevel), mMinLevel(other.mMinLevel), mMaxLevel(other.mMaxLevel), mTree(other.mTree) { mChildIterList.updateBackPointers(); mValueIterList.updateBackPointers(); } template inline TreeValueIteratorBase& TreeValueIteratorBase::operator=(const TreeValueIteratorBase& other) { if (&other != this) { mChildIterList = other.mChildIterList; mValueIterList = other.mValueIterList; mLevel = other.mLevel; mMinLevel = other.mMinLevel; mMaxLevel = other.mMaxLevel; mTree = other.mTree; mChildIterList.updateBackPointers(); mValueIterList.updateBackPointers(); } return *this; } template inline void TreeValueIteratorBase::setMinDepth(Index minDepth) { mMaxLevel = int(ROOT_LEVEL - minDepth); // level = ROOT_LEVEL - depth if (int(mLevel) > mMaxLevel) this->next(); } template inline void TreeValueIteratorBase::setMaxDepth(Index maxDepth) { // level = ROOT_LEVEL - depth mMinLevel = int(ROOT_LEVEL - std::min(maxDepth, this->getLeafDepth())); if (int(mLevel) < mMinLevel) this->next(); } template inline bool TreeValueIteratorBase::next() { do { if (!this->advance()) return false; } while (int(mLevel) < mMinLevel || int(mLevel) > mMaxLevel); return true; } template inline bool TreeValueIteratorBase::advance(bool dontIncrement) { Index vPos = mValueIterList.pos(mLevel), cPos = mChildIterList.pos(mLevel); if (vPos == cPos && mChildIterList.test(mLevel)) { /// @todo Once ValueOff iterators properly skip child pointers, remove this block. mValueIterList.next(mLevel); vPos = mValueIterList.pos(mLevel); } if (vPos < cPos) { if (dontIncrement) return true; if (mValueIterList.next(mLevel)) { if (mValueIterList.pos(mLevel) == cPos && mChildIterList.test(mLevel)) { /// @todo Once ValueOff iterators properly skip child pointers, remove this block. mValueIterList.next(mLevel); } // If there is a next value and it precedes the next child, return. if (mValueIterList.pos(mLevel) < cPos) return true; } } else { // Advance to the next child, which may or may not precede the next value. if (!dontIncrement) mChildIterList.next(mLevel); } #ifdef DEBUG_TREE_VALUE_ITERATOR std::cout << "\n" << this->summary() << std::flush; #endif // Descend to the lowest level at which the next value precedes the next child. while (mChildIterList.pos(mLevel) < mValueIterList.pos(mLevel)) { #ifdef ENABLE_TREE_VALUE_DEPTH_BOUND_OPTIMIZATION if (int(mLevel) == mMinLevel) { // If the current node lies at the lowest allowed level, none of its // children can be visited, so advance its child iterator to the end. /// @todo Consider adding methods to iterators to advance to the end /// in one step, instead of by repeated increments. while (mChildIterList.test(mLevel)) mChildIterList.next(mLevel); } else #endif if (mChildIterList.down(mLevel)) { --mLevel; // descend one level mValueIterList.initLevel(mLevel, mChildIterList); if (mValueIterList.pos(mLevel) == mChildIterList.pos(mLevel) && mChildIterList.test(mLevel)) { /// @todo Once ValueOff iterators properly skip child pointers, remove this block. mValueIterList.next(mLevel); } } else break; #ifdef DEBUG_TREE_VALUE_ITERATOR std::cout << "\n" << this->summary() << std::flush; #endif } // Ascend to the nearest level at which one of the iterators is not yet exhausted. while (!mChildIterList.test(mLevel) && !mValueIterList.test(mLevel)) { if (mLevel == ROOT_LEVEL) return false; ++mLevel; mChildIterList.next(mLevel); this->advance(/*dontIncrement=*/true); } return true; } template inline bool TreeValueIteratorBase::getBoundingBox(CoordBBox& bbox) const { if (!this->test()) { bbox = CoordBBox(); return false; } bbox.min() = mValueIterList.getCoord(mLevel); bbox.max() = bbox.min().offsetBy(mValueIterList.getChildDim(mLevel) - 1); return true; } template inline std::string TreeValueIteratorBase::summary() const { std::ostringstream ostr; for (int lvl = int(ROOT_LEVEL); lvl >= 0 && lvl >= int(mLevel); --lvl) { if (lvl == 0) ostr << "leaf"; else if (lvl == int(ROOT_LEVEL)) ostr << "root"; else ostr << "int" << (ROOT_LEVEL - lvl); ostr << " v" << mValueIterList.pos(lvl) << " c" << mChildIterList.pos(lvl); if (lvl > int(mLevel)) ostr << " / "; } if (this->test() && mValueIterList.pos(mLevel) < mChildIterList.pos(mLevel)) { if (mLevel == 0) { ostr << " " << this->getCoord(); } else { ostr << " " << this->getBoundingBox(); } } return ostr.str(); } //////////////////////////////////////// /// @brief Base class for tree-traversal iterators over all nodes template class NodeIteratorBase { public: typedef _TreeT TreeT; typedef RootChildOnIterT RootIterT; typedef typename RootIterT::NodeType RootNodeT; typedef typename RootIterT::NonConstNodeType NCRootNodeT; static const Index ROOT_LEVEL = RootNodeT::LEVEL; typedef typename iter::InvertedTree::Type InvTreeT; static const Index LEAF_LEVEL = 0, ROOT_DEPTH = 0, LEAF_DEPTH = ROOT_LEVEL; typedef IterTraits RootIterTraits; NodeIteratorBase(); NodeIteratorBase(TreeT&); NodeIteratorBase(const NodeIteratorBase& other); NodeIteratorBase& operator=(const NodeIteratorBase& other); /// Specify the depth of the highest level of the tree to which to ascend (depth 0 = root). void setMinDepth(Index minDepth); /// Return the depth of the highest level of the tree to which this iterator ascends. Index getMinDepth() const { return ROOT_LEVEL - Index(mMaxLevel); } /// Specify the depth of the lowest level of the tree to which to descend (depth 0 = root). void setMaxDepth(Index maxDepth); /// Return the depth of the lowest level of the tree to which this iterator ascends. Index getMaxDepth() const { return ROOT_LEVEL - Index(mMinLevel); } //@{ /// Return @c true if this iterator is not yet exhausted. bool test() const { return !mDone; } operator bool() const { return this->test(); } //@} /// @brief Advance to the next tile or voxel value. /// @return @c true if this iterator is not yet exhausted. bool next(); /// Advance the iterator to the next leaf node. void increment() { this->next(); } NodeIteratorBase& operator++() { this->increment(); return *this; } /// Increment the iterator n times. void increment(Index n) { for (Index i = 0; i < n && this->next(); ++i) {} } /// @brief Return the level in the tree (0 = leaf) of the node to which /// this iterator is currently pointing. Index getLevel() const { return mLevel; } /// @brief Return the depth in the tree (0 = root) of the node to which /// this iterator is currently pointing. Index getDepth() const { return ROOT_LEVEL - mLevel; } static Index getLeafDepth() { return LEAF_DEPTH; } /// @brief Return the global coordinates of the voxel or tile to which /// this iterator is currently pointing. Coord getCoord() const; /// @brief Return in @a bbox the axis-aligned bounding box of /// the voxel or tile to which this iterator is currently pointing. /// @return false if the bounding box is empty. bool getBoundingBox(CoordBBox& bbox) const; /// @brief Return the axis-aligned bounding box of the voxel or tile to which /// this iterator is currently pointing. CoordBBox getBoundingBox() const { CoordBBox b; this->getBoundingBox(b); return b; } /// @brief Return the node to which the iterator is pointing. /// @note This iterator doesn't have the usual dereference operators (* and ->), /// because they would have to be overloaded by the returned node type. template void getNode(NodeT*& node) const { node = NULL; mIterList.getNode(mLevel, node); } TreeT* getTree() const { return mTree; } std::string summary() const; private: struct PrevItem { typedef RootIterT IterT; }; IterListItem mIterList; Index mLevel; int mMinLevel, mMaxLevel; bool mDone; TreeT* mTree; }; // class NodeIteratorBase template inline NodeIteratorBase::NodeIteratorBase(): mIterList(NULL), mLevel(ROOT_LEVEL), mMinLevel(int(LEAF_LEVEL)), mMaxLevel(int(ROOT_LEVEL)), mDone(true), mTree(NULL) { } template inline NodeIteratorBase::NodeIteratorBase(TreeT& tree): mIterList(NULL), mLevel(ROOT_LEVEL), mMinLevel(int(LEAF_LEVEL)), mMaxLevel(int(ROOT_LEVEL)), mDone(false), mTree(&tree) { mIterList.setIter(RootIterTraits::begin(tree.getRootNode())); } template inline NodeIteratorBase::NodeIteratorBase(const NodeIteratorBase& other): mIterList(other.mIterList), mLevel(other.mLevel), mMinLevel(other.mMinLevel), mMaxLevel(other.mMaxLevel), mDone(other.mDone), mTree(other.mTree) { mIterList.updateBackPointers(); } template inline NodeIteratorBase& NodeIteratorBase::operator=(const NodeIteratorBase& other) { if (&other != this) { mLevel = other.mLevel; mMinLevel = other.mMinLevel; mMaxLevel = other.mMaxLevel; mDone = other.mDone; mTree = other.mTree; mIterList = other.mIterList; mIterList.updateBackPointers(); } return *this; } template inline void NodeIteratorBase::setMinDepth(Index minDepth) { mMaxLevel = int(ROOT_LEVEL - minDepth); // level = ROOT_LEVEL - depth if (int(mLevel) > mMaxLevel) this->next(); } template inline void NodeIteratorBase::setMaxDepth(Index maxDepth) { // level = ROOT_LEVEL - depth mMinLevel = int(ROOT_LEVEL - std::min(maxDepth, this->getLeafDepth())); if (int(mLevel) < mMinLevel) this->next(); } template inline bool NodeIteratorBase::next() { do { if (mDone) return false; // If the iterator over the current node points to a child, // descend to the child (depth-first traversal). if (int(mLevel) > mMinLevel && mIterList.test(mLevel)) { if (!mIterList.down(mLevel)) return false; --mLevel; } else { // Ascend to the nearest ancestor that has other children. while (!mIterList.test(mLevel)) { if (mLevel == ROOT_LEVEL) { // Can't ascend higher than the root. mDone = true; return false; } ++mLevel; // ascend one level mIterList.next(mLevel); // advance to the next child, if there is one } // Descend to the child. if (!mIterList.down(mLevel)) return false; --mLevel; } } while (int(mLevel) < mMinLevel || int(mLevel) > mMaxLevel); return true; } template inline Coord NodeIteratorBase::getCoord() const { if (mLevel != ROOT_LEVEL) return mIterList.getCoord(mLevel + 1); RootNodeT* root = NULL; this->getNode(root); return root ? root->getMinIndex() : Coord::min(); } template inline bool NodeIteratorBase::getBoundingBox(CoordBBox& bbox) const { if (mLevel == ROOT_LEVEL) { RootNodeT* root = NULL; this->getNode(root); if (root == NULL) { bbox = CoordBBox(); return false; } root->getIndexRange(bbox); return true; } bbox.min() = mIterList.getCoord(mLevel + 1); bbox.max() = bbox.min().offsetBy(mIterList.getChildDim(mLevel + 1) - 1); return true; } template inline std::string NodeIteratorBase::summary() const { std::ostringstream ostr; for (int lvl = int(ROOT_LEVEL); lvl >= 0 && lvl >= int(mLevel); --lvl) { if (lvl == 0) ostr << "leaf"; else if (lvl == int(ROOT_LEVEL)) ostr << "root"; else ostr << "int" << (ROOT_LEVEL - lvl); ostr << " c" << mIterList.pos(lvl); if (lvl > int(mLevel)) ostr << " / "; } CoordBBox bbox; this->getBoundingBox(bbox); ostr << " " << bbox; return ostr.str(); } //////////////////////////////////////// /// @brief Base class for tree-traversal iterators over all leaf nodes (but not leaf voxels) template class LeafIteratorBase { public: typedef RootChildOnIterT RootIterT; typedef typename RootIterT::NodeType RootNodeT; typedef typename RootIterT::NonConstNodeType NCRootNodeT; static const Index ROOT_LEVEL = RootNodeT::LEVEL; typedef typename iter::InvertedTree::Type InvTreeT; typedef typename boost::mpl::front::type NCLeafNodeT; typedef typename CopyConstness::Type LeafNodeT; static const Index LEAF_LEVEL = 0, LEAF_PARENT_LEVEL = LEAF_LEVEL + 1; typedef IterTraits RootIterTraits; LeafIteratorBase(): mIterList(NULL), mTree(NULL) {} LeafIteratorBase(TreeT& tree): mIterList(NULL), mTree(&tree) { // Initialize the iterator list with a root node iterator. mIterList.setIter(RootIterTraits::begin(tree.getRootNode())); // Descend along the first branch, initializing the node iterator at each level. Index lvl = ROOT_LEVEL; for ( ; lvl > 0 && mIterList.down(lvl); --lvl) {} // If the first branch terminated above the leaf level, backtrack to the next branch. if (lvl > 0) this->next(); } LeafIteratorBase(const LeafIteratorBase& other): mIterList(other.mIterList), mTree(other.mTree) { mIterList.updateBackPointers(); } LeafIteratorBase& operator=(const LeafIteratorBase& other) { if (&other != this) { mTree = other.mTree; mIterList = other.mIterList; mIterList.updateBackPointers(); } return *this; } //@{ /// Return the leaf node to which the iterator is pointing. LeafNodeT* getLeaf() const { LeafNodeT* n = NULL; mIterList.getNode(LEAF_LEVEL, n); return n; } LeafNodeT& operator*() const { return *this->getLeaf(); } LeafNodeT* operator->() const { return this->getLeaf(); } //@} bool test() const { return mIterList.test(LEAF_PARENT_LEVEL); } operator bool() const { return this->test(); } //@{ /// Advance the iterator to the next leaf node. bool next(); void increment() { this->next(); } LeafIteratorBase& operator++() { this->increment(); return *this; } //@} /// Increment the iterator n times. void increment(Index n) { for (Index i = 0; i < n && this->next(); ++i) {} } TreeT* getTree() const { return mTree; } private: struct PrevItem { typedef RootIterT IterT; }; /// @note Even though a LeafIterator doesn't iterate over leaf voxels, /// the first item of this linked list of node iterators is a leaf node iterator, /// whose purpose is only to provide access to its parent leaf node. IterListItem mIterList; TreeT* mTree; }; // class LeafIteratorBase template inline bool LeafIteratorBase::next() { // If the iterator is valid for the current node one level above the leaf level, // advance the iterator to the node's next child. if (mIterList.test(LEAF_PARENT_LEVEL) && mIterList.next(LEAF_PARENT_LEVEL)) { mIterList.down(LEAF_PARENT_LEVEL); // initialize the leaf iterator return true; } Index lvl = LEAF_PARENT_LEVEL; while (!mIterList.test(LEAF_PARENT_LEVEL)) { if (mIterList.test(lvl)) { mIterList.next(lvl); } else { do { // Ascend to the nearest level at which // one of the iterators is not yet exhausted. if (lvl == ROOT_LEVEL) return false; ++lvl; if (mIterList.test(lvl)) mIterList.next(lvl); } while (!mIterList.test(lvl)); } // Descend to the lowest child, but not as far as the leaf iterator. while (lvl > LEAF_PARENT_LEVEL && mIterList.down(lvl)) --lvl; } mIterList.down(LEAF_PARENT_LEVEL); // initialize the leaf iterator return true; } //////////////////////////////////////// /// An IteratorRange wraps a tree or node iterator, giving the iterator TBB /// splittable range semantics. template class IteratorRange { public: IteratorRange(const IterT& iter, size_t grainSize = 8): mIter(iter), mGrainSize(grainSize), mSize(0) { mSize = this->size(); } IteratorRange(IteratorRange& other, tbb::split): mIter(other.mIter), mGrainSize(other.mGrainSize), mSize(other.mSize >> 1) { other.increment(mSize); } /// @brief Return a reference to this range's iterator. /// @note The reference is const, because the iterator should not be /// incremented directly. Use this range object's increment() instead. const IterT& iterator() const { return mIter; } bool empty() const { return mSize == 0 || !mIter.test(); } bool test() const { return !this->empty(); } operator bool() const { return !this->empty(); } /// @brief Return @c true if this range is splittable (i.e., if the iterator /// can be advanced more than mGrainSize times). bool is_divisible() const { return mSize > mGrainSize; } /// Advance the iterator @a n times. void increment(Index n = 1) { for ( ; n > 0 && mSize > 0; --n, --mSize, ++mIter) {} } /// Advance the iterator to the next item. IteratorRange& operator++() { this->increment(); return *this; } /// @brief Advance the iterator to the next item. /// @return @c true if the iterator is not yet exhausted. bool next() { this->increment(); return this->test(); } private: Index size() const { Index n = 0; for (IterT it(mIter); it.test(); ++n, ++it) {} return n; } IterT mIter; size_t mGrainSize; /// @note mSize is only an estimate of the number of times mIter can be incremented /// before it is exhausted (because the topology of the underlying tree could change /// during iteration). For the purpose of range splitting, though, that should be /// sufficient, since the two halves need not be of exactly equal size. Index mSize; }; //////////////////////////////////////// /// @brief Base class for tree-traversal iterators over real and virtual voxel values /// @todo class TreeVoxelIteratorBase; } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_TREEITERATOR_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/NodeUnion.h0000644000000000000000000001167112252453157014233 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file NodeUnion.h /// /// @author Peter Cucka /// /// NodeUnion is a templated helper class that controls access to either /// the child node pointer or the value for a particular element of a root /// or internal node. For space efficiency, the child pointer and the value /// are unioned, since the two are never in use simultaneously. /// Template specializations of NodeUnion allow for values of either POD /// (int, float, pointer, etc.) or class (std::string, math::Vec, etc.) types. /// (The latter cannot be stored directly in a union.) #ifndef OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED #define OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { // Internal implementation of a union of a child node pointer and a value template class NodeUnionImpl; // Partial specialization for values of non-class types // (int, float, pointer, etc.) that stores elements by value template class NodeUnionImpl { private: union { ChildT* child; ValueT value; } mUnion; public: NodeUnionImpl() { setChild(NULL); } ChildT* getChild() const { return mUnion.child; } const ValueT& getValue() const { return mUnion.value; } ValueT& getValue() { return mUnion.value; } void setChild(ChildT* child) { mUnion.child = child; } void setValue(const ValueT& val) { mUnion.value = val; } }; // Partial specialization for values of class types (std::string, // math::Vec, etc.) that stores elements by pointer template class NodeUnionImpl { private: union { ChildT* child; ValueT* value; } mUnion; bool mHasChild; public: NodeUnionImpl(): mHasChild(true) { setChild(NULL); } NodeUnionImpl(const NodeUnionImpl& other) { if (other.mHasChild) setChild(other.getChild()); else setValue(other.getValue()); } NodeUnionImpl& operator=(const NodeUnionImpl& other) { if (other.mHasChild) setChild(other.getChild()); else setValue(other.getValue()); } ~NodeUnionImpl() { setChild(NULL); } ChildT* getChild() const { return mHasChild ? mUnion.child : NULL; } void setChild(ChildT* child) { if (!mHasChild) delete mUnion.value; mUnion.child = child; mHasChild = true; } const ValueT& getValue() const { return *mUnion.value; } ValueT& getValue() { return *mUnion.value; } void setValue(const ValueT& val) { /// @todo To minimize storage across nodes, intern and reuse /// common values, using, e.g., boost::flyweight. if (!mHasChild) delete mUnion.value; mUnion.value = new ValueT(val); mHasChild = false; } }; template struct NodeUnion: public NodeUnionImpl< boost::is_class::value, ValueT, ChildT> { NodeUnion() {} }; } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/LeafManager.h0000644000000000000000000005765312252453157014511 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file LeafManager.h /// /// A LeafManager manages a linear array of pointers to a given tree's /// leaf nodes, as well as optional auxiliary buffers (one or more per leaf) /// that can be swapped with the leaf nodes' voxel data buffers. /// The leaf array is useful for multithreaded computations over /// leaf voxels in a tree with static topology but varying voxel values. /// The auxiliary buffers are convenient for temporal integration. /// Efficient methods are provided for multithreaded swapping and synching /// (i.e., copying the contents) of these buffers. #ifndef OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED #define OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED #include #include #include #include #include #include #include "TreeIterator.h" // for CopyConstness namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { namespace leafmgr { //@{ /// Useful traits for Tree types template struct TreeTraits { static const bool IsConstTree = false; typedef typename TreeT::LeafIter LeafIterType; }; template struct TreeTraits { static const bool IsConstTree = true; typedef typename TreeT::LeafCIter LeafIterType; }; //@} } // namespace leafmgr /// This helper class implements LeafManager methods that need to be /// specialized for const vs. non-const trees. template struct LeafManagerImpl { typedef typename ManagerT::RangeType RangeT; typedef typename ManagerT::LeafType LeafT; typedef typename ManagerT::BufferType BufT; static inline void doSwapLeafBuffer(const RangeT& r, size_t auxBufferIdx, LeafT** leafs, BufT* bufs, size_t bufsPerLeaf) { for (size_t n = r.begin(), m = r.end(), N = bufsPerLeaf; n != m; ++n) { leafs[n]->swap(bufs[n * N + auxBufferIdx]); } } }; //////////////////////////////////////// /// @brief This class manages a linear array of pointers to a given tree's /// leaf nodes, as well as optional auxiliary buffers (one or more per leaf) /// that can be swapped with the leaf nodes' voxel data buffers. /// @details The leaf array is useful for multithreaded computations over /// leaf voxels in a tree with static topology but varying voxel values. /// The auxiliary buffers are convenient for temporal integration. /// Efficient methods are provided for multithreaded swapping and sync'ing /// (i.e., copying the contents) of these buffers. /// /// @note Buffer index 0 denotes a leaf node's internal voxel data buffer. /// Any auxiliary buffers are indexed starting from one. template class LeafManager { public: typedef TreeT TreeType; typedef typename TreeType::LeafNodeType NonConstLeafType; typedef typename CopyConstness::Type LeafType; typedef typename leafmgr::TreeTraits::LeafIterType LeafIterType; typedef typename LeafType::Buffer NonConstBufferType; typedef typename CopyConstness::Type BufferType; typedef tbb::blocked_range RangeType;//leaf index range static const bool IsConstTree = leafmgr::TreeTraits::IsConstTree; class LeafRange { public: class Iterator { public: Iterator(const LeafRange& range, size_t pos): mRange(range), mPos(pos) { assert(this->isValid()); } /// Advance to the next leaf node. Iterator& operator++() { ++mPos; return *this; } /// Return a reference to the leaf node to which this iterator is pointing. LeafType& operator*() const { return mRange.mLeafManager.leaf(mPos); } /// Return a pointer to the leaf node to which this iterator is pointing. LeafType* operator->() const { return &(this->operator*()); } /// @brief Return the nth buffer for the leaf node to which this iterator is pointing, /// where n = @a bufferIdx and n = 0 corresponds to the leaf node's own buffer. BufferType& buffer(size_t bufferIdx) { return mRange.mLeafManager.getBuffer(mPos, bufferIdx); } /// Return the index into the leaf array of the current leaf node. size_t pos() const { return mPos; } bool isValid() const { return mPos>=mRange.mBegin && mPos<=mRange.mEnd; } /// Return @c true if this iterator is not yet exhausted. bool test() const { return mPos < mRange.mEnd; } /// Return @c true if this iterator is not yet exhausted. operator bool() const { return this->test(); } /// Return @c true if this iterator is exhausted. bool empty() const { return !this->test(); } //bool operator<( const Iterator& other ) const { return mPos < other.mPos; } void operator=( const Iterator& other) { mRange = other.mRange; mPos = other.mPos; } bool operator!=(const Iterator& other) const { return (mPos != other.mPos) || (&mRange != &other.mRange); } bool operator==(const Iterator& other) const { return !(*this != other); } const LeafRange& leafRange() const { return mRange; } private: const LeafRange& mRange; size_t mPos; };// end Iterator LeafRange(size_t begin, size_t end, const LeafManager& leafManager, size_t grainSize=1): mEnd(end), mBegin(begin), mGrainSize(grainSize), mLeafManager(leafManager) {} Iterator begin() const {return Iterator(*this, mBegin);} Iterator end() const {return Iterator(*this, mEnd);} size_t size() const { return mEnd - mBegin; } size_t grainsize() const { return mGrainSize; } const LeafManager& leafManager() const { return mLeafManager; } bool empty() const {return !(mBegin < mEnd);} bool is_divisible() const {return mGrainSize < this->size();} LeafRange(LeafRange& r, tbb::split): mEnd(r.mEnd), mBegin(doSplit(r)), mGrainSize(r.mGrainSize), mLeafManager(r.mLeafManager) {} private: size_t mEnd, mBegin, mGrainSize; const LeafManager& mLeafManager; static size_t doSplit(LeafRange& r) { assert(r.is_divisible()); size_t middle = r.mBegin + (r.mEnd - r.mBegin) / 2u; r.mEnd = middle; return middle; } };// end of LeafRange /// @brief Constructor from a tree reference and an auxiliary buffer count /// (default is no auxiliary buffers) LeafManager(TreeType& tree, size_t auxBuffersPerLeaf=0, bool serial=false): mTree(&tree), mLeafCount(0), mAuxBufferCount(0), mAuxBuffersPerLeaf(auxBuffersPerLeaf), mLeafs(NULL), mAuxBuffers(NULL), mTask(0), mIsMaster(true) { this->rebuild(serial); } /// Shallow copy constructor called by tbb::parallel_for() threads /// /// @note This should never get called directly LeafManager(const LeafManager& other): mTree(other.mTree), mLeafCount(other.mLeafCount), mAuxBufferCount(other.mAuxBufferCount), mAuxBuffersPerLeaf(other.mAuxBuffersPerLeaf), mLeafs(other.mLeafs), mAuxBuffers(other.mAuxBuffers), mTask(other.mTask), mIsMaster(false) { } virtual ~LeafManager() { if (mIsMaster) { delete [] mLeafs; delete [] mAuxBuffers; } } /// @brief (Re)initialize by resizing (if necessary) and repopulating the leaf array /// and by deleting existing auxiliary buffers and allocating new ones. /// @details Call this method if the tree's topology, and therefore the number /// of leaf nodes, changes. New auxiliary buffers are initialized with copies /// of corresponding leaf node buffers. void rebuild(bool serial=false) { this->initLeafArray(); this->initAuxBuffers(serial); } //@{ /// Repopulate the leaf array and delete and reallocate auxiliary buffers. void rebuild(size_t auxBuffersPerLeaf, bool serial=false) { mAuxBuffersPerLeaf = auxBuffersPerLeaf; this->rebuild(serial); } void rebuild(TreeType& tree, bool serial=false) { mTree = &tree; this->rebuild(serial); } void rebuild(TreeType& tree, size_t auxBuffersPerLeaf, bool serial=false) { mTree = &tree; mAuxBuffersPerLeaf = auxBuffersPerLeaf; this->rebuild(serial); } //@} /// @brief Change the number of auxiliary buffers. /// @details If auxBuffersPerLeaf is 0, all existing auxiliary buffers are deleted. /// New auxiliary buffers are initialized with copies of corresponding leaf node buffers. /// This method does not rebuild the leaf array. void rebuildAuxBuffers(size_t auxBuffersPerLeaf, bool serial=false) { mAuxBuffersPerLeaf = auxBuffersPerLeaf; this->initAuxBuffers(serial); } /// @brief Remove the auxiliary buffers, but don't rebuild the leaf array. void removeAuxBuffers() { this->rebuildAuxBuffers(0); } /// @brief Remove the auxiliary buffers and rebuild the leaf array. void rebuildLeafArray() { this->removeAuxBuffers(); this->initLeafArray(); } /// @deprecated Use rebuildLeafArray() instead. OPENVDB_DEPRECATED void rebuildLeafs() { this->rebuildLeafArray(); } /// Return the total number of allocated auxiliary buffers. size_t auxBufferCount() const { return mAuxBufferCount; } /// Return the number of auxiliary buffers per leaf node. size_t auxBuffersPerLeaf() const { return mAuxBuffersPerLeaf; } /// Return the number of leaf nodes. size_t leafCount() const { return mLeafCount; } /// Return the tree associated with this manager. TreeType& tree() { return *mTree; } /// Return @c true if the tree associated with this manager is immutable. bool isConstTree() const { return this->IsConstTree; } /// @brief Return a pointer to the leaf node at index @a leafIdx in the array. /// @note For performance reasons no range check is performed (other than an assertion)! LeafType& leaf(size_t leafIdx) const { assert(leafIdx 0), /// but it is not safe to modify the leaf buffer (@a bufferIdx = 0). BufferType& getBuffer(size_t leafIdx, size_t bufferIdx) const { assert(leafIdx < mLeafCount); assert(bufferIdx == 0 || bufferIdx - 1 < mAuxBuffersPerLeaf); return bufferIdx == 0 ? mLeafs[leafIdx]->buffer() : mAuxBuffers[leafIdx * mAuxBuffersPerLeaf + bufferIdx - 1]; } /// @brief Return a @c tbb::blocked_range of leaf array indices. /// /// @note Consider using leafRange() instead, which provides access methods /// to leaf nodes and buffers. RangeType getRange(size_t grainsize = 1) const { return RangeType(0, mLeafCount, grainsize); } /// Return a TBB-compatible LeafRange. LeafRange leafRange(size_t grainsize = 1) const { return LeafRange(0, mLeafCount, *this, grainsize); } /// @brief Swap each leaf node's buffer with the nth corresponding auxiliary buffer, /// where n = @a bufferIdx. /// @return @c true if the swap was successful /// @param bufferIdx index of the buffer that will be swapped with /// the corresponding leaf node buffer /// @param serial if false, swap buffers in parallel using multiple threads. /// @note Recall that the indexing of auxiliary buffers is 1-based, since /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes /// the first auxiliary buffer. bool swapLeafBuffer(size_t bufferIdx, bool serial = false) { if (bufferIdx == 0 || bufferIdx > mAuxBuffersPerLeaf || this->isConstTree()) return false; mTask = boost::bind(&LeafManager::doSwapLeafBuffer, _1, _2, bufferIdx - 1); this->cook(serial ? 0 : 512); return true;//success } /// @brief Swap any two buffers for each leaf node. /// @note Recall that the indexing of auxiliary buffers is 1-based, since /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes /// the first auxiliary buffer. bool swapBuffer(size_t bufferIdx1, size_t bufferIdx2, bool serial = false) { const size_t b1 = std::min(bufferIdx1, bufferIdx2); const size_t b2 = std::max(bufferIdx1, bufferIdx2); if (b1 == b2 || b2 > mAuxBuffersPerLeaf) return false; if (b1 == 0) { if (this->isConstTree()) return false; mTask = boost::bind(&LeafManager::doSwapLeafBuffer, _1, _2, b2-1); } else { mTask = boost::bind(&LeafManager::doSwapAuxBuffer, _1, _2, b1-1, b2-1); } this->cook(serial ? 0 : 512); return true;//success } /// @brief Sync up the specified auxiliary buffer with the corresponding leaf node buffer. /// @return @c true if the sync was successful /// @param bufferIdx index of the buffer that will contain a /// copy of the corresponding leaf node buffer /// @param serial if false, sync buffers in parallel using multiple threads. /// @note Recall that the indexing of auxiliary buffers is 1-based, since /// buffer index 0 denotes the leaf node buffer. So buffer index 1 denotes /// the first auxiliary buffer. bool syncAuxBuffer(size_t bufferIdx, bool serial = false) { if (bufferIdx == 0 || bufferIdx > mAuxBuffersPerLeaf) return false; mTask = boost::bind(&LeafManager::doSyncAuxBuffer, _1, _2, bufferIdx - 1); this->cook(serial ? 0 : 64); return true;//success } /// @brief Sync up all auxiliary buffers with their corresponding leaf node buffers. /// @return true if the sync was successful /// @param serial if false, sync buffers in parallel using multiple threads. bool syncAllBuffers(bool serial = false) { switch (mAuxBuffersPerLeaf) { case 0: return false;//nothing to do case 1: mTask = boost::bind(&LeafManager::doSyncAllBuffers1, _1, _2); break; case 2: mTask = boost::bind(&LeafManager::doSyncAllBuffers2, _1, _2); break; default: mTask = boost::bind(&LeafManager::doSyncAllBuffersN, _1, _2); break; } this->cook(serial ? 0 : 64); return true;//success } /// @brief Threaded method that applies a user-supplied functor /// to each leaf node in the LeafManager /// /// @param op user-supplied functor, see examples for interface details. /// @param threaded optional toggle to disable threading, on by default. /// @param grainSize optional parameter to specify the grainsize /// for threading, one by default. /// /// @warning The functor object is deep-copied to create TBB tasks. /// /// @par Example: /// @code /// // Functor to offset a tree's voxel values with values from another tree. /// template /// struct OffsetOp /// { /// typedef tree::ValueAccessor Accessor; /// /// OffsetOp(const TreeType& tree): mRhsTreeAcc(tree) {} /// /// template /// void operator()(LeafNodeType &lhsLeaf, size_t) const /// { /// const LeafNodeType * rhsLeaf = mRhsTreeAcc.probeConstLeaf(lhsLeaf.origin()); /// if (rhsLeaf) { /// typename LeafNodeType::ValueOnIter iter = lhsLeaf.beginValueOn(); /// for (; iter; ++iter) { /// iter.setValue(iter.getValue() + rhsLeaf->getValue(iter.pos())); /// } /// } /// } /// private: /// Accessor mRhsTreeAcc; /// }; /// /// // usage: /// tree::LeafManager leafNodes(lhsTree); /// leafNodes.foreach(OffsetOp(rhsTree)); /// /// // A functor that performs a min operation between different auxiliary buffers. /// template /// struct MinOp /// { /// typedef typename LeafManagerType::BufferType BufferType; /// /// MinOp(LeafManagerType& leafNodes): mLeafs(leafNodes) {} /// /// template /// void operator()(LeafNodeType &leaf, size_t leafIndex) const /// { /// // get the first buffer /// BufferType& buffer = mLeafs.getBuffer(leafIndex, 1); /// /// // min ... /// } /// private: /// LeafManagerType& mLeafs; /// }; /// @endcode template void foreach(const LeafOp& op, bool threaded = true, size_t grainSize=1) { LeafTransformer transform(op); transform.run(this->leafRange(grainSize), threaded); } //////////////////////////////////////////////////////////////////////////////////// // All methods below are for internal use only and should never be called directly /// Used internally by tbb::parallel_for() - never call it directly! void operator()(const RangeType& r) const { if (mTask) mTask(const_cast(this), r); else OPENVDB_THROW(ValueError, "task is undefined"); } private: void initLeafArray() { const size_t leafCount = mTree->leafCount(); if (leafCount != mLeafCount) { delete [] mLeafs; mLeafs = (leafCount == 0) ? NULL : new LeafType*[leafCount]; mLeafCount = leafCount; } LeafIterType iter = mTree->beginLeaf(); for (size_t n = 0; n != leafCount; ++n, ++iter) mLeafs[n] = iter.getLeaf(); } void initAuxBuffers(bool serial) { const size_t auxBufferCount = mLeafCount * mAuxBuffersPerLeaf; if (auxBufferCount != mAuxBufferCount) { delete [] mAuxBuffers; mAuxBuffers = (auxBufferCount == 0) ? NULL : new NonConstBufferType[auxBufferCount]; mAuxBufferCount = auxBufferCount; } this->syncAllBuffers(serial); } void cook(size_t grainsize) { if (grainsize>0) { tbb::parallel_for(this->getRange(grainsize), *this); } else { (*this)(this->getRange()); } } void doSwapLeafBuffer(const RangeType& r, size_t auxBufferIdx) { LeafManagerImpl::doSwapLeafBuffer( r, auxBufferIdx, mLeafs, mAuxBuffers, mAuxBuffersPerLeaf); } void doSwapAuxBuffer(const RangeType& r, size_t auxBufferIdx1, size_t auxBufferIdx2) { for (size_t N = mAuxBuffersPerLeaf, n = N*r.begin(), m = N*r.end(); n != m; n+=N) { mAuxBuffers[n + auxBufferIdx1].swap(mAuxBuffers[n + auxBufferIdx2]); } } void doSyncAuxBuffer(const RangeType& r, size_t auxBufferIdx) { for (size_t n = r.begin(), m = r.end(), N = mAuxBuffersPerLeaf; n != m; ++n) { mAuxBuffers[n*N + auxBufferIdx] = mLeafs[n]->buffer(); } } void doSyncAllBuffers1(const RangeType& r) { for (size_t n = r.begin(), m = r.end(); n != m; ++n) { mAuxBuffers[n] = mLeafs[n]->buffer(); } } void doSyncAllBuffers2(const RangeType& r) { for (size_t n = r.begin(), m = r.end(); n != m; ++n) { const BufferType& leafBuffer = mLeafs[n]->buffer(); mAuxBuffers[2*n ] = leafBuffer; mAuxBuffers[2*n+1] = leafBuffer; } } void doSyncAllBuffersN(const RangeType& r) { for (size_t n = r.begin(), m = r.end(), N = mAuxBuffersPerLeaf; n != m; ++n) { const BufferType& leafBuffer = mLeafs[n]->buffer(); for (size_t i=n*N, j=i+N; i!=j; ++i) mAuxBuffers[i] = leafBuffer; } } /// @brief Private member class that applies a user-defined /// functor to all the leaf nodes. template struct LeafTransformer { LeafTransformer(const LeafOp& leafOp) : mLeafOp(leafOp) {} void run(const LeafRange& range, bool threaded = true) { threaded ? tbb::parallel_for(range, *this) : (*this)(range); } void operator()(const LeafRange& range) const { for (typename LeafRange::Iterator it = range.begin(); it; ++it) mLeafOp(*it, it.pos()); } const LeafOp mLeafOp; }; typedef typename boost::function FuncType; TreeType* mTree; size_t mLeafCount, mAuxBufferCount, mAuxBuffersPerLeaf; LeafType** mLeafs;//array of LeafNode pointers NonConstBufferType* mAuxBuffers;//array of auxiliary buffers FuncType mTask; const bool mIsMaster; };//end of LeafManager class // Partial specializations of LeafManager methods for const trees template struct LeafManagerImpl > { typedef LeafManager ManagerT; typedef typename ManagerT::RangeType RangeT; typedef typename ManagerT::LeafType LeafT; typedef typename ManagerT::BufferType BufT; static inline void doSwapLeafBuffer(const RangeT&, size_t /*auxBufferIdx*/, LeafT**, BufT*, size_t /*bufsPerLeaf*/) { // Buffers can't be swapped into const trees. } }; } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_LEAFMANAGER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/InternalNode.h0000644000000000000000000032564512252453157014730 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file InternalNode.h /// /// @brief Internal table nodes for OpenVDB trees #ifndef OPENVDB_TREE_INTERNALNODE_HAS_BEEN_INCLUDED #define OPENVDB_TREE_INTERNALNODE_HAS_BEEN_INCLUDED #include #include #include #include #include // for io::readData(), etc. #include // for Abs(), isExactlyEqual() #include #include #include "Iterator.h" #include "NodeUnion.h" #include "Util.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { template class InternalNode { public: typedef _ChildNodeType ChildNodeType; typedef typename ChildNodeType::LeafNodeType LeafNodeType; typedef typename ChildNodeType::ValueType ValueType; typedef NodeUnion UnionType; typedef util::NodeMask NodeMaskType; static const Index LOG2DIM = Log2Dim, TOTAL = Log2Dim + ChildNodeType::TOTAL, DIM = 1 << TOTAL, NUM_VALUES = 1 << (3 * Log2Dim), LEVEL = 1 + ChildNodeType::LEVEL; // level 0 = leaf static const Index64 NUM_VOXELS = uint64_t(1) << (3 * TOTAL); // total # of voxels represented by this node /// @brief ValueConverter::Type is the type of an InternalNode having the same /// child hierarchy and dimensions as this node but a different value type, T. template struct ValueConverter { typedef InternalNode::Type, Log2Dim> Type; }; InternalNode() {} explicit InternalNode(const ValueType& offValue); InternalNode(const Coord&, const ValueType& value, bool active = false); /// Deep copy constructor InternalNode(const InternalNode&); /// Topology copy constructor template InternalNode(const InternalNode& other, const ValueType& background, TopologyCopy); /// Topology copy constructor template InternalNode(const InternalNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy); virtual ~InternalNode(); protected: typedef typename NodeMaskType::OnIterator MaskOnIterator; typedef typename NodeMaskType::OffIterator MaskOffIterator; typedef typename NodeMaskType::DenseIterator MaskDenseIterator; // Type tags to disambiguate template instantiations struct ValueOn {}; struct ValueOff {}; struct ValueAll {}; struct ChildOn {}; struct ChildOff {}; struct ChildAll {}; // The following class templates implement the iterator interfaces specified in Iterator.h // by providing getItem(), setItem() and/or modifyItem() methods. template struct ChildIter: public SparseIteratorBase< MaskIterT, ChildIter, NodeT, ChildT> { ChildIter() {} ChildIter(const MaskIterT& iter, NodeT* parent): SparseIteratorBase< MaskIterT, ChildIter, NodeT, ChildT>(iter, parent) {} ChildT& getItem(Index pos) const { return *(this->parent().getChildNode(pos)); } // Note: setItem() can't be called on const iterators. void setItem(Index pos, const ChildT& c) const { this->parent().resetChildNode(pos, &c); } // Note: modifyItem() isn't implemented, since it's not useful for child node pointers. };// ChildIter template struct ValueIter: public SparseIteratorBase< MaskIterT, ValueIter, NodeT, ValueT> { ValueIter() {} ValueIter(const MaskIterT& iter, NodeT* parent): SparseIteratorBase< MaskIterT, ValueIter, NodeT, ValueT>(iter, parent) {} const ValueT& getItem(Index pos) const { return this->parent().mNodes[pos].getValue(); } // Note: setItem() can't be called on const iterators. void setItem(Index pos, const ValueT& v) const { this->parent().mNodes[pos].setValue(v); } // Note: modifyItem() can't be called on const iterators. template void modifyItem(Index pos, const ModifyOp& op) const { op(this->parent().mNodes[pos].getValue()); } };// ValueIter template struct DenseIter: public DenseIteratorBase< MaskDenseIterator, DenseIter, NodeT, ChildT, ValueT> { typedef DenseIteratorBase BaseT; typedef typename BaseT::NonConstValueType NonConstValueT; DenseIter() {} DenseIter(const MaskDenseIterator& iter, NodeT* parent): DenseIteratorBase(iter, parent) {} bool getItem(Index pos, ChildT*& child, NonConstValueT& value) const { child = this->parent().getChildNode(pos); if (!child) value = this->parent().mNodes[pos].getValue(); return (child != NULL); } // Note: setItem() can't be called on const iterators. void setItem(Index pos, ChildT* child) const { this->parent().resetChildNode(pos, child); } // Note: unsetItem() can't be called on const iterators. void unsetItem(Index pos, const ValueT& value) const { this->parent().unsetChildNode(pos, value); } };// DenseIter public: // Iterators (see Iterator.h for usage) typedef ChildIter ChildOnIter; typedef ChildIter ChildOnCIter; typedef ValueIter ChildOffIter; typedef ValueIter ChildOffCIter; typedef DenseIter ChildAllIter; typedef DenseIter ChildAllCIter; typedef ValueIter ValueOnIter; typedef ValueIter ValueOnCIter; typedef ValueIter ValueOffIter; typedef ValueIter ValueOffCIter; typedef ValueIter ValueAllIter; typedef ValueIter ValueAllCIter; ChildOnCIter cbeginChildOn() const { return ChildOnCIter(mChildMask.beginOn(), this); } ChildOffCIter cbeginChildOff() const { return ChildOffCIter(mChildMask.beginOff(), this); } ChildAllCIter cbeginChildAll() const { return ChildAllCIter(mChildMask.beginDense(), this); } ChildOnCIter beginChildOn() const { return cbeginChildOn(); } ChildOffCIter beginChildOff() const { return cbeginChildOff(); } ChildAllCIter beginChildAll() const { return cbeginChildAll(); } ChildOnIter beginChildOn() { return ChildOnIter(mChildMask.beginOn(), this); } ChildOffIter beginChildOff() { return ChildOffIter(mChildMask.beginOff(), this); } ChildAllIter beginChildAll() { return ChildAllIter(mChildMask.beginDense(), this); } ValueOnCIter cbeginValueOn() const { return ValueOnCIter(mValueMask.beginOn(), this); } ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mChildMask.beginOff(), this); } ValueOnCIter beginValueOn() const { return cbeginValueOn(); } ValueOffCIter beginValueOff() const { return cbeginValueOff(); } ValueAllCIter beginValueAll() const { return cbeginValueAll(); } ValueOnIter beginValueOn() { return ValueOnIter(mValueMask.beginOn(), this); } ValueOffIter beginValueOff() { return ValueOffIter(mValueMask.beginOff(), this); } ValueAllIter beginValueAll() { return ValueAllIter(mChildMask.beginOff(), this); } static Index dim() { return DIM; } static Index getLevel() { return LEVEL; } static void getNodeLog2Dims(std::vector& dims); static Index getChildDim() { return ChildNodeType::DIM; } /// Return the linear table offset of the given global or local coordinates. static Index coordToOffset(const Coord& xyz); /// @brief Return the local coordinates for a linear table offset, /// where offset 0 has coordinates (0, 0, 0). static void offsetToLocalCoord(Index n, Coord& xyz); /// Return the global coordinates for a linear table offset. Coord offsetToGlobalCoord(Index n) const; /// Return the grid index coordinates of this node's local origin. const Coord& origin() const { return mOrigin; } /// @brief Return the grid index coordinates of this node's local origin. /// @deprecated Use origin() instead. OPENVDB_DEPRECATED Coord getOrigin() const { return mOrigin; } /// Set the grid index coordinates of this node's local origin. void setOrigin(const Coord& origin) { mOrigin = origin; } Index32 leafCount() const; Index32 nonLeafCount() const; Index64 onVoxelCount() const; Index64 offVoxelCount() const; Index64 onLeafVoxelCount() const; Index64 offLeafVoxelCount() const; Index64 onTileCount() const; /// Return the total amount of memory in bytes occupied by this node and its children. Index64 memUsage() const; /// @brief Expand the specified bounding box so that it includes the active tiles /// of this internal node as well as all the active values in its child nodes. /// If visitVoxels is false LeafNodes will be approximated as dense, i.e. with all /// voxels active. Else the individual active voxels are visited to produce a tight bbox. void evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels = true) const; OPENVDB_DEPRECATED void evalActiveVoxelBoundingBox(CoordBBox& bbox) const; /// @brief Return the bounding box of this node, i.e., the full index space /// spanned by the node regardless of its content. CoordBBox getNodeBoundingBox() const { return CoordBBox::createCube(mOrigin, DIM); } bool isEmpty() const { return mChildMask.isOff(); } /// Return @c true if all of this node's table entries have the same active state /// and the same constant value to within the given tolerance, /// and return that value in @a constValue and the active state in @a state. bool isConstant(ValueType& constValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// Return @c true if this node has no children and only contains inactive values. bool isInactive() const { return this->isChildMaskOff() && this->isValueMaskOff(); } /// Return @c true if the voxel at the given coordinates is active. bool isValueOn(const Coord& xyz) const; /// Return @c true if the voxel at the given offset is active. bool isValueOn(Index offset) const { return mValueMask.isOn(offset); } /// Return @c true if this node or any of its child nodes have any active tiles. bool hasActiveTiles() const; const ValueType& getValue(const Coord& xyz) const; bool probeValue(const Coord& xyz, ValueType& value) const; /// @brief Return the level of the tree (0 = leaf) at which the value /// at the given coordinates resides. Index getValueLevel(const Coord& xyz) const; /// @brief If the first entry in this node's table is a tile, return the tile's value. /// Otherwise, return the result of calling getFirstValue() on the child. const ValueType& getFirstValue() const; /// @brief If the last entry in this node's table is a tile, return the tile's value. /// Otherwise, return the result of calling getLastValue() on the child. const ValueType& getLastValue() const; /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on); /// Set the value of the voxel at the given coordinates but don't change its active state. void setValueOnly(const Coord& xyz, const ValueType& value); /// Mark the voxel at the given coordinates as active but don't change its value. void setValueOn(const Coord& xyz); /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValueOn(const Coord& xyz, const ValueType& value); /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz); /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value); /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. template void modifyValue(const Coord& xyz, const ModifyOp& op); /// Apply a functor to the voxel at the given coordinates. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); /// Return the value of the voxel at the given coordinates and, if necessary, update /// the accessor with pointers to the nodes along the path from the root node to /// the node containing the voxel. /// @note Used internally by ValueAccessor. template const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const; /// Return @c true if the voxel at the given coordinates is active and, if necessary, /// update the accessor with pointers to the nodes along the path from the root node /// to the node containing the voxel. /// @note Used internally by ValueAccessor. template bool isValueOnAndCache(const Coord& xyz, AccessorT&) const; /// Change the value of the voxel at the given coordinates and mark it as active. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Set the value of the voxel at the given coordinate but preserves its active state. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); /// Apply a functor to the voxel at the given coordinates. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); /// Change the value of the voxel at the given coordinates and mark it as inactive. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Set the active state of the voxel at the given coordinates without changing its value. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&); /// Return, in @a value, the value of the voxel at the given coordinates and, /// if necessary, update the accessor with pointers to the nodes along /// the path from the root node to the node containing the voxel. /// @return @c true if the voxel at the given coordinates is active /// @note Used internally by ValueAccessor. template bool probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT&) const; /// @brief Return the level of the tree (0 = leaf) at which the value /// at the given coordinates resides. /// /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template Index getValueLevelAndCache(const Coord& xyz, AccessorT&) const; /// Mark all values (both tiles and voxels) as active. void setValuesOn(); // // I/O // void writeTopology(std::ostream&, bool toHalf = false) const; void readTopology(std::istream&, bool fromHalf = false); void writeBuffers(std::ostream&, bool toHalf = false) const; void readBuffers(std::istream&, bool fromHalf = false); // // Aux methods // /// @brief Set all voxels within an axis-aligned box to a constant value. /// (The min and max coordinates are inclusive.) void fill(const CoordBBox& bbox, const ValueType&, bool active = true); /// @brief Overwrite each inactive value in this node and in any child nodes with /// a new value whose magnitude is equal to the specified background value and whose /// sign is consistent with that of the lexicographically closest active value. /// @details This is primarily useful for propagating the sign from the (active) voxels /// in a narrow-band level set to the inactive voxels outside the narrow band. void signedFloodFill(const ValueType& background); /// @brief Overwrite each inactive value in this node and in any child nodes with /// either @a outside or @a inside, depending on the sign of the lexicographically /// closest active value. /// @details Specifically, an inactive value is set to @a outside if the closest active /// value in the lexicographic direction is positive, otherwise it is set to @a inside. void signedFloodFill(const ValueType& outside, const ValueType& inside); /// Change the sign of all the values represented in this node and /// its child nodes. void negate(); /// Densify active tiles, i.e., replace them with leaf-level active voxels. void voxelizeActiveTiles(); /// @brief Copy into a dense grid the values of the voxels that lie within /// a given bounding box. /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid /// @param dense dense grid with a stride in @e z of one (see tools::Dense /// in tools/Dense.h for the required API) /// @note @a bbox is assumed to be identical to or contained in the coordinate domains /// of both the dense grid and this node, i.e., no bounds checking is performed. template void copyToDense(const CoordBBox& bbox, DenseT& dense) const; /// @brief Efficiently merge another tree into this tree using one of several schemes. /// @warning This operation cannibalizes the other tree. template void merge(InternalNode& other, const ValueType& background, const ValueType& otherBackground); /// @brief Merge, using one of several schemes, this node (and its descendants) /// with a tile of the same dimensions and the given value and active state. template void merge(const ValueType& tileValue, bool tileActive); /// @brief Union this branch's set of active values with the other branch's /// active values. The value type of the other branch can be different. /// @details The resulting state of a value is active if the corresponding value /// was already active OR if it is active in the other tree. Also, a resulting /// value maps to a voxel if the corresponding value already mapped to a voxel /// OR if it is a voxel in the other tree. Thus, a resulting value can only /// map to a tile if the corresponding value already mapped to a tile /// AND if it is a tile value in other tree. /// /// Specifically, active tiles and voxels in this branch are not changed, and /// tiles or voxels that were inactive in this branch but active in the other branch /// are marked as active in this branch but left with their original values. template void topologyUnion(const InternalNode& other); /// @brief Intersects this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. /// @details The resulting state of a value is active only if the corresponding /// value was already active AND if it is active in the other tree. Also, a /// resulting value maps to a voxel if the corresponding value /// already mapped to an active voxel in either of the two grids /// and it maps to an active tile or voxel in the other grid. /// /// @note This operation can delete branches in this grid if they /// overlap with inactive tiles in the other grid. Likewise active /// voxels can be turned into unactive voxels resulting in leaf /// nodes with no active values. Thus, it is recommended to /// subsequently call prune. template void topologyIntersection(const InternalNode& other, const ValueType& background); /// @brief Difference this node's set of active values with the active values /// of the other node, whose @c ValueType may be different. So a /// resulting voxel will be active only if the original voxel is /// active in this node and inactive in the other node. /// /// @details The last dummy argument is required to match the signature /// for InternalNode::topologyDifference. /// /// @note This operation modifies only active states, not /// values. Also note that this operation can result in all voxels /// being inactive so consider subsequnetly calling prune. template void topologyDifference(const InternalNode& other, const ValueType& background); template void combine(InternalNode& other, CombineOp&); template void combine(const ValueType& value, bool valueIsActive, CombineOp&); template void combine2(const InternalNode& other0, const InternalNode& other1, CombineOp&); template void combine2(const ValueType& value, const InternalNode& other, bool valIsActive, CombineOp&); template void combine2(const InternalNode& other, const ValueType& val, bool valIsActive, CombineOp&); /// @brief Calls the templated functor BBoxOp with bounding box /// information for all active tiles and leaf nodes in this node. /// An additional level argument is provided for each callback. /// /// @note The bounding boxes are guarenteed to be non-overlapping. template void visitActiveBBox(BBoxOp&) const; template void visit(VisitorOp&); template void visit(VisitorOp&) const; template void visit2Node(OtherNodeType& other, VisitorOp&); template void visit2Node(OtherNodeType& other, VisitorOp&) const; template void visit2(IterT& otherIter, VisitorOp&, bool otherIsLHS = false); template void visit2(IterT& otherIter, VisitorOp&, bool otherIsLHS = false) const; /// @brief Call the @c PruneOp functor for each child node and, if the functor /// returns @c true, prune the node and replace it with a tile. /// /// This method is used to implement all of the various pruning algorithms /// (prune(), pruneInactive(), etc.). It should rarely be called directly. /// @see openvdb/tree/Util.h for the definition of the @c PruneOp functor template void pruneOp(PruneOp&); /// @brief Reduce the memory footprint of this tree by replacing with tiles /// any nodes whose values are all the same (optionally to within a tolerance) /// and have the same active state. void prune(const ValueType& tolerance = zeroVal()); /// @brief Reduce the memory footprint of this tree by replacing with /// tiles of the given value any nodes whose values are all inactive. void pruneInactive(const ValueType&); /// @brief Reduce the memory footprint of this tree by replacing with /// background tiles any nodes whose values are all inactive. void pruneInactive(); /// @brief Add the specified leaf to this node, possibly creating a child branch /// in the process. If the leaf node already exists, replace it. void addLeaf(LeafNodeType* leaf); /// @brief Same as addLeaf() except, if necessary, update the accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template void addLeafAndCache(LeafNodeType* leaf, AccessorT&); /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z) /// and replace it with a tile of the specified value and state. /// If no such node exists, leave the tree unchanged and return @c NULL. /// /// @note The caller takes ownership of the node and is responsible for deleting it. /// /// @warning Since this method potentially removes nodes and branches of the tree, /// it is important to clear the caches of all ValueAccessors associated with this tree. template NodeT* stealNode(const Coord& xyz, const ValueType& value, bool state); /// @brief Add a tile at the specified tree level that contains voxel (x, y, z), /// possibly creating a parent branch or deleting a child branch in the process. void addTile(Index level, const Coord& xyz, const ValueType& value, bool state); /// @brief Delete any existing child branch at the specified offset and add a tile. void addTile(Index offset, const ValueType& value, bool state); /// @brief Same as addTile() except, if necessary, update the accessor with pointers /// to the nodes along the path from the root node to the node containing (x, y, z). template void addTileAndCache(Index level, const Coord& xyz, const ValueType&, bool state, AccessorT&); //@{ /// @brief Return a pointer to the node that contains voxel (x, y, z). /// If no such node exists, return NULL. template NodeType* probeNode(const Coord& xyz); template const NodeType* probeConstNode(const Coord& xyz) const; //@} //@{ /// @brief Same as probeNode() except, if necessary, update the accessor with pointers /// to the nodes along the path from the root node to the node containing (x, y, z). template NodeType* probeNodeAndCache(const Coord& xyz, AccessorT&); template const NodeType* probeConstNodeAndCache(const Coord& xyz, AccessorT&) const; //@} //@{ /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). /// If no such node exists, return NULL. LeafNodeType* probeLeaf(const Coord& xyz); const LeafNodeType* probeConstLeaf(const Coord& xyz) const; const LeafNodeType* probeLeaf(const Coord& xyz) const; //@} //@{ /// @brief Same as probeLeaf() except, if necessary, update the accessor with pointers /// to the nodes along the path from the root node to the node containing (x, y, z). template LeafNodeType* probeLeafAndCache(const Coord& xyz, AccessorT& acc); template const LeafNodeType* probeConstLeafAndCache(const Coord& xyz, AccessorT& acc) const; template const LeafNodeType* probeLeafAndCache(const Coord& xyz, AccessorT& acc) const; //@} /// @brief Return the leaf node that contains voxel (x, y, z). /// If no such node exists, create one, but preserve the values and /// active states of all voxels. /// /// @details Use this method to preallocate a static tree topology /// over which to safely perform multithreaded processing. LeafNodeType* touchLeaf(const Coord& xyz); /// @brief Same as touchLeaf() except, if necessary, update the accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template LeafNodeType* touchLeafAndCache(const Coord& xyz, AccessorT&); /// @brief Change inactive tiles or voxels with value oldBackground to newBackground /// or -oldBackground to -newBackground. Active values are unchanged. void resetBackground(const ValueType& oldBackground, const ValueType& newBackground); /// @brief Return @c true if the given tree branch has the same node and active value /// topology as this tree branch (but possibly a different @c ValueType). template bool hasSameTopology(const InternalNode* other) const; protected: //@{ /// Allow iterators to call mask accessor methods (setValueMask(), setChildMask(), etc.). /// @todo Make mask accessors public? friend class IteratorBase; friend class IteratorBase; friend class IteratorBase; //@} /// @brief During topology-only construction, access is needed /// to protected/private members of other template instances. template friend class InternalNode; // Mask accessors public: bool isValueMaskOn(Index n) const { return mValueMask.isOn(n); } bool isValueMaskOn() const { return mValueMask.isOn(); } bool isValueMaskOff(Index n) const { return mValueMask.isOff(n); } bool isValueMaskOff() const { return mValueMask.isOff(); } bool isChildMaskOn(Index n) const { return mChildMask.isOn(n); } bool isChildMaskOff(Index n) const { return mChildMask.isOff(n); } bool isChildMaskOff() const { return mChildMask.isOff(); } protected: //@{ /// Use a mask accessor to ensure consistency between the child and value masks; /// i.e., the value mask should always be off wherever the child mask is on. void setValueMask(Index n, bool on) { mValueMask.set(n, mChildMask.isOn(n) ? false : on); } //@} void makeChildNodeEmpty(Index n, const ValueType& value); void setChildNode( Index i, ChildNodeType* child);//assumes a tile void resetChildNode(Index i, ChildNodeType* child);//checks for an existing child ChildNodeType* unsetChildNode(Index i, const ValueType& value); template static inline void doVisit(NodeT&, VisitorOp&); template static inline void doVisit2Node(NodeT&, OtherNodeT&, VisitorOp&); template static inline void doVisit2(NodeT&, OtherChildAllIterT&, VisitorOp&, bool otherIsLHS); ChildNodeType* getChildNode(Index n); const ChildNodeType* getChildNode(Index n) const; UnionType mNodes[NUM_VALUES]; NodeMaskType mChildMask, mValueMask; /// Global grid index coordinates (x,y,z) of the local origin of this node Coord mOrigin; }; // class InternalNode //////////////////////////////////////// template inline InternalNode::InternalNode(const ValueType& background) { for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(background); } template inline InternalNode::InternalNode(const Coord& origin, const ValueType& val, bool active): mOrigin(origin[0] & ~(DIM - 1), // zero out the low-order bits origin[1] & ~(DIM - 1), origin[2] & ~(DIM - 1)) { if (active) mValueMask.setOn(); for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(val); } template inline InternalNode::InternalNode(const InternalNode& other): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { for (Index i = 0; i < NUM_VALUES; ++i) { if (isChildMaskOn(i)) { mNodes[i].setChild(new ChildNodeType(*(other.mNodes[i].getChild()))); } else { mNodes[i].setValue(other.mNodes[i].getValue()); } } } template template inline InternalNode::InternalNode(const InternalNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { for (Index i = 0; i < NUM_VALUES; ++i) { if (isChildMaskOn(i)) { mNodes[i].setChild(new ChildNodeType(*(other.mNodes[i].getChild()), offValue, onValue, TopologyCopy())); } else { mNodes[i].setValue(isValueMaskOn(i) ? onValue : offValue); } } } template template inline InternalNode::InternalNode(const InternalNode& other, const ValueType& background, TopologyCopy): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(background); for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { mNodes[iter.pos()].setChild(new ChildNodeType(*(other.mNodes[iter.pos()].getChild()), background, TopologyCopy())); } } template inline InternalNode::~InternalNode() { for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { delete mNodes[iter.pos()].getChild(); } } //////////////////////////////////////// template inline Index32 InternalNode::leafCount() const { if (ChildNodeType::getLevel() == 0) return mChildMask.countOn(); Index32 sum = 0; for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->leafCount(); } return sum; } template inline Index32 InternalNode::nonLeafCount() const { Index32 sum = 1; if (ChildNodeType::getLevel() == 0) return sum; for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->nonLeafCount(); } return sum; } template inline Index64 InternalNode::onVoxelCount() const { Index64 sum = ChildT::NUM_VOXELS * mValueMask.countOn(); for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->onVoxelCount(); } return sum; } template inline Index64 InternalNode::offVoxelCount() const { Index64 sum = ChildT::NUM_VOXELS * (NUM_VALUES-mValueMask.countOn()-mChildMask.countOn()); for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->offVoxelCount(); } return sum; } template inline Index64 InternalNode::onLeafVoxelCount() const { Index64 sum = 0; for (ChildOnCIter iter = this->beginChildOn(); iter; ++iter) { sum += mNodes[iter.pos()].getChild()->onLeafVoxelCount(); } return sum; } template inline Index64 InternalNode::offLeafVoxelCount() const { Index64 sum = 0; for (ChildOnCIter iter = this->beginChildOn(); iter; ++iter) { sum += mNodes[iter.pos()].getChild()->offLeafVoxelCount(); } return sum; } template inline Index64 InternalNode::onTileCount() const { Index64 sum = mValueMask.countOn(); for (ChildOnCIter iter = this->cbeginChildOn(); LEVEL>1 && iter; ++iter) { sum += iter->onTileCount(); } return sum; } template inline Index64 InternalNode::memUsage() const { Index64 sum = NUM_VALUES * sizeof(UnionType) + mChildMask.memUsage() + mValueMask.memUsage() + sizeof(mOrigin); for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { sum += iter->memUsage(); } return sum; } // Deprecated template inline void InternalNode::evalActiveVoxelBoundingBox(CoordBBox& bbox) const { if (bbox.isInside(this->getNodeBoundingBox())) return; for (ValueOnCIter i = this->cbeginValueOn(); i; ++i) bbox.expand(i.getCoord(), ChildT::DIM); for (ChildOnCIter i = this->cbeginChildOn(); i; ++i) i->evalActiveVoxelBoundingBox(bbox); } template inline void InternalNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const { if (bbox.isInside(this->getNodeBoundingBox())) return; for (ValueOnCIter i = this->cbeginValueOn(); i; ++i) bbox.expand(i.getCoord(), ChildT::DIM); for (ChildOnCIter i = this->cbeginChildOn(); i; ++i) i->evalActiveBoundingBox(bbox, visitVoxels); } //////////////////////////////////////// template template inline void InternalNode::pruneOp(PruneOp& op) { for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { const Index i = iter.pos(); ChildT* child = mNodes[i].getChild(); if (!op(*child)) continue; delete child; mChildMask.setOff(i); mValueMask.set(i, op.state); mNodes[i].setValue(op.value); } } template inline void InternalNode::prune(const ValueType& tolerance) { TolerancePrune op(tolerance); this->pruneOp(op); } template inline void InternalNode::pruneInactive(const ValueType& bg) { InactivePrune op(bg); this->pruneOp(op); } template inline void InternalNode::pruneInactive() { this->pruneInactive(this->getBackground()); } //////////////////////////////////////// template template inline NodeT* InternalNode::stealNode(const Coord& xyz, const ValueType& value, bool state) { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return NULL; ChildT* child = mNodes[n].getChild(); if (boost::is_same::value) { mChildMask.setOff(n); mValueMask.set(n, state); mNodes[n].setValue(value); } return (boost::is_same::value) ? reinterpret_cast(child) : child->template stealNode(xyz, value, state); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template template inline NodeT* InternalNode::probeNode(const Coord& xyz) { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return NULL; ChildT* child = mNodes[n].getChild(); return (boost::is_same::value) ? reinterpret_cast(child) : child->template probeNode(xyz); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline NodeT* InternalNode::probeNodeAndCache(const Coord& xyz, AccessorT& acc) { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return NULL; ChildT* child = mNodes[n].getChild(); acc.insert(xyz, child); return (boost::is_same::value) ? reinterpret_cast(child) : child->template probeNodeAndCache(xyz, acc); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline const NodeT* InternalNode::probeConstNode(const Coord& xyz) const { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return NULL; const ChildT* child = mNodes[n].getChild(); return (boost::is_same::value) ? reinterpret_cast(child) : child->template probeConstNode(xyz); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline const NodeT* InternalNode::probeConstNodeAndCache(const Coord& xyz, AccessorT& acc) const { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return NULL; const ChildT* child = mNodes[n].getChild(); acc.insert(xyz, child); return (boost::is_same::value) ? reinterpret_cast(child) : child->template probeConstNodeAndCache(xyz, acc); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template inline typename ChildT::LeafNodeType* InternalNode::probeLeaf(const Coord& xyz) { return this->template probeNode(xyz); } template template inline typename ChildT::LeafNodeType* InternalNode::probeLeafAndCache(const Coord& xyz, AccessorT& acc) { return this->template probeNodeAndCache(xyz, acc); } template template inline const typename ChildT::LeafNodeType* InternalNode::probeLeafAndCache(const Coord& xyz, AccessorT& acc) const { return this->probeConstLeafAndCache(xyz, acc); } template inline const typename ChildT::LeafNodeType* InternalNode::probeConstLeaf(const Coord& xyz) const { return this->template probeConstNode(xyz); } template template inline const typename ChildT::LeafNodeType* InternalNode::probeConstLeafAndCache(const Coord& xyz, AccessorT& acc) const { return this->template probeConstNodeAndCache(xyz, acc); } //////////////////////////////////////// template inline void InternalNode::addLeaf(LeafNodeType* leaf) { assert(leaf != NULL); const Coord& xyz = leaf->origin(); const Index n = this->coordToOffset(xyz); ChildT* child = NULL; if (mChildMask.isOff(n)) { if (ChildT::LEVEL>0) { child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); } else { child = reinterpret_cast(leaf); } this->setChildNode(n, child); } else { if (ChildT::LEVEL>0) { child = mNodes[n].getChild(); } else { delete mNodes[n].getChild(); child = reinterpret_cast(leaf); mNodes[n].setChild(child); } } child->addLeaf(leaf); } template template inline void InternalNode::addLeafAndCache(LeafNodeType* leaf, AccessorT& acc) { assert(leaf != NULL); const Coord& xyz = leaf->origin(); const Index n = this->coordToOffset(xyz); ChildT* child = NULL; if (mChildMask.isOff(n)) { if (ChildT::LEVEL>0) { child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); acc.insert(xyz, child);//we only cache internal nodes } else { child = reinterpret_cast(leaf); } this->setChildNode(n, child); } else { if (ChildT::LEVEL>0) { child = mNodes[n].getChild(); acc.insert(xyz, child);//we only cache internal nodes } else { delete mNodes[n].getChild(); child = reinterpret_cast(leaf); mNodes[n].setChild(child); } } child->addLeafAndCache(leaf, acc); } //////////////////////////////////////// template inline void InternalNode::addTile(Index n, const ValueType& value, bool state) { assert(n < NUM_VALUES); this->makeChildNodeEmpty(n, value); mValueMask.set(n, state); } template inline void InternalNode::addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { if (LEVEL >= level) { const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) {// tile case if (LEVEL > level) { ChildT* child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); this->setChildNode(n, child); child->addTile(level, xyz, value, state); } else { mValueMask.set(n, state); mNodes[n].setValue(value); } } else {// child branch case ChildT* child = mNodes[n].getChild(); if (LEVEL > level) { child->addTile(level, xyz, value, state); } else { delete child; mChildMask.setOff(n); mValueMask.set(n, state); mNodes[n].setValue(value); } } } } template template inline void InternalNode::addTileAndCache(Index level, const Coord& xyz, const ValueType& value, bool state, AccessorT& acc) { if (LEVEL >= level) { const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) {// tile case if (LEVEL > level) { ChildT* child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); this->setChildNode(n, child); acc.insert(xyz, child); child->addTileAndCache(level, xyz, value, state, acc); } else { mValueMask.set(n, state); mNodes[n].setValue(value); } } else {// child branch case ChildT* child = mNodes[n].getChild(); if (LEVEL > level) { acc.insert(xyz, child); child->addTileAndCache(level, xyz, value, state, acc); } else { delete child; mChildMask.setOff(n); mValueMask.set(n, state); mNodes[n].setValue(value); } } } } //////////////////////////////////////// template inline typename ChildT::LeafNodeType* InternalNode::touchLeaf(const Coord& xyz) { const Index n = this->coordToOffset(xyz); ChildT* child = NULL; if (mChildMask.isOff(n)) { child = new ChildT(xyz, mNodes[n].getValue(), mValueMask.isOn(n)); this->setChildNode(n, child); } else { child = mNodes[n].getChild(); } return child->touchLeaf(xyz); } template template inline typename ChildT::LeafNodeType* InternalNode::touchLeafAndCache(const Coord& xyz, AccessorT& acc) { const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) { this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), mValueMask.isOn(n))); } acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->touchLeafAndCache(xyz, acc); } //////////////////////////////////////// template inline bool InternalNode::isConstant(ValueType& constValue, bool& state, const ValueType& tolerance) const { bool allEqual = true, firstValue = true, valueState = true; ValueType value = zeroVal(); for (Index i = 0; allEqual && i < NUM_VALUES; ++i) { if (this->isChildMaskOff(i)) { // If entry i is a value, check if it is within tolerance // and whether its active state matches the other entries. if (firstValue) { firstValue = false; valueState = isValueMaskOn(i); value = mNodes[i].getValue(); } else { allEqual = (isValueMaskOn(i) == valueState) && math::isApproxEqual(mNodes[i].getValue(), value, tolerance); } } else { // If entry i is a child, check if the child is constant and within tolerance // and whether its active state matches the other entries. ValueType childValue = zeroVal(); bool isChildOn = false; if (mNodes[i].getChild()->isConstant(childValue, isChildOn, tolerance)) { if (firstValue) { firstValue = false; valueState = isChildOn; value = childValue; } else { allEqual = (isChildOn == valueState) && math::isApproxEqual(childValue, value, tolerance); } } else { // child is not constant allEqual = false; } } } if (allEqual) { constValue = value; state = valueState; } return allEqual; } //////////////////////////////////////// template inline bool InternalNode::hasActiveTiles() const { const bool anyActiveTiles = !mValueMask.isOff(); if (LEVEL==1 || anyActiveTiles) return anyActiveTiles; for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { if (iter->hasActiveTiles()) return true; } return false; } template inline bool InternalNode::isValueOn(const Coord& xyz) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOff(n)) return this->isValueMaskOn(n); return mNodes[n].getChild()->isValueOn(xyz); } template template inline bool InternalNode::isValueOnAndCache(const Coord& xyz, AccessorT& acc) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOff(n)) return this->isValueMaskOn(n); acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->isValueOnAndCache(xyz, acc); } template inline const typename ChildT::ValueType& InternalNode::getValue(const Coord& xyz) const { const Index n = this->coordToOffset(xyz); return this->isChildMaskOff(n) ? mNodes[n].getValue() : mNodes[n].getChild()->getValue(xyz); } template template inline const typename ChildT::ValueType& InternalNode::getValueAndCache(const Coord& xyz, AccessorT& acc) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOn(n)) { acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->getValueAndCache(xyz, acc); } return mNodes[n].getValue(); } template inline Index InternalNode::getValueLevel(const Coord& xyz) const { const Index n = this->coordToOffset(xyz); return this->isChildMaskOff(n) ? LEVEL : mNodes[n].getChild()->getValueLevel(xyz); } template template inline Index InternalNode::getValueLevelAndCache(const Coord& xyz, AccessorT& acc) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOn(n)) { acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->getValueLevelAndCache(xyz, acc); } return LEVEL; } template inline bool InternalNode::probeValue(const Coord& xyz, ValueType& value) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOff(n)) { value = mNodes[n].getValue(); return this->isValueMaskOn(n); } return mNodes[n].getChild()->probeValue(xyz, value); } template template inline bool InternalNode::probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT& acc) const { const Index n = this->coordToOffset(xyz); if (this->isChildMaskOn(n)) { acc.insert(xyz, mNodes[n].getChild()); return mNodes[n].getChild()->probeValueAndCache(xyz, value, acc); } value = mNodes[n].getValue(); return this->isValueMaskOn(n); } template inline void InternalNode::setValueOff(const Coord& xyz) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild && this->isValueMaskOn(n)) { // If the voxel belongs to a constant tile that is active, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), /*active=*/true)); } if (hasChild) mNodes[n].getChild()->setValueOff(xyz); } template inline void InternalNode::setValueOn(const Coord& xyz) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild && !this->isValueMaskOn(n)) { // If the voxel belongs to a constant tile that is inactive, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), /*active=*/false)); } if (hasChild) mNodes[n].getChild()->setValueOn(xyz); } template inline void InternalNode::setValueOff(const Coord& xyz, const ValueType& value) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool active = this->isValueMaskOn(n); if (active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel belongs to a tile that is either active or that // has a constant value that is different from the one provided, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) mNodes[n].getChild()->setValueOff(xyz, value); } template template inline void InternalNode::setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool active = this->isValueMaskOn(n); if (active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel belongs to a tile that is either active or that // has a constant value that is different from the one provided, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) { ChildT* child = mNodes[n].getChild(); acc.insert(xyz, child); child->setValueOffAndCache(xyz, value, acc); } } template inline void InternalNode::setValueOn(const Coord& xyz, const ValueType& value) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool active = this->isValueMaskOn(n); // tile's active state if (!active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel belongs to a tile that is either inactive or that // has a constant value that is different from the one provided, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) mNodes[n].getChild()->setValueOn(xyz, value); } template template inline void InternalNode::setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool active = this->isValueMaskOn(n); if (!active || !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel belongs to a tile that is either inactive or that // has a constant value that is different from the one provided, // a child subtree must be constructed. hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) { acc.insert(xyz, mNodes[n].getChild()); mNodes[n].getChild()->setValueAndCache(xyz, value, acc); } } template inline void InternalNode::setValueOnly(const Coord& xyz, const ValueType& value) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild && !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel has a tile value that is different from the one provided, // a child subtree must be constructed. const bool active = this->isValueMaskOn(n); hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } if (hasChild) mNodes[n].getChild()->setValueOnly(xyz, value); } template template inline void InternalNode::setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild && !math::isExactlyEqual(mNodes[n].getValue(), value)) { // If the voxel has a tile value that is different from the one provided, // a child subtree must be constructed. const bool active = this->isValueMaskOn(n); hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } if (hasChild) { acc.insert(xyz, mNodes[n].getChild()); mNodes[n].getChild()->setValueOnlyAndCache(xyz, value, acc); } } template inline void InternalNode::setActiveState(const Coord& xyz, bool on) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { if (on != this->isValueMaskOn(n)) { // If the voxel belongs to a tile with the wrong active state, // then a child subtree must be constructed. // 'on' is the voxel's new state, therefore '!on' is the tile's current state hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), !on)); } } if (hasChild) mNodes[n].getChild()->setActiveState(xyz, on); } template template inline void InternalNode::setActiveStateAndCache(const Coord& xyz, bool on, AccessorT& acc) { const Index n = this->coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { if (on != this->isValueMaskOn(n)) { // If the voxel belongs to a tile with the wrong active state, // then a child subtree must be constructed. // 'on' is the voxel's new state, therefore '!on' is the tile's current state hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), !on)); } } if (hasChild) { ChildT* child = mNodes[n].getChild(); acc.insert(xyz, child); child->setActiveStateAndCache(xyz, on, acc); } } template inline void InternalNode::setValuesOn() { mValueMask = !mChildMask; for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { mNodes[iter.pos()].getChild()->setValuesOn(); } } template template inline void InternalNode::modifyValue(const Coord& xyz, const ModifyOp& op) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { // Need to create a child if the tile is inactive, // in order to activate voxel (x, y, z). const bool active = this->isValueMaskOn(n); bool createChild = !active; if (!createChild) { // Need to create a child if applying the functor // to the tile value produces a different value. const ValueType& tileVal = mNodes[n].getValue(); ValueType modifiedVal = tileVal; op(modifiedVal); createChild = !math::isExactlyEqual(tileVal, modifiedVal); } if (createChild) { hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) mNodes[n].getChild()->modifyValue(xyz, op); } template template inline void InternalNode::modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT& acc) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { // Need to create a child if the tile is inactive, // in order to activate voxel (x, y, z). const bool active = this->isValueMaskOn(n); bool createChild = !active; if (!createChild) { // Need to create a child if applying the functor // to the tile value produces a different value. const ValueType& tileVal = mNodes[n].getValue(); ValueType modifiedVal = tileVal; op(modifiedVal); createChild = !math::isExactlyEqual(tileVal, modifiedVal); } if (createChild) { hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, mNodes[n].getValue(), active)); } } if (hasChild) { ChildNodeType* child = mNodes[n].getChild(); acc.insert(xyz, child); child->modifyValueAndCache(xyz, op, acc); } } template template inline void InternalNode::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool tileState = this->isValueMaskOn(n); const ValueType& tileVal = mNodes[n].getValue(); bool modifiedState = !tileState; ValueType modifiedVal = tileVal; op(modifiedVal, modifiedState); // Need to create a child if applying the functor to the tile // produces a different value or active state. if (modifiedState != tileState || !math::isExactlyEqual(modifiedVal, tileVal)) { hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, tileVal, tileState)); } } if (hasChild) mNodes[n].getChild()->modifyValueAndActiveState(xyz, op); } template template inline void InternalNode::modifyValueAndActiveStateAndCache( const Coord& xyz, const ModifyOp& op, AccessorT& acc) { const Index n = InternalNode::coordToOffset(xyz); bool hasChild = this->isChildMaskOn(n); if (!hasChild) { const bool tileState = this->isValueMaskOn(n); const ValueType& tileVal = mNodes[n].getValue(); bool modifiedState = !tileState; ValueType modifiedVal = tileVal; op(modifiedVal, modifiedState); // Need to create a child if applying the functor to the tile // produces a different value or active state. if (modifiedState != tileState || !math::isExactlyEqual(modifiedVal, tileVal)) { hasChild = true; this->setChildNode(n, new ChildNodeType(xyz, tileVal, tileState)); } } if (hasChild) { ChildNodeType* child = mNodes[n].getChild(); acc.insert(xyz, child); child->modifyValueAndActiveStateAndCache(xyz, op, acc); } } //////////////////////////////////////// template inline void InternalNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) { Coord xyz, tileMin, tileMax; for (int x = bbox.min().x(); x <= bbox.max().x(); x = tileMax.x() + 1) { xyz.setX(x); for (int y = bbox.min().y(); y <= bbox.max().y(); y = tileMax.y() + 1) { xyz.setY(y); for (int z = bbox.min().z(); z <= bbox.max().z(); z = tileMax.z() + 1) { xyz.setZ(z); // Get the bounds of the tile that contains voxel (x, y, z). const Index n = this->coordToOffset(xyz); tileMin = this->offsetToGlobalCoord(n); tileMax = tileMin.offsetBy(ChildT::DIM - 1); if (xyz != tileMin || Coord::lessThan(bbox.max(), tileMax)) { // If the box defined by (xyz, bbox.max()) doesn't completely enclose // the tile to which xyz belongs, create a child node (or retrieve // the existing one). ChildT* child = NULL; if (this->isChildMaskOff(n)) { // Replace the tile with a newly-created child that is initialized // with the tile's value and active state. child = new ChildT(xyz, mNodes[n].getValue(), this->isValueMaskOn(n)); this->setChildNode(n, child); } else { child = mNodes[n].getChild(); } // Forward the fill request to the child. if (child) { child->fill(CoordBBox(xyz, Coord::minComponent(bbox.max(), tileMax)), value, active); } } else { // If the box given by (xyz, bbox.max()) completely encloses // the tile to which xyz belongs, create the tile (if it // doesn't already exist) and give it the fill value. this->makeChildNodeEmpty(n, value); mValueMask.set(n, active); } } } } } //////////////////////////////////////// template template inline void InternalNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const { typedef typename DenseT::ValueType DenseValueType; const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); const Coord& min = dense.bbox().min(); for (Coord xyz = bbox.min(), max; xyz[0] <= bbox.max()[0]; xyz[0] = max[0] + 1) { for (xyz[1] = bbox.min()[1]; xyz[1] <= bbox.max()[1]; xyz[1] = max[1] + 1) { for (xyz[2] = bbox.min()[2]; xyz[2] <= bbox.max()[2]; xyz[2] = max[2] + 1) { const Index n = this->coordToOffset(xyz); // Get max coordinates of the child node that contains voxel xyz. max = this->offsetToGlobalCoord(n).offsetBy(ChildT::DIM-1); // Get the bbox of the interection of bbox and the child node CoordBBox sub(xyz, Coord::minComponent(bbox.max(), max)); if (this->isChildMaskOn(n)) {//is a child mNodes[n].getChild()->copyToDense(sub, dense); } else {//a tile value const ValueType value = mNodes[n].getValue(); sub.translate(-min); DenseValueType* a0 = dense.data() + zStride*sub.min()[2]; for (Int32 x=sub.min()[0], ex=sub.max()[0]+1; x inline void InternalNode::writeTopology(std::ostream& os, bool toHalf) const { mChildMask.save(os); mValueMask.save(os); { // Copy all of this node's values into an array. boost::shared_array values(new ValueType[NUM_VALUES]); const ValueType zero = zeroVal(); for (Index i = 0; i < NUM_VALUES; ++i) { values[i] = (mChildMask.isOff(i) ? mNodes[i].getValue() : zero); } // Compress (optionally) and write out the contents of the array. io::writeCompressedValues(os, values.get(), NUM_VALUES, mValueMask, mChildMask, toHalf); } // Write out the child nodes in order. for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { iter->writeTopology(os, toHalf); } } template inline void InternalNode::readTopology(std::istream& is, bool fromHalf) { mChildMask.load(is); mValueMask.load(is); if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_INTERNALNODE_COMPRESSION) { for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOn(i)) { ChildNodeType* child = new ChildNodeType(offsetToGlobalCoord(i), zeroVal()); mNodes[i].setChild(child); child->readTopology(is); } else { ValueType value; is.read(reinterpret_cast(&value), sizeof(ValueType)); mNodes[i].setValue(value); } } } else { const bool oldVersion = (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION); const Index numValues = (oldVersion ? mChildMask.countOff() : NUM_VALUES); { // Read in (and uncompress, if necessary) all of this node's values // into a contiguous array. boost::shared_array values(new ValueType[numValues]); io::readCompressedValues(is, values.get(), numValues, mValueMask, fromHalf); // Copy values from the array into this node's table. if (oldVersion) { Index n = 0; for (ValueAllIter iter = this->beginValueAll(); iter; ++iter) { mNodes[iter.pos()].setValue(values[n++]); } assert(n == numValues); } else { for (ValueAllIter iter = this->beginValueAll(); iter; ++iter) { mNodes[iter.pos()].setValue(values[iter.pos()]); } } } // Read in all child nodes and insert them into the table at their proper locations. for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { ChildNodeType* child = new ChildNodeType(iter.getCoord(), zeroVal()); mNodes[iter.pos()].setChild(child); child->readTopology(is, fromHalf); } } } //////////////////////////////////////// template inline const typename ChildT::ValueType& InternalNode::getFirstValue() const { return (this->isChildMaskOn(0) ? mNodes[0].getChild()->getFirstValue() : mNodes[0].getValue()); } template inline const typename ChildT::ValueType& InternalNode::getLastValue() const { const Index n = NUM_VALUES - 1; return (this->isChildMaskOn(n) ? mNodes[n].getChild()->getLastValue() : mNodes[n].getValue()); } //////////////////////////////////////// template inline void InternalNode::signedFloodFill(const ValueType& background) { this->signedFloodFill(background, math::negative(background)); } template inline void InternalNode::signedFloodFill(const ValueType& outsideValue, const ValueType& insideValue) { // First, flood fill all child nodes. for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { iter->signedFloodFill(outsideValue, insideValue); } const Index first = mChildMask.findFirstOn(); if (first < NUM_VALUES) { bool xInside = math::isNegative(mNodes[first].getChild()->getFirstValue()), yInside = xInside, zInside = xInside; for (Index x = 0; x != (1 << Log2Dim); ++x) { const int x00 = x << (2 * Log2Dim); // offset for block(x, 0, 0) if (isChildMaskOn(x00)) { xInside = math::isNegative(mNodes[x00].getChild()->getLastValue()); } yInside = xInside; for (Index y = 0; y != (1 << Log2Dim); ++y) { const Index xy0 = x00 + (y << Log2Dim); // offset for block(x, y, 0) if (isChildMaskOn(xy0)) { yInside = math::isNegative(mNodes[xy0].getChild()->getLastValue()); } zInside = yInside; for (Index z = 0; z != (1 << Log2Dim); ++z) { const Index xyz = xy0 + z; // offset for block(x, y, z) if (isChildMaskOn(xyz)) { zInside = math::isNegative(mNodes[xyz].getChild()->getLastValue()); } else { mNodes[xyz].setValue(zInside ? insideValue : outsideValue); } } } } } else {//no child nodes exist simply use the sign of the first tile value. const ValueType v = math::isNegative(mNodes[0].getValue()) ? insideValue : outsideValue; for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(v); } } template inline void InternalNode::negate() { for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOn(i)) { mNodes[i].getChild()->negate(); } else { mNodes[i].setValue(math::negative(mNodes[i].getValue())); } } } template inline void InternalNode::voxelizeActiveTiles() { for (ValueOnIter iter = this->beginValueOn(); iter; ++iter) { this->setChildNode(iter.pos(), new ChildNodeType(iter.getCoord(), iter.getValue(), true)); } for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) iter->voxelizeActiveTiles(); } //////////////////////////////////////// template template inline void InternalNode::merge(InternalNode& other, const ValueType& background, const ValueType& otherBackground) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN switch (Policy) { case MERGE_ACTIVE_STATES: default: { for (ChildOnIter iter = other.beginChildOn(); iter; ++iter) { const Index n = iter.pos(); if (mChildMask.isOn(n)) { // Merge this node's child with the other node's child. mNodes[n].getChild()->template merge(*iter, background, otherBackground); } else if (mValueMask.isOff(n)) { // Replace this node's inactive tile with the other node's child // and replace the other node's child with a tile of undefined value // (which is okay since the other tree is assumed to be cannibalized // in the process of merging). ChildNodeType* child = other.mNodes[n].getChild(); other.mChildMask.setOff(n); child->resetBackground(otherBackground, background); this->setChildNode(n, child); } } // Copy active tile values. for (ValueOnCIter iter = other.cbeginValueOn(); iter; ++iter) { const Index n = iter.pos(); if (mValueMask.isOff(n)) { // Replace this node's child or inactive tile with the other node's active tile. this->makeChildNodeEmpty(n, iter.getValue()); mValueMask.setOn(n); } } break; } case MERGE_NODES: { for (ChildOnIter iter = other.beginChildOn(); iter; ++iter) { const Index n = iter.pos(); if (mChildMask.isOn(n)) { // Merge this node's child with the other node's child. mNodes[n].getChild()->template merge(*iter, background, otherBackground); } else { // Replace this node's tile (regardless of its active state) with // the other node's child and replace the other node's child with // a tile of undefined value (which is okay since the other tree // is assumed to be cannibalized in the process of merging). ChildNodeType* child = other.mNodes[n].getChild(); other.mChildMask.setOff(n); child->resetBackground(otherBackground, background); this->setChildNode(n, child); } } break; } case MERGE_ACTIVE_STATES_AND_NODES: { // Transfer children from the other tree to this tree. for (ChildOnIter iter = other.beginChildOn(); iter; ++iter) { const Index n = iter.pos(); if (mChildMask.isOn(n)) { // Merge this node's child with the other node's child. mNodes[n].getChild()->template merge(*iter, background, otherBackground); } else { // Replace this node's tile with the other node's child, leaving the other // node with an inactive tile of undefined value (which is okay since // the other tree is assumed to be cannibalized in the process of merging). ChildNodeType* child = other.mNodes[n].getChild(); other.mChildMask.setOff(n); child->resetBackground(otherBackground, background); if (mValueMask.isOn(n)) { // Merge the child with this node's active tile. child->template merge(mNodes[n].getValue(), /*on=*/true); mValueMask.setOff(n); } mChildMask.setOn(n); mNodes[n].setChild(child); } } // Merge active tiles into this tree. for (ValueOnCIter iter = other.cbeginValueOn(); iter; ++iter) { const Index n = iter.pos(); if (mChildMask.isOn(n)) { // Merge the other node's active tile into this node's child. mNodes[n].getChild()->template merge(iter.getValue(), /*on=*/true); } else if (mValueMask.isOff(n)) { // Replace this node's inactive tile with the other node's active tile. mNodes[n].setValue(iter.getValue()); mValueMask.setOn(n); } } break; } } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline void InternalNode::merge(const ValueType& tileValue, bool tileActive) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy != MERGE_ACTIVE_STATES_AND_NODES) return; // For MERGE_ACTIVE_STATES_AND_NODES, inactive tiles in the other tree are ignored. if (!tileActive) return; // Iterate over this node's children and inactive tiles. for (ValueOffIter iter = this->beginValueOff(); iter; ++iter) { const Index n = iter.pos(); if (mChildMask.isOn(n)) { // Merge the other node's active tile into this node's child. mNodes[n].getChild()->template merge(tileValue, /*on=*/true); } else { // Replace this node's inactive tile with the other node's active tile. iter.setValue(tileValue); mValueMask.setOn(n); } } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template template inline void InternalNode::topologyUnion(const InternalNode& other) { typedef typename InternalNode::ChildOnCIter OtherChildIter; typedef typename InternalNode::ValueOnCIter OtherValueIter; // Loop over other node's child nodes for (OtherChildIter iter = other.cbeginChildOn(); iter; ++iter) { const Index i = iter.pos(); if (mChildMask.isOn(i)) {//this has a child node mNodes[i].getChild()->topologyUnion(*iter); } else {// this is a tile so replace it with a child branch with identical topology ChildNodeType* child = new ChildNodeType(*iter, mNodes[i].getValue(), TopologyCopy()); if (mValueMask.isOn(i)) { mValueMask.isOff(i);//we're replacing the active tile with a child branch child->setValuesOn();//activate all values since it was an active tile } mChildMask.setOn(i); mNodes[i].setChild(child); } } // Loop over other node's active tiles for (OtherValueIter iter = other.cbeginValueOn(); iter; ++iter) { const Index i = iter.pos(); if (mChildMask.isOn(i)) { mNodes[i].getChild()->setValuesOn(); } else if (mValueMask.isOff(i)) { //inactive tile mValueMask.setOn(i); } } } template template inline void InternalNode::topologyIntersection(const InternalNode& other, const ValueType& background) { // Loop over this node's child nodes for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { const Index i = iter.pos(); if (other.mChildMask.isOn(i)) {//other also has a child node iter->topologyIntersection(*(other.mNodes[i].getChild()), background); } else if (other.mValueMask.isOff(i)) {//other is an inactive tile delete mNodes[i].getChild();//convert child to an inactive tile mNodes[i].setValue(background); mChildMask.setOff(i); mValueMask.setOff(i); } } // Loop over this node's active tiles for (ValueOnCIter iter = this->cbeginValueOn(); iter; ++iter) { const Index i = iter.pos(); if (other.mChildMask.isOn(i)) {//other has a child node ChildNodeType* child = new ChildNodeType(*(other.mNodes[i].getChild()), *iter, TopologyCopy()); this->setChildNode(i, child);//replace the active tile with a child branch } else if (other.mValueMask.isOff(i)) {//other is an inactive tile mValueMask.setOff(i);//convert active tile to an inactive tile } } } template template inline void InternalNode::topologyDifference(const InternalNode& other, const ValueType& background) { typedef typename InternalNode::ChildOnCIter OtherChildIter; typedef typename InternalNode::ValueOnCIter OtherValueIter; // Loop over other node's child nodes for (OtherChildIter iter = other.cbeginChildOn(); iter; ++iter) { const Index i = iter.pos(); if (mChildMask.isOn(i)) {//this has a child node mNodes[i].getChild()->topologyDifference(*iter, background); } else if (mValueMask.isOn(i)) {// this is an active tile ChildNodeType* child = new ChildNodeType(iter.getCoord(), mNodes[i].getValue(), true); child->topologyDifference(*iter, background); this->setChildNode(i, child);//we're replacing the active tile with a child branch } } // Loop over other node's active tiles for (OtherValueIter iter = other.cbeginValueOn(); iter; ++iter) { const Index i = iter.pos(); if (mChildMask.isOn(i)) {//this has a child node delete mNodes[i].getChild();//convert child to an inactive tile mNodes[i].setValue(background); mChildMask.setOff(i); mValueMask.setOff(i); } else if (mValueMask.isOn(i)) {//this is an active tile mValueMask.setOff(i);//convert active tile to an inactive tile } } } //////////////////////////////////////// template template inline void InternalNode::combine(InternalNode& other, CombineOp& op) { const ValueType zero = zeroVal(); CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOff(i) && other.isChildMaskOff(i)) { // Both this node and the other node have constant values (tiles). // Combine the two values and store the result as this node's new tile value. op(args.setARef(mNodes[i].getValue()) .setAIsActive(isValueMaskOn(i)) .setBRef(other.mNodes[i].getValue()) .setBIsActive(other.isValueMaskOn(i))); mNodes[i].setValue(args.result()); mValueMask.set(i, args.resultIsActive()); } else if (this->isChildMaskOn(i) && other.isChildMaskOff(i)) { // Combine this node's child with the other node's constant value. ChildNodeType* child = mNodes[i].getChild(); assert(child); if (child) { child->combine(other.mNodes[i].getValue(), other.isValueMaskOn(i), op); } } else if (this->isChildMaskOff(i) && other.isChildMaskOn(i)) { // Combine this node's constant value with the other node's child. ChildNodeType* child = other.mNodes[i].getChild(); assert(child); if (child) { // Combine this node's constant value with the other node's child, // but use a new functor in which the A and B values are swapped, // since the constant value is the A value, not the B value. SwappedCombineOp swappedOp(op); child->combine(mNodes[i].getValue(), isValueMaskOn(i), swappedOp); // Steal the other node's child. other.mChildMask.setOff(i); other.mNodes[i].setValue(zero); this->setChildNode(i, child); } } else /*if (isChildMaskOn(i) && other.isChildMaskOn(i))*/ { // Combine this node's child with the other node's child. ChildNodeType *child = mNodes[i].getChild(), *otherChild = other.mNodes[i].getChild(); assert(child); assert(otherChild); if (child && otherChild) { child->combine(*otherChild, op); } } } } template template inline void InternalNode::combine(const ValueType& value, bool valueIsActive, CombineOp& op) { CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOff(i)) { // Combine this node's constant value with the given constant value. op(args.setARef(mNodes[i].getValue()) .setAIsActive(isValueMaskOn(i)) .setBRef(value) .setBIsActive(valueIsActive)); mNodes[i].setValue(args.result()); mValueMask.set(i, args.resultIsActive()); } else /*if (isChildMaskOn(i))*/ { // Combine this node's child with the given constant value. ChildNodeType* child = mNodes[i].getChild(); assert(child); if (child) child->combine(value, valueIsActive, op); } } } //////////////////////////////////////// template template inline void InternalNode::combine2(const InternalNode& other0, const InternalNode& other1, CombineOp& op) { CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (other0.isChildMaskOff(i) && other1.isChildMaskOff(i)) { op(args.setARef(other0.mNodes[i].getValue()) .setAIsActive(other0.isValueMaskOn(i)) .setBRef(other1.mNodes[i].getValue()) .setBIsActive(other1.isValueMaskOn(i))); // Replace child i with a constant value. this->makeChildNodeEmpty(i, args.result()); mValueMask.set(i, args.resultIsActive()); } else { ChildNodeType* otherChild = other0.isChildMaskOn(i) ? other0.mNodes[i].getChild() : other1.mNodes[i].getChild(); assert(otherChild); if (this->isChildMaskOff(i)) { // Add a new child with the same coordinates, etc. as the other node's child. this->setChildNode(i, new ChildNodeType(otherChild->origin(), mNodes[i].getValue())); } if (other0.isChildMaskOff(i)) { // Combine node1's child with node0's constant value // and write the result into child i. mNodes[i].getChild()->combine2(other0.mNodes[i].getValue(), *other1.mNodes[i].getChild(), other0.isValueMaskOn(i), op); } else if (other1.isChildMaskOff(i)) { // Combine node0's child with node1's constant value // and write the result into child i. mNodes[i].getChild()->combine2(*other0.mNodes[i].getChild(), other1.mNodes[i].getValue(), other1.isValueMaskOn(i), op); } else { // Combine node0's child with node1's child // and write the result into child i. mNodes[i].getChild()->combine2(*other0.mNodes[i].getChild(), *other1.mNodes[i].getChild(), op); } } } } template template inline void InternalNode::combine2(const ValueType& value, const InternalNode& other, bool valueIsActive, CombineOp& op) { CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (other.isChildMaskOff(i)) { op(args.setARef(value) .setAIsActive(valueIsActive) .setBRef(other.mNodes[i].getValue()) .setBIsActive(other.isValueMaskOn(i))); // Replace child i with a constant value. this->makeChildNodeEmpty(i, args.result()); mValueMask.set(i, args.resultIsActive()); } else { ChildNodeType* otherChild = other.mNodes[i].getChild(); assert(otherChild); if (this->isChildMaskOff(i)) { // Add a new child with the same coordinates, etc. // as the other node's child. /// @todo Could the other node's ChildNodeType be different from this node's? this->setChildNode(i, new ChildNodeType(*otherChild)); } // Combine the other node's child with a constant value // and write the result into child i. mNodes[i].getChild()->combine2(value, *otherChild, valueIsActive, op); } } } template template inline void InternalNode::combine2(const InternalNode& other, const ValueType& value, bool valueIsActive, CombineOp& op) { CombineArgs args; for (Index i = 0; i < NUM_VALUES; ++i) { if (other.isChildMaskOff(i)) { op(args.setARef(other.mNodes[i].getValue()) .setAIsActive(other.isValueMaskOn(i)) .setBRef(value) .setBIsActive(valueIsActive)); // Replace child i with a constant value. this->makeChildNodeEmpty(i, args.result()); mValueMask.set(i, args.resultIsActive()); } else { ChildNodeType* otherChild = other.mNodes[i].getChild(); assert(otherChild); if (this->isChildMaskOff(i)) { // Add a new child with the same coordinates, etc. as the other node's child. this->setChildNode(i, new ChildNodeType(otherChild->origin(), mNodes[i].getValue())); } // Combine the other node's child with a constant value // and write the result into child i. mNodes[i].getChild()->combine2(*otherChild, value, valueIsActive, op); } } } //////////////////////////////////////// template template inline void InternalNode::visitActiveBBox(BBoxOp& op) const { for (ValueOnCIter i = this->cbeginValueOn(); i; ++i) { #ifdef _MSC_VER op.operator()(CoordBBox::createCube(i.getCoord(), ChildNodeType::DIM)); #else op.template operator()(CoordBBox::createCube(i.getCoord(), ChildNodeType::DIM)); #endif } if (op.template descent()) { for (ChildOnCIter i = this->cbeginChildOn(); i; ++i) i->visitActiveBBox(op); } else { for (ChildOnCIter i = this->cbeginChildOn(); i; ++i) { #ifdef _MSC_VER op.operator()(i->getNodeBoundingBox()); #else op.template operator()(i->getNodeBoundingBox()); #endif } } } template template inline void InternalNode::visit(VisitorOp& op) { doVisit(*this, op); } template template inline void InternalNode::visit(VisitorOp& op) const { doVisit(*this, op); } template template inline void InternalNode::doVisit(NodeT& self, VisitorOp& op) { typename NodeT::ValueType val; for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { if (op(iter)) continue; if (typename ChildAllIterT::ChildNodeType* child = iter.probeChild(val)) { child->visit(op); } } } //////////////////////////////////////// template template inline void InternalNode::visit2Node(OtherNodeType& other, VisitorOp& op) { doVisit2Node(*this, other, op); } template template inline void InternalNode::visit2Node(OtherNodeType& other, VisitorOp& op) const { doVisit2Node(*this, other, op); } template template< typename NodeT, typename OtherNodeT, typename VisitorOp, typename ChildAllIterT, typename OtherChildAllIterT> inline void InternalNode::doVisit2Node(NodeT& self, OtherNodeT& other, VisitorOp& op) { // Allow the two nodes to have different ValueTypes, but not different dimensions. BOOST_STATIC_ASSERT(OtherNodeT::NUM_VALUES == NodeT::NUM_VALUES); BOOST_STATIC_ASSERT(OtherNodeT::LEVEL == NodeT::LEVEL); typename NodeT::ValueType val; typename OtherNodeT::ValueType otherVal; ChildAllIterT iter = self.beginChildAll(); OtherChildAllIterT otherIter = other.beginChildAll(); for ( ; iter && otherIter; ++iter, ++otherIter) { const size_t skipBranch = static_cast(op(iter, otherIter)); typename ChildAllIterT::ChildNodeType* child = (skipBranch & 1U) ? NULL : iter.probeChild(val); typename OtherChildAllIterT::ChildNodeType* otherChild = (skipBranch & 2U) ? NULL : otherIter.probeChild(otherVal); if (child != NULL && otherChild != NULL) { child->visit2Node(*otherChild, op); } else if (child != NULL) { child->visit2(otherIter, op); } else if (otherChild != NULL) { otherChild->visit2(iter, op, /*otherIsLHS=*/true); } } } //////////////////////////////////////// template template inline void InternalNode::visit2(OtherChildAllIterType& otherIter, VisitorOp& op, bool otherIsLHS) { doVisit2( *this, otherIter, op, otherIsLHS); } template template inline void InternalNode::visit2(OtherChildAllIterType& otherIter, VisitorOp& op, bool otherIsLHS) const { doVisit2( *this, otherIter, op, otherIsLHS); } template template inline void InternalNode::doVisit2(NodeT& self, OtherChildAllIterT& otherIter, VisitorOp& op, bool otherIsLHS) { if (!otherIter) return; const size_t skipBitMask = (otherIsLHS ? 2U : 1U); typename NodeT::ValueType val; for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { const size_t skipBranch = static_cast( otherIsLHS ? op(otherIter, iter) : op(iter, otherIter)); typename ChildAllIterT::ChildNodeType* child = (skipBranch & skipBitMask) ? NULL : iter.probeChild(val); if (child != NULL) child->visit2(otherIter, op, otherIsLHS); } } //////////////////////////////////////// template inline void InternalNode::writeBuffers(std::ostream& os, bool toHalf) const { for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { iter->writeBuffers(os, toHalf); } } template inline void InternalNode::readBuffers(std::istream& is, bool fromHalf) { for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { iter->readBuffers(is, fromHalf); } } //////////////////////////////////////// template void InternalNode::getNodeLog2Dims(std::vector& dims) { dims.push_back(Log2Dim); ChildNodeType::getNodeLog2Dims(dims); } template inline void InternalNode::offsetToLocalCoord(Index n, Coord &xyz) { assert(n<(1<<3*Log2Dim)); xyz.setX(n >> 2*Log2Dim); n &= ((1<<2*Log2Dim)-1); xyz.setY(n >> Log2Dim); xyz.setZ(n & ((1< inline Index InternalNode::coordToOffset(const Coord& xyz) { return (((xyz[0] & (DIM-1u)) >> ChildNodeType::TOTAL) << 2*Log2Dim) + (((xyz[1] & (DIM-1u)) >> ChildNodeType::TOTAL) << Log2Dim) + ((xyz[2] & (DIM-1u)) >> ChildNodeType::TOTAL); } template inline Coord InternalNode::offsetToGlobalCoord(Index n) const { Coord local; this->offsetToLocalCoord(n, local); local <<= ChildT::TOTAL; return local + this->origin(); } //////////////////////////////////////// template inline void InternalNode::resetBackground(const ValueType& oldBackground, const ValueType& newBackground) { if (math::isExactlyEqual(oldBackground, newBackground)) return; for (Index i = 0; i < NUM_VALUES; ++i) { if (this->isChildMaskOn(i)) { mNodes[i].getChild()->resetBackground(oldBackground, newBackground); } else if (this->isValueMaskOff(i)) { if (math::isApproxEqual(mNodes[i].getValue(), oldBackground)) { mNodes[i].setValue(newBackground); } else if (math::isApproxEqual(mNodes[i].getValue(), math::negative(oldBackground))) { mNodes[i].setValue(math::negative(newBackground)); } } } } template template inline bool InternalNode::hasSameTopology( const InternalNode* other) const { if (Log2Dim != OtherLog2Dim || mChildMask != other->mChildMask || mValueMask != other->mValueMask) return false; for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { if (!iter->hasSameTopology(other->mNodes[iter.pos()].getChild())) return false; } return true; } template inline void InternalNode::resetChildNode(Index i, ChildNodeType* child) { assert(child); if (this->isChildMaskOn(i)) { delete mNodes[i].getChild(); } else { mChildMask.setOn(i); mValueMask.setOff(i); } mNodes[i].setChild(child); } template inline void InternalNode::setChildNode(Index i, ChildNodeType* child) { assert(child); assert(mChildMask.isOff(i)); mChildMask.setOn(i); mValueMask.setOff(i); mNodes[i].setChild(child); } template inline ChildT* InternalNode::unsetChildNode(Index i, const ValueType& value) { if (this->isChildMaskOff(i)) { mNodes[i].setValue(value); return NULL; } ChildNodeType* child = mNodes[i].getChild(); mChildMask.setOff(i); mNodes[i].setValue(value); return child; } template inline void InternalNode::makeChildNodeEmpty(Index n, const ValueType& value) { delete this->unsetChildNode(n, value); } template inline ChildT* InternalNode::getChildNode(Index n) { return (this->isChildMaskOn(n) ? mNodes[n].getChild() : NULL); } template inline const ChildT* InternalNode::getChildNode(Index n) const { return (this->isChildMaskOn(n) ? mNodes[n].getChild() : NULL); } } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_INTERNALNODE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/RootNode.h0000644000000000000000000034131512252453157014067 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// /// /// @file RootNode.h /// /// @brief The root node of an OpenVDB tree #ifndef OPENVDB_TREE_ROOTNODE_HAS_BEEN_INCLUDED #define OPENVDB_TREE_ROOTNODE_HAS_BEEN_INCLUDED #include #include #include #include #include //for boost::mpl::vector #include #include #include #include #include #include // for truncateRealToHalf() #include // for isZero(), isExactlyEqual(), etc. #include #include // for backward compatibility only (see readTopology()) #include #include "Util.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { template struct NodeChain; // forward declaration template class RootNode { public: typedef ChildType ChildNodeType; typedef typename ChildType::LeafNodeType LeafNodeType; typedef typename ChildType::ValueType ValueType; static const Index LEVEL = 1 + ChildType::LEVEL; // level 0 = leaf /// NodeChainType is a list of this tree's node types, from LeafNodeType to RootNode. typedef typename NodeChain::Type NodeChainType; BOOST_STATIC_ASSERT(boost::mpl::size::value == LEVEL + 1); /// @brief ValueConverter::Type is the type of a RootNode having the same /// child hierarchy as this node but a different value type, T. template struct ValueConverter { typedef RootNode::Type> Type; }; /// Construct a new tree with a background value of 0. RootNode(); /// Construct a new tree with the given background value. explicit RootNode(const ValueType& background); RootNode(const RootNode& other) { *this = other; } /// @brief Construct a new tree that reproduces the topology and active states of /// another tree (which may have a different ValueType), but not the other tree's values. /// @details All tiles and voxels that are active in the other tree are set to /// @a foreground in the new tree, and all inactive tiles and voxels are set to @a background. /// @param other the root node of a tree having (possibly) a different ValueType /// @param background the value to which inactive tiles and voxels are initialized /// @param foreground the value to which active tiles and voxels are initialized template RootNode(const RootNode& other, const ValueType& background, const ValueType& foreground, TopologyCopy); /// @brief Construct a new tree that reproduces the topology and active states of /// another tree (which may have a different ValueType), but not the other tree's values. /// All tiles and voxels in the new tree are set to @a background regardless of /// their active states in the other tree. /// @param other the root node of a tree having (possibly) a different ValueType /// @param background the value to which inactive tiles and voxels are initialized /// @note This copy constructor is generally faster than the one that takes both /// a foreground and a background value. Its main application is in multithreaded /// operations where the topology of the output tree exactly matches the input tree. template RootNode(const RootNode& other, const ValueType& background, TopologyCopy); RootNode& operator=(const RootNode& other); ~RootNode() { this->clearTable(); } private: struct Tile { Tile(): value(zeroVal()), active(false) {} Tile(const ValueType& v, bool b): value(v), active(b) {} ValueType value; bool active; }; // This lightweight struct pairs child pointers and tiles. struct NodeStruct { ChildType* child; Tile tile; NodeStruct(): child(NULL) {} NodeStruct(ChildType& c): child(&c) {} NodeStruct(const Tile& t): child(NULL), tile(t) {} ~NodeStruct() {} ///< @note doesn't delete child bool isChild() const { return child != NULL; } bool isTile() const { return child == NULL; } bool isTileOff() const { return isTile() && !tile.active; } bool isTileOn() const { return isTile() && tile.active; } void set(ChildType& c) { delete child; child = &c; } void set(const Tile& t) { delete child; child = NULL; tile = t; } ChildType& steal(const Tile& t) { ChildType* c = child; child = NULL; tile = t; return *c; } }; typedef std::map MapType; typedef typename MapType::iterator MapIter; typedef typename MapType::const_iterator MapCIter; typedef std::set CoordSet; typedef typename CoordSet::iterator CoordSetIter; typedef typename CoordSet::const_iterator CoordSetCIter; static void setTile(const MapIter& i, const Tile& t) { i->second.set(t); } static void setChild(const MapIter& i, ChildType& c) { i->second.set(c); } static Tile& getTile(const MapIter& i) { return i->second.tile; } static const Tile& getTile(const MapCIter& i) { return i->second.tile; } static ChildType& getChild(const MapIter& i) { return *(i->second.child); } static const ChildType& getChild(const MapCIter& i) { return *(i->second.child); } static ChildType& stealChild(const MapIter& i, const Tile& t) {return i->second.steal(t);} static const ChildType& stealChild(const MapCIter& i,const Tile& t) {return i->second.steal(t);} static bool isChild(const MapCIter& i) { return i->second.isChild(); } static bool isChild(const MapIter& i) { return i->second.isChild(); } static bool isTile(const MapCIter& i) { return i->second.isTile(); } static bool isTile(const MapIter& i) { return i->second.isTile(); } static bool isTileOff(const MapCIter& i) { return i->second.isTileOff(); } static bool isTileOff(const MapIter& i) { return i->second.isTileOff(); } static bool isTileOn(const MapCIter& i) { return i->second.isTileOn(); } static bool isTileOn(const MapIter& i) { return i->second.isTileOn(); } struct NullPred { static inline bool test(const MapIter&) { return true; } static inline bool test(const MapCIter&) { return true; } }; struct ValueOnPred { static inline bool test(const MapIter& i) { return isTileOn(i); } static inline bool test(const MapCIter& i) { return isTileOn(i); } }; struct ValueOffPred { static inline bool test(const MapIter& i) { return isTileOff(i); } static inline bool test(const MapCIter& i) { return isTileOff(i); } }; struct ValueAllPred { static inline bool test(const MapIter& i) { return isTile(i); } static inline bool test(const MapCIter& i) { return isTile(i); } }; struct ChildOnPred { static inline bool test(const MapIter& i) { return isChild(i); } static inline bool test(const MapCIter& i) { return isChild(i); } }; struct ChildOffPred { static inline bool test(const MapIter& i) { return isTile(i); } static inline bool test(const MapCIter& i) { return isTile(i); } }; template class BaseIter { public: typedef _RootNodeT RootNodeT; typedef _MapIterT MapIterT; // either MapIter or MapCIter bool operator==(const BaseIter& other) const { return (mParentNode == other.mParentNode) && (mIter == other.mIter); } bool operator!=(const BaseIter& other) const { return !(*this == other); } RootNodeT* getParentNode() const { return mParentNode; } /// Return a reference to the node over which this iterator iterates. RootNodeT& parent() const { if (!mParentNode) OPENVDB_THROW(ValueError, "iterator references a null parent node"); return *mParentNode; } bool test() const { assert(mParentNode); return mIter != mParentNode->mTable.end(); } operator bool() const { return this->test(); } void increment() { ++mIter; this->skip(); } bool next() { this->increment(); return this->test(); } void increment(Index n) { for (int i = 0; i < n && this->next(); ++i) {} } /// @brief Return this iterator's position as an offset from /// the beginning of the parent node's map. Index pos() const { return !mParentNode ? 0U : Index(std::distance(mParentNode->mTable.begin(), mIter)); } bool isValueOn() const { return RootNodeT::isTileOn(mIter); } bool isValueOff() const { return RootNodeT::isTileOff(mIter); } void setValueOn(bool on = true) const { mIter->second.tile.active = on; } void setValueOff() const { mIter->second.tile.active = false; } /// Return the coordinates of the item to which this iterator is pointing. Coord getCoord() const { return mIter->first; } /// Return in @a xyz the coordinates of the item to which this iterator is pointing. void getCoord(Coord& xyz) const { xyz = this->getCoord(); } protected: BaseIter(): mParentNode(NULL) {} BaseIter(RootNodeT& parent, const MapIterT& iter): mParentNode(&parent), mIter(iter) {} void skip() { while (this->test() && !FilterPredT::test(mIter)) ++mIter; } RootNodeT* mParentNode; MapIterT mIter; }; // BaseIter template class ChildIter: public BaseIter { public: typedef BaseIter BaseT; typedef RootNodeT NodeType; typedef NodeType ValueType; typedef ChildNodeT ChildNodeType; typedef typename boost::remove_const::type NonConstNodeType; typedef typename boost::remove_const::type NonConstValueType; typedef typename boost::remove_const::type NonConstChildNodeType; using BaseT::mIter; ChildIter() {} ChildIter(RootNodeT& parent, const MapIterT& iter): BaseT(parent, iter) { BaseT::skip(); } ChildIter& operator++() { BaseT::increment(); return *this; } ChildNodeT& getValue() const { return getChild(mIter); } ChildNodeT& operator*() const { return this->getValue(); } ChildNodeT* operator->() const { return &this->getValue(); } }; // ChildIter template class ValueIter: public BaseIter { public: typedef BaseIter BaseT; typedef RootNodeT NodeType; typedef ValueT ValueType; typedef typename boost::remove_const::type NonConstNodeType; typedef typename boost::remove_const::type NonConstValueType; using BaseT::mIter; ValueIter() {} ValueIter(RootNodeT& parent, const MapIterT& iter): BaseT(parent, iter) { BaseT::skip(); } ValueIter& operator++() { BaseT::increment(); return *this; } ValueT& getValue() const { return getTile(mIter).value; } ValueT& operator*() const { return this->getValue(); } ValueT* operator->() const { return &(this->getValue()); } void setValue(const ValueT& v) const { assert(isTile(mIter)); getTile(mIter).value = v; } template void modifyValue(const ModifyOp& op) const { assert(isTile(mIter)); op(getTile(mIter).value); } }; // ValueIter template class DenseIter: public BaseIter { public: typedef BaseIter BaseT; typedef RootNodeT NodeType; typedef ValueT ValueType; typedef ChildNodeT ChildNodeType; typedef typename boost::remove_const::type NonConstNodeType; typedef typename boost::remove_const::type NonConstValueType; typedef typename boost::remove_const::type NonConstChildNodeType; using BaseT::mIter; DenseIter() {} DenseIter(RootNodeT& parent, const MapIterT& iter): BaseT(parent, iter) {} DenseIter& operator++() { BaseT::increment(); return *this; } bool isChildNode() const { return isChild(mIter); } ChildNodeT* probeChild(NonConstValueType& value) const { if (isChild(mIter)) return &getChild(mIter); value = getTile(mIter).value; return NULL; } bool probeChild(ChildNodeT*& child, NonConstValueType& value) const { child = this->probeChild(value); return child != NULL; } bool probeValue(NonConstValueType& value) const { return !this->probeChild(value); } void setChild(ChildNodeT& c) const { RootNodeT::setChild(mIter, c); } void setChild(ChildNodeT* c) const { assert(c != NULL); RootNodeT::setChild(mIter, *c); } void setValue(const ValueT& v) const { if (isTile(mIter)) getTile(mIter).value = v; /// @internal For consistency with iterators for other node types /// (see, e.g., InternalNode::DenseIter::unsetItem()), we don't call /// setTile() here, because that would also delete the child. else stealChild(mIter, Tile(v, /*active=*/true)); } }; // DenseIter public: typedef ChildIter ChildOnIter; typedef ChildIter ChildOnCIter; typedef ValueIter ChildOffIter; typedef ValueIter ChildOffCIter; typedef DenseIter ChildAllIter; typedef DenseIter ChildAllCIter; typedef ValueIter ValueOnIter; typedef ValueIter ValueOnCIter; typedef ValueIter ValueOffIter; typedef ValueIter ValueOffCIter; typedef ValueIter ValueAllIter; typedef ValueIter ValueAllCIter; ChildOnCIter cbeginChildOn() const { return ChildOnCIter(*this, mTable.begin()); } ChildOffCIter cbeginChildOff() const { return ChildOffCIter(*this, mTable.begin()); } ChildAllCIter cbeginChildAll() const { return ChildAllCIter(*this, mTable.begin()); } ChildOnCIter beginChildOn() const { return cbeginChildOn(); } ChildOffCIter beginChildOff() const { return cbeginChildOff(); } ChildAllCIter beginChildAll() const { return cbeginChildAll(); } ChildOnIter beginChildOn() { return ChildOnIter(*this, mTable.begin()); } ChildOffIter beginChildOff() { return ChildOffIter(*this, mTable.begin()); } ChildAllIter beginChildAll() { return ChildAllIter(*this, mTable.begin()); } ValueOnCIter cbeginValueOn() const { return ValueOnCIter(*this, mTable.begin()); } ValueOffCIter cbeginValueOff() const { return ValueOffCIter(*this, mTable.begin()); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(*this, mTable.begin()); } ValueOnCIter beginValueOn() const { return cbeginValueOn(); } ValueOffCIter beginValueOff() const { return cbeginValueOff(); } ValueAllCIter beginValueAll() const { return cbeginValueAll(); } ValueOnIter beginValueOn() { return ValueOnIter(*this, mTable.begin()); } ValueOffIter beginValueOff() { return ValueOffIter(*this, mTable.begin()); } ValueAllIter beginValueAll() { return ValueAllIter(*this, mTable.begin()); } /// Return the total amount of memory in bytes occupied by this node and its children. Index64 memUsage() const; /// @brief Expand the specified bbox so it includes the active tiles of /// this root node as well as all the active values in its child /// nodes. If visitVoxels is false LeafNodes will be approximated /// as dense, i.e. with all voxels active. Else the individual /// active voxels are visited to produce a tight bbox. void evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels = true) const; OPENVDB_DEPRECATED void evalActiveVoxelBoundingBox(CoordBBox& bbox) const; /// Return the bounding box of this RootNode, i.e., an infinite bounding box. static CoordBBox getNodeBoundingBox() { return CoordBBox::inf(); } /// @brief Change inactive tiles or voxels with a value equal to +/- the /// old background to the specified value (with the same sign). Active values /// are unchanged. /// @param value The new background value /// @param updateChildNodes If true (which is the default) the /// background values of the child nodes is also updated. Else /// only the background value stored in the RootNode itself is changed. void setBackground(const ValueType& value, bool updateChildNodes = true); /// Return this node's background value. const ValueType& background() const { return mBackground; } /// Return @c true if the given tile is inactive and has the background value. bool isBackgroundTile(const Tile&) const; //@{ /// Return @c true if the given iterator points to an inactive tile with the background value. bool isBackgroundTile(const MapIter&) const; bool isBackgroundTile(const MapCIter&) const; //@} /// Return the number of background tiles. size_t numBackgroundTiles() const; /// @brief Remove all background tiles. /// @return the number of tiles removed. size_t eraseBackgroundTiles(); void clear() { this->clearTable(); } /// Return @c true if this node's table is either empty or contains only background tiles. bool empty() const { return mTable.size() == numBackgroundTiles(); } /// @brief Expand this node's table so that (x, y, z) is included in the index range. /// @return @c true if an expansion was performed (i.e., if (x, y, z) was not already /// included in the index range). bool expand(const Coord& xyz); static Index getLevel() { return LEVEL; } static void getNodeLog2Dims(std::vector& dims); static Index getChildDim() { return ChildType::DIM; } /// Return the number of entries in this node's table. Index getTableSize() const { return mTable.size(); } Index getWidth() const { return this->getMaxIndex()[0] - this->getMinIndex()[0]; } Index getHeight() const { return this->getMaxIndex()[1] - this->getMinIndex()[1]; } Index getDepth() const { return this->getMaxIndex()[2] - this->getMinIndex()[2]; } /// Return the smallest index of the current tree. Coord getMinIndex() const; /// Return the largest index of the current tree. Coord getMaxIndex() const; /// Return the current index range. Both min and max are inclusive. void getIndexRange(CoordBBox& bbox) const; /// @brief Return @c true if the given tree has the same node and active value /// topology as this tree (but possibly a different @c ValueType). template bool hasSameTopology(const RootNode& other) const; /// Return @c false if the other node's dimensions don't match this node's. template static bool hasSameConfiguration(const RootNode& other); Index32 leafCount() const; Index32 nonLeafCount() const; Index64 onVoxelCount() const; Index64 offVoxelCount() const; Index64 onLeafVoxelCount() const; Index64 offLeafVoxelCount() const; Index64 onTileCount() const; bool isValueOn(const Coord& xyz) const; bool hasActiveTiles() const; const ValueType& getValue(const Coord& xyz) const; bool probeValue(const Coord& xyz, ValueType& value) const; /// @brief Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides. /// @details If (x, y, z) isn't explicitly represented in the tree (i.e., /// it is implicitly a background voxel), return -1. int getValueDepth(const Coord& xyz) const; /// Set the active state of the voxel at the given coordinates but don't change its value. void setActiveState(const Coord& xyz, bool on); /// Set the value of the voxel at the given coordinates but don't change its active state. void setValueOnly(const Coord& xyz, const ValueType& value); /// Set the value of the voxel at the given coordinates and mark the voxel as active. void setValueOn(const Coord& xyz, const ValueType& value); /// Mark the voxel at the given coordinates as inactive but don't change its value. void setValueOff(const Coord& xyz); /// Set the value of the voxel at the given coordinates and mark the voxel as inactive. void setValueOff(const Coord& xyz, const ValueType& value); /// @brief Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. template void modifyValue(const Coord& xyz, const ModifyOp& op); /// Apply a functor to the voxel at the given coordinates. template void modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op); /// @brief Set all voxels within a given box to a constant value, if necessary /// subdividing tiles that intersect the box. /// @param bbox inclusive coordinates of opposite corners of an axis-aligned box /// @param value the value to which to set voxels within the box /// @param active if true, mark voxels within the box as active, /// otherwise mark them as inactive void fill(const CoordBBox& bbox, const ValueType& value, bool active = true); /// @brief Copy into a dense grid the values of all voxels, both active and inactive, /// that intersect a given bounding box. /// @param bbox inclusive bounding box of the voxels to be copied into the dense grid /// @param dense dense grid with a stride in @e z of one (see tools::Dense /// in tools/Dense.h for the required API) template void copyToDense(const CoordBBox& bbox, DenseT& dense) const; // // I/O // bool writeTopology(std::ostream&, bool toHalf = false) const; bool readTopology(std::istream&, bool fromHalf = false); void writeBuffers(std::ostream&, bool toHalf = false) const; void readBuffers(std::istream&, bool fromHalf = false); // // Voxel access // /// Return the value of the voxel at the given coordinates and, if necessary, update /// the accessor with pointers to the nodes along the path from the root node to /// the node containing the voxel. /// @note Used internally by ValueAccessor. template const ValueType& getValueAndCache(const Coord& xyz, AccessorT&) const; /// Return @c true if the voxel at the given coordinates is active and, if necessary, /// update the accessor with pointers to the nodes along the path from the root node /// to the node containing the voxel. /// @note Used internally by ValueAccessor. template bool isValueOnAndCache(const Coord& xyz, AccessorT&) const; /// Change the value of the voxel at the given coordinates and mark it as active. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Set the value of the voxel at the given coordinates without changing its active state. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Apply a functor to the value of the voxel at the given coordinates /// and mark the voxel as active. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); /// Apply a functor to the voxel at the given coordinates. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void modifyValueAndActiveStateAndCache(const Coord& xyz, const ModifyOp& op, AccessorT&); /// Change the value of the voxel at the given coordinates and mark it as inactive. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT&); /// Set the active state of the voxel at the given coordinates without changing its value. /// If necessary, update the accessor with pointers to the nodes along the path /// from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template void setActiveStateAndCache(const Coord& xyz, bool on, AccessorT&); /// Return, in @a value, the value of the voxel at the given coordinates and, /// if necessary, update the accessor with pointers to the nodes along /// the path from the root node to the node containing the voxel. /// @return @c true if the voxel at the given coordinates is active /// @note Used internally by ValueAccessor. template bool probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT&) const; /// Return the tree depth (0 = root) at which the value of voxel (x, y, z) resides. /// If (x, y, z) isn't explicitly represented in the tree (i.e., it is implicitly /// a background voxel), return -1. If necessary, update the accessor with pointers /// to the nodes along the path from the root node to the node containing the voxel. /// @note Used internally by ValueAccessor. template int getValueDepthAndCache(const Coord& xyz, AccessorT&) const; /// Call the @c PruneOp functor for each child node and, if the functor /// returns @c true, prune the node and replace it with a tile. /// /// This method is used to implement all of the various pruning algorithms /// (prune(), pruneInactive(), etc.). It should rarely be called directly. /// @see openvdb/tree/Util.h for the definition of the @c PruneOp functor template void pruneOp(PruneOp&); /// @brief Reduce the memory footprint of this tree by replacing with tiles /// any nodes whose values are all the same (optionally to within a tolerance) /// and have the same active state. void prune(const ValueType& tolerance = zeroVal()); /// @brief Reduce the memory footprint of this tree by replacing with /// tiles of the given value any nodes whose values are all inactive. void pruneInactive(const ValueType&); /// @brief Reduce the memory footprint of this tree by replacing with /// background tiles any nodes whose values are all inactive. void pruneInactive(); /// @brief Reduce the memory footprint of this tree by replacing with tiles /// any non-leaf nodes whose values are all the same (optionally to within a tolerance) /// and have the same active state. void pruneTiles(const ValueType& tolerance); /// @brief Add the given leaf node to this tree, creating a new branch if necessary. /// If a leaf node with the same origin already exists, replace it. void addLeaf(LeafNodeType* leaf); /// @brief Same as addLeaf() but, if necessary, update the given accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template void addLeafAndCache(LeafNodeType* leaf, AccessorT&); /// @brief Return a pointer to the node of type @c NodeT that contains voxel (x, y, z) /// and replace it with a tile of the specified value and state. /// If no such node exists, leave the tree unchanged and return @c NULL. /// /// @note The caller takes ownership of the node and is responsible for deleting it. /// /// @warning Since this method potentially removes nodes and branches of the tree, /// it is important to clear the caches of all ValueAccessors associated with this tree. template NodeT* stealNode(const Coord& xyz, const ValueType& value, bool state); /// @brief Add a tile containing voxel (x, y, z) at the specified tree level, /// creating a new branch if necessary. Delete any existing lower-level nodes /// that contain (x, y, z). void addTile(Index level, const Coord& xyz, const ValueType& value, bool state); /// @brief Same as addTile() but, if necessary, update the given accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template void addTileAndCache(Index level, const Coord& xyz, const ValueType&, bool state, AccessorT&); /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). /// If no such node exists, create one that preserves the values and /// active states of all voxels. /// @details Use this method to preallocate a static tree topology /// over which to safely perform multithreaded processing. LeafNodeType* touchLeaf(const Coord& xyz); /// @brief Same as touchLeaf() but, if necessary, update the given accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template LeafNodeType* touchLeafAndCache(const Coord& xyz, AccessorT& acc); //@{ /// @brief Return a pointer to the node that contains voxel (x, y, z). /// If no such node exists, return NULL. template NodeT* probeNode(const Coord& xyz); template const NodeT* probeConstNode(const Coord& xyz) const; //@} //@{ /// @brief Same as probeNode() but, if necessary, update the given accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template NodeT* probeNodeAndCache(const Coord& xyz, AccessorT& acc); template const NodeT* probeConstNodeAndCache(const Coord& xyz, AccessorT& acc) const; //@} //@{ /// @brief Return a pointer to the leaf node that contains voxel (x, y, z). /// If no such node exists, return NULL. LeafNodeType* probeLeaf(const Coord& xyz); const LeafNodeType* probeConstLeaf(const Coord& xyz) const; const LeafNodeType* probeLeaf(const Coord& xyz) const; //@} //@{ /// @brief Same as probeLeaf() but, if necessary, update the given accessor with pointers /// to the nodes along the path from the root node to the node containing the coordinate. template LeafNodeType* probeLeafAndCache(const Coord& xyz, AccessorT& acc); template const LeafNodeType* probeConstLeafAndCache(const Coord& xyz, AccessorT& acc) const; template const LeafNodeType* probeLeafAndCache(const Coord& xyz, AccessorT& acc) const; //@} // // Aux methods // /// @brief Set the values of all inactive voxels and tiles of a narrow-band /// level set from the signs of the active voxels, setting outside values to /// +background and inside values to -background. /// @warning This method should only be used on closed, narrow-band level sets. void signedFloodFill(); /// @brief Set the values of all inactive voxels and tiles of a narrow-band /// level set from the signs of the active voxels, setting exterior values to /// @a outside and interior values to @a inside. Set the background value /// of this tree to @a outside. /// @warning This method should only be used on closed, narrow-band level sets. void signedFloodFill(const ValueType& outside, const ValueType& inside); /// Densify active tiles, i.e., replace them with leaf-level active voxels. void voxelizeActiveTiles(); /// @brief Efficiently merge another tree into this tree using one of several schemes. /// @details This operation is primarily intended to combine trees that are mostly /// non-overlapping (for example, intermediate trees from computations that are /// parallelized across disjoint regions of space). /// @note This operation is not guaranteed to produce an optimally sparse tree. /// Follow merge() with prune() for optimal sparseness. /// @warning This operation always empties the other tree. template void merge(RootNode& other); /// @brief Union this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. /// @details The resulting state of a value is active if the corresponding value /// was already active OR if it is active in the other tree. Also, a resulting /// value maps to a voxel if the corresponding value already mapped to a voxel /// OR if it is a voxel in the other tree. Thus, a resulting value can only /// map to a tile if the corresponding value already mapped to a tile /// AND if it is a tile value in other tree. /// /// @note This operation modifies only active states, not values. /// Specifically, active tiles and voxels in this tree are not changed, and /// tiles or voxels that were inactive in this tree but active in the other tree /// are marked as active in this tree but left with their original values. template void topologyUnion(const RootNode& other); /// @brief Intersects this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. /// @details The resulting state of a value is active only if the corresponding /// value was already active AND if it is active in the other tree. Also, a /// resulting value maps to a voxel if the corresponding value /// already mapped to an active voxel in either of the two grids /// and it maps to an active tile or voxel in the other grid. /// /// @note This operation can delete branches in this grid if they /// overlap with inactive tiles in the other grid. Likewise active /// voxels can be turned into inactive voxels resulting in leaf /// nodes with no active values. Thus, it is recommended to /// subsequently call prune. template void topologyIntersection(const RootNode& other); /// @brief Difference this tree's set of active values with the active values /// of the other tree, whose @c ValueType may be different. So a /// resulting voxel will be active only if the original voxel is /// active in this tree and inactive in the other tree. /// /// @note This operation can delete branches in this grid if they /// overlap with active tiles in the other grid. Likewise active /// voxels can be turned into inactive voxels resulting in leaf /// nodes with no active values. Thus, it is recommended to /// subsequently call prune. template void topologyDifference(const RootNode& other); template void combine(RootNode& other, CombineOp&, bool prune = false); template void combine2(const RootNode& other0, const RootNode& other1, CombineOp& op, bool prune = false); /// @brief Call the templated functor BBoxOp with bounding box /// information for all active tiles and leaf nodes in the tree. /// An additional level argument is provided for each callback. /// /// @note The bounding boxes are guaranteed to be non-overlapping. template void visitActiveBBox(BBoxOp&) const; template void visit(VisitorOp&); template void visit(VisitorOp&) const; template void visit2(OtherRootNodeType& other, VisitorOp&); template void visit2(OtherRootNodeType& other, VisitorOp&) const; private: /// During topology-only construction, access is needed /// to protected/private members of other template instances. template friend class RootNode; /// Currently no-op, but can be used to define empty and delete keys for mTable void initTable() {} inline void clearTable(); //@{ /// @internal Used by doVisit2(). void resetTable(MapType& table) { mTable.swap(table); table.clear(); } void resetTable(const MapType&) const {} //@} Index getChildCount() const; Index getTileCount() const; Index getActiveTileCount() const; Index getInactiveTileCount() const; /// Return a MapType key for the given coordinates. static Coord coordToKey(const Coord& xyz) { return xyz & ~(ChildType::DIM - 1); } /// Insert this node's mTable keys into the given set. void insertKeys(CoordSet&) const; /// Return @c true if this node's mTable contains the given key. bool hasKey(const Coord& key) const { return mTable.find(key) != mTable.end(); } //@{ /// @brief Look up the given key in this node's mTable. /// @return an iterator pointing to the matching mTable entry or to mTable.end(). MapIter findKey(const Coord& key) { return mTable.find(key); } MapCIter findKey(const Coord& key) const { return mTable.find(key); } //@} //@{ /// @brief Convert the given coordinates to a key and look the key up in this node's mTable. /// @return an iterator pointing to the matching mTable entry or to mTable.end(). MapIter findCoord(const Coord& xyz) { return mTable.find(coordToKey(xyz)); } MapCIter findCoord(const Coord& xyz) const { return mTable.find(coordToKey(xyz)); } //@} /// @brief Convert the given coordinates to a key and look the key up in this node's mTable. /// @details If the key is not found, insert a background tile with that key. /// @return an iterator pointing to the matching mTable entry. MapIter findOrAddCoord(const Coord& xyz); /// @throw TypeError if the other node's dimensions don't match this node's. template static void enforceSameConfiguration(const RootNode& other); template static inline void doVisit(RootNodeT&, VisitorOp&); template static inline void doVisit2(RootNodeT&, OtherRootNodeT&, VisitorOp&); MapType mTable; ValueType mBackground; }; // end of RootNode class //////////////////////////////////////// /// @brief NodeChain::Type is a boost::mpl::vector /// that lists the types of the nodes of the tree rooted at RootNodeType in reverse order, /// from LeafNode to RootNode. /// @details For example, if RootNodeType is /// @code /// RootNode > > /// @endcode /// then NodeChain::Type is /// @code /// boost::mpl::vector< /// LeafNode, /// InternalNode, /// InternalNode >, /// RootNode > > > /// @endcode /// /// @note Use the following to get the Nth node type, where N=0 is the LeafNodeType: /// @code /// boost::mpl::at >::type /// @endcode template struct NodeChain { typedef typename NodeChain::Type SubtreeT; typedef typename boost::mpl::push_back::type Type; }; /// Specialization to terminate NodeChain template struct NodeChain { typedef typename boost::mpl::vector::type Type; }; //////////////////////////////////////// template inline RootNode::RootNode(): mBackground(zeroVal()) { this->initTable(); } template inline RootNode::RootNode(const ValueType& background) : mBackground(background) { this->initTable(); } template template inline RootNode::RootNode(const RootNode& other, const ValueType& backgd, const ValueType& foregd, TopologyCopy): mBackground(backgd) { typedef RootNode OtherRootT; /// @todo Can this be avoided with partial specialization? enforceSameConfiguration(other); const Tile bgTile(backgd, /*active=*/false), fgTile(foregd, true); this->initTable(); for (typename OtherRootT::MapCIter i=other.mTable.begin(), e=other.mTable.end(); i != e; ++i) { mTable[i->first] = OtherRootT::isTile(i) ? NodeStruct(OtherRootT::isTileOn(i) ? fgTile : bgTile) : NodeStruct(*(new ChildT(OtherRootT::getChild(i), backgd, foregd, TopologyCopy()))); } } template template inline RootNode::RootNode(const RootNode& other, const ValueType& backgd, TopologyCopy): mBackground(backgd) { typedef RootNode OtherRootT; /// @todo Can this be avoided with partial specialization? enforceSameConfiguration(other); const Tile bgTile(backgd, /*active=*/false), fgTile(backgd, true); this->initTable(); for (typename OtherRootT::MapCIter i=other.mTable.begin(), e=other.mTable.end(); i != e; ++i) { mTable[i->first] = OtherRootT::isTile(i) ? NodeStruct(OtherRootT::isTileOn(i) ? fgTile : bgTile) : NodeStruct(*(new ChildT(OtherRootT::getChild(i), backgd, TopologyCopy()))); } } template inline RootNode& RootNode::operator=(const RootNode& other) { mBackground = other.mBackground; this->clearTable(); this->initTable(); for (MapCIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { mTable[i->first] = isTile(i) ? NodeStruct(getTile(i)) : NodeStruct(*(new ChildT(getChild(i)))); } return *this; } //////////////////////////////////////// template inline void RootNode::setBackground(const ValueType& background, bool updateChildNodes) { if (math::isExactlyEqual(background, mBackground)) return; if (updateChildNodes) { // Traverse the tree, replacing occurrences of mBackground with background // and -mBackground with -background. for (MapIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { ChildT *child = iter->second.child; if (child) { child->resetBackground(/*old=*/mBackground, /*new=*/background); } else { Tile& tile = getTile(iter); if (tile.active) continue;//only change inactive tiles if (math::isApproxEqual(tile.value, mBackground)) { tile.value = background; } else if (math::isApproxEqual(tile.value, math::negative(mBackground))) { tile.value = math::negative(background); } } } } mBackground = background; } template inline bool RootNode::isBackgroundTile(const Tile& tile) const { return !tile.active && math::isApproxEqual(tile.value, mBackground); } template inline bool RootNode::isBackgroundTile(const MapIter& iter) const { return isTileOff(iter) && math::isApproxEqual(getTile(iter).value, mBackground); } template inline bool RootNode::isBackgroundTile(const MapCIter& iter) const { return isTileOff(iter) && math::isApproxEqual(getTile(iter).value, mBackground); } template inline size_t RootNode::numBackgroundTiles() const { size_t count = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isBackgroundTile(i)) ++count; } return count; } template inline size_t RootNode::eraseBackgroundTiles() { std::set keysToErase; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isBackgroundTile(i)) keysToErase.insert(i->first); } for (std::set::iterator i = keysToErase.begin(), e = keysToErase.end(); i != e; ++i) { mTable.erase(*i); } return keysToErase.size(); } //////////////////////////////////////// template inline void RootNode::insertKeys(CoordSet& keys) const { for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { keys.insert(i->first); } } template inline typename RootNode::MapIter RootNode::findOrAddCoord(const Coord& xyz) { const Coord key = coordToKey(xyz); std::pair result = mTable.insert( typename MapType::value_type(key, NodeStruct(Tile(mBackground, /*active=*/false)))); return result.first; } template inline bool RootNode::expand(const Coord& xyz) { const Coord key = coordToKey(xyz); std::pair result = mTable.insert( typename MapType::value_type(key, NodeStruct(Tile(mBackground, /*active=*/false)))); return result.second; // return true if the key did not already exist } //////////////////////////////////////// template inline void RootNode::getNodeLog2Dims(std::vector& dims) { dims.push_back(0); // magic number; RootNode has no Log2Dim ChildT::getNodeLog2Dims(dims); } template inline Coord RootNode::getMinIndex() const { return mTable.empty() ? Coord(0) : mTable.begin()->first; } template inline Coord RootNode::getMaxIndex() const { return mTable.empty() ? Coord(0) : mTable.rbegin()->first + Coord(ChildT::DIM - 1); } template inline void RootNode::getIndexRange(CoordBBox& bbox) const { bbox.min() = this->getMinIndex(); bbox.max() = this->getMaxIndex(); } //////////////////////////////////////// template template inline bool RootNode::hasSameTopology(const RootNode& other) const { typedef RootNode OtherRootT; typedef typename OtherRootT::MapType OtherMapT; typedef typename OtherRootT::MapIter OtherIterT; typedef typename OtherRootT::MapCIter OtherCIterT; if (!hasSameConfiguration(other)) return false; // Create a local copy of the other node's table. OtherMapT copyOfOtherTable = other.mTable; // For each entry in this node's table... for (MapCIter thisIter = mTable.begin(); thisIter != mTable.end(); ++thisIter) { if (this->isBackgroundTile(thisIter)) continue; // ignore background tiles // Fail if there is no corresponding entry in the other node's table. OtherCIterT otherIter = other.findKey(thisIter->first); if (otherIter == other.mTable.end()) return false; // Fail if this entry is a tile and the other is a child or vice-versa. if (isChild(thisIter)) {//thisIter points to a child if (OtherRootT::isTile(otherIter)) return false; // Fail if both entries are children, but the children have different topology. if (!getChild(thisIter).hasSameTopology(&OtherRootT::getChild(otherIter))) return false; } else {//thisIter points to a tile if (OtherRootT::isChild(otherIter)) return false; if (getTile(thisIter).active != OtherRootT::getTile(otherIter).active) return false; } // Remove tiles and child nodes with matching topology from // the copy of the other node's table. This is required since // the two root tables can include an arbitrary number of // background tiles and still have the same topology! copyOfOtherTable.erase(otherIter->first); } // Fail if the remaining entries in copyOfOtherTable are not all background tiles. for (OtherIterT i = copyOfOtherTable.begin(), e = copyOfOtherTable.end(); i != e; ++i) { if (!other.isBackgroundTile(i)) return false; } return true; } template template inline bool RootNode::hasSameConfiguration(const RootNode&) { std::vector thisDims, otherDims; RootNode::getNodeLog2Dims(thisDims); RootNode::getNodeLog2Dims(otherDims); return (thisDims == otherDims); } template template inline void RootNode::enforceSameConfiguration(const RootNode&) { std::vector thisDims, otherDims; RootNode::getNodeLog2Dims(thisDims); RootNode::getNodeLog2Dims(otherDims); if (thisDims != otherDims) { std::ostringstream ostr; ostr << "grids have incompatible configurations (" << thisDims[0]; for (size_t i = 1, N = thisDims.size(); i < N; ++i) ostr << " x " << thisDims[i]; ostr << " vs. " << otherDims[0]; for (size_t i = 1, N = otherDims.size(); i < N; ++i) ostr << " x " << otherDims[i]; ostr << ")"; OPENVDB_THROW(TypeError, ostr.str()); } } //////////////////////////////////////// template inline Index64 RootNode::memUsage() const { Index64 sum = sizeof(*this); for (MapCIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (const ChildT *child = iter->second.child) { sum += child->memUsage(); } } return sum; } template inline void RootNode::evalActiveVoxelBoundingBox(CoordBBox& bbox) const { for (MapCIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (const ChildT *child = iter->second.child) { child->evalActiveVoxelBoundingBox(bbox); } else if (isTileOn(iter)) { bbox.expand(iter->first, ChildT::DIM); } } } template inline void RootNode::evalActiveBoundingBox(CoordBBox& bbox, bool visitVoxels) const { for (MapCIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (const ChildT *child = iter->second.child) { child->evalActiveBoundingBox(bbox, visitVoxels); } else if (isTileOn(iter)) { bbox.expand(iter->first, ChildT::DIM); } } } template inline void RootNode::clearTable() { for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { delete i->second.child; } mTable.clear(); } template inline Index RootNode::getChildCount() const { Index sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) ++sum; } return sum; } template inline Index RootNode::getTileCount() const { Index sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isTile(i)) ++sum; } return sum; } template inline Index RootNode::getActiveTileCount() const { Index sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isTileOn(i)) ++sum; } return sum; } template inline Index RootNode::getInactiveTileCount() const { Index sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isTileOff(i)) ++sum; } return sum; } template inline Index32 RootNode::leafCount() const { Index32 sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) sum += getChild(i).leafCount(); } return sum; } template inline Index32 RootNode::nonLeafCount() const { Index32 sum = 1; if (ChildT::LEVEL != 0) { for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) sum += getChild(i).nonLeafCount(); } } return sum; } template inline Index64 RootNode::onVoxelCount() const { Index64 sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) { sum += getChild(i).onVoxelCount(); } else if (isTileOn(i)) { sum += ChildT::NUM_VOXELS; } } return sum; } template inline Index64 RootNode::offVoxelCount() const { Index64 sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) { sum += getChild(i).offVoxelCount(); } else if (isTileOff(i) && !this->isBackgroundTile(i)) { sum += ChildT::NUM_VOXELS; } } return sum; } template inline Index64 RootNode::onLeafVoxelCount() const { Index64 sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) sum += getChild(i).onLeafVoxelCount(); } return sum; } template inline Index64 RootNode::offLeafVoxelCount() const { Index64 sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) sum += getChild(i).offLeafVoxelCount(); } return sum; } template inline Index64 RootNode::onTileCount() const { Index64 sum = 0; for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) { sum += getChild(i).onTileCount(); } else if (isTileOn(i)) { sum += 1; } } return sum; } //////////////////////////////////////// template inline bool RootNode::isValueOn(const Coord& xyz) const { MapCIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTileOff(iter)) return false; return isTileOn(iter) ? true : getChild(iter).isValueOn(xyz); } template inline bool RootNode::hasActiveTiles() const { for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i) ? getChild(i).hasActiveTiles() : getTile(i).active) return true; } return false; } template template inline bool RootNode::isValueOnAndCache(const Coord& xyz, AccessorT& acc) const { MapCIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTileOff(iter)) return false; if (isTileOn(iter)) return true; acc.insert(xyz, &getChild(iter)); return getChild(iter).isValueOnAndCache(xyz, acc); } template inline const typename ChildT::ValueType& RootNode::getValue(const Coord& xyz) const { MapCIter iter = this->findCoord(xyz); return iter == mTable.end() ? mBackground : (isTile(iter) ? getTile(iter).value : getChild(iter).getValue(xyz)); } template template inline const typename ChildT::ValueType& RootNode::getValueAndCache(const Coord& xyz, AccessorT& acc) const { MapCIter iter = this->findCoord(xyz); if (iter == mTable.end()) return mBackground; if (isChild(iter)) { acc.insert(xyz, &getChild(iter)); return getChild(iter).getValueAndCache(xyz, acc); } return getTile(iter).value; } template inline int RootNode::getValueDepth(const Coord& xyz) const { MapCIter iter = this->findCoord(xyz); return iter == mTable.end() ? -1 : (isTile(iter) ? 0 : int(LEVEL) - int(getChild(iter).getValueLevel(xyz))); } template template inline int RootNode::getValueDepthAndCache(const Coord& xyz, AccessorT& acc) const { MapCIter iter = this->findCoord(xyz); if (iter == mTable.end()) return -1; if (isTile(iter)) return 0; acc.insert(xyz, &getChild(iter)); return int(LEVEL) - int(getChild(iter).getValueLevelAndCache(xyz, acc)); } template inline void RootNode::setValueOff(const Coord& xyz) { MapIter iter = this->findCoord(xyz); if (iter != mTable.end() && !isTileOff(iter)) { if (isTileOn(iter)) { setChild(iter, *new ChildT(xyz, getTile(iter).value, /*active=*/true)); } getChild(iter).setValueOff(xyz); } } template inline void RootNode::setActiveState(const Coord& xyz, bool on) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { if (on) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else { // Nothing to do; (x, y, z) is background and therefore already inactive. } } else if (isChild(iter)) { child = &getChild(iter); } else if (on != getTile(iter).active) { child = new ChildT(xyz, getTile(iter).value, !on); setChild(iter, *child); } if (child) child->setActiveState(xyz, on); } template template inline void RootNode::setActiveStateAndCache(const Coord& xyz, bool on, AccessorT& acc) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { if (on) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else { // Nothing to do; (x, y, z) is background and therefore already inactive. } } else if (isChild(iter)) { child = &getChild(iter); } else if (on != getTile(iter).active) { child = new ChildT(xyz, getTile(iter).value, !on); setChild(iter, *child); } if (child) { acc.insert(xyz, child); child->setActiveStateAndCache(xyz, on, acc); } } template inline void RootNode::setValueOff(const Coord& xyz, const ValueType& value) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { if (!math::isExactlyEqual(mBackground, value)) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } } else if (isChild(iter)) { child = &getChild(iter); } else if (isTileOn(iter) || !math::isExactlyEqual(getTile(iter).value, value)) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } if (child) child->setValueOff(xyz, value); } template template inline void RootNode::setValueOffAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { if (!math::isExactlyEqual(mBackground, value)) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } } else if (isChild(iter)) { child = &getChild(iter); } else if (isTileOn(iter) || !math::isExactlyEqual(getTile(iter).value, value)) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } if (child) { acc.insert(xyz, child); child->setValueOffAndCache(xyz, value, acc); } } template inline void RootNode::setValueOn(const Coord& xyz, const ValueType& value) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else if (isTileOff(iter) || !math::isExactlyEqual(getTile(iter).value, value)) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } if (child) child->setValueOn(xyz, value); } template template inline void RootNode::setValueAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else if (isTileOff(iter) || !math::isExactlyEqual(getTile(iter).value, value)) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } if (child) { acc.insert(xyz, child); child->setValueAndCache(xyz, value, acc); } } template inline void RootNode::setValueOnly(const Coord& xyz, const ValueType& value) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else if (!math::isExactlyEqual(getTile(iter).value, value)) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } if (child) child->setValueOnly(xyz, value); } template template inline void RootNode::setValueOnlyAndCache(const Coord& xyz, const ValueType& value, AccessorT& acc) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else if (!math::isExactlyEqual(getTile(iter).value, value)) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } if (child) { acc.insert(xyz, child); child->setValueOnlyAndCache(xyz, value, acc); } } template template inline void RootNode::modifyValue(const Coord& xyz, const ModifyOp& op) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else { // Need to create a child if the tile is inactive, // in order to activate voxel (x, y, z). bool createChild = isTileOff(iter); if (!createChild) { // Need to create a child if applying the functor // to the tile value produces a different value. const ValueType& tileVal = getTile(iter).value; ValueType modifiedVal = tileVal; op(modifiedVal); createChild = !math::isExactlyEqual(tileVal, modifiedVal); } if (createChild) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } } if (child) child->modifyValue(xyz, op); } template template inline void RootNode::modifyValueAndCache(const Coord& xyz, const ModifyOp& op, AccessorT& acc) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else { // Need to create a child if the tile is inactive, // in order to activate voxel (x, y, z). bool createChild = isTileOff(iter); if (!createChild) { // Need to create a child if applying the functor // to the tile value produces a different value. const ValueType& tileVal = getTile(iter).value; ValueType modifiedVal = tileVal; op(modifiedVal); createChild = !math::isExactlyEqual(tileVal, modifiedVal); } if (createChild) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } } if (child) { acc.insert(xyz, child); child->modifyValueAndCache(xyz, op, acc); } } template template inline void RootNode::modifyValueAndActiveState(const Coord& xyz, const ModifyOp& op) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else { const Tile& tile = getTile(iter); bool modifiedState = tile.active; ValueType modifiedVal = tile.value; op(modifiedVal, modifiedState); // Need to create a child if applying the functor to the tile // produces a different value or active state. if (modifiedState != tile.active || !math::isExactlyEqual(modifiedVal, tile.value)) { child = new ChildT(xyz, tile.value, tile.active); setChild(iter, *child); } } if (child) child->modifyValueAndActiveState(xyz, op); } template template inline void RootNode::modifyValueAndActiveStateAndCache( const Coord& xyz, const ModifyOp& op, AccessorT& acc) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else { const Tile& tile = getTile(iter); bool modifiedState = tile.active; ValueType modifiedVal = tile.value; op(modifiedVal, modifiedState); // Need to create a child if applying the functor to the tile // produces a different value or active state. if (modifiedState != tile.active || !math::isExactlyEqual(modifiedVal, tile.value)) { child = new ChildT(xyz, tile.value, tile.active); setChild(iter, *child); } } if (child) { acc.insert(xyz, child); child->modifyValueAndActiveStateAndCache(xyz, op, acc); } } template inline bool RootNode::probeValue(const Coord& xyz, ValueType& value) const { MapCIter iter = this->findCoord(xyz); if (iter == mTable.end()) { value = mBackground; return false; } else if (isChild(iter)) { return getChild(iter).probeValue(xyz, value); } value = getTile(iter).value; return isTileOn(iter); } template template inline bool RootNode::probeValueAndCache(const Coord& xyz, ValueType& value, AccessorT& acc) const { MapCIter iter = this->findCoord(xyz); if (iter == mTable.end()) { value = mBackground; return false; } else if (isChild(iter)) { acc.insert(xyz, &getChild(iter)); return getChild(iter).probeValueAndCache(xyz, value, acc); } value = getTile(iter).value; return isTileOn(iter); } //////////////////////////////////////// template inline void RootNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) { if (bbox.empty()) return; Coord xyz, tileMax; for (int x = bbox.min().x(); x <= bbox.max().x(); x = tileMax.x() + 1) { xyz.setX(x); for (int y = bbox.min().y(); y <= bbox.max().y(); y = tileMax.y() + 1) { xyz.setY(y); for (int z = bbox.min().z(); z <= bbox.max().z(); z = tileMax.z() + 1) { xyz.setZ(z); // Get the bounds of the tile that contains voxel (x, y, z). Coord tileMin = coordToKey(xyz); tileMax = tileMin.offsetBy(ChildT::DIM - 1); if (xyz != tileMin || Coord::lessThan(bbox.max(), tileMax)) { // If the box defined by (xyz, bbox.max()) doesn't completely enclose // the tile to which xyz belongs, create a child node (or retrieve // the existing one). ChildT* child = NULL; MapIter iter = this->findKey(tileMin); if (iter == mTable.end()) { // No child or tile exists. Create a child and initialize it // with the background value. child = new ChildT(xyz, mBackground); mTable[tileMin] = NodeStruct(*child); } else if (isTile(iter)) { // Replace the tile with a newly-created child that is initialized // with the tile's value and active state. const Tile& tile = getTile(iter); child = new ChildT(xyz, tile.value, tile.active); mTable[tileMin] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } // Forward the fill request to the child. if (child) { child->fill(CoordBBox(xyz, Coord::minComponent(bbox.max(), tileMax)), value, active); } } else { // If the box given by (xyz, bbox.max()) completely encloses // the tile to which xyz belongs, create the tile (if it // doesn't already exist) and give it the fill value. MapIter iter = this->findOrAddCoord(tileMin); setTile(iter, Tile(value, active)); } } } } } template template inline void RootNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const { typedef typename DenseT::ValueType DenseValueType; const size_t xStride = dense.xStride(), yStride = dense.yStride(), zStride = dense.zStride(); const Coord& min = dense.bbox().min(); CoordBBox nodeBBox; for (Coord xyz = bbox.min(); xyz[0] <= bbox.max()[0]; xyz[0] = nodeBBox.max()[0] + 1) { for (xyz[1] = bbox.min()[1]; xyz[1] <= bbox.max()[1]; xyz[1] = nodeBBox.max()[1] + 1) { for (xyz[2] = bbox.min()[2]; xyz[2] <= bbox.max()[2]; xyz[2] = nodeBBox.max()[2] + 1) { // Get the coordinate bbox of the child node that contains voxel xyz. nodeBBox = CoordBBox::createCube(coordToKey(xyz), ChildT::DIM); // Get the coordinate bbox of the interection of inBBox and nodeBBox CoordBBox sub(xyz, Coord::minComponent(bbox.max(), nodeBBox.max())); MapCIter iter = this->findKey(nodeBBox.min()); if (iter != mTable.end() && isChild(iter)) {//is a child getChild(iter).copyToDense(sub, dense); } else {//is background or a tile value const ValueType value = iter==mTable.end() ? mBackground : getTile(iter).value; sub.translate(-min); DenseValueType* a0 = dense.data() + zStride*sub.min()[2]; for (Int32 x=sub.min()[0], ex=sub.max()[0]+1; x inline bool RootNode::writeTopology(std::ostream& os, bool toHalf) const { if (!toHalf) { os.write(reinterpret_cast(&mBackground), sizeof(ValueType)); } else { ValueType truncatedVal = io::truncateRealToHalf(mBackground); os.write(reinterpret_cast(&truncatedVal), sizeof(ValueType)); } io::setGridBackgroundValuePtr(os, &mBackground); const Index numTiles = this->getTileCount(), numChildren = this->getChildCount(); os.write(reinterpret_cast(&numTiles), sizeof(Index)); os.write(reinterpret_cast(&numChildren), sizeof(Index)); if (numTiles == 0 && numChildren == 0) return false; // Write tiles. for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) continue; os.write(reinterpret_cast(i->first.asPointer()), 3 * sizeof(Int32)); os.write(reinterpret_cast(&getTile(i).value), sizeof(ValueType)); os.write(reinterpret_cast(&getTile(i).active), sizeof(bool)); } // Write child nodes. for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isTile(i)) continue; os.write(reinterpret_cast(i->first.asPointer()), 3 * sizeof(Int32)); getChild(i).writeTopology(os, toHalf); } return true; // not empty } template inline bool RootNode::readTopology(std::istream& is, bool fromHalf) { // Delete the existing tree. this->clearTable(); if (io::getFormatVersion(is) < OPENVDB_FILE_VERSION_ROOTNODE_MAP) { // Read and convert an older-format RootNode. // For backward compatibility with older file formats, read both // outside and inside background values. is.read(reinterpret_cast(&mBackground), sizeof(ValueType)); ValueType inside; is.read(reinterpret_cast(&inside), sizeof(ValueType)); io::setGridBackgroundValuePtr(is, &mBackground); // Read the index range. Coord rangeMin, rangeMax; is.read(reinterpret_cast(rangeMin.asPointer()), 3 * sizeof(Int32)); is.read(reinterpret_cast(rangeMax.asPointer()), 3 * sizeof(Int32)); this->initTable(); Index tableSize = 0, log2Dim[4] = { 0, 0, 0, 0 }; Int32 offset[3]; for (int i = 0; i < 3; ++i) { offset[i] = rangeMin[i] >> ChildT::TOTAL; rangeMin[i] = offset[i] << ChildT::TOTAL; log2Dim[i] = 1 + util::FindHighestOn((rangeMax[i] >> ChildT::TOTAL) - offset[i]); tableSize += log2Dim[i]; rangeMax[i] = (((1 << log2Dim[i]) + offset[i]) << ChildT::TOTAL) - 1; } log2Dim[3] = log2Dim[1] + log2Dim[2]; tableSize = 1U << tableSize; // Read masks. util::RootNodeMask childMask(tableSize), valueMask(tableSize); childMask.load(is); valueMask.load(is); // Read child nodes/values. for (Index i = 0; i < tableSize; ++i) { // Compute origin = offset2coord(i). Index n = i; Coord origin; origin[0] = (n >> log2Dim[3]) + offset[0]; n &= (1U << log2Dim[3]) - 1; origin[1] = (n >> log2Dim[2]) + offset[1]; origin[2] = (n & ((1U << log2Dim[2]) - 1)) + offset[1]; origin <<= ChildT::TOTAL; if (childMask.isOn(i)) { // Read in and insert a child node. ChildT* child = new ChildT(origin, mBackground); child->readTopology(is); mTable[origin] = NodeStruct(*child); } else { // Read in a tile value and insert a tile, but only if the value // is either active or non-background. ValueType value; is.read(reinterpret_cast(&value), sizeof(ValueType)); if (valueMask.isOn(i) || (!math::isApproxEqual(value, mBackground))) { mTable[origin] = NodeStruct(Tile(value, valueMask.isOn(i))); } } } return true; } // Read a RootNode that was stored in the current format. is.read(reinterpret_cast(&mBackground), sizeof(ValueType)); io::setGridBackgroundValuePtr(is, &mBackground); Index numTiles = 0, numChildren = 0; is.read(reinterpret_cast(&numTiles), sizeof(Index)); is.read(reinterpret_cast(&numChildren), sizeof(Index)); if (numTiles == 0 && numChildren == 0) return false; Int32 vec[3]; ValueType value; bool active; // Read tiles. for (Index n = 0; n < numTiles; ++n) { is.read(reinterpret_cast(vec), 3 * sizeof(Int32)); is.read(reinterpret_cast(&value), sizeof(ValueType)); is.read(reinterpret_cast(&active), sizeof(bool)); mTable[Coord(vec)] = NodeStruct(Tile(value, active)); } // Read child nodes. for (Index n = 0; n < numChildren; ++n) { is.read(reinterpret_cast(vec), 3 * sizeof(Int32)); Coord origin(vec); ChildT* child = new ChildT(origin, mBackground); child->readTopology(is, fromHalf); mTable[Coord(vec)] = NodeStruct(*child); } return true; // not empty } template inline void RootNode::writeBuffers(std::ostream& os, bool toHalf) const { for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) getChild(i).writeBuffers(os, toHalf); } } template inline void RootNode::readBuffers(std::istream& is, bool fromHalf) { for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) getChild(i).readBuffers(is, fromHalf); } } //////////////////////////////////////// template template inline void RootNode::pruneOp(PruneOp& op) { for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isTile(i)|| !op(this->getChild(i))) continue; this->setTile(i, Tile(op.value, op.state)); } this->eraseBackgroundTiles(); } template inline void RootNode::prune(const ValueType& tolerance) { TolerancePrune op(tolerance); this->pruneOp(op); } template inline void RootNode::pruneInactive(const ValueType& bg) { InactivePrune op(bg); this->pruneOp(op); } template inline void RootNode::pruneInactive() { this->pruneInactive(mBackground); } template inline void RootNode::pruneTiles(const ValueType& tolerance) { TolerancePrune op(tolerance); this->pruneOp(op); } //////////////////////////////////////// template template inline NodeT* RootNode::stealNode(const Coord& xyz, const ValueType& value, bool state) { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return NULL; return (boost::is_same::value) ? reinterpret_cast(&stealChild(iter, Tile(value, state))) : getChild(iter).template stealNode(xyz, value, state); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template inline void RootNode::addLeaf(LeafNodeType* leaf) { if (leaf == NULL) return; ChildT* child = NULL; const Coord& xyz = leaf->origin(); MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { if (ChildT::LEVEL>0) { child = new ChildT(xyz, mBackground, false); } else { child = reinterpret_cast(leaf); } mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { if (ChildT::LEVEL>0) { child = &getChild(iter); } else { child = reinterpret_cast(leaf); setChild(iter, *child);//this also deletes the existing child node } } else {//tile if (ChildT::LEVEL>0) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); } else { child = reinterpret_cast(leaf); } setChild(iter, *child); } child->addLeaf(leaf); } template template inline void RootNode::addLeafAndCache(LeafNodeType* leaf, AccessorT& acc) { if (leaf == NULL) return; ChildT* child = NULL; const Coord& xyz = leaf->origin(); MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { if (ChildT::LEVEL>0) { child = new ChildT(xyz, mBackground, false); } else { child = reinterpret_cast(leaf); } mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { if (ChildT::LEVEL>0) { child = &getChild(iter); } else { child = reinterpret_cast(leaf); setChild(iter, *child);//this also deletes the existing child node } } else {//tile if (ChildT::LEVEL>0) { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); } else { child = reinterpret_cast(leaf); } setChild(iter, *child); } acc.insert(xyz, child); child->addLeafAndCache(leaf, acc); } template inline void RootNode::addTile(Index level, const Coord& xyz, const ValueType& value, bool state) { if (LEVEL >= level) { MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) {//background if (LEVEL > level) { ChildT* child = new ChildT(xyz, mBackground, false); mTable[this->coordToKey(xyz)] = NodeStruct(*child); child->addTile(level, xyz, value, state); } else { mTable[this->coordToKey(xyz)] = NodeStruct(Tile(value, state)); } } else if (isChild(iter)) {//child if (LEVEL > level) { getChild(iter).addTile(level, xyz, value, state); } else { setTile(iter, Tile(value, state));//this also deletes the existing child node } } else {//tile if (LEVEL > level) { ChildT* child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); child->addTile(level, xyz, value, state); } else { setTile(iter, Tile(value, state)); } } } } template template inline void RootNode::addTileAndCache(Index level, const Coord& xyz, const ValueType& value, bool state, AccessorT& acc) { if (LEVEL >= level) { MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) {//background if (LEVEL > level) { ChildT* child = new ChildT(xyz, mBackground, false); acc.insert(xyz, child); mTable[this->coordToKey(xyz)] = NodeStruct(*child); child->addTileAndCache(level, xyz, value, state, acc); } else { mTable[this->coordToKey(xyz)] = NodeStruct(Tile(value, state)); } } else if (isChild(iter)) {//child if (LEVEL > level) { ChildT* child = &getChild(iter); acc.insert(xyz, child); child->addTileAndCache(level, xyz, value, state, acc); } else { setTile(iter, Tile(value, state));//this also deletes the existing child node } } else {//tile if (LEVEL > level) { ChildT* child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); acc.insert(xyz, child); setChild(iter, *child); child->addTileAndCache(level, xyz, value, state, acc); } else { setTile(iter, Tile(value, state)); } } } } //////////////////////////////////////// template inline typename ChildT::LeafNodeType* RootNode::touchLeaf(const Coord& xyz) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground, false); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } return child->touchLeaf(xyz); } template template inline typename ChildT::LeafNodeType* RootNode::touchLeafAndCache(const Coord& xyz, AccessorT& acc) { ChildT* child = NULL; MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) { child = new ChildT(xyz, mBackground, false); mTable[this->coordToKey(xyz)] = NodeStruct(*child); } else if (isChild(iter)) { child = &getChild(iter); } else { child = new ChildT(xyz, getTile(iter).value, isTileOn(iter)); setChild(iter, *child); } acc.insert(xyz, child); return child->touchLeafAndCache(xyz, acc); } //////////////////////////////////////// template template inline NodeT* RootNode::probeNode(const Coord& xyz) { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return NULL; ChildT* child = &getChild(iter); return (boost::is_same::value) ? reinterpret_cast(child) : child->template probeNode(xyz); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline const NodeT* RootNode::probeConstNode(const Coord& xyz) const { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapCIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return NULL; const ChildT* child = &getChild(iter); return (boost::is_same::value) ? reinterpret_cast(child) : child->template probeConstNode(xyz); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template inline typename ChildT::LeafNodeType* RootNode::probeLeaf(const Coord& xyz) { return this->template probeNode(xyz); } template inline const typename ChildT::LeafNodeType* RootNode::probeConstLeaf(const Coord& xyz) const { return this->template probeConstNode(xyz); } template template inline typename ChildT::LeafNodeType* RootNode::probeLeafAndCache(const Coord& xyz, AccessorT& acc) { return this->template probeNodeAndCache(xyz, acc); } template template inline const typename ChildT::LeafNodeType* RootNode::probeConstLeafAndCache(const Coord& xyz, AccessorT& acc) const { return this->template probeConstNodeAndCache(xyz, acc); } template template inline const typename ChildT::LeafNodeType* RootNode::probeLeafAndCache(const Coord& xyz, AccessorT& acc) const { return this->probeConstLeafAndCache(xyz, acc); } template template inline NodeT* RootNode::probeNodeAndCache(const Coord& xyz, AccessorT& acc) { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return NULL; ChildT* child = &getChild(iter); acc.insert(xyz, child); return (boost::is_same::value) ? reinterpret_cast(child) : child->template probeNodeAndCache(xyz, acc); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline const NodeT* RootNode::probeConstNodeAndCache(const Coord& xyz, AccessorT& acc) const { if ((NodeT::LEVEL == ChildT::LEVEL && !(boost::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return NULL; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapCIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return NULL; const ChildT* child = &getChild(iter); acc.insert(xyz, child); return (boost::is_same::value) ? reinterpret_cast(child) : child->template probeConstNodeAndCache(xyz, acc); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template inline void RootNode::signedFloodFill() { this->signedFloodFill(mBackground, math::negative(mBackground)); } template inline void RootNode::signedFloodFill(const ValueType& outside, const ValueType& inside) { const ValueType zero = zeroVal(); mBackground = outside; // First, flood fill all child nodes and put child-keys into a sorted set CoordSet nodeKeys; for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isTile(i)) continue; getChild(i).signedFloodFill(outside, inside); nodeKeys.insert(i->first);//only add inactive tiles! } // We employ a simple z-scanline algorithm that inserts inactive // tiles with the inside value if they are sandwiched // between inside child nodes only! const Tile insideTile(inside, /*on=*/false); CoordSetCIter b = nodeKeys.begin(), e = nodeKeys.end(); if ( b == e ) return; for (CoordSetCIter a = b++; b != e; ++a, ++b) { Coord d = *b - *a; // delta of neighboring keys if (d[0]!=0 || d[1]!=0 || d[2]==Int32(ChildT::DIM)) continue;//not z-scanline or neighbors MapIter i = mTable.find(*a), j = mTable.find(*b); const ValueType fill[] = { getChild(i).getLastValue(), getChild(j).getFirstValue() }; if (!(fill[0] < zero) || !(fill[1] < zero)) continue; // scanline isn't inside for (Coord c = *a + Coord(0u,0u,ChildT::DIM); c[2] != (*b)[2]; c[2] += ChildT::DIM) { mTable[c] = insideTile; } } } //////////////////////////////////////// template inline void RootNode::voxelizeActiveTiles() { for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isTileOff(i)) continue; ChildT* child = i->second.child; if (child==NULL) { child = new ChildT(i->first, this->getTile(i).value, true); i->second.child = child; } child->voxelizeActiveTiles(); } } //////////////////////////////////////// template template inline void RootNode::merge(RootNode& other) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN switch (Policy) { default: case MERGE_ACTIVE_STATES: for (MapIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { MapIter j = mTable.find(i->first); if (other.isChild(i)) { if (j == mTable.end()) { // insert other node's child ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); child.resetBackground(other.mBackground, mBackground); mTable[i->first] = NodeStruct(child); } else if (isTile(j)) { if (isTileOff(j)) { // replace inactive tile with other node's child ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); child.resetBackground(other.mBackground, mBackground); setChild(j, child); } } else { // merge both child nodes getChild(j).template merge(getChild(i), other.mBackground, mBackground); } } else if (other.isTileOn(i)) { if (j == mTable.end()) { // insert other node's active tile mTable[i->first] = i->second; } else if (!isTileOn(j)) { // Replace anything except an active tile with the other node's active tile. setTile(j, Tile(other.getTile(i).value, true)); } } } break; case MERGE_NODES: for (MapIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { MapIter j = mTable.find(i->first); if (other.isChild(i)) { if (j == mTable.end()) { // insert other node's child ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); child.resetBackground(other.mBackground, mBackground); mTable[i->first] = NodeStruct(child); } else if (isTile(j)) { // replace tile with other node's child ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); child.resetBackground(other.mBackground, mBackground); setChild(j, child); } else { // merge both child nodes getChild(j).template merge( getChild(i), other.mBackground, mBackground); } } } break; case MERGE_ACTIVE_STATES_AND_NODES: for (MapIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { MapIter j = mTable.find(i->first); if (other.isChild(i)) { if (j == mTable.end()) { // Steal and insert the other node's child. ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); child.resetBackground(other.mBackground, mBackground); mTable[i->first] = NodeStruct(child); } else if (isTile(j)) { // Replace this node's tile with the other node's child. ChildNodeType& child = stealChild(i, Tile(other.mBackground, /*on=*/false)); child.resetBackground(other.mBackground, mBackground); const Tile tile = getTile(j); setChild(j, child); if (tile.active) { // Merge the other node's child with this node's active tile. child.template merge( tile.value, tile.active); } } else /*if (isChild(j))*/ { // Merge the other node's child into this node's child. getChild(j).template merge(getChild(i), other.mBackground, mBackground); } } else if (other.isTileOn(i)) { if (j == mTable.end()) { // Insert a copy of the other node's active tile. mTable[i->first] = i->second; } else if (isTileOff(j)) { // Replace this node's inactive tile with a copy of the other's active tile. setTile(j, Tile(other.getTile(i).value, true)); } else if (isChild(j)) { // Merge the other node's active tile into this node's child. const Tile& tile = getTile(i); getChild(j).template merge( tile.value, tile.active); } } // else if (other.isTileOff(i)) {} // ignore the other node's inactive tiles } break; } // Empty the other tree so as not to leave it in a partially cannibalized state. other.clear(); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template template inline void RootNode::topologyUnion(const RootNode& other) { typedef RootNode OtherRootT; typedef typename OtherRootT::MapCIter OtherCIterT; enforceSameConfiguration(other); for (OtherCIterT i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { MapIter j = mTable.find(i->first); if (other.isChild(i)) { if (j == mTable.end()) { // create child branch with identical topology mTable[i->first] = NodeStruct( *(new ChildT(other.getChild(i), mBackground, TopologyCopy()))); } else if (this->isChild(j)) { // union with child branch this->getChild(j).topologyUnion(other.getChild(i)); } else {// this is a tile so replace it with a child branch with identical topology ChildT* child = new ChildT( other.getChild(i), this->getTile(j).value, TopologyCopy()); if (this->isTileOn(j)) child->setValuesOn();//this is an active tile this->setChild(j, *child); } } else if (other.isTileOn(i)) { // other is an active tile if (j == mTable.end()) { // insert an active tile mTable[i->first] = NodeStruct(Tile(mBackground, true)); } else if (this->isChild(j)) { this->getChild(j).setValuesOn(); } else if (this->isTileOff(j)) { this->setTile(j, Tile(this->getTile(j).value, true)); } } } } template template inline void RootNode::topologyIntersection(const RootNode& other) { typedef RootNode OtherRootT; typedef typename OtherRootT::MapCIter OtherCIterT; enforceSameConfiguration(other); std::set tmp;//keys to erase for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { OtherCIterT j = other.mTable.find(i->first); if (this->isChild(i)) { if (j == other.mTable.end() || other.isTileOff(j)) { tmp.insert(i->first);//delete child branch } else if (other.isChild(j)) { // intersect with child branch this->getChild(i).topologyIntersection(other.getChild(j), mBackground); } } else if (this->isTileOn(i)) { if (j == other.mTable.end() || other.isTileOff(j)) { this->setTile(i, Tile(this->getTile(i).value, false));//turn inactive } else if (other.isChild(j)) { //replace with a child branch with identical topology ChildT* child = new ChildT(other.getChild(j), this->getTile(i).value, TopologyCopy()); this->setChild(i, *child); } } } for (std::set::iterator i = tmp.begin(), e = tmp.end(); i != e; ++i) mTable.erase(*i); } template template inline void RootNode::topologyDifference(const RootNode& other) { typedef RootNode OtherRootT; typedef typename OtherRootT::MapCIter OtherCIterT; enforceSameConfiguration(other); for (OtherCIterT i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { MapIter j = mTable.find(i->first); if (other.isChild(i)) { if (j == mTable.end() || this->isTileOff(j)) { //do nothing } else if (this->isChild(j)) { // difference with child branch this->getChild(j).topologyDifference(other.getChild(i), mBackground); } else if (this->isTileOn(j)) { // this is an active tile so create a child node and descent ChildT* child = new ChildT(j->first, this->getTile(j).value, true); child->topologyDifference(other.getChild(i), mBackground); this->setChild(j, *child); } } else if (other.isTileOn(i)) { // other is an active tile if (j == mTable.end() || this->isTileOff(j)) { // do nothing } else if (this->isChild(j)) { mTable.erase(j->first);//delete child } else if (this->isTileOn(j)) { this->setTile(j, Tile(this->getTile(j).value, false)); } } } } //////////////////////////////////////// template template inline void RootNode::combine(RootNode& other, CombineOp& op, bool prune) { CombineArgs args; CoordSet keys; this->insertKeys(keys); other.insertKeys(keys); for (CoordSetCIter i = keys.begin(), e = keys.end(); i != e; ++i) { MapIter iter = findOrAddCoord(*i), otherIter = other.findOrAddCoord(*i); if (isTile(iter) && isTile(otherIter)) { // Both this node and the other node have constant values (tiles). // Combine the two values and store the result as this node's new tile value. op(args.setARef(getTile(iter).value) .setAIsActive(isTileOn(iter)) .setBRef(getTile(otherIter).value) .setBIsActive(isTileOn(otherIter))); setTile(iter, Tile(args.result(), args.resultIsActive())); } else if (isChild(iter) && isTile(otherIter)) { // Combine this node's child with the other node's constant value. ChildT& child = getChild(iter); child.combine(getTile(otherIter).value, isTileOn(otherIter), op); } else if (isTile(iter) && isChild(otherIter)) { // Combine this node's constant value with the other node's child, // but use a new functor in which the A and B values are swapped, // since the constant value is the A value, not the B value. SwappedCombineOp swappedOp(op); ChildT& child = getChild(otherIter); child.combine(getTile(iter).value, isTileOn(iter), swappedOp); // Steal the other node's child. setChild(iter, stealChild(otherIter, Tile())); } else /*if (isChild(iter) && isChild(otherIter))*/ { // Combine this node's child with the other node's child. ChildT &child = getChild(iter), &otherChild = getChild(otherIter); child.combine(otherChild, op); } if (prune && isChild(iter)) getChild(iter).prune(); } // Combine background values. op(args.setARef(mBackground).setBRef(other.mBackground)); mBackground = args.result(); // Empty the other tree so as not to leave it in a partially cannibalized state. other.clear(); } //////////////////////////////////////// template template inline void RootNode::combine2(const RootNode& other0, const RootNode& other1, CombineOp& op, bool prune) { CombineArgs args; CoordSet keys; other0.insertKeys(keys); other1.insertKeys(keys); const NodeStruct bg0(Tile(other0.mBackground, /*active=*/false)), bg1(Tile(other1.mBackground, /*active=*/false)); for (CoordSetCIter i = keys.begin(), e = keys.end(); i != e; ++i) { MapIter thisIter = this->findOrAddCoord(*i); MapCIter iter0 = other0.findKey(*i), iter1 = other1.findKey(*i); const NodeStruct &ns0 = (iter0 != other0.mTable.end()) ? iter0->second : bg0, &ns1 = (iter1 != other1.mTable.end()) ? iter1->second : bg1; if (ns0.isTile() && ns1.isTile()) { // Both input nodes have constant values (tiles). // Combine the two values and add a new tile to this node with the result. op(args.setARef(ns0.tile.value) .setAIsActive(ns0.isTileOn()) .setBRef(ns1.tile.value) .setBIsActive(ns1.isTileOn())); setTile(thisIter, Tile(args.result(), args.resultIsActive())); } else { ChildT& otherChild = ns0.isChild() ? *ns0.child : *ns1.child; if (!isChild(thisIter)) { // Add a new child with the same coordinates, etc. as the other node's child. setChild(thisIter, *(new ChildT(otherChild.origin(), getTile(thisIter).value))); } ChildT& child = getChild(thisIter); if (ns0.isTile()) { // Combine node1's child with node0's constant value // and write the result into this node's child. child.combine2(ns0.tile.value, *ns1.child, ns0.isTileOn(), op); } else if (ns1.isTile()) { // Combine node0's child with node1's constant value // and write the result into this node's child. child.combine2(*ns0.child, ns1.tile.value, ns1.isTileOn(), op); } else { // Combine node0's child with node1's child // and write the result into this node's child. child.combine2(*ns0.child, *ns1.child, op); } } if (prune && isChild(thisIter)) getChild(thisIter).prune(); } // Combine background values. op(args.setARef(other0.mBackground).setBRef(other1.mBackground)); mBackground = args.result(); } //////////////////////////////////////// template template inline void RootNode::visitActiveBBox(BBoxOp& op) const { const bool descent = op.template descent(); for (MapCIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isTileOff(i)) continue; if (this->isChild(i) && descent) { this->getChild(i).visitActiveBBox(op); } else { #ifdef _MSC_VER op.operator()(CoordBBox::createCube(i->first, ChildT::DIM)); #else op.template operator()(CoordBBox::createCube(i->first, ChildT::DIM)); #endif } } } template template inline void RootNode::visit(VisitorOp& op) { doVisit(*this, op); } template template inline void RootNode::visit(VisitorOp& op) const { doVisit(*this, op); } template template inline void RootNode::doVisit(RootNodeT& self, VisitorOp& op) { typename RootNodeT::ValueType val; for (ChildAllIterT iter = self.beginChildAll(); iter; ++iter) { if (op(iter)) continue; if (typename ChildAllIterT::ChildNodeType* child = iter.probeChild(val)) { child->visit(op); } } } //////////////////////////////////////// template template inline void RootNode::visit2(OtherRootNodeType& other, VisitorOp& op) { doVisit2(*this, other, op); } template template inline void RootNode::visit2(OtherRootNodeType& other, VisitorOp& op) const { doVisit2(*this, other, op); } template template< typename RootNodeT, typename OtherRootNodeT, typename VisitorOp, typename ChildAllIterT, typename OtherChildAllIterT> inline void RootNode::doVisit2(RootNodeT& self, OtherRootNodeT& other, VisitorOp& op) { /// @todo Allow the two nodes to have different ValueTypes, but not /// different fan-out factors or different index space bounds. enforceSameConfiguration(other); typename RootNodeT::ValueType val; typename OtherRootNodeT::ValueType otherVal; // The two nodes are required to have corresponding table entries, // but since that might require background tiles to be added to one or both, // and the nodes might be const, we operate on shallow copies of the nodes instead. RootNodeT copyOfSelf(self.mBackground); copyOfSelf.mTable = self.mTable; OtherRootNodeT copyOfOther(other.mBackground); copyOfOther.mTable = other.mTable; // Add background tiles to both nodes as needed. CoordSet keys; self.insertKeys(keys); other.insertKeys(keys); for (CoordSetCIter i = keys.begin(), e = keys.end(); i != e; ++i) { copyOfSelf.findOrAddCoord(*i); copyOfOther.findOrAddCoord(*i); } ChildAllIterT iter = copyOfSelf.beginChildAll(); OtherChildAllIterT otherIter = copyOfOther.beginChildAll(); for ( ; iter && otherIter; ++iter, ++otherIter) { const size_t skipBranch = static_cast(op(iter, otherIter)); typename ChildAllIterT::ChildNodeType* child = (skipBranch & 1U) ? NULL : iter.probeChild(val); typename OtherChildAllIterT::ChildNodeType* otherChild = (skipBranch & 2U) ? NULL : otherIter.probeChild(otherVal); if (child != NULL && otherChild != NULL) { child->visit2Node(*otherChild, op); } else if (child != NULL) { child->visit2(otherIter, op); } else if (otherChild != NULL) { otherChild->visit2(iter, op, /*otherIsLHS=*/true); } } // Remove any background tiles that were added above, // as well as any that were created by the visitors. copyOfSelf.eraseBackgroundTiles(); copyOfOther.eraseBackgroundTiles(); // If either input node is non-const, replace its table with // the (possibly modified) copy. self.resetTable(copyOfSelf.mTable); other.resetTable(copyOfOther.mTable); } } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_ROOTNODE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/tree/Iterator.h0000644000000000000000000003164212252453157014126 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file Iterator.h /// /// @author Peter Cucka and Ken Museth #ifndef OPENVDB_TREE_ITERATOR_HAS_BEEN_INCLUDED #define OPENVDB_TREE_ITERATOR_HAS_BEEN_INCLUDED #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// @brief Base class for iterators over internal and leaf nodes /// /// This class is typically not instantiated directly, since it doesn't provide methods /// to dereference the iterator. Those methods (@vdblink::tree::SparseIteratorBase::operator*() /// operator*()@endlink, @vdblink::tree::SparseIteratorBase::setValue() setValue()@endlink, etc.) /// are implemented in the @vdblink::tree::SparseIteratorBase sparse@endlink and /// @vdblink::tree::DenseIteratorBase dense@endlink iterator subclasses. template class IteratorBase { public: IteratorBase(): mParentNode(NULL) {} IteratorBase(const MaskIterT& iter, NodeT* parent): mParentNode(parent), mMaskIter(iter) {} void operator=(const IteratorBase& other) { mParentNode = other.mParentNode; mMaskIter = other.mMaskIter; } bool operator==(const IteratorBase& other) const { return (mParentNode == other.mParentNode) && (mMaskIter == other.mMaskIter); } bool operator!=(const IteratorBase& other) const { return !(*this == other); } /// Return a pointer to the node (if any) over which this iterator is iterating. NodeT* getParentNode() const { return mParentNode; } /// @brief Return a reference to the node over which this iterator is iterating. /// @throw ValueError if there is no parent node. NodeT& parent() const { if (!mParentNode) OPENVDB_THROW(ValueError, "iterator references a null node"); return *mParentNode; } /// Return this iterator's position as an index into the parent node's table. Index offset() const { return mMaskIter.offset(); } /// Identical to offset Index pos() const { return mMaskIter.offset(); } /// Return @c true if this iterator is not yet exhausted. bool test() const { return mMaskIter.test(); } /// Return @c true if this iterator is not yet exhausted. operator bool() const { return this->test(); } /// Advance to the next item in the parent node's table. bool next() { return mMaskIter.next(); } /// Advance to the next item in the parent node's table. void increment() { mMaskIter.increment(); } /// Advance to the next item in the parent node's table. IteratorBase& operator++() { this->increment(); return *this; } /// Advance @a n items in the parent node's table. void increment(Index n) { mMaskIter.increment(n); } /// @brief Return @c true if this iterator is pointing to an active value. /// Return @c false if it is pointing to either an inactive value or a child node. bool isValueOn() const { return parent().isValueMaskOn(this->pos()); } /// @brief If this iterator is pointing to a value, set the value's active state. /// Otherwise, do nothing. void setValueOn(bool on = true) const { parent().setValueMask(this->pos(), on); } /// @brief If this iterator is pointing to a value, mark the value as inactive. /// @details If this iterator is pointing to a child node, then the current item /// in the parent node's table is required to be inactive. In that case, /// this method has no effect. void setValueOff() const { parent().mValueMask.setOff(this->pos()); } /// Return the coordinates of the item to which this iterator is pointing. Coord getCoord() const { return parent().offsetToGlobalCoord(this->pos()); } /// Return in @a xyz the coordinates of the item to which this iterator is pointing. void getCoord(Coord& xyz) const { xyz = this->getCoord(); } private: /// @note This parent node pointer is mutable, because setValueOn() and /// setValueOff(), though const, need to call non-const methods on the parent. /// There is a distinction between a const iterator (e.g., const ValueOnIter), /// which is an iterator that can't be incremented, and an iterator over /// a const node (e.g., ValueOnCIter), which might be const or non-const itself /// but can't call non-const methods like setValue() on the node. mutable NodeT* mParentNode; MaskIterT mMaskIter; }; // class IteratorBase //////////////////////////////////////// /// @brief Base class for sparse iterators over internal and leaf nodes template< typename MaskIterT, // mask iterator type (OnIterator, OffIterator, etc.) typename IterT, // SparseIteratorBase subclass (the "Curiously Recurring Template Pattern") typename NodeT, // type of node over which to iterate typename ItemT> // type of value to which this iterator points struct SparseIteratorBase: public IteratorBase { typedef NodeT NodeType; typedef ItemT ValueType; typedef typename boost::remove_const::type NonConstNodeType; typedef typename boost::remove_const::type NonConstValueType; static const bool IsSparseIterator = true, IsDenseIterator = false; SparseIteratorBase() {} SparseIteratorBase(const MaskIterT& iter, NodeT* parent): IteratorBase(iter, parent) {} /// @brief Return the item at the given index in the parent node's table. /// @note All subclasses must implement this accessor. ItemT& getItem(Index) const; /// @brief Set the value of the item at the given index in the parent node's table. /// @note All non-const iterator subclasses must implement this accessor. void setItem(Index, const ItemT&) const; /// Return a reference to the item to which this iterator is pointing. ItemT& operator*() const { return this->getValue(); } /// Return a pointer to the item to which this iterator is pointing. ItemT* operator->() const { return &(this->operator*()); } /// Return the item to which this iterator is pointing. ItemT& getValue() const { return static_cast(this)->getItem(this->pos()); // static polymorphism } /// @brief Set the value of the item to which this iterator is pointing. /// (Not valid for const iterators.) void setValue(const ItemT& value) const { BOOST_STATIC_ASSERT(!boost::is_const::value); static_cast(this)->setItem(this->pos(), value); // static polymorphism } /// @brief Apply a functor to the item to which this iterator is pointing. /// (Not valid for const iterators.) /// @param op a functor of the form void op(ValueType&) const that modifies /// its argument in place /// @see Tree::modifyValue() template void modifyValue(const ModifyOp& op) const { BOOST_STATIC_ASSERT(!boost::is_const::value); static_cast(this)->modifyItem(this->pos(), op); // static polymorphism } }; // class SparseIteratorBase //////////////////////////////////////// /// @brief Base class for dense iterators over internal and leaf nodes /// @note Dense iterators have no @c %operator*() or @c %operator->(), /// because their return type would have to vary depending on whether /// the iterator is pointing to a value or a child node. template< typename MaskIterT, // mask iterator type (typically a DenseIterator) typename IterT, // DenseIteratorBase subclass (the "Curiously Recurring Template Pattern") typename NodeT, // type of node over which to iterate typename SetItemT, // type of set value (ChildNodeType, for non-leaf nodes) typename UnsetItemT> // type of unset value (ValueType, usually) struct DenseIteratorBase: public IteratorBase { typedef NodeT NodeType; typedef UnsetItemT ValueType; typedef SetItemT ChildNodeType; typedef typename boost::remove_const::type NonConstNodeType; typedef typename boost::remove_const::type NonConstValueType; typedef typename boost::remove_const::type NonConstChildNodeType; static const bool IsSparseIterator = false, IsDenseIterator = true; DenseIteratorBase() {} DenseIteratorBase(const MaskIterT& iter, NodeT* parent): IteratorBase(iter, parent) {} /// @brief Return @c true if the item at the given index in the parent node's table /// is a set value and return either the set value in @a child or the unset value /// in @a value. /// @note All subclasses must implement this accessor. bool getItem(Index, SetItemT*& child, NonConstValueType& value) const; /// @brief Set the value of the item at the given index in the parent node's table. /// @note All non-const iterator subclasses must implement this accessor. void setItem(Index, SetItemT*) const; /// @brief "Unset" the value of the item at the given index in the parent node's table. /// @note All non-const iterator subclasses must implement this accessor. void unsetItem(Index, const UnsetItemT&) const; /// Return @c true if this iterator is pointing to a child node. bool isChildNode() const { return this->parent().isChildMaskOn(this->pos()); } /// @brief If this iterator is pointing to a child node, return a pointer to the node. /// Otherwise, return NULL and, in @a value, the value to which this iterator is pointing. SetItemT* probeChild(NonConstValueType& value) const { SetItemT* child = NULL; static_cast(this)->getItem(this->pos(), child, value); // static polymorphism return child; } /// @brief If this iterator is pointing to a child node, return @c true and return /// a pointer to the child node in @a child. Otherwise, return @c false and return /// the value to which this iterator is pointing in @a value. bool probeChild(SetItemT*& child, NonConstValueType& value) const { child = probeChild(value); return (child != NULL); } /// @brief Return @c true if this iterator is pointing to a value and return /// the value in @a value. Otherwise, return @c false. bool probeValue(NonConstValueType& value) const { SetItemT* child = NULL; const bool isChild = static_cast(this)-> // static polymorphism getItem(this->pos(), child, value); return !isChild; } /// @brief Replace with the given child node the item in the parent node's table /// to which this iterator is pointing. void setChild(SetItemT* child) const { static_cast(this)->setItem(this->pos(), child); // static polymorphism } /// @brief Replace with the given value the item in the parent node's table /// to which this iterator is pointing. void setValue(const UnsetItemT& value) const { static_cast(this)->unsetItem(this->pos(), value); // static polymorphism } }; // struct DenseIteratorBase } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_ITERATOR_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Platform.cc0000644000000000000000000000365512252453157013323 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // For Windows, we need these includes to ensure all OPENVDB_API // functions/classes are compiled into the shared library. #include "openvdb.h" #include "Exceptions.h" // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/0000755000000000000000000000000012252453157012540 5ustar rootrootopenvdb/python/test/0000755000000000000000000000000012252453157013517 5ustar rootrootopenvdb/python/test/TestOpenVDB.py0000644000000000000000000006631712252453157016203 0ustar rootroot#!/usr/local/bin/python # Copyright (c) 2012-2013 DreamWorks Animation LLC # # All rights reserved. This software is distributed under the # Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) # # Redistributions of source code must retain the above copyright # and license notice and the following restrictions and disclaimer. # # * Neither the name of DreamWorks Animation nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE # LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. """ Unit tests for the OpenVDB Python module These are intended primarily to test the Python-to-C++ and C++-to-Python bindings, not the OpenVDB library itself. """ import os, os.path import sys import unittest try: import pyopenvdb as openvdb except ImportError: import studioenv from studio.ani import Ani from studio import logging from studio import openvdb def valueFactory(zeroValue, elemValue): """ Return elemValue converted to a value of the same type as zeroValue. If zeroValue is a sequence, return a sequence of the same type and length, with each element set to elemValue. """ val = zeroValue typ = type(val) try: # If the type is a sequence type, return a sequence of the appropriate length. size = len(val) val = typ([elemValue]) * size except TypeError: # Return a scalar value of the appropriate type. val = typ(elemValue) return val class TestOpenVDB(unittest.TestCase): def run(self, result=None, *args, **kwargs): super(TestOpenVDB, self).run(result, *args, **kwargs) def setUp(self): # Make output files and directories world-writable. self.umask = os.umask(0) def tearDown(self): os.umask(self.umask) def testModule(self): # At a minimum, BoolGrid, FloatGrid and Vec3SGrid should exist. self.assert_(openvdb.BoolGrid in openvdb.GridTypes) self.assert_(openvdb.FloatGrid in openvdb.GridTypes) self.assert_(openvdb.Vec3SGrid in openvdb.GridTypes) # Verify that it is possible to construct a grid of each supported type. for cls in openvdb.GridTypes: grid = cls() acc = grid.getAccessor() acc.setValueOn((-1, -2, 3)) self.assertEqual(grid.activeVoxelCount(), 1) def testTransform(self): xform1 = openvdb.createLinearTransform( [[.5, 0, 0, 0], [0, 1, 0, 0], [0, 0, 2, 0], [1, 2, 3, 1]]) self.assert_(xform1.typeName != '') self.assertEqual(xform1.indexToWorld((1, 1, 1)), (1.5, 3, 5)) xform2 = xform1 self.assertEqual(xform2, xform1) xform2 = xform1.deepCopy() self.assertEqual(xform2, xform1) xform2 = openvdb.createFrustumTransform(taper=0.5, depth=100, xyzMin=(0, 0, 0), xyzMax=(100, 100, 100), voxelSize=0.25) self.assertNotEqual(xform2, xform1) worldp = xform2.indexToWorld((10, 10, 10)) worldp = map(lambda x: int(round(x * 1000000)), worldp) self.assertEqual(worldp, [-110000, -110000, 2500000]) grid = openvdb.FloatGrid() self.assertEqual(grid.transform, openvdb.createLinearTransform()) grid.transform = openvdb.createLinearTransform(2.0) self.assertEqual(grid.transform, openvdb.createLinearTransform(2.0)) def testGridCopy(self): grid = openvdb.FloatGrid() self.assert_(grid.sharesWith(grid)) self.assertFalse(grid.sharesWith([])) # wrong type; Grid expected copyOfGrid = grid.copy() self.assert_(copyOfGrid.sharesWith(grid)) deepCopyOfGrid = grid.deepCopy() self.assertFalse(deepCopyOfGrid.sharesWith(grid)) self.assertFalse(deepCopyOfGrid.sharesWith(copyOfGrid)) def testGridProperties(self): expected = { openvdb.BoolGrid: ('bool', False, True), openvdb.FloatGrid: ('float', 0.0, 1.0), openvdb.Vec3SGrid: ('vec3s', (0, 0, 0), (-1, 0, 1)), } for factory in expected: valType, bg, newbg = expected[factory] grid = factory() self.assertEqual(grid.valueTypeName, valType) def setValueType(obj): obj.valueTypeName = 'double' # Grid.valueTypeName is read-only, so setting it raises an exception. self.assertRaises(AttributeError, lambda obj=grid: setValueType(obj)) self.assertEqual(grid.background, bg) grid.background = newbg self.assertEqual(grid.background, newbg) self.assertEqual(grid.name, '') grid.name = 'test' self.assertEqual(grid.name, 'test') self.assertFalse(grid.saveFloatAsHalf) grid.saveFloatAsHalf = True self.assert_(grid.saveFloatAsHalf) self.assert_(grid.treeDepth > 2) def testGridMetadata(self): grid = openvdb.BoolGrid() self.assertEqual(grid.metadata, {}) meta = { 'name': 'test', 'saveFloatAsHalf': True, 'xyz': (-1, 0, 1) } grid.metadata = meta self.assertEqual(grid.metadata, meta) meta['xyz'] = (-100, 100, 0) grid.updateMetadata(meta) self.assertEqual(grid.metadata, meta) self.assertEqual(set(grid.iterkeys()), set(meta.iterkeys())) for name in meta: self.assert_(name in grid) self.assertEqual(grid[name], meta[name]) for name in grid: self.assert_(name in grid) self.assertEqual(grid[name], meta[name]) self.assert_('xyz' in grid) del grid['xyz'] self.assertFalse('xyz' in grid) grid['xyz'] = meta['xyz'] self.assert_('xyz' in grid) grid.addStatsMetadata() meta = grid.getStatsMetadata() self.assertEqual(0, meta["file_voxel_count"]) def testGridFill(self): grid = openvdb.FloatGrid() acc = grid.getAccessor() ijk = (1, 1, 1) self.assertRaises(TypeError, lambda: grid.fill("", (7, 7, 7), 1, False)) self.assertRaises(TypeError, lambda: grid.fill((0, 0, 0), "", 1, False)) self.assertRaises(TypeError, lambda: grid.fill((0, 0, 0), (7, 7, 7), "", False)) self.assertFalse(acc.isValueOn(ijk)) grid.fill((0, 0, 0), (7, 7, 7), 1, active=False) self.assertEqual(acc.getValue(ijk), 1) self.assertFalse(acc.isValueOn(ijk)) grid.fill((0, 0, 0), (7, 7, 7), 2, active=True) self.assertEqual(acc.getValue(ijk), 2) self.assert_(acc.isValueOn(ijk)) activeCount = grid.activeVoxelCount() acc.setValueOn(ijk, 2.125) self.assertEqual(grid.activeVoxelCount(), activeCount) grid.fill(ijk, ijk, 2.125, active=True) self.assertEqual(acc.getValue(ijk), 2.125) self.assert_(acc.isValueOn(ijk)) self.assertEqual(grid.activeVoxelCount(), activeCount) leafCount = grid.leafCount() grid.prune() self.assertAlmostEqual(acc.getValue(ijk), 2.125) self.assert_(acc.isValueOn(ijk)) self.assertEqual(grid.leafCount(), leafCount) self.assertEqual(grid.activeVoxelCount(), activeCount) grid.prune(tolerance=0.2) self.assertEqual(grid.activeVoxelCount(), activeCount) self.assertEqual(acc.getValue(ijk), 2) self.assert_(acc.isValueOn(ijk)) self.assert_(grid.leafCount() < leafCount) def testGridIterators(self): onCoords = set([(-10, -10, -10), (0, 0, 0), (1, 1, 1)]) for factory in openvdb.GridTypes: grid = factory() acc = grid.getAccessor() for c in onCoords: acc.setValueOn(c) coords = set(value.min for value in grid.iterOnValues()) self.assertEqual(coords, onCoords) n = 0 for _ in grid.iterAllValues(): n += 1 for _ in grid.iterOffValues(): n -= 1 self.assertEqual(n, len(onCoords)) grid = factory() grid.fill((0, 0, 1), (18, 18, 18), grid.oneValue) # make active activeCount = grid.activeVoxelCount() # Iterate over active values (via a const iterator) and verify # that the cumulative active voxel count matches the grid's. count = 0 for value in grid.citerOnValues(): count += value.count self.assertEqual(count, activeCount) # Via a non-const iterator, turn off every other active value. # Then verify that the cumulative active voxel count is half the original count. state = True for value in grid.iterOnValues(): count -= value.count value.active = state state = not state self.assertEqual(grid.activeVoxelCount(), activeCount / 2) # Verify that writing through a const iterator is not allowed. value = grid.citerOnValues().next() self.assertRaises(AttributeError, lambda: setattr(value, 'active', 0)) self.assertRaises(AttributeError, lambda: setattr(value, 'depth', 0)) # Verify that some value attributes are immutable, even given a non-const iterator. value = grid.iterOnValues().next() self.assertRaises(AttributeError, lambda: setattr(value, 'min', (0, 0, 0))) self.assertRaises(AttributeError, lambda: setattr(value, 'max', (0, 0, 0))) self.assertRaises(AttributeError, lambda: setattr(value, 'count', 1)) def testMap(self): grid = openvdb.BoolGrid() grid.fill((-4, -4, -4), (5, 5, 5), grid.zeroValue) # make active grid.mapOn(lambda x: not x) # replace active False values with True n = sum(item.value for item in grid.iterOnValues()) self.assertEqual(n, 10 * 10 * 10) grid = openvdb.FloatGrid() grid.fill((-4, -4, -4), (5, 5, 5), grid.oneValue) grid.mapOn(lambda x: x * 2) n = sum(item.value for item in grid.iterOnValues()) self.assertEqual(n, 10 * 10 * 10 * 2) grid = openvdb.Vec3SGrid() grid.fill((-4, -4, -4), (5, 5, 5), grid.zeroValue) grid.mapOn(lambda x: (0, 1, 0)) n = sum(item.value[1] for item in grid.iterOnValues()) self.assertEqual(n, 10 * 10 * 10) def testValueAccessor(self): coords = set([(-10, -10, -10), (0, 0, 0), (1, 1, 1)]) for factory in openvdb.GridTypes: grid = factory() zero, one = grid.zeroValue, grid.oneValue acc = grid.getAccessor() cacc = grid.getConstAccessor() leafDepth = grid.treeDepth - 1 self.assertRaises(TypeError, lambda: cacc.setValueOn((5, 5, 5), zero)) self.assertRaises(TypeError, lambda: cacc.setValueOff((5, 5, 5), zero)) self.assertRaises(TypeError, lambda: cacc.setActiveState((5, 5, 5), True)) self.assertRaises(TypeError, lambda: acc.setValueOn("", zero)) self.assertRaises(TypeError, lambda: acc.setValueOff("", zero)) if grid.valueTypeName != "bool": self.assertRaises(TypeError, lambda: acc.setValueOn((5, 5, 5), object())) self.assertRaises(TypeError, lambda: acc.setValueOff((5, 5, 5), object())) for c in coords: grid.clear() # All voxels are inactive, background (0), and stored at the root. self.assertEqual(acc.getValue(c), zero) self.assertEqual(cacc.getValue(c), zero) self.assertFalse(acc.isValueOn(c)) self.assertFalse(cacc.isValueOn(c)) self.assertEqual(acc.getValueDepth(c), -1) self.assertEqual(cacc.getValueDepth(c), -1) acc.setValueOn(c) # active / 0 / leaf self.assertEqual(acc.getValue(c), zero) self.assertEqual(cacc.getValue(c), zero) self.assert_(acc.isValueOn(c)) self.assert_(cacc.isValueOn(c)) self.assertEqual(acc.getValueDepth(c), leafDepth) self.assertEqual(cacc.getValueDepth(c), leafDepth) acc.setValueOff(c, grid.oneValue) # inactive / 1 / leaf self.assertEqual(acc.getValue(c), one) self.assertEqual(cacc.getValue(c), one) self.assertFalse(acc.isValueOn(c)) self.assertFalse(cacc.isValueOn(c)) self.assertEqual(acc.getValueDepth(c), leafDepth) self.assertEqual(cacc.getValueDepth(c), leafDepth) # Verify that an accessor remains valid even after its grid is deleted # (because the C++ wrapper retains a reference to the C++ grid). def scoped(): grid = factory() acc = grid.getAccessor() cacc = grid.getConstAccessor() one = grid.oneValue acc.setValueOn((0, 0, 0), one) del grid self.assertEqual(acc.getValue((0, 0, 0)), one) self.assertEqual(cacc.getValue((0, 0, 0)), one) scoped() def testValueAccessorCopy(self): xyz = (0, 0, 0) grid = openvdb.BoolGrid() acc = grid.getAccessor() self.assertEqual(acc.getValue(xyz), False) self.assertFalse(acc.isValueOn(xyz)) copyOfAcc = acc.copy() self.assertEqual(copyOfAcc.getValue(xyz), False) self.assertFalse(copyOfAcc.isValueOn(xyz)) # Verify that changes made to the grid through one accessor are reflected in the other. acc.setValueOn(xyz, True) self.assertEqual(acc.getValue(xyz), True) self.assert_(acc.isValueOn(xyz)) self.assertEqual(copyOfAcc.getValue(xyz), True) self.assert_(copyOfAcc.isValueOn(xyz)) copyOfAcc.setValueOff(xyz) self.assertEqual(acc.getValue(xyz), True) self.assertFalse(acc.isValueOn(xyz)) self.assertEqual(copyOfAcc.getValue(xyz), True) self.assertFalse(copyOfAcc.isValueOn(xyz)) # Verify that the two accessors are distinct, by checking that they # have cached different sets of nodes. xyz2 = (-1, -1, -1) copyOfAcc.setValueOn(xyz2) self.assert_(copyOfAcc.isCached(xyz2)) self.assertFalse(copyOfAcc.isCached(xyz)) self.assert_(acc.isCached(xyz)) self.assertFalse(acc.isCached(xyz2)) def testPickle(self): import pickle # Test pickling of transforms of various types. testXforms = [ openvdb.createLinearTransform(voxelSize=0.1), openvdb.createLinearTransform(matrix=[[1,0,0,0],[0,2,0,0],[0,0,3,0],[4,3,2,1]]), openvdb.createFrustumTransform((0,0,0), (10,10,10), taper=0.8, depth=10.0), ] for xform in testXforms: s = pickle.dumps(xform) restoredXform = pickle.loads(s) self.assertEqual(restoredXform, xform) # Test pickling of grids of various types. for factory in openvdb.GridTypes: # Construct a grid. grid = factory() # Add some metadata to the grid. meta = { 'name': 'test', 'saveFloatAsHalf': True, 'xyz': (-1, 0, 1) } grid.metadata = meta # Add some voxel data to the grid. active = True for width in range(63, 0, -10): val = valueFactory(grid.zeroValue, width) grid.fill((0, 0, 0), (width,)*3, val, active) active = not active # Pickle the grid to a string, then unpickle the string. s = pickle.dumps(grid) restoredGrid = pickle.loads(s) # Verify that the original and unpickled grids' metadata are equal. self.assertEqual(restoredGrid.metadata, meta) # Verify that the original and unpickled grids have the same active values. for restored, original in zip(restoredGrid.iterOnValues(), grid.iterOnValues()): self.assertEqual(restored, original) # Verify that the original and unpickled grids have the same inactive values. for restored, original in zip(restoredGrid.iterOffValues(), grid.iterOffValues()): self.assertEqual(restored, original) def testGridCombine(self): # Construct two grids and add some voxel data to each. aGrid, bGrid = openvdb.FloatGrid(), openvdb.FloatGrid(background=1.0) for width in range(63, 1, -10): aGrid.fill((0, 0, 0), (width,)*3, width) bGrid.fill((0, 0, 0), (width,)*3, 2 * width) # Save a copy of grid A. copyOfAGrid = aGrid.deepCopy() # Combine corresponding values of the two grids, storing the result in grid A. # (Since the grids have the same topology and B's active values are twice A's, # the function computes 2*min(a, 2*a) + 3*max(a, 2*a) = 2*a + 3*(2*a) = 8*a # for active values, and 2*min(0, 1) + 3*max(0, 1) = 2*0 + 3*1 = 3 # for inactive values.) aGrid.combine(bGrid, lambda a, b: 2 * min(a, b) + 3 * max(a, b)) self.assert_(bGrid.empty()) # Verify that the resulting grid's values are as expected. for original, combined in zip(copyOfAGrid.iterOnValues(), aGrid.iterOnValues()): self.assertEqual(combined.min, original.min) self.assertEqual(combined.max, original.max) self.assertEqual(combined.depth, original.depth) self.assertEqual(combined.value, 8 * original.value) for original, combined in zip(copyOfAGrid.iterOffValues(), aGrid.iterOffValues()): self.assertEqual(combined.min, original.min) self.assertEqual(combined.max, original.max) self.assertEqual(combined.depth, original.depth) self.assertEqual(combined.value, 3) def testLevelSetSphere(self): HALF_WIDTH = 4 sphere = openvdb.createLevelSetSphere(halfWidth=HALF_WIDTH, voxelSize=1.0, radius=100.0) lo, hi = sphere.evalMinMax() self.assert_(lo >= -HALF_WIDTH) self.assert_(hi <= HALF_WIDTH) def testCopyFromArray(self): import random import time # Skip this test if NumPy is not available. try: import numpy as np except ImportError: return # Skip this test if the OpenVDB module was built without NumPy support. arr = np.ndarray((1, 2, 1)) grid = openvdb.FloatGrid() try: grid.copyFromArray(arr) except NotImplementedError: return # Verify that a non-three-dimensional array can't be copied into a grid. grid = openvdb.FloatGrid() self.assertRaises(TypeError, lambda: grid.copyFromArray('abc')) arr = np.ndarray((1, 2)) self.assertRaises(ValueError, lambda: grid.copyFromArray(arr)) # Verify that complex-valued arrays are not supported. arr = np.ndarray((1, 2, 1), dtype = complex) grid = openvdb.FloatGrid() self.assertRaises(TypeError, lambda: grid.copyFromArray(arr)) ARRAY_DIM = 201 BG, FG = 0, 1 # Generate some random voxel coordinates. random.seed(0) def randCoord(): return tuple(random.randint(0, ARRAY_DIM-1) for i in range(3)) coords = set(randCoord() for i in range(200)) def createArrays(): # Test both scalar- and vec3-valued (i.e., four-dimensional) arrays. for shape in ( (ARRAY_DIM, ARRAY_DIM, ARRAY_DIM), # scalar array (ARRAY_DIM, ARRAY_DIM, ARRAY_DIM, 3) # vec3 array ): for dtype in (np.float32, np.int32, np.float64, np.int64, np.uint32, np.bool): # Create a NumPy array, fill it with the background value, # then set some elements to the foreground value. arr = np.ndarray(shape, dtype) arr.fill(BG) bg = arr[0, 0, 0] for c in coords: arr[c] = FG yield arr # Test copying from arrays of various types to grids of various types. for cls in openvdb.GridTypes: for arr in createArrays(): isScalarArray = (len(arr.shape) == 3) isScalarGrid = False try: len(cls.zeroValue) # values of vector grids are sequences, which have a length except TypeError: isScalarGrid = True # values of scalar grids have no length gridBG = valueFactory(cls.zeroValue, BG) gridFG = valueFactory(cls.zeroValue, FG) # Create an empty grid. grid = cls(gridBG) acc = grid.getAccessor() # Verify that scalar arrays can't be copied into vector grids # and vector arrays can't be copied into scalar grids. if isScalarGrid != isScalarArray: self.assertRaises(ValueError, lambda: grid.copyFromArray(arr)) continue # Copy values from the NumPy array to the grid, marking # background values as inactive and all other values as active. now = time.clock() grid.copyFromArray(arr) elapsed = time.clock() - now #print 'copied %d voxels from %s array to %s in %f sec' % ( # arr.shape[0] * arr.shape[1] * arr.shape[2], # str(arr.dtype) + ('' if isScalarArray else '[]'), # grid.__class__.__name__, elapsed) # Verify that the grid's active voxels match the array's foreground elements. self.assertEqual(grid.activeVoxelCount(), len(coords)) for c in coords: self.assertEqual(acc.getValue(c), gridFG) for value in grid.iterOnValues(): self.assert_(value.min in coords) def testCopyToArray(self): import random import time # Skip this test if NumPy is not available. try: import numpy as np except ImportError: return # Skip this test if the OpenVDB module was built without NumPy support. arr = np.ndarray((1, 2, 1)) grid = openvdb.FloatGrid() try: grid.copyFromArray(arr) except NotImplementedError: return # Verify that a grid can't be copied into a non-three-dimensional array. grid = openvdb.FloatGrid() self.assertRaises(TypeError, lambda: grid.copyToArray('abc')) arr = np.ndarray((1, 2)) self.assertRaises(ValueError, lambda: grid.copyToArray(arr)) # Verify that complex-valued arrays are not supported. arr = np.ndarray((1, 2, 1), dtype = complex) grid = openvdb.FloatGrid() self.assertRaises(TypeError, lambda: grid.copyToArray(arr)) ARRAY_DIM = 201 BG, FG = 0, 1 # Generate some random voxel coordinates. random.seed(0) def randCoord(): return tuple(random.randint(0, ARRAY_DIM-1) for i in range(3)) coords = set(randCoord() for i in range(200)) def createArrays(): # Test both scalar- and vec3-valued (i.e., four-dimensional) arrays. for shape in ( (ARRAY_DIM, ARRAY_DIM, ARRAY_DIM), # scalar array (ARRAY_DIM, ARRAY_DIM, ARRAY_DIM, 3) # vec3 array ): for dtype in (np.float32, np.int32, np.float64, np.int64, np.uint32, np.bool): # Return a new NumPy array. arr = np.ndarray(shape, dtype) arr.fill(-100) yield arr # Test copying from arrays of various types to grids of various types. for cls in openvdb.GridTypes: for arr in createArrays(): isScalarArray = (len(arr.shape) == 3) isScalarGrid = False try: len(cls.zeroValue) # values of vector grids are sequences, which have a length except TypeError: isScalarGrid = True # values of scalar grids have no length gridBG = valueFactory(cls.zeroValue, BG) gridFG = valueFactory(cls.zeroValue, FG) # Create an empty grid, fill it with the background value, # then set some elements to the foreground value. grid = cls(gridBG) acc = grid.getAccessor() for c in coords: acc.setValueOn(c, gridFG) # Verify that scalar grids can't be copied into vector arrays # and vector grids can't be copied into scalar arrays. if isScalarGrid != isScalarArray: self.assertRaises(ValueError, lambda: grid.copyToArray(arr)) continue # Copy values from the grid to the NumPy array. now = time.clock() grid.copyToArray(arr) elapsed = time.clock() - now #print 'copied %d voxels from %s to %s array in %f sec' % ( # arr.shape[0] * arr.shape[1] * arr.shape[2], grid.__class__.__name__, # str(arr.dtype) + ('' if isScalarArray else '[]'), elapsed) # Verify that the grid's active voxels match the array's foreground elements. for c in coords: self.assertEqual(arr[c] if isScalarArray else tuple(arr[c]), gridFG) arr[c] = gridBG self.assertEqual(np.amin(arr), BG) self.assertEqual(np.amax(arr), BG) if __name__ == '__main__': print 'Testing', os.path.dirname(openvdb.__file__) sys.stdout.flush() try: logging.configure(sys.argv) args = Ani(sys.argv).userArgs() # strip out ANI-related arguments except NameError: args = sys.argv # Unlike CppUnit, PyUnit doesn't use the "-t" flag to identify # test names, so for consistency, strip out any "-t" arguments, # so that, e.g., "TestOpenVDB.py -t TestOpenVDB.testTransform" # is equivalent to "TestOpenVDB.py TestOpenVDB.testTransform". args = [a for a in args if a != '-t'] unittest.main(argv=args) # Copyright (c) 2012-2013 DreamWorks Animation LLC # All rights reserved. This software is distributed under the # Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyIntGrid.cc0000644000000000000000000000407012252453157014761 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file pyIntGrid.cc /// @brief Boost.Python wrappers for scalar, integer-valued openvdb::Grid types #include "pyGrid.h" void exportIntGrid() { pyGrid::exportGrid(); #ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES pyGrid::exportGrid(); pyGrid::exportGrid(); #endif } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyFloatGrid.cc0000644000000000000000000000554212252453157015301 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file pyFloatGrid.cc /// @author Peter Cucka /// @brief Boost.Python wrappers for scalar, floating-point openvdb::Grid types #include "pyGrid.h" /// Create a Python wrapper for each supported Grid type. void exportFloatGrid() { // Add a module-level list that gives the types of all supported Grid classes. py::scope().attr("GridTypes") = py::list(); // Specify that py::numeric::array should refer to the Python type numpy.ndarray // (rather than the older Numeric.array). py::numeric::array::set_module_and_type("numpy", "ndarray"); pyGrid::exportGrid(); #ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES pyGrid::exportGrid(); #endif py::def("createLevelSetSphere", &pyGrid::createLevelSetSphere, (py::arg("radius"), py::arg("center")=openvdb::Coord(), py::arg("voxelSize")=1.0, py::arg("halfWidth")=openvdb::LEVEL_SET_HALF_WIDTH), "createLevelSetSphere(radius, center, voxelSize, halfWidth) -> FloatGrid\n\n" "Return a grid containing a narrow-band level set representation\n" "of a sphere."); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyTransform.cc0000644000000000000000000003111112252453157015370 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include "openvdb/openvdb.h" #include "pyutil.h" namespace py = boost::python; using namespace openvdb::OPENVDB_VERSION_NAME; namespace pyTransform { inline void scale1(math::Transform& t, double s) { t.preScale(s); } inline void scale3(math::Transform& t, const Vec3d& xyz) { t.preScale(xyz); } inline Vec3d voxelDim0(math::Transform& t) { return t.voxelSize(); } inline Vec3d voxelDim1(math::Transform& t, const Vec3d& p) { return t.voxelSize(p); } inline double voxelVolume0(math::Transform& t) { return t.voxelVolume(); } inline double voxelVolume1(math::Transform& t, const Vec3d& p) { return t.voxelVolume(p); } inline Vec3d indexToWorld(math::Transform& t, const Vec3d& p) { return t.indexToWorld(p); } inline Vec3d worldToIndex(math::Transform& t, const Vec3d& p) { return t.worldToIndex(p); } inline std::string info(math::Transform& t) { std::ostringstream ostr; t.print(ostr); return ostr.str(); } inline math::Transform::Ptr createLinearFromDim(double dim) { return math::Transform::createLinearTransform(dim); } inline math::Transform::Ptr createLinearFromMat(py::object obj) { Mat4R m; // Verify that obj is a four-element sequence. bool is4x4Seq = (PySequence_Check(obj.ptr()) && PySequence_Length(obj.ptr()) == 4); if (is4x4Seq) { for (size_t row = 0; is4x4Seq && row < 4; ++row) { // Verify that each element of obj is itself a four-element sequence. py::object rowObj = obj[row]; if (PySequence_Check(rowObj.ptr()) && PySequence_Length(rowObj.ptr()) == 4) { // Extract four numeric values from this row of the sequence. for (size_t col = 0; is4x4Seq && col < 4; ++col) { if (py::extract(rowObj[col]).check()) { m[row][col] = py::extract(rowObj[col]); } else { is4x4Seq = false; } } } else { is4x4Seq = false; } } } if (!is4x4Seq) { PyErr_Format(PyExc_ValueError, "expected a 4 x 4 sequence of numeric values"); py::throw_error_already_set(); } return math::Transform::createLinearTransform(m); } inline math::Transform::Ptr createFrustum(const Coord& xyzMin, const Coord& xyzMax, double taper, double depth, double voxelDim = 1.0) { return math::Transform::createFrustumTransform( BBoxd(xyzMin.asVec3d(), xyzMax.asVec3d()), taper, depth, voxelDim); } //////////////////////////////////////// struct PickleSuite: py::pickle_suite { enum { STATE_DICT = 0, STATE_MAJOR, STATE_MINOR, STATE_FORMAT, STATE_XFORM }; /// Return @c true, indicating that this pickler preserves a Transform's __dict__. static bool getstate_manages_dict() { return true; } /// Return a tuple representing the state of the given Transform. static py::tuple getstate(py::object xformObj) { py::tuple state; py::extract x(xformObj); if (x.check()) { // Extract a Transform from the Python object. math::Transform xform = x(); std::ostringstream ostr(std::ios_base::binary); // Serialize the Transform to a string. xform.write(ostr); // Construct a state tuple comprising the Python object's __dict__, // the version numbers of the serialization format, // and the serialized Transform. state = py::make_tuple( xformObj.attr("__dict__"), uint32_t(OPENVDB_LIBRARY_MAJOR_VERSION), uint32_t(OPENVDB_LIBRARY_MINOR_VERSION), uint32_t(OPENVDB_FILE_VERSION), ostr.str()); } return state; } /// Restore the given Transform to a saved state. static void setstate(py::object xformObj, py::object stateObj) { math::Transform::Ptr xform; { py::extract x(xformObj); if (x.check()) xform = x(); else return; } py::tuple state; { py::extract x(stateObj); if (x.check()) state = x(); } bool badState = (py::len(state) != 5); if (!badState) { // Restore the object's __dict__. py::extract x(state[int(STATE_DICT)]); if (x.check()) { py::dict d = py::extract(xformObj.attr("__dict__"))(); d.update(x()); } else { badState = true; } } openvdb::VersionId libVersion; uint32_t formatVersion = 0; if (!badState) { // Extract the serialization format version numbers. const int idx[3] = { STATE_MAJOR, STATE_MINOR, STATE_FORMAT }; uint32_t version[3] = { 0, 0, 0 }; for (int i = 0; i < 3 && !badState; ++i) { py::extract x(state[idx[i]]); if (x.check()) version[i] = x(); else badState = true; } libVersion.first = version[0]; libVersion.second = version[1]; formatVersion = version[2]; } std::string serialized; if (!badState) { // Extract the string containing the serialized Transform. py::extract x(state[int(STATE_XFORM)]); if (x.check()) serialized = x(); else badState = true; } if (badState) { PyErr_SetObject(PyExc_ValueError, ("expected (dict, int, int, int, str) tuple in call to __setstate__; found %s" % stateObj.attr("__repr__")()).ptr()); py::throw_error_already_set(); } // Restore the internal state of the C++ object. std::istringstream istr(serialized, std::ios_base::binary); io::setVersion(istr, libVersion, formatVersion); xform->read(istr); } }; // struct PickleSuite } // namespace pyTransform void exportTransform() { py::enum_("Axis") .value("X", math::X_AXIS) .value("Y", math::Y_AXIS) .value("Z", math::Z_AXIS); py::class_("Transform", py::init<>()) .def("deepCopy", &math::Transform::copy, "deepCopy() -> Transform\n\n" "Return a copy of this transform.") /// @todo Should this also be __str__()? .def("info", &pyTransform::info, "info() -> str\n\n" "Return a string containing a description of this transform.\n") .def_pickle(pyTransform::PickleSuite()) .add_property("typeName", &math::Transform::mapType, "name of this transform's type") .add_property("isLinear", &math::Transform::isLinear, "True if this transform is linear") .def("rotate", &math::Transform::preRotate, (py::arg("radians"), py::arg("axis") = math::X_AXIS), "rotate(radians, axis)\n\n" "Accumulate a rotation about either Axis.X, Axis.Y or Axis.Z.") .def("translate", &math::Transform::postTranslate, py::arg("xyz"), "translate((x, y, z))\n\n" "Accumulate a translation.") .def("scale", &pyTransform::scale1, py::arg("s"), "scale(s)\n\n" "Accumulate a uniform scale.") .def("scale", &pyTransform::scale3, py::arg("sxyz"), "scale((sx, sy, sz))\n\n" "Accumulate a nonuniform scale.") .def("shear", &math::Transform::preShear, (py::arg("s"), py::arg("axis0"), py::arg("axis1")), "shear(s, axis0, axis1)\n\n" "Accumulate a shear (axis0 and axis1 are either\n" "Axis.X, Axis.Y or Axis.Z).") .def("voxelSize", &pyTransform::voxelDim0, "voxelSize() -> (dx, dy, dz)\n\n" "Return the size of voxels of the linear component of this transform.") .def("voxelSize", &pyTransform::voxelDim1, py::arg("xyz"), "voxelSize((x, y, z)) -> (dx, dy, dz)\n\n" "Return the size of the voxel at position (x, y, z).") .def("voxelVolume", &pyTransform::voxelVolume0, "voxelVolume() -> float\n\n" "Return the voxel volume of the linear component of this transform.") .def("voxelVolume", &pyTransform::voxelVolume1, py::arg("xyz"), "voxelVolume((x, y, z)) -> float\n\n" "Return the voxel volume at position (x, y, z).") .def("indexToWorld", &pyTransform::indexToWorld, py::arg("xyz"), "indexToWorld((x, y, z)) -> (x', y', z')\n\n" "Apply this transformation to the given coordinates.") .def("worldToIndex", &pyTransform::worldToIndex, py::arg("xyz"), "worldToIndex((x, y, z)) -> (x', y', z')\n\n" "Apply the inverse of this transformation to the given coordinates.") .def("worldToIndexCellCentered", &math::Transform::worldToIndexCellCentered, py::arg("xyz"), "worldToIndexCellCentered((x, y, z)) -> (i, j, k)\n\n" "Apply the inverse of this transformation to the given coordinates\n" "and round the result to the nearest integer coordinates.") .def("worldToIndexNodeCentered", &math::Transform::worldToIndexNodeCentered, py::arg("xyz"), "worldToIndexNodeCentered((x, y, z)) -> (i, j, k)\n\n" "Apply the inverse of this transformation to the given coordinates\n" "and round the result down to the nearest integer coordinates.") // Allow Transforms to be compared for equality and inequality. .def(py::self == py::other()) .def(py::self != py::other()) ; py::def("createLinearTransform", &pyTransform::createLinearFromMat, py::arg("matrix"), "createLinearTransform(matrix) -> Transform\n\n" "Create a new linear transform from a 4 x 4 matrix given as a sequence\n" "of the form [[a, b, c, d], [e, f, g, h], [i, j, k, l], [m, n, o, p]],\n" "where [m, n, o, p] is the translation component."); py::def("createLinearTransform", &pyTransform::createLinearFromDim, (py::arg("voxelSize") = 1.0), "createLinearTransform(voxelSize) -> Transform\n\n" "Create a new linear transform with the given uniform voxel size."); py::def("createFrustumTransform", &pyTransform::createFrustum, (py::arg("xyzMin"), py::arg("xyzMax"), py::arg("taper"), py::arg("depth"), py::arg("voxelSize") = 1.0), "createFrustumTransform(xyzMin, xyzMax, taper, depth, voxelSize) -> Transform\n\n" "Create a new frustum transform with unit bounding box (xyzMin, xyzMax)\n" "and the given taper, depth and uniform voxel size."); // allows Transform::Ptr Grid::getTransform() to work py::register_ptr_to_python(); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyutil.h0000644000000000000000000002414512252453157014245 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_PYUTIL_HAS_BEEN_INCLUDED #define OPENVDB_PYUTIL_HAS_BEEN_INCLUDED #include "openvdb/openvdb.h" #include #include #include // for std::pair #include #include namespace pyutil { /// Return a new @c boost::python::object that borrows (i.e., doesn't /// take over ownership of) the given @c PyObject's reference. inline boost::python::object pyBorrow(PyObject* obj) { return boost::python::object(boost::python::handle<>(boost::python::borrowed(obj))); } /// @brief Given a @c PyObject that implements the sequence protocol /// (e.g., a @c PyListObject), return the value of type @c ValueT /// at index @a idx in the sequence. /// @details Raise a Python @c TypeError exception if the value /// at index @a idx is not convertible to type @c ValueT. template inline ValueT getSequenceItem(PyObject* obj, int idx) { return boost::python::extract(pyBorrow(obj)[idx]); } //////////////////////////////////////// template struct GridTraitsBase { /// @brief Return the name of the Python class that wraps this grid type /// (e.g., "FloatGrid" for openvdb::FloatGrid). /// /// @note This name is not the same as GridType::type(). /// The latter returns a name like "Tree_float_5_4_3". static const char* name(); /// Return the name of this grid type's value type ("bool", "float", "vec3s", etc.). static const char* valueTypeName() { return openvdb::typeNameAsString(); } /// @brief Return a description of this grid type. /// /// @note This name is generated at runtime for each call to descr(). static const std::string descr() { return std::string("OpenVDB grid with voxels of type ") + valueTypeName(); } }; // struct GridTraitsBase template struct GridTraits: public GridTraitsBase { }; /// Map a grid type to a traits class that derives from GridTraitsBase /// and that defines a name() method. #define GRID_TRAITS(_typ, _name) \ template<> struct GridTraits<_typ>: public GridTraitsBase<_typ> { \ static const char* name() { return _name; } \ } GRID_TRAITS(openvdb::FloatGrid, "FloatGrid"); GRID_TRAITS(openvdb::Vec3SGrid, "Vec3SGrid"); GRID_TRAITS(openvdb::BoolGrid, "BoolGrid"); #ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES GRID_TRAITS(openvdb::DoubleGrid, "DoubleGrid"); GRID_TRAITS(openvdb::Int32Grid, "Int32Grid"); GRID_TRAITS(openvdb::Int64Grid, "Int64Grid"); GRID_TRAITS(openvdb::Vec3IGrid, "Vec3IGrid"); GRID_TRAITS(openvdb::Vec3DGrid, "Vec3DGrid"); #endif #undef GRID_TRAITS //////////////////////////////////////// // Note that the elements are pointers to C strings (char**), because // boost::python::class_::def_readonly() requires a pointer to a static member. typedef std::pair CStringPair; /// @brief Enum-like mapping from string keys to string values, with characteristics /// of both (Python) classes and class instances (as well as NamedTuples) /// @details /// - (@e key, @e value) pairs can be accessed as class attributes (\"MyClass.MY_KEY\") /// - (@e key, @e value) pairs can be accessed via dict lookup on instances /// (\"MyClass()['MY_KEY']\") /// - (@e key, @e value) pairs can't be modified or reassigned /// - instances are iterable (\"for key in MyClass(): ...\") /// /// A @c Descr class must implement the following interface: /// @code /// struct MyDescr /// { /// // Return the Python name for the enum class. /// static const char* name(); /// // Return the docstring for the enum class. /// static const char* doc(); /// // Return the ith (key, value) pair, in the form of /// // a pair of *pointers* to C strings /// static CStringPair item(int i); /// }; /// @endcode template struct StringEnum { /// Return the (key, value) map as a Python dict. static boost::python::dict items() { static tbb::mutex sMutex; static boost::python::dict itemDict; if (!itemDict) { // The first time this function is called, populate // the static dict with (key, value) pairs. tbb::mutex::scoped_lock lock(sMutex); if (!itemDict) { for (int i = 0; ; ++i) { const CStringPair item = Descr::item(i); OPENVDB_START_THREADSAFE_STATIC_WRITE if (item.first) { itemDict[boost::python::str(*item.first)] = boost::python::str(*item.second); } OPENVDB_FINISH_THREADSAFE_STATIC_WRITE else break; } } } return itemDict; } /// Return the keys as a Python list of strings. static boost::python::object keys() { return items().attr("keys")(); } /// Return the number of keys as a Python int. boost::python::object numItems() const { return boost::python::object(boost::python::len(items())); } /// Return the value (as a Python string) for the given key. boost::python::object getItem(boost::python::object keyObj) const { return items()[keyObj]; } /// Return a Python iterator over the keys. boost::python::object iter() const { return items().attr("__iter__")(); } /// Register this enum. static void wrap() { boost::python::class_ cls( /*classname=*/Descr::name(), /*docstring=*/Descr::doc()); cls.def("keys", &StringEnum::keys, "keys() -> list") .staticmethod("keys") .def("__len__", &StringEnum::numItems, "__len__() -> int") .def("__iter__", &StringEnum::iter, "__iter__() -> iterator") .def("__getitem__", &StringEnum::getItem, "__getitem__(str) -> str") /*end*/; // Add a read-only, class-level attribute for each (key, value) pair. for (int i = 0; ; ++i) { const CStringPair item = Descr::item(i); if (item.first) cls.def_readonly(*item.first, item.second); else break; } } }; //////////////////////////////////////// /// @brief From the given Python object, extract a value of type @c T. /// /// If the object cannot be converted to type @c T, raise a @c TypeError with a more /// Pythonic error message (incorporating the provided class and function names, etc.) /// than the one that would be generated by boost::python::extract(), e.g., /// "TypeError: expected float, found str as argument 2 to FloatGrid.prune()" instead of /// "TypeError: No registered converter was able to produce a C++ rvalue of type /// boost::shared_ptr inline T extractArg( boost::python::object obj, const char* functionName, const char* className = NULL, int argIdx = 0, // args are numbered starting from 1 const char* expectedType = NULL) { boost::python::extract val(obj); if (!val.check()) { // Generate an error string of the form // "expected , found as argument // to .()", where and // are optional. std::ostringstream os; os << "expected "; if (expectedType) os << expectedType; else os << openvdb::typeNameAsString(); const std::string actualType = boost::python::extract(obj.attr("__class__").attr("__name__")); os << ", found " << actualType << " as argument"; if (argIdx > 0) os << " " << argIdx; os << " to "; if (className) os << className << "."; os << functionName << "()"; PyErr_SetString(PyExc_TypeError, os.str().c_str()); boost::python::throw_error_already_set(); } return val(); } //////////////////////////////////////// /// Return str(val) for the given value. template inline std::string str(const T& val) { return boost::python::extract(boost::python::str(val)); } /// Return the name of the given Python object's class. inline std::string className(boost::python::object obj) { std::string s = boost::python::extract( obj.attr("__class__").attr("__name__")); return s; } } // namespace pyutil #endif // OPENVDB_PYUTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyopenvdb.h0000644000000000000000000001054712252453157014726 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file pyopenvdb.h /// /// @brief Glue functions for access to pyOpenVDB objects from C++ code /// @details Use these functions in your own Python function implementations /// to extract an OpenVDB grid from or wrap a grid in a @c PyObject. /// For example (using Boost.Python), /// @code /// #include /// #include /// #include /// /// // Implementation of a Python function that processes pyOpenVDB grids /// boost::python::object /// processGrid(boost::python::object inObj) /// { /// boost::python::object outObj; /// try { /// // Extract an OpenVDB grid from the input argument. /// if (openvdb::GridBase::Ptr grid = /// pyopenvdb::getGridFromPyObject(inObj)) /// { /// grid = grid->deepCopyGrid(); /// /// // Process the grid... /// /// // Wrap the processed grid in a PyObject. /// outObj = pyopenvdb::getPyObjectFromGrid(grid); /// } /// } catch (openvdb::TypeError& e) { /// PyErr_Format(PyExc_TypeError, e.what()); /// boost::python::throw_error_already_set(); /// } /// return outObj; /// } /// /// BOOST_PYTHON_MODULE(mymodule) /// { /// openvdb::initialize(); /// /// // Definition of a Python function that processes pyOpenVDB grids /// boost::python::def(/*name=*/"processGrid", &processGrid, /*argname=*/"grid"); /// } /// @endcode /// Then, from Python, /// @code /// import openvdb /// import mymodule /// /// grid = openvdb.read('myGrid.vdb', 'MyGrid') /// grid = mymodule.processGrid(grid) /// openvdb.write('myProcessedGrid.vdb', [grid]) /// @endcode #ifndef PYOPENVDB_HAS_BEEN_INCLUDED #define PYOPENVDB_HAS_BEEN_INCLUDED #include #include "openvdb/Grid.h" namespace pyopenvdb { //@{ /// @brief Return a pointer to the OpenVDB grid held by the given Python object. /// @throw openvdb::TypeError if the Python object is not one of the pyOpenVDB grid types. /// (See the Python module's GridTypes global variable for the list of supported grid types.) openvdb::GridBase::Ptr getGridFromPyObject(PyObject*); openvdb::GridBase::Ptr getGridFromPyObject(const boost::python::object&); //@} /// @brief Return a new Python object that holds the given OpenVDB grid. /// @return @c None if the given grid pointer is null. /// @throw openvdb::TypeError if the grid is not of a supported type. /// (See the Python module's GridTypes global variable for the list of supported grid types.) boost::python::object getPyObjectFromGrid(const openvdb::GridBase::Ptr&); } // namespace pyopenvdb #endif // PYOPENVDB_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyAccessor.h0000644000000000000000000003214112252453157015025 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #ifndef OPENVDB_PYACCESSOR_HAS_BEEN_INCLUDED #define OPENVDB_PYACCESSOR_HAS_BEEN_INCLUDED #include #include "openvdb/openvdb.h" #include "pyutil.h" namespace pyAccessor { namespace py = boost::python; using namespace openvdb::OPENVDB_VERSION_NAME; //@{ /// Type traits for grid accessors template struct AccessorTraits { typedef _GridT GridT; typedef GridT NonConstGridT; typedef typename NonConstGridT::Ptr GridPtrT; typedef typename NonConstGridT::Accessor AccessorT; typedef typename AccessorT::ValueType ValueT; static const bool IsConst = false; static const char* typeName() { return "Accessor"; } static void setActiveState(AccessorT& acc, const Coord& ijk, bool on) { acc.setActiveState(ijk, on); } static void setValueOnly(AccessorT& acc, const Coord& ijk, const ValueT& val) { acc.setValueOnly(ijk, val); } static void setValueOn(AccessorT& acc, const Coord& ijk) { acc.setValueOn(ijk); } static void setValueOn(AccessorT& acc, const Coord& ijk, const ValueT& val) { acc.setValueOn(ijk, val); } static void setValueOff(AccessorT& acc, const Coord& ijk) { acc.setValueOff(ijk); } static void setValueOff(AccessorT& acc, const Coord& ijk, const ValueT& val) { acc.setValueOff(ijk, val); } }; // Partial specialization for const accessors template struct AccessorTraits { typedef const _GridT GridT; typedef _GridT NonConstGridT; typedef typename NonConstGridT::ConstPtr GridPtrT; typedef typename NonConstGridT::ConstAccessor AccessorT; typedef typename AccessorT::ValueType ValueT; static const bool IsConst = true; static const char* typeName() { return "ConstAccessor"; } static void setActiveState(AccessorT&, const Coord&, bool) { notWritable(); } static void setValueOnly(AccessorT&, const Coord&, const ValueT&) { notWritable(); } static void setValueOn(AccessorT&, const Coord&) { notWritable(); } static void setValueOn(AccessorT&, const Coord&, const ValueT&) { notWritable(); } static void setValueOff(AccessorT&, const Coord&) { notWritable(); } static void setValueOff(AccessorT&, const Coord&, const ValueT&) { notWritable(); } static void notWritable() { PyErr_SetString(PyExc_TypeError, "accessor is read-only"); py::throw_error_already_set(); } }; //@} //////////////////////////////////////// /// Variant of pyutil::extractArg() that extracts a Coord from a py::object /// argument to a given ValueAccessor method template inline Coord extractCoordArg(py::object obj, const char* functionName, int argIdx = 0) { return pyutil::extractArg(obj, functionName, AccessorTraits::typeName(), argIdx, "tuple(int, int, int)"); } /// Variant of pyutil::extractArg() that extracts a value of type /// ValueAccessor::ValueType from an argument to a ValueAccessor method template inline typename GridT::ValueType extractValueArg( py::object obj, const char* functionName, int argIdx = 0, // args are numbered starting from 1 const char* expectedType = NULL) { return pyutil::extractArg( obj, functionName, AccessorTraits::typeName(), argIdx, expectedType); } //////////////////////////////////////// /// @brief ValueAccessor wrapper class that also stores a grid pointer, /// so that the grid doesn't get deleted as long as the accessor is live /// /// @internal This class could have just been made to inherit from ValueAccessor, /// but the method wrappers allow for more Pythonic error messages. For example, /// if we constructed the Python getValue() method directly from the corresponding /// ValueAccessor method, as follows, /// /// .def("getValue", &Accessor::getValue, ...) /// /// then the conversion from a Python type to a Coord& would be done /// automatically. But if the Python method were called with an object of /// a type that is not convertible to a Coord, then the TypeError message /// would say something like "TypeError: No registered converter was able to /// produce a C++ rvalue of type openvdb::math::Coord...". /// Handling the type conversion manually is more work, but it allows us to /// instead generate messages like "TypeError: expected tuple(int, int, int), /// found str as argument to FloatGridAccessor.getValue()". template class AccessorWrap { public: typedef AccessorTraits<_GridType> Traits; typedef typename Traits::AccessorT Accessor; typedef typename Traits::ValueT ValueType; typedef typename Traits::NonConstGridT GridType; typedef typename Traits::GridPtrT GridPtrType; AccessorWrap(GridPtrType grid): mGrid(grid), mAccessor(grid->getAccessor()) {} AccessorWrap copy() const { return *this; } void clear() { mAccessor.clear(); } GridPtrType parent() const { return mGrid; } ValueType getValue(py::object coordObj) { const Coord ijk = extractCoordArg(coordObj, "getValue"); return mAccessor.getValue(ijk); } int getValueDepth(py::object coordObj) { const Coord ijk = extractCoordArg(coordObj, "getValueDepth"); return mAccessor.getValueDepth(ijk); } int isVoxel(py::object coordObj) { const Coord ijk = extractCoordArg(coordObj, "isVoxel"); return mAccessor.isVoxel(ijk); } py::tuple probeValue(py::object coordObj) { const Coord ijk = extractCoordArg(coordObj, "probeValue"); ValueType value; bool on = mAccessor.probeValue(ijk, value); return py::make_tuple(value, on); } bool isValueOn(py::object coordObj) { const Coord ijk = extractCoordArg(coordObj, "isValueOn"); return mAccessor.isValueOn(ijk); } void setActiveState(py::object coordObj, bool on) { const Coord ijk = extractCoordArg(coordObj, "setActiveState", /*argIdx=*/1); Traits::setActiveState(mAccessor, ijk, on); } void setValueOnly(py::object coordObj, py::object valObj) { Coord ijk = extractCoordArg(coordObj, "setValueOnly", 1); ValueType val = extractValueArg(valObj, "setValueOnly", 2); Traits::setValueOnly(mAccessor, ijk, val); } void setValueOn(py::object coordObj, py::object valObj) { Coord ijk = extractCoordArg(coordObj, "setValueOn", 1); if (valObj.is_none()) { Traits::setValueOn(mAccessor, ijk); } else { ValueType val = extractValueArg(valObj, "setValueOn", 2); Traits::setValueOn(mAccessor, ijk, val); } } void setValueOff(py::object coordObj, py::object valObj) { Coord ijk = extractCoordArg(coordObj, "setValueOff", 1); if (valObj.is_none()) { Traits::setValueOff(mAccessor, ijk); } else { ValueType val = extractValueArg(valObj, "setValueOff", 2); Traits::setValueOff(mAccessor, ijk, val); } } int isCached(py::object coordObj) { const Coord ijk = extractCoordArg(coordObj, "isCached"); return mAccessor.isCached(ijk); } /// @brief Define a Python wrapper class for this C++ class. static void wrap() { const std::string pyGridTypeName = pyutil::GridTraits::name(), pyValueTypeName = openvdb::typeNameAsString(), pyAccessorTypeName = Traits::typeName(); py::class_ clss( pyAccessorTypeName.c_str(), (std::string(Traits::IsConst ? "Read-only" : "Read/write") + " access by (i, j, k) index coordinates to the voxels\nof a " + pyGridTypeName).c_str(), py::no_init); clss.def("copy", &AccessorWrap::copy, ("copy() -> " + pyAccessorTypeName + "\n\n" "Return a copy of this accessor.").c_str()) .def("clear", &AccessorWrap::clear, "clear()\n\n" "Clear this accessor of all cached data.") .add_property("parent", &AccessorWrap::parent, ("this accessor's parent " + pyGridTypeName).c_str()) // // Voxel access // .def("getValue", &AccessorWrap::getValue, py::arg("ijk"), ("getValue(ijk) -> " + pyValueTypeName + "\n\n" "Return the value of the voxel at coordinates (i, j, k).").c_str()) .def("getValueDepth", &AccessorWrap::getValueDepth, py::arg("ijk"), "getValueDepth(ijk) -> int\n\n" "Return the tree depth (0 = root) at which the value of voxel\n" "(i, j, k) resides. If (i, j, k) isn't explicitly represented in\n" "the tree (i.e., it is implicitly a background voxel), return -1.") .def("isVoxel", &AccessorWrap::isVoxel, py::arg("ijk"), "isVoxel(ijk) -> bool\n\n" "Return True if voxel (i, j, k) resides at the leaf level of the tree.") .def("probeValue", &AccessorWrap::probeValue, py::arg("ijk"), "probeValue(ijk) -> value, bool\n\n" "Return the value of the voxel at coordinates (i, j, k)\n" "together with the voxel's active state.") .def("isValueOn", &AccessorWrap::isValueOn, py::arg("ijk"), "isValueOn(ijk) -> bool\n\n" "Return the active state of the voxel at coordinates (i, j, k).") .def("setActiveState", &AccessorWrap::setActiveState, (py::arg("ijk"), py::arg("on")), "setActiveState(ijk, on)\n\n" "Mark voxel (i, j, k) as either active or inactive (True or False),\n" "but don't change its value.") .def("setValueOnly", &AccessorWrap::setValueOnly, (py::arg("ijk"), py::arg("value")), "setValueOnly(ijk, value)\n\n" "Set the value of voxel (i, j, k), but don't change its active state.") .def("setValueOn", &AccessorWrap::setValueOn, (py::arg("ijk"), py::arg("value") = py::object()), "setValueOn(ijk, value=None)\n\n" "Mark voxel (i, j, k) as active and, if the given value\n" "is not None, set the voxel's value.\n") .def("setValueOff", &AccessorWrap::setValueOff, (py::arg("ijk"), py::arg("value") = py::object()), "setValueOff(ijk, value=None)\n\n" "Mark voxel (i, j, k) as inactive and, if the given value\n" "is not None, set the voxel's value.") .def("isCached", &AccessorWrap::isCached, py::arg("ijk"), "isCached(ijk) -> bool\n\n" "Return True if this accessor has cached the path to voxel (i, j, k).") ; // py::class_ } private: const GridPtrType mGrid; Accessor mAccessor; }; // class AccessorWrap } // namespace pyAccessor #endif // OPENVDB_PYACCESSOR_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyGrid.h0000644000000000000000000020643112252453157014155 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file pyGrid.h /// @author Peter Cucka /// @brief Boost.Python wrapper for openvdb::Grid #ifndef OPENVDB_PYGRID_HAS_BEEN_INCLUDED #define OPENVDB_PYGRID_HAS_BEEN_INCLUDED #include #include #ifdef PY_OPENVDB_USE_NUMPY #include // for PyArray_DATA() #endif #include "openvdb/openvdb.h" #include "openvdb/io/Stream.h" #include "openvdb/tools/LevelSetSphere.h" #include "openvdb/tools/Dense.h" #include "pyutil.h" #include "pyAccessor.h" // for pyAccessor::AccessorWrap #include "pyopenvdb.h" #include namespace py = boost::python; using namespace openvdb::OPENVDB_VERSION_NAME; namespace pyopenvdb { inline py::object getPyObjectFromGrid(const GridBase::Ptr& grid) { if (!grid) return py::object(); #define CONVERT_BASE_TO_GRID(GridType, grid) \ if (grid->isType()) { \ return py::object(gridPtrCast(grid)); \ } CONVERT_BASE_TO_GRID(FloatGrid, grid); CONVERT_BASE_TO_GRID(Vec3SGrid, grid); CONVERT_BASE_TO_GRID(BoolGrid, grid); #ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES CONVERT_BASE_TO_GRID(DoubleGrid, grid); CONVERT_BASE_TO_GRID(Int32Grid, grid); CONVERT_BASE_TO_GRID(Int64Grid, grid); CONVERT_BASE_TO_GRID(Vec3IGrid, grid); CONVERT_BASE_TO_GRID(Vec3DGrid, grid); #endif #undef CONVERT_BASE_TO_GRID OPENVDB_THROW(TypeError, grid->type() + " is not a supported OpenVDB grid type"); } inline openvdb::GridBase::Ptr getGridFromPyObject(const boost::python::object& gridObj) { if (!gridObj) return GridBase::Ptr(); #define CONVERT_GRID_TO_BASE(GridPtrType) \ { \ py::extract x(gridObj); \ if (x.check()) return x(); \ } // Extract a grid pointer of one of the supported types // from the input object, then cast it to a base pointer. CONVERT_GRID_TO_BASE(FloatGrid::Ptr); CONVERT_GRID_TO_BASE(Vec3SGrid::Ptr); CONVERT_GRID_TO_BASE(BoolGrid::Ptr); #ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES CONVERT_GRID_TO_BASE(DoubleGrid::Ptr); CONVERT_GRID_TO_BASE(Int32Grid::Ptr); CONVERT_GRID_TO_BASE(Int64Grid::Ptr); CONVERT_GRID_TO_BASE(Vec3IGrid::Ptr); CONVERT_GRID_TO_BASE(Vec3DGrid::Ptr); #endif #undef CONVERT_GRID_TO_BASE OPENVDB_THROW(TypeError, pyutil::className(gridObj) + " is not a supported OpenVDB grid type"); } inline openvdb::GridBase::Ptr getGridFromPyObject(PyObject* gridObj) { return getGridFromPyObject(pyutil::pyBorrow(gridObj)); } } // namespace pyopenvdb //////////////////////////////////////// namespace pyGrid { inline py::object getGridFromGridBase(GridBase::Ptr grid) { py::object obj; try { obj = pyopenvdb::getPyObjectFromGrid(grid); } catch (openvdb::TypeError& e) { PyErr_SetString(PyExc_TypeError, e.what()); py::throw_error_already_set(); return py::object(); } return obj; } /// GridBase is not exposed in Python because it isn't really needed /// (and because exposing it would be complicated, requiring wrapping /// pure virtual functions like GridBase::baseTree()), but there are /// a few cases where, internally, we need to extract a GridBase::Ptr /// from a py::object. Hence this converter. inline GridBase::Ptr getGridBaseFromGrid(py::object gridObj) { GridBase::Ptr grid; try { grid = pyopenvdb::getGridFromPyObject(gridObj); } catch (openvdb::TypeError& e) { PyErr_SetString(PyExc_TypeError, e.what()); py::throw_error_already_set(); return GridBase::Ptr(); } return grid; } //////////////////////////////////////// /// Variant of pyutil::extractArg() that uses the class name of a given grid type template inline T extractValueArg( py::object obj, const char* functionName, int argIdx = 0, // args are numbered starting from 1 const char* expectedType = NULL) { return pyutil::extractArg(obj, functionName, pyutil::GridTraits::name(), argIdx, expectedType); } /// @brief Variant of pyutil::extractArg() that uses the class name /// and @c ValueType of a given grid type template inline typename GridType::ValueType extractValueArg( py::object obj, const char* functionName, int argIdx = 0, // args are numbered starting from 1 const char* expectedType = NULL) { return extractValueArg( obj, functionName, argIdx, expectedType); } //////////////////////////////////////// template inline typename GridType::Ptr copyGrid(const GridType& grid) { return grid.copy(); } template inline bool sharesWith(const GridType& grid, py::object other) { py::extract x(other); if (x.check()) { typename GridType::ConstPtr otherGrid = x(); return (&otherGrid->tree() == &grid.tree()); } return false; } //////////////////////////////////////// template inline std::string getValueType() { return pyutil::GridTraits::valueTypeName(); } template inline typename GridType::ValueType getZeroValue() { return openvdb::zeroVal(); } template inline typename GridType::ValueType getOneValue() { typedef typename GridType::ValueType ValueT; return ValueT(openvdb::zeroVal() + 1); } template inline bool notEmpty(const GridType& grid) { return !grid.empty(); } template inline typename GridType::ValueType getGridBackground(const GridType& grid) { return grid.background(); } template inline void setGridBackground(GridType& grid, py::object obj) { grid.setBackground(extractValueArg(obj, "setBackground")); } inline void setGridName(GridBase::Ptr grid, py::object strObj) { if (grid) { if (!strObj) { // if name is None grid->removeMeta(GridBase::META_GRID_NAME); } else { const std::string name = pyutil::extractArg( strObj, "setName", /*className=*/NULL, /*argIdx=*/1, "str"); grid->setName(name); } } } inline void setGridCreator(GridBase::Ptr grid, py::object strObj) { if (grid) { if (!strObj) { // if name is None grid->removeMeta(GridBase::META_GRID_CREATOR); } else { const std::string name = pyutil::extractArg( strObj, "setCreator", /*className=*/NULL, /*argIdx=*/1, "str"); grid->setCreator(name); } } } inline std::string getGridClass(GridBase::ConstPtr grid) { return GridBase::gridClassToString(grid->getGridClass()); } inline void setGridClass(GridBase::Ptr grid, py::object strObj) { if (!strObj) { grid->clearGridClass(); } else { const std::string name = pyutil::extractArg( strObj, "setGridClass", /*className=*/NULL, /*argIdx=*/1, "str"); grid->setGridClass(GridBase::stringToGridClass(name)); } } inline std::string getVecType(GridBase::ConstPtr grid) { return GridBase::vecTypeToString(grid->getVectorType()); } inline void setVecType(GridBase::Ptr grid, py::object strObj) { if (!strObj) { grid->clearVectorType(); } else { const std::string name = pyutil::extractArg( strObj, "setVectorType", /*className=*/NULL, /*argIdx=*/1, "str"); grid->setVectorType(GridBase::stringToVecType(name)); } } inline std::string gridInfo(GridBase::ConstPtr grid, int verbosity) { std::ostringstream ostr; grid->print(ostr, std::max(1, verbosity)); return ostr.str(); } //////////////////////////////////////// inline void setGridTransform(GridBase::Ptr grid, py::object xformObj) { if (grid) { if (math::Transform::Ptr xform = pyutil::extractArg( xformObj, "setTransform", /*className=*/NULL, /*argIdx=*/1, "Transform")) { grid->setTransform(xform); } else { PyErr_SetString(PyExc_ValueError, "null transform"); py::throw_error_already_set(); } } } //////////////////////////////////////// // Helper class to construct a pyAccessor::AccessorWrap for a given grid, // permitting partial specialization for const vs. non-const grids template struct AccessorHelper { typedef typename pyAccessor::AccessorWrap Wrapper; static Wrapper wrap(typename GridType::Ptr grid) { if (!grid) { PyErr_SetString(PyExc_ValueError, "null grid"); py::throw_error_already_set(); } return Wrapper(grid); } }; // Specialization for const grids template struct AccessorHelper { typedef typename pyAccessor::AccessorWrap Wrapper; static Wrapper wrap(typename GridType::ConstPtr grid) { if (!grid) { PyErr_SetString(PyExc_ValueError, "null grid"); py::throw_error_already_set(); } return Wrapper(grid); } }; /// Return a non-const accessor (wrapped in a pyAccessor::AccessorWrap) for the given grid. template inline typename AccessorHelper::Wrapper getAccessor(typename GridType::Ptr grid) { return AccessorHelper::wrap(grid); } /// @brief Return a const accessor (wrapped in a pyAccessor::AccessorWrap) for the given grid. /// @internal Note that the grid pointer is non-const, even though the grid is /// treated as const. This is because we don't expose a const grid type in Python. template inline typename AccessorHelper::Wrapper getConstAccessor(typename GridType::Ptr grid) { return AccessorHelper::wrap(grid); } //////////////////////////////////////// template inline py::tuple evalLeafBoundingBox(const GridType& grid) { CoordBBox bbox; grid.tree().evalLeafBoundingBox(bbox); return py::make_tuple(bbox.min(), bbox.max()); } template inline Coord evalLeafDim(const GridType& grid) { Coord dim; grid.tree().evalLeafDim(dim); return dim; } template inline py::tuple evalActiveVoxelBoundingBox(const GridType& grid) { CoordBBox bbox = grid.evalActiveVoxelBoundingBox(); return py::make_tuple(bbox.min(), bbox.max()); } template inline py::tuple getNodeLog2Dims(const GridType& grid) { std::vector dims; grid.tree().getNodeLog2Dims(dims); py::list lst; for (size_t i = 0, N = dims.size(); i < N; ++i) { lst.append(dims[i]); } return py::tuple(lst); } template inline Index treeDepth(const GridType& grid) { return grid.tree().treeDepth(); } template inline Index32 leafCount(const GridType& grid) { return grid.tree().leafCount(); } template inline Index32 nonLeafCount(const GridType& grid) { return grid.tree().nonLeafCount(); } template inline Index64 activeLeafVoxelCount(const GridType& grid) { return grid.tree().activeLeafVoxelCount(); } template inline py::tuple evalMinMax(const GridType& grid) { typename GridType::ValueType vmin, vmax; grid.tree().evalMinMax(vmin, vmax); return py::make_tuple(vmin, vmax); } template inline py::tuple getIndexRange(const GridType& grid) { CoordBBox bbox; grid.tree().getIndexRange(bbox); return py::make_tuple(bbox.min(), bbox.max()); } //template //inline void //expandIndexRange(GridType& grid, py::object coordObj) //{ // Coord xyz = extractValueArg( // coordObj, "expand", 0, "tuple(int, int, int)"); // grid.tree().expand(xyz); //} //////////////////////////////////////// inline py::dict getAllMetadata(GridBase::ConstPtr grid) { if (grid) return py::dict(static_cast(*grid)); return py::dict(); } inline void replaceAllMetadata(GridBase::Ptr grid, const MetaMap& metadata) { if (grid) { grid->clearMetadata(); for (MetaMap::ConstMetaIterator it = metadata.beginMeta(); it != metadata.endMeta(); ++it) { if (it->second) grid->insertMeta(it->first, *it->second); } } } inline void updateMetadata(GridBase::Ptr grid, const MetaMap& metadata) { if (grid) { for (MetaMap::ConstMetaIterator it = metadata.beginMeta(); it != metadata.endMeta(); ++it) { if (it->second) { grid->removeMeta(it->first); grid->insertMeta(it->first, *it->second); } } } } inline py::dict getStatsMetadata(GridBase::ConstPtr grid) { MetaMap::ConstPtr metadata; if (grid) metadata = grid->getStatsMetadata(); if (metadata) return py::dict(*metadata); return py::dict(); } inline py::object getMetadataKeys(GridBase::ConstPtr grid) { if (grid) return py::dict(static_cast(*grid)).iterkeys(); return py::object(); } inline py::object getMetadata(GridBase::ConstPtr grid, py::object nameObj) { if (!grid) return py::object(); const std::string name = pyutil::extractArg( nameObj, "__getitem__", NULL, /*argIdx=*/1, "str"); Metadata::ConstPtr metadata = (*grid)[name]; if (!metadata) { PyErr_SetString(PyExc_KeyError, name.c_str()); py::throw_error_already_set(); } // Use the MetaMap-to-dict converter (see pyOpenVDBModule.cc) to convert // the Metadata value to a Python object of the appropriate type. /// @todo Would be more efficient to convert the Metadata object /// directly to a Python object. MetaMap metamap; metamap.insertMeta(name, *metadata); return py::dict(metamap)[name]; } inline void setMetadata(GridBase::Ptr grid, py::object nameObj, py::object valueObj) { if (!grid) return; const std::string name = pyutil::extractArg( nameObj, "__setitem__", NULL, /*argIdx=*/1, "str"); // Insert the Python object into a Python dict, then use the dict-to-MetaMap // converter (see pyOpenVDBModule.cc) to convert the dict to a MetaMap // containing a Metadata object of the appropriate type. /// @todo Would be more efficient to convert the Python object /// directly to a Metadata object. py::dict dictObj; dictObj[name] = valueObj; MetaMap metamap = py::extract(dictObj); if (Metadata::Ptr metadata = metamap[name]) { grid->removeMeta(name); grid->insertMeta(name, *metadata); } } inline void removeMetadata(GridBase::Ptr grid, const std::string& name) { if (grid) { Metadata::Ptr metadata = (*grid)[name]; if (!metadata) { PyErr_SetString(PyExc_KeyError, name.c_str()); py::throw_error_already_set(); } grid->removeMeta(name); } } inline bool hasMetadata(GridBase::ConstPtr grid, const std::string& name) { if (grid) return ((*grid)[name].get() != NULL); return false; } //////////////////////////////////////// template inline void prune(GridType& grid, py::object tolerance) { grid.tree().prune(extractValueArg(tolerance, "prune")); } template inline void pruneInactive(GridType& grid, py::object valObj) { if (valObj.is_none()) { grid.tree().pruneInactive(); } else { grid.tree().pruneInactive(extractValueArg(valObj, "pruneInactive")); } } template inline void fill(GridType& grid, py::object minObj, py::object maxObj, py::object valObj, bool active) { const Coord bmin = extractValueArg(minObj, "fill", 1, "tuple(int, int, int)"), bmax = extractValueArg(maxObj, "fill", 2, "tuple(int, int, int)"); grid.fill(CoordBBox(bmin, bmax), extractValueArg(valObj, "fill", 3), active); } template inline void signedFloodFill(GridType& grid) { grid.signedFloodFill(); } //////////////////////////////////////// #ifndef PY_OPENVDB_USE_NUMPY template inline void copyFromArray(GridType&, const py::object&, py::object, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); } template inline void copyToArray(GridType&, const py::object&, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); } #else // if defined(PY_OPENVDB_USE_NUMPY) template struct NumPyToCpp {}; //template<> struct NumPyToCpp { typedef half type; }; template<> struct NumPyToCpp { typedef float type; }; template<> struct NumPyToCpp { typedef double type; }; template<> struct NumPyToCpp { typedef bool type; }; template<> struct NumPyToCpp { typedef Int16 type; }; template<> struct NumPyToCpp { typedef Int32 type; }; template<> struct NumPyToCpp { typedef Int64 type; }; template<> struct NumPyToCpp { typedef Index32 type; }; template<> struct NumPyToCpp { typedef Index64 type; }; // Abstract base class for helper classes that copy data between // NumPy arrays of various types and grids of various types template class CopyOpBase { public: typedef typename GridType::ValueType ValueT; CopyOpBase(bool toGrid, GridType& grid, py::object arrObj, py::object coordObj, py::object tolObj) : mToGrid(toGrid) , mGrid(&grid) { const char* const opName[2] = { "copyToArray", "copyFromArray" }; // Extract the coordinates (i, j, k) of the voxel at which to start populating data. // Voxel (i, j, k) will correspond to array element (0, 0, 0). const Coord origin = extractValueArg( coordObj, opName[toGrid], 1, "tuple(int, int, int)"); // Extract a reference to (not a copy of) the NumPy array. const py::numeric::array arrayObj = pyutil::extractArg( arrObj, opName[toGrid], pyutil::GridTraits::name(), /*argIdx=*/1, "numpy.ndarray"); const PyArray_Descr* dtype = PyArray_DESCR(arrayObj.ptr()); const py::object shape = arrayObj.attr("shape"); if (PyObject_HasAttrString(arrayObj.ptr(), "dtype")) { mArrayTypeName = pyutil::str(arrayObj.attr("dtype")); } else { mArrayTypeName = "'_'"; mArrayTypeName[1] = dtype->kind; } mArray = PyArray_DATA(arrayObj.ptr()); mArrayTypeNum = dtype->type_num; mTolerance = extractValueArg(tolObj, opName[toGrid], 2); for (long i = 0, N = py::len(shape); i < N; ++i) { mArrayDims.push_back(py::extract(shape[i])); } // Compute the bounding box of the region of the grid that is to be copied from or to. mBBox.reset(origin, origin.offsetBy(mArrayDims[0]-1, mArrayDims[1]-1, mArrayDims[2]-1)); } virtual ~CopyOpBase() {} void operator()() const { try { if (mToGrid) { copyFromArray(); // copy data from the array to the grid } else { copyToArray(); // copy data from the grid to the array } } catch (openvdb::TypeError&) { PyErr_Format(PyExc_TypeError, "unsupported NumPy data type %s", mArrayTypeName.c_str()); boost::python::throw_error_already_set(); } } protected: virtual void validate() const = 0; virtual void copyFromArray() const = 0; virtual void copyToArray() const = 0; template void fromArray() const { validate(); tools::Dense valArray(mBBox, static_cast(mArray)); tools::copyFromDense(valArray, *mGrid, mTolerance); } template void toArray() const { validate(); tools::Dense valArray(mBBox, static_cast(mArray)); tools::copyToDense(*mGrid, valArray); } bool mToGrid; // if true, copy from the array to the grid, else vice-versa void* mArray; GridType* mGrid; int mArrayTypeNum; std::vector mArrayDims; std::string mArrayTypeName; CoordBBox mBBox; ValueT mTolerance; }; // class CopyOpBase // Helper subclass that can be specialized for various grid and NumPy array types template class CopyOp: public CopyOpBase {}; // Specialization for scalar grids template class CopyOp: public CopyOpBase { public: CopyOp(bool toGrid, GridType& grid, py::object arrObj, py::object coordObj, py::object tolObj = py::object(zeroVal())): CopyOpBase(toGrid, grid, arrObj, coordObj, tolObj) { } protected: virtual void validate() const { if (this->mArrayDims.size() != 3) { std::ostringstream os; os << "expected 3-dimensional array, found " << this->mArrayDims.size() << "-dimensional array"; PyErr_SetString(PyExc_ValueError, os.str().c_str()); boost::python::throw_error_already_set(); } } virtual void copyFromArray() const { switch (this->mArrayTypeNum) { case NPY_FLOAT: this->template fromArray::type>(); break; case NPY_DOUBLE: this->template fromArray::type>(); break; case NPY_BOOL: this->template fromArray::type>(); break; case NPY_INT16: this->template fromArray::type>(); break; case NPY_INT32: this->template fromArray::type>(); break; case NPY_INT64: this->template fromArray::type>(); break; case NPY_UINT32: this->template fromArray::type>(); break; case NPY_UINT64: this->template fromArray::type>(); break; default: throw openvdb::TypeError(); break; } } virtual void copyToArray() const { switch (this->mArrayTypeNum) { case NPY_FLOAT: this->template toArray::type>(); break; case NPY_DOUBLE: this->template toArray::type>(); break; case NPY_BOOL: this->template toArray::type>(); break; case NPY_INT16: this->template toArray::type>(); break; case NPY_INT32: this->template toArray::type>(); break; case NPY_INT64: this->template toArray::type>(); break; case NPY_UINT32: this->template toArray::type>(); break; case NPY_UINT64: this->template toArray::type>(); break; default: throw openvdb::TypeError(); break; } } }; // class CopyOp // Specialization for Vec3 grids template class CopyOp: public CopyOpBase { public: CopyOp(bool toGrid, GridType& grid, py::object arrObj, py::object coordObj, py::object tolObj = py::object(zeroVal())): CopyOpBase(toGrid, grid, arrObj, coordObj, tolObj) { } protected: virtual void validate() const { if (this->mArrayDims.size() != 4) { std::ostringstream os; os << "expected 4-dimensional array, found " << this->mArrayDims.size() << "-dimensional array"; PyErr_SetString(PyExc_ValueError, os.str().c_str()); boost::python::throw_error_already_set(); } if (this->mArrayDims[3] != 3) { std::ostringstream os; os << "expected " << this->mArrayDims[0] << "x" << this->mArrayDims[1] << "x" << this->mArrayDims[2] << "x3 array, found " << this->mArrayDims[0] << "x" << this->mArrayDims[1] << "x" << this->mArrayDims[2] << "x" << this->mArrayDims[3] << " array"; PyErr_SetString(PyExc_ValueError, os.str().c_str()); boost::python::throw_error_already_set(); } } virtual void copyFromArray() const { switch (this->mArrayTypeNum) { case NPY_FLOAT: this->template fromArray::type> >(); break; case NPY_DOUBLE: this->template fromArray::type> >(); break; case NPY_BOOL: this->template fromArray::type> >(); break; case NPY_INT16: this->template fromArray::type> >(); break; case NPY_INT32: this->template fromArray::type> >(); break; case NPY_INT64: this->template fromArray::type> >(); break; case NPY_UINT32: this->template fromArray::type> >(); break; case NPY_UINT64: this->template fromArray::type> >(); break; default: throw openvdb::TypeError(); break; } } virtual void copyToArray() const { switch (this->mArrayTypeNum) { case NPY_FLOAT: this->template toArray::type> >(); break; case NPY_DOUBLE: this->template toArray::type> >(); break; case NPY_BOOL: this->template toArray::type> >(); break; case NPY_INT16: this->template toArray::type> >(); break; case NPY_INT32: this->template toArray::type> >(); break; case NPY_INT64: this->template toArray::type> >(); break; case NPY_UINT32: this->template toArray::type> >(); break; case NPY_UINT64: this->template toArray::type> >(); break; default: throw openvdb::TypeError(); break; } } }; // class CopyOp template inline void copyFromArray(GridType& grid, py::object arrayObj, py::object coordObj, py::object toleranceObj) { typedef typename GridType::ValueType ValueT; CopyOp::Size> op(/*toGrid=*/true, grid, arrayObj, coordObj, toleranceObj); op(); } template inline void copyToArray(GridType& grid, py::object arrayObj, py::object coordObj) { typedef typename GridType::ValueType ValueT; CopyOp::Size> op(/*toGrid=*/false, grid, arrayObj, coordObj); op(); } #endif // defined(PY_OPENVDB_USE_NUMPY) //////////////////////////////////////// template inline void applyMap(const char* methodName, GridType& grid, py::object funcObj) { typedef typename GridType::ValueType ValueT; for (IterType it = grid.tree().template begin(); it; ++it) { // Evaluate the functor. py::object result = funcObj(*it); // Verify that the result is of type GridType::ValueType. py::extract val(result); if (!val.check()) { PyErr_Format(PyExc_TypeError, "expected callable argument to %s.%s() to return %s, found %s", pyutil::GridTraits::name(), methodName, openvdb::typeNameAsString(), pyutil::className(result).c_str()); py::throw_error_already_set(); } it.setValue(val()); } } template inline void mapOn(GridType& grid, py::object funcObj) { applyMap("mapOn", grid, funcObj); } template inline void mapOff(GridType& grid, py::object funcObj) { applyMap("mapOff", grid, funcObj); } template inline void mapAll(GridType& grid, py::object funcObj) { applyMap("mapAll", grid, funcObj); } //////////////////////////////////////// template struct TreeCombineOp { typedef typename GridType::TreeType TreeT; typedef typename GridType::ValueType ValueT; TreeCombineOp(py::object _op): op(_op) {} void operator()(const ValueT& a, const ValueT& b, ValueT& result) { py::object resultObj = op(a, b); py::extract val(resultObj); if (!val.check()) { PyErr_Format(PyExc_TypeError, "expected callable argument to %s.combine() to return %s, found %s", pyutil::GridTraits::name(), openvdb::typeNameAsString(), pyutil::className(resultObj).c_str()); py::throw_error_already_set(); } result = val(); } py::object op; }; template inline void combine(GridType& grid, py::object otherGridObj, py::object funcObj) { typedef typename GridType::Ptr GridPtr; GridPtr otherGrid = extractValueArg(otherGridObj, "combine", 1, pyutil::GridTraits::name()); TreeCombineOp op(funcObj); grid.tree().combine(otherGrid->tree(), op, /*prune=*/true); } //////////////////////////////////////// template inline typename GridType::Ptr createLevelSetSphere(float radius, const openvdb::Vec3f& center, float voxelSize, float halfWidth) { return tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); } //////////////////////////////////////// template class IterWrap; // forward declaration // // Type traits for various iterators // template struct IterTraits { // IterT the type of the iterator // name() function returning the base name of the iterator type (e.g., "ValueOffIter") // descr() function returning a string describing the iterator // begin() function returning a begin iterator for a given grid }; template struct IterTraits { typedef typename GridT::ValueOnCIter IterT; static std::string name() { return "ValueOnCIter"; } static std::string descr() { return std::string("Read-only iterator over the active values (tile and voxel)\nof a ") + pyutil::GridTraits::type>::name(); } static IterWrap begin(typename GridT::Ptr g) { return IterWrap(g, g->cbeginValueOn()); } }; // IterTraits template struct IterTraits { typedef typename GridT::ValueOffCIter IterT; static std::string name() { return "ValueOffCIter"; } static std::string descr() { return std::string("Read-only iterator over the inactive values (tile and voxel)\nof a ") + pyutil::GridTraits::type>::name(); } static IterWrap begin(typename GridT::Ptr g) { return IterWrap(g, g->cbeginValueOff()); } }; // IterTraits template struct IterTraits { typedef typename GridT::ValueAllCIter IterT; static std::string name() { return "ValueAllCIter"; } static std::string descr() { return std::string("Read-only iterator over all tile and voxel values of a ") + pyutil::GridTraits::type>::name(); } static IterWrap begin(typename GridT::Ptr g) { return IterWrap(g, g->cbeginValueAll()); } }; // IterTraits template struct IterTraits { typedef typename GridT::ValueOnIter IterT; static std::string name() { return "ValueOnIter"; } static std::string descr() { return std::string("Read/write iterator over the active values (tile and voxel)\nof a ") + pyutil::GridTraits::type>::name(); } static IterWrap begin(typename GridT::Ptr g) { return IterWrap(g, g->beginValueOn()); } }; // IterTraits template struct IterTraits { typedef typename GridT::ValueOffIter IterT; static std::string name() { return "ValueOffIter"; } static std::string descr() { return std::string("Read/write iterator over the inactive values (tile and voxel)\nof a ") + pyutil::GridTraits::type>::name(); } static IterWrap begin(typename GridT::Ptr g) { return IterWrap(g, g->beginValueOff()); } }; // IterTraits template struct IterTraits { typedef typename GridT::ValueAllIter IterT; static std::string name() { return "ValueAllIter"; } static std::string descr() { return std::string("Read/write iterator over all tile and voxel values of a ") + pyutil::GridTraits::type>::name(); } static IterWrap begin(typename GridT::Ptr g) { return IterWrap(g, g->beginValueAll()); } }; // IterTraits //////////////////////////////////////// // Helper class to modify a grid through a non-const iterator template struct IterItemSetter { typedef typename GridT::ValueType ValueT; static void setValue(const IterT& iter, const ValueT& val) { iter.setValue(val); } static void setActive(const IterT& iter, bool on) { iter.setActiveState(on); } }; // Partial specialization for const iterators template struct IterItemSetter { typedef typename GridT::ValueType ValueT; static void setValue(const IterT& iter, const ValueT& val) { PyErr_SetString(PyExc_AttributeError, "can't set attribute 'value'"); py::throw_error_already_set(); } static void setActive(const IterT& iter, bool on) { PyErr_SetString(PyExc_AttributeError, "can't set attribute 'active'"); py::throw_error_already_set(); } }; /// @brief Value returned by the next() method of a grid's value iterator /// @details This class allows both dictionary-style (e.g., items["depth"]) and /// attribute access (e.g., items.depth) to the items returned by an iterator. /// @todo Create a reusable base class for "named dicts" like this? template class IterValueProxy { public: typedef _GridT GridT; typedef _IterT IterT; typedef typename GridT::ValueType ValueT; typedef IterItemSetter SetterT; IterValueProxy(typename GridT::ConstPtr grid, const IterT& iter): mGrid(grid), mIter(iter) {} IterValueProxy copy() const { return *this; } typename GridT::ConstPtr parent() const { return mGrid; } ValueT getValue() const { return *mIter; } bool getActive() const { return mIter.isValueOn(); } Index getDepth() const { return mIter.getDepth(); } Coord getBBoxMin() const { return mIter.getBoundingBox().min(); } Coord getBBoxMax() const { return mIter.getBoundingBox().max(); } Index64 getVoxelCount() const { return mIter.getVoxelCount(); } void setValue(const ValueT& val) { SetterT::setValue(mIter, val); } void setActive(bool on) { SetterT::setActive(mIter, on); } /// Return this dictionary's keys as a list of C strings. static const char* const * keys() { static const char* const sKeys[] = { "value", "active", "depth", "min", "max", "count", NULL }; return sKeys; } /// Return @c true if the given string is a valid key. static bool hasKey(const std::string& key) { for (int i = 0; keys()[i] != NULL; ++i) { if (key == keys()[i]) return true; } return false; } /// Return this dictionary's keys as a Python list of Python strings. static py::list getKeys() { py::list keyList; for (int i = 0; keys()[i] != NULL; ++i) keyList.append(keys()[i]); return keyList; } /// @brief Return the value for the given key. /// @throw KeyError if the key is invalid py::object getItem(py::object keyObj) const { py::extract x(keyObj); if (x.check()) { const std::string key = x(); if (key == "value") return py::object(this->getValue()); else if (key == "active") return py::object(this->getActive()); else if (key == "depth") return py::object(this->getDepth()); else if (key == "min") return py::object(this->getBBoxMin()); else if (key == "max") return py::object(this->getBBoxMax()); else if (key == "count") return py::object(this->getVoxelCount()); } PyErr_SetObject(PyExc_KeyError, ("%s" % keyObj.attr("__repr__")()).ptr()); py::throw_error_already_set(); return py::object(); } /// @brief Set the value for the given key. /// @throw KeyError if the key is invalid /// @throw AttributeError if the key refers to a read-only item void setItem(py::object keyObj, py::object valObj) { py::extract x(keyObj); if (x.check()) { const std::string key = x(); if (key == "value") { this->setValue(py::extract(valObj)); return; } else if (key == "active") { this->setActive(py::extract(valObj)); return; } else if (this->hasKey(key)) { PyErr_SetObject(PyExc_AttributeError, ("can't set attribute '%s'" % keyObj.attr("__repr__")()).ptr()); py::throw_error_already_set(); } } PyErr_SetObject(PyExc_KeyError, ("'%s'" % keyObj.attr("__repr__")()).ptr()); py::throw_error_already_set(); } bool operator==(const IterValueProxy& other) const { return (other.getActive() == this->getActive() && other.getDepth() == this->getDepth() && other.getValue() == this->getValue() && other.getBBoxMin() == this->getBBoxMin() && other.getBBoxMax() == this->getBBoxMax() && other.getVoxelCount() == this->getVoxelCount()); } bool operator!=(const IterValueProxy& other) const { return !(*this == other); } /// Print this dictionary to a stream. std::ostream& put(std::ostream& os) const { // valuesAsStrings = ["%s: %s" % key, repr(this[key]) for key in this.keys()] py::list valuesAsStrings; for (int i = 0; this->keys()[i] != NULL; ++i) { py::str key(this->keys()[i]), val(this->getItem(key).attr("__repr__")()); valuesAsStrings.append("'%s': %s" % py::make_tuple(key, val)); } // print ", ".join(valuesAsStrings) py::object joined = py::str(", ").attr("join")(valuesAsStrings); std::string s = py::extract(joined); os << "{" << s << "}"; return os; } /// Return a string describing this dictionary. std::string info() const { std::ostringstream os; os << *this; return os.str(); } private: // To keep the iterator's grid from being deleted (leaving the iterator dangling), // store a shared pointer to the grid. const typename GridT::ConstPtr mGrid; const IterT mIter; // the iterator may not be incremented }; // class IterValueProxy template inline std::ostream& operator<<(std::ostream& os, const IterValueProxy& iv) { return iv.put(os); } //////////////////////////////////////// /// Wrapper for a grid's value iterator classes template class IterWrap { public: typedef _GridT GridT; typedef _IterT IterT; typedef typename GridT::ValueType ValueT; typedef IterValueProxy IterValueProxyT; typedef IterTraits Traits; IterWrap(typename GridT::ConstPtr grid, const IterT& iter): mGrid(grid), mIter(iter) {} typename GridT::ConstPtr parent() const { return mGrid; } /// Return an IterValueProxy for the current iterator position. IterValueProxyT next() { if (!mIter) { PyErr_SetString(PyExc_StopIteration, "no more values"); py::throw_error_already_set(); } IterValueProxyT result(mGrid, mIter); ++mIter; return result; } static py::object returnSelf(const py::object& obj) { return obj; } /// @brief Define a Python wrapper class for this C++ class and another for /// the IterValueProxy class returned by iterators of this type. static void wrap() { const std::string gridClassName = pyutil::GridTraits::type>::name(), iterClassName = /*gridClassName +*/ Traits::name(), valueClassName = /*gridClassName +*/ "Value"; py::class_( iterClassName.c_str(), /*docstring=*/Traits::descr().c_str(), /*ctor=*/py::no_init) // can only be instantiated from C++, not from Python .add_property("parent", &IterWrap::parent, ("the " + gridClassName + " over which to iterate").c_str()) .def("next", &IterWrap::next, ("next() -> " + valueClassName).c_str()) .def("__iter__", &returnSelf); py::class_( valueClassName.c_str(), /*docstring=*/("Proxy for a tile or voxel value in a " + gridClassName).c_str(), /*ctor=*/py::no_init) // can only be instantiated from C++, not from Python .def("copy", &IterValueProxyT::copy, ("copy() -> " + valueClassName + "\n\n" "Return a shallow copy of this value, i.e., one that shares\n" "its data with the original.").c_str()) .add_property("parent", &IterValueProxyT::parent, ("the " + gridClassName + " to which this value belongs").c_str()) .def("__str__", &IterValueProxyT::info) .def("__repr__", &IterValueProxyT::info) .def("__eq__", &IterValueProxyT::operator==) .def("__ne__", &IterValueProxyT::operator!=) .add_property("value", &IterValueProxyT::getValue, &IterValueProxyT::setValue, "value of this tile or voxel") .add_property("active", &IterValueProxyT::getActive, &IterValueProxyT::setActive, "active state of this tile or voxel") .add_property("depth", &IterValueProxyT::getDepth, "tree depth at which this value is stored") .add_property("min", &IterValueProxyT::getBBoxMin, "lower bound of the axis-aligned bounding box of this tile or voxel") .add_property("max", &IterValueProxyT::getBBoxMax, "upper bound of the axis-aligned bounding box of this tile or voxel") .add_property("count", &IterValueProxyT::getVoxelCount, "number of voxels spanned by this value") .def("keys", &IterValueProxyT::getKeys, "keys() -> list\n\n" "Return a list of keys for this tile or voxel.") .staticmethod("keys") .def("__contains__", &IterValueProxyT::hasKey, "__contains__(key) -> bool\n\n" "Return True if the given key exists.") .staticmethod("__contains__") .def("__getitem__", &IterValueProxyT::getItem, "__getitem__(key) -> value\n\n" "Return the value of the item with the given key.") .def("__setitem__", &IterValueProxyT::getItem, "__setitem__(key, value)\n\n" "Set the value of the item with the given key."); } private: // To keep this iterator's grid from being deleted, leaving the iterator dangling, // store a shared pointer to the grid. const typename GridT::ConstPtr mGrid; IterT mIter; }; // class IterWrap //////////////////////////////////////// template struct PickleSuite: py::pickle_suite { typedef typename GridT::Ptr GridPtrT; /// Return @c true, indicating that this pickler preserves a Grid's __dict__. static bool getstate_manages_dict() { return true; } /// Return a tuple representing the state of the given Grid. static py::tuple getstate(py::object gridObj) { py::tuple state; // Extract a Grid from the Python object. GridPtrT grid; py::extract x(gridObj); if (x.check()) grid = x(); if (grid) { // Serialize the Grid to a string. std::ostringstream ostr(std::ios_base::binary); { openvdb::io::Stream strm(ostr); strm.setGridStatsMetadataEnabled(false); strm.write(openvdb::GridPtrVec(1, grid)); } // Construct a state tuple comprising the Python object's __dict__ // and the serialized Grid. state = py::make_tuple(gridObj.attr("__dict__"), ostr.str()); } return state; } /// Restore the given Grid to a saved state. static void setstate(py::object gridObj, py::object stateObj) { GridPtrT grid; { py::extract x(gridObj); if (x.check()) grid = x(); } if (!grid) return; py::tuple state; { py::extract x(stateObj); if (x.check()) state = x(); } bool badState = (py::len(state) != 2); if (!badState) { // Restore the object's __dict__. py::extract x(state[0]); if (x.check()) { py::dict d = py::extract(gridObj.attr("__dict__"))(); d.update(x()); } else { badState = true; } } std::string serialized; if (!badState) { // Extract the string containing the serialized Grid. py::extract x(state[1]); if (x.check()) serialized = x(); else badState = true; } if (badState) { PyErr_SetObject(PyExc_ValueError, ("expected (dict, str) tuple in call to __setstate__; found %s" % stateObj.attr("__repr__")()).ptr()); py::throw_error_already_set(); } // Restore the internal state of the C++ object. GridPtrVecPtr grids; { std::istringstream istr(serialized, std::ios_base::binary); io::Stream strm(istr); grids = strm.getGrids(); // (note: file-level metadata is ignored) } if (grids && !grids->empty()) { if (GridPtrT savedGrid = gridPtrCast((*grids)[0])) { grid->MetaMap::operator=(*savedGrid); ///< @todo add a Grid::setMetadata() method? grid->setTransform(savedGrid->transformPtr()); grid->setTree(savedGrid->treePtr()); } } } }; // struct PickleSuite //////////////////////////////////////// /// Create a Python wrapper for a particular template instantiation of Grid. template inline void exportGrid() { typedef typename GridType::ValueType ValueT; typedef typename GridType::Ptr GridPtr; typedef pyutil::GridTraits Traits; typedef typename GridType::ValueOnCIter ValueOnCIterT; typedef typename GridType::ValueOffCIter ValueOffCIterT; typedef typename GridType::ValueAllCIter ValueAllCIterT; typedef typename GridType::ValueOnIter ValueOnIterT; typedef typename GridType::ValueOffIter ValueOffIterT; typedef typename GridType::ValueAllIter ValueAllIterT; math::Transform::Ptr (GridType::*getTransform)() = &GridType::transformPtr; const std::string pyGridTypeName = Traits::name(); const std::string defaultCtorDescr = "Initialize with a background value of " + pyutil::str(pyGrid::getZeroValue()) + "."; // Define the Grid wrapper class and make it the current scope. { py::class_ clss( /*classname=*/pyGridTypeName.c_str(), /*docstring=*/(Traits::descr()).c_str(), /*ctor=*/py::init<>(defaultCtorDescr.c_str()) ); py::scope gridClassScope = clss; clss.def(py::init(py::args("background"), "Initialize with the given background value.")) .def("copy", &pyGrid::copyGrid, ("copy() -> " + pyGridTypeName + "\n\n" "Return a shallow copy of this grid, i.e., a grid\n" "that shares its voxel data with this grid.").c_str()) .def("deepCopy", &GridType::deepCopy, ("deepCopy() -> " + pyGridTypeName + "\n\n" "Return a deep copy of this grid.\n").c_str()) .def_pickle(pyGrid::PickleSuite()) .def("sharesWith", &pyGrid::sharesWith, ("sharesWith(" + pyGridTypeName + ") -> bool\n\n" "Return True if this grid shares its voxel data with the given grid.").c_str()) /// @todo Any way to set a docstring for a class property? .add_static_property("valueTypeName", &pyGrid::getValueType) /// @todo docstring = "name of this grid's value type" .add_static_property("zeroValue", &pyGrid::getZeroValue) /// @todo docstring = "zero, as expressed in this grid's value type" .add_static_property("oneValue", &pyGrid::getOneValue) /// @todo docstring = "one, as expressed in this grid's value type" /// @todo Is Grid.typeName ever needed? //.add_static_property("typeName", &GridType::gridType) /// @todo docstring = to "name of this grid's type" .add_property("background", &pyGrid::getGridBackground, &pyGrid::setGridBackground, "value of this grid's background voxels") .add_property("name", &GridType::getName, &pyGrid::setGridName, "this grid's name") .add_property("creator", &GridType::getCreator, &pyGrid::setGridCreator, "description of this grid's creator") .add_property("transform", getTransform, &pyGrid::setGridTransform, "transform associated with this grid") .add_property("gridClass", &pyGrid::getGridClass, &pyGrid::setGridClass, "the class of volumetric data (level set, fog volume, etc.)\nstored in this grid") .add_property("vectorType", &pyGrid::getVecType, &pyGrid::setVecType, "how transforms are applied to values stored in this grid") .def("getAccessor", &pyGrid::getAccessor, ("getAccessor() -> " + pyGridTypeName + "Accessor\n\n" "Return an accessor that provides random read and write access\n" "to this grid's voxels.").c_str()) .def("getConstAccessor", &pyGrid::getConstAccessor, ("getConstAccessor() -> " + pyGridTypeName + "Accessor\n\n" "Return an accessor that provides random read-only access\n" "to this grid's voxels.").c_str()) // // Metadata // .add_property("metadata", &pyGrid::getAllMetadata, &pyGrid::replaceAllMetadata, "dict of this grid's metadata\n\n" "Setting this attribute replaces all of this grid's metadata,\n" "but mutating it in place has no effect on the grid, since\n" "the value of this attribute is a only a copy of the metadata.\n" "Use either indexing or updateMetadata() to mutate metadata in place.") .def("updateMetadata", &pyGrid::updateMetadata, "updateMetadata(dict)\n\n" "Add metadata to this grid, replacing any existing items\n" "having the same names as the new items.") .def("addStatsMetadata", &GridType::addStatsMetadata, "addStatsMetadata()\n\n" "Add metadata to this grid comprising the current values\n" "of statistics like the active voxel count and bounding box.\n" "(This metadata is not automatically kept up-to-date with\n" "changes to this grid.)") .def("getStatsMetadata", &pyGrid::getStatsMetadata, "getStatsMetadata() -> dict\n\n" "Return a (possibly empty) dict containing just the metadata\n" "that was added to this grid with addStatsMetadata().") .def("__getitem__", &pyGrid::getMetadata, "__getitem__(name) -> value\n\n" "Return the metadata value associated with the given name.") .def("__setitem__", &pyGrid::setMetadata, "__setitem__(name, value)\n\n" "Add metadata to this grid, replacing any existing item having\n" "the same name as the new item.") .def("__delitem__", &pyGrid::removeMetadata, "__delitem__(name)\n\n" "Remove the metadata with the given name.") .def("__contains__", &pyGrid::hasMetadata, "__contains__(name) -> bool\n\n" "Return True if this grid contains metadata with the given name.") .def("__iter__", &pyGrid::getMetadataKeys, "__iter__() -> iterator\n\n" "Return an iterator over this grid's metadata keys.") .def("iterkeys", &pyGrid::getMetadataKeys, "iterkeys() -> iterator\n\n" "Return an iterator over this grid's metadata keys.") .add_property("saveFloatAsHalf", &GridType::saveFloatAsHalf, &GridType::setSaveFloatAsHalf, "if True, write floating-point voxel values as 16-bit half floats") // // Statistics // .def("memUsage", &GridType::memUsage, "memUsage() -> int\n\n" "Return the memory usage of this grid in bytes.") .def("evalLeafBoundingBox", &pyGrid::evalLeafBoundingBox, "evalLeafBoundingBox() -> xyzMin, xyzMax\n\n" "Return the coordinates of opposite corners of the axis-aligned\n" "bounding box of all leaf nodes.") .def("evalLeafDim", &pyGrid::evalLeafDim, "evalLeafDim() -> x, y, z\n\n" "Return the dimensions of the axis-aligned bounding box\n" "of all leaf nodes.") .def("evalActiveVoxelBoundingBox", &pyGrid::evalActiveVoxelBoundingBox, "evalActiveVoxelBoundingBox() -> xyzMin, xyzMax\n\n" "Return the coordinates of opposite corners of the axis-aligned\n" "bounding box of all active voxels.") .def("evalActiveVoxelDim", &GridType::evalActiveVoxelDim, "evalActiveVoxelDim() -> x, y, z\n\n" "Return the dimensions of the axis-aligned bounding box of all\n" "active voxels.") .add_property("treeDepth", &pyGrid::treeDepth, "depth of this grid's tree from root node to leaf node") .def("nodeLog2Dims", &pyGrid::getNodeLog2Dims, "list of Log2Dims of the nodes of this grid's tree\n" "in order from root to leaf") .def("leafCount", &pyGrid::leafCount, "leafCount() -> int\n\n" "Return the number of leaf nodes in this grid's tree.") .def("nonLeafCount", &pyGrid::nonLeafCount, "nonLeafCount() -> int\n\n" "Return the number of non-leaf nodes in this grid's tree.") .def("activeVoxelCount", &GridType::activeVoxelCount, "activeVoxelCount() -> int\n\n" "Return the number of active voxels in this grid.") .def("activeLeafVoxelCount", &pyGrid::activeLeafVoxelCount, "activeLeafVoxelCount() -> int\n\n" "Return the number of active voxels that are stored\n" "in the leaf nodes of this grid's tree.") .def("evalMinMax", &pyGrid::evalMinMax, "evalMinMax() -> min, max\n\n" "Return the minimum and maximum active values in this grid.") .def("getIndexRange", &pyGrid::getIndexRange, "getIndexRange() -> min, max\n\n" "Return the minimum and maximum coordinates that are represented\n" "in this grid. These might include background voxels.") //.def("expand", &pyGrid::expandIndexRange, // py::arg("xyz"), // "expand(xyz)\n\n" // "Expand this grid's index range to include the given coordinates.") .def("info", &pyGrid::gridInfo, py::arg("verbosity")=1, "info(verbosity=1) -> str\n\n" "Return a string containing information about this grid\n" "with a specified level of verbosity.\n") // // Tools // .def("fill", &pyGrid::fill, (py::arg("min"), py::arg("max"), py::arg("value"), py::arg("active")=true), "fill(min, max, value, active=True)\n\n" "Set all voxels within a given axis-aligned box to\n" "a constant value (either active or inactive).") .def("signedFloodFill", &pyGrid::signedFloodFill, "signedFloodFill()\n\n" "Propagate the sign from a narrow-band level set into inactive\n" "voxels and tiles.") .def("copyFromArray", &pyGrid::copyFromArray, (py::arg("array"), py::arg("ijk")=Coord(0), py::arg("tolerance")=pyGrid::getZeroValue()), ("copyFromArray(array, ijk=(0, 0, 0), tolerance=0)\n\n" "Populate this grid, starting at voxel (i, j, k), with values\nfrom a " + std::string(openvdb::VecTraits::IsVec ? "four" : "three") + "-dimensional array. Mark voxels as inactive\n" "if and only if their values are equal to this grid's\n" "background value within the given tolerance.").c_str()) .def("copyToArray", &pyGrid::copyToArray, (py::arg("array"), py::arg("ijk")=Coord(0)), ("copyToArray(array, ijk=(0, 0, 0))\n\nPopulate a " + std::string(openvdb::VecTraits::IsVec ? "four" : "three") + "-dimensional array with values\n" "from this grid, starting at voxel (i, j, k).").c_str()) .def("prune", &pyGrid::prune, (py::arg("tolerance")=0), "prune(tolerance=0)\n\n" "Remove nodes whose values all have the same active state\n" "and are equal to within a given tolerance.") .def("pruneInactive", &pyGrid::pruneInactive, (py::arg("value")=py::object()), "pruneInactive(value=None)\n\n" "Remove nodes whose values are all inactive and replace them\n" "with either background tiles or tiles of the given value\n" "(if the value is not None).") .def("empty", &GridType::empty, "empty() -> bool\n\n" "Return True if this grid contains only background voxels.") .def("__nonzero__", &pyGrid::notEmpty) .def("clear", &GridType::clear, "clear()\n\n" "Remove all tiles from this grid and all nodes other than the root node.") .def("merge", &GridType::merge, ("merge(" + pyGridTypeName + ")\n\n" "Move child nodes from the other grid into this grid wherever\n" "those nodes correspond to constant-value tiles in this grid,\n" "and replace leaf-level inactive voxels in this grid with\n" "corresponding voxels in the other grid that are active.\n\n" "Note: this operation always empties the other grid.").c_str()) .def("mapOn", &pyGrid::mapOn, py::arg("function"), "mapOn(function)\n\n" "Iterate over all the active (\"on\") values (tile and voxel)\n" "of this grid and replace each value with function(value).\n\n" "Example: grid.mapOn(lambda x: x * 2 if x < 0.5 else x)") .def("mapOff", &pyGrid::mapOff, py::arg("function"), "mapOff(function)\n\n" "Iterate over all the inactive (\"off\") values (tile and voxel)\n" "of this grid and replace each value with function(value).\n\n" "Example: grid.mapOff(lambda x: x * 2 if x < 0.5 else x)") .def("mapAll", &pyGrid::mapAll, py::arg("function"), "mapAll(function)\n\n" "Iterate over all values (tile and voxel) of this grid\n" "and replace each value with function(value).\n\n" "Example: grid.mapAll(lambda x: x * 2 if x < 0.5 else x)") .def("combine", &pyGrid::combine, (py::arg("grid"), py::arg("function")), "combine(grid, function)\n\n" "Compute function(self, other) over all corresponding pairs\n" "of values (tile or voxel) of this grid and the other grid\n" "and store the result in this grid.\n\n" "Note: this operation always empties the other grid.\n\n" "Example: grid.combine(otherGrid, lambda a, b: min(a, b))") // // Iterators // .def("citerOnValues", &pyGrid::IterTraits::begin, "citerOnValues() -> iterator\n\n" "Return a read-only iterator over this grid's active\ntile and voxel values.") .def("citerOffValues", &pyGrid::IterTraits::begin, "iterOffValues() -> iterator\n\n" "Return a read-only iterator over this grid's inactive\ntile and voxel values.") .def("citerAllValues", &pyGrid::IterTraits::begin, "iterAllValues() -> iterator\n\n" "Return a read-only iterator over all of this grid's\ntile and voxel values.") .def("iterOnValues", &pyGrid::IterTraits::begin, "iterOnValues() -> iterator\n\n" "Return a read/write iterator over this grid's active\ntile and voxel values.") .def("iterOffValues", &pyGrid::IterTraits::begin, "iterOffValues() -> iterator\n\n" "Return a read/write iterator over this grid's inactive\ntile and voxel values.") .def("iterAllValues", &pyGrid::IterTraits::begin, "iterAllValues() -> iterator\n\n" "Return a read/write iterator over all of this grid's\ntile and voxel values.") ; // py::class_ py::implicitly_convertible(); py::implicitly_convertible(); /// @todo Is there a way to implicitly convert GridType references to GridBase /// references without wrapping the GridBase class? The following doesn't compile, /// because GridBase has pure virtual functions: /// @code /// py::implicitly_convertible(); /// @endcode // Wrap const and non-const value accessors and expose them // as nested classes of the Grid class. pyAccessor::AccessorWrap::wrap(); pyAccessor::AccessorWrap::wrap(); // Wrap tree value iterators and expose them as nested classes of the Grid class. IterWrap::wrap(); IterWrap::wrap(); IterWrap::wrap(); IterWrap::wrap(); IterWrap::wrap(); IterWrap::wrap(); } // gridClassScope // Add the Python type object for this grid type to the module-level list. py::extract(py::scope().attr("GridTypes"))().append( py::scope().attr(pyGridTypeName.c_str())); } } // namespace pyGrid #endif // OPENVDB_PYGRID_HAS_BEEN_INCLUDED // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyVec3Grid.cc0000644000000000000000000000406212252453157015030 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// // /// @file pyVec3Grid.cc /// @brief Boost.Python wrappers for vector-valued openvdb::Grid types #include "pyGrid.h" void exportVec3Grid() { pyGrid::exportGrid(); #ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES pyGrid::exportGrid(); pyGrid::exportGrid(); #endif } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyOpenVDBModule.cc0000644000000000000000000006404712252453157016036 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include // for strncmp(), strrchr(), etc. #include #include #include #include #include "openvdb/openvdb.h" #include "pyopenvdb.h" #include "pyGrid.h" #include "pyutil.h" namespace py = boost::python; // Forward declarations void exportTransform(); void exportMetadata(); void exportFloatGrid(); void exportIntGrid(); void exportVec3Grid(); namespace _openvdbmodule { using namespace openvdb; /// Helper class to convert between a Python numeric sequence /// (tuple, list, etc.) and an openvdb::Coord struct CoordConverter { /// @return a Python tuple object equivalent to the given Coord. static PyObject* convert(const openvdb::Coord& xyz) { py::object obj = py::make_tuple(xyz[0], xyz[1], xyz[2]); Py_INCREF(obj.ptr()); ///< @todo is this the right way to ensure that the object ///< doesn't get freed on exit? return obj.ptr(); } /// @return NULL if the given Python object is not convertible to a Coord. static void* convertible(PyObject* obj) { if (!PySequence_Check(obj)) return NULL; // not a Python sequence Py_ssize_t len = PySequence_Length(obj); if (len != 3 && len != 1) return NULL; // not the right length return obj; } /// Convert from a Python object to a Coord. static void construct(PyObject* obj, py::converter::rvalue_from_python_stage1_data* data) { // Construct a Coord in the provided memory location. typedef py::converter::rvalue_from_python_storage StorageT; void* storage = reinterpret_cast(data)->storage.bytes; new (storage) openvdb::Coord; // placement new data->convertible = storage; openvdb::Coord* xyz = static_cast(storage); // Populate the Coord. switch (PySequence_Length(obj)) { case 1: xyz->reset(pyutil::getSequenceItem(obj, 0)); break; case 3: xyz->reset( pyutil::getSequenceItem(obj, 0), pyutil::getSequenceItem(obj, 1), pyutil::getSequenceItem(obj, 2)); break; default: PyErr_Format(PyExc_ValueError, "expected a sequence of three integers"); py::throw_error_already_set(); break; } } /// Register both the Coord-to-tuple and the sequence-to-Coord converters. static void registerConverter() { py::to_python_converter(); py::converter::registry::push_back( &CoordConverter::convertible, &CoordConverter::construct, py::type_id()); } }; // struct CoordConverter /// @todo CoordBBoxConverter? //////////////////////////////////////// /// Helper class to convert between a Python numeric sequence /// (tuple, list, etc.) and an openvdb::Vec template struct VecConverter { static PyObject* convert(const VecT& v) { py::object obj; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN switch (VecT::size) { // compile-time constant case 2: obj = py::make_tuple(v[0], v[1]); break; case 3: obj = py::make_tuple(v[0], v[1], v[2]); break; case 4: obj = py::make_tuple(v[0], v[1], v[2], v[3]); break; default: { py::list lst; for (int n = 0; n < VecT::size; ++n) lst.append(v[n]); obj = lst; } } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END Py_INCREF(obj.ptr()); return obj.ptr(); } static void* convertible(PyObject* obj) { if (!PySequence_Check(obj)) return NULL; // not a Python sequence Py_ssize_t len = PySequence_Length(obj); if (len != VecT::size) return NULL; // Check that all elements of the Python sequence are convertible // to the Vec's value type. py::object seq = pyutil::pyBorrow(obj); for (int i = 0; i < VecT::size; ++i) { if (!py::extract(seq[i]).check()) { return NULL; } } return obj; } static void construct(PyObject* obj, py::converter::rvalue_from_python_stage1_data* data) { // Construct a Vec in the provided memory location. typedef py::converter::rvalue_from_python_storage StorageT; void* storage = reinterpret_cast(data)->storage.bytes; new (storage) VecT; // placement new data->convertible = storage; VecT* v = static_cast(storage); // Populate the vector. for (int n = 0; n < VecT::size; ++n) { (*v)[n] = pyutil::getSequenceItem(obj, n); } } static void registerConverter() { py::to_python_converter >(); py::converter::registry::push_back( &VecConverter::convertible, &VecConverter::construct, py::type_id()); } }; // struct VecConverter //////////////////////////////////////// /// Helper class to convert between a Python dict and an openvdb::MetaMap /// @todo Consider implementing a separate, templated converter for /// the various Metadata types. struct MetaMapConverter { static PyObject* convert(const MetaMap& metaMap) { py::dict ret; for (MetaMap::ConstMetaIterator it = metaMap.beginMeta(); it != metaMap.endMeta(); ++it) { if (Metadata::Ptr meta = it->second) { py::object obj(meta); const std::string typeName = meta->typeName(); if (typeName == StringMetadata::staticTypeName()) { obj = py::str(static_cast(*meta).value()); } else if (typeName == DoubleMetadata::staticTypeName()) { obj = py::object(static_cast(*meta).value()); } else if (typeName == FloatMetadata::staticTypeName()) { obj = py::object(static_cast(*meta).value()); } else if (typeName == Int32Metadata::staticTypeName()) { obj = py::object(static_cast(*meta).value()); } else if (typeName == Int64Metadata::staticTypeName()) { obj = py::object(static_cast(*meta).value()); } else if (typeName == BoolMetadata::staticTypeName()) { obj = py::object(static_cast(*meta).value()); } else if (typeName == Vec2DMetadata::staticTypeName()) { const Vec2d v = static_cast(*meta).value(); obj = py::make_tuple(v[0], v[1]); } else if (typeName == Vec2IMetadata::staticTypeName()) { const Vec2i v = static_cast(*meta).value(); obj = py::make_tuple(v[0], v[1]); } else if (typeName == Vec2SMetadata::staticTypeName()) { const Vec2s v = static_cast(*meta).value(); obj = py::make_tuple(v[0], v[1]); } else if (typeName == Vec3DMetadata::staticTypeName()) { const Vec3d v = static_cast(*meta).value(); obj = py::make_tuple(v[0], v[1], v[2]); } else if (typeName == Vec3IMetadata::staticTypeName()) { const Vec3i v = static_cast(*meta).value(); obj = py::make_tuple(v[0], v[1], v[2]); } else if (typeName == Vec3SMetadata::staticTypeName()) { const Vec3s v = static_cast(*meta).value(); obj = py::make_tuple(v[0], v[1], v[2]); } ret[it->first] = obj; } } Py_INCREF(ret.ptr()); return ret.ptr(); } static void* convertible(PyObject* obj) { return (PyMapping_Check(obj) ? obj : NULL); } static void construct(PyObject* obj, py::converter::rvalue_from_python_stage1_data* data) { // Construct a MetaMap in the provided memory location. typedef py::converter::rvalue_from_python_storage StorageT; void* storage = reinterpret_cast(data)->storage.bytes; new (storage) MetaMap; // placement new data->convertible = storage; MetaMap* metaMap = static_cast(storage); // Populate the map. py::dict pyDict(pyutil::pyBorrow(obj)); py::list keys = pyDict.keys(); for (size_t i = 0, N = py::len(keys); i < N; ++i) { std::string name; py::object key = keys[i]; if (py::extract(key).check()) { name = py::extract(key); } else { const std::string keyAsStr = py::extract(key.attr("__str__")()), keyType = pyutil::className(key); PyErr_Format(PyExc_TypeError, "expected string as metadata name, found object" " \"%s\" of type %s", keyAsStr.c_str(), keyType.c_str()); py::throw_error_already_set(); } // Note: the order of the following tests is significant, as it // avoids unnecessary type promotion (e.g., of ints to floats). py::object val = pyDict[keys[i]]; Metadata::Ptr value; if (py::extract(val).check()) { value.reset( new StringMetadata(py::extract(val))); } else if (PyInt_Check(val.ptr()) && PyInt_AsLong(val.ptr()) <= std::numeric_limits::max() && PyInt_AsLong(val.ptr()) >= std::numeric_limits::min()) { value.reset(new Int32Metadata(py::extract(val))); } else if (PyInt_Check(val.ptr()) || PyLong_Check(val.ptr())) { value.reset(new Int64Metadata(py::extract(val))); //} else if (py::extract(val).check()) { // value.reset(new FloatMetadata(py::extract(val))); } else if (py::extract(val).check()) { value.reset(new DoubleMetadata(py::extract(val))); } else if (py::extract(val).check()) { value.reset(new Vec2IMetadata(py::extract(val))); } else if (py::extract(val).check()) { value.reset(new Vec2DMetadata(py::extract(val))); } else if (py::extract(val).check()) { value.reset(new Vec2SMetadata(py::extract(val))); } else if (py::extract(val).check()) { value.reset(new Vec3IMetadata(py::extract(val))); } else if (py::extract(val).check()) { value.reset(new Vec3DMetadata(py::extract(val))); } else if (py::extract(val).check()) { value.reset(new Vec3SMetadata(py::extract(val))); } else if (py::extract(val).check()) { value = py::extract(val); } else { const std::string valAsStr = py::extract(val.attr("__str__")()), valType = pyutil::className(val); PyErr_Format(PyExc_TypeError, "metadata value \"%s\" of type %s is not allowed", valAsStr.c_str(), valType.c_str()); py::throw_error_already_set(); } if (value) metaMap->insertMeta(name, *value); } } static void registerConverter() { py::to_python_converter(); py::converter::registry::push_back( &MetaMapConverter::convertible, &MetaMapConverter::construct, py::type_id()); } }; // struct MetaMapConverter //////////////////////////////////////// template void translateException(const T&) {} /// @brief Define a function that translates an OpenVDB exception into /// the equivalent Python exception. /// @details openvdb::Exception::what() typically returns a string of the form /// ": ". To avoid duplication of the exception name in Python /// stack traces, the function strips off the ": " prefix. To do that, /// it needs the class name in the form of a string, hence the preprocessor macro. #define PYOPENVDB_CATCH(_openvdbname, _pyname) \ template<> \ void translateException<_openvdbname>(const _openvdbname& e) \ { \ const char* name = #_openvdbname; \ if (const char* c = std::strrchr(name, ':')) name = c + 1; \ const int namelen = std::strlen(name); \ const char* msg = e.what(); \ if (0 == std::strncmp(msg, name, namelen)) msg += namelen; \ if (0 == std::strncmp(msg, ": ", 2)) msg += 2; \ PyErr_SetString(_pyname, msg); \ } /// Define an overloaded function that translate all OpenVDB exceptions into /// their Python equivalents. /// @todo IllegalValueException and LookupError are redundant and should someday be removed. PYOPENVDB_CATCH(openvdb::ArithmeticError, PyExc_ArithmeticError) PYOPENVDB_CATCH(openvdb::IllegalValueException, PyExc_ValueError) PYOPENVDB_CATCH(openvdb::IndexError, PyExc_IndexError) PYOPENVDB_CATCH(openvdb::IoError, PyExc_IOError) PYOPENVDB_CATCH(openvdb::KeyError, PyExc_KeyError) PYOPENVDB_CATCH(openvdb::LookupError, PyExc_LookupError) PYOPENVDB_CATCH(openvdb::NotImplementedError, PyExc_NotImplementedError) PYOPENVDB_CATCH(openvdb::ReferenceError, PyExc_ReferenceError) PYOPENVDB_CATCH(openvdb::RuntimeError, PyExc_RuntimeError) PYOPENVDB_CATCH(openvdb::TypeError, PyExc_TypeError) PYOPENVDB_CATCH(openvdb::ValueError, PyExc_ValueError) #undef PYOPENVDB_CATCH //////////////////////////////////////// py::object readFromFile(const std::string& filename, const std::string& gridName) { io::File vdbFile(filename); vdbFile.open(); if (!vdbFile.hasGrid(gridName)) { PyErr_Format(PyExc_KeyError, "file %s has no grid named \"%s\"", filename.c_str(), gridName.c_str()); py::throw_error_already_set(); } return pyGrid::getGridFromGridBase(vdbFile.readGrid(gridName)); } py::tuple readAllFromFile(const std::string& filename) { io::File vdbFile(filename); vdbFile.open(); GridPtrVecPtr grids = vdbFile.getGrids(); MetaMap::Ptr metadata = vdbFile.getMetadata(); vdbFile.close(); py::list gridList; for (GridPtrVec::const_iterator it = grids->begin(); it != grids->end(); ++it) { gridList.append(pyGrid::getGridFromGridBase(*it)); } return py::make_tuple(gridList, py::dict(*metadata)); } py::dict readFileMetadata(const std::string& filename) { io::File vdbFile(filename); vdbFile.open(); MetaMap::Ptr metadata = vdbFile.getMetadata(); vdbFile.close(); return py::dict(*metadata); } py::object readGridMetadataFromFile(const std::string& filename, const std::string& gridName) { io::File vdbFile(filename); vdbFile.open(); if (!vdbFile.hasGrid(gridName)) { PyErr_Format(PyExc_KeyError, "file %s has no grid named \"%s\"", filename.c_str(), gridName.c_str()); py::throw_error_already_set(); } return pyGrid::getGridFromGridBase(vdbFile.readGridMetadata(gridName)); } py::list readAllGridMetadataFromFile(const std::string& filename) { io::File vdbFile(filename); vdbFile.open(); GridPtrVecPtr grids = vdbFile.readAllGridMetadata(); vdbFile.close(); py::list gridList; for (GridPtrVec::const_iterator it = grids->begin(); it != grids->end(); ++it) { gridList.append(pyGrid::getGridFromGridBase(*it)); } return gridList; } void writeToFile(const std::string& filename, py::object gridOrSeqObj, py::object dictObj) { GridPtrVec gridVec; try { GridBase::Ptr base = pyopenvdb::getGridFromPyObject(gridOrSeqObj); gridVec.push_back(base); } catch (openvdb::TypeError&) { for (py::stl_input_iterator it(gridOrSeqObj), end; it != end; ++it) { if (GridBase::Ptr base = pyGrid::getGridBaseFromGrid(*it)) { gridVec.push_back(base); } } } io::File vdbFile(filename); if (dictObj.is_none()) { vdbFile.write(gridVec); } else { MetaMap metadata = py::extract(dictObj); vdbFile.write(gridVec, metadata); } vdbFile.close(); } //////////////////////////////////////// // Descriptor for the openvdb::GridClass enum (for use with pyutil::StringEnum) struct GridClassDescr { static const char* name() { return "GridClass"; } static const char* doc() { return "Classes of volumetric data (level set, fog volume, etc.)"; } static pyutil::CStringPair item(int i) { static const int sCount = 4; static const char* const sStrings[sCount][2] = { { "UNKNOWN", strdup(GridBase::gridClassToString(GRID_UNKNOWN).c_str()) }, { "LEVEL_SET", strdup(GridBase::gridClassToString(GRID_LEVEL_SET).c_str()) }, { "FOG_VOLUME", strdup(GridBase::gridClassToString(GRID_FOG_VOLUME).c_str()) }, { "STAGGERED", strdup(GridBase::gridClassToString(GRID_STAGGERED).c_str()) } }; if (i >= 0 && i < sCount) return pyutil::CStringPair(&sStrings[i][0], &sStrings[i][1]); return pyutil::CStringPair(static_cast(NULL), static_cast(NULL)); } }; // Descriptor for the openvdb::VecType enum (for use with pyutil::StringEnum) struct VecTypeDescr { static const char* name() { return "VectorType"; } static const char* doc() { return "The type of a vector determines how transforms are applied to it.\n" "- INVARIANT:\n" " does not transform (e.g., tuple, uvw, color)\n" "- COVARIANT:\n" " apply inverse-transpose transformation with w = 0\n" " and ignore translation (e.g., gradient/normal)\n" "- COVARIANT_NORMALIZE:\n" " apply inverse-transpose transformation with w = 0\n" " and ignore translation, vectors are renormalized\n" " (e.g., unit normal)\n" "- CONTRAVARIANT_RELATIVE:\n" " apply \"regular\" transformation with w = 0 and ignore\n" " translation (e.g., displacement, velocity, acceleration)\n" "- CONTRAVARIANT_ABSOLUTE:\n" " apply \"regular\" transformation with w = 1 so that\n" " vector translates (e.g., position)"; } static pyutil::CStringPair item(int i) { static const int sCount = 5; static const char* const sStrings[sCount][2] = { { "INVARIANT", strdup(GridBase::vecTypeToString(openvdb::VEC_INVARIANT).c_str()) }, { "COVARIANT", strdup(GridBase::vecTypeToString(openvdb::VEC_COVARIANT).c_str()) }, { "COVARIANT_NORMALIZE", strdup(GridBase::vecTypeToString(openvdb::VEC_COVARIANT_NORMALIZE).c_str()) }, { "CONTRAVARIANT_RELATIVE", strdup(GridBase::vecTypeToString(openvdb::VEC_CONTRAVARIANT_RELATIVE).c_str()) }, { "CONTRAVARIANT_ABSOLUTE", strdup(GridBase::vecTypeToString(openvdb::VEC_CONTRAVARIANT_ABSOLUTE).c_str()) } }; if (i >= 0 && i < sCount) return std::make_pair(&sStrings[i][0], &sStrings[i][1]); return pyutil::CStringPair(static_cast(NULL), static_cast(NULL)); } }; } // namespace _openvdbmodule //////////////////////////////////////// #ifdef DWA_OPENVDB #define PY_OPENVDB_MODULE_NAME _openvdb #else #define PY_OPENVDB_MODULE_NAME pyopenvdb #endif BOOST_PYTHON_MODULE(PY_OPENVDB_MODULE_NAME) { // Don't auto-generate ugly, C++-style function signatures. py::docstring_options docOptions; docOptions.disable_signatures(); docOptions.enable_user_defined(); using namespace openvdb::OPENVDB_VERSION_NAME; // Initialize OpenVDB. initialize(); _openvdbmodule::CoordConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::VecConverter::registerConverter(); _openvdbmodule::MetaMapConverter::registerConverter(); #define PYOPENVDB_TRANSLATE_EXCEPTION(_classname) \ py::register_exception_translator<_classname>(&_openvdbmodule::translateException<_classname>) PYOPENVDB_TRANSLATE_EXCEPTION(ArithmeticError); PYOPENVDB_TRANSLATE_EXCEPTION(IllegalValueException); PYOPENVDB_TRANSLATE_EXCEPTION(IndexError); PYOPENVDB_TRANSLATE_EXCEPTION(IoError); PYOPENVDB_TRANSLATE_EXCEPTION(KeyError); PYOPENVDB_TRANSLATE_EXCEPTION(LookupError); PYOPENVDB_TRANSLATE_EXCEPTION(NotImplementedError); PYOPENVDB_TRANSLATE_EXCEPTION(ReferenceError); PYOPENVDB_TRANSLATE_EXCEPTION(RuntimeError); PYOPENVDB_TRANSLATE_EXCEPTION(TypeError); PYOPENVDB_TRANSLATE_EXCEPTION(ValueError); #undef PYOPENVDB_TRANSLATE_EXCEPTION // Export the python bindings. exportTransform(); exportMetadata(); exportFloatGrid(); exportIntGrid(); exportVec3Grid(); py::def("read", &_openvdbmodule::readFromFile, (py::arg("filename"), py::arg("gridname")), "read(filename, gridname) -> Grid\n\n" "Read a single grid from a .vdb file."); py::def("readAll", &_openvdbmodule::readAllFromFile, py::arg("filename"), "readAll(filename) -> list, dict\n\n" "Read a .vdb file and return a list of grids and\n" "a dict of file-level metadata."); py::def("readMetadata", &_openvdbmodule::readFileMetadata, py::arg("filename"), "readMetadata(filename) -> dict\n\n" "Read file-level metadata from a .vdb file."); py::def("readGridMetadata", &_openvdbmodule::readGridMetadataFromFile, (py::arg("filename"), py::arg("gridname")), "readGridMetadata(filename, gridname) -> Grid\n\n" "Read a single grid's metadata and transform (but not its tree)\n" "from a .vdb file."); py::def("readAllGridMetadata", &_openvdbmodule::readAllGridMetadataFromFile, py::arg("filename"), "readAllGridMetadata(filename) -> list\n\n" "Read a .vdb file and return a list of grids populated with\n" "their metadata and transforms, but not their trees."); py::def("write", &_openvdbmodule::writeToFile, (py::arg("filename"), py::arg("grids"), py::arg("metadata") = py::object()), "write(filename, grids, metadata=None)\n\n" "Write a grid or a sequence of grids and, optionally, a dict\n" "of (name, value) metadata pairs to a .vdb file."); // Add some useful module-level constants. py::scope().attr("COORD_MIN") = openvdb::Coord::min(); py::scope().attr("COORD_MAX") = openvdb::Coord::max(); py::scope().attr("LEVEL_SET_HALF_WIDTH") = openvdb::LEVEL_SET_HALF_WIDTH; pyutil::StringEnum<_openvdbmodule::GridClassDescr>::wrap(); pyutil::StringEnum<_openvdbmodule::VecTypeDescr>::wrap(); } // BOOST_PYTHON_MODULE // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/python/pyMetadata.cc0000644000000000000000000000755512252453157015154 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2013 DreamWorks Animation LLC // // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) // // Redistributions of source code must retain the above copyright // and license notice and the following restrictions and disclaimer. // // * Neither the name of DreamWorks Animation nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE // LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. // /////////////////////////////////////////////////////////////////////////// #include #include "openvdb/openvdb.h" namespace py = boost::python; using namespace openvdb::OPENVDB_VERSION_NAME; namespace { class MetadataWrap: public Metadata, public py::wrapper { public: Name typeName() const { return this->get_override("typeName")(); } Metadata::Ptr copy() const { return this->get_override("copy")(); } void copy(const Metadata& other) { this->get_override("copy")(other); } std::string str() const { return this->get_override("str")(); } bool asBool() const { return this->get_override("asBool")(); } Index32 size() const { return this->get_override("size")(); } protected: void readValue(std::istream& is, Index32 numBytes) { this->get_override("readValue")(is, numBytes); } void writeValue(std::ostream& os) const { this->get_override("writeValue")(os); } }; // aliases disambiguate the different versions of copy Metadata::Ptr (MetadataWrap::*copy0)() const = &MetadataWrap::copy; void (MetadataWrap::*copy1)(const Metadata&) = &MetadataWrap::copy; } // end anonymous namespace void exportMetadata() { py::class_ clss( /*classname=*/"Metadata", /*docstring=*/ "Class that holds the value of a single item of metadata of a type\n" "for which no Python equivalent exists (typically a custom type)", /*ctor=*/py::no_init // can only be instantiated from C++, not from Python ); clss.def("copy", py::pure_virtual(copy0), "copy() -> Metadata\n\nReturn a copy of this value.") .def("copy", py::pure_virtual(copy1), "copy() -> Metadata\n\nReturn a copy of this value.") .def("type", py::pure_virtual(&Metadata::typeName), "type() -> str\n\nReturn the name of this value's type.") .def("size", py::pure_virtual(&Metadata::size), "size() -> int\n\nReturn the size of this value in bytes.") .def("__nonzero__", py::pure_virtual(&Metadata::asBool)) .def("__str__", py::pure_virtual(&Metadata::str)) ; py::register_ptr_to_python(); } // Copyright (c) 2012-2013 DreamWorks Animation LLC // All rights reserved. This software is distributed under the // Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) openvdb/Makefile0000644000000000000000000006354512252453157012674 0ustar rootroot# Copyright (c) 2012-2013 DreamWorks Animation LLC # # All rights reserved. This software is distributed under the # Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) # # Redistributions of source code must retain the above copyright # and license notice and the following restrictions and disclaimer. # # * Neither the name of DreamWorks Animation nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE # LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. # # Makefile for the OpenVDB library # See INSTALL for a list of requirements. # # Targets: # lib the OpenVDB library # # doc HTML documentation (doc/html/index.html) # pdfdoc PDF documentation (doc/latex/refman.pdf; # requires LaTeX and ghostscript) # python OpenVDB Python module # pytest unit tests for the Python module # pydoc HTML documentation for the Python module # (doc/html/python/index.html) # vdb_print command-line tool to inspect OpenVDB files # vdb_render command-line tool to ray-trace OpenVDB files # vdb_view command-line tool to view OpenVDB files # vdb_test unit tests for the OpenVDB library # # all [default target] all of the above # install install all of the above except vdb_test # into subdirectories of INSTALL_DIR # depend recompute source file header dependencies # clean delete generated files from the local directory # test run tests # # Options: # shared=no link executables against static OpenVDB libraries # (default: link against shared libraries) # debug=yes build with debugging symbols and without optimization # verbose=yes run commands (e.g., doxygen) in verbose mode # # The following variables must be defined, either here or on the command line # (e.g., "make install INSTALL_DIR=/usr/local"): # # Note that if you plan to build the Houdini OpenVDB tools (distributed # separately), you must build the OpenVDB library and the Houdini tools # against compatible versions of the Boost, OpenEXR and TBB libraries. # Fortunately, all three are included in the Houdini HDK, so the relevant # variables below point by default to the HDK library and header directories: # $(HDSO) and $(HT)/include, respectively. (Source the houdini_setup script # to set those two environment variables.) # # To build the OpenVDB Python module, you will need local distributions of # Python, Boost.Python, and optionally NumPy. As of Houdini 12.5, the HDK # includes versions 2.5 and 2.6 of Python as well as the Boost.Python headers. # Unfortunately, it does not include the libboost_python library, nor does it # include NumPy, so both Boost.Python and NumPy have to be built separately. # Point the variables $(BOOST_PYTHON_LIB_DIR), $(BOOST_PYTHON_LIB) and # $(NUMPY_INCL_DIR) below to your local distributions of those libraries. # # The directory into which to install libraries, executables and header files INSTALL_DIR := /tmp/OpenVDB # The parent directory of the boost/ header directory BOOST_INCL_DIR := $(HT)/include # The parent directory of the OpenEXR/ header directory EXR_INCL_DIR := $(HT)/include # The directory containing libIlmImf, libIlmThread, etc. EXR_LIB_DIR := $(HDSO) EXR_LIB := -lIlmImf -lIlmThread -lIex -lImath # The parent directory of the OpenEXR/ header directory (which contains half.h) HALF_INCL_DIR := $(EXR_INCL_DIR) # The directory containing libHalf HALF_LIB_DIR := $(EXR_LIB_DIR) HALF_LIB := -lHalf # The parent directory of the tbb/ header directory TBB_INCL_DIR := $(HT)/include # The directory containing libtbb TBB_LIB_DIR := $(HDSO) TBB_LIB := -ltbb # A scalable, concurrent malloc replacement library # such as jemalloc (included in the Houdini HDK) or TBB malloc # (leave blank if unavailable) CONCURRENT_MALLOC_LIB := -ljemalloc #CONCURRENT_MALLOC_LIB := -ltbbmalloc_proxy -ltbbmalloc # The directory containing the malloc replacement library CONCURRENT_MALLOC_LIB_DIR := $(HDSO) # The parent directory of the cppunit/ header directory # (leave blank if CppUnit is unavailable) CPPUNIT_INCL_DIR := /rel/map/generic-2013.22/sys_include # The directory containing libcppunit CPPUNIT_LIB_DIR := /rel/depot/third_party_build/cppunit/1.10.2-7/opt-ws5-x86_64-gccWS5_64/lib CPPUNIT_LIB := -lcppunit # The directory containing glfw.h # (leave blank if GLFW is unavailable) GLFW_INCL_DIR := /rel/third_party/glfw/current/include # The directory containing libglfw GLFW_LIB_DIR := /rel/third_party/glfw/current/lib GLFW_LIB := -lglfw # The version of Python for which to build the OpenVDB module # (leave blank if Python is unavailable) PYTHON_VERSION := 2.6 # The directory containing Python.h PYTHON_INCL_DIR := $(HFS)/python/include/python$(PYTHON_VERSION) # The directory containing pyconfig.h PYCONFIG_INCL_DIR := $(PYTHON_INCL_DIR) # The directory containing libpython PYTHON_LIB_DIR := $(HFS)/python/lib PYTHON_LIB := -lpython$(PYTHON_VERSION) # The directory containing libboost_python BOOST_PYTHON_LIB_DIR := /rel/depot/third_party_build/boost/rhel6-1.46.1-0/lib BOOST_PYTHON_LIB := -lboost_python-gcc41-mt-python26-1_46_1 # The directory containing arrayobject.h # (leave blank if NumPy is unavailable) NUMPY_INCL_DIR := /rel/map/generic-2013.22/include # The Epydoc executable # (leave blank if Epydoc is unavailable) EPYDOC := epydoc # Set PYTHON_WRAP_ALL_GRID_TYPES to "yes" to specify that the Python module # should expose (almost) all of the grid types defined in openvdb.h # Otherwise, only FloatGrid, BoolGrid and Vec3SGrid will be exposed # (see, e.g., exportIntGrid() in python/pyIntGrid.cc). # Compiling the Python module with PYTHON_WRAP_ALL_GRID_TYPES set to "yes" # can be very memory-intensive. PYTHON_WRAP_ALL_GRID_TYPES := no # The Doxygen executable # (leave blank if Doxygen is unavailable) DOXYGEN := doxygen # # Ideally, users shouldn't need to change anything below this line. # SHELL = /bin/bash # Turn off implicit rules for speed. .SUFFIXES: # Determine the platform. ifeq ("$(OS)","Windows_NT") WINDOWS_NT := 1 else UNAME_S := $(shell uname -s) ifeq ("$(UNAME_S)","Linux") LINUX := 1 else ifeq ("$(UNAME_S)","Darwin") MBSD := 1 endif endif endif ifeq (yes,$(strip $(debug))) OPTIMIZE := -g else OPTIMIZE := -O3 -DNDEBUG endif ifeq (yes,$(strip $(verbose))) QUIET := QUIET_TEST := -v else QUIET := > /dev/null QUIET_TEST := $(QUIET) endif has_glfw := no ifeq (3,$(words $(strip $(GLFW_LIB_DIR) $(GLFW_INCL_DIR) $(GLFW_LIB)))) has_glfw := yes endif has_python := no ifeq (7,$(words $(strip $(PYTHON_VERSION) $(PYTHON_LIB_DIR) $(PYTHON_INCL_DIR) \ $(PYCONFIG_INCL_DIR) $(PYTHON_LIB) $(BOOST_PYTHON_LIB_DIR) $(BOOST_PYTHON_LIB)))) has_python := yes endif INCLDIRS := -I . -I .. -I $(BOOST_INCL_DIR) -I $(HALF_INCL_DIR) -I $(TBB_INCL_DIR) CXXFLAGS += -pthread $(OPTIMIZE) $(INCLDIRS) LIBS := \ -ldl -lm -lz \ -L$(HALF_LIB_DIR) $(HALF_LIB) \ -L$(TBB_LIB_DIR) $(TBB_LIB) \ # LIBS_RPATH := \ -ldl -lm -lz \ -Wl,-rpath,$(HALF_LIB_DIR) -L$(HALF_LIB_DIR) $(HALF_LIB) \ -Wl,-rpath,$(TBB_LIB_DIR) -L$(TBB_LIB_DIR) $(TBB_LIB) \ # ifneq (,$(strip $(CONCURRENT_MALLOC_LIB))) ifneq (,$(strip $(CONCURRENT_MALLOC_LIB_DIR))) LIBS_RPATH += -Wl,-rpath,$(CONCURRENT_MALLOC_LIB_DIR) -L$(CONCURRENT_MALLOC_LIB_DIR) endif endif ifdef LINUX LIBS += -lrt LIBS_RPATH += -lrt endif INCLUDE_NAMES := \ Exceptions.h \ Grid.h \ io/Archive.h \ io/Compression.h \ io/File.h \ io/GridDescriptor.h \ io/Queue.h \ io/Stream.h \ math/BBox.h \ math/Coord.h \ math/FiniteDifference.h \ math/Hermite.h \ math/LegacyFrustum.h \ math/Maps.h \ math/Mat.h \ math/Mat3.h \ math/Mat4.h \ math/Math.h \ math/Operators.h \ math/Proximity.h \ math/QuantizedUnitVec.h \ math/Quat.h \ math/Ray.h \ math/Stats.h \ math/Stencils.h \ math/Transform.h\ math/Tuple.h\ math/Vec2.h \ math/Vec3.h \ math/Vec4.h \ Metadata.h \ metadata/Metadata.h \ metadata/MetaMap.h \ metadata/StringMetadata.h \ openvdb.h \ Platform.h \ PlatformConfig.h \ tools/Composite.h \ tools/Dense.h \ tools/Filter.h \ tools/GridOperators.h \ tools/GridTransformer.h \ tools/Interpolation.h \ tools/LevelSetAdvect.h \ tools/LevelSetFilter.h \ tools/LevelSetFracture.h \ tools/LevelSetMeasure.h \ tools/LevelSetMorph.h \ tools/LevelSetRebuild.h \ tools/LevelSetSphere.h \ tools/LevelSetTracker.h \ tools/LevelSetUtil.h \ tools/MeshToVolume.h \ tools/Morphology.h \ tools/ParticlesToLevelSet.h \ tools/PointAdvect.h \ tools/PointScatter.h \ tools/RayIntersector.h \ tools/RayTracer.h \ tools/Statistics.h \ tools/ValueTransformer.h \ tools/VolumeToMesh.h \ tools/VolumeToSpheres.h \ tree/InternalNode.h \ tree/Iterator.h \ tree/LeafManager.h \ tree/LeafNode.h \ tree/LeafNodeBool.h \ tree/NodeUnion.h \ tree/RootNode.h \ tree/Tree.h \ tree/TreeIterator.h \ tree/Util.h \ tree/ValueAccessor.h \ Types.h \ util/Formats.h \ util/logging.h \ util/MapsUtil.h \ util/Name.h \ util/NodeMasks.h \ util/NullInterrupter.h \ util/Util.h \ version.h \ # SRC_NAMES := \ Grid.cc \ io/Archive.cc \ io/Compression.cc \ io/File.cc \ io/GridDescriptor.cc \ io/Queue.cc \ io/Stream.cc \ math/Hermite.cc \ math/Maps.cc \ math/Proximity.cc \ math/QuantizedUnitVec.cc \ math/Transform.cc \ metadata/Metadata.cc \ metadata/MetaMap.cc \ openvdb.cc \ Platform.cc \ util/Formats.cc \ util/Util.cc \ # UNITTEST_INCLUDE_NAMES := \ unittest/util.h \ # UNITTEST_SRC_NAMES := \ unittest/main.cc \ unittest/TestBBox.cc \ unittest/TestCoord.cc \ unittest/TestCpt.cc \ unittest/TestCurl.cc \ unittest/TestDense.cc \ unittest/TestDivergence.cc \ unittest/TestDoubleMetadata.cc \ unittest/TestExceptions.cc \ unittest/TestFile.cc \ unittest/TestFloatMetadata.cc \ unittest/TestGradient.cc \ unittest/TestGrid.cc \ unittest/TestGridBbox.cc \ unittest/TestGridDescriptor.cc \ unittest/TestGridIO.cc \ unittest/TestGridTransformer.cc \ unittest/TestHermite.cc \ unittest/TestInit.cc \ unittest/TestInt32Metadata.cc \ unittest/TestInt64Metadata.cc \ unittest/TestInternalOrigin.cc \ unittest/TestLaplacian.cc \ unittest/TestLeaf.cc \ unittest/TestLeafBool.cc \ unittest/TestLeafIO.cc \ unittest/TestLeafOrigin.cc \ unittest/TestLevelSetRayIntersector.cc \ unittest/TestLevelSetUtil.cc \ unittest/TestLinearInterp.cc \ unittest/TestMaps.cc \ unittest/TestMat4Metadata.cc \ unittest/TestMath.cc \ unittest/TestMeanCurvature.cc \ unittest/TestMeshToVolume.cc \ unittest/TestMetadata.cc \ unittest/TestMetadataIO.cc \ unittest/TestMetaMap.cc \ unittest/TestName.cc \ unittest/TestNodeIterator.cc \ unittest/TestNodeMask.cc \ unittest/TestParticlesToLevelSet.cc \ unittest/TestPrePostAPI.cc \ unittest/TestQuadraticInterp.cc \ unittest/TestQuantizedUnitVec.cc \ unittest/TestQuat.cc \ unittest/TestRay.cc \ unittest/TestStats.cc \ unittest/TestStream.cc \ unittest/TestStringMetadata.cc \ unittest/TestTools.cc \ unittest/TestTransform.cc \ unittest/TestTree.cc \ unittest/TestTreeCombine.cc \ unittest/TestTreeGetSetValues.cc \ unittest/TestTreeIterators.cc \ unittest/TestTreeVisitor.cc \ unittest/TestValueAccessor.cc \ unittest/TestVec2Metadata.cc \ unittest/TestVec3Metadata.cc \ unittest/TestVolumeRayIntersector.cc \ unittest/TestVolumeToMesh.cc \ # DOC_FILES := doc/doc.txt doc/faq.txt doc/changes.txt doc/codingstyle.txt doc/examplecode.txt doc/api_0_98_0.txt doc/math.txt doc/python.txt DOC_INDEX := doc/html/index.html DOC_PDF := doc/latex/refman.pdf LIBVIEWER_INCLUDE_NAMES := \ viewer/Camera.h \ viewer/ClipBox.h \ viewer/Font.h \ viewer/RenderModules.h \ viewer/Viewer.h \ # # Used for "install" target only LIBVIEWER_PUBLIC_INCLUDE_NAMES := \ viewer/Viewer.h \ # LIBVIEWER_SRC_NAMES := \ viewer/Camera.cc \ viewer/ClipBox.cc \ viewer/Font.cc \ viewer/RenderModules.cc \ viewer/Viewer.cc \ # ifdef MBSD LIBVIEWER_FLAGS := -framework Cocoa -framework OpenGL -framework IOKit else LIBVIEWER_FLAGS := -lGL -lGLU endif CMD_INCLUDE_NAMES := \ # CMD_SRC_NAMES := \ cmd/openvdb_print/main.cc \ cmd/openvdb_render/main.cc \ cmd/openvdb_view/main.cc \ # PYTHON_INCLUDE_NAMES := \ python/pyopenvdb.h \ python/pyutil.h \ python/pyAccessor.h \ python/pyGrid.h \ # # Used for "install" target only PYTHON_PUBLIC_INCLUDE_NAMES := \ python/pyopenvdb.h \ # PYTHON_SRC_NAMES := \ python/pyFloatGrid.cc \ python/pyIntGrid.cc \ python/pyMetadata.cc \ python/pyOpenVDBModule.cc \ python/pyTransform.cc \ python/pyVec3Grid.cc \ # PYCXXFLAGS := -fPIC -I python -I $(PYTHON_INCL_DIR) -I $(PYCONFIG_INCL_DIR) ifneq ($(strip $(NUMPY_INCL_DIR)),) PYCXXFLAGS += -I $(NUMPY_INCL_DIR) -DPY_OPENVDB_USE_NUMPY endif ifneq (no,$(strip $(PYTHON_WRAP_ALL_GRID_TYPES))) PYCXXFLAGS += -DPY_OPENVDB_WRAP_ALL_GRID_TYPES endif HEADER_SUBDIRS := $(dir $(INCLUDE_NAMES)) ALL_INCLUDE_FILES := \ $(INCLUDE_NAMES) \ $(UNITTEST_INCLUDE_NAMES) \ $(CMD_INCLUDE_NAMES) \ $(LIBVIEWER_INCLUDE_NAMES) \ $(PYTHON_INCLUDE_NAMES) \ # SRC_FILES := \ $(SRC_NAMES) \ $(UNITTEST_SRC_NAMES) \ $(CMD_SRC_NAMES) \ $(LIBVIEWER_SRC_NAMES) \ $(PYTHON_SRC_NAMES) \ # ALL_SRC_FILES := $(SRC_FILES) OBJ_NAMES := $(SRC_NAMES:.cc=.o) UNITTEST_OBJ_NAMES := $(UNITTEST_SRC_NAMES:.cc=.o) LIBVIEWER_OBJ_NAMES := $(LIBVIEWER_SRC_NAMES:.cc=.o) PYTHON_OBJ_NAMES := $(PYTHON_SRC_NAMES:.cc=.o) LIB_MAJOR_VERSION=$(shell grep 'define OPENVDB_LIBRARY_MAJOR_VERSION_NUMBER ' \ version.h | sed 's/[^0-9]*//g') LIB_MINOR_VERSION=$(shell grep 'define OPENVDB_LIBRARY_MINOR_VERSION_NUMBER ' \ version.h | sed 's/[^0-9]*//g') LIB_PATCH_VERSION=$(shell grep 'define OPENVDB_LIBRARY_PATCH_VERSION_NUMBER ' \ version.h | sed 's/[^0-9]*//g') LIB_VERSION=$(LIB_MAJOR_VERSION).$(LIB_MINOR_VERSION).$(LIB_PATCH_VERSION) SO_VERSION=$(LIB_MAJOR_VERSION).$(LIB_MINOR_VERSION) LIBOPENVDB_NAME=libopenvdb LIBOPENVDB_STATIC := $(LIBOPENVDB_NAME).a LIBOPENVDB_SHARED := $(LIBOPENVDB_NAME).so.$(LIB_VERSION) LIBOPENVDB_SONAME := $(LIBOPENVDB_NAME).so.$(SO_VERSION) ifndef MBSD LIBOPENVDB_SONAME_FLAGS := -Wl,-soname,$(LIBOPENVDB_SONAME) endif # TODO: libopenvdb_viewer is currently built into vdb_view and is not installed separately. LIBVIEWER_NAME=libopenvdb_viewer LIBVIEWER_STATIC := $(LIBVIEWER_NAME).a LIBVIEWER_SHARED := $(LIBVIEWER_NAME).so.$(LIB_VERSION) LIBVIEWER_SONAME := $(LIBVIEWER_NAME).so.$(SO_VERSION) ifndef MBSD LIBVIEWER_SONAME_FLAGS := -Wl,-soname,$(LIBVIEWER_SONAME) endif PYTHON_MODULE_NAME=pyopenvdb PYTHON_MODULE := $(PYTHON_MODULE_NAME).so PYTHON_SONAME := $(PYTHON_MODULE_NAME).so.$(SO_VERSION) ifndef MBSD PYTHON_SONAME_FLAGS := -Wl,-soname,$(PYTHON_SONAME) endif ifeq (no,$(strip $(shared))) LIBOPENVDB := $(LIBOPENVDB_STATIC) LIBVIEWER := $(LIBVIEWER_STATIC) else LIBOPENVDB := $(LIBOPENVDB_SHARED) LIBVIEWER := $(LIBVIEWER_SHARED) LIBOPENVDB_RPATH := -Wl,-rpath,$(INSTALL_DIR)/lib endif # shared DEPEND := dependencies # Get the list of dependencies that are newer than the current target, # but limit the list to at most three entries. list_deps = $(if $(wordlist 4,5,$(?F)),$(firstword $(?F)) and others,$(wordlist 1,3,$(?F))) ALL_PRODUCTS := \ $(LIBOPENVDB) \ vdb_test \ vdb_print \ vdb_render \ vdb_view \ $(DEPEND) \ $(LIBOPENVDB_NAME).so \ $(LIBOPENVDB_SONAME) \ $(PYTHON_MODULE) \ # .SUFFIXES: .o .cc .PHONY: all clean depend doc install lib pdfdoc pydoc pytest python test viewerlib .cc.o: @echo "Building $@ because of $(call list_deps)" $(CXX) -c $(CXXFLAGS) -fPIC -o $@ $< all: lib python vdb_print vdb_render vdb_test depend $(OBJ_NAMES): %.o: %.cc @echo "Building $@ because of $(call list_deps)" $(CXX) -c -DOPENVDB_PRIVATE $(CXXFLAGS) -fPIC -o $@ $< ifneq (no,$(strip $(shared))) # Build shared library lib: $(LIBOPENVDB_NAME).so $(LIBOPENVDB_SONAME) $(LIBOPENVDB_NAME).so: $(LIBOPENVDB_SHARED) ln -f -s $< $@ $(LIBOPENVDB_SONAME): $(LIBOPENVDB_SHARED) ln -f -s $< $@ $(LIBOPENVDB_SHARED): $(OBJ_NAMES) @echo "Building $@ because of $(call list_deps)" $(CXX) $(CXXFLAGS) -shared -o $@ $^ $(LIBS_RPATH) $(LIBOPENVDB_SONAME_FLAGS) else # Build static library lib: $(LIBOPENVDB) $(LIBOPENVDB_STATIC): $(OBJ_NAMES) @echo "Building $@ because of $(call list_deps)" $(AR) cr $@ $^ endif # shared $(DOC_INDEX): doxygen-config $(INCLUDE_NAMES) $(SRC_NAMES) $(DOC_FILES) @echo "Generating documentation because of $(call list_deps)" echo 'OUTPUT_DIRECTORY=./doc' | cat doxygen-config - | $(DOXYGEN) - $(QUIET) $(DOC_PDF): doxygen-config $(INCLUDE_NAMES) $(SRC_NAMES) $(DOC_FILES) @echo "Generating documentation because of $(call list_deps)" echo -e 'OUTPUT_DIRECTORY=./doc\nGENERATE_LATEX=YES\nGENERATE_HTML=NO' \ | cat doxygen-config - | $(DOXYGEN) - $(QUIET) \ && cd ./doc/latex && make refman.pdf $(QUIET) \ && echo 'Created doc/latex/refman.pdf' ifneq ($(strip $(DOXYGEN)),) doc: $(DOC_INDEX) pdfdoc: $(DOC_PDF) else doc: @echo "$@"': $$DOXYGEN is undefined' pdfdoc: @echo "$@"': $$DOXYGEN is undefined' endif vdb_print: $(LIBOPENVDB) cmd/openvdb_print/main.cc @echo "Building $@ because of $(call list_deps)" $(CXX) $(CXXFLAGS) -o $@ cmd/openvdb_print/main.cc -I . \ $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) vdb_render: $(LIBOPENVDB) cmd/openvdb_render/main.cc @echo "Building $@ because of $(call list_deps)" $(CXX) $(CXXFLAGS) -o $@ cmd/openvdb_render/main.cc -I . -I $(EXR_INCL_DIR) \ $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ -Wl,-rpath,$(EXR_LIB_DIR) -L$(EXR_LIB_DIR) $(EXR_LIB) \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) ifneq (yes,$(has_glfw)) vdb_view: @echo "$@"': GLFW is unavailable' else # Create an openvdb_viewer/ symlink to the viewer/ subdirectory, # to mirror the DWA directory structure. openvdb_viewer: ln -f -s viewer openvdb_viewer $(LIBVIEWER_INCLUDE_NAMES): openvdb_viewer $(LIBVIEWER_OBJ_NAMES): $(LIBVIEWER_INCLUDE_NAMES) $(LIBVIEWER_OBJ_NAMES): %.o: %.cc @echo "Building $@ because of $(call list_deps)" $(CXX) -c $(CXXFLAGS) -I . -I $(GLFW_INCL_DIR) -DGL_GLEXT_PROTOTYPES=1 -fPIC -o $@ $< vdb_view: $(LIBOPENVDB) $(LIBVIEWER_OBJ_NAMES) cmd/openvdb_view/main.cc @echo "Building $@ because of $(call list_deps)" $(CXX) $(CXXFLAGS) -o $@ cmd/openvdb_view/main.cc $(LIBVIEWER_OBJ_NAMES) \ -I . -Wl,-rpath,$(GLFW_LIB_DIR) -L$(GLFW_LIB_DIR) $(GLFW_LIB) \ $(LIBVIEWER_FLAGS) $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) endif # Build the Python module $(PYTHON_OBJ_NAMES): $(PYTHON_INCLUDE_NAMES) $(PYTHON_OBJ_NAMES): %.o: %.cc @echo "Building $@ because of $(call list_deps)" $(CXX) -c $(CXXFLAGS) -I . $(PYCXXFLAGS) -o $@ $< $(PYTHON_MODULE): $(LIBOPENVDB) $(PYTHON_OBJ_NAMES) @echo "Building $@ because of $(call list_deps)" $(CXX) $(CXXFLAGS) $(PYCXXFLAGS) -shared $(PYTHON_SONAME_FLAGS) \ -o $@ $(PYTHON_OBJ_NAMES) $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ -Wl,-rpath,$(PYTHON_LIB_DIR) -L$(PYTHON_LIB_DIR) $(PYTHON_LIB) \ -Wl,-rpath,$(BOOST_PYTHON_LIB_DIR) -L$(BOOST_PYTHON_LIB_DIR) $(BOOST_PYTHON_LIB) \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) ifeq (yes,$(has_python)) ifneq ($(strip $(EPYDOC)),) pydoc: $(PYTHON_MODULE) $(LIBOPENVDB_SONAME) @echo "Generating Python module documentation because of $(call list_deps)" pydocdir=doc/html/python; \ mkdir -p $${pydocdir}; \ echo "Created $${pydocdir}"; \ export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(CURDIR); \ export PYTHONPATH=${PYTHONPATH}:$(CURDIR); \ $(EPYDOC) --html -o $${pydocdir} $(PYTHON_MODULE_NAME) $(QUIET) else pydoc: @echo "$@"': $$EPYDOC is undefined' endif pytest: $(PYTHON_MODULE) $(LIBOPENVDB_SONAME) @echo "Testing Python module $(PYTHON_MODULE)" export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(CURDIR); \ export PYTHONPATH=${PYTHONPATH}:$(CURDIR); \ python$(PYTHON_VERSION) ./python/test/TestOpenVDB.py $(QUIET_TEST) python: $(PYTHON_MODULE) else python pytest pydoc: @echo "$@"': Python is unavailable' endif $(UNITTEST_OBJ_NAMES): %.o: %.cc @echo "Building $@ because of $(call list_deps)" $(CXX) -c $(CXXFLAGS) -I $(CPPUNIT_INCL_DIR) -fPIC -o $@ $< ifneq ($(strip $(CPPUNIT_INCL_DIR)),) vdb_test: $(LIBOPENVDB) $(UNITTEST_OBJ_NAMES) @echo "Building $@ because of $(call list_deps)" $(CXX) $(CXXFLAGS) -o $@ $(UNITTEST_OBJ_NAMES) \ $(LIBS_RPATH) $(CONCURRENT_MALLOC_LIB) \ -Wl,-rpath,$(CPPUNIT_LIB_DIR) -L$(CPPUNIT_LIB_DIR) $(CPPUNIT_LIB) \ $(LIBOPENVDB_RPATH) -L$(CURDIR) $(LIBOPENVDB) test: vdb_test @echo "Testing $(LIBOPENVDB_NAME)" export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(CURDIR); ./vdb_test $(QUIET_TEST) else vdb_test: @echo "$@"': $$(CPPUNIT_INCL_DIR) is undefined' test: @echo "$@"': $$(CPPUNIT_INCL_DIR) is undefined' endif install: lib python vdb_print vdb_render vdb_view doc pydoc mkdir -p $(INSTALL_DIR)/include/openvdb @echo "Created $(INSTALL_DIR)/include/openvdb" pushd $(INSTALL_DIR)/include/openvdb > /dev/null; \ mkdir -p $(HEADER_SUBDIRS); popd > /dev/null for f in $(INCLUDE_NAMES); \ do cp -f $$f $(INSTALL_DIR)/include/openvdb/$$f; done @# if [ -f $(LIBVIEWER) ]; \ then \ mkdir -p $(INSTALL_DIR)/include/openvdb_viewer; \ echo "Created $(INSTALL_DIR)/include/openvdb_viewer"; \ cp -f $(LIBVIEWER_PUBLIC_INCLUDE_NAMES) $(INSTALL_DIR)/include/openvdb_viewer/; \ fi @echo "Copied header files to $(INSTALL_DIR)/include" @# mkdir -p $(INSTALL_DIR)/lib @echo "Created $(INSTALL_DIR)/lib/" cp -f $(LIBOPENVDB) $(INSTALL_DIR)/lib pushd $(INSTALL_DIR)/lib > /dev/null; \ if [ -f $(LIBOPENVDB_SHARED) ]; then \ ln -f -s $(LIBOPENVDB_SHARED) $(LIBOPENVDB_NAME).so; \ ln -f -s $(LIBOPENVDB_SHARED) $(LIBOPENVDB_SONAME); \ fi; \ popd > /dev/null @echo "Copied libopenvdb to $(INSTALL_DIR)/lib/" @# if [ -f $(LIBVIEWER) ]; \ then \ cp -f $(LIBVIEWER) $(INSTALL_DIR)/lib; \ pushd $(INSTALL_DIR)/lib > /dev/null; \ if [ -f $(LIBVIEWER_SHARED) ]; then \ ln -f -s $(LIBVIEWER_SHARED) $(LIBVIEWER_NAME).so; fi; \ popd > /dev/null; \ echo "Copied libopenvdb_viewer to $(INSTALL_DIR)/lib/"; \ fi @# if [ -f $(PYTHON_MODULE) ]; \ then \ installdir=$(INSTALL_DIR)/python/include/python$(PYTHON_VERSION); \ mkdir -p $${installdir}; \ echo "Created $${installdir}"; \ cp -f $(PYTHON_PUBLIC_INCLUDE_NAMES) $${installdir}/; \ echo "Copied Python header files to $${installdir}"; \ installdir=$(INSTALL_DIR)/python/lib/python$(PYTHON_VERSION); \ mkdir -p $${installdir}; \ echo "Created $${installdir}"; \ cp -f $(PYTHON_MODULE) $${installdir}/; \ pushd $${installdir} > /dev/null; \ ln -f -s $(PYTHON_MODULE) $(PYTHON_SONAME); \ popd > /dev/null; \ echo "Copied Python module to $${installdir}"; \ fi @# mkdir -p $(INSTALL_DIR)/bin @echo "Created $(INSTALL_DIR)/bin/" cp -f vdb_print $(INSTALL_DIR)/bin @echo "Copied vdb_print to $(INSTALL_DIR)/bin/" cp -f vdb_render $(INSTALL_DIR)/bin @echo "Copied vdb_render to $(INSTALL_DIR)/bin/" if [ -f vdb_view ]; \ then \ cp -f vdb_view $(INSTALL_DIR)/bin; \ echo "Copied vdb_view to $(INSTALL_DIR)/bin/"; \ fi @# if [ -d doc/html ]; \ then \ mkdir -p $(INSTALL_DIR)/share/doc/openvdb; \ echo "Created $(INSTALL_DIR)/share/doc/openvdb/"; \ cp -r -f doc/html $(INSTALL_DIR)/share/doc/openvdb; \ echo "Copied documentation to $(INSTALL_DIR)/share/doc/openvdb/"; \ fi # TODO: This accumulates all source file dependencies into a single file # containing a rule for each *.o file. Consider generating a separate # dependency file for each *.o file instead. $(DEPEND): $(ALL_INCLUDE_FILES) $(ALL_SRC_FILES) @echo "Generating dependencies because of $(call list_deps)" $(RM) $(DEPEND) for f in $(SRC_NAMES) $(CMD_SRC_NAMES); \ do $(CXX) $(CXXFLAGS) -O0 \ -MM $$f -MT `echo $$f | sed 's%\.[^.]*%.o%'` >> $(DEPEND); \ done if [ -d "$(CPPUNIT_INCL_DIR)" ]; \ then \ for f in $(UNITTEST_SRC_NAMES); \ do $(CXX) $(CXXFLAGS) -O0 \ -MM $$f -MT `echo $$f | sed 's%\.[^.]*%.o%'` \ -I $(CPPUNIT_INCL_DIR) >> $(DEPEND); \ done; \ fi depend: $(DEPEND) clean: $(RM) $(OBJ_NAMES) $(ALL_PRODUCTS) $(DEPEND) $(RM) $(LIBOPENVDB_STATIC) $(RM) $(LIBOPENVDB_SHARED) $(RM) $(LIBVIEWER_OBJ_NAMES) $(RM) $(PYTHON_OBJ_NAMES) $(RM) $(UNITTEST_OBJ_NAMES) $(RM) -r ./doc/html ./doc/latex ifneq ($(strip $(wildcard $(DEPEND))),) include $(DEPEND) endif # Copyright (c) 2012-2013 DreamWorks Animation LLC # All rights reserved. This software is distributed under the # Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )