openvdb/0000755000000000000000000000000013200122400011171 5ustar rootrootopenvdb/CHANGES0000644000000000000000000030237113200122206012176 0ustar rootrootOpenVDB Version History ======================= Version 5.0.0 - November 6, 2017 Some changes in this release (see "ABI changes" below) alter the grid ABI so that it is incompatible with earlier versions of the OpenVDB library, such as the ones built into Houdini up to and including Houdini 16. To preserve ABI compatibility, when compiling OpenVDB or any dependent code define the macro OPENVDB_ABI_VERSION_NUMBER=N, where, for example, N is 3 for Houdini 15, 15.5 and 16 and 4 for the upcoming release. New features: - Added a getLibraryAbiVersionString() function, which returns a string such as "5.0.0abi3". - Added a WeakPtr type alias for ABI compatibility. - Metadata fields of unregistered types are no longer discarded after being read from a .vdb file, and although their values are not printable, they can be written back to disk. - Added a DESTDIR_LIB_DIR Makefile variable for Linux multiarch support. [Contributed by Mathieu Malaterre] - Added tools to create potential flow fields, as described in the 2017 SIGGRAPH OpenVDB course. [Contributed by Double Negative] - Added tools to create mask grids from point data grids and to compute point counts. [Contributed by Dan Bailey] - Added tools to scatter OpenVDB points randomly throughout a volume. [Contributed by Nick Avramoussis] Improvements: - Significantly improved the performance of point data grid group filters. [Contributed by Double Negative] Bug fixes: - Fixed bugs in tools::ClosestSurfacePoint's distance calculations that caused searches to produce incorrect results. - Fixed a locking issue that affected multithreaded access to PointDataLeafNodes when delayed loading was in effect. [Contributed by Dan Bailey] ABI changes: - Made tree::InternalNode's destructor non-virtual. - The fix for a delayed-loading race condition in the LeafBuffer class that was only partially rolled out in the previous release is now enabled on all platforms. - Replaced a points::AttributeArray bit flag with an atomic integer to address a threading issue during delayed loading. [Contributed by Dan Bailey] - Deprecated the OPENVDB_2_ABI_COMPATIBLE and OPENVDB_3_ABI_COMPATIBLE macros in favor of a new OPENVDB_ABI_VERSION_NUMBER macro. The new macro defaults to the library major version number but can be set at compile time to an earlier version number to disable ABI changes since that version. (Older ABIs will not be supported indefinitely, however.) For example, compile OpenVDB and any dependent code with "-DOPENVDB_ABI_VERSION_NUMBER=4" to use the 4.x ABI. API changes: - Replaced tools::ClosestSurfacePoint::initialize() with tools::ClosestSurfacePoint::create(), which returns a newly-allocated and properly initialized object. - Removed methods that were deprecated in version 4.0.0 or earlier, including io::File::readGridPartial(), points::initialize(), points::uninitialize() and util::PagedArray::pop_back(). - Deprecated IllegalValueException in favor of ValueError. - Changed the naming scheme for the library namespace from openvdb::vX_Y_Z to openvdb::vX_YabiN, where X, Y, Z and N are the major, minor, patch and ABI version numbers, respectively. The abiN suffix is added only when the library is built using an older ABI version. Python: - Reimplemented NumPy support for Boost 1.65 compatibility. Houdini: - Fixed bugs that caused the Ray SOP's closest surface point searches to produce incorrect results. - Changed the VdbPrimCIterator::FilterFunc type from boost::function to std::function. - Changed the houdini_utils::OpPolicyPtr type from boost:shared_ptr to std::shared_ptr. - Debug-level log messages generated by OpenVDB are no longer forwarded to Houdini's error manager. - Fixed a bug in the Read SOP that made it impossible to select among grids of the same name in a file. - Added houdini_utils::ParmFactory::setAttrChoiceList(), a convenience method for the creation of menus of attributes. - Added a Potential Flow SOP. [Contributed by Double Negative] - Added point data grid support to the Scatter SOP. [Contributed by Nick Avramoussis] - Added mask and point count output options to the Points Convert SOP. [Contributed by Dan Bailey] Version 4.0.2 - July 28, 2017 New features: - Added tools::compActiveLeafVoxels(), which composites active voxel values from a source tree into a destination tree. It is threaded and faster than existing tools that merge trees, however it operates only on leaf nodes. - Added a vdb_test -f option that reads a list of tests to be run from a text file. - Added functions for deleting points from point data grids based on group membership. [Contributed by Double Negative] - Enabled display of point data grids in vdb_view. [Contributed by Nick Avramoussis] - Added a view mode indicator to vdb_view. - Added isFinite(), isInfinite(), isNan(), and isZero() methods to math::Mat and added isZero() to math::Tuple. - Added tools::interiorMask(), which constructs a boolean mask grid from the active voxels of an input grid or, if the input grid is a level set, from the interior voxels of the level set. - Added begin() and end() iterator methods (and related methods) to math::CoordBBox, so that it can be used in range-based for loops. - tools::clip() now accepts either a box, a mask grid or a camera frustum as the clipping region. The latter is new in this version. Improvements: - Moved the isFinite(), isInfinite() and isNan() methods from math::Vec3 et al. to math::Tuple. Bug fixes: - Fixed a delayed-loading race condition that could result in crashes. [Reported by Dan Bailey] To preserve ABI compatibility, this fix is currently enabled only on platforms for which the alignment of a tbb::atomic is the same as for a uint32_t. On other platforms, warnings will be logged during OpenVDB initialization, and it is recommended to disable delayed loading in that case (for example, by defining the environment variable OPENVDB_DISABLE_DELAYED_LOAD). - Fixed a delayed-loading memory leak in the PointDataLeafNode. [Contributed by Double Negative] - Changed the random number seeding mechanism for .vdb file UUIDs to avoid duplicate IDs. [Reported by Jason Lefley] - Fixed an off-by-one bug in the resampler that produced grid patterns of missing interior voxels for scale factors greater than one. Houdini: - As of Houdini 16.0.549, houdini_utils::OpFactory can generate help cards for operators automatically. New OpFactory::setDocumentation() and ParmFactory::setDocumentation() methods allow one to add custom help text in wiki markup format. - Added help cards for all SOPs. Houdini 16.0.578 or later is required. [Contributed by Dan Bailey and SideFX] - The Extended Operator Info window in Houdini 16 now renders correctly for OpenVDB SOPs, instead of displaying a Python stack trace. [Contributed by Dan Bailey] - Added a Points Delete SOP for deleting points from point data grids based on group membership. [Contributed by Double Negative] - Added a Mantra VRAY procedural and a delayed load SHOP for rendering point data grids. Houdini 16 is required. [Contributed by Double Negative] - Replaced the Combine SOP's "A/B Pairs" and "Flatten" toggles with a menu of collation options that include flattening only A grids and flattening groups of A grids independently. - Added a slider to the Remove Divergence SOP to set the error tolerance for the pressure solver. - Added value type conversion options (for VDB output) to the Convert SOP. - Added a Densify SOP that replaces active tiles with leaf voxels. - Fixed a bug in the Rasterize Points SOP that capped density values to one instead of to the particles' densities. - The Convert and To Polygons SOPs now accept grids of any type as surface masks, not just level set or SDF grids. - Added an option to the Clip SOP to clip to a camera frustum. Version 4.0.1 - March 8, 2017 New features: - Added functions to util/logging.h to simplify configuration of the logging system (via command-line arguments, in particular). - Added LeafManager::activeLeafVoxelCount(), a faster, threaded alternative to Tree::activeLeafVoxelCount(). - Added a -shuffle option that causes vdb_test to run unit tests in random order, which can help to identify unintended dependencies between tests. - Added vdb_lod, a command-line tool to generate volume mipmaps for level-of-detail effects. - Added methods to compute the median value of active, inactive or all voxels in leaf nodes. Improvements: - Added a Metadata::str() specialization for StringMetadata that eliminates the overhead of writing to a string stream. - Made various minor improvements to util::PagedArray. - Added an install_lib build target to the Makefile. [Contributed by Double Negative] - Added documentation and Cookbook examples for OpenVDB Points. [Contributed by Double Negative] - Registration of OpenVDB Points grid and attribute types is now handled in openvdb::initialize(), and openvdb::points::initialize() and openvdb::points::uninitialize() are therefore deprecated. - Extended multi-pass I/O to handle a variable number of passes per leaf node. [Contributed by Double Negative] - Addressed a name conflict between macros in util/NodeMasks.h and symbols in the Eigen library. [Reported by Trevor Thomson] Bug fixes: - tools::fillWithSpheres() and tools::ClosestSurfacePoint now correctly handle isosurfaces outside the input volume's narrow band. - tools::MultiResGrid now supports all standard grid types, including BoolGrid and MaskGrid. - LeafNode::fill() now correctly clips the fill region to the node's bounding box. - Grid::denseFill() no longer densifies all existing active tiles, and it now correctly handles both active and inactive fill values. - Fixed a bug that caused tools::copyToDense() to only partially populate the output array when delayed loading was in effect. [Reported by Stuart Levy] - Fixed an issue with duplicate registration of PointDataGrid attribute types. [Reported by SideFX] - Fixed an uninitialized memory bug in tools::meshToVolume(). [Reported by SideFX] - Fixed a thread race condition in math::QuantizedUnitVec that could cause it to produce incorrect results. [Contributed by Jeff Lait] - Fixed a dangling pointer bug in tools::ParticleAtlas. [Contributed by SideFX] - Grid operators (divergence, gradient, etc.) now produce correct results even for grids with active tile values. - Fixed a bug when writing an out-of-core points::AttributeArray that could cause corruption of the metadata associated with the array. [Contributed by Double Negative] Python: - Added functions getLoggingLevel(), setLoggingLevel(), and setProgramName(), to allow configuration of the logging system. Houdini: - Fixed a crash in the Ray SOP when the user selected an isosurface outside the target volume's narrow band. - The LOD SOP now supports all standard grid types, including boolean grids. - Added houdini_utils::ParmFactory::setGroupChoiceList(), a convenience method for the creation of menus of primitive groups. - Made various small changes for Houdini 16 compatibility. [Contributed by SideFX] - The Create SOP now supports matching the new grids' transform, voxel size, and topology to a reference grid. If the topology is being matched, it can optionally be resampled to a different voxel size. - Added some support for point data grids to the Clip, Topology To Level Set and Visualize SOPs. [Contributed by Double Negative] - Compression is no longer enabled by default in the Points Convert SOP for normals and colors, because they are not guaranteed to have a [0, 1] range. [Contributed by Double Negative] - Added a 16-bit truncation compression option to the Points Convert SOP. [Contributed by Double Negative] - Fixed a build issue with the GR_PrimVDBPoints render hook plugin that could cause hython to report a DSO error. [Reported by Double Negative] - Added an install_lib build target to the Makefile. - Rewrote the Remove Divergence SOP to actually remove divergence from vector fields on collocated grids, and added support for stationary and moving obstacles and an option to output a pressure field. - The Analysis SOP now produces correct results for grids with active tile values. - Added a sparse/dense toggle to the Fill SOP. - Added openvdb_houdini::startLogForwarding(), stopLogForwarding() and isLogForwarding(), which control the forwarding of log messages to Houdini's error manager. Forwarding of library warnings and error messages is now enabled by default for SOPs when OpenVDB is built with log4cplus. Version 4.0.0 - November 15, 2016 Highlights: - Incorporated Double Negative's OpenVDB Points library. See https://github.com/dneg/openvdb_points_dev for details. - Introduced some C++11 constructs. A C++11-compatible compiler is now required. - Blosc-compressed .vdb files are now as much as 20% smaller. - Vector-valued grids are now constructed and destroyed much faster. This change and other changes in this release (see "ABI changes" below) alter the grid ABI so that it is incompatible with earlier versions of the OpenVDB library, such as the ones in Houdini 15, 15.5 and 16. To disable these changes and preserve ABI compatibility, define the macro OPENVDB_3_ABI_COMPATIBLE when compiling OpenVDB or any code that depends on OpenVDB. New features: - Added an option to the point scattering tools to specify how far each point may be displaced from the center of its host voxel or tile. - Added a toggle to tools::clip() to invert the clipping mask. - Custom leaf node implementations may now optimize their file layout by inheriting from io::MultiPass. Voxel data for grids with such leaf nodes will be written and read in multiple passes, allowing blocks of related data to be stored contiguously. [Contributed by Double Negative] - Added Tree::unallocatedLeafCount(), which returns the number of leaf nodes with unallocated data buffers (typically due to delayed loading). Improvements: - Vector-valued grids are now constructed and destroyed much faster. - Changed math::Coord's data representation to facilitate C++11 uniform initialization. - Delayed loading from io::Files is now faster due to the use of seeks instead of reads. [Contributed by Double Negative] - Made many small changes to address type conversion and other warnings reported by newer compilers, including Clang 3.8. - Improved Blosc compression ratios and write times by increasing the block size. [Contributed by Dan Bailey] Bug fixes: - Fixed a bug that caused topology operations (union, intersection and difference) on MaskGrids to sometimes produce incorrect results. (MaskGrids are used internally in a number of tools.) - Changed GridBase::copyGrid() and Grid::copy() to close const-correctness holes. - tools::fillWithSpheres() now returns an empty list of spheres instead of crashing when the user selects an isosurface that lies outside the bounding volume's narrow band. - Fixed a null pointer dereference when copying grids that were loaded with io::File::readGridPartial(). [Reported by Nick Avramoussis] ABI changes: - Added a NodeUnion template specialization for non-POD value types that significantly expedites construction and destruction of vector-valued grids. - Changed math::Coord's internal data representation. - Replaced occurrences of boost::shared_ptr with std::shared_ptr. - Changed GridBase::copyGrid() and Grid::copy() to close const-correctness holes. - Added virtual function Tree::unallocatedLeafCount(). API changes: - Introduced some C++11 constructs. A C++11-compatible compiler is now required. - Added a parameter to the point scattering tools to control the displacement of each point from the center of its host voxel or tile. The default behavior, as before, is to allow each point to be placed (randomly) anywhere within its voxel or tile. - Renamed LeafManager::getPreFixSum() to LeafManager::getPrefixSum(). - Made LeafNode::Buffer a top-level class and renamed it to LeafBuffer. [Contributed by Double Negative] - Deprecated io::File::readGridPartial() in favor of delayed loading. - tools::ClosestSurfacePoint::initialize() now returns a boolean indicating whether initialization was successful. - Dropped the CopyPolicy enum and added GridBase::copyGridWithNewTree() and Grid::copyWithNewTree() in order to close const-correctness holes that allowed newly-constructed, non-const grids to share their trees with existing const grids. (Where that behavior is still required, use a ConstPtrCast.) Python: - Fixed a build issue with Python 3 and NumPy. [Contributed by Jonathan Scruggs] Houdini: - Certain changes in this release (see "ABI changes" above) alter the grid ABI so that it is incompatible with earlier versions of the OpenVDB library, such as the ones built into Houdini 15, 15.5 and 16. To disable these changes and preserve ABI compatibility, define the macro OPENVDB_3_ABI_COMPATIBLE when compiling OpenVDB or any code that depends on OpenVDB. - Introduced some C++11 constructs that are incompatible with versions of Houdini older than 15.0. - Fixed a bug in the Rasterize Points SOP that caused vector-valued attributes to be transferred as scalars. [Contributed by Double Negative] - Added a toggle to the Clip SOP to invert the clipping mask. - Added a slider to the Scatter SOP to specify how far each point may be displaced from the center of its host voxel or tile. Version 3.2.0 - August 10, 2016 Highlights: - New features: tool to produce and store a sequences of progressively lower resolution grids (mipmaps), an acceleration structure for fast range and nearest-neighbor searches on particles, arbitrary volume and level set specific segmentation tools, a new binary mask grid type and an efficient point to level set conversion scheme. - Optimizations: Faster volume to mesh conversion and threaded grid destruction, morphological dilation, csg operations and fracture tool. - New Houdini nodes: Segment, LOD and Topology To Level Set. New features: - Added tools::MultiResGrid a tool to produce and store a sequences of progressively lower resolution grids (mipmaps). - Added tools::ParticleAtlas an acceleration structure for fast range and nearest-neighbor searches on particles, points with radius. - Added tools::segmentActiveVoxels(), which operates on grids of arbitrary type and separates connected components of a grid's active voxels into distinct grids or trees. - Added tools::segmentSDF(), which separates disjoint signed-distance-field surfaces into distinct grids or trees. - Added tools::extractActiveVoxelSegmentMasks(), which constructs a mask for each connected component of a grid's active voxels. - Added threaded level-set CSG tools csgUnionCopy(), csgIntersectionCopy() and csgDifferenceCopy(), which, unlike the existing CSG tools, produce new grids rather than modifying their input grids. These new tools are faster and use less memory than the existing tools (if only because the input grids never need to be deep-copied). - Added a threaded dilateActiveValues() tool with tile value support. - Added tools::PointsToMask, which activates voxels that intersect points from a given list. - Added a new MaskGrid type that uses a single bit-field to represent both voxel values and states for the leafnode to reduce memory usage. - Added tools::topologyToLevelSet(), which generates a level set from the implicit boundary between active and inactive voxels in an input grid of arbitrary type. - Added tools::LevelSetPlatonic a new tool that produces narrow-band level sets of the five Platonic solids. - Added tools::extractIsosurfaceMask() which masks voxels that intersect the implicit surface defined by the given isovalue. - Added tree::LeafManager::getPreFixSum() for user-managed external buffers. - Added tools::Dense::print(). - Added the math::CoordBBox::Iterator class to conveniently iterate over coordinates covered a CoordBBox. - Added bit-wise operations, a component-wise constructor, and a getCornerPoints() method to the CoordBBox class. - Added a new LeafManager constructor to create the structure from an existing array of leafnodes. - Added the templated math::MinMax class to compute the extrema of arbitrary value types. - Added sparseFill and denseFill methods to the Grid, Tree and RootNode classes. Improvements: - Complete overhaul of the VolumeToMesh tool brings significant performance improvements and enhanced region masking, tile support and bool volume surfacing. - Improved the performance, parallel scaling and memory usage, of tools::LevelSetFracture and updated to use the new segmentSDF scheme. - Improved the performance of tools::LevelSetAdvection by up to five times. - Improved the performance of Tree::voxelizeActiveTiles by means of multithreading. - Improved the performance of tools::meshToVolume(), particularly for large narrow-band widths and for signed distance fields with dense interior regions. - Threaded the Tree destructor and the Tree::clear() method. - Added a parameter to tools::signedFloodFill() and to tools::signedFloodFillWithValues() to constrain the flood fill to specific levels of the tree. - Added active tile count to Tree::print. - Improved the API of tools::Dense with non-const access methods. - Added LeafManager::reduce and similar methods to NodeManager. [Contributed by Brett Tully] - Improved constructors of math::Mat3 and math::Mat4. - Added math::Mat3::cofactor(). - Added math::Mat3::setRows, math::Mat4::setRows, math::Mat3::setColumns Mat3::setColumns, and math::Mat4::setColumns. - Added util::NodeMask::isConstant method for faster bit processing. - For better robustness, tools::Prune now uses the median of voxel values when replacing voxels with a tile. - Added a toggle to tools::PointPartitioner to select between cell-centered and node-centered transforms. Bug fixes: - Fixed a bug in tools::LevelSetAdvection that could cause non-deterministic behavior. [Reported by Jeff Lait] - Fixed a bug that allowed for unexpected implicit conversion between grids of different value types. - Fixed a bug whereby the origins of leaf nodes with value type bool were ignored during equality comparisons. - tools::GridTransformer now correctly handles affine transforms with shear and/or reflection. - Fixed a bug in tools::meshToVolume() that could produce incorrect distances for large narrow-band widths. - Fixed a bug in tools::meshToVolume() that produced different results on machines with different core counts. - Fixed a threading bug in tools::compReplace() that could cause crashes. - Resolved a floating-point exception in math::QuantizedUnitVec::pack() caused by calling the method with a zero-length vector. [Contributed by Rick Hankins] - Fixed a potential threading bug in io::Queue. [Contributed by Josip Sumecki] - Fixed a possible division-by-zero bug in openvdb/tools/LevelSetAdvect.h. [Contributed by Rick Hankins] - Corrected the outer product method to not return the transpose result. [Contributed by Gergely Klar] - Fixed a memory overallocation issue in tools::VolumeAdvect. - Fix bug in tools::VolumeToMesh failing to clear its state when exiting early. [Contributed by Edward Lam] - Fixed bug in tools::PointIndexIterator::worldSpaceSearchAndUpdate that resulted in missing point indices. [Reported by Rick Hankins] - Fixed Windows build issues in unit tests. [Contributed by Edward Lam and Steven Caron] - Fixed isApproxZero() so that it works correctly when tolerance is zero. [Reported by Joshua Olson] - Fixed bugs in tree::NodeUnion that could cause crashes. - Fixed memory leak in tools::mesh_to_volume_internal::ExpandNarrowband [Reported by Kvin Dietrich] - Fixed parameter type inconsistencies in math/Stencils.h and tools/RayIntersector.h. [Contributed by Kvin Dietrich and Nick Avramoussis] - Fixed a bug in the VolumeToMesh tool that produced artifacts for adaptive surface extraction on clipped level sets. [Reported by Jeff Lait] - Corrected empty grid background value intools::meshToVolume(). [Contributed by Jeff Lait] - Fixed a bug in tools::volumeToMesh that could produce NaNs. [Reported by Rick Hankins] - Fixed a bug in the "Advect Points SOP" that could cause a crash when the input grids were of incorrect type. [Reported by SideFX] API changes: - Deprecated math::Mat3::setBasis and math::Mat4::setBasis. - Renamed math::GudonovsNormSqrd to math::GodunovsNormSqrd [Contributed by Branislav Radjenovic] - Renamed ValueType to PosType in the PointArray interface. - Deprecated tree::Tree::addLeaf(LeafNode&) and added tree::Tree::addLeaf(LeafNode*). Python: - Updated the Python module for Python 3 compatibility. - Updated the Python module for Boost 1.60 compatibility, to address "no to_python (by-value) converter found" exceptions. Maya: - Fixed bugs related to data ownership, and improved error checking. [Contributed by Crawford Doran] - Updated the Read and Write DAG nodes to support file sequences and subframe evaluation. Houdini: - Added a Segment SOP that separates a grid's connected components into distinct grids. - Added a LOD SOP that produces a sequences of progressively lower resolution grids. - Added a Topology To Level Set SOP that generates a narrow-band signed distance field / level set from the interface between active and inactive voxels in an arbitrary grid. - Revamped the From Particles SOP UI and added a more efficient level set conversion method that supports Houdini 15 packed points. - Updated the Rasterize Points SOP with support for frustum transforms, sub region masking and orientation logic that matches the native Copy SOP's orientation. - Updated the Platonic SOP with support for all five Platonic solids. - Added hooks for registering SOP_NodeVDB text callbacks for different grid types. [Contributed by Nick Avramoussis] - The Resample and Combine SOPs now correctly handle affine transforms with shear and/or reflection. - Removed the StaggeredBoxSampler code path in SOP_OpenVDB_Advect because it introduces bias. [Contributed by Fredrik Salomonsson] - Fixed a bug in the Ray SOP whereby the distance attribute was created with the wrong data type. [Contributed by Nick Avramoussis] - The From Polygon SOP now allows the user to either specify the voxel count along an axis or the voxel size in world units (the only option in the past). Version 3.1.0 - October 1, 2015 Highlights: - New features: advection of arbitrary volumes, general-purpose preconditioned linear solver and Poisson solver, segmentation of topologically-enclosed regions of a volume, new and faster bitmask operators, concurrent paged array, volume diagnostics - Optimizations: threaded grid constructors and topology operations; faster mesh to volume conversion, SDF to fog volume conversion and grid pruning; faster, unbounded particle partitioning - New Houdini nodes: Advect, Diagnostics, Rasterize Points, Remap, Remove Divergence, Sort Points New features: - Added tools::VolumeAdvection, for sparse advection of non-level-set volumes. - Added a preconditioned conjugate gradient solver. - Added a Poisson solver for functions sampled on grids. - Added tools::extractEnclosedRegion, which detects topologically-enclosed (watertight) exterior regions (cavities) that can result from CSG union operations between level sets with concavities that are capped. (See the unit test TestPoissonSolver::testSolveWithSegmentDomain for an example in which this tool is used to identify regions of trapped fluid when solving for pressure in a volume of incompressible fluid.) - Added util::PagedArray, a concurrent, dynamic linear array data structure with fast O(1) value access (both random and sequential). - Added tools::Sampler, which provides a unified API for both staggered and non-staggered interpolation of various orders. - Added equality and inequality operators to Metadata and MetaMap. - Added tools::CheckLevelSet and tools::CheckFogVolume, which perform various tests on symmetric, narrow-band level sets and fog volumes, respectively, to diagnose potential issues. - Added support for value accessors that aren't registered with their trees. (Bypassing accessor registration can improve performance in rare cases but should be used with caution, since the accessor will be left in an invalid state if the tree topology is modified.) - Added a tree::stealNodes() method that transfers ownership of all nodes of a certain type and inserts them into a linear array. - Added a tools::createLevelSetBox() factory function for level-set grids. - Added tools::Dense::offsetToCoord(). - Added LeafNode::Buffer::data(), which provides direct access to a leaf node's voxel value array, avoiding out-of-core overhead. Use with caution. - Added a util::NodeMask::foreach() method for efficient evaluation of complex bitwise operations. - Added a bitwise difference method to util::NodeMask. - Added a -version flag to vdb_print, vdb_render and vdb_view. Improvements: - Deep, conversion and topology copy Grid constructors are now threaded and up to five times faster. - Grid::topologyUnion(), Grid::topologyIntersection() and Grid::topologyDifference() are now much faster due to threading. - Significantly improved the performance, parallel scaling and memory usage of the tools::meshToVolume(), and implemented a more robust inside/outside sign classification scheme. - Reimplemented tools::PointPartitioner for improved performance, concurrency and memory usage. The tool is now unbounded in the sense that points may be distributed anywhere in index space. - Significantly improved the performance of tools::sdfToFogVolume(). - Significantly improved the performance of the tools::sdfInteriorMask() and added support for both grid and tree inputs. - Made various optimizations and improvements to tools::LevelSetMorphing. - Aggregated tools::DiscreteField and tools::EnrightField (formerly in tools/LevelSetAdvect.h) and tools::VelocitySampler and tools::VelocityIntegrator (formerly in tools/PointAdvect.h) into a single header, tools/VelocityFields.h. - Modified tools::signedFloodFill() to accept grids of any signed scalar value type, not just floating-point grids. - tools::prune() is now faster, and it employs an improved compression technique on trees with floating-point values. Bug fixes: - Fixed a build issue that could result in spurious "Blosc encoding is not supported" errors unless OPENVDB_USE_BLOSC was #defined when compiling client code. - Added NaN and inf checks to tools::PointPartitioner. - Fixed a vdb_view issue whereby the frame buffer size did not necessarily match the window size. [Contributed by Rafael Campos] - Fixed a roundoff issue in tools::LevelSetTracker that could result in NaNs. - Changed tools::CheckNormGrad to check the magnitude of the gradient rather than the square of the magnitude. - Fixed parameter type inconsistencies in math/Ray.h and tools/RayIntersector.h. [Contributed by Kvin Dietrich] - Fixed incorrect handling of signed values in tools::clip() (and the Clip SOP). API changes: - Removed the math::Hermite class since it was no longer used and caused build issues for some. - Refactored the LevelSetAdvection, LevelSetFilter, LevelSetMeasure and LevelSetTracker tools. - Extended the API of tools::Diagnose and disabled copy construction. - Extended and unified the API of various Samplers. - Added an optional template argument to the ValueAccessor class to allow for unregistered accessors. Houdini: - Added a Rasterize Points SOP that produces density volumes and transfers arbitrary point attributes using a weighted-average scheme. The node incorporates a VOP subnetwork for procedural modeling, and its accompanying creation script defines a default network with VEX procedures for cloud and velocity field modeling. (See the creation script file header for installation details.) - Merged the Advect Level Set SOP into a new Advect SOP that supports advection of arbitrary volumes, not just level sets. - Added a Remove Divergence SOP that eliminates divergence from a velocity field. - Added a Diagnostics SOP that can identify various problems with level sets, fog volumes and other grids. - Added a Sort Points SOP that spatially reorders a list of points so that points that are close together in space are also close together in the list. This can improve CPU cache coherency and performance for random-access operations. - Added a Remap SOP that maps voxel values in an input range to values in an output range through a user-defined transfer function. - Added an option to the Convert SOP to activate interior voxels. [Contributed by SESI] - The To Spheres SOP can now optionally output a pscale attribute. - Added openvdb_houdini::SOP_NodeVDB::duplicateSourceStealable(), which in conjunction with the Unload flag can help to minimize deep copying of grids between nodes. The Advect, Convert, Fill, Filter, Fracture, Noise, Offset Level Set, Prune, Remap, Remove Divergence, Renormalize Level Set, Resize Narrow Band, Smooth Level Set and Transform SOPs all have this optimization enabled, meaning that they can potentially steal, rather than copy, data from upstream nodes that have the Unload flag enabled. [Contributed by Double Negative] - Redesigned the UI of the Visualize SOP and added toggles to draw with or without color, to use the grid name as the attribute name for points with values, and to attach grid index coordinates to points. - Added toggles to the Filter, Rebuild Level Set, Resize Narrow Band, Smooth Level Set and To Spheres SOPs to specify units in either world space or index space. - Fixed an issue whereby grids generated by the Rebuild Level Set SOP did not always display as surfaces in the viewport. - The Metadata SOP now sets appropriate viewport visualization options when the grid class is changed. Version 3.0.0 - January 14, 2015 - The io::File class now supports delayed loading of .vdb files, meaning that memory is not allocated for voxel values until the values are actually accessed. (This feature is enabled by default.) Until a grid has been fully loaded, its source .vdb file must not be modified or deleted, so for safety, io::File::open() automatically makes private copies of source files that are smaller than a user-specified limit (see io::File::setCopyMaxBytes()). The limit can be set to zero to disable copying, but if it cannot be guaranteed that a file will not be modified, then it is best not to enable delayed loading for that file. - .vdb files can now optionally be compressed with the Blosc LZ4 codec. Blosc compresses almost as well as ZLIB, but it is much faster. - Added tools::PointPartitioner, a tool for fast spatial sorting of points stored in an external array, and tools::PointIndexGrid, an acceleration structure for fast range and nearest-neighbor searches. - Added tree::NodeManager, which linearizes a tree to facilitate efficient multithreading across all tree levels. - Added tools::prune() (and other variants), which replaces and outperforms Tree::prune(). - Added tools::signedFloodFill(), which replaces and outperforms Tree::signedFloodFill(). - Added tools::changeBackground (and other variants), which replaces and outperforms Tree::setBackground(). - Added a fast but approximate narrow-band level set dilation method, a fast narrow-band level set erosion method, and a masked normalization method to tools::LevelSetTracker. - Added tools::Diagnose, which performs multithreaded diagnostics on grids to identify issues like values that are NaNs or out-of-range. It optionally generates a boolean grid of all values that fail user-defined tests. - Added optional alpha masks to tools::LevelSetMorphing. - Fixed an intermittent crash in tools::LevelSetMorphing. - Added tools::topologyToLevelSet(), which generates a level set from the implicit boundary between active and inactive voxels in an arbitrary input grid. [DWA internal] - Improved the performance of point scattering (by orders of magnitude) and added a DenseUniformPointScatter class as well as support for fractional numbers of particles per voxel. - Added edge-adjacent (6+12=18 neighbors) and vertex-adjacent (6+12+8=26 neighbors) dilation algorithms to tools::Morphology::dilateVoxels(). The default dilation pattern is still face-adjacent (6 neighbors). - Improved the performance and memory footprint of the ParticlesToLevelSet tool for large numbers (tens to hundreds of millions) of particles. - Added Tree::getNodes(), which allows for fast construction of linear arrays of tree nodes for use in multithreaded code such as the LeafManager or NodeManager. - Added math::Extrema and tools::extrema() to efficiently compute minimum and maximum values in a grid. - Added support for material color grids to all level set shaders, and added an option to vdb_render that allows one to specify a reference grid to be used for material color lookups. - Added openvdb::getLibraryVersionString() and OPENVDB_LIBRARY_VERSION_STRING. - Modified the mesh to volume converter to always set the grid background value to the exterior narrow-band width, and added finite value checks to narrow band parameters. - tools::volumeToMesh() now compiles for all grid types but throws an exception if the input grid does not have a scalar value type. - Added an io::File::readGrid() overload and readBuffers() overloads to the grid, tree and node classes that allow one to specify a bounding box against which to clip a grid while reading it. For large grids, clipping while reading can result in significantly lower memory usage than clipping after reading. - Added Grid::clipGrid(), which clips a grid against a world-space bounding box, and Grid::clip() and Tree::clip(), which clip against an index-space bounding box. - Added tools::clip(), which clips a grid either against a bounding box or against the active voxels of a mask grid. - io::File::readGridPartial() allocates the nodes of a grid's tree as before, but it now allocates leaf nodes without data buffers. (This feature is mainly for internal use; partially-read grids should be used with care if at all, and they should be treated as read-only.) - Grid names retrieved using an io::File::NameIterator now always uniquely identify grids; they no longer generate 'more than one grid named "x"' warnings when there are multiple grids of the same name in a file (for files written starting with this version of the OpenVDB library). - Fixed a bug in Tree::ValueOffIter that could cause depth-bounded iterators to return incorrect values. - Eliminated a recursive call in TreeValueIteratorBase::advance() that could cause crashes on systems with a limited stack size. - Fixed memory leaks in RootNode::topologyDifference() and RootNode::topologyIntersection(). - Fixed a memory leak in io::Queue when the queue was full and a write task could not be added within the timeout interval. - Fixed a potential division by zero crash in tools::compDiv() with integer-valued grids. - Fixed kernel normalization in tools::Filter so that it is correct for integer-valued grids. - Fixed a bug in LeafNode::Buffer::getValue() whereby Visual C++ would return a reference to a temporary. [Contributed by SESI] - Fixed a bug in tools::ParticlesToLevelSet related to attribute transfer when leaf nodes are produced without active values. - Added util/CpuTimer.h and removed the more simplistic CpuTimer from unittest/util.h. - Eliminated the use of getopt() for command-line argument parsing in vdb_test. - openvdb::initialize() now properly initializes log4cplus if it is enabled, eliminating "No appenders could be found" errors. - Fixed a bug in the QuantizedUnitVec::pack() method that caused quantization artifacts. - Added convenience class tools::AlphaMask to tools/Interpolation.h - Added constructors and methods to both math::RandInt and math::Rand01 to set and reset the random seed value. - Added convenience methods for transforming bounding boxes to math::Transform. - vdb_view is now compatible with both GLFW 2 and GLFW 3. - Made many small changes to address type conversion and other warnings reported by newer compilers like GCC 4.8 and ICC 14. - Replaced the HALF_INCL_DIR and HALF_LIB_DIR Makefile variables with ILMBASE_INCL_DIR and ILMBASE_LIB_DIR and added ILMBASE_LIB, to match OpenEXR's library organization. [Contributed by Double Negative] - Eliminated most local (function-scope) static variables, because Visual C++ doesn't guarantee thread-safe initialization of local statics. [Contributed by SESI] - Fixed a bug in readString() related to empty strings. [Contributed by Fabio Piparo] - Fixed a bug in the tools::VolumeToMesh simplification scheme that was creating visual artifacts. API changes: - The addition of a GridBase::readBuffers() virtual function overload and the GridBase::clip(), GridBase::readNonresidentBuffers() and Tree::clipUnallocatedNodes() virtual functions changes the grid ABI so that it is incompatible with earlier versions of the OpenVDB library (such as the ones in Houdini 12.5 and 13). Define the macro OPENVDB_2_ABI_COMPATIBLE when compiling OpenVDB to disable these changes and preserve ABI compatibility. - All shaders now have a template argument to specify the type of an optional material color grid, but the default type mimics the old, uniform color behavior. - Removed a deprecated io::Stream::write() overload. - The point counts in the UniformPointScatter and NonUniformPointScatter tools are now specified and returned as Index64. - math::RandInt has an extra template argument to specify the integer type. The RandomInt typedef is unchanged. - io::readData(), io::HalfReader::read() and io::HalfWriter::write() now take a uint32_t argument indicating the type of compression instead of a bool indicating whether compression is enabled. - Removed io::Archive::isCompressionEnabled() and io::Archive::setCompressionEnabled() and renamed io::Archive::compressionFlags() and io::Archive::setCompressionFlags() to io::Archive::compression() and io::Archive::setCompression(). - Internal and leaf node classes are now required to provide "PartialCreate" constructors that optionally bypass the allocation of voxel buffers. Leaf node classes must now also provide allocate() and isAllocated() methods to manage the allocation of their buffers. - Removed pruneInactive() and pruneLevelSet() methods from the Tree and various node classes. These methods have been replaced by the much faster pruning functions found in tools/Prune.h. - Removed signedFloodFill() methods from the Grid, Tree and various node classes. These methods have been replaced by the much faster functions found in tools/SignedFloodFill.h. - Removed Grid::setBackground() and Tree::setBackground() (use the faster changeBackground() tool instead), and removed the default argument from RootNode::setBackground(). Python: - Added grid methods convertToPolygons() and convertToQuads(), which convert volumes to meshes, and createLevelSetFromPolygons(), which converts meshes to volumes. NumPy is required. Maya: - Added an adaptive polygonal surface extraction node. Houdini: - Added a new Resize Narrow Band SOP that can efficiently adjust the width of a level set's narrow band. This allows, for example, for a level set to be created quickly from points or polygons with a very narrow band that is then quickly resized to a desired width. - Fixed bugs in the Smooth Level Set and Reshape Level Set SOPs that caused them to ignore the selected discretization scheme. - Added a Morph Level Set SOP. - Added a From Points SOP to very quickly generate a level set from a point cloud, ignoring any radius attribute. [DWA internal] - Added a Voxel Scale mode to the Resample SOP. - Improved the performance and memory footprint of the From Particles SOP for large numbers (tens to hundreds of millions) of particles. - The Scatter SOP now accepts fractional numbers of particles per voxel. - Improved the performance of the Scatter SOP by more than an order of magnitude. - The Clip SOP now has a toggle to choose explicitly between a mask grid or a bounding box as the clipping region. As a consequence, the mask grid can now be unnamed. - Added the OpenVDB library version number to the Extended Operator Information for all SOPs. - SOPs are now linked with an rpath to the directory containing the OpenVDB library. - Like the native Houdini file SOP, the Read SOP now allows missing frames to be reported either as errors or as warnings. - The Read SOP now has an optional input for geometry, the bounding box of which can be used to clip grids as they are read. For large grids, clipping while reading can result in significantly lower memory usage than clipping after reading. - The From Polygons and Convert SOPs now default to using the polygon soup mesh representation, which uses less memory. Version 2.3.0 - April 23, 2014 - Added tools::extractSparseTree(), which selectively extracts and transforms data from a dense grid to produce a sparse tree, and tools::extractSparseTreeWithMask(), which copies data from the index-space intersection of a sparse tree and a dense input grid. - Added copy constructors to the Grid, Tree, RootNode, InternalNode and LeafNode classes, and an assignment operator overload to RootNode, that allow the source and destination to have different value types. - Modified Tree::combine2() to permit combination of trees with different value types. - Added CanConvertType and RootNode::SameConfiguration metafunctions, which perform compile-time tests for value type and tree type compatibility, and a RootNode::hasCompatibleValueType() method, which does runtime checking. - Added optional support for logging using log4cplus. See logging.h and the INSTALL file for details. - Added VolumeRayIntersector::hits(), which returns all the hit segments along a ray. This is generally more efficient than repeated calls to VolumeRayIntersector::march(). - Added member class Ray::TimeSpan and method Ray::valid(), and deprecated method Ray::test(). - Fixed a bug in VolumeHDDA that could cause rendering artifacts when a ray's start time was zero. [Contributed by Mike Farnsworth] - Added tools::compositeToDense(), which composites data from a sparse tree into a dense array, using a sparse alpha mask. Over, Add, Sub, Min, Max, Mult, and Set are supported operations. - Added tools::transformDense(), which applies a functor to the value of each voxel of a dense grid within a given bounding box. - Improved the performance of node iterators. API changes: - Collected the digital differential analyzer code from math/Ray.h and tools/RayIntersector.h into a new header file, math/DDA.h. - Rewrote VolumeHDDA and made several changes to its API. (VolumeHDDA is used internally by VolumeRayIntersector, whose API is unchanged.) - Tree::combine2(), RootNode::combine2(), InternalNode::combine2(), LeafNode::combine2() and CombineArgs all now require an additional template argument, which determines the type of the other tree. - Assignment operators for LeafManager::LeafRange::Iterator, BaseMaskIterator, NodeMask and RootNodeMask now return references to the respective objects. - Removed a number of methods that were deprecated in version 2.0.0 or earlier. Houdini: - Added a Clip SOP, which does volumetric clipping. - Added an Occlusion Mask SOP, which generates a mask of the voxels inside a camera frustum that are occluded by objects in an input grid. - The Combine SOP now applies the optional signed flood fill only to level set grids, since that operation isn't meaningful for other grids. - The Filter SOP now processes all grid types, not just scalar grids. Version 2.2.0 - February 20, 2014 - Added a simple, multithreaded volume renderer, and added volume rendering support to the vdb_render command-line renderer. - Added an option to the LevelSetRayIntersector and to vdb_render to specify the isovalue of the level set. - Added methods to the LevelSetRayIntersector to return the time of intersection along a world or index ray and to return the level set isovalue. - Improved the performance of the VolumeRayIntersector and added support for voxel dilation to account for interpolation kernels. - Added a section to the Cookbook on interpolation using BoxSampler, GridSampler, DualGridSampler, et al. - Added a section to the Overview on grids and grid metadata. - Modified tools::DualGridSampler so it is more consistent with tools::GridSampler. - tools::cpt(), tools::curl(), tools::laplacian(), tools::meanCurvature() and tools::normalize() now output grids with appropriate vector types (covariant, contravariant, etc.). - Added tools::transformVectors(), which applies an affine transformation to the voxel values of a vector-valued grid in accordance with the grid's Vector Type and World Space/Local Space metadata setting. - Added tools::compDiv(), which combines grids by dividing the values of corresponding voxels. - Fixed a bug in the mean curvature computation that could produce NaNs in regions with constant values. - Added a Grid::topologyDifference() method. - Added exp() and sum() methods to math::Vec2, math::Vec3 and math::Vec4. - Improved tools::fillWithSpheres() for small volumes that are just a few voxels across. - Improved the accuracy of the mesh to volume converter. - Fixed a bug in the mesh to volume converter that caused incorrect sign classifications for narrow-band level sets. - Fixed a bug in NonlinearFrustumMap::applyIJT() that resulted in incorrect values when computing the gradient of a grid with a frustum transform. - Fixed a file I/O bug whereby some .vdb files could not be read correctly if they contained grids with more than two distinct inactive values. - Fixed an off-by-one bug in the numbering of unnamed grids in .vdb files. The first unnamed grid in a file is now retrieved using the name "[0]", instead of "[1]". - Fixed a build issue reported by Clang 3.2 in tools/GridOperators.h. - Fixed a memory leak in tools::Film. - Added library and file format version number constants to the Python module. - Improved convergence in the volume renderer. [Contributed by Jerry Tessendorf and Mark Matthews] - Made various changes for compatibility with Houdini 13 and with C++11 compilers. [Contributed by SESI] API changes: - tools::VolumeRayIntersector::march() no longer returns an int to distinguish tile vs. voxel hits. Instead, it now returns false if no intersection is detected and true otherwise. Also, t0 and t1 might now correspond to the first and last hits of multiple adjacent leaf nodes and/or active tiles. - tools::DualGridSampler is no longer templated on the target grid type, and the value accessor is now passed as an argument. - The .vdb file format has changed slightly. Tools built with older versions of OpenVDB should be recompiled to ensure that they can read files in the new format. Houdini: - Added topology union, intersection and difference operations to the Combine SOP. These operations combine the active voxel topologies of grids that may have different value types. - Added a Divide operation to the Combine SOP. - Added support for boolean grids to the Combine, Resample, Scatter, Prune and Visualize SOPs. - The Fill SOP now accepts a vector as the fill value, and it allows the fill region bounds to be specified either in index space (as before), in world space, or using the bounds of geometry connected to an optional new reference input. - Added a toggle to the Offset Level Set SOP to specify the offset in either world or voxel units. - Added a toggle to the Transform and Resample SOPs to apply the transform to the voxel values of vector-valued grids, in accordance with those grids' Vector Type and World Space/Local Space metadata settings. - Added a Vector Type menu to the Vector Merge SOP. - Removed masking options from the Renormalize SOP (since masking is not supported yet). - Reimplemented the Vector Merge SOP for better performance and interruptibility and to fix a bug in the handling of tile values. 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 ZLIB and produces comparable file sizes for level set and fog volume grids. (ZLIB 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. - ZLIB 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/io/0000755000000000000000000000000013200122400011600 5ustar rootrootopenvdb/io/Compression.cc0000644000000000000000000002276613200122377014442 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #ifdef OPENVDB_USE_BLOSC #include #endif 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("zip"); if (flags & COMPRESS_BLOSC) words.push_back("blosc"); 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. if (data == nullptr) { is.seekg(-numZippedBytes, std::ios_base::cur); } else { 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 { if (data == nullptr) { // Seek over the compressed data. is.seekg(numZippedBytes, std::ios_base::cur); } 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")); } } } } #ifndef OPENVDB_USE_BLOSC void bloscToStream(std::ostream&, const char*, size_t, size_t) { OPENVDB_THROW(IoError, "Blosc encoding is not supported"); } #else void bloscToStream(std::ostream& os, const char* data, size_t valSize, size_t numVals) { const size_t inBytes = valSize * numVals; int outBytes = int(inBytes) + BLOSC_MAX_OVERHEAD; boost::shared_array compressedData(new char[outBytes]); outBytes = blosc_compress_ctx( /*clevel=*/9, // 0 (no compression) to 9 (maximum compression) /*doshuffle=*/true, /*typesize=*/sizeof(float), //for optimal float and Vec3f compression /*srcsize=*/inBytes, /*src=*/data, /*dest=*/compressedData.get(), /*destsize=*/outBytes, BLOSC_LZ4_COMPNAME, /*blocksize=*/inBytes,//previously set to 256 (in v3.x) /*numthreads=*/1); if (outBytes <= 0) { std::ostringstream ostr; ostr << "Blosc failed to compress " << inBytes << " byte" << (inBytes == 1 ? "" : "s"); if (outBytes < 0) ostr << " (internal error " << outBytes << ")"; OPENVDB_LOG_DEBUG(ostr.str()); // Write the size of the uncompressed data. Int64 negBytes = -inBytes; os.write(reinterpret_cast(&negBytes), 8); // Write the uncompressed data. os.write(reinterpret_cast(data), inBytes); } else { // Write the size of the compressed data. Int64 numBytes = outBytes; os.write(reinterpret_cast(&numBytes), 8); // Write the compressed data. os.write(reinterpret_cast(compressedData.get()), outBytes); } } #endif #ifndef OPENVDB_USE_BLOSC void bloscFromStream(std::istream&, char*, size_t) { OPENVDB_THROW(IoError, "Blosc decoding is not supported"); } #else void bloscFromStream(std::istream& is, char* data, size_t numBytes) { // Read the size of the compressed data. // A negative size indicates uncompressed data. Int64 numCompressedBytes; is.read(reinterpret_cast(&numCompressedBytes), 8); if (numCompressedBytes <= 0) { // Read the uncompressed data. if (data == nullptr) { is.seekg(-numCompressedBytes, std::ios_base::cur); } else { is.read(data, -numCompressedBytes); } if (size_t(-numCompressedBytes) != numBytes) { OPENVDB_THROW(RuntimeError, "Expected to read a " << numBytes << "-byte uncompressed chunk, got a " << -numCompressedBytes << "-byte chunk"); } } else { if (data == nullptr) { // Seek over the compressed data. is.seekg(numCompressedBytes, std::ios_base::cur); } else { // Read the compressed data. boost::shared_array compressedData(new char[numCompressedBytes]); is.read(reinterpret_cast(compressedData.get()), numCompressedBytes); // Uncompress the data. const int numUncompressedBytes = blosc_decompress_ctx( /*src=*/compressedData.get(), /*dest=*/data, numBytes, /*numthreads=*/1); if (numUncompressedBytes < 1) { OPENVDB_LOG_DEBUG("blosc_decompress() returned error code " << numUncompressedBytes); } if (numUncompressedBytes != Int64(numBytes)) { OPENVDB_THROW(RuntimeError, "Expected to decompress " << numBytes << " byte" << (numBytes == 1 ? "" : "s") << ", got " << numUncompressedBytes << " byte" << (numUncompressedBytes == 1 ? "" : "s")); } } } } #endif } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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.h0000644000000000000000000002435213200122377013355 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Compression.h" // for COMPRESS_ZIP, etc. #include #include #include #include // for VersionId #include #include #include #include #include #include class TestFile; namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { class GridDescriptor; /// Grid serializer/unserializer class OPENVDB_API Archive { public: using Ptr = SharedPtr; using ConstPtr = SharedPtr; static const uint32_t DEFAULT_COMPRESSION_FLAGS; Archive(); Archive(const Archive&) = default; Archive& operator=(const Archive&) = default; virtual ~Archive(); /// @brief Return a copy of this archive. virtual 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 OpenVDB library includes support for the Blosc compressor. static bool hasBloscCompression(); /// Return a bit mask specifying compression options for the data stream. uint32_t compression() const { return mCompression; } /// @brief Specify whether and how the data stream should be compressed. /// @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 setCompression(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 {} /// @brief Return @c true if delayed loading is enabled. /// @details If enabled, delayed loading can be disabled for individual files, /// but not vice-versa. /// @note Define the environment variable @c OPENVDB_DISABLE_DELAYED_LOAD /// to disable delayed loading unconditionally. static bool isDelayedLoadingEnabled(); 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&); /// 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&); #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// @brief Populate the given grid from the input stream, but only where it /// intersects the given world-space bounding box. static void readGrid(GridBase::Ptr, const GridDescriptor&, std::istream&, const BBoxd&); /// @brief Populate the given grid from the input stream, but only where it /// intersects the given index-space bounding box. static void readGrid(GridBase::Ptr, const GridDescriptor&, std::istream&, const CoordBBox&); #endif using NamedGridMap = std::map; /// @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-2017 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.h0000644000000000000000000001333113200122377014713 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 GridDescriptor&) = default; GridDescriptor& operator=(const GridDescriptor&) = default; ~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-2017 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.h0000644000000000000000000002466213200122377013064 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for std::copy #include #include // for std::back_inserter #include 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. /// using FilenameMap = tbb::concurrent_hash_map; /// 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(std::bind(&MyNotifier::callback, ¬ifier, /// std::placeholders::_1, std::placeholders::_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 using Id = Index32; /// 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; using Notifier = std::function; /// @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&); struct Impl; std::unique_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-2017 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.cc0000644000000000000000000002411213200122377013210 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // 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 { using Mutex = tbb::mutex; using Lock = Mutex::scoped_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) {} ~Task() override {} 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) {} tbb::task* execute() override { 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 nullptr; // no successor to this task } private: GridCPtrVec mGrids; SharedPtr mArchive; MetaMap mMetadata; }; } // unnamed namespace //////////////////////////////////////// // Private implementation details of a Queue struct Queue::Impl { using NotifierMap = std::map; /// @todo Provide more information than just "succeeded" or "failed"? using StatusMap = tbb::concurrent_hash_map; 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 = std::bind(&Impl::setStatusWithNotification, this, std::placeholders::_1, std::placeholders::_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) { const Queue::Id taskId = mImpl->mNextId++; // From the "GUI Thread" chapter in the TBB Design Patterns guide OutputTask* task = new(tbb::task::allocate_root()) OutputTask(taskId, grids, archive, metadata); try { mImpl->enqueue(*task); } catch (openvdb::RuntimeError&) { // Destroy the task if it could not be enqueued, then rethrow the exception. tbb::task::destroy(*task); throw; } return taskId; } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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.h0000644000000000000000000002566013200122377012656 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "io.h" // for MappedFile::Notifier #include "Archive.h" #include "GridDescriptor.h" #include // for std::copy() #include #include // for std::back_inserter() #include #include #include 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: using NameMap = std::multimap; using NameMapCIter = NameMap::const_iterator; explicit File(const std::string& filename); ~File() override; /// @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. SharedPtr copy() const override; /// @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; /// @brief 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. /// @details If @a delayLoad is true, map the file into memory and enable delayed loading /// of grids, and if a notifier is provided, call it when the file gets unmapped. /// @note Define the environment variable @c OPENVDB_DISABLE_DELAYED_LOAD to disable /// delayed loading unconditionally. /// @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. /// @see setCopyMaxBytes bool open(bool delayLoad = true, const MappedFile::Notifier& = MappedFile::Notifier()); /// Return @c true if the file has been opened for reading. bool isOpen() const; /// Close the file once we are done reading from it. void close(); /// @brief Return this file's current size on disk in bytes. /// @throw IoError if the file size cannot be determined. Index64 getSize() const; /// @brief Return the size in bytes above which this file will not be /// automatically copied during delayed loading. Index64 copyMaxBytes() const; /// @brief If this file is opened with delayed loading enabled, make a private copy /// of the file if its size in bytes is less than the specified value. /// @details Making a private copy ensures that the file can't change on disk /// before it has been fully read. /// @warning If the file is larger than this size, it is the user's responsibility /// to ensure that it does not change on disk before it has been fully read. /// Undefined behavior and/or a crash might result otherwise. /// @note Copying is enabled by default, but it can be disabled for individual files /// by setting the maximum size to zero bytes. A default size limit can be specified /// by setting the environment variable @c OPENVDB_DELAYED_LOAD_COPY_MAX_BYTES /// to the desired number of bytes. void setCopyMaxBytes(Index64 bytes); /// 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&); /// Read an entire grid, including all of its data blocks. GridBase::Ptr readGrid(const Name&); #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// @brief Read a grid, including its data blocks, but only where it /// intersects the given world-space bounding box. GridBase::Ptr readGrid(const Name&, const BBoxd&); #endif /// @todo GridPtrVec readAllGrids(const Name&) /// @brief Write the grids in the given container to the file whose name /// was given in the constructor. void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const override; /// @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(const NameIterator&) = default; ~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: /// 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; /// @brief Read a grid, including its data blocks, but only where it /// intersects the given world-space bounding box. GridBase::Ptr readGridByName(const Name&, const BBoxd&); /// 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; #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// Read in and return the region of the grid specified by the given grid descriptor /// that intersects the given world-space bounding box. GridBase::Ptr readGrid(const GridDescriptor&, const BBoxd&) const; /// Read in and return the region of the grid specified by the given grid descriptor /// that intersects the given index-space bounding box. GridBase::Ptr readGrid(const GridDescriptor&, const CoordBBox&) const; #endif /// @brief 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; /// @brief Retrieve a grid from @c mNamedGrids. Return a null pointer /// if @c mNamedGrids was not populated (because this file is random-access). /// @throw KeyError if no grid with the given name exists in this file. GridBase::Ptr retrieveCachedGrid(const Name&) const; void writeGrids(const GridCPtrVec&, const MetaMap&) const; MetaMap::Ptr fileMetadata(); MetaMap::ConstPtr fileMetadata() const; const NameMap& gridDescriptors() const; NameMap& gridDescriptors(); std::istream& inputStream() const; friend class ::TestFile; friend class ::TestStream; struct Impl; std::unique_ptr mImpl; }; //////////////////////////////////////// inline void File::write(const GridCPtrVec& grids, const MetaMap& meta) const { this->writeGrids(grids, meta); } template inline void File::write(const GridPtrContainerT& container, const MetaMap& meta) const { GridCPtrVec grids; std::copy(container.begin(), container.end(), std::back_inserter(grids)); this->writeGrids(grids, meta); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_FILE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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/io.h0000644000000000000000000002665213200122377012410 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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_IO_HAS_BEEN_INCLUDED #define OPENVDB_IO_IO_HAS_BEEN_INCLUDED #include #include // for SharedPtr #include #include #include #include // for std::ios_base #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { class MetaMap; namespace io { /// @brief Container for metadata describing how to unserialize grids from and/or /// serialize grids to a stream (which file format, compression scheme, etc. to use) /// @details This class is mainly for internal use. class OPENVDB_API StreamMetadata { public: using Ptr = SharedPtr; using ConstPtr = SharedPtr; StreamMetadata(); StreamMetadata(const StreamMetadata&); explicit StreamMetadata(std::ios_base&); ~StreamMetadata(); StreamMetadata& operator=(const StreamMetadata&); /// @brief Transfer metadata items directly to the given stream. /// @todo Deprecate direct transfer; use StreamMetadata structs everywhere. void transferTo(std::ios_base&) const; uint32_t fileVersion() const; void setFileVersion(uint32_t); VersionId libraryVersion() const; void setLibraryVersion(VersionId); uint32_t compression() const; void setCompression(uint32_t); uint32_t gridClass() const; void setGridClass(uint32_t); const void* backgroundPtr() const; void setBackgroundPtr(const void*); bool halfFloat() const; void setHalfFloat(bool); bool writeGridStats() const; void setWriteGridStats(bool); bool seekable() const; void setSeekable(bool); bool countingPasses() const; void setCountingPasses(bool); uint32_t pass() const; void setPass(uint32_t); //@{ /// @brief Return a (reference to a) copy of the metadata of the grid /// currently being read or written. /// @details Some grid metadata might duplicate information returned by /// gridClass(), backgroundPtr() and other accessors, but those values /// are not guaranteed to be kept in sync. MetaMap& gridMetadata(); const MetaMap& gridMetadata() const; //@} using AuxDataMap = std::map; //@{ /// @brief Return a map that can be populated with arbitrary user data. AuxDataMap& auxData(); const AuxDataMap& auxData() const; //@} /// Return a string describing this stream metadata. std::string str() const; private: struct Impl; std::unique_ptr mImpl; }; // class StreamMetadata /// Write a description of the given metadata to an output stream. std::ostream& operator<<(std::ostream&, const StreamMetadata&); std::ostream& operator<<(std::ostream&, const StreamMetadata::AuxDataMap&); //////////////////////////////////////// /// @brief Leaf nodes that require multi-pass I/O must inherit from this struct. /// @sa Grid::hasMultiPassIO() struct MultiPass {}; //////////////////////////////////////// class File; /// @brief Handle to control the lifetime of a memory-mapped .vdb file class OPENVDB_API MappedFile { public: using Ptr = SharedPtr; ~MappedFile(); MappedFile(const MappedFile&) = delete; // not copyable MappedFile& operator=(const MappedFile&) = delete; /// Return the filename of the mapped file. std::string filename() const; /// @brief Return a new stream buffer for the mapped file. /// @details Typical usage is /// @code /// openvdb::io::MappedFile::Ptr mappedFile = ...; /// auto buf = mappedFile->createBuffer(); /// std::istream istrm{buf.get()}; /// // Read from istrm... /// @endcode /// The buffer must persist as long as the stream is open. SharedPtr createBuffer() const; using Notifier = std::function; /// @brief Register a function that will be called with this file's name /// when the file is unmapped. void setNotifier(const Notifier&); /// Deregister the notifier. void clearNotifier(); private: friend class File; explicit MappedFile(const std::string& filename, bool autoDelete = false); class Impl; std::unique_ptr mImpl; }; // class MappedFile //////////////////////////////////////// /// Return a string (possibly empty) describing the given system error code. std::string getErrorString(int errorNum); /// Return a string (possibly empty) describing the most recent system error. std::string getErrorString(); //////////////////////////////////////// /// @brief Return the file format version number associated with the given input stream. /// @sa File::setFormatVersion() OPENVDB_API uint32_t getFormatVersion(std::ios_base&); /// @brief Return the (major, minor) library version number associated with the given input stream. /// @sa File::setLibraryVersion() OPENVDB_API VersionId getLibraryVersion(std::ios_base&); /// @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::ios_base&); /// 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); /// @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 true if floating-point values should be quantized to 16 bits when writing /// to the given stream or promoted back from 16-bit to full precision when reading from it. OPENVDB_API bool getHalfFloat(std::ios_base&); /// @brief Specify whether floating-point values should be quantized to 16 bits when writing /// to the given stream or promoted back from 16-bit to full precision when reading from it. OPENVDB_API void setHalfFloat(std::ios_base&, bool); /// @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); /// @brief Return @c true if grid statistics (active voxel count and bounding box, etc.) /// should be computed and stored as grid metadata when writing to the given stream. OPENVDB_API bool getWriteGridStatsMetadata(std::ios_base&); /// @brief Specify whether to compute grid statistics (active voxel count and bounding box, etc.) /// and store them as grid metadata when writing to the given stream. OPENVDB_API void setWriteGridStatsMetadata(std::ios_base&, bool writeGridStats); /// @brief Return a shared pointer to the memory-mapped file with which the given stream /// is associated, or a null pointer if the stream is not associated with a memory-mapped file. OPENVDB_API SharedPtr getMappedFilePtr(std::ios_base&); /// @brief Associate the given stream with (a shared pointer to) a memory-mapped file. /// @note The shared pointer object (not just the io::MappedFile object to which it points) /// must remain valid until the file is closed. OPENVDB_API void setMappedFilePtr(std::ios_base&, SharedPtr&); /// @brief Return a shared pointer to an object that stores metadata (file format, /// compression scheme, etc.) for use when reading from or writing to the given stream. OPENVDB_API SharedPtr getStreamMetadataPtr(std::ios_base&); /// @brief Associate the given stream with (a shared pointer to) an object that stores /// metadata (file format, compression scheme, etc.) for use when reading from /// or writing to the stream. /// @details If @a transfer is true, copy metadata from the object directly to the stream /// (for backward compatibility with older versions of the library). /// @note The shared pointer object (not just the io::StreamMetadata object to which it points) /// must remain valid until the file is closed. OPENVDB_API void setStreamMetadataPtr(std::ios_base&, SharedPtr&, bool transfer = true); /// @brief Dissociate the given stream from its metadata object (if it has one) /// and return a shared pointer to the object. OPENVDB_API SharedPtr clearStreamMetadataPtr(std::ios_base&); } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_IO_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.cc0000644000000000000000000006321413200122377013011 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 io/File.cc #include "File.h" #include "TempFile.h" #include #include #include #include #ifndef _MSC_VER #include #include #include #endif #include #include // for getenv(), strtoul() #include // for strerror_r() #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { // Implementation details of the File class struct File::Impl { enum { DEFAULT_COPY_MAX_BYTES = 500000000 }; // 500 MB struct NoBBox {}; // Common implementation of the various File::readGrid() overloads, // with and without bounding box clipping template static GridBase::Ptr readGrid(const File& file, const GridDescriptor& gd, const BoxType& bbox) { // This method should not be called for files that don't contain grid offsets. assert(file.inputHasGridOffsets()); GridBase::Ptr grid = file.createGrid(gd); gd.seekToGrid(file.inputStream()); unarchive(file, grid, gd, bbox); return grid; } static void unarchive(const File& file, GridBase::Ptr& grid, const GridDescriptor& gd, NoBBox) { file.Archive::readGrid(grid, gd, file.inputStream()); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 static void unarchive(const File& file, GridBase::Ptr& grid, const GridDescriptor& gd, const CoordBBox& indexBBox) { file.Archive::readGrid(grid, gd, file.inputStream(), indexBBox); } static void unarchive(const File& file, GridBase::Ptr& grid, const GridDescriptor& gd, const BBoxd& worldBBox) { file.Archive::readGrid(grid, gd, file.inputStream(), worldBBox); } #endif static Index64 getDefaultCopyMaxBytes() { Index64 result = DEFAULT_COPY_MAX_BYTES; if (const char* s = std::getenv("OPENVDB_DELAYED_LOAD_COPY_MAX_BYTES")) { char* endptr = nullptr; result = std::strtoul(s, &endptr, /*base=*/10); } return result; } std::string mFilename; // The file-level metadata MetaMap::Ptr mMeta; // The memory-mapped file MappedFile::Ptr mFileMapping; // The buffer for the input stream, if it is a memory-mapped file SharedPtr mStreamBuf; // The file stream that is open for reading std::unique_ptr mInStream; // File-level stream metadata (file format, compression, etc.) StreamMetadata::Ptr mStreamMetadata; // Flag indicating if we have read in the global information (header, // metadata, and grid descriptors) for this VDB file bool mIsOpen; // File size limit for copying during delayed loading Index64 mCopyMaxBytes; // 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; }; // class File::Impl //////////////////////////////////////// File::File(const std::string& filename): mImpl(new Impl) { mImpl->mFilename = filename; mImpl->mIsOpen = false; mImpl->mCopyMaxBytes = Impl::getDefaultCopyMaxBytes(); setInputHasGridOffsets(true); } File::~File() { } File::File(const File& other) : Archive(other) , mImpl(new Impl) { *this = other; } File& File::operator=(const File& other) { if (&other != this) { Archive::operator=(other); const Impl& otherImpl = *other.mImpl; mImpl->mFilename = otherImpl.mFilename; mImpl->mMeta = otherImpl.mMeta; mImpl->mIsOpen = false; // don't want two file objects reading from the same stream mImpl->mCopyMaxBytes = otherImpl.mCopyMaxBytes; mImpl->mGridDescriptors = otherImpl.mGridDescriptors; mImpl->mNamedGrids = otherImpl.mNamedGrids; mImpl->mGrids = otherImpl.mGrids; } return *this; } SharedPtr File::copy() const { return SharedPtr{new File{*this}}; } //////////////////////////////////////// const std::string& File::filename() const { return mImpl->mFilename; } MetaMap::Ptr File::fileMetadata() { return mImpl->mMeta; } MetaMap::ConstPtr File::fileMetadata() const { return mImpl->mMeta; } const File::NameMap& File::gridDescriptors() const { return mImpl->mGridDescriptors; } File::NameMap& File::gridDescriptors() { return mImpl->mGridDescriptors; } std::istream& File::inputStream() const { if (!mImpl->mInStream) { OPENVDB_THROW(IoError, filename() << " is not open for reading"); } return *mImpl->mInStream; } //////////////////////////////////////// Index64 File::getSize() const { /// @internal boost::filesystem::file_size() would be a more portable alternative, /// but as of 9/2014, Houdini ships without the Boost.Filesystem library, /// which makes it much less convenient to use that library. Index64 result = std::numeric_limits::max(); std::string mesg = "could not get size of file " + filename(); #ifdef _MSC_VER // Get the file size by seeking to the end of the file. std::ifstream fstrm(filename()); if (fstrm) { fstrm.seekg(0, fstrm.end); result = static_cast(fstrm.tellg()); } else { OPENVDB_THROW(IoError, mesg); } #else // Get the file size using the stat() system call. struct stat info; if (0 != ::stat(filename().c_str(), &info)) { std::string s = getErrorString(); if (!s.empty()) mesg += " (" + s + ")"; OPENVDB_THROW(IoError, mesg); } if (!S_ISREG(info.st_mode)) { mesg += " (not a regular file)"; OPENVDB_THROW(IoError, mesg); } result = static_cast(info.st_size); #endif return result; } Index64 File::copyMaxBytes() const { return mImpl->mCopyMaxBytes; } void File::setCopyMaxBytes(Index64 bytes) { mImpl->mCopyMaxBytes = bytes; } //////////////////////////////////////// bool File::isOpen() const { return mImpl->mIsOpen; } bool File::open(bool delayLoad, const MappedFile::Notifier& notifier) { if (isOpen()) { OPENVDB_THROW(IoError, filename() << " is already open"); } mImpl->mInStream.reset(); // Open the file. std::unique_ptr newStream; SharedPtr newStreamBuf; MappedFile::Ptr newFileMapping; if (!delayLoad || !Archive::isDelayedLoadingEnabled()) { newStream.reset(new std::ifstream( filename().c_str(), std::ios_base::in | std::ios_base::binary)); } else { bool isTempFile = false; std::string fname = filename(); if (getSize() < copyMaxBytes()) { // If the file is not too large, make a temporary private copy of it // and open the copy instead. The original file can then be modified // or removed without affecting delayed load. try { TempFile tempFile; std::ifstream fstrm(filename().c_str(), std::ios_base::in | std::ios_base::binary); boost::iostreams::copy(fstrm, tempFile); fname = tempFile.filename(); isTempFile = true; } catch (std::exception& e) { std::string mesg; if (e.what()) mesg = std::string(" (") + e.what() + ")"; OPENVDB_LOG_WARN("failed to create a temporary copy of " << filename() << " for delayed loading" << mesg << "; will read directly from " << filename() << " instead"); } } // While the file is open, its mapping, stream buffer and stream // must all be maintained. Once the file is closed, the buffer and // the stream can be discarded, but the mapping needs to persist // if any grids were lazily loaded. try { newFileMapping.reset(new MappedFile(fname, /*autoDelete=*/isTempFile)); newStreamBuf = newFileMapping->createBuffer(); newStream.reset(new std::istream(newStreamBuf.get())); } catch (std::exception& e) { std::ostringstream ostr; ostr << "could not open file " << filename(); if (e.what() != nullptr) ostr << " (" << e.what() << ")"; OPENVDB_THROW(IoError, ostr.str()); } } if (newStream->fail()) { OPENVDB_THROW(IoError, "could not open file " << filename()); } // Read in the file header. bool newFile = false; try { newFile = Archive::readHeader(*newStream); } catch (IoError& e) { if (e.what() && std::string("not a VDB file") == e.what()) { // Rethrow, adding the filename. OPENVDB_THROW(IoError, filename() << " is not a VDB file"); } throw; } mImpl->mFileMapping = newFileMapping; if (mImpl->mFileMapping) mImpl->mFileMapping->setNotifier(notifier); mImpl->mStreamBuf = newStreamBuf; mImpl->mInStream.swap(newStream); // Tag the input stream with the file format and library version numbers // and other metadata. mImpl->mStreamMetadata.reset(new StreamMetadata); mImpl->mStreamMetadata->setSeekable(true); io::setStreamMetadataPtr(inputStream(), mImpl->mStreamMetadata, /*transfer=*/false); Archive::setFormatVersion(inputStream()); Archive::setLibraryVersion(inputStream()); Archive::setDataCompression(inputStream()); io::setMappedFilePtr(inputStream(), mImpl->mFileMapping); // Read in the VDB metadata. mImpl->mMeta = MetaMap::Ptr(new MetaMap); mImpl->mMeta->readMeta(inputStream()); if (!inputHasGridOffsets()) { OPENVDB_LOG_DEBUG_RUNTIME("file " << filename() << " does not support partial reading"); mImpl->mGrids.reset(new GridPtrVec); mImpl->mNamedGrids.clear(); // Stream in the entire contents of the file and append all grids to mGrids. const int32_t gridCount = readGridCount(inputStream()); for (int32_t i = 0; i < gridCount; ++i) { GridDescriptor gd; gd.read(inputStream()); GridBase::Ptr grid = createGrid(gd); Archive::readGrid(grid, gd, inputStream()); gridDescriptors().insert(std::make_pair(gd.gridName(), gd)); mImpl->mGrids->push_back(grid); mImpl->mNamedGrids[gd.uniqueName()] = grid; } // Connect instances (grids that share trees with other grids). for (NameMapCIter it = gridDescriptors().begin(); it != gridDescriptors().end(); ++it) { Archive::connectInstance(it->second, mImpl->mNamedGrids); } } else { // Read in just the grid descriptors. readGridDescriptors(inputStream()); } mImpl->mIsOpen = true; return newFile; // true if file is not identical to opened file } void File::close() { // Reset all data. mImpl->mMeta.reset(); mImpl->mGridDescriptors.clear(); mImpl->mGrids.reset(); mImpl->mNamedGrids.clear(); mImpl->mInStream.reset(); mImpl->mStreamBuf.reset(); mImpl->mStreamMetadata.reset(); mImpl->mFileMapping.reset(); mImpl->mIsOpen = false; setInputHasGridOffsets(true); } //////////////////////////////////////// bool File::hasGrid(const Name& name) const { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading"); } return (findDescriptor(name) != gridDescriptors().end()); } MetaMap::Ptr File::getMetadata() const { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " 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(*mImpl->mMeta)); } GridPtrVecPtr File::getGrids() const { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " 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 = mImpl->mGrids; } else { ret.reset(new GridPtrVec); Archive::NamedGridMap namedGrids; // Read all grids represented by the GridDescriptors. for (NameMapCIter i = gridDescriptors().begin(), e = gridDescriptors().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 = gridDescriptors().begin(), e = gridDescriptors().end(); i != e; ++i) { Archive::connectInstance(i->second, namedGrids); } } return ret; } GridBase::Ptr File::retrieveCachedGrid(const Name& name) const { // If the file has grid offsets, grids are read on demand // and not cached in mNamedGrids. if (inputHasGridOffsets()) return GridBase::Ptr(); // If the file does not have grid offsets, mNamedGrids should already // contain the entire contents of the file. // Search by unique name. Archive::NamedGridMap::const_iterator it = mImpl->mNamedGrids.find(GridDescriptor::stringAsUniqueName(name)); // If not found, search by grid name. if (it == mImpl->mNamedGrids.end()) it = mImpl->mNamedGrids.find(name); if (it == mImpl->mNamedGrids.end()) { OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\""); } return it->second; } //////////////////////////////////////// GridPtrVecPtr File::readAllGridMetadata() { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " 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 = mImpl->mGrids->size(); i < N; ++i) { // Return copies of the grids, but with empty trees. #if OPENVDB_ABI_VERSION_NUMBER <= 3 ret->push_back((*mImpl->mGrids)[i]->copyGrid(/*treePolicy=*/CP_NEW)); #else ret->push_back((*mImpl->mGrids)[i]->copyGridWithNewTree()); #endif } } else { // Read just the metadata and transforms for all grids. for (NameMapCIter i = gridDescriptors().begin(), e = gridDescriptors().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.) #if OPENVDB_ABI_VERSION_NUMBER <= 3 ret->push_back(grid->copyGrid(/*treePolicy=*/CP_NEW)); #else ret->push_back(grid->copyGridWithNewTree()); #endif } } return ret; } GridBase::Ptr File::readGridMetadata(const Name& name) { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " 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 == gridDescriptors().end()) { OPENVDB_THROW(KeyError, filename() << " 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); } #if OPENVDB_ABI_VERSION_NUMBER <= 3 return ret->copyGrid(/*treePolicy=*/CP_NEW); #else return ret->copyGridWithNewTree(); #endif } //////////////////////////////////////// GridBase::Ptr File::readGrid(const Name& name) { return readGridByName(name, BBoxd()); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 GridBase::Ptr File::readGrid(const Name& name, const BBoxd& bbox) { return readGridByName(name, bbox); } #endif #if OPENVDB_ABI_VERSION_NUMBER <= 2 GridBase::Ptr File::readGridByName(const Name& name, const BBoxd&) #else GridBase::Ptr File::readGridByName(const Name& name, const BBoxd& bbox) #endif { if (!isOpen()) { OPENVDB_THROW(IoError, filename() << " is not open for reading."); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 const bool clip = bbox.isSorted(); #endif // If a grid with the given name was already read and cached // (along with the entire contents of the file, because the file // doesn't support random access), retrieve and return it. GridBase::Ptr grid = retrieveCachedGrid(name); if (grid) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (clip) { grid = grid->deepCopyGrid(); grid->clipGrid(bbox); } #endif return grid; } NameMapCIter it = findDescriptor(name); if (it == gridDescriptors().end()) { OPENVDB_THROW(KeyError, filename() << " has no grid named \"" << name << "\""); } // Seek to and read in the grid from the file. const GridDescriptor& gd = it->second; #if OPENVDB_ABI_VERSION_NUMBER <= 2 grid = readGrid(gd); #else grid = (clip ? readGrid(gd, bbox) : readGrid(gd)); #endif if (gd.isInstance()) { /// @todo Refactor to share code with Archive::connectInstance()? NameMapCIter parentIt = findDescriptor(GridDescriptor::nameAsString(gd.instanceParentName())); if (parentIt == gridDescriptors().end()) { OPENVDB_THROW(KeyError, "missing instance parent \"" << GridDescriptor::nameAsString(gd.instanceParentName()) << "\" for grid " << GridDescriptor::nameAsString(gd.uniqueName()) << " in file " << filename()); } GridBase::Ptr parent; #if OPENVDB_ABI_VERSION_NUMBER <= 2 parent = readGrid(parentIt->second); #else if (clip) { const CoordBBox indexBBox = grid->constTransform().worldToIndexNodeCentered(bbox); parent = readGrid(parentIt->second, indexBBox); } else { parent = readGrid(parentIt->second); } #endif if (parent) grid->setTree(parent->baseTreePtr()); } return grid; } //////////////////////////////////////// void File::writeGrids(const GridCPtrVec& grids, const MetaMap& meta) const { if (isOpen()) { OPENVDB_THROW(IoError, filename() << " cannot be written because it is open for reading"); } // Create a file stream and write it out. std::ofstream file; file.open(filename().c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); if (file.fail()) { OPENVDB_THROW(IoError, "could not open " << filename() << " for writing"); } // Write out the vdb. Archive::write(file, grids, /*seekable=*/true, meta); file.close(); } //////////////////////////////////////// void File::readGridDescriptors(std::istream& is) { // This method should not be called for files that don't contain grid offsets. assert(inputHasGridOffsets()); gridDescriptors().clear(); for (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. gridDescriptors().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 = gridDescriptors().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 = gridDescriptors().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(filename() << " has more than one grid named \"" << name << "\""); } NameMapCIter ret = gridDescriptors().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 " << filename() << ": 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(inputStream()); // Read the grid partially. readGridPartial(grid, inputStream(), gd.isInstance(), readTopology); // Promote to a const grid. GridBase::ConstPtr constGrid = grid; return constGrid; } GridBase::Ptr File::readGrid(const GridDescriptor& gd) const { return Impl::readGrid(*this, gd, Impl::NoBBox()); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 GridBase::Ptr File::readGrid(const GridDescriptor& gd, const BBoxd& bbox) const { return Impl::readGrid(*this, gd, bbox); } GridBase::Ptr File::readGrid(const GridDescriptor& gd, const CoordBBox& bbox) const { return Impl::readGrid(*this, gd, bbox); } #endif 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, filename() << " is not open for reading"); } return File::NameIterator(gridDescriptors().begin()); } File::NameIterator File::endName() const { return File::NameIterator(gridDescriptors().end()); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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.h0000644000000000000000000001065313200122377013226 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Archive.h" #include #include 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: /// @brief Read grids from an input stream. /// @details If @a delayLoad is true, map the contents of the input stream /// into memory and enable delayed loading of grids. /// @note Define the environment variable @c OPENVDB_DISABLE_DELAYED_LOAD /// to disable delayed loading unconditionally. explicit Stream(std::istream&, bool delayLoad = true); /// Construct an archive for stream output. Stream(); /// Construct an archive for output to the given stream. explicit Stream(std::ostream&); Stream(const Stream&); Stream& operator=(const Stream&); ~Stream() override; /// @brief Return a copy of this archive. Archive::Ptr copy() const override; /// 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(); /// @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. void write(const GridCPtrVec&, const MetaMap& = MetaMap()) const override; /// @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; 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; struct Impl; std::unique_ptr mImpl; }; //////////////////////////////////////// template inline void Stream::write(const GridPtrContainerT& container, const MetaMap& metadata) const { GridCPtrVec grids; std::copy(container.begin(), container.end(), std::back_inserter(grids)); this->write(grids, metadata); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_STREAM_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000006733513200122377014305 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "io.h" // for getDataCompression(), etc. #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 /// ZLIB compression to internal and leaf node value buffers.
/// When reading grids other than level sets or fog volumes, indicate that /// the value buffers of internal and leaf nodes are ZLIB-compressed.
/// ZLIB compresses well but is slow. /// ///
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. /// ///
COMPRESS_BLOSC ///
When writing grids other than level sets or fog volumes, apply /// Blosc compression to internal and leaf node value buffers.
/// When reading grids other than level sets or fog volumes, indicate that /// the value buffers of internal and leaf nodes are Blosc-compressed.
/// Blosc is much faster than ZLIB and produces comparable file sizes. ///
enum { COMPRESS_NONE = 0, COMPRESS_ZIP = 0x1, COMPRESS_ACTIVE_MASK = 0x2, COMPRESS_BLOSC = 0x4 }; /// 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 /*6*/ NO_MASK_AND_ALL_VALS // > 2 inactive vals, so no mask compression at all }; //////////////////////////////////////// /// @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 static HalfT convert(const T& val) { return val; } }; template<> struct RealToHalf { enum { isReal = true }; typedef half HalfT; static HalfT convert(float val) { return HalfT(val); } }; template<> struct RealToHalf { enum { isReal = true }; typedef half HalfT; // A half can only be constructed from a float, so cast the value to a float first. static HalfT convert(double val) { return HalfT(float(val)); } }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec2H HalfT; static HalfT convert(const Vec2s& val) { return HalfT(val); } }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec2H HalfT; // A half can only be constructed from a float, so cast the vector's elements to floats first. static HalfT convert(const Vec2d& val) { return HalfT(Vec2s(val)); } }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec3H HalfT; static HalfT convert(const Vec3s& val) { return HalfT(val); } }; template<> struct RealToHalf { enum { isReal = true }; typedef Vec3H HalfT; // A half can only be constructed from a float, so cast the vector's elements to floats first. static HalfT convert(const Vec3d& val) { return HalfT(Vec3s(val)); } }; /// Return the given value truncated to 16-bit float precision. template inline T truncateRealToHalf(const T& val) { return T(RealToHalf::convert(val)); } //////////////////////////////////////// OPENVDB_API void zipToStream(std::ostream&, const char* data, size_t numBytes); OPENVDB_API void unzipFromStream(std::istream&, char* data, size_t numBytes); OPENVDB_API void bloscToStream(std::ostream&, const char* data, size_t valSize, size_t numVals); OPENVDB_API void bloscFromStream(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 compression whether and how the data is compressed (either COMPRESS_NONE, /// COMPRESS_ZIP, COMPRESS_ACTIVE_MASK or COMPRESS_BLOSC) /// @throw IoError if @a compression is COMPRESS_BLOSC but OpenVDB was compiled /// without Blosc support. /// @details 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, uint32_t compression) { if (compression & COMPRESS_BLOSC) { bloscFromStream(is, reinterpret_cast(data), sizeof(T) * count); } else if (compression & COMPRESS_ZIP) { unzipFromStream(is, reinterpret_cast(data), sizeof(T) * count); } else { if (data == nullptr) { assert(!getStreamMetadataPtr(is) || getStreamMetadataPtr(is)->seekable()); is.seekg(sizeof(T) * count, std::ios_base::cur); } 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, uint32_t /*compression*/) { 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); if (data != nullptr) 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, uint32_t compression) { readData(is, data, count, compression); } }; /// 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, uint32_t compression) { if (count < 1) return; if (data == nullptr) { // seek mode - pass through null pointer readData(is, nullptr, count, compression); } else { std::vector halfData(count); // temp buffer into which to read half float values readData(is, reinterpret_cast(&halfData[0]), count, compression); // 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 compression whether and how to compress the data (either COMPRESS_NONE, /// COMPRESS_ZIP, COMPRESS_ACTIVE_MASK or COMPRESS_BLOSC) /// @throw IoError if @a compression is COMPRESS_BLOSC but OpenVDB was compiled /// without Blosc support. /// @details 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, uint32_t compression) { if (compression & COMPRESS_BLOSC) { bloscToStream(os, reinterpret_cast(data), sizeof(T), count); } else if (compression & COMPRESS_ZIP) { zipToStream(os, reinterpret_cast(data), sizeof(T) * count); } else { os.write(reinterpret_cast(data), sizeof(T) * count); } } /// Specialization for std::string output template<> inline void writeData(std::ostream& os, const std::string* data, Index count, uint32_t /*compression*/) ///< @todo add compression { 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, uint32_t compression) { writeData(os, data, count, compression); } }; /// 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, uint32_t compression) { 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] = RealToHalf::convert(data[i]); writeData(os, reinterpret_cast(&halfData[0]), count, compression); } }; #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, uint32_t compression) { 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] = RealToHalf::convert(data[i]); writeData(os, reinterpret_cast(&halfData[0]), count, compression); } }; #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 maskCompressed = compression & COMPRESS_ACTIVE_MASK; const bool seek = (destBuf == nullptr); assert(!seek || (!getStreamMetadataPtr(is) || getStreamMetadataPtr(is)->seekable())); int8_t metadata = NO_MASK_AND_ALL_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. if (seek && !maskCompressed) { is.seekg(/*bytes=*/1, std::ios_base::cur); } else { 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_AND_ONE_INACTIVE_VAL || metadata == MASK_AND_ONE_INACTIVE_VAL || metadata == MASK_AND_TWO_INACTIVE_VALS) { // Read one of at most two distinct inactive values. if (seek) { is.seekg(/*bytes=*/sizeof(ValueT), std::ios_base::cur); } else { is.read(reinterpret_cast(&inactiveVal0), /*bytes=*/sizeof(ValueT)); } if (metadata == MASK_AND_TWO_INACTIVE_VALS) { // Read the second of two distinct inactive values. if (seek) { is.seekg(/*bytes=*/sizeof(ValueT), std::ios_base::cur); } else { is.read(reinterpret_cast(&inactiveVal1), /*bytes=*/sizeof(ValueT)); } } } MaskT selectionMask; if (metadata == MASK_AND_NO_INACTIVE_VALS || metadata == MASK_AND_ONE_INACTIVE_VAL || metadata == MASK_AND_TWO_INACTIVE_VALS) { // For use in mask compression (only), read the bitmask that selects // between two distinct inactive values. if (seek) { is.seekg(/*bytes=*/selectionMask.memUsage(), std::ios_base::cur); } else { selectionMask.load(is); } } ValueT* tempBuf = destBuf; boost::scoped_array scopedTempBuf; Index tempCount = destCount; if (maskCompressed && metadata != NO_MASK_AND_ALL_VALS && getFormatVersion(is) >= OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { tempCount = valueMask.countOn(); if (!seek && 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, (seek ? nullptr : tempBuf), tempCount, compression); } else { readData(is, (seek ? nullptr : tempBuf), tempCount, compression); } // 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 (!seek && 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 maskCompress = compress & COMPRESS_ACTIVE_MASK; Index tempCount = srcCount; ValueT* tempBuf = srcBuf; boost::scoped_array scopedTempBuf; int8_t metadata = NO_MASK_AND_ALL_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; } } } else if (numUniqueInactiveVals > 2) { metadata = NO_MASK_AND_ALL_VALS; } os.write(reinterpret_cast(&metadata), /*bytes=*/1); if (metadata == NO_MASK_AND_ONE_INACTIVE_VAL || metadata == MASK_AND_ONE_INACTIVE_VAL || metadata == MASK_AND_TWO_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 = static_cast(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_AND_ALL_VALS) { // 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, compress); } else { writeData(os, tempBuf, tempCount, compress); } } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_COMPRESSION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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/TempFile.cc0000644000000000000000000001416413200122377013637 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TempFile.cc #include "TempFile.h" #include #ifndef _MSC_VER #include #include #include // for BOOST_VERSION #include // for std::getenv(), mkstemp() #include // for mode_t #include // for mkdir(), umask() #include // for access() #else #include // for std::filebuf #endif #include // for std::tmpnam(), L_tmpnam, P_tmpdir #include #include #include #include #ifndef DWA_BOOST_VERSION #define DWA_BOOST_VERSION (10 * BOOST_VERSION) #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { struct TempFile::TempFileImpl { const std::string& filename() const { return mPath; } bool is_open() const { return mBuffer.is_open(); } /// @internal boost::filesystem::unique_path(), etc. might be useful here, /// but as of 9/2014, Houdini ships without the Boost.Filesystem library, /// which makes it much less convenient to use that library. #ifndef _MSC_VER TempFileImpl(std::ostream& os): mFileDescr(-1) { this->init(os); } void init(std::ostream& os) { std::string fn = this->getTempDir() + "/openvdb_temp_XXXXXX"; std::vector fnbuf(fn.begin(), fn.end()); fnbuf.push_back(char(0)); //const mode_t savedMode = ::umask(~(S_IRUSR | S_IWUSR)); mFileDescr = ::mkstemp(&fnbuf[0]); //::umask(savedMode); if (mFileDescr < 0) { OPENVDB_THROW(IoError, "failed to generate temporary file"); } mPath.assign(&fnbuf[0]); #if DWA_BOOST_VERSION >= 1046000 mDevice = DeviceType(mFileDescr, boost::iostreams::never_close_handle); #else mDevice = DeviceType(mFileDescr, /*closeOnExit=*/false); #endif mBuffer.open(mDevice); os.rdbuf(&mBuffer); if (!os.good()) { OPENVDB_THROW(IoError, "failed to open temporary file " + mPath); } } void close() { mBuffer.close(); if (mFileDescr >= 0) ::close(mFileDescr); } static std::string getTempDir() { if (const char* dir = std::getenv("OPENVDB_TEMP_DIR")) { if (0 != ::access(dir, F_OK)) { ::mkdir(dir, S_IRUSR | S_IWUSR | S_IXUSR); if (0 != ::access(dir, F_OK)) { OPENVDB_THROW(IoError, "failed to create OPENVDB_TEMP_DIR (" + std::string(dir) + ")"); } } return dir; } if (const char* dir = std::getenv("TMPDIR")) return dir; return P_tmpdir; } using DeviceType = boost::iostreams::file_descriptor_sink; using BufferType = boost::iostreams::stream_buffer; std::string mPath; DeviceType mDevice; BufferType mBuffer; int mFileDescr; #else // _MSC_VER // Use only standard library routines; no POSIX. TempFileImpl(std::ostream& os) { this->init(os); } void init(std::ostream& os) { char fnbuf[L_tmpnam]; const char* filename = std::tmpnam(fnbuf); if (!filename) { OPENVDB_THROW(IoError, "failed to generate name for temporary file"); } /// @todo This is not safe, since another process could open a file /// with this name before we do. Unfortunately, there is no safe, /// portable way to create a temporary file. mPath = filename; const std::ios_base::openmode mode = (std::ios_base::out | std::ios_base::binary); os.rdbuf(mBuffer.open(mPath.c_str(), mode)); if (!os.good()) { OPENVDB_THROW(IoError, "failed to open temporary file " + mPath); } } void close() { mBuffer.close(); } std::string mPath; std::filebuf mBuffer; #endif // _MSC_VER private: TempFileImpl(const TempFileImpl&); // disable copying TempFileImpl& operator=(const TempFileImpl&); // disable assignment }; TempFile::TempFile(): std::ostream(nullptr), mImpl(new TempFileImpl(*this)) {} TempFile::~TempFile() { this->close(); } const std::string& TempFile::filename() const { return mImpl->filename(); } bool TempFile::is_open() const { return mImpl->is_open(); } void TempFile::close() { mImpl->close(); } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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/TempFile.h0000644000000000000000000000575213200122377013504 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TempFile.h #ifndef OPENVDB_IO_TEMPFILE_HAS_BEEN_INCLUDED #define OPENVDB_IO_TEMPFILE_HAS_BEEN_INCLUDED #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { /// Output stream to a unique temporary file class OPENVDB_API TempFile: public std::ostream { public: /// @brief Create and open a unique file. /// @details On UNIX systems, the file is created in the directory specified by /// the environment variable @c OPENVDB_TEMP_DIR, if that variable is defined, /// or else in the directory specified by @c TMPDIR, if that variable is defined. /// Otherwise (and on non-UNIX systems), the file is created in the system default /// temporary directory. TempFile(); ~TempFile(); /// Return the path to the temporary file. const std::string& filename() const; /// Return @c true if the file is open for writing. bool is_open() const; /// Close the file. void close(); private: struct TempFileImpl; std::unique_ptr mImpl; }; } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_IO_TEMPFILE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.cc0000644000000000000000000012647313200122377013522 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "GridDescriptor.h" #include "io.h" #include #include #include // Boost.Interprocess uses a header-only portion of Boost.DateTime #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-macros" #endif #define BOOST_DATE_TIME_NO_LIB #ifdef __clang__ #pragma clang diagnostic pop #endif #include #include #include #include #include #include #include #ifdef _MSC_VER #include // open_existing_file(), close_file() extern "C" __declspec(dllimport) bool __stdcall GetFileTime( void* fh, void* ctime, void* atime, void* mtime); // boost::interprocess::detail was renamed to boost::interprocess::ipcdetail in Boost 1.48. // Ensure that both namespaces exist. namespace boost { namespace interprocess { namespace detail {} namespace ipcdetail {} } } #else #include // for struct stat #include // for stat() #include // for unlink() #endif #include // for std::find_if() #include // for errno #include // for getenv() #include // for std::memcpy() #include // for std::time() #include #include #include #include #include #include // for std::error_code() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { #ifdef OPENVDB_USE_BLOSC const uint32_t Archive::DEFAULT_COMPRESSION_FLAGS = (COMPRESS_BLOSC | COMPRESS_ACTIVE_MASK); #else const uint32_t Archive::DEFAULT_COMPRESSION_FLAGS = (COMPRESS_ZIP | COMPRESS_ACTIVE_MASK); #endif namespace { // 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; int halfFloat; int mappedFile; int metadata; } sStreamState; const long StreamState::MAGIC_NUMBER = long((uint64_t(OPENVDB_MAGIC) << 32) | (uint64_t(OPENVDB_MAGIC))); //////////////////////////////////////// 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) != nullptr) { // 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; if (other.mappedFile != 0) { // memory-mapped file support was added in OpenVDB 3.0.0 mappedFile = other.mappedFile; metadata = other.metadata; halfFloat = other.halfFloat; } else { mappedFile = std::ios_base::xalloc(); metadata = std::ios_base::xalloc(); halfFloat = std::ios_base::xalloc(); } } 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(); mappedFile = std::ios_base::xalloc(); metadata = std::ios_base::xalloc(); halfFloat = 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) = nullptr; } } // unnamed namespace //////////////////////////////////////// struct StreamMetadata::Impl { uint32_t mFileVersion = OPENVDB_FILE_VERSION; VersionId mLibraryVersion = { OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION }; uint32_t mCompression = COMPRESS_NONE; uint32_t mGridClass = GRID_UNKNOWN; const void* mBackgroundPtr = nullptr; ///< @todo use Metadata::Ptr? bool mHalfFloat = false; bool mWriteGridStats = false; bool mSeekable = false; bool mCountingPasses = false; uint32_t mPass = 0; MetaMap mGridMetadata; AuxDataMap mAuxData; }; // struct StreamMetadata StreamMetadata::StreamMetadata(): mImpl(new Impl) { } StreamMetadata::StreamMetadata(const StreamMetadata& other): mImpl(new Impl(*other.mImpl)) { } StreamMetadata::StreamMetadata(std::ios_base& strm): mImpl(new Impl) { mImpl->mFileVersion = getFormatVersion(strm); mImpl->mLibraryVersion = getLibraryVersion(strm); mImpl->mCompression = getDataCompression(strm); mImpl->mGridClass = getGridClass(strm); mImpl->mHalfFloat = getHalfFloat(strm); mImpl->mWriteGridStats = getWriteGridStatsMetadata(strm); } StreamMetadata::~StreamMetadata() { } StreamMetadata& StreamMetadata::operator=(const StreamMetadata& other) { if (&other != this) { mImpl.reset(new Impl(*other.mImpl)); } return *this; } void StreamMetadata::transferTo(std::ios_base& strm) const { io::setVersion(strm, mImpl->mLibraryVersion, mImpl->mFileVersion); io::setDataCompression(strm, mImpl->mCompression); io::setGridBackgroundValuePtr(strm, mImpl->mBackgroundPtr); io::setGridClass(strm, mImpl->mGridClass); io::setHalfFloat(strm, mImpl->mHalfFloat); io::setWriteGridStatsMetadata(strm, mImpl->mWriteGridStats); } uint32_t StreamMetadata::fileVersion() const { return mImpl->mFileVersion; } VersionId StreamMetadata::libraryVersion() const { return mImpl->mLibraryVersion; } uint32_t StreamMetadata::compression() const { return mImpl->mCompression; } uint32_t StreamMetadata::gridClass() const { return mImpl->mGridClass; } const void* StreamMetadata::backgroundPtr() const { return mImpl->mBackgroundPtr; } bool StreamMetadata::halfFloat() const { return mImpl->mHalfFloat; } bool StreamMetadata::writeGridStats() const { return mImpl->mWriteGridStats; } bool StreamMetadata::seekable() const { return mImpl->mSeekable; } bool StreamMetadata::countingPasses() const { return mImpl->mCountingPasses; } uint32_t StreamMetadata::pass() const { return mImpl->mPass; } MetaMap& StreamMetadata::gridMetadata() { return mImpl->mGridMetadata; } const MetaMap& StreamMetadata::gridMetadata() const { return mImpl->mGridMetadata; } StreamMetadata::AuxDataMap& StreamMetadata::auxData() { return mImpl->mAuxData; } const StreamMetadata::AuxDataMap& StreamMetadata::auxData() const { return mImpl->mAuxData; } void StreamMetadata::setFileVersion(uint32_t v) { mImpl->mFileVersion = v; } void StreamMetadata::setLibraryVersion(VersionId v) { mImpl->mLibraryVersion = v; } void StreamMetadata::setCompression(uint32_t c) { mImpl->mCompression = c; } void StreamMetadata::setGridClass(uint32_t c) { mImpl->mGridClass = c; } void StreamMetadata::setBackgroundPtr(const void* ptr) { mImpl->mBackgroundPtr = ptr; } void StreamMetadata::setHalfFloat(bool b) { mImpl->mHalfFloat = b; } void StreamMetadata::setWriteGridStats(bool b) { mImpl->mWriteGridStats = b; } void StreamMetadata::setSeekable(bool b) { mImpl->mSeekable = b; } void StreamMetadata::setCountingPasses(bool b) { mImpl->mCountingPasses = b; } void StreamMetadata::setPass(uint32_t i) { mImpl->mPass = i; } std::string StreamMetadata::str() const { std::ostringstream ostr; ostr << std::boolalpha; ostr << "version: " << libraryVersion().first << "." << libraryVersion().second << "/" << fileVersion() << "\n"; ostr << "class: " << GridBase::gridClassToString(static_cast(gridClass())) << "\n"; ostr << "compression: " << compressionToString(compression()) << "\n"; ostr << "half_float: " << halfFloat() << "\n"; ostr << "seekable: " << seekable() << "\n"; ostr << "pass: " << pass() << "\n"; ostr << "counting_passes: " << countingPasses() << "\n"; ostr << "write_grid_stats_metadata: " << writeGridStats() << "\n"; if (!auxData().empty()) ostr << auxData(); if (gridMetadata().metaCount() != 0) { ostr << "grid_metadata:\n" << gridMetadata().str(/*indent=*/" "); } return ostr.str(); } std::ostream& operator<<(std::ostream& os, const StreamMetadata& meta) { os << meta.str(); return os; } namespace { template inline bool writeAsType(std::ostream& os, const boost::any& val) { if (val.type() == typeid(T)) { os << boost::any_cast(val); return true; } return false; } } // unnamed namespace std::ostream& operator<<(std::ostream& os, const StreamMetadata::AuxDataMap& auxData) { for (StreamMetadata::AuxDataMap::const_iterator it = auxData.begin(), end = auxData.end(); it != end; ++it) { os << it->first << ": "; // Note: boost::any doesn't support serialization. const boost::any& val = it->second; if (!writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val) && !writeAsType(os, val)) { os << val.type().name() << "(...)"; } os << "\n"; } return os; } //////////////////////////////////////// // Memory-mapping a VDB file permits threaded input (and output, potentially, // though that might not be practical for compressed files or files containing // multiple grids). In particular, a memory-mapped file can be loaded lazily, // meaning that the voxel buffers of the leaf nodes of a grid's tree are not allocated // until they are actually accessed. When access to its buffer is requested, // a leaf node allocates memory for the buffer and then streams in (and decompresses) // its contents from the memory map, starting from a stream offset that was recorded // at the time the node was constructed. The memory map must persist as long as // there are unloaded leaf nodes; this is ensured by storing a shared pointer // to the map in each unloaded node. class MappedFile::Impl { public: Impl(const std::string& filename, bool autoDelete) : mMap(filename.c_str(), boost::interprocess::read_only) , mRegion(mMap, boost::interprocess::read_only) , mAutoDelete(autoDelete) { mLastWriteTime = this->getLastWriteTime(); if (mAutoDelete) { #ifndef _MSC_VER // On Unix systems, unlink the file so that it gets deleted once it is closed. ::unlink(mMap.get_name()); #endif } } ~Impl() { std::string filename; if (const char* s = mMap.get_name()) filename = s; OPENVDB_LOG_DEBUG_RUNTIME("closing memory-mapped file " << filename); if (mNotifier) mNotifier(filename); if (mAutoDelete) { if (!boost::interprocess::file_mapping::remove(filename.c_str())) { if (errno != ENOENT) { // Warn if the file exists but couldn't be removed. std::string mesg = getErrorString(); if (!mesg.empty()) mesg = " (" + mesg + ")"; OPENVDB_LOG_WARN("failed to remove temporary file " << filename << mesg); } } } } Index64 getLastWriteTime() const { Index64 result = 0; const char* filename = mMap.get_name(); #ifdef _MSC_VER // boost::interprocess::detail was renamed to boost::interprocess::ipcdetail in Boost 1.48. using namespace boost::interprocess::detail; using namespace boost::interprocess::ipcdetail; if (void* fh = open_existing_file(filename, boost::interprocess::read_only)) { struct { unsigned long lo, hi; } mtime; // Windows FILETIME struct if (GetFileTime(fh, nullptr, nullptr, &mtime)) { result = (Index64(mtime.hi) << 32) | mtime.lo; } close_file(fh); } #else struct stat info; if (0 == ::stat(filename, &info)) { result = Index64(info.st_mtime); } #endif return result; } boost::interprocess::file_mapping mMap; boost::interprocess::mapped_region mRegion; bool mAutoDelete; Notifier mNotifier; mutable tbb::atomic mLastWriteTime; private: Impl(const Impl&); // not copyable Impl& operator=(const Impl&); // not copyable }; MappedFile::MappedFile(const std::string& filename, bool autoDelete): mImpl(new Impl(filename, autoDelete)) { } MappedFile::~MappedFile() { } std::string MappedFile::filename() const { std::string result; if (const char* s = mImpl->mMap.get_name()) result = s; return result; } SharedPtr MappedFile::createBuffer() const { if (!mImpl->mAutoDelete && mImpl->mLastWriteTime > 0) { // Warn if the file has been modified since it was opened // (but don't bother checking if it is a private, temporary file). if (mImpl->getLastWriteTime() > mImpl->mLastWriteTime) { OPENVDB_LOG_WARN("file " << this->filename() << " might have changed on disk" << " since it was opened"); mImpl->mLastWriteTime = 0; // suppress further warnings } } return SharedPtr{ new boost::iostreams::stream_buffer{ static_cast(mImpl->mRegion.get_address()), mImpl->mRegion.get_size()}}; } void MappedFile::setNotifier(const Notifier& notifier) { mImpl->mNotifier = notifier; } void MappedFile::clearNotifier() { mImpl->mNotifier = nullptr; } //////////////////////////////////////// std::string getErrorString(int errorNum) { return std::error_code(errorNum, std::generic_category()).message(); } std::string getErrorString() { return getErrorString(errno); } //////////////////////////////////////// Archive::Archive() : mFileVersion(OPENVDB_FILE_VERSION) , mLibraryVersion(OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION) , mUuid(boost::uuids::nil_uuid()) , mInputHasGridOffsets(false) , mEnableInstancing(true) , mCompression(DEFAULT_COMPRESSION_FLAGS) , mEnableGridStats(true) { } Archive::~Archive() { } Archive::Ptr Archive::copy() const { return Archive::Ptr(new Archive(*this)); } //////////////////////////////////////// std::string Archive::getUniqueTag() const { return boost::uuids::to_string(mUuid); } bool Archive::isIdentical(const std::string& uuidStr) const { return uuidStr == getUniqueTag(); } //////////////////////////////////////// uint32_t getFormatVersion(std::ios_base& is) { /// @todo get from StreamMetadata return static_cast(is.iword(sStreamState.fileVersion)); } void Archive::setFormatVersion(std::istream& is) { is.iword(sStreamState.fileVersion) = mFileVersion; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setFileVersion(mFileVersion); } } VersionId getLibraryVersion(std::ios_base& is) { /// @todo get from StreamMetadata 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; ///< @todo remove is.iword(sStreamState.libraryMinorVersion) = mLibraryVersion.second; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setLibraryVersion(mLibraryVersion); } } std::string getVersion(std::ios_base& 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; ///< @todo remove is.iword(sStreamState.libraryMajorVersion) = OPENVDB_LIBRARY_MAJOR_VERSION; ///< @todo remove is.iword(sStreamState.libraryMinorVersion) = OPENVDB_LIBRARY_MINOR_VERSION; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setFileVersion(OPENVDB_FILE_VERSION); meta->setLibraryVersion(VersionId( OPENVDB_LIBRARY_MAJOR_VERSION, OPENVDB_LIBRARY_MINOR_VERSION)); } } void setVersion(std::ios_base& strm, const VersionId& libraryVersion, uint32_t fileVersion) { strm.iword(sStreamState.fileVersion) = fileVersion; ///< @todo remove strm.iword(sStreamState.libraryMajorVersion) = libraryVersion.first; ///< @todo remove strm.iword(sStreamState.libraryMinorVersion) = libraryVersion.second; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setFileVersion(fileVersion); meta->setLibraryVersion(libraryVersion); } } std::string Archive::version() const { std::ostringstream ostr; ostr << mLibraryVersion.first << "." << mLibraryVersion.second << "/" << mFileVersion; return ostr.str(); } //////////////////////////////////////// uint32_t getDataCompression(std::ios_base& strm) { /// @todo get from StreamMetadata return uint32_t(strm.iword(sStreamState.dataCompression)); } void setDataCompression(std::ios_base& strm, uint32_t c) { strm.iword(sStreamState.dataCompression) = c; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setCompression(c); } } void Archive::setDataCompression(std::istream& is) { io::setDataCompression(is, mCompression); ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(is)) { meta->setCompression(mCompression); } } //static bool Archive::hasBloscCompression() { #ifdef OPENVDB_USE_BLOSC return true; #else return false; #endif } void Archive::setGridCompression(std::ostream& os, const GridBase& grid) const { // Start with the options that are enabled globally for this archive. uint32_t c = compression(); // Disable options that are inappropriate for the given grid. switch (grid.getGridClass()) { case GRID_LEVEL_SET: case GRID_FOG_VOLUME: // ZLIB compression is not used on level sets or fog volumes. c = c & ~COMPRESS_ZIP; break; case GRID_STAGGERED: case GRID_UNKNOWN: break; } io::setDataCompression(os, c); os.write(reinterpret_cast(&c), sizeof(uint32_t)); } void Archive::readGridCompression(std::istream& is) { if (getFormatVersion(is) >= OPENVDB_FILE_VERSION_NODE_MASK_COMPRESSION) { uint32_t c = COMPRESS_NONE; is.read(reinterpret_cast(&c), sizeof(uint32_t)); io::setDataCompression(is, c); } } //////////////////////////////////////// bool getWriteGridStatsMetadata(std::ios_base& strm) { /// @todo get from StreamMetadata return strm.iword(sStreamState.writeGridStatsMetadata) != 0; } void setWriteGridStatsMetadata(std::ios_base& strm, bool writeGridStats) { strm.iword(sStreamState.writeGridStatsMetadata) = writeGridStats; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setWriteGridStats(writeGridStats); } } //////////////////////////////////////// uint32_t getGridClass(std::ios_base& strm) { /// @todo get from StreamMetadata const uint32_t val = static_cast(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); ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setGridClass(cls); } } bool getHalfFloat(std::ios_base& strm) { /// @todo get from StreamMetadata return strm.iword(sStreamState.halfFloat) != 0; } void setHalfFloat(std::ios_base& strm, bool halfFloat) { strm.iword(sStreamState.halfFloat) = halfFloat; ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setHalfFloat(halfFloat); } } const void* getGridBackgroundValuePtr(std::ios_base& strm) { /// @todo get from StreamMetadata return strm.pword(sStreamState.gridBackground); } void setGridBackgroundValuePtr(std::ios_base& strm, const void* background) { strm.pword(sStreamState.gridBackground) = const_cast(background); ///< @todo remove if (StreamMetadata::Ptr meta = getStreamMetadataPtr(strm)) { meta->setBackgroundPtr(background); } } MappedFile::Ptr getMappedFilePtr(std::ios_base& strm) { if (const void* ptr = strm.pword(sStreamState.mappedFile)) { return *static_cast(ptr); } return MappedFile::Ptr(); } void setMappedFilePtr(std::ios_base& strm, io::MappedFile::Ptr& mappedFile) { strm.pword(sStreamState.mappedFile) = &mappedFile; } StreamMetadata::Ptr getStreamMetadataPtr(std::ios_base& strm) { if (const void* ptr = strm.pword(sStreamState.metadata)) { return *static_cast(ptr); } return StreamMetadata::Ptr(); } void setStreamMetadataPtr(std::ios_base& strm, StreamMetadata::Ptr& meta, bool transfer) { strm.pword(sStreamState.metadata) = &meta; if (transfer && meta) meta->transferTo(strm); } StreamMetadata::Ptr clearStreamMetadataPtr(std::ios_base& strm) { StreamMetadata::Ptr result = getStreamMetadataPtr(strm); strm.pword(sStreamState.metadata) = nullptr; return result; } //////////////////////////////////////// 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_BLOSC_COMPRESSION) { // Prior to the introduction of Blosc, ZLIB was the default compression scheme. mCompression = (COMPRESS_ZIP | COMPRESS_ACTIVE_MASK); } 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. std::mt19937 ran; ran.seed(std::mt19937::result_type(std::random_device()() + std::time(nullptr))); 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())); } } //////////////////////////////////////// //static bool Archive::isDelayedLoadingEnabled() { #if OPENVDB_ABI_VERSION_NUMBER <= 2 return false; #else return (nullptr == std::getenv("OPENVDB_DISABLE_DELAYED_LOAD")); #endif } namespace { struct NoBBox {}; template void doReadGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is, const BoxType& bbox) { struct Local { static void readBuffers(GridBase& g, std::istream& istrm, NoBBox) { g.readBuffers(istrm); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 static void readBuffers(GridBase& g, std::istream& istrm, const CoordBBox& indexBBox) { g.readBuffers(istrm, indexBBox); } static void readBuffers(GridBase& g, std::istream& istrm, const BBoxd& worldBBox) { g.readBuffers(istrm, g.constTransform().worldToIndexNodeCentered(worldBBox)); } #endif }; // Restore the file-level stream metadata on exit. struct OnExit { OnExit(std::ios_base& strm_): strm(&strm_), ptr(strm_.pword(sStreamState.metadata)) {} ~OnExit() { strm->pword(sStreamState.metadata) = ptr; } std::ios_base* strm; void* ptr; }; OnExit restore(is); // Stream metadata varies per grid, and it needs to persist // in case delayed load is in effect. io::StreamMetadata::Ptr streamMetadata; if (io::StreamMetadata::Ptr meta = io::getStreamMetadataPtr(is)) { // Make a grid-level copy of the file-level stream metadata. streamMetadata.reset(new StreamMetadata(*meta)); } else { streamMetadata.reset(new StreamMetadata); } streamMetadata->setHalfFloat(grid->saveFloatAsHalf()); io::setStreamMetadataPtr(is, streamMetadata, /*transfer=*/false); io::setGridClass(is, GRID_UNKNOWN); io::setGridBackgroundValuePtr(is, nullptr); grid->readMeta(is); // Add a description of the compression settings to the grid as metadata. /// @todo Would this be useful? //const uint32_t c = getDataCompression(is); //grid->insertMeta(GridBase::META_FILE_COMPRESSION, // StringMetadata(compressionToString(c))); streamMetadata->gridMetadata() = static_cast(*grid); 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); Local::readBuffers(*grid, is, bbox); } } else { // Older versions of the library stored the transform after the topology. grid->readTopology(is); grid->readTransform(is); Local::readBuffers(*grid, is, bbox); } 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()); } } } } // unnamed namespace 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); doReadGrid(grid, gd, is, NoBBox()); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 void Archive::readGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is, const BBoxd& worldBBox) { readGridCompression(is); doReadGrid(grid, gd, is, worldBBox); } void Archive::readGrid(GridBase::Ptr grid, const GridDescriptor& gd, std::istream& is, const CoordBBox& indexBBox) { readGridCompression(is); doReadGrid(grid, gd, is, indexBBox); } #endif //////////////////////////////////////// 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::StreamMetadata::Ptr streamMetadata = io::getStreamMetadataPtr(os); if (!streamMetadata) { streamMetadata.reset(new StreamMetadata); io::setStreamMetadataPtr(os, streamMetadata, /*transfer=*/false); } io::setDataCompression(os, compression()); io::setWriteGridStatsMetadata(os, 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)); using TreeMap = std::map; using TreeMapIter = TreeMap::iterator; TreeMap treeMap; // Determine which grid names are unique and which are not. using NameHistogram = std::map; NameHistogram nameCount; for (GridCPtrVecCIter i = grids.begin(), e = grids.end(); i != e; ++i) { if (const GridBase::ConstPtr& grid = *i) { const std::string name = grid->getName(); NameHistogram::iterator it = nameCount.find(name); if (it != nameCount.end()) it->second++; else nameCount[name] = 1; } } 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(); if (name.empty() || nameCount[name] > 1) { name = GridDescriptor::addSuffix(name, 0); } for (int n = 1; 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, compression()); } } void Archive::writeGrid(GridDescriptor& gd, GridBase::ConstPtr grid, std::ostream& os, bool seekable) const { // Restore file-level stream metadata on exit. struct OnExit { OnExit(std::ios_base& strm_): strm(&strm_), ptr(strm_.pword(sStreamState.metadata)) {} ~OnExit() { strm->pword(sStreamState.metadata) = ptr; } std::ios_base* strm; void* ptr; }; OnExit restore(os); // Stream metadata varies per grid, so make a copy of the file-level stream metadata. io::StreamMetadata::Ptr streamMetadata; if (io::StreamMetadata::Ptr meta = io::getStreamMetadataPtr(os)) { streamMetadata.reset(new StreamMetadata(*meta)); } else { streamMetadata.reset(new StreamMetadata); } streamMetadata->setHalfFloat(grid->saveFloatAsHalf()); streamMetadata->gridMetadata() = static_cast(*grid); io::setStreamMetadataPtr(os, streamMetadata, /*transfer=*/false); // 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. const auto copyOfGrid = grid->copyGrid(); // shallow copy ConstPtrCast(copyOfGrid)->addStatsMetadata(); ConstPtrCast(copyOfGrid)->insertMeta(GridBase::META_FILE_COMPRESSION, StringMetadata(compressionToString(getDataCompression(os)))); 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-2017 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.cc0000644000000000000000000001432113200122377015051 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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) { 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-2017 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.cc0000644000000000000000000001740613200122377013367 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "File.h" ///< @todo refactor #include "GridDescriptor.h" #include "TempFile.h" #include #include #include #include // for remove() #include // for std::bind() #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace io { struct Stream::Impl { Impl(): mOutputStream{nullptr} {} Impl(const Impl& other) { *this = other; } Impl& operator=(const Impl& other) { if (&other != this) { mMeta = other.mMeta; ///< @todo deep copy? mGrids = other.mGrids; ///< @todo deep copy? mOutputStream = other.mOutputStream; mFile.reset(); } return *this; } MetaMap::Ptr mMeta; GridPtrVecPtr mGrids; std::ostream* mOutputStream; std::unique_ptr mFile; }; //////////////////////////////////////// namespace { /// @todo Use MappedFile auto-deletion instead. void removeTempFile(const std::string expectedFilename, const std::string& filename) { if (filename == expectedFilename) { if (0 != std::remove(filename.c_str())) { std::string mesg = getErrorString(); if (!mesg.empty()) mesg = " (" + mesg + ")"; OPENVDB_LOG_WARN("failed to remove temporary file " << filename << mesg); } } } } Stream::Stream(std::istream& is, bool delayLoad): mImpl(new Impl) { if (!is) return; if (delayLoad && Archive::isDelayedLoadingEnabled()) { // Copy the contents of the stream to a temporary private file // and open the file instead. std::unique_ptr tempFile; try { tempFile.reset(new TempFile); } catch (std::exception& e) { std::string mesg; if (e.what()) mesg = std::string(" (") + e.what() + ")"; OPENVDB_LOG_WARN("failed to create a temporary file for delayed loading" << mesg << "; will read directly from the input stream instead"); } if (tempFile) { boost::iostreams::copy(is, *tempFile); const std::string& filename = tempFile->filename(); mImpl->mFile.reset(new File(filename)); mImpl->mFile->setCopyMaxBytes(0); // don't make a copy of the temporary file /// @todo Need to pass auto-deletion flag to MappedFile. mImpl->mFile->open(delayLoad, std::bind(&removeTempFile, filename, std::placeholders::_1)); } } if (!mImpl->mFile) { readHeader(is); // Tag the input stream with the library and file format version numbers // and the compression options specified in the header. StreamMetadata::Ptr streamMetadata(new StreamMetadata); io::setStreamMetadataPtr(is, streamMetadata, /*transfer=*/false); io::setVersion(is, libraryVersion(), fileVersion()); io::setDataCompression(is, compression()); // Read in the VDB metadata. mImpl->mMeta.reset(new MetaMap); mImpl->mMeta->readMeta(is); // Read in the number of grids. const int32_t gridCount = readGridCount(is); // Read in all grids and insert them into mGrids. mImpl->mGrids.reset(new GridPtrVec); std::vector descriptors; descriptors.reserve(gridCount); Archive::NamedGridMap namedGrids; for (int32_t i = 0; i < gridCount; ++i) { GridDescriptor gd; gd.read(is); descriptors.push_back(gd); GridBase::Ptr grid = readGrid(gd, is); mImpl->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(): mImpl(new Impl) { } Stream::Stream(std::ostream& os): mImpl(new Impl) { mImpl->mOutputStream = &os; } Stream::~Stream() { } Stream::Stream(const Stream& other): Archive(other), mImpl(new Impl(*other.mImpl)) { } Stream& Stream::operator=(const Stream& other) { if (&other != this) { mImpl.reset(new Impl(*other.mImpl)); } return *this; } SharedPtr Stream::copy() const { return SharedPtr(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::write(const GridCPtrVec& grids, const MetaMap& metadata) const { if (mImpl->mOutputStream == nullptr) { OPENVDB_THROW(ValueError, "no output stream was specified"); } this->writeGrids(*mImpl->mOutputStream, grids, metadata); } 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 { MetaMap::Ptr result; if (mImpl->mFile) { result = mImpl->mFile->getMetadata(); } else if (mImpl->mMeta) { // Return a deep copy of the file-level metadata // that was read when this object was constructed. result.reset(new MetaMap(*mImpl->mMeta)); } return result; } GridPtrVecPtr Stream::getGrids() { if (mImpl->mFile) { return mImpl->mFile->getGrids(); } return mImpl->mGrids; } } // namespace io } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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.h0000644000000000000000000005344613200122377012477 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #include #if OPENVDB_ABI_VERSION_NUMBER <= 3 #include #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { // One-dimensional scalar types using Index32 = uint32_t; using Index64 = uint64_t; using Index = Index32; using Int16 = int16_t; using Int32 = int32_t; using Int64 = int64_t; using Int = Int32; using Byte = unsigned char; using Real = double; // Two-dimensional vector types using Vec2R = math::Vec2; using Vec2I = math::Vec2; using Vec2f = math::Vec2; using Vec2H = math::Vec2; using math::Vec2i; using math::Vec2s; using math::Vec2d; // Three-dimensional vector types using Vec3R = math::Vec3; using Vec3I = math::Vec3; using Vec3f = math::Vec3; using Vec3H = math::Vec3; using Vec3U8 = math::Vec3; using Vec3U16 = math::Vec3; using math::Vec3i; using math::Vec3s; using math::Vec3d; using math::Coord; using math::CoordBBox; using BBoxd = math::BBox; // Four-dimensional vector types using Vec4R = math::Vec4; using Vec4I = math::Vec4; using Vec4f = math::Vec4; using Vec4H = math::Vec4; using math::Vec4i; using math::Vec4s; using math::Vec4d; // Three-dimensional matrix types using Mat3R = math::Mat3; // Four-dimensional matrix types using Mat4R = math::Mat4; using Mat4d = math::Mat4; using Mat4s = math::Mat4; // Quaternions using QuatR = math::Quat; // Dummy type for a voxel with a binary mask value, e.g. the active state class ValueMask {}; #if OPENVDB_ABI_VERSION_NUMBER <= 3 // Use Boost shared pointers in OpenVDB 3 ABI compatibility mode. template using SharedPtr = boost::shared_ptr; template using WeakPtr = boost::weak_ptr; template inline SharedPtr ConstPtrCast(const SharedPtr& ptr) { return boost::const_pointer_cast(ptr); } template inline SharedPtr DynamicPtrCast(const SharedPtr& ptr) { return boost::dynamic_pointer_cast(ptr); } template inline SharedPtr StaticPtrCast(const SharedPtr& ptr) { return boost::static_pointer_cast(ptr); } #else // if OPENVDB_ABI_VERSION_NUMBER > 3 // Use STL shared pointers from OpenVDB 4 on. template using SharedPtr = std::shared_ptr; template using WeakPtr = std::weak_ptr; /// @brief Return a new shared pointer that points to the same object /// as the given pointer but with possibly different const-ness. /// @par Example: /// @code /// FloatGrid::ConstPtr grid = ...; /// FloatGrid::Ptr nonConstGrid = ConstPtrCast(grid); /// FloatGrid::ConstPtr constGrid = ConstPtrCast(nonConstGrid); /// @endcode template inline SharedPtr ConstPtrCast(const SharedPtr& ptr) { return std::const_pointer_cast(ptr); } /// @brief Return a new shared pointer that is either null or points to /// the same object as the given pointer after a @c dynamic_cast. /// @par Example: /// @code /// GridBase::ConstPtr grid = ...; /// FloatGrid::ConstPtr floatGrid = DynamicPtrCast(grid); /// @endcode template inline SharedPtr DynamicPtrCast(const SharedPtr& ptr) { return std::dynamic_pointer_cast(ptr); } /// @brief Return a new shared pointer that points to the same object /// as the given pointer after a @c static_cast. /// @par Example: /// @code /// FloatGrid::Ptr floatGrid = ...; /// GridBase::Ptr grid = StaticPtrCast(floatGrid); /// @endcode template inline SharedPtr StaticPtrCast(const SharedPtr& ptr) { return std::static_pointer_cast(ptr); } #endif //////////////////////////////////////// /// @brief Integer wrapper, required to distinguish PointIndexGrid and /// PointDataGrid from Int32Grid and Int64Grid /// @note @c Kind is a dummy parameter used to create distinct types. template struct PointIndex { static_assert(std::is_integral::value, "PointIndex requires an integer value type"); using IntType = IntType_; PointIndex(IntType i = IntType(0)): mIndex(i) {} operator IntType() const { return mIndex; } /// Needed to support the (zeroVal() + val) idiom. template PointIndex operator+(T x) { return PointIndex(mIndex + IntType(x)); } private: IntType mIndex; }; using PointIndex32 = PointIndex; using PointIndex64 = PointIndex; using PointDataIndex32 = PointIndex; using PointDataIndex64 = PointIndex; //////////////////////////////////////// template struct VecTraits { static const bool IsVec = false; static const int Size = 1; using ElementType = T; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 2; using ElementType = T; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 3; using ElementType = T; }; template struct VecTraits > { static const bool IsVec = true; static const int Size = 4; using ElementType = T; }; //////////////////////////////////////// /// @brief CanConvertType::value is @c true if a value /// of type @a ToType can be constructed from a value of type @a FromType. template struct CanConvertType { enum { value = std::is_constructible::value }; }; // Specializations for vector types, which can be constructed from values // of their own ValueTypes (or values that can be converted to their ValueTypes), // but only explicitly template struct CanConvertType > { enum { value = true }; }; template struct CanConvertType > { enum { value = true }; }; template struct CanConvertType > { enum { value = true }; }; template struct CanConvertType, math::Vec2 > { enum {value = true}; }; template struct CanConvertType, math::Vec3 > { enum {value = true}; }; template struct CanConvertType, math::Vec4 > { enum {value = true}; }; template struct CanConvertType > { enum { value = CanConvertType::value }; }; template struct CanConvertType > { enum { value = CanConvertType::value }; }; template struct CanConvertType > { enum { value = CanConvertType::value }; }; template<> struct CanConvertType { enum {value = true}; }; template<> struct CanConvertType { enum {value = true}; }; template struct CanConvertType { enum {value = CanConvertType::value}; }; template struct CanConvertType { enum {value = CanConvertType::value}; }; //////////////////////////////////////// // 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 "mask"; } template<> inline const char* typeNameAsString() { return "half"; } template<> inline const char* typeNameAsString() { return "float"; } template<> inline const char* typeNameAsString() { return "double"; } template<> inline const char* typeNameAsString() { return "uint8"; } template<> inline const char* typeNameAsString() { return "int16"; } template<> inline const char* typeNameAsString() { return "uint16"; } 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 "vec2i"; } template<> inline const char* typeNameAsString() { return "vec2s"; } template<> inline const char* typeNameAsString() { return "vec2d"; } template<> inline const char* typeNameAsString() { return "vec3u8"; } template<> inline const char* typeNameAsString() { return "vec3u16"; } 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"; } template<> inline const char* typeNameAsString() { return "quats"; } template<> inline const char* typeNameAsString() { return "quatd"; } template<> inline const char* typeNameAsString() { return "ptidx32"; } template<> inline const char* typeNameAsString() { return "ptidx64"; } template<> inline const char* typeNameAsString() { return "ptdataidx32"; } template<> inline const char* typeNameAsString() { return "ptdataidx64"; } //////////////////////////////////////// /// @brief This struct collects both input and output arguments to "grid combiner" functors /// used with the tree::TypedGrid::combineExtended() and combine2Extended() methods. /// AValueType and BValueType are the value types 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: using AValueT = AValueType; using BValueT = BValueType; CombineArgs() : mAValPtr(nullptr) , mBValPtr(nullptr) , mResultValPtr(&mResultVal) , mAIsActive(false) , mBIsActive(false) , mResultIsActive(false) { } /// Use this constructor when the result value is stored externally. CombineArgs(const AValueType& a, const BValueType& b, AValueType& result, bool aOn = false, bool bOn = false) : mAValPtr(&a) , mBValPtr(&b) , mResultValPtr(&result) , mAIsActive(aOn) , mBIsActive(bOn) { this->updateResultActive(); } /// Use this constructor when the result value should be stored in this struct. CombineArgs(const AValueType& a, const BValueType& b, bool aOn = false, bool bOn = false) : mAValPtr(&a) , mBValPtr(&b) , mResultValPtr(&mResultVal) , mAIsActive(aOn) , mBIsActive(bOn) { this->updateResultActive(); } /// Get the A input value. const AValueType& a() const { return *mAValPtr; } /// Get the B input value. const BValueType& b() const { return *mBValPtr; } //@{ /// Get the output value. const AValueType& result() const { return *mResultValPtr; } AValueType& result() { return *mResultValPtr; } //@} /// Set the output value. CombineArgs& setResult(const AValueType& val) { *mResultValPtr = val; return *this; } /// Redirect the A value to a new external source. CombineArgs& setARef(const AValueType& a) { mAValPtr = &a; return *this; } /// Redirect the B value to a new external source. CombineArgs& setBRef(const BValueType& b) { mBValPtr = &b; return *this; } /// Redirect the result value to a new external destination. CombineArgs& setResultRef(AValueType& 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 AValueType* mAValPtr; // pointer to input value from A grid const BValueType* mBValPtr; // pointer to input value from B grid AValueType mResultVal; // computed output value (unused if stored externally) AValueType* 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; }; //////////////////////////////////////// #if OPENVDB_ABI_VERSION_NUMBER <= 3 /// 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 }; #endif /// @brief Tag dispatch class that distinguishes shallow copy constructors /// from deep copy constructors class ShallowCopy {}; /// @brief Tag dispatch class that distinguishes topology copy constructors /// from deep copy constructors class TopologyCopy {}; /// @brief Tag dispatch class that distinguishes constructors during file input class PartialCreate {}; } // 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-2017 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/doc/0000755000000000000000000000000013200122652011747 5ustar rootrootopenvdb/doc/codingstyle.txt0000644000000000000000000003151513200122206015034 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 sStyleContents 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). -# Use @c internal (or @c detail) or module_internal for symbols such as templates that must be defined in header files but that are implementation details not intended for public use. @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()) -# Accessor methods that return a member variable by reference or a primitive type by value are just the variable name (e.g., Grid::tree()). -# Accessor methods 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) -# The @c m prefix may be omitted for members of structs used in the public API (e.g., struct Color { float r, g, b; }). @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 letter (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 -# With @c enum, all uppercase with words separated by underscores (e.g., @c GRID_LEVEL_SET, @c VEC_INVARIANT) and with a common prefix -# With enum class, mixed case with the first letter uppercase (e.g., @c GridClass::Unknown, @c GridClass::LevelSet) @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 at the default warning level. -# 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 -# Indentation is 4 spaces. Do not use tabs. -# Lines are no more than 100 characters long. -# Use Unix-style carriage returns ("\n") rather than Windows/DOS ones ("\r\n"). -# 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 @c +, @c -, @c *, @c /, @c &&, and @c ||. For clarity in mathematical situations, you may omit the spaces on either side of @c * and @c / operators to emphasize their precedence over @c + and @c -. -# Do not leave a space between any dereferencing operator (such as @c *, @c &, @c [], @c ->, or @c .) 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. -# Do not use anonymous namespaces in 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. @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. -# Document public code with Doxygen comments, formatted as follows: @verbatim /// @brief Create a new grid of type @c GridType classified as a "Level Set", /// i.e., a narrow-band level set. /// /// @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); @endverbatim @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 global static constants instead. (Local static constants are not guaranteed to be initialized in a thread-safe manner.) -# 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. -# 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 @c const wherever 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 if closely related. -# 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/python.txt0000644000000000000000000005204713200122206014034 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 sPyContents Contents - @ref sPyBasics - @ref sPyHandlingMetadata - @ref sPyAccessors - @ref sPyIteration - @ref sPyNumPy - @ref sPyMeshConversion - @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 sPyMeshConversion Mesh conversion Also available if the OpenVDB Python module is compiled with NumPy support (see @ref sPyNumPy "above") are grid methods to convert polygonal meshes to level sets (see @vdblink::tools::meshToLevelSet() tools::meshToLevelSet@endlink for some restrictions) and to convert isosurfaces of scalar-valued grids to meshes. @code{.py} >>> import pyopenvdb as vdb >>> import numpy >>> grid = vdb.read('bunny.vdb', 'ls_bunny') # Convert a volume to a quadrilateral mesh. >>> points, quads = grid.convertToQuads() # World-space vertices of the mesh: >>> points array([[-14.05082607, -0.10118673, -0.40250054], [-14.05230808, -0.05570767, -0.45693323], [-14.05613995, -0.0734246 , -0.42150033], ..., [ 7.25201273, 13.25417805, 6.45283508], [ 7.25596714, 13.31225586, 6.40827513], [ 7.30518484, 13.21096039, 6.40724468]], dtype=float32) # Quadrilateral faces of the mesh, given by # 4-tuples of indices into the vertex list: >>> quads array([[ 5, 2, 1, 4], [ 11, 7, 6, 10], [ 14, 9, 8, 13], ..., [1327942, 1327766, 1339685, 1339733], [1339728, 1327921, 1327942, 1339733], [1339733, 1339685, 1339661, 1339728]], dtype=uint32) # Convert the mesh back to a (single-precision) volume. # Give the resulting grid the original grid's transform. >>> gridFromQuads = vdb.FloatGrid.createLevelSetFromPolygons( ... points, quads=quads, transform=grid.transform) # Alternatively, mesh a volume adaptively for a lower polygon count. # Adaptive meshing generates both triangular and quadrilateral faces. >>> points, triangles, quads = grid.convertToPolygons(adaptivity=0.5) # World-space vertices of the mesh: >>> points array([[-14.02906322, -0.07213751, -0.49265194], [-14.11877823, -0.11127799, -0.17118289], [-13.85006142, -0.08145611, -0.86669081], ..., [ 7.31098318, 12.97358608, 6.55133963], [ 7.20240211, 12.80632019, 6.80356836], [ 7.23679161, 13.28100395, 6.45595646]], dtype=float32) # Triangular faces of the mesh, given by # triples of indices into the vertex list: >>> triangles array([[ 8, 7, 0], [ 14, 9, 8], [ 14, 11, 9], ..., [22794, 22796, 22797], [22784, 22783, 22810], [22796, 22795, 22816]], dtype=uint32) # Quadrilateral faces of the mesh, given by # 4-tuples of indices into the vertex list: >>> quads array([[ 4, 3, 6, 5], [ 8, 9, 10, 7], [ 11, 12, 10, 9], ..., [23351, 23349, 23341, 23344], [23344, 23117, 23169, 23351], [23169, 23167, 23349, 23351]], dtype=uint32) # Convert the mesh to a double-precision volume. >>> doubleGridFromPolys = vdb.DoubleGrid.createLevelSetFromPolygons( ... points, triangles, quads, transform=grid.transform) @endcode The mesh representation is similar to that of the commonly-used Wavefront .obj file format, except that the vertex array is indexed starting from 0 rather than 1. To output mesh data to a file in .obj format, one might do the following: @code{.py} >>> def writeObjFile(filename, points, triangles=[], quads=[]): ... f = open(filename, 'w') ... # Output vertices. ... for xyz in points: ... f.write('v %g %g %g\n' % tuple(xyz)) ... f.write('\n') ... # Output faces. ... for ijk in triangles: ... f.write('f %d %d %d\n' % ... (ijk[0]+1, ijk[1]+1, ijk[2]+1)) # offset vertex indices by one ... for ijkl in quads: ... f.write('f %d %d %d %d\n' % ... (ijkl[0]+1, ijkl[1]+1, ijkl[2]+1, ijkl[3]+1)) ... f.close() ... >>> mesh = grid.convertToPolygons(adaptivity=0.8) >>> writeObjFile('bunny.obj', *mesh) @endcode @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/doc.txt0000644000000000000000000006471313200122206013263 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 changes "Release Notes" for what’s new in this version of @b OpenVDB. 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 points "OpenVDB Points" to store point data using @b OpenVDB. See @subpage python "Using OpenVDB in Python" to get started with the @b OpenVDB Python module. 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 secGrid - @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 @b Tree with a @b 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 @b 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 @b 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 @b 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 LeafNode@endlinks and usually one or more levels of @vdblink::tree::InternalNode InternalNode@endlinks 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 @b RootNode and InternalNodes increasingly subdivide the three-dimensional index space, and the LeafNodes hold the actual unique voxels. The type of a @b 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 using FloatTree = tree::Tree4::Type; using BoolTree = tree::Tree4::Type; @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 @b LeafNode holds a three-dimensional grid of 23 voxels on a side (i.e., an 8×8×8 voxel grid). Internally, the @b LeafNode is said to be at “level 0” of the tree. At “level 1” of this tree is the first @b InternalNode, and it indexes a 24×24×24 = 16×16×16 grid, each entry of which is either a @b LeafNode or a constant value that represents an 8×8×8 block of voxels. At “level 2” is the second @b InternalNode in this configuration; it in turn indexes a 25×25×25 = 32×32×32 grid of level-1 InternalNodes and/or values, and so the @b InternalNode at level 2 subsumes a three-dimensional block of voxels of size 32×16×8 = 4096 on a side. Unlike the InternalNodes and LeafNodes, the @b 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 @b 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 @b InternalNode at level 1 is equivalent to a constant-value cube of voxels of the same size, 8×8×8, as a @b 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 @b 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 the job of a @b Transform. A simple linear transform assumes a lattice-like structure with a fixed physical distance Δ between indices, so that @xyz = (Δi, Δj, Δk). @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. @b 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 @vdblink::math::Transform Transform @endlink provides a context for interpreting the information held in a tree by associating a location in world space with each entry in the tree. The actual implementation of the @b 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 @b 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 @b 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 secGrid The Grid For many applications, it might not be necessary ever to operate directly on trees, though there are often significant performance improvements to be gained by exploiting the tree structure. The @vdblink::Grid Grid@endlink, however, is the preferred interface through which to manage voxel data, in part because a grid associates with a tree additional and often necessary information that is not accessible through the tree itself. A @vdblink::Grid Grid@endlink contains smart pointers to a @vdblink::tree::Tree Tree@endlink object and a @vdblink::math::Transform Transform@endlink object, either or both of which might be shared with other grids. As mentioned above, the transform provides for the interpretation of voxel locations. Other grid metadata, notably the grid class, the vector type and the world space/local space toggle, affect the interpretation of voxel values. OpenVDB is particularly well-suited (though by no means exclusively so) to the representation of narrow-band level sets and fog volumes. A narrow-band level set is represented by three distinct regions of voxels: an @b outside (or background) region of inactive voxels having a constant, positive distance from the level set surface; an @b inside region of inactive voxels having a constant, negative distance; and a thin band of active voxels (normally three voxels wide on either side of the surface) whose values are signed distances. Similarly, a fog volume is represented by an outside region of inactive voxels with value zero, an inside region of active voxels with value one, and a thin band of active voxels, with values typically varying linearly between zero and one, that separates the inside from the outside. Identifying a grid as a level set or a fog volume, by setting its @vdblink::GridClass grid class@endlink with @vdblink::Grid::setGridClass() setGridClass@endlink, allows tools to invoke alternative implementations that are better-suited or better-optimized for those classes. For example, resampling (in particular, scaling) a level set should normally not be done without updating its signed distance values. The @vdblink::tools::resampleToMatch() resampleToMatch@endlink tool automatically recomputes signed distances for grids that are identified as level sets. (The lower-level @vdblink::tools::GridResampler GridResampler@endlink does not, but it is optimized for level set grids in that it transforms only voxels in the narrow band and relies on @vdblink::Grid::signedFloodFill() signed flood fill@endlink to reconstruct the inside and outside regions.) Other tools whose behavior is affected by the grid class include the @vdblink::tools::divergence() divergence@endlink operator (which has an alternative implementation for @ref sStaggered "staggered velocity" grids), the @vdblink::tools::volumeToMesh() volume to mesh@endlink converter, and the @vdblink::tools::fillWithSpheres() sphere packing@endlink tool. In addition, a number of level-set-specific tools, such as the @vdblink::tools::LevelSetTracker level set tracker@endlink, throw exceptions when invoked on grids that are not identified as level sets. It is important, therefore, to set a grid’s class appropriately. When a vector-valued grid is transformed or resampled, it is often necessary for the transform to be applied not just to voxel locations but also to voxel values. By default, grids are identified as “world-space”, meaning that if the grid is vector-valued, its voxel values should be transformed. Alternatively, voxel values of grids identified as “local-space”, via @vdblink::Grid::setIsInWorldSpace() setIsInWorldSpace@endlink, do not undergo transformation. A world-space grid’s @vdblink::VecType vector type@endlink, specified with @vdblink::Grid::setVectorType() setVectorType@endlink, may be invariant, covariant or contravariant, which determines how transforms are applied to the grid’s voxel values (for details see, for example, Covariance and contravariance of vectors [Wikipedia]). The @vdblink::tools::transformVectors() transformVectors@endlink tool can be used to apply a transform to a grid’s voxel values, and it handles all of the supported vector types. A grid can optionally be assigned @vdblink::Grid::setName() name@endlink and @vdblink::Grid::setCreator() creator@endlink strings. These are purely informational, though it might be desirable to name grids so as to easily select which ones to read from files that contain multiple grids. In the absence of grid names, or at least of unique names, OpenVDB’s file I/O routines recognize an ordinal suffix: “[0]” refers to the first unnamed grid, “[1]” refers to the second, and so on, and “density[0]” and “density[1]” refer to the first and second grids named “density”. Also of interest for file I/O is a grid’s “@vdblink::Grid::setSaveFloatAsHalf() save float as half@endlink” setting, which allows it to be written more compactly using 16-bit floating point values rather than full-precision values. Finally, during file output certain statistics are computed and stored as per-grid metadata. These include the grid’s index-space active voxel bounding box, its active voxel count and its memory usage in bytes. This information can also be @vdblink::io::File::readAllGridMetadata() retrieved@endlink efficiently from a file. @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 @b 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, @b Tree::ValueOnCIter is a read-only iterator over all active values (both tile and voxel) of a tree, whereas @b 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 @b 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 using GridType = openvdb::FloatGrid; GridType grid = ...; for (GridType::ValueOnCIter iter = grid.cbeginValueOn(); iter.test(); ++iter) ... @endcode or more compactly @code for (auto 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, @b Grid::cbeginValueOn returns a @c const iterator to the first of a grid’s active values, whereas @b LeafNode::beginValueAll returns a non-const iterator to the first of a leaf node’s values, both active and inactive. (@c const overloads of begin* methods are usually provided, so that if the @b Grid is itself @c const, Grid::begin* will actually return a @c 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 @b test and @b next, a @b 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 By convention in OpenVDB, voxels in the narrow band of a narrow-band level set 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 @b LeafNode in a tree exactly once. See the @vdblink::tree::LeafIteratorBase LeafIteratorBase@endlink class for details, and also the related @vdblink::tree::LeafManager LeafManager@endlink acceleration structure. @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 (@b RootNode, @b InternalNode and @b 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 @b RootNode, @b InternalNode or @b 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.txt0000644000000000000000000004432013200122206013437 0ustar rootroot/** @page transformsAndMaps Transforms and Maps @section sMathContents 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, @b 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, 3×3 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 @b 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 Δ on a side, the transform @xyz = (Δi + Δ/2, Δj + Δ/2, Δk + Δ /2) will place the center of the first cube (i.e., the image of (0,0,0)) at the world space location of (Δ/2, Δ/2, Δ/2), and the cube will have walls coincident with the x=0, y=0 and z=0 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 @ijk = (0,0,0), and the transform would lack the offset used in the cell-centered case; so it would simply be @xyz = (Δi, Δj, Δk). @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 @b 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 @b 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 @b voxelVolume can not in general be computed from the @b voxelSize, because there is no guarantee that a general transform will convert a cube-shaped voxel into another cube. As a result, @b 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 (i−1/2, jk) face, the second element with (ij−1/2, k), and the third element with (ijk−1/2). @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 @b 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., @xyz = (Δi, Δj, Δk), where Δ 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 4×4 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 xrange = f(xdomain) that map 3-vectors from one space (the domain) to another (the range), or from the range back to the domain (xdomain = f−1(xrange)), 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.txt0000644000000000000000000022312113200122206014772 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 sCookbookContents 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 sIteratorRange - @ref sInterpolation - @ref sSamplers - @ref sGridSampler - @ref sDualGridSampler - @ref sXformTools - @ref sResamplingTools - @ref sValueXformTools - @ref sCombiningGrids - @ref sCsgTools - @ref sCompTools - @ref sCombineTools - @ref sGenericProg - @ref sTypedGridMethods - @ref sPointsHelloWorld - @ref sPointsConversion - @ref sPointsGeneration - @ref sPointIterationFiltering - @ref sPointIteration - @ref sPointGroups - @ref sPointFiltering - @ref sPointCustomFiltering - @ref sPointStride - @ref sConstantStride @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. openvdb::tools::signedFloodFill(grid.tree()); } @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. @subsection sIteratorRange Iterator Range A @vdblink::tree::IteratorRange tree::IteratorRange@endlink wraps any grid or tree iterator and gives the iterator TBB splittable range semantics, so that it can be used as the Range argument to functions like @c tbb::parallel_for() and @c tbb::parallel_reduce(). (This is in fact how @vdblink::tools::foreach() tools::foreach()@endlink and @vdblink::tools::transformValues() tools::transformValues()@endlink are implemented; see @ref sValueXformTools, below, for more on those functions.) There is some overhead to splitting, since grid and tree iterators are not random-access, but the overhead should typically be negligible compared with the amount of work done per subrange. The following is a complete program that uses @vdblink::tree::IteratorRange tree::IteratorRange@endlink. The program iterates in parallel over the leaf nodes of a tree (by splitting the iteration range of a @vdblink::tree::Tree::LeafCIter Tree::LeafCIter@endlink) and computes the total number of active leaf-level voxels by incrementing a global, thread-safe counter. @code #include #include #include #include #include #include // Global active voxel counter, atomically updated from multiple threads tbb::atomic activeLeafVoxelCount; // Functor for use with tbb::parallel_for() that operates on a grid's leaf nodes template struct LeafProcessor { typedef typename GridType::TreeType TreeType; typedef typename TreeType::LeafNodeType LeafNode; // Define an IteratorRange that splits the iteration space of a leaf iterator. typedef openvdb::tree::IteratorRange IterRange; void operator()(IterRange& range) const { // Note: this code must be thread-safe. // Iterate over a subrange of the leaf iterator's iteration space. for ( ; range; ++range) { // Retrieve the leaf node to which the iterator is pointing. const LeafNode& leaf = *range.iterator(); // Update the global counter. activeLeafVoxelCount.fetch_and_add(leaf.onVoxelCount()); } } }; int main() { openvdb::initialize(); // Generate a level set grid. openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere(/*radius=*/20.0, /*center=*/openvdb::Vec3f(1.5, 2, 3), /*voxel size=*/0.5); // Construct a functor for use with tbb::parallel_for() // that processes the leaf nodes of a FloatGrid. typedef LeafProcessor FloatLeafProc; FloatLeafProc proc; // Wrap a leaf iterator in an IteratorRange. FloatLeafProc::IterRange range(grid->tree().cbeginLeaf()); // Iterate over leaf nodes in parallel. tbb::parallel_for(range, proc); std::cout << activeLeafVoxelCount << " active leaf voxels" << std::endl; // The computed voxel count should equal the grid's active voxel count, // since all of the active voxels in a level set grid are stored at the // leaf level (that is, there are no active tiles in a level set grid). assert(activeLeafVoxelCount == grid->activeVoxelCount()); } @endcode @section sInterpolation Interpolation of grid values Applications such as rendering require evaluation of grids at arbitrary, fractional coordinates in either index or world space. This is achieved, of course, by interpolating between known grid values at neighboring whole-voxel locations, that is, at integer coordinates in index space. The following sections introduce OpenVDB’s various interpolation schemes as well as the @ref sGridSampler and @ref sDualGridSampler classes for efficient, continuous sampling of grids. In most cases, GridSampler is the preferred interface for interpolation, but note that when a fixed transform is to be applied to all values in a grid (that is, the grid is to be resampled), it is both easier and more efficient to use the multithreaded @vdblink::tools::GridTransformer GridTransformer@endlink class, introduced in @ref sXformTools. @subsection sSamplers Index-space samplers OpenVDB offers low-level zero-, first- and second-order interpolators @vdblink::tools::PointSampler PointSampler@endlink, @vdblink::tools::BoxSampler BoxSampler@endlink and @vdblink::tools::QuadraticSampler QuadraticSampler@endlink, in addition to the variants @vdblink::tools::StaggeredPointSampler StaggeredPointSampler@endlink, @vdblink::tools::StaggeredBoxSampler StaggeredBoxSampler@endlink and @vdblink::tools::StaggeredQuadraticSampler StaggeredQuadraticSampler@endlink for @ref sStaggered "staggered" velocity grids. @code #include #include const GridType grid = ...; // Choose fractional coordinates in index space. const openvdb::Vec3R ijk(10.5, -100.2, 50.3); // Compute the value of the grid at ijk via nearest-neighbor (zero-order) // interpolation. GridType::ValueType v0 = openvdb::tools::PointSampler::sample(grid.tree(), ijk); // Compute the value via trilinear (first-order) interpolation. GridType::ValueType v1 = openvdb::tools::BoxSampler::sample(grid.tree(), ijk); // Compute the value via triquadratic (second-order) interpolation. GridType::ValueType v2 = openvdb::tools::QuadraticSampler::sample(grid.tree(), ijk); @endcode These examples invoke the @vdblink::tree::Tree::getValue() getValue@endlink method on the grid’s tree to fetch sample values in the neighborhood of @ijk. Accessing values via the tree is thread-safe due to the lack of caching, but for that reason it is also suboptimal. For better performance, use @ref subsecValueAccessor "value accessors" (but be careful to use one accessor per computational thread): @code GridType::ConstAccessor accessor = grid.getConstAccessor(); GridType::ValueType v0 = openvdb::tools::PointSampler::sample(accessor, ijk); GridType::ValueType v1 = openvdb::tools::BoxSampler::sample(accessor, ijk); GridType::ValueType v2 = openvdb::tools::QuadraticSampler::sample(accessor, ijk); @endcode Another issue with these low-level interpolators is that they operate only in index space. To interpolate in world space, use the higher-level classes discussed below. @subsection sGridSampler Grid Sampler The @vdblink::tools::GridSampler GridSampler@endlink class allows for continuous sampling in both world space and index space and can be used with grids, trees or value accessors. @code #include #include const GridType grid = ...; // Instantiate the GridSampler template on the grid type and on a box sampler // for thread-safe but uncached trilinear interpolation. openvdb::tools::GridSampler sampler(grid); // Compute the value of the grid at fractional coordinates in index space. GridType::ValueType indexValue = sampler.isSample(openvdb::Vec3R(10.5, -100.2, 50.3)); // Compute the value of the grid at a location in world space. GridType::ValueType worldValue = sampler.wsSample(openvdb::Vec3R(0.25, 1.4, -1.1)); // Request a value accessor for accelerated access. // (Because value accessors employ a cache, it is important to declare // one accessor per thread.) GridType::ConstAccessor accessor = grid.getConstAccessor(); // Instantiate the GridSampler template on the accessor type and on // a box sampler for accelerated trilinear interpolation. openvdb::tools::GridSampler fastSampler(accessor, grid.transform()); // Compute the value of the grid at fractional coordinates in index space. indexValue = fastSampler.isSample(openvdb::Vec3R(10.5, -100.2, 50.3)); // Compute the value of the grid at a location in world space. worldValue = fastSampler.wsSample(openvdb::Vec3R(0.25, 1.4, -1.1)); @endcode Note that when constructing a GridSampler with either a tree or a value accessor, you must also supply an index-to-world transform. When constructing a GridSampler with a grid, the grid's transform is used automatically. @subsection sDualGridSampler Dual Grid Sampler It might sometimes be necessary to interpolate values from a source grid into the index space of a target grid. If this transformation is to be applied to all of the values in the source grid, then it is best to use the tools in GridTransformer.h. For other cases, consider using the @vdblink::tools::DualGridSampler DualGridSampler@endlink class. Like the GridSampler class, this class can be used with grids, trees or value accessors. In addition, DualGridSampler checks if the source and target grids are aligned (that is, they have the same transform), in which case it avoids unnecessary interpolation. @code #include #include const GridType sourceGrid = ...; // Instantiate the DualGridSampler template on the grid type and on // a box sampler for thread-safe but uncached trilinear interpolation. openvdb::tools::DualGridSampler sampler(sourceGrid, targetGrid.constTransform()); // Compute the value of the source grid at a location in the // target grid's index space. GridType::ValueType value = sampler(openvdb::Coord(-23, -50, 202)); // Request a value accessor for accelerated access to the source grid. // (Because value accessors employ a cache, it is important to declare // one accessor per thread.) GridType::ConstAccessor accessor = sourceGrid.getConstAccessor(); // Instantiate the DualGridSampler template on the accessor type and on // a box sampler for accelerated trilinear interpolation. openvdb::tools::DualGridSampler fastSampler(accessor, sourceGrid.constTransform(), targetGrid.constTransform()); // Compute the value of the source grid at a location in the // target grid's index space. value = fastSampler(openvdb::Coord(-23, -50, 202)); @endcode Note that interpolation is done by invoking a DualGridSampler as a functor, in contrast to the more general-purpose GridSampler. @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 (like @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 @anchor openvdbPointsHelloWorld @section sPointsHelloWorld “Hello, World” for OpenVDB Points This is a simple example showing how to convert a few points, perform I/O and iterate over them to extract their world-space positions. For more information about using OpenVDB to store point data, see the @ref openvdbPointsOverview "OpenVDB Points Documentation". @code #include #include #include #include #include int main() { // Initialize grid types and point attributes types. openvdb::initialize(); // Create a vector with four point positions. std::vector positions; positions.push_back(openvdb::Vec3R(0, 1, 0)); positions.push_back(openvdb::Vec3R(1.5, 3.5, 1)); positions.push_back(openvdb::Vec3R(-1, 6, -2)); positions.push_back(openvdb::Vec3R(1.1, 1.25, 0.06)); // The VDB Point-Partioner is used when bucketing points and requires a // specific interface. For convenience, we use the PointAttributeVector // wrapper around an stl vector wrapper here, however it is also possible to // write one for a custom data structure in order to match the interface // required. openvdb::points::PointAttributeVector positionsWrapper(positions); // This method computes a voxel-size to match the number of // points / voxel requested. Although it won't be exact, it typically offers // a good balance of memory against performance. int pointsPerVoxel = 8; float voxelSize = openvdb::points::computeVoxelSize(positionsWrapper, pointsPerVoxel); // Print the voxel-size to cout std::cout << "VoxelSize=" << voxelSize << std::endl; // Create a transform using this voxel-size. openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); // Create a PointDataGrid containing these four points and using the // transform given. This function has two template parameters, (1) the codec // to use for storing the position, (2) the grid we want to create // (ie a PointDataGrid). // We use no compression here for the positions. openvdb::points::PointDataGrid::Ptr grid = openvdb::points::createPointDataGrid(positions, *transform); // Set the name of the grid grid->setName("Points"); // Create a VDB file object for writing. openvdb::io::File file("mypoints.vdb"); // Add the PointDataGrid pointer to a container. openvdb::GridPtrVec grids; grids.push_back(grid); // Write out the contents of the container. file.write(grids); file.close(); // Create a new VDB file object for reading. openvdb::io::File newFile("mypoints.vdb"); // Open the file. This reads the file header, but not any grids. newFile.open(); // Read the grid by name. openvdb::GridBase::Ptr baseGrid = newFile.readGrid("Points"); newFile.close(); // From the example above, "Points" is known to be a PointDataGrid, // so cast the generic grid pointer to a PointDataGrid pointer. grid = openvdb::gridPtrCast(baseGrid); openvdb::Index64 count = openvdb::points::pointCount(grid->tree()); std::cout << "PointCount=" << count << std::endl; // Iterate over all the leaf nodes in the grid. for (auto leafIter = grid->tree().cbeginLeaf(); leafIter; ++leafIter) { // Verify the leaf origin. std::cout << "Leaf" << leafIter->origin() << std::endl; // Extract the position attribute from the leaf by name (P is position). const openvdb::points::AttributeArray& array = leafIter->constAttributeArray("P"); // Create a read-only AttributeHandle. Position always uses Vec3f. openvdb::points::AttributeHandle positionHandle(array); // Iterate over the point indices in the leaf. for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { // Extract the voxel-space position of the point. openvdb::Vec3f voxelPosition = positionHandle.get(*indexIter); // Extract the world-space position of the voxel. const openvdb::Vec3d xyz = indexIter.getCoord().asVec3d(); // Compute the world-space position of the point. openvdb::Vec3f worldPosition = grid->transform().indexToWorld(voxelPosition + xyz); // Verify the index and world-space position of the point std::cout << "* PointIndex=[" << *indexIter << "] "; std::cout << "WorldPosition=" << worldPosition << std::endl; } } } @endcode Output: @code VoxelSize=3.34716 PointCount=4 Leaf[0, 0, -8] * PointIndex=[0] WorldPosition=[-1, 6, -2] Leaf[0, 0, 0] * PointIndex=[0] WorldPosition=[0, 1, 0] * PointIndex=[1] WorldPosition=[1.1, 1.25, 0.06] * PointIndex=[2] WorldPosition=[1.5, 3.5, 1] @endcode @section sPointsConversion Converting Point Attributes This example is the same as the @ref sPointsHelloWorld example, however it demonstrates converting radius in addition to position. It uses a tailored attribute compression for the radius to demonstrate how to reduce memory. These methods heavily rely on the point conversion methods contained in points/PointConversion.h. @code #include #include #include #include #include int main() { // Initialize grid types and point attributes types. openvdb::initialize(); // Create a vector with four point positions. std::vector positions; positions.push_back(openvdb::Vec3R(0, 1, 0)); positions.push_back(openvdb::Vec3R(1.5, 3.5, 1)); positions.push_back(openvdb::Vec3R(-1, 6, -2)); positions.push_back(openvdb::Vec3R(1.1, 1.25, 0.06)); // Create a vector with four radii. std::vector radius; radius.push_back(0.1); radius.push_back(0.15); radius.push_back(0.2); radius.push_back(0.5); // The VDB Point-Partioner is used when bucketing points and requires a // specific interface. For convenience, we use the PointAttributeVector // wrapper around an stl vector wrapper here, however it is also possible to // write one for a custom data structure in order to match the interface // required. openvdb::points::PointAttributeVector positionsWrapper(positions); // This method computes a voxel-size to match the number of // points / voxel requested. Although it won't be exact, it typically offers // a good balance of memory against performance. int pointsPerVoxel = 8; float voxelSize = openvdb::points::computeVoxelSize(positionsWrapper, pointsPerVoxel); // Create a transform using this voxel-size. openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); // Create a PointIndexGrid. This can be done automatically on creation of // the grid, however as this index grid is required for the position and // radius attributes, we create one we can use for both attribute creation. openvdb::tools::PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid( positionsWrapper, *transform); // Create a PointDataGrid containing these four points and using the point // index grid. This requires the positions wrapper. openvdb::points::PointDataGrid::Ptr grid = openvdb::points::createPointDataGrid(*pointIndexGrid, positionsWrapper, *transform); // Append a "pscale" attribute to the grid to hold the radius. // This attribute storage uses a unit range codec to reduce the memory // storage requirements down from 4-bytes to just 1-byte per value. This is // only possible because accuracy of the radius is not that important to us // and the values are always within unit range (0.0 => 1.0). // Note that this attribute type is not registered by default so needs to be // explicitly registered. using Codec = openvdb::points::FixedPointCodec; openvdb::points::TypedAttributeArray::registerType(); openvdb::NamePair radiusAttribute = openvdb::points::TypedAttributeArray::attributeType(); openvdb::points::appendAttribute(grid->tree(), "pscale", radiusAttribute); // Create a wrapper around the radius vector. openvdb::points::PointAttributeVector radiusWrapper(radius); // Populate the "pscale" attribute on the points openvdb::points::populateAttribute>( grid->tree(), pointIndexGrid->tree(), "pscale", radiusWrapper); // Set the name of the grid grid->setName("Points"); // Iterate over all the leaf nodes in the grid. for (auto leafIter = grid->tree().cbeginLeaf(); leafIter; ++leafIter) { // Verify the leaf origin. std::cout << "Leaf" << leafIter->origin() << std::endl; // Extract the position attribute from the leaf by name (P is position). const openvdb::points::AttributeArray& positionArray = leafIter->constAttributeArray("P"); // Extract the radius attribute from the leaf by name (pscale is radius). const openvdb::points::AttributeArray& radiusArray = leafIter->constAttributeArray("pscale"); // Create read-only handles for position and radius. openvdb::points::AttributeHandle positionHandle(positionArray); openvdb::points::AttributeHandle radiusHandle(radiusArray); // Iterate over the point indices in the leaf. for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { // Extract the voxel-space position of the point. openvdb::Vec3f voxelPosition = positionHandle.get(*indexIter); // Extract the world-space position of the voxel. openvdb::Vec3d xyz = indexIter.getCoord().asVec3d(); // Compute the world-space position of the point. openvdb::Vec3f worldPosition = grid->transform().indexToWorld(voxelPosition + xyz); // Extract the radius of the point. float radius = radiusHandle.get(*indexIter); // Verify the index, world-space position and radius of the point. std::cout << "* PointIndex=[" << *indexIter << "] "; std::cout << "WorldPosition=" << worldPosition << " "; std::cout << "Radius=" << radius << std::endl; } } } @endcode Output: @code Leaf[0, 0, -8] * PointIndex=[0] WorldPosition=[-1, 6, -2] Radius=0.2 Leaf[0, 0, 0] * PointIndex=[0] WorldPosition=[0, 1, 0] Radius=0.0999924 * PointIndex=[1] WorldPosition=[1.1, 1.25, 0.06] Radius=0.499992 * PointIndex=[2] WorldPosition=[1.5, 3.5, 1] Radius=0.149996 @endcode @section sPointsGeneration Random Point Generation This example demonstrates how to create a new point grid and to populate it with random point positions initialized inside a level set sphere. Note that multi-threaded scattering tools are currently in development for introduction to the library to handle this use-case. @code #include #include #include #include int main() { // Initialize grid types and point attributes types. openvdb::initialize(); // Generate a level set grid. openvdb::FloatGrid::Ptr sphereGrid = openvdb::tools::createLevelSetSphere(/*radius=*/20.0, /*center=*/openvdb::Vec3f(1.5, 2, 3), /*voxel size=*/0.5); // Retrieve the number of leaf nodes in the grid. openvdb::Index leafCount = sphereGrid->tree().leafCount(); // Use the topology to create a PointDataTree openvdb::points::PointDataTree::Ptr pointTree( new openvdb::points::PointDataTree(sphereGrid->tree(), 0, openvdb::TopologyCopy())); // Ensure all tiles have been voxelized pointTree->voxelizeActiveTiles(); // Define the position type and codec using fixed-point 16-bit compression. using PositionAttribute = openvdb::points::TypedAttributeArray>; openvdb::NamePair positionType = PositionAttribute::attributeType(); // Create a new Attribute Descriptor with position only openvdb::points::AttributeSet::Descriptor::Ptr descriptor( openvdb::points::AttributeSet::Descriptor::create(positionType)); // Determine the number of points / voxel and points / leaf. openvdb::Index pointsPerVoxel = 8; openvdb::Index voxelsPerLeaf = openvdb::points::PointDataGrid::TreeType::LeafNodeType::SIZE; openvdb::Index pointsPerLeaf = pointsPerVoxel * voxelsPerLeaf; // Iterate over the leaf nodes in the point tree. for (auto leafIter = pointTree->beginLeaf(); leafIter; ++leafIter) { // Initialize the attributes using the descriptor and point count. leafIter->initializeAttributes(descriptor, pointsPerLeaf); // Initialize the voxel offsets openvdb::Index offset(0); for (openvdb::Index index = 0; index < voxelsPerLeaf; ++index) { offset += pointsPerVoxel; leafIter->setOffsetOn(index, offset); } } // Create the points grid. openvdb::points::PointDataGrid::Ptr points = openvdb::points::PointDataGrid::create(pointTree); // Set the name of the grid. points->setName("Points"); // Copy the transform from the sphere grid. points->setTransform(sphereGrid->transform().copy()); // Randomize the point positions. std::mt19937 generator(/*seed=*/0); std::uniform_real_distribution<> distribution(-0.5, 0.5); // Iterate over the leaf nodes in the point tree. for (auto leafIter = points->tree().beginLeaf(); leafIter; ++leafIter) { // Create an AttributeWriteHandle for position. // Note that the handle only requires the value type, not the codec. openvdb::points::AttributeArray& array = leafIter->attributeArray("P"); openvdb::points::AttributeWriteHandle handle(array); // Iterate over the point indices in the leaf. for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { // Compute a new random position (in the range -0.5 => 0.5). openvdb::Vec3f positionVoxelSpace(distribution(generator)); // Set the position of this point. // As point positions are stored relative to the voxel center, it is // not necessary to convert these voxel space values into // world-space during this process. handle.set(*indexIter, positionVoxelSpace); } } // Verify the point count. openvdb::Index count = openvdb::points::pointCount(points->tree()); std::cout << "LeafCount=" << leafCount << std::endl; std::cout << "PointCount=" << count << std::endl; } @endcode Output: @code LeafCount=660 PointCount=2703360 @endcode @section sPointIterationFiltering Point Iteration, Groups and Filtering This section demonstrates how to iterate over points and to use point groups and custom filters during iteration. See the documentation describing iteration and filtering under @ref openvdbPointsIterators "OpenVDB Points Iteration" for more information. @subsection sPointIteration Point Iteration Iterating over point attribute data is most easily done by iterating over the leaf nodes of a PointDataGrid and then the index indices of the attribute within the leaf and extracting the values from a handle bound to the attribute stored within the leaf. This example demonstrates single-threaded, read-only iteration over all float values of an attribute called "name". @code for (auto leafIter = pointTree.beginLeaf(); leafIter; ++leafIter) { openvdb::points::AttributeArray& array = leafIter->constAttributeArray("name"); openvdb::points::AttributeHandle handle(array); // Iterate over active indices in the leaf. for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { // Retrieve value float value = handle.get(*indexIter); } } @endcode This example demonstrates single-threaded, read-write iteration for a similar float attribute by setting all values to be 5.0f. @code for (auto leafIter = pointTree.beginLeaf(); leafIter; ++leafIter) { openvdb::points::AttributeArray& array = leafIter->attributeArray("name"); openvdb::points::AttributeWriteHandle handle(array); // Iterate over active indices in the leaf. for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { // Set value handle.set(*indexIter, 5.0f); } } @endcode Here is the same read-only example using TBB and a custom operator for reading values using multi-threaded access. In this example, we also find the index of the attribute in the descriptor to avoid having to look this up each time (assuming that all leaf nodes share the same descriptor). A similar approach can be used for multi-threaded writing. @code struct ReadValueOp { explicit ReadValueOp(openvdb::Index64 index) : mIndex(index) { } void operator()(const openvdb::tree::LeafManager< openvdb::points::PointDataTree>::LeafRange& range) const { for (auto leafIter = range.begin(); leafIter; ++leafIter) { for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { const openvdb::points::AttributeArray& array = leafIter->constAttributeArray(mIndex); openvdb::points::AttributeHandle handle(array); float value = handle.get(*indexIter); } } } openvdb::Index64 mIndex; }; // Create a leaf iterator for the PointDataTree. auto leafIter = pointTree.cbeginLeaf(); // Check that the tree has leaf nodes. if (!leafIter) { std::cerr << "No Leaf Nodes" << std::endl; } // Retrieve the index from the descriptor. auto descriptor = leafIter->attributeSet().descriptor(); openvdb::Index64 index = descriptor.find("name"); // Check that the attribute has been found. if (index == openvdb::points::AttributeSet::INVALID_POS) { std::cerr << "Invalid Attribute" << std::endl; } // Create a leaf manager for the points tree. openvdb::tree::LeafManager leafManager( pointsTree); // Create a new operator ReadValueOp op(index); // Evaluate in parallel tbb::parallel_for(leafManager.leafRange(), op); @endcode Tip: To run a multi-threaded operator as single-threaded for debugging, set the grainsize argument to a number larger than the number of leaf nodes (it defaults to 1). @code // Evaluate parallel operator in serial tbb::parallel_for(leafManager.leafRange(/*grainsize=*/1000000), op); @endcode @subsection sPointGroups Creating and Assigning Point Groups Point groups in OpenVDB are analagous to Houdini point groups as an efficient way of tagging specific points to belong to a named group. This example uses the data set generated in the @ref sPointsGeneration example. @code // Append a new (empty) group to the point tree. openvdb::points::appendGroup(points->tree(), "positiveY"); // Count all points that belong to this group. openvdb::Index groupCount = openvdb::points::groupPointCount(points->tree(), "positiveY"); // Verify group is empty. std::cout << "PointCount=" << count << std::endl; std::cout << "EmptyGroupPointCount=" << groupCount << std::endl; // Create leaf node iterator for points tree. auto leafIter = points->tree().beginLeaf(); if (!leafIter) { std::cerr << "No Leaf Nodes" << std::endl; } // Extract the group index. openvdb::points::AttributeSet::Descriptor::GroupIndex groupIndex = leafIter->attributeSet().groupIndex("positiveY"); // Iterate over leaf nodes. for (auto leafIter = points->tree().beginLeaf(); leafIter; ++leafIter) { // Create a read-only position handle. const openvdb::points::AttributeArray& positionArray = leafIter->constAttributeArray("P"); openvdb::points::AttributeHandle positionHandle( positionArray); // Create a read-write group handle. openvdb::points::GroupWriteHandle groupHandle = leafIter->groupWriteHandle("positiveY"); // Iterate over the point indices in the leaf. for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { // Extract the voxel-space position of the point. openvdb::Vec3f voxelPosition = positionHandle.get(*indexIter); // Extract the world-space position of the voxel. openvdb::Vec3d xyz = indexIter.getCoord().asVec3d(); // Compute the world-space position of the point. openvdb::Vec3f worldPosition = points->transform().indexToWorld(voxelPosition + xyz); // If the world-space position is greater than zero in Y, add this // point to the group. if (worldPosition.y() > 0.0f) { groupHandle.set(*indexIter, /*on=*/true); } } // Attempt to compact the array for efficiency if all points in a leaf // have the same membership for example. groupHandle.compact(); } // Count all points in this group once again. groupCount = openvdb::points::groupPointCount(points->tree(), "positiveY"); // Verify group membership. std::cout << "GroupPointCount=" << groupCount << std::endl; @endcode Output: @code PointCount=2703360 EmptyGroupPointCount=0 GroupPointCount=1463740 @endcode @subsection sPointFiltering Point Filtering using Groups One highly useful feature of groups is to be able to use them for performing filtered iteration. Here is an example iterating over all the points in the same data set to compute the average position in Y. @code openvdb::Index64 iterationCount(0); double averageY(0.0); // Iterate over leaf nodes. for (auto leafIter = points->tree().beginLeaf(); leafIter; ++leafIter) { // Create a read-only position handle. const openvdb::points::AttributeArray& positionArray = leafIter->constAttributeArray("P"); openvdb::points::AttributeHandle positionHandle( positionArray); // Iterate over the point indices in the leaf. for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { // Extract the world-space position of the point. openvdb::Vec3f voxelPosition = positionHandle.get(*indexIter); openvdb::Vec3d xyz = indexIter.getCoord().asVec3d(); openvdb::Vec3f worldPosition = points->transform().indexToWorld(voxelPosition + xyz); // Increment the sum. averageY += worldPosition.y(); // Track iteration iterationCount++; } } averageY /= double(count); std::cout << "IterationCount=" << iterationCount << std::endl; std::cout << "AveragePositionInY=" << averageY << std::endl; @endcode Output: @code IterationCount=2703360 AveragePositionInY=1.89564 @endcode And the same example filtering using the "positiveY" group during iteration. @code iterationCount = 0; double averageYPositive(0.0); // Create a "positiveY" group filter. openvdb::points::GroupFilter filter("positiveY"); // Iterate over leaf nodes. for (auto leafIter = points->tree().beginLeaf(); leafIter; ++leafIter) { // Create a read-only position handle. const openvdb::points::AttributeArray& positionArray = leafIter->constAttributeArray("P"); openvdb::points::AttributeHandle positionHandle( positionArray); // Iterate over the point indices in the leaf that match the filter. for (auto indexIter = leafIter->beginIndexOn(filter); indexIter; ++indexIter) { // Extract the world-space position of the point. openvdb::Vec3f voxelPosition = positionHandle.get(*indexIter); openvdb::Vec3d xyz = indexIter.getCoord().asVec3d(); openvdb::Vec3f worldPosition = points->transform().indexToWorld(voxelPosition + xyz); // Increment the sum. averageYPositive += worldPosition.y(); // Track iteration iterationCount++; } } averageYPositive /= double(groupCount); std::cout << "IterationCount=" << iterationCount << std::endl; std::cout << "AveragePositivePositionInY=" << averageYPositive << std::endl; @endcode Output: @code IterationCount=1463740 AveragePositivePositionInY=11.373 @endcode This approach still performs this operation in two passes, (1) creating and assigning the groups and (2) iterating using the group. @subsection sPointCustomFiltering Point Filtering using Custom Filters For common operations, it is typically faster to sacrifice the flexibility of point groups for a custom filter. This is using the same data set from the previous example. @code // Evalutate true for points that are positive in Y only struct PositiveYFilter { using Handle = openvdb::points::AttributeHandle; explicit PositiveYFilter(const openvdb::math::Transform& transform) : mTransform(transform) { } PositiveYFilter(const PositiveYFilter& filter) : mTransform(filter.mTransform) { if (filter.mPositionHandle) { mPositionHandle.reset(new Handle(*filter.mPositionHandle)); } } inline bool initialized() const { return bool(mPositionHandle); } template void reset(const LeafT& leaf) { mPositionHandle.reset(new Handle(leaf.constAttributeArray("P"))); } template bool valid(const IterT& indexIter) const { openvdb::Vec3f voxelPosition = mPositionHandle->get(*indexIter); openvdb::Vec3d xyz = indexIter.getCoord().asVec3d(); openvdb::Vec3f worldPosition = mTransform.indexToWorld(voxelPosition + xyz); return worldPosition.y() > 0.0f; } const openvdb::math::Transform& mTransform; Handle::UniquePtr mPositionHandle; }; // Drop the "positiveY" group. openvdb::points::dropGroup(points->tree(), "positiveY"); // Create a new positive-Y filter. PositiveYFilter positiveYFilter(points->transform()); iterationCount = 0.0; // Iterate over the points using the custom filter for (auto leafIter = points->tree().beginLeaf(); leafIter; ++leafIter) { for (auto indexIter = leafIter->beginIndexOn(positiveYFilter); indexIter; ++indexIter) { // Track iteration iterationCount++; } } std::cout << "IterationCount=" << iterationCount << std::endl; @endcode Output: @code IterationCount=1463740 @endcode @section sPointStride Strided Point Attributes Point attributes can have a stride greater than one in order to store multiple values with each attribute with each point. @subsection sConstantStride Constant Stride Attributes A stride can be constant so that each attribute has the same number of values. This example demonstrates using a hard-coded 10 samples per point in an attribute called "samples". @code // Store 10 values per point in an attribute called samples. openvdb::Index stride(10); openvdb::points::appendAttribute(points->tree(), "samples", openvdb::points::TypedAttributeArray::attributeType(), stride); // Iterate over leaf nodes. for (auto leafIter = points->tree().beginLeaf(); leafIter; ++leafIter) { // Create a read-write samples handle. openvdb::points::AttributeArray& array( leafIter->attributeArray("samples")); openvdb::points::AttributeWriteHandle handle(array); // Iterate over the point indices in the leaf. for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { // Use ascending sample values for each element in the strided array for (int i = 0; i < 10; i++) { handle.set(*indexIter, /*strideIndex=*/i, float(i)); } } } @endcode */ openvdb/doc/faq.txt0000644000000000000000000003174313200122206013262 0ustar rootroot/** @page faq Frequently Asked Questions @section sFAQContents 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/points.txt0000644000000000000000000002627013200122206014026 0ustar rootroot/** @page points OpenVDB Points @section secPtContents Contents - @ref secPtOverview - @ref secPtCompression - @ref secPtLocality - @ref secPtAttributes - @ref secPtTypedAttributeArray - @ref secPtAttributeHandle - @ref secPtAttributePerformance - @ref secPtAttributeSet - @ref secPtPointTree - @ref secPtPointIndexTree - @ref secPtPointDataTree - @ref secPtSparsity - @ref secPtBackground - @ref secPtActiveValues - @ref secPtIndexIterators - @ref secPtIndexFilters - @ref secPtSpaceAndTrans @section secPtOverview Introduction @anchor openvdbPointsOverview The @b OpenVDB library can store point data within an OpenVDB hierarchy in two different ways. A @b PointIndexGrid stores index offsets that reference data stored in an external linear point array, a @b PointDataGrid stores the points with attributes directly in the VDB Grid. This document focuses mainly on the localised style of storage used by @b PointDataGrids. Using this storage mechanism, points are spatially-organised into VDB voxels to provide faster access and a greater opportunity for data compression compared with linear point arrays. See the @ref openvdbPointsHelloWorld "Cookbook" for code examples to get started using @b OpenVDB @b Points. @section secPtCompression Compression A key motivation behind the library is to increase data compression and thus reduce the memory and disk space requirements of the point set. There are three types of compression used in this library to store point attribute data - (1) @b value @b compression where the codec is selected specifically based on the intent of the data, (2) @b uniform @b compression where arrays of identical values can be collapsed down into a single value, (3) @b stream @b compression where the Blosc stream compressor can be used to pack and unpack streams of data using a fast, lossless compression codec. Uniform and stream compression are offered in other applications, notably Houdini, but with the case of stream compression, used only for disk storage. Value compression is provided based on the principal that a good general-purpose compression scheme will usually not be superior to one that is specifically tuned to the range and intention of how the data will be used. The most commonly used style of value compression in OpenVDB Points is quantization for point position data. Floating-point world-space positions will typically use a much higher accuracy than desired around the origin and a much lower accuracy than desired far from the origin, which is as a result of the dynamic range used in floating-point that makes it usable at different orders of magnitude. More tailored position compression is introduced by splitting positions into an integer offset provided by the VDB voxels and grid transform and a reduced precision fixed-point offset from the center of the voxel. In addition, other attributes such as velocity can benefit from value compression. For example, a unit vector scheme can compress a 3 x float vector (12 bytes) into just 2 bytes. @section secPtLocality Locality Data compression isn't the only area to benefit from a spatially organised data set. Due to the effects of L1 and L2 caching in modern CPUs, improved cache locality through storing point data close in memory to their neighbors can bring about a big improvement in performance. A number of point rasterization gather-style tests that compared spatially organised data with randomly organised linear data when dereferenced using a spatial acceleration structure consistently resulted in a performance improvement of between 2x and 3x. @section secPtAttributes Attributes Attribute storage is provided as an independent toolset within the library to allow for use outside of OpenVDB grids. @subsection secPtTypedAttributeArray TypedAttributeArray The TypedAttributeArray stores array data with a specified value type and compression codec, which form the template signature. @code template class TypedAttributeArray: public AttributeArray @endcode The base AttributeArray class from which the TypedAttributeArray derives is not templated and can be used for all non-typed operations such as serialization. Here is an example of floating-point scalar attribute storage using a truncation codec to reduce the footprint from 32-bit to 16-bit: @code openvdb::points::TypedAttributeArray @endcode @subsection secPtAttributeHandle AttributeHandle AttributeHandle and AttributeWriteHandle classes provide access to the array data without requiring knowledge of the codec. This is important as it can allow users to add their own attribute compression schemes without requiring any modification to existing code. AttributeHandles provide benefits to memory usage in allowing data to be packed and unpacked efficiently when using stream compression. When compressed with a stream compression scheme, the AttributeHandle unpacks attribute data into a local (uncompressed) buffer on access and discards this temporary data once the AttributeHandle is destroyed. This has the benefit of retaining the stream compression during access which lowers the peak memory substantially. This is how to create a float AttributeHandle bound to a specific attribute in a leaf: @code auto handle = AttributeHandle::create(leaf->constAttributeArray("attribute_name")); @endcode Note that ensuring const access to the attribute array will prevent redundant copying when using a read-only handle. @subsection secPtAttributePerformance TypedAttributeArray vs AttributeHandle One key benefit of AttributeHandles is that they ensure the data being accessed has been uncompressed and is in-core on creation of the handle to avoid the need to perform these checks when the data is being accessed and modified for improved performance. An AttributeHandle may also be provided a specific codec if known to eliminate the indirection cost. @subsection secPtAttributeSet AttributeSet and Descriptor The AttributeSet stores a collection of attribute arrays along with a Descriptor that tracks the type of each of the attribute arrays. In typical use cases, the Descriptor is shared amongst leaf nodes with many of the algorithms making this assumption for performance reasons. However, it is still possible to configure the data so that leaf nodes in the same grid can use different Descriptors with different numbers and types of attributes. A typical use-case for this might be to reduce precision of an attribute further from a camera where accuracy is deemed less important and can be traded for a reduced memory or disk footprint. @section secPtPointTree The Point Tree @subsection secPtPointIndexTree Point Index Tree As mentioned in the introduction, the PointIndexTree is a structure in OpenVDB that stores an array of offset indices to a linear point array in the leaf nodes. In constrast, the PointDataTree stores the actual data localised in the leaf nodes. The PointIndexTree has this tree configuration: @code typedef tree::Tree4::Type PointIndexTree; @endcode @subsection secPtPointDataTree Point Data Tree The PointDataTree has this tree configuration: @code typedef tree::Tree4::Type PointDataTree; @endcode Note that with both the PointIndexTree and the PointDataTree, the data type is actually a 32-bit unsigned integer, but it is provided in this form to distinguish it from other LeafNodes that store the same data type. None of the other components within the Tree or Grid hierarchy change. It is for this reason that many of the existing features of OpenVDB such as serialization work without requiring additional functionality in OpenVDB. @section secPtSparsity Voxel Values For the PointDataTree, the voxel values represent the end position in the linear attribute arrays attached to each LeafNode. Using the value of the previous voxel (zero for the first voxel), the offset for the start position can be deduced. @subsection secPtBackground Background and Tile Values There are three distinct ways of storing data in an OpenVDB tree: voxel values, tile values, and a background value. Unfortunately the background value and tile values make little sense for point data. While technically it would be valid to use a non-zero background value and tile value, this would simply mean that only the first voxel in the LeafNode contains points which is neither an efficient storage mechanism nor particularly common. For this reason, the LeafNode constructor may take a background value but it is internally overriden to be zero on construction. @subsection secPtActiveValues Active Values Any voxel or tile can be classified as either @b active or @b inactive. The interpretation of this state is application-specific, however there are some conventions, such as using the active state to denote the narrow band in a levelset. For points, the most intuitive use of the active state is to mark a voxel as active if it contains points and inactive otherwise. This allows iteration over the points in a LeafNode to be accelerated to only iterate over voxels that contain points. @subsection secPtIndexIterators Index Iterators @anchor openvdbPointsIterators An index iterator contains a value iterator and an index filter, however the most common usage is provided by the convenience methods on the leaf node: @code auto iterAll = leaf.beginIndexAll(); auto iterOn = leaf.beginIndexOn(); auto iterOff = leaf.beginIndexOff(); for (; iterOn; ++iterOn) { Index32 index = *iterOn; } @endcode These are analagous to the value iterators in the LeafNode and are creating an index iterator that wraps the value iterator together with a null filter. It is also possible to iterate over indices within a specific voxel by using the beginIndexVoxel convenience method on the leaf: @code auto iterVoxel = leaf.beginIndexVoxel(ijk); @endcode For performance and simplicity, it is recommended to use the all/on/off index iterators where possible, as these hide the explicit voxel iteration from the user meaning it is only necessary to iterate over the indices within the leaf. It is also possible to retrieve the voxel coordinate from the index iterator directly. @subsection secPtIndexFilters Index Filters Index filters provide an easy way of only iterating over indices that match a set criteria. One such index filter provided by the library is to iterate over the points within a group: @code openvdb::points::GroupFilter filter("test_point_group"); auto iter = leaf.beginIndexOn(filter); @endcode As the index iterator is templated on the filter, it's possible to construct filters that do relatively complicated operations provided they meet the basic interface requirements of an IndexFilter. @section secPtSpaceAndTrans Voxel Space, Index Space, World Space Points are stored in @b voxel @b space, meaning all point positions lie between (-0.5, -0.5, -0.5) and (0.5, 0.5, 0.5) with the center of the voxel being (0.0, 0.0, 0.0). The position of the point can be extracted in @b index @b space by adding the voxel space position to the ijk value of the voxel. The position of the point can be extracted in @b world @b space by using the grid transform to do an indexToWorld conversion. See the @ref openvdbPointsHelloWorld "Cookbook" for code examples to get started using @b OpenVDB @b Points. */ openvdb/doc/changes.txt0000644000000000000000000036204413200122206014124 0ustar rootroot/** @page changes Release Notes @htmlonly @endhtmlonly @par Version 5.0.0 - November 6, 2017 @par
Some changes in this release (see @ref v5_0_0_ABI_changes "ABI changes" below) alter the grid ABI so that it is incompatible with earlier versions of the OpenVDB library, such as the ones built into Houdini up to and including Houdini 16. To preserve ABI compatibility, when compiling OpenVDB or any dependent code define the macro OPENVDB_ABI_VERSION_NUMBER=N, where, for example, N is 3 for Houdini 15, 15.5 and 16 and 4 for the upcoming release.
@par New features: - Added a @vdblink::getLibraryAbiVersionString() getLibraryAbiVersionString@endlink function, which returns a string such as "5.0.0abi3". - Added a @vdblink::WeakPtr weak pointer@endlink type alias for ABI compatibility. - Metadata fields of unregistered types are no longer discarded after being read from a .vdb file, and although their values are not printable, they can be written back to disk. - Added a @c DESTDIR_LIB_DIR Makefile variable for Linux multiarch support. [Contributed by Mathieu Malaterre] - Added tools to create @link tools/PotentialFlow.h potential flow@endlink fields, as described in the 2017 SIGGRAPH OpenVDB course. [Contributed by Double Negative] - Added @link points/PointMask.h tools@endlink to create mask grids from point data grids and to compute @vdblink::points::pointCountGrid() point counts@endlink. [Contributed by Dan Bailey] - Added @link points/PointScatter.h tools@endlink to scatter @ref secPtOverview "OpenVDB points" randomly throughout a volume. [Contributed by Nick Avramoussis] @par Improvements: - Significantly improved the performance of point data grid @vdblink::points::MultiGroupFilter group filters@endlink. [Contributed by Double Negative] @par Bug fixes: - Fixed bugs in the @vdblink::tools::ClosestSurfacePoint ClosestSurfacePoint@endlink tool’s distance calculations that caused searches to produce incorrect results. - Fixed a locking issue that affected multithreaded access to @vdblink::points::PointDataLeafNode PointDataLeafNode@endlink‍s when delayed loading was in effect. [Contributed by Dan Bailey] @anchor v5_0_0_ABI_changes @par ABI changes: - Made @vdblink::tree::InternalNode InternalNode@endlink’s destructor non-virtual. - The @ref v4_0_2_delayed_load_fix "fix" for a delayed-loading race condition in the @vdblink::tree::LeafBuffer LeafBuffer@endlink class that was only partially rolled out in the previous release is now enabled on all platforms. - Replaced a bit flag with an atomic integer in @vdblink::points::AttributeArray points::AttributeArray@endlink to address a threading issue during delayed loading. [Contributed by Dan Bailey] - Deprecated the @c OPENVDB_2_ABI_COMPATIBLE and @c OPENVDB_3_ABI_COMPATIBLE macros in favor of a new @c OPENVDB_ABI_VERSION_NUMBER macro. The new macro defaults to the library major version number but can be set at compile time to an earlier version number to disable ABI changes since that version. (Older ABIs will not be supported indefinitely, however.) For example, compile OpenVDB and any dependent code with -DOPENVDB_ABI_VERSION_NUMBER=4 to use the 4.x ABI. @par API changes: - Replaced @b tools::ClosestSurfacePoint::initialize with @vdblink::tools::ClosestSurfacePoint::create() tools::ClosestSurfacePoint::create@endlink, which returns a newly-allocated and properly initialized object. - Removed methods that were deprecated in version 4.0.0 or earlier, including @b io::File::readGridPartial, @b points::initialize, @b points::uninitialize and @b util::PagedArray::pop_back. - Deprecated @vdblink::IllegalValueException IllegalValueException@endlink in favor of @vdblink::ValueError ValueError@endlink. - Changed the naming scheme for the @link OPENVDB_VERSION_NAME library namespace@endlink from openvdb::vX_Y_Z to openvdb::vX_YabiN, where @e X, @e Y, @e Z and @e N are the major, minor, patch and ABI version numbers, respectively. The abiN suffix is added only when the library is built using an older ABI version. @par Python: - Reimplemented NumPy support for Boost 1.65 compatibility. @par Houdini: - Fixed bugs that caused the Ray SOP’s closest surface point searches to produce incorrect results. - Changed the @b VdbPrimCIterator::FilterFunc type from @b boost::function to @b std::function. - Changed the @b houdini_utils::OpPolicyPtr type from @b boost:shared_ptr to @b std::shared_ptr. - Debug-level log messages generated by OpenVDB are no longer forwarded to Houdini’s error manager. - Fixed a bug in the Read SOP that made it impossible to select among grids of the same name in a file. - Added @b houdini_utils::ParmFactory::setAttrChoiceList, a convenience method for the creation of menus of attributes. - Added a Potential Flow SOP. [Contributed by Double Negative] - Added point data grid support to the Scatter SOP. [Contributed by Nick Avramoussis] - Added mask and point count output options to the Points Convert SOP. [Contributed by Dan Bailey] @htmlonly @endhtmlonly @par Version 4.0.2 - July 28, 2017 @par New features: - Added @vdblink::tools::compActiveLeafVoxels compActiveLeafVoxels@endlink, which composites active voxel values from a source tree into a destination tree. It is threaded and faster than existing tools that merge trees, however it operates only on leaf nodes. - Added a vdb_test -f option that reads a list of tests to be run from a text file. - Added functions for @link points/PointDelete.h deleting points@endlink from point data grids based on group membership. [Contributed by Double Negative] - Enabled display of point data grids in vdb_view. [Contributed by Nick Avramoussis] - Added a view mode indicator to vdb_view. - Added @vdblink::math::Mat::isFinite() isFinite@endlink, @vdblink::math::Mat::isNan() isNan@endlink, and @vdblink::math::Mat::isZero() isZero@endlink methods to @vdblink::math::Mat math::Mat@endlink and added @vdblink::math::Tuple::isZero() isZero@endlink to @vdblink::math::Tuple math::Tuple@endlink. - Added @vdblink::tools::interiorMask() tools::interiorMask@endlink, which constructs a boolean mask grid from the active voxels of an input grid or, if the input grid is a level set, from the interior voxels of the level set. - Added @vdblink::math::CoordBBox::begin() begin@endlink and @vdblink::math::CoordBBox::end() end@endlink iterator methods (and related methods) to @vdblink::math::CoordBBox CoordBBox@endlink, so that it can be used in range-based for loops. - The @link tools/Clip.h clip@endlink tool now accepts either a box, a mask grid or a camera frustum as the clipping region. The latter is new in this version. @par Improvements: - Moved the @vdblink::math::Tuple::isFinite() isFinite@endlink, @vdblink::math::Tuple::isInfinite() isInfinite@endlink, and @vdblink::math::Tuple::isNan() isNan@endlink methods from @vdblink::math::Vec3 math::Vec3@endlink et al. to @vdblink::math::Tuple math::Tuple@endlink. @par Bug fixes: - @anchor v4_0_2_delayed_load_fix Fixed a delayed-loading race condition that could result in crashes. [Reported by Dan Bailey]
@b Note: To preserve ABI compatibility, this fix is currently enabled only on platforms for which the alignment of a tbb::atomic<uint32_t> is the same as for a @c uint32_t. On other platforms, warnings will be logged during OpenVDB initialization, and it is recommended to disable delayed loading in that case (for example, by defining the environment variable @c OPENVDB_DISABLE_DELAYED_LOAD).
- Fixed a delayed-loading memory leak in the @vdblink::points::PointDataLeafNode PointDataLeafNode@endlink. [Contributed by Double Negative] - Changed the random number seeding mechanism for .vdb file UUIDs to avoid duplicate IDs. [Reported by Jason Lefley] - Fixed an off-by-one bug in the @vdblink::tools::GridResampler resampler@endlink that produced grid patterns of missing interior voxels for scale factors greater than one. @par Houdini: - As of Houdini 16.0.549, @c houdini_utils::OpFactory can generate help cards for operators automatically. New @c OpFactory::setDocumentation and @c ParmFactory::setDocumentation methods allow one to add custom help text in wiki markup format. - Added help cards for all SOPs. Houdini 16.0.578 or later is required. [Contributed by Dan Bailey and SideFX] - The Extended Operator Info window in Houdini 16 now renders correctly for OpenVDB SOPs, instead of displaying a Python stack trace. [Contributed by Dan Bailey] - Added a Points Delete SOP for deleting points from point data grids based on group membership. [Contributed by Double Negative] - Added a Mantra VRAY procedural and a delayed load SHOP for rendering point data grids. Houdini 16 is required. [Contributed by Double Negative] - Replaced the Combine SOP’s “A/B Pairs” and “Flatten” toggles with a menu of collation options that include flattening only A grids and flattening groups of A grids independently. - Added a slider to the Remove Divergence SOP to set the error tolerance for the pressure solver. - Added value type conversion options (for VDB output) to the Convert SOP. - Added a Densify SOP that replaces active tiles with leaf voxels. - Fixed a bug in the Rasterize Points SOP that capped density values to one instead of to the particles’ densities. - The Convert and To Polygons SOPs now accept grids of any type as surface masks, not just level set or SDF grids. - Added an option to the Clip SOP to clip to a camera frustum. @htmlonly @endhtmlonly @par Version 4.0.1 - March 8, 2017 @par New features: - Added functions to util/logging.h to simplify configuration of the logging system (via command-line arguments, in particular). - Added @vdblink::tree::LeafManager::activeLeafVoxelCount() LeafManager::activeLeafVoxelCount@endlink, a faster, threaded alternative to @vdblink::tree::Tree::activeLeafVoxelCount() Tree::activeLeafVoxelCount@endlink. - Added a -shuffle option that causes vdb_test to run unit tests in random order, which can help to identify unintended dependencies between tests. - Added @c vdb_lod, a command-line tool to generate volume mipmaps for level-of-detail effects. - Added methods to compute the median value of @vdblink::tree::LeafNode::medianOn() active@endlink, @vdblink::tree::LeafNode::medianOff() inactive@endlink or @vdblink::tree::LeafNode::medianAll() all@endlink voxels in leaf nodes. @par Improvements: - Added a @vdblink::Metadata::str() Metadata::str@endlink specialization for @vdblink::StringMetadata StringMetadata@endlink that eliminates the overhead of writing to a string stream. - Made various minor improvements to @vdblink::util::PagedArray util::PagedArray@endlink. - Added an @c install_lib build target to the Makefile. [Contributed by Double Negative] - Added @subpage points "documentation" and Cookbook @ref openvdbPointsHelloWorld "examples" for OpenVDB Points. [Contributed by Double Negative] - Registration of OpenVDB Points grid and attribute types is now handled in @vdblink::initialize() openvdb::initialize@endlink, and @vdblink::points::initialize() points::initialize@endlink and @vdblink::points::uninitialize() points::uninitialize@endlink are therefore deprecated. - Extended multi-pass I/O to handle a variable number of passes per leaf node. [Contributed by Double Negative] - Addressed a name conflict between macros in util/NodeMasks.h and symbols in the Eigen library. [Reported by Trevor Thomson] @par Bug fixes: - The @vdblink::tools::fillWithSpheres() fillWithSpheres@endlink and @vdblink::tools::ClosestSurfacePoint ClosestSurfacePoint@endlink tools now correctly handle isosurfaces outside the input volume’s narrow band. - The @vdblink::tools::MultiResGrid MultiResGrid@endlink tool now supports all standard grid types, including @vdblink::BoolGrid BoolGrid@endlink and @vdblink::MaskGrid MaskGrid@endlink. - @vdblink::tree::LeafNode::fill() LeafNode::fill@endlink now correctly clips the fill region to the node’s bounding box. - @vdblink::Grid::denseFill() Grid::denseFill@endlink no longer densifies all existing active tiles, and it now correctly handles both active and inactive fill values. - Fixed a bug that caused @vdblink::tools::copyToDense() tools::copyToDense@endlink to only partially populate the output array when delayed loading was in effect. [Reported by Stuart Levy] - Fixed an issue with duplicate registration of @link points/PointDataGrid.h PointDataGrid@endlink attribute types. [Reported by SideFX] - Fixed an uninitialized memory bug in the @vdblink::tools::meshToVolume() mesh to volume@endlink converter. [Reported by SideFX] - Fixed a thread race condition in @vdblink::math::QuantizedUnitVec QuantizedUnitVec@endlink that could cause it to produce incorrect results. [Contributed by Jeff Lait] - Fixed a dangling pointer bug in the @vdblink::tools::ParticleAtlas particle atlas@endlink tool. [Contributed by SideFX] - Grid operators (@vdblink::tools::divergence() divergence@endlink, @vdblink::tools::gradient() gradient@endlink, etc.) now produce correct results even for grids with active tile values. - Fixed a bug when writing an out-of-core @vdblink::points::AttributeArray points::AttributeArray@endlink that could cause corruption of the metadata associated with the array. [Contributed by Double Negative] @par Python: - Added functions @c getLoggingLevel, @c setLoggingLevel, and @c setProgramName, to allow configuration of the logging system. @par Houdini: - Fixed a crash in the Ray SOP when the user selected an isosurface outside the target volume’s narrow band. - The LOD SOP now supports all standard grid types, including boolean grids. - Added @c houdini_utils::ParmFactory::setGroupChoiceList, a convenience method for the creation of menus of primitive groups. - Made various small changes for Houdini 16 compatibility. [Contributed by SideFX] - The Create SOP now supports matching the new grids’ transform, voxel size, and topology to a reference grid. If the topology is being matched, it can optionally be resampled to a different voxel size. - Added some support for point data grids to the Clip, Topology To Level Set and Visualize SOPs. [Contributed by Double Negative] - Compression is no longer enabled by default in the Points Convert SOP for normals and colors, because they are not guaranteed to have a [0, 1] range. [Contributed by Double Negative] - Added a 16-bit truncation compression option to the Points Convert SOP. [Contributed by Double Negative] - Fixed a build issue with the GR_PrimVDBPoints render hook plugin that could cause @c hython to report a DSO error. [Reported by Double Negative] - Added an @c install_lib build target to the Makefile. - Rewrote the Remove Divergence SOP to actually remove divergence from vector fields on collocated grids, and added support for stationary and moving obstacles and an option to output a pressure field. - The Analysis SOP now produces correct results for grids with active tile values. - Added a sparse/dense toggle to the Fill SOP. - Added @c openvdb_houdini::startLogForwarding, @c openvdb_houdini::stopLogForwarding and @c openvdb_houdini::isLogForwarding, which control the forwarding of log messages to Houdini’s error manager. Forwarding of library warnings and error messages is now enabled by default for SOPs when OpenVDB is built with log4cplus. @htmlonly @endhtmlonly @par Version 4.0.0 - November 15, 2016 @par Highlights: - Incorporated Double Negative’s OpenVDB Points library. - Introduced some C++11 constructs. A C++11-compatible compiler is now required. - Blosc-compressed .vdb files are now as much as 20% smaller. - Vector-valued grids are now constructed and destroyed much faster.
@b Note: This change and other changes in this release (see @ref v4_0_0_ABI_changes "ABI changes" below) alter the grid ABI so that it is incompatible with earlier versions of the OpenVDB library, such as the ones built into Houdini 15, 15.5 and 16. To disable these changes and preserve ABI compatibility, define the macro @c OPENVDB_3_ABI_COMPATIBLE when compiling OpenVDB or any code that depends on OpenVDB.
@par New features: - Added an option to the @link PointScatter.h point scattering@endlink tools to specify how far each point may be displaced from the center of its host voxel or tile. - Added a toggle to the @vdblink::tools::clip() clip@endlink tool to invert the clipping mask. - Custom leaf node implementations may now optimize their file layout by inheriting from @vdblink::io::MultiPass io::MultiPass@endlink. Voxel data for grids with such leaf nodes will be written and read in multiple passes, allowing blocks of related data to be stored contiguously. [Contributed by Double Negative] - Added @vdblink::tree::Tree::unallocatedLeafCount() Tree::unallocatedLeafCount@endlink, which returns the number of leaf nodes with unallocated data buffers (typically due to delayed loading). @par Improvements: - Vector-valued grids are now constructed and destroyed much faster. - Changed @vdblink::math::Coord Coord@endlink’s data representation to facilitate C++11 uniform initialization. - Delayed loading from @vdblink::io::File io::Files@endlink is now faster due to the use of seeks instead of reads. [Contributed by Double Negative] - Made many small changes to address type conversion and other warnings reported by newer compilers, including Clang 3.8. - Improved Blosc compression ratios and write times by increasing the block size. [Contributed by Dan Bailey] @par Bug fixes: - Fixed a bug that caused topology operations (@vdblink::Grid::topologyUnion() union@endlink, @vdblink::Grid::topologyIntersection() intersection@endlink and @vdblink::Grid::topologyDifference() difference@endlink) on @vdblink::MaskGrid MaskGrids@endlink to sometimes produce incorrect results. (MaskGrids are used internally in a number of tools.) - Changed @vdblink::GridBase::copyGrid() GridBase::copyGrid@endlink and @vdblink::Grid::copy() Grid::copy@endlink to close const-correctness holes. - @vdblink::tools::fillWithSpheres() tools::fillWithSpheres@endlink now returns an empty list of spheres instead of crashing when the user selects an isosurface that lies outside the bounding volume’s narrow band. - Fixed a null pointer dereference when copying grids that were loaded with @c io::File::readGridPartial. [Reported by Nick Avramoussis] @anchor v4_0_0_ABI_changes @par ABI changes: - Added a @vdblink::tree::NodeUnion NodeUnion@endlink template specialization for non-POD value types that significantly expedites construction and destruction of vector-valued grids. - Changed @vdblink::math::Coord Coord@endlink’s data representation to facilitate C++11 uniform initialization. - Replaced occurrences of boost::shared_ptr with std::shared_ptr. - Changed @vdblink::GridBase::copyGrid() GridBase::copyGrid@endlink and @vdblink::Grid::copy() Grid::copy@endlink to close const-correctness holes. - Added virtual function @vdblink::tree::Tree::unallocatedLeafCount() Tree::unallocatedLeafCount@endlink. @par API changes: - Introduced some C++11 constructs. A C++11-compatible compiler is now required. - Added a parameter to the @link PointScatter.h point scattering@endlink tools to control the displacement of each point from the center of its host voxel or tile. The default behavior, as before, is to allow each point to be placed (randomly) anywhere within its voxel or tile. - Renamed @c LeafManager::getPreFixSum to @vdblink::tree::LeafManager::getPrefixSum() LeafManager::getPrefixSum@endlink. - Made @c LeafNode::Buffer a top-level class and renamed it to @vdblink::tree::LeafBuffer LeafBuffer@endlink. [Contributed by Double Negative] - Deprecated @c io::File::readGridPartial in favor of delayed loading. - @c tools::ClosestSurfacePoint::initialize now returns a boolean indicating whether initialization was successful. - Dropped the @c CopyPolicy enum and added @vdblink::GridBase::copyGridWithNewTree() GridBase::copyGridWithNewTree@endlink and @vdblink::Grid::copyWithNewTree() Grid::copyWithNewTree@endlink in order to close const-correctness holes that allowed newly-constructed, non-const grids to share their trees with existing const grids. (Where that behavior is still required, use a @vdblink::ConstPtrCast ConstPtrCast@endlink.) @par Python: - Fixed a build issue with Python 3 and NumPy. [Contributed by Jonathan Scruggs] @par Houdini: - Certain changes in this release (see @ref v4_0_0_ABI_changes "ABI changes" above) alter the grid ABI so that it is incompatible with earlier versions of the OpenVDB library, such as the ones built into Houdini 15, 15.5 and 16. To disable these changes and preserve ABI compatibility, define the macro @c OPENVDB_3_ABI_COMPATIBLE when compiling OpenVDB or any code that depends on OpenVDB. - Introduced some C++11 constructs that are incompatible with versions of Houdini older than 15.0. - Fixed a bug in the Rasterize Points SOP that caused vector-valued attributes to be transferred as scalars. [Contributed by Double Negative] - Added a toggle to the Clip SOP to invert the clipping mask. - Added a slider to the Scatter SOP to specify how far each point may be displaced from the center of its host voxel or tile. @htmlonly @endhtmlonly @par Version 3.2.0 - August 10, 2016 @par Highlights: - New features: tool to produce and store a sequences of progressively lower resolution grids (mipmaps), an acceleration structure for fast range and nearest-neighbor searches on particles, arbitrary volume and level set specific segmentation tools, a new binary mask grid type and an efficient point to level set conversion scheme. - Optimizations: Faster volume to mesh conversion and threaded grid destruction, morphological dilation, csg operations and fracture tool. - New Houdini nodes: Segment, LOD and Topology To Level Set. @par New features: - Added @link MultiResGrid.h tools::MultiResGrid@endlink a tool to produce and store a sequences of progressively lower resolution grids (mipmaps). - Added @link ParticleAtlas.h tools::ParticleAtlas@endlink an acceleration structure for fast range and nearest-neighbor searches on particles, points with radius. - Added @vdblink::tools::segmentActiveVoxels() segmentActiveVoxels@endlink, which operates on grids of arbitrary type and separates connected components of a grid’s active voxels into distinct grids or trees. - Added @vdblink::tools::segmentSDF() segmentSDF@endlink, which separates disjoint signed-distance-field surfaces into distinct grids or trees. - Added @vdblink::tools::extractActiveVoxelSegmentMasks() extractActiveVoxelSegmentMasks@endlink, which constructs a mask for each connected component of a grid’s active voxels. - Added threaded level-set CSG tools @vdblink::tools::csgUnionCopy() csgUnionCopy@endlink, @vdblink::tools::csgIntersectionCopy() csgIntersectionCopy@endlink and @vdblink::tools::csgDifferenceCopy() csgDifferenceCopy@endlink, which, unlike the existing CSG tools, produce new grids rather than modifying their input grids. These new tools are faster and use less memory than the existing tools (if only because the input grids never need to be deep-copied). - Added a threaded @vdblink::tools::dilateActiveValues dilateActiveValues()@endlink tool with tile value support. - Added a @vdblink::tools::PointsToMask PointsToMask@endlink tool, which activates voxels that intersect points from a given list. - Added a new @link openvdb.h MaskGrid@endlink type that uses a single bit-field to represent both voxel values and states for the @link tree/LeafNodeMask.h leafnode@endlink to reduce memory usage. - Added a @vdblink::tools::topologyToLevelSet() topologyToLevelSet@endlink tool that generates a level set from the implicit boundary between active and inactive voxels in an input grid of arbitrary type. - Added @link LevelSetPlatonic.h tools::LevelSetPlatonic@endlink a new tool that produces narrow-band level sets of the five Platonic solids. - Added @vdblink::tools::extractIsosurfaceMask() extractIsosurfaceMask@endlink which masks voxels that intersect the implicit surface defined by the given isovalue. - Added a @vdblink::tree::LeafManager::getPrefixSum() getPrefixSum@endlink method to the @vdblink::tree::LeafManager LeafManager@endlink, for user-managed external buffers. - Added a @vdblink::tools::Dense::print() print@endlink method to the @vdblink::tools::Dense Dense@endlink grid class. - Added the @vdblink::math::CoordBBox::Iterator CoordBBox::Iterator@endlink class to conveniently iterate over coordinates covered a CoordBBox. - Added bit-wise operations to the @vdblink::math::CoordBBox CoordBBox@endlink class. - New component wise constructor for the @vdblink::math::CoordBBox CoordBBox@endlink class as well as the method @vdblink::math::CoordBBox::getCornerPoints CoordBBox::getCornerPoints@endlink. - Added a new @vdblink::tree::LeafManager LeafManager@endlink constructor to create the structure from an existing array of leafnodes. - Added active tile count to @vdblink::tree::Tree::print Tree::print@endlink. - Added the templated @vdblink::math::MinMax MinMax@endlink class to compute the extrema of arbitrary value types. - Added @vdblink::Grid::sparseFill() sparseFill@endlink and @vdblink::Grid::denseFill() denseFill@endlink methods to the Grid, Tree and RootNode classes. @par Improvements: - Complete overhaul of the @vdblink::tools::VolumeToMesh VolumeToMesh@endlink tool brings significant performance improvements and enhanced region masking, tile support and bool volume surfacing. - Improved the performance, parallel scaling and memory usage, of @vdblink::tools::LevelSetFracture tools::LevelSetFracture@endlink and updated to use the new @vdblink::tools::segmentSDF() segmentSDF@endlink scheme. - Improved the performance of @vdblink::tools::LevelSetAdvection tools::LevelSetAdvection@endlink by up to five times. - Improved the performance of @vdblink::tree::Tree::voxelizeActiveTiles() Tree::voxelizeActiveTiles@endlink by means of multi-threading. - Improved the performance of the @vdblink::tools::meshToVolume() mesh-to-volume converter@endlink, particularly for large narrow-band widths and for signed distance fields with dense interior regions. - Threaded the Tree destructor and the @vdblink::tree::Tree::clear() Tree::clear@endlink method. - Added a parameter to the @vdblink::tools::signedFloodFill() signedFloodFill@endlink and @vdblink::tools::signedFloodFillWithValues() signedFloodFillWithValues@endlink tools to constrain the flood fill to specific levels of the tree. - Added @vdblink::tree::LeafManager::reduce LeafManager::reduce@endlink and similar methods to @vdblink::tree::NodeManager NodeManager@endlink [Contributed by Brett Tully] - Improved constructors of @vdblink::math::Mat3 math::Mat3@endlink and @vdblink::math::Mat4 Mat4@endlink. - Added @vdblink::math::Mat3::cofactor Mat3::cofactor@endlink. - Added @vdblink::math::Mat3::setRows Mat3::setRows@endlink, @vdblink::math::Mat4::setRows Mat4::setRows@endlink, @vdblink::math::Mat3::setColumns Mat3::setColumns@endlink and @vdblink::math::Mat4::setColumns Mat4::setColumns@endlink. - Added @vdblink::util::NodeMask::isConstant NodeMask::isConstant@endlink method for faster bit processing. - @vdblink::tools::prune tools::prune@endlink performs an improved estimate of tile values by means of medians. - Added toggle to switch between cell centered and node centered transforms to @vdblink::tools::PointPartitioner tools::PointPartitioner@endlink @par Bug fixes: - Fixed a bug in @vdblink::tools::LevelSetAdvection tools::LevelSetAdvection@endlink that could cause non-deterministic behavior. [Reported by Jeff Lait] - Fixed a bug that allowed for unexpected implicit conversion between grids of different value types. - Fixed a bug whereby the origins of leaf nodes with value type @c bool were ignored during equality comparisons. - The @vdblink::tools::GridTransformer grid transformer tool@endlink now correctly handles affine transforms with shear and/or reflection. - Fixed a bug in the @vdblink::tools::meshToVolume() mesh-to-volume converter@endlink that could produce incorrect distances for large bandwidths. - Fixed a bug in @vdblink::tools::meshToVolume() mesh-to-volume converter@endlink that produced different results on machines with different core counts. - Fixed a threading bug in the @vdblink::tools::compReplace() compReplace@endlink tool that could cause crashes. - Resolved a floating-point exception in @vdblink::math::QuantizedUnitVec::pack() math::QuantizedUnitVec::pack@endlink caused by calling the method with a zero-length vector. [Contributed by Rick Hankins] - Improved the API of @vdblink::tools::Dense Dense@endlink with non-const access methods. - Fixed a potential threading bug in @vdblink::io::Queue io::Queue@endlink. [Contributed by Josip Šumečki] - Fixed a possible division-by-zero bug in openvdb/tools/LevelSetAdvect.h. [Contributed by Rick Hankins] - Corrected the @vdblink::math::outerProduct outer product@endlink method to not return the transpose result. [Contributed by Gergely Klar] - Fixed a memory overallocation issue in @vdblink::tools::VolumeAdvection VolumeAdvection@endlink. - Fix bug in @vdblink::tools::VolumeToMesh tools::VolumeToMesh@endlink failing to clear its state when exiting early. [Contributed by Edward Lam] - Fixed bug in @vdblink::tools::PointIndexIterator::worldSpaceSearchAndUpdate tools::PointIndexIterator::worldSpaceSearchAndUpdate@endlink that resulted in missing point indices. [Reported by Rick Hankins] - Fixed Windows build issues in unit tests. [Contributed by Edward Lam and Steven Caron] - Fixed @vdblink::math::isApproxZero() isApproxZero@endlink so that it works correctly when tolerance is zero. [Reported by Joshua Olson] - Fixed bugs in @vdblink::tree::NodeUnion NodeUnion@endlink that could cause crashes. - Fixed memory leak in @vdblink::tools::mesh_to_volume_internal::ExpandNarrowband tools::mesh_to_volume_internal::ExpandNarrowband@endlink [Reported by Kévin Dietrich] - Fixed parameter type inconsistencies in @link math/Stencils.h@endlink and @link tools/RayIntersector.h@endlink. [Contributed by Kévin Dietrich and Nick Avramoussis] - Fixed a bug in the @vdblink::tools::VolumeToMesh VolumeToMesh@endlink tool that produced artifacts for adaptive surface extraction on clipped level sets. [Reported by Jeff Lait] - Corrected empty grid background value in @vdblink::tools::meshToVolume() mesh-to-volume converter@endlink [Contributed by Jeff Lait] - Fixed a bug in @vdblink::tools::volumeToMesh volume-to-mesh converter@endlink that could produce NaNs.[Reported by Rick Hankins] - Fixed a bug in the "Advect Points SOP" that could cause a crash when the input grids were of incorrect type.[Reported by SideFX] @par API changes: - Deprecated @c math::Mat3::setBasis and @c math::Mat4::setBasis. - Renamed @c GudonovsNormSqrd to @vdblink::math::GodunovsNormSqrd GodunovsNormSqrd@endlink [Contributed by Branislav Radjenovic] - Renamed @c ValueType to @c PosType in the PointArray interface. - Deprecated tree::Tree::addLeaf(LeafNode&) and added tree::Tree::addLeaf(LeafNode*). @par Python: - Updated the Python module for Python 3 compatibility. - Updated the Python module for Boost 1.60 compatibility, to address “no to_python (by-value) converter found” exceptions. @par Maya: - Fixed bugs related to data ownership, and improved error checking. [Contributed by Crawford Doran] - Updated the Read and Write DAG nodes to support file sequences and subframe evaluation. @par Houdini: - Added a Segment SOP that separates a grid’s connected components into distinct grids. - Added a LOD SOP that produces a sequences of progressively lower resolution grids. - Added a Topology To Level Set SOP that generates a narrow-band signed distance field / level set from the interface between active and inactive voxels in an arbitrary grid. - Revamped the From Particles SOP UI and added a more efficient level set conversion method that supports Houdini 15 packed points. - Updated the Rasterize Points SOP with support for frustum transforms, sub region masking and orientation logic that matches the native Copy SOP’s orientation. - Updated the Platonic SOP with support for all five Platonic solids. - Added hooks for registering SOP_NodeVDB text callbacks for different grid types. [Contributed by Nick Avramoussis] - The Resample and Combine SOPs now correctly handle affine transforms with shear and/or reflection. - Removed the StaggeredBoxSampler code path in SOP_OpenVDB_Advect because it introduces bias. [Contributed by Fredrik Salomonsson] - Fixed a bug in the Ray SOP whereby the distance attribute was created with the wrong data type. [Contributed by Nick Avramoussis] - The From Polygon SOP now allows the user to either specify the voxel count along an axis or the voxel size in world units (the only option in the past). @htmlonly @endhtmlonly @par Version 3.1.0 - October 1, 2015 @par Highlights: - New features: advection of arbitrary volumes, general-purpose preconditioned linear solver and Poisson solver, segmentation of topologically-enclosed regions of a volume, new and faster bitmask operators, concurrent paged array, volume diagnostics - Optimizations: threaded grid constructors and topology operations; faster mesh to volume conversion, SDF to fog volume conversion and grid pruning; faster, unbounded particle partitioning - New Houdini nodes: Advect, Diagnostics, Rasterize Points, Remap, Remove Divergence, Sort Points @par New features: - Added a @vdblink::tools::VolumeAdvection volume advection@endlink tool for sparse advection of non-level-set volumes. - Added a preconditioned @vdblink::math::pcg::solve() conjugate gradient solver@endlink. - Added a @vdblink::tools::poisson::solve() Poisson solver@endlink for functions sampled on grids. - Added @vdblink::tools::extractEnclosedRegion extractEnclosedRegion@endlink, which detects topologically-enclosed (watertight) exterior regions (cavities) that can result from CSG union operations between level sets with concavities that are capped. (See the unit test @c TestPoissonSolver::testSolveWithSegmentDomain for an example in which this tool is used to identify regions of trapped fluid when solving for pressure in a volume of incompressible fluid.) - Added @vdblink::util::PagedArray PagedArray@endlink, a concurrent, dynamic linear array data structure with fast O(1) value access (both random and sequential). - Added @vdblink::tools::Sampler Sampler@endlink, which provides a unified API for both staggered and non-staggered interpolation of various orders. - Added equality and inequality operators to @vdblink::Metadata Metadata@endlink and @vdblink::MetaMap MetaMap@endlink. - Added @vdblink::tools::CheckLevelSet CheckLevelSet@endlink and @vdblink::tools::CheckFogVolume CheckFogVolume@endlink tools that perform various tests on symmetric, narrow-band level sets and fog volumes, respectively, to diagnose potential issues. - Added support for value accessors that are not registered with their trees. (Bypassing accessor registration can improve performance in rare cases but should be used with caution, since the accessor will be left in an invalid state if the tree topology is modified.) - Added a @vdblink::tree::Tree::stealNodes() stealNodes@endlink method that transfers ownership of all nodes in a tree of a certain type and inserts them into a linear array. - Added a @vdblink::tools::createLevelSetBox() tools::createLevelSetBox@endlink factory function for level-set grids. - Added @vdblink::tools::Dense::offsetToCoord() Dense::offsetToCoord@endlink. - Added @vdblink::tree::LeafBuffer::data() LeafNode::Buffer::data@endlink, which provides direct access to a leaf node’s voxel value array, avoiding out-of-core overhead. Use with caution. - Added a @vdblink::util::NodeMask::foreach() NodeMask::foreach@endlink method for efficient evaluation of complex bitwise operations. - Added a bitwise difference method to @vdblink::util::NodeMask::operator-=() NodeMask@endlink. - Added a @c -version option to @c vdb_print, @c vdb_render and @c vdb_view. @par Improvements: - Deep, conversion and topology copy @vdblink::Grid Grid@endlink constructors are now threaded and up to five times faster. - @vdblink::Grid::topologyUnion() Grid::topologyUnion@endlink, @vdblink::Grid::topologyIntersection() Grid::topologyIntersection@endlink, and @vdblink::Grid::topologyDifference() Grid::topologyDifference@endlink are now much faster due to threading. - Significantly improved the performance, parallel scaling and memory usage of the @vdblink::tools::meshToVolume() mesh to volume@endlink converter, and implemented a more robust inside/outside sign classification scheme. - Reimplemented the @vdblink::tools::PointPartitioner point partitioning@endlink tool for improved performance, concurrency and memory usage. The tool is now unbounded in the sense that points may be distributed anywhere in index space. - Significantly improved the performance of the @vdblink::tools::sdfToFogVolume() SDF to fog volume@endlink converter. - Significantly improved the performance of the @vdblink::tools::sdfInteriorMask() sdfInteriorMask@endlink tool and added support for both grid and tree inputs. - Made various optimizations and improvements to the @vdblink::tools::LevelSetMorphing level set morphing@endlink tool. - Aggregated @vdblink::tools::DiscreteField DiscreteField@endlink and @vdblink::tools::EnrightField EnrightField@endlink (formerly in tools/LevelSetAdvect.h) and @vdblink::tools::VelocitySampler VelocitySampler@endlink and @vdblink::tools::VelocityIntegrator VelocityIntegrator@endlink (formerly in tools/PointAdvect.h) into a single header, tools/VelocityFields.h. - Modified the @vdblink::tools::signedFloodFill() signed flood fill@endlink tool to accept grids of any signed scalar value type, not just floating-point grids. - The @vdblink::tools::prune() prune@endlink tool is now faster, and it employs an improved compression technique on trees with floating-point values. @par Bug fixes: - Fixed a build issue that could result in spurious “Blosc encoding is not supported” errors unless @c OPENVDB_USE_BLOSC was @#defined when compiling client code. - Added NaN and inf checks to the @vdblink::tools::PointPartitioner point partitioning@endlink tool. - Fixed a vdb_view issue whereby the frame buffer size did not necessarily match the window size. [Contributed by Rafael Campos] - Fixed a roundoff issue in @vdblink::tools::LevelSetTracker LevelSetTracker@endlink that could result in NaNs. - Changed @vdblink::tools::CheckNormGrad CheckNormGrad@endlink to check the magnitude of the gradient rather than the square of the magnitude. - Fixed parameter type inconsistencies in math/Ray.h and tools/RayIntersector.h. [Contributed by Kévin Dietrich] - Fixed incorrect handling of signed values in the @vdblink::tools::clip() clip@endlink tool (and the Clip SOP). @par API changes: - Removed the math::Hermite class since it was no longer used and caused build issues for some. - Refactored the @vdblink::tools::LevelSetAdvection level set advection@endlink, @vdblink::tools::LevelSetFilter level set filtering@endlink, @vdblink::tools::LevelSetMeasure level set measuring@endlink and @vdblink::tools::LevelSetTracker level set tracking@endlink tools. - Extended the API of the @vdblink::tools::Diagnose Diagnose@endlink tool and disabled copy construction. - Extended and unified the API of various Samplers. - Added an optional template argument to the @vdblink::tree::ValueAccessor ValueAccessor@endlink class to allow for unregistered accessors. @par Houdini: - Added a Rasterize Points SOP that produces density volumes and transfers arbitrary point attributes using a weighted-average scheme. The node incorporates a VOP subnetwork for procedural modeling, and its accompanying creation script defines a default network with VEX procedures for cloud and velocity field modeling. (See the creation script file header for installation details.) - Merged the Advect Level Set SOP into a new Advect SOP that supports advection of arbitrary volumes, not just level sets. - Added a Remove Divergence SOP that eliminates divergence from a velocity field. - Added a Diagnostics SOP that can identify various problems with level sets, fog volumes and other grids. - Added a Sort Points SOP that spatially reorders a list of points so that points that are close together in space are also close together in the list. This can improve CPU cache coherency and performance for random-access operations. - Added a Remap SOP that maps voxel values in an input range to values in an output range through a user-defined transfer function. - Added an option to the Convert SOP to activate interior voxels. [Contributed by SESI] - The To Spheres SOP can now optionally output a pscale attribute. - Added openvdb_houdini::SOP_NodeVDB::duplicateSourceStealable(), which in conjunction with the Unload flag can help to minimize deep copying of grids between nodes. The Advect, Convert, Fill, Filter, Fracture, Noise, Offset Level Set, Prune, Remap, Remove Divergence, Renormalize Level Set, Resize Narrow Band, Smooth Level Set and Transform SOPs all have this optimization enabled, meaning that they can potentially steal, rather than copy, data from upstream nodes that have the Unload flag enabled. [Contributed by Double Negative] - Redesigned the UI of the Visualize SOP and added toggles to draw with or without color, to use the grid name as the attribute name for points with values, and to attach grid index coordinates to points. - Added toggles to the Filter, Rebuild Level Set, Resize Narrow Band, Smooth Level Set and To Spheres SOPs to specify units in either world space or index space. - Fixed an issue whereby grids generated by the Rebuild Level Set SOP did not always display as surfaces in the viewport. - The Metadata SOP now sets appropriate viewport visualization options when the grid class is changed. @htmlonly @endhtmlonly @par Version 3.0.0 - January 14, 2015 - The @vdblink::io::File File@endlink class now supports delayed loading of .vdb files, meaning that memory is not allocated for voxel values until the values are actually accessed. (This feature is enabled by default.) Until a grid has been fully loaded, its source .vdb file must not be modified or deleted, so for safety, @vdblink::io::File::open() File::open@endlink automatically makes private copies of source files that are smaller than a user-specified limit (see @vdblink::io::File::setCopyMaxBytes() File::setCopyMaxBytes@endlink). The limit can be set to zero to disable copying, but if it cannot be guaranteed that a file will not be modified, then it is best not to enable delayed loading for that file. - .vdb files can now optionally be compressed with the Blosc LZ4 codec. Blosc compresses almost as well as ZLIB, but it is much faster. - Added @vdblink::tools::PointPartitioner PointPartitioner@endlink, a tool for fast spatial sorting of points stored in an external array, and @link PointIndexGrid.h PointIndexGrid@endlink, an acceleration structure for fast range and nearest-neighbor searches. - Added @link NodeManager.h tree::NodeManager@endlink, which linearizes a tree to facilitate efficient multithreading across all tree levels. - Added @vdblink::tools::prune() tools::prune@endlink (and other variants), which replaces and outperforms @c Tree::prune. - Added @vdblink::tools::signedFloodFill() tools::signedFloodFill@endlink, which replaces and outperforms @c Tree::signedFloodFill. - Added @vdblink::tools::changeBackground() tools::changeBackground@endlink (and other variants), which replaces and outperforms @c Tree::setBackground(). - Added a fast but approximate narrow-band level set @vdblink::tools::LevelSetTracker::dilate() dilation@endlink method, a fast narrow-band level set @vdblink::tools::LevelSetTracker::erode() erosion@endlink method, and a @vdblink::tools::LevelSetTracker::normalize(const MaskType*) masked normalization@endlink method to @vdblink::tools::LevelSetTracker LevelSetTracker@endlink. - Added @vdblink::tools::Diagnose Diagnose@endlink, which performs multithreaded diagnostics on grids to identify issues like values that are NaNs or out-of-range. It optionally generates a boolean grid of all values that fail user-defined tests. - Added optional alpha masks to @vdblink::tools::LevelSetMorphing LevelSetMorphing@endlink. - Fixed an intermittent crash in @vdblink::tools::LevelSetMorphing LevelSetMorphing@endlink. - Added @c tools::topologyToLevelSet(), which generates a level set from the implicit boundary between active and inactive voxels in an arbitrary input grid. [DWA internal] - Improved the performance of point scattering (by orders of magnitude) and added a @vdblink::tools::DenseUniformPointScatter DenseUniformPointScatter@endlink class as well as support for fractional numbers of particles per voxel. - Improved the performance and memory footprint of the @vdblink::tools::ParticlesToLevelSet ParticlesToLevelSet@endlink tool for large numbers (tens to hundreds of millions) of particles. - Added edge-adjacent (6+12=18 neighbors) and vertex-adjacent (6+12+8=26 neighbors) dilation algorithms to @vdblink::tools::Morphology::dilateVoxels Morphology::dilateVoxels@endlink. The default dilation pattern is still face-adjacent (6 neighbors). - Added @vdblink::tree::Tree::getNodes() Tree::getNodes@endlink, which allows for fast construction of linear arrays of tree nodes for use in multithreaded code such as the @vdblink::tree::LeafManager LeafManager@endlink or @link NodeManager.h tree::NodeManager@endlink. - Added @vdblink::math::Extrema math::Extrema@endlink and @vdblink::tools::extrema() tools::extrema@endlink to efficiently compute minimum and maximum values in a grid. - Added support for material color grids to all level set @vdblink::tools::BaseShader shaders@endlink, and added an option to @c vdb_render that allows one to specify a reference grid to be used for material color lookups. - Added @vdblink::getLibraryVersionString() getLibraryVersionString@endlink and @link OPENVDB_LIBRARY_VERSION_STRING@endlink. - Modified the mesh to volume converter to always set the grid background value to the exterior narrow-band width, and added finite value checks to narrow band parameters. - @vdblink::tools::volumeToMesh() tools::volumeToMesh@endlink now compiles for all grid types but throws an exception if the input grid does not have a scalar value type. - Added a @vdblink::io::File::readGrid(const Name&, const BBoxd&) File::readGrid@endlink overload and @vdblink::GridBase::readBuffers(std::istream&, const CoordBBox&) readBuffers@endlink overloads to the grid, tree and node classes that allow one to specify a bounding box against which to clip a grid while reading it. For large grids, clipping while reading can result in significantly lower memory usage than clipping after reading. - Added @vdblink::GridBase::clipGrid() GridBase::clipGrid@endlink, which clips a grid against a world-space bounding box, and @vdblink::GridBase::clip() GridBase::clip@endlink and @vdblink::tree::Tree::clip() Tree::clip@endlink, which clip against an index-space bounding box. - Added @vdblink::tools::clip() tools::clip@endlink, which clips a grid either against a bounding box or against the active voxels of a mask grid. - @c io::File::readGridPartial allocates the nodes of a grid’s tree as before, but it now allocates leaf nodes without data buffers. (This feature is mainly for internal use. Partially-read grids should be used with care if at all, and they should be treated as read-only.) - Grid names retrieved using a @vdblink::io::File::NameIterator File::NameIterator@endlink now always uniquely identify grids; they no longer generate ‘more than one grid named “x”’ warnings when there are multiple grids of the same name in a file (for files written starting with this version of the OpenVDB library). - Fixed a bug in @vdblink::tree::Tree::ValueOffIter Tree::ValueOffIter@endlink that could cause @vdblink::tree::TreeValueIteratorBase::setMaxDepth() depth-bounded@endlink iterators to return incorrect values. - Eliminated a recursive call in @vdblink::tree::TreeValueIteratorBase::next() TreeValueIteratorBase::next@endlink that could cause crashes on systems with a limited stack size. - Fixed memory leaks in @vdblink::tree::RootNode::topologyDifference() RootNode::topologyDifference@endlink and @vdblink::tree::RootNode::topologyIntersection() RootNode::topologyIntersection@endlink. - Fixed a memory leak in @vdblink::io::Queue io::Queue@endlink when the queue was full and a write task could not be added within the timeout interval. - Fixed a potential division by zero crash in @vdblink::tools::compDiv() tools::compDiv@endlink with integer-valued grids. - Fixed kernel normalization in the @vdblink::tools::Filter filter tool@endlink so that it is correct for integer-valued grids. - Fixed a bug in @vdblink::tree::LeafBuffer::getValue() LeafNode::Buffer::getValue@endlink whereby Visual C++ would return a reference to a temporary. [Contributed by SESI] - Fixed a bug in @vdblink::tools::ParticlesToLevelSet tools::ParticlesToLevelSet@endlink related to attribute transfer when leaf nodes are produced without active values. - Added @vdblink::util::CpuTimer util::CpuTimer@endlink and removed the more simplistic @c unittest_util::CpuTimer from @c unittest/util.h. - Eliminated the use of @c getopt for command-line argument parsing in @c vdb_test. - @vdblink::initialize() openvdb::initialize@endlink now properly initializes log4cplus if it is enabled, eliminating “No appenders could be found” errors. - Fixed a bug in the @vdblink::math::QuantizedUnitVec::pack() QuantizedUnitVec::pack@endlink method that caused quantization artifacts. - Added convenience class @vdblink::tools::AlphaMask AlphaMask@endlink - Added constructors and methods to both @vdblink::math::RandInt RandInt@endlink and @vdblink::math::Rand01 Rand01@endlink to set and reset the random seed value. - Added convenience methods for @vdblink::math::Transform::indexToWorld(const BBoxd&) const transforming@endlink @vdblink::math::Transform::worldToIndex(const BBoxd&) const bounding@endlink @vdblink::math::Transform::worldToIndexCellCentered(const BBoxd&) const boxes@endlink to @vdblink::math::Transform math::Transform@endlink. - @c vdb_view is now compatible with both GLFW 2 and GLFW 3. - Made many small changes to address type conversion and other warnings reported by newer compilers like GCC 4.8 and ICC 14. - Replaced the @c HALF_INCL_DIR and @c HALF_LIB_DIR Makefile variables with @c ILMBASE_INCL_DIR and @c ILMBASE_LIB_DIR and added @c ILMBASE_LIB, to match OpenEXR’s library organization. [Contributed by Double Negative] - Eliminated most local (function-scope) static variables, because Visual C++ doesn’t guarantee thread-safe initialization of local statics. [Contributed by SESI] - Fixed a bug in @vdblink::readString() readString@endlink related to empty strings. [Contributed by Fabio Piparo] - Fixed a bug in the @vdblink::tools::VolumeToMesh VolumeToMesh@endlink simplification scheme that was creating visual artifacts. @par API changes: - The addition of a @vdblink::GridBase::readBuffers(std::istream&, const CoordBBox&) GridBase::readBuffers@endlink virtual function overload and the @vdblink::GridBase::clip() GridBase::clip@endlink @vdblink::GridBase::readNonresidentBuffers() GridBase::readNonresidentBuffers@endlink and @vdblink::tree::Tree::clipUnallocatedNodes() Tree::clipUnallocatedNodes@endlink virtual functions changes the grid ABI so that it is incompatible with earlier versions of the OpenVDB library (such as the ones in Houdini 12.5 and 13). Define the macro @c OPENVDB_2_ABI_COMPATIBLE when compiling OpenVDB to disable these changes and preserve ABI compatibility. - All @vdblink::tools::BaseShader shaders@endlink now have a template argument to specify the type of an optional material color grid, but the default type mimics the old, uniform color behavior. - Removed a deprecated @vdblink::io::Stream::write() io::Stream::write@endlink overload. - The point counts in @vdblink::tools::UniformPointScatter UniformPointScatter@endlink and @vdblink::tools::NonUniformPointScatter NonUniformPointScatter@endlink are now specified and returned as @vdblink::Index64 Index64@endlink. - @vdblink::math::RandInt RandInt@endlink has an extra template argument to specify the integer type. The @vdblink::math::RandomInt RandomInt@endlink typedef is unchanged. - @vdblink::io::readData() io::readData@endlink, @vdblink::io::HalfReader::read() io::HalfReader::read@endlink and @vdblink::io::HalfWriter::write() io::HalfWriter::write@endlink now take a @c uint32_t argument indicating the type of compression instead of a @c bool indicating whether compression is enabled. - Removed @c io::Archive::isCompressionEnabled() and @c io::Archive::setCompressionEnabled() and renamed @c io::Archive::compressionFlags() and @c io::Archive::setCompressionFlags() to @vdblink::io::Archive::compression() io::Archive::compression@endlink and @vdblink::io::Archive::setCompression() io::Archive::setCompression@endlink. - Internal and leaf node classes are now required to provide "PartialCreate" constructors that optionally bypass the allocation of voxel buffers. Leaf node classes must now also provide @vdblink::tree::LeafNode::allocate() allocate@endlink and @vdblink::tree::LeafNode::isAllocated() isAllocated@endlink methods to manage the allocation of their buffers. - Removed @c pruneInactive and @c pruneLevelSet methods from the @vdblink::tree::Tree Tree@endlink and various node classes. These methods have been replaced by the much faster pruning functions found in tools/Prune.h. - Removed @c signedFloodFill methods from the @vdblink::Grid Grid@endlink, @vdblink::tree::Tree Tree@endlink and various node classes. These methods have been replaced by the much faster functions found in tools/SignedFloodFill.h. - Removed @c Grid::setBackground() and @c Tree::setBackground() (use the faster @vdblink::tools::changeBackground() changeBackground@endlink tool instead), and removed the default argument from @vdblink::tree::RootNode::setBackground() RootNode::setBackground@endlink. @par Python: - Added grid methods @c convertToPolygons() and @c convertToQuads(), which convert volumes to meshes, and @c createLevelSetFromPolygons(), which converts meshes to volumes. NumPy is required. @par Maya: - Added an adaptive polygonal surface extraction node. @par Houdini: - Added a new Resize Narrow Band SOP that can efficiently adjust the width of a level set’s narrow band. This allows, for example, for a level set to be created quickly from points or polygons with a very narrow band that is then quickly resized to a desired width. - Fixed bugs in the Smooth Level Set and Reshape Level Set SOPs that caused them to ignore the selected discretization scheme. - Added a Morph Level Set SOP. - Added a From Points SOP to very quickly generate a level set from a point cloud, ignoring any radius attribute. [DWA internal] - Added a Voxel Scale mode to the Resample SOP. - Improved the performance and memory footprint of the From Particles SOP for large numbers (tens to hundreds of millions) of particles. - The Scatter SOP now accepts fractional numbers of particles per voxel. - Improved the performance of the Scatter SOP by more than an order of magnitude. - The Clip SOP now has a toggle to choose explicitly between a mask grid or a bounding box as the clipping region. As a consequence, the mask grid can now be unnamed. - Added the OpenVDB library version number to the Extended Operator Information for all SOPs. - SOPs are now linked with an rpath to the directory containing the OpenVDB library. - Like the native Houdini file SOP, the Read SOP now allows missing frames to be reported either as errors or as warnings. - The Read SOP now has an optional input for geometry, the bounding box of which can be used to clip grids as they are read. For large grids, clipping while reading can result in significantly lower memory usage than clipping after reading. - The From Polygons and Convert SOPs now default to using the polygon soup mesh representation, which uses less memory. @htmlonly @endhtmlonly @par Version 2.3.0 - April 23, 2014 - Added @vdblink::tools::extractSparseTree() extractSparseTree@endlink, which selectively extracts and transforms data from a dense grid to produce a sparse tree, and @vdblink::tools::extractSparseTreeWithMask() extractSparseTreeWithMask@endlink, which copies data from the index-space intersection of a sparse tree and a dense input grid. - Added copy constructors to the @vdblink::Grid::Grid(const Grid&) Grid@endlink, @vdblink::tree::Tree::Tree(const Tree&) Tree@endlink, @vdblink::tree::RootNode::RootNode(const RootNode&) RootNode@endlink, @vdblink::tree::InternalNode::InternalNode(const InternalNode&) InternalNode@endlink and @vdblink::tree::LeafNode::LeafNode(const LeafNode&) LeafNode@endlink classes, and an assignment operator overload to @vdblink::tree::RootNode::operator=(const RootNode&) RootNode@endlink, that allow the source and destination to have different value types. - Modified @vdblink::tree::Tree::combine2() Tree::combine2@endlink to permit combination of trees with different value types. - Added @vdblink::CanConvertType CanConvertType@endlink and @vdblink::tree::RootNode::SameConfiguration RootNode::SameConfiguration@endlink metafunctions, which perform compile-time tests for value type and tree type compatibility, and a @vdblink::tree::RootNode::hasCompatibleValueType() RootNode::hasCompatibleValueType@endlink method, which does runtime checking. - Added optional support for logging using log4cplus. See logging.h and the @c INSTALL file for details. - Added @vdblink::tools::VolumeRayIntersector::hits() VolumeRayIntersector::hits@endlink, which returns all the hit segments along a ray. This is generally more efficient than repeated calls to @vdblink::tools::VolumeRayIntersector::march() VolumeRayIntersector::march@endlink. - Added member class @vdblink::math::Ray::TimeSpan Ray::TimeSpan@endlink and method @vdblink::math::Ray::valid() Ray::valid@endlink, and deprecated method @vdblink::math::Ray::test() Ray::test@endlink. - Fixed a bug in @vdblink::math::VolumeHDDA VolumeHDDA@endlink that could cause rendering artifacts when a ray’s start time was zero. [Contributed by Mike Farnsworth] - Added a @vdblink::tools::compositeToDense() compositeToDense@endlink tool, which composites data from a sparse tree into a dense array, using a sparse alpha mask. Over, Add, Sub, Min, Max, Mult, and Set are supported operations. - Added a @vdblink::tools::transformDense() transformDense@endlink tool, which applies a functor to the value of each voxel of a dense grid within a given bounding box. - Improved the performance of node iterators. @par API changes: - Collected the digital differential analyzer code from math/Ray.h and tools/RayIntersector.h into a new header file, math/DDA.h. - Rewrote @vdblink::math::VolumeHDDA VolumeHDDA@endlink and made several changes to its API. (@vdblink::math::VolumeHDDA VolumeHDDA@endlink is used internally by @vdblink::tools::VolumeRayIntersector VolumeRayIntersector@endlink, whose API is unchanged.) - @vdblink::tree::Tree::combine2() Tree::combine2@endlink, @vdblink::tree::RootNode::combine2() RootNode::combine2@endlink, @vdblink::tree::InternalNode::combine2() InternalNode::combine2@endlink, @vdblink::tree::LeafNode::combine2() LeafNode::combine2@endlink and @vdblink::CombineArgs CombineArgs@endlink all now require an additional template argument, which determines the type of the other tree. - Assignment operators for @vdblink::tree::LeafManager::LeafRange::Iterator::operator=() LeafManager::LeafRange::Iterator@endlink, @vdblink::util::BaseMaskIterator::operator=() BaseMaskIterator@endlink, @vdblink::util::NodeMask::operator=() NodeMask@endlink and @vdblink::util::RootNodeMask::operator=() RootNodeMask@endlink now return references to the respective objects. - Removed a number of methods that were deprecated in version 2.0.0 or earlier. @par Houdini: - Added a Clip SOP, which does volumetric clipping. - Added an Occlusion Mask SOP, which generates a mask of the voxels inside a camera frustum that are occluded by objects in an input grid. - The Combine SOP now applies the optional signed flood fill only to level set grids, since that operation isn’t meaningful for other grids. - The Filter SOP now processes all grid types, not just scalar grids. @htmlonly @endhtmlonly @par Version 2.2.0 - February 20, 2014 - Added a simple, multithreaded @vdblink::tools::VolumeRender volume renderer@endlink, and added volume rendering support to the @c vdb_render command-line renderer. - Added an option to the @vdblink::tools::LevelSetRayIntersector LevelSetRayIntersector@endlink and to @c vdb_render to specify the isovalue of the level set. - Added methods to the @vdblink::tools::LevelSetRayIntersector LevelSetRayIntersector@endlink to return the time of intersection along a world or index ray and to return the level set isovalue. - Improved the performance of the @vdblink::tools::VolumeRayIntersector VolumeRayIntersector@endlink and added support for voxel dilation to account for interpolation kernels. - Added a @ref sInterpolation "section" to the Cookbook on interpolation using @vdblink::tools::BoxSampler BoxSampler@endlink, @vdblink::tools::GridSampler GridSampler@endlink, @vdblink::tools::DualGridSampler DualGridSampler@endlink, et al. - Added a @ref secGrid "section" to the Overview on grids and grid metadata. - Modified @vdblink::tools::DualGridSampler DualGridSampler@endlink so it is more consistent with @vdblink::tools::GridSampler GridSampler@endlink. - The @vdblink::tools::cpt() cpt@endlink, @vdblink::tools::curl() curl@endlink, @vdblink::tools::laplacian() laplacian@endlink, @vdblink::tools::meanCurvature() meanCurvature@endlink and @vdblink::tools::normalize() normalize@endlink tools now output grids with appropriate @vdblink::VecType vector types@endlink (covariant, contravariant, etc.). - Added a @vdblink::tools::transformVectors() transformVectors@endlink tool, which applies an affine transformation to the voxel values of a vector-valued grid in accordance with the grid’s @vdblink::VecType vector type@endlink and @vdblink::Grid::isInWorldSpace() world space/local space@endlink setting. - Added a @vdblink::tools::compDiv() compDiv@endlink tool, which combines grids by dividing the values of corresponding voxels. - Fixed a bug in the mean curvature computation that could produce NaNs in regions with constant values. - Added a @vdblink::Grid::topologyDifference() Grid::topologyDifference@endlink method. - Added @vdblink::math::Vec3::exp() exp@endlink and @vdblink::math::Vec3::sum() sum@endlink methods to @vdblink::math::Vec2 Vec2@endlink, @vdblink::math::Vec3 Vec3@endlink and @vdblink::math::Vec4 Vec4@endlink. - Improved the @vdblink::tools::fillWithSpheres() fillWithSpheres@endlink tool for small volumes that are just a few voxels across. - Improved the accuracy of the mesh to volume converter. - Fixed a bug in the mesh to volume converter that caused incorrect sign classifications for narrow-band level sets. - Fixed a bug in @vdblink::math::NonlinearFrustumMap::applyIJT() NonlinearFrustumMap::applyIJT@endlink that resulted in incorrect values when computing the gradient of a grid with a frustum transform. - Fixed a file I/O bug whereby some .vdb files could not be read correctly if they contained grids with more than two distinct inactive values. - Fixed an off-by-one bug in the numbering of unnamed grids in .vdb files. The first unnamed grid in a file is now retrieved using the name “[0]”, instead of “[1]”. - Fixed a build issue reported by Clang 3.2 in tools/GridOperators.h. - Fixed a memory leak in @vdblink::tools::Film Film@endlink. - Added library and file format version number constants to the Python module. - Improved convergence in the @vdblink::tools::VolumeRender volume renderer@endlink. [Contributed by Jerry Tessendorf and Mark Matthews] - Made various changes for compatibility with Houdini 13 and with C++11 compilers. [Contributed by SESI] @par API changes: - @vdblink::tools::VolumeRayIntersector::march() VolumeRayIntersector::march@endlink no longer returns an @c int to distinguish tile vs. voxel hits. Instead, it now returns @c false if no intersection is detected and @c true otherwise. Also, @e t0 and @e t1 might now correspond to the first and last hits of multiple adjacent leaf nodes and/or active tiles. - @vdblink::tools::DualGridSampler DualGridSampler@endlink is no longer templated on the target grid type, and the value accessor is now passed as an argument. - The .vdb file format has changed slightly. Tools built with older versions of OpenVDB should be recompiled to ensure that they can read files in the new format. @par Houdini: - Added topology union, intersection and difference operations to the Combine SOP. These operations combine the active voxel topologies of grids that may have different value types. - Added a Divide operation to the Combine SOP. - Added support for boolean grids to the Combine, Resample, Scatter, Prune and Visualize SOPs. - The Fill SOP now accepts a vector as the fill value, and it allows the fill region bounds to be specified either in index space (as before), in world space, or using the bounds of geometry connected to an optional new reference input. - Added a toggle to the Offset Level Set SOP to specify the offset in either world or voxel units. - Added a toggle to the Transform and Resample SOPs to apply the transform to the voxel values of vector-valued grids, in accordance with those grids’ @vdblink::VecType vector types@endlink and @vdblink::Grid::isInWorldSpace() world space/local space@endlink settings. - Added a Vector Type menu to the Vector Merge SOP. - Removed masking options from the Renormalize SOP (since masking is not supported yet). - Reimplemented the Vector Merge SOP for better performance and interruptibility and to fix a bug in the handling of tile values. @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 @c math::randUniform() 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. - 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 @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 Grid::setBackground() 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 ZLIB and produces comparable file sizes for level set and fog volume grids. (ZLIB 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 Tree::pruneLevelSet(), a specialized Tree::pruneInactive() 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 Tree::pruneOp()-compatible functor. Tree::LevelSetPrune is a specialized Tree::pruneInactive for level-set grids and is used in interface tracking. - Added a GridBase::pruneGrid() 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 Tree::pruneInactive() 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. - ZLIB 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. @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 @c CopyPolicy 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 Tree::prune() 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 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 @link PointScatter.h tool@endlink (and a Houdini SOP) to scatter points randomly throughout a volume. */ openvdb/doc/api_0_98_0.txt0000644000000000000000000002016313200122206014234 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/Platform.cc0000644000000000000000000000365513200122377013312 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000001234013200122377013152 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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" //#ifdef OPENVDB_ENABLE_POINTS #include "points/PointDataGrid.h" //#endif #include "tools/PointIndexGrid.h" #include "util/logging.h" #include #ifdef OPENVDB_USE_BLOSC #include #endif namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { typedef tbb::mutex Mutex; typedef Mutex::scoped_lock Lock; namespace { // Declare this at file scope to ensure thread-safe initialization. Mutex sInitMutex; bool sIsInitialized = false; } void initialize() { Lock lock(sInitMutex); if (sIsInitialized) return; logging::initialize(); // 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(); MaskGrid::registerGrid(); FloatGrid::registerGrid(); DoubleGrid::registerGrid(); Int32Grid::registerGrid(); Int64Grid::registerGrid(); StringGrid::registerGrid(); Vec3IGrid::registerGrid(); Vec3SGrid::registerGrid(); Vec3DGrid::registerGrid(); // Register types associated with point index grids. Metadata::registerType(typeNameAsString(), Int32Metadata::createMetadata); Metadata::registerType(typeNameAsString(), Int64Metadata::createMetadata); tools::PointIndexGrid::registerGrid(); // Register types associated with point data grids. //#ifdef OPENVDB_ENABLE_POINTS points::internal::initialize(); //#endif #ifdef OPENVDB_USE_BLOSC blosc_init(); if (blosc_set_compressor("lz4") < 0) { OPENVDB_LOG_WARN("Blosc LZ4 compressor is unavailable"); } /// @todo blosc_set_nthreads(int nthreads); #endif #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(); //#ifdef OPENVDB_ENABLE_POINTS points::internal::uninitialize(); //#endif #ifdef OPENVDB_USE_BLOSC // We don't want to destroy Blosc, because it might have been // initialized by some other library. //blosc_destroy(); #endif } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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/0000755000000000000000000000000013200122400012122 5ustar rootrootopenvdb/math/Vec2.h0000644000000000000000000004210013200122377013104 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Math.h" #include "Tuple.h" #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Mat2; template class Vec2: public Tuple<2, T> { public: using value_type = T; using ValueType = T; /// Trivial constructor, the vector is NOT initialized Vec2() {} /// @brief Construct a vector all of whose components have the given value. explicit Vec2(T val) { this->mm[0] = this->mm[1] = val; } /// Constructor with two 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]); } /// @brief Construct a vector all of whose components have the given value, /// which may be of an arithmetic type different from this vector's value type. /// @details Type conversion warnings are suppressed. template explicit Vec2(Other val, typename std::enable_if::value, Conversion>::type = Conversion{}) { this->mm[0] = this->mm[1] = static_cast(val); } /// 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]); } /// Return a reference to itsef after the exponent has been /// applied to all the vector components. inline const Vec2& exp() { this->mm[0] = std::exp(this->mm[0]); this->mm[1] = std::exp(this->mm[1]); return *this; } /// Return a reference to itself after log has been /// applied to all the vector components. inline const Vec2& log() { this->mm[0] = std::log(this->mm[0]); this->mm[1] = std::log(this->mm[1]); return *this; } /// Return the sum of all the vector components. inline T sum() const { return this->mm[0] + this->mm[1]; } /// Return the product of all the vector components. inline T product() const { return this->mm[0] * 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; } /// return normalized this, or (1, 0) if this is null vector Vec2 unitSafe() const { T l2 = lengthSqr(); return l2 ? *this/static_cast(sqrt(l2)) : Vec2(1,0); } /// Multiply each element of this vector by @a scalar. template const Vec2 &operator*=(S scalar) { this->mm[0] *= scalar; this->mm[1] *= scalar; return *this; } /// Multiply each element of this vector by the corresponding element of the given vector. template const Vec2 &operator*=(const Vec2 &v1) { this->mm[0] *= v1[0]; this->mm[1] *= v1[1]; return *this; } /// Divide each element of this vector by @a scalar. template const Vec2 &operator/=(S scalar) { this->mm[0] /= scalar; this->mm[1] /= scalar; return *this; } /// Divide each element of this vector by the corresponding element of the given vector. template const Vec2 &operator/=(const Vec2 &v1) { this->mm[0] /= v1[0]; this->mm[1] /= v1[1]; return *this; } /// Add @a scalar to each element of this vector. template const Vec2 &operator+=(S scalar) { this->mm[0] += scalar; this->mm[1] += scalar; return *this; } /// Add each element of the given vector to the corresponding element of this vector. template const Vec2 &operator+=(const Vec2 &v1) { this->mm[0] += v1[0]; this->mm[1] += v1[1]; return *this; } /// Subtract @a scalar from each element of this vector. template const Vec2 &operator-=(S scalar) { this->mm[0] -= scalar; this->mm[1] -= scalar; return *this; } /// Subtract each element of the given vector from the corresponding element of this vector. 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]); } /// Predefined constants, e.g. Vec2f v = Vec2f::xNegAxis(); static Vec2 zero() { return Vec2(0, 0); } static Vec2 ones() { return Vec2(1, 1); } }; /// Multiply each element of the given vector by @a scalar and return the result. template inline Vec2::type> operator*(S scalar, const Vec2 &v) { return v * scalar; } /// Multiply each element of the given vector by @a scalar and return the result. template inline Vec2::type> operator*(const Vec2 &v, S scalar) { Vec2::type> result(v); result *= scalar; return result; } /// Multiply corresponding elements of @a v0 and @a v1 and return the result. template inline Vec2::type> operator*(const Vec2 &v0, const Vec2 &v1) { Vec2::type> result(v0[0] * v1[0], v0[1] * v1[1]); return result; } /// Divide @a scalar by each element of the given vector and return the result. template inline Vec2::type> operator/(S scalar, const Vec2 &v) { return Vec2::type>(scalar/v[0], scalar/v[1]); } /// Divide each element of the given vector by @a scalar and return the result. template inline Vec2::type> operator/(const Vec2 &v, S scalar) { Vec2::type> result(v); result /= scalar; return result; } /// Divide corresponding elements of @a v0 and @a v1 and return the result. template inline Vec2::type> operator/(const Vec2 &v0, const Vec2 &v1) { Vec2::type> result(v0[0] / v1[0], v0[1] / v1[1]); return result; } /// Add corresponding elements of @a v0 and @a v1 and return the result. template inline Vec2::type> operator+(const Vec2 &v0, const Vec2 &v1) { Vec2::type> result(v0); result += v1; return result; } /// Add @a scalar to each element of the given vector and return the result. template inline Vec2::type> operator+(const Vec2 &v, S scalar) { Vec2::type> result(v); result += scalar; return result; } /// Subtract corresponding elements of @a v0 and @a v1 and return the result. template inline Vec2::type> operator-(const Vec2 &v0, const Vec2 &v1) { Vec2::type> result(v0); result -= v1; return result; } /// Subtract @a scalar from each element of the given vector and return the result. 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()); } template inline Vec2 Abs(const Vec2& v) { return Vec2(Abs(v[0]), Abs(v[1])); } /// 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())); } /// @brief Return a vector with the exponent applied to each of /// the components of the input vector. template inline Vec2 Exp(Vec2 v) { return v.exp(); } /// @brief Return a vector with log applied to each of /// the components of the input vector. template inline Vec2 Log(Vec2 v) { return v.log(); } using Vec2i = Vec2; using Vec2ui = Vec2; using Vec2s = Vec2; using Vec2d = Vec2; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_VEC2_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000024054313200122377014104 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 /// /// @brief Defines various finite difference stencils by means of the /// "curiously recurring template pattern" on a BaseStencil /// that caches stencil values and stores a ValueAccessor for /// fast lookup. #ifndef OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED #define OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED #include #include #include // for Pow2, needed by WENO and Godunov #include // for Real #include // for Coord #include // for WENO5 and GodunovsNormSqrd #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { //////////////////////////////////////// template class BaseStencil { public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridT::ValueType ValueType; typedef tree::ValueAccessor AccessorType; typedef std::vector BufferType; typedef typename BufferType::iterator IterType; /// @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. template inline void moveTo(const Vec3& 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 { BufferType 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 = int(mStencil.size()); n < s; ++n) sum += mStencil[n]; return sum / mStencil.size(); } /// @brief Return the smallest value in the stencil buffer. inline ValueType min() const { IterType iter = std::min_element(mStencil.begin(), mStencil.end()); return *iter; } /// @brief Return the largest value in the stencil buffer. inline ValueType max() const { IterType iter = std::max_element(mStencil.begin(), mStencil.end()); return *iter; } /// @brief Return the coordinates of the center point of the stencil. inline const Coord& getCenterCoord() const { return mCenter; } /// @brief Return the value at the center of the stencil inline const ValueType& getCenterValue() const { return mStencil[0]; } /// @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 = 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.tree()) , mStencil(size) , mCenter(Coord::max()) { } const GridType* mGrid; AccessorType mCache; BufferType mStencil; Coord mCenter; }; // BaseStencil class //////////////////////////////////////// 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, GridT, IsSafe> { typedef SevenPointStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridT::ValueType ValueType; static const int SIZE = 7; SevenPointStencil(const GridT& 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; };// SevenPointStencil class //////////////////////////////////////// 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, GridT, IsSafe> { typedef BoxStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridT::ValueType ValueType; 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 math::Vec3& xyz) const { const ValueType u = xyz[0] - BaseType::mCenter[0]; assert(u>=0 && u<=1); const ValueType v = xyz[1] - BaseType::mCenter[1]; assert(v>=0 && v<=1); const ValueType w = xyz[2] - BaseType::mCenter[2]; assert(w>=0 && w<=1); ValueType V = BaseType::template getValue<0,0,0>(); ValueType A = static_cast(V + (BaseType::template getValue<0,0,1>() - V) * w); V = BaseType::template getValue< 0, 1, 0>(); ValueType B = static_cast(V + (BaseType::template getValue<0,1,1>() - V) * w); ValueType C = static_cast(A + (B - A) * v); V = BaseType::template getValue<1,0,0>(); A = static_cast(V + (BaseType::template getValue<1,0,1>() - V) * w); V = BaseType::template getValue<1,1,0>(); B = static_cast(V + (BaseType::template getValue<1,1,1>() - V) * w); ValueType D = static_cast(A + (B - A) * v); return static_cast(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 math::Vec3 gradient(const math::Vec3& xyz) const { const ValueType u = xyz[0] - BaseType::mCenter[0]; assert(u>=0 && u<=1); const ValueType v = xyz[1] - BaseType::mCenter[1]; assert(v>=0 && v<=1); const ValueType 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 = static_cast(D[0] + (D[1]- D[0]) * v); ValueType B = static_cast(D[2] + (D[3]- D[2]) * v); math::Vec3 grad(zeroVal(), zeroVal(), static_cast(A + (B - A) * u)); D[0] = static_cast(BaseType::template getValue<0,0,0>() + D[0] * w); D[1] = static_cast(BaseType::template getValue<0,1,0>() + D[1] * w); D[2] = static_cast(BaseType::template getValue<1,0,0>() + D[2] * w); D[3] = static_cast(BaseType::template getValue<1,1,0>() + D[3] * w); // X component A = static_cast(D[0] + (D[1] - D[0]) * v); B = static_cast(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] = static_cast(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; };// BoxStencil class //////////////////////////////////////// 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, GridT, IsSafe > { typedef SecondOrderDenseStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; 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; };// SecondOrderDenseStencil class //////////////////////////////////////// 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, GridT, IsSafe> { typedef ThirteenPointStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; 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; };// ThirteenPointStencil class //////////////////////////////////////// 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, GridT, IsSafe> { typedef FourthOrderDenseStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; 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; };// FourthOrderDenseStencil class //////////////////////////////////////// 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, GridT, IsSafe> { typedef NineteenPointStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; 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; };// NineteenPointStencil class //////////////////////////////////////// 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, GridT, IsSafe> { typedef SixthOrderDenseStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; 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; };// SixthOrderDenseStencil class ////////////////////////////////////////////////////////////////////// namespace { // anonymous namespace for stencil-layout map // the seven point stencil with a different layout from SevenPt template struct GradPt {}; template<> struct GradPt< 0, 0, 0> { enum { idx = 0 }; }; template<> struct GradPt< 1, 0, 0> { enum { idx = 2 }; }; template<> struct GradPt< 0, 1, 0> { enum { idx = 4 }; }; template<> struct GradPt< 0, 0, 1> { enum { idx = 6 }; }; template<> struct GradPt<-1, 0, 0> { enum { idx = 1 }; }; template<> struct GradPt< 0,-1, 0> { enum { idx = 3 }; }; template<> struct GradPt< 0, 0,-1> { enum { idx = 5 }; }; } /// 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, GridT, IsSafe> { typedef GradStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; 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 Godunov'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::GodunovsNormSqrd(mStencil[0] > zeroVal(), 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 math::Vec3 gradient() const { return math::Vec3(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 math::Vec3 gradient(const math::Vec3& V) const { return math::Vec3( 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 typename BaseType::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 math::Vec3 cpt() { const Coord& ijk = BaseType::getCenterCoord(); const ValueType d = ValueType(mStencil[0] * 0.5 * mInvDx2); // distance in voxels / (2dx^2) return math::Vec3(ijk[0] - d*(mStencil[2] - mStencil[1]), ijk[1] - d*(mStencil[4] - mStencil[3]), ijk[2] - d*(mStencil[6] - mStencil[5])); } /// Return linear offset for the specified stencil point relative to its center template unsigned int pos() const { return GradPt::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; const ValueType mInv2Dx, mInvDx2; }; // GradStencil class //////////////////////////////////////// /// @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, GridT, IsSafe> { typedef WenoStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridType::ValueType ValueType; 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 Godunov'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 ValueType &isoValue = zeroVal()) const { const typename BaseType::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::GodunovsNormSqrd(mStencil[0] > isoValue, 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 static_cast( mInvDx2*math::GodunovsNormSqrd(v[0]>isoValue, 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 math::Vec3 gradient(const math::Vec3& V) const { const typename BaseType::BufferType& v = mStencil; return 2*mInv2Dx * math::Vec3( 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 math::Vec3 gradient() const { return mInv2Dx * math::Vec3(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 typename BaseType::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; }; // WenoStencil class ////////////////////////////////////////////////////////////////////// template class CurvatureStencil: public BaseStencil, GridT, IsSafe> { typedef CurvatureStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; typedef typename GridT::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; return this->meanCurvature(alpha, beta) ? ValueType(alpha*mInv2Dx/math::Pow3(beta)) : 0; } /// 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; return this->meanCurvature(alpha, beta) ? ValueType(alpha*mInvDx2/(2*math::Pow2(beta))) : 0; } /// 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 bool 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 normGrad = Dx2 + Dy2 + Dz2; if (normGrad <= math::Tolerance::value()) { alpha = beta = 0; return false; } const Real 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(normGrad); // * 1/dx return true; } template friend class BaseStencil; // allow base class to call init() using BaseType::mCache; using BaseType::mStencil; const ValueType mInv2Dx, mInvDx2; }; // CurvatureStencil class ////////////////////////////////////////////////////////////////////// /// @brief Dense stencil of a given width template class DenseStencil: public BaseStencil, GridT, IsSafe> { typedef DenseStencil SelfT; typedef BaseStencil BaseType; public: typedef GridT GridType; typedef typename GridT::TreeType TreeType; 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; };// DenseStencil class } // end math namespace } // namespace OPENVDB_VERSION_NAME } // end openvdb namespace #endif // OPENVDB_MATH_STENCILS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000005235113200122377013116 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Math.h" #include "Tuple.h" #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Mat3; template class Vec3: public Tuple<3, T> { public: using value_type = T; using ValueType = T; /// Trivial constructor, the vector is NOT initialized Vec3() {} /// @brief Construct a vector all of whose components have the given value. 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]; } /// @brief Construct a Vec3 from a 3-Tuple with a possibly different value type. /// @details Type conversion warnings are suppressed. 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]); } /// @brief Construct a vector all of whose components have the given value, /// which may be of an arithmetic type different from this vector's value type. /// @details Type conversion warnings are suppressed. template explicit Vec3(Other val, typename std::enable_if::value, Conversion>::type = Conversion{}) { this->mm[0] = this->mm[1] = this->mm[2] = static_cast(val); } /// @brief Construct a Vec3 from another Vec3 with a possibly different value type. /// @details Type conversion warnings are suppressed. 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; } /// @brief Assignment operator /// @details Type conversion warnings are not suppressed. template const Vec3& operator=(const Vec3 &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]; 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; } /// Multiply each element of this vector by @a scalar. template const Vec3 &operator*=(S scalar) { this->mm[0] = static_cast(this->mm[0] * scalar); this->mm[1] = static_cast(this->mm[1] * scalar); this->mm[2] = static_cast(this->mm[2] * scalar); return *this; } /// Multiply each element of this vector by the corresponding element of the given vector. template const Vec3 &operator*=(const Vec3 &v1) { this->mm[0] *= v1[0]; this->mm[1] *= v1[1]; this->mm[2] *= v1[2]; return *this; } /// Divide each element of this vector by @a scalar. template const Vec3 &operator/=(S scalar) { this->mm[0] /= scalar; this->mm[1] /= scalar; this->mm[2] /= scalar; return *this; } /// Divide each element of this vector by the corresponding element of the given vector. template const Vec3 &operator/=(const Vec3 &v1) { this->mm[0] /= v1[0]; this->mm[1] /= v1[1]; this->mm[2] /= v1[2]; return *this; } /// Add @a scalar to each element of this vector. template const Vec3 &operator+=(S scalar) { this->mm[0] = static_cast(this->mm[0] + scalar); this->mm[1] = static_cast(this->mm[1] + scalar); this->mm[2] = static_cast(this->mm[2] + scalar); return *this; } /// Add each element of the given vector to the corresponding element of this vector. template const Vec3 &operator+=(const Vec3 &v1) { this->mm[0] += v1[0]; this->mm[1] += v1[1]; this->mm[2] += v1[2]; return *this; } /// Subtract @a scalar from each element of this vector. template const Vec3 &operator-=(S scalar) { this->mm[0] -= scalar; this->mm[1] -= scalar; this->mm[2] -= scalar; return *this; } /// Subtract each element of the given vector from the corresponding element of this vector. template const Vec3 &operator-=(const Vec3 &v1) { this->mm[0] -= v1[0]; this->mm[1] -= v1[1]; this->mm[2] -= v1[2]; return *this; } /// Return a reference to itself after the exponent has been /// applied to all the vector components. inline const Vec3& exp() { this->mm[0] = std::exp(this->mm[0]); this->mm[1] = std::exp(this->mm[1]); this->mm[2] = std::exp(this->mm[2]); return *this; } /// Return a reference to itself after log has been /// applied to all the vector components. inline const Vec3& log() { this->mm[0] = std::log(this->mm[0]); this->mm[1] = std::log(this->mm[1]); this->mm[2] = std::log(this->mm[2]); return *this; } /// Return the sum of all the vector components. inline T sum() const { return this->mm[0] + this->mm[1] + this->mm[2]; } /// Return the product of all the vector components. inline T product() const { return this->mm[0] * this->mm[1] * this->mm[2]; } /// 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; } /// return normalized this, or (1, 0, 0) if this is null vector Vec3 unitSafe() const { T l2 = lengthSqr(); return l2 ? *this / static_cast(sqrt(l2)) : Vec3(1, 0 ,0); } // 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 = static_cast(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 = static_cast(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); 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); u.mm[1] = +this->mm[2]*l; u.mm[2] = -this->mm[1]*l; } return u; } /// Return a vector with the components of this in ascending order Vec3 sorted() const { Vec3 r(*this); if( r.mm[0] > r.mm[1] ) std::swap(r.mm[0], r.mm[1]); if( r.mm[1] > r.mm[2] ) std::swap(r.mm[1], r.mm[2]); if( r.mm[0] > r.mm[1] ) std::swap(r.mm[0], r.mm[1]); return r; } /// Return the vector (z, y, x) Vec3 reversed() const { return Vec3(this->mm[2], this->mm[1], this->mm[0]); } /// Predefined constants, e.g. Vec3d v = Vec3d::xNegAxis(); static Vec3 zero() { return Vec3(0, 0, 0); } static Vec3 ones() { return Vec3(1, 1, 1); } }; /// 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); } /// Multiply each element of the given vector by @a scalar and return the result. template inline Vec3::type> operator*(S scalar, const Vec3 &v) { return v*scalar; } /// Multiply each element of the given vector by @a scalar and return the result. template inline Vec3::type> operator*(const Vec3 &v, S scalar) { Vec3::type> result(v); result *= scalar; return result; } /// Multiply corresponding elements of @a v0 and @a v1 and return the result. 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; } /// Divide @a scalar by each element of the given vector and return the result. template inline Vec3::type> operator/(S scalar, const Vec3 &v) { return Vec3::type>(scalar/v[0], scalar/v[1], scalar/v[2]); } /// Divide each element of the given vector by @a scalar and return the result. template inline Vec3::type> operator/(const Vec3 &v, S scalar) { Vec3::type> result(v); result /= scalar; return result; } /// Divide corresponding elements of @a v0 and @a v1 and return the result. 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; } /// Add corresponding elements of @a v0 and @a v1 and return the result. template inline Vec3::type> operator+(const Vec3 &v0, const Vec3 &v1) { Vec3::type> result(v0); result += v1; return result; } /// Add @a scalar to each element of the given vector and return the result. template inline Vec3::type> operator+(const Vec3 &v, S scalar) { Vec3::type> result(v); result += scalar; return result; } /// Subtract corresponding elements of @a v0 and @a v1 and return the result. template inline Vec3::type> operator-(const Vec3 &v0, const Vec3 &v1) { Vec3::type> result(v0); result -= v1; return result; } /// Subtract @a scalar from each element of the given vector and return the result. 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()); } template inline Vec3 Abs(const Vec3& v) { return Vec3(Abs(v[0]), Abs(v[1]), Abs(v[2])); } /// 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())); } /// @brief Return a vector with the exponent applied to each of /// the components of the input vector. template inline Vec3 Exp(Vec3 v) { return v.exp(); } /// @brief Return a vector with log applied to each of /// the components of the input vector. template inline Vec3 Log(Vec3 v) { return v.log(); } using Vec3i = Vec3; using Vec3ui = Vec3; using Vec3s = Vec3; using Vec3d = Vec3; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_VEC3_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000007645113200122377013046 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Math.h" #include #include // for std::max() #include #include #include 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: using value_type = T; using ValueType = T; 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]; } } Mat& operator=(Mat const& src) { if (&src != this) { for (unsigned i = 0; i < numElements(); ++i) { mm[i] = src.mm[i]; } } return *this; } /// @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(std::to_string(mm[(i*SIZE)+j])); } 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(",\n"); ret.append(indent); } } 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); } /// Return the maximum of the absolute of all elements in this matrix T absMax() const { T x = static_cast(std::fabs(mm[0])); for (unsigned i = 1; i < numElements(); ++i) { x = std::max(x, static_cast(std::fabs(mm[i]))); } return x; } /// True if a Nan is present in this matrix bool isNan() const { for (unsigned i = 0; i < numElements(); ++i) { if (std::isnan(mm[i])) return true; } return false; } /// True if an Inf is present in this matrix bool isInfinite() const { for (unsigned i = 0; i < numElements(); ++i) { if (std::isinf(mm[i])) return true; } return false; } /// True if no Nan or Inf values are present bool isFinite() const { for (unsigned i = 0; i < numElements(); ++i) { if (!std::isfinite(mm[i])) return false; } return true; } /// True if all elements are exactly zero bool isZero() const { for (unsigned i = 0; i < numElements(); ++i) { if (!isZero(mm[i])) return false; } return true; } protected: T mm[SIZE*SIZE]; }; template class Quat; template class Vec3; /// @brief Return the rotation matrix specified by the given quaternion. /// @details The quaternion is normalized and used to construct the matrix. /// Note that the matrix is transposed to match post-multiplication semantics. template MatType rotation(const Quat &q, typename MatType::value_type eps = static_cast(1.0e-8)) { using T = typename MatType::value_type; T qdot(q.dot(q)); T s(0); if (!isApproxEqual(qdot, T(0.0),eps)) { s = T(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 Return a matrix for rotation by @a angle radians about the given @a 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) { using T = typename MatType::value_type; 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"); } } /// @brief Return a matrix for rotation by @a angle radians about the given @a axis. /// @note The axis must be a unit vector. template MatType rotation(const Vec3 &_axis, typename MatType::value_type angle) { using T = typename MatType::value_type; 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); } /// @brief Return the Euler angles composing the given rotation matrix. /// @details 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 = static_cast(1.0e-8)) { using ValueType = typename MatType::value_type; using V = Vec3; ValueType phi, theta, psi; switch(rotationOrder) { case XYZ_ROTATION: if (isApproxEqual(mat[2][0], ValueType(1.0), eps)) { theta = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[1][2], mat[1][1])); psi = phi; } else if (isApproxEqual(mat[2][0], ValueType(-1.0), eps)) { theta = ValueType(-M_PI_2); phi = ValueType(0.5 * atan2(mat[1][2], mat[1][1])); psi = -phi; } else { psi = ValueType(atan2(-mat[1][0],mat[0][0])); phi = ValueType(atan2(-mat[2][1],mat[2][2])); theta = ValueType(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 = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[0][1], mat[0][0])); psi = phi; } else if (isApproxEqual(mat[1][2], ValueType(-1.0), eps)) { theta = ValueType(-M_PI/2); phi = ValueType(0.5 * atan2(mat[0][1],mat[2][1])); psi = -phi; } else { psi = ValueType(atan2(-mat[0][2], mat[2][2])); phi = ValueType(atan2(-mat[1][0], mat[1][1])); theta = ValueType(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 = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[2][0], mat[2][2])); psi = phi; } else if (isApproxEqual(mat[0][1], ValueType(-1.0), eps)) { theta = ValueType(-M_PI/2); phi = ValueType(0.5 * atan2(mat[2][0], mat[1][0])); psi = -phi; } else { psi = ValueType(atan2(-mat[2][1], mat[1][1])); phi = ValueType(atan2(-mat[0][2], mat[0][0])); theta = ValueType(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 = ValueType(0.0); phi = ValueType(0.5 * atan2(mat[1][2], mat[1][1])); psi = phi; } else if (isApproxEqual(mat[0][0], ValueType(-1.0), eps)) { theta = ValueType(M_PI); psi = ValueType(0.5 * atan2(mat[2][1], -mat[1][1])); phi = - psi; } else { psi = ValueType(atan2(mat[2][0], -mat[1][0])); phi = ValueType(atan2(mat[0][2], mat[0][1])); theta = ValueType(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 = ValueType(0.0); phi = ValueType(0.5 * atan2(mat[0][1], mat[0][0])); psi = phi; } else if (isApproxEqual(mat[2][2], ValueType(-1.0), eps)) { theta = ValueType(M_PI); phi = ValueType(0.5 * atan2(mat[0][1], mat[0][0])); psi = -phi; } else { psi = ValueType(atan2(mat[0][2], mat[1][2])); phi = ValueType(atan2(mat[2][0], -mat[2][1])); theta = ValueType(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 = ValueType(-M_PI_2); phi = ValueType(0.5 * atan2(-mat[1][0], mat[0][0])); psi = phi; } else if (isApproxEqual(mat[2][1], ValueType(-1.0), eps)) { theta = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[1][0], mat[0][0])); psi = -phi; } else { psi = ValueType(atan2(mat[0][1], mat[1][1])); phi = ValueType(atan2(mat[2][0], mat[2][2])); theta = ValueType(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 = ValueType(-M_PI_2); phi = ValueType(0.5 * atan2(-mat[1][0], mat[1][1])); psi = phi; } else if (isApproxEqual(mat[0][2], ValueType(-1.0), eps)) { theta = ValueType(M_PI_2); phi = ValueType(0.5 * atan2(mat[2][1], mat[2][0])); psi = -phi; } else { psi = ValueType(atan2(mat[1][2], mat[2][2])); phi = ValueType(atan2(mat[0][1], mat[0][0])); theta = ValueType(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 = ValueType(M_PI_2); psi = ValueType(0.5 * atan2(mat[2][1], mat[2][2])); phi = -psi; } else if (isApproxEqual(mat[1][0], ValueType(1.0), eps)) { theta = ValueType(-M_PI_2); psi = ValueType(0.5 * atan2(- mat[2][1], mat[2][2])); phi = psi; } else { psi = ValueType(atan2(mat[2][0], mat[0][0])); phi = ValueType(atan2(mat[1][2], mat[1][1])); theta = ValueType(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 Return a rotation matrix that maps @a v1 onto @a v2 /// about the cross product of @a v1 and @a v2. template MatType rotation( const Vec3& _v1, const Vec3& _v2, typename MatType::value_type eps=1.0e-8) { using T = typename MatType::value_type; 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 a matrix that scales by @a s. template MatType scale(const Vec3& s) { // Gets identity, then sets top 3 diagonal // Inefficient by 3 sets. MatType result; result.setIdentity(); result[0][0] = s[0]; result[1][1] = s[1]; result[2][2] = s[2]; return result; } /// Return a Vec3 representing the lengths of the passed matrix's upper 3×3's rows. template Vec3 getScale(const MatType &mat) { using V = Vec3; 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()); } /// @brief Return a copy of the given matrix with its upper 3×3 rows normalized. /// @details This can be geometrically interpreted 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); } /// @brief Return a copy of the given matrix with its upper 3×3 rows normalized, /// and return the length of each of these rows in @a scaling. /// @details 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) { using T = typename MatType::value_type; 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 @a axis0 by a fraction of @a 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) { using T = typename MatType::value_type; 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; } /// @brief Return an orientation matrix such that z points along @a direction, /// and y is along the @a direction / @a vertical plane. template MatType aim(const Vec3& direction, const Vec3& vertical) { using T = typename MatType::value_type; 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; } /// @brief This function snaps a specific axis to a specific direction, /// preserving scaling. /// @details It does this using minimum energy, thus posing a unique solution if /// basis & direction aren't parallel. /// @note @a direction need not be unit. template inline MatType snapMatBasis(const MatType& source, Axis axis, const Vec3& direction) { using T = typename MatType::value_type; Vec3 unitDir(direction.unit()); Vec3 ourUnitAxis(source.row(axis).unit()); // Are the two parallel? T parallel = unitDir.dot(ourUnitAxis); // Already snapped! if (isApproxEqual(parallel, T(1.0))) return source; if (isApproxEqual(parallel, T(-1.0))) { OPENVDB_THROW(ValueError, "Cannot snap to inverse axis"); } // Find angle between our basis and the one specified T angleBetween(angle(unitDir, ourUnitAxis)); // Caclulate axis to rotate along Vec3 rotationAxis = unitDir.cross(ourUnitAxis); MatType rotation; rotation.setToRotation(rotationAxis, angleBetween); return source * rotation; } /// @brief Write 0s along Mat4's last row and column, and a 1 on its diagonal. /// @details Useful initialization when we're initializing just the 3×3 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; } /// @brief Solve for A=B*B, given A. /// @details Denman-Beavers square root iteration template inline void sqrtSolve(const MatType& aA, MatType& aB, double aTol=0.01) { unsigned int iterations = static_cast(log(aTol)/log(0.5)); MatType Y[2], Z[2]; Y[0] = aA; Z[0] = MatType::identity(); unsigned int current = 0; for (unsigned int iteration=0; iteration < iterations; iteration++) { unsigned int last = current; current = !current; MatType invY = Y[last].inverse(); MatType invZ = Z[last].inverse(); Y[current] = 0.5 * (Y[last] + invZ); Z[current] = 0.5 * (Z[last] + invY); } aB = Y[current]; } template inline void powSolve(const MatType& aA, MatType& aB, double aPower, double aTol=0.01) { unsigned int iterations = static_cast(log(aTol)/log(0.5)); const bool inverted = (aPower < 0.0); if (inverted) { aPower = -aPower; } unsigned int whole = static_cast(aPower); double fraction = aPower - whole; MatType R = MatType::identity(); MatType partial = aA; double contribution = 1.0; for (unsigned int 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; } } /// @brief Determine if a matrix is an identity matrix. template inline bool isIdentity(const MatType& m) { return m.eq(MatType::identity()); } /// @brief Determine if a matrix is invertible. template inline bool isInvertible(const MatType& m) { using ValueType = typename MatType::ValueType; return !isApproxEqual(m.det(), ValueType(0)); } /// @brief Determine if a matrix is symmetric. /// @details This implicitly uses math::isApproxEqual() to determine equality. template inline bool isSymmetric(const MatType& m) { return m.eq(m.transpose()); } /// Determine if a matrix is unitary (i.e., rotation or reflection). template inline bool isUnitary(const MatType& m) { using ValueType = typename MatType::ValueType; if (!isApproxEqual(std::abs(m.det()), ValueType(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)); } /// Return the L norm of an N×N matrix. template typename MatType::ValueType lInfinityNorm(const MatType& matrix) { int n = MatType::size; typename MatType::ValueType norm = 0; for( int j = 0; jL1 norm of an N×N matrix. template 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-2017 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.h0000644000000000000000000005522013200122377013362 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for std::min(), std::max() #include // for std::array #include #include #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: using Int32 = int32_t; using Index32 = uint32_t; using Vec3i = Vec3; using Vec3I = Vec3; using ValueType = Int32; using Limits = std::numeric_limits; Coord(): mVec{{0, 0, 0}} {} explicit Coord(Int32 xyz): mVec{{xyz, xyz, xyz}} {} Coord(Int32 x, Int32 y, Int32 z): mVec{{x, y, z}} {} explicit Coord(const Vec3i& v): mVec{{v[0], v[1], v[2]}} {} explicit Coord(const Vec3I& v): mVec{{Int32(v[0]), Int32(v[1]), Int32(v[2])}} {} explicit Coord(const Int32* v): mVec{{v[0], v[1], v[2]}} {} /// @brief Return the smallest possible coordinate static Coord min() { return Coord(Limits::min()); } /// @brief Return the largest possible coordinate static Coord max() { return Coord(Limits::max()); } /// @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]))); } /// @brief Reset all three coordinates with the specified arguments Coord& reset(Int32 x, Int32 y, Int32 z) { mVec[0] = x; mVec[1] = y; mVec[2] = z; return *this; } /// @brief Reset all three coordinates with the same specified argument Coord& reset(Int32 xyz) { return this->reset(xyz, xyz, xyz); } Coord& setX(Int32 x) { mVec[0] = x; return *this; } Coord& setY(Int32 y) { mVec[1] = y; return *this; } Coord& setZ(Int32 z) { mVec[2] = z; return *this; } Coord& offset(Int32 dx, Int32 dy, Int32 dz) { mVec[0] += dx; mVec[1] += dy; mVec[2] += dz; 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; 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; return *this; } Coord& operator|= (Int32 n) { mVec[0]|=n; mVec[1]|=n; mVec[2]|=n; 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() { return mVec[0]; } Int32& y() { return mVec[1]; } Int32& z() { return mVec[2]; } Int32& operator[](size_t i) { assert(i < 3); return mVec[i]; } const Int32* data() const { return mVec.data(); } Int32* data() { return mVec.data(); } const Int32* asPointer() const { return mVec.data(); } Int32* asPointer() { return mVec.data(); } 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.data()); } 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); } /// 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())); } /// Return true if any of the components of @a a are smaller than the /// corresponding components of @a b. 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.data()), sizeof(mVec)); } void write(std::ostream& os) const { os.write(reinterpret_cast(mVec.data()), sizeof(mVec)); } private: std::array mVec; }; // 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: using Index64 = uint64_t; using ValueType = Coord::ValueType; /// @brief Iterator over the Coord domain covered by a CoordBBox /// @note If ZYXOrder is @c true, @e z is the fastest-moving coordinate, /// otherwise the traversal is in XYZ order (i.e., @e x is fastest-moving). template class Iterator { public: /// @brief C-tor from a bounding box Iterator(const CoordBBox& b): mPos(b.min()), mMin(b.min()), mMax(b.max()) {} /// @brief Increment the iterator to point to the next coordinate. /// @details Iteration stops one past the maximum coordinate /// along the axis determined by the template parameter. Iterator& operator++() { ZYXOrder ? next<2,1,0>() : next<0,1,2>(); return *this; } /// @brief Return @c true if the iterator still points to a valid coordinate. operator bool() const { return ZYXOrder ? (mPos[0] <= mMax[0]) : (mPos[2] <= mMax[2]); } /// @brief Return a const reference to the coordinate currently pointed to. const Coord& operator*() const { return mPos; } /// Return @c true if this iterator and the given iterator point to the same coordinate. bool operator==(const Iterator& other) const { return ((mPos == other.mPos) && (mMin == other.mMin) && (mMax == other.mMax)); } /// Return @c true if this iterator and the given iterator point to different coordinates. bool operator!=(const Iterator& other) const { return !(*this == other); } private: template void next() { if (mPos[a] < mMax[a]) { ++mPos[a]; } // this is the most common case else if (mPos[b] < mMax[b]) { mPos[a] = mMin[a]; ++mPos[b]; } else if (mPos[c] <= mMax[c]) { mPos[a] = mMin[a]; mPos[b] = mMin[b]; ++mPos[c]; } } Coord mPos, mMin, mMax; friend class CoordBBox; // for CoordBBox::end() };// CoordBBox::Iterator using ZYXIterator = Iterator; using XYZIterator = Iterator; /// @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 Construct from individual components of the min and max bounds. CoordBBox(ValueType xMin, ValueType yMin, ValueType zMin, ValueType xMax, ValueType yMax, ValueType zMax) : mMin(xMin, yMin, zMin), mMax(xMax, yMax, zMax) { } /// @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); } /// @brief Return the minimum coordinate. /// @note The start coordinate is inclusive. Coord getStart() const { return mMin; } /// @brief Return the maximum coordinate plus one. /// @note This end coordinate is exclusive. Coord getEnd() const { return mMax.offsetBy(1); } /// @brief Return a ZYX-order iterator that points to the minimum coordinate. ZYXIterator begin() const { return ZYXIterator{*this}; } /// @brief Return a ZYX-order iterator that points to the minimum coordinate. ZYXIterator beginZYX() const { return ZYXIterator{*this}; } /// @brief Return an XYZ-order iterator that points to the minimum coordinate. XYZIterator beginXYZ() const { return XYZIterator{*this}; } /// @brief Return a ZYX-order iterator that points past the maximum coordinate. ZYXIterator end() const { ZYXIterator it{*this}; it.mPos[0] = mMax[0] + 1; return it; } /// @brief Return a ZYX-order iterator that points past the maximum coordinate. ZYXIterator endZYX() const { return end(); } /// @brief Return an XYZ-order iterator that points past the maximum coordinate. XYZIterator endXYZ() const { XYZIterator it{*this}; it.mPos[2] = mMax[2] + 1; return it; } bool operator==(const CoordBBox& rhs) const { return mMin == rhs.mMin && mMax == rhs.mMax; } bool operator!=(const CoordBBox& rhs) const { return !(*this == rhs); } /// @brief Return @c true if this bounding box is empty (i.e., encloses no coordinates). 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 (i.e., encloses at least one coordinate). operator bool() const { return !this->empty(); } /// Return @c true if this bounding box is nonempty (i.e., encloses at least one coordinate). 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); } /// Return a new instance that is expanded by the specified padding. CoordBBox expandBy(ValueType padding) const { return CoordBBox(mMin.offsetBy(-padding),mMax.offsetBy(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 /// (tx, ty, tz). void translate(const Coord& t) { mMin += t; mMax += t; } /// @brief Populates an array with the eight corner points of this bounding box. /// @details The ordering of the corner points is lexicographic. /// @warning It is assumed that the pointer can be incremented at /// least seven times, i.e. has storage for eight Coord elements! void getCornerPoints(Coord *p) const { assert(p != nullptr); p->reset(mMin.x(), mMin.y(), mMin.z()); ++p; p->reset(mMin.x(), mMin.y(), mMax.z()); ++p; p->reset(mMin.x(), mMax.y(), mMin.z()); ++p; p->reset(mMin.x(), mMax.y(), mMax.z()); ++p; p->reset(mMax.x(), mMin.y(), mMin.z()); ++p; p->reset(mMax.x(), mMin.y(), mMax.z()); ++p; p->reset(mMax.x(), mMax.y(), mMin.z()); ++p; p->reset(mMax.x(), mMax.y(), mMax.z()); } //@{ /// @brief Bit-wise operations performed on both the min and max members CoordBBox operator>> (size_t n) const { return CoordBBox(mMin>>n, mMax>>n); } CoordBBox operator<< (size_t n) const { return CoordBBox(mMin<>=(size_t n) { mMin >>= n; mMax >>= n; return *this; } CoordBBox operator& (Coord::Int32 n) const { return CoordBBox(mMin & n, mMax & n); } CoordBBox operator| (Coord::Int32 n) const { return CoordBBox(mMin | n, mMax | n); } CoordBBox& operator&= (Coord::Int32 n) { mMin &= n; mMax &= n; return *this; } CoordBBox& operator|= (Coord::Int32 n) { mMin |= n; mMax |= n; return *this; } //@} /// 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; } inline Coord Abs(const Coord& xyz) { return Coord(Abs(xyz[0]), Abs(xyz[1]), Abs(xyz[2])); } //@{ /// 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-2017 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/ConjGradient.h0000644000000000000000000016515613200122377014675 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 ConjGradient.h /// @authors D.J. Hill, Peter Cucka /// @brief Preconditioned conjugate gradient solver (solves @e Ax = @e b using /// the conjugate gradient method with one of a selection of preconditioners) #ifndef OPENVDB_MATH_CONJGRADIENT_HAS_BEEN_INCLUDED #define OPENVDB_MATH_CONJGRADIENT_HAS_BEEN_INCLUDED #include #include #include #include #include "Math.h" // for Abs(), isZero(), Max(), Sqrt() #include #include #include // for std::lower_bound() #include #include // for std::isfinite() #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { namespace pcg { using SizeType = Index32; using SizeRange = tbb::blocked_range; template class Vector; template class SparseStencilMatrix; template class Preconditioner; template class JacobiPreconditioner; template class IncompleteCholeskyPreconditioner; /// Information about the state of a conjugate gradient solution struct State { bool success; int iterations; double relativeError; double absoluteError; }; /// Return default termination conditions for a conjugate gradient solver. template inline State terminationDefaults() { State s; s.success = false; s.iterations = 50; s.relativeError = 1.0e-6; s.absoluteError = std::numeric_limits::epsilon() * 100.0; return s; } //////////////////////////////////////// /// @brief Solve @e Ax = @e b via the preconditioned conjugate gradient method. /// /// @param A a symmetric, positive-definite, @e N x @e N matrix /// @param b a vector of size @e N /// @param x a vector of size @e N /// @param preconditioner a Preconditioner matrix /// @param termination termination conditions given as a State object with the following fields: ///
///
success ///
ignored ///
iterations ///
the maximum number of iterations, with or without convergence ///
relativeError ///
the relative error ||bAx|| / ||b|| /// that denotes convergence ///
absoluteError ///
the absolute error ||bAx|| that denotes convergence /// /// @throw ArithmeticError if either @a x or @a b is not of the appropriate size. template inline State solve( const PositiveDefMatrix& A, const Vector& b, Vector& x, Preconditioner& preconditioner, const State& termination = terminationDefaults()); /// @brief Solve @e Ax = @e b via the preconditioned conjugate gradient method. /// /// @param A a symmetric, positive-definite, @e N x @e N matrix /// @param b a vector of size @e N /// @param x a vector of size @e N /// @param preconditioner a Preconditioner matrix /// @param termination termination conditions given as a State object with the following fields: ///
///
success ///
ignored ///
iterations ///
the maximum number of iterations, with or without convergence ///
relativeError ///
the relative error ||bAx|| / ||b|| /// that denotes convergence ///
absoluteError ///
the absolute error ||bAx|| that denotes convergence /// @param interrupter an object adhering to the util::NullInterrupter interface /// with which computation can be interrupted /// /// @throw ArithmeticError if either @a x or @a b is not of the appropriate size. /// @throw RuntimeError if the computation is interrupted. template inline State solve( const PositiveDefMatrix& A, const Vector& b, Vector& x, Preconditioner& preconditioner, Interrupter& interrupter, const State& termination = terminationDefaults()); //////////////////////////////////////// /// Lightweight, variable-length vector template class Vector { public: using ValueType = T; using Ptr = SharedPtr; /// Construct an empty vector. Vector(): mData(nullptr), mSize(0) {} /// Construct a vector of @a n elements, with uninitialized values. Vector(SizeType n): mData(new T[n]), mSize(n) {} /// Construct a vector of @a n elements and initialize each element to the given value. Vector(SizeType n, const ValueType& val): mData(new T[n]), mSize(n) { this->fill(val); } ~Vector() { mSize = 0; delete[] mData; mData = nullptr; } /// Deep copy the given vector. Vector(const Vector&); /// Deep copy the given vector. Vector& operator=(const Vector&); /// Return the number of elements in this vector. SizeType size() const { return mSize; } /// Return @c true if this vector has no elements. bool empty() const { return (mSize == 0); } /// @brief Reset this vector to have @a n elements, with uninitialized values. /// @warning All of this vector's existing values will be lost. void resize(SizeType n); /// Swap internal storage with another vector, which need not be the same size. void swap(Vector& other) { std::swap(mData, other.mData); std::swap(mSize, other.mSize); } /// Set all elements of this vector to @a value. void fill(const ValueType& value); //@{ /// @brief Multiply each element of this vector by @a s. template void scale(const Scalar& s); template Vector& operator*=(const Scalar& s) { this->scale(s); return *this; } //@} /// Return the dot product of this vector with the given vector, which must be the same size. ValueType dot(const Vector&) const; /// Return the infinity norm of this vector. ValueType infNorm() const; /// Return the L2 norm of this vector. ValueType l2Norm() const { return Sqrt(this->dot(*this)); } /// Return @c true if every element of this vector has a finite value. bool isFinite() const; /// @brief Return @c true if this vector is equivalent to the given vector /// to within the specified tolerance. template bool eq(const Vector& other, ValueType eps = Tolerance::value()) const; /// Return a string representation of this vector. std::string str() const; //@{ /// @brief Return the value of this vector's ith element. inline T& at(SizeType i) { return mData[i]; } inline const T& at(SizeType i) const { return mData[i]; } inline T& operator[](SizeType i) { return this->at(i); } inline const T& operator[](SizeType i) const { return this->at(i); } //@} //@{ /// @brief Return a pointer to this vector's elements. inline T* data() { return mData; } inline const T* data() const { return mData; } inline const T* constData() const { return mData; } //@} private: // Functor for use with tbb::parallel_for() template struct ScaleOp; struct DeterministicDotProductOp; // Functors for use with tbb::parallel_reduce() template struct EqOp; struct InfNormOp; struct IsFiniteOp; T* mData; SizeType mSize; }; using VectorS = Vector; using VectorD = Vector; //////////////////////////////////////// /// @brief Sparse, square matrix representing a 3D stencil operator of size @a STENCIL_SIZE /// @details The implementation is a variation on compressed row storage (CRS). template class SparseStencilMatrix { public: using ValueType = ValueType_; using VectorType = Vector; using Ptr = SharedPtr; class ConstValueIter; class ConstRow; class RowEditor; static const ValueType sZeroValue; /// Construct an @a n x @a n matrix with at most @a STENCIL_SIZE nonzero elements per row. SparseStencilMatrix(SizeType n); /// Deep copy the given matrix. SparseStencilMatrix(const SparseStencilMatrix&); //@{ /// Return the number of rows in this matrix. SizeType numRows() const { return mNumRows; } SizeType size() const { return mNumRows; } //@} /// @brief Set the value at the given coordinates. /// @warning It is not safe to set values in the same row simultaneously /// from multiple threads. void setValue(SizeType row, SizeType col, const ValueType&); //@{ /// @brief Return the value at the given coordinates. /// @warning It is not safe to get values from a row while another thread /// is setting values in that row. const ValueType& getValue(SizeType row, SizeType col) const; const ValueType& operator()(SizeType row, SizeType col) const; //@} /// Return a read-only view onto the given row of this matrix. ConstRow getConstRow(SizeType row) const; /// Return a read/write view onto the given row of this matrix. RowEditor getRowEditor(SizeType row); //@{ /// @brief Multiply all elements in the matrix by @a s; template void scale(const Scalar& s); template SparseStencilMatrix& operator*=(const Scalar& s) { this->scale(s); return *this; } //@} /// @brief Multiply this matrix by @a inVec and return the result in @a resultVec. /// @throw ArithmeticError if either @a inVec or @a resultVec is not of size @e N, /// where @e N x @e N is the size of this matrix. template void vectorMultiply(const Vector& inVec, Vector& resultVec) const; /// @brief Multiply this matrix by the vector represented by the array @a inVec /// and return the result in @a resultVec. /// @warning Both @a inVec and @a resultVec must have at least @e N elements, /// where @e N x @e N is the size of this matrix. template void vectorMultiply(const VecValueType* inVec, VecValueType* resultVec) const; /// @brief Return @c true if this matrix is equivalent to the given matrix /// to within the specified tolerance. template bool eq(const SparseStencilMatrix& other, ValueType eps = Tolerance::value()) const; /// Return @c true if every element of this matrix has a finite value. bool isFinite() const; /// Return a string representation of this matrix. std::string str() const; private: struct RowData { RowData(ValueType* v, SizeType* c, SizeType& s): mVals(v), mCols(c), mSize(s) {} ValueType* mVals; SizeType* mCols; SizeType& mSize; }; struct ConstRowData { ConstRowData(const ValueType* v, const SizeType* c, const SizeType& s): mVals(v), mCols(c), mSize(s) {} const ValueType* mVals; const SizeType* mCols; const SizeType& mSize; }; /// Base class for row accessors template class RowBase { public: using DataType = DataType_; static SizeType capacity() { return STENCIL_SIZE; } RowBase(const DataType& data): mData(data) {} bool empty() const { return (mData.mSize == 0); } const SizeType& size() const { return mData.mSize; } const ValueType& getValue(SizeType columnIdx, bool& active) const; const ValueType& getValue(SizeType columnIdx) const; /// Return an iterator over the stored values in this row. ConstValueIter cbegin() const; /// @brief Return @c true if this row is equivalent to the given row /// to within the specified tolerance. template bool eq(const RowBase& other, ValueType eps = Tolerance::value()) const; /// @brief Return the dot product of this row with the first /// @a vecSize elements of @a inVec. /// @warning @a inVec must have at least @a vecSize elements. template VecValueType dot(const VecValueType* inVec, SizeType vecSize) const; /// Return the dot product of this row with the given vector. template VecValueType dot(const Vector& inVec) const; /// Return a string representation of this row. std::string str() const; protected: friend class ConstValueIter; const ValueType& value(SizeType i) const { return mData.mVals[i]; } SizeType column(SizeType i) const { return mData.mCols[i]; } /// @brief Return the array index of the first column index that is /// equal to or greater than the given column index. /// @note If @a columnIdx is larger than any existing column index, /// the return value will point beyond the end of the array. SizeType find(SizeType columnIdx) const; DataType mData; }; using ConstRowBase = RowBase; public: /// Iterator over the stored values in a row of this matrix class ConstValueIter { public: const ValueType& operator*() const { if (mData.mSize == 0) return SparseStencilMatrix::sZeroValue; return mData.mVals[mCursor]; } SizeType column() const { return mData.mCols[mCursor]; } void increment() { mCursor++; } ConstValueIter& operator++() { increment(); return *this; } operator bool() const { return (mCursor < mData.mSize); } void reset() { mCursor = 0; } private: friend class SparseStencilMatrix; ConstValueIter(const RowData& d): mData(d.mVals, d.mCols, d.mSize), mCursor(0) {} ConstValueIter(const ConstRowData& d): mData(d), mCursor(0) {} const ConstRowData mData; SizeType mCursor; }; /// Read-only accessor to a row of this matrix class ConstRow: public ConstRowBase { public: ConstRow(const ValueType* valueHead, const SizeType* columnHead, const SizeType& rowSize); }; // class ConstRow /// Read/write accessor to a row of this matrix class RowEditor: public RowBase<> { public: RowEditor(ValueType* valueHead, SizeType* columnHead, SizeType& rowSize, SizeType colSize); /// Set the number of entries in this row to zero. void clear(); /// @brief Set the value of the entry in the specified column. /// @return the current number of entries stored in this row. SizeType setValue(SizeType column, const ValueType& value); //@{ /// @brief Scale all of the entries in this row. template void scale(const Scalar&); template RowEditor& operator*=(const Scalar& s) { this->scale(s); return *this; } //@} private: const SizeType mNumColumns; // used only for bounds checking }; // class RowEditor private: // Functors for use with tbb::parallel_for() struct MatrixCopyOp; template struct VecMultOp; template struct RowScaleOp; // Functors for use with tbb::parallel_reduce() struct IsFiniteOp; template struct EqOp; const SizeType mNumRows; std::unique_ptr mValueArray; std::unique_ptr mColumnIdxArray; std::unique_ptr mRowSizeArray; }; // class SparseStencilMatrix //////////////////////////////////////// /// Base class for conjugate gradient preconditioners template class Preconditioner { public: using ValueType = T; using Ptr = SharedPtr; template Preconditioner(const SparseStencilMatrix&) {} virtual ~Preconditioner() = default; virtual bool isValid() const { return true; } /// @brief Apply this preconditioner to a residue vector: /// @e z = M−1r /// @param r residue vector /// @param[out] z preconditioned residue vector virtual void apply(const Vector& r, Vector& z) = 0; }; //////////////////////////////////////// namespace internal { // Functor for use with tbb::parallel_for() to copy data from one array to another template struct CopyOp { CopyOp(const T* from_, T* to_): from(from_), to(to_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) to[n] = from[n]; } const T* from; T* to; }; // Functor for use with tbb::parallel_for() to fill an array with a constant value template struct FillOp { FillOp(T* data_, const T& val_): data(data_), val(val_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) data[n] = val; } T* data; const T val; }; // Functor for use with tbb::parallel_for() that computes a * x + y template struct LinearOp { LinearOp(const T& a_, const T* x_, const T* y_, T* out_): a(a_), x(x_), y(y_), out(out_) {} void operator()(const SizeRange& range) const { if (isExactlyEqual(a, T(1))) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) out[n] = x[n] + y[n]; } else if (isExactlyEqual(a, T(-1))) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) out[n] = -x[n] + y[n]; } else { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) out[n] = a * x[n] + y[n]; } } const T a, *x, *y; T* out; }; } // namespace internal //////////////////////////////////////// inline std::ostream& operator<<(std::ostream& os, const State& state) { os << (state.success ? "succeeded with " : "") << "rel. err. " << state.relativeError << ", abs. err. " << state.absoluteError << " after " << state.iterations << " iteration" << (state.iterations == 1 ? "" : "s"); return os; } //////////////////////////////////////// template inline Vector::Vector(const Vector& other): mData(new T[other.mSize]), mSize(other.mSize) { tbb::parallel_for(SizeRange(0, mSize), internal::CopyOp(/*from=*/other.mData, /*to=*/mData)); } template inline Vector& Vector::operator=(const Vector& other) { // Update the internal storage to the correct size if (mSize != other.mSize) { mSize = other.mSize; delete[] mData; mData = new T[mSize]; } // Deep copy the data tbb::parallel_for(SizeRange(0, mSize), internal::CopyOp(/*from=*/other.mData, /*to=*/mData)); return *this; } template inline void Vector::resize(SizeType n) { if (n != mSize) { if (mData) delete[] mData; mData = new T[n]; mSize = n; } } template inline void Vector::fill(const ValueType& value) { tbb::parallel_for(SizeRange(0, mSize), internal::FillOp(mData, value)); } template template struct Vector::ScaleOp { ScaleOp(T* data_, const Scalar& s_): data(data_), s(s_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) data[n] *= s; } T* data; const Scalar s; }; template template inline void Vector::scale(const Scalar& s) { tbb::parallel_for(SizeRange(0, mSize), ScaleOp(mData, s)); } template struct Vector::DeterministicDotProductOp { DeterministicDotProductOp(const T* a_, const T* b_, const SizeType binCount_, const SizeType arraySize_, T* reducetmp_): a(a_), b(b_), binCount(binCount_), arraySize(arraySize_), reducetmp(reducetmp_) {} void operator()(const SizeRange& range) const { const SizeType binSize = arraySize / binCount; // Iterate over bins (array segments) for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { const SizeType begin = n * binSize; const SizeType end = (n == binCount-1) ? arraySize : begin + binSize; // Compute the partial sum for this array segment T sum = zeroVal(); for (SizeType i = begin; i < end; ++i) { sum += a[i] * b[i]; } // Store the partial sum reducetmp[n] = sum; } } const T* a; const T* b; const SizeType binCount; const SizeType arraySize; T* reducetmp; }; template inline T Vector::dot(const Vector& other) const { assert(this->size() == other.size()); const T* aData = this->data(); const T* bData = other.data(); SizeType arraySize = this->size(); T result = zeroVal(); if (arraySize < 1024) { // Compute the dot product in serial for small arrays for (SizeType n = 0; n < arraySize; ++n) { result += aData[n] * bData[n]; } } else { // Compute the dot product by segmenting the arrays into // a predetermined number of sub arrays in parallel and // accumulate the finial result in series. const SizeType binCount = 100; T partialSums[100]; tbb::parallel_for(SizeRange(0, binCount), DeterministicDotProductOp(aData, bData, binCount, arraySize, partialSums)); for (SizeType n = 0; n < binCount; ++n) { result += partialSums[n]; } } return result; } template struct Vector::InfNormOp { InfNormOp(const T* data_): data(data_) {} T operator()(const SizeRange& range, T maxValue) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { maxValue = Max(maxValue, Abs(data[n])); } return maxValue; } const T* data; }; template inline T Vector::infNorm() const { // Parallelize over the elements of this vector. T result = tbb::parallel_reduce(SizeRange(0, this->size()), /*seed=*/zeroVal(), InfNormOp(this->data()), /*join=*/[](T max1, T max2) { return Max(max1, max2); }); return result; } template struct Vector::IsFiniteOp { IsFiniteOp(const T* data_): data(data_) {} bool operator()(const SizeRange& range, bool finite) const { if (finite) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { if (!std::isfinite(data[n])) return false; } } return finite; } const T* data; }; template inline bool Vector::isFinite() const { // Parallelize over the elements of this vector. bool finite = tbb::parallel_reduce(SizeRange(0, this->size()), /*seed=*/true, IsFiniteOp(this->data()), /*join=*/[](bool finite1, bool finite2) { return (finite1 && finite2); }); return finite; } template template struct Vector::EqOp { EqOp(const T* a_, const OtherValueType* b_, T e): a(a_), b(b_), eps(e) {} bool operator()(const SizeRange& range, bool equal) const { if (equal) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { if (!isApproxEqual(a[n], b[n], eps)) return false; } } return equal; } const T* a; const OtherValueType* b; const T eps; }; template template inline bool Vector::eq(const Vector& other, ValueType eps) const { if (this->size() != other.size()) return false; bool equal = tbb::parallel_reduce(SizeRange(0, this->size()), /*seed=*/true, EqOp(this->data(), other.data(), eps), /*join=*/[](bool eq1, bool eq2) { return (eq1 && eq2); }); return equal; } template inline std::string Vector::str() const { std::ostringstream ostr; ostr << "["; std::string sep; for (SizeType n = 0, N = this->size(); n < N; ++n) { ostr << sep << (*this)[n]; sep = ", "; } ostr << "]"; return ostr.str(); } //////////////////////////////////////// template const ValueType SparseStencilMatrix::sZeroValue = zeroVal(); template inline SparseStencilMatrix::SparseStencilMatrix(SizeType numRows) : mNumRows(numRows) , mValueArray(new ValueType[mNumRows * STENCIL_SIZE]) , mColumnIdxArray(new SizeType[mNumRows * STENCIL_SIZE]) , mRowSizeArray(new SizeType[mNumRows]) { // Initialize the matrix to a null state by setting the size of each row to zero. tbb::parallel_for(SizeRange(0, mNumRows), internal::FillOp(mRowSizeArray.get(), /*value=*/0)); } template struct SparseStencilMatrix::MatrixCopyOp { MatrixCopyOp(const SparseStencilMatrix& from_, SparseStencilMatrix& to_): from(&from_), to(&to_) {} void operator()(const SizeRange& range) const { const ValueType* fromVal = from->mValueArray.get(); const SizeType* fromCol = from->mColumnIdxArray.get(); ValueType* toVal = to->mValueArray.get(); SizeType* toCol = to->mColumnIdxArray.get(); for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { toVal[n] = fromVal[n]; toCol[n] = fromCol[n]; } } const SparseStencilMatrix* from; SparseStencilMatrix* to; }; template inline SparseStencilMatrix::SparseStencilMatrix(const SparseStencilMatrix& other) : mNumRows(other.mNumRows) , mValueArray(new ValueType[mNumRows * STENCIL_SIZE]) , mColumnIdxArray(new SizeType[mNumRows * STENCIL_SIZE]) , mRowSizeArray(new SizeType[mNumRows]) { SizeType size = mNumRows * STENCIL_SIZE; // Copy the value and column index arrays from the other matrix to this matrix. tbb::parallel_for(SizeRange(0, size), MatrixCopyOp(/*from=*/other, /*to=*/*this)); // Copy the row size array from the other matrix to this matrix. tbb::parallel_for(SizeRange(0, mNumRows), internal::CopyOp(/*from=*/other.mRowSizeArray.get(), /*to=*/mRowSizeArray.get())); } template inline void SparseStencilMatrix::setValue(SizeType row, SizeType col, const ValueType& val) { assert(row < mNumRows); this->getRowEditor(row).setValue(col, val); } template inline const ValueType& SparseStencilMatrix::getValue(SizeType row, SizeType col) const { assert(row < mNumRows); return this->getConstRow(row).getValue(col); } template inline const ValueType& SparseStencilMatrix::operator()(SizeType row, SizeType col) const { return this->getValue(row,col); } template template struct SparseStencilMatrix::RowScaleOp { RowScaleOp(SparseStencilMatrix& m, const Scalar& s_): mat(&m), s(s_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { RowEditor row = mat->getRowEditor(n); row.scale(s); } } SparseStencilMatrix* mat; const Scalar s; }; template template inline void SparseStencilMatrix::scale(const Scalar& s) { // Parallelize over the rows in the matrix. tbb::parallel_for(SizeRange(0, mNumRows), RowScaleOp(*this, s)); } template template struct SparseStencilMatrix::VecMultOp { VecMultOp(const SparseStencilMatrix& m, const VecValueType* i, VecValueType* o): mat(&m), in(i), out(o) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { ConstRow row = mat->getConstRow(n); out[n] = row.dot(in, mat->numRows()); } } const SparseStencilMatrix* mat; const VecValueType* in; VecValueType* out; }; template template inline void SparseStencilMatrix::vectorMultiply( const Vector& inVec, Vector& resultVec) const { if (inVec.size() != mNumRows) { OPENVDB_THROW(ArithmeticError, "matrix and input vector have incompatible sizes (" << mNumRows << "x" << mNumRows << " vs. " << inVec.size() << ")"); } if (resultVec.size() != mNumRows) { OPENVDB_THROW(ArithmeticError, "matrix and result vector have incompatible sizes (" << mNumRows << "x" << mNumRows << " vs. " << resultVec.size() << ")"); } vectorMultiply(inVec.data(), resultVec.data()); } template template inline void SparseStencilMatrix::vectorMultiply( const VecValueType* inVec, VecValueType* resultVec) const { // Parallelize over the rows in the matrix. tbb::parallel_for(SizeRange(0, mNumRows), VecMultOp(*this, inVec, resultVec)); } template template struct SparseStencilMatrix::EqOp { EqOp(const SparseStencilMatrix& a_, const SparseStencilMatrix& b_, ValueType e): a(&a_), b(&b_), eps(e) {} bool operator()(const SizeRange& range, bool equal) const { if (equal) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { if (!a->getConstRow(n).eq(b->getConstRow(n), eps)) return false; } } return equal; } const SparseStencilMatrix* a; const SparseStencilMatrix* b; const ValueType eps; }; template template inline bool SparseStencilMatrix::eq( const SparseStencilMatrix& other, ValueType eps) const { if (this->numRows() != other.numRows()) return false; bool equal = tbb::parallel_reduce(SizeRange(0, this->numRows()), /*seed=*/true, EqOp(*this, other, eps), /*join=*/[](bool eq1, bool eq2) { return (eq1 && eq2); }); return equal; } template struct SparseStencilMatrix::IsFiniteOp { IsFiniteOp(const SparseStencilMatrix& m): mat(&m) {} bool operator()(const SizeRange& range, bool finite) const { if (finite) { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { const ConstRow row = mat->getConstRow(n); for (ConstValueIter it = row.cbegin(); it; ++it) { if (!std::isfinite(*it)) return false; } } } return finite; } const SparseStencilMatrix* mat; }; template inline bool SparseStencilMatrix::isFinite() const { // Parallelize over the rows of this matrix. bool finite = tbb::parallel_reduce(SizeRange(0, this->numRows()), /*seed=*/true, IsFiniteOp(*this), /*join=*/[](bool finite1, bool finite2) { return (finite1&&finite2); }); return finite; } template inline std::string SparseStencilMatrix::str() const { std::ostringstream ostr; for (SizeType n = 0, N = this->size(); n < N; ++n) { ostr << n << ": " << this->getConstRow(n).str() << "\n"; } return ostr.str(); } template inline typename SparseStencilMatrix::RowEditor SparseStencilMatrix::getRowEditor(SizeType i) { assert(i < mNumRows); const SizeType head = i * STENCIL_SIZE; return RowEditor(&mValueArray[head], &mColumnIdxArray[head], mRowSizeArray[i], mNumRows); } template inline typename SparseStencilMatrix::ConstRow SparseStencilMatrix::getConstRow(SizeType i) const { assert(i < mNumRows); const SizeType head = i * STENCIL_SIZE; // index for this row into main storage return ConstRow(&mValueArray[head], &mColumnIdxArray[head], mRowSizeArray[i]); } template template inline SizeType SparseStencilMatrix::RowBase::find(SizeType columnIdx) const { if (this->empty()) return mData.mSize; // Get a pointer to the first column index that is equal to or greater than the given index. // (This assumes that the data is sorted by column.) const SizeType* colPtr = std::lower_bound(mData.mCols, mData.mCols + mData.mSize, columnIdx); // Return the offset of the pointer from the beginning of the array. return static_cast(colPtr - mData.mCols); } template template inline const ValueType& SparseStencilMatrix::RowBase::getValue( SizeType columnIdx, bool& active) const { active = false; SizeType idx = this->find(columnIdx); if (idx < this->size() && this->column(idx) == columnIdx) { active = true; return this->value(idx); } return SparseStencilMatrix::sZeroValue; } template template inline const ValueType& SparseStencilMatrix::RowBase::getValue(SizeType columnIdx) const { SizeType idx = this->find(columnIdx); if (idx < this->size() && this->column(idx) == columnIdx) { return this->value(idx); } return SparseStencilMatrix::sZeroValue; } template template inline typename SparseStencilMatrix::ConstValueIter SparseStencilMatrix::RowBase::cbegin() const { return ConstValueIter(mData); } template template template inline bool SparseStencilMatrix::RowBase::eq( const RowBase& other, ValueType eps) const { if (this->size() != other.size()) return false; for (ConstValueIter it = cbegin(), oit = other.cbegin(); it || oit; ++it, ++oit) { if (it.column() != oit.column()) return false; if (!isApproxEqual(*it, *oit, eps)) return false; } return true; } template template template inline VecValueType SparseStencilMatrix::RowBase::dot( const VecValueType* inVec, SizeType vecSize) const { VecValueType result = zeroVal(); for (SizeType idx = 0, N = std::min(vecSize, this->size()); idx < N; ++idx) { result += static_cast(this->value(idx) * inVec[this->column(idx)]); } return result; } template template template inline VecValueType SparseStencilMatrix::RowBase::dot( const Vector& inVec) const { return dot(inVec.data(), inVec.size()); } template template inline std::string SparseStencilMatrix::RowBase::str() const { std::ostringstream ostr; std::string sep; for (SizeType n = 0, N = this->size(); n < N; ++n) { ostr << sep << "(" << this->column(n) << ", " << this->value(n) << ")"; sep = ", "; } return ostr.str(); } template inline SparseStencilMatrix::ConstRow::ConstRow( const ValueType* valueHead, const SizeType* columnHead, const SizeType& rowSize): ConstRowBase(ConstRowData(const_cast(valueHead), const_cast(columnHead), const_cast(rowSize))) { } template inline SparseStencilMatrix::RowEditor::RowEditor( ValueType* valueHead, SizeType* columnHead, SizeType& rowSize, SizeType colSize): RowBase<>(RowData(valueHead, columnHead, rowSize)), mNumColumns(colSize) { } template inline void SparseStencilMatrix::RowEditor::clear() { // Note: since mSize is a reference, this modifies the underlying matrix. RowBase<>::mData.mSize = 0; } template inline SizeType SparseStencilMatrix::RowEditor::setValue( SizeType column, const ValueType& value) { assert(column < mNumColumns); RowData& data = RowBase<>::mData; // Get the offset of the first column index that is equal to or greater than // the column to be modified. SizeType offset = this->find(column); if (offset < data.mSize && data.mCols[offset] == column) { // If the column already exists, just update its value. data.mVals[offset] = value; return data.mSize; } // Check that it is safe to add a new column. assert(data.mSize < this->capacity()); if (offset >= data.mSize) { // The new column's index is larger than any existing index. Append the new column. data.mVals[data.mSize] = value; data.mCols[data.mSize] = column; } else { // Insert the new column at the computed offset after shifting subsequent columns. for (SizeType i = data.mSize; i > offset; --i) { data.mVals[i] = data.mVals[i - 1]; data.mCols[i] = data.mCols[i - 1]; } data.mVals[offset] = value; data.mCols[offset] = column; } ++data.mSize; return data.mSize; } template template inline void SparseStencilMatrix::RowEditor::scale(const Scalar& s) { for (int idx = 0, N = this->size(); idx < N; ++idx) { RowBase<>::mData.mVals[idx] *= s; } } //////////////////////////////////////// /// Diagonal preconditioner template class JacobiPreconditioner: public Preconditioner { private: struct InitOp; struct ApplyOp; public: using ValueType = typename MatrixType::ValueType; using BaseType = Preconditioner; using VectorType = Vector; using Ptr = SharedPtr; JacobiPreconditioner(const MatrixType& A): BaseType(A), mDiag(A.numRows()) { // Initialize vector mDiag with the values from the matrix diagonal. tbb::parallel_for(SizeRange(0, A.numRows()), InitOp(A, mDiag.data())); } ~JacobiPreconditioner() override = default; void apply(const Vector& r, Vector& z) override { const SizeType size = mDiag.size(); assert(r.size() == z.size()); assert(r.size() == size); tbb::parallel_for(SizeRange(0, size), ApplyOp(mDiag.data(), r.data(), z.data())); } /// Return @c true if all values along the diagonal are finite. bool isFinite() const { return mDiag.isFinite(); } private: // Functor for use with tbb::parallel_for() struct InitOp { InitOp(const MatrixType& m, ValueType* v): mat(&m), vec(v) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { const ValueType val = mat->getValue(n, n); assert(!isApproxZero(val, ValueType(0.0001))); vec[n] = static_cast(1.0 / val); } } const MatrixType* mat; ValueType* vec; }; // Functor for use with tbb::parallel_reduce() struct ApplyOp { ApplyOp(const ValueType* x_, const ValueType* y_, ValueType* out_): x(x_), y(y_), out(out_) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) out[n] = x[n] * y[n]; } const ValueType *x, *y; ValueType* out; }; // The Jacobi preconditioner is a diagonal matrix VectorType mDiag; }; // class JacobiPreconditioner /// Preconditioner using incomplete Cholesky factorization template class IncompleteCholeskyPreconditioner: public Preconditioner { private: struct CopyToLowerOp; struct TransposeOp; public: using ValueType = typename MatrixType::ValueType; using BaseType = Preconditioner; using VectorType = Vector; using Ptr = SharedPtr; using TriangularMatrix = SparseStencilMatrix; using TriangleConstRow = typename TriangularMatrix::ConstRow; using TriangleRowEditor = typename TriangularMatrix::RowEditor; IncompleteCholeskyPreconditioner(const MatrixType& matrix) : BaseType(matrix) , mLowerTriangular(matrix.numRows()) , mUpperTriangular(matrix.numRows()) , mTempVec(matrix.numRows()) { // Size of matrix const SizeType numRows = mLowerTriangular.numRows(); // Copy the upper triangular part to the lower triangular part. tbb::parallel_for(SizeRange(0, numRows), CopyToLowerOp(matrix, mLowerTriangular)); // Build the Incomplete Cholesky Matrix // // Algorithm: // // for (k = 0; k < size; ++k) { // A(k,k) = sqrt(A(k,k)); // for (i = k +1, i < size; ++i) { // if (A(i,k) == 0) continue; // A(i,k) = A(i,k) / A(k,k); // } // for (j = k+1; j < size; ++j) { // for (i = j; i < size; ++i) { // if (A(i,j) == 0) continue; // A(i,j) -= A(i,k)*A(j,k); // } // } // } mPassedCompatibilityCondition = true; for (SizeType k = 0; k < numRows; ++k) { TriangleConstRow crow_k = mLowerTriangular.getConstRow(k); ValueType diagonalValue = crow_k.getValue(k); // Test if the matrix build has failed. if (diagonalValue < 1.e-5) { mPassedCompatibilityCondition = false; break; } diagonalValue = Sqrt(diagonalValue); TriangleRowEditor row_k = mLowerTriangular.getRowEditor(k); row_k.setValue(k, diagonalValue); // Exploit the fact that the matrix is symmetric. typename MatrixType::ConstRow srcRow = matrix.getConstRow(k); typename MatrixType::ConstValueIter citer = srcRow.cbegin(); for ( ; citer; ++citer) { SizeType ii = citer.column(); if (ii < k+1) continue; // look above diagonal TriangleRowEditor row_ii = mLowerTriangular.getRowEditor(ii); row_ii.setValue(k, *citer / diagonalValue); } // for (j = k+1; j < size; ++j) replaced by row iter below citer.reset(); // k,j entries for ( ; citer; ++citer) { SizeType j = citer.column(); if (j < k+1) continue; TriangleConstRow row_j = mLowerTriangular.getConstRow(j); ValueType a_jk = row_j.getValue(k); // a_jk is non zero if a_kj is non zero // Entry (i,j) is non-zero if matrix(j,i) is nonzero typename MatrixType::ConstRow mask = matrix.getConstRow(j); typename MatrixType::ConstValueIter maskIter = mask.cbegin(); for ( ; maskIter; ++maskIter) { SizeType i = maskIter.column(); if (i < j) continue; TriangleConstRow crow_i = mLowerTriangular.getConstRow(i); ValueType a_ij = crow_i.getValue(j); ValueType a_ik = crow_i.getValue(k); TriangleRowEditor row_i = mLowerTriangular.getRowEditor(i); a_ij -= a_ik * a_jk; row_i.setValue(j, a_ij); } } } // Build the transpose of the IC matrix: mUpperTriangular tbb::parallel_for(SizeRange(0, numRows), TransposeOp(matrix, mLowerTriangular, mUpperTriangular)); } ~IncompleteCholeskyPreconditioner() override = default; bool isValid() const override { return mPassedCompatibilityCondition; } void apply(const Vector& rVec, Vector& zVec) override { if (!mPassedCompatibilityCondition) { OPENVDB_THROW(ArithmeticError, "invalid Cholesky decomposition"); } // Solve mUpperTriangular * mLowerTriangular * rVec = zVec; SizeType size = mLowerTriangular.numRows(); zVec.fill(zeroVal()); ValueType* zData = zVec.data(); if (size == 0) return; assert(rVec.size() == size); assert(zVec.size() == size); // Allocate a temp vector mTempVec.fill(zeroVal()); ValueType* tmpData = mTempVec.data(); const ValueType* rData = rVec.data(); // Solve mLowerTriangular * tmp = rVec; for (SizeType i = 0; i < size; ++i) { typename TriangularMatrix::ConstRow row = mLowerTriangular.getConstRow(i); ValueType diagonal = row.getValue(i); ValueType dot = row.dot(mTempVec); tmpData[i] = (rData[i] - dot) / diagonal; if (!std::isfinite(tmpData[i])) { OPENVDB_LOG_DEBUG_RUNTIME("1 diagonal was " << diagonal); OPENVDB_LOG_DEBUG_RUNTIME("1a diagonal " << row.getValue(i)); } } // Solve mUpperTriangular * zVec = tmp; for (SizeType ii = 0; ii < size; ++ii) { SizeType i = size - 1 - ii; typename TriangularMatrix::ConstRow row = mUpperTriangular.getConstRow(i); ValueType diagonal = row.getValue(i); ValueType dot = row.dot(zVec); zData[i] = (tmpData[i] - dot) / diagonal; if (!std::isfinite(zData[i])) { OPENVDB_LOG_DEBUG_RUNTIME("2 diagonal was " << diagonal); } } } const TriangularMatrix& lowerMatrix() const { return mLowerTriangular; } const TriangularMatrix& upperMatrix() const { return mUpperTriangular; } private: // Functor for use with tbb::parallel_for() struct CopyToLowerOp { CopyToLowerOp(const MatrixType& m, TriangularMatrix& l): mat(&m), lower(&l) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { typename TriangularMatrix::RowEditor outRow = lower->getRowEditor(n); outRow.clear(); typename MatrixType::ConstRow inRow = mat->getConstRow(n); for (typename MatrixType::ConstValueIter it = inRow.cbegin(); it; ++it) { if (it.column() > n) continue; // skip above diagonal outRow.setValue(it.column(), *it); } } } const MatrixType* mat; TriangularMatrix* lower; }; // Functor for use with tbb::parallel_for() struct TransposeOp { TransposeOp(const MatrixType& m, const TriangularMatrix& l, TriangularMatrix& u): mat(&m), lower(&l), upper(&u) {} void operator()(const SizeRange& range) const { for (SizeType n = range.begin(), N = range.end(); n < N; ++n) { typename TriangularMatrix::RowEditor outRow = upper->getRowEditor(n); outRow.clear(); // Use the fact that matrix is symmetric. typename MatrixType::ConstRow inRow = mat->getConstRow(n); for (typename MatrixType::ConstValueIter it = inRow.cbegin(); it; ++it) { const SizeType column = it.column(); if (column < n) continue; // only set upper triangle outRow.setValue(column, lower->getValue(column, n)); } } } const MatrixType* mat; const TriangularMatrix* lower; TriangularMatrix* upper; }; TriangularMatrix mLowerTriangular; TriangularMatrix mUpperTriangular; Vector mTempVec; bool mPassedCompatibilityCondition; }; // class IncompleteCholeskyPreconditioner //////////////////////////////////////// namespace internal { /// Compute @e ax + @e y. template inline void axpy(const T& a, const T* xVec, const T* yVec, T* resultVec, SizeType size) { tbb::parallel_for(SizeRange(0, size), LinearOp(a, xVec, yVec, resultVec)); } /// Compute @e ax + @e y. template inline void axpy(const T& a, const Vector& xVec, const Vector& yVec, Vector& result) { assert(xVec.size() == yVec.size()); assert(xVec.size() == result.size()); axpy(a, xVec.data(), yVec.data(), result.data(), xVec.size()); } /// Compute @e r = @e b − @e Ax. template inline void computeResidual(const MatrixOperator& A, const VecValueType* x, const VecValueType* b, VecValueType* r) { // Compute r = A * x. A.vectorMultiply(x, r); // Compute r = b - r. tbb::parallel_for(SizeRange(0, A.numRows()), LinearOp(-1.0, r, b, r)); } /// Compute @e r = @e b − @e Ax. template inline void computeResidual(const MatrixOperator& A, const Vector& x, const Vector& b, Vector& r) { assert(x.size() == b.size()); assert(x.size() == r.size()); assert(x.size() == A.numRows()); computeResidual(A, x.data(), b.data(), r.data()); } } // namespace internal //////////////////////////////////////// template inline State solve( const PositiveDefMatrix& Amat, const Vector& bVec, Vector& xVec, Preconditioner& precond, const State& termination) { util::NullInterrupter interrupter; return solve(Amat, bVec, xVec, precond, interrupter, termination); } template inline State solve( const PositiveDefMatrix& Amat, const Vector& bVec, Vector& xVec, Preconditioner& precond, Interrupter& interrupter, const State& termination) { using ValueType = typename PositiveDefMatrix::ValueType; using VectorType = Vector; State result; result.success = false; result.iterations = 0; result.relativeError = 0.0; result.absoluteError = 0.0; const SizeType size = Amat.numRows(); if (size == 0) { OPENVDB_LOG_WARN("pcg::solve(): matrix has dimension zero"); return result; } if (size != bVec.size()) { OPENVDB_THROW(ArithmeticError, "A and b have incompatible sizes" << size << "x" << size << " vs. " << bVec.size() << ")"); } if (size != xVec.size()) { OPENVDB_THROW(ArithmeticError, "A and x have incompatible sizes" << size << "x" << size << " vs. " << xVec.size() << ")"); } // Temp vectors VectorType zVec(size); // transformed residual (M^-1 r) VectorType pVec(size); // search direction VectorType qVec(size); // A * p // Compute norm of B (the source) const ValueType tmp = bVec.infNorm(); const ValueType infNormOfB = isZero(tmp) ? ValueType(1) : tmp; // Compute rVec: residual = b - Ax. VectorType rVec(size); // vector of residuals internal::computeResidual(Amat, xVec, bVec, rVec); assert(rVec.isFinite()); // Normalize the residual norm with the source norm and look for early out. result.absoluteError = static_cast(rVec.infNorm()); result.relativeError = static_cast(result.absoluteError / infNormOfB); if (result.relativeError <= termination.relativeError) { result.success = true; return result; } // Iterations of the CG solve ValueType rDotZPrev(1); // inner product of // Keep track of the minimum error to monitor convergence. ValueType minL2Error = std::numeric_limits::max(); ValueType l2Error; int iteration = 0; for ( ; iteration < termination.iterations; ++iteration) { if (interrupter.wasInterrupted()) { OPENVDB_THROW(RuntimeError, "conjugate gradient solver was interrupted"); } OPENVDB_LOG_DEBUG_RUNTIME("pcg::solve() " << result); result.iterations = iteration + 1; // Apply preconditioner to residual // z_{k} = M^-1 r_{k} precond.apply(rVec, zVec); // const ValueType rDotZ = rVec.dot(zVec); assert(std::isfinite(rDotZ)); if (0 == iteration) { // Initialize pVec = zVec; } else { const ValueType beta = rDotZ / rDotZPrev; // p = beta * p + z internal::axpy(beta, pVec, zVec, /*result */pVec); } // q_{k} = A p_{k} Amat.vectorMultiply(pVec, qVec); // alpha = / const ValueType pAp = pVec.dot(qVec); assert(std::isfinite(pAp)); const ValueType alpha = rDotZ / pAp; rDotZPrev = rDotZ; // x_{k} = x_{k-1} + alpha * p_{k} internal::axpy(alpha, pVec, xVec, /*result=*/xVec); // r_{k} = r_{k-1} - alpha_{k-1} A p_{k} internal::axpy(-alpha, qVec, rVec, /*result=*/rVec); // update tolerances l2Error = rVec.l2Norm(); minL2Error = Min(l2Error, minL2Error); result.absoluteError = static_cast(rVec.infNorm()); result.relativeError = static_cast(result.absoluteError / infNormOfB); if (l2Error > 2 * minL2Error) { // The solution started to diverge. result.success = false; break; } if (!std::isfinite(result.absoluteError)) { // Total divergence of solution result.success = false; break; } if (result.absoluteError <= termination.absoluteError) { // Convergence result.success = true; break; } if (result.relativeError <= termination.relativeError) { // Convergence result.success = true; break; } } OPENVDB_LOG_DEBUG_RUNTIME("pcg::solve() " << result); return result; } } // namespace pcg } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_CONJGRADIENT_HAS_BEEN_INCLUDED /////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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/math/QuantizedUnitVec.cc0000644000000000000000000042567613200122377015734 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { // The table below is generated as follows: // // uint16_t xbits, ybits; // double x, y, z, w; // for (uint16_t b = 0; b < 8192; ++b) { // xbits = uint16_t((b & MASK_XSLOT) >> 7); // ybits = b & MASK_YSLOT; // if ((xbits + ybits) > 126) { // xbits = uint16_t(127 - xbits); // ybits = uint16_t(127 - ybits); // } // x = double(xbits); // y = double(ybits); // z = double(126 - xbits - ybits); // w = 1.0 / std::sqrt(x*x + y*y + z*z); // sNormalizationWeights[b] = float(w); // } float QuantizedUnitVec::sNormalizationWeights[8192] = { 0.007936508395f, 0.007999744266f, 0.008063467219f, 0.008127664216f, 0.008192319423f, 0.008257416077f, 0.008322936483f, 0.008388860151f, 0.008455166593f, 0.008521833457f, 0.008588834666f, 0.008656143211f, 0.008723732084f, 0.00879156962f, 0.008859624155f, 0.008927859366f, 0.008996240795f, 0.009064726532f, 0.009133277461f, 0.009201847948f, 0.00927039329f, 0.009338863194f, 0.009407208301f, 0.009475374594f, 0.009543305263f, 0.009610942565f, 0.009678225033f, 0.009745089337f, 0.009811468422f, 0.009877296165f, 0.009942499921f, 0.01000700705f, 0.01007074397f, 0.01013363153f, 0.01019559242f, 0.01025654469f, 0.01031640731f, 0.01037509646f, 0.01043252647f, 0.01048861258f, 0.01054326911f, 0.01059640758f, 0.01064794231f, 0.01069778763f, 0.01074585691f, 0.01079206541f, 0.01083632838f, 0.01087856572f, 0.01091869641f, 0.01095664315f, 0.01099232957f, 0.01102568675f, 0.01105664484f, 0.01108513959f, 0.01111111138f, 0.01113450434f, 0.01115526911f, 0.01117335912f, 0.01118873432f, 0.01120136213f, 0.01121121366f, 0.01121826563f, 0.01122250315f, 0.0112239169f, 0.01122250315f, 0.01121826563f, 0.01121121366f, 0.01120136213f, 0.01118873432f, 0.01117335912f, 0.01115526911f, 0.01113450434f, 0.01111111138f, 0.01108513959f, 0.01105664484f, 0.01102568675f, 0.01099232957f, 0.01095664315f, 0.01091869641f, 0.01087856572f, 0.01083632838f, 0.01079206541f, 0.01074585691f, 0.01069778763f, 0.01064794231f, 0.01059640758f, 0.01054326911f, 0.01048861258f, 0.01043252647f, 0.01037509646f, 0.01031640731f, 0.01025654469f, 0.01019559242f, 0.01013363153f, 0.01007074397f, 0.01000700705f, 0.009942499921f, 0.009877296165f, 0.009811468422f, 0.009745089337f, 0.009678225033f, 0.009610942565f, 0.009543305263f, 0.009475374594f, 0.009407208301f, 0.009338863194f, 0.00927039329f, 0.009201847948f, 0.009133277461f, 0.009064726532f, 0.008996240795f, 0.008927859366f, 0.008859624155f, 0.00879156962f, 0.008723732084f, 0.008656143211f, 0.008588834666f, 0.008521833457f, 0.008455166593f, 0.008388860151f, 0.008322936483f, 0.008257416077f, 0.008192319423f, 0.008127664216f, 0.008063467219f, 0.007999744266f, 0.007936508395f, 0.007873771712f, 0.007999744266f, 0.008063991554f, 0.008128738031f, 0.008193968795f, 0.008259668946f, 0.008325820789f, 0.008392404765f, 0.008459401317f, 0.008526788093f, 0.00859454181f, 0.008662636392f, 0.008731043898f, 0.008799735457f, 0.008868678473f, 0.008937839419f, 0.009007181972f, 0.00907666795f, 0.009146256372f, 0.009215905331f, 0.009285567328f, 0.009355195798f, 0.009424740449f, 0.009494146332f, 0.009563359432f, 0.009632320143f, 0.009700968862f, 0.009769240394f, 0.00983707048f, 0.00990438927f, 0.009971125983f, 0.01003720704f, 0.01010255609f, 0.01016709674f, 0.01023074798f, 0.01029342785f, 0.01035505254f, 0.01041553635f, 0.01047479361f, 0.01053273678f, 0.01058927551f, 0.01064432319f, 0.01069778763f, 0.01074958127f, 0.01079961471f, 0.01084779948f, 0.01089404803f, 0.01093827467f, 0.0109803956f, 0.01102032885f, 0.01105799619f, 0.01109332126f, 0.0111262314f, 0.01115665678f, 0.01118453499f, 0.01120980456f, 0.01123241056f, 0.01125230361f, 0.01126943901f, 0.01128377859f, 0.01129528973f, 0.01130394638f, 0.01130972803f, 0.01131262258f, 0.01131262258f, 0.01130972803f, 0.01130394638f, 0.01129528973f, 0.01128377859f, 0.01126943901f, 0.01125230361f, 0.01123241056f, 0.01120980456f, 0.01118453499f, 0.01115665678f, 0.0111262314f, 0.01109332126f, 0.01105799619f, 0.01102032885f, 0.0109803956f, 0.01093827467f, 0.01089404803f, 0.01084779948f, 0.01079961471f, 0.01074958127f, 0.01069778763f, 0.01064432319f, 0.01058927551f, 0.01053273678f, 0.01047479361f, 0.01041553635f, 0.01035505254f, 0.01029342785f, 0.01023074798f, 0.01016709674f, 0.01010255609f, 0.01003720704f, 0.009971125983f, 0.00990438927f, 0.00983707048f, 0.009769240394f, 0.009700968862f, 0.009632320143f, 0.009563359432f, 0.009494146332f, 0.009424740449f, 0.009355195798f, 0.009285567328f, 0.009215905331f, 0.009146256372f, 0.00907666795f, 0.009007181972f, 0.008937839419f, 0.008868678473f, 0.008799735457f, 0.008731043898f, 0.008662636392f, 0.00859454181f, 0.008526788093f, 0.008459401317f, 0.008392404765f, 0.008325820789f, 0.008259668946f, 0.008193968795f, 0.008128738031f, 0.008063991554f, 0.007999744266f, 0.007936008275f, 0.007936508395f, 0.008063467219f, 0.008128738031f, 0.008194519207f, 0.008260795847f, 0.008327552117f, 0.008394770324f, 0.008462429978f, 0.008530510589f, 0.008598989807f, 0.008667841554f, 0.008737040684f, 0.008806557395f, 0.008876360953f, 0.008946419694f, 0.009016696364f, 0.009087154642f, 0.009157754481f, 0.009228453971f, 0.009299207479f, 0.009369968437f, 0.009440686554f, 0.009511308745f, 0.009581780061f, 0.00965204183f, 0.009722034447f, 0.009791694582f, 0.009860955179f, 0.009929747321f, 0.009998000227f, 0.01006564032f, 0.01013259124f, 0.01019877382f, 0.01026410609f, 0.01032850612f, 0.01039188914f, 0.01045416761f, 0.01051525306f, 0.01057505608f, 0.01063348539f, 0.01069044974f, 0.01074585691f, 0.01079961471f, 0.01085163094f, 0.0109018134f, 0.01095007174f, 0.01099631656f, 0.01104046032f, 0.0110824164f, 0.01112210099f, 0.01115943585f, 0.01119434182f, 0.01122674625f, 0.01125658024f, 0.01128377859f, 0.01130828168f, 0.01133003552f, 0.01134899072f, 0.01136510447f, 0.01137833856f, 0.01138866507f, 0.0113960579f, 0.01140050031f, 0.01140198205f, 0.01140050031f, 0.0113960579f, 0.01138866507f, 0.01137833856f, 0.01136510447f, 0.01134899072f, 0.01133003552f, 0.01130828168f, 0.01128377859f, 0.01125658024f, 0.01122674625f, 0.01119434182f, 0.01115943585f, 0.01112210099f, 0.0110824164f, 0.01104046032f, 0.01099631656f, 0.01095007174f, 0.0109018134f, 0.01085163094f, 0.01079961471f, 0.01074585691f, 0.01069044974f, 0.01063348539f, 0.01057505608f, 0.01051525306f, 0.01045416761f, 0.01039188914f, 0.01032850612f, 0.01026410609f, 0.01019877382f, 0.01013259124f, 0.01006564032f, 0.009998000227f, 0.009929747321f, 0.009860955179f, 0.009791694582f, 0.009722034447f, 0.00965204183f, 0.009581780061f, 0.009511308745f, 0.009440686554f, 0.009369968437f, 0.009299207479f, 0.009228453971f, 0.009157754481f, 0.009087154642f, 0.009016696364f, 0.008946419694f, 0.008876360953f, 0.008806557395f, 0.008737040684f, 0.008667841554f, 0.008598989807f, 0.008530510589f, 0.008462429978f, 0.008394770324f, 0.008327552117f, 0.008260795847f, 0.008194519207f, 0.008128738031f, 0.008063467219f, 0.007998720743f, 0.007999744266f, 0.007999744266f, 0.008127664216f, 0.008193968795f, 0.008260795847f, 0.008328129537f, 0.008395953104f, 0.008464248851f, 0.008532994427f, 0.008602170274f, 0.008671752177f, 0.008741713129f, 0.008812026121f, 0.008882662281f, 0.008953588083f, 0.00902477093f, 0.009096172638f, 0.009167755023f, 0.009239477105f, 0.009311294183f, 0.009383158758f, 0.009455023333f, 0.009526834823f, 0.00959853828f, 0.009670076892f, 0.009741389193f, 0.009812413715f, 0.009883082472f, 0.009953328408f, 0.01002307981f, 0.01009226125f, 0.01016079728f, 0.01022860687f, 0.01029560994f, 0.01036172081f, 0.01042685378f, 0.01049092133f, 0.01055383217f, 0.01061549596f, 0.01067581866f, 0.01073470619f, 0.01079206541f, 0.01084779948f, 0.0109018134f, 0.01095401309f, 0.01100430358f, 0.01105259173f, 0.01109878626f, 0.01114279591f, 0.01118453499f, 0.0112239169f, 0.01126086153f, 0.01129528973f, 0.01132712793f, 0.01135630626f, 0.01138276048f, 0.01140643191f, 0.01142726559f, 0.01144521404f, 0.01146023627f, 0.01147229597f, 0.01148136612f, 0.0114874253f, 0.01149045862f, 0.01149045862f, 0.0114874253f, 0.01148136612f, 0.01147229597f, 0.01146023627f, 0.01144521404f, 0.01142726559f, 0.01140643191f, 0.01138276048f, 0.01135630626f, 0.01132712793f, 0.01129528973f, 0.01126086153f, 0.0112239169f, 0.01118453499f, 0.01114279591f, 0.01109878626f, 0.01105259173f, 0.01100430358f, 0.01095401309f, 0.0109018134f, 0.01084779948f, 0.01079206541f, 0.01073470619f, 0.01067581866f, 0.01061549596f, 0.01055383217f, 0.01049092133f, 0.01042685378f, 0.01036172081f, 0.01029560994f, 0.01022860687f, 0.01016079728f, 0.01009226125f, 0.01002307981f, 0.009953328408f, 0.009883082472f, 0.009812413715f, 0.009741389193f, 0.009670076892f, 0.00959853828f, 0.009526834823f, 0.009455023333f, 0.009383158758f, 0.009311294183f, 0.009239477105f, 0.009167755023f, 0.009096172638f, 0.00902477093f, 0.008953588083f, 0.008882662281f, 0.008812026121f, 0.008741713129f, 0.008671752177f, 0.008602170274f, 0.008532994427f, 0.008464248851f, 0.008395953104f, 0.008328129537f, 0.008260795847f, 0.008193968795f, 0.008127664216f, 0.008061895147f, 0.008063467219f, 0.008063991554f, 0.008063467219f, 0.008192319423f, 0.008259668946f, 0.008327552117f, 0.008395953104f, 0.008464855142f, 0.008534237742f, 0.008604080416f, 0.008674361743f, 0.008745054714f, 0.008816135116f, 0.008887572214f, 0.008959336206f, 0.009031393565f, 0.009103707969f, 0.009176243097f, 0.009248957038f, 0.009321806021f, 0.009394746274f, 0.009467727505f, 0.009540699422f, 0.009613607079f, 0.009686394595f, 0.009759000503f, 0.009831363335f, 0.0099034179f, 0.009975093417f, 0.01004632004f, 0.01011702232f, 0.0101871239f, 0.01025654469f, 0.01032520272f, 0.01039301138f, 0.010459885f, 0.0105257323f, 0.01059046388f, 0.0106539838f, 0.01071619987f, 0.01077701338f, 0.01083632838f, 0.01089404803f, 0.01095007174f, 0.01100430358f, 0.01105664484f, 0.01110699773f, 0.01115526911f, 0.01120136213f, 0.01124518644f, 0.01128665265f, 0.01132567506f, 0.01136216894f, 0.0113960579f, 0.01142726559f, 0.01145572308f, 0.01148136612f, 0.01150413603f, 0.01152398065f, 0.01154085156f, 0.0115547115f, 0.01156552508f, 0.0115732681f, 0.01157792099f, 0.0115794735f, 0.01157792099f, 0.0115732681f, 0.01156552508f, 0.0115547115f, 0.01154085156f, 0.01152398065f, 0.01150413603f, 0.01148136612f, 0.01145572308f, 0.01142726559f, 0.0113960579f, 0.01136216894f, 0.01132567506f, 0.01128665265f, 0.01124518644f, 0.01120136213f, 0.01115526911f, 0.01110699773f, 0.01105664484f, 0.01100430358f, 0.01095007174f, 0.01089404803f, 0.01083632838f, 0.01077701338f, 0.01071619987f, 0.0106539838f, 0.01059046388f, 0.0105257323f, 0.010459885f, 0.01039301138f, 0.01032520272f, 0.01025654469f, 0.0101871239f, 0.01011702232f, 0.01004632004f, 0.009975093417f, 0.0099034179f, 0.009831363335f, 0.009759000503f, 0.009686394595f, 0.009613607079f, 0.009540699422f, 0.009467727505f, 0.009394746274f, 0.009321806021f, 0.009248957038f, 0.009176243097f, 0.009103707969f, 0.009031393565f, 0.008959336206f, 0.008887572214f, 0.008816135116f, 0.008745054714f, 0.008674361743f, 0.008604080416f, 0.008534237742f, 0.008464855142f, 0.008395953104f, 0.008327552117f, 0.008259668946f, 0.008192319423f, 0.008125517517f, 0.008127664216f, 0.008128738031f, 0.008128738031f, 0.008127664216f, 0.008257416077f, 0.008325820789f, 0.008394770324f, 0.008464248851f, 0.008534237742f, 0.008604717441f, 0.008675667457f, 0.008747061715f, 0.00881887693f, 0.008891084231f, 0.008963654749f, 0.009036554955f, 0.00910975039f, 0.009183204733f, 0.009256878868f, 0.009330729023f, 0.009404712357f, 0.00947877951f, 0.00955288019f, 0.009626962245f, 0.009700968862f, 0.009774839506f, 0.009848512709f, 0.009921924211f, 0.009995004162f, 0.01006768085f, 0.01013988163f, 0.01021152735f, 0.01028253883f, 0.01035283227f, 0.0104223229f, 0.01049092133f, 0.01055853814f, 0.01062507927f, 0.01069044974f, 0.0107545536f, 0.01081729215f, 0.01087856572f, 0.01093827467f, 0.01099631656f, 0.01105259173f, 0.01110699773f, 0.01115943585f, 0.01120980456f, 0.01125800703f, 0.01130394638f, 0.01134752948f, 0.01138866507f, 0.01142726559f, 0.01146324724f, 0.01149653178f, 0.01152704284f, 0.0115547115f, 0.0115794735f, 0.01160127111f, 0.01162005402f, 0.01163577568f, 0.01164839976f, 0.01165789459f, 0.0116642369f, 0.01166741177f, 0.01166741177f, 0.0116642369f, 0.01165789459f, 0.01164839976f, 0.01163577568f, 0.01162005402f, 0.01160127111f, 0.0115794735f, 0.0115547115f, 0.01152704284f, 0.01149653178f, 0.01146324724f, 0.01142726559f, 0.01138866507f, 0.01134752948f, 0.01130394638f, 0.01125800703f, 0.01120980456f, 0.01115943585f, 0.01110699773f, 0.01105259173f, 0.01099631656f, 0.01093827467f, 0.01087856572f, 0.01081729215f, 0.0107545536f, 0.01069044974f, 0.01062507927f, 0.01055853814f, 0.01049092133f, 0.0104223229f, 0.01035283227f, 0.01028253883f, 0.01021152735f, 0.01013988163f, 0.01006768085f, 0.009995004162f, 0.009921924211f, 0.009848512709f, 0.009774839506f, 0.009700968862f, 0.009626962245f, 0.00955288019f, 0.00947877951f, 0.009404712357f, 0.009330729023f, 0.009256878868f, 0.009183204733f, 0.00910975039f, 0.009036554955f, 0.008963654749f, 0.008891084231f, 0.00881887693f, 0.008747061715f, 0.008675667457f, 0.008604717441f, 0.008534237742f, 0.008464248851f, 0.008394770324f, 0.008325820789f, 0.008257416077f, 0.00818957109f, 0.008192319423f, 0.008193968795f, 0.008194519207f, 0.008193968795f, 0.008192319423f, 0.008322936483f, 0.008392404765f, 0.008462429978f, 0.008532994427f, 0.008604080416f, 0.008675667457f, 0.008747731335f, 0.008820248768f, 0.008893193677f, 0.008966536261f, 0.009040246718f, 0.009114289656f, 0.009188630618f, 0.009263231419f, 0.009338049218f, 0.009413041174f, 0.009488161653f, 0.009563359432f, 0.009638582356f, 0.009713775478f, 0.009788879193f, 0.009863832965f, 0.009938570671f, 0.01001302525f, 0.01008712593f, 0.01016079728f, 0.01023396198f, 0.01030653995f, 0.01037844829f, 0.01044960041f, 0.01051990688f, 0.01058927551f, 0.0106576141f, 0.01072482392f, 0.01079080813f, 0.01085546613f, 0.01091869641f, 0.0109803956f, 0.01104046032f, 0.01109878626f, 0.01115526911f, 0.01120980456f, 0.01126228925f, 0.01131262258f, 0.01136070304f, 0.01140643191f, 0.01144971419f, 0.01149045862f, 0.01152857486f, 0.01156397816f, 0.01159659028f, 0.01162633486f, 0.01165314391f, 0.01167695317f, 0.01169770677f, 0.0117153544f, 0.0117298523f, 0.011741166f, 0.01174926758f, 0.0117541356f, 0.01175575983f, 0.0117541356f, 0.01174926758f, 0.011741166f, 0.0117298523f, 0.0117153544f, 0.01169770677f, 0.01167695317f, 0.01165314391f, 0.01162633486f, 0.01159659028f, 0.01156397816f, 0.01152857486f, 0.01149045862f, 0.01144971419f, 0.01140643191f, 0.01136070304f, 0.01131262258f, 0.01126228925f, 0.01120980456f, 0.01115526911f, 0.01109878626f, 0.01104046032f, 0.0109803956f, 0.01091869641f, 0.01085546613f, 0.01079080813f, 0.01072482392f, 0.0106576141f, 0.01058927551f, 0.01051990688f, 0.01044960041f, 0.01037844829f, 0.01030653995f, 0.01023396198f, 0.01016079728f, 0.01008712593f, 0.01001302525f, 0.009938570671f, 0.009863832965f, 0.009788879193f, 0.009713775478f, 0.009638582356f, 0.009563359432f, 0.009488161653f, 0.009413041174f, 0.009338049218f, 0.009263231419f, 0.009188630618f, 0.009114289656f, 0.009040246718f, 0.008966536261f, 0.008893193677f, 0.008820248768f, 0.008747731335f, 0.008675667457f, 0.008604080416f, 0.008532994427f, 0.008462429978f, 0.008392404765f, 0.008322936483f, 0.008254040033f, 0.008257416077f, 0.008259668946f, 0.008260795847f, 0.008260795847f, 0.008259668946f, 0.008257416077f, 0.008388860151f, 0.008459401317f, 0.008530510589f, 0.008602170274f, 0.008674361743f, 0.008747061715f, 0.008820248768f, 0.008893896826f, 0.00896797888f, 0.009042463265f, 0.00911732018f, 0.009192512371f, 0.009268003516f, 0.0093437545f, 0.00941972062f, 0.009495858103f, 0.009572117589f, 0.009648446925f, 0.009724793024f, 0.009801096283f, 0.009877296165f, 0.009953328408f, 0.01002912689f, 0.01010461897f, 0.01017973199f, 0.01025438774f, 0.01032850612f, 0.01040200423f, 0.01047479361f, 0.01054678671f, 0.01061788946f, 0.01068800688f, 0.0107570421f, 0.01082489453f, 0.01089146268f, 0.01095664315f, 0.01102032885f, 0.0110824164f, 0.01114279591f, 0.01120136213f, 0.01125800703f, 0.01131262258f, 0.01136510447f, 0.01141534653f, 0.01146324724f, 0.01150870696f, 0.01155162696f, 0.01159191411f, 0.01162947994f, 0.0116642369f, 0.01169610675f, 0.01172501314f, 0.01175088901f, 0.01177367195f, 0.0117933061f, 0.01180974208f, 0.01182294171f, 0.01183286961f, 0.01183950249f, 0.01184282266f, 0.01184282266f, 0.01183950249f, 0.01183286961f, 0.01182294171f, 0.01180974208f, 0.0117933061f, 0.01177367195f, 0.01175088901f, 0.01172501314f, 0.01169610675f, 0.0116642369f, 0.01162947994f, 0.01159191411f, 0.01155162696f, 0.01150870696f, 0.01146324724f, 0.01141534653f, 0.01136510447f, 0.01131262258f, 0.01125800703f, 0.01120136213f, 0.01114279591f, 0.0110824164f, 0.01102032885f, 0.01095664315f, 0.01089146268f, 0.01082489453f, 0.0107570421f, 0.01068800688f, 0.01061788946f, 0.01054678671f, 0.01047479361f, 0.01040200423f, 0.01032850612f, 0.01025438774f, 0.01017973199f, 0.01010461897f, 0.01002912689f, 0.009953328408f, 0.009877296165f, 0.009801096283f, 0.009724793024f, 0.009648446925f, 0.009572117589f, 0.009495858103f, 0.00941972062f, 0.0093437545f, 0.009268003516f, 0.009192512371f, 0.00911732018f, 0.009042463265f, 0.00896797888f, 0.008893896826f, 0.008820248768f, 0.008747061715f, 0.008674361743f, 0.008602170274f, 0.008530510589f, 0.008459401317f, 0.008388860151f, 0.008318902925f, 0.008322936483f, 0.008325820789f, 0.008327552117f, 0.008328129537f, 0.008327552117f, 0.008325820789f, 0.008322936483f, 0.008455166593f, 0.008526788093f, 0.008598989807f, 0.008671752177f, 0.008745054714f, 0.00881887693f, 0.008893193677f, 0.00896797888f, 0.009043202735f, 0.009118835442f, 0.009194843471f, 0.00927118957f, 0.009347835556f, 0.009424740449f, 0.009501857683f, 0.009579141624f, 0.009656541049f, 0.009734002873f, 0.009811468422f, 0.009888879955f, 0.009966172278f, 0.01004327927f, 0.01012013014f, 0.01019665226f, 0.01027276739f, 0.01034839638f, 0.01042345539f, 0.01049785595f, 0.0105715096f, 0.01064432319f, 0.01071619987f, 0.01078704093f, 0.01085674576f, 0.01092521101f, 0.01099232957f, 0.01105799619f, 0.01112210099f, 0.01118453499f, 0.01124518644f, 0.01130394638f, 0.01136070304f, 0.01141534653f, 0.01146776881f, 0.01151786372f, 0.01156552508f, 0.01161065139f, 0.01165314391f, 0.01169290766f, 0.0117298523f, 0.01176389214f, 0.01179494616f, 0.01182294171f, 0.01184780896f, 0.01186948828f, 0.01188792568f, 0.0119030755f, 0.01191489771f, 0.01192336436f, 0.01192845311f, 0.01193015091f, 0.01192845311f, 0.01192336436f, 0.01191489771f, 0.0119030755f, 0.01188792568f, 0.01186948828f, 0.01184780896f, 0.01182294171f, 0.01179494616f, 0.01176389214f, 0.0117298523f, 0.01169290766f, 0.01165314391f, 0.01161065139f, 0.01156552508f, 0.01151786372f, 0.01146776881f, 0.01141534653f, 0.01136070304f, 0.01130394638f, 0.01124518644f, 0.01118453499f, 0.01112210099f, 0.01105799619f, 0.01099232957f, 0.01092521101f, 0.01085674576f, 0.01078704093f, 0.01071619987f, 0.01064432319f, 0.0105715096f, 0.01049785595f, 0.01042345539f, 0.01034839638f, 0.01027276739f, 0.01019665226f, 0.01012013014f, 0.01004327927f, 0.009966172278f, 0.009888879955f, 0.009811468422f, 0.009734002873f, 0.009656541049f, 0.009579141624f, 0.009501857683f, 0.009424740449f, 0.009347835556f, 0.00927118957f, 0.009194843471f, 0.009118835442f, 0.009043202735f, 0.00896797888f, 0.008893193677f, 0.00881887693f, 0.008745054714f, 0.008671752177f, 0.008598989807f, 0.008526788093f, 0.008455166593f, 0.00838414114f, 0.008388860151f, 0.008392404765f, 0.008394770324f, 0.008395953104f, 0.008395953104f, 0.008394770324f, 0.008392404765f, 0.008388860151f, 0.008521833457f, 0.00859454181f, 0.008667841554f, 0.008741713129f, 0.008816135116f, 0.008891084231f, 0.008966536261f, 0.009042463265f, 0.009118835442f, 0.009195621125f, 0.009272783995f, 0.009350287728f, 0.009428090416f, 0.009506150149f, 0.009584420361f, 0.00966285076f, 0.009741389193f, 0.009819980711f, 0.009898564778f, 0.009977078997f, 0.01005545817f, 0.01013363153f, 0.01021152735f, 0.0102890674f, 0.01036617346f, 0.01044276077f, 0.01051874273f, 0.01059402898f, 0.01066852547f, 0.01074213628f, 0.01081476174f, 0.01088629849f, 0.01095664315f, 0.01102568675f, 0.01109332126f, 0.01115943585f, 0.0112239169f, 0.01128665265f, 0.01134752948f, 0.01140643191f, 0.01146324724f, 0.01151786372f, 0.01157016866f, 0.01162005402f, 0.01166741177f, 0.01171213947f, 0.0117541356f, 0.0117933061f, 0.0118295569f, 0.01186280511f, 0.01189296879f, 0.01191997528f, 0.01194375753f, 0.01196425594f, 0.01198141929f, 0.01199520286f, 0.01200557221f, 0.01201249938f, 0.01201596763f, 0.01201596763f, 0.01201249938f, 0.01200557221f, 0.01199520286f, 0.01198141929f, 0.01196425594f, 0.01194375753f, 0.01191997528f, 0.01189296879f, 0.01186280511f, 0.0118295569f, 0.0117933061f, 0.0117541356f, 0.01171213947f, 0.01166741177f, 0.01162005402f, 0.01157016866f, 0.01151786372f, 0.01146324724f, 0.01140643191f, 0.01134752948f, 0.01128665265f, 0.0112239169f, 0.01115943585f, 0.01109332126f, 0.01102568675f, 0.01095664315f, 0.01088629849f, 0.01081476174f, 0.01074213628f, 0.01066852547f, 0.01059402898f, 0.01051874273f, 0.01044276077f, 0.01036617346f, 0.0102890674f, 0.01021152735f, 0.01013363153f, 0.01005545817f, 0.009977078997f, 0.009898564778f, 0.009819980711f, 0.009741389193f, 0.00966285076f, 0.009584420361f, 0.009506150149f, 0.009428090416f, 0.009350287728f, 0.009272783995f, 0.009195621125f, 0.009118835442f, 0.009042463265f, 0.008966536261f, 0.008891084231f, 0.008816135116f, 0.008741713129f, 0.008667841554f, 0.00859454181f, 0.008521833457f, 0.008449732326f, 0.008455166593f, 0.008459401317f, 0.008462429978f, 0.008464248851f, 0.008464855142f, 0.008464248851f, 0.008462429978f, 0.008459401317f, 0.008455166593f, 0.008588834666f, 0.008662636392f, 0.008737040684f, 0.008812026121f, 0.008887572214f, 0.008963654749f, 0.009040246718f, 0.00911732018f, 0.009194843471f, 0.009272783995f, 0.009351104498f, 0.009429766797f, 0.00950872805f, 0.009587943554f, 0.00966736488f, 0.009746940807f, 0.009826615453f, 0.00990633294f, 0.009986029007f, 0.01006564032f, 0.01014509797f, 0.01022432931f, 0.01030325703f, 0.01038180385f, 0.010459885f, 0.01053741388f, 0.01061430015f, 0.01069044974f, 0.01076576579f, 0.01084014867f, 0.01091349311f, 0.01098569483f, 0.01105664484f, 0.0111262314f, 0.01119434182f, 0.01126086153f, 0.01132567506f, 0.01138866507f, 0.01144971419f, 0.01150870696f, 0.01156552508f, 0.01162005402f, 0.01167218015f, 0.01172179077f, 0.01176877879f, 0.01181303803f, 0.01185446698f, 0.01189296879f, 0.01192845311f, 0.0119608324f, 0.01199002843f, 0.01201596763f, 0.01203858573f, 0.01205782313f, 0.01207363233f, 0.01208597142f, 0.01209480781f, 0.01210011914f, 0.01210189145f, 0.01210011914f, 0.01209480781f, 0.01208597142f, 0.01207363233f, 0.01205782313f, 0.01203858573f, 0.01201596763f, 0.01199002843f, 0.0119608324f, 0.01192845311f, 0.01189296879f, 0.01185446698f, 0.01181303803f, 0.01176877879f, 0.01172179077f, 0.01167218015f, 0.01162005402f, 0.01156552508f, 0.01150870696f, 0.01144971419f, 0.01138866507f, 0.01132567506f, 0.01126086153f, 0.01119434182f, 0.0111262314f, 0.01105664484f, 0.01098569483f, 0.01091349311f, 0.01084014867f, 0.01076576579f, 0.01069044974f, 0.01061430015f, 0.01053741388f, 0.010459885f, 0.01038180385f, 0.01030325703f, 0.01022432931f, 0.01014509797f, 0.01006564032f, 0.009986029007f, 0.00990633294f, 0.009826615453f, 0.009746940807f, 0.00966736488f, 0.009587943554f, 0.00950872805f, 0.009429766797f, 0.009351104498f, 0.009272783995f, 0.009194843471f, 0.00911732018f, 0.009040246718f, 0.008963654749f, 0.008887572214f, 0.008812026121f, 0.008737040684f, 0.008662636392f, 0.008588834666f, 0.008515651338f, 0.008521833457f, 0.008526788093f, 0.008530510589f, 0.008532994427f, 0.008534237742f, 0.008534237742f, 0.008532994427f, 0.008530510589f, 0.008526788093f, 0.008521833457f, 0.008656143211f, 0.008731043898f, 0.008806557395f, 0.008882662281f, 0.008959336206f, 0.009036554955f, 0.009114289656f, 0.009192512371f, 0.00927118957f, 0.009350287728f, 0.009429766797f, 0.00950958766f, 0.009589706548f, 0.009670076892f, 0.009750646539f, 0.009831363335f, 0.00991217047f, 0.009993007407f, 0.01007380895f, 0.01015450899f, 0.01023503393f, 0.01031531021f, 0.01039525773f, 0.01047479361f, 0.01055383217f, 0.01063228305f, 0.01071005128f, 0.01078704093f, 0.01086314954f, 0.01093827467f, 0.01101230737f, 0.01108513959f, 0.01115665678f, 0.01122674625f, 0.01129528973f, 0.01136216894f, 0.01142726559f, 0.01149045862f, 0.01155162696f, 0.01161065139f, 0.01166741177f, 0.01172179077f, 0.01177367195f, 0.01182294171f, 0.01186948828f, 0.01191320643f, 0.0119539937f, 0.01199175231f, 0.01202639099f, 0.01205782313f, 0.01208597142f, 0.01211076323f, 0.01213213522f, 0.01215003151f, 0.01216440555f, 0.01217522006f, 0.0121824462f, 0.01218606345f, 0.01218606345f, 0.0121824462f, 0.01217522006f, 0.01216440555f, 0.01215003151f, 0.01213213522f, 0.01211076323f, 0.01208597142f, 0.01205782313f, 0.01202639099f, 0.01199175231f, 0.0119539937f, 0.01191320643f, 0.01186948828f, 0.01182294171f, 0.01177367195f, 0.01172179077f, 0.01166741177f, 0.01161065139f, 0.01155162696f, 0.01149045862f, 0.01142726559f, 0.01136216894f, 0.01129528973f, 0.01122674625f, 0.01115665678f, 0.01108513959f, 0.01101230737f, 0.01093827467f, 0.01086314954f, 0.01078704093f, 0.01071005128f, 0.01063228305f, 0.01055383217f, 0.01047479361f, 0.01039525773f, 0.01031531021f, 0.01023503393f, 0.01015450899f, 0.01007380895f, 0.009993007407f, 0.00991217047f, 0.009831363335f, 0.009750646539f, 0.009670076892f, 0.009589706548f, 0.00950958766f, 0.009429766797f, 0.009350287728f, 0.00927118957f, 0.009192512371f, 0.009114289656f, 0.009036554955f, 0.008959336206f, 0.008882662281f, 0.008806557395f, 0.008731043898f, 0.008656143211f, 0.008581873029f, 0.008588834666f, 0.00859454181f, 0.008598989807f, 0.008602170274f, 0.008604080416f, 0.008604717441f, 0.008604080416f, 0.008602170274f, 0.008598989807f, 0.00859454181f, 0.008588834666f, 0.008723732084f, 0.008799735457f, 0.008876360953f, 0.008953588083f, 0.009031393565f, 0.00910975039f, 0.009188630618f, 0.009268003516f, 0.009347835556f, 0.009428090416f, 0.00950872805f, 0.009589706548f, 0.009670981206f, 0.009752500802f, 0.009834215976f, 0.009916068986f, 0.009998000227f, 0.01007994823f, 0.01016184594f, 0.01024362259f, 0.01032520272f, 0.01040650904f, 0.0104874596f, 0.01056796685f, 0.01064794231f, 0.01072729193f, 0.0108059179f, 0.01088371873f, 0.01096059103f, 0.0110364249f, 0.01111111138f, 0.01118453499f, 0.01125658024f, 0.01132712793f, 0.0113960579f, 0.01146324724f, 0.01152857486f, 0.01159191411f, 0.01165314391f, 0.01171213947f, 0.01176877879f, 0.01182294171f, 0.01187450811f, 0.01192336436f, 0.01196939778f, 0.01201249938f, 0.01205256768f, 0.01208950393f, 0.01212321594f, 0.0121536199f, 0.0121806385f, 0.01220420003f, 0.01222424489f, 0.01224071812f, 0.01225357689f, 0.01226278674f, 0.01226832252f, 0.01227016933f, 0.01226832252f, 0.01226278674f, 0.01225357689f, 0.01224071812f, 0.01222424489f, 0.01220420003f, 0.0121806385f, 0.0121536199f, 0.01212321594f, 0.01208950393f, 0.01205256768f, 0.01201249938f, 0.01196939778f, 0.01192336436f, 0.01187450811f, 0.01182294171f, 0.01176877879f, 0.01171213947f, 0.01165314391f, 0.01159191411f, 0.01152857486f, 0.01146324724f, 0.0113960579f, 0.01132712793f, 0.01125658024f, 0.01118453499f, 0.01111111138f, 0.0110364249f, 0.01096059103f, 0.01088371873f, 0.0108059179f, 0.01072729193f, 0.01064794231f, 0.01056796685f, 0.0104874596f, 0.01040650904f, 0.01032520272f, 0.01024362259f, 0.01016184594f, 0.01007994823f, 0.009998000227f, 0.009916068986f, 0.009834215976f, 0.009752500802f, 0.009670981206f, 0.009589706548f, 0.00950872805f, 0.009428090416f, 0.009347835556f, 0.009268003516f, 0.009188630618f, 0.00910975039f, 0.009031393565f, 0.008953588083f, 0.008876360953f, 0.008799735457f, 0.008723732084f, 0.008648370393f, 0.008656143211f, 0.008662636392f, 0.008667841554f, 0.008671752177f, 0.008674361743f, 0.008675667457f, 0.008675667457f, 0.008674361743f, 0.008671752177f, 0.008667841554f, 0.008662636392f, 0.008656143211f, 0.00879156962f, 0.008868678473f, 0.008946419694f, 0.00902477093f, 0.009103707969f, 0.009183204733f, 0.009263231419f, 0.0093437545f, 0.009424740449f, 0.009506150149f, 0.009587943554f, 0.009670076892f, 0.009752500802f, 0.009835166857f, 0.009918019176f, 0.01000100002f, 0.01008404791f, 0.01016709674f, 0.01025007758f, 0.01033291686f, 0.01041553635f, 0.01049785595f, 0.01057978906f, 0.01066124719f, 0.01074213628f, 0.01082235854f, 0.0109018134f, 0.0109803956f, 0.01105799619f, 0.01113450434f, 0.01120980456f, 0.01128377859f, 0.01135630626f, 0.01142726559f, 0.01149653178f, 0.01156397816f, 0.01162947994f, 0.01169290766f, 0.0117541356f, 0.01181303803f, 0.01186948828f, 0.01192336436f, 0.0119745452f, 0.01202291343f, 0.01206835546f, 0.01211076323f, 0.01215003151f, 0.01218606345f, 0.01221876871f, 0.0122480616f, 0.01227386575f, 0.01229611505f, 0.01231474802f, 0.01232971624f, 0.01234097779f, 0.01234850287f, 0.01235227007f, 0.01235227007f, 0.01234850287f, 0.01234097779f, 0.01232971624f, 0.01231474802f, 0.01229611505f, 0.01227386575f, 0.0122480616f, 0.01221876871f, 0.01218606345f, 0.01215003151f, 0.01211076323f, 0.01206835546f, 0.01202291343f, 0.0119745452f, 0.01192336436f, 0.01186948828f, 0.01181303803f, 0.0117541356f, 0.01169290766f, 0.01162947994f, 0.01156397816f, 0.01149653178f, 0.01142726559f, 0.01135630626f, 0.01128377859f, 0.01120980456f, 0.01113450434f, 0.01105799619f, 0.0109803956f, 0.0109018134f, 0.01082235854f, 0.01074213628f, 0.01066124719f, 0.01057978906f, 0.01049785595f, 0.01041553635f, 0.01033291686f, 0.01025007758f, 0.01016709674f, 0.01008404791f, 0.01000100002f, 0.009918019176f, 0.009835166857f, 0.009752500802f, 0.009670076892f, 0.009587943554f, 0.009506150149f, 0.009424740449f, 0.0093437545f, 0.009263231419f, 0.009183204733f, 0.009103707969f, 0.00902477093f, 0.008946419694f, 0.008868678473f, 0.00879156962f, 0.008715113625f, 0.008723732084f, 0.008731043898f, 0.008737040684f, 0.008741713129f, 0.008745054714f, 0.008747061715f, 0.008747731335f, 0.008747061715f, 0.008745054714f, 0.008741713129f, 0.008737040684f, 0.008731043898f, 0.008723732084f, 0.008859624155f, 0.008937839419f, 0.009016696364f, 0.009096172638f, 0.009176243097f, 0.009256878868f, 0.009338049218f, 0.00941972062f, 0.009501857683f, 0.009584420361f, 0.00966736488f, 0.009750646539f, 0.009834215976f, 0.009918019176f, 0.01000200026f, 0.01008609962f, 0.01017025113f, 0.01025438774f, 0.01033843681f, 0.0104223229f, 0.01050596405f, 0.01058927551f, 0.01067216974f, 0.0107545536f, 0.01083632838f, 0.01091739535f, 0.01099764649f, 0.01107697561f, 0.01115526911f, 0.01123241056f, 0.01130828168f, 0.01138276048f, 0.01145572308f, 0.01152704284f, 0.01159659028f, 0.0116642369f, 0.0117298523f, 0.0117933061f, 0.01185446698f, 0.01191320643f, 0.01196939778f, 0.01202291343f, 0.01207363233f, 0.01212143432f, 0.01216620672f, 0.01220783778f, 0.0122462241f, 0.01228126884f, 0.01231288072f, 0.01234097779f, 0.01236548461f, 0.01238633506f, 0.01240347326f, 0.01241685264f, 0.01242643595f, 0.01243219618f, 0.01243411843f, 0.01243219618f, 0.01242643595f, 0.01241685264f, 0.01240347326f, 0.01238633506f, 0.01236548461f, 0.01234097779f, 0.01231288072f, 0.01228126884f, 0.0122462241f, 0.01220783778f, 0.01216620672f, 0.01212143432f, 0.01207363233f, 0.01202291343f, 0.01196939778f, 0.01191320643f, 0.01185446698f, 0.0117933061f, 0.0117298523f, 0.0116642369f, 0.01159659028f, 0.01152704284f, 0.01145572308f, 0.01138276048f, 0.01130828168f, 0.01123241056f, 0.01115526911f, 0.01107697561f, 0.01099764649f, 0.01091739535f, 0.01083632838f, 0.0107545536f, 0.01067216974f, 0.01058927551f, 0.01050596405f, 0.0104223229f, 0.01033843681f, 0.01025438774f, 0.01017025113f, 0.01008609962f, 0.01000200026f, 0.009918019176f, 0.009834215976f, 0.009750646539f, 0.00966736488f, 0.009584420361f, 0.009501857683f, 0.00941972062f, 0.009338049218f, 0.009256878868f, 0.009176243097f, 0.009096172638f, 0.009016696364f, 0.008937839419f, 0.008859624155f, 0.008782071993f, 0.00879156962f, 0.008799735457f, 0.008806557395f, 0.008812026121f, 0.008816135116f, 0.00881887693f, 0.008820248768f, 0.008820248768f, 0.00881887693f, 0.008816135116f, 0.008812026121f, 0.008806557395f, 0.008799735457f, 0.00879156962f, 0.008927859366f, 0.009007181972f, 0.009087154642f, 0.009167755023f, 0.009248957038f, 0.009330729023f, 0.009413041174f, 0.009495858103f, 0.009579141624f, 0.00966285076f, 0.009746940807f, 0.009831363335f, 0.009916068986f, 0.01000100002f, 0.01008609962f, 0.01017130353f, 0.01025654469f, 0.01034175418f, 0.01042685378f, 0.01051176619f, 0.01059640758f, 0.01068068855f, 0.01076451875f, 0.01084779948f, 0.01093043014f, 0.01101230737f, 0.01109332126f, 0.01117335912f, 0.01125230361f, 0.01133003552f, 0.01140643191f, 0.01148136612f, 0.0115547115f, 0.01162633486f, 0.01169610675f, 0.01176389214f, 0.0118295569f, 0.01189296879f, 0.0119539937f, 0.01201249938f, 0.01206835546f, 0.01212143432f, 0.01217161212f, 0.01221876871f, 0.01226278674f, 0.01230355818f, 0.01234097779f, 0.01237494871f, 0.01240538247f, 0.01243219618f, 0.01245531905f, 0.01247468684f, 0.01249024551f, 0.01250195317f, 0.01250977721f, 0.01251369435f, 0.01251369435f, 0.01250977721f, 0.01250195317f, 0.01249024551f, 0.01247468684f, 0.01245531905f, 0.01243219618f, 0.01240538247f, 0.01237494871f, 0.01234097779f, 0.01230355818f, 0.01226278674f, 0.01221876871f, 0.01217161212f, 0.01212143432f, 0.01206835546f, 0.01201249938f, 0.0119539937f, 0.01189296879f, 0.0118295569f, 0.01176389214f, 0.01169610675f, 0.01162633486f, 0.0115547115f, 0.01148136612f, 0.01140643191f, 0.01133003552f, 0.01125230361f, 0.01117335912f, 0.01109332126f, 0.01101230737f, 0.01093043014f, 0.01084779948f, 0.01076451875f, 0.01068068855f, 0.01059640758f, 0.01051176619f, 0.01042685378f, 0.01034175418f, 0.01025654469f, 0.01017130353f, 0.01008609962f, 0.01000100002f, 0.009916068986f, 0.009831363335f, 0.009746940807f, 0.00966285076f, 0.009579141624f, 0.009495858103f, 0.009413041174f, 0.009330729023f, 0.009248957038f, 0.009167755023f, 0.009087154642f, 0.009007181972f, 0.008927859366f, 0.008849211037f, 0.008859624155f, 0.008868678473f, 0.008876360953f, 0.008882662281f, 0.008887572214f, 0.008891084231f, 0.008893193677f, 0.008893896826f, 0.008893193677f, 0.008891084231f, 0.008887572214f, 0.008882662281f, 0.008876360953f, 0.008868678473f, 0.008859624155f, 0.008996240795f, 0.00907666795f, 0.009157754481f, 0.009239477105f, 0.009321806021f, 0.009404712357f, 0.009488161653f, 0.009572117589f, 0.009656541049f, 0.009741389193f, 0.009826615453f, 0.00991217047f, 0.009998000227f, 0.01008404791f, 0.01017025113f, 0.01025654469f, 0.01034285966f, 0.01042912249f, 0.01051525306f, 0.01060117036f, 0.01068678591f, 0.01077201031f, 0.01085674576f, 0.01094089262f, 0.01102434658f, 0.01110699773f, 0.01118873432f, 0.01126943901f, 0.01134899072f, 0.01142726559f, 0.01150413603f, 0.0115794735f, 0.01165314391f, 0.01172501314f, 0.01179494616f, 0.01186280511f, 0.01192845311f, 0.01199175231f, 0.01205256768f, 0.01211076323f, 0.01216620672f, 0.01221876871f, 0.01226832252f, 0.01231474802f, 0.01235792786f, 0.01239775307f, 0.01243411843f, 0.01246692892f, 0.01249609515f, 0.01252153981f, 0.0125431912f, 0.01256099064f, 0.01257488597f, 0.01258483995f, 0.0125908237f, 0.01259282045f, 0.0125908237f, 0.01258483995f, 0.01257488597f, 0.01256099064f, 0.0125431912f, 0.01252153981f, 0.01249609515f, 0.01246692892f, 0.01243411843f, 0.01239775307f, 0.01235792786f, 0.01231474802f, 0.01226832252f, 0.01221876871f, 0.01216620672f, 0.01211076323f, 0.01205256768f, 0.01199175231f, 0.01192845311f, 0.01186280511f, 0.01179494616f, 0.01172501314f, 0.01165314391f, 0.0115794735f, 0.01150413603f, 0.01142726559f, 0.01134899072f, 0.01126943901f, 0.01118873432f, 0.01110699773f, 0.01102434658f, 0.01094089262f, 0.01085674576f, 0.01077201031f, 0.01068678591f, 0.01060117036f, 0.01051525306f, 0.01042912249f, 0.01034285966f, 0.01025654469f, 0.01017025113f, 0.01008404791f, 0.009998000227f, 0.00991217047f, 0.009826615453f, 0.009741389193f, 0.009656541049f, 0.009572117589f, 0.009488161653f, 0.009404712357f, 0.009321806021f, 0.009239477105f, 0.009157754481f, 0.00907666795f, 0.008996240795f, 0.008916495368f, 0.008927859366f, 0.008937839419f, 0.008946419694f, 0.008953588083f, 0.008959336206f, 0.008963654749f, 0.008966536261f, 0.00896797888f, 0.00896797888f, 0.008966536261f, 0.008963654749f, 0.008959336206f, 0.008953588083f, 0.008946419694f, 0.008937839419f, 0.008927859366f, 0.009064726532f, 0.009146256372f, 0.009228453971f, 0.009311294183f, 0.009394746274f, 0.00947877951f, 0.009563359432f, 0.009648446925f, 0.009734002873f, 0.009819980711f, 0.00990633294f, 0.009993007407f, 0.01007994823f, 0.01016709674f, 0.01025438774f, 0.01034175418f, 0.01042912249f, 0.01051641535f, 0.01060355362f, 0.01069044974f, 0.01077701338f, 0.01086314954f, 0.0109487595f, 0.01103373803f, 0.01111797616f, 0.01120136213f, 0.01128377859f, 0.01136510447f, 0.01144521404f, 0.01152398065f, 0.01160127111f, 0.01167695317f, 0.01175088901f, 0.01182294171f, 0.01189296879f, 0.0119608324f, 0.01202639099f, 0.01208950393f, 0.01215003151f, 0.01220783778f, 0.01226278674f, 0.01231474802f, 0.01236359403f, 0.01240920182f, 0.01245145593f, 0.01249024551f, 0.01252546813f, 0.01255702879f, 0.01258483995f, 0.01260882709f, 0.01262892038f, 0.01264506485f, 0.01265721396f, 0.0126653323f, 0.01266939752f, 0.01266939752f, 0.0126653323f, 0.01265721396f, 0.01264506485f, 0.01262892038f, 0.01260882709f, 0.01258483995f, 0.01255702879f, 0.01252546813f, 0.01249024551f, 0.01245145593f, 0.01240920182f, 0.01236359403f, 0.01231474802f, 0.01226278674f, 0.01220783778f, 0.01215003151f, 0.01208950393f, 0.01202639099f, 0.0119608324f, 0.01189296879f, 0.01182294171f, 0.01175088901f, 0.01167695317f, 0.01160127111f, 0.01152398065f, 0.01144521404f, 0.01136510447f, 0.01128377859f, 0.01120136213f, 0.01111797616f, 0.01103373803f, 0.0109487595f, 0.01086314954f, 0.01077701338f, 0.01069044974f, 0.01060355362f, 0.01051641535f, 0.01042912249f, 0.01034175418f, 0.01025438774f, 0.01016709674f, 0.01007994823f, 0.009993007407f, 0.00990633294f, 0.009819980711f, 0.009734002873f, 0.009648446925f, 0.009563359432f, 0.00947877951f, 0.009394746274f, 0.009311294183f, 0.009228453971f, 0.009146256372f, 0.009064726532f, 0.008983888663f, 0.008996240795f, 0.009007181972f, 0.009016696364f, 0.00902477093f, 0.009031393565f, 0.009036554955f, 0.009040246718f, 0.009042463265f, 0.009043202735f, 0.009042463265f, 0.009040246718f, 0.009036554955f, 0.009031393565f, 0.00902477093f, 0.009016696364f, 0.009007181972f, 0.008996240795f, 0.009133277461f, 0.009215905331f, 0.009299207479f, 0.009383158758f, 0.009467727505f, 0.00955288019f, 0.009638582356f, 0.009724793024f, 0.009811468422f, 0.009898564778f, 0.009986029007f, 0.01007380895f, 0.01016184594f, 0.01025007758f, 0.01033843681f, 0.01042685378f, 0.01051525306f, 0.01060355362f, 0.01069167163f, 0.0107795177f, 0.01086699776f, 0.01095401309f, 0.01104046032f, 0.0111262314f, 0.01121121366f, 0.01129528973f, 0.01137833856f, 0.01146023627f, 0.01154085156f, 0.01162005402f, 0.01169770677f, 0.01177367195f, 0.01184780896f, 0.01191997528f, 0.01199002843f, 0.01205782313f, 0.01212321594f, 0.01218606345f, 0.0122462241f, 0.01230355818f, 0.01235792786f, 0.01240920182f, 0.01245725155f, 0.01250195317f, 0.0125431912f, 0.01258085575f, 0.01261484437f, 0.01264506485f, 0.01267143246f, 0.01269387174f, 0.01271232124f, 0.01272672601f, 0.01273704506f, 0.0127432486f, 0.01274531893f, 0.0127432486f, 0.01273704506f, 0.01272672601f, 0.01271232124f, 0.01269387174f, 0.01267143246f, 0.01264506485f, 0.01261484437f, 0.01258085575f, 0.0125431912f, 0.01250195317f, 0.01245725155f, 0.01240920182f, 0.01235792786f, 0.01230355818f, 0.0122462241f, 0.01218606345f, 0.01212321594f, 0.01205782313f, 0.01199002843f, 0.01191997528f, 0.01184780896f, 0.01177367195f, 0.01169770677f, 0.01162005402f, 0.01154085156f, 0.01146023627f, 0.01137833856f, 0.01129528973f, 0.01121121366f, 0.0111262314f, 0.01104046032f, 0.01095401309f, 0.01086699776f, 0.0107795177f, 0.01069167163f, 0.01060355362f, 0.01051525306f, 0.01042685378f, 0.01033843681f, 0.01025007758f, 0.01016184594f, 0.01007380895f, 0.009986029007f, 0.009898564778f, 0.009811468422f, 0.009724793024f, 0.009638582356f, 0.00955288019f, 0.009467727505f, 0.009383158758f, 0.009299207479f, 0.009215905331f, 0.009133277461f, 0.009051349014f, 0.009064726532f, 0.00907666795f, 0.009087154642f, 0.009096172638f, 0.009103707969f, 0.00910975039f, 0.009114289656f, 0.00911732018f, 0.009118835442f, 0.009118835442f, 0.00911732018f, 0.009114289656f, 0.00910975039f, 0.009103707969f, 0.009096172638f, 0.009087154642f, 0.00907666795f, 0.009064726532f, 0.009201847948f, 0.009285567328f, 0.009369968437f, 0.009455023333f, 0.009540699422f, 0.009626962245f, 0.009713775478f, 0.009801096283f, 0.009888879955f, 0.009977078997f, 0.01006564032f, 0.01015450899f, 0.01024362259f, 0.01033291686f, 0.0104223229f, 0.01051176619f, 0.01060117036f, 0.01069044974f, 0.0107795177f, 0.01086828113f, 0.01095664315f, 0.01104449946f, 0.01113174483f, 0.01121826563f, 0.01130394638f, 0.01138866507f, 0.01147229597f, 0.0115547115f, 0.01163577568f, 0.0117153544f, 0.0117933061f, 0.01186948828f, 0.01194375753f, 0.01201596763f, 0.01208597142f, 0.0121536199f, 0.01221876871f, 0.01228126884f, 0.01234097779f, 0.01239775307f, 0.01245145593f, 0.01250195317f, 0.01254911628f, 0.01259282045f, 0.01263295114f, 0.01266939752f, 0.01270206179f, 0.01273085084f, 0.01275568269f, 0.01277648844f, 0.01279320568f, 0.01280578785f, 0.01281419583f, 0.01281840634f, 0.01281840634f, 0.01281419583f, 0.01280578785f, 0.01279320568f, 0.01277648844f, 0.01275568269f, 0.01273085084f, 0.01270206179f, 0.01266939752f, 0.01263295114f, 0.01259282045f, 0.01254911628f, 0.01250195317f, 0.01245145593f, 0.01239775307f, 0.01234097779f, 0.01228126884f, 0.01221876871f, 0.0121536199f, 0.01208597142f, 0.01201596763f, 0.01194375753f, 0.01186948828f, 0.0117933061f, 0.0117153544f, 0.01163577568f, 0.0115547115f, 0.01147229597f, 0.01138866507f, 0.01130394638f, 0.01121826563f, 0.01113174483f, 0.01104449946f, 0.01095664315f, 0.01086828113f, 0.0107795177f, 0.01069044974f, 0.01060117036f, 0.01051176619f, 0.0104223229f, 0.01033291686f, 0.01024362259f, 0.01015450899f, 0.01006564032f, 0.009977078997f, 0.009888879955f, 0.009801096283f, 0.009713775478f, 0.009626962245f, 0.009540699422f, 0.009455023333f, 0.009369968437f, 0.009285567328f, 0.009201847948f, 0.009118835442f, 0.009133277461f, 0.009146256372f, 0.009157754481f, 0.009167755023f, 0.009176243097f, 0.009183204733f, 0.009188630618f, 0.009192512371f, 0.009194843471f, 0.009195621125f, 0.009194843471f, 0.009192512371f, 0.009188630618f, 0.009183204733f, 0.009176243097f, 0.009167755023f, 0.009157754481f, 0.009146256372f, 0.009133277461f, 0.00927039329f, 0.009355195798f, 0.009440686554f, 0.009526834823f, 0.009613607079f, 0.009700968862f, 0.009788879193f, 0.009877296165f, 0.009966172278f, 0.01005545817f, 0.01014509797f, 0.01023503393f, 0.01032520272f, 0.01041553635f, 0.01050596405f, 0.01059640758f, 0.01068678591f, 0.01077701338f, 0.01086699776f, 0.01095664315f, 0.01104584709f, 0.01113450434f, 0.01122250315f, 0.01130972803f, 0.0113960579f, 0.01148136612f, 0.01156552508f, 0.01164839976f, 0.0117298523f, 0.01180974208f, 0.01188792568f, 0.01196425594f, 0.01203858573f, 0.01211076323f, 0.0121806385f, 0.0122480616f, 0.01231288072f, 0.01237494871f, 0.01243411843f, 0.01249024551f, 0.0125431912f, 0.01259282045f, 0.01263900381f, 0.0126816174f, 0.01272054669f, 0.01275568269f, 0.0127869295f, 0.01281419583f, 0.01283740439f, 0.01285648718f, 0.01287138835f, 0.01288206317f, 0.01288848184f, 0.01289062295f, 0.01288848184f, 0.01288206317f, 0.01287138835f, 0.01285648718f, 0.01283740439f, 0.01281419583f, 0.0127869295f, 0.01275568269f, 0.01272054669f, 0.0126816174f, 0.01263900381f, 0.01259282045f, 0.0125431912f, 0.01249024551f, 0.01243411843f, 0.01237494871f, 0.01231288072f, 0.0122480616f, 0.0121806385f, 0.01211076323f, 0.01203858573f, 0.01196425594f, 0.01188792568f, 0.01180974208f, 0.0117298523f, 0.01164839976f, 0.01156552508f, 0.01148136612f, 0.0113960579f, 0.01130972803f, 0.01122250315f, 0.01113450434f, 0.01104584709f, 0.01095664315f, 0.01086699776f, 0.01077701338f, 0.01068678591f, 0.01059640758f, 0.01050596405f, 0.01041553635f, 0.01032520272f, 0.01023503393f, 0.01014509797f, 0.01005545817f, 0.009966172278f, 0.009877296165f, 0.009788879193f, 0.009700968862f, 0.009613607079f, 0.009526834823f, 0.009440686554f, 0.009355195798f, 0.00927039329f, 0.009186304174f, 0.009201847948f, 0.009215905331f, 0.009228453971f, 0.009239477105f, 0.009248957038f, 0.009256878868f, 0.009263231419f, 0.009268003516f, 0.00927118957f, 0.009272783995f, 0.009272783995f, 0.00927118957f, 0.009268003516f, 0.009263231419f, 0.009256878868f, 0.009248957038f, 0.009239477105f, 0.009228453971f, 0.009215905331f, 0.009201847948f, 0.009338863194f, 0.009424740449f, 0.009511308745f, 0.00959853828f, 0.009686394595f, 0.009774839506f, 0.009863832965f, 0.009953328408f, 0.01004327927f, 0.01013363153f, 0.01022432931f, 0.01031531021f, 0.01040650904f, 0.01049785595f, 0.01058927551f, 0.01068068855f, 0.01077201031f, 0.01086314954f, 0.01095401309f, 0.01104449946f, 0.01113450434f, 0.0112239169f, 0.01131262258f, 0.01140050031f, 0.0114874253f, 0.0115732681f, 0.01165789459f, 0.011741166f, 0.01182294171f, 0.0119030755f, 0.01198141929f, 0.01205782313f, 0.01213213522f, 0.01220420003f, 0.01227386575f, 0.01234097779f, 0.01240538247f, 0.01246692892f, 0.01252546813f, 0.01258085575f, 0.01263295114f, 0.0126816174f, 0.01272672601f, 0.01276815403f, 0.01280578785f, 0.01283952035f, 0.01286925655f, 0.0128949089f, 0.01291640475f, 0.01293367799f, 0.01294667926f, 0.01295536757f, 0.0129597187f, 0.0129597187f, 0.01295536757f, 0.01294667926f, 0.01293367799f, 0.01291640475f, 0.0128949089f, 0.01286925655f, 0.01283952035f, 0.01280578785f, 0.01276815403f, 0.01272672601f, 0.0126816174f, 0.01263295114f, 0.01258085575f, 0.01252546813f, 0.01246692892f, 0.01240538247f, 0.01234097779f, 0.01227386575f, 0.01220420003f, 0.01213213522f, 0.01205782313f, 0.01198141929f, 0.0119030755f, 0.01182294171f, 0.011741166f, 0.01165789459f, 0.0115732681f, 0.0114874253f, 0.01140050031f, 0.01131262258f, 0.0112239169f, 0.01113450434f, 0.01104449946f, 0.01095401309f, 0.01086314954f, 0.01077201031f, 0.01068068855f, 0.01058927551f, 0.01049785595f, 0.01040650904f, 0.01031531021f, 0.01022432931f, 0.01013363153f, 0.01004327927f, 0.009953328408f, 0.009863832965f, 0.009774839506f, 0.009686394595f, 0.00959853828f, 0.009511308745f, 0.009424740449f, 0.009338863194f, 0.009253707714f, 0.00927039329f, 0.009285567328f, 0.009299207479f, 0.009311294183f, 0.009321806021f, 0.009330729023f, 0.009338049218f, 0.0093437545f, 0.009347835556f, 0.009350287728f, 0.009351104498f, 0.009350287728f, 0.009347835556f, 0.0093437545f, 0.009338049218f, 0.009330729023f, 0.009321806021f, 0.009311294183f, 0.009299207479f, 0.009285567328f, 0.00927039329f, 0.009407208301f, 0.009494146332f, 0.009581780061f, 0.009670076892f, 0.009759000503f, 0.009848512709f, 0.009938570671f, 0.01002912689f, 0.01012013014f, 0.01021152735f, 0.01030325703f, 0.01039525773f, 0.0104874596f, 0.01057978906f, 0.01067216974f, 0.01076451875f, 0.01085674576f, 0.0109487595f, 0.01104046032f, 0.01113174483f, 0.01122250315f, 0.01131262258f, 0.01140198205f, 0.01149045862f, 0.01157792099f, 0.0116642369f, 0.01174926758f, 0.01183286961f, 0.01191489771f, 0.01199520286f, 0.01207363233f, 0.01215003151f, 0.01222424489f, 0.01229611505f, 0.01236548461f, 0.01243219618f, 0.01249609515f, 0.01255702879f, 0.01261484437f, 0.01266939752f, 0.01272054669f, 0.01276815403f, 0.01281209197f, 0.01285223942f, 0.01288848184f, 0.01292071585f, 0.01294884924f, 0.0129727982f, 0.01299249288f, 0.01300787181f, 0.01301889122f, 0.01302551571f, 0.01302772667f, 0.01302551571f, 0.01301889122f, 0.01300787181f, 0.01299249288f, 0.0129727982f, 0.01294884924f, 0.01292071585f, 0.01288848184f, 0.01285223942f, 0.01281209197f, 0.01276815403f, 0.01272054669f, 0.01266939752f, 0.01261484437f, 0.01255702879f, 0.01249609515f, 0.01243219618f, 0.01236548461f, 0.01229611505f, 0.01222424489f, 0.01215003151f, 0.01207363233f, 0.01199520286f, 0.01191489771f, 0.01183286961f, 0.01174926758f, 0.0116642369f, 0.01157792099f, 0.01149045862f, 0.01140198205f, 0.01131262258f, 0.01122250315f, 0.01113174483f, 0.01104046032f, 0.0109487595f, 0.01085674576f, 0.01076451875f, 0.01067216974f, 0.01057978906f, 0.0104874596f, 0.01039525773f, 0.01030325703f, 0.01021152735f, 0.01012013014f, 0.01002912689f, 0.009938570671f, 0.009848512709f, 0.009759000503f, 0.009670076892f, 0.009581780061f, 0.009494146332f, 0.009407208301f, 0.009320996702f, 0.009338863194f, 0.009355195798f, 0.009369968437f, 0.009383158758f, 0.009394746274f, 0.009404712357f, 0.009413041174f, 0.00941972062f, 0.009424740449f, 0.009428090416f, 0.009429766797f, 0.009429766797f, 0.009428090416f, 0.009424740449f, 0.00941972062f, 0.009413041174f, 0.009404712357f, 0.009394746274f, 0.009383158758f, 0.009369968437f, 0.009355195798f, 0.009338863194f, 0.009475374594f, 0.009563359432f, 0.00965204183f, 0.009741389193f, 0.009831363335f, 0.009921924211f, 0.01001302525f, 0.01010461897f, 0.01019665226f, 0.0102890674f, 0.01038180385f, 0.01047479361f, 0.01056796685f, 0.01066124719f, 0.0107545536f, 0.01084779948f, 0.01094089262f, 0.01103373803f, 0.0111262314f, 0.01121826563f, 0.01130972803f, 0.01140050031f, 0.01149045862f, 0.0115794735f, 0.01166741177f, 0.0117541356f, 0.01183950249f, 0.01192336436f, 0.01200557221f, 0.01208597142f, 0.01216440555f, 0.01224071812f, 0.01231474802f, 0.01238633506f, 0.01245531905f, 0.01252153981f, 0.01258483995f, 0.01264506485f, 0.01270206179f, 0.01275568269f, 0.01280578785f, 0.01285223942f, 0.0128949089f, 0.01293367799f, 0.01296843402f, 0.01299907733f, 0.01302551571f, 0.01304767188f, 0.01306547876f, 0.01307888143f, 0.01308783889f, 0.01309232507f, 0.01309232507f, 0.01308783889f, 0.01307888143f, 0.01306547876f, 0.01304767188f, 0.01302551571f, 0.01299907733f, 0.01296843402f, 0.01293367799f, 0.0128949089f, 0.01285223942f, 0.01280578785f, 0.01275568269f, 0.01270206179f, 0.01264506485f, 0.01258483995f, 0.01252153981f, 0.01245531905f, 0.01238633506f, 0.01231474802f, 0.01224071812f, 0.01216440555f, 0.01208597142f, 0.01200557221f, 0.01192336436f, 0.01183950249f, 0.0117541356f, 0.01166741177f, 0.0115794735f, 0.01149045862f, 0.01140050031f, 0.01130972803f, 0.01121826563f, 0.0111262314f, 0.01103373803f, 0.01094089262f, 0.01084779948f, 0.0107545536f, 0.01066124719f, 0.01056796685f, 0.01047479361f, 0.01038180385f, 0.0102890674f, 0.01019665226f, 0.01010461897f, 0.01001302525f, 0.009921924211f, 0.009831363335f, 0.009741389193f, 0.00965204183f, 0.009563359432f, 0.009475374594f, 0.009388119914f, 0.009407208301f, 0.009424740449f, 0.009440686554f, 0.009455023333f, 0.009467727505f, 0.00947877951f, 0.009488161653f, 0.009495858103f, 0.009501857683f, 0.009506150149f, 0.00950872805f, 0.00950958766f, 0.00950872805f, 0.009506150149f, 0.009501857683f, 0.009495858103f, 0.009488161653f, 0.00947877951f, 0.009467727505f, 0.009455023333f, 0.009440686554f, 0.009424740449f, 0.009407208301f, 0.009543305263f, 0.009632320143f, 0.009722034447f, 0.009812413715f, 0.0099034179f, 0.009995004162f, 0.01008712593f, 0.01017973199f, 0.01027276739f, 0.01036617346f, 0.010459885f, 0.01055383217f, 0.01064794231f, 0.01074213628f, 0.01083632838f, 0.01093043014f, 0.01102434658f, 0.01111797616f, 0.01121121366f, 0.01130394638f, 0.0113960579f, 0.0114874253f, 0.01157792099f, 0.01166741177f, 0.01175575983f, 0.01184282266f, 0.01192845311f, 0.01201249938f, 0.01209480781f, 0.01217522006f, 0.01225357689f, 0.01232971624f, 0.01240347326f, 0.01247468684f, 0.0125431912f, 0.01260882709f, 0.01267143246f, 0.01273085084f, 0.0127869295f, 0.01283952035f, 0.01288848184f, 0.01293367799f, 0.01297498215f, 0.01301227603f, 0.0130454516f, 0.01307440922f, 0.01309906319f, 0.01311933808f, 0.01313517336f, 0.01314651966f, 0.01315334067f, 0.01315561775f, 0.01315334067f, 0.01314651966f, 0.01313517336f, 0.01311933808f, 0.01309906319f, 0.01307440922f, 0.0130454516f, 0.01301227603f, 0.01297498215f, 0.01293367799f, 0.01288848184f, 0.01283952035f, 0.0127869295f, 0.01273085084f, 0.01267143246f, 0.01260882709f, 0.0125431912f, 0.01247468684f, 0.01240347326f, 0.01232971624f, 0.01225357689f, 0.01217522006f, 0.01209480781f, 0.01201249938f, 0.01192845311f, 0.01184282266f, 0.01175575983f, 0.01166741177f, 0.01157792099f, 0.0114874253f, 0.0113960579f, 0.01130394638f, 0.01121121366f, 0.01111797616f, 0.01102434658f, 0.01093043014f, 0.01083632838f, 0.01074213628f, 0.01064794231f, 0.01055383217f, 0.010459885f, 0.01036617346f, 0.01027276739f, 0.01017973199f, 0.01008712593f, 0.009995004162f, 0.0099034179f, 0.009812413715f, 0.009722034447f, 0.009632320143f, 0.009543305263f, 0.009455023333f, 0.009475374594f, 0.009494146332f, 0.009511308745f, 0.009526834823f, 0.009540699422f, 0.00955288019f, 0.009563359432f, 0.009572117589f, 0.009579141624f, 0.009584420361f, 0.009587943554f, 0.009589706548f, 0.009589706548f, 0.009587943554f, 0.009584420361f, 0.009579141624f, 0.009572117589f, 0.009563359432f, 0.00955288019f, 0.009540699422f, 0.009526834823f, 0.009511308745f, 0.009494146332f, 0.009475374594f, 0.009610942565f, 0.009700968862f, 0.009791694582f, 0.009883082472f, 0.009975093417f, 0.01006768085f, 0.01016079728f, 0.01025438774f, 0.01034839638f, 0.01044276077f, 0.01053741388f, 0.01063228305f, 0.01072729193f, 0.01082235854f, 0.01091739535f, 0.01101230737f, 0.01110699773f, 0.01120136213f, 0.01129528973f, 0.01138866507f, 0.01148136612f, 0.0115732681f, 0.0116642369f, 0.0117541356f, 0.01184282266f, 0.01193015091f, 0.01201596763f, 0.01210011914f, 0.0121824462f, 0.01226278674f, 0.01234097779f, 0.01241685264f, 0.01249024551f, 0.01256099064f, 0.01262892038f, 0.01269387174f, 0.01275568269f, 0.01281419583f, 0.01286925655f, 0.01292071585f, 0.01296843402f, 0.01301227603f, 0.01305211708f, 0.01308783889f, 0.01311933808f, 0.01314651966f, 0.01316929981f, 0.01318760961f, 0.01320139226f, 0.0132106049f, 0.01321521774f, 0.01321521774f, 0.0132106049f, 0.01320139226f, 0.01318760961f, 0.01316929981f, 0.01314651966f, 0.01311933808f, 0.01308783889f, 0.01305211708f, 0.01301227603f, 0.01296843402f, 0.01292071585f, 0.01286925655f, 0.01281419583f, 0.01275568269f, 0.01269387174f, 0.01262892038f, 0.01256099064f, 0.01249024551f, 0.01241685264f, 0.01234097779f, 0.01226278674f, 0.0121824462f, 0.01210011914f, 0.01201596763f, 0.01193015091f, 0.01184282266f, 0.0117541356f, 0.0116642369f, 0.0115732681f, 0.01148136612f, 0.01138866507f, 0.01129528973f, 0.01120136213f, 0.01110699773f, 0.01101230737f, 0.01091739535f, 0.01082235854f, 0.01072729193f, 0.01063228305f, 0.01053741388f, 0.01044276077f, 0.01034839638f, 0.01025438774f, 0.01016079728f, 0.01006768085f, 0.009975093417f, 0.009883082472f, 0.009791694582f, 0.009700968862f, 0.009610942565f, 0.009521651082f, 0.009543305263f, 0.009563359432f, 0.009581780061f, 0.00959853828f, 0.009613607079f, 0.009626962245f, 0.009638582356f, 0.009648446925f, 0.009656541049f, 0.00966285076f, 0.00966736488f, 0.009670076892f, 0.009670981206f, 0.009670076892f, 0.00966736488f, 0.00966285076f, 0.009656541049f, 0.009648446925f, 0.009638582356f, 0.009626962245f, 0.009613607079f, 0.00959853828f, 0.009581780061f, 0.009563359432f, 0.009543305263f, 0.009678225033f, 0.009769240394f, 0.009860955179f, 0.009953328408f, 0.01004632004f, 0.01013988163f, 0.01023396198f, 0.01032850612f, 0.01042345539f, 0.01051874273f, 0.01061430015f, 0.01071005128f, 0.0108059179f, 0.0109018134f, 0.01099764649f, 0.01109332126f, 0.01118873432f, 0.01128377859f, 0.01137833856f, 0.01147229597f, 0.01156552508f, 0.01165789459f, 0.01174926758f, 0.01183950249f, 0.01192845311f, 0.01201596763f, 0.01210189145f, 0.01218606345f, 0.01226832252f, 0.01234850287f, 0.01242643595f, 0.01250195317f, 0.01257488597f, 0.01264506485f, 0.01271232124f, 0.01277648844f, 0.01283740439f, 0.0128949089f, 0.01294884924f, 0.01299907733f, 0.0130454516f, 0.01308783889f, 0.01312611811f, 0.01316017378f, 0.01318990346f, 0.01321521774f, 0.01323603839f, 0.01325230021f, 0.01326395292f, 0.01327095926f, 0.01327329688f, 0.01327095926f, 0.01326395292f, 0.01325230021f, 0.01323603839f, 0.01321521774f, 0.01318990346f, 0.01316017378f, 0.01312611811f, 0.01308783889f, 0.0130454516f, 0.01299907733f, 0.01294884924f, 0.0128949089f, 0.01283740439f, 0.01277648844f, 0.01271232124f, 0.01264506485f, 0.01257488597f, 0.01250195317f, 0.01242643595f, 0.01234850287f, 0.01226832252f, 0.01218606345f, 0.01210189145f, 0.01201596763f, 0.01192845311f, 0.01183950249f, 0.01174926758f, 0.01165789459f, 0.01156552508f, 0.01147229597f, 0.01137833856f, 0.01128377859f, 0.01118873432f, 0.01109332126f, 0.01099764649f, 0.0109018134f, 0.0108059179f, 0.01071005128f, 0.01061430015f, 0.01051874273f, 0.01042345539f, 0.01032850612f, 0.01023396198f, 0.01013988163f, 0.01004632004f, 0.009953328408f, 0.009860955179f, 0.009769240394f, 0.009678225033f, 0.009587943554f, 0.009610942565f, 0.009632320143f, 0.00965204183f, 0.009670076892f, 0.009686394595f, 0.009700968862f, 0.009713775478f, 0.009724793024f, 0.009734002873f, 0.009741389193f, 0.009746940807f, 0.009750646539f, 0.009752500802f, 0.009752500802f, 0.009750646539f, 0.009746940807f, 0.009741389193f, 0.009734002873f, 0.009724793024f, 0.009713775478f, 0.009700968862f, 0.009686394595f, 0.009670076892f, 0.00965204183f, 0.009632320143f, 0.009610942565f, 0.009745089337f, 0.00983707048f, 0.009929747321f, 0.01002307981f, 0.01011702232f, 0.01021152735f, 0.01030653995f, 0.01040200423f, 0.01049785595f, 0.01059402898f, 0.01069044974f, 0.01078704093f, 0.01088371873f, 0.0109803956f, 0.01107697561f, 0.01117335912f, 0.01126943901f, 0.01136510447f, 0.01146023627f, 0.0115547115f, 0.01164839976f, 0.011741166f, 0.01183286961f, 0.01192336436f, 0.01201249938f, 0.01210011914f, 0.01218606345f, 0.01227016933f, 0.01235227007f, 0.01243219618f, 0.01250977721f, 0.01258483995f, 0.01265721396f, 0.01272672601f, 0.01279320568f, 0.01285648718f, 0.01291640475f, 0.0129727982f, 0.01302551571f, 0.01307440922f, 0.01311933808f, 0.01316017378f, 0.01319679338f, 0.013229087f, 0.01325695775f, 0.01328031812f, 0.01329909544f, 0.01331323106f, 0.01332267933f, 0.01332741138f, 0.01332741138f, 0.01332267933f, 0.01331323106f, 0.01329909544f, 0.01328031812f, 0.01325695775f, 0.013229087f, 0.01319679338f, 0.01316017378f, 0.01311933808f, 0.01307440922f, 0.01302551571f, 0.0129727982f, 0.01291640475f, 0.01285648718f, 0.01279320568f, 0.01272672601f, 0.01265721396f, 0.01258483995f, 0.01250977721f, 0.01243219618f, 0.01235227007f, 0.01227016933f, 0.01218606345f, 0.01210011914f, 0.01201249938f, 0.01192336436f, 0.01183286961f, 0.011741166f, 0.01164839976f, 0.0115547115f, 0.01146023627f, 0.01136510447f, 0.01126943901f, 0.01117335912f, 0.01107697561f, 0.0109803956f, 0.01088371873f, 0.01078704093f, 0.01069044974f, 0.01059402898f, 0.01049785595f, 0.01040200423f, 0.01030653995f, 0.01021152735f, 0.01011702232f, 0.01002307981f, 0.009929747321f, 0.00983707048f, 0.009745089337f, 0.009653841145f, 0.009678225033f, 0.009700968862f, 0.009722034447f, 0.009741389193f, 0.009759000503f, 0.009774839506f, 0.009788879193f, 0.009801096283f, 0.009811468422f, 0.009819980711f, 0.009826615453f, 0.009831363335f, 0.009834215976f, 0.009835166857f, 0.009834215976f, 0.009831363335f, 0.009826615453f, 0.009819980711f, 0.009811468422f, 0.009801096283f, 0.009788879193f, 0.009774839506f, 0.009759000503f, 0.009741389193f, 0.009722034447f, 0.009700968862f, 0.009678225033f, 0.009811468422f, 0.00990438927f, 0.009998000227f, 0.01009226125f, 0.0101871239f, 0.01028253883f, 0.01037844829f, 0.01047479361f, 0.0105715096f, 0.01066852547f, 0.01076576579f, 0.01086314954f, 0.01096059103f, 0.01105799619f, 0.01115526911f, 0.01125230361f, 0.01134899072f, 0.01144521404f, 0.01154085156f, 0.01163577568f, 0.0117298523f, 0.01182294171f, 0.01191489771f, 0.01200557221f, 0.01209480781f, 0.0121824462f, 0.01226832252f, 0.01235227007f, 0.01243411843f, 0.01251369435f, 0.0125908237f, 0.0126653323f, 0.01273704506f, 0.01280578785f, 0.01287138835f, 0.01293367799f, 0.01299249288f, 0.01304767188f, 0.01309906319f, 0.01314651966f, 0.01318990346f, 0.013229087f, 0.01326395292f, 0.0132943932f, 0.01332031563f, 0.01334163733f, 0.01335829217f, 0.01337022707f, 0.01337740291f, 0.01337979734f, 0.01337740291f, 0.01337022707f, 0.01335829217f, 0.01334163733f, 0.01332031563f, 0.0132943932f, 0.01326395292f, 0.013229087f, 0.01318990346f, 0.01314651966f, 0.01309906319f, 0.01304767188f, 0.01299249288f, 0.01293367799f, 0.01287138835f, 0.01280578785f, 0.01273704506f, 0.0126653323f, 0.0125908237f, 0.01251369435f, 0.01243411843f, 0.01235227007f, 0.01226832252f, 0.0121824462f, 0.01209480781f, 0.01200557221f, 0.01191489771f, 0.01182294171f, 0.0117298523f, 0.01163577568f, 0.01154085156f, 0.01144521404f, 0.01134899072f, 0.01125230361f, 0.01115526911f, 0.01105799619f, 0.01096059103f, 0.01086314954f, 0.01076576579f, 0.01066852547f, 0.0105715096f, 0.01047479361f, 0.01037844829f, 0.01028253883f, 0.0101871239f, 0.01009226125f, 0.009998000227f, 0.00990438927f, 0.009811468422f, 0.009719279595f, 0.009745089337f, 0.009769240394f, 0.009791694582f, 0.009812413715f, 0.009831363335f, 0.009848512709f, 0.009863832965f, 0.009877296165f, 0.009888879955f, 0.009898564778f, 0.00990633294f, 0.00991217047f, 0.009916068986f, 0.009918019176f, 0.009918019176f, 0.009916068986f, 0.00991217047f, 0.00990633294f, 0.009898564778f, 0.009888879955f, 0.009877296165f, 0.009863832965f, 0.009848512709f, 0.009831363335f, 0.009812413715f, 0.009791694582f, 0.009769240394f, 0.009745089337f, 0.009877296165f, 0.009971125983f, 0.01006564032f, 0.01016079728f, 0.01025654469f, 0.01035283227f, 0.01044960041f, 0.01054678671f, 0.01064432319f, 0.01074213628f, 0.01084014867f, 0.01093827467f, 0.0110364249f, 0.01113450434f, 0.01123241056f, 0.01133003552f, 0.01142726559f, 0.01152398065f, 0.01162005402f, 0.0117153544f, 0.01180974208f, 0.0119030755f, 0.01199520286f, 0.01208597142f, 0.01217522006f, 0.01226278674f, 0.01234850287f, 0.01243219618f, 0.01251369435f, 0.01259282045f, 0.01266939752f, 0.0127432486f, 0.01281419583f, 0.01288206317f, 0.01294667926f, 0.01300787181f, 0.01306547876f, 0.01311933808f, 0.01316929981f, 0.01321521774f, 0.01325695775f, 0.0132943932f, 0.01332741138f, 0.01335590892f, 0.01337979734f, 0.01339900028f, 0.0134134572f, 0.01342312153f, 0.01342796069f, 0.01342796069f, 0.01342312153f, 0.0134134572f, 0.01339900028f, 0.01337979734f, 0.01335590892f, 0.01332741138f, 0.0132943932f, 0.01325695775f, 0.01321521774f, 0.01316929981f, 0.01311933808f, 0.01306547876f, 0.01300787181f, 0.01294667926f, 0.01288206317f, 0.01281419583f, 0.0127432486f, 0.01266939752f, 0.01259282045f, 0.01251369435f, 0.01243219618f, 0.01234850287f, 0.01226278674f, 0.01217522006f, 0.01208597142f, 0.01199520286f, 0.0119030755f, 0.01180974208f, 0.0117153544f, 0.01162005402f, 0.01152398065f, 0.01142726559f, 0.01133003552f, 0.01123241056f, 0.01113450434f, 0.0110364249f, 0.01093827467f, 0.01084014867f, 0.01074213628f, 0.01064432319f, 0.01054678671f, 0.01044960041f, 0.01035283227f, 0.01025654469f, 0.01016079728f, 0.01006564032f, 0.009971125983f, 0.009877296165f, 0.009784192778f, 0.009811468422f, 0.00983707048f, 0.009860955179f, 0.009883082472f, 0.0099034179f, 0.009921924211f, 0.009938570671f, 0.009953328408f, 0.009966172278f, 0.009977078997f, 0.009986029007f, 0.009993007407f, 0.009998000227f, 0.01000100002f, 0.01000200026f, 0.01000100002f, 0.009998000227f, 0.009993007407f, 0.009986029007f, 0.009977078997f, 0.009966172278f, 0.009953328408f, 0.009938570671f, 0.009921924211f, 0.0099034179f, 0.009883082472f, 0.009860955179f, 0.00983707048f, 0.009811468422f, 0.009942499921f, 0.01003720704f, 0.01013259124f, 0.01022860687f, 0.01032520272f, 0.0104223229f, 0.01051990688f, 0.01061788946f, 0.01071619987f, 0.01081476174f, 0.01091349311f, 0.01101230737f, 0.01111111138f, 0.01120980456f, 0.01130828168f, 0.01140643191f, 0.01150413603f, 0.01160127111f, 0.01169770677f, 0.0117933061f, 0.01188792568f, 0.01198141929f, 0.01207363233f, 0.01216440555f, 0.01225357689f, 0.01234097779f, 0.01242643595f, 0.01250977721f, 0.0125908237f, 0.01266939752f, 0.01274531893f, 0.01281840634f, 0.01288848184f, 0.01295536757f, 0.01301889122f, 0.01307888143f, 0.01313517336f, 0.01318760961f, 0.01323603839f, 0.01328031812f, 0.01332031563f, 0.01335590892f, 0.01338698901f, 0.0134134572f, 0.01343523059f, 0.0134522384f, 0.01346442662f, 0.01347175613f, 0.01347420178f, 0.01347175613f, 0.01346442662f, 0.0134522384f, 0.01343523059f, 0.0134134572f, 0.01338698901f, 0.01335590892f, 0.01332031563f, 0.01328031812f, 0.01323603839f, 0.01318760961f, 0.01313517336f, 0.01307888143f, 0.01301889122f, 0.01295536757f, 0.01288848184f, 0.01281840634f, 0.01274531893f, 0.01266939752f, 0.0125908237f, 0.01250977721f, 0.01242643595f, 0.01234097779f, 0.01225357689f, 0.01216440555f, 0.01207363233f, 0.01198141929f, 0.01188792568f, 0.0117933061f, 0.01169770677f, 0.01160127111f, 0.01150413603f, 0.01140643191f, 0.01130828168f, 0.01120980456f, 0.01111111138f, 0.01101230737f, 0.01091349311f, 0.01081476174f, 0.01071619987f, 0.01061788946f, 0.01051990688f, 0.0104223229f, 0.01032520272f, 0.01022860687f, 0.01013259124f, 0.01003720704f, 0.009942499921f, 0.009848512709f, 0.009877296165f, 0.00990438927f, 0.009929747321f, 0.009953328408f, 0.009975093417f, 0.009995004162f, 0.01001302525f, 0.01002912689f, 0.01004327927f, 0.01005545817f, 0.01006564032f, 0.01007380895f, 0.01007994823f, 0.01008404791f, 0.01008609962f, 0.01008609962f, 0.01008404791f, 0.01007994823f, 0.01007380895f, 0.01006564032f, 0.01005545817f, 0.01004327927f, 0.01002912689f, 0.01001302525f, 0.009995004162f, 0.009975093417f, 0.009953328408f, 0.009929747321f, 0.00990438927f, 0.009877296165f, 0.01000700705f, 0.01010255609f, 0.01019877382f, 0.01029560994f, 0.01039301138f, 0.01049092133f, 0.01058927551f, 0.01068800688f, 0.01078704093f, 0.01088629849f, 0.01098569483f, 0.01108513959f, 0.01118453499f, 0.01128377859f, 0.01138276048f, 0.01148136612f, 0.0115794735f, 0.01167695317f, 0.01177367195f, 0.01186948828f, 0.01196425594f, 0.01205782313f, 0.01215003151f, 0.01224071812f, 0.01232971624f, 0.01241685264f, 0.01250195317f, 0.01258483995f, 0.0126653323f, 0.0127432486f, 0.01281840634f, 0.01289062295f, 0.0129597187f, 0.01302551571f, 0.01308783889f, 0.01314651966f, 0.01320139226f, 0.01325230021f, 0.01329909544f, 0.01334163733f, 0.01337979734f, 0.0134134572f, 0.01344251167f, 0.01346686855f, 0.01348644961f, 0.01350119151f, 0.01351104677f, 0.01351598185f, 0.01351598185f, 0.01351104677f, 0.01350119151f, 0.01348644961f, 0.01346686855f, 0.01344251167f, 0.0134134572f, 0.01337979734f, 0.01334163733f, 0.01329909544f, 0.01325230021f, 0.01320139226f, 0.01314651966f, 0.01308783889f, 0.01302551571f, 0.0129597187f, 0.01289062295f, 0.01281840634f, 0.0127432486f, 0.0126653323f, 0.01258483995f, 0.01250195317f, 0.01241685264f, 0.01232971624f, 0.01224071812f, 0.01215003151f, 0.01205782313f, 0.01196425594f, 0.01186948828f, 0.01177367195f, 0.01167695317f, 0.0115794735f, 0.01148136612f, 0.01138276048f, 0.01128377859f, 0.01118453499f, 0.01108513959f, 0.01098569483f, 0.01088629849f, 0.01078704093f, 0.01068800688f, 0.01058927551f, 0.01049092133f, 0.01039301138f, 0.01029560994f, 0.01019877382f, 0.01010255609f, 0.01000700705f, 0.00991217047f, 0.009942499921f, 0.009971125983f, 0.009998000227f, 0.01002307981f, 0.01004632004f, 0.01006768085f, 0.01008712593f, 0.01010461897f, 0.01012013014f, 0.01013363153f, 0.01014509797f, 0.01015450899f, 0.01016184594f, 0.01016709674f, 0.01017025113f, 0.01017130353f, 0.01017025113f, 0.01016709674f, 0.01016184594f, 0.01015450899f, 0.01014509797f, 0.01013363153f, 0.01012013014f, 0.01010461897f, 0.01008712593f, 0.01006768085f, 0.01004632004f, 0.01002307981f, 0.009998000227f, 0.009971125983f, 0.009942499921f, 0.01007074397f, 0.01016709674f, 0.01026410609f, 0.01036172081f, 0.010459885f, 0.01055853814f, 0.0106576141f, 0.0107570421f, 0.01085674576f, 0.01095664315f, 0.01105664484f, 0.01115665678f, 0.01125658024f, 0.01135630626f, 0.01145572308f, 0.0115547115f, 0.01165314391f, 0.01175088901f, 0.01184780896f, 0.01194375753f, 0.01203858573f, 0.01213213522f, 0.01222424489f, 0.01231474802f, 0.01240347326f, 0.01249024551f, 0.01257488597f, 0.01265721396f, 0.01273704506f, 0.01281419583f, 0.01288848184f, 0.0129597187f, 0.01302772667f, 0.01309232507f, 0.01315334067f, 0.0132106049f, 0.01326395292f, 0.01331323106f, 0.01335829217f, 0.01339900028f, 0.01343523059f, 0.01346686855f, 0.01349381451f, 0.01351598185f, 0.01353329886f, 0.01354570966f, 0.01355317142f, 0.01355566178f, 0.01355317142f, 0.01354570966f, 0.01353329886f, 0.01351598185f, 0.01349381451f, 0.01346686855f, 0.01343523059f, 0.01339900028f, 0.01335829217f, 0.01331323106f, 0.01326395292f, 0.0132106049f, 0.01315334067f, 0.01309232507f, 0.01302772667f, 0.0129597187f, 0.01288848184f, 0.01281419583f, 0.01273704506f, 0.01265721396f, 0.01257488597f, 0.01249024551f, 0.01240347326f, 0.01231474802f, 0.01222424489f, 0.01213213522f, 0.01203858573f, 0.01194375753f, 0.01184780896f, 0.01175088901f, 0.01165314391f, 0.0115547115f, 0.01145572308f, 0.01135630626f, 0.01125658024f, 0.01115665678f, 0.01105664484f, 0.01095664315f, 0.01085674576f, 0.0107570421f, 0.0106576141f, 0.01055853814f, 0.010459885f, 0.01036172081f, 0.01026410609f, 0.01016709674f, 0.01007074397f, 0.009975093417f, 0.01000700705f, 0.01003720704f, 0.01006564032f, 0.01009226125f, 0.01011702232f, 0.01013988163f, 0.01016079728f, 0.01017973199f, 0.01019665226f, 0.01021152735f, 0.01022432931f, 0.01023503393f, 0.01024362259f, 0.01025007758f, 0.01025438774f, 0.01025654469f, 0.01025654469f, 0.01025438774f, 0.01025007758f, 0.01024362259f, 0.01023503393f, 0.01022432931f, 0.01021152735f, 0.01019665226f, 0.01017973199f, 0.01016079728f, 0.01013988163f, 0.01011702232f, 0.01009226125f, 0.01006564032f, 0.01003720704f, 0.01000700705f, 0.01013363153f, 0.01023074798f, 0.01032850612f, 0.01042685378f, 0.0105257323f, 0.01062507927f, 0.01072482392f, 0.01082489453f, 0.01092521101f, 0.01102568675f, 0.0111262314f, 0.01122674625f, 0.01132712793f, 0.01142726559f, 0.01152704284f, 0.01162633486f, 0.01172501314f, 0.01182294171f, 0.01191997528f, 0.01201596763f, 0.01211076323f, 0.01220420003f, 0.01229611505f, 0.01238633506f, 0.01247468684f, 0.01256099064f, 0.01264506485f, 0.01272672601f, 0.01280578785f, 0.01288206317f, 0.01295536757f, 0.01302551571f, 0.01309232507f, 0.01315561775f, 0.01321521774f, 0.01327095926f, 0.01332267933f, 0.01337022707f, 0.0134134572f, 0.0134522384f, 0.01348644961f, 0.01351598185f, 0.01354074106f, 0.01356064621f, 0.01357563306f, 0.01358565222f, 0.01359067019f, 0.01359067019f, 0.01358565222f, 0.01357563306f, 0.01356064621f, 0.01354074106f, 0.01351598185f, 0.01348644961f, 0.0134522384f, 0.0134134572f, 0.01337022707f, 0.01332267933f, 0.01327095926f, 0.01321521774f, 0.01315561775f, 0.01309232507f, 0.01302551571f, 0.01295536757f, 0.01288206317f, 0.01280578785f, 0.01272672601f, 0.01264506485f, 0.01256099064f, 0.01247468684f, 0.01238633506f, 0.01229611505f, 0.01220420003f, 0.01211076323f, 0.01201596763f, 0.01191997528f, 0.01182294171f, 0.01172501314f, 0.01162633486f, 0.01152704284f, 0.01142726559f, 0.01132712793f, 0.01122674625f, 0.0111262314f, 0.01102568675f, 0.01092521101f, 0.01082489453f, 0.01072482392f, 0.01062507927f, 0.0105257323f, 0.01042685378f, 0.01032850612f, 0.01023074798f, 0.01013363153f, 0.01003720704f, 0.01007074397f, 0.01010255609f, 0.01013259124f, 0.01016079728f, 0.0101871239f, 0.01021152735f, 0.01023396198f, 0.01025438774f, 0.01027276739f, 0.0102890674f, 0.01030325703f, 0.01031531021f, 0.01032520272f, 0.01033291686f, 0.01033843681f, 0.01034175418f, 0.01034285966f, 0.01034175418f, 0.01033843681f, 0.01033291686f, 0.01032520272f, 0.01031531021f, 0.01030325703f, 0.0102890674f, 0.01027276739f, 0.01025438774f, 0.01023396198f, 0.01021152735f, 0.0101871239f, 0.01016079728f, 0.01013259124f, 0.01010255609f, 0.01007074397f, 0.01019559242f, 0.01029342785f, 0.01039188914f, 0.01049092133f, 0.01059046388f, 0.01069044974f, 0.01079080813f, 0.01089146268f, 0.01099232957f, 0.01109332126f, 0.01119434182f, 0.01129528973f, 0.0113960579f, 0.01149653178f, 0.01159659028f, 0.01169610675f, 0.01179494616f, 0.01189296879f, 0.01199002843f, 0.01208597142f, 0.0121806385f, 0.01227386575f, 0.01236548461f, 0.01245531905f, 0.0125431912f, 0.01262892038f, 0.01271232124f, 0.01279320568f, 0.01287138835f, 0.01294667926f, 0.01301889122f, 0.01308783889f, 0.01315334067f, 0.01321521774f, 0.01327329688f, 0.01332741138f, 0.01337740291f, 0.01342312153f, 0.01346442662f, 0.01350119151f, 0.01353329886f, 0.01356064621f, 0.01358314604f, 0.01360072289f, 0.01361331902f, 0.0136208944f, 0.01362342201f, 0.0136208944f, 0.01361331902f, 0.01360072289f, 0.01358314604f, 0.01356064621f, 0.01353329886f, 0.01350119151f, 0.01346442662f, 0.01342312153f, 0.01337740291f, 0.01332741138f, 0.01327329688f, 0.01321521774f, 0.01315334067f, 0.01308783889f, 0.01301889122f, 0.01294667926f, 0.01287138835f, 0.01279320568f, 0.01271232124f, 0.01262892038f, 0.0125431912f, 0.01245531905f, 0.01236548461f, 0.01227386575f, 0.0121806385f, 0.01208597142f, 0.01199002843f, 0.01189296879f, 0.01179494616f, 0.01169610675f, 0.01159659028f, 0.01149653178f, 0.0113960579f, 0.01129528973f, 0.01119434182f, 0.01109332126f, 0.01099232957f, 0.01089146268f, 0.01079080813f, 0.01069044974f, 0.01059046388f, 0.01049092133f, 0.01039188914f, 0.01029342785f, 0.01019559242f, 0.01009843498f, 0.01013363153f, 0.01016709674f, 0.01019877382f, 0.01022860687f, 0.01025654469f, 0.01028253883f, 0.01030653995f, 0.01032850612f, 0.01034839638f, 0.01036617346f, 0.01038180385f, 0.01039525773f, 0.01040650904f, 0.01041553635f, 0.0104223229f, 0.01042685378f, 0.01042912249f, 0.01042912249f, 0.01042685378f, 0.0104223229f, 0.01041553635f, 0.01040650904f, 0.01039525773f, 0.01038180385f, 0.01036617346f, 0.01034839638f, 0.01032850612f, 0.01030653995f, 0.01028253883f, 0.01025654469f, 0.01022860687f, 0.01019877382f, 0.01016709674f, 0.01013363153f, 0.01025654469f, 0.01035505254f, 0.01045416761f, 0.01055383217f, 0.0106539838f, 0.0107545536f, 0.01085546613f, 0.01095664315f, 0.01105799619f, 0.01115943585f, 0.01126086153f, 0.01136216894f, 0.01146324724f, 0.01156397816f, 0.0116642369f, 0.01176389214f, 0.01186280511f, 0.0119608324f, 0.01205782313f, 0.0121536199f, 0.0122480616f, 0.01234097779f, 0.01243219618f, 0.01252153981f, 0.01260882709f, 0.01269387174f, 0.01277648844f, 0.01285648718f, 0.01293367799f, 0.01300787181f, 0.01307888143f, 0.01314651966f, 0.0132106049f, 0.01327095926f, 0.01332741138f, 0.01337979734f, 0.01342796069f, 0.01347175613f, 0.01351104677f, 0.01354570966f, 0.01357563306f, 0.01360072289f, 0.0136208944f, 0.01363608148f, 0.01364623569f, 0.01365132071f, 0.01365132071f, 0.01364623569f, 0.01363608148f, 0.0136208944f, 0.01360072289f, 0.01357563306f, 0.01354570966f, 0.01351104677f, 0.01347175613f, 0.01342796069f, 0.01337979734f, 0.01332741138f, 0.01327095926f, 0.0132106049f, 0.01314651966f, 0.01307888143f, 0.01300787181f, 0.01293367799f, 0.01285648718f, 0.01277648844f, 0.01269387174f, 0.01260882709f, 0.01252153981f, 0.01243219618f, 0.01234097779f, 0.0122480616f, 0.0121536199f, 0.01205782313f, 0.0119608324f, 0.01186280511f, 0.01176389214f, 0.0116642369f, 0.01156397816f, 0.01146324724f, 0.01136216894f, 0.01126086153f, 0.01115943585f, 0.01105799619f, 0.01095664315f, 0.01085546613f, 0.0107545536f, 0.0106539838f, 0.01055383217f, 0.01045416761f, 0.01035505254f, 0.01025654469f, 0.01015869901f, 0.01019559242f, 0.01023074798f, 0.01026410609f, 0.01029560994f, 0.01032520272f, 0.01035283227f, 0.01037844829f, 0.01040200423f, 0.01042345539f, 0.01044276077f, 0.010459885f, 0.01047479361f, 0.0104874596f, 0.01049785595f, 0.01050596405f, 0.01051176619f, 0.01051525306f, 0.01051641535f, 0.01051525306f, 0.01051176619f, 0.01050596405f, 0.01049785595f, 0.0104874596f, 0.01047479361f, 0.010459885f, 0.01044276077f, 0.01042345539f, 0.01040200423f, 0.01037844829f, 0.01035283227f, 0.01032520272f, 0.01029560994f, 0.01026410609f, 0.01023074798f, 0.01019559242f, 0.01031640731f, 0.01041553635f, 0.01051525306f, 0.01061549596f, 0.01071619987f, 0.01081729215f, 0.01091869641f, 0.01102032885f, 0.01112210099f, 0.0112239169f, 0.01132567506f, 0.01142726559f, 0.01152857486f, 0.01162947994f, 0.0117298523f, 0.0118295569f, 0.01192845311f, 0.01202639099f, 0.01212321594f, 0.01221876871f, 0.01231288072f, 0.01240538247f, 0.01249609515f, 0.01258483995f, 0.01267143246f, 0.01275568269f, 0.01283740439f, 0.01291640475f, 0.01299249288f, 0.01306547876f, 0.01313517336f, 0.01320139226f, 0.01326395292f, 0.01332267933f, 0.01337740291f, 0.01342796069f, 0.01347420178f, 0.01351598185f, 0.01355317142f, 0.01358565222f, 0.01361331902f, 0.01363608148f, 0.01365386508f, 0.01366661023f, 0.01367427502f, 0.01367683243f, 0.01367427502f, 0.01366661023f, 0.01365386508f, 0.01363608148f, 0.01361331902f, 0.01358565222f, 0.01355317142f, 0.01351598185f, 0.01347420178f, 0.01342796069f, 0.01337740291f, 0.01332267933f, 0.01326395292f, 0.01320139226f, 0.01313517336f, 0.01306547876f, 0.01299249288f, 0.01291640475f, 0.01283740439f, 0.01275568269f, 0.01267143246f, 0.01258483995f, 0.01249609515f, 0.01240538247f, 0.01231288072f, 0.01221876871f, 0.01212321594f, 0.01202639099f, 0.01192845311f, 0.0118295569f, 0.0117298523f, 0.01162947994f, 0.01152857486f, 0.01142726559f, 0.01132567506f, 0.0112239169f, 0.01112210099f, 0.01102032885f, 0.01091869641f, 0.01081729215f, 0.01071619987f, 0.01061549596f, 0.01051525306f, 0.01041553635f, 0.01031640731f, 0.01021792181f, 0.01025654469f, 0.01029342785f, 0.01032850612f, 0.01036172081f, 0.01039301138f, 0.0104223229f, 0.01044960041f, 0.01047479361f, 0.01049785595f, 0.01051874273f, 0.01053741388f, 0.01055383217f, 0.01056796685f, 0.01057978906f, 0.01058927551f, 0.01059640758f, 0.01060117036f, 0.01060355362f, 0.01060355362f, 0.01060117036f, 0.01059640758f, 0.01058927551f, 0.01057978906f, 0.01056796685f, 0.01055383217f, 0.01053741388f, 0.01051874273f, 0.01049785595f, 0.01047479361f, 0.01044960041f, 0.0104223229f, 0.01039301138f, 0.01036172081f, 0.01032850612f, 0.01029342785f, 0.01025654469f, 0.01037509646f, 0.01047479361f, 0.01057505608f, 0.01067581866f, 0.01077701338f, 0.01087856572f, 0.0109803956f, 0.0110824164f, 0.01118453499f, 0.01128665265f, 0.01138866507f, 0.01149045862f, 0.01159191411f, 0.01169290766f, 0.0117933061f, 0.01189296879f, 0.01199175231f, 0.01208950393f, 0.01218606345f, 0.01228126884f, 0.01237494871f, 0.01246692892f, 0.01255702879f, 0.01264506485f, 0.01273085084f, 0.01281419583f, 0.0128949089f, 0.0129727982f, 0.01304767188f, 0.01311933808f, 0.01318760961f, 0.01325230021f, 0.01331323106f, 0.01337022707f, 0.01342312153f, 0.01347175613f, 0.01351598185f, 0.01355566178f, 0.01359067019f, 0.0136208944f, 0.01364623569f, 0.01366661023f, 0.01368195191f, 0.01369220857f, 0.01369734481f, 0.01369734481f, 0.01369220857f, 0.01368195191f, 0.01366661023f, 0.01364623569f, 0.0136208944f, 0.01359067019f, 0.01355566178f, 0.01351598185f, 0.01347175613f, 0.01342312153f, 0.01337022707f, 0.01331323106f, 0.01325230021f, 0.01318760961f, 0.01311933808f, 0.01304767188f, 0.0129727982f, 0.0128949089f, 0.01281419583f, 0.01273085084f, 0.01264506485f, 0.01255702879f, 0.01246692892f, 0.01237494871f, 0.01228126884f, 0.01218606345f, 0.01208950393f, 0.01199175231f, 0.01189296879f, 0.0117933061f, 0.01169290766f, 0.01159191411f, 0.01149045862f, 0.01138866507f, 0.01128665265f, 0.01118453499f, 0.0110824164f, 0.0109803956f, 0.01087856572f, 0.01077701338f, 0.01067581866f, 0.01057505608f, 0.01047479361f, 0.01037509646f, 0.01027602144f, 0.01031640731f, 0.01035505254f, 0.01039188914f, 0.01042685378f, 0.010459885f, 0.01049092133f, 0.01051990688f, 0.01054678671f, 0.0105715096f, 0.01059402898f, 0.01061430015f, 0.01063228305f, 0.01064794231f, 0.01066124719f, 0.01067216974f, 0.01068068855f, 0.01068678591f, 0.01069044974f, 0.01069167163f, 0.01069044974f, 0.01068678591f, 0.01068068855f, 0.01067216974f, 0.01066124719f, 0.01064794231f, 0.01063228305f, 0.01061430015f, 0.01059402898f, 0.0105715096f, 0.01054678671f, 0.01051990688f, 0.01049092133f, 0.010459885f, 0.01042685378f, 0.01039188914f, 0.01035505254f, 0.01031640731f, 0.01043252647f, 0.01053273678f, 0.01063348539f, 0.01073470619f, 0.01083632838f, 0.01093827467f, 0.01104046032f, 0.01114279591f, 0.01124518644f, 0.01134752948f, 0.01144971419f, 0.01155162696f, 0.01165314391f, 0.0117541356f, 0.01185446698f, 0.0119539937f, 0.01205256768f, 0.01215003151f, 0.0122462241f, 0.01234097779f, 0.01243411843f, 0.01252546813f, 0.01261484437f, 0.01270206179f, 0.0127869295f, 0.01286925655f, 0.01294884924f, 0.01302551571f, 0.01309906319f, 0.01316929981f, 0.01323603839f, 0.01329909544f, 0.01335829217f, 0.0134134572f, 0.01346442662f, 0.01351104677f, 0.01355317142f, 0.01359067019f, 0.01362342201f, 0.01365132071f, 0.01367427502f, 0.01369220857f, 0.01370506082f, 0.01371278986f, 0.01371536963f, 0.01371278986f, 0.01370506082f, 0.01369220857f, 0.01367427502f, 0.01365132071f, 0.01362342201f, 0.01359067019f, 0.01355317142f, 0.01351104677f, 0.01346442662f, 0.0134134572f, 0.01335829217f, 0.01329909544f, 0.01323603839f, 0.01316929981f, 0.01309906319f, 0.01302551571f, 0.01294884924f, 0.01286925655f, 0.0127869295f, 0.01270206179f, 0.01261484437f, 0.01252546813f, 0.01243411843f, 0.01234097779f, 0.0122462241f, 0.01215003151f, 0.01205256768f, 0.0119539937f, 0.01185446698f, 0.0117541356f, 0.01165314391f, 0.01155162696f, 0.01144971419f, 0.01134752948f, 0.01124518644f, 0.01114279591f, 0.01104046032f, 0.01093827467f, 0.01083632838f, 0.01073470619f, 0.01063348539f, 0.01053273678f, 0.01043252647f, 0.01033291686f, 0.01037509646f, 0.01041553635f, 0.01045416761f, 0.01049092133f, 0.0105257323f, 0.01055853814f, 0.01058927551f, 0.01061788946f, 0.01064432319f, 0.01066852547f, 0.01069044974f, 0.01071005128f, 0.01072729193f, 0.01074213628f, 0.0107545536f, 0.01076451875f, 0.01077201031f, 0.01077701338f, 0.0107795177f, 0.0107795177f, 0.01077701338f, 0.01077201031f, 0.01076451875f, 0.0107545536f, 0.01074213628f, 0.01072729193f, 0.01071005128f, 0.01069044974f, 0.01066852547f, 0.01064432319f, 0.01061788946f, 0.01058927551f, 0.01055853814f, 0.0105257323f, 0.01049092133f, 0.01045416761f, 0.01041553635f, 0.01037509646f, 0.01048861258f, 0.01058927551f, 0.01069044974f, 0.01079206541f, 0.01089404803f, 0.01099631656f, 0.01109878626f, 0.01120136213f, 0.01130394638f, 0.01140643191f, 0.01150870696f, 0.01161065139f, 0.01171213947f, 0.01181303803f, 0.01191320643f, 0.01201249938f, 0.01211076323f, 0.01220783778f, 0.01230355818f, 0.01239775307f, 0.01249024551f, 0.01258085575f, 0.01266939752f, 0.01275568269f, 0.01283952035f, 0.01292071585f, 0.01299907733f, 0.01307440922f, 0.01314651966f, 0.01321521774f, 0.01328031812f, 0.01334163733f, 0.01339900028f, 0.0134522384f, 0.01350119151f, 0.01354570966f, 0.01358565222f, 0.0136208944f, 0.01365132071f, 0.01367683243f, 0.01369734481f, 0.01371278986f, 0.01372311637f, 0.013728288f, 0.013728288f, 0.01372311637f, 0.01371278986f, 0.01369734481f, 0.01367683243f, 0.01365132071f, 0.0136208944f, 0.01358565222f, 0.01354570966f, 0.01350119151f, 0.0134522384f, 0.01339900028f, 0.01334163733f, 0.01328031812f, 0.01321521774f, 0.01314651966f, 0.01307440922f, 0.01299907733f, 0.01292071585f, 0.01283952035f, 0.01275568269f, 0.01266939752f, 0.01258085575f, 0.01249024551f, 0.01239775307f, 0.01230355818f, 0.01220783778f, 0.01211076323f, 0.01201249938f, 0.01191320643f, 0.01181303803f, 0.01171213947f, 0.01161065139f, 0.01150870696f, 0.01140643191f, 0.01130394638f, 0.01120136213f, 0.01109878626f, 0.01099631656f, 0.01089404803f, 0.01079206541f, 0.01069044974f, 0.01058927551f, 0.01048861258f, 0.01038852427f, 0.01043252647f, 0.01047479361f, 0.01051525306f, 0.01055383217f, 0.01059046388f, 0.01062507927f, 0.0106576141f, 0.01068800688f, 0.01071619987f, 0.01074213628f, 0.01076576579f, 0.01078704093f, 0.0108059179f, 0.01082235854f, 0.01083632838f, 0.01084779948f, 0.01085674576f, 0.01086314954f, 0.01086699776f, 0.01086828113f, 0.01086699776f, 0.01086314954f, 0.01085674576f, 0.01084779948f, 0.01083632838f, 0.01082235854f, 0.0108059179f, 0.01078704093f, 0.01076576579f, 0.01074213628f, 0.01071619987f, 0.01068800688f, 0.0106576141f, 0.01062507927f, 0.01059046388f, 0.01055383217f, 0.01051525306f, 0.01047479361f, 0.01043252647f, 0.01054326911f, 0.01064432319f, 0.01074585691f, 0.01084779948f, 0.01095007174f, 0.01105259173f, 0.01115526911f, 0.01125800703f, 0.01136070304f, 0.01146324724f, 0.01156552508f, 0.01166741177f, 0.01176877879f, 0.01186948828f, 0.01196939778f, 0.01206835546f, 0.01216620672f, 0.01226278674f, 0.01235792786f, 0.01245145593f, 0.0125431912f, 0.01263295114f, 0.01272054669f, 0.01280578785f, 0.01288848184f, 0.01296843402f, 0.0130454516f, 0.01311933808f, 0.01318990346f, 0.01325695775f, 0.01332031563f, 0.01337979734f, 0.01343523059f, 0.01348644961f, 0.01353329886f, 0.01357563306f, 0.01361331902f, 0.01364623569f, 0.01367427502f, 0.01369734481f, 0.01371536963f, 0.013728288f, 0.01373605616f, 0.01373864897f, 0.01373605616f, 0.013728288f, 0.01371536963f, 0.01369734481f, 0.01367427502f, 0.01364623569f, 0.01361331902f, 0.01357563306f, 0.01353329886f, 0.01348644961f, 0.01343523059f, 0.01337979734f, 0.01332031563f, 0.01325695775f, 0.01318990346f, 0.01311933808f, 0.0130454516f, 0.01296843402f, 0.01288848184f, 0.01280578785f, 0.01272054669f, 0.01263295114f, 0.0125431912f, 0.01245145593f, 0.01235792786f, 0.01226278674f, 0.01216620672f, 0.01206835546f, 0.01196939778f, 0.01186948828f, 0.01176877879f, 0.01166741177f, 0.01156552508f, 0.01146324724f, 0.01136070304f, 0.01125800703f, 0.01115526911f, 0.01105259173f, 0.01095007174f, 0.01084779948f, 0.01074585691f, 0.01064432319f, 0.01054326911f, 0.01044276077f, 0.01048861258f, 0.01053273678f, 0.01057505608f, 0.01061549596f, 0.0106539838f, 0.01069044974f, 0.01072482392f, 0.0107570421f, 0.01078704093f, 0.01081476174f, 0.01084014867f, 0.01086314954f, 0.01088371873f, 0.0109018134f, 0.01091739535f, 0.01093043014f, 0.01094089262f, 0.0109487595f, 0.01095401309f, 0.01095664315f, 0.01095664315f, 0.01095401309f, 0.0109487595f, 0.01094089262f, 0.01093043014f, 0.01091739535f, 0.0109018134f, 0.01088371873f, 0.01086314954f, 0.01084014867f, 0.01081476174f, 0.01078704093f, 0.0107570421f, 0.01072482392f, 0.01069044974f, 0.0106539838f, 0.01061549596f, 0.01057505608f, 0.01053273678f, 0.01048861258f, 0.01059640758f, 0.01069778763f, 0.01079961471f, 0.0109018134f, 0.01100430358f, 0.01110699773f, 0.01120980456f, 0.01131262258f, 0.01141534653f, 0.01151786372f, 0.01162005402f, 0.01172179077f, 0.01182294171f, 0.01192336436f, 0.01202291343f, 0.01212143432f, 0.01221876871f, 0.01231474802f, 0.01240920182f, 0.01250195317f, 0.01259282045f, 0.0126816174f, 0.01276815403f, 0.01285223942f, 0.01293367799f, 0.01301227603f, 0.01308783889f, 0.01316017378f, 0.013229087f, 0.0132943932f, 0.01335590892f, 0.0134134572f, 0.01346686855f, 0.01351598185f, 0.01356064621f, 0.01360072289f, 0.01363608148f, 0.01366661023f, 0.01369220857f, 0.01371278986f, 0.013728288f, 0.01373864897f, 0.0137438383f, 0.0137438383f, 0.01373864897f, 0.013728288f, 0.01371278986f, 0.01369220857f, 0.01366661023f, 0.01363608148f, 0.01360072289f, 0.01356064621f, 0.01351598185f, 0.01346686855f, 0.0134134572f, 0.01335590892f, 0.0132943932f, 0.013229087f, 0.01316017378f, 0.01308783889f, 0.01301227603f, 0.01293367799f, 0.01285223942f, 0.01276815403f, 0.0126816174f, 0.01259282045f, 0.01250195317f, 0.01240920182f, 0.01231474802f, 0.01221876871f, 0.01212143432f, 0.01202291343f, 0.01192336436f, 0.01182294171f, 0.01172179077f, 0.01162005402f, 0.01151786372f, 0.01141534653f, 0.01131262258f, 0.01120980456f, 0.01110699773f, 0.01100430358f, 0.0109018134f, 0.01079961471f, 0.01069778763f, 0.01059640758f, 0.01049554255f, 0.01054326911f, 0.01058927551f, 0.01063348539f, 0.01067581866f, 0.01071619987f, 0.0107545536f, 0.01079080813f, 0.01082489453f, 0.01085674576f, 0.01088629849f, 0.01091349311f, 0.01093827467f, 0.01096059103f, 0.0109803956f, 0.01099764649f, 0.01101230737f, 0.01102434658f, 0.01103373803f, 0.01104046032f, 0.01104449946f, 0.01104584709f, 0.01104449946f, 0.01104046032f, 0.01103373803f, 0.01102434658f, 0.01101230737f, 0.01099764649f, 0.0109803956f, 0.01096059103f, 0.01093827467f, 0.01091349311f, 0.01088629849f, 0.01085674576f, 0.01082489453f, 0.01079080813f, 0.0107545536f, 0.01071619987f, 0.01067581866f, 0.01063348539f, 0.01058927551f, 0.01054326911f, 0.01064794231f, 0.01074958127f, 0.01085163094f, 0.01095401309f, 0.01105664484f, 0.01115943585f, 0.01126228925f, 0.01136510447f, 0.01146776881f, 0.01157016866f, 0.01167218015f, 0.01177367195f, 0.01187450811f, 0.0119745452f, 0.01207363233f, 0.01217161212f, 0.01226832252f, 0.01236359403f, 0.01245725155f, 0.01254911628f, 0.01263900381f, 0.01272672601f, 0.01281209197f, 0.0128949089f, 0.01297498215f, 0.01305211708f, 0.01312611811f, 0.01319679338f, 0.01326395292f, 0.01332741138f, 0.01338698901f, 0.01344251167f, 0.01349381451f, 0.01354074106f, 0.01358314604f, 0.0136208944f, 0.01365386508f, 0.01368195191f, 0.01370506082f, 0.01372311637f, 0.01373605616f, 0.0137438383f, 0.01374643482f, 0.0137438383f, 0.01373605616f, 0.01372311637f, 0.01370506082f, 0.01368195191f, 0.01365386508f, 0.0136208944f, 0.01358314604f, 0.01354074106f, 0.01349381451f, 0.01344251167f, 0.01338698901f, 0.01332741138f, 0.01326395292f, 0.01319679338f, 0.01312611811f, 0.01305211708f, 0.01297498215f, 0.0128949089f, 0.01281209197f, 0.01272672601f, 0.01263900381f, 0.01254911628f, 0.01245725155f, 0.01236359403f, 0.01226832252f, 0.01217161212f, 0.01207363233f, 0.0119745452f, 0.01187450811f, 0.01177367195f, 0.01167218015f, 0.01157016866f, 0.01146776881f, 0.01136510447f, 0.01126228925f, 0.01115943585f, 0.01105664484f, 0.01095401309f, 0.01085163094f, 0.01074958127f, 0.01064794231f, 0.01054678671f, 0.01059640758f, 0.01064432319f, 0.01069044974f, 0.01073470619f, 0.01077701338f, 0.01081729215f, 0.01085546613f, 0.01089146268f, 0.01092521101f, 0.01095664315f, 0.01098569483f, 0.01101230737f, 0.0110364249f, 0.01105799619f, 0.01107697561f, 0.01109332126f, 0.01110699773f, 0.01111797616f, 0.0111262314f, 0.01113174483f, 0.01113450434f, 0.01113450434f, 0.01113174483f, 0.0111262314f, 0.01111797616f, 0.01110699773f, 0.01109332126f, 0.01107697561f, 0.01105799619f, 0.0110364249f, 0.01101230737f, 0.01098569483f, 0.01095664315f, 0.01092521101f, 0.01089146268f, 0.01085546613f, 0.01081729215f, 0.01077701338f, 0.01073470619f, 0.01069044974f, 0.01064432319f, 0.01059640758f, 0.01069778763f, 0.01079961471f, 0.0109018134f, 0.01100430358f, 0.01110699773f, 0.01120980456f, 0.01131262258f, 0.01141534653f, 0.01151786372f, 0.01162005402f, 0.01172179077f, 0.01182294171f, 0.01192336436f, 0.01202291343f, 0.01212143432f, 0.01221876871f, 0.01231474802f, 0.01240920182f, 0.01250195317f, 0.01259282045f, 0.0126816174f, 0.01276815403f, 0.01285223942f, 0.01293367799f, 0.01301227603f, 0.01308783889f, 0.01316017378f, 0.013229087f, 0.0132943932f, 0.01335590892f, 0.0134134572f, 0.01346686855f, 0.01351598185f, 0.01356064621f, 0.01360072289f, 0.01363608148f, 0.01366661023f, 0.01369220857f, 0.01371278986f, 0.013728288f, 0.01373864897f, 0.0137438383f, 0.0137438383f, 0.01373864897f, 0.013728288f, 0.01371278986f, 0.01369220857f, 0.01366661023f, 0.01363608148f, 0.01360072289f, 0.01356064621f, 0.01351598185f, 0.01346686855f, 0.0134134572f, 0.01335590892f, 0.0132943932f, 0.013229087f, 0.01316017378f, 0.01308783889f, 0.01301227603f, 0.01293367799f, 0.01285223942f, 0.01276815403f, 0.0126816174f, 0.01259282045f, 0.01250195317f, 0.01240920182f, 0.01231474802f, 0.01221876871f, 0.01212143432f, 0.01202291343f, 0.01192336436f, 0.01182294171f, 0.01172179077f, 0.01162005402f, 0.01151786372f, 0.01141534653f, 0.01131262258f, 0.01120980456f, 0.01110699773f, 0.01100430358f, 0.0109018134f, 0.01079961471f, 0.01069778763f, 0.01059640758f, 0.01064794231f, 0.01069778763f, 0.01074585691f, 0.01079206541f, 0.01083632838f, 0.01087856572f, 0.01091869641f, 0.01095664315f, 0.01099232957f, 0.01102568675f, 0.01105664484f, 0.01108513959f, 0.01111111138f, 0.01113450434f, 0.01115526911f, 0.01117335912f, 0.01118873432f, 0.01120136213f, 0.01121121366f, 0.01121826563f, 0.01122250315f, 0.0112239169f, 0.01122250315f, 0.01121826563f, 0.01121121366f, 0.01120136213f, 0.01118873432f, 0.01117335912f, 0.01115526911f, 0.01113450434f, 0.01111111138f, 0.01108513959f, 0.01105664484f, 0.01102568675f, 0.01099232957f, 0.01095664315f, 0.01091869641f, 0.01087856572f, 0.01083632838f, 0.01079206541f, 0.01074585691f, 0.01069778763f, 0.01064794231f, 0.01074585691f, 0.01084779948f, 0.01095007174f, 0.01105259173f, 0.01115526911f, 0.01125800703f, 0.01136070304f, 0.01146324724f, 0.01156552508f, 0.01166741177f, 0.01176877879f, 0.01186948828f, 0.01196939778f, 0.01206835546f, 0.01216620672f, 0.01226278674f, 0.01235792786f, 0.01245145593f, 0.0125431912f, 0.01263295114f, 0.01272054669f, 0.01280578785f, 0.01288848184f, 0.01296843402f, 0.0130454516f, 0.01311933808f, 0.01318990346f, 0.01325695775f, 0.01332031563f, 0.01337979734f, 0.01343523059f, 0.01348644961f, 0.01353329886f, 0.01357563306f, 0.01361331902f, 0.01364623569f, 0.01367427502f, 0.01369734481f, 0.01371536963f, 0.013728288f, 0.01373605616f, 0.01373864897f, 0.01373605616f, 0.013728288f, 0.01371536963f, 0.01369734481f, 0.01367427502f, 0.01364623569f, 0.01361331902f, 0.01357563306f, 0.01353329886f, 0.01348644961f, 0.01343523059f, 0.01337979734f, 0.01332031563f, 0.01325695775f, 0.01318990346f, 0.01311933808f, 0.0130454516f, 0.01296843402f, 0.01288848184f, 0.01280578785f, 0.01272054669f, 0.01263295114f, 0.0125431912f, 0.01245145593f, 0.01235792786f, 0.01226278674f, 0.01216620672f, 0.01206835546f, 0.01196939778f, 0.01186948828f, 0.01176877879f, 0.01166741177f, 0.01156552508f, 0.01146324724f, 0.01136070304f, 0.01125800703f, 0.01115526911f, 0.01105259173f, 0.01095007174f, 0.01084779948f, 0.01074585691f, 0.01064432319f, 0.01069778763f, 0.01074958127f, 0.01079961471f, 0.01084779948f, 0.01089404803f, 0.01093827467f, 0.0109803956f, 0.01102032885f, 0.01105799619f, 0.01109332126f, 0.0111262314f, 0.01115665678f, 0.01118453499f, 0.01120980456f, 0.01123241056f, 0.01125230361f, 0.01126943901f, 0.01128377859f, 0.01129528973f, 0.01130394638f, 0.01130972803f, 0.01131262258f, 0.01131262258f, 0.01130972803f, 0.01130394638f, 0.01129528973f, 0.01128377859f, 0.01126943901f, 0.01125230361f, 0.01123241056f, 0.01120980456f, 0.01118453499f, 0.01115665678f, 0.0111262314f, 0.01109332126f, 0.01105799619f, 0.01102032885f, 0.0109803956f, 0.01093827467f, 0.01089404803f, 0.01084779948f, 0.01079961471f, 0.01074958127f, 0.01069778763f, 0.01079206541f, 0.01089404803f, 0.01099631656f, 0.01109878626f, 0.01120136213f, 0.01130394638f, 0.01140643191f, 0.01150870696f, 0.01161065139f, 0.01171213947f, 0.01181303803f, 0.01191320643f, 0.01201249938f, 0.01211076323f, 0.01220783778f, 0.01230355818f, 0.01239775307f, 0.01249024551f, 0.01258085575f, 0.01266939752f, 0.01275568269f, 0.01283952035f, 0.01292071585f, 0.01299907733f, 0.01307440922f, 0.01314651966f, 0.01321521774f, 0.01328031812f, 0.01334163733f, 0.01339900028f, 0.0134522384f, 0.01350119151f, 0.01354570966f, 0.01358565222f, 0.0136208944f, 0.01365132071f, 0.01367683243f, 0.01369734481f, 0.01371278986f, 0.01372311637f, 0.013728288f, 0.013728288f, 0.01372311637f, 0.01371278986f, 0.01369734481f, 0.01367683243f, 0.01365132071f, 0.0136208944f, 0.01358565222f, 0.01354570966f, 0.01350119151f, 0.0134522384f, 0.01339900028f, 0.01334163733f, 0.01328031812f, 0.01321521774f, 0.01314651966f, 0.01307440922f, 0.01299907733f, 0.01292071585f, 0.01283952035f, 0.01275568269f, 0.01266939752f, 0.01258085575f, 0.01249024551f, 0.01239775307f, 0.01230355818f, 0.01220783778f, 0.01211076323f, 0.01201249938f, 0.01191320643f, 0.01181303803f, 0.01171213947f, 0.01161065139f, 0.01150870696f, 0.01140643191f, 0.01130394638f, 0.01120136213f, 0.01109878626f, 0.01099631656f, 0.01089404803f, 0.01079206541f, 0.01069044974f, 0.01074585691f, 0.01079961471f, 0.01085163094f, 0.0109018134f, 0.01095007174f, 0.01099631656f, 0.01104046032f, 0.0110824164f, 0.01112210099f, 0.01115943585f, 0.01119434182f, 0.01122674625f, 0.01125658024f, 0.01128377859f, 0.01130828168f, 0.01133003552f, 0.01134899072f, 0.01136510447f, 0.01137833856f, 0.01138866507f, 0.0113960579f, 0.01140050031f, 0.01140198205f, 0.01140050031f, 0.0113960579f, 0.01138866507f, 0.01137833856f, 0.01136510447f, 0.01134899072f, 0.01133003552f, 0.01130828168f, 0.01128377859f, 0.01125658024f, 0.01122674625f, 0.01119434182f, 0.01115943585f, 0.01112210099f, 0.0110824164f, 0.01104046032f, 0.01099631656f, 0.01095007174f, 0.0109018134f, 0.01085163094f, 0.01079961471f, 0.01074585691f, 0.01083632838f, 0.01093827467f, 0.01104046032f, 0.01114279591f, 0.01124518644f, 0.01134752948f, 0.01144971419f, 0.01155162696f, 0.01165314391f, 0.0117541356f, 0.01185446698f, 0.0119539937f, 0.01205256768f, 0.01215003151f, 0.0122462241f, 0.01234097779f, 0.01243411843f, 0.01252546813f, 0.01261484437f, 0.01270206179f, 0.0127869295f, 0.01286925655f, 0.01294884924f, 0.01302551571f, 0.01309906319f, 0.01316929981f, 0.01323603839f, 0.01329909544f, 0.01335829217f, 0.0134134572f, 0.01346442662f, 0.01351104677f, 0.01355317142f, 0.01359067019f, 0.01362342201f, 0.01365132071f, 0.01367427502f, 0.01369220857f, 0.01370506082f, 0.01371278986f, 0.01371536963f, 0.01371278986f, 0.01370506082f, 0.01369220857f, 0.01367427502f, 0.01365132071f, 0.01362342201f, 0.01359067019f, 0.01355317142f, 0.01351104677f, 0.01346442662f, 0.0134134572f, 0.01335829217f, 0.01329909544f, 0.01323603839f, 0.01316929981f, 0.01309906319f, 0.01302551571f, 0.01294884924f, 0.01286925655f, 0.0127869295f, 0.01270206179f, 0.01261484437f, 0.01252546813f, 0.01243411843f, 0.01234097779f, 0.0122462241f, 0.01215003151f, 0.01205256768f, 0.0119539937f, 0.01185446698f, 0.0117541356f, 0.01165314391f, 0.01155162696f, 0.01144971419f, 0.01134752948f, 0.01124518644f, 0.01114279591f, 0.01104046032f, 0.01093827467f, 0.01083632838f, 0.01073470619f, 0.01079206541f, 0.01084779948f, 0.0109018134f, 0.01095401309f, 0.01100430358f, 0.01105259173f, 0.01109878626f, 0.01114279591f, 0.01118453499f, 0.0112239169f, 0.01126086153f, 0.01129528973f, 0.01132712793f, 0.01135630626f, 0.01138276048f, 0.01140643191f, 0.01142726559f, 0.01144521404f, 0.01146023627f, 0.01147229597f, 0.01148136612f, 0.0114874253f, 0.01149045862f, 0.01149045862f, 0.0114874253f, 0.01148136612f, 0.01147229597f, 0.01146023627f, 0.01144521404f, 0.01142726559f, 0.01140643191f, 0.01138276048f, 0.01135630626f, 0.01132712793f, 0.01129528973f, 0.01126086153f, 0.0112239169f, 0.01118453499f, 0.01114279591f, 0.01109878626f, 0.01105259173f, 0.01100430358f, 0.01095401309f, 0.0109018134f, 0.01084779948f, 0.01079206541f, 0.01087856572f, 0.0109803956f, 0.0110824164f, 0.01118453499f, 0.01128665265f, 0.01138866507f, 0.01149045862f, 0.01159191411f, 0.01169290766f, 0.0117933061f, 0.01189296879f, 0.01199175231f, 0.01208950393f, 0.01218606345f, 0.01228126884f, 0.01237494871f, 0.01246692892f, 0.01255702879f, 0.01264506485f, 0.01273085084f, 0.01281419583f, 0.0128949089f, 0.0129727982f, 0.01304767188f, 0.01311933808f, 0.01318760961f, 0.01325230021f, 0.01331323106f, 0.01337022707f, 0.01342312153f, 0.01347175613f, 0.01351598185f, 0.01355566178f, 0.01359067019f, 0.0136208944f, 0.01364623569f, 0.01366661023f, 0.01368195191f, 0.01369220857f, 0.01369734481f, 0.01369734481f, 0.01369220857f, 0.01368195191f, 0.01366661023f, 0.01364623569f, 0.0136208944f, 0.01359067019f, 0.01355566178f, 0.01351598185f, 0.01347175613f, 0.01342312153f, 0.01337022707f, 0.01331323106f, 0.01325230021f, 0.01318760961f, 0.01311933808f, 0.01304767188f, 0.0129727982f, 0.0128949089f, 0.01281419583f, 0.01273085084f, 0.01264506485f, 0.01255702879f, 0.01246692892f, 0.01237494871f, 0.01228126884f, 0.01218606345f, 0.01208950393f, 0.01199175231f, 0.01189296879f, 0.0117933061f, 0.01169290766f, 0.01159191411f, 0.01149045862f, 0.01138866507f, 0.01128665265f, 0.01118453499f, 0.0110824164f, 0.0109803956f, 0.01087856572f, 0.01077701338f, 0.01083632838f, 0.01089404803f, 0.01095007174f, 0.01100430358f, 0.01105664484f, 0.01110699773f, 0.01115526911f, 0.01120136213f, 0.01124518644f, 0.01128665265f, 0.01132567506f, 0.01136216894f, 0.0113960579f, 0.01142726559f, 0.01145572308f, 0.01148136612f, 0.01150413603f, 0.01152398065f, 0.01154085156f, 0.0115547115f, 0.01156552508f, 0.0115732681f, 0.01157792099f, 0.0115794735f, 0.01157792099f, 0.0115732681f, 0.01156552508f, 0.0115547115f, 0.01154085156f, 0.01152398065f, 0.01150413603f, 0.01148136612f, 0.01145572308f, 0.01142726559f, 0.0113960579f, 0.01136216894f, 0.01132567506f, 0.01128665265f, 0.01124518644f, 0.01120136213f, 0.01115526911f, 0.01110699773f, 0.01105664484f, 0.01100430358f, 0.01095007174f, 0.01089404803f, 0.01083632838f, 0.01091869641f, 0.01102032885f, 0.01112210099f, 0.0112239169f, 0.01132567506f, 0.01142726559f, 0.01152857486f, 0.01162947994f, 0.0117298523f, 0.0118295569f, 0.01192845311f, 0.01202639099f, 0.01212321594f, 0.01221876871f, 0.01231288072f, 0.01240538247f, 0.01249609515f, 0.01258483995f, 0.01267143246f, 0.01275568269f, 0.01283740439f, 0.01291640475f, 0.01299249288f, 0.01306547876f, 0.01313517336f, 0.01320139226f, 0.01326395292f, 0.01332267933f, 0.01337740291f, 0.01342796069f, 0.01347420178f, 0.01351598185f, 0.01355317142f, 0.01358565222f, 0.01361331902f, 0.01363608148f, 0.01365386508f, 0.01366661023f, 0.01367427502f, 0.01367683243f, 0.01367427502f, 0.01366661023f, 0.01365386508f, 0.01363608148f, 0.01361331902f, 0.01358565222f, 0.01355317142f, 0.01351598185f, 0.01347420178f, 0.01342796069f, 0.01337740291f, 0.01332267933f, 0.01326395292f, 0.01320139226f, 0.01313517336f, 0.01306547876f, 0.01299249288f, 0.01291640475f, 0.01283740439f, 0.01275568269f, 0.01267143246f, 0.01258483995f, 0.01249609515f, 0.01240538247f, 0.01231288072f, 0.01221876871f, 0.01212321594f, 0.01202639099f, 0.01192845311f, 0.0118295569f, 0.0117298523f, 0.01162947994f, 0.01152857486f, 0.01142726559f, 0.01132567506f, 0.0112239169f, 0.01112210099f, 0.01102032885f, 0.01091869641f, 0.01081729215f, 0.01087856572f, 0.01093827467f, 0.01099631656f, 0.01105259173f, 0.01110699773f, 0.01115943585f, 0.01120980456f, 0.01125800703f, 0.01130394638f, 0.01134752948f, 0.01138866507f, 0.01142726559f, 0.01146324724f, 0.01149653178f, 0.01152704284f, 0.0115547115f, 0.0115794735f, 0.01160127111f, 0.01162005402f, 0.01163577568f, 0.01164839976f, 0.01165789459f, 0.0116642369f, 0.01166741177f, 0.01166741177f, 0.0116642369f, 0.01165789459f, 0.01164839976f, 0.01163577568f, 0.01162005402f, 0.01160127111f, 0.0115794735f, 0.0115547115f, 0.01152704284f, 0.01149653178f, 0.01146324724f, 0.01142726559f, 0.01138866507f, 0.01134752948f, 0.01130394638f, 0.01125800703f, 0.01120980456f, 0.01115943585f, 0.01110699773f, 0.01105259173f, 0.01099631656f, 0.01093827467f, 0.01087856572f, 0.01095664315f, 0.01105799619f, 0.01115943585f, 0.01126086153f, 0.01136216894f, 0.01146324724f, 0.01156397816f, 0.0116642369f, 0.01176389214f, 0.01186280511f, 0.0119608324f, 0.01205782313f, 0.0121536199f, 0.0122480616f, 0.01234097779f, 0.01243219618f, 0.01252153981f, 0.01260882709f, 0.01269387174f, 0.01277648844f, 0.01285648718f, 0.01293367799f, 0.01300787181f, 0.01307888143f, 0.01314651966f, 0.0132106049f, 0.01327095926f, 0.01332741138f, 0.01337979734f, 0.01342796069f, 0.01347175613f, 0.01351104677f, 0.01354570966f, 0.01357563306f, 0.01360072289f, 0.0136208944f, 0.01363608148f, 0.01364623569f, 0.01365132071f, 0.01365132071f, 0.01364623569f, 0.01363608148f, 0.0136208944f, 0.01360072289f, 0.01357563306f, 0.01354570966f, 0.01351104677f, 0.01347175613f, 0.01342796069f, 0.01337979734f, 0.01332741138f, 0.01327095926f, 0.0132106049f, 0.01314651966f, 0.01307888143f, 0.01300787181f, 0.01293367799f, 0.01285648718f, 0.01277648844f, 0.01269387174f, 0.01260882709f, 0.01252153981f, 0.01243219618f, 0.01234097779f, 0.0122480616f, 0.0121536199f, 0.01205782313f, 0.0119608324f, 0.01186280511f, 0.01176389214f, 0.0116642369f, 0.01156397816f, 0.01146324724f, 0.01136216894f, 0.01126086153f, 0.01115943585f, 0.01105799619f, 0.01095664315f, 0.01085546613f, 0.01091869641f, 0.0109803956f, 0.01104046032f, 0.01109878626f, 0.01115526911f, 0.01120980456f, 0.01126228925f, 0.01131262258f, 0.01136070304f, 0.01140643191f, 0.01144971419f, 0.01149045862f, 0.01152857486f, 0.01156397816f, 0.01159659028f, 0.01162633486f, 0.01165314391f, 0.01167695317f, 0.01169770677f, 0.0117153544f, 0.0117298523f, 0.011741166f, 0.01174926758f, 0.0117541356f, 0.01175575983f, 0.0117541356f, 0.01174926758f, 0.011741166f, 0.0117298523f, 0.0117153544f, 0.01169770677f, 0.01167695317f, 0.01165314391f, 0.01162633486f, 0.01159659028f, 0.01156397816f, 0.01152857486f, 0.01149045862f, 0.01144971419f, 0.01140643191f, 0.01136070304f, 0.01131262258f, 0.01126228925f, 0.01120980456f, 0.01115526911f, 0.01109878626f, 0.01104046032f, 0.0109803956f, 0.01091869641f, 0.01099232957f, 0.01109332126f, 0.01119434182f, 0.01129528973f, 0.0113960579f, 0.01149653178f, 0.01159659028f, 0.01169610675f, 0.01179494616f, 0.01189296879f, 0.01199002843f, 0.01208597142f, 0.0121806385f, 0.01227386575f, 0.01236548461f, 0.01245531905f, 0.0125431912f, 0.01262892038f, 0.01271232124f, 0.01279320568f, 0.01287138835f, 0.01294667926f, 0.01301889122f, 0.01308783889f, 0.01315334067f, 0.01321521774f, 0.01327329688f, 0.01332741138f, 0.01337740291f, 0.01342312153f, 0.01346442662f, 0.01350119151f, 0.01353329886f, 0.01356064621f, 0.01358314604f, 0.01360072289f, 0.01361331902f, 0.0136208944f, 0.01362342201f, 0.0136208944f, 0.01361331902f, 0.01360072289f, 0.01358314604f, 0.01356064621f, 0.01353329886f, 0.01350119151f, 0.01346442662f, 0.01342312153f, 0.01337740291f, 0.01332741138f, 0.01327329688f, 0.01321521774f, 0.01315334067f, 0.01308783889f, 0.01301889122f, 0.01294667926f, 0.01287138835f, 0.01279320568f, 0.01271232124f, 0.01262892038f, 0.0125431912f, 0.01245531905f, 0.01236548461f, 0.01227386575f, 0.0121806385f, 0.01208597142f, 0.01199002843f, 0.01189296879f, 0.01179494616f, 0.01169610675f, 0.01159659028f, 0.01149653178f, 0.0113960579f, 0.01129528973f, 0.01119434182f, 0.01109332126f, 0.01099232957f, 0.01089146268f, 0.01095664315f, 0.01102032885f, 0.0110824164f, 0.01114279591f, 0.01120136213f, 0.01125800703f, 0.01131262258f, 0.01136510447f, 0.01141534653f, 0.01146324724f, 0.01150870696f, 0.01155162696f, 0.01159191411f, 0.01162947994f, 0.0116642369f, 0.01169610675f, 0.01172501314f, 0.01175088901f, 0.01177367195f, 0.0117933061f, 0.01180974208f, 0.01182294171f, 0.01183286961f, 0.01183950249f, 0.01184282266f, 0.01184282266f, 0.01183950249f, 0.01183286961f, 0.01182294171f, 0.01180974208f, 0.0117933061f, 0.01177367195f, 0.01175088901f, 0.01172501314f, 0.01169610675f, 0.0116642369f, 0.01162947994f, 0.01159191411f, 0.01155162696f, 0.01150870696f, 0.01146324724f, 0.01141534653f, 0.01136510447f, 0.01131262258f, 0.01125800703f, 0.01120136213f, 0.01114279591f, 0.0110824164f, 0.01102032885f, 0.01095664315f, 0.01102568675f, 0.0111262314f, 0.01122674625f, 0.01132712793f, 0.01142726559f, 0.01152704284f, 0.01162633486f, 0.01172501314f, 0.01182294171f, 0.01191997528f, 0.01201596763f, 0.01211076323f, 0.01220420003f, 0.01229611505f, 0.01238633506f, 0.01247468684f, 0.01256099064f, 0.01264506485f, 0.01272672601f, 0.01280578785f, 0.01288206317f, 0.01295536757f, 0.01302551571f, 0.01309232507f, 0.01315561775f, 0.01321521774f, 0.01327095926f, 0.01332267933f, 0.01337022707f, 0.0134134572f, 0.0134522384f, 0.01348644961f, 0.01351598185f, 0.01354074106f, 0.01356064621f, 0.01357563306f, 0.01358565222f, 0.01359067019f, 0.01359067019f, 0.01358565222f, 0.01357563306f, 0.01356064621f, 0.01354074106f, 0.01351598185f, 0.01348644961f, 0.0134522384f, 0.0134134572f, 0.01337022707f, 0.01332267933f, 0.01327095926f, 0.01321521774f, 0.01315561775f, 0.01309232507f, 0.01302551571f, 0.01295536757f, 0.01288206317f, 0.01280578785f, 0.01272672601f, 0.01264506485f, 0.01256099064f, 0.01247468684f, 0.01238633506f, 0.01229611505f, 0.01220420003f, 0.01211076323f, 0.01201596763f, 0.01191997528f, 0.01182294171f, 0.01172501314f, 0.01162633486f, 0.01152704284f, 0.01142726559f, 0.01132712793f, 0.01122674625f, 0.0111262314f, 0.01102568675f, 0.01092521101f, 0.01099232957f, 0.01105799619f, 0.01112210099f, 0.01118453499f, 0.01124518644f, 0.01130394638f, 0.01136070304f, 0.01141534653f, 0.01146776881f, 0.01151786372f, 0.01156552508f, 0.01161065139f, 0.01165314391f, 0.01169290766f, 0.0117298523f, 0.01176389214f, 0.01179494616f, 0.01182294171f, 0.01184780896f, 0.01186948828f, 0.01188792568f, 0.0119030755f, 0.01191489771f, 0.01192336436f, 0.01192845311f, 0.01193015091f, 0.01192845311f, 0.01192336436f, 0.01191489771f, 0.0119030755f, 0.01188792568f, 0.01186948828f, 0.01184780896f, 0.01182294171f, 0.01179494616f, 0.01176389214f, 0.0117298523f, 0.01169290766f, 0.01165314391f, 0.01161065139f, 0.01156552508f, 0.01151786372f, 0.01146776881f, 0.01141534653f, 0.01136070304f, 0.01130394638f, 0.01124518644f, 0.01118453499f, 0.01112210099f, 0.01105799619f, 0.01099232957f, 0.01105664484f, 0.01115665678f, 0.01125658024f, 0.01135630626f, 0.01145572308f, 0.0115547115f, 0.01165314391f, 0.01175088901f, 0.01184780896f, 0.01194375753f, 0.01203858573f, 0.01213213522f, 0.01222424489f, 0.01231474802f, 0.01240347326f, 0.01249024551f, 0.01257488597f, 0.01265721396f, 0.01273704506f, 0.01281419583f, 0.01288848184f, 0.0129597187f, 0.01302772667f, 0.01309232507f, 0.01315334067f, 0.0132106049f, 0.01326395292f, 0.01331323106f, 0.01335829217f, 0.01339900028f, 0.01343523059f, 0.01346686855f, 0.01349381451f, 0.01351598185f, 0.01353329886f, 0.01354570966f, 0.01355317142f, 0.01355566178f, 0.01355317142f, 0.01354570966f, 0.01353329886f, 0.01351598185f, 0.01349381451f, 0.01346686855f, 0.01343523059f, 0.01339900028f, 0.01335829217f, 0.01331323106f, 0.01326395292f, 0.0132106049f, 0.01315334067f, 0.01309232507f, 0.01302772667f, 0.0129597187f, 0.01288848184f, 0.01281419583f, 0.01273704506f, 0.01265721396f, 0.01257488597f, 0.01249024551f, 0.01240347326f, 0.01231474802f, 0.01222424489f, 0.01213213522f, 0.01203858573f, 0.01194375753f, 0.01184780896f, 0.01175088901f, 0.01165314391f, 0.0115547115f, 0.01145572308f, 0.01135630626f, 0.01125658024f, 0.01115665678f, 0.01105664484f, 0.01095664315f, 0.01102568675f, 0.01109332126f, 0.01115943585f, 0.0112239169f, 0.01128665265f, 0.01134752948f, 0.01140643191f, 0.01146324724f, 0.01151786372f, 0.01157016866f, 0.01162005402f, 0.01166741177f, 0.01171213947f, 0.0117541356f, 0.0117933061f, 0.0118295569f, 0.01186280511f, 0.01189296879f, 0.01191997528f, 0.01194375753f, 0.01196425594f, 0.01198141929f, 0.01199520286f, 0.01200557221f, 0.01201249938f, 0.01201596763f, 0.01201596763f, 0.01201249938f, 0.01200557221f, 0.01199520286f, 0.01198141929f, 0.01196425594f, 0.01194375753f, 0.01191997528f, 0.01189296879f, 0.01186280511f, 0.0118295569f, 0.0117933061f, 0.0117541356f, 0.01171213947f, 0.01166741177f, 0.01162005402f, 0.01157016866f, 0.01151786372f, 0.01146324724f, 0.01140643191f, 0.01134752948f, 0.01128665265f, 0.0112239169f, 0.01115943585f, 0.01109332126f, 0.01102568675f, 0.01108513959f, 0.01118453499f, 0.01128377859f, 0.01138276048f, 0.01148136612f, 0.0115794735f, 0.01167695317f, 0.01177367195f, 0.01186948828f, 0.01196425594f, 0.01205782313f, 0.01215003151f, 0.01224071812f, 0.01232971624f, 0.01241685264f, 0.01250195317f, 0.01258483995f, 0.0126653323f, 0.0127432486f, 0.01281840634f, 0.01289062295f, 0.0129597187f, 0.01302551571f, 0.01308783889f, 0.01314651966f, 0.01320139226f, 0.01325230021f, 0.01329909544f, 0.01334163733f, 0.01337979734f, 0.0134134572f, 0.01344251167f, 0.01346686855f, 0.01348644961f, 0.01350119151f, 0.01351104677f, 0.01351598185f, 0.01351598185f, 0.01351104677f, 0.01350119151f, 0.01348644961f, 0.01346686855f, 0.01344251167f, 0.0134134572f, 0.01337979734f, 0.01334163733f, 0.01329909544f, 0.01325230021f, 0.01320139226f, 0.01314651966f, 0.01308783889f, 0.01302551571f, 0.0129597187f, 0.01289062295f, 0.01281840634f, 0.0127432486f, 0.0126653323f, 0.01258483995f, 0.01250195317f, 0.01241685264f, 0.01232971624f, 0.01224071812f, 0.01215003151f, 0.01205782313f, 0.01196425594f, 0.01186948828f, 0.01177367195f, 0.01167695317f, 0.0115794735f, 0.01148136612f, 0.01138276048f, 0.01128377859f, 0.01118453499f, 0.01108513959f, 0.01098569483f, 0.01105664484f, 0.0111262314f, 0.01119434182f, 0.01126086153f, 0.01132567506f, 0.01138866507f, 0.01144971419f, 0.01150870696f, 0.01156552508f, 0.01162005402f, 0.01167218015f, 0.01172179077f, 0.01176877879f, 0.01181303803f, 0.01185446698f, 0.01189296879f, 0.01192845311f, 0.0119608324f, 0.01199002843f, 0.01201596763f, 0.01203858573f, 0.01205782313f, 0.01207363233f, 0.01208597142f, 0.01209480781f, 0.01210011914f, 0.01210189145f, 0.01210011914f, 0.01209480781f, 0.01208597142f, 0.01207363233f, 0.01205782313f, 0.01203858573f, 0.01201596763f, 0.01199002843f, 0.0119608324f, 0.01192845311f, 0.01189296879f, 0.01185446698f, 0.01181303803f, 0.01176877879f, 0.01172179077f, 0.01167218015f, 0.01162005402f, 0.01156552508f, 0.01150870696f, 0.01144971419f, 0.01138866507f, 0.01132567506f, 0.01126086153f, 0.01119434182f, 0.0111262314f, 0.01105664484f, 0.01111111138f, 0.01120980456f, 0.01130828168f, 0.01140643191f, 0.01150413603f, 0.01160127111f, 0.01169770677f, 0.0117933061f, 0.01188792568f, 0.01198141929f, 0.01207363233f, 0.01216440555f, 0.01225357689f, 0.01234097779f, 0.01242643595f, 0.01250977721f, 0.0125908237f, 0.01266939752f, 0.01274531893f, 0.01281840634f, 0.01288848184f, 0.01295536757f, 0.01301889122f, 0.01307888143f, 0.01313517336f, 0.01318760961f, 0.01323603839f, 0.01328031812f, 0.01332031563f, 0.01335590892f, 0.01338698901f, 0.0134134572f, 0.01343523059f, 0.0134522384f, 0.01346442662f, 0.01347175613f, 0.01347420178f, 0.01347175613f, 0.01346442662f, 0.0134522384f, 0.01343523059f, 0.0134134572f, 0.01338698901f, 0.01335590892f, 0.01332031563f, 0.01328031812f, 0.01323603839f, 0.01318760961f, 0.01313517336f, 0.01307888143f, 0.01301889122f, 0.01295536757f, 0.01288848184f, 0.01281840634f, 0.01274531893f, 0.01266939752f, 0.0125908237f, 0.01250977721f, 0.01242643595f, 0.01234097779f, 0.01225357689f, 0.01216440555f, 0.01207363233f, 0.01198141929f, 0.01188792568f, 0.0117933061f, 0.01169770677f, 0.01160127111f, 0.01150413603f, 0.01140643191f, 0.01130828168f, 0.01120980456f, 0.01111111138f, 0.01101230737f, 0.01108513959f, 0.01115665678f, 0.01122674625f, 0.01129528973f, 0.01136216894f, 0.01142726559f, 0.01149045862f, 0.01155162696f, 0.01161065139f, 0.01166741177f, 0.01172179077f, 0.01177367195f, 0.01182294171f, 0.01186948828f, 0.01191320643f, 0.0119539937f, 0.01199175231f, 0.01202639099f, 0.01205782313f, 0.01208597142f, 0.01211076323f, 0.01213213522f, 0.01215003151f, 0.01216440555f, 0.01217522006f, 0.0121824462f, 0.01218606345f, 0.01218606345f, 0.0121824462f, 0.01217522006f, 0.01216440555f, 0.01215003151f, 0.01213213522f, 0.01211076323f, 0.01208597142f, 0.01205782313f, 0.01202639099f, 0.01199175231f, 0.0119539937f, 0.01191320643f, 0.01186948828f, 0.01182294171f, 0.01177367195f, 0.01172179077f, 0.01166741177f, 0.01161065139f, 0.01155162696f, 0.01149045862f, 0.01142726559f, 0.01136216894f, 0.01129528973f, 0.01122674625f, 0.01115665678f, 0.01108513959f, 0.01113450434f, 0.01123241056f, 0.01133003552f, 0.01142726559f, 0.01152398065f, 0.01162005402f, 0.0117153544f, 0.01180974208f, 0.0119030755f, 0.01199520286f, 0.01208597142f, 0.01217522006f, 0.01226278674f, 0.01234850287f, 0.01243219618f, 0.01251369435f, 0.01259282045f, 0.01266939752f, 0.0127432486f, 0.01281419583f, 0.01288206317f, 0.01294667926f, 0.01300787181f, 0.01306547876f, 0.01311933808f, 0.01316929981f, 0.01321521774f, 0.01325695775f, 0.0132943932f, 0.01332741138f, 0.01335590892f, 0.01337979734f, 0.01339900028f, 0.0134134572f, 0.01342312153f, 0.01342796069f, 0.01342796069f, 0.01342312153f, 0.0134134572f, 0.01339900028f, 0.01337979734f, 0.01335590892f, 0.01332741138f, 0.0132943932f, 0.01325695775f, 0.01321521774f, 0.01316929981f, 0.01311933808f, 0.01306547876f, 0.01300787181f, 0.01294667926f, 0.01288206317f, 0.01281419583f, 0.0127432486f, 0.01266939752f, 0.01259282045f, 0.01251369435f, 0.01243219618f, 0.01234850287f, 0.01226278674f, 0.01217522006f, 0.01208597142f, 0.01199520286f, 0.0119030755f, 0.01180974208f, 0.0117153544f, 0.01162005402f, 0.01152398065f, 0.01142726559f, 0.01133003552f, 0.01123241056f, 0.01113450434f, 0.0110364249f, 0.01111111138f, 0.01118453499f, 0.01125658024f, 0.01132712793f, 0.0113960579f, 0.01146324724f, 0.01152857486f, 0.01159191411f, 0.01165314391f, 0.01171213947f, 0.01176877879f, 0.01182294171f, 0.01187450811f, 0.01192336436f, 0.01196939778f, 0.01201249938f, 0.01205256768f, 0.01208950393f, 0.01212321594f, 0.0121536199f, 0.0121806385f, 0.01220420003f, 0.01222424489f, 0.01224071812f, 0.01225357689f, 0.01226278674f, 0.01226832252f, 0.01227016933f, 0.01226832252f, 0.01226278674f, 0.01225357689f, 0.01224071812f, 0.01222424489f, 0.01220420003f, 0.0121806385f, 0.0121536199f, 0.01212321594f, 0.01208950393f, 0.01205256768f, 0.01201249938f, 0.01196939778f, 0.01192336436f, 0.01187450811f, 0.01182294171f, 0.01176877879f, 0.01171213947f, 0.01165314391f, 0.01159191411f, 0.01152857486f, 0.01146324724f, 0.0113960579f, 0.01132712793f, 0.01125658024f, 0.01118453499f, 0.01111111138f, 0.01115526911f, 0.01125230361f, 0.01134899072f, 0.01144521404f, 0.01154085156f, 0.01163577568f, 0.0117298523f, 0.01182294171f, 0.01191489771f, 0.01200557221f, 0.01209480781f, 0.0121824462f, 0.01226832252f, 0.01235227007f, 0.01243411843f, 0.01251369435f, 0.0125908237f, 0.0126653323f, 0.01273704506f, 0.01280578785f, 0.01287138835f, 0.01293367799f, 0.01299249288f, 0.01304767188f, 0.01309906319f, 0.01314651966f, 0.01318990346f, 0.013229087f, 0.01326395292f, 0.0132943932f, 0.01332031563f, 0.01334163733f, 0.01335829217f, 0.01337022707f, 0.01337740291f, 0.01337979734f, 0.01337740291f, 0.01337022707f, 0.01335829217f, 0.01334163733f, 0.01332031563f, 0.0132943932f, 0.01326395292f, 0.013229087f, 0.01318990346f, 0.01314651966f, 0.01309906319f, 0.01304767188f, 0.01299249288f, 0.01293367799f, 0.01287138835f, 0.01280578785f, 0.01273704506f, 0.0126653323f, 0.0125908237f, 0.01251369435f, 0.01243411843f, 0.01235227007f, 0.01226832252f, 0.0121824462f, 0.01209480781f, 0.01200557221f, 0.01191489771f, 0.01182294171f, 0.0117298523f, 0.01163577568f, 0.01154085156f, 0.01144521404f, 0.01134899072f, 0.01125230361f, 0.01115526911f, 0.01105799619f, 0.01113450434f, 0.01120980456f, 0.01128377859f, 0.01135630626f, 0.01142726559f, 0.01149653178f, 0.01156397816f, 0.01162947994f, 0.01169290766f, 0.0117541356f, 0.01181303803f, 0.01186948828f, 0.01192336436f, 0.0119745452f, 0.01202291343f, 0.01206835546f, 0.01211076323f, 0.01215003151f, 0.01218606345f, 0.01221876871f, 0.0122480616f, 0.01227386575f, 0.01229611505f, 0.01231474802f, 0.01232971624f, 0.01234097779f, 0.01234850287f, 0.01235227007f, 0.01235227007f, 0.01234850287f, 0.01234097779f, 0.01232971624f, 0.01231474802f, 0.01229611505f, 0.01227386575f, 0.0122480616f, 0.01221876871f, 0.01218606345f, 0.01215003151f, 0.01211076323f, 0.01206835546f, 0.01202291343f, 0.0119745452f, 0.01192336436f, 0.01186948828f, 0.01181303803f, 0.0117541356f, 0.01169290766f, 0.01162947994f, 0.01156397816f, 0.01149653178f, 0.01142726559f, 0.01135630626f, 0.01128377859f, 0.01120980456f, 0.01113450434f, 0.01117335912f, 0.01126943901f, 0.01136510447f, 0.01146023627f, 0.0115547115f, 0.01164839976f, 0.011741166f, 0.01183286961f, 0.01192336436f, 0.01201249938f, 0.01210011914f, 0.01218606345f, 0.01227016933f, 0.01235227007f, 0.01243219618f, 0.01250977721f, 0.01258483995f, 0.01265721396f, 0.01272672601f, 0.01279320568f, 0.01285648718f, 0.01291640475f, 0.0129727982f, 0.01302551571f, 0.01307440922f, 0.01311933808f, 0.01316017378f, 0.01319679338f, 0.013229087f, 0.01325695775f, 0.01328031812f, 0.01329909544f, 0.01331323106f, 0.01332267933f, 0.01332741138f, 0.01332741138f, 0.01332267933f, 0.01331323106f, 0.01329909544f, 0.01328031812f, 0.01325695775f, 0.013229087f, 0.01319679338f, 0.01316017378f, 0.01311933808f, 0.01307440922f, 0.01302551571f, 0.0129727982f, 0.01291640475f, 0.01285648718f, 0.01279320568f, 0.01272672601f, 0.01265721396f, 0.01258483995f, 0.01250977721f, 0.01243219618f, 0.01235227007f, 0.01227016933f, 0.01218606345f, 0.01210011914f, 0.01201249938f, 0.01192336436f, 0.01183286961f, 0.011741166f, 0.01164839976f, 0.0115547115f, 0.01146023627f, 0.01136510447f, 0.01126943901f, 0.01117335912f, 0.01107697561f, 0.01115526911f, 0.01123241056f, 0.01130828168f, 0.01138276048f, 0.01145572308f, 0.01152704284f, 0.01159659028f, 0.0116642369f, 0.0117298523f, 0.0117933061f, 0.01185446698f, 0.01191320643f, 0.01196939778f, 0.01202291343f, 0.01207363233f, 0.01212143432f, 0.01216620672f, 0.01220783778f, 0.0122462241f, 0.01228126884f, 0.01231288072f, 0.01234097779f, 0.01236548461f, 0.01238633506f, 0.01240347326f, 0.01241685264f, 0.01242643595f, 0.01243219618f, 0.01243411843f, 0.01243219618f, 0.01242643595f, 0.01241685264f, 0.01240347326f, 0.01238633506f, 0.01236548461f, 0.01234097779f, 0.01231288072f, 0.01228126884f, 0.0122462241f, 0.01220783778f, 0.01216620672f, 0.01212143432f, 0.01207363233f, 0.01202291343f, 0.01196939778f, 0.01191320643f, 0.01185446698f, 0.0117933061f, 0.0117298523f, 0.0116642369f, 0.01159659028f, 0.01152704284f, 0.01145572308f, 0.01138276048f, 0.01130828168f, 0.01123241056f, 0.01115526911f, 0.01118873432f, 0.01128377859f, 0.01137833856f, 0.01147229597f, 0.01156552508f, 0.01165789459f, 0.01174926758f, 0.01183950249f, 0.01192845311f, 0.01201596763f, 0.01210189145f, 0.01218606345f, 0.01226832252f, 0.01234850287f, 0.01242643595f, 0.01250195317f, 0.01257488597f, 0.01264506485f, 0.01271232124f, 0.01277648844f, 0.01283740439f, 0.0128949089f, 0.01294884924f, 0.01299907733f, 0.0130454516f, 0.01308783889f, 0.01312611811f, 0.01316017378f, 0.01318990346f, 0.01321521774f, 0.01323603839f, 0.01325230021f, 0.01326395292f, 0.01327095926f, 0.01327329688f, 0.01327095926f, 0.01326395292f, 0.01325230021f, 0.01323603839f, 0.01321521774f, 0.01318990346f, 0.01316017378f, 0.01312611811f, 0.01308783889f, 0.0130454516f, 0.01299907733f, 0.01294884924f, 0.0128949089f, 0.01283740439f, 0.01277648844f, 0.01271232124f, 0.01264506485f, 0.01257488597f, 0.01250195317f, 0.01242643595f, 0.01234850287f, 0.01226832252f, 0.01218606345f, 0.01210189145f, 0.01201596763f, 0.01192845311f, 0.01183950249f, 0.01174926758f, 0.01165789459f, 0.01156552508f, 0.01147229597f, 0.01137833856f, 0.01128377859f, 0.01118873432f, 0.01109332126f, 0.01117335912f, 0.01125230361f, 0.01133003552f, 0.01140643191f, 0.01148136612f, 0.0115547115f, 0.01162633486f, 0.01169610675f, 0.01176389214f, 0.0118295569f, 0.01189296879f, 0.0119539937f, 0.01201249938f, 0.01206835546f, 0.01212143432f, 0.01217161212f, 0.01221876871f, 0.01226278674f, 0.01230355818f, 0.01234097779f, 0.01237494871f, 0.01240538247f, 0.01243219618f, 0.01245531905f, 0.01247468684f, 0.01249024551f, 0.01250195317f, 0.01250977721f, 0.01251369435f, 0.01251369435f, 0.01250977721f, 0.01250195317f, 0.01249024551f, 0.01247468684f, 0.01245531905f, 0.01243219618f, 0.01240538247f, 0.01237494871f, 0.01234097779f, 0.01230355818f, 0.01226278674f, 0.01221876871f, 0.01217161212f, 0.01212143432f, 0.01206835546f, 0.01201249938f, 0.0119539937f, 0.01189296879f, 0.0118295569f, 0.01176389214f, 0.01169610675f, 0.01162633486f, 0.0115547115f, 0.01148136612f, 0.01140643191f, 0.01133003552f, 0.01125230361f, 0.01117335912f, 0.01120136213f, 0.01129528973f, 0.01138866507f, 0.01148136612f, 0.0115732681f, 0.0116642369f, 0.0117541356f, 0.01184282266f, 0.01193015091f, 0.01201596763f, 0.01210011914f, 0.0121824462f, 0.01226278674f, 0.01234097779f, 0.01241685264f, 0.01249024551f, 0.01256099064f, 0.01262892038f, 0.01269387174f, 0.01275568269f, 0.01281419583f, 0.01286925655f, 0.01292071585f, 0.01296843402f, 0.01301227603f, 0.01305211708f, 0.01308783889f, 0.01311933808f, 0.01314651966f, 0.01316929981f, 0.01318760961f, 0.01320139226f, 0.0132106049f, 0.01321521774f, 0.01321521774f, 0.0132106049f, 0.01320139226f, 0.01318760961f, 0.01316929981f, 0.01314651966f, 0.01311933808f, 0.01308783889f, 0.01305211708f, 0.01301227603f, 0.01296843402f, 0.01292071585f, 0.01286925655f, 0.01281419583f, 0.01275568269f, 0.01269387174f, 0.01262892038f, 0.01256099064f, 0.01249024551f, 0.01241685264f, 0.01234097779f, 0.01226278674f, 0.0121824462f, 0.01210011914f, 0.01201596763f, 0.01193015091f, 0.01184282266f, 0.0117541356f, 0.0116642369f, 0.0115732681f, 0.01148136612f, 0.01138866507f, 0.01129528973f, 0.01120136213f, 0.01110699773f, 0.01118873432f, 0.01126943901f, 0.01134899072f, 0.01142726559f, 0.01150413603f, 0.0115794735f, 0.01165314391f, 0.01172501314f, 0.01179494616f, 0.01186280511f, 0.01192845311f, 0.01199175231f, 0.01205256768f, 0.01211076323f, 0.01216620672f, 0.01221876871f, 0.01226832252f, 0.01231474802f, 0.01235792786f, 0.01239775307f, 0.01243411843f, 0.01246692892f, 0.01249609515f, 0.01252153981f, 0.0125431912f, 0.01256099064f, 0.01257488597f, 0.01258483995f, 0.0125908237f, 0.01259282045f, 0.0125908237f, 0.01258483995f, 0.01257488597f, 0.01256099064f, 0.0125431912f, 0.01252153981f, 0.01249609515f, 0.01246692892f, 0.01243411843f, 0.01239775307f, 0.01235792786f, 0.01231474802f, 0.01226832252f, 0.01221876871f, 0.01216620672f, 0.01211076323f, 0.01205256768f, 0.01199175231f, 0.01192845311f, 0.01186280511f, 0.01179494616f, 0.01172501314f, 0.01165314391f, 0.0115794735f, 0.01150413603f, 0.01142726559f, 0.01134899072f, 0.01126943901f, 0.01118873432f, 0.01121121366f, 0.01130394638f, 0.0113960579f, 0.0114874253f, 0.01157792099f, 0.01166741177f, 0.01175575983f, 0.01184282266f, 0.01192845311f, 0.01201249938f, 0.01209480781f, 0.01217522006f, 0.01225357689f, 0.01232971624f, 0.01240347326f, 0.01247468684f, 0.0125431912f, 0.01260882709f, 0.01267143246f, 0.01273085084f, 0.0127869295f, 0.01283952035f, 0.01288848184f, 0.01293367799f, 0.01297498215f, 0.01301227603f, 0.0130454516f, 0.01307440922f, 0.01309906319f, 0.01311933808f, 0.01313517336f, 0.01314651966f, 0.01315334067f, 0.01315561775f, 0.01315334067f, 0.01314651966f, 0.01313517336f, 0.01311933808f, 0.01309906319f, 0.01307440922f, 0.0130454516f, 0.01301227603f, 0.01297498215f, 0.01293367799f, 0.01288848184f, 0.01283952035f, 0.0127869295f, 0.01273085084f, 0.01267143246f, 0.01260882709f, 0.0125431912f, 0.01247468684f, 0.01240347326f, 0.01232971624f, 0.01225357689f, 0.01217522006f, 0.01209480781f, 0.01201249938f, 0.01192845311f, 0.01184282266f, 0.01175575983f, 0.01166741177f, 0.01157792099f, 0.0114874253f, 0.0113960579f, 0.01130394638f, 0.01121121366f, 0.01111797616f, 0.01120136213f, 0.01128377859f, 0.01136510447f, 0.01144521404f, 0.01152398065f, 0.01160127111f, 0.01167695317f, 0.01175088901f, 0.01182294171f, 0.01189296879f, 0.0119608324f, 0.01202639099f, 0.01208950393f, 0.01215003151f, 0.01220783778f, 0.01226278674f, 0.01231474802f, 0.01236359403f, 0.01240920182f, 0.01245145593f, 0.01249024551f, 0.01252546813f, 0.01255702879f, 0.01258483995f, 0.01260882709f, 0.01262892038f, 0.01264506485f, 0.01265721396f, 0.0126653323f, 0.01266939752f, 0.01266939752f, 0.0126653323f, 0.01265721396f, 0.01264506485f, 0.01262892038f, 0.01260882709f, 0.01258483995f, 0.01255702879f, 0.01252546813f, 0.01249024551f, 0.01245145593f, 0.01240920182f, 0.01236359403f, 0.01231474802f, 0.01226278674f, 0.01220783778f, 0.01215003151f, 0.01208950393f, 0.01202639099f, 0.0119608324f, 0.01189296879f, 0.01182294171f, 0.01175088901f, 0.01167695317f, 0.01160127111f, 0.01152398065f, 0.01144521404f, 0.01136510447f, 0.01128377859f, 0.01120136213f, 0.01121826563f, 0.01130972803f, 0.01140050031f, 0.01149045862f, 0.0115794735f, 0.01166741177f, 0.0117541356f, 0.01183950249f, 0.01192336436f, 0.01200557221f, 0.01208597142f, 0.01216440555f, 0.01224071812f, 0.01231474802f, 0.01238633506f, 0.01245531905f, 0.01252153981f, 0.01258483995f, 0.01264506485f, 0.01270206179f, 0.01275568269f, 0.01280578785f, 0.01285223942f, 0.0128949089f, 0.01293367799f, 0.01296843402f, 0.01299907733f, 0.01302551571f, 0.01304767188f, 0.01306547876f, 0.01307888143f, 0.01308783889f, 0.01309232507f, 0.01309232507f, 0.01308783889f, 0.01307888143f, 0.01306547876f, 0.01304767188f, 0.01302551571f, 0.01299907733f, 0.01296843402f, 0.01293367799f, 0.0128949089f, 0.01285223942f, 0.01280578785f, 0.01275568269f, 0.01270206179f, 0.01264506485f, 0.01258483995f, 0.01252153981f, 0.01245531905f, 0.01238633506f, 0.01231474802f, 0.01224071812f, 0.01216440555f, 0.01208597142f, 0.01200557221f, 0.01192336436f, 0.01183950249f, 0.0117541356f, 0.01166741177f, 0.0115794735f, 0.01149045862f, 0.01140050031f, 0.01130972803f, 0.01121826563f, 0.0111262314f, 0.01121121366f, 0.01129528973f, 0.01137833856f, 0.01146023627f, 0.01154085156f, 0.01162005402f, 0.01169770677f, 0.01177367195f, 0.01184780896f, 0.01191997528f, 0.01199002843f, 0.01205782313f, 0.01212321594f, 0.01218606345f, 0.0122462241f, 0.01230355818f, 0.01235792786f, 0.01240920182f, 0.01245725155f, 0.01250195317f, 0.0125431912f, 0.01258085575f, 0.01261484437f, 0.01264506485f, 0.01267143246f, 0.01269387174f, 0.01271232124f, 0.01272672601f, 0.01273704506f, 0.0127432486f, 0.01274531893f, 0.0127432486f, 0.01273704506f, 0.01272672601f, 0.01271232124f, 0.01269387174f, 0.01267143246f, 0.01264506485f, 0.01261484437f, 0.01258085575f, 0.0125431912f, 0.01250195317f, 0.01245725155f, 0.01240920182f, 0.01235792786f, 0.01230355818f, 0.0122462241f, 0.01218606345f, 0.01212321594f, 0.01205782313f, 0.01199002843f, 0.01191997528f, 0.01184780896f, 0.01177367195f, 0.01169770677f, 0.01162005402f, 0.01154085156f, 0.01146023627f, 0.01137833856f, 0.01129528973f, 0.01121121366f, 0.01122250315f, 0.01131262258f, 0.01140198205f, 0.01149045862f, 0.01157792099f, 0.0116642369f, 0.01174926758f, 0.01183286961f, 0.01191489771f, 0.01199520286f, 0.01207363233f, 0.01215003151f, 0.01222424489f, 0.01229611505f, 0.01236548461f, 0.01243219618f, 0.01249609515f, 0.01255702879f, 0.01261484437f, 0.01266939752f, 0.01272054669f, 0.01276815403f, 0.01281209197f, 0.01285223942f, 0.01288848184f, 0.01292071585f, 0.01294884924f, 0.0129727982f, 0.01299249288f, 0.01300787181f, 0.01301889122f, 0.01302551571f, 0.01302772667f, 0.01302551571f, 0.01301889122f, 0.01300787181f, 0.01299249288f, 0.0129727982f, 0.01294884924f, 0.01292071585f, 0.01288848184f, 0.01285223942f, 0.01281209197f, 0.01276815403f, 0.01272054669f, 0.01266939752f, 0.01261484437f, 0.01255702879f, 0.01249609515f, 0.01243219618f, 0.01236548461f, 0.01229611505f, 0.01222424489f, 0.01215003151f, 0.01207363233f, 0.01199520286f, 0.01191489771f, 0.01183286961f, 0.01174926758f, 0.0116642369f, 0.01157792099f, 0.01149045862f, 0.01140198205f, 0.01131262258f, 0.01122250315f, 0.01113174483f, 0.01121826563f, 0.01130394638f, 0.01138866507f, 0.01147229597f, 0.0115547115f, 0.01163577568f, 0.0117153544f, 0.0117933061f, 0.01186948828f, 0.01194375753f, 0.01201596763f, 0.01208597142f, 0.0121536199f, 0.01221876871f, 0.01228126884f, 0.01234097779f, 0.01239775307f, 0.01245145593f, 0.01250195317f, 0.01254911628f, 0.01259282045f, 0.01263295114f, 0.01266939752f, 0.01270206179f, 0.01273085084f, 0.01275568269f, 0.01277648844f, 0.01279320568f, 0.01280578785f, 0.01281419583f, 0.01281840634f, 0.01281840634f, 0.01281419583f, 0.01280578785f, 0.01279320568f, 0.01277648844f, 0.01275568269f, 0.01273085084f, 0.01270206179f, 0.01266939752f, 0.01263295114f, 0.01259282045f, 0.01254911628f, 0.01250195317f, 0.01245145593f, 0.01239775307f, 0.01234097779f, 0.01228126884f, 0.01221876871f, 0.0121536199f, 0.01208597142f, 0.01201596763f, 0.01194375753f, 0.01186948828f, 0.0117933061f, 0.0117153544f, 0.01163577568f, 0.0115547115f, 0.01147229597f, 0.01138866507f, 0.01130394638f, 0.01121826563f, 0.0112239169f, 0.01131262258f, 0.01140050031f, 0.0114874253f, 0.0115732681f, 0.01165789459f, 0.011741166f, 0.01182294171f, 0.0119030755f, 0.01198141929f, 0.01205782313f, 0.01213213522f, 0.01220420003f, 0.01227386575f, 0.01234097779f, 0.01240538247f, 0.01246692892f, 0.01252546813f, 0.01258085575f, 0.01263295114f, 0.0126816174f, 0.01272672601f, 0.01276815403f, 0.01280578785f, 0.01283952035f, 0.01286925655f, 0.0128949089f, 0.01291640475f, 0.01293367799f, 0.01294667926f, 0.01295536757f, 0.0129597187f, 0.0129597187f, 0.01295536757f, 0.01294667926f, 0.01293367799f, 0.01291640475f, 0.0128949089f, 0.01286925655f, 0.01283952035f, 0.01280578785f, 0.01276815403f, 0.01272672601f, 0.0126816174f, 0.01263295114f, 0.01258085575f, 0.01252546813f, 0.01246692892f, 0.01240538247f, 0.01234097779f, 0.01227386575f, 0.01220420003f, 0.01213213522f, 0.01205782313f, 0.01198141929f, 0.0119030755f, 0.01182294171f, 0.011741166f, 0.01165789459f, 0.0115732681f, 0.0114874253f, 0.01140050031f, 0.01131262258f, 0.0112239169f, 0.01113450434f, 0.01122250315f, 0.01130972803f, 0.0113960579f, 0.01148136612f, 0.01156552508f, 0.01164839976f, 0.0117298523f, 0.01180974208f, 0.01188792568f, 0.01196425594f, 0.01203858573f, 0.01211076323f, 0.0121806385f, 0.0122480616f, 0.01231288072f, 0.01237494871f, 0.01243411843f, 0.01249024551f, 0.0125431912f, 0.01259282045f, 0.01263900381f, 0.0126816174f, 0.01272054669f, 0.01275568269f, 0.0127869295f, 0.01281419583f, 0.01283740439f, 0.01285648718f, 0.01287138835f, 0.01288206317f, 0.01288848184f, 0.01289062295f, 0.01288848184f, 0.01288206317f, 0.01287138835f, 0.01285648718f, 0.01283740439f, 0.01281419583f, 0.0127869295f, 0.01275568269f, 0.01272054669f, 0.0126816174f, 0.01263900381f, 0.01259282045f, 0.0125431912f, 0.01249024551f, 0.01243411843f, 0.01237494871f, 0.01231288072f, 0.0122480616f, 0.0121806385f, 0.01211076323f, 0.01203858573f, 0.01196425594f, 0.01188792568f, 0.01180974208f, 0.0117298523f, 0.01164839976f, 0.01156552508f, 0.01148136612f, 0.0113960579f, 0.01130972803f, 0.01122250315f }; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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/BBox.h0000644000000000000000000003604613200122377013153 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for std::min(), std::max() #include // for std::abs() #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief Axis-aligned bounding box template class BBox { public: using Vec3Type = Vec3T; using ValueType = Vec3T; using VectorType = Vec3T; using ElementType = typename Vec3Type::ValueType; /// @brief The default constructor creates an invalid bounding box. BBox(); /// @brief Construct a bounding box that exactly encloses the given /// minimum and maximum points. BBox(const Vec3T& xyzMin, const Vec3T& xyzMax); /// @brief Construct a bounding box that exactly encloses the given /// minimum and maximum points. /// @details If @a sorted is false, sort the points by their /// @e x, @e y and @e z components. BBox(const Vec3T& xyzMin, const Vec3T& xyzMax, bool sorted); /// @brief Contruct a cubical bounding box from a minimum coordinate /// and an edge length. /// @note Inclusive for integral ElementTypes BBox(const Vec3T& xyzMin, const ElementType& length); /// @brief Construct a bounding box that exactly encloses two points, /// whose coordinates are given by an array of six values, /// x1, y1, z1, /// x2, y2 and z2. /// @details If @a sorted is false, sort the points by their /// @e x, @e y and @e z components. explicit BBox(const ElementType* xyz, bool sorted = true); BBox(const BBox&) = default; BBox& operator=(const BBox&) = default; /// @brief Sort the mininum and maximum points of this bounding box /// by their @e x, @e y and @e z components. void sort(); /// @brief Return a const reference to the minimum point of this bounding box. const Vec3T& min() const { return mMin; } /// @brief Return a const reference to the maximum point of this bounding box. const Vec3T& max() const { return mMax; } /// @brief Return a non-const reference to the minimum point of this bounding box. Vec3T& min() { return mMin; } /// @brief Return a non-const reference to the maximum point of this bounding box. Vec3T& max() { return mMax; } /// @brief Return @c true if this bounding box is identical to the given bounding box. bool operator==(const BBox& rhs) const; /// @brief Return @c true if this bounding box differs from the given bounding box. bool operator!=(const BBox& rhs) const { return !(*this == rhs); } /// @brief Return @c true if this bounding box is empty, i.e., it has no (positive) volume. bool empty() const; /// @brief Return @c true if this bounding box has (positive) volume. bool hasVolume() const { return !this->empty(); } /// @brief Return @c true if this bounding box has (positive) volume. operator bool() const { return !this->empty(); } /// @brief Return @c true if all components of the minimum point are less than /// or equal to the corresponding components of the maximum point. /// @details This is equivalent to testing whether this bounding box has nonnegative volume. /// @note For floating-point ElementTypes a tolerance is used for this test. bool isSorted() const; /// @brief Return the center point of this bounding box. Vec3d getCenter() const; /// @brief Return the extents of this bounding box, i.e., the length along each axis. /// @note Inclusive for integral ElementTypes Vec3T extents() const; /// @brief Return the index (0, 1 or 2) of the longest axis. size_t maxExtent() const { return MaxIndex(mMax - mMin); } /// @brief Return the index (0, 1 or 2) of the shortest axis. size_t minExtent() const { return MinIndex(mMax - mMin); } /// @brief Return the volume enclosed by this bounding box. ElementType volume() const { Vec3T e = this->extents(); return e[0] * e[1] * e[2]; } /// @brief Return @c true if the given point is inside this bounding box. bool isInside(const Vec3T& xyz) const; /// @brief Return @c true if the given bounding box is inside this bounding box. bool isInside(const BBox&) const; /// @brief Return @c true if the given bounding box overlaps with this bounding box. bool hasOverlap(const BBox&) const; /// @brief Return @c true if the given bounding box overlaps with this bounding box. bool intersects(const BBox& other) const { return hasOverlap(other); } /// @brief Pad this bounding box. void expand(ElementType padding); /// @brief Expand this bounding box to enclose the given point. void expand(const Vec3T& xyz); /// @brief Union this bounding box with the given bounding box. void expand(const BBox&); /// @brief Union this bounding box with the cubical bounding box with /// minimum point @a xyzMin and the given edge length. /// @note Inclusive for integral ElementTypes void expand(const Vec3T& xyzMin, const ElementType& length); /// @brief Translate this bounding box by /// (tx, ty, tz). void translate(const Vec3T& t); /// @brief Apply a map to this bounding box. template BBox applyMap(const MapType& map) const; /// @brief Apply the inverse of a map to this bounding box template BBox applyInverseMap(const MapType& map) const; /// @brief Unserialize this bounding box from the given stream. void read(std::istream& is) { mMin.read(is); mMax.read(is); } /// @brief 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 = std::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 bool BBox::empty() const { if (std::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 (std::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 (int 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 (std::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 (std::is_integral::value) { return (mMax - mMin) + Vec3T(1, 1, 1); } else { return (mMax - mMin); } } //////////////////////////////////////// template inline bool BBox::isInside(const Vec3T& xyz) const { if (std::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 (std::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 (std::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 (int i = 0; i < 3; ++i) { mMin[i] -= dx; mMax[i] += dx; } } template inline void BBox::expand(const Vec3T& xyz) { for (int 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 (int 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 = std::is_integral::value ? length-1 : length; for (int 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 { using Vec3R = Vec3; 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 { using Vec3R = Vec3; 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-2017 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.cc0000644000000000000000000002140113200122377013344 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 { using Mutex = tbb::mutex; using Lock = Mutex::scoped_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 = nullptr; // Caller is responsible for calling this function serially. MapRegistry* MapRegistry::staticInstance() { if (mInstance == nullptr) { 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 StaticPtrCast(affine); } Mat4d approxInverse(const Mat4d& mat4d) { if (std::abs(mat4d.det()) >= 3 * math::Tolerance::value()) { try { return mat4d.inverse(); } 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-2017 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.h0000644000000000000000000004543613200122377013236 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Mat.h" #include "Mat3.h" #include "Math.h" #include "Vec3.h" #include #include #include #include #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 = T(sin(angle*T(0.5))); mm[0] = axis.x() * s; mm[1] = axis.y() * s; mm[2] = axis.z() * s; mm[3] = T(cos(angle*T(0.5))); } /// Constructor given rotation as axis and angle Quat(math::Axis axis, T angle) { T s = T(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] = T(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(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(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(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 = T(sin(angle*T(0.5))); mm[0] = axis.x() * s; mm[1] = axis.y() * s; mm[2] = axis.z() * s; mm[3] = T(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 = T(1.0e-8)) { T d = T(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 = T(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(static_cast(&mm), sizeof(T) * 4); } void read(std::istream& is) { is.read(static_cast(&mm), sizeof(T) * 4); } protected: T mm[4]; }; /// Multiply each element of the given quaternion by @a scalar and return the result. 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) { using MatType = Mat3; 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); } using Quats = Quat; using Quatd = Quat; } // namespace math template<> inline math::Quats zeroVal() { return math::Quats::zero(); } template<> inline math::Quatd zeroVal() { return math::Quatd::zero(); } } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif //OPENVDB_MATH_QUAT_H_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000001675213200122377015115 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.h0000644000000000000000000004443013200122377013116 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Math.h" #include "Tuple.h" #include "Vec3.h" #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Mat3; template class Vec4: public Tuple<4, T> { public: using value_type = T; using ValueType = T; /// Trivial constructor, the vector is NOT initialized Vec4() {} /// @brief Construct a vector all of whose components have the given value. explicit Vec4(T val) { this->mm[0] = this->mm[1] = this->mm[2] = this->mm[3] = val; } /// Constructor with four arguments, e.g. Vec4f v(1,2,3,4); 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]); } /// @brief Construct a vector all of whose components have the given value, /// which may be of an arithmetic type different from this vector's value type. /// @details Type conversion warnings are suppressed. template explicit Vec4(Other val, typename std::enable_if::value, Conversion>::type = Conversion{}) { this->mm[0] = this->mm[1] = this->mm[2] = this->mm[3] = static_cast(val); } /// 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]); } /// Return a reference to itself after the exponent has been /// applied to all the vector components. inline const Vec4& exp() { this->mm[0] = std::exp(this->mm[0]); this->mm[1] = std::exp(this->mm[1]); this->mm[2] = std::exp(this->mm[2]); this->mm[3] = std::exp(this->mm[3]); return *this; } /// Return a reference to itself after log has been /// applied to all the vector components. inline const Vec4& log() { this->mm[0] = std::log(this->mm[0]); this->mm[1] = std::log(this->mm[1]); this->mm[2] = std::log(this->mm[2]); this->mm[3] = std::log(this->mm[3]); return *this; } /// Return the sum of all the vector components. inline T sum() const { return this->mm[0] + this->mm[1] + this->mm[2] + this->mm[3]; } /// Return the product of all the vector components. inline T product() const { return this->mm[0] * this->mm[1] * this->mm[2] * 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; } /// return normalized this, or (1, 0, 0, 0) if this is null vector Vec4 unitSafe() const { T l2 = lengthSqr(); return l2 ? *this / static_cast(sqrt(l2)) : Vec4(1, 0, 0, 0); } /// Multiply each element of this vector by @a scalar. template const Vec4 &operator*=(S scalar) { this->mm[0] *= scalar; this->mm[1] *= scalar; this->mm[2] *= scalar; this->mm[3] *= scalar; return *this; } /// Multiply each element of this vector by the corresponding element of the given vector. 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; } /// Divide each element of this vector by @a scalar. template const Vec4 &operator/=(S scalar) { this->mm[0] /= scalar; this->mm[1] /= scalar; this->mm[2] /= scalar; this->mm[3] /= scalar; return *this; } /// Divide each element of this vector by the corresponding element of the given vector. 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; } /// Add @a scalar to each element of this vector. template const Vec4 &operator+=(S scalar) { this->mm[0] += scalar; this->mm[1] += scalar; this->mm[2] += scalar; this->mm[3] += scalar; return *this; } /// Add each element of the given vector to the corresponding element of this vector. 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; } /// Subtract @a scalar from each element of this vector. template const Vec4 &operator-=(S scalar) { this->mm[0] -= scalar; this->mm[1] -= scalar; this->mm[2] -= scalar; this->mm[3] -= scalar; return *this; } /// Subtract each element of the given vector from the corresponding element of this vector. 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; } /// 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); } static Vec4 ones() { return Vec4(1, 1, 1, 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); } /// Multiply each element of the given vector by @a scalar and return the result. template inline Vec4::type> operator*(S scalar, const Vec4 &v) { return v*scalar; } /// Multiply each element of the given vector by @a scalar and return the result. template inline Vec4::type> operator*(const Vec4 &v, S scalar) { Vec4::type> result(v); result *= scalar; return result; } /// Multiply corresponding elements of @a v0 and @a v1 and return the result. 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; } /// Divide @a scalar by each element of the given vector and return the result. 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]); } /// Divide each element of the given vector by @a scalar and return the result. template inline Vec4::type> operator/(const Vec4 &v, S scalar) { Vec4::type> result(v); result /= scalar; return result; } /// Divide corresponding elements of @a v0 and @a v1 and return the result. 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; } /// Add corresponding elements of @a v0 and @a v1 and return the result. template inline Vec4::type> operator+(const Vec4 &v0, const Vec4 &v1) { Vec4::type> result(v0); result += v1; return result; } /// Add @a scalar to each element of the given vector and return the result. template inline Vec4::type> operator+(const Vec4 &v, S scalar) { Vec4::type> result(v); result += scalar; return result; } /// Subtract corresponding elements of @a v0 and @a v1 and return the result. template inline Vec4::type> operator-(const Vec4 &v0, const Vec4 &v1) { Vec4::type> result(v0); result -= v1; return result; } /// Subtract @a scalar from each element of the given vector and return the result. template inline Vec4::type> operator-(const Vec4 &v, S scalar) { Vec4::type> result(v); result -= scalar; return result; } template inline bool isApproxEqual(const Vec4& a, const Vec4& b) { return a.eq(b); } template inline bool isApproxEqual(const Vec4& a, const Vec4& b, const Vec4& eps) { return isApproxEqual(a[0], b[0], eps[0]) && isApproxEqual(a[1], b[1], eps[1]) && isApproxEqual(a[2], b[2], eps[2]) && isApproxEqual(a[3], b[3], eps[3]); } template inline Vec4 Abs(const Vec4& v) { return Vec4(Abs(v[0]), Abs(v[1]), Abs(v[2]), Abs(v[3])); } /// @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())); } /// @brief Return a vector with the exponent applied to each of /// the components of the input vector. template inline Vec4 Exp(Vec4 v) { return v.exp(); } /// @brief Return a vector with log applied to each of /// the components of the input vector. template inline Vec4 Log(Vec4 v) { return v.log(); } using Vec4i = Vec4; using Vec4ui = Vec4; using Vec4s = Vec4; using Vec4d = Vec4; } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_VEC4_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000000677513200122377014333 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 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, /// return the point on @c abc closest to @c p and the corresponding barycentric coordinates. /// /// @details 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, /// return the point on @c ab closest to @c p and @c t the parametric distance to @c b. /// /// @param a The segment's first vertex point. /// @param b The segment'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); } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TOOLS_MESH_TO_VOLUME_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.cc0000644000000000000000000004112513200122377014424 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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(ConstPtrCast(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(ConstPtrCast(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 = StaticPtrCast(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 = StaticPtrCast(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 = StaticPtrCast(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 = StaticPtrCast(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 = StaticPtrCast(frustum); } } void Transform::postMult(const Mat3d& m) { Mat4d mat4 = Mat4d::identity(); mat4.setMat3(m); postMult(mat4); } //////////////////////////////////////// BBoxd Transform::indexToWorld(const CoordBBox& indexBBox) const { return this->indexToWorld(BBoxd(indexBBox.min().asVec3d(), indexBBox.max().asVec3d())); } BBoxd Transform::indexToWorld(const BBoxd& indexBBox) const { const Vec3d &imin = indexBBox.min(), &imax = indexBBox.max(); Vec3d corners[8]; corners[0] = imin; corners[1] = Vec3d(imax(0), imin(1), imin(2)); corners[2] = Vec3d(imax(0), imax(1), imin(2)); corners[3] = Vec3d(imin(0), imax(1), imin(2)); corners[4] = Vec3d(imin(0), imin(1), imax(2)); corners[5] = Vec3d(imax(0), imin(1), imax(2)); corners[6] = imax; corners[7] = Vec3d(imin(0), imax(1), imax(2)); BBoxd worldBBox; Vec3d &wmin = worldBBox.min(), &wmax = worldBBox.max(); wmin = wmax = this->indexToWorld(corners[0]); for (int i = 1; i < 8; ++i) { Vec3d image = this->indexToWorld(corners[i]); wmin = minComponent(wmin, image); wmax = maxComponent(wmax, image); } return worldBBox; } BBoxd Transform::worldToIndex(const BBoxd& worldBBox) const { Vec3d indexMin, indexMax; calculateBounds(*this, worldBBox.min(), worldBBox.max(), indexMin, indexMax); return BBoxd(indexMin, indexMax); } CoordBBox Transform::worldToIndexCellCentered(const BBoxd& worldBBox) const { Vec3d indexMin, indexMax; calculateBounds(*this, worldBBox.min(), worldBBox.max(), indexMin, indexMax); return CoordBBox(Coord::round(indexMin), Coord::round(indexMax)); } CoordBBox Transform::worldToIndexNodeCentered(const BBoxd& worldBBox) const { Vec3d indexMin, indexMax; calculateBounds(*this, worldBBox.min(), worldBBox.max(), indexMin, indexMax); return CoordBBox(Coord::floor(indexMin), Coord::floor(indexMax)); } //////////////////////////////////////// // 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); const int iw = int(w); // Print rows of the linear component matrix side-by-side with frustum parameters. ostr << indent << std::left << std::setw(iw) << "linear:" << " frustum:\n"; ostr << indent << " " << std::left << std::setw(iw) << linearRow[0] << " taper: " << frustum.getTaper() << "\n"; ostr << indent << " " << std::left << std::setw(iw) << linearRow[1] << " depth: " << frustum.getDepth() << "\n"; std::ostringstream ostmp; ostmp << indent << " " << std::left << std::setw(iw) << linearRow[2] << " bounds: " << frustum.getBBox(); if (ostmp.str().size() < 79) { ostr << ostmp.str() << "\n"; ostr << indent << " " << std::left << std::setw(iw) << 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(iw) << linearRow[2] << " bounds: " << frustum.getBBox().min() << " ->\n"; ostr << indent << " " << std::left << std::setw(iw) << 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-2017 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.h0000644000000000000000000001251713200122377015560 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief Unit vector occupying only 16 bits /// @details Stores two quantized components. Based on the /// "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() {} // 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 // normalization weights, 32 kilobytes. static float sNormalizationWeights[MASK_SLOTS + 1]; }; // class QuantizedUnitVec //////////////////////////////////////// template inline uint16_t QuantizedUnitVec::pack(const Vec3& vec) { if (math::isZero(vec)) return 0; 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 = static_cast((x * w)); uint16_t ybits = static_cast((y * w)); // 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 = static_cast(127 - xbits); ybits = static_cast(127 - ybits); } // Pack components into their respective slots. data = static_cast(data | (xbits << 7)); data = static_cast(data | ybits); return data; } inline Vec3s QuantizedUnitVec::unpack(const uint16_t data) { const float w = sNormalizationWeights[data & MASK_SLOTS]; uint16_t xbits = static_cast((data & MASK_XSLOT) >> 7); uint16_t ybits = static_cast(data & MASK_YSLOT); // Check if the complement components where stored and revert. if ((xbits + ybits) > 126) { xbits = static_cast(127 - xbits); ybits = static_cast(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 = static_cast((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-2017 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.h0000644000000000000000000006635713200122377013135 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Vec3.h" #include "Mat.h" #include // for std::copy() #include #include #include 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. using value_type = T; using ValueType = T; using MyBase = Mat<3, T>; /// 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] = static_cast(a); MyBase::mm[1] = static_cast(b); MyBase::mm[2] = static_cast(c); MyBase::mm[3] = static_cast(d); MyBase::mm[4] = static_cast(e); MyBase::mm[5] = static_cast(f); MyBase::mm[6] = static_cast(g); MyBase::mm[7] = static_cast(h); MyBase::mm[8] = static_cast(i); } // constructor1Test /// Construct matrix from rows or columns vectors (defaults to rows /// for historical reasons) template Mat3(const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, bool rows = true) { if (rows) { this->setRows(v1, v2, v3); } else { this->setColumns(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] = static_cast(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() { static const Mat3 sIdentity = Mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); return sIdentity; } /// Predefined constant for zero matrix static const Mat3& zero() { static const Mat3 sZero = Mat3( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); 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 rows of this matrix to the vectors v1, v2, v3 void setRows(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]; } // setRows /// Set the columns of this matrix to the vectors v1, v2, v3 void setColumns(const Vec3 &v1, const Vec3 &v2, const Vec3 &v3) { MyBase::mm[0] = v1[0]; MyBase::mm[1] = v2[0]; MyBase::mm[2] = v3[0]; MyBase::mm[3] = v1[1]; MyBase::mm[4] = v2[1]; MyBase::mm[5] = v3[1]; MyBase::mm[6] = v1[2]; MyBase::mm[7] = v2[2]; MyBase::mm[8] = v3[2]; } // setColumns /// 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 /// Return a matrix with the 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 /// Return @c true if this matrix is equivalent to @a m within a tolerance of @a eps. 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; // } /// Multiply each element of this matrix by @a scalar. 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; } /// Add each element of the given matrix to the corresponding element of this matrix. 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; } /// Subtract each element of the given matrix from the corresponding element of this matrix. 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; } /// Multiply this matrix by the given matrix. 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; } /// @brief Return the cofactor matrix of this matrix. Mat3 cofactor() const { return Mat3( MyBase::mm[4] * MyBase::mm[8] - MyBase::mm[5] * MyBase::mm[7], MyBase::mm[5] * MyBase::mm[6] - MyBase::mm[3] * MyBase::mm[8], MyBase::mm[3] * MyBase::mm[7] - MyBase::mm[4] * MyBase::mm[6], MyBase::mm[2] * MyBase::mm[7] - MyBase::mm[1] * MyBase::mm[8], MyBase::mm[0] * MyBase::mm[8] - MyBase::mm[2] * MyBase::mm[6], MyBase::mm[1] * MyBase::mm[6] - MyBase::mm[0] * MyBase::mm[7], MyBase::mm[1] * MyBase::mm[5] - MyBase::mm[2] * MyBase::mm[4], MyBase::mm[2] * MyBase::mm[3] - MyBase::mm[0] * MyBase::mm[5], MyBase::mm[0] * MyBase::mm[4] - MyBase::mm[1] * MyBase::mm[3]); } /// Return the adjoint of this matrix, i.e., the transpose of its cofactor. 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 ArithmeticError if singular Mat3 inverse(T tolerance = 0) const { Mat3 inv(this->adjoint()); const 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 the result will be invalid. if (isApproxEqual(det,T(0.0),tolerance)) { OPENVDB_THROW(ArithmeticError, "Inversion of singular 3x3 matrix"); } return inv * (T(1)/det); } // invertTest /// Determinant of matrix T det() const { const T co00 = MyBase::mm[4]*MyBase::mm[8] - MyBase::mm[5]*MyBase::mm[7]; const T co10 = MyBase::mm[5]*MyBase::mm[6] - MyBase::mm[3]*MyBase::mm[8]; const T co20 = MyBase::mm[3]*MyBase::mm[7] - MyBase::mm[4]*MyBase::mm[6]; return MyBase::mm[0]*co00 + MyBase::mm[1]*co10 + MyBase::mm[2]*co20; } // 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 snapMatBasis(*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 /// @brief Treat @a diag as a diagonal matrix and return the product /// of this matrix with @a diag (from the right). Mat3 timesDiagonal(const Vec3& diag) const { Mat3 ret(*this); ret.mm[0] *= diag(0); ret.mm[1] *= diag(1); ret.mm[2] *= diag(2); ret.mm[3] *= diag(0); ret.mm[4] *= diag(1); ret.mm[5] *= diag(2); ret.mm[6] *= diag(0); ret.mm[7] *= diag(1); ret.mm[8] *= diag(2); return ret; } }; // class Mat3 /// @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 Multiply each element of the given matrix by @a scalar and return the result. template Mat3::type> operator*(S scalar, const Mat3 &m) { return m*scalar; } /// @relates Mat3 /// @brief Multiply each element of the given matrix by @a scalar and return the result. template Mat3::type> operator*(const Mat3 &m, S scalar) { Mat3::type> result(m); result *= scalar; return result; } /// @relates Mat3 /// @brief Add corresponding elements of @a m0 and @a m1 and return the result. template Mat3::type> operator+(const Mat3 &m0, const Mat3 &m1) { Mat3::type> result(m0); result += m1; return result; } /// @relates Mat3 /// @brief Subtract corresponding elements of @a m0 and @a m1 and return the result. template Mat3::type> operator-(const Mat3 &m0, const Mat3 &m1) { Mat3::type> result(m0); result -= m1; return result; } /// @brief Multiply @a m0 by @a m1 and return the resulting matrix. template Mat3::type>operator*(const Mat3 &m0, const Mat3 &m1) { Mat3::type> result(m0); result *= m1; return result; } /// @relates Mat3 /// @brief Multiply @a _m by @a _v and return the resulting vector. 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 Multiply @a _v by @a _m and return the resulting vector. 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 Multiply @a _v by @a _m and replace @a _v with the resulting vector. template inline Vec3 &operator *= (Vec3 &_v, const Mat3 &_m) { Vec3 mult = _v * _m; _v = mult; return _v; } /// Returns outer product of v1, v2, i.e. v1 v2^T if v1 and v2 are /// column vectors, e.g. M = Mat3f::outerproduct(v1,v2); template Mat3 outerProduct(const Vec3& v1, const Vec3& v2) { return Mat3(v1[0]*v2[0], v1[0]*v2[1], v1[0]*v2[2], v1[1]*v2[0], v1[1]*v2[1], v1[1]*v2[2], v1[2]*v2[0], v1[2]*v2[1], v1[2]*v2[2]); }// outerProduct using Mat3s = Mat3; using Mat3d = Mat3; using Mat3f = Mat3d; /// 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 mat3_internal { template inline 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); } } } // namespace mat3_internal /// @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 inline 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; } } } mat3_internal::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-2017 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.cc0000644000000000000000000001225013200122377014452 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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); } } } } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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.h0000644000000000000000000025744713200122377015524 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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/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 #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 integration 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 Implementation of nominally fifth-order finite-difference WENO /// @details 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) and v5 = f(x+2dx), /// return an 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 fifth-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.01f) { 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 = 1.0e-6 * static_cast(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 static_cast(static_cast( 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 GodunovsNormSqrd(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 GodunovsNormSqrd(bool isOutside, const Vec3& gradient_m, const Vec3& gradient_p) { return GodunovsNormSqrd(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; using F4 = simd::Float4; const F4 C(13.f / 12.f), eps(1.0e-6f * scale2), two(2.0), three(3.0), four(4.0), five(5.0), fourth(0.25), A1 = F4(0.1f) / Pow2(C*Pow2(v1-two*v2+v3) + fourth*Pow2(v1-four*v2+three*v3) + eps), A2 = F4(0.6f) / Pow2(C*Pow2(v2-two*v3+v4) + fourth*Pow2(v2-v4) + eps), A3 = F4(0.3f) / 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 GodunovsNormSqrd(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) { 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& 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 static_cast(xp3/3.0 - 1.5*xp2 + 3.0*xp1 - 11.0*xp0/6.0); } // 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 operator 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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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 static_cast(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 static_cast(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 static_cast(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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Stencil::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) { using ValueType = typename Stencil::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) { using ValueType = typename Stencil::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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Stencil::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) { using ValueType = typename Stencil::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) { using ValueType = typename Stencil::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 { // using value_type = typename Accessor::ValueType::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 { //using ValueType = 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( 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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueType = typename Accessor::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) { using ValueT = typename Accessor::ValueType; ValueT tmp1 = D1::inX(grid, ijk.offsetBy(0, 1, 0)) - D1::inX(grid, ijk.offsetBy(0,-1, 0)); ValueT tmp2 = D1::inX(grid, ijk.offsetBy(0, 2, 0)) - D1::inX(grid, ijk.offsetBy(0,-2, 0)); ValueT tmp3 = D1::inX(grid, ijk.offsetBy(0, 3, 0)) - D1::inX(grid, ijk.offsetBy(0,-3, 0)); return ValueT(0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3); } template static typename Accessor::ValueType inXandZ(const Accessor& grid, const Coord& ijk) { using ValueT = typename Accessor::ValueType; ValueT tmp1 = D1::inX(grid, ijk.offsetBy(0, 0, 1)) - D1::inX(grid, ijk.offsetBy(0, 0,-1)); ValueT tmp2 = D1::inX(grid, ijk.offsetBy(0, 0, 2)) - D1::inX(grid, ijk.offsetBy(0, 0,-2)); ValueT tmp3 = D1::inX(grid, ijk.offsetBy(0, 0, 3)) - D1::inX(grid, ijk.offsetBy(0, 0,-3)); return ValueT(0.75*tmp1 - 0.15*tmp2 + 1./60*tmp3); } template static typename Accessor::ValueType inYandZ(const Accessor& grid, const Coord& ijk) { using ValueT = typename Accessor::ValueType; ValueT tmp1 = D1::inY(grid, ijk.offsetBy(0, 0, 1)) - D1::inY(grid, ijk.offsetBy(0, 0,-1)); ValueT tmp2 = D1::inY(grid, ijk.offsetBy(0, 0, 2)) - D1::inY(grid, ijk.offsetBy(0, 0,-2)); ValueT tmp3 = D1::inY(grid, ijk.offsetBy(0, 0, 3)) - D1::inY(grid, ijk.offsetBy(0, 0,-3)); return ValueT(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-2017 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.h0000644000000000000000000003223713200122377013052 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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. #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::swap() #include // for std::ostream #include // for std::numeric_limits::max() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { template class Ray { public: static_assert(std::is_floating_point::value, "math::Ray requires a floating-point value type"); using RealType = RealT; using Vec3Type = Vec3; using Vec3T = Vec3Type; struct TimeSpan { RealT t0, t1; /// @brief Default constructor TimeSpan() {} /// @brief Constructor TimeSpan(RealT _t0, RealT _t1) : t0(_t0), t1(_t1) {} /// @brief Set both times inline void set(RealT _t0, RealT _t1) { t0=_t0; t1=_t1; } /// @brief Get both times inline void get(RealT& _t0, RealT& _t1) const { _t0=t0; _t1=t1; } /// @brief Return @c true if t1 is larger than t0 by at least eps. inline bool valid(RealT eps=math::Delta::value()) const { return (t1-t0)>eps; } /// @brief Return the midpoint of the ray. inline RealT mid() const { return 0.5*(t0 + t1); } /// @brief Multiplies both times inline void scale(RealT s) {assert(s>0); t0*=s; t1*=s; } /// @brief Return @c true if time is inclusive inline bool test(RealT t) const { return (t>=t0 && t<=t1); } }; 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), mTimeSpan(t0, 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); mTimeSpan.t0 = t0; } inline void setMaxTime(RealT t1) { assert(t1>0); mTimeSpan.t1 = t1; } inline void setTimes( RealT t0 = math::Delta::value(), RealT t1 = std::numeric_limits::max()) { assert(t0>0 && t1>0); mTimeSpan.set(t0, t1); } inline void scaleTimes(RealT scale) { mTimeSpan.scale(scale); } inline void reset( const Vec3Type& eye, const Vec3Type& direction, RealT t0 = math::Delta::value(), 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 mTimeSpan.t0;} inline RealT t1() const {return mTimeSpan.t1;} /// @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)(mTimeSpan.t0); } /// @brief Return the endpoint of the ray. inline Vec3R end() const { return (*this)(mTimeSpan.t1); } /// @brief Return the midpoint of the ray. inline Vec3R mid() const { return (*this)(mTimeSpan.mid()); } /// @brief Return @c true if t1 is larger than t0 by at least eps. inline bool valid(RealT eps=math::Delta::value()) const { return mTimeSpan.valid(eps); } /// @brief Return @c true if @a time is within t0 and t1, both inclusive. inline bool test(RealT time) const { return mTimeSpan.test(time); } /// @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 normalized 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::isRelOrApproxEqual(mDir.length(), RealT(1), Tolerance::value(), Delta::value())); const Vec3T eye = map.applyMap(mEye); const Vec3T dir = map.applyJacobian(mDir); const RealT length = dir.length(); return Ray(eye, dir/length, length*mTimeSpan.t0, length*mTimeSpan.t1); } /// @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 normalized 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::isRelOrApproxEqual(mDir.length(), RealT(1), Tolerance::value(), Delta::value())); const Vec3T eye = map.applyInverseMap(mEye); const Vec3T dir = map.applyInverseJacobian(mDir); const RealT length = dir.length(); return Ray(eye, dir/length, length*mTimeSpan.t0, length*mTimeSpan.t1); } /// @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 < mTimeSpan.t0) t0 = mTimeSpan.t0; if (t1 > mTimeSpan.t1) t1 = mTimeSpan.t1; 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) mTimeSpan.set(t0, 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 { mTimeSpan.get(t0, t1); for (int 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) mTimeSpan.set(t0, 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; TimeSpan mTimeSpan; }; // 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="< // for ostringstream #include #include #include #include #include #include // for std::less #include "Math.h" namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief Templated class to compute the minimum and maximum values. template > class MinMax { using Limits = std::numeric_limits; public: /// @brief Empty constructor /// /// @warning Only use this constructor with POD types MinMax() : mMin(Limits::max()), mMax(Limits::lowest()) { static_assert(std::numeric_limits::is_specialized, "openvdb::math::MinMax default constructor requires a std::numeric_limits specialization"); } /// @brief Constructor MinMax(const ValueType &min, const ValueType &max) : mMin(min), mMax(max) { } /// @brief Default copy constructor MinMax(const MinMax &other) = default; /// Add a single sample. inline void add(const ValueType &val, const Less &less = Less()) { if (less(val, mMin)) mMin = val; if (less(mMax, val)) mMax = val; } /// Return the minimum value. inline const ValueType& min() const { return mMin; } /// Return the maximum value. inline const ValueType& max() const { return mMax; } /// Add the samples from the other Stats instance. inline void add(const MinMax& other, const Less &less = Less()) { if (less(other.mMin, mMin)) mMin = other.mMin; if (less(mMax, other.mMax)) mMax = other.mMax; } /// @brief Print MinMax 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 << "MinMax "; if (!name.empty()) os << "for \"" << name << "\" "; os << " Min=" << mMin << ", Max=" << mMax << std::endl; strm << os.str(); } protected: ValueType mMin, mMax; };//end MinMax /// @brief This class computes the minimum and maximum values of a population /// of floating-point values. class Extrema { public: /// @brief Constructor /// @warning The min/max values are initiated to extreme values Extrema() : mSize(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); } /// Add @a n samples with constant value @a val. void add(double val, uint64_t n) { mSize += n; mMin = std::min(val, mMin); mMax = std::max(val, mMax); } /// 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 range defined as the maximum value minus the minimum value. inline double range() const { return mMax - mMin; } /// Add the samples from the other Stats instance. void add(const Extrema& other) { if (other.mSize > 0) this->join(other); } /// @brief Print extrema 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 << "Extrema "; if (!name.empty()) os << "for \"" << name << "\" "; if (mSize>0) { os << "with " << mSize << " samples:\n" << " Min=" << mMin << ", Max=" << mMax << ", Range="<< this->range() << std::endl; } else { os << ": no samples were added." << std::endl; } strm << os.str(); } protected: inline void join(const Extrema& other) { assert(other.mSize > 0); mSize += other.mSize; mMin = std::min(mMin, other.mMin); mMax = std::max(mMax, other.mMax); } uint64_t mSize; double mMin, mMax; };//end Extrema /// @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 Extrema { public: Stats() : Extrema() , mAvg(0.0) , mAux(0.0) { } /// Add a single sample. void add(double val) { Extrema::add(val); 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) { const double denom = 1.0/double(mSize + n); const double delta = val - mAvg; mAvg += denom * delta * double(n); mAux += denom * delta * delta * double(mSize) * double(n); Extrema::add(val, n); } /// Add the samples from the other Stats instance. void add(const Stats& other) { if (other.mSize > 0) { const double denom = 1.0/double(mSize + other.mSize); const double delta = other.mAvg - mAvg; mAvg += denom * delta * double(other.mSize); mAux += other.mAux + denom * delta * delta * double(mSize) * double(other.mSize); Extrema::join(other); } } //@{ /// 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(); } protected: using Extrema::mSize; using Extrema::mMin; using Extrema::mMax; double mAvg, mAux; }; // 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) { if ( mMax <= mMin ) { OPENVDB_THROW(ValueError, "Histogram: expected min < max"); } else if ( numBins == 0 ) { OPENVDB_THROW(ValueError, "Histogram: expected at least one bin"); } 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 (int i = 0, e = int(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; };// end Histogram } // namespace math } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_MATH_STATS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000030514313200122377013216 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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/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 // for io::getFormatVersion() #include #include #include // for std::abs() #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; using UnitaryAndTranslationMap = CompoundMap; using SpectralDecomposedMap = CompoundMap, UnitaryMap>; using SymmetricMap = SpectralDecomposedMap; using FullyDecomposedMap = CompoundMap; using PolarDecomposedMap = CompoundMap; //////////////////////////////////////// /// 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 SharedPtr 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 SharedPtr 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 SharedPtr createPolarDecomposedMap(const Mat3d& m); /// @brief reduces an AffineMap to a ScaleMap or a ScaleTranslateMap when it can OPENVDB_API SharedPtr simplify(SharedPtr 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: using Ptr = SharedPtr; using ConstPtr = SharedPtr; using MapFactory = Ptr (*)(); MapBase(const MapBase&) = default; virtual ~MapBase() = default; virtual SharedPtr 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: using MapDictionary = std::map; 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: using Ptr = SharedPtr; using ConstPtr = SharedPtr; 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() override = default; /// 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 override { return MapBase::Ptr(new AffineMap(*this)); } MapBase::Ptr inverseMap() const override { 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 override { return mapType(); } static Name mapType() { return Name("AffineMap"); } /// Return @c true (an AffineMap is always linear). bool isLinear() const override { return true; } /// Return @c false ( test if this is unitary with translation ) bool hasUniformScale() const override { Mat3d mat = mMatrix.getMat3(); const double det = mat.det(); if (isApproxEqual(det, double(0))) { return false; } else { mat *= (1.0 / pow(std::abs(det), 1.0/3.0)); return isUnitary(mat); } } bool isEqual(const MapBase& other) const override { 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 override { return in * mMatrix; } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const override {return in * mMatrixInv; } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const override { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const override { return mMatrix.transform3x3(in); } /// @brief 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 override { return applyInverseJacobian(in); } /// @brief Return the Inverse Jacobian of the map applied to @a in /// (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const override { 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 override { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const override { 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 override { return applyIJT(in); } /// Return the transpose of the inverse Jacobian of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const override { return in * mJacobianInv; } /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& m) const override { return mJacobianInv.transpose()* m * mJacobianInv; } Mat3d applyIJC(const Mat3d& in, const Vec3d& , const Vec3d& ) const override { return applyIJC(in); } /// Return the determinant of the Jacobian, ignores argument double determinant(const Vec3d& ) const override { return determinant(); } /// Return the determinant of the Jacobian double determinant() const override { 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 override { return mVoxelSize; } Vec3d voxelSize(const Vec3d&) const override { 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) override { mMatrix.read(is); updateAcceleration(); } /// write serialization void write(std::ostream& os) const override { mMatrix.write(os); } /// string serialization, useful for debugging std::string str() const override { 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 SharedPtr createDecomposedMap() { return createFullyDecomposedMap(mMatrix); } /// Return AffineMap::Ptr to a deep copy of the current AffineMap AffineMap::Ptr getAffineMap() const override { 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 override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr preTranslate(const Vec3d& t) const override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreTranslation(t); return StaticPtrCast(affineMap); } MapBase::Ptr preScale(const Vec3d& s) const override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreScale(s); return StaticPtrCast(affineMap); } MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const override { 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 override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr postTranslate(const Vec3d& t) const override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostTranslation(t); return StaticPtrCast(affineMap); } MapBase::Ptr postScale(const Vec3d& s) const override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostScale(s); return StaticPtrCast(affineMap); } MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const override { 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: using Ptr = SharedPtr; using ConstPtr = SharedPtr; 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() override = default; /// 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 override { return MapBase::Ptr(new ScaleMap(*this)); } MapBase::Ptr inverseMap() const override { 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 override { return mapType(); } static Name mapType() { return Name("ScaleMap"); } /// Return @c true (a ScaleMap is always linear). bool isLinear() const override { return true; } /// Return @c true if the values have the same magitude (eg. -1, 1, -1 would be a rotation). bool hasUniformScale() const override { 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 override { 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 override { 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 override { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const override { return applyMap(in); } /// @brief 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 override { return applyInverseJacobian(in); } /// @brief Return the Inverse Jacobian of the map applied to @a in /// (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const override { return applyInverseMap(in); } /// @brief Return the Jacobian Transpose of the map applied to @a in. /// @details This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const override { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const override { 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 override { return applyIJT(in);} /// Return the transpose of the inverse Jacobian of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const override { return applyInverseMap(in); } /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& in) const override { 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 override { return applyIJC(in); } /// Return the product of the scale values, ignores argument double determinant(const Vec3d&) const override { return determinant(); } /// Return the product of the scale values double determinant() const override { 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 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). /// @details This is equivalent to the absolute values of the scale values Vec3d voxelSize() const override { return mVoxelSize; } Vec3d voxelSize(const Vec3d&) const override { return voxelSize(); } //@} /// read serialization void read(std::istream& is) override { mScaleValues.read(is); mVoxelSize.read(is); mScaleValuesInverse.read(is); mInvScaleSqr.read(is); mInvTwiceScale.read(is); } /// write serialization void write(std::ostream& os) const override { mScaleValues.write(os); mVoxelSize.write(os); mScaleValuesInverse.write(os); mInvScaleSqr.write(os); mInvTwiceScale.write(os); } /// string serialization, useful for debuging std::string str() const override { std::ostringstream buffer; buffer << " - scale: " << mScaleValues << std::endl; buffer << " - voxel dimensions: " << mVoxelSize << std::endl; return buffer.str(); } bool isEqual(const MapBase& other) const override { 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 override { 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 override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr preTranslate(const Vec3d&) const override; MapBase::Ptr preScale(const Vec3d&) const override; MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const override { 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 override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr postTranslate(const Vec3d&) const override; MapBase::Ptr postScale(const Vec3d&) const override; MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const override { 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: using Ptr = SharedPtr; using ConstPtr = SharedPtr; UniformScaleMap(): ScaleMap(Vec3d(1,1,1)) {} UniformScaleMap(double scale): ScaleMap(Vec3d(scale, scale, scale)) {} UniformScaleMap(const UniformScaleMap& other): ScaleMap(other) {} ~UniformScaleMap() override = default; /// 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 override { return MapBase::Ptr(new UniformScaleMap(*this)); } MapBase::Ptr inverseMap() const override { 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 override { return mapType(); } static Name mapType() { return Name("UniformScaleMap"); } bool isEqual(const MapBase& other) const override { return isEqualBase(*this, other); } bool operator==(const UniformScaleMap& other) const { return ScaleMap::operator==(other); } bool operator!=(const UniformScaleMap& other) const { return !(*this == other); } /// @brief Return a MapBase::Ptr to a UniformScaleTraslateMap that is the result of /// pre-translation on this map MapBase::Ptr preTranslate(const Vec3d&) const override; /// @brief Return a MapBase::Ptr to a UniformScaleTraslateMap that is the result of /// post-translation on this map MapBase::Ptr postTranslate(const Vec3d&) const override; }; // 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: using Ptr = SharedPtr; using ConstPtr = SharedPtr; // 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() override = default; /// 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 override { return MapBase::Ptr(new TranslationMap(*this)); } MapBase::Ptr inverseMap() const override { 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 override { return mapType(); } static Name mapType() { return Name("TranslationMap"); } /// Return @c true (a TranslationMap is always linear). bool isLinear() const override { return true; } /// Return @c false (by convention true) bool hasUniformScale() const override { return true; } /// Return the image of @c in under the map Vec3d applyMap(const Vec3d& in) const override { return in + mTranslation; } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const override { return in - mTranslation; } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const override { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const override { return in; } /// @brief 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 override { return applyInverseJacobian(in); } /// @brief Return the Inverse Jacobian of the map applied to @a in /// (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const override { return in; } /// @brief Return the Jacobian Transpose of the map applied to @a in. /// @details This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const override { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const override { 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 override { 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 override {return in;} /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& mat) const override {return mat;} Mat3d applyIJC(const Mat3d& mat, const Vec3d&, const Vec3d&) const override { return applyIJC(mat); } /// Return @c 1 double determinant(const Vec3d& ) const override { return determinant(); } /// Return @c 1 double determinant() const override { return 1.0; } /// Return (1,1,1). Vec3d voxelSize() const override { return Vec3d(1,1,1);} /// Return (1,1,1). Vec3d voxelSize(const Vec3d&) const override { return voxelSize();} /// Return the translation vector const Vec3d& getTranslation() const { return mTranslation; } /// read serialization void read(std::istream& is) override { mTranslation.read(is); } /// write serialization void write(std::ostream& os) const override { mTranslation.write(os); } /// string serialization, useful for debuging std::string str() const override { std::ostringstream buffer; buffer << " - translation: " << mTranslation << std::endl; return buffer.str(); } bool isEqual(const MapBase& other) const override { 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 override { 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 override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr preTranslate(const Vec3d& t) const override { return MapBase::Ptr(new TranslationMap(t + mTranslation)); } MapBase::Ptr preScale(const Vec3d& v) const override; MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const override { 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 override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr postTranslate(const Vec3d& t) const override { // post and pre are the same for this return MapBase::Ptr(new TranslationMap(t + mTranslation)); } MapBase::Ptr postScale(const Vec3d& v) const override; MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const override { 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: using Ptr = SharedPtr; using ConstPtr = SharedPtr; 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() override = default; /// 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 override { return MapBase::Ptr(new ScaleTranslateMap(*this)); } MapBase::Ptr inverseMap() const override { 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 override { return mapType(); } static Name mapType() { return Name("ScaleTranslateMap"); } /// Return @c true (a ScaleTranslateMap is always linear). bool isLinear() const override { 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 override { 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 override { 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 override { 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 override { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const override { return in * mScaleValues; } /// @brief 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 override { return applyInverseJacobian(in); } /// @brief Return the Inverse Jacobian of the map applied to @a in /// (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const override { return in * mScaleValuesInverse; } /// @brief Return the Jacobian Transpose of the map applied to @a in. /// @details This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const override { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const override { 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 override { return applyIJT(in);} /// Return the transpose of the inverse Jacobian of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const override { 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 override { 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 override { return applyIJC(in); } /// Return the product of the scale values, ignores argument double determinant(const Vec3d&) const override { return determinant(); } /// Return the product of the scale values double determinant() const override { return mScaleValues.x() * mScaleValues.y() * mScaleValues.z(); } /// Return the absolute values of the scale values Vec3d voxelSize() const override { return mVoxelSize;} /// Return the absolute values of the scale values, ignores argument Vec3d voxelSize(const Vec3d&) const override { 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) override { 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 override { 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 override { std::ostringstream buffer; buffer << " - translation: " << mTranslation << std::endl; buffer << " - scale: " << mScaleValues << std::endl; buffer << " - voxel dimensions: " << mVoxelSize << std::endl; return buffer.str(); } bool isEqual(const MapBase& other) const override { 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 override { 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 override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr preTranslate(const Vec3d& t) const override { 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 override; MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const override { 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 override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostRotation(axis, radians); return simplify(affineMap); } MapBase::Ptr postTranslate(const Vec3d& t) const override { return MapBase::Ptr( new ScaleTranslateMap(mScaleValues, mTranslation + t)); } MapBase::Ptr postScale(const Vec3d& v) const override; MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const override { 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: using Ptr = SharedPtr; using ConstPtr = SharedPtr; 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() override = default; /// 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 override { return MapBase::Ptr(new UniformScaleTranslateMap(*this)); } MapBase::Ptr inverseMap() const override { 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 override { return mapType(); } static Name mapType() { return Name("UniformScaleTranslateMap"); } bool isEqual(const MapBase& other) const override { 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 override { 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 override { 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: using Ptr = SharedPtr; using ConstPtr = SharedPtr; /// 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() override = default; /// 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 override { return MapBase::Ptr(new UnitaryMap(*this)); } MapBase::Ptr inverseMap() const override { 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 override { return mapType(); } /// Return @c UnitaryMap static Name mapType() { return Name("UnitaryMap"); } /// Return @c true (a UnitaryMap is always linear). bool isLinear() const override { return true; } /// Return @c false (by convention true) bool hasUniformScale() const override { return true; } bool isEqual(const MapBase& other) const override { 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 override { return mAffineMap.applyMap(in); } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const override { return mAffineMap.applyInverseMap(in); } Vec3d applyJacobian(const Vec3d& in, const Vec3d&) const override { return applyJacobian(in); } /// Return the Jacobian of the map applied to @a in. Vec3d applyJacobian(const Vec3d& in) const override { return mAffineMap.applyJacobian(in); } /// @brief 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 override { return applyInverseJacobian(in); } /// @brief Return the Inverse Jacobian of the map applied to @a in /// (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const override { return mAffineMap.applyInverseJacobian(in); } /// @brief Return the Jacobian Transpose of the map applied to @a in. /// @details This tranforms range-space gradients to domain-space gradients Vec3d applyJT(const Vec3d& in, const Vec3d&) const override { return applyJT(in); } /// Return the Jacobian Transpose of the map applied to @a in. Vec3d applyJT(const Vec3d& in) const override { return applyInverseMap(in); // the transpose of the unitary map is its inverse } /// @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 override { return applyIJT(in);} /// Return the transpose of the inverse Jacobian of the map applied to @c in Vec3d applyIJT(const Vec3d& in) const override { return mAffineMap.applyIJT(in); } /// Return the Jacobian Curvature: zero for a linear map Mat3d applyIJC(const Mat3d& in) const override { return mAffineMap.applyIJC(in); } Mat3d applyIJC(const Mat3d& in, const Vec3d&, const Vec3d& ) const override { return applyIJC(in); } /// Return the determinant of the Jacobian, ignores argument double determinant(const Vec3d&) const override { return determinant(); } /// Return the determinant of the Jacobian double determinant() const override { return mAffineMap.determinant(); } /// @{ /// @brief Returns 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 override { return mAffineMap.voxelSize();} Vec3d voxelSize(const Vec3d&) const override { return voxelSize();} /// @} /// read serialization void read(std::istream& is) override { mAffineMap.read(is); } /// write serialization void write(std::ostream& os) const override { mAffineMap.write(os); } /// string serialization, useful for debuging std::string str() const override { std::ostringstream buffer; buffer << mAffineMap.str(); return buffer.str(); } /// Return AffineMap::Ptr to an AffineMap equivalent to *this AffineMap::Ptr getAffineMap() const override { return AffineMap::Ptr(new AffineMap(mAffineMap)); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the given rotation. MapBase::Ptr preRotate(double radians, Axis axis) const override { UnitaryMap first(axis, radians); UnitaryMap::Ptr unitaryMap(new UnitaryMap(first, *this)); return StaticPtrCast(unitaryMap); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the given translation. MapBase::Ptr preTranslate(const Vec3d& t) const override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreTranslation(t); return simplify(affineMap); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the given scale. MapBase::Ptr preScale(const Vec3d& v) const override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPreScale(v); return simplify(affineMap); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the given shear. MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const override { 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 appending the given rotation. MapBase::Ptr postRotate(double radians, Axis axis) const override { UnitaryMap second(axis, radians); UnitaryMap::Ptr unitaryMap(new UnitaryMap(*this, second)); return StaticPtrCast(unitaryMap); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of appending the given translation. MapBase::Ptr postTranslate(const Vec3d& t) const override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostTranslation(t); return simplify(affineMap); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of appending the given scale. MapBase::Ptr postScale(const Vec3d& v) const override { AffineMap::Ptr affineMap = getAffineMap(); affineMap->accumPostScale(v); return simplify(affineMap); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of appending the given shear. MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const override { 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. /// First it will take a box of size (Lx X Ly X Lz) defined by a member data bounding box /// and map it into a frustum with near plane (1 X Ly/Lx) and prescribed depth /// Then this frustum is transformed by an internal second map: most often a uniform scale, /// but other effects can be achieved by accumulating translation, shear and rotation: these /// are all applied to the second map class OPENVDB_API NonlinearFrustumMap: public MapBase { public: using Ptr = SharedPtr; using ConstPtr = SharedPtr; 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() override = default; /// 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 override { 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 override { 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 override { return mapType(); } /// Return @c NonlinearFrustumMap static Name mapType() { return Name("NonlinearFrustumMap"); } /// Return @c false (a NonlinearFrustumMap is never linear). bool isLinear() const override { return false; } /// Return @c false (by convention false) bool hasUniformScale() const override { 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; } bool isEqual(const MapBase& other) const override { 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 override { return mSecondMap.applyMap(applyFrustumMap(in)); } /// Return the pre-image of @c in under the map Vec3d applyInverseMap(const Vec3d& in) const override { return applyFrustumInverseMap(mSecondMap.applyInverseMap(in)); } /// Return the Jacobian of the linear second map applied to @c in Vec3d applyJacobian(const Vec3d& in) const override { return mSecondMap.applyJacobian(in); } /// Return the Jacobian defined at @c isloc applied to @c in Vec3d applyJacobian(const Vec3d& in, const Vec3d& isloc) const override { // 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); } /// @brief Return the Inverse Jacobian of the map applied to @a in /// (i.e. inverse map with out translation) Vec3d applyInverseJacobian(const Vec3d& in) const override { 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 override { // 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; } /// @brief Return the Jacobian Transpose of the map applied to vector @c in at @c indexloc. /// @details This tranforms range-space gradients to domain-space gradients. Vec3d applyJT(const Vec3d& in, const Vec3d& isloc) const override { 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 override { 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 override { 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 override { 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(i) = 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 override { 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 override { 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 override {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 override { 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 override { 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 override { 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 override { 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) override { // 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 override { 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 override { 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 given rotation to the linear part of this map MapBase::Ptr preRotate(double radians, Axis axis = X_AXIS) const override { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preRotate(radians, axis))); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the given translation to the linear part of this map MapBase::Ptr preTranslate(const Vec3d& t) const override { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preTranslate(t))); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the given scale to the linear part of this map MapBase::Ptr preScale(const Vec3d& s) const override { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.preScale(s))); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of prepending the given shear to the linear part of this map MapBase::Ptr preShear(double shear, Axis axis0, Axis axis1) const override { 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 appending the given rotation to the linear part of this map. MapBase::Ptr postRotate(double radians, Axis axis = X_AXIS) const override { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postRotate(radians, axis))); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of appending the given translation to the linear part of this map. MapBase::Ptr postTranslate(const Vec3d& t) const override { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postTranslate(t))); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of appending the given scale to the linear part of this map. MapBase::Ptr postScale(const Vec3d& s) const override { return MapBase::Ptr( new NonlinearFrustumMap(mBBox, mTaper, mDepth, mSecondMap.postScale(s))); } /// @brief Return a MapBase::Ptr to a new map that is the result /// of appending the given shear to the linear part of this map. MapBase::Ptr postShear(double shear, Axis axis0, Axis axis1) const override { 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: using MyType = CompoundMap; using Ptr = SharedPtr; using ConstPtr = SharedPtr; 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-2017 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.h0000644000000000000000000006704513200122377013215 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #include #include // for std::max() #include #include // for std::ceil(), std::fabs(), std::pow(), std::sqrt(), etc. #include // for abs(int) #include #include #include // for std::is_arithmetic // 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)") #elif defined(__clang__) #define OPENVDB_NO_FP_EQUALITY_WARNING_BEGIN \ PRAGMA(clang diagnostic push) \ PRAGMA(clang diagnostic ignored "-Wfloat-equal") #define OPENVDB_NO_FP_EQUALITY_WARNING_END \ PRAGMA(clang diagnostic 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 nullptr 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 <================== /// @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; std::uniform_real_distribution mRand; public: using ValueType = FloatType; /// @brief Initialize the generator. /// @param engine random number generator Rand01(const EngineType& engine): mEngine(engine) {} /// @brief Initialize the generator. /// @param seed seed value for the random number generator Rand01(unsigned int seed): mEngine(static_cast(seed)) {} /// Set the seed value for the random number generator void setSeed(unsigned int seed) { mEngine.seed(static_cast(seed)); } /// Return a const reference to the random number generator. const EngineType& engine() const { return mEngine; } /// Return a uniformly distributed random number in the range [0, 1). FloatType operator()() { return mRand(mEngine); } }; using Random01 = Rand01; /// @brief Simple random integer generator /// @details Thread-safe as long as each thread has its own RandInt instance template class RandInt { private: using Distr = std::uniform_int_distribution; EngineType mEngine; Distr mRand; public: /// @brief Initialize the generator. /// @param engine random number generator /// @param imin,imax generate integers that are uniformly distributed over [imin, imax] RandInt(const EngineType& engine, IntType imin, IntType imax): mEngine(engine), mRand(std::min(imin, imax), std::max(imin, imax)) {} /// @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, IntType imin, IntType 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(IntType imin, IntType imax) { mRand = Distr(std::min(imin, imax), std::max(imin, imax)); } /// Set the seed value for the random number generator void setSeed(unsigned int seed) { mEngine.seed(static_cast(seed)); } /// Return a const reference to the random number generator. const EngineType& engine() const { return mEngine; } /// Return a randomly-generated integer in the current range. IntType operator()() { return mRand(mEngine); } /// @brief Return a randomly-generated integer in the new range [imin, imax], /// without changing the current range. IntType operator()(IntType imin, IntType imax) { const IntType lo = std::min(imin, imax), hi = std::max(imin, imax); return mRand(mEngine, typename Distr::param_type(lo, hi)); } }; using RandomInt = RandInt; // ==========> Clamp <================== /// Return @a x clamped to [@a min, @a max] template inline Type Clamp(Type x, Type min, Type max) { assert( !(min>max) ); return x > 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 0, 1 if @a x > 1 or else (3 − 2 @a x) @a x². template inline Type SmoothUnitStep(Type x) { return x > 0 ? x < 1 ? (3-2*x)*x*x : Type(1) : Type(0); } /// @brief Return 0 if @a x < @a min, 1 if @a x > @a max or else (3 − 2 @a t) @a t², /// where @a t = (@a x − @a min)/(@a max − @a min). template inline Type SmoothUnitStep(Type x, Type min, Type max) { assert(min < max); return SmoothUnitStep((x-min)/(max-min)); } // ==========> 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 labs(i); #endif } inline float Abs(float x) { return std::fabs(x); } inline double Abs(double x) { return std::fabs(x); } inline long double Abs(long double x) { return std::fabs(x); } inline uint32_t Abs(uint32_t i) { return i; } inline uint64_t Abs(uint64_t i) { return i; } inline bool Abs(bool b) { return b; } // 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; } /// Return @c true if @a x is finite. template::value, int>::type = 0> inline bool isFinite(const Type& x) { return std::isfinite(x); } /// @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 the reference 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 @a x2. template inline Type Pow2(Type x) { return x*x; } /// Return @a x3. template inline Type Pow3(Type x) { return x*x*x; } /// Return @a x4. template inline Type Pow4(Type x) { return Pow2(Pow2(x)); } /// Return @a x@a n. 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 @a b@a e. 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 std::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)); } // ============> Exp <================== /// Return @a e@a x. template inline Type Exp(const Type& x) { return std::exp(x); } // ============> Sin <================== //@{ /// Return sin @a x. inline float Sin(const float& x) { return std::sin(x); } inline double Sin(const double& x) { return std::sin(x); } //@} // ============> Cos <================== //@{ /// Return cos @a x. inline float Cos(const float& x) { return std::cos(x); } inline double Cos(const double& x) { return std::cos(x); } //@} //////////////////////////////////////// /// 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 std::sqrt(x); } inline double Sqrt(double x) { return std::sqrt(x); } inline long double Sqrt(long double x) { return std::sqrt(x); } //@} //@{ /// Return the cube root of a floating-point value. inline float Cbrt(float x) { return std::cbrt(x); } inline double Cbrt(double x) { return std::cbrt(x); } inline long double Cbrt(long double x) { return std::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 std::fmod(x, y); } inline double Mod(double x, double y) { return std::fmod(x, y); } inline long double Mod(long double x, long double y) { return std::fmod(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 std::ceil(x); } inline double RoundUp(double x) { return std::ceil(x); } inline long double RoundUp(long double x) { return std::ceil(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 std::floor(x); } inline double RoundDown(double x) { return std::floor(x); } inline long double RoundDown(long double x) { return std::floor(x); } //@} /// 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 @a x rounded to the nearest integer. inline float Round(float x) { return RoundDown(x + 0.5f); } inline double Round(double x) { return RoundDown(x + 0.5); } inline long double Round(long double x) { return RoundDown(x + 0.5l); } //@} /// Return the euclidean remainder of @a x. /// Note unlike % operator this will always return a positive result template inline Type EuclideanRemainder(Type x) { return x - RoundDown(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 or equal 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; } //////////////////////////////////////// /// @brief 8-bit integer values print to std::ostreams as characters. /// Cast them so that they print as integers instead. template inline auto PrintCast(const T& val) -> typename std::enable_if::value && !std::is_same::value, const T&>::type { return val; } inline int32_t PrintCast(int8_t val) { return int32_t(val); } inline uint32_t PrintCast(uint8_t val) { return uint32_t(val); } //////////////////////////////////////// /// 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 { using type = typename boost::numeric::conversion_traits::supertype; }; /// @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 than 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) { #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif 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 than 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 largest vector components. template size_t MaxIndex(const Vec3T& v) { #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif 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-2017 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/Operators.h0000644000000000000000000024017513200122377014277 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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/Operators.h #ifndef OPENVDB_MATH_OPERATORS_HAS_BEEN_INCLUDED #define OPENVDB_MATH_OPERATORS_HAS_BEEN_INCLUDED #include "FiniteDifference.h" #include "Stencils.h" #include "Maps.h" #include "Transform.h" #include // for std::sqrt() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { // Simple tools to help determine when type conversions are needed template 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 { using ValueType = typename T::ValueType; using Vec3Type = math::Vec3; }; } // 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) { using ValueType = typename Accessor::ValueType; using Vec3Type = Vec3; return Vec3Type( D1::inX(grid, ijk), D1::inY(grid, ijk), D1::inZ(grid, ijk) ); } // stencil access version template static Vec3 result(const StencilT& stencil) { using ValueType = typename StencilT::ValueType; using Vec3Type = Vec3; 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 { using StencilType = SevenPointStencil; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_1ST; static const DScheme BD = BD_1ST; template struct ISStencil { using StencilType = SevenPointStencil; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_2ND; static const DScheme BD = BD_2ND; template struct ISStencil { using StencilType = ThirteenPointStencil; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_3RD; static const DScheme BD = BD_3RD; template struct ISStencil { using StencilType = NineteenPointStencil; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_WENO5; static const DScheme BD = BD_WENO5; template struct ISStencil { using StencilType = NineteenPointStencil; }; }; template<> struct BIAS_SCHEME { static const DScheme FD = FD_HJWENO5; static const DScheme BD = BD_HJWENO5; template struct ISStencil { using StencilType = NineteenPointStencil; }; }; //@{ /// @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) { using ValueType = typename Accessor::ValueType; using Vec3Type = Vec3; 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) { using ValueType = typename StencilT::ValueType; using Vec3Type = Vec3; 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) { using ValueType = typename Accessor::ValueType; using Vec3Type = math::Vec3; Vec3Type up = ISGradient::result(grid, ijk); Vec3Type down = ISGradient::result(grid, ijk); return math::GodunovsNormSqrd(grid.getValue(ijk)>0, down, up); } // stencil access version template static typename StencilT::ValueType result(const StencilT& stencil) { using ValueType = typename StencilT::ValueType; using Vec3Type = math::Vec3; Vec3Type up = ISGradient::result(stencil); Vec3Type down = ISGradient::result(stencil); return math::GodunovsNormSqrd(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) { struct GetValue { const Accessor& acc; GetValue(const Accessor& acc_): acc(acc_) {} // Return the grid value at ijk converted to simd::Float4::value_type (= float). inline simd::Float4::value_type operator()(const Coord& ijk_) { return static_cast(acc.getValue(ijk_)); } } valueAt(grid); // SSE optimized const simd::Float4 v1(valueAt(ijk.offsetBy(-2, 0, 0)) - valueAt(ijk.offsetBy(-3, 0, 0)), valueAt(ijk.offsetBy( 0,-2, 0)) - valueAt(ijk.offsetBy( 0,-3, 0)), valueAt(ijk.offsetBy( 0, 0,-2)) - valueAt(ijk.offsetBy( 0, 0,-3)), 0), v2(valueAt(ijk.offsetBy(-1, 0, 0)) - valueAt(ijk.offsetBy(-2, 0, 0)), valueAt(ijk.offsetBy( 0,-1, 0)) - valueAt(ijk.offsetBy( 0,-2, 0)), valueAt(ijk.offsetBy( 0, 0,-1)) - valueAt(ijk.offsetBy( 0, 0,-2)), 0), v3(valueAt(ijk ) - valueAt(ijk.offsetBy(-1, 0, 0)), valueAt(ijk ) - valueAt(ijk.offsetBy( 0,-1, 0)), valueAt(ijk ) - valueAt(ijk.offsetBy( 0, 0,-1)), 0), v4(valueAt(ijk.offsetBy( 1, 0, 0)) - valueAt(ijk ), valueAt(ijk.offsetBy( 0, 1, 0)) - valueAt(ijk ), valueAt(ijk.offsetBy( 0, 0, 1)) - valueAt(ijk ), 0), v5(valueAt(ijk.offsetBy( 2, 0, 0)) - valueAt(ijk.offsetBy( 1, 0, 0)), valueAt(ijk.offsetBy( 0, 2, 0)) - valueAt(ijk.offsetBy( 0, 1, 0)), valueAt(ijk.offsetBy( 0, 0, 2)) - valueAt(ijk.offsetBy( 0, 0, 1)), 0), v6(valueAt(ijk.offsetBy( 3, 0, 0)) - valueAt(ijk.offsetBy( 2, 0, 0)), valueAt(ijk.offsetBy( 0, 3, 0)) - valueAt(ijk.offsetBy( 0, 2, 0)), valueAt(ijk.offsetBy( 0, 0, 3)) - valueAt(ijk.offsetBy( 0, 0, 2)), 0), down = math::WENO5(v1, v2, v3, v4, v5), up = math::WENO5(v6, v5, v4, v3, v2); return math::GodunovsNormSqrd(grid.getValue(ijk)>0, down, up); } // stencil access version template static typename StencilT::ValueType result(const StencilT& s) { using F4Val = simd::Float4::value_type; // SSE optimized const simd::Float4 v1(F4Val(s.template getValue<-2, 0, 0>()) - F4Val(s.template getValue<-3, 0, 0>()), F4Val(s.template getValue< 0,-2, 0>()) - F4Val(s.template getValue< 0,-3, 0>()), F4Val(s.template getValue< 0, 0,-2>()) - F4Val(s.template getValue< 0, 0,-3>()), 0), v2(F4Val(s.template getValue<-1, 0, 0>()) - F4Val(s.template getValue<-2, 0, 0>()), F4Val(s.template getValue< 0,-1, 0>()) - F4Val(s.template getValue< 0,-2, 0>()), F4Val(s.template getValue< 0, 0,-1>()) - F4Val(s.template getValue< 0, 0,-2>()), 0), v3(F4Val(s.template getValue< 0, 0, 0>()) - F4Val(s.template getValue<-1, 0, 0>()), F4Val(s.template getValue< 0, 0, 0>()) - F4Val(s.template getValue< 0,-1, 0>()), F4Val(s.template getValue< 0, 0, 0>()) - F4Val(s.template getValue< 0, 0,-1>()), 0), v4(F4Val(s.template getValue< 1, 0, 0>()) - F4Val(s.template getValue< 0, 0, 0>()), F4Val(s.template getValue< 0, 1, 0>()) - F4Val(s.template getValue< 0, 0, 0>()), F4Val(s.template getValue< 0, 0, 1>()) - F4Val(s.template getValue< 0, 0, 0>()), 0), v5(F4Val(s.template getValue< 2, 0, 0>()) - F4Val(s.template getValue< 1, 0, 0>()), F4Val(s.template getValue< 0, 2, 0>()) - F4Val(s.template getValue< 0, 1, 0>()), F4Val(s.template getValue< 0, 0, 2>()) - F4Val(s.template getValue< 0, 0, 1>()), 0), v6(F4Val(s.template getValue< 3, 0, 0>()) - F4Val(s.template getValue< 2, 0, 0>()), F4Val(s.template getValue< 0, 3, 0>()) - F4Val(s.template getValue< 0, 2, 0>()), F4Val(s.template getValue< 0, 0, 3>()) - F4Val(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::GodunovsNormSqrd(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) { using ValueT = typename Accessor::ValueType; return static_cast( (-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) { using ValueT = typename StencilT::ValueType; return static_cast( (-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) { using ValueT = typename Accessor::ValueType; return static_cast( (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) { using ValueT = typename StencilT::ValueType; return static_cast( (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) { using Vec3Type = typename Accessor::ValueType; 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) { using Vec3Type = typename StencilT::ValueType; 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 { /// @brief Random access version /// @return @c true if the gradient is nonzero, in which case the mean curvature /// is returned in two parts, @a alpha and @a beta, where @a alpha is the numerator /// in ∇ · (∇Φ / |∇Φ|) and @a beta is |∇Φ|. template static bool result(const Accessor& grid, const Coord& ijk, typename Accessor::ValueType& alpha, typename Accessor::ValueType& beta) { using ValueType = typename Accessor::ValueType; const ValueType Dx = D1::inX(grid, ijk); const ValueType Dy = D1::inY(grid, ijk); const ValueType Dz = D1::inZ(grid, ijk); const ValueType Dx2 = Dx*Dx; const ValueType Dy2 = Dy*Dy; const ValueType Dz2 = Dz*Dz; const ValueType normGrad = Dx2 + Dy2 + Dz2; if (normGrad <= math::Tolerance::value()) { alpha = beta = 0; return false; } const ValueType Dxx = D2::inX(grid, ijk); const ValueType Dyy = D2::inY(grid, ijk); const ValueType Dzz = D2::inZ(grid, ijk); const ValueType Dxy = D2::inXandY(grid, ijk); const ValueType Dyz = D2::inYandZ(grid, ijk); const 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(normGrad))); // * 1/dx return true; } /// @brief Stencil access version /// @return @c true if the gradient is nonzero, in which case the mean curvature /// is returned in two parts, @a alpha and @a beta, where @a alpha is the numerator /// in ∇ · (∇Φ / |∇Φ|) and @a beta is |∇Φ|. template static bool result(const StencilT& stencil, typename StencilT::ValueType& alpha, typename StencilT::ValueType& beta) { using ValueType = typename StencilT::ValueType; const ValueType Dx = D1::inX(stencil); const ValueType Dy = D1::inY(stencil); const ValueType Dz = D1::inZ(stencil); const ValueType Dx2 = Dx*Dx; const ValueType Dy2 = Dy*Dy; const ValueType Dz2 = Dz*Dz; const ValueType normGrad = Dx2 + Dy2 + Dz2; if (normGrad <= math::Tolerance::value()) { alpha = beta = 0; return false; } const ValueType Dxx = D2::inX(stencil); const ValueType Dyy = D2::inY(stencil); const ValueType Dzz = D2::inZ(stencil); const ValueType Dxy = D2::inXandY(stencil); const ValueType Dyz = D2::inYandZ(stencil); const 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(normGrad))); // * 1/dx return true; } }; //////////////////////////////////////////////////////// // --- 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) { using Vec3Type = typename internal::ReturnValue::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) { using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename internal::ReturnValue::ValueType; using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename internal::ReturnValue::ValueType; using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename internal::ReturnValue::ValueType; using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename internal::ReturnValue::ValueType; using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename internal::ReturnValue::ValueType; using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename internal::ReturnValue::ValueType; using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename internal::ReturnValue::ValueType; using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename internal::ReturnValue::ValueType; using Vec3Type = typename internal::ReturnValue::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) { using ValueType = typename Accessor::ValueType; using Vec3Type = math::Vec3; 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) { using ValueType = typename StencilT::ValueType; using Vec3Type = math::Vec3; 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) { using ValueType = typename Accessor::ValueType; using Vec3Type = math::Vec3; Vec3Type up = Gradient::result(map, grid, ijk); Vec3Type down = Gradient::result(map, grid, ijk); return math::GodunovsNormSqrd(grid.getValue(ijk)>0, down, up); } // stencil access version template static typename StencilT::ValueType result(const MapType& map, const StencilT& stencil) { using ValueType = typename StencilT::ValueType; using Vec3Type = math::Vec3; Vec3Type up = Gradient::result(map, stencil); Vec3Type down = Gradient::result(map, stencil); return math::GodunovsNormSqrd(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) { using ValueType = typename Accessor::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) { using ValueType = typename StencilT::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) { using ValueType = typename Accessor::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) { using ValueType = typename StencilT::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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using ValueType = typename Accessor::ValueType::value_type; 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) { using ValueType = typename StencilT::ValueType::value_type; 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) { using Vec3Type = typename Accessor::ValueType; 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) { using Vec3Type = typename StencilT::ValueType; 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) { using Vec3Type = typename Accessor::ValueType; using ValueType = typename Vec3Type::value_type; return ISCurl::result(grid, ijk) * ValueType(map.getInvScale()[0]); } // Stencil access version template static typename StencilT::ValueType result(const UniformScaleMap& map, const StencilT& stencil) { using Vec3Type = typename StencilT::ValueType; using ValueType = typename Vec3Type::value_type; 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) { using Vec3Type = typename Accessor::ValueType; using ValueType = typename Vec3Type::value_type; return ISCurl::result(grid, ijk) * ValueType(map.getInvScale()[0]); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleTranslateMap& map, const StencilT& stencil) { using Vec3Type = typename StencilT::ValueType; using ValueType = typename Vec3Type::value_type; 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) { using Vec3Type = typename Accessor::ValueType; using ValueType = typename Vec3Type::value_type; return ISCurl::result(grid, ijk) * ValueType(map.getInvTwiceScale()[0]); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleMap& map, const StencilT& stencil) { using Vec3Type = typename StencilT::ValueType; using ValueType = typename Vec3Type::value_type; 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) { using Vec3Type = typename Accessor::ValueType; using ValueType = typename Vec3Type::value_type; return ISCurl::result(grid, ijk) * ValueType(map.getInvTwiceScale()[0]); } // stencil access version template static typename StencilT::ValueType result(const UniformScaleTranslateMap& map, const StencilT& stencil) { using Vec3Type = typename StencilT::ValueType; using ValueType = typename Vec3Type::value_type; 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) { using ValueType = typename Accessor::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(static_cast(D1::inX(grid, ijk)), static_cast(D1::inY(grid, ijk)), static_cast(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) { using ValueType = typename StencilT::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) { using ValueType = typename Accessor::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) { using ValueType = typename StencilT::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) { using ValueType = typename Accessor::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) { using ValueType = typename StencilT::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) { using ValueType = typename Accessor::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) { using ValueType = typename StencilT::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) { using ValueType = typename Accessor::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) { using ValueType = typename StencilT::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) { using ValueType = typename Accessor::ValueType; using Vec3Type = Vec3; // 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) { using ValueType = typename StencilT::ValueType; using Vec3Type = Vec3; // 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) { using ValueType = typename Accessor::ValueType; using Vec3Type = Vec3; // 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) { using ValueType = typename StencilT::ValueType; using Vec3Type = Vec3; // 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. /// @details The mean curvature is returned in two parts, @a alpha and @a beta, /// where @a alpha is the numerator in ∇ · (∇Φ / |∇Φ|) /// and @a beta is |∇Φ|. template struct MeanCurvature { /// @brief Random access version /// @return @c true if the gradient is nonzero, in which case the mean curvature /// is returned in two parts, @a alpha and @a beta, where @a alpha is the numerator /// in ∇ · (∇Φ / |∇Φ|) and @a beta is |∇Φ|. template static bool compute(const MapType& map, const Accessor& grid, const Coord& ijk, double& alpha, double& beta) { using ValueType = typename Accessor::ValueType; // compute the gradient in index and world space Vec3d d1_is(static_cast(D1::inX(grid, ijk)), static_cast(D1::inY(grid, ijk)), static_cast(D1::inZ(grid, ijk))), d1_ws; if (is_linear::value) {//resolved at compiletime d1_ws = map.applyIJT(d1_is); } else { d1_ws = map.applyIJT(d1_is, ijk.asVec3d()); } const double Dx2 = d1_ws(0)*d1_ws(0); const double Dy2 = d1_ws(1)*d1_ws(1); const double Dz2 = d1_ws(2)*d1_ws(2); const double normGrad = Dx2 + Dy2 + Dz2; if (normGrad <= math::Tolerance::value()) { alpha = beta = 0; return false; } // 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 second derivatives to world space Mat3d d2_ws; if (is_linear::value) {//resolved at compiletime d2_ws = map.applyIJC(d2_is); } else { d2_ws = map.applyIJC(d2_is, d1_is, ijk.asVec3d()); } // assemble the nominator and denominator for mean curvature alpha = (Dx2*(d2_ws(1,1)+d2_ws(2,2))+Dy2*(d2_ws(0,0)+d2_ws(2,2)) +Dz2*(d2_ws(0,0)+d2_ws(1,1)) -2*(d1_ws(0)*(d1_ws(1)*d2_ws(0,1)+d1_ws(2)*d2_ws(0,2)) +d1_ws(1)*d1_ws(2)*d2_ws(1,2))); beta = std::sqrt(normGrad); // * 1/dx return true; } template static typename Accessor::ValueType result(const MapType& map, const Accessor& grid, const Coord& ijk) { using ValueType = typename Accessor::ValueType; double alpha, beta; return compute(map, grid, ijk, alpha, beta) ? ValueType(alpha/(2. *math::Pow3(beta))) : 0; } template static typename Accessor::ValueType normGrad(const MapType& map, const Accessor& grid, const Coord& ijk) { using ValueType = typename Accessor::ValueType; double alpha, beta; return compute(map, grid, ijk, alpha, beta) ? ValueType(alpha/(2. *math::Pow2(beta))) : 0; } /// @brief Stencil access version /// @return @c true if the gradient is nonzero, in which case the mean curvature /// is returned in two parts, @a alpha and @a beta, where @a alpha is the numerator /// in ∇ · (∇Φ / |∇Φ|) and @a beta is |∇Φ|. template static bool compute(const MapType& map, const StencilT& stencil, double& alpha, double& beta) { using ValueType = typename StencilT::ValueType; // compute the gradient in index and world space Vec3d d1_is(D1::inX(stencil), D1::inY(stencil), D1::inZ(stencil) ), d1_ws; if (is_linear::value) {//resolved at compiletime d1_ws = map.applyIJT(d1_is); } else { d1_ws = map.applyIJT(d1_is, stencil.getCenterCoord().asVec3d()); } const double Dx2 = d1_ws(0)*d1_ws(0); const double Dy2 = d1_ws(1)*d1_ws(1); const double Dz2 = d1_ws(2)*d1_ws(2); const double normGrad = Dx2 + Dy2 + Dz2; if (normGrad <= math::Tolerance::value()) { alpha = beta = 0; return false; } // 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 second derivatives to world space Mat3d d2_ws; if (is_linear::value) {//resolved at compiletime d2_ws = map.applyIJC(d2_is); } else { d2_ws = map.applyIJC(d2_is, d1_is, stencil.getCenterCoord().asVec3d()); } // for return alpha = (Dx2*(d2_ws(1,1)+d2_ws(2,2))+Dy2*(d2_ws(0,0)+d2_ws(2,2)) +Dz2*(d2_ws(0,0)+d2_ws(1,1)) -2*(d1_ws(0)*(d1_ws(1)*d2_ws(0,1)+d1_ws(2)*d2_ws(0,2)) +d1_ws(1)*d1_ws(2)*d2_ws(1,2))); beta = std::sqrt(normGrad); // * 1/dx return true; } template static typename StencilT::ValueType result(const MapType& map, const StencilT stencil) { using ValueType = typename StencilT::ValueType; double alpha, beta; return compute(map, stencil, alpha, beta) ? ValueType(alpha/(2*math::Pow3(beta))) : 0; } template static typename StencilT::ValueType normGrad(const MapType& map, const StencilT stencil) { using ValueType = typename StencilT::ValueType; double alpha, beta; return compute(map, stencil, alpha, beta) ? ValueType(alpha/(2*math::Pow2(beta))) : 0; } }; template struct MeanCurvature { // random access version template static typename Accessor::ValueType result(const TranslationMap&, const Accessor& grid, const Coord& ijk) { using ValueType = typename Accessor::ValueType; ValueType alpha, beta; return ISMeanCurvature::result(grid, ijk, alpha, beta) ? ValueType(alpha /(2*math::Pow3(beta))) : 0; } template static typename Accessor::ValueType normGrad(const TranslationMap&, const Accessor& grid, const Coord& ijk) { using ValueType = typename Accessor::ValueType; ValueType alpha, beta; return ISMeanCurvature::result(grid, ijk, alpha, beta) ? ValueType(alpha/(2*math::Pow2(beta))) : 0; } // stencil access version template static typename StencilT::ValueType result(const TranslationMap&, const StencilT& stencil) { using ValueType = typename StencilT::ValueType; ValueType alpha, beta; return ISMeanCurvature::result(stencil, alpha, beta) ? ValueType(alpha /(2*math::Pow3(beta))) : 0; } template static typename StencilT::ValueType normGrad(const TranslationMap&, const StencilT& stencil) { using ValueType = typename StencilT::ValueType; ValueType alpha, beta; return ISMeanCurvature::result(stencil, alpha, beta) ? ValueType(alpha/(2*math::Pow2(beta))) : 0; } }; template struct MeanCurvature { // random access version template static typename Accessor::ValueType result(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { using ValueType = typename Accessor::ValueType; ValueType alpha, beta; if (ISMeanCurvature::result(grid, ijk, alpha, beta)) { ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return ValueType(alpha*inv2dx/math::Pow3(beta)); } return 0; } template static typename Accessor::ValueType normGrad(const UniformScaleMap& map, const Accessor& grid, const Coord& ijk) { using ValueType = typename Accessor::ValueType; ValueType alpha, beta; if (ISMeanCurvature::result(grid, ijk, alpha, beta)) { ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ValueType(alpha*invdxdx/(2*math::Pow2(beta))); } return 0; } // stencil access version template static typename StencilT::ValueType result(const UniformScaleMap& map, const StencilT& stencil) { using ValueType = typename StencilT::ValueType; ValueType alpha, beta; if (ISMeanCurvature::result(stencil, alpha, beta)) { ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return ValueType(alpha*inv2dx/math::Pow3(beta)); } return 0; } template static typename StencilT::ValueType normGrad(const UniformScaleMap& map, const StencilT& stencil) { using ValueType = typename StencilT::ValueType; ValueType alpha, beta; if (ISMeanCurvature::result(stencil, alpha, beta)) { ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ValueType(alpha*invdxdx/(2*math::Pow2(beta))); } return 0; } }; template struct MeanCurvature { // random access version template static typename Accessor::ValueType result(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { using ValueType = typename Accessor::ValueType; ValueType alpha, beta; if (ISMeanCurvature::result(grid, ijk, alpha, beta)) { ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return ValueType(alpha*inv2dx/math::Pow3(beta)); } return 0; } template static typename Accessor::ValueType normGrad(const UniformScaleTranslateMap& map, const Accessor& grid, const Coord& ijk) { using ValueType = typename Accessor::ValueType; ValueType alpha, beta; if (ISMeanCurvature::result(grid, ijk, alpha, beta)) { ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ValueType(alpha*invdxdx/(2*math::Pow2(beta))); } return 0; } // stencil access version template static typename StencilT::ValueType result(const UniformScaleTranslateMap& map, const StencilT& stencil) { using ValueType = typename StencilT::ValueType; ValueType alpha, beta; if (ISMeanCurvature::result(stencil, alpha, beta)) { ValueType inv2dx = ValueType(map.getInvTwiceScale()[0]); return ValueType(alpha*inv2dx/math::Pow3(beta)); } return 0; } template static typename StencilT::ValueType normGrad(const UniformScaleTranslateMap& map, const StencilT& stencil) { using ValueType = typename StencilT::ValueType; ValueType alpha, beta; if (ISMeanCurvature::result(stencil, alpha, beta)) { ValueType invdxdx = ValueType(map.getInvScaleSqr()[0]); return ValueType(alpha*invdxdx/(2*math::Pow2(beta))); } return 0; } }; /// @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(ConstPtrCast(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-2017 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.h0000644000000000000000000002036413200122377013406 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "Math.h" #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief Dummy class for tag dispatch of conversion constructors struct Conversion {}; /// @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; /// @brief Default ctor. Does nothing. /// @details This is required because declaring a copy (or other) constructor /// prevents the compiler from synthesizing a default constructor. Tuple() {} /// Copy constructor. Used when the class signature matches exactly. Tuple(Tuple const& src) { for (int i = 0; i < SIZE; ++i) { mm[i] = src.mm[i]; } } /// @brief Assignment operator /// @details This is required because declaring a copy (or other) constructor /// prevents the compiler from synthesizing a default assignment operator. Tuple& operator=(Tuple const& src) { if (&src != this) { for (int i = 0; i < SIZE; ++i) { mm[i] = src.mm[i]; } } return *this; } /// @brief Conversion constructor. /// @details 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) { enum { COPY_END = (SIZE < src_size ? SIZE : src_size) }; for (int i = 0; i < COPY_END; ++i) { mm[i] = src[i]; } for (int i = COPY_END; 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 << PrintCast(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); } /// True if a Nan is present in this tuple bool isNan() const { for (int i = 0; i < SIZE; ++i) { if (std::isnan(mm[i])) return true; } return false; } /// True if an Inf is present in this tuple bool isInfinite() const { for (int i = 0; i < SIZE; ++i) { if (std::isinf(mm[i])) return true; } return false; } /// True if no Nan or Inf values are present bool isFinite() const { for (int i = 0; i < SIZE; ++i) { if (!std::isfinite(mm[i])) return false; } return true; } /// True if all elements are exactly zero bool isZero() const { for (int i = 0; i < SIZE; ++i) { if (!isZero(mm[i])) return false; } return true; } 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 (int 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 (int 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 the absolute value of the given Tuple. template Tuple Abs(const Tuple& t) { Tuple result; for (int i = 0; i < SIZE; ++i) result[i] = math::Abs(t[i]); return result; } /// Return @c true if a Nan is present in the tuple. template inline bool isNan(const Tuple& t) { return t.isNan(); } /// Return @c true if an Inf is present in the tuple. template inline bool isInfinite(const Tuple& t) { return t.isInfinite(); } /// Return @c true if no Nan or Inf values are present. template inline bool isFinite(const Tuple& t) { return t.isFinite(); } /// Return @c true if all elements are exactly equal to zero. template inline bool isZero(const Tuple& t) { return t.isZero(); } //////////////////////////////////////// /// 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-2017 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/DDA.h0000644000000000000000000003412613200122377012706 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 DDA.h /// /// @author Ken Museth /// /// @brief Digital Differential Analyzers specialized for VDB. #ifndef OPENVDB_MATH_DDA_HAS_BEEN_INCLUDED #define OPENVDB_MATH_DDA_HAS_BEEN_INCLUDED #include "Coord.h" #include "Math.h" #include "Vec3.h" #include // for std::ostream #include // for std::numeric_limits::max() namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace math { /// @brief A Digital Differential Analyzer specialized for OpenVDB grids /// @note Conceptually similar to Bresenham's line algorithm applied /// to a 3D Ray intersecting OpenVDB nodes or voxels. Log2Dim = 0 /// corresponds to a voxel and Log2Dim a tree node of size 2^Log2Dim. /// /// @note The Ray template class is expected to have the following /// methods: test(time), t0(), t1(), invDir(), and operator()(time). /// See the example Ray class above for their definition. template class DDA { public: typedef typename RayT::RealType RealType; typedef RealType RealT; typedef typename RayT::Vec3Type Vec3Type; typedef Vec3Type Vec3T; /// @brief uninitialized constructor DDA() {} DDA(const RayT& ray) { this->init(ray); } DDA(const RayT& ray, RealT startTime) { this->init(ray, startTime); } DDA(const RayT& ray, RealT startTime, RealT maxTime) { this->init(ray, startTime, maxTime); } inline void init(const RayT& ray, RealT startTime, RealT maxTime) { assert(startTime <= maxTime); static const int DIM = 1 << Log2Dim; mT0 = startTime; mT1 = maxTime; const Vec3T &pos = ray(mT0), &dir = ray.dir(), &inv = ray.invDir(); mVoxel = Coord::floor(pos) & (~(DIM-1)); for (int axis = 0; axis < 3; ++axis) { if (math::isZero(dir[axis])) {//handles dir = +/- 0 mStep[axis] = 0;//dummy value mNext[axis] = std::numeric_limits::max();//i.e. disabled! mDelta[axis] = std::numeric_limits::max();//dummy value } else if (inv[axis] > 0) { mStep[axis] = DIM; mNext[axis] = mT0 + (mVoxel[axis] + DIM - pos[axis]) * inv[axis]; mDelta[axis] = mStep[axis] * inv[axis]; } else { mStep[axis] = -DIM; mNext[axis] = mT0 + (mVoxel[axis] - pos[axis]) * inv[axis]; mDelta[axis] = mStep[axis] * inv[axis]; } } } inline void init(const RayT& ray) { this->init(ray, ray.t0(), ray.t1()); } inline void init(const RayT& ray, RealT startTime) { this->init(ray, startTime, ray.t1()); } /// @brief Increment the voxel index to next intersected voxel or node /// and returns true if the step in time does not exceed maxTime. inline bool step() { const int stepAxis = static_cast(math::MinIndex(mNext)); mT0 = mNext[stepAxis]; mNext[stepAxis] += mDelta[stepAxis]; mVoxel[stepAxis] += mStep[stepAxis]; return mT0 <= mT1; } /// @brief Return the index coordinates of the next node or voxel /// intersected by the ray. If Log2Dim = 0 the return value is the /// actual signed coordinate of the voxel, else it is the origin /// of the corresponding VDB tree node or tile. /// @note Incurs no computational overhead. inline const Coord& voxel() const { return mVoxel; } /// @brief Return the time (parameterized along the Ray) of the /// first hit of a tree node of size 2^Log2Dim. /// @details This value is initialized to startTime or ray.t0() /// depending on the constructor used. /// @note Incurs no computational overhead. inline RealType time() const { return mT0; } /// @brief Return the maximum time (parameterized along the Ray). inline RealType maxTime() const { return mT1; } /// @brief Return the time (parameterized along the Ray) of the /// second (i.e. next) hit of a tree node of size 2^Log2Dim. /// @note Incurs a (small) computational overhead. inline RealType next() const { return math::Min(mT1, mNext[0], mNext[1], mNext[2]); } /// @brief Print information about this DDA for debugging. /// @param os a stream to which to write textual information. void print(std::ostream& os = std::cout) const { os << "Dim=" << (1< #include #include "Math.h" #include "Mat3.h" #include "Vec3.h" #include "Vec4.h" #include // for std::copy(), std::swap() #include #include #include 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. using value_type = T; using ValueType = T; using MyBase = Mat<4, T>; /// 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) { for (int 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] = T(a); MyBase::mm[ 1] = T(b); MyBase::mm[ 2] = T(c); MyBase::mm[ 3] = T(d); MyBase::mm[ 4] = T(e); MyBase::mm[ 5] = T(f); MyBase::mm[ 6] = T(g); MyBase::mm[ 7] = T(h); MyBase::mm[ 8] = T(i); MyBase::mm[ 9] = T(j); MyBase::mm[10] = T(k); MyBase::mm[11] = T(l); MyBase::mm[12] = T(m); MyBase::mm[13] = T(n); MyBase::mm[14] = T(o); MyBase::mm[15] = T(p); } /// Construct matrix from rows or columns vectors (defaults to rows /// for historical reasons) template Mat4(const Vec4 &v1, const Vec4 &v2, const Vec4 &v3, const Vec4 &v4, bool rows = true) { if (rows) { this->setRows(v1, v2, v3, v4); } else { this->setColumns(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() { static const Mat4 sIdentity = Mat4( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ); return sIdentity; } /// Predefined constant for zero matrix static const Mat4& zero() { static const Mat4 sZero = Mat4( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); 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 rows of this matrix to the vectors v1, v2, v3, v4 void setRows(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 the columns of this matrix to the vectors v1, v2, v3, v4 void setColumns(const Vec4 &v1, const Vec4 &v2, const Vec4 &v3, const Vec4 &v4) { MyBase::mm[ 0] = v1[0]; MyBase::mm[ 1] = v2[0]; MyBase::mm[ 2] = v3[0]; MyBase::mm[ 3] = v4[0]; MyBase::mm[ 4] = v1[1]; MyBase::mm[ 5] = v2[1]; MyBase::mm[ 6] = v3[1]; MyBase::mm[ 7] = v4[1]; MyBase::mm[ 8] = v1[2]; MyBase::mm[ 9] = v2[2]; MyBase::mm[10] = v3[2]; MyBase::mm[11] = v4[2]; MyBase::mm[12] = v1[3]; MyBase::mm[13] = v2[3]; MyBase::mm[14] = v3[3]; 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; } /// Return @c true if this matrix is equivalent to @a m within a tolerance of @a eps. 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 /// Multiply each element of this matrix by @a scalar. 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; } /// Add each element of the given matrix to the corresponding element of this matrix. 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; } /// Subtract each element of the given matrix from the corresponding element of this matrix. 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; } /// Multiply this matrix by the given matrix. 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; } /// 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 = static_cast(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); } }; // class Mat4 /// @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 Multiply each element of the given matrix by @a scalar and return the result. template Mat4::type> operator*(S scalar, const Mat4 &m) { return m*scalar; } /// @relates Mat4 /// @brief Multiply each element of the given matrix by @a scalar and return the result. template Mat4::type> operator*(const Mat4 &m, S scalar) { Mat4::type> result(m); result *= scalar; return result; } /// @relates Mat4 /// @brief Multiply @a _m by @a _v and return the resulting vector. 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 Multiply @a _v by @a _m and return the resulting vector. 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 Multiply @a _m by @a _v and return the resulting vector. 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 Multiply @a _v by @a _m and return the resulting vector. 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 Add corresponding elements of @a m0 and @a m1 and return the result. template Mat4::type> operator+(const Mat4 &m0, const Mat4 &m1) { Mat4::type> result(m0); result += m1; return result; } /// @relates Mat4 /// @brief Subtract corresponding elements of @a m0 and @a m1 and return the result. template Mat4::type> operator-(const Mat4 &m0, const Mat4 &m1) { Mat4::type> result(m0); result -= m1; return result; } /// @relates Mat4 /// @brief Multiply @a m0 by @a m1 and return the resulting matrix. 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)); } using Mat4s = Mat4; using Mat4d = Mat4; using Mat4f = Mat4d; } // 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-2017 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.h0000644000000000000000000002532413200122377014271 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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. /// @see Transform::worldToIndex(const BBoxd&) const OPENVDB_API void calculateBounds(const Transform& t, const Vec3d& minWS, const Vec3d& maxWS, Vec3d& minIS, Vec3d& maxIS); /// @todo Calculate an axis-aligned bounding box in index space from a /// bounding sphere in world space. //void calculateBounds(const Transform& t, const Vec3d& center, const Real radius, // Vec3d& minIS, Vec3d& maxIS); //////////////////////////////////////// /// @class Transform class OPENVDB_API Transform { public: using Ptr = SharedPtr; using ConstPtr = SharedPtr; 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));} //@} //@{ /// @brief Apply this transformation to the given index-space bounding box. /// @return an axis-aligned world-space bounding box BBoxd indexToWorld(const CoordBBox&) const; BBoxd indexToWorld(const BBoxd&) const; //@} //@{ /// @brief Apply the inverse of this transformation to the given world-space bounding box. /// @return an axis-aligned index-space bounding box BBoxd worldToIndex(const BBoxd&) const; CoordBBox worldToIndexCellCentered(const BBoxd&) const; CoordBBox worldToIndexNodeCentered(const BBoxd&) const; //@} //@{ /// 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 StaticPtrCast(mMap); } return typename MapType::Ptr(); } template inline typename MapType::ConstPtr Transform::map() const { return ConstPtrCast( 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-2017 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.h0000644000000000000000000001646613200122377013160 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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" #define PRAGMA(x) _Pragma(#x) /// 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 /// Macro for determining if there are sufficient C++0x/C++11 features #ifdef __INTEL_COMPILER #ifdef __INTEL_CXX11_MODE__ #define OPENVDB_HAS_CXX11 1 #endif #elif defined(__clang__) #ifndef _LIBCPP_VERSION #include #endif #ifdef _LIBCPP_VERSION #define OPENVDB_HAS_CXX11 1 #endif #elif defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus > 199711L) #define OPENVDB_HAS_CXX11 1 #elif defined(_MSC_VER) #if (_MSC_VER >= 1700) #define OPENVDB_HAS_CXX11 1 #endif #endif #if defined(__GNUC__) && !OPENVDB_CHECK_GCC(4, 4) // ICC uses GCC's standard library headers, so even if the ICC version // is recent enough for C++11, the GCC version might not be. #undef OPENVDB_HAS_CXX11 #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)") #elif defined(__clang__) #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN \ PRAGMA(clang diagnostic push) \ PRAGMA(clang diagnostic ignored "-Wunreachable-code") #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_END \ PRAGMA(clang diagnostic pop) #else #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN #define OPENVDB_NO_UNREACHABLE_CODE_WARNING_END #endif #ifdef _MSC_VER /// 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 #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-2017 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/0000755000000000000000000000000013200122400012130 5ustar rootrootopenvdb/tree/ValueAccessor.h0000644000000000000000000031251213200122377015061 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // The IsSafe template parameter is explained in the warning below. 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. /// /// @warning If IsSafe = false then the ValueAccessor will not register itself /// with the tree from which it is constructed. While in some rare cases this can /// lead to better performance (since it avoids the small overhead of insertion /// on creation and deletion on destruction) it is also unsafe if the tree is /// modified. So unless you're an expert it is highly recommended to set /// IsSafe = true, which is the default in all derived ValueAccessors defined /// below. However if you know that the tree is no being modifed for the lifespan /// of the ValueAccessor AND the work performed per ValueAccessor is small relative /// to overhead of registering it you should consider setting IsSafe = false. If /// this turns out to improve performance you should really rewrite your code so as /// to better amortize the construction of the ValueAccessor, i.e. reuse it as much /// as possible! template class ValueAccessorBase { public: static const bool IsConstTree = boost::is_const::value; /// @brief Return true if this accessor is safe, i.e. registered /// by the tree from which it is constructed. Un-registered /// accessors can in rare cases be faster because it avoids the /// (small) overhead of registration, but they are unsafe if the /// tree is modified. So unless you're an expert it is highly /// recommended to set IsSafe = true (which is the default). static bool isSafe() { return IsSafe; } ValueAccessorBase(TreeType& tree): mTree(&tree) { if (IsSafe) tree.attachAccessor(*this); } virtual ~ValueAccessorBase() { if (IsSafe && 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 (IsSafe && mTree) mTree->attachAccessor(*this); } ValueAccessorBase& operator=(const ValueAccessorBase& other) { if (&other != this) { if (IsSafe && mTree) mTree->releaseAccessor(*this); mTree = other.mTree; if (IsSafe && mTree) mTree->attachAccessor(*this); } return *this; } virtual void clear() = 0; protected: // Allow trees to deregister themselves. template friend class Tree; virtual void release() { mTree = nullptr; } 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 IsSafe if IsSafe = false then the ValueAccessor will /// not register itself with the tree from which /// it is consturcted (see warning). /// @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) /// /// @warning If IsSafe = false then the ValueAccessor will not register itself /// with the tree from which it is constructed. While in some rare cases this can /// lead to better performance (since it avoids the small overhead of insertion /// on creation and deletion on destruction) it is also unsafe if the tree is /// modified. So unless you're an expert it is highly recommended to set /// IsSafe = true, which is the default. However if you know that the tree is no /// being modifed for the lifespan of the ValueAccessor AND the work performed /// per ValueAccessor is small relative to overhead of registering it you should /// consider setting IsSafe = false. If this improves performance you should /// really rewrite your code so as to better amortize the construction of the /// ValueAccessor, i.e. reuse it as much as possible! /// /// @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, IsSafe> { 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 = nullptr; 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 = nullptr; 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 @c nullptr 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 @c nullptr 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 class ValueAccessor : public ValueAccessor0 { public: 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 class ValueAccessor : public ValueAccessor1 { public: 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 class ValueAccessor : public ValueAccessor2 { public: 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 class ValueAccessor: public ValueAccessor3 { public: ValueAccessor(TreeType& tree): ValueAccessor3(tree) {} ValueAccessor(const ValueAccessor&) = default; ValueAccessor& operator=(const ValueAccessor&) = default; virtual ~ValueAccessor() = default; }; //////////////////////////////////////// /// @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 class ValueAccessorRW: public ValueAccessor { public: 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(nullptr), 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 != nullptr) ? 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 = nullptr; } /// 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 = nullptr; 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(nullptr) {} 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 = nullptr; } void clear() { mRoot = nullptr; } 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, IsSafe> { 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 nullptr; } /// 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, IsSafe> { 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(nullptr) { } /// 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 = nullptr; 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 = nullptr; 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 @c nullptr. 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 nullptr; 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 @c nullptr. 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 nullptr; 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 = nullptr; } 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() : nullptr); } template void getNode(const OtherNodeType*& node) { node = nullptr; } void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = nullptr; } 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, IsSafe> { 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(nullptr), mKey1(Coord::max()), mNode1(nullptr) {} /// 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 = nullptr; 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 = nullptr; 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 @c nullptr. 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 nullptr; 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 @c nullptr. 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 @c nullptr. 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 nullptr; 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 @c nullptr. 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 @c nullptr. 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 nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// Remove all the cached nodes and invalidate the corresponding hash-keys. virtual void clear() { mKey0 = Coord::max(); mNode0 = nullptr; mKey1 = Coord::max(); mNode1 = nullptr; } 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() : nullptr); } template void getNode(const OtherNodeType*& node) { node = nullptr; } void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = nullptr; } void eraseNode(const NodeT1*) { mKey1 = Coord::max(); mNode1 = nullptr; } 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, IsSafe> { 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(nullptr), mKey1(Coord::max()), mNode1(nullptr), mKey2(Coord::max()), mNode2(nullptr) {} /// 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 = nullptr; 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 = nullptr; 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 @c nullptr. 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 nullptr; 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 @c nullptr. 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 @c nullptr. 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 nullptr; 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 @c nullptr. 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 = nullptr; mKey1 = Coord::max(); mNode1 = nullptr; mKey2 = Coord::max(); mNode2 = nullptr; } 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() : nullptr); } template void getNode(const OtherNodeType*& node) { node = nullptr; } void eraseNode(const NodeT0*) { mKey0 = Coord::max(); mNode0 = nullptr; } void eraseNode(const NodeT1*) { mKey1 = Coord::max(); mNode1 = nullptr; } void eraseNode(const NodeT2*) { mKey2 = Coord::max(); mNode2 = nullptr; } 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-2017 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/NodeManager.h0000644000000000000000000010415713200122377014506 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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/NodeManager.h /// /// @author Ken Museth /// /// @brief NodeManager produces linear arrays of all tree nodes /// allowing for efficient threading and bottom-up processing. /// /// @note A NodeManager can be constructed from a Tree or LeafManager. /// The latter is slightly more efficient since the cached leaf nodes will be reused. #ifndef OPENVDB_TREE_NODEMANAGER_HAS_BEEN_INCLUDED #define OPENVDB_TREE_NODEMANAGER_HAS_BEEN_INCLUDED #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { // Produce linear arrays of all tree nodes, to facilitate efficient threading // and bottom-up processing. template class NodeManager; //////////////////////////////////////// /// @brief This class caches tree nodes of a specific type in a linear array. /// /// @note It is for internal use and should rarely be used directly. template class NodeList { public: using value_type = NodeT*; using ListT = std::deque; NodeList() {} void push_back(NodeT* node) { mList.push_back(node); } NodeT& operator()(size_t n) const { assert(nsize();} class Iterator { public: Iterator(const NodeRange& range, size_t pos): mRange(range), mPos(pos) { assert(this->isValid()); } Iterator(const Iterator&) = default; Iterator& operator=(const Iterator&) = default; /// Advance to the next node. Iterator& operator++() { ++mPos; return *this; } /// Return a reference to the node to which this iterator is pointing. NodeT& operator*() const { return mRange.mNodeList(mPos); } /// Return a pointer to the node to which this iterator is pointing. NodeT* operator->() const { return &(this->operator*()); } /// Return the index into the list of the current 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) || (&mRange != &other.mRange); } bool operator==(const Iterator& other) const { return !(*this != other); } const NodeRange& nodeRange() const { return mRange; } private: const NodeRange& mRange; size_t mPos; };// NodeList::NodeRange::Iterator Iterator begin() const {return Iterator(*this, mBegin);} Iterator end() const {return Iterator(*this, mEnd);} private: size_t mEnd, mBegin, mGrainSize; const NodeList& mNodeList; static size_t doSplit(NodeRange& r) { assert(r.is_divisible()); size_t middle = r.mBegin + (r.mEnd - r.mBegin) / 2u; r.mEnd = middle; return middle; } };// NodeList::NodeRange /// Return a TBB-compatible NodeRange. NodeRange nodeRange(size_t grainsize = 1) const { return NodeRange(0, this->nodeCount(), *this, grainsize); } template void foreach(const NodeOp& op, bool threaded = true, size_t grainSize=1) { NodeTransformer transform(op); transform.run(this->nodeRange(grainSize), threaded); } template void reduce(NodeOp& op, bool threaded = true, size_t grainSize=1) { NodeReducer transform(op); transform.run(this->nodeRange(grainSize), threaded); } private: // Private struct of NodeList that performs parallel_for template struct NodeTransformer { NodeTransformer(const NodeOp& nodeOp) : mNodeOp(nodeOp) { } void run(const NodeRange& range, bool threaded = true) { threaded ? tbb::parallel_for(range, *this) : (*this)(range); } void operator()(const NodeRange& range) const { for (typename NodeRange::Iterator it = range.begin(); it; ++it) mNodeOp(*it); } const NodeOp mNodeOp; };// NodeList::NodeTransformer // Private struct of NodeList that performs parallel_reduce template struct NodeReducer { NodeReducer(NodeOp& nodeOp) : mNodeOp(&nodeOp), mOwnsOp(false) { } NodeReducer(const NodeReducer& other, tbb::split) : mNodeOp(new NodeOp(*(other.mNodeOp), tbb::split())), mOwnsOp(true) { } ~NodeReducer() { if (mOwnsOp) delete mNodeOp; } void run(const NodeRange& range, bool threaded = true) { threaded ? tbb::parallel_reduce(range, *this) : (*this)(range); } void operator()(const NodeRange& range) { NodeOp &op = *mNodeOp; for (typename NodeRange::Iterator it = range.begin(); it; ++it) op(*it); } void join(const NodeReducer& other) { mNodeOp->join(*(other.mNodeOp)); } NodeOp *mNodeOp; const bool mOwnsOp; };// NodeList::NodeReducer protected: ListT mList; };// NodeList ///////////////////////////////////////////// /// @brief This class is a link in a chain that each caches tree nodes /// of a specific type in a linear array. /// /// @note It is for internal use and should rarely be used directly. template class NodeManagerLink { public: NodeManagerLink() {} virtual ~NodeManagerLink() {} void clear() { mList.clear(); mNext.clear(); } template void init(ParentT& parent, TreeOrLeafManagerT& tree) { parent.getNodes(mList); for (size_t i=0, n=mList.nodeCount(); i void rebuild(ParentT& parent) { mList.clear(); parent.getNodes(mList); for (size_t i=0, n=mList.nodeCount(); i void foreachBottomUp(const NodeOp& op, bool threaded, size_t grainSize) { mNext.foreachBottomUp(op, threaded, grainSize); mList.foreach(op, threaded, grainSize); } template void foreachTopDown(const NodeOp& op, bool threaded, size_t grainSize) { mList.foreach(op, threaded, grainSize); mNext.foreachTopDown(op, threaded, grainSize); } template void reduceBottomUp(NodeOp& op, bool threaded, size_t grainSize) { mNext.reduceBottomUp(op, threaded, grainSize); mList.reduce(op, threaded, grainSize); } template void reduceTopDown(NodeOp& op, bool threaded, size_t grainSize) { mList.reduce(op, threaded, grainSize); mNext.reduceTopDown(op, threaded, grainSize); } protected: NodeList mList; NodeManagerLink mNext; };// NodeManagerLink class //////////////////////////////////////// /// @private /// @brief Specialization that terminates the chain of cached tree nodes /// @note It is for internal use and should rarely be used directly. template class NodeManagerLink { public: NodeManagerLink() {} virtual ~NodeManagerLink() {} /// @brief Clear all the cached tree nodes void clear() { mList.clear(); } template void rebuild(ParentT& parent) { mList.clear(); parent.getNodes(mList); } Index64 nodeCount() const { return mList.nodeCount(); } Index64 nodeCount(Index) const { return mList.nodeCount(); } template void foreachBottomUp(const NodeOp& op, bool threaded, size_t grainSize) { mList.foreach(op, threaded, grainSize); } template void foreachTopDown(const NodeOp& op, bool threaded, size_t grainSize) { mList.foreach(op, threaded, grainSize); } template void reduceBottomUp(NodeOp& op, bool threaded, size_t grainSize) { mList.reduce(op, threaded, grainSize); } template void reduceTopDown(NodeOp& op, bool threaded, size_t grainSize) { mList.reduce(op, threaded, grainSize); } template void init(ParentT& parent, TreeOrLeafManagerT& tree) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (TreeOrLeafManagerT::DEPTH == 2 && NodeT::LEVEL == 0) { tree.getNodes(mList); } else { parent.getNodes(mList); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } protected: NodeList mList; };// NodeManagerLink class //////////////////////////////////////// /// @brief To facilitate threading over the nodes of a tree, cache /// node pointers in linear arrays, one for each level of the tree. /// /// @details This implementation works with trees of any depth, but /// optimized specializations are provided for the most typical tree depths. template class NodeManager { public: static const Index LEVELS = _LEVELS; static_assert(LEVELS > 0, "expected instantiation of template specialization"); // see specialization below using RootNodeType = typename TreeOrLeafManagerT::RootNodeType; static_assert(RootNodeType::LEVEL >= LEVELS, "number of levels exceeds root node height"); NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { mChain.init(mRoot, tree); } virtual ~NodeManager() {} /// @brief Clear all the cached tree nodes void clear() { mChain.clear(); } /// @brief Clear and recache all the tree nodes from the /// tree. This is required if tree nodes have been added or removed. void rebuild() { mChain.rebuild(mRoot); } /// @brief Return a reference to the root node. const RootNodeType& root() const { return mRoot; } /// @brief Return the total number of cached nodes (excluding the root node) Index64 nodeCount() const { return mChain.nodeCount(); } /// @brief Return the number of cached nodes at level @a i, where /// 0 corresponds to the lowest level. Index64 nodeCount(Index i) const { return mChain.nodeCount(i); } //@{ /// @brief Threaded method that applies a user-supplied functor /// to all the nodes in the tree. /// /// @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 all the inactive values of a tree. Note /// // this implementation also illustrates how different /// // computation can be applied to the different node types. /// template /// struct OffsetOp /// { /// using ValueT = typename TreeT::ValueType; /// using RootT = typename TreeT::RootNodeType; /// using LeafT = typename TreeT::LeafNodeType; /// OffsetOp(const ValueT& v) : mOffset(v) {} /// /// // Processes the root node. Required by the NodeManager /// void operator()(RootT& root) const /// { /// for (typename RootT::ValueOffIter i = root.beginValueOff(); i; ++i) *i += mOffset; /// } /// // Processes the leaf nodes. Required by the NodeManager /// void operator()(LeafT& leaf) const /// { /// for (typename LeafT::ValueOffIter i = leaf.beginValueOff(); i; ++i) *i += mOffset; /// } /// // Processes the internal nodes. Required by the NodeManager /// template /// void operator()(NodeT& node) const /// { /// for (typename NodeT::ValueOffIter i = node.beginValueOff(); i; ++i) *i += mOffset; /// } /// private: /// const ValueT mOffset; /// }; /// /// // usage: /// OffsetOp op(3.0f); /// tree::NodeManager nodes(tree); /// nodes.foreachBottomUp(op); /// /// // or if a LeafManager already exists /// using T = tree::LeafManager; /// OffsetOp op(3.0f); /// tree::NodeManager nodes(leafManager); /// nodes.foreachBottomUp(op); /// /// @endcode template void foreachBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mChain.foreachBottomUp(op, threaded, grainSize); op(mRoot); } template void foreachTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mChain.foreachTopDown(op, threaded, grainSize); } //@} //@{ /// @brief Threaded method that processes nodes with a user supplied functor /// /// @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 count nodes in a tree /// template /// struct NodeCountOp /// { /// NodeCountOp() : nodeCount(TreeType::DEPTH, 0), totalCount(0) /// { /// } /// NodeCountOp(const NodeCountOp& other, tbb::split) : /// nodeCount(TreeType::DEPTH, 0), totalCount(0) /// { /// } /// void join(const NodeCountOp& other) /// { /// for (size_t i = 0; i < nodeCount.size(); ++i) { /// nodeCount[i] += other.nodeCount[i]; /// } /// totalCount += other.totalCount; /// } /// // do nothing for the root node /// void operator()(const typename TreeT::RootNodeType& node) /// { /// } /// // count the internal and leaf nodes /// template /// void operator()(const NodeT& node) /// { /// ++(nodeCount[NodeT::LEVEL]); /// ++totalCount; /// } /// std::vector nodeCount; /// openvdb::Index64 totalCount; /// }; /// /// // usage: /// NodeCountOp op; /// tree::NodeManager nodes(tree); /// nodes.reduceBottomUp(op); /// /// // or if a LeafManager already exists /// NodeCountOp op; /// using T = tree::LeafManager; /// T leafManager(tree); /// tree::NodeManager nodes(leafManager); /// nodes.reduceBottomUp(op); /// /// @endcode template void reduceBottomUp(NodeOp& op, bool threaded = true, size_t grainSize=1) { mChain.reduceBottomUp(op, threaded, grainSize); op(mRoot); } template void reduceTopDown(NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mChain.reduceTopDown(op, threaded, grainSize); } //@} protected: RootNodeType& mRoot; NodeManagerLink mChain; private: NodeManager(const NodeManager&) {}//disallow copy-construction };// NodeManager class //////////////////////////////////////////// /// @private /// Template specialization of the NodeManager with no caching of nodes template class NodeManager { public: using RootNodeType = typename TreeOrLeafManagerT::RootNodeType; static const Index LEVELS = 0; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) {} virtual ~NodeManager() {} /// @brief Clear all the cached tree nodes void clear() {} /// @brief Clear and recache all the tree nodes from the /// tree. This is required if tree nodes have been added or removed. void rebuild() {} /// @brief Return a reference to the root node. const RootNodeType& root() const { return mRoot; } /// @brief Return the total number of cached nodes (excluding the root node) Index64 nodeCount() const { return 0; } Index64 nodeCount(Index) const { return 0; } template void foreachBottomUp(const NodeOp& op, bool, size_t) { op(mRoot); } template void foreachTopDown(const NodeOp& op, bool, size_t) { op(mRoot); } template void reduceBottomUp(NodeOp& op, bool, size_t) { op(mRoot); } template void reduceTopDown(NodeOp& op, bool, size_t) { op(mRoot); } protected: RootNodeType& mRoot; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<0> //////////////////////////////////////////// /// @private /// Template specialization of the NodeManager with one level of nodes template class NodeManager { public: using RootNodeType = typename TreeOrLeafManagerT::RootNodeType; static_assert(RootNodeType::LEVEL > 0, "expected instantiation of template specialization"); static const Index LEVELS = 1; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (TreeOrLeafManagerT::DEPTH == 2 && NodeT0::LEVEL == 0) { tree.getNodes(mList0); } else { mRoot.getNodes(mList0); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } virtual ~NodeManager() {} /// @brief Clear all the cached tree nodes void clear() { mList0.clear(); } /// @brief Clear and recache all the tree nodes from the /// tree. This is required if tree nodes have been added or removed. void rebuild() { mList0.clear(); mRoot.getNodes(mList0); } /// @brief Return a reference to the root node. const RootNodeType& root() const { return mRoot; } /// @brief Return the total number of cached nodes (excluding the root node) Index64 nodeCount() const { return mList0.nodeCount(); } /// @brief Return the number of cached nodes at level @a i, where /// 0 corresponds to the lowest level. Index64 nodeCount(Index i) const { return i==0 ? mList0.nodeCount() : 0; } template void foreachBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.foreach(op, threaded, grainSize); op(mRoot); } template void foreachTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList0.foreach(op, threaded, grainSize); } template void reduceBottomUp(NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.reduce(op, threaded, grainSize); op(mRoot); } template void reduceTopDown(NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList0.reduce(op, threaded, grainSize); } protected: using NodeT1 = RootNodeType; using NodeT0 = typename NodeT1::ChildNodeType; using ListT0 = NodeList; NodeT1& mRoot; ListT0 mList0; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<1> //////////////////////////////////////////// /// @private /// Template specialization of the NodeManager with two levels of nodes template class NodeManager { public: using RootNodeType = typename TreeOrLeafManagerT::RootNodeType; static_assert(RootNodeType::LEVEL > 1, "expected instantiation of template specialization"); static const Index LEVELS = 2; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { mRoot.getNodes(mList1); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (TreeOrLeafManagerT::DEPTH == 2 && NodeT0::LEVEL == 0) { tree.getNodes(mList0); } else { for (size_t i=0, n=mList1.nodeCount(); iclear(); mRoot.getNodes(mList1); for (size_t i=0, n=mList1.nodeCount(); i void foreachBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); op(mRoot); } template void foreachTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList1.foreach(op, threaded, grainSize); mList0.foreach(op, threaded, grainSize); } template void reduceBottomUp(NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.reduce(op, threaded, grainSize); mList1.reduce(op, threaded, grainSize); op(mRoot); } template void reduceTopDown(NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList1.reduce(op, threaded, grainSize); mList0.reduce(op, threaded, grainSize); } protected: using NodeT2 = RootNodeType; using NodeT1 = typename NodeT2::ChildNodeType; // upper level using NodeT0 = typename NodeT1::ChildNodeType; // lower level using ListT1 = NodeList; // upper level using ListT0 = NodeList; // lower level NodeT2& mRoot; ListT1 mList1; ListT0 mList0; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<2> //////////////////////////////////////////// /// @private /// Template specialization of the NodeManager with three levels of nodes template class NodeManager { public: using RootNodeType = typename TreeOrLeafManagerT::RootNodeType; static_assert(RootNodeType::LEVEL > 2, "expected instantiation of template specialization"); static const Index LEVELS = 3; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { mRoot.getNodes(mList2); for (size_t i=0, n=mList2.nodeCount(); iclear(); mRoot.getNodes(mList2); for (size_t i=0, n=mList2.nodeCount(); i void foreachBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); mList2.foreach(op, threaded, grainSize); op(mRoot); } template void foreachTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList2.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); mList0.foreach(op, threaded, grainSize); } template void reduceBottomUp(NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.reduce(op, threaded, grainSize); mList1.reduce(op, threaded, grainSize); mList2.reduce(op, threaded, grainSize); op(mRoot); } template void reduceTopDown(NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList2.reduce(op, threaded, grainSize); mList1.reduce(op, threaded, grainSize); mList0.reduce(op, threaded, grainSize); } protected: using NodeT3 = RootNodeType; using NodeT2 = typename NodeT3::ChildNodeType; // upper level using NodeT1 = typename NodeT2::ChildNodeType; // mid level using NodeT0 = typename NodeT1::ChildNodeType; // lower level using ListT2 = NodeList; // upper level of internal nodes using ListT1 = NodeList; // lower level of internal nodes using ListT0 = NodeList; // lower level of internal nodes or leafs NodeT3& mRoot; ListT2 mList2; ListT1 mList1; ListT0 mList0; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<3> //////////////////////////////////////////// /// @private /// Template specialization of the NodeManager with four levels of nodes template class NodeManager { public: using RootNodeType = typename TreeOrLeafManagerT::RootNodeType; static_assert(RootNodeType::LEVEL > 3, "expected instantiation of template specialization"); static const Index LEVELS = 4; NodeManager(TreeOrLeafManagerT& tree) : mRoot(tree.root()) { mRoot.getNodes(mList3); for (size_t i=0, n=mList3.nodeCount(); iclear(); mRoot.getNodes(mList3); for (size_t i=0, n=mList3.nodeCount(); i void foreachBottomUp(const NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); mList2.foreach(op, threaded, grainSize); mList3.foreach(op, threaded, grainSize); op(mRoot); } template void foreachTopDown(const NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList3.foreach(op, threaded, grainSize); mList2.foreach(op, threaded, grainSize); mList1.foreach(op, threaded, grainSize); mList0.foreach(op, threaded, grainSize); } template void reduceBottomUp(NodeOp& op, bool threaded = true, size_t grainSize=1) { mList0.reduce(op, threaded, grainSize); mList1.reduce(op, threaded, grainSize); mList2.reduce(op, threaded, grainSize); mList3.reduce(op, threaded, grainSize); op(mRoot); } template void reduceTopDown(NodeOp& op, bool threaded = true, size_t grainSize=1) { op(mRoot); mList3.reduce(op, threaded, grainSize); mList2.reduce(op, threaded, grainSize); mList1.reduce(op, threaded, grainSize); mList0.reduce(op, threaded, grainSize); } protected: using NodeT4 = RootNodeType; using NodeT3 = typename NodeT4::ChildNodeType; // upper level using NodeT2 = typename NodeT3::ChildNodeType; // upper mid level using NodeT1 = typename NodeT2::ChildNodeType; // lower mid level using NodeT0 = typename NodeT1::ChildNodeType; // lower level using ListT3 = NodeList; // upper level of internal nodes using ListT2 = NodeList; // upper mid level of internal nodes using ListT1 = NodeList; // lower mid level of internal nodes using ListT0 = NodeList; // lower level of internal nodes or leafs NodeT4& mRoot; ListT3 mList3; ListT2 mList2; ListT1 mList1; ListT0 mList0; private: NodeManager(const NodeManager&) {} // disallow copy-construction }; // NodeManager<4> } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_NODEMANAGER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000040736713200122377014070 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for truncateRealToHalf() #include // for isZero(), isExactlyEqual(), etc. #include #include // for backward compatibility only (see readTopology()) #include #include #include //for boost::mpl::vector #include #include #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { // Forward declarations template struct NodeChain; template struct SameRootConfig; template struct RootNodeCopyHelper; template struct RootNodeCombineHelper; template class RootNode { public: using ChildNodeType = ChildType; using LeafNodeType = typename ChildType::LeafNodeType; using ValueType = typename ChildType::ValueType; using BuildType = typename ChildType::BuildType; static const Index LEVEL = 1 + ChildType::LEVEL; // level 0 = leaf /// NodeChainType is a list of this tree's node types, from LeafNodeType to RootNode. using NodeChainType = typename NodeChain::Type; static_assert(boost::mpl::size::value == LEVEL + 1, "wrong number of entries in RootNode node chain"); /// @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 { using Type = RootNode::Type>; }; /// @brief SameConfiguration::value is @c true if and only if /// OtherNodeType is the type of a RootNode whose ChildNodeType has the same /// configuration as this node's ChildNodeType. template struct SameConfiguration { static const bool value = SameRootConfig::value; }; /// 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 a tree of a different ValueType but the same configuration (levels, /// node dimensions and branching factors). Cast the other tree's values to /// this tree's ValueType. /// @throw TypeError if the other tree's configuration doesn't match this tree's /// or if this tree's ValueType is not constructible from the other tree's ValueType. template explicit 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 /// @throw TypeError if the other tree's configuration doesn't match this tree's. 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. /// @throw TypeError if the other tree's configuration doesn't match this tree's. template RootNode(const RootNode& other, const ValueType& background, TopologyCopy); /// @brief Copy a root node of the same type as this node. RootNode& operator=(const RootNode& other); /// @brief Copy a root node of the same tree configuration as this node /// but a different ValueType. /// @throw TypeError if the other tree's configuration doesn't match this tree's. /// @note This node's ValueType must be constructible from the other node's ValueType. /// For example, a root node with values of type float can be assigned to a root node /// with values of type Vec3s, because a Vec3s can be constructed from a float. /// But a Vec3s root node cannot be assigned to a float root node. template RootNode& operator=(const RootNode& other); ~RootNode() { this->clear(); } 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(nullptr) {} NodeStruct(ChildType& c): child(&c) {} NodeStruct(const Tile& t): child(nullptr), tile(t) {} NodeStruct(const NodeStruct&) = default; NodeStruct& operator=(const NodeStruct&) = default; ~NodeStruct() {} ///< @note doesn't delete child bool isChild() const { return child != nullptr; } bool isTile() const { return child == nullptr; } 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 = nullptr; tile = t; } ChildType& steal(const Tile& t) { ChildType* c=child; child=nullptr; tile=t; return *c; } }; using MapType = std::map; using MapIter = typename MapType::iterator; using MapCIter = typename MapType::const_iterator; using CoordSet = std::set; using CoordSetIter = typename CoordSet::iterator; using CoordSetCIter = typename CoordSet::const_iterator; 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: using RootNodeT = _RootNodeT; using 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(nullptr) {} 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: using BaseT = BaseIter; using NodeType = RootNodeT; using ValueType = NodeType; using ChildNodeType = ChildNodeT; using NonConstNodeType = typename std::remove_const::type; using NonConstValueType = typename std::remove_const::type; using NonConstChildNodeType = typename std::remove_const::type; 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: using BaseT = BaseIter; using NodeType = RootNodeT; using ValueType = ValueT; using NonConstNodeType = typename std::remove_const::type; using NonConstValueType = typename std::remove_const::type; 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: using BaseT = BaseIter; using NodeType = RootNodeT; using ValueType = ValueT; using ChildNodeType = ChildNodeT; using NonConstNodeType = typename std::remove_const::type; using NonConstValueType = typename std::remove_const::type; using NonConstChildNodeType = typename std::remove_const::type; 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 nullptr; } bool probeChild(ChildNodeT*& child, NonConstValueType& value) const { child = this->probeChild(value); return child != nullptr; } 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 != nullptr); 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: using ChildOnIter = ChildIter; using ChildOnCIter = ChildIter; using ChildOffIter = ValueIter; using ChildOffCIter = ValueIter; using ChildAllIter = DenseIter; using ChildAllCIter = DenseIter; using ValueOnIter = ValueIter; using ValueOnCIter = ValueIter; using ValueOffIter = ValueIter; using ValueOffCIter = ValueIter; using ValueAllIter = ValueIter; using ValueAllCIter = ValueIter; 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; /// 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 the background values of the /// child nodes is also updated. Else only the background value /// stored in the RootNode itself is changed. /// /// @note Instead of setting @a updateChildNodes to true, consider /// using tools::changeBackground or /// tools::changeLevelSetBackground which are multi-threaded! void setBackground(const ValueType& value, bool updateChildNodes); /// 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(); inline void clear(); /// 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 static_cast(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); /// Return @c true if values of the other node's ValueType can be converted /// to values of this node's ValueType. template static bool hasCompatibleValueType(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; /// Return @c true if this root node, or any of its child nodes, have active tiles. 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 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); void sparseFill(const CoordBBox& bbox, const ValueType& value, bool active = true) { this->fill(bbox, value, active); } //@} /// @brief Set all voxels within a given axis-aligned box to a constant value /// and ensure that those voxels are all represented at the leaf level. /// @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. /// @sa voxelizeActiveTiles() void denseFill(const CoordBBox& bbox, const ValueType& value, bool active = true); /// @brief Densify active tiles, i.e., replace them with leaf-level active voxels. /// /// @param threaded if true, this operation is multi-threaded (over the internal nodes). /// /// @warning This method can explode the tree's memory footprint, especially if it /// contains active tiles at the upper levels (in particular the root level)! /// /// @sa denseFill() void voxelizeActiveTiles(bool threaded = 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); void readBuffers(std::istream&, const CoordBBox&, 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; /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&); /// @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. /// /// @note Consider instead using tools::prune which is multi-threaded! void prune(const ValueType& tolerance = zeroVal()); /// @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 nullptr. /// /// @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 root level, /// deleting the existing branch if necessary. void addTile(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 @c nullptr. 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 @c nullptr. 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 Adds all nodes of a certain type to a container with the following API: /// @code /// struct ArrayT { /// using value_type = ...;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// using value_type = LeafType*; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.getNodes(array); /// @endcode template void getNodes(ArrayT& array); template void getNodes(ArrayT& array) const; //@} //@{ /// @brief Steals all nodes of a certain type from the tree and /// adds them to a container with the following API: /// @code /// struct ArrayT { /// using value_type = ...;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// using value_type = LeafType*; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.stealNodes(array); /// @endcode template void stealNodes(ArrayT& array, const ValueType& value, bool state); template void stealNodes(ArrayT& array) { this->stealNodes(array, mBackground, false); } //@} /// @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 OtherRootNode& 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; template friend struct RootNodeCopyHelper; template friend struct RootNodeCombineHelper; /// Currently no-op, but can be used to define empty and delete keys for mTable void initTable() {} //@{ /// @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); /// @brief Verify that the tree rooted at @a other has the same configuration /// (levels, branching factors and node dimensions) as this tree, but allow /// their ValueTypes to differ. /// @throw TypeError if the other tree's configuration doesn't match this tree's. template static void enforceSameConfiguration(const RootNode& other); /// @brief Verify that @a other has values of a type that can be converted /// to this node's ValueType. /// @details For example, values of type float are compatible with values of type Vec3s, /// because a Vec3s can be constructed from a float. But the reverse is not true. /// @throw TypeError if the other node's ValueType is not convertible into this node's. template static void enforceCompatibleValueTypes(const RootNode& other); template void doCombine2(const RootNode&, const OtherRootNode&, CombineOp&, bool prune); 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 { using SubtreeT = typename NodeChain::Type; using Type = typename boost::mpl::push_back::type; }; /// Specialization to terminate NodeChain template struct NodeChain { using Type = typename boost::mpl::vector::type; }; //////////////////////////////////////// //@{ /// Helper metafunction used to implement RootNode::SameConfiguration /// (which, as an inner class, can't be independently specialized) template struct SameRootConfig { static const bool value = false; }; template struct SameRootConfig > { static const bool value = ChildT1::template SameConfiguration::value; }; //@} //////////////////////////////////////// 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) { using OtherRootT = RootNode; 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) { using OtherRootT = RootNode; 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()))); } } //////////////////////////////////////// // This helper class is a friend of RootNode and is needed so that assignment // with value conversion can be specialized for compatible and incompatible // pairs of RootNode types. template struct RootNodeCopyHelper { static inline void copyWithValueConversion(RootT& self, const OtherRootT& other) { // If the two root nodes have different configurations or incompatible ValueTypes, // throw an exception. self.enforceSameConfiguration(other); self.enforceCompatibleValueTypes(other); // One of the above two tests should throw, so we should never get here: std::ostringstream ostr; ostr << "cannot convert a " << typeid(OtherRootT).name() << " to a " << typeid(RootT).name(); OPENVDB_THROW(TypeError, ostr.str()); } }; // Specialization for root nodes of compatible types template struct RootNodeCopyHelper { static inline void copyWithValueConversion(RootT& self, const OtherRootT& other) { using ValueT = typename RootT::ValueType; using ChildT = typename RootT::ChildNodeType; using NodeStruct = typename RootT::NodeStruct; using Tile = typename RootT::Tile; using OtherValueT = typename OtherRootT::ValueType; using OtherMapCIter = typename OtherRootT::MapCIter; using OtherTile = typename OtherRootT::Tile; struct Local { /// @todo Consider using a value conversion functor passed as an argument instead. static inline ValueT convertValue(const OtherValueT& val) { return ValueT(val); } }; self.mBackground = Local::convertValue(other.mBackground); self.clear(); self.initTable(); for (OtherMapCIter i = other.mTable.begin(), e = other.mTable.end(); i != e; ++i) { if (other.isTile(i)) { // Copy the other node's tile, but convert its value to this node's ValueType. const OtherTile& otherTile = other.getTile(i); self.mTable[i->first] = NodeStruct( Tile(Local::convertValue(otherTile.value), otherTile.active)); } else { // Copy the other node's child, but convert its values to this node's ValueType. self.mTable[i->first] = NodeStruct(*(new ChildT(other.getChild(i)))); } } } }; // Overload for root nodes of the same type as this node template inline RootNode& RootNode::operator=(const RootNode& other) { if (&other != this) { mBackground = other.mBackground; this->clear(); 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; } // Overload for root nodes of different types template template inline RootNode& RootNode::operator=(const RootNode& other) { using OtherRootT = RootNode; using OtherValueT = typename OtherRootT::ValueType; static const bool compatible = (SameConfiguration::value && CanConvertType::value); RootNodeCopyHelper::copyWithValueConversion(*this, other); 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 { using OtherRootT = RootNode; using OtherMapT = typename OtherRootT::MapType; using OtherIterT = typename OtherRootT::MapIter; using OtherCIterT = typename OtherRootT::MapCIter; 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 template inline bool RootNode::hasCompatibleValueType(const RootNode&) { using OtherValueType = typename OtherChildType::ValueType; return CanConvertType::value; } template template inline void RootNode::enforceCompatibleValueTypes(const RootNode&) { using OtherValueType = typename OtherChildType::ValueType; if (!CanConvertType::value) { std::ostringstream ostr; ostr << "values of type " << typeNameAsString() << " cannot be converted to type " << typeNameAsString(); 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::clear() { for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { delete i->second.child; } mTable.clear(); } 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 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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 = nullptr; 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; // Iterate over the fill region in axis-aligned, tile-sized chunks. // (The first and last chunks along each axis might be smaller than a tile.) 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 = nullptr; 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 filled // 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) { const Coord tmp = Coord::minComponent(bbox.max(), tileMax); child->fill(CoordBBox(xyz, tmp), 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 inline void RootNode::denseFill(const CoordBBox& bbox, const ValueType& value, bool active) { if (bbox.empty()) return; if (active && mTable.empty()) { // If this tree is empty, then a sparse fill followed by (threaded) // densification of active tiles is the more efficient approach. sparseFill(bbox, value, active); voxelizeActiveTiles(/*threaded=*/true); return; } // Iterate over the fill region in axis-aligned, tile-sized chunks. // (The first and last chunks along each axis might be smaller than a tile.) 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). tileMin = coordToKey(xyz); tileMax = tileMin.offsetBy(ChildT::DIM - 1); // Retrieve the table entry for the tile that contains xyz, // or, if there is no table entry, add a background tile. const auto iter = findOrAddCoord(tileMin); if (isTile(iter)) { // If the table entry is a tile, replace it with a child node // that is filled with the tile's value and active state. const auto& tile = getTile(iter); auto* child = new ChildT{tileMin, tile.value, tile.active}; setChild(iter, *child); } // Forward the fill request to the child. getChild(iter).denseFill(bbox, value, active); } } } } //////////////////////////////////////// template inline void RootNode::voxelizeActiveTiles(bool threaded) { // There is little point in threading over the root table since each tile // spans a huge index space (by default 4096^3) and hence we expect few // active tiles if any at all. In fact, you're very likely to run out of // memory if this method is called on a tree with root-level active tiles! for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isTileOff(i)) continue; ChildT* child = i->second.child; if (child == nullptr) { // If this table entry is an active tile (i.e., not off and not a child node), // replace it with a child node filled with active tiles of the same value. child = new ChildT{i->first, this->getTile(i).value, true}; i->second.child = child; } child->voxelizeActiveTiles(threaded); } } //////////////////////////////////////// template template inline void RootNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const { using DenseValueType = typename DenseT::ValueType; 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->clear(); 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. #if OPENVDB_ABI_VERSION_NUMBER <= 2 ChildT* child = new ChildT(origin, mBackground); #else ChildT* child = new ChildT(PartialCreate(), origin, mBackground); #endif 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); #if OPENVDB_ABI_VERSION_NUMBER <= 2 ChildT* child = new ChildT(origin, mBackground); #else ChildT* child = new ChildT(PartialCreate(), origin, mBackground); #endif 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 inline void RootNode::readBuffers(std::istream& is, const CoordBBox& clipBBox, bool fromHalf) { const Tile bgTile(mBackground, /*active=*/false); for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (isChild(i)) { // Stream in and clip the branch rooted at this child. // (We can't skip over children that lie outside the clipping region, // because buffers are serialized in depth-first order and need to be // unserialized in the same order.) ChildT& child = getChild(i); child.readBuffers(is, clipBBox, fromHalf); } } // Clip root-level tiles and prune children that were clipped. this->clip(clipBBox); } //////////////////////////////////////// template inline void RootNode::clip(const CoordBBox& clipBBox) { const Tile bgTile(mBackground, /*active=*/false); // Iterate over a copy of this node's table so that we can modify the original. // (Copying the table copies child node pointers, not the nodes themselves.) MapType copyOfTable(mTable); for (MapIter i = copyOfTable.begin(), e = copyOfTable.end(); i != e; ++i) { const Coord& xyz = i->first; // tile or child origin CoordBBox tileBBox(xyz, xyz.offsetBy(ChildT::DIM - 1)); // tile or child bounds if (!clipBBox.hasOverlap(tileBBox)) { // This table entry lies completely outside the clipping region. Delete it. setTile(this->findCoord(xyz), bgTile); // delete any existing child node first mTable.erase(xyz); } else if (!clipBBox.isInside(tileBBox)) { // This table entry does not lie completely inside the clipping region // and must be clipped. if (isChild(i)) { getChild(i).clip(clipBBox, mBackground); } else { // Replace this tile with a background tile, then fill the clip region // with the tile's original value. (This might create a child branch.) tileBBox.intersect(clipBBox); const Tile& origTile = getTile(i); setTile(this->findCoord(xyz), bgTile); this->sparseFill(tileBBox, origTile.value, origTile.active); } } else { // This table entry lies completely inside the clipping region. Leave it intact. } } this->prune(); // also erases root-level background tiles } //////////////////////////////////////// template inline void RootNode::prune(const ValueType& tolerance) { bool state = false; ValueType value = zeroVal(); for (MapIter i = mTable.begin(), e = mTable.end(); i != e; ++i) { if (this->isTile(i)) continue; this->getChild(i).prune(tolerance); if (this->getChild(i).isConstant(value, state, tolerance)) { this->setTile(i, Tile(value, state)); } } this->eraseBackgroundTiles(); } //////////////////////////////////////// template template inline NodeT* RootNode::stealNode(const Coord& xyz, const ValueType& value, bool state) { if ((NodeT::LEVEL == ChildT::LEVEL && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return nullptr; return (std::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 == nullptr) return; ChildT* child = nullptr; 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 == nullptr) return; ChildT* child = nullptr; 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(const Coord& xyz, const ValueType& value, bool state) { MapIter iter = this->findCoord(xyz); if (iter == mTable.end()) {//background mTable[this->coordToKey(xyz)] = NodeStruct(Tile(value, state)); } else {//child or tile setTile(iter, Tile(value, state));//this also deletes the existing child node } } 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 = nullptr; 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 = nullptr; 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 && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return nullptr; ChildT* child = &getChild(iter); return (std::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 && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapCIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return nullptr; const ChildT* child = &getChild(iter); return (std::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 && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return nullptr; ChildT* child = &getChild(iter); acc.insert(xyz, child); return (std::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 && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN MapCIter iter = this->findCoord(xyz); if (iter == mTable.end() || isTile(iter)) return nullptr; const ChildT* child = &getChild(iter); acc.insert(xyz, child); return (std::is_same::value) ? reinterpret_cast(child) : child->template probeConstNodeAndCache(xyz, acc); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template template inline void RootNode::getNodes(ArrayT& array) { using NodePtr = typename ArrayT::value_type; static_assert(std::is_pointer::value, "argument to getNodes() must be a pointer array"); using NodeType = typename std::remove_pointer::type; using NonConstNodeType = typename std::remove_const::type; using result = typename boost::mpl::contains::type; static_assert(result::value, "can't extract non-const nodes from a const tree"); using ArrayChildT = typename std::conditional< std::is_const::value, const ChildT, ChildT>::type; for (MapIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (ChildT* child = iter->second.child) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (std::is_same::value) { array.push_back(reinterpret_cast(iter->second.child)); } else { child->getNodes(array);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } } template template inline void RootNode::getNodes(ArrayT& array) const { using NodePtr = typename ArrayT::value_type; static_assert(std::is_pointer::value, "argument to getNodes() must be a pointer array"); using NodeType = typename std::remove_pointer::type; static_assert(std::is_const::value, "argument to getNodes() must be an array of const node pointers"); using NonConstNodeType = typename std::remove_const::type; using result = typename boost::mpl::contains::type; static_assert(result::value, "can't extract non-const nodes from a const tree"); for (MapCIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (const ChildNodeType *child = iter->second.child) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (std::is_same::value) { array.push_back(reinterpret_cast(iter->second.child)); } else { child->getNodes(array);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } } //////////////////////////////////////// template template inline void RootNode::stealNodes(ArrayT& array, const ValueType& value, bool state) { using NodePtr = typename ArrayT::value_type; static_assert(std::is_pointer::value, "argument to stealNodes() must be a pointer array"); using NodeType = typename std::remove_pointer::type; using NonConstNodeType = typename std::remove_const::type; using result = typename boost::mpl::contains::type; static_assert(result::value, "can't extract non-const nodes from a const tree"); using ArrayChildT = typename std::conditional< std::is_const::value, const ChildT, ChildT>::type; for (MapIter iter=mTable.begin(); iter!=mTable.end(); ++iter) { if (ChildT* child = iter->second.child) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (std::is_same::value) { array.push_back(reinterpret_cast(&stealChild(iter, Tile(value, state)))); } else { child->stealNodes(array, value, state);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } } //////////////////////////////////////// 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) { using OtherRootT = RootNode; using OtherCIterT = typename OtherRootT::MapCIter; 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) { using OtherRootT = RootNode; using OtherCIterT = typename OtherRootT::MapCIter; 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) { MapIter it = this->findCoord(*i); setTile(it, Tile()); // delete any existing child node first mTable.erase(it); } } template template inline void RootNode::topologyDifference(const RootNode& other) { using OtherRootT = RootNode; using OtherCIterT = typename OtherRootT::MapCIter; 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)) { setTile(j, Tile()); // delete any existing child node first mTable.erase(j); } 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(); } //////////////////////////////////////// // This helper class is a friend of RootNode and is needed so that combine2 // can be specialized for compatible and incompatible pairs of RootNode types. template struct RootNodeCombineHelper { static inline void combine2(RootT& self, const RootT&, const OtherRootT& other1, CombineOp&, bool) { // If the two root nodes have different configurations or incompatible ValueTypes, // throw an exception. self.enforceSameConfiguration(other1); self.enforceCompatibleValueTypes(other1); // One of the above two tests should throw, so we should never get here: std::ostringstream ostr; ostr << "cannot combine a " << typeid(OtherRootT).name() << " into a " << typeid(RootT).name(); OPENVDB_THROW(TypeError, ostr.str()); } }; // Specialization for root nodes of compatible types template struct RootNodeCombineHelper { static inline void combine2(RootT& self, const RootT& other0, const OtherRootT& other1, CombineOp& op, bool prune) { self.doCombine2(other0, other1, op, prune); } }; template template inline void RootNode::combine2(const RootNode& other0, const OtherRootNode& other1, CombineOp& op, bool prune) { using OtherValueType = typename OtherRootNode::ValueType; static const bool compatible = (SameConfiguration::value && CanConvertType::value); RootNodeCombineHelper::combine2( *this, other0, other1, op, prune); } template template inline void RootNode::doCombine2(const RootNode& other0, const OtherRootNode& other1, CombineOp& op, bool prune) { enforceSameConfiguration(other1); using OtherValueT = typename OtherRootNode::ValueType; using OtherTileT = typename OtherRootNode::Tile; using OtherNodeStructT = typename OtherRootNode::NodeStruct; using OtherMapCIterT = typename OtherRootNode::MapCIter; CombineArgs args; CoordSet keys; other0.insertKeys(keys); other1.insertKeys(keys); const NodeStruct bg0(Tile(other0.mBackground, /*active=*/false)); const OtherNodeStructT bg1(OtherTileT(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); OtherMapCIterT iter1 = other1.findKey(*i); const NodeStruct& ns0 = (iter0 != other0.mTable.end()) ? iter0->second : bg0; const OtherNodeStructT& 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 { if (!isChild(thisIter)) { // Add a new child with the same coordinates, etc. as the other node's child. const Coord& childOrigin = ns0.isChild() ? ns0.child->origin() : ns1.child->origin(); setChild(thisIter, *(new ChildT(childOrigin, 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) { 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) ? nullptr : iter.probeChild(val); typename OtherChildAllIterT::ChildNodeType* otherChild = (skipBranch & 2U) ? nullptr : otherIter.probeChild(otherVal); if (child != nullptr && otherChild != nullptr) { child->visit2Node(*otherChild, op); } else if (child != nullptr) { child->visit2(otherIter, op); } else if (otherChild != nullptr) { 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-2017 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.h0000644000000000000000000010305713200122377014466 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 /// /// @brief 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. /// @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 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 "TreeIterator.h" // for CopyConstness #include #include #include #include #include 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; using LeafIterType = typename TreeT::LeafIter; }; template struct TreeTraits { static const bool IsConstTree = true; using LeafIterType = typename TreeT::LeafCIter; }; //@} } // namespace leafmgr /// This helper class implements LeafManager methods that need to be /// specialized for const vs. non-const trees. template struct LeafManagerImpl { using RangeT = typename ManagerT::RangeType; using LeafT = typename ManagerT::LeafType; using BufT = typename ManagerT::BufferType; 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: using TreeType = TreeT; using ValueType = typename TreeT::ValueType; using RootNodeType = typename TreeT::RootNodeType; using NonConstLeafType = typename TreeType::LeafNodeType; using LeafType = typename CopyConstness::Type; using LeafNodeType = LeafType; using LeafIterType = typename leafmgr::TreeTraits::LeafIterType; using NonConstBufferType = typename LeafType::Buffer; using BufferType = typename CopyConstness::Type; using RangeType = tbb::blocked_range; // leaf index range static const Index DEPTH = 2; // root + leaf nodes 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()); } Iterator(const Iterator&) = default; Iterator& operator=(const Iterator&) = default; /// 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; } /// Return @c true if the position of this iterator is in a valid range. 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) || (&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 /// @note The default is no auxiliary buffers LeafManager(TreeType& tree, size_t auxBuffersPerLeaf=0, bool serial=false) : mTree(&tree) , mLeafCount(0) , mAuxBufferCount(0) , mAuxBuffersPerLeaf(auxBuffersPerLeaf) , mLeafs(nullptr) , mAuxBuffers(nullptr) , mTask(0) , mIsMaster(true) { this->rebuild(serial); } /// @brief Construct directly from an existing array of leafnodes. /// @warning The leafnodes are implicitly assumed to exist in the /// input @a tree. LeafManager(TreeType& tree, LeafType** begin, LeafType** end, size_t auxBuffersPerLeaf=0, bool serial=false) : mTree(&tree) , mLeafCount(end-begin) , mAuxBufferCount(0) , mAuxBuffersPerLeaf(auxBuffersPerLeaf) , mLeafs(new LeafType*[mLeafCount]) , mAuxBuffers(nullptr) , mTask(0) , mIsMaster(true) { size_t n = mLeafCount; LeafType **target = mLeafs, **source = begin; while (n--) *target++ = *source++; if (auxBuffersPerLeaf) this->initAuxBuffers(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(); } /// @brief Return the total number of allocated auxiliary buffers. size_t auxBufferCount() const { return mAuxBufferCount; } /// @brief Return the number of auxiliary buffers per leaf node. size_t auxBuffersPerLeaf() const { return mAuxBuffersPerLeaf; } /// @brief Return the number of leaf nodes. size_t leafCount() const { return mLeafCount; } /// @brief Return the number of active voxels in the leaf nodes. /// @note Multi-threaded for better performance than Tree::activeLeafVoxelCount Index64 activeLeafVoxelCount() const { return tbb::parallel_reduce(this->leafRange(), Index64(0), [] (const LeafRange& range, Index64 sum) -> Index64 { for (const auto& leaf: range) { sum += leaf.onVoxelCount(); } return sum; }, [] (Index64 n, Index64 m) -> Index64 { return n + m; }); } /// Return a const reference to tree associated with this manager. const TreeType& tree() const { return *mTree; } /// Return a reference to the tree associated with this manager. TreeType& tree() { return *mTree; } /// Return a const reference to root node associated with this manager. const RootNodeType& root() const { return mTree->root(); } /// Return a reference to the root node associated with this manager. RootNodeType& root() { return mTree->root(); } /// 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) { namespace ph = std::placeholders; if (bufferIdx == 0 || bufferIdx > mAuxBuffersPerLeaf || this->isConstTree()) return false; mTask = std::bind(&LeafManager::doSwapLeafBuffer, ph::_1, ph::_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) { namespace ph = std::placeholders; 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 = std::bind(&LeafManager::doSwapLeafBuffer, ph::_1, ph::_2, b2-1); } else { mTask = std::bind(&LeafManager::doSwapAuxBuffer, ph::_1, ph::_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) { namespace ph = std::placeholders; if (bufferIdx == 0 || bufferIdx > mAuxBuffersPerLeaf) return false; mTask = std::bind(&LeafManager::doSyncAuxBuffer, ph::_1, ph::_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) { namespace ph = std::placeholders; switch (mAuxBuffersPerLeaf) { case 0: return false;//nothing to do case 1: mTask = std::bind(&LeafManager::doSyncAllBuffers1, ph::_1, ph::_2); break; case 2: mTask = std::bind(&LeafManager::doSyncAllBuffers2, ph::_1, ph::_2); break; default: mTask = std::bind(&LeafManager::doSyncAllBuffersN, ph::_1, ph::_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. /// /// @details The user-supplied functor needs to define the methods /// required for tbb::parallel_for. /// /// @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. /// This allows the function to use non-thread-safe members /// like a ValueAccessor. /// /// @par Example: /// @code /// // Functor to offset a tree's voxel values with values from another tree. /// template /// struct OffsetOp /// { /// using Accessor = tree::ValueAccessor; /// /// 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())); /// } /// } /// } /// 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 /// { /// using BufferType = typename LeafManagerType::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 ... /// } /// 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); } /// @brief Threaded method that applies a user-supplied functor /// to each leaf node in the LeafManager. Unlike foreach /// (defined above) this method performs a reduction on /// all the leaf nodes. /// /// @details The user-supplied functor needs to define the methods /// required for tbb::parallel_reduce. /// /// @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. /// This allows the function to use non-thread-safe members /// like a ValueAccessor. /// /// @par Example: /// @code /// // Functor to count the number of negative (active) leaf values /// struct CountOp /// { /// CountOp() : mCounter(0) {} /// CountOp(const CountOp &other) : mCounter(other.mCounter) {} /// CountOp(const CountOp &other, tbb::split) : mCounter(0) {} /// template /// void operator()(LeafNodeType &leaf, size_t) /// { /// typename LeafNodeType::ValueOnIter iter = leaf.beginValueOn(); /// for (; iter; ++iter) if (*iter < 0.0f) ++mCounter; /// } /// void join(const CountOp &other) {mCounter += other.mCounter;} /// size_t mCounter; /// }; /// /// // usage: /// tree::LeafManager leafNodes(tree); /// MinValueOp min; /// leafNodes.reduce(min); /// std::cerr << "Number of negative active voxels = " << min.mCounter << std::endl; /// /// @endcode template void reduce(LeafOp& op, bool threaded = true, size_t grainSize=1) { LeafReducer transform(op); transform.run(this->leafRange(grainSize), threaded); } /// @brief Insert pointers to nodes of the specified type into the array. /// @details The type of node pointer is defined by the type /// ArrayT::value_type. If the node type is a LeafNode the nodes /// are inserted from this LeafManager, else of the corresponding tree. template void getNodes(ArrayT& array) { using T = typename ArrayT::value_type; static_assert(std::is_pointer::value, "argument to getNodes() must be a pointer array"); using LeafT = typename std::conditional::type>::value, const LeafType, LeafType>::type; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (std::is_same::value) { array.resize(mLeafCount); for (size_t i=0; i(mLeafs[i]); } else { mTree->getNodes(array); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// @brief Insert node pointers of the specified type into the array. /// @details The type of node pointer is defined by the type /// ArrayT::value_type. If the node type is a LeafNode the nodes /// are inserted from this LeafManager, else of the corresponding tree. template void getNodes(ArrayT& array) const { using T = typename ArrayT::value_type; static_assert(std::is_pointer::value, "argument to getNodes() must be a pointer array"); static_assert(std::is_const::type>::value, "argument to getNodes() must be an array of const node pointers"); OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (std::is_same::value) { array.resize(mLeafCount); for (size_t i=0; i(mLeafs[i]); } else { mTree->getNodes(array); } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } /// @brief Generate a linear array of prefix sums of offsets into the /// active voxels in the leafs. So @a offsets[n]+m is the offset to the /// mth active voxel in the nth leaf node (useful for /// user-managed value buffers, e.g. in tools/LevelSetAdvect.h). /// @return The total number of active values in the leaf nodes /// @param offsets array of prefix sums of offsets to active voxels /// @param size on input, the size of @a offsets; on output, its new size /// @param grainSize optional grain size for threading /// @details If @a offsets is @c nullptr or @a size is smaller than the /// total number of active voxels (the return value) then @a offsets /// is reallocated and @a size equals the total number of active voxels. size_t getPrefixSum(size_t*& offsets, size_t& size, size_t grainSize=1) const { if (offsets == nullptr || size < mLeafCount) { delete [] offsets; offsets = new size_t[mLeafCount]; size = mLeafCount; } size_t prefix = 0; if ( grainSize > 0 ) { PrefixSum tmp(this->leafRange( grainSize ), offsets, prefix); } else {// serial for (size_t i=0; ionVoxelCount(); } } return prefix; } //////////////////////////////////////////////////////////////////////////////////// // 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: // This a simple wrapper for a c-style array so it mimics the api // of a std container, e.g. std::vector or std::deque, and can be // passed to Tree::getNodes(). struct MyArray { using value_type = LeafType*;//required by Tree::getNodes value_type* ptr; MyArray(value_type* array) : ptr(array) {} void push_back(value_type leaf) { *ptr++ = leaf; }//required by Tree::getNodes }; void initLeafArray() { const size_t leafCount = mTree->leafCount(); if (leafCount != mLeafCount) { delete [] mLeafs; mLeafs = (leafCount == 0) ? nullptr : new LeafType*[leafCount]; mLeafCount = leafCount; } MyArray a(mLeafs); mTree->getNodes(a); } void initAuxBuffers(bool serial) { const size_t auxBufferCount = mLeafCount * mAuxBuffersPerLeaf; if (auxBufferCount != mAuxBufferCount) { delete [] mAuxBuffers; mAuxBuffers = (auxBufferCount == 0) ? nullptr : 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 perform parallel_for on all the leaf nodes. template struct LeafTransformer { LeafTransformer(const LeafOp &leafOp) : mLeafOp(leafOp) { } void run(const LeafRange &range, bool threaded) const { 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; };// LeafTransformer /// @brief Private member class that applies a user-defined /// functor to perform parallel_reduce on all the leaf nodes. template struct LeafReducer { LeafReducer(LeafOp &leafOp) : mLeafOp(&leafOp), mOwnsOp(false) { } LeafReducer(const LeafReducer &other, tbb::split) : mLeafOp(new LeafOp(*(other.mLeafOp), tbb::split())), mOwnsOp(true) { } ~LeafReducer() { if (mOwnsOp) delete mLeafOp; } void run(const LeafRange& range, bool threaded) { threaded ? tbb::parallel_reduce(range, *this) : (*this)(range); } void operator()(const LeafRange& range) { LeafOp &op = *mLeafOp;//local registry for (typename LeafRange::Iterator it = range.begin(); it; ++it) op(*it, it.pos()); } void join(const LeafReducer& other) { mLeafOp->join(*(other.mLeafOp)); } LeafOp *mLeafOp; const bool mOwnsOp; };// LeafReducer // Helper class to compute a prefix sum of offsets to active voxels struct PrefixSum { PrefixSum(const LeafRange& r, size_t* offsets, size_t& prefix) : mOffsets(offsets) { tbb::parallel_for( r, *this); for (size_t i=0, leafCount = r.size(); ionVoxelCount(); } } size_t* mOffsets; };// PrefixSum using FuncType = typename std::function; 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 > { using ManagerT = LeafManager; using RangeT = typename ManagerT::RangeType; using LeafT = typename ManagerT::LeafType; using BufT = typename ManagerT::BufferType; 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-2017 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/LeafBuffer.h0000644000000000000000000004674613200122377014340 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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_LEAFBUFFER_HAS_BEEN_INCLUDED #define OPENVDB_TREE_LEAFBUFFER_HAS_BEEN_INCLUDED #include #include // for io::readCompressedValues(), etc #include #include #include #include // for std::swap #include // for offsetof() #include #include #include class TestLeaf; namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { namespace internal { /// @internal For delayed loading to be threadsafe, LeafBuffer::mOutOfCore must be /// memory-fenced when it is set in LeafBuffer::doLoad(), otherwise that operation /// could be reordered ahead of others in doLoad(), with the possible result that /// other threads could see the buffer as in-core before it has been fully loaded. /// Making mOutOfCore a TBB atomic solves the problem, since TBB atomics are release-fenced /// by default (unlike STL atomics, which are not even guaranteed to be lock-free). /// However, TBB atomics have stricter alignment requirements than their underlying value_types, /// so a LeafBuffer with an atomic mOutOfCore is potentially ABI-incompatible with /// its non-atomic counterpart. /// This helper class conditionally declares mOutOfCore as an atomic only if doing so /// doesn't break ABI compatibility. template struct LeafBufferFlags { #if OPENVDB_ABI_VERSION_NUMBER >= 5 /// The type of LeafBuffer::mOutOfCore using type = tbb::atomic; static constexpr bool IsAtomic = true; #else // OPENVDB_ABI_VERSION_NUMBER < 5 // These structs need to have the same data members as LeafBuffer. struct Atomic { union { T* data; void* ptr; }; tbb::atomic i; tbb::spin_mutex mutex; }; struct NonAtomic { union { T* data; void* ptr; }; Index32 i; tbb::spin_mutex mutex; }; #ifndef __INTEL_COMPILER /// @c true if LeafBuffer::mOutOfCore is atomic, @c false otherwise static constexpr bool IsAtomic = ((sizeof(Atomic) == sizeof(NonAtomic)) && (offsetof(Atomic, i) == offsetof(NonAtomic, i))); #else // We can't use offsetof() with ICC, because it requires the arguments // to be POD types. (C++11 requires only that they be standard layout types, // which Atomic and NonAtomic are.) static constexpr bool IsAtomic = (sizeof(Atomic) == sizeof(NonAtomic)); #endif /// The size of a LeafBuffer when LeafBuffer::mOutOfCore is atomic static constexpr size_t size = sizeof(Atomic); /// The type of LeafBuffer::mOutOfCore using type = typename std::conditional, Index32>::type; #endif }; } // namespace internal /// @brief Array of fixed size 23Log2Dim that stores /// the voxel values of a LeafNode template class LeafBuffer { public: using ValueType = T; using StorageType = ValueType; using NodeMaskType = util::NodeMask; static const Index SIZE = 1 << 3 * Log2Dim; #if OPENVDB_ABI_VERSION_NUMBER >= 3 struct FileInfo { FileInfo(): bufpos(0) , maskpos(0) {} std::streamoff bufpos; std::streamoff maskpos; io::MappedFile::Ptr mapping; SharedPtr meta; }; #endif #if OPENVDB_ABI_VERSION_NUMBER <= 2 /// Default constructor LeafBuffer(): mData(new ValueType[SIZE]) {} /// Construct a buffer populated with the specified value. explicit LeafBuffer(const ValueType& val): mData(new ValueType[SIZE]) { this->fill(val); } /// Copy constructor LeafBuffer(const LeafBuffer& other): mData(new ValueType[SIZE]) { *this = other; } /// Destructor ~LeafBuffer() { delete[] mData; } /// Return @c true if this buffer's values have not yet been read from disk. bool isOutOfCore() const { return false; } /// Return @c true if memory for this buffer has not yet been allocated. bool empty() const { return (mData == nullptr); } #else /// Default constructor inline LeafBuffer(): mData(new ValueType[SIZE]), mOutOfCore(0) {} /// Construct a buffer populated with the specified value. explicit inline LeafBuffer(const ValueType&); /// Copy constructor inline LeafBuffer(const LeafBuffer&); /// Construct a buffer but don't allocate memory for the full array of values. LeafBuffer(PartialCreate, const ValueType&): mData(nullptr), mOutOfCore(0) {} /// Destructor inline ~LeafBuffer(); /// Return @c true if this buffer's values have not yet been read from disk. bool isOutOfCore() const { return bool(mOutOfCore); } /// Return @c true if memory for this buffer has not yet been allocated. bool empty() const { return !mData || this->isOutOfCore(); } #endif /// Allocate memory for this buffer if it has not already been allocated. bool allocate() { if (mData == nullptr) mData = new ValueType[SIZE]; return true; } /// Populate this buffer with a constant value. inline void fill(const ValueType&); /// Return a const reference to the i'th element of this buffer. const ValueType& getValue(Index i) const { return this->at(i); } /// Return a const reference to the i'th element of this buffer. const ValueType& operator[](Index i) const { return this->at(i); } /// Set the i'th value of this buffer to the specified value. inline void setValue(Index i, const ValueType&); /// Copy the other buffer's values into this buffer. inline LeafBuffer& operator=(const LeafBuffer&); /// @brief Return @c true if the contents of the other buffer /// exactly equal the contents of this buffer. inline bool operator==(const LeafBuffer&) const; /// @brief Return @c true if the contents of the other buffer /// are not exactly equal to the contents of this buffer. inline bool operator!=(const LeafBuffer& other) const { return !(other == *this); } /// Exchange this buffer's values with the other buffer's values. inline void swap(LeafBuffer&); /// Return the memory footprint of this buffer in bytes. inline Index memUsage() const; /// Return the number of values contained in this buffer. static Index size() { return SIZE; } /// @brief Return a const pointer to the array of voxel values. /// @details This method guarantees that the buffer is allocated and loaded. /// @warning This method should only be used by experts seeking low-level optimizations. const ValueType* data() const; /// @brief Return a pointer to the array of voxel values. /// @details This method guarantees that the buffer is allocated and loaded. /// @warning This method should only be used by experts seeking low-level optimizations. ValueType* data(); private: /// If this buffer is empty, return zero, otherwise return the value at index @ i. inline const ValueType& at(Index i) const; /// @brief Return a non-const reference to the value at index @a i. /// @details This method is private since it makes assumptions about the /// buffer's memory layout. LeafBuffers associated with custom leaf node types /// (e.g., a bool buffer implemented as a bitmask) might not be able to /// return non-const references to their values. ValueType& operator[](Index i) { return const_cast(this->at(i)); } bool deallocate(); #if OPENVDB_ABI_VERSION_NUMBER <= 2 void setOutOfCore(bool) {} void loadValues() const {} void doLoad() const {} bool detachFromFile() { return false; } #else inline void setOutOfCore(bool b) { mOutOfCore = b; } // To facilitate inlining in the common case in which the buffer is in-core, // the loading logic is split into a separate function, doLoad(). inline void loadValues() const { if (this->isOutOfCore()) this->doLoad(); } inline void doLoad() const; inline bool detachFromFile(); #endif #if OPENVDB_ABI_VERSION_NUMBER <= 2 ValueType* mData; #else using FlagsType = typename internal::LeafBufferFlags::type; union { ValueType* mData; FileInfo* mFileInfo; }; FlagsType mOutOfCore; // interpreted as bool; extra bits reserved for future use tbb::spin_mutex mMutex; // 1 byte //int8_t mReserved[3]; // padding for alignment static const ValueType sZero; #endif friend class ::TestLeaf; // Allow the parent LeafNode to access this buffer's data pointer. template friend class LeafNode; }; // class LeafBuffer //////////////////////////////////////// #if OPENVDB_ABI_VERSION_NUMBER >= 3 template const T LeafBuffer::sZero = zeroVal(); template inline LeafBuffer::LeafBuffer(const ValueType& val) : mData(new ValueType[SIZE]) , mOutOfCore(0) { this->fill(val); } template inline LeafBuffer::~LeafBuffer() { if (this->isOutOfCore()) { this->detachFromFile(); } else { this->deallocate(); } } template inline LeafBuffer::LeafBuffer(const LeafBuffer& other) : mData(nullptr) , mOutOfCore(other.mOutOfCore) { if (other.isOutOfCore()) { mFileInfo = new FileInfo(*other.mFileInfo); } else if (other.mData != nullptr) { this->allocate(); ValueType* target = mData; const ValueType* source = other.mData; Index n = SIZE; while (n--) *target++ = *source++; } } #endif // OPENVDB_ABI_VERSION_NUMBER >= 3 template inline void LeafBuffer::setValue(Index i, const ValueType& val) { assert(i < SIZE); #if OPENVDB_ABI_VERSION_NUMBER <= 2 mData[i] = val; #else this->loadValues(); if (mData) mData[i] = val; #endif } template inline LeafBuffer& LeafBuffer::operator=(const LeafBuffer& other) { if (&other != this) { #if OPENVDB_ABI_VERSION_NUMBER <= 2 if (other.mData != nullptr) { this->allocate(); ValueType* target = mData; const ValueType* source = other.mData; Index n = SIZE; while (n--) *target++ = *source++; } #else // OPENVDB_ABI_VERSION_NUMBER >= 3 if (this->isOutOfCore()) { this->detachFromFile(); } else { if (other.isOutOfCore()) this->deallocate(); } if (other.isOutOfCore()) { mOutOfCore = other.mOutOfCore; mFileInfo = new FileInfo(*other.mFileInfo); } else if (other.mData != nullptr) { this->allocate(); ValueType* target = mData; const ValueType* source = other.mData; Index n = SIZE; while (n--) *target++ = *source++; } #endif } return *this; } template inline void LeafBuffer::fill(const ValueType& val) { this->detachFromFile(); if (mData != nullptr) { ValueType* target = mData; Index n = SIZE; while (n--) *target++ = val; } } template inline bool LeafBuffer::operator==(const LeafBuffer& other) const { this->loadValues(); other.loadValues(); const ValueType *target = mData, *source = other.mData; if (!target && !source) return true; if (!target || !source) return false; Index n = SIZE; while (n && math::isExactlyEqual(*target++, *source++)) --n; return n == 0; } template inline void LeafBuffer::swap(LeafBuffer& other) { std::swap(mData, other.mData); #if OPENVDB_ABI_VERSION_NUMBER >= 3 std::swap(mOutOfCore, other.mOutOfCore); #endif } template inline Index LeafBuffer::memUsage() const { size_t n = sizeof(*this); #if OPENVDB_ABI_VERSION_NUMBER <= 2 if (mData) n += SIZE * sizeof(ValueType); #else if (this->isOutOfCore()) n += sizeof(FileInfo); else if (mData) n += SIZE * sizeof(ValueType); #endif return static_cast(n); } template inline const typename LeafBuffer::ValueType* LeafBuffer::data() const { #if OPENVDB_ABI_VERSION_NUMBER >= 3 this->loadValues(); if (mData == nullptr) { LeafBuffer* self = const_cast(this); // This lock will be contended at most once. tbb::spin_mutex::scoped_lock lock(self->mMutex); if (mData == nullptr) self->mData = new ValueType[SIZE]; } #endif return mData; } template inline typename LeafBuffer::ValueType* LeafBuffer::data() { #if OPENVDB_ABI_VERSION_NUMBER >= 3 this->loadValues(); if (mData == nullptr) { // This lock will be contended at most once. tbb::spin_mutex::scoped_lock lock(mMutex); if (mData == nullptr) mData = new ValueType[SIZE]; } #endif return mData; } template inline const typename LeafBuffer::ValueType& LeafBuffer::at(Index i) const { assert(i < SIZE); #if OPENVDB_ABI_VERSION_NUMBER <= 2 return mData[i]; #else this->loadValues(); // We can't use the ternary operator here, otherwise Visual C++ returns // a reference to a temporary. if (mData) return mData[i]; else return sZero; #endif } template inline bool LeafBuffer::deallocate() { if (mData != nullptr && !this->isOutOfCore()) { delete[] mData; mData = nullptr; return true; } return false; } #if OPENVDB_ABI_VERSION_NUMBER >= 3 template inline void LeafBuffer::doLoad() const { if (!this->isOutOfCore()) return; LeafBuffer* self = const_cast*>(this); // This lock will be contended at most once, after which this buffer // will no longer be out-of-core. tbb::spin_mutex::scoped_lock lock(self->mMutex); if (!this->isOutOfCore()) return; std::unique_ptr info(self->mFileInfo); assert(info.get() != nullptr); assert(info->mapping.get() != nullptr); assert(info->meta.get() != nullptr); /// @todo For now, we have to clear the mData pointer in order for allocate() to take effect. self->mData = nullptr; self->allocate(); SharedPtr buf = info->mapping->createBuffer(); std::istream is(buf.get()); io::setStreamMetadataPtr(is, info->meta, /*transfer=*/true); NodeMaskType mask; is.seekg(info->maskpos); mask.load(is); is.seekg(info->bufpos); io::readCompressedValues(is, self->mData, SIZE, mask, io::getHalfFloat(is)); self->setOutOfCore(false); } template inline bool LeafBuffer::detachFromFile() { if (this->isOutOfCore()) { delete mFileInfo; mFileInfo = nullptr; this->setOutOfCore(false); return true; } return false; } #endif // OPENVDB_ABI_VERSION_NUMBER >= 3 //////////////////////////////////////// // Partial specialization for bool ValueType template class LeafBuffer { public: using NodeMaskType = util::NodeMask; using WordType = typename NodeMaskType::Word; using ValueType = bool; using StorageType = WordType; static const Index WORD_COUNT = NodeMaskType::WORD_COUNT; static const Index SIZE = 1 << 3 * Log2Dim; // These static declarations must be on separate lines to avoid VC9 compiler errors. static const bool sOn; static const bool sOff; LeafBuffer() {} LeafBuffer(bool on): mData(on) {} LeafBuffer(const NodeMaskType& other): mData(other) {} LeafBuffer(const LeafBuffer& other): mData(other.mData) {} ~LeafBuffer() {} void fill(bool val) { mData.set(val); } LeafBuffer& operator=(const LeafBuffer& b) { if (&b != this) { mData=b.mData; } return *this; } const bool& getValue(Index i) const { assert(i < SIZE); // We can't use the ternary operator here, otherwise Visual C++ returns // a reference to a temporary. if (mData.isOn(i)) return sOn; else return sOff; } const bool& operator[](Index i) const { return this->getValue(i); } bool operator==(const LeafBuffer& other) const { return mData == other.mData; } bool operator!=(const LeafBuffer& other) const { return mData != other.mData; } void setValue(Index i, bool val) { assert(i < SIZE); mData.set(i, val); } void swap(LeafBuffer& other) { if (&other != this) std::swap(mData, other.mData); } Index memUsage() const { return sizeof(*this); } static Index size() { return SIZE; } /// @brief Return a pointer to the C-style array of words encoding the bits. /// @warning This method should only be used by experts seeking low-level optimizations. WordType* data() { return &(mData.template getWord(0)); } /// @brief Return a const pointer to the C-style array of words encoding the bits. /// @warning This method should only be used by experts seeking low-level optimizations. const WordType* data() const { return const_cast(this)->data(); } private: // Allow the parent LeafNode to access this buffer's data. template friend class LeafNode; NodeMaskType mData; }; // class LeafBuffer /// @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 LeafBuffer::sOn = true; template const bool LeafBuffer::sOff = false; } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_LEAFBUFFER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000022674513200122377014013 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #include // for io::readData(), etc. #include "Iterator.h" #include "LeafBuffer.h" #include // for std::nth_element() #include #include #include #include #include #include class TestLeaf; template class TestLeafIO; namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { template struct SameLeafConfig; // forward declaration /// @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: using BuildType = T; using ValueType = T; using Buffer = LeafBuffer; using LeafNodeType = LeafNode; using NodeMaskType = util::NodeMask; using Ptr = SharedPtr; 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 /// dimensions as this node but a different value type, T. template struct ValueConverter { using Type = LeafNode; }; /// @brief SameConfiguration::value is @c true if and only if /// OtherNodeType is the type of a LeafNode with the same dimensions as this node. template struct SameConfiguration { static const bool value = SameLeafConfig::value; }; /// 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); #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// @brief "Partial creation" constructor used during file input /// @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 /// @details This constructor does not allocate memory for voxel values. LeafNode(PartialCreate, const Coord& coords, const ValueType& value = zeroVal(), bool active = false); #endif /// Deep copy constructor LeafNode(const LeafNode&); /// Deep assignment operator LeafNode& operator=(const LeafNode&) = default; /// Value conversion copy constructor template explicit LeafNode(const LeafNode& other); /// 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(); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// Return @c true if memory for this node's buffer has been allocated. bool isAllocated() const { return !mBuffer.isOutOfCore() && !mBuffer.empty(); } /// Allocate memory for this node's buffer if it has not already been allocated. bool allocate() { return mBuffer.allocate(); } #endif /// 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& bbox, bool visitVoxels = true) 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; } //@{ /// 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, state and origin equivalence. bool operator==(const LeafNode& other) const; bool operator!=(const LeafNode& other) const { return !(other == *this); } protected: using MaskOnIterator = typename NodeMaskType::OnIterator; using MaskOffIterator = typename NodeMaskType::OffIterator; using MaskDenseIterator = typename NodeMaskType::DenseIterator; // 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> { using BaseT = SparseIteratorBase; 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> { using BaseT = DenseIteratorBase; using NonConstValueT = typename BaseT::NonConstValueType; 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 = nullptr; 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: using ValueOnIter = ValueIter; using ValueOnCIter = ValueIter; using ValueOffIter = ValueIter; using ValueOffCIter = ValueIter; using ValueAllIter = ValueIter; using ValueAllCIter = ValueIter; using ChildOnIter = ChildIter; using ChildOnCIter = ChildIter; using ChildOffIter = ChildIter; using ChildOffCIter = ChildIter; using ChildAllIter = DenseIter; using ChildAllCIter = DenseIter; 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 Read buffers that intersect the given bounding box. /// @param is the stream from which to read /// @param bbox an index-space bounding box /// @param fromHalf if true, floating-point input values are assumed to be 16-bit void readBuffers(std::istream& is, const CoordBBox& bbox, 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.setValue(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) { ValueType val = mBuffer[offset]; op(val); mBuffer.setValue(offset, val); 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); ValueType val = mBuffer[offset]; op(val, state); mBuffer.setValue(offset, val); 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 that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&, const ValueType& background); /// 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 within an axis-aligned box to the specified value and active state. void denseFill(const CoordBBox& bbox, const ValueType& value, bool active = true) { this->fill(bbox, value, active); } /// 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); void negate(); /// @brief No-op /// @details This function exists only to enable template instantiation. void voxelizeActiveTiles(bool = true) {} 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, because it can deactivate all of this node's voxels, /// consider subsequently 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 OtherType&, bool valueIsActive, CombineOp&); template void combine2(const ValueType&, const OtherNodeT& other, bool valueIsActive, CombineOp&); template void combine2(const LeafNode& b0, const OtherNodeT& 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 prune(const ValueType& /*tolerance*/ = zeroVal()) {} void addLeaf(LeafNode*) {} template void addLeafAndCache(LeafNode*, AccessorT&) {} template NodeT* stealNode(const Coord&, const ValueType&, bool) { return nullptr; } template NodeT* probeNode(const Coord&) { return nullptr; } template const NodeT* probeConstNode(const Coord&) const { return nullptr; } template void getNodes(ArrayT&) const {} template void stealNodes(ArrayT&, const ValueType&, bool) {} //@} 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 (!(std::is_same::value)) return nullptr; 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 (!(std::is_same::value)) return nullptr; 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 in the range this->getFirstValue() +/- @a tolerance. /// /// /// @param firstValue Is updated with the first value of this leaf node. /// @param state Is updated with the state of all values IF method /// returns @c true. Else the value is undefined! /// @param tolerance The tolerance used to determine if values are /// approximatly equal to the for value. bool isConstant(ValueType& firstValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// Return @c true if all of this node's values have the same active state /// and the range (@a maxValue - @a minValue) < @a tolerance. /// /// @param minValue Is updated with the minimum of all values IF method /// returns @c true. Else the value is undefined! /// @param maxValue Is updated with the maximum of all values IF method /// returns @c true. Else the value is undefined! /// @param state Is updated with the state of all values IF method /// returns @c true. Else the value is undefined! /// @param tolerance The tolerance used to determine if values are /// approximatly constant. bool isConstant(ValueType& minValue, ValueType& maxValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// @brief Computes the median value of all the active AND inactive voxels in this node. /// @return The median value of all values in this node. /// /// @param tmp Optional temporary storage that can hold at least NUM_VALUES values /// Use of this temporary storage can improve performance /// when this method is called multiple times. /// /// @note If tmp = this->buffer().data() then the median /// value is computed very efficiently (in place) but /// the voxel values in this node are re-shuffeled! /// /// @warning If tmp != nullptr then it is the responsibility of /// the client code that it points to enough memory to /// hold NUM_VALUES elements of type ValueType. ValueType medianAll(ValueType *tmp = nullptr) const; /// @brief Computes the median value of all the active voxels in this node. /// @return The number of active voxels. /// /// @param value If the return value is non zero @a value is updated /// with the median value. /// /// @param tmp Optional temporary storage that can hold at least /// as many values as there are active voxels in this node. /// Use of this temporary storage can improve performance /// when this method is called multiple times. /// /// @warning If tmp != nullptr then it is the responsibility of /// the client code that it points to enough memory to /// hold the number of active voxels of type ValueType. Index medianOn(ValueType &value, ValueType *tmp = nullptr) const; /// @brief Computes the median value of all the inactive voxels in this node. /// @return The number of inactive voxels. /// /// @param value If the return value is non zero @a value is updated /// with the median value. /// /// @param tmp Optional temporary storage that can hold at least /// as many values as there are inactive voxels in this node. /// Use of this temporary storage can improve performance /// when this method is called multiple times. /// /// @warning If tmp != nullptr then it is the responsibility of /// the client code that it points to enough memory to /// hold the number of inactive voxels of type ValueType. Index medianOff(ValueType &value, ValueType *tmp = nullptr) 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; // 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; } const NodeMaskType& valueMask() const { 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); } inline void skipCompressedValues(bool seekable, std::istream&, bool fromHalf); /// 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); private: /// 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; }; // end of LeafNode class //////////////////////////////////////// //@{ /// Helper metafunction used to implement LeafNode::SameConfiguration /// (which, as an inner class, can't be independently specialized) template struct SameLeafConfig { static const bool value = false; }; template struct SameLeafConfig > { static const bool value = true; }; //@} //////////////////////////////////////// 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))) { } #if OPENVDB_ABI_VERSION_NUMBER >= 3 template inline LeafNode::LeafNode(PartialCreate, const Coord& xyz, const ValueType& val, bool active): mBuffer(PartialCreate(), val), mValueMask(active), mOrigin(xyz & (~(DIM - 1))) { } #endif template inline LeafNode::LeafNode(const LeafNode& other): mBuffer(other.mBuffer), mValueMask(other.valueMask()), mOrigin(other.mOrigin) { } // Copy-construct from a leaf node with the same configuration but a different ValueType. template template inline LeafNode::LeafNode(const LeafNode& other): mValueMask(other.valueMask()), mOrigin(other.mOrigin) { struct Local { /// @todo Consider using a value conversion functor passed as an argument instead. static inline ValueType convertValue(const OtherValueType& val) { return ValueType(val); } }; for (Index i = 0; i < SIZE; ++i) { mBuffer[i] = Local::convertValue(other.mBuffer[i]); } } template template inline LeafNode::LeafNode(const LeafNode& other, const ValueType& background, TopologyCopy): mBuffer(background), mValueMask(other.valueMask()), mOrigin(other.mOrigin) { } template template inline LeafNode::LeafNode(const LeafNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy): mValueMask(other.valueMask()), mOrigin(other.mOrigin) { for (Index i = 0; i < SIZE; ++i) { mBuffer[i] = (mValueMask.isOn(i) ? onValue : offValue); } } 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 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.setValue(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::clip(const CoordBBox& clipBBox, const T& background) { CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. Fill it with the background. this->fill(background, /*active=*/false); } else if (clipBBox.isInside(nodeBBox)) { // This node lies completely inside the clipping region. Leave it intact. return; } // This node isn't completely contained inside the clipping region. // Set any voxels that lie outside the region to the background value. // Construct a boolean mask that is on inside the clipping region and off outside it. NodeMaskType mask; nodeBBox.intersect(clipBBox); Coord xyz; int &x = xyz.x(), &y = xyz.y(), &z = xyz.z(); for (x = nodeBBox.min().x(); x <= nodeBBox.max().x(); ++x) { for (y = nodeBBox.min().y(); y <= nodeBBox.max().y(); ++y) { for (z = nodeBBox.min().z(); z <= nodeBBox.max().z(); ++z) { mask.setOn(static_cast(this->coordToOffset(xyz))); } } } // Set voxels that lie in the inactive region of the mask (i.e., outside // the clipping region) to the background value. for (MaskOffIterator maskIter = mask.beginOff(); maskIter; ++maskIter) { this->setValueOff(maskIter.pos(), background); } } //////////////////////////////////////// template inline void LeafNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif auto clippedBBox = this->getNodeBoundingBox(); clippedBBox.intersect(bbox); if (!clippedBBox) return; for (Int32 x = clippedBBox.min().x(); x <= clippedBBox.max().x(); ++x) { const Index offsetX = (x & (DIM-1u)) << 2*Log2Dim; for (Int32 y = clippedBBox.min().y(); y <= clippedBBox.max().y(); ++y) { const Index offsetXY = offsetX + ((y & (DIM-1u)) << Log2Dim); for (Int32 z = clippedBBox.min().z(); z <= clippedBBox.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 { #if OPENVDB_ABI_VERSION_NUMBER >= 3 mBuffer.loadValues(); #endif using DenseValueType = typename DenseT::ValueType; 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) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif using DenseValueType = typename DenseT::ValueType; 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::skipCompressedValues(bool seekable, std::istream& is, bool fromHalf) { if (seekable) { // Seek over voxel values. io::readCompressedValues( is, nullptr, SIZE, mValueMask, fromHalf); } else { // Read and discard voxel values. Buffer temp; io::readCompressedValues(is, temp.mData, SIZE, mValueMask, fromHalf); } } template inline void LeafNode::readBuffers(std::istream& is, bool fromHalf) { this->readBuffers(is, CoordBBox::inf(), fromHalf); } template inline void LeafNode::readBuffers(std::istream& is, const CoordBBox& clipBBox, bool fromHalf) { SharedPtr meta = io::getStreamMetadataPtr(is); const bool seekable = meta && meta->seekable(); #if OPENVDB_ABI_VERSION_NUMBER >= 3 std::streamoff maskpos = is.tellg(); #endif if (seekable) { // Seek over the value mask. mValueMask.seek(is); } else { // 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)); } CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. skipCompressedValues(seekable, is, fromHalf); mValueMask.setOff(); mBuffer.setOutOfCore(false); } else { #if OPENVDB_ABI_VERSION_NUMBER >= 3 // If this node lies completely inside the clipping region and it is being read // from a memory-mapped file, delay loading of its buffer until the buffer // is actually accessed. (If this node requires clipping, its buffer // must be accessed and therefore must be loaded.) io::MappedFile::Ptr mappedFile = io::getMappedFilePtr(is); const bool delayLoad = ((mappedFile.get() != nullptr) && clipBBox.isInside(nodeBBox)); if (delayLoad) { mBuffer.setOutOfCore(true); mBuffer.mFileInfo = new typename Buffer::FileInfo; mBuffer.mFileInfo->meta = meta; mBuffer.mFileInfo->bufpos = is.tellg(); mBuffer.mFileInfo->mapping = mappedFile; // Save the offset to the value mask, because the in-memory copy // might change before the value buffer gets read. mBuffer.mFileInfo->maskpos = maskpos; // Skip over voxel values. skipCompressedValues(seekable, is, fromHalf); } else { #endif mBuffer.allocate(); io::readCompressedValues(is, mBuffer.mData, SIZE, mValueMask, fromHalf); mBuffer.setOutOfCore(false); // Get this tree's background value. T background = zeroVal(); if (const void* bgPtr = io::getGridBackgroundValuePtr(is)) { background = *static_cast(bgPtr); } this->clip(clipBBox, background); #if OPENVDB_ABI_VERSION_NUMBER >= 3 } #endif } 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); mBuffer.loadValues(); 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.valueMask() && mBuffer == other.mBuffer; } template inline Index64 LeafNode::memUsage() const { // Use sizeof(*this) to capture alignment-related padding // (but note that sizeof(*this) includes sizeof(mBuffer)). return sizeof(*this) + mBuffer.memUsage() - sizeof(mBuffer); } 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& firstValue, bool& state, const ValueType& tolerance) const { if (!mValueMask.isConstant(state)) return false;// early termination firstValue = mBuffer[0]; for (Index i = 1; i < SIZE; ++i) { if ( !math::isApproxEqual(mBuffer[i], firstValue, tolerance) ) return false;// early termination } return true; } template inline bool LeafNode::isConstant(ValueType& minValue, ValueType& maxValue, bool& state, const ValueType& tolerance) const { if (!mValueMask.isConstant(state)) return false;// early termination minValue = maxValue = mBuffer[0]; for (Index i = 1; i < SIZE; ++i) { const T& v = mBuffer[i]; if (v < minValue) { if ((maxValue - v) > tolerance) return false;// early termination minValue = v; } else if (v > maxValue) { if ((v - minValue) > tolerance) return false;// early termination maxValue = v; } } return true; } template inline T LeafNode::medianAll(T *tmp) const { std::unique_ptr data(nullptr); if (tmp == nullptr) {//allocate temporary storage data.reset(new T[NUM_VALUES]); tmp = data.get(); } if (tmp != mBuffer.data()) { const T* src = mBuffer.data(); for (T* dst = tmp; dst-tmp < NUM_VALUES;) *dst++ = *src++; } static const size_t midpoint = (NUM_VALUES - 1) >> 1; std::nth_element(tmp, tmp + midpoint, tmp + NUM_VALUES); return tmp[midpoint]; } template inline Index LeafNode::medianOn(T &value, T *tmp) const { const Index count = mValueMask.countOn(); if (count == NUM_VALUES) {//special case: all voxels are active value = this->medianAll(tmp); return NUM_VALUES; } else if (count == 0) { return 0; } std::unique_ptr data(nullptr); if (tmp == nullptr) {//allocate temporary storage data.reset(new T[count]);// 0 < count < NUM_VALUES tmp = data.get(); } for (auto iter=this->cbeginValueOn(); iter; ++iter) *tmp++ = *iter; T *begin = tmp - count; const size_t midpoint = (count - 1) >> 1; std::nth_element(begin, begin + midpoint, tmp); value = begin[midpoint]; return count; } template inline Index LeafNode::medianOff(T &value, T *tmp) const { const Index count = mValueMask.countOff(); if (count == NUM_VALUES) {//special case: all voxels are inactive value = this->medianAll(tmp); return NUM_VALUES; } else if (count == 0) { return 0; } std::unique_ptr data(nullptr); if (tmp == nullptr) {//allocate temporary storage data.reset(new T[count]);// 0 < count < NUM_VALUES tmp = data.get(); } for (auto iter=this->cbeginValueOff(); iter; ++iter) *tmp++ = *iter; T *begin = tmp - count; const size_t midpoint = (count - 1) >> 1; std::nth_element(begin, begin + midpoint, tmp); value = begin[midpoint]; return count; } //////////////////////////////////////// template inline void LeafNode::addTile(Index /*level*/, const Coord& xyz, const ValueType& val, bool active) { 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::resetBackground(const ValueType& oldBackground, const ValueType& newBackground) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif 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) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy == MERGE_NODES) return; typename NodeMaskType::OnIterator iter = other.valueMask().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) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif 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.valueMask(); } template template inline void LeafNode::topologyIntersection(const LeafNode& other, const ValueType&) { mValueMask &= other.valueMask(); } template template inline void LeafNode::topologyDifference(const LeafNode& other, const ValueType&) { mValueMask &= !other.valueMask(); } template inline void LeafNode::negate() { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif for (Index i = 0; i < SIZE; ++i) { mBuffer[i] = -mBuffer[i]; } } //////////////////////////////////////// template template inline void LeafNode::combine(const LeafNode& other, CombineOp& op) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif CombineArgs args; for (Index i = 0; i < SIZE; ++i) { op(args.setARef(mBuffer[i]) .setAIsActive(mValueMask.isOn(i)) .setBRef(other.mBuffer[i]) .setBIsActive(other.valueMask().isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } template template inline void LeafNode::combine(const ValueType& value, bool valueIsActive, CombineOp& op) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif 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 OtherType& value, bool valueIsActive, CombineOp& op) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif CombineArgs args; args.setBRef(value).setBIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { op(args.setARef(other.mBuffer[i]) .setAIsActive(other.valueMask().isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } template template inline void LeafNode::combine2(const ValueType& value, const OtherNodeT& other, bool valueIsActive, CombineOp& op) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif CombineArgs args; args.setARef(value).setAIsActive(valueIsActive); for (Index i = 0; i < SIZE; ++i) { op(args.setBRef(other.mBuffer[i]) .setBIsActive(other.valueMask().isOn(i)) .setResultRef(mBuffer[i])); mValueMask.set(i, args.resultIsActive()); } } template template inline void LeafNode::combine2(const LeafNode& b0, const OtherNodeT& b1, CombineOp& op) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (!this->allocate()) return; #endif CombineArgs args; for (Index i = 0; i < SIZE; ++i) { mValueMask.set(i, b0.valueMask().isOn(i) || b1.valueMask().isOn(i)); op(args.setARef(b0.mBuffer[i]) .setAIsActive(b0.valueMask().isOn(i)) .setBRef(b1.mBuffer[i]) .setBIsActive(b1.valueMask().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. static_assert(OtherNodeT::SIZE == NodeT::SIZE, "can't visit nodes of different sizes simultaneously"); static_assert(OtherNodeT::LEVEL == NodeT::LEVEL, "can't visit nodes at different tree levels simultaneously"); 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" // Specialization for LeafNodes with mask information only #include "LeafNodeMask.h" #endif // OPENVDB_TREE_LEAFNODE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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/LeafNodeMask.h0000644000000000000000000017254213200122377014622 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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_LEAF_NODE_MASK_HAS_BEEN_INCLUDED #define OPENVDB_TREE_LEAF_NODE_MASK_HAS_BEEN_INCLUDED #include #include #include // for io::readData(), etc. #include // for math::isZero() #include #include "LeafNode.h" #include "Iterator.h" #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// @brief LeafNode specialization for values of type ValueMask that encodes both /// the active states and the boolean values of (2^Log2Dim)^3 voxels /// in a single bit mask, i.e. voxel values and states are indistinguishable! template class LeafNode { public: using LeafNodeType = LeafNode; using BuildType = ValueMask;// this is a rare case where using ValueType = bool;// value type != build type using Buffer = LeafBuffer;// buffer uses the bool specialization using NodeMaskType = util::NodeMask; using Ptr = SharedPtr; // 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 { using Type = LeafNode; }; /// @brief SameConfiguration::value is @c true if and only if /// OtherNodeType is the type of a LeafNode with the same dimensions as this node. template struct SameConfiguration { static const bool value = SameLeafConfig::value; }; /// Default constructor LeafNode(); /// Constructor /// @param xyz the coordinates of a voxel that lies within the node /// @param value the initial value = state for all of this node's voxels /// @param dummy dummy value explicit LeafNode(const Coord& xyz, bool value = false, bool dummy = false); #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// "Partial creation" constructor used during file input LeafNode(PartialCreate, const Coord& xyz, bool value = false, bool dummy = false); #endif /// Deep copy constructor LeafNode(const LeafNode&); /// Value conversion copy constructor template explicit LeafNode(const LeafNode& other); /// Topology copy constructor template LeafNode(const LeafNode& other, TopologyCopy); //@{ /// @brief 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; } /// 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 active voxels. Index64 onVoxelCount() const { return mBuffer.mData.countOn(); } /// Return the number of inactive voxels. Index64 offVoxelCount() const { return mBuffer.mData.countOff(); } Index64 onLeafVoxelCount() const { return this->onVoxelCount(); } Index64 offLeafVoxelCount() const { return this->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 mBuffer.mData.isOff(); } /// Return @c true if this node only contains active voxels. bool isDense() const { return mBuffer.mData.isOn(); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// @brief Return @c true if memory for this node's buffer has been allocated. /// @details Currently, boolean leaf nodes don't support partial creation, /// so this always returns @c true. bool isAllocated() const { return true; } /// @brief Allocate memory for this node's buffer if it has not already been allocated. /// @details Currently, boolean leaf nodes don't support partial creation, /// so this has no effect. bool allocate() { return true; } #endif /// 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& bbox, bool visitVoxels = true) 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; } //@{ /// 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); void readBuffers(std::istream& is, const CoordBBox&, 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); mBuffer.mData.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) { mBuffer.mData.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); mBuffer.mData.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() { mBuffer.mData.setOn(); } /// Mark all voxels as inactive but don't change their values. void setValuesOff() { mBuffer.mData.setOff(); } /// Return @c true if the voxel at the given coordinates is active. bool isValueOn(const Coord& xyz) const { return mBuffer.mData.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 mBuffer.mData.isOn(offset); } /// Return @c false since leaf nodes never contain tiles. static bool hasActiveTiles() { return false; } /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&, bool background); /// Set all voxels within an axis-aligned box to the specified value. void fill(const CoordBBox& bbox, bool value, bool = false); /// Set all voxels within an axis-aligned box to the specified value. void denseFill(const CoordBBox& bbox, bool value, bool = false) { this->fill(bbox, value); } /// Set the state of all voxels to the specified active state. void fill(const bool& value, bool dummy = false); /// @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 (mBuffer.mData.isOn(0)) return Buffer::sOn; else return Buffer::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 (mBuffer.mData.isOn(SIZE-1)) return Buffer::sOn; else return Buffer::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; /// @brief Computes the median value of all the active and inactive voxels in this node. /// @return The median value. /// /// @details The median for boolean values is defined as the mode /// of the values, i.e. the value that occurs most often. bool medianAll() const; /// @brief Computes the median value of all the active voxels in this node. /// @return The number of active voxels. /// /// @param value Updated with the median value of all the active voxels. /// /// @note Since the value and state are shared for this /// specialization of the LeafNode the @a value will always be true! Index medianOn(ValueType &value) const; /// @brief Computes the median value of all the inactive voxels in this node. /// @return The number of inactive voxels. /// /// @param value Updated with the median value of all the inactive /// voxels. /// /// @note Since the value and state are shared for this /// specialization of the LeafNode the @a value will always be false! Index medianOff(ValueType &value) const; /// Return @c true if all of this node's values are inactive. bool isInactive() const { return mBuffer.mData.isOff(); } /// @brief no-op since for this temaplte specialization voxel /// values and states are indistinguishable. void resetBackground(bool, bool) {} /// @brief Invert the bits of the voxels, i.e. states and values void negate() { mBuffer.mData.toggle(); } template void merge(const LeafNode& other, bool bg = false, bool otherBG = false); template void merge(bool tileValue, bool tileActive=false); /// @brief No-op /// @details This function exists only to enable template instantiation. void voxelizeActiveTiles(bool = true) {} /// @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, because it can deactivate all of this node's voxels, /// consider subsequently 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, const OtherType&, bool valueIsActive, CombineOp&); template void combine2(bool, const OtherNodeT& other, bool valueIsActive, CombineOp&); template void combine2(const LeafNode& b0, const OtherNodeT& 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 prune(const ValueType& /*tolerance*/ = zeroVal()) {} void addLeaf(LeafNode*) {} template void addLeafAndCache(LeafNode*, AccessorT&) {} template NodeT* stealNode(const Coord&, const ValueType&, bool) { return nullptr; } template NodeT* probeNode(const Coord&) { return nullptr; } template const NodeT* probeConstNode(const Coord&) const { return nullptr; } template void getNodes(ArrayT&) const {} template void stealNodes(ArrayT&, const ValueType&, bool) {} //@} 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 (!(std::is_same::value)) return nullptr; 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 (!(std::is_same::value)) return nullptr; return reinterpret_cast(this); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //@} // // Iterators // protected: using MaskOnIter = typename NodeMaskType::OnIterator; using MaskOffIter = typename NodeMaskType::OffIterator; using MaskDenseIter = typename NodeMaskType::DenseIterator; 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> { using BaseT = SparseIteratorBase; 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> { using BaseT = DenseIteratorBase; using NonConstValueT = typename BaseT::NonConstValueType; 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 = nullptr; 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: using ValueOnIter = ValueIter; using ValueOnCIter = ValueIter; using ValueOffIter = ValueIter; using ValueOffCIter = ValueIter; using ValueAllIter = ValueIter; using ValueAllCIter = ValueIter; using ChildOnIter = ChildIter; using ChildOnCIter = ChildIter; using ChildOffIter = ChildIter; using ChildOffCIter = ChildIter; using ChildAllIter = DenseIter; using ChildAllCIter = DenseIter; ValueOnCIter cbeginValueOn() const { return ValueOnCIter(mBuffer.mData.beginOn(), this); } ValueOnCIter beginValueOn() const { return ValueOnCIter(mBuffer.mData.beginOn(), this); } ValueOnIter beginValueOn() { return ValueOnIter(mBuffer.mData.beginOn(), this); } ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mBuffer.mData.beginOff(), this); } ValueOffCIter beginValueOff() const { return ValueOffCIter(mBuffer.mData.beginOff(), this); } ValueOffIter beginValueOff() { return ValueOffIter(mBuffer.mData.beginOff(), this); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mBuffer.mData.beginDense(), this); } ValueAllCIter beginValueAll() const { return ValueAllCIter(mBuffer.mData.beginDense(), this); } ValueAllIter beginValueAll() { return ValueAllIter(mBuffer.mData.beginDense(), this); } ValueOnCIter cendValueOn() const { return ValueOnCIter(mBuffer.mData.endOn(), this); } ValueOnCIter endValueOn() const { return ValueOnCIter(mBuffer.mData.endOn(), this); } ValueOnIter endValueOn() { return ValueOnIter(mBuffer.mData.endOn(), this); } ValueOffCIter cendValueOff() const { return ValueOffCIter(mBuffer.mData.endOff(), this); } ValueOffCIter endValueOff() const { return ValueOffCIter(mBuffer.mData.endOff(), this); } ValueOffIter endValueOff() { return ValueOffIter(mBuffer.mData.endOff(), this); } ValueAllCIter cendValueAll() const { return ValueAllCIter(mBuffer.mData.endDense(), this); } ValueAllCIter endValueAll() const { return ValueAllCIter(mBuffer.mData.endDense(), this); } ValueAllIter endValueAll() { return ValueAllIter(mBuffer.mData.endDense(), this); } // Note that [c]beginChildOn() and [c]beginChildOff() actually return end iterators, // because leaf nodes have no children. ChildOnCIter cbeginChildOn() const { return ChildOnCIter(mBuffer.mData.endOn(), this); } ChildOnCIter beginChildOn() const { return ChildOnCIter(mBuffer.mData.endOn(), this); } ChildOnIter beginChildOn() { return ChildOnIter(mBuffer.mData.endOn(), this); } ChildOffCIter cbeginChildOff() const { return ChildOffCIter(mBuffer.mData.endOff(), this); } ChildOffCIter beginChildOff() const { return ChildOffCIter(mBuffer.mData.endOff(), this); } ChildOffIter beginChildOff() { return ChildOffIter(mBuffer.mData.endOff(), this); } ChildAllCIter cbeginChildAll() const { return ChildAllCIter(mBuffer.mData.beginDense(), this); } ChildAllCIter beginChildAll() const { return ChildAllCIter(mBuffer.mData.beginDense(), this); } ChildAllIter beginChildAll() { return ChildAllIter(mBuffer.mData.beginDense(), this); } ChildOnCIter cendChildOn() const { return ChildOnCIter(mBuffer.mData.endOn(), this); } ChildOnCIter endChildOn() const { return ChildOnCIter(mBuffer.mData.endOn(), this); } ChildOnIter endChildOn() { return ChildOnIter(mBuffer.mData.endOn(), this); } ChildOffCIter cendChildOff() const { return ChildOffCIter(mBuffer.mData.endOff(), this); } ChildOffCIter endChildOff() const { return ChildOffCIter(mBuffer.mData.endOff(), this); } ChildOffIter endChildOff() { return ChildOffIter(mBuffer.mData.endOff(), this); } ChildAllCIter cendChildAll() const { return ChildAllCIter(mBuffer.mData.endDense(), this); } ChildAllCIter endChildAll() const { return ChildAllCIter(mBuffer.mData.endDense(), this); } ChildAllIter endChildAll() { return ChildAllIter(mBuffer.mData.endDense(), this); } // // Mask accessors // bool isValueMaskOn(Index n) const { return mBuffer.mData.isOn(n); } bool isValueMaskOn() const { return mBuffer.mData.isOn(); } bool isValueMaskOff(Index n) const { return mBuffer.mData.isOff(n); } bool isValueMaskOff() const { return mBuffer.mData.isOff(); } const NodeMaskType& getValueMask() const { return mBuffer.mData; } const NodeMaskType& valueMask() const { return mBuffer.mData; } NodeMaskType& getValueMask() { return mBuffer.mData; } void setValueMask(const NodeMaskType& mask) { mBuffer.mData = 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) { mBuffer.mData.set(n, on); } void setValueMaskOn(Index n) { mBuffer.mData.setOn(n); } void setValueMaskOff(Index n) { mBuffer.mData.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 representing the values AND state of voxels Buffer mBuffer; /// Global grid index coordinates (x,y,z) of the local origin of this node Coord mOrigin; 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; //@} template friend class LeafBuffer; }; // class LeafNode //////////////////////////////////////// template inline LeafNode::LeafNode() : mOrigin(0, 0, 0) { } template inline LeafNode::LeafNode(const Coord& xyz, bool value, bool active) : mBuffer(value || active) , mOrigin(xyz & (~(DIM - 1))) { } #if OPENVDB_ABI_VERSION_NUMBER >= 3 template inline LeafNode::LeafNode(PartialCreate, const Coord& xyz, bool value, bool active) : mBuffer(value || active) , mOrigin(xyz & (~(DIM - 1))) { } #endif template inline LeafNode::LeafNode(const LeafNode &other) : mBuffer(other.mBuffer) , mOrigin(other.mOrigin) { } // Copy-construct from a leaf node with the same configuration but a different ValueType. template template inline LeafNode::LeafNode(const LeafNode& other) : mBuffer(other.valueMask()) , mOrigin(other.origin()) { } template template inline LeafNode::LeafNode(const LeafNode& other, bool, TopologyCopy) : mBuffer(other.valueMask())// value = active state , mOrigin(other.origin()) { } template template inline LeafNode::LeafNode(const LeafNode& other, TopologyCopy) : mBuffer(other.valueMask())// value = active state , mOrigin(other.origin()) { } template template inline LeafNode::LeafNode(const LeafNode& other, bool offValue, bool onValue, TopologyCopy) : mBuffer(other.valueMask()) , mOrigin(other.origin()) { if (offValue==true) { if (onValue==false) { mBuffer.mData.toggle(); } else { mBuffer.mData.setOn(); } } } template inline LeafNode::~LeafNode() { } //////////////////////////////////////// template inline Index64 LeafNode::memUsage() const { // Use sizeof(*this) to capture alignment-related padding return sizeof(*this); } 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 && mBuffer.mData == other->getValueMask()); } template inline std::string LeafNode::str() const { std::ostringstream ostr; ostr << "LeafNode @" << mOrigin << ": "; for (Index32 n = 0; n < SIZE; ++n) ostr << (mBuffer.mData.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*/) { mBuffer.mData.load(is); } template inline void LeafNode::writeTopology(std::ostream& os, bool /*toHalf*/) const { mBuffer.mData.save(os); } template inline void LeafNode::readBuffers(std::istream& is, const CoordBBox& clipBBox, bool fromHalf) { // Boolean LeafNodes don't currently implement lazy loading. // Instead, load the full buffer, then clip it. this->readBuffers(is, fromHalf); // Get this tree's background value. bool background = false; if (const void* bgPtr = io::getGridBackgroundValuePtr(is)) { background = *static_cast(bgPtr); } this->clip(clipBBox, background); } template inline void LeafNode::readBuffers(std::istream& is, bool /*fromHalf*/) { // Read in the value mask = buffer. mBuffer.mData.load(is); // Read in the origin. is.read(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); } template inline void LeafNode::writeBuffers(std::ostream& os, bool /*toHalf*/) const { // Write out the value mask = buffer. mBuffer.mData.save(os); // Write out the origin. os.write(reinterpret_cast(&mOrigin), sizeof(Coord::ValueType) * 3); } //////////////////////////////////////// template inline bool LeafNode::operator==(const LeafNode& other) const { return mOrigin == other.mOrigin && mBuffer == other.mBuffer; } template inline bool LeafNode::operator!=(const LeafNode& other) const { return !(this->operator==(other)); } //////////////////////////////////////// template inline bool LeafNode::isConstant(bool& constValue, bool& state, bool) const { if (!mBuffer.mData.isConstant(state)) return false; constValue = state; return true; } //////////////////////////////////////// template inline bool LeafNode::medianAll() const { const Index countTrue = mBuffer.mData.countOn(); return countTrue > (NUM_VALUES >> 1); } template inline Index LeafNode::medianOn(bool& state) const { const Index countTrueOn = mBuffer.mData.countOn(); state = true;//since value and state are the same for this specialization of the leaf node return countTrueOn; } template inline Index LeafNode::medianOff(bool& state) const { const Index countFalseOff = mBuffer.mData.countOff(); state = false;//since value and state are the same for this specialization of the leaf node return countFalseOff; } //////////////////////////////////////// template inline void LeafNode::addTile(Index /*level*/, const Coord& xyz, bool val, bool active) { this->addTile(this->coordToOffset(xyz), val, active); } template inline void LeafNode::addTile(Index offset, bool val, bool active) { assert(offset < SIZE); this->setValueOnly(offset, val); this->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 Buffer::sOn; else return Buffer::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 Buffer::sOn; else return Buffer::sOff; } template inline bool LeafNode::probeValue(const Coord& xyz, bool& val) const { const Index offset = this->coordToOffset(xyz); val = mBuffer.mData.isOn(offset); return val; } 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); 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) { mBuffer.mData.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); 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); } 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 = val; op(val, state); mBuffer.mData.set(offset, val); } //////////////////////////////////////// template template inline void LeafNode::merge(const LeafNode& other, bool /*bg*/, bool /*otherBG*/) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy == MERGE_NODES) return; mBuffer.mData |= other.mBuffer.mData; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } template template inline void LeafNode::merge(bool tileValue, bool) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (Policy != MERGE_ACTIVE_STATES_AND_NODES) return; if (tileValue) mBuffer.mData.setOn(); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// template template inline void LeafNode::topologyUnion(const LeafNode& other) { mBuffer.mData |= other.valueMask(); } template template inline void LeafNode::topologyIntersection(const LeafNode& other, const bool&) { mBuffer.mData &= other.valueMask(); } template template inline void LeafNode::topologyDifference(const LeafNode& other, const bool&) { mBuffer.mData &= !other.valueMask(); } //////////////////////////////////////// template inline void LeafNode::clip(const CoordBBox& clipBBox, bool background) { CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. Fill it with background tiles. this->fill(nodeBBox, background, /*active=*/false); } else if (clipBBox.isInside(nodeBBox)) { // This node lies completely inside the clipping region. Leave it intact. return; } // This node isn't completely contained inside the clipping region. // Set any voxels that lie outside the region to the background value. // Construct a boolean mask that is on inside the clipping region and off outside it. NodeMaskType mask; nodeBBox.intersect(clipBBox); Coord xyz; int &x = xyz.x(), &y = xyz.y(), &z = xyz.z(); for (x = nodeBBox.min().x(); x <= nodeBBox.max().x(); ++x) { for (y = nodeBBox.min().y(); y <= nodeBBox.max().y(); ++y) { for (z = nodeBBox.min().z(); z <= nodeBBox.max().z(); ++z) { mask.setOn(static_cast(this->coordToOffset(xyz))); } } } // Set voxels that lie in the inactive region of the mask (i.e., outside // the clipping region) to the background value. for (MaskOffIter maskIter = mask.beginOff(); maskIter; ++maskIter) { this->setValueOff(maskIter.pos(), background); } } //////////////////////////////////////// template inline void LeafNode::fill(const CoordBBox& bbox, bool value, bool) { auto clippedBBox = this->getNodeBoundingBox(); clippedBBox.intersect(bbox); if (!clippedBBox) return; for (Int32 x = clippedBBox.min().x(); x <= clippedBBox.max().x(); ++x) { const Index offsetX = (x & (DIM-1u))<<2*Log2Dim; for (Int32 y = clippedBBox.min().y(); y <= clippedBBox.max().y(); ++y) { const Index offsetXY = offsetX + ((y & (DIM-1u))<< Log2Dim); for (Int32 z = clippedBBox.min().z(); z <= clippedBBox.max().z(); ++z) { const Index offset = offsetXY + (z & (DIM-1u)); mBuffer.mData.set(offset, value); } } } } template inline void LeafNode::fill(const bool& value, bool) { mBuffer.fill(value); } //////////////////////////////////////// template template inline void LeafNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const { using DenseValueType = typename DenseT::ValueType; 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) { using DenseValueType = typename DenseT::ValueType; struct Local { inline static bool toBool(const DenseValueType& v) { return !math::isZero(v); } }; 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 == Local::toBool(*s2))) { mBuffer.mData.set(n2, background); } else { mBuffer.mData.set(n2, Local::toBool(*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(aVal) .setBRef(bVal) .setBIsActive(bVal) .setResultRef(result)); 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(aVal) .setResultRef(result)); mBuffer.mData.set(i, result); } } //////////////////////////////////////// template template inline void LeafNode::combine2(const LeafNode& other, const OtherType& 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(aVal) .setResultRef(result)); mBuffer.mData.set(i, result); } } template template inline void LeafNode::combine2(bool value, const OtherNodeT& 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(bVal) .setResultRef(result)); mBuffer.mData.set(i, result); } } template template inline void LeafNode::combine2(const LeafNode& b0, const OtherNodeT& b1, CombineOp& op) { CombineArgs args; for (Index i = 0; i < SIZE; ++i) { bool result = false, b0Val = b0.mBuffer.mData.isOn(i), b1Val = b1.mBuffer.mData.isOn(i); op(args.setARef(b0Val) .setAIsActive(b0Val) .setBRef(b1Val) .setBIsActive(b1Val) .setResultRef(result)); 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::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. static_assert(OtherNodeT::SIZE == NodeT::SIZE, "can't visit nodes of different sizes simultaneously"); static_assert(OtherNodeT::LEVEL == NodeT::LEVEL, "can't visit nodes at different tree levels simultaneously"); 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_LEAF_NODE_MASK_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000001754313200122377014226 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 /// /// @details 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 when possible, since the two are never in use simultaneously. #ifndef OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED #define OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED #include #include // for std::memcpy() #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { #if OPENVDB_ABI_VERSION_NUMBER >= 4 // Forward declaration of traits class template struct CopyTraits; // Default implementation that stores the child pointer and the value separately // (i.e., not in a union) // This implementation is not used for POD, math::Vec or math::Coord value types. template class NodeUnion { private: ChildT* mChild; ValueT mValue; public: NodeUnion(): mChild(nullptr), mValue() {} ChildT* getChild() const { return mChild; } void setChild(ChildT* child) { mChild = child; } const ValueT& getValue() const { return mValue; } ValueT& getValue() { return mValue; } void setValue(const ValueT& val) { mValue = val; } }; // Template specialization for values of POD types (int, float, pointer, etc.) template class NodeUnion::value>::type> { private: union { ChildT* mChild; ValueT mValue; }; public: NodeUnion(): mChild(nullptr) {} ChildT* getChild() const { return mChild; } void setChild(ChildT* child) { mChild = child; } const ValueT& getValue() const { return mValue; } ValueT& getValue() { return mValue; } void setValue(const ValueT& val) { mValue = val; } }; // Template specialization for values of types such as math::Vec3f and math::Coord // for which CopyTraits::IsCopyable is true template class NodeUnion::IsCopyable>::type> { private: union { ChildT* mChild; ValueT mValue; }; public: NodeUnion(): mChild(nullptr) {} NodeUnion(const NodeUnion& other): mChild(nullptr) { std::memcpy(this, &other, sizeof(*this)); } NodeUnion& operator=(const NodeUnion& rhs) { std::memcpy(this, &rhs, sizeof(*this)); return *this; } ChildT* getChild() const { return mChild; } void setChild(ChildT* child) { mChild = child; } const ValueT& getValue() const { return mValue; } ValueT& getValue() { return mValue; } void setValue(const ValueT& val) { mValue = val; } }; /// @details A type T is copyable if /// # T stores member values by value (vs. by pointer or reference) /// and T's true byte size is given by sizeof(T). /// # T has a trivial destructor /// # T has a default constructor /// # T has an assignment operator template struct CopyTraits { static const bool IsCopyable = false; }; template struct CopyTraits> { static const bool IsCopyable = true; }; template struct CopyTraits> { static const bool IsCopyable = true; }; template struct CopyTraits> { static const bool IsCopyable = true; }; template<> struct CopyTraits { static const bool IsCopyable = true; }; //////////////////////////////////////// #else // OPENVDB_ABI_VERSION_NUMBER <= 3 // Prior to OpenVDB 4 and the introduction of C++11, values of non-POD types // were heap-allocated and stored by pointer due to C++98 restrictions on unions. // 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() { mUnion.child = nullptr; } ChildT* getChild() const { return mUnion.child; } void setChild(ChildT* child) { mUnion.child = child; } const ValueT& getValue() const { return mUnion.value; } ValueT& getValue() { return mUnion.value; } 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) { this->setChild(nullptr); } NodeUnionImpl(const NodeUnionImpl& other) : mHasChild(true) { if (other.mHasChild) { this->setChild(other.getChild()); } else { this->setValue(other.getValue()); } } NodeUnionImpl& operator=(const NodeUnionImpl& other) { if (other.mHasChild) { this->setChild(other.getChild()); } else { this->setValue(other.getValue()); } return *this; } ~NodeUnionImpl() { this->setChild(nullptr); } ChildT* getChild() const { return mHasChild ? mUnion.child : nullptr; } 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) { if (!mHasChild) delete mUnion.value; mUnion.value = new ValueT(val); mHasChild = false; } }; template struct NodeUnion: public NodeUnionImpl::value, ValueT, ChildT> { NodeUnion() {} }; #endif } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_NODEUNION_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000020024113200122377014606 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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_LEAF_NODE_BOOL_HAS_BEEN_INCLUDED #define OPENVDB_TREE_LEAF_NODE_BOOL_HAS_BEEN_INCLUDED #include #include // for io::readData(), etc. #include // for math::isZero() #include #include "LeafNode.h" #include "Iterator.h" #include #include #include #include #include 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: using LeafNodeType = LeafNode; using BuildType = bool; using ValueType = bool; using Buffer = LeafBuffer; using NodeMaskType = util::NodeMask; using Ptr = SharedPtr; // 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 { using Type = LeafNode; }; /// @brief SameConfiguration::value is @c true if and only if /// OtherNodeType is the type of a LeafNode with the same dimensions as this node. template struct SameConfiguration { static const bool value = SameLeafConfig::value; }; /// 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); #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// "Partial creation" constructor used during file input LeafNode(PartialCreate, const Coord& xyz, bool value = false, bool active = false); #endif /// Deep copy constructor LeafNode(const LeafNode&); /// Deep assignment operator LeafNode& operator=(const LeafNode&) = default; /// Value conversion copy constructor template explicit LeafNode(const LeafNode& other); /// Topology copy constructor template LeafNode(const LeafNode& other, TopologyCopy); //@{ /// @brief 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(); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// @brief Return @c true if memory for this node's buffer has been allocated. /// @details Currently, boolean leaf nodes don't support partial creation, /// so this always returns @c true. bool isAllocated() const { return true; } /// @brief Allocate memory for this node's buffer if it has not already been allocated. /// @details Currently, boolean leaf nodes don't support partial creation, /// so this has no effect. bool allocate() { return true; } #endif /// 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& bbox, bool visitVoxels = true) 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; } //@{ /// 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); void readBuffers(std::istream& is, const CoordBBox&, 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 that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&, bool background); /// 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 within an axis-aligned box to the specified value and active state. void denseFill(const CoordBBox& bbox, bool val, bool on = true) { this->fill(bbox, val, on); } /// 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 Buffer::sOn; else return Buffer::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 Buffer::sOn; else return Buffer::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; /// @brief Computes the median value of all the active and inactive voxels in this node. /// @return The median value. /// /// @details The median for boolean values is defined as the mode /// of the values, i.e. the value that occurs most often. bool medianAll() const; /// @brief Computes the median value of all the active voxels in this node. /// @return The number of active voxels. /// @param value Updated with the median value of all the active voxels. /// /// @details The median for boolean values is defined as the mode /// of the values, i.e. the value that occurs most often. Index medianOn(ValueType &value) const; /// @brief Computes the median value of all the inactive voxels in this node. /// @return The number of inactive voxels. /// @param value Updated with the median value of all the inactive voxels. /// /// @details The median for boolean values is defined as the mode /// of the values, i.e. the value that occurs most often. Index medianOff(ValueType &value) 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); /// @brief No-op /// @details This function exists only to enable template instantiation. void voxelizeActiveTiles(bool = true) {} /// @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, because it can deactivate all of this node's voxels, /// consider subsequently 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, const OtherType&, bool valueIsActive, CombineOp&); template void combine2(bool, const OtherNodeT& other, bool valueIsActive, CombineOp&); template void combine2(const LeafNode& b0, const OtherNodeT& 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 prune(const ValueType& /*tolerance*/ = zeroVal()) {} void addLeaf(LeafNode*) {} template void addLeafAndCache(LeafNode*, AccessorT&) {} template NodeT* stealNode(const Coord&, const ValueType&, bool) { return nullptr; } template NodeT* probeNode(const Coord&) { return nullptr; } template const NodeT* probeConstNode(const Coord&) const { return nullptr; } template void getNodes(ArrayT&) const {} template void stealNodes(ArrayT&, const ValueType&, bool) {} //@} 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 (!(std::is_same::value)) return nullptr; 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 (!(std::is_same::value)) return nullptr; return reinterpret_cast(this); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //@} // // Iterators // protected: using MaskOnIter = typename NodeMaskType::OnIterator; using MaskOffIter = typename NodeMaskType::OffIterator; using MaskDenseIter = typename NodeMaskType::DenseIterator; 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> { using BaseT = SparseIteratorBase; 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> { using BaseT = DenseIteratorBase; using NonConstValueT = typename BaseT::NonConstValueType; 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 = nullptr; 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: using ValueOnIter = ValueIter; using ValueOnCIter = ValueIter; using ValueOffIter = ValueIter; using ValueOffCIter = ValueIter; using ValueAllIter = ValueIter; using ValueAllCIter = ValueIter; using ChildOnIter = ChildIter; using ChildOnCIter = ChildIter; using ChildOffIter = ChildIter; using ChildOffCIter = ChildIter; using ChildAllIter = DenseIter; using ChildAllCIter = DenseIter; 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; } const NodeMaskType& valueMask() 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; 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 //////////////////////////////////////// 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))) { } #if OPENVDB_ABI_VERSION_NUMBER >= 3 template inline LeafNode::LeafNode(PartialCreate, const Coord& xyz, bool value, bool active) : mValueMask(active) , mBuffer(value) , mOrigin(xyz & (~(DIM - 1))) { /// @todo For now, this is identical to the non-PartialCreate constructor. /// Consider modifying the Buffer class to allow it to be constructed /// without allocating a bitmask. } #endif template inline LeafNode::LeafNode(const LeafNode &other) : mValueMask(other.valueMask()) , mBuffer(other.mBuffer) , mOrigin(other.mOrigin) { } // Copy-construct from a leaf node with the same configuration but a different ValueType. template template inline LeafNode::LeafNode(const LeafNode& other) : mValueMask(other.valueMask()) , mOrigin(other.origin()) { struct Local { /// @todo Consider using a value conversion functor passed as an argument instead. static inline bool convertValue(const ValueT& val) { return bool(val); } }; for (Index i = 0; i < SIZE; ++i) { mBuffer.setValue(i, Local::convertValue(other.mBuffer[i])); } } template template inline LeafNode::LeafNode(const LeafNode& other, bool background, TopologyCopy) : mValueMask(other.valueMask()) , mBuffer(background) , mOrigin(other.origin()) { } template template inline LeafNode::LeafNode(const LeafNode& other, TopologyCopy) : mValueMask(other.valueMask()) , mBuffer(other.valueMask())// value = active state , mOrigin(other.origin()) { } template template inline LeafNode::LeafNode(const LeafNode& other, bool offValue, bool onValue, TopologyCopy) : mValueMask(other.valueMask()) , mBuffer(other.valueMask()) , mOrigin(other.origin()) { if (offValue) { if (!onValue) mBuffer.mData.toggle(); else mBuffer.mData.setOn(); } } template inline LeafNode::~LeafNode() { } //////////////////////////////////////// template inline Index64 LeafNode::memUsage() const { // Use sizeof(*this) to capture alignment-related padding return sizeof(*this); } 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, const CoordBBox& clipBBox, bool fromHalf) { // Boolean LeafNodes don't currently implement lazy loading. // Instead, load the full buffer, then clip it. this->readBuffers(is, fromHalf); // Get this tree's background value. bool background = false; if (const void* bgPtr = io::getGridBackgroundValuePtr(is)) { background = *static_cast(bgPtr); } this->clip(clipBBox, background); } 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.) std::unique_ptr 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 mOrigin == other.mOrigin && mValueMask == other.valueMask() && mBuffer == other.mBuffer; } 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 { if (!mValueMask.isConstant(state)) 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; constValue = mBuffer.mData.isOn(); return true; } //////////////////////////////////////// template inline bool LeafNode::medianAll() const { const Index countTrue = mBuffer.mData.countOn(); return countTrue > (NUM_VALUES >> 1); } template inline Index LeafNode::medianOn(bool& state) const { const NodeMaskType tmp = mBuffer.mData & mValueMask;//both true and active const Index countTrueOn = tmp.countOn(), countOn = mValueMask.countOn(); state = countTrueOn > (NUM_VALUES >> 1); return countOn; } template inline Index LeafNode::medianOff(bool& state) const { const NodeMaskType tmp = mBuffer.mData & (!mValueMask);//both true and inactive const Index countTrueOff = tmp.countOn(), countOff = mValueMask.countOff(); state = countTrueOff > (NUM_VALUES >> 1); return countOff; } //////////////////////////////////////// template inline void LeafNode::addTile(Index /*level*/, const Coord& xyz, bool val, bool active) { this->addTile(this->coordToOffset(xyz), val, active); } template inline void LeafNode::addTile(Index offset, bool val, bool active) { assert(offset < SIZE); this->setValueOnly(offset, val); this->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 Buffer::sOn; else return Buffer::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 Buffer::sOn; else return Buffer::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.valueMask().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.valueMask(); } template template inline void LeafNode::topologyIntersection(const LeafNode& other, const bool&) { mValueMask &= other.valueMask(); } template template inline void LeafNode::topologyDifference(const LeafNode& other, const bool&) { mValueMask &= !other.valueMask(); } //////////////////////////////////////// template inline void LeafNode::clip(const CoordBBox& clipBBox, bool background) { CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. Fill it with background tiles. this->fill(nodeBBox, background, /*active=*/false); } else if (clipBBox.isInside(nodeBBox)) { // This node lies completely inside the clipping region. Leave it intact. return; } // This node isn't completely contained inside the clipping region. // Set any voxels that lie outside the region to the background value. // Construct a boolean mask that is on inside the clipping region and off outside it. NodeMaskType mask; nodeBBox.intersect(clipBBox); Coord xyz; int &x = xyz.x(), &y = xyz.y(), &z = xyz.z(); for (x = nodeBBox.min().x(); x <= nodeBBox.max().x(); ++x) { for (y = nodeBBox.min().y(); y <= nodeBBox.max().y(); ++y) { for (z = nodeBBox.min().z(); z <= nodeBBox.max().z(); ++z) { mask.setOn(static_cast(this->coordToOffset(xyz))); } } } // Set voxels that lie in the inactive region of the mask (i.e., outside // the clipping region) to the background value. for (MaskOffIter maskIter = mask.beginOff(); maskIter; ++maskIter) { this->setValueOff(maskIter.pos(), background); } } //////////////////////////////////////// template inline void LeafNode::fill(const CoordBBox& bbox, bool value, bool active) { auto clippedBBox = this->getNodeBoundingBox(); clippedBBox.intersect(bbox); if (!clippedBBox) return; for (Int32 x = clippedBBox.min().x(); x <= clippedBBox.max().x(); ++x) { const Index offsetX = (x & (DIM-1u))<<2*Log2Dim; for (Int32 y = clippedBBox.min().y(); y <= clippedBBox.max().y(); ++y) { const Index offsetXY = offsetX + ((y & (DIM-1u))<< Log2Dim); for (Int32 z = clippedBBox.min().z(); z <= clippedBBox.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 { using DenseValueType = typename DenseT::ValueType; 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) { using DenseValueType = typename DenseT::ValueType; struct Local { inline static bool toBool(const DenseValueType& v) { return !math::isZero(v); } }; 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 == Local::toBool(*s2))) { mValueMask.setOff(n2); mBuffer.mData.set(n2, background); } else { mValueMask.setOn(n2); mBuffer.mData.set(n2, Local::toBool(*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.valueMask().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, const OtherType& 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.valueMask().isOn(i)) .setResultRef(result)); mValueMask.set(i, args.resultIsActive()); mBuffer.mData.set(i, result); } } template template inline void LeafNode::combine2(bool value, const OtherNodeT& 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.valueMask().isOn(i)) .setResultRef(result)); mValueMask.set(i, args.resultIsActive()); mBuffer.mData.set(i, result); } } template template inline void LeafNode::combine2(const LeafNode& b0, const OtherNodeT& 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.valueMask().isOn(i) || b1.valueMask().isOn(i)); bool result = false, b0Val = b0.mBuffer.mData.isOn(i), b1Val = b1.mBuffer.mData.isOn(i); op(args.setARef(b0Val) .setAIsActive(b0.valueMask().isOn(i)) .setBRef(b1Val) .setBIsActive(b1.valueMask().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::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. static_assert(OtherNodeT::SIZE == NodeT::SIZE, "can't visit nodes of different sizes simultaneously"); static_assert(OtherNodeT::LEVEL == NodeT::LEVEL, "can't visit nodes at different tree levels simultaneously"); 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_LEAF_NODE_BOOL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000003161513200122377014115 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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(nullptr) {} IteratorBase(const MaskIterT& iter, NodeT* parent): mParentNode(parent), mMaskIter(iter) {} IteratorBase(const IteratorBase&) = default; IteratorBase& operator=(const IteratorBase&) = default; 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 nullptr and, in @a value, the value to which this iterator is pointing. SetItemT* probeChild(NonConstValueType& value) const { SetItemT* child = nullptr; 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 != nullptr); } /// @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 = nullptr; 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-2017 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.h0000644000000000000000000014776413200122377014752 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 _ValueIterT ValueIterT; 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.root())); mValueIterList.setIter(IterTraits::begin(tree.root())); 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) { bool recurse = false; do { recurse = false; 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 just advance its child iterator. mChildIterList.next(mLevel); 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 #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); dontIncrement = true; recurse = true; } } while (recurse); 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); } template void getNode(const 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.root())); } 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.root())); // 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-2017 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.h0000644000000000000000000037566413200122377014725 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for io::readCompressedValues(), etc. #include // for math::isExactlyEqual(), etc. #include #include #include "Iterator.h" #include "NodeUnion.h" #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { template struct SameInternalConfig; // forward declaration template class InternalNode { public: using ChildNodeType = _ChildNodeType; using LeafNodeType = typename ChildNodeType::LeafNodeType; using ValueType = typename ChildNodeType::ValueType; using BuildType = typename ChildNodeType::BuildType; using UnionType = NodeUnion; using NodeMaskType = util::NodeMask; static const Index LOG2DIM = Log2Dim, // log2 of tile count in one dimension TOTAL = Log2Dim + ChildNodeType::TOTAL, // log2 of voxel count in one dimension DIM = 1 << TOTAL, // total voxel count in one dimension NUM_VALUES = 1 << (3 * Log2Dim), // total voxel count represented by this node LEVEL = 1 + ChildNodeType::LEVEL; // level 0 = leaf static const Index64 NUM_VOXELS = uint64_t(1) << (3 * TOTAL); // total voxel count 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 { using Type = InternalNode::Type, Log2Dim>; }; /// @brief SameConfiguration::value is @c true if and only if OtherNodeType /// is the type of an InternalNode with the same dimensions as this node and whose /// ChildNodeType has the same configuration as this node's ChildNodeType. template struct SameConfiguration { static const bool value = SameInternalConfig::value; }; /// @brief Default constructor /// @warning The resulting InternalNode is uninitialized InternalNode() {} /// @brief Constructor of an InternalNode with dense inactive tiles of the specified value. /// @param offValue Background value used for inactive values explicit InternalNode(const ValueType& offValue); /// @brief Constructs an InternalNode with dense tiles /// @param origin The location in index space of the fist tile value /// @param fillValue Value assigned to all the tiles /// @param active State assigned to all the tiles InternalNode(const Coord& origin, const ValueType& fillValue, bool active = false); #if OPENVDB_ABI_VERSION_NUMBER >= 3 InternalNode(PartialCreate, const Coord&, const ValueType& fillValue, bool active = false); #endif /// @brief Deep copy constructor /// /// @note This method is multi-threaded! InternalNode(const InternalNode&); /// @brief Value conversion copy constructor /// /// @note This method is multi-threaded! template explicit InternalNode(const InternalNode& other); /// @brief Topology copy constructor /// /// @note This method is multi-threaded! template InternalNode(const InternalNode& other, const ValueType& background, TopologyCopy); /// @brief Topology copy constructor /// /// @note This method is multi-threaded! template InternalNode(const InternalNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy); #if OPENVDB_ABI_VERSION_NUMBER < 5 virtual ~InternalNode(); #else ~InternalNode(); #endif protected: using MaskOnIterator = typename NodeMaskType::OnIterator; using MaskOffIterator = typename NodeMaskType::OffIterator; using MaskDenseIterator = typename NodeMaskType::DenseIterator; // 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. // Sparse iterator that visits child nodes of an InternalNode 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 { assert(this->parent().isChildMaskOn(pos)); 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 // Sparse iterator that visits tile values of an InternalNode 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 // Dense iterator that visits both tiles and child nodes of an InternalNode template struct DenseIter: public DenseIteratorBase< MaskDenseIterator, DenseIter, NodeT, ChildT, ValueT> { using BaseT = DenseIteratorBase; using NonConstValueT = typename BaseT::NonConstValueType; DenseIter() {} DenseIter(const MaskDenseIterator& iter, NodeT* parent): DenseIteratorBase(iter, parent) {} bool getItem(Index pos, ChildT*& child, NonConstValueT& value) const { if (this->parent().isChildMaskOn(pos)) { child = this->parent().getChildNode(pos); return true; } child = nullptr; value = this->parent().mNodes[pos].getValue(); return false; } // 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) using ChildOnIter = ChildIter; using ChildOnCIter = ChildIter; using ChildOffIter = ValueIter; using ChildOffCIter = ValueIter; using ChildAllIter = DenseIter; using ChildAllCIter = DenseIter; using ValueOnIter = ValueIter; using ValueOnCIter = ValueIter; using ValueOffIter = ValueIter; using ValueOffCIter = ValueIter; using ValueAllIter = ValueIter; using ValueAllCIter = ValueIter; 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); } /// @warning This iterator will also visit child nodes so use isChildMaskOn to skip them! ValueOffCIter cbeginValueOff() const { return ValueOffCIter(mValueMask.beginOff(), this); } ValueAllCIter cbeginValueAll() const { return ValueAllCIter(mChildMask.beginOff(), this); } ValueOnCIter beginValueOn() const { return cbeginValueOn(); } /// @warning This iterator will also visit child nodes so use isChildMaskOn to skip them! ValueOffCIter beginValueOff() const { return cbeginValueOff(); } ValueAllCIter beginValueAll() const { return cbeginValueAll(); } ValueOnIter beginValueOn() { return ValueOnIter(mValueMask.beginOn(), this); } /// @warning This iterator will also visit child nodes so use isChildMaskOn to skip them! ValueOffIter beginValueOff() { return ValueOffIter(mValueMask.beginOff(), this); } ValueAllIter beginValueAll() { return ValueAllIter(mChildMask.beginOff(), this); } /// @return The dimension of this InternalNode /// @details The number of voxels in one coordinate direction covered by this node static Index dim() { return DIM; } /// @return The level of this node /// @details Level 0 is by definition the level of the leaf nodes static Index getLevel() { return LEVEL; } /// @brief Populated an stil::vector with the dimension of all the /// nodes in the branch starting with this node. static void getNodeLog2Dims(std::vector& dims); /// @return The dimension of the child nodes of this node. /// @details The number of voxels in one coordinate direction /// covered by a child node of this node. 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; } /// 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; /// @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); } /// @return True if this node contains no child nodes. 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 firstValue and the active state in @a state. /// /// @note This method also returns @c false if this node contains any child nodes. bool isConstant(ValueType& firstValue, bool& state, const ValueType& tolerance = zeroVal()) const; /// Return @c true if all of this node's tables entries have /// the same active @a state and the range of its values satisfy /// (@a maxValue - @a minValue) <= @a tolerance. /// /// @param minValue Is updated with the minimum of all values IF method /// returns @c true. Else the value is undefined! /// @param maxValue Is updated with the maximum of all values IF method /// returns @c true. Else the value is undefined! /// @param state Is updated with the state of all values IF method /// returns @c true. Else the value is undefined! /// @param tolerance The tolerance used to determine if values are /// approximatly constant. /// /// @note This method also returns @c false if this node contains any child nodes. bool isConstant(ValueType& minValue, ValueType& maxValue, 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); void readBuffers(std::istream&, const CoordBBox&, bool fromHalf = false); // // Aux methods // /// Change the sign of all the values represented in this node and its child nodes. void negate(); /// @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 all voxels within a given axis-aligned box to a constant value /// and ensure that those voxels are all represented at the leaf level. /// @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. /// @sa voxelizeActiveTiles() void denseFill(const CoordBBox& bbox, const ValueType& value, bool active = true); /// @brief Densify active tiles, i.e., replace them with leaf-level active voxels. /// @param threaded if true, this operation is multi-threaded (over the internal nodes). /// @sa denseFill() void voxelizeActiveTiles(bool threaded = true); /// @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 OtherNodeType& other1, CombineOp&); template void combine2(const ValueType& value, const OtherNodeType& other, bool valIsActive, CombineOp&); template void combine2(const InternalNode& other, const OtherValueType&, 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; /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&, const ValueType& background); /// @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 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 nullptr. /// /// @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 nullptr. 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 @c nullptr. 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 Adds all nodes of a certain type to a container with the following API: /// @code /// struct ArrayT { /// using value_type = ...;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// using value_type = LeafType*; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.getNodes(array); /// @endcode template void getNodes(ArrayT& array); template void getNodes(ArrayT& array) const; //@} /// @brief Steals all nodes of a certain type from the tree and /// adds them to a container with the following API: /// @code /// struct ArrayT { /// using value_type = ...;// defines the type of nodes to be added to the array /// void push_back(value_type nodePtr);// method that add nodes to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// using value_type = LeafType*; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.stealNodes(array); /// @endcode template void stealNodes(ArrayT& array, const ValueType& value, bool state); /// @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(); } const NodeMaskType& getValueMask() const { return mValueMask; } const NodeMaskType& getChildMask() const { return mChildMask; } NodeMaskType getValueOffMask() const { NodeMaskType mask = mValueMask; mask |= mChildMask; mask.toggle(); return mask; } const UnionType* getTable() const { return mNodes; } 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); ///@{ /// @brief Returns a pointer to the child node at the linear offset n. /// @warning This protected method assumes that a child node exists at /// the specified linear offset! ChildNodeType* getChildNode(Index n); const ChildNodeType* getChildNode(Index n) const; ///@} ///@{ /// @brief Protected member classes for recursive multi-threading struct VoxelizeActiveTiles; template struct DeepCopy; template struct TopologyCopy1; template struct TopologyCopy2; template struct TopologyUnion; template struct TopologyDifference; template struct TopologyIntersection; ///@} 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 //////////////////////////////////////// //@{ /// Helper metafunction used to implement InternalNode::SameConfiguration /// (which, as an inner class, can't be independently specialized) template struct SameInternalConfig { static const bool value = false; }; template struct SameInternalConfig > { static const bool value = ChildT1::template SameConfiguration::value; }; //@} //////////////////////////////////////// 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); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 // For InternalNodes, the PartialCreate constructor is identical to its // non-PartialCreate counterpart. template inline InternalNode::InternalNode(PartialCreate, const Coord& origin, const ValueType& val, bool active) : mOrigin(origin[0] & ~(DIM-1), origin[1] & ~(DIM-1), origin[2] & ~(DIM-1)) { if (active) mValueMask.setOn(); for (Index i = 0; i < NUM_VALUES; ++i) mNodes[i].setValue(val); } #endif template template struct InternalNode::DeepCopy { DeepCopy(const OtherInternalNode* source, InternalNode* target) : s(source), t(target) { tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); //(*this)(tbb::blocked_range(0, NUM_VALUES));//serial } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (s->mChildMask.isOff(i)) { t->mNodes[i].setValue(ValueType(s->mNodes[i].getValue())); } else { t->mNodes[i].setChild(new ChildNodeType(*(s->mNodes[i].getChild()))); } } } const OtherInternalNode* s; InternalNode* t; };// DeepCopy template inline InternalNode::InternalNode(const InternalNode& other): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { DeepCopy > tmp(&other, this); } // Copy-construct from a node with the same configuration but a different ValueType. template template inline InternalNode::InternalNode(const InternalNode& other) : mChildMask(other.mChildMask) , mValueMask(other.mValueMask) , mOrigin(other.mOrigin) { DeepCopy > tmp(&other, this); } template template struct InternalNode::TopologyCopy1 { TopologyCopy1(const OtherInternalNode* source, InternalNode* target, const ValueType& background) : s(source), t(target), b(background) { tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); //(*this)(tbb::blocked_range(0, NUM_VALUES));//serial } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (s->isChildMaskOn(i)) { t->mNodes[i].setChild(new ChildNodeType(*(s->mNodes[i].getChild()), b, TopologyCopy())); } else { t->mNodes[i].setValue(b); } } } const OtherInternalNode* s; InternalNode* t; const ValueType &b; };// TopologyCopy1 template template inline InternalNode::InternalNode(const InternalNode& other, const ValueType& background, TopologyCopy): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { TopologyCopy1 > tmp(&other, this, background); } template template struct InternalNode::TopologyCopy2 { TopologyCopy2(const OtherInternalNode* source, InternalNode* target, const ValueType& offValue, const ValueType& onValue) : s(source), t(target), offV(offValue), onV(onValue) { tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (s->isChildMaskOn(i)) { t->mNodes[i].setChild(new ChildNodeType(*(s->mNodes[i].getChild()), offV, onV, TopologyCopy())); } else { t->mNodes[i].setValue(s->isValueMaskOn(i) ? onV : offV); } } } const OtherInternalNode* s; InternalNode* t; const ValueType &offV, &onV; };// TopologyCopy2 template template inline InternalNode::InternalNode(const InternalNode& other, const ValueType& offValue, const ValueType& onValue, TopologyCopy): mChildMask(other.mChildMask), mValueMask(other.mValueMask), mOrigin(other.mOrigin) { TopologyCopy2 > tmp(&other, this, offValue, onValue); } 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; } 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 inline void InternalNode::prune(const ValueType& tolerance) { bool state = false; ValueType value = zeroVal(); for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { const Index i = iter.pos(); ChildT* child = mNodes[i].getChild(); child->prune(tolerance); if (child->isConstant(value, state, tolerance)) { delete child; mChildMask.setOff(i); mValueMask.set(i, state); mNodes[i].setValue(value); } } } //////////////////////////////////////// template template inline NodeT* InternalNode::stealNode(const Coord& xyz, const ValueType& value, bool state) { if ((NodeT::LEVEL == ChildT::LEVEL && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return nullptr; ChildT* child = mNodes[n].getChild(); if (std::is_same::value) { mChildMask.setOff(n); mValueMask.set(n, state); mNodes[n].setValue(value); } return (std::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 && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return nullptr; ChildT* child = mNodes[n].getChild(); return (std::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 && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return nullptr; ChildT* child = mNodes[n].getChild(); acc.insert(xyz, child); return (std::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 && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return nullptr; const ChildT* child = mNodes[n].getChild(); return (std::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 && !(std::is_same::value)) || NodeT::LEVEL > ChildT::LEVEL) return nullptr; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN const Index n = this->coordToOffset(xyz); if (mChildMask.isOff(n)) return nullptr; const ChildT* child = mNodes[n].getChild(); acc.insert(xyz, child); return (std::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 != nullptr); const Coord& xyz = leaf->origin(); const Index n = this->coordToOffset(xyz); ChildT* child = nullptr; 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 != nullptr); const Coord& xyz = leaf->origin(); const Index n = this->coordToOffset(xyz); ChildT* child = nullptr; 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 = nullptr; 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& firstValue, bool& state, const ValueType& tolerance) const { if (!mChildMask.isOff() || !mValueMask.isConstant(state)) return false;// early termination firstValue = mNodes[0].getValue(); for (Index i = 1; i < NUM_VALUES; ++i) { if (!math::isApproxEqual(mNodes[i].getValue(), firstValue, tolerance)) { return false; // early termination } } return true; } //////////////////////////////////////// template inline bool InternalNode::isConstant(ValueType& minValue, ValueType& maxValue, bool& state, const ValueType& tolerance) const { if (!mChildMask.isOff() || !mValueMask.isConstant(state)) return false;// early termination minValue = maxValue = mNodes[0].getValue(); for (Index i = 1; i < NUM_VALUES; ++i) { const ValueType& v = mNodes[i].getValue(); if (v < minValue) { if ((maxValue - v) > tolerance) return false;// early termination minValue = v; } else if (v > maxValue) { if ((v - minValue) > tolerance) return false;// early termination maxValue = v; } } return true; } //////////////////////////////////////// template inline bool InternalNode::hasActiveTiles() const { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN 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; OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } 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::clip(const CoordBBox& clipBBox, const ValueType& background) { CoordBBox nodeBBox = this->getNodeBoundingBox(); if (!clipBBox.hasOverlap(nodeBBox)) { // This node lies completely outside the clipping region. Fill it with background tiles. this->fill(nodeBBox, background, /*active=*/false); } else if (clipBBox.isInside(nodeBBox)) { // This node lies completely inside the clipping region. Leave it intact. return; } // This node isn't completely contained inside the clipping region. // Clip tiles and children, and replace any that lie outside the region // with background tiles. for (Index pos = 0; pos < NUM_VALUES; ++pos) { const Coord xyz = this->offsetToGlobalCoord(pos); // tile or child origin CoordBBox tileBBox(xyz, xyz.offsetBy(ChildT::DIM - 1)); // tile or child bounds if (!clipBBox.hasOverlap(tileBBox)) { // This table entry lies completely outside the clipping region. // Replace it with a background tile. this->makeChildNodeEmpty(pos, background); mValueMask.setOff(pos); } else if (!clipBBox.isInside(tileBBox)) { // This table entry does not lie completely inside the clipping region // and must be clipped. if (this->isChildMaskOn(pos)) { mNodes[pos].getChild()->clip(clipBBox, background); } else { // Replace this tile with a background tile, then fill the clip region // with the tile's original value. (This might create a child branch.) tileBBox.intersect(clipBBox); const ValueType val = mNodes[pos].getValue(); const bool on = this->isValueMaskOn(pos); mNodes[pos].setValue(background); mValueMask.setOff(pos); this->fill(tileBBox, val, on); } } else { // This table entry lies completely inside the clipping region. Leave it intact. } } } //////////////////////////////////////// template inline void InternalNode::fill(const CoordBBox& bbox, const ValueType& value, bool active) { auto clippedBBox = this->getNodeBoundingBox(); clippedBBox.intersect(bbox); if (!clippedBBox) return; // Iterate over the fill region in axis-aligned, tile-sized chunks. // (The first and last chunks along each axis might be smaller than a tile.) Coord xyz, tileMin, tileMax; for (int x = clippedBBox.min().x(); x <= clippedBBox.max().x(); x = tileMax.x() + 1) { xyz.setX(x); for (int y = clippedBBox.min().y(); y <= clippedBBox.max().y(); y = tileMax.y() + 1) { xyz.setY(y); for (int z = clippedBBox.min().z(); z <= clippedBBox.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(clippedBBox.max(), tileMax)) { // If the box defined by (xyz, clippedBBox.max()) doesn't completely enclose // the tile to which xyz belongs, create a child node (or retrieve // the existing one). ChildT* child = nullptr; 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) { const Coord tmp = Coord::minComponent(clippedBBox.max(), tileMax); child->fill(CoordBBox(xyz, tmp), value, active); } } else { // If the box given by (xyz, clippedBBox.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 inline void InternalNode::denseFill(const CoordBBox& bbox, const ValueType& value, bool active) { auto clippedBBox = this->getNodeBoundingBox(); clippedBBox.intersect(bbox); if (!clippedBBox) return; // Iterate over the fill region in axis-aligned, tile-sized chunks. // (The first and last chunks along each axis might be smaller than a tile.) Coord xyz, tileMin, tileMax; for (int x = clippedBBox.min().x(); x <= clippedBBox.max().x(); x = tileMax.x() + 1) { xyz.setX(x); for (int y = clippedBBox.min().y(); y <= clippedBBox.max().y(); y = tileMax.y() + 1) { xyz.setY(y); for (int z = clippedBBox.min().z(); z <= clippedBBox.max().z(); z = tileMax.z() + 1) { xyz.setZ(z); // Get the table index of the tile that contains voxel (x, y, z). const auto n = this->coordToOffset(xyz); // Retrieve the child node at index n, or replace the tile at index n with a child. ChildT* child = nullptr; if (this->isChildMaskOn(n)) { child = mNodes[n].getChild(); } else { // Replace the tile with a newly-created child that is filled // with the tile's value and active state. child = new ChildT{xyz, mNodes[n].getValue(), this->isValueMaskOn(n)}; this->setChildNode(n, child); } // Get the bounds of the tile that contains voxel (x, y, z). tileMin = this->offsetToGlobalCoord(n); tileMax = tileMin.offsetBy(ChildT::DIM - 1); // Forward the fill request to the child. child->denseFill(CoordBBox{xyz, clippedBBox.max()}, value, active); } } } } //////////////////////////////////////// template template inline void InternalNode::copyToDense(const CoordBBox& bbox, DenseT& dense) const { using DenseValueType = typename DenseT::ValueType; 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. std::unique_ptr valuePtr(new ValueType[NUM_VALUES]); ValueType* values = valuePtr.get(); 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, 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) { #if OPENVDB_ABI_VERSION_NUMBER >= 3 const ValueType background = (!io::getGridBackgroundValuePtr(is) ? zeroVal() : *static_cast(io::getGridBackgroundValuePtr(is))); #endif 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 = #if OPENVDB_ABI_VERSION_NUMBER <= 2 new ChildNodeType(offsetToGlobalCoord(i), zeroVal()); #else new ChildNodeType(PartialCreate(), offsetToGlobalCoord(i), background); #endif 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. std::unique_ptr valuePtr(new ValueType[numValues]); ValueType* values = valuePtr.get(); io::readCompressedValues(is, values, 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) { #if OPENVDB_ABI_VERSION_NUMBER <= 2 ChildNodeType* child = new ChildNodeType(iter.getCoord(), zeroVal()); #else ChildNodeType* child = new ChildNodeType(PartialCreate(), iter.getCoord(), background); #endif 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::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 struct InternalNode::VoxelizeActiveTiles { VoxelizeActiveTiles(InternalNode &node) : mNode(&node) { //(*this)(tbb::blocked_range(0, NUM_VALUES));//single thread for debugging tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); node.mChildMask |= node.mValueMask; node.mValueMask.setOff(); } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (mNode->mChildMask.isOn(i)) {// Loop over node's child nodes mNode->mNodes[i].getChild()->voxelizeActiveTiles(true); } else if (mNode->mValueMask.isOn(i)) {// Loop over node's active tiles const Coord &ijk = mNode->offsetToGlobalCoord(i); ChildNodeType *child = new ChildNodeType(ijk, mNode->mNodes[i].getValue(), true); child->voxelizeActiveTiles(true); mNode->mNodes[i].setChild(child); } } } InternalNode* mNode; };// VoxelizeActiveTiles template inline void InternalNode::voxelizeActiveTiles(bool threaded) { if (threaded) { VoxelizeActiveTiles tmp(*this); } else { 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(false); } } //////////////////////////////////////// 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 struct InternalNode::TopologyUnion { using W = typename NodeMaskType::Word; struct A { inline void operator()(W &tV, const W& sV, const W& tC) const { tV = (tV | sV) & ~tC; } }; TopologyUnion(const OtherInternalNode* source, InternalNode* target) : s(source), t(target) { //(*this)(tbb::blocked_range(0, NUM_VALUES));//single thread for debugging tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); // Bit processing is done in a single thread! t->mChildMask |= s->mChildMask;//serial but very fast bitwise post-process A op; t->mValueMask.foreach(s->mValueMask, t->mChildMask, op); assert((t->mValueMask & t->mChildMask).isOff());//no overlapping active tiles or child nodes } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (s->mChildMask.isOn(i)) {// Loop over other node's child nodes const typename OtherInternalNode::ChildNodeType& other = *(s->mNodes[i].getChild()); if (t->mChildMask.isOn(i)) {//this has a child node t->mNodes[i].getChild()->topologyUnion(other); } else {// this is a tile so replace it with a child branch with identical topology ChildT* child = new ChildT(other, t->mNodes[i].getValue(), TopologyCopy()); if (t->mValueMask.isOn(i)) child->setValuesOn();//activate all values t->mNodes[i].setChild(child); } } else if (s->mValueMask.isOn(i) && t->mChildMask.isOn(i)) { t->mNodes[i].getChild()->setValuesOn(); } } } const OtherInternalNode* s; InternalNode* t; };// TopologyUnion template template inline void InternalNode::topologyUnion(const InternalNode& other) { TopologyUnion > tmp(&other, this); } template template struct InternalNode::TopologyIntersection { using W = typename NodeMaskType::Word; struct A { inline void operator()(W &tC, const W& sC, const W& sV, const W& tV) const { tC = (tC & (sC | sV)) | (tV & sC); } }; TopologyIntersection(const OtherInternalNode* source, InternalNode* target, const ValueType& background) : s(source), t(target), b(background) { //(*this)(tbb::blocked_range(0, NUM_VALUES));//single thread for debugging tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); // Bit processing is done in a single thread! A op; t->mChildMask.foreach(s->mChildMask, s->mValueMask, t->mValueMask, op); t->mValueMask &= s->mValueMask; assert((t->mValueMask & t->mChildMask).isOff());//no overlapping active tiles or child nodes } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (t->mChildMask.isOn(i)) {// Loop over this node's child nodes ChildT* child = t->mNodes[i].getChild(); if (s->mChildMask.isOn(i)) {//other also has a child node child->topologyIntersection(*(s->mNodes[i].getChild()), b); } else if (s->mValueMask.isOff(i)) {//other is an inactive tile delete child;//convert child to an inactive tile t->mNodes[i].setValue(b); } } else if (t->mValueMask.isOn(i) && s->mChildMask.isOn(i)) {//active tile -> a branch t->mNodes[i].setChild(new ChildT(*(s->mNodes[i].getChild()), t->mNodes[i].getValue(), TopologyCopy())); } } } const OtherInternalNode* s; InternalNode* t; const ValueType& b; };// TopologyIntersection template template inline void InternalNode::topologyIntersection( const InternalNode& other, const ValueType& background) { TopologyIntersection > tmp(&other, this, background); } template template struct InternalNode::TopologyDifference { using W = typename NodeMaskType::Word; struct A {inline void operator()(W &tC, const W& sC, const W& sV, const W& tV) const { tC = (tC & (sC | ~sV)) | (tV & sC); } }; struct B {inline void operator()(W &tV, const W& sC, const W& sV, const W& tC) const { tV &= ~((tC & sV) | (sC | sV)); } }; TopologyDifference(const OtherInternalNode* source, InternalNode* target, const ValueType& background) : s(source), t(target), b(background) { //(*this)(tbb::blocked_range(0, NUM_VALUES));//single thread for debugging tbb::parallel_for(tbb::blocked_range(0, NUM_VALUES), *this); // Bit processing is done in a single thread! const NodeMaskType oldChildMask(t->mChildMask);//important to avoid cross pollution A op1; t->mChildMask.foreach(s->mChildMask, s->mValueMask, t->mValueMask, op1); B op2; t->mValueMask.foreach(t->mChildMask, s->mValueMask, oldChildMask, op2); assert((t->mValueMask & t->mChildMask).isOff());//no overlapping active tiles or child nodes } void operator()(const tbb::blocked_range &r) const { for (Index i = r.begin(), end=r.end(); i!=end; ++i) { if (t->mChildMask.isOn(i)) {// Loop over this node's child nodes ChildT* child = t->mNodes[i].getChild(); if (s->mChildMask.isOn(i)) { child->topologyDifference(*(s->mNodes[i].getChild()), b); } else if (s->mValueMask.isOn(i)) { delete child;//convert child to an inactive tile t->mNodes[i].setValue(b); } } else if (t->mValueMask.isOn(i)) {//this is an active tile if (s->mChildMask.isOn(i)) { const typename OtherInternalNode::ChildNodeType& other = *(s->mNodes[i].getChild()); ChildT* child = new ChildT(other.origin(), t->mNodes[i].getValue(), true); child->topologyDifference(other, b); t->mNodes[i].setChild(child);//replace the active tile with a child branch } } } } const OtherInternalNode* s; InternalNode* t; const ValueType& b; };// TopologyDifference template template inline void InternalNode::topologyDifference(const InternalNode& other, const ValueType& background) { TopologyDifference > tmp(&other, this, background); } //////////////////////////////////////// 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 OtherNodeType& 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 { if (this->isChildMaskOff(i)) { // Add a new child with the same coordinates, etc. as the other node's child. const Coord& childOrigin = other0.isChildMaskOn(i) ? other0.mNodes[i].getChild()->origin() : other1.mNodes[i].getChild()->origin(); this->setChildNode(i, new ChildNodeType(childOrigin, 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 OtherNodeType& 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 { typename OtherNodeType::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)); } // 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 OtherValueType& 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. static_assert(OtherNodeT::NUM_VALUES == NodeT::NUM_VALUES, "visit2() requires nodes to have the same dimensions"); static_assert(OtherNodeT::LEVEL == NodeT::LEVEL, "visit2() requires nodes to be at the same tree 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) ? nullptr : iter.probeChild(val); typename OtherChildAllIterT::ChildNodeType* otherChild = (skipBranch & 2U) ? nullptr : otherIter.probeChild(otherVal); if (child != nullptr && otherChild != nullptr) { child->visit2Node(*otherChild, op); } else if (child != nullptr) { child->visit2(otherIter, op); } else if (otherChild != nullptr) { 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) ? nullptr : iter.probeChild(val); if (child != nullptr) 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 inline void InternalNode::readBuffers(std::istream& is, const CoordBBox& clipBBox, bool fromHalf) { for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { // Stream in the branch rooted at this child. // (We can't skip over children that lie outside the clipping region, // because buffers are serialized in depth-first order and need to be // unserialized in the same order.) iter->readBuffers(is, clipBBox, fromHalf); } // Get this tree's background value. ValueType background = zeroVal(); if (const void* bgPtr = io::getGridBackgroundValuePtr(is)) { background = *static_cast(bgPtr); } this->clip(clipBBox, background); } //////////////////////////////////////// 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 template inline void InternalNode::getNodes(ArrayT& array) { using T = typename ArrayT::value_type; static_assert(std::is_pointer::value, "argument to getNodes() must be a pointer array"); using ArrayChildT = typename std::conditional< std::is_const::type>::value, const ChildT, ChildT>::type; for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (std::is_same::value) { array.push_back(reinterpret_cast(mNodes[iter.pos()].getChild())); } else { iter->getNodes(array);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } template template inline void InternalNode::getNodes(ArrayT& array) const { using T = typename ArrayT::value_type; static_assert(std::is_pointer::value, "argument to getNodes() must be a pointer array"); static_assert(std::is_const::type>::value, "argument to getNodes() must be an array of const node pointers"); for (ChildOnCIter iter = this->cbeginChildOn(); iter; ++iter) { OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (std::is_same::value) { array.push_back(reinterpret_cast(mNodes[iter.pos()].getChild())); } else { iter->getNodes(array);//descent } OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } } //////////////////////////////////////// template template inline void InternalNode::stealNodes(ArrayT& array, const ValueType& value, bool state) { using T = typename ArrayT::value_type; static_assert(std::is_pointer::value, "argument to stealNodes() must be a pointer array"); using ArrayChildT = typename std::conditional< std::is_const::type>::value, const ChildT, ChildT>::type; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN for (ChildOnIter iter = this->beginChildOn(); iter; ++iter) { const Index n = iter.pos(); if (std::is_same::value) { array.push_back(reinterpret_cast(mNodes[n].getChild())); mValueMask.set(n, state); mNodes[n].setValue(value); } else { iter->stealNodes(array, value, state);//descent } } if (std::is_same::value) mChildMask.setOff(); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END } //////////////////////////////////////// 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 nullptr; } 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) { assert(this->isChildMaskOn(n)); return mNodes[n].getChild(); } template inline const ChildT* InternalNode::getChildNode(Index n) const { assert(this->isChildMaskOn(n)); return mNodes[n].getChild(); } } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_INTERNALNODE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000026340513200122377013227 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "RootNode.h" #include "InternalNode.h" #include "LeafNode.h" #include "TreeIterator.h" #include "ValueAccessor.h" #include #include #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace tree { /// @brief Base class for typed trees class OPENVDB_API TreeBase { public: using Ptr = SharedPtr; using ConstPtr = SharedPtr; TreeBase() = default; TreeBase(const TreeBase&) = default; TreeBase& operator=(const TreeBase&) = delete; // disallow assignment virtual ~TreeBase() = default; /// 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 than 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; #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// @brief Replace with background tiles any nodes whose voxel buffers /// have not yet been allocated. /// @details Typically, unallocated nodes are leaf nodes whose voxel buffers /// are not yet resident in memory because delayed loading is in effect. /// @sa readNonresidentBuffers, io::File::open virtual void clipUnallocatedNodes() = 0; #endif #if OPENVDB_ABI_VERSION_NUMBER >= 4 /// Return the total number of unallocated leaf nodes residing in this tree. virtual Index32 unallocatedLeafCount() const = 0; #endif // // 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; #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// Return the total number of active tiles. virtual Index64 activeTileCount() const = 0; #endif /// 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; #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// Read all of this tree's data buffers that intersect the given bounding box. virtual void readBuffers(std::istream&, const CoordBBox&, bool saveFloatAsHalf = false) = 0; /// @brief Read all of this tree's data buffers that are not yet resident in memory /// (because delayed loading is in effect). /// @details If this tree was read from a memory-mapped file, this operation /// disconnects the tree from the file. /// @sa clipUnallocatedNodes, io::File::open, io::MappedFile virtual void readNonresidentBuffers() const = 0; #endif /// 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; /// 4: include minimum and maximum voxel values /// @warning @a verboseLevel 4 forces loading of any unallocated nodes. virtual void print(std::ostream& os = std::cout, int verboseLevel = 1) const; }; //////////////////////////////////////// template class Tree: public TreeBase { public: using Ptr = SharedPtr; using ConstPtr = SharedPtr; using RootNodeType = _RootNodeType; using ValueType = typename RootNodeType::ValueType; using BuildType = typename RootNodeType::BuildType; using LeafNodeType = typename RootNodeType::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 { using Type = Tree::Type>; }; Tree() {} Tree& operator=(const Tree&) = delete; // disallow assignment /// Deep copy constructor Tree(const Tree& other): TreeBase(other), mRoot(other.mRoot) { } /// @brief Value conversion deep copy constructor /// /// Deep copy a tree of the same configuration as this tree type but a different /// ValueType, casting the other tree's values to this tree's ValueType. /// @throw TypeError if the other tree's configuration doesn't match this tree's /// or if this tree's ValueType is not constructible from the other tree's ValueType. template explicit Tree(const Tree& other): TreeBase(other), mRoot(other.root()) { } /// @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 /// @throw TypeError if the other tree's configuration doesn't match this tree's. template Tree(const OtherTreeType& other, const ValueType& inactiveValue, const ValueType& activeValue, TopologyCopy): TreeBase(other), mRoot(other.root(), 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 /// @throw TypeError if the other tree's configuration doesn't match this tree's. template Tree(const OtherTreeType& other, const ValueType& background, TopologyCopy): TreeBase(other), mRoot(other.root(), background, TopologyCopy()) { } /// Empty tree constructor Tree(const ValueType& background): mRoot(background) {} ~Tree() override { this->clear(); releaseAllAccessors(); } /// Return a pointer to a deep copy of this tree TreeBase::Ptr copy() const override { return TreeBase::Ptr(new Tree(*this)); } /// Return the name of the type of a voxel's value (e.g., "float" or "vec3d") Name valueType() const override { return typeNameAsString(); } /// Return the name of this type of tree. static const Name& treeType(); /// Return the name of this type of tree. const Name& type() const override { 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; } //@} // // 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; bool evalLeafBoundingBox(CoordBBox& bbox) const override; bool evalActiveVoxelBoundingBox(CoordBBox& bbox) const override; bool evalActiveVoxelDim(Coord& dim) const override; bool evalLeafDim(Coord& dim) const override; /// @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. void readTopology(std::istream&, bool saveFloatAsHalf = false) override; /// @brief Write the tree topology to a stream. /// /// This will write the tree structure and tile values, but not voxel data. void writeTopology(std::ostream&, bool saveFloatAsHalf = false) const override; /// Read all data buffers for this tree. void readBuffers(std::istream&, bool saveFloatAsHalf = false) override; #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// Read all of this tree's data buffers that intersect the given bounding box. void readBuffers(std::istream&, const CoordBBox&, bool saveFloatAsHalf = false) override; /// @brief Read all of this tree's data buffers that are not yet resident in memory /// (because delayed loading is in effect). /// @details If this tree was read from a memory-mapped file, this operation /// disconnects the tree from the file. /// @sa clipUnallocatedNodes, io::File::open, io::MappedFile void readNonresidentBuffers() const override; #endif /// Write out all data buffers for this tree. void writeBuffers(std::ostream&, bool saveFloatAsHalf = false) const override; void print(std::ostream& os = std::cout, int verboseLevel = 1) const override; // // Statistics // /// @brief Return the depth of this tree. /// /// A tree with only a root node and leaf nodes has depth 2, for example. Index treeDepth() const override { return DEPTH; } /// Return the number of leaf nodes. Index32 leafCount() const override { return mRoot.leafCount(); } /// Return the number of non-leaf nodes. Index32 nonLeafCount() const override { return mRoot.nonLeafCount(); } /// Return the number of active voxels stored in leaf nodes. Index64 activeLeafVoxelCount() const override { return mRoot.onLeafVoxelCount(); } /// Return the number of inactive voxels stored in leaf nodes. Index64 inactiveLeafVoxelCount() const override { return mRoot.offLeafVoxelCount(); } /// Return the total number of active voxels. Index64 activeVoxelCount() const override { return mRoot.onVoxelCount(); } /// Return the number of inactive voxels within the bounding box of all active voxels. Index64 inactiveVoxelCount() const override; #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// Return the total number of active tiles. Index64 activeTileCount() const override { return mRoot.onTileCount(); } #else Index64 activeTileCount() const { return mRoot.onTileCount(); } #endif /// Return the minimum and maximum active values in this tree. void evalMinMax(ValueType &min, ValueType &max) const; Index64 memUsage() const override { 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(); } /// Set all voxels that lie outside the given axis-aligned box to the background. void clip(const CoordBBox&); #if OPENVDB_ABI_VERSION_NUMBER >= 3 /// @brief Replace with background tiles any nodes whose voxel buffers /// have not yet been allocated. /// @details Typically, unallocated nodes are leaf nodes whose voxel buffers /// are not yet resident in memory because delayed loading is in effect. /// @sa readNonresidentBuffers, io::File::open void clipUnallocatedNodes() override; #endif #if OPENVDB_ABI_VERSION_NUMBER >= 4 /// Return the total number of unallocated leaf nodes residing in this tree. Index32 unallocatedLeafCount() const override; #endif //@{ /// @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 sparseFill(const CoordBBox& bbox, const ValueType& value, bool active = true); void fill(const CoordBBox& bbox, const ValueType& value, bool active = true) { this->sparseFill(bbox, value, active); } //@} /// @brief Set all voxels within a given axis-aligned box to a constant value /// and ensure that those voxels are all represented at the leaf level. /// @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. /// @sa voxelizeActiveTiles() void denseFill(const CoordBBox& bbox, const ValueType& value, bool active = true); /// @brief Densify active tiles, i.e., replace them with leaf-level active voxels. /// /// @param threaded if true, this operation is multi-threaded (over the internal nodes). /// /// @warning This method can explode the tree's memory footprint, especially if it /// contains active tiles at the upper levels (in particular the root level)! /// /// @sa denseFill() void voxelizeActiveTiles(bool threaded = true); /// @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. /// @warning Will soon be deprecated! void prune(const ValueType& tolerance = zeroVal()) { this->clearAllAccessors(); mRoot.prune(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. /// /// @warning Ownership of the leaf is transferred to the tree so /// the client code should not attempt to delete the leaf pointer! void addLeaf(LeafNodeType* leaf) { assert(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 nullptr. /// @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 @c nullptr. 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 @c nullptr. LeafNodeType* probeLeaf(const Coord& xyz); const LeafNodeType* probeConstLeaf(const Coord& xyz) const; const LeafNodeType* probeLeaf(const Coord& xyz) const { return this->probeConstLeaf(xyz); } //@} //@{ /// @brief Adds all nodes of a certain type to a container with the following API: /// @code /// struct ArrayT { /// using value_type = ...; // the type of node to be added to the array /// void push_back(value_type nodePtr); // add a node to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// using value_type = LeafType*; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.getNodes(array); /// @endcode template void getNodes(ArrayT& array) { mRoot.getNodes(array); } template void getNodes(ArrayT& array) const { mRoot.getNodes(array); } //@} /// @brief Steals all nodes of a certain type from the tree and /// adds them to a container with the following API: /// @code /// struct ArrayT { /// using value_type = ...; // the type of node to be added to the array /// void push_back(value_type nodePtr); // add a node to the array /// }; /// @endcode /// @details An example of a wrapper around a c-style array is: /// @code /// struct MyArray { /// using value_type = LeafType*; /// value_type* ptr; /// MyArray(value_type* array) : ptr(array) {} /// void push_back(value_type leaf) { *ptr++ = leaf; } ///}; /// @endcode /// @details An example that constructs a list of pointer to all leaf nodes is: /// @code /// std::vector array;//most std contains have the required API /// array.reserve(tree.leafCount());//this is a fast preallocation. /// tree.stealNodes(array); /// @endcode template void stealNodes(ArrayT& array) { this->clearAllAccessors(); mRoot.stealNodes(array); } template void stealNodes(ArrayT& array, const ValueType& value, bool state) { this->clearAllAccessors(); mRoot.stealNodes(array, value, state); } // // 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(); /// 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; //@} //@{ /// Dummy implementations 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; //@} //@{ /// Dummy implementations 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. Metadata::Ptr getBackgroundValue() const override; /// @brief Return this tree's background value. /// /// @note Use tools::changeBackground to efficiently modify the /// background values. Else use tree.root().setBackground, which /// is serial and hence slower. const ValueType& background() const { return mRoot.background(); } /// Min and max are both inclusive. void getIndexRange(CoordBBox& bbox) const override { mRoot.getIndexRange(bbox); } /// @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 tools::pruneInactive. 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 tools::pruneInactive. 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 with the same configuration (levels and node dimensions) /// as this tree but with the B tree possibly having a different value type /// @param op a functor of the form void op(const T1& a, const T2& b, T1& result), /// where @c T1 is this tree's and the A tree's @c ValueType and @c T2 is the /// B 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) /// /// @throw TypeError if the B tree's configuration doesn't match this tree's /// or if this tree's ValueType is not constructible from the B tree's ValueType. /// /// @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 OtherTreeType& b, CombineOp& op, bool prune = false); #ifndef _MSC_VER template void combine2(const Tree& a, const OtherTreeType& b, const CombineOp& op, bool prune = false); #endif /// Like combine2(), but with /// @param a,b two trees with the same configuration (levels and node dimensions) /// as this tree but with the B tree possibly having a different value type /// @param op a functor of the form void op(CombineArgs& args), where /// @c T1 is this tree's and the A tree's @c ValueType and @c T2 is the B tree's /// @c ValueType, 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. /// /// @throw TypeError if the B tree's configuration doesn't match this tree's /// or if this tree's ValueType is not constructible from the B tree's ValueType. /// /// @see openvdb/Types.h for the definition of the CombineArgs struct. /// /// @par Example: /// Compute the per-voxel maximum values of two single-precision 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 aTree = ...; /// FloatTree bTree = ...; /// FloatTree resultTree; /// resultTree.combine2Extended(aTree, bTree, Local::max); /// } /// @endcode /// /// @par Example: /// Compute the per-voxel maximum values of a double-precision and a single-precision /// floating-point tree, @c aTree and @c bTree, and store the result in a third, /// double-precision 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()); /// } /// } /// }; /// DoubleTree aTree = ...; /// FloatTree bTree = ...; /// DoubleTree resultTree; /// resultTree.combine2Extended(aTree, bTree, Local::max); /// } /// @endcode template void combine2Extended(const Tree& a, const OtherTreeType& b, ExtendedCombineOp& op, bool prune = false); #ifndef _MSC_VER template void combine2Extended(const Tree& a, const OtherTreeType& b, const ExtendedCombineOp&, bool prune = false); #endif /// @brief Use sparse traversal to call the given functor with bounding box /// information for all active tiles and leaf nodes or active voxels in the tree. /// /// @note The bounding boxes are guaranteed to be non-overlapping. /// @param op a functor with a templated call operator of the form /// template void operator()(const CoordBBox& bbox), /// where bbox is the bounding box of either an active tile /// (if @c LEVEL > 0), a leaf node or an active voxel. /// The functor must also provide a templated method of the form /// template bool descent() that returns @c false /// if bounding boxes below the specified tree level are not to be visited. /// In such cases of early tree termination, a bounding box is instead /// derived from each terminating child node. /// /// @par Example: /// Visit and process all active tiles and leaf nodes in a tree, but don't /// descend to the active voxels. The smallest bounding boxes that will be /// visited are those of leaf nodes or level-1 active tiles. /// @code /// { /// struct ProcessTilesAndLeafNodes { /// // Descend to leaf nodes, but no further. /// template inline bool descent() { return LEVEL > 0; } /// // Use this version to descend to voxels: /// //template inline bool descent() { return true; } /// /// template /// inline void operator()(const CoordBBox &bbox) { /// if (LEVEL > 0) { /// // code to process an active tile /// } else { /// // code to process a leaf node /// } /// } /// }; /// ProcessTilesAndLeafNodes 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 /// { /// using RootT = typename TreeT::RootNodeType; /// 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 == nullptr) { /// 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 using NodeIter = NodeIteratorBase; using NodeCIter = NodeIteratorBase; //@} //@{ /// Iterator over all leaf nodes in this tree using LeafIter = LeafIteratorBase; using LeafCIter = LeafIteratorBase; //@} //@{ /// 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); } //@} using ValueAllIter = TreeValueIteratorBase; using ValueAllCIter = TreeValueIteratorBase; using ValueOnIter = TreeValueIteratorBase; using ValueOnCIter = TreeValueIteratorBase; using ValueOffIter = TreeValueIteratorBase; using ValueOffCIter = TreeValueIteratorBase; //@{ /// 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: using AccessorRegistry = tbb::concurrent_hash_map*, bool>; using ConstAccessorRegistry = tbb::concurrent_hash_map*, bool>; /// @brief Notify all registered accessors, by calling ValueAccessor::release(), /// that this tree is about to be deleted. void releaseAllAccessors(); // TBB body object used to deallocates nodes in parallel. template struct DeallocateNodes { DeallocateNodes(std::vector& nodes) : mNodes(nodes.empty() ? nullptr : &nodes.front()) { } void operator()(const tbb::blocked_range& range) const { for (size_t n = range.begin(), N = range.end(); n < N; ++n) { delete mNodes[n]; mNodes[n] = nullptr; } } NodeType ** const mNodes; }; // // Data members // RootNodeType mRoot; // root node of the tree mutable AccessorRegistry mAccessorRegistry; mutable ConstAccessorRegistry mConstAccessorRegistry; static tbb::atomic sTreeTypeName; }; // end of Tree class template tbb::atomic Tree<_RootNodeType>::sTreeTypeName; /// @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 { using Type = Tree, N1>>>; }; /// @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 { using Type = Tree, N2>, N1>>>; }; /// @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 { using Type = Tree, N3>, N2>, N1>>>; }; //////////////////////////////////////// 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 #if OPENVDB_ABI_VERSION_NUMBER >= 3 << " Active tile Count: " << activeTileCount() << std::endl #endif << " 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); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 template inline void Tree::readBuffers(std::istream &is, const CoordBBox& bbox, bool saveFloatAsHalf) { this->clearAllAccessors(); mRoot.readBuffers(is, bbox, saveFloatAsHalf); } template inline void Tree::readNonresidentBuffers() const { for (LeafCIter it = this->cbeginLeaf(); it; ++it) { // Retrieving the value of a leaf voxel forces loading of the leaf node's voxel buffer. it->getValue(Index(0)); } } #endif template inline void Tree::writeBuffers(std::ostream &os, bool saveFloatAsHalf) const { mRoot.writeBuffers(os, saveFloatAsHalf); } template inline void Tree::clear() { std::vector leafnodes; this->stealNodes(leafnodes); tbb::parallel_for(tbb::blocked_range(0, leafnodes.size()), DeallocateNodes(leafnodes)); std::vector internalNodes; this->stealNodes(internalNodes); tbb::parallel_for(tbb::blocked_range(0, internalNodes.size()), DeallocateNodes(internalNodes)); mRoot.clear(); this->clearAllAccessors(); } //////////////////////////////////////// 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(nullptr); for (typename AccessorRegistry::iterator it = mAccessorRegistry.begin(); it != mAccessorRegistry.end(); ++it) { it->first->release(); } mAccessorRegistry.clear(); mAccessorRegistry.erase(nullptr); 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 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::clip(const CoordBBox& bbox) { this->clearAllAccessors(); return mRoot.clip(bbox); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 template inline void Tree::clipUnallocatedNodes() { this->clearAllAccessors(); for (LeafIter it = this->beginLeaf(); it; ) { const LeafNodeType* leaf = it.getLeaf(); ++it; // advance the iterator before deleting the leaf node if (!leaf->isAllocated()) { this->addTile(/*level=*/0, leaf->origin(), this->background(), /*active=*/false); } } } #endif #if OPENVDB_ABI_VERSION_NUMBER >= 4 template inline Index32 Tree::unallocatedLeafCount() const { Index32 sum = 0; for (auto it = this->cbeginLeaf(); it; ++it) if (!it->isAllocated()) ++sum; return sum; } #endif template inline void Tree::sparseFill(const CoordBBox& bbox, const ValueType& value, bool active) { this->clearAllAccessors(); return mRoot.sparseFill(bbox, value, active); } template inline void Tree::denseFill(const CoordBBox& bbox, const ValueType& value, bool active) { this->clearAllAccessors(); return mRoot.denseFill(bbox, value, active); } template inline void Tree::voxelizeActiveTiles(bool threaded) { this->clearAllAccessors(); mRoot.voxelizeActiveTiles(threaded); } template Metadata::Ptr Tree::getBackgroundValue() const { Metadata::Ptr result; if (Metadata::isRegisteredType(valueType())) { using MetadataT = TypedMetadata; result = Metadata::createMetadata(valueType()); if (result->typeName() == MetadataT::staticTypeName()) { MetadataT* m = static_cast(result.get()); m->value() = mRoot.background(); } } return result; } //////////////////////////////////////// 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.root()); } template template inline void Tree::topologyIntersection(const Tree& other) { this->clearAllAccessors(); mRoot.topologyIntersection(other.root()); } template template inline void Tree::topologyDifference(const Tree& other) { this->clearAllAccessors(); mRoot.topologyDifference(other.root()); } //////////////////////////////////////// /// @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.root(), 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 OtherTreeType& 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 OtherTreeType& 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 OtherTreeType& b, ExtendedCombineOp& op, bool prune) { this->clearAllAccessors(); mRoot.combine2(a.root(), b.root(), op, prune); } /// @internal This overload is needed (for ICC and GCC, but not for VC) to disambiguate /// code like the following, where the functor argument is a temporary: /// tree.combine2Extended(aTree, bTree, MyCombineOp(...)). #ifndef _MSC_VER template template inline void Tree::combine2Extended(const Tree& a, const OtherTreeType& b, const ExtendedCombineOp& op, bool prune) { this->clearAllAccessors(); mRoot.template combine2(a.root(), b.root(), 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(); using OtherRootNodeType = typename OtherTreeType::RootNodeType; mRoot.template visit2(other.root(), op); } template template inline void Tree::visit2(OtherTreeType& other, VisitorOp& op) const { using OtherRootNodeType = typename OtherTreeType::RootNodeType; mRoot.template visit2(other.root(), 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(); using OtherRootNodeType = typename OtherTreeType::RootNodeType; mRoot.template visit2(other.root(), 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 { using OtherRootNodeType = typename OtherTreeType::RootNodeType; mRoot.template visit2(other.root(), op); } //////////////////////////////////////// template inline const Name& Tree::treeType() { if (sTreeTypeName == nullptr) { 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 (sTreeTypeName.compare_and_swap(s, nullptr) != nullptr) delete s; } return *sTreeTypeName; } template template inline bool Tree::hasSameTopology(const Tree& other) const { return mRoot.hasSameTopology(other.root()); } 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 { /// @todo optimize 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; /// @todo Consider using boost::io::ios_precision_saver instead. 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); 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"; } os << " Background value: " << mRoot.background() << "\n"; return; } // The following is tree information that is expensive to extract. ValueType minVal = zeroVal(), maxVal = zeroVal(); if (verboseLevel > 3) { // This forces loading of all non-resident nodes. this->evalMinMax(minVal, maxVal); } std::vector nodeCount(dims.size()); for (NodeCIter it = cbeginNode(); it; ++it) ++(nodeCount[it.getDepth()]); Index64 totalNodeCount = 0; for (size_t i = 0; i < nodeCount.size(); ++i) totalNodeCount += nodeCount[i]; // Print node types, counts and sizes. 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"; // Statistics of topology and values if (verboseLevel > 3) { os << " Min value: " << minVal << "\n"; os << " Max value: " << maxVal << "\n"; } const Index64 leafCount = *nodeCount.rbegin(), numActiveVoxels = this->activeVoxelCount(), numActiveLeafVoxels = this->activeLeafVoxelCount(), numActiveTiles = this->activeTileCount(); os << " Number of active voxels: " << util::formattedInt(numActiveVoxels) << "\n"; os << " Number of active tiles: " << util::formattedInt(numActiveTiles) << "\n"; Coord dim(0, 0, 0); Index64 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 * double(numActiveVoxels)) / double(totalVoxels); os << " Percentage of active voxels: " << std::setprecision(3) << activeRatio << "%\n"; if (leafCount > 0) { const double fillRatio = (100.0 * double(numActiveLeafVoxels)) / (double(leafCount) * double(LeafNodeType::NUM_VOXELS)); os << " Average leaf node fill ratio: " << fillRatio << "%\n"; } #if OPENVDB_ABI_VERSION_NUMBER >= 3 if (verboseLevel > 2) { Index64 sum = 0;// count the number of unallocated leaf nodes for (auto it = this->cbeginLeaf(); it; ++it) if (!it->isAllocated()) ++sum; os << " Number of unallocated nodes: " << util::formattedInt(sum) << " (" << (100.0 * double(sum) / double(totalNodeCount)) << "%)\n"; } #endif } else { os << " Tree is empty!\n"; } os << std::flush; if (verboseLevel == 2) return; // Memory footprint in bytes const Index64 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: "); util::printBytes(os, voxelsMem, " Active leaf voxels: "); if (numActiveVoxels) { util::printBytes(os, denseMem, " Dense equivalent: "); os << " Actual footprint is " << (100.0 * double(actualMem) / double(denseMem)) << "% of an equivalent dense volume\n"; os << " Leaf voxel footprint is " << (100.0 * double(voxelsMem) / double(actualMem)) << "% of actual footprint\n"; } } } // namespace tree } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_TREE_TREE_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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/INSTALL0000644000000000000000000002750613200122206012240 0ustar rootrootInstalling OpenVDB ================== Requirements ------------ - A C++11-compatible compiler, such as GNU GCC (gcc.gnu.org), version 4.8 or later, Clang (clang.llvm.org), version 3.8 or later, or Intel ICC (software.intel.com), version 15 or later - GNU gmake (www.gnu.org/software/make/), version 3.81 or later - Boost (www.boost.org), version 1.53.0 or later; 1.57.0 or later for the Python module (Linux: yum install boost-devel; OS X: port install boost +python27) - libz (zlib.net) (Linux: yum install zlib-devel) - OpenEXR (www.openexr.com), for the 16-bit float Half class in half.h and for .exr file output in vdb_render - 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.11 or later (www.stack.nl/~dimitri/doxygen/), for documentation - CppUnit (www.freedesktop.org/wiki/Software/cppunit), version 1.10 or later (Linux: yum install cppunit-devel) - Houdini HDK (http://www.sidefx.com/get/download-houdini/), version 15.0 or later - Blosc compression library (www.blosc.org), version 1.5.0 or later (included in the Houdini HDK as of version 14.0) - 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 - log4cplus (log4cplus.sourceforge.net), version 1.1.2 or later, for error logging - 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) BOOST_LIB_DIR the directory containing libboost_iostreams, etc. BOOST_LIB linker flags for libboost_iostreams and libboost_system BOOST_THREAD_LIB linker flags for libboost_thread ILMBASE_INCL_DIR the parent directory of the OpenEXR/ header directory (which contains half.h) ILMBASE_LIB_DIR the directory containing libHalf.so and/or libHalf.a ILMBASE_LIB linker flags for libIlmThread, libIex and libImath HALF_LIB linker flag(s) for the Half library (e.g., -lHalf) EXR_INCL_DIR the parent directory of the OpenEXR/ header directory (Note: some OpenEXR headers incorrectly include other OpenEXR headers with, e.g., '#include ' instead of '#include "ImfName.h"'. When compiling with Clang, set EXR_INCL_DIR to the parent directory of the OpenEXR/ directory and ILMBASE_INCL_DIR to the OpenEXR/ directory itself to avoid errors.) EXR_LIB_DIR the directory containing libIlmImf EXR_LIB linker flags for libIlmImf 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) BLOSC_INCL_DIR the parent directory of the blosc.h header BLOSC_LIB_DIR the directory containing libblosc BLOSC_LIB linker flags for libblosc 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) GLFW_MAJOR_VERSION the major version number of the GLFW library (header filenames changed between GLFW 2 and 3, so this must be specified explicitly) LOG4CPLUS_INCL_DIR the parent directory of the log4cplus/ header directory (leave blank if log4cplus is not available) LOG4CPLUS_LIB_DIR directory containing liblog4cplus.so or liblog4cplus.a LOG4CPLUS_LIB linker flags for the log4cplus library (e.g., -llog4cplus) PYTHON_VERSION the version of Python for which to build the OpenVDB module (e.g., 2.7) (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.7) 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. Also note that certain new features in OpenVDB 4 (see the CHANGES file for details) necessitated changes to the ABI of the Grid class, rendering it incompatible with earlier versions of the library, such as the ones built into Houdini 15, 15.5 and 16. Passing grids between native VDB nodes in a scene graph and nodes built against the new ABI will lead to crashes, so to use OpenVDB 4 with those versions of Houdini, set abi=3 to compile with the incompatible features disabled. To build the OpenVDB Python module, you will need local installations of Python, Boost.Python, and optionally NumPy. Houdini ships with Python 2 as well as Boost.Python headers, but 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.5.0.0 the OpenVDB library openvdb/libopenvdb.so symlink to libopenvdb.so.5.0.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.5.0 libopenvdb.so.5.0.0 python/ include/ python$(PYTHON_VERSION)/ pyopenvdb.h lib/ python$(PYTHON_VERSION)/ pyopenvdb.so pyopenvdb.so.5.0 share/ doc/ openvdb/ html/ index.html ... python/ index.html ... EOF openvdb/Metadata.cc0000644000000000000000000001501613200122377013240 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for std::min() #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { using Mutex = tbb::mutex; using Lock = Mutex::scoped_lock; using createMetadata = Metadata::Ptr (*)(); using MetadataFactoryMap = std::map; using MetadataFactoryMapCIter = MetadataFactoryMap::const_iterator; 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 = nullptr; if (registry == nullptr) { #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(); } //////////////////////////////////////// bool Metadata::operator==(const Metadata& other) const { if (other.size() != this->size()) return false; if (other.typeName() != this->typeName()) return false; std::ostringstream bytes(std::ios_base::binary), otherBytes(std::ios_base::binary); try { this->writeValue(bytes); other.writeValue(otherBytes); return (bytes.str() == otherBytes.str()); } catch (Exception&) {} return false; } //////////////////////////////////////// #if OPENVDB_ABI_VERSION_NUMBER >= 5 Metadata::Ptr UnknownMetadata::copy() const { Metadata::Ptr metadata{new UnknownMetadata{mTypeName}}; static_cast(metadata.get())->setValue(mBytes); return metadata; } void UnknownMetadata::copy(const Metadata& other) { std::ostringstream ostr(std::ios_base::binary); other.write(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); const auto numBytes = readSize(istr); readValue(istr, numBytes); } void UnknownMetadata::readValue(std::istream& is, Index32 numBytes) { mBytes.clear(); if (numBytes > 0) { ByteVec buffer(numBytes); is.read(reinterpret_cast(&buffer[0]), numBytes); mBytes.swap(buffer); } } void UnknownMetadata::writeValue(std::ostream& os) const { if (!mBytes.empty()) { os.write(reinterpret_cast(&mBytes[0]), mBytes.size()); } } #else // if OPENVDB_ABI_VERSION_NUMBER < 5 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"); } #endif } // namespace OPENVDB_VERSION_NAME } // namespace openvdb // Copyright (c) 2012-2017 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/0000755000000000000000000000000013200122400012512 5ustar rootrootopenvdb/python/pyAccessor.h0000644000000000000000000003214113200122377015014 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000000565513200122377015275 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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" void exportFloatGrid(); /// 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(); #ifndef PY_OPENVDB_USE_BOOST_PYTHON_NUMPY // 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"); #endif 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-2017 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/test/0000755000000000000000000000000013200122400013471 5ustar rootrootopenvdb/python/test/TestOpenVDB.py0000644000000000000000000007712113200122377016165 0ustar rootroot#!/usr/local/bin/python # Copyright (c) 2012-2017 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.assertTrue(openvdb.BoolGrid in openvdb.GridTypes) self.assertTrue(openvdb.FloatGrid in openvdb.GridTypes) self.assertTrue(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.assertTrue(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 = [int(round(x * 1000000)) for x in 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.assertTrue(grid.sharesWith(grid)) self.assertFalse(grid.sharesWith([])) # wrong type; Grid expected copyOfGrid = grid.copy() self.assertTrue(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.assertTrue(grid.saveFloatAsHalf) self.assertTrue(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.keys())) for name in meta: self.assertTrue(name in grid) self.assertEqual(grid[name], meta[name]) for name in grid: self.assertTrue(name in grid) self.assertEqual(grid[name], meta[name]) self.assertTrue('xyz' in grid) del grid['xyz'] self.assertFalse('xyz' in grid) grid['xyz'] = meta['xyz'] self.assertTrue('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.assertTrue(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.assertTrue(acc.isValueOn(ijk)) self.assertEqual(grid.activeVoxelCount(), activeCount) leafCount = grid.leafCount() grid.prune() self.assertAlmostEqual(acc.getValue(ijk), 2.125) self.assertTrue(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.0) # median self.assertTrue(acc.isValueOn(ijk)) self.assertTrue(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.assertTrue(acc.isValueOn(c)) self.assertTrue(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.assertTrue(acc.isValueOn(xyz)) self.assertEqual(copyOfAcc.getValue(xyz), True) self.assertTrue(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.assertTrue(copyOfAcc.isCached(xyz2)) self.assertFalse(copyOfAcc.isCached(xyz)) self.assertTrue(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.assertTrue(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.assertTrue(lo >= -HALF_WIDTH) self.assertTrue(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.zeros((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.zeros((1, 2)) self.assertRaises(ValueError, lambda: grid.copyFromArray(arr)) # Verify that complex-valued arrays are not supported. arr = np.zeros((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.assertTrue(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.zeros((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.zeros((1, 2)) self.assertRaises(ValueError, lambda: grid.copyToArray(arr)) # Verify that complex-valued arrays are not supported. arr = np.zeros((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) def testMeshConversion(self): import time # Skip this test if NumPy is not available. try: import numpy as np except ImportError: return # Test mesh to volume conversion. # Generate the vertices of a cube. cubeVertices = [(x, y, z) for x in (0, 100) for y in (0, 100) for z in (0, 100)] cubePoints = np.array(cubeVertices, float) # Generate the faces of a cube. cubeQuads = np.array([ (0, 1, 3, 2), # left (0, 2, 6, 4), # front (4, 6, 7, 5), # right (5, 7, 3, 1), # back (2, 3, 7, 6), # top (0, 4, 5, 1), # bottom ], float) voxelSize = 2.0 halfWidth = 3.0 xform = openvdb.createLinearTransform(voxelSize) # Only scalar, floating-point grids support createLevelSetFromPolygons() # (and the OpenVDB module might have been compiled without DoubleGrid support). grids = [] for gridType in [n for n in openvdb.GridTypes if n.__name__ in ('FloatGrid', 'DoubleGrid')]: # Skip this test if the OpenVDB module was built without NumPy support. try: grid = gridType.createLevelSetFromPolygons( cubePoints, quads=cubeQuads, transform=xform, halfWidth=halfWidth) except NotImplementedError: return #openvdb.write('/tmp/testMeshConversion.vdb', grid) self.assertEqual(grid.transform, xform) self.assertEqual(grid.background, halfWidth * voxelSize) dim = grid.evalActiveVoxelDim() self.assertTrue(50 < dim[0] < 58) self.assertTrue(50 < dim[1] < 58) self.assertTrue(50 < dim[2] < 58) grids.append(grid) # Boolean-valued grids can't be used to store level sets. self.assertRaises(TypeError, lambda: openvdb.BoolGrid.createLevelSetFromPolygons( cubePoints, quads=cubeQuads, transform=xform, halfWidth=halfWidth)) # Vector-valued grids can't be used to store level sets. self.assertRaises(TypeError, lambda: openvdb.Vec3SGrid.createLevelSetFromPolygons( cubePoints, quads=cubeQuads, transform=xform, halfWidth=halfWidth)) # The "points" argument to createLevelSetFromPolygons() must be a NumPy array. self.assertRaises(TypeError, lambda: openvdb.FloatGrid.createLevelSetFromPolygons( cubeVertices, quads=cubeQuads, transform=xform, halfWidth=halfWidth)) # The "points" argument to createLevelSetFromPolygons() must be a NumPy float or int array. self.assertRaises(TypeError, lambda: openvdb.FloatGrid.createLevelSetFromPolygons( np.array(cubeVertices, bool), quads=cubeQuads, transform=xform, halfWidth=halfWidth)) # The "triangles" argument to createLevelSetFromPolygons() must be an N x 3 NumPy array. self.assertRaises(TypeError, lambda: openvdb.FloatGrid.createLevelSetFromPolygons( cubePoints, triangles=cubeQuads, transform=xform, halfWidth=halfWidth)) # Test volume to mesh conversion. # Vector-valued grids can't be meshed. self.assertRaises(TypeError, lambda: openvdb.Vec3SGrid().convertToQuads()) for grid in grids: points, quads = grid.convertToQuads() # These checks are intended mainly to test the Python/C++ bindings, # not the OpenVDB volume to mesh converter. self.assertTrue(len(points) > 8) self.assertTrue(len(quads) > 6) pmin, pmax = points.min(0), points.max(0) self.assertTrue(-2 < pmin[0] < 2) self.assertTrue(-2 < pmin[1] < 2) self.assertTrue(-2 < pmin[2] < 2) self.assertTrue(98 < pmax[0] < 102) self.assertTrue(98 < pmax[1] < 102) self.assertTrue(98 < pmax[2] < 102) points, triangles, quads = grid.convertToPolygons(adaptivity=1) self.assertTrue(len(points) > 8) pmin, pmax = points.min(0), points.max(0) self.assertTrue(-2 < pmin[0] < 2) self.assertTrue(-2 < pmin[1] < 2) self.assertTrue(-2 < pmin[2] < 2) self.assertTrue(98 < pmax[0] < 102) self.assertTrue(98 < pmax[1] < 102) self.assertTrue(98 < pmax[2] < 102) if __name__ == '__main__': print('Testing %s' % 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-2017 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.cc0000644000000000000000000001004413200122377015126 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 static_cast(this->get_override("typeName")()); } Metadata::Ptr copy() const { return static_cast(this->get_override("copy")()); } void copy(const Metadata& other) { this->get_override("copy")(other); } std::string str() const {return static_cast(this->get_override("str")());} bool asBool() const { return static_cast(this->get_override("asBool")()); } Index32 size() const { return static_cast(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(); 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-2017 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.h0000644000000000000000000027020613200122377014145 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #include #ifndef DWA_BOOST_VERSION #include #define DWA_BOOST_VERSION (10 * BOOST_VERSION) #endif #ifdef PY_OPENVDB_USE_NUMPY #if DWA_BOOST_VERSION >= 1065000 // boost::python::numeric was replaced with boost::python::numpy in Boost 1.65. // (boost::python::numpy requires NumPy 1.7 or later.) #include //#include // for PyArray_Descr (see pyGrid::arrayTypeId()) #define PY_OPENVDB_USE_BOOST_PYTHON_NUMPY #else #define PY_ARRAY_UNIQUE_SYMBOL PY_OPENVDB_ARRAY_API #define NO_IMPORT_ARRAY // NumPy gets initialized during module initialization #include #ifdef NPY_1_7_API_VERSION #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #endif #include // for PyArrayObject #endif #include "openvdb/tools/MeshToVolume.h" #include "openvdb/tools/VolumeToMesh.h" // for tools::volumeToMesh() #endif #include "openvdb/openvdb.h" #include "openvdb/io/Stream.h" #include "openvdb/math/Math.h" // for math::isExactlyEqual() #include "openvdb/tools/LevelSetSphere.h" #include "openvdb/tools/Dense.h" #include "openvdb/tools/ChangeBackground.h" #include "openvdb/tools/Prune.h" #include "openvdb/tools/SignedFloodFill.h" #include "pyutil.h" #include "pyAccessor.h" // for pyAccessor::AccessorWrap #include "pyopenvdb.h" #include // for std::max() #include // for memcpy() #include #include #include #include namespace py = boost::python; #ifdef __clang__ // This is a private header, so it's OK to include a "using namespace" directive. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wheader-hygiene" #endif using namespace openvdb::OPENVDB_VERSION_NAME; #ifdef __clang__ #pragma clang diagnostic pop #endif 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 = nullptr) { 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 = nullptr) { return extractValueArg( obj, functionName, argIdx, expectedType); } //////////////////////////////////////// template inline typename GridType::Ptr copyGrid(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() { using ValueT = typename GridType::ValueType; 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) { tools::changeBackground(grid.tree(), 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=*/nullptr, /*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=*/nullptr, /*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=*/nullptr, /*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=*/nullptr, /*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=*/nullptr, /*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 { using Wrapper = typename pyAccessor::AccessorWrap; 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 { using Wrapper = typename pyAccessor::AccessorWrap; 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) { #if PY_MAJOR_VERSION >= 3 // Return an iterator over the "keys" view of a dict. return py::import("builtins").attr("iter")( py::dict(static_cast(*grid)).keys()); #else return py::dict(static_cast(*grid)).iterkeys(); #endif } 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__", nullptr, /*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__", nullptr, /*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() != nullptr); return false; } //////////////////////////////////////// template inline void prune(GridType& grid, py::object tolerance) { tools::prune(grid.tree(), extractValueArg(tolerance, "prune")); } template inline void pruneInactive(GridType& grid, py::object valObj) { if (valObj.is_none()) { tools::pruneInactive(grid.tree()); } else { tools::pruneInactiveWithValue( grid.tree(), 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) { tools::signedFloodFill(grid.tree()); } //////////////////////////////////////// #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) using ArrayDimVec = std::vector; #ifdef PY_OPENVDB_USE_BOOST_PYTHON_NUMPY // ID numbers for supported value types enum class DtId { NONE, FLOAT, DOUBLE, BOOL, INT16, INT32, INT64, UINT32, UINT64/*, HALF*/ }; using NumPyArrayType = py::numpy::ndarray; #else // if !defined PY_OPENVDB_USE_BOOST_PYTHON_NUMPY // NumPy type numbers for supported value types enum class DtId { NONE = NPY_NOTYPE, FLOAT = NPY_FLOAT, DOUBLE = NPY_DOUBLE, BOOL = NPY_BOOL, INT16 = NPY_INT16, INT32 = NPY_INT32, INT64 = NPY_INT64, UINT32 = NPY_UINT32, UINT64 = NPY_UINT64, //HALF = NPY_HALF }; using NumPyArrayType = py::numeric::array; #endif // PY_OPENVDB_USE_BOOST_PYTHON_NUMPY template struct NumPyToCpp {}; template<> struct NumPyToCpp { using type = float; }; template<> struct NumPyToCpp { using type = double; }; template<> struct NumPyToCpp { using type = bool; }; template<> struct NumPyToCpp { using type = Int16; }; template<> struct NumPyToCpp { using type = Int32; }; template<> struct NumPyToCpp { using type = Int64; }; template<> struct NumPyToCpp { using type = Index32; }; template<> struct NumPyToCpp { using type = Index64; }; //template<> struct NumPyToCpp { using type = half; }; #if 0 template struct CppToNumPy { static const DtId typeId = DtId::NONE; }; template<> struct CppToNumPy { static const DtId typeId = DtId::FLOAT; }; template<> struct CppToNumPy { static const DtId typeId = DtId::DOUBLE; }; template<> struct CppToNumPy { static const DtId typeId = DtId::BOOL; }; template<> struct CppToNumPy { static const DtId typeId = DtId::INT16; }; template<> struct CppToNumPy { static const DtId typeId = DtId::INT32; }; template<> struct CppToNumPy { static const DtId typeId = DtId::INT64; }; template<> struct CppToNumPy { static const DtId typeId = DtId::UINT32; }; template<> struct CppToNumPy { static const DtId typeId = DtId::UINT64; }; //template<> struct CppToNumPy { static const DtId typeId = DtId::HALF; }; #endif #ifdef PY_OPENVDB_USE_BOOST_PYTHON_NUMPY // Return the ID number of the given NumPy array's data type. /// @todo Revisit this if and when py::numpy::dtype ever provides a type number accessor. inline DtId arrayTypeId(const py::numpy::ndarray& arrayObj) { namespace np = py::numpy; const auto dtype = arrayObj.get_dtype(); #if 0 // More efficient than np::equivalent(), but requires NumPy headers. if (const auto* descr = reinterpret_cast(dtype.ptr())) { const auto typeId = static_cast(descr->type_num); switch (typeId) { case DtId::NONE: break; case DtId::FLOAT: case DtId::DOUBLE: case DtId::BOOL: case DtId::INT16: case DtId::INT32: case DtId::INT64: case DtId::UINT32: case DtId::UINT64: return typeId; } throw openvdb::TypeError{}; } #else if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::FLOAT; if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::DOUBLE; if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::BOOL; if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::INT16; if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::INT32; if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::INT64; if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::UINT32; if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::UINT64; //if (np::equivalent(dtype, np::dtype::get_builtin())) return DtId::HALF; #endif throw openvdb::TypeError{}; } // Return a string description of the given NumPy array's data type. inline std::string arrayTypeName(const py::numpy::ndarray& arrayObj) { return pyutil::str(arrayObj.get_dtype()); } // Return the dimensions of the given NumPy array. inline ArrayDimVec arrayDimensions(const py::numpy::ndarray& arrayObj) { ArrayDimVec dims; for (int i = 0, N = arrayObj.get_nd(); i < N; ++i) { dims.push_back(static_cast(arrayObj.shape(i))); } return dims; } #else // !defined PY_OPENVDB_USE_BOOST_PYTHON_NUMPY // Return the ID number of the given NumPy array's data type. inline DtId arrayTypeId(const py::numeric::array& arrayObj) { const PyArray_Descr* dtype = nullptr; if (PyArrayObject* arrayObjPtr = reinterpret_cast(arrayObj.ptr())) { dtype = PyArray_DESCR(arrayObjPtr); } if (dtype) return static_cast(dtype->type_num); throw openvdb::TypeError{}; } // Return a string description of the given NumPy array's data type. inline std::string arrayTypeName(const py::numeric::array& arrayObj) { std::string name; if (PyObject_HasAttrString(arrayObj.ptr(), "dtype")) { name = pyutil::str(arrayObj.attr("dtype")); } else { name = "'_'"; PyArrayObject* arrayObjPtr = reinterpret_cast(arrayObj.ptr()); name[1] = PyArray_DESCR(arrayObjPtr)->kind; } return name; } // Return the dimensions of the given NumPy array. inline ArrayDimVec arrayDimensions(const py::numeric::array& arrayObj) { const py::object shape = arrayObj.attr("shape"); ArrayDimVec dims; for (long i = 0, N = py::len(shape); i < N; ++i) { dims.push_back(py::extract(shape[i])); } return dims; } inline py::object copyNumPyArray(PyArrayObject* arrayObj, NPY_ORDER order = NPY_CORDER) { #ifdef __GNUC__ // Silence GCC "casting between pointer-to-function and pointer-to-object" warnings. __extension__ #endif auto obj = pyutil::pyBorrow(PyArray_NewCopy(arrayObj, order)); return obj; } #endif // PY_OPENVDB_USE_BOOST_PYTHON_NUMPY // Abstract base class for helper classes that copy data between // NumPy arrays of various types and grids of various types template class CopyOpBase { public: using ValueT = typename GridType::ValueType; 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, // or throw an exception if arrObj is not a NumPy array object. const auto arrayObj = pyutil::extractArg( arrObj, opName[toGrid], pyutil::GridTraits::name(), /*argIdx=*/1, "numpy.ndarray"); #ifdef PY_OPENVDB_USE_BOOST_PYTHON_NUMPY mArray = arrayObj.get_data(); #else mArray = PyArray_DATA(reinterpret_cast(arrayObj.ptr())); #endif mArrayTypeName = arrayTypeName(arrayObj); mArrayTypeId = arrayTypeId(arrayObj); mArrayDims = arrayDimensions(arrayObj); mTolerance = extractValueArg(tolObj, opName[toGrid], 2); // Compute the bounding box of the region of the grid that is to be copied from or to. Coord bboxMax = origin; for (size_t n = 0, N = std::min(mArrayDims.size(), 3); n < N; ++n) { bboxMax[n] += int(mArrayDims[n]) - 1; } mBBox.reset(origin, bboxMax); } 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; DtId mArrayTypeId; ArrayDimVec 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: void validate() const override { 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(); } } void copyFromArray() const override { switch (this->mArrayTypeId) { case DtId::FLOAT: this->template fromArray::type>(); break; case DtId::DOUBLE:this->template fromArray::type>();break; case DtId::BOOL: this->template fromArray::type>(); break; case DtId::INT16: this->template fromArray::type>(); break; case DtId::INT32: this->template fromArray::type>(); break; case DtId::INT64: this->template fromArray::type>(); break; case DtId::UINT32:this->template fromArray::type>();break; case DtId::UINT64:this->template fromArray::type>();break; default: throw openvdb::TypeError(); break; } } void copyToArray() const override { switch (this->mArrayTypeId) { case DtId::FLOAT: this->template toArray::type>(); break; case DtId::DOUBLE: this->template toArray::type>(); break; case DtId::BOOL: this->template toArray::type>(); break; case DtId::INT16: this->template toArray::type>(); break; case DtId::INT32: this->template toArray::type>(); break; case DtId::INT64: this->template toArray::type>(); break; case DtId::UINT32: this->template toArray::type>(); break; case DtId::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: void validate() const override { 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(); } } void copyFromArray() const override { switch (this->mArrayTypeId) { case DtId::FLOAT: this->template fromArray::type>>(); break; case DtId::DOUBLE: this->template fromArray::type>>(); break; case DtId::BOOL: this->template fromArray::type>>(); break; case DtId::INT16: this->template fromArray::type>>(); break; case DtId::INT32: this->template fromArray::type>>(); break; case DtId::INT64: this->template fromArray::type>>(); break; case DtId::UINT32: this->template fromArray::type>>(); break; case DtId::UINT64: this->template fromArray::type>>(); break; default: throw openvdb::TypeError(); break; } } void copyToArray() const override { switch (this->mArrayTypeId) { case DtId::FLOAT: this->template toArray::type>>(); break; case DtId::DOUBLE: this->template toArray::type>>(); break; case DtId::BOOL: this->template toArray::type>>(); break; case DtId::INT16: this->template toArray::type>>(); break; case DtId::INT32: this->template toArray::type>>(); break; case DtId::INT64: this->template toArray::type>>(); break; case DtId::UINT32: this->template toArray::type>>(); break; case DtId::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) { using ValueT = typename GridType::ValueType; CopyOp::Size> op(/*toGrid=*/true, grid, arrayObj, coordObj, toleranceObj); op(); } template inline void copyToArray(GridType& grid, py::object arrayObj, py::object coordObj) { using ValueT = typename GridType::ValueType; CopyOp::Size> op(/*toGrid=*/false, grid, arrayObj, coordObj); op(); } #endif // defined(PY_OPENVDB_USE_NUMPY) //////////////////////////////////////// #ifndef PY_OPENVDB_USE_NUMPY template inline typename GridType::Ptr meshToLevelSet(py::object, py::object, py::object, py::object, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); return typename GridType::Ptr(); } template inline py::object volumeToQuadMesh(const GridType&, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); return py::object(); } template inline py::object volumeToMesh(const GridType&, py::object, py::object) { PyErr_SetString(PyExc_NotImplementedError, "this module was built without NumPy support"); boost::python::throw_error_already_set(); return py::object(); } #else // if defined(PY_OPENVDB_USE_NUMPY) // Helper class for meshToLevelSet() template struct CopyVecOp { void operator()(const void* srcPtr, DstT* dst, size_t count) { const SrcT* src = static_cast(srcPtr); for (size_t i = count; i > 0; --i, ++src, ++dst) { *dst = static_cast(*src); } } }; // Partial specialization for source and destination arrays of the same type template struct CopyVecOp { void operator()(const void* srcPtr, T* dst, size_t count) { const T* src = static_cast(srcPtr); ::memcpy(dst, src, count * sizeof(T)); } }; // Helper function for use with meshToLevelSet() to copy vectors of various types // and sizes from NumPy arrays to STL vectors template inline void copyVecArray(NumPyArrayType& arrayObj, std::vector& vec) { using ValueT = typename VecT::ValueType; // Get the input array dimensions. const auto dims = arrayDimensions(arrayObj); const size_t M = dims.empty() ? 0 : dims[0]; const size_t N = VecT().numElements(); if (M == 0 || N == 0) return; // Preallocate the output vector. vec.resize(M); // Copy values from the input array to the output vector (with type conversion, if necessary). #ifdef PY_OPENVDB_USE_BOOST_PYTHON_NUMPY const void* src = arrayObj.get_data(); #else PyArrayObject* arrayObjPtr = reinterpret_cast(arrayObj.ptr()); const void* src = PyArray_DATA(arrayObjPtr); #endif ValueT* dst = &vec[0][0]; switch (arrayTypeId(arrayObj)) { case DtId::FLOAT: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case DtId::DOUBLE: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case DtId::INT16: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case DtId::INT32: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case DtId::INT64: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case DtId::UINT32: CopyVecOp::type, ValueT>()(src, dst, M*N); break; case DtId::UINT64: CopyVecOp::type, ValueT>()(src, dst, M*N); break; default: break; } } /// @brief Given NumPy arrays of points, triangle indices and quad indices, /// call tools::meshToLevelSet() to generate a level set grid. template inline typename GridType::Ptr meshToLevelSet(py::object pointsObj, py::object trianglesObj, py::object quadsObj, py::object xformObj, py::object halfWidthObj) { struct Local { // Return the name of the Python grid method (for use in error messages). static const char* methodName() { return "createLevelSetFromPolygons"; } // Raise a Python exception if the given NumPy array does not have dimensions M x N // or does not have an integer or floating-point data type. static void validate2DNumPyArray(NumPyArrayType arrayObj, const size_t N, const char* desiredType) { const auto dims = arrayDimensions(arrayObj); bool wrongArrayType = false; // Check array dimensions. if (dims.size() != 2 || dims[1] != N) { wrongArrayType = true; } else { // Check array data type. switch (arrayTypeId(arrayObj)) { case DtId::FLOAT: case DtId::DOUBLE: //case DtId::HALF: case DtId::INT16: case DtId::INT32: case DtId::INT64: case DtId::UINT32: case DtId::UINT64: break; default: wrongArrayType = true; break; } } if (wrongArrayType) { // Generate an error message and raise a Python TypeError. std::ostringstream os; os << "expected N x 3 numpy.ndarray of " << desiredType << ", found "; switch (dims.size()) { case 0: os << "zero-dimensional"; break; case 1: os << "one-dimensional"; break; default: os << dims[0]; for (size_t i = 1; i < dims.size(); ++i) { os << " x " << dims[i]; } break; } os << " " << arrayTypeName(arrayObj) << " array as argument 1 to " << pyutil::GridTraits::name() << "." << methodName() << "()"; PyErr_SetString(PyExc_TypeError, os.str().c_str()); py::throw_error_already_set(); } } }; // Extract the narrow band half width from the arguments to this method. const float halfWidth = extractValueArg( halfWidthObj, Local::methodName(), /*argIdx=*/5, "float"); // Extract the transform from the arguments to this method. math::Transform::Ptr xform = math::Transform::createLinearTransform(); if (!xformObj.is_none()) { xform = extractValueArg( xformObj, Local::methodName(), /*argIdx=*/4, "Transform"); } // Extract the list of mesh vertices from the arguments to this method. std::vector points; if (!pointsObj.is_none()) { // Extract a reference to (not a copy of) a NumPy array argument, // or throw an exception if the argument is not a NumPy array object. auto arrayObj = extractValueArg( pointsObj, Local::methodName(), /*argIdx=*/1, "numpy.ndarray"); // Throw an exception if the array has the wrong type or dimensions. Local::validate2DNumPyArray(arrayObj, /*N=*/3, /*desiredType=*/"float"); // Copy values from the array to the vector. copyVecArray(arrayObj, points); } // Extract the list of triangle indices from the arguments to this method. std::vector triangles; if (!trianglesObj.is_none()) { auto arrayObj = extractValueArg( trianglesObj, Local::methodName(), /*argIdx=*/2, "numpy.ndarray"); Local::validate2DNumPyArray(arrayObj, /*N=*/3, /*desiredType=*/"int"); copyVecArray(arrayObj, triangles); } // Extract the list of quad indices from the arguments to this method. std::vector quads; if (!quadsObj.is_none()) { auto arrayObj = extractValueArg( quadsObj, Local::methodName(), /*argIdx=*/3, "numpy.ndarray"); Local::validate2DNumPyArray(arrayObj, /*N=*/4, /*desiredType=*/"int"); copyVecArray(arrayObj, quads); } // Generate and return a level set grid. return tools::meshToLevelSet(*xform, points, triangles, quads, halfWidth); } template inline py::object volumeToQuadMesh(const GridType& grid, py::object isovalueObj) { const double isovalue = pyutil::extractArg( isovalueObj, "convertToQuads", /*className=*/nullptr, /*argIdx=*/2, "float"); // Mesh the input grid and populate lists of mesh vertices and face vertex indices. std::vector points; std::vector quads; tools::volumeToMesh(grid, points, quads, isovalue); #ifdef PY_OPENVDB_USE_BOOST_PYTHON_NUMPY const py::object own; auto dtype = py::numpy::dtype::get_builtin(); auto shape = py::make_tuple(points.size(), 3); auto stride = py::make_tuple(3 * sizeof(Vec3s::value_type), sizeof(Vec3s::value_type)); // Create a deep copy of the array (because the point vector will be destroyed // when this function returns). auto pointArrayObj = py::numpy::from_data(points.data(), dtype, shape, stride, own).copy(); dtype = py::numpy::dtype::get_builtin(); shape = py::make_tuple(quads.size(), 4); stride = py::make_tuple(4 * sizeof(Vec4I::value_type), sizeof(Vec4I::value_type)); auto quadArrayObj = py::numpy::from_data( quads.data(), dtype, shape, stride, own).copy(); // deep copy #else // !defined PY_OPENVDB_USE_BOOST_PYTHON_NUMPY // Copy vertices into an N x 3 NumPy array. py::object pointArrayObj = py::numeric::array(py::list(), "float32"); if (!points.empty()) { npy_intp dims[2] = { npy_intp(points.size()), 3 }; // Construct a NumPy array that wraps the point vector. if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*nd=*/2, dims, NPY_FLOAT, &points[0]))) { // Create a deep copy of the array (because the point vector will be // destroyed when this function returns). pointArrayObj = copyNumPyArray(arrayObj, NPY_CORDER); } } // Copy face indices into an N x 4 NumPy array. py::object quadArrayObj = py::numeric::array(py::list(), "uint32"); if (!quads.empty()) { npy_intp dims[2] = { npy_intp(quads.size()), 4 }; if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*dims=*/2, dims, NPY_UINT32, &quads[0]))) { quadArrayObj = copyNumPyArray(arrayObj, NPY_CORDER); } } #endif // PY_OPENVDB_USE_BOOST_PYTHON_NUMPY return py::make_tuple(pointArrayObj, quadArrayObj); } template inline py::object volumeToMesh(const GridType& grid, py::object isovalueObj, py::object adaptivityObj) { const double isovalue = pyutil::extractArg( isovalueObj, "convertToPolygons", /*className=*/nullptr, /*argIdx=*/2, "float"); const double adaptivity = pyutil::extractArg( adaptivityObj, "convertToPolygons", /*className=*/nullptr, /*argIdx=*/3, "float"); // Mesh the input grid and populate lists of mesh vertices and face vertex indices. std::vector points; std::vector triangles; std::vector quads; tools::volumeToMesh(grid, points, triangles, quads, isovalue, adaptivity); #ifdef PY_OPENVDB_USE_BOOST_PYTHON_NUMPY const py::object own; auto dtype = py::numpy::dtype::get_builtin(); auto shape = py::make_tuple(points.size(), 3); auto stride = py::make_tuple(3 * sizeof(Vec3s::value_type), sizeof(Vec3s::value_type)); // Create a deep copy of the array (because the point vector will be destroyed // when this function returns). auto pointArrayObj = py::numpy::from_data(points.data(), dtype, shape, stride, own).copy(); dtype = py::numpy::dtype::get_builtin(); shape = py::make_tuple(triangles.size(), 3); stride = py::make_tuple(3 * sizeof(Vec3I::value_type), sizeof(Vec3I::value_type)); auto triangleArrayObj = py::numpy::from_data( triangles.data(), dtype, shape, stride, own).copy(); // deep copy dtype = py::numpy::dtype::get_builtin(); shape = py::make_tuple(quads.size(), 4); stride = py::make_tuple(4 * sizeof(Vec4I::value_type), sizeof(Vec4I::value_type)); auto quadArrayObj = py::numpy::from_data( quads.data(), dtype, shape, stride, own).copy(); // deep copy #else // !defined PY_OPENVDB_USE_BOOST_PYTHON_NUMPY // Copy vertices into an N x 3 NumPy array. py::object pointArrayObj = py::numeric::array(py::list(), "float32"); if (!points.empty()) { npy_intp dims[2] = { npy_intp(points.size()), 3 }; // Construct a NumPy array that wraps the point vector. if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*dims=*/2, dims, NPY_FLOAT, &points[0]))) { // Create a deep copy of the array (because the point vector will be // destroyed when this function returns). pointArrayObj = copyNumPyArray(arrayObj, NPY_CORDER); } } // Copy triangular face indices into an N x 3 NumPy array. py::object triangleArrayObj = py::numeric::array(py::list(), "uint32"); if (!triangles.empty()) { npy_intp dims[2] = { npy_intp(triangles.size()), 3 }; if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*dims=*/2, dims, NPY_UINT32, &triangles[0]))) { triangleArrayObj = copyNumPyArray(arrayObj, NPY_CORDER); } } // Copy quadrilateral face indices into an N x 4 NumPy array. py::object quadArrayObj = py::numeric::array(py::list(), "uint32"); if (!quads.empty()) { npy_intp dims[2] = { npy_intp(quads.size()), 4 }; if (PyArrayObject* arrayObj = reinterpret_cast( PyArray_SimpleNewFromData(/*dims=*/2, dims, NPY_UINT32, &quads[0]))) { quadArrayObj = copyNumPyArray(arrayObj, NPY_CORDER); } } #endif // PY_OPENVDB_USE_BOOST_PYTHON_NUMPY return py::make_tuple(pointArrayObj, triangleArrayObj, quadArrayObj); } #endif // defined(PY_OPENVDB_USE_NUMPY) //////////////////////////////////////// template inline void applyMap(const char* methodName, GridType& grid, py::object funcObj) { using ValueT = typename GridType::ValueType; 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 { using TreeT = typename GridType::TreeType; using ValueT = typename GridType::ValueType; 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) { using GridPtr = typename GridType::Ptr; 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 { using IterT = typename GridT::ValueOnCIter; 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 { using IterT = typename GridT::ValueOffCIter; 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 { using IterT = typename GridT::ValueAllCIter; 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 { using IterT = typename GridT::ValueOnIter; 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 { using IterT = typename GridT::ValueOffIter; 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 { using IterT = typename GridT::ValueAllIter; 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 { using ValueT = typename GridT::ValueType; 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 { using ValueT = typename GridT::ValueType; static void setValue(const IterT&, const ValueT&) { PyErr_SetString(PyExc_AttributeError, "can't set attribute 'value'"); py::throw_error_already_set(); } static void setActive(const IterT&, 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: using GridT = _GridT; using IterT = _IterT; using ValueT = typename GridT::ValueType; using SetterT = IterItemSetter; 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", nullptr }; 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] != nullptr; ++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] != nullptr; ++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() && math::isExactlyEqual(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] != nullptr; ++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: using GridT = _GridT; using IterT = _IterT; using ValueT = typename GridT::ValueType; using IterValueProxyT = IterValueProxy; using Traits = IterTraits; 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("__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: public py::pickle_suite { using GridPtrT = typename GridT::Ptr; /// 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. #if PY_MAJOR_VERSION >= 3 // Convert the byte string to a "bytes" sequence. const std::string s = ostr.str(); py::object bytesObj = pyutil::pyBorrow(PyBytes_FromStringAndSize(s.data(), s.size())); #else py::str bytesObj(ostr.str()); #endif state = py::make_tuple(gridObj.attr("__dict__"), bytesObj); } 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 sequence containing the serialized Grid. py::object bytesObj = state[1]; #if PY_MAJOR_VERSION >= 3 badState = true; if (PyBytes_Check(bytesObj.ptr())) { // Convert the "bytes" sequence to a byte string. char* buf = nullptr; Py_ssize_t length = 0; if (-1 != PyBytes_AsStringAndSize(bytesObj.ptr(), &buf, &length)) { if (buf != nullptr && length > 0) { serialized.assign(buf, buf + length); badState = false; } } } #else py::extract x(bytesObj); if (x.check()) serialized = x(); else badState = true; #endif } if (badState) { PyErr_SetObject(PyExc_ValueError, #if PY_MAJOR_VERSION >= 3 ("expected (dict, bytes) tuple in call to __setstate__; found %s" #else ("expected (dict, str) tuple in call to __setstate__; found %s" #endif % 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() { using ValueT = typename GridType::ValueType; using GridPtr = typename GridType::Ptr; using Traits = pyutil::GridTraits; using ValueOnCIterT = typename GridType::ValueOnCIter; using ValueOffCIterT = typename GridType::ValueOffCIter; using ValueAllCIterT = typename GridType::ValueAllCIter; using ValueOnIterT = typename GridType::ValueOnIter; using ValueOffIterT = typename GridType::ValueOffIter; using ValueAllIterT = typename GridType::ValueAllIter; 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("convertToQuads", &pyGrid::volumeToQuadMesh, (py::arg("isovalue")=0), "convertToQuads(isovalue=0) -> points, quads\n\n" "Uniformly mesh a scalar grid that has a continuous isosurface\n" "at the given isovalue. Return a NumPy array of world-space\n" "points and a NumPy array of 4-tuples of point indices, which\n" "specify the vertices of the quadrilaterals that form the mesh.") .def("convertToPolygons", &pyGrid::volumeToMesh, (py::arg("isovalue")=0, py::arg("adaptivity")=0), "convertToPolygons(isovalue=0, adaptivity=0) -> points, triangles, quads\n\n" "Adaptively mesh a scalar grid that has a continuous isosurface\n" "at the given isovalue. Return a NumPy array of world-space\n" "points and NumPy arrays of 3- and 4-tuples of point indices,\n" "which specify the vertices of the triangles and quadrilaterals\n" "that form the mesh. Adaptivity can vary from 0 to 1, where 0\n" "produces a high-polygon-count mesh that closely approximates\n" "the isosurface, and 1 produces a lower-polygon-count mesh\n" "with some loss of surface detail.") .def("createLevelSetFromPolygons", &pyGrid::meshToLevelSet, (py::arg("points"), py::arg("triangles")=py::object(), py::arg("quads")=py::object(), py::arg("transform")=py::object(), py::arg("halfWidth")=openvdb::LEVEL_SET_HALF_WIDTH), ("createLevelSetFromPolygons(points, triangles=None, quads=None,\n" " transform=None, halfWidth=" + boost::lexical_cast(openvdb::LEVEL_SET_HALF_WIDTH) + ") -> " + pyGridTypeName + "\n\n" "Convert a triangle and/or quad mesh to a narrow-band level set volume.\n" "The mesh must form a closed surface, but the surface need not be\n" "manifold and may have self intersections and degenerate faces.\n" "The mesh is described by a NumPy array of world-space points\n" "and NumPy arrays of 3- and 4-tuples of point indices that specify\n" "the vertices of the triangles and quadrilaterals that form the mesh.\n" "Either the triangle or the quad array may be empty or None.\n" "The resulting volume will have the given transform (or the identity\n" "transform if no transform is given) and a narrow band width of\n" "2 x halfWidth voxels.").c_str()) .staticmethod("createLevelSetFromPolygons") .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_ #if DWA_BOOST_VERSION >= 1060000 && DWA_BOOST_VERSION < 1065000 // Boost versions 1.60 through 1.6x, for some x < 5, require the GridPtr-to-Python // object converter to be explicitly registered. py::register_ptr_to_python(); #endif 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-2017 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.cc0000644000000000000000000007452313200122377016025 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for std::make_pair() #include #include #include #ifndef DWA_BOOST_VERSION #include #define DWA_BOOST_VERSION (10 * BOOST_VERSION) #endif #if defined PY_OPENVDB_USE_NUMPY && DWA_BOOST_VERSION < 1065000 #define PY_ARRAY_UNIQUE_SYMBOL PY_OPENVDB_ARRAY_API #include #ifdef NPY_1_7_API_VERSION #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #endif #include // for import_array() #endif #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 nullptr if the given Python object is not convertible to a Coord. static void* convertible(PyObject* obj) { if (!PySequence_Check(obj)) return nullptr; // not a Python sequence Py_ssize_t len = PySequence_Length(obj); if (len != 3 && len != 1) return nullptr; // 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. using StorageT = py::converter::rvalue_from_python_storage; 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 nullptr; // not a Python sequence Py_ssize_t len = PySequence_Length(obj); if (len != VecT::size) return nullptr; // 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 nullptr; } } return obj; } static void construct(PyObject* obj, py::converter::rvalue_from_python_stage1_data* data) { // Construct a Vec in the provided memory location. using StorageT = py::converter::rvalue_from_python_storage; 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 : nullptr); } static void construct(PyObject* obj, py::converter::rvalue_from_python_stage1_data* data) { // Construct a MetaMap in the provided memory location. using StorageT = py::converter::rvalue_from_python_storage; 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 (PyLong_Check(val.ptr()) && PyLong_AsLong(val.ptr()) <= std::numeric_limits::max() && PyLong_AsLong(val.ptr()) >= std::numeric_limits::min()) { value.reset(new Int32Metadata(py::extract(val))); } else if (PyLong_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 = int(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&, const std::string&); py::tuple readAllFromFile(const std::string&); py::dict readFileMetadata(const std::string&); py::object readGridMetadataFromFile(const std::string&, const std::string&); py::list readAllGridMetadataFromFile(const std::string&); void writeToFile(const std::string&, py::object, py::object); 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(); } //////////////////////////////////////// std::string getLoggingLevel(); void setLoggingLevel(py::object); void setProgramName(py::object, bool); std::string getLoggingLevel() { switch (logging::getLevel()) { case logging::Level::Debug: return "debug"; case logging::Level::Info: return "info"; case logging::Level::Warn: return "warn"; case logging::Level::Error: return "error"; case logging::Level::Fatal: break; } return "fatal"; } void setLoggingLevel(py::object pyLevelObj) { std::string levelStr; if (!py::extract(pyLevelObj).check()) { levelStr = py::extract(pyLevelObj.attr("__str__")()); } else { const py::str pyLevelStr = py::extract(pyLevelObj.attr("lower")().attr("lstrip")("-")); levelStr = py::extract(pyLevelStr); if (levelStr == "debug") { logging::setLevel(logging::Level::Debug); return; } else if (levelStr == "info") { logging::setLevel(logging::Level::Info); return; } else if (levelStr == "warn") { logging::setLevel(logging::Level::Warn); return; } else if (levelStr == "error") { logging::setLevel(logging::Level::Error); return; } else if (levelStr == "fatal") { logging::setLevel(logging::Level::Fatal); return; } } PyErr_Format(PyExc_ValueError, "expected logging level \"debug\", \"info\", \"warn\", \"error\", or \"fatal\"," " got \"%s\"", levelStr.c_str()); py::throw_error_already_set(); } void setProgramName(py::object nameObj, bool color) { if (py::extract(nameObj).check()) { logging::setProgramName(py::extract(nameObj), color); } else { const std::string str = py::extract(nameObj.attr("__str__")()), typ = pyutil::className(nameObj).c_str(); PyErr_Format(PyExc_TypeError, "expected string as program name, got \"%s\" of type %s", str.c_str(), typ.c_str()); py::throw_error_already_set(); } } //////////////////////////////////////// // 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(nullptr), static_cast(nullptr)); } }; // 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(nullptr), static_cast(nullptr)); } }; } // namespace _openvdbmodule //////////////////////////////////////// #ifdef DWA_OPENVDB #define PY_OPENVDB_MODULE_NAME _openvdb extern "C" { void init_openvdb(); } #else #define PY_OPENVDB_MODULE_NAME pyopenvdb extern "C" { void initpyopenvdb(); } #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(); #ifdef PY_OPENVDB_USE_NUMPY // Initialize NumPy. #ifdef PY_OPENVDB_USE_BOOST_PYTHON_NUMPY boost::python::numpy::initialize(); #else #if PY_MAJOR_VERSION >= 3 if (_import_array()) {} #else import_array(); #endif #endif #endif 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."); py::def("getLoggingLevel", &_openvdbmodule::getLoggingLevel, "getLoggingLevel() -> str\n\n" "Return the severity threshold (\"debug\", \"info\", \"warn\", \"error\",\n" "or \"fatal\") for error messages."); py::def("setLoggingLevel", &_openvdbmodule::setLoggingLevel, (py::arg("level")), "setLoggingLevel(level)\n\n" "Specify the severity threshold (\"debug\", \"info\", \"warn\", \"error\",\n" "or \"fatal\") for error messages. Messages of lower severity\n" "will be suppressed."); py::def("setProgramName", &_openvdbmodule::setProgramName, (py::arg("name"), py::arg("color") = true), "setProgramName(name, color=True)\n\n" "Specify the program name to be displayed in error messages,\n" "and optionally specify whether to print error messages in color."); // Add some useful module-level constants. py::scope().attr("LIBRARY_VERSION") = py::make_tuple( openvdb::OPENVDB_LIBRARY_MAJOR_VERSION, openvdb::OPENVDB_LIBRARY_MINOR_VERSION, openvdb::OPENVDB_LIBRARY_PATCH_VERSION); py::scope().attr("FILE_FORMAT_VERSION") = openvdb::OPENVDB_FILE_VERSION; 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-2017 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.cc0000644000000000000000000003351113200122377015365 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 Coord worldToIndexCellCentered(math::Transform& t, const Vec3d& p) { return t.worldToIndexCellCentered(p); } inline Coord worldToIndexNodeCentered(math::Transform& t, const Vec3d& p) { return t.worldToIndexNodeCentered(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 (int 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 (int 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: public 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. #if PY_MAJOR_VERSION >= 3 // Convert the byte string to a "bytes" sequence. const std::string s = ostr.str(); py::object bytesObj = pyutil::pyBorrow(PyBytes_FromStringAndSize(s.data(), s.size())); #else py::str bytesObj(ostr.str()); #endif state = py::make_tuple( xformObj.attr("__dict__"), uint32_t(OPENVDB_LIBRARY_MAJOR_VERSION), uint32_t(OPENVDB_LIBRARY_MINOR_VERSION), uint32_t(OPENVDB_FILE_VERSION), bytesObj); } return state; } /// Restore the given Transform to a saved state. static void setstate(py::object xformObj, py::object stateObj) { math::Transform* xform = nullptr; { 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 sequence containing the serialized Transform. py::object bytesObj = state[int(STATE_XFORM)]; #if PY_MAJOR_VERSION >= 3 badState = true; if (PyBytes_Check(bytesObj.ptr())) { // Convert the "bytes" sequence to a byte string. char* buf = NULL; Py_ssize_t length = 0; if (-1 != PyBytes_AsStringAndSize(bytesObj.ptr(), &buf, &length)) { if (buf != NULL && length > 0) { serialized.assign(buf, buf + length); badState = false; } } } #else py::extract x(bytesObj); if (x.check()) serialized = x(); else badState = true; #endif } if (badState) { PyErr_SetObject(PyExc_ValueError, #if PY_MAJOR_VERSION >= 3 ("expected (dict, int, int, int, bytes) tuple in call to __setstate__; found %s" #else ("expected (dict, int, int, int, str) tuple in call to __setstate__; found %s" #endif % 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(); 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", &pyTransform::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", &pyTransform::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-2017 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.h0000644000000000000000000002414513200122377014234 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000000411313200122377015014 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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(); void exportVec3Grid() { pyGrid::exportGrid(); #ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES pyGrid::exportGrid(); pyGrid::exportGrid(); #endif } // Copyright (c) 2012-2017 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.h0000644000000000000000000001054713200122377014715 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000000412013200122377014744 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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(); void exportIntGrid() { pyGrid::exportGrid(); #ifdef PY_OPENVDB_WRAP_ALL_GRID_TYPES pyGrid::exportGrid(); pyGrid::exportGrid(); #endif } // Copyright (c) 2012-2017 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/0000755000000000000000000000000013200122400013050 5ustar rootrootopenvdb/unittest/TestNodeMask.cc0000644000000000000000000001601413200122377015737 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // 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] = static_cast(::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-2017 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.cc0000644000000000000000000000562213200122377016747 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000002174013200122377015355 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 remove() 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(testReadAllMask); CPPUNIT_TEST(testReadAllFloat); CPPUNIT_TEST(testReadAllVec3S); CPPUNIT_TEST(testReadAllFloat5432); CPPUNIT_TEST_SUITE_END(); void testReadAllBool() { readAllTest(); } void testReadAllMask() { readAllTest(); } void testReadAllFloat() { readAllTest(); } void testReadAllVec3S() { readAllTest(); } void testReadAllFloat5432() { Float5432Grid::registerGrid(); 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-2017 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.cc0000644000000000000000000005733213200122377016174 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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; using BBoxd = BBox; 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 = openvdb::StaticPtrCast(t->baseMap()); NonlinearFrustumMap::Ptr frustuminv = openvdb::StaticPtrCast(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 = openvdb::StaticPtrCast(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-2017 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.cc0000644000000000000000000005055213200122377020140 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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(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: typedef openvdb::Vec3R PosType; 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 = openvdb::Index32(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 = int(pa.size()); i < s; ++i) { dist=openvdb::math::Min(dist,(xyz-pa.pos(i)).length()-pa.radius(i)); } const float val = ls->tree().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 = int(pa.size()); i < s; ++i) { double d = (xyz-pa.pos(i)).length()-pa.radius(i); if (dtree().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-2017 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.cc0000644000000000000000000004473613200122377016136 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "util.h" // for unittest_util::makeSphere() #include #include class TestLaplacian: public CppUnit::TestFixture { public: void setUp() override { openvdb::initialize(); } void tearDown() override { 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; 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(static_cast(c[0]), static_cast(c[1]), static_cast(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 FloatGrid::ConstAccessor 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; 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(static_cast(c[0]), static_cast(c[1]), static_cast(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; 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(static_cast(c[0]), static_cast(c[1]), static_cast(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; FloatGrid::ConstAccessor 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 = StaticPtrCast(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 = StaticPtrCast( 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 = float(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; 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(static_cast(c[0]), static_cast(c[1]), static_cast(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 = StaticPtrCast(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; 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; 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-2017 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/TestPointDelete.cc0000644000000000000000000002164313200122377016456 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #ifdef _MSC_VER #include #endif using namespace openvdb::points; class TestPointDelete: public CppUnit::TestCase { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestPointDelete); CPPUNIT_TEST(testDeleteFromGroups); CPPUNIT_TEST_SUITE_END(); void testDeleteFromGroups(); }; // class TestPointDelete CPPUNIT_TEST_SUITE_REGISTRATION(TestPointDelete); //////////////////////////////////////// void TestPointDelete::testDeleteFromGroups() { using openvdb::math::Vec3s; using openvdb::tools::PointIndexGrid; using openvdb::Index64; const float voxelSize(1.0); openvdb::math::Transform::Ptr transform(openvdb::math::Transform::createLinearTransform(voxelSize)); std::vector positions6Points = { {1, 1, 1}, {1, 2, 1}, {2, 1, 1}, {2, 2, 1}, {100, 100, 100}, {100, 101, 100} }; const PointAttributeVector pointList6Points(positions6Points); { // delete from a tree with 2 leaves, checking that group membership is updated as // expected PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid(pointList6Points, *transform); PointDataGrid::Ptr grid = createPointDataGrid(*pointIndexGrid, pointList6Points, *transform); PointDataTree& tree = grid->tree(); // first test will delete 3 groups, with the third one empty. appendGroup(tree, "test1"); appendGroup(tree, "test2"); appendGroup(tree, "test3"); appendGroup(tree, "test4"); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(6)); std::vector membership1{1, 0, 0, 0, 0, 1}; setGroup(tree, pointIndexGrid->tree(), membership1, "test1"); std::vector membership2{0, 0, 1, 1, 0, 1}; setGroup(tree, pointIndexGrid->tree(), membership2, "test2"); std::vector groupsToDelete{"test1", "test2", "test3"}; deleteFromGroups(tree, groupsToDelete); // 4 points should have been deleted, so only 2 remain CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(2)); // check that first three groups are deleted but the last is not const PointDataTree::LeafCIter leafIterAfterDeletion = tree.cbeginLeaf(); AttributeSet attributeSetAfterDeletion = leafIterAfterDeletion->attributeSet(); AttributeSet::Descriptor& descriptor = attributeSetAfterDeletion.descriptor(); CPPUNIT_ASSERT(!descriptor.hasGroup("test1")); CPPUNIT_ASSERT(!descriptor.hasGroup("test2")); CPPUNIT_ASSERT(!descriptor.hasGroup("test3")); CPPUNIT_ASSERT(descriptor.hasGroup("test4")); } { // check deletion from a single leaf tree and that attribute values are preserved // correctly after deletion std::vector positions4Points = { {1, 1, 1}, {1, 2, 1}, {2, 1, 1}, {2, 2, 1}, }; const PointAttributeVector pointList4Points(positions4Points); PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid(pointList4Points, *transform); PointDataGrid::Ptr grid = createPointDataGrid(*pointIndexGrid, pointList4Points, *transform); PointDataTree& tree = grid->tree(); appendGroup(tree, "test"); appendAttribute(tree, "testAttribute", TypedAttributeArray::attributeType()); CPPUNIT_ASSERT(tree.beginLeaf()); const PointDataTree::LeafIter leafIter = tree.beginLeaf(); AttributeWriteHandle testAttributeWriteHandle(leafIter->attributeArray("testAttribute")); for(int i = 0; i < 4; i++) { testAttributeWriteHandle.set(i,i+1); } std::vector membership{0, 1, 1, 0}; setGroup(tree, pointIndexGrid->tree(), membership, "test"); deleteFromGroup(tree, "test"); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(2)); const PointDataTree::LeafCIter leafIterAfterDeletion = tree.cbeginLeaf(); const AttributeSet attributeSetAfterDeletion = leafIterAfterDeletion->attributeSet(); const AttributeSet::Descriptor& descriptor = attributeSetAfterDeletion.descriptor(); CPPUNIT_ASSERT(descriptor.find("testAttribute") != AttributeSet::INVALID_POS); AttributeHandle testAttributeHandle(*attributeSetAfterDeletion.get("testAttribute")); CPPUNIT_ASSERT_EQUAL(1, testAttributeHandle.get(0)); CPPUNIT_ASSERT_EQUAL(4, testAttributeHandle.get(1)); } { // test the invert flag using data similar to that used in the first test PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid(pointList6Points, *transform); PointDataGrid::Ptr grid = createPointDataGrid(*pointIndexGrid, pointList6Points, *transform); PointDataTree& tree = grid->tree(); appendGroup(tree, "test1"); appendGroup(tree, "test2"); appendGroup(tree, "test3"); appendGroup(tree, "test4"); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(6)); std::vector membership1{1, 0, 1, 1, 0, 1}; setGroup(tree, pointIndexGrid->tree(), membership1, "test1"); std::vector membership2{0, 0, 1, 1, 0, 1}; setGroup(tree, pointIndexGrid->tree(), membership2, "test2"); std::vector groupsToDelete{"test1", "test3"}; deleteFromGroups(tree, groupsToDelete, true); const PointDataTree::LeafCIter leafIterAfterDeletion = tree.cbeginLeaf(); const AttributeSet attributeSetAfterDeletion = leafIterAfterDeletion->attributeSet(); const AttributeSet::Descriptor& descriptor = attributeSetAfterDeletion.descriptor(); // no groups should be dropped when invert = true CPPUNIT_ASSERT_EQUAL(static_cast(descriptor.groupMap().size()), static_cast(4)); // 4 points should remain since test1 and test3 have 4 members between then CPPUNIT_ASSERT_EQUAL(static_cast(pointCount(tree)), static_cast(4)); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000000547313200122377017154 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000001165613200122377015150 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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/TestPointsToMask.cc0000644000000000000000000001616513200122377016640 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 math::Random01 #include #include #include #include #include #include #include "util.h" // for genPoints struct TestPointsToMask: public CppUnit::TestCase { CPPUNIT_TEST_SUITE(TestPointsToMask); CPPUNIT_TEST(testPointsToMask); CPPUNIT_TEST_SUITE_END(); void testPointsToMask(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPointsToMask); //////////////////////////////////////// namespace { class PointList { public: PointList(const std::vector& points) : mPoints(&points) {} size_t size() const { return mPoints->size(); } void getPos(size_t n, openvdb::Vec3R& xyz) const { xyz = (*mPoints)[n]; } protected: std::vector const * const mPoints; }; // PointList } // namespace //////////////////////////////////////// void TestPointsToMask::testPointsToMask() { {// BoolGrid // generate one point std::vector points; points.push_back( openvdb::Vec3R(-19.999, 4.50001, 6.71) ); //points.push_back( openvdb::Vec3R( 20,-4.5,-5.2) ); PointList pointList(points); // construct an empty mask grid openvdb::BoolGrid grid( false ); const float voxelSize = 0.1f; grid.setTransform( openvdb::math::Transform::createLinearTransform(voxelSize) ); CPPUNIT_ASSERT( grid.empty() ); // generate mask from points openvdb::tools::PointsToMask mask( grid ); mask.addPoints( pointList ); CPPUNIT_ASSERT(!grid.empty() ); CPPUNIT_ASSERT_EQUAL( 1, int(grid.activeVoxelCount()) ); openvdb::BoolGrid::ValueOnCIter iter = grid.cbeginValueOn(); //std::cerr << "Coord = " << iter.getCoord() << std::endl; const openvdb::Coord p(-200, 45, 67); CPPUNIT_ASSERT( iter.getCoord() == p ); CPPUNIT_ASSERT(grid.tree().isValueOn( p ) ); } {// MaskGrid // generate one point std::vector points; points.push_back( openvdb::Vec3R(-19.999, 4.50001, 6.71) ); //points.push_back( openvdb::Vec3R( 20,-4.5,-5.2) ); PointList pointList(points); // construct an empty mask grid openvdb::MaskGrid grid( false ); const float voxelSize = 0.1f; grid.setTransform( openvdb::math::Transform::createLinearTransform(voxelSize) ); CPPUNIT_ASSERT( grid.empty() ); // generate mask from points openvdb::tools::PointsToMask<> mask( grid ); mask.addPoints( pointList ); CPPUNIT_ASSERT(!grid.empty() ); CPPUNIT_ASSERT_EQUAL( 1, int(grid.activeVoxelCount()) ); openvdb::TopologyGrid::ValueOnCIter iter = grid.cbeginValueOn(); //std::cerr << "Coord = " << iter.getCoord() << std::endl; const openvdb::Coord p(-200, 45, 67); CPPUNIT_ASSERT( iter.getCoord() == p ); CPPUNIT_ASSERT(grid.tree().isValueOn( p ) ); } // generate shared transformation openvdb::Index64 voxelCount = 0; const float voxelSize = 0.001f; const openvdb::math::Transform::Ptr xform = openvdb::math::Transform::createLinearTransform(voxelSize); // generate lots of points std::vector points; unittest_util::genPoints(15000000, points); PointList pointList(points); //openvdb::util::CpuTimer timer; {// serial BoolGrid // construct an empty mask grid openvdb::BoolGrid grid( false ); grid.setTransform( xform ); CPPUNIT_ASSERT( grid.empty() ); // generate mask from points openvdb::tools::PointsToMask mask( grid ); //timer.start("\nSerial BoolGrid"); mask.addPoints( pointList, 0 ); //timer.stop(); CPPUNIT_ASSERT(!grid.empty() ); //grid.print(std::cerr, 3); voxelCount = grid.activeVoxelCount(); } {// parallel BoolGrid // construct an empty mask grid openvdb::BoolGrid grid( false ); grid.setTransform( xform ); CPPUNIT_ASSERT( grid.empty() ); // generate mask from points openvdb::tools::PointsToMask mask( grid ); //timer.start("\nParallel BoolGrid"); mask.addPoints( pointList ); //timer.stop(); CPPUNIT_ASSERT(!grid.empty() ); //grid.print(std::cerr, 3); CPPUNIT_ASSERT_EQUAL( voxelCount, grid.activeVoxelCount() ); } {// parallel MaskGrid // construct an empty mask grid openvdb::MaskGrid grid( false ); grid.setTransform( xform ); CPPUNIT_ASSERT( grid.empty() ); // generate mask from points openvdb::tools::PointsToMask<> mask( grid ); //timer.start("\nParallel MaskGrid"); mask.addPoints( pointList ); //timer.stop(); CPPUNIT_ASSERT(!grid.empty() ); //grid.print(std::cerr, 3); CPPUNIT_ASSERT_EQUAL( voxelCount, grid.activeVoxelCount() ); } {// parallel create TopologyGrid //timer.start("\nParallel Create MaskGrid"); openvdb::MaskGrid::Ptr grid = openvdb::tools::createPointMask(pointList, *xform); //timer.stop(); CPPUNIT_ASSERT(!grid->empty() ); //grid->print(std::cerr, 3); CPPUNIT_ASSERT_EQUAL( voxelCount, grid->activeVoxelCount() ); } } // Copyright (c) 2012-2017 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/TestVolumeToSpheres.cc0000644000000000000000000002064613200122377017350 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for createLevelSetSphere #include // for sdfToFogVolume #include // for fillWithSpheres #include class TestVolumeToSpheres: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestVolumeToSpheres); CPPUNIT_TEST(testFromLevelSet); CPPUNIT_TEST(testFromFog); CPPUNIT_TEST(testClosestSurfacePoint); CPPUNIT_TEST_SUITE_END(); void testFromLevelSet(); void testFromFog(); void testClosestSurfacePoint(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestVolumeToSpheres); //////////////////////////////////////// void TestVolumeToSpheres::testFromLevelSet() { const float radius = 20.0f, voxelSize = 1.0f, halfWidth = 3.0f; const openvdb::Vec3f center(15.0f, 13.0f, 16.0f); openvdb::FloatGrid::ConstPtr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, halfWidth); const int maxSphereCount = 100, instanceCount = 10000; const bool overlapping = false; const float isovalue = 0.0, minRadius = 5.0, maxRadius = std::numeric_limits::max(); { std::vector spheres; openvdb::tools::fillWithSpheres(*grid, spheres, maxSphereCount, overlapping, minRadius, maxRadius, isovalue, instanceCount); CPPUNIT_ASSERT_EQUAL(1, int(spheres.size())); //for (size_t i=0; i< spheres.size(); ++i) { // std::cout << "\nSphere #" << i << ": " << spheres[i] << std::endl; //} const auto tolerance = 2.0 * voxelSize; CPPUNIT_ASSERT_DOUBLES_EQUAL(center[0], spheres[0][0], tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(center[1], spheres[0][1], tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(center[2], spheres[0][2], tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(radius, spheres[0][3], tolerance); } { // Verify that an isovalue outside the narrow band still produces a valid sphere. std::vector spheres; openvdb::tools::fillWithSpheres(*grid, spheres, maxSphereCount, overlapping, minRadius, maxRadius, 1.5 * halfWidth, instanceCount); CPPUNIT_ASSERT_EQUAL(1, int(spheres.size())); } { // Verify that an isovalue inside the narrow band produces no spheres. std::vector spheres; openvdb::tools::fillWithSpheres(*grid, spheres, maxSphereCount, overlapping, minRadius, maxRadius, -1.5 * halfWidth, instanceCount); CPPUNIT_ASSERT_EQUAL(0, int(spheres.size())); } } void TestVolumeToSpheres::testFromFog() { const float radius = 20.0f, voxelSize = 1.0f, halfWidth = 3.0f; const openvdb::Vec3f center(15.0f, 13.0f, 16.0f); auto grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, halfWidth); openvdb::tools::sdfToFogVolume(*grid); const int maxSphereCount = 100, instanceCount = 10000; const bool overlapping = false; const float isovalue = 0.01f, minRadius = 5.0, maxRadius = std::numeric_limits::max(); { std::vector spheres; openvdb::tools::fillWithSpheres(*grid, spheres, maxSphereCount, overlapping, minRadius, maxRadius, isovalue, instanceCount); //for (size_t i=0; i< spheres.size(); ++i) { // std::cout << "\nSphere #" << i << ": " << spheres[i] << std::endl; //} CPPUNIT_ASSERT_EQUAL(1, int(spheres.size())); const auto tolerance = 2.0 * voxelSize; CPPUNIT_ASSERT_DOUBLES_EQUAL(center[0], spheres[0][0], tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(center[1], spheres[0][1], tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(center[2], spheres[0][2], tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(radius, spheres[0][3], tolerance); } { // Verify that an isovalue outside the narrow band still produces valid spheres. std::vector spheres; openvdb::tools::fillWithSpheres(*grid, spheres, maxSphereCount, overlapping, minRadius, maxRadius, 10.0f, instanceCount); CPPUNIT_ASSERT(!spheres.empty()); } } void TestVolumeToSpheres::testClosestSurfacePoint() { using namespace openvdb; const float voxelSize = 1.0f; const Vec3f center{0.0f}; // ensure multiple internal nodes for (const float radius: { 8.0f, 50.0f }) { // Construct a spherical level set. const auto sphere = tools::createLevelSetSphere(radius, center, voxelSize); CPPUNIT_ASSERT(sphere); // Construct the corners of a cube that exactly encloses the sphere. const std::vector corners{ { -radius, -radius, -radius }, { -radius, -radius, radius }, { -radius, radius, -radius }, { -radius, radius, radius }, { radius, -radius, -radius }, { radius, -radius, radius }, { radius, radius, -radius }, { radius, radius, radius }, }; // Compute the distance from a corner of the cube to the surface of the sphere. const auto distToSurface = Vec3d{radius}.length() - radius; auto csp = tools::ClosestSurfacePoint::create(*sphere); CPPUNIT_ASSERT(csp); // Move each corner point to the closest surface point. auto points = corners; std::vector distances; bool ok = csp->searchAndReplace(points, distances); CPPUNIT_ASSERT(ok); CPPUNIT_ASSERT_EQUAL(8, int(points.size())); CPPUNIT_ASSERT_EQUAL(8, int(distances.size())); for (auto d: distances) { CPPUNIT_ASSERT((std::abs(d - distToSurface) / distToSurface) < 0.01); // rel err < 1% } for (int i = 0; i < 8; ++i) { const auto intersection = corners[i] + distToSurface * (center - corners[i]).unit(); CPPUNIT_ASSERT(points[i].eq(intersection, /*tolerance=*/0.1)); } // Place a point inside the sphere. points.clear(); distances.clear(); points.emplace_back(1, 0, 0); ok = csp->searchAndReplace(points, distances); CPPUNIT_ASSERT(ok); CPPUNIT_ASSERT_EQUAL(1, int(points.size())); CPPUNIT_ASSERT_EQUAL(1, int(distances.size())); CPPUNIT_ASSERT((std::abs(radius - 1 - distances[0]) / (radius - 1)) < 0.01); CPPUNIT_ASSERT(points[0].eq(Vec3R{radius, 0, 0}, /*tolerance=*/0.5)); ///< @todo off by half a voxel in y and z } } // Copyright (c) 2012-2017 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/TestLeafManager.cc0000644000000000000000000003254013200122377016402 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "util.h" // for unittest_util::makeSphere() #include class TestLeafManager: public CppUnit::TestFixture { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestLeafManager); CPPUNIT_TEST(testBasics); CPPUNIT_TEST(testActiveLeafVoxelCount); CPPUNIT_TEST(testForeach); CPPUNIT_TEST(testReduce); CPPUNIT_TEST_SUITE_END(); void testBasics(); void testActiveLeafVoxelCount(); void testForeach(); void testReduce(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafManager); void TestLeafManager::testBasics() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3f; using openvdb::FloatGrid; using openvdb::FloatTree; const Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f; const int dim = 128, half_width = 5; const float voxel_size = 1.0f/dim; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/half_width*voxel_size); FloatTree& tree = grid->tree(); grid->setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/voxel_size)); unittest_util::makeSphere( Coord(dim), center, radius, *grid, unittest_util::SPHERE_SPARSE_NARROW_BAND); const size_t leafCount = tree.leafCount(); //grid->print(std::cout, 3); {// test with no aux buffers openvdb::tree::LeafManager r(tree); CPPUNIT_ASSERT_EQUAL(leafCount, r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBufferCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBuffersPerLeaf()); size_t n = 0; for (FloatTree::LeafCIter iter=tree.cbeginLeaf(); iter; ++iter, ++n) { CPPUNIT_ASSERT(r.leaf(n) == *iter); CPPUNIT_ASSERT(r.getBuffer(n,0) == iter->buffer()); } CPPUNIT_ASSERT_EQUAL(r.leafCount(), n); CPPUNIT_ASSERT(!r.swapBuffer(0,0)); r.rebuildAuxBuffers(2); CPPUNIT_ASSERT_EQUAL(leafCount, r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(2), r.auxBuffersPerLeaf()); CPPUNIT_ASSERT_EQUAL(size_t(2*leafCount),r.auxBufferCount()); for (n=0; n r(tree, 2); CPPUNIT_ASSERT_EQUAL(leafCount, r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(2), r.auxBuffersPerLeaf()); CPPUNIT_ASSERT_EQUAL(size_t(2*leafCount),r.auxBufferCount()); size_t n = 0; for (FloatTree::LeafCIter iter=tree.cbeginLeaf(); iter; ++iter, ++n) { CPPUNIT_ASSERT(r.leaf(n) == *iter); CPPUNIT_ASSERT(r.getBuffer(n,0) == iter->buffer()); CPPUNIT_ASSERT(r.getBuffer(n,0) == r.getBuffer(n,1)); CPPUNIT_ASSERT(r.getBuffer(n,1) == r.getBuffer(n,2)); CPPUNIT_ASSERT(r.getBuffer(n,0) == r.getBuffer(n,2)); } CPPUNIT_ASSERT_EQUAL(r.leafCount(), n); for (n=0; n r(tree); for (size_t numAuxBuffers = 0; numAuxBuffers <= 2; ++numAuxBuffers += 2) { r.rebuildAuxBuffers(numAuxBuffers); CPPUNIT_ASSERT_EQUAL(leafCount, r.leafCount()); CPPUNIT_ASSERT_EQUAL(int(numAuxBuffers * leafCount), int(r.auxBufferCount())); CPPUNIT_ASSERT_EQUAL(numAuxBuffers, r.auxBuffersPerLeaf()); size_t n = 0; for (FloatTree::LeafCIter iter = tree.cbeginLeaf(); iter; ++iter, ++n) { CPPUNIT_ASSERT(r.leaf(n) == *iter); // Verify that each aux buffer was initialized with a copy of the leaf buffer. for (size_t bufIdx = 0; bufIdx < numAuxBuffers; ++bufIdx) { CPPUNIT_ASSERT(r.getBuffer(n, bufIdx) == iter->buffer()); } } CPPUNIT_ASSERT_EQUAL(r.leafCount(), n); for (size_t i = 0; i < numAuxBuffers; ++i) { for (size_t j = 0; j < numAuxBuffers; ++j) { // Verify that swapping buffers with themselves and swapping // leaf buffers with aux buffers have no effect. const bool canSwap = (i != j && i != 0 && j != 0); CPPUNIT_ASSERT_EQUAL(canSwap, r.swapBuffer(i, j)); } } } } } void TestLeafManager::testActiveLeafVoxelCount() { using namespace openvdb; for (const Int32 dim: { 87, 1023, 1024, 2023 }) { const CoordBBox denseBBox{Coord{0}, Coord{dim - 1}}; const auto size = denseBBox.volume(); // Create a large dense tree for testing but use a MaskTree to // minimize the memory overhead MaskTree tree{false}; tree.denseFill(denseBBox, true, true); // Add some tiles, which should not contribute to the leaf voxel count. tree.addTile(/*level=*/2, Coord{10000}, true, true); tree.addTile(/*level=*/1, Coord{-10000}, true, true); tree.addTile(/*level=*/1, Coord{20000}, false, false); tree::LeafManager mgr(tree); // On a dual CPU Intel(R) Xeon(R) E5-2697 v3 @ 2.60GHz // the speedup of LeafManager::activeLeafVoxelCount over // Tree::activeLeafVoxelCount is ~15x (assuming a LeafManager already exists) //openvdb::util::CpuTimer t("\nTree::activeVoxelCount"); const auto treeActiveVoxels = tree.activeVoxelCount(); //t.restart("\nTree::activeLeafVoxelCount"); const auto treeActiveLeafVoxels = tree.activeLeafVoxelCount(); //t.restart("\nLeafManager::activeLeafVoxelCount"); const auto mgrActiveLeafVoxels = mgr.activeLeafVoxelCount();//multi-threaded //t.stop(); //std::cerr << "Old1 = " << treeActiveVoxels << " old2 = " << treeActiveLeafVoxels // << " New = " << mgrActiveLeafVoxels << std::endl; CPPUNIT_ASSERT(size < treeActiveVoxels); CPPUNIT_ASSERT_EQUAL(size, treeActiveLeafVoxels); CPPUNIT_ASSERT_EQUAL(size, mgrActiveLeafVoxels); } } namespace { struct ForeachOp { ForeachOp(float v) : mV(v) {} template void operator()(T &leaf, size_t) const { for (typename T::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) { if ( *iter > mV) iter.setValue( 2.0f ); } } const float mV; };// ForeachOp struct ReduceOp { ReduceOp(float v) : mV(v), mN(0) {} ReduceOp(const ReduceOp &other) : mV(other.mV), mN(other.mN) {} ReduceOp(const ReduceOp &other, tbb::split) : mV(other.mV), mN(0) {} template void operator()(T &leaf, size_t) { for (typename T::ValueOnIter iter = leaf.beginValueOn(); iter; ++iter) { if ( *iter > mV) ++mN; } } void join(const ReduceOp &other) {mN += other.mN;} const float mV; openvdb::Index mN; };// ReduceOp }//unnamed namespace void TestLeafManager::testForeach() { using namespace openvdb; FloatTree tree( 0.0f ); const int dim = int(FloatTree::LeafNodeType::dim()); const CoordBBox bbox1(Coord(0),Coord(dim-1)); const CoordBBox bbox2(Coord(dim),Coord(2*dim-1)); tree.fill( bbox1, -1.0f); tree.fill( bbox2, 1.0f); tree.voxelizeActiveTiles(); for (CoordBBox::Iterator iter(bbox1); iter; ++iter) { CPPUNIT_ASSERT_EQUAL( -1.0f, tree.getValue(*iter)); } for (CoordBBox::Iterator iter(bbox2); iter; ++iter) { CPPUNIT_ASSERT_EQUAL( 1.0f, tree.getValue(*iter)); } tree::LeafManager r(tree); CPPUNIT_ASSERT_EQUAL(size_t(2), r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBufferCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBuffersPerLeaf()); ForeachOp op(0.0f); r.foreach(op); CPPUNIT_ASSERT_EQUAL(size_t(2), r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBufferCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBuffersPerLeaf()); for (CoordBBox::Iterator iter(bbox1); iter; ++iter) { CPPUNIT_ASSERT_EQUAL( -1.0f, tree.getValue(*iter)); } for (CoordBBox::Iterator iter(bbox2); iter; ++iter) { CPPUNIT_ASSERT_EQUAL( 2.0f, tree.getValue(*iter)); } } void TestLeafManager::testReduce() { using namespace openvdb; FloatTree tree( 0.0f ); const int dim = int(FloatTree::LeafNodeType::dim()); const CoordBBox bbox1(Coord(0),Coord(dim-1)); const CoordBBox bbox2(Coord(dim),Coord(2*dim-1)); tree.fill( bbox1, -1.0f); tree.fill( bbox2, 1.0f); tree.voxelizeActiveTiles(); for (CoordBBox::Iterator iter(bbox1); iter; ++iter) { CPPUNIT_ASSERT_EQUAL( -1.0f, tree.getValue(*iter)); } for (CoordBBox::Iterator iter(bbox2); iter; ++iter) { CPPUNIT_ASSERT_EQUAL( 1.0f, tree.getValue(*iter)); } tree::LeafManager r(tree); CPPUNIT_ASSERT_EQUAL(size_t(2), r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBufferCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBuffersPerLeaf()); ReduceOp op(0.0f); r.reduce(op); CPPUNIT_ASSERT_EQUAL(FloatTree::LeafNodeType::numValues(), op.mN); CPPUNIT_ASSERT_EQUAL(size_t(2), r.leafCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBufferCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), r.auxBuffersPerLeaf()); Index n = 0; for (CoordBBox::Iterator iter(bbox1); iter; ++iter) { ++n; CPPUNIT_ASSERT_EQUAL( -1.0f, tree.getValue(*iter)); } CPPUNIT_ASSERT_EQUAL(FloatTree::LeafNodeType::numValues(), n); n = 0; for (CoordBBox::Iterator iter(bbox2); iter; ++iter) { ++n; CPPUNIT_ASSERT_EQUAL( 1.0f, tree.getValue(*iter)); } CPPUNIT_ASSERT_EQUAL(FloatTree::LeafNodeType::numValues(), n); } // Copyright (c) 2012-2017 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/TestMultiResGrid.cc0000644000000000000000000003470013200122377016612 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for remove() class TestMultiResGrid : public CppUnit::TestCase { // Use to test logic in openvdb::tools::MultiResGrid struct CoordMask { static int Mask(int i, int j, int k) { return (i & 1) | ((j & 1) << 1) | ((k & 1) << 2); } CoordMask() : mask(0) {} CoordMask(const openvdb::Coord &c ) : mask( Mask(c[0],c[1],c[2]) ) {} inline void setCoord(int i, int j, int k) { mask = Mask(i,j,k); } inline void setCoord(const openvdb::Coord &c) { mask = Mask(c[0],c[1],c[2]); } inline bool allEven() const { return mask == 0; } inline bool xOdd() const { return mask == 1; } inline bool yOdd() const { return mask == 2; } inline bool zOdd() const { return mask == 4; } inline bool xyOdd() const { return mask == 3; } inline bool xzOdd() const { return mask == 5; } inline bool yzOdd() const { return mask == 6; } inline bool allOdd() const { return mask == 7; } int mask; };// CoordMask public: CPPUNIT_TEST_SUITE(TestMultiResGrid); CPPUNIT_TEST(testTwosComplement); CPPUNIT_TEST(testCoordMask); CPPUNIT_TEST(testManualTopology); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testModels); CPPUNIT_TEST_SUITE_END(); void testTwosComplement(); void testCoordMask(); void testManualTopology(); void testIO(); void testModels(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMultiResGrid); // Uncomment to test on models from our web-site //#define TestMultiResGrid_DATA_PATH "/home/kmu/src/openvdb/data/" //#define TestMultiResGrid_DATA_PATH "/usr/pic1/Data/OpenVDB/LevelSetModels/" void TestMultiResGrid::testTwosComplement() { // test bit-operations that assume 2's complement representation of negative integers CPPUNIT_ASSERT_EQUAL( 1, 13 & 1 );// odd CPPUNIT_ASSERT_EQUAL( 1,-13 & 1 );// odd CPPUNIT_ASSERT_EQUAL( 0, 12 & 1 );// even CPPUNIT_ASSERT_EQUAL( 0,-12 & 1 );// even CPPUNIT_ASSERT_EQUAL( 0, 0 & 1 );// even for (int i=-50; i<=50; ++i) { if ( (i % 2) == 0 ) {//i.e. even number CPPUNIT_ASSERT_EQUAL( 0, i & 1); CPPUNIT_ASSERT_EQUAL( i, (i >> 1) << 1 ); } else {//i.e. odd number CPPUNIT_ASSERT_EQUAL( 1, i & 1); CPPUNIT_ASSERT( i != (i >> 1) << 1 ); } } } void TestMultiResGrid::testCoordMask() { using namespace openvdb; CoordMask mask; mask.setCoord(-4, 2, 18); CPPUNIT_ASSERT(mask.allEven()); mask.setCoord(1, 2, -6); CPPUNIT_ASSERT(mask.xOdd()); mask.setCoord(4, -3, -6); CPPUNIT_ASSERT(mask.yOdd()); mask.setCoord(-8, 2, -7); CPPUNIT_ASSERT(mask.zOdd()); mask.setCoord(1, -3, 2); CPPUNIT_ASSERT(mask.xyOdd()); mask.setCoord(1, 2, -7); CPPUNIT_ASSERT(mask.xzOdd()); mask.setCoord(-10, 3, -3); CPPUNIT_ASSERT(mask.yzOdd()); mask.setCoord(1, 3,-3); CPPUNIT_ASSERT(mask.allOdd()); } void TestMultiResGrid::testManualTopology() { // Perform tests when the sparsity (or topology) of the multiple grids is defined manually using namespace openvdb; typedef tools::MultiResGrid MultiResGridT; const double background = -1.0; const size_t levels = 4; MultiResGridT::Ptr mrg(new MultiResGridT( levels, background)); CPPUNIT_ASSERT(mrg != nullptr); CPPUNIT_ASSERT_EQUAL(levels , mrg->numLevels()); CPPUNIT_ASSERT_EQUAL(size_t(0), mrg->finestLevel()); CPPUNIT_ASSERT_EQUAL(levels-1, mrg->coarsestLevel()); // Define grid domain so they exactly overlap! const int w = 16;//half-width of dense patch on the finest grid level const CoordBBox bbox( Coord(-w), Coord(w) );// both inclusive // First check all trees against the background value for (size_t level = 0; level < mrg->numLevels(); ++level) { for (CoordBBox::Iterator iter(bbox>>level); iter; ++iter) { CPPUNIT_ASSERT_DOUBLES_EQUAL(background, mrg->tree(level).getValue(*iter), /*tolerance=*/0.0); } } // Fill all trees according to a power of two refinement pattern for (size_t level = 0; level < mrg->numLevels(); ++level) { mrg->tree(level).fill( bbox>>level, double(level)); mrg->tree(level).voxelizeActiveTiles();// avoid active tiles // Check values for (CoordBBox::Iterator iter(bbox>>level); iter; ++iter) { CPPUNIT_ASSERT_DOUBLES_EQUAL(double(level), mrg->tree(level).getValue(*iter), /*tolerance=*/0.0); } //mrg->tree( level ).print(std::cerr, 2); // Check bounding box of active voxels CoordBBox bbox_actual;// Expected Tree dimensions: 33^3 -> 17^3 -> 9^3 ->5^3 mrg->tree( level ).evalActiveVoxelBoundingBox( bbox_actual ); CPPUNIT_ASSERT_EQUAL( bbox >> level, bbox_actual ); } //pick a grid point that is shared between all the grids const Coord ijk(0); // Value at ijk equals the level CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, mrg->tree(2).getValue(ijk>>2), /*tolerance=*/ 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, mrg->sampleValue<0>(ijk, size_t(2)), /*tolerance=*/ 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, mrg->sampleValue<1>(ijk, size_t(2)), /*tolerance=*/ 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, mrg->sampleValue<2>(ijk, size_t(2)), /*tolerance=*/ 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, mrg->sampleValue<1>(ijk, 2.0f), /*tolerance=*/ 0.001); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, mrg->sampleValue<1>(ijk, float(2)), /*tolerance=*/ 0.001); // Value at ijk at floating point level CPPUNIT_ASSERT_DOUBLES_EQUAL(2.25, mrg->sampleValue<1>(ijk, 2.25f), /*tolerance=*/ 0.001); // Value at a floating-point position close to ijk and a floating point level CPPUNIT_ASSERT_DOUBLES_EQUAL(2.25, mrg->sampleValue<1>(Vec3R(0.124), 2.25f), /*tolerance=*/ 0.001); // prolongate at a given point at top level CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, mrg->prolongateVoxel(ijk, 0), /*tolerance=*/ 0.0); // First check the coarsest level (3) for (CoordBBox::Iterator iter(bbox>>size_t(3)); iter; ++iter) { CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, mrg->tree(3).getValue(*iter), /*tolerance=*/0.0); } // Prolongate from level 3 -> level 2 and check values mrg->prolongateActiveVoxels(2); for (CoordBBox::Iterator iter(bbox>>size_t(2)); iter; ++iter) { CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, mrg->tree(2).getValue(*iter), /*tolerance=*/0.0); } // Prolongate from level 2 -> level 1 and check values mrg->prolongateActiveVoxels(1); for (CoordBBox::Iterator iter(bbox>>size_t(1)); iter; ++iter) { CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, mrg->tree(1).getValue(*iter), /*tolerance=*/0.0); } // Prolongate from level 1 -> level 0 and check values mrg->prolongateActiveVoxels(0); for (CoordBBox::Iterator iter(bbox); iter; ++iter) { CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, mrg->tree(0).getValue(*iter), /*tolerance=*/0.0); } // Redefine values at the finest level and check values mrg->finestTree().fill( bbox, 5.0 ); mrg->finestTree().voxelizeActiveTiles();// avoid active tiles for (CoordBBox::Iterator iter(bbox); iter; ++iter) { CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, mrg->tree(0).getValue(*iter), /*tolerance=*/0.0); } // USE RESTRICTION BY INJECTION since it doesn't have boundary issues // // Restrict from level 0 -> level 1 and check values // mrg->restrictActiveVoxels(1); // for (CoordBBox::Iterator iter((bbox>>1UL).expandBy(-1)); iter; ++iter) { // CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, mrg->tree(1).getValue(*iter), /*tolerance=*/0.0); // } // // Restrict from level 1 -> level 2 and check values // mrg->restrictActiveVoxels(2); // for (CoordBBox::Iterator iter(bbox>>2UL); iter; ++iter) { // CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, mrg->tree(2).getValue(*iter), /*tolerance=*/0.0); // } // // Restrict from level 2 -> level 3 and check values // mrg->restrictActiveVoxels(3); // for (CoordBBox::Iterator iter(bbox>>3UL); iter; ++iter) { // CPPUNIT_ASSERT_DOUBLES_EQUAL(5.0, mrg->tree(3).getValue(*iter), /*tolerance=*/0.0); // } } void TestMultiResGrid::testIO() { using namespace openvdb; const float radius = 1.0f; const Vec3f center(0.0f, 0.0f, 0.0f); const float voxelSize = 0.01f; openvdb::FloatGrid::Ptr ls = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); ls->setName("LevelSetSphere"); typedef tools::MultiResGrid MultiResGridT; const size_t levels = 4; // Generate LOD sequence MultiResGridT mrg( levels, *ls, /* reduction by injection */ false ); //mrg.print( std::cout, 3 ); CPPUNIT_ASSERT_EQUAL(levels , mrg.numLevels()); CPPUNIT_ASSERT_EQUAL(size_t(0), mrg.finestLevel()); CPPUNIT_ASSERT_EQUAL(levels-1, mrg.coarsestLevel()); // Check inside and outside values for ( size_t level = 1; level < mrg.numLevels(); ++level) { const float inside = mrg.sampleValue<1>( Coord(0,0,0), 0UL, level ); CPPUNIT_ASSERT_DOUBLES_EQUAL( -ls->background(), inside,/*tolerance=*/ 0.001 ); const float outside = mrg.sampleValue<1>( Coord( int(1.1*radius/voxelSize) ), 0UL, level ); CPPUNIT_ASSERT_DOUBLES_EQUAL( ls->background(), outside,/*tolerance=*/ 0.001 ); } const std::string filename( "sphere.vdb" ); // Write grids io::File outputFile( filename ); outputFile.write( *mrg.grids() ); outputFile.close(); // Read grids openvdb::initialize(); openvdb::io::File file( filename ); file.open(); GridPtrVecPtr grids = file.getGrids(); CPPUNIT_ASSERT_EQUAL( levels, grids->size() ); //std::cerr << "\nsize = " << grids->size() << std::endl; for ( size_t i=0; isize(); ++i ) { FloatGrid::Ptr grid = gridPtrCast(grids->at(i)); CPPUNIT_ASSERT_EQUAL( grid->activeVoxelCount(), mrg.tree(i).activeVoxelCount() ); //grid->print(std::cerr, 3); } file.close(); ::remove(filename.c_str()); } void TestMultiResGrid::testModels() { using namespace openvdb; #ifdef TestMultiResGrid_DATA_PATH initialize();//required whenever I/O of OpenVDB files is performed! const std::string path(TestMultiResGrid_DATA_PATH); std::vector filenames; filenames.push_back("armadillo.vdb"); filenames.push_back("buddha.vdb"); filenames.push_back("bunny.vdb"); filenames.push_back("crawler.vdb"); filenames.push_back("dragon.vdb"); filenames.push_back("iss.vdb"); filenames.push_back("utahteapot.vdb"); util::CpuTimer timer; for ( size_t i=0; i\"" << filenames[i] << "\" =====================" << std::endl; std::cerr << "Reading \"" << filenames[i] << "\" ..."; io::File file( path + filenames[i] ); file.open(false);//disable delayed loading FloatGrid::Ptr model = gridPtrCast(file.getGrids()->at(0)); std::cerr << " done\nProcessing \"" << filenames[i] << "\" ..."; timer.start("\nMultiResGrid processing"); tools::MultiResGrid mrg( 6, model ); timer.stop(); std::cerr << "\n High-res level set " << tools::checkLevelSet(*mrg.grid(0)) << "\n"; std::cerr << " done\nWriting \"" << filenames[i] << "\" ..."; io::File file( "/tmp/" + filenames[i] ); file.write( *mrg.grids() ); file.close(); std::cerr << " done\n" << std::endl; // {// in-betweening // timer.start("\nIn-betweening"); // FloatGrid::Ptr model3 = mrg.createGrid( 1.9999f ); // timer.stop(); // // // std::cerr << "\n" << tools::checkLevelSet(*model3) << "\n"; // // // GridPtrVecPtr grids2( new GridPtrVec ); // grids2->push_back( model3 ); // io::File file2( "/tmp/inbetween_" + filenames[i] ); // file2.write( *grids2 ); // file2.close(); // } // {// prolongate // timer.start("\nProlongate"); // mrg.prolongateActiveVoxels(1); // FloatGrid::Ptr model31= mrg.grid(1); // timer.stop(); // GridPtrVecPtr grids2( new GridPtrVec ); // grids2->push_back( model31 ); // io::File file2( "/tmp/prolongate_" + filenames[i] ); // file2.write( *grids2 ); // file2.close(); // } //::remove(filenames[i].c_str()); } #endif } // Copyright (c) 2012-2017 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.cc0000644000000000000000000001367013200122377015763 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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: void setUp() override { openvdb::Metadata::clearRegistry(); } void tearDown() override { openvdb::Metadata::clearRegistry(); } CPPUNIT_TEST_SUITE(TestMetadata); CPPUNIT_TEST(testMetadataRegistry); CPPUNIT_TEST(testMetadataAsBool); CPPUNIT_TEST(testCustomMetadata); CPPUNIT_TEST_SUITE_END(); void testMetadataRegistry(); void testMetadataAsBool(); void testCustomMetadata(); }; 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()); } } void TestMetadata::testCustomMetadata() { using namespace openvdb; const Vec3i expected(1, 2, 3); std::ostringstream ostr(std::ios_base::binary); { Vec3IMetadata::registerType(); Vec3IMetadata meta(expected); // Write Vec3I metadata to a byte string. meta.write(ostr); } // Unregister Vec3I metadata. Metadata::clearRegistry(); #if OPENVDB_ABI_VERSION_NUMBER >= 5 { std::istringstream istr(ostr.str(), std::ios_base::binary); UnknownMetadata meta; // Verify that metadata of an unregistered type can be read successfully. CPPUNIT_ASSERT_NO_THROW(meta.read(istr)); // Verify that the metadata matches the original vector value. CPPUNIT_ASSERT_EQUAL(sizeof(Vec3i), size_t(meta.size())); CPPUNIT_ASSERT(meta.value().size() == size_t(meta.size())); CPPUNIT_ASSERT_EQUAL(expected, *reinterpret_cast(&meta.value()[0])); ostr.str(""); meta.write(ostr); // Verify that UnknownMetadata can be copied. auto metaPtr = meta.copy(); CPPUNIT_ASSERT(metaPtr.get() != nullptr); CPPUNIT_ASSERT(meta == *metaPtr); // Verify that typed metadata can be copied into UnknownMetadata. meta.copy(Vec3IMetadata(expected)); CPPUNIT_ASSERT_EQUAL(sizeof(expected), size_t(meta.size())); const auto* ptr = reinterpret_cast(&expected); CPPUNIT_ASSERT(UnknownMetadata::ByteVec(ptr, ptr + sizeof(expected)) == meta.value()); } #endif Vec3IMetadata::registerType(); { std::istringstream istr(ostr.str(), std::ios_base::binary); Vec3IMetadata meta; meta.read(istr); CPPUNIT_ASSERT_EQUAL(expected, meta.value()); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000027034613200122377015127 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for tools::sdfToFogVolume() #include #include #include #include "util.h" // for unittest_util::makeSphere() #include #include // for tbb::this_tbb_thread::sleep() #include // for std::sort() #include // for remove() and rename() #include #include // for std::bind() #include #include #include #include #include #include #include #include // for stat() #include #ifndef _WIN32 #include #endif #ifdef OPENVDB_USE_BLOSC #include #include // for memset() #endif class TestFile: public CppUnit::TestCase { public: void setUp() override {} void tearDown() override { 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(testReadGrid); #if OPENVDB_ABI_VERSION_NUMBER >= 3 CPPUNIT_TEST(testReadClippedGrid); #endif CPPUNIT_TEST(testMultiPassIO); CPPUNIT_TEST(testHasGrid); CPPUNIT_TEST(testNameIterator); CPPUNIT_TEST(testReadOldFileFormat); CPPUNIT_TEST(testCompression); CPPUNIT_TEST(testAsync); #ifdef OPENVDB_USE_BLOSC CPPUNIT_TEST(testBlosc); #endif 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 testReadGrid(); #if OPENVDB_ABI_VERSION_NUMBER >= 3 void testReadClippedGrid(); #endif void testMultiPassIO(); void testHasGrid(); void testNameIterator(); void testReadOldFileFormat(); void testCompression(); void testAsync(); #ifdef OPENVDB_USE_BLOSC void testBlosc(); #endif }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFile); //////////////////////////////////////// void TestFile::testHeader() { using namespace openvdb::io; File file("something.vdb2"); std::ostringstream ostr(std::ios_base::binary), ostr2(std::ios_base::binary); file.writeHeader(ostr2, /*seekable=*/true); std::string uuidStr = file.getUniqueTag(); file.writeHeader(ostr, /*seekable=*/true); // Verify that a file gets a new UUID each time it is written. CPPUNIT_ASSERT(!file.isIdentical(uuidStr)); uuidStr = 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 uint32_t version = openvdb::OPENVDB_FILE_VERSION; CPPUNIT_ASSERT_EQUAL(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(uuidStr, file.getUniqueTag()); //std::cerr << "\nuuid=" << uuidStr << std::endl; CPPUNIT_ASSERT(file.isIdentical(uuidStr)); remove("something.vdb2"); } void TestFile::testWriteGrid() { using namespace openvdb; using namespace openvdb::io; using TreeType = Int32Tree; using GridType = Grid; 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 = DynamicPtrCast(gd2_grid->baseTreePtr()); CPPUNIT_ASSERT(tree2.get() != nullptr); 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; using TreeType = Int32Tree; using GridType = Grid; 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 = DynamicPtrCast(gd_in_grid->baseTreePtr()); CPPUNIT_ASSERT(grid_in.get() != nullptr); 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 = DynamicPtrCast(gd2_in_grid->baseTreePtr()); CPPUNIT_ASSERT(grid2_in.get() != nullptr); 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; using TreeType = Vec3STree; using GridType = Grid; // Register all grid types. initialize(); // Ensure that the registry is cleared on exit. struct Local { static void uninitialize(char*) { openvdb::uninitialize(); } }; SharedPtr onExit(nullptr, Local::uninitialize); // Create two test grids. GridType::Ptr grid1 = createGrid(/*bg=*/Vec3s(1, 1, 1)); TreeType& tree1 = grid1->tree(); CPPUNIT_ASSERT(grid1.get() != nullptr); grid1->setTransform(math::Transform::createLinearTransform(0.1)); grid1->setName("grid1"); GridType::Ptr grid2 = createGrid(/*bg=*/Vec3s(2, 2, 2)); CPPUNIT_ASSERT(grid2.get() != nullptr); 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(float(x), float(y), float(z))); tree2.setValue(Coord(x, y, z), Vec3s(float(x), float(y), float(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() != nullptr); CPPUNIT_ASSERT(bgrid1->isType()); CPPUNIT_ASSERT(bgrid2.get() != nullptr); CPPUNIT_ASSERT(bgrid2->isType()); const TreeType& btree1 = StaticPtrCast(bgrid1)->tree(); CPPUNIT_ASSERT_EQUAL(Vec3s(10, 10, 10), btree1.getValue(Coord(10, 10, 10))); const TreeType& btree2 = StaticPtrCast(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(); // Remove something.vdb2 when done. We must declare this here before the // other grid smart_ptr's because we re-use them in the test several times. // We will not be able to remove something.vdb2 on Windows if the pointers // are still referencing data opened by the "file" variable. const char* filename = "something.vdb2"; SharedPtr scopedFile(filename, ::remove); // 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. { 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() != nullptr); 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() != nullptr); CPPUNIT_ASSERT_EQUAL(4, int(grids->size())); GridBase::Ptr grid = findGridByName(*grids, "density"); CPPUNIT_ASSERT(grid.get() != nullptr); Int32Tree::Ptr density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != nullptr); grid.reset(); grid = findGridByName(*grids, "density_copy"); CPPUNIT_ASSERT(grid.get() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != nullptr); // 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() != nullptr); FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != nullptr); 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() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != nullptr); // 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() != nullptr); density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != nullptr); grid = findGridByName(*grids, "density_copy"); CPPUNIT_ASSERT(grid.get() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != nullptr); // 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() != nullptr); temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != nullptr); 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() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != temperature); // Rewrite with instancing disabled, then reread with instancing enabled. file.close(); { /// @todo (FX-7063) For now, write to a new file, then, when there's /// no longer a need for delayed load from the old file, replace it /// with the new file. const char* tempFilename = "somethingelse.vdb"; SharedPtr scopedTempFile(tempFilename, ::remove); io::File vdbFile(tempFilename); vdbFile.setInstancingEnabled(false); vdbFile.write(*grids, *meta); grids.reset(); // Note: Windows requires that the destination not exist, before we can rename to it. std::remove(filename); std::rename(tempFilename, filename); } 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() != nullptr); density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != nullptr); #if OPENVDB_ABI_VERSION_NUMBER >= 4 CPPUNIT_ASSERT(density->unallocatedLeafCount() > 0); CPPUNIT_ASSERT_EQUAL(density->leafCount(), density->unallocatedLeafCount()); #endif grid = findGridByName(*grids, "density_copy"); CPPUNIT_ASSERT(grid.get() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != nullptr); 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() != nullptr); temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != nullptr); 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() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr() != temperature); } void TestFile::testReadGridDescriptors() { using namespace openvdb; using namespace openvdb::io; using GridType = Int32Grid; using TreeType = GridType::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(reinterpret_cast(&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.gridDescriptors().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.gridDescriptors().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; using TreeType = Int32Tree; // Register data types. openvdb::initialize(); logging::LevelScope suppressLogging{logging::Level::Fatal}; // 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 = "testGridNaming.vdb2"; SharedPtr 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)); // Read the current grid. GridBase::ConstPtr grid = file.readGrid(name); CPPUNIT_ASSERT(grid.get() != nullptr); // 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")); } // Read all three grids at once. GridPtrVecPtr allGrids = file.getGrids(); CPPUNIT_ASSERT(allGrids.get() != nullptr); 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() != nullptr); 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() != nullptr); 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() != nullptr); 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() != nullptr); 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 = "testEmptyFile.vdb2"; SharedPtr 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() != nullptr); CPPUNIT_ASSERT(grids->empty()); CPPUNIT_ASSERT(meta.get() != nullptr); CPPUNIT_ASSERT_EQUAL(0, int(meta->metaCount())); } void TestFile::testEmptyGridIO() { using namespace openvdb; using namespace openvdb::io; using GridType = Int32Grid; const char* filename = "something.vdb2"; SharedPtr 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(reinterpret_cast(&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.gridDescriptors().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() != nullptr); 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.gridDescriptors().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() != nullptr); 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; using FloatGrid = openvdb::FloatGrid; using IntGrid = openvdb::Int32Grid; using FloatTree = FloatGrid::TreeType; using IntTree = Int32Grid::TreeType; // 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 closing. CPPUNIT_ASSERT_THROW(vdbfile.open(), openvdb::IoError); vdbfile.close(); CPPUNIT_ASSERT(!vdbfile.open());//opening the same file CPPUNIT_ASSERT(vdbfile.isOpen()); uint32_t version = OPENVDB_FILE_VERSION; CPPUNIT_ASSERT_EQUAL(version, vdbfile.fileVersion()); CPPUNIT_ASSERT_EQUAL(version, io::getFormatVersion(vdbfile.inputStream())); 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.inputStream()).first); CPPUNIT_ASSERT_EQUAL(OPENVDB_LIBRARY_MINOR_VERSION, io::getLibraryVersion(vdbfile.inputStream()).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.gridDescriptors().count("density"))); CPPUNIT_ASSERT_EQUAL(1, int(vdbfile.gridDescriptors().count("temperature"))); io::File::NameMapCIter it = vdbfile.findDescriptor("density"); CPPUNIT_ASSERT(it != vdbfile.gridDescriptors().end()); io::GridDescriptor gd = it->second; CPPUNIT_ASSERT_EQUAL(IntTree::treeType(), gd.gridType()); it = vdbfile.findDescriptor("temperature"); CPPUNIT_ASSERT(it != vdbfile.gridDescriptors().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_THROW(vdbfile2.inputStream(), openvdb::IoError); // Clear registries. GridBase::clearRegistry(); Metadata::clearRegistry(); math::MapRegistry::clear(); // Test closing the file. vdbfile.close(); CPPUNIT_ASSERT(vdbfile.isOpen() == false); CPPUNIT_ASSERT(vdbfile.fileMetadata().get() == nullptr); CPPUNIT_ASSERT_EQUAL(0, int(vdbfile.gridDescriptors().size())); CPPUNIT_ASSERT_THROW(vdbfile.inputStream(), openvdb::IoError); remove("something.vdb2"); } void TestFile::testNonVdbOpen() { std::ofstream file("dummy.vdb2", std::ios_base::binary); int64_t something = 1; file.write(reinterpret_cast(&something), sizeof(int64_t)); file.close(); openvdb::io::File vdbfile("dummy.vdb2"); CPPUNIT_ASSERT_THROW(vdbfile.open(), openvdb::IoError); CPPUNIT_ASSERT_THROW(vdbfile.inputStream(), openvdb::IoError); 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; using FloatGrid = openvdb::FloatGrid; using IntGrid = openvdb::Int32Grid; using FloatTree = FloatGrid::TreeType; using IntTree = Int32Grid::TreeType; // 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() != nullptr); IntTree::Ptr density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != nullptr); grid.reset(); grid = findGridByName(*grids2, "temperature"); CPPUNIT_ASSERT(grid.get() != nullptr); FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != nullptr); 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() != nullptr); 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() != nullptr); 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 = "testReadGridMetadata.vdb2"; SharedPtr 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() != nullptr); 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() != nullptr); CPPUNIT_ASSERT_EQUAL(std::string("igrid"), grid->getName()); gridMetadata->push_back(grid); grid = vdbfile.readGridMetadata("fgrid"); CPPUNIT_ASSERT(grid.get() != nullptr); 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() != nullptr); CPPUNIT_ASSERT(grid->getName() == "igrid" || grid->getName() == "fgrid"); CPPUNIT_ASSERT(grid->baseTreePtr().get() != nullptr); // 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->metaCount() != 0); 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::testReadGrid() { using namespace openvdb; using FloatGrid = openvdb::FloatGrid; using IntGrid = openvdb::Int32Grid; using FloatTree = FloatGrid::TreeType; using IntTree = Int32Grid::TreeType; // 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"); vdbfile2.open(); CPPUNIT_ASSERT(vdbfile2.isOpen()); // Get Temperature GridBase::Ptr temperature = vdbfile2.readGrid("temperature"); CPPUNIT_ASSERT(temperature.get() != nullptr); FloatTree::Ptr typedTemperature = gridPtrCast(temperature)->treePtr(); CPPUNIT_ASSERT(typedTemperature.get() != nullptr); 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() != nullptr); IntTree::Ptr typedDensity = gridPtrCast(density)->treePtr(); CPPUNIT_ASSERT(typedDensity.get() != nullptr); 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"); } //////////////////////////////////////// #if OPENVDB_ABI_VERSION_NUMBER >= 3 template void validateClippedGrid(const GridT& clipped, const typename GridT::ValueType& fg) { using namespace openvdb; using ValueT = typename GridT::ValueType; const CoordBBox bbox = clipped.evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(4, bbox.min().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.min().y()); CPPUNIT_ASSERT_EQUAL(-6, bbox.min().z()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().y()); CPPUNIT_ASSERT_EQUAL(6, bbox.max().z()); CPPUNIT_ASSERT_EQUAL(6 + 6 + 1, int(clipped.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(2, int(clipped.constTree().leafCount())); typename GridT::ConstAccessor acc = clipped.getConstAccessor(); const ValueT bg = clipped.background(); Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = -10; x <= 10; ++x) { for (y = -10; y <= 10; ++y) { for (z = -10; z <= 10; ++z) { if (x == 4 && y == 4 && z >= -6 && z <= 6) { CPPUNIT_ASSERT_EQUAL(fg, acc.getValue(Coord(4, 4, z))); } else { CPPUNIT_ASSERT_EQUAL(bg, acc.getValue(Coord(x, y, z))); } } } } } // See also TestGrid::testClipping() void TestFile::testReadClippedGrid() { using namespace openvdb; // Register types. openvdb::initialize(); // World-space clipping region const BBoxd clipBox(Vec3d(4.0, 4.0, -6.0), Vec3d(4.9, 4.9, 6.0)); // Create grids of several types and fill a cubic region of each with a foreground value. const bool bfg = true; BoolGrid::Ptr bgrid = BoolGrid::create(/*bg=*/zeroVal()); bgrid->setName("bgrid"); bgrid->fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/bfg, /*active=*/true); const float ffg = 5.f; FloatGrid::Ptr fgrid = FloatGrid::create(/*bg=*/zeroVal()); fgrid->setName("fgrid"); fgrid->fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/ffg, /*active=*/true); const Vec3s vfg(1.f, -2.f, 3.f); Vec3SGrid::Ptr vgrid = Vec3SGrid::create(/*bg=*/zeroVal()); vgrid->setName("vgrid"); vgrid->fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/vfg, /*active=*/true); GridPtrVec srcGrids; srcGrids.push_back(bgrid); srcGrids.push_back(fgrid); srcGrids.push_back(vgrid); const char* filename = "testReadClippedGrid.vdb"; SharedPtr scopedFile(filename, ::remove); 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); } // Open the file for reading. io::File vdbfile(filename); vdbfile.open(); GridBase::Ptr grid; // Read and clip each grid. CPPUNIT_ASSERT_NO_THROW(grid = vdbfile.readGrid("bgrid", clipBox)); CPPUNIT_ASSERT(grid.get() != nullptr); CPPUNIT_ASSERT_NO_THROW(bgrid = gridPtrCast(grid)); validateClippedGrid(*bgrid, bfg); CPPUNIT_ASSERT_NO_THROW(grid = vdbfile.readGrid("fgrid", clipBox)); CPPUNIT_ASSERT(grid.get() != nullptr); CPPUNIT_ASSERT_NO_THROW(fgrid = gridPtrCast(grid)); validateClippedGrid(*fgrid, ffg); CPPUNIT_ASSERT_NO_THROW(grid = vdbfile.readGrid("vgrid", clipBox)); CPPUNIT_ASSERT(grid.get() != nullptr); CPPUNIT_ASSERT_NO_THROW(vgrid = gridPtrCast(grid)); validateClippedGrid(*vgrid, vfg); } } #endif // OPENVDB_ABI_VERSION_NUMBER >= 3 //////////////////////////////////////// namespace { template struct MultiPassLeafNode; // forward declaration // Dummy value type using MultiPassValue = openvdb::PointIndex; // Tree configured to match the default OpenVDB configuration using MultiPassTree = openvdb::tree::Tree< openvdb::tree::RootNode< openvdb::tree::InternalNode< openvdb::tree::InternalNode< MultiPassLeafNode, 4>, 5>>>; using MultiPassGrid = openvdb::Grid; template struct MultiPassLeafNode: public openvdb::tree::LeafNode, openvdb::io::MultiPass { // The following had to be copied from the LeafNode class // to make the derived class compatible with the tree structure. using LeafNodeType = MultiPassLeafNode; using Ptr = openvdb::SharedPtr; using BaseLeaf = openvdb::tree::LeafNode; using NodeMaskType = openvdb::util::NodeMask; using ValueType = T; using ValueOnCIter = typename BaseLeaf::template ValueIter; using ChildOnIter = typename BaseLeaf::template ChildIter; using ChildOnCIter = typename BaseLeaf::template ChildIter< typename NodeMaskType::OnIterator, const MultiPassLeafNode, typename BaseLeaf::ChildOn>; MultiPassLeafNode(const openvdb::Coord& coords, const T& value, bool active = false) : BaseLeaf(coords, value, active) {} #if OPENVDB_ABI_VERSION_NUMBER >= 3 MultiPassLeafNode(openvdb::PartialCreate, const openvdb::Coord& coords, const T& value, bool active = false): BaseLeaf(openvdb::PartialCreate(), coords, value, active) {} #endif MultiPassLeafNode(const MultiPassLeafNode& rhs): BaseLeaf(rhs) {} ValueOnCIter cbeginValueOn() const { return ValueOnCIter(this->getValueMask().beginOn(),this); } ChildOnCIter cbeginChildOn() const { return ChildOnCIter(this->getValueMask().endOn(), this); } ChildOnIter beginChildOn() { return ChildOnIter(this->getValueMask().endOn(), this); } // Methods in use for reading and writing multiple buffers void readBuffers(std::istream& is, const openvdb::CoordBBox&, bool fromHalf = false) { this->readBuffers(is, fromHalf); } void readBuffers(std::istream& is, bool /*fromHalf*/ = false) { const openvdb::io::StreamMetadata::Ptr meta = openvdb::io::getStreamMetadataPtr(is); if (!meta) { OPENVDB_THROW(openvdb::IoError, "Cannot write out a MultiBufferLeaf without StreamMetadata."); } // clamp pass to 16-bit integer const uint32_t pass(static_cast(meta->pass())); // Read in the stored pass number. uint32_t readPass; is.read(reinterpret_cast(&readPass), sizeof(uint32_t)); CPPUNIT_ASSERT_EQUAL(pass, readPass); // Record the pass number. mReadPasses.push_back(readPass); if (pass == 0) { // Read in the node's origin. openvdb::Coord origin; is.read(reinterpret_cast(&origin), sizeof(openvdb::Coord)); CPPUNIT_ASSERT_EQUAL(origin, this->origin()); } } void writeBuffers(std::ostream& os, bool /*toHalf*/ = false) const { const openvdb::io::StreamMetadata::Ptr meta = openvdb::io::getStreamMetadataPtr(os); if (!meta) { OPENVDB_THROW(openvdb::IoError, "Cannot read in a MultiBufferLeaf without StreamMetadata."); } // clamp pass to 16-bit integer const uint32_t pass(static_cast(meta->pass())); // Leaf traversal analysis deduces the number of passes to perform for this leaf // then updates the leaf traversal value to ensure all passes will be written. if (meta->countingPasses()) { if (mNumPasses > pass) meta->setPass(mNumPasses); return; } // Record the pass number. CPPUNIT_ASSERT(mWritePassesPtr); const_cast&>(*mWritePassesPtr).push_back(pass); // Write out the pass number. os.write(reinterpret_cast(&pass), sizeof(uint32_t)); if (pass == 0) { // Write out the node's origin and the pass number. const auto origin = this->origin(); os.write(reinterpret_cast(&origin), sizeof(openvdb::Coord)); } } uint32_t mNumPasses = 0; // Pointer to external vector in which to record passes as they are written std::vector* mWritePassesPtr = nullptr; // Vector in which to record passes as they are read // (this needs to be internal, because leaf nodes are constructed as a grid is read) std::vector mReadPasses; }; // struct MultiPassLeafNode } // anonymous namespace void TestFile::testMultiPassIO() { using namespace openvdb; openvdb::initialize(); MultiPassGrid::registerGrid(); // Create a multi-buffer grid. const MultiPassGrid::Ptr grid = openvdb::createGrid(); grid->setName("test"); grid->setTransform(math::Transform::createLinearTransform(1.0)); MultiPassGrid::TreeType& tree = grid->tree(); tree.setValue(Coord(0, 0, 0), 5); tree.setValue(Coord(0, 10, 0), 5); CPPUNIT_ASSERT_EQUAL(2, int(tree.leafCount())); const GridPtrVec grids{grid}; // Vector in which to record pass numbers (to ensure blocked ordering) std::vector writePasses; { // Specify the required number of I/O passes for each leaf node. MultiPassGrid::TreeType::LeafIter leafIter = tree.beginLeaf(); leafIter->mNumPasses = 3; leafIter->mWritePassesPtr = &writePasses; ++leafIter; leafIter->mNumPasses = 2; leafIter->mWritePassesPtr = &writePasses; } const char* filename = "testMultiPassIO.vdb"; SharedPtr scopedFile(filename, ::remove); { // Verify that passes are written to a file in the correct order. io::File(filename).write(grids); CPPUNIT_ASSERT_EQUAL(6, int(writePasses.size())); CPPUNIT_ASSERT_EQUAL(0, writePasses[0]); // leaf 0 CPPUNIT_ASSERT_EQUAL(0, writePasses[1]); // leaf 1 CPPUNIT_ASSERT_EQUAL(1, writePasses[2]); // leaf 0 CPPUNIT_ASSERT_EQUAL(1, writePasses[3]); // leaf 1 CPPUNIT_ASSERT_EQUAL(2, writePasses[4]); // leaf 0 CPPUNIT_ASSERT_EQUAL(2, writePasses[5]); // leaf 1 } { // Verify that passes are read in the correct order. io::File file(filename); file.open(); const auto newGrid = GridBase::grid(file.readGrid("test")); auto leafIter = newGrid->tree().beginLeaf(); CPPUNIT_ASSERT_EQUAL(3, int(leafIter->mReadPasses.size())); CPPUNIT_ASSERT_EQUAL(0, leafIter->mReadPasses[0]); CPPUNIT_ASSERT_EQUAL(1, leafIter->mReadPasses[1]); CPPUNIT_ASSERT_EQUAL(2, leafIter->mReadPasses[2]); ++leafIter; CPPUNIT_ASSERT_EQUAL(3, int(leafIter->mReadPasses.size())); CPPUNIT_ASSERT_EQUAL(0, leafIter->mReadPasses[0]); CPPUNIT_ASSERT_EQUAL(1, leafIter->mReadPasses[1]); CPPUNIT_ASSERT_EQUAL(2, leafIter->mReadPasses[2]); } #if OPENVDB_ABI_VERSION_NUMBER >= 4 { // Verify that when using multi-pass and bbox clipping that each leaf node // is still being read before being clipped io::File file(filename); file.open(); const auto newGrid = GridBase::grid( file.readGrid("test", BBoxd(Vec3d(0), Vec3d(1)))); CPPUNIT_ASSERT_EQUAL(Index32(1), newGrid->tree().leafCount()); auto leafIter = newGrid->tree().beginLeaf(); CPPUNIT_ASSERT_EQUAL(3, int(leafIter->mReadPasses.size())); CPPUNIT_ASSERT_EQUAL(0, leafIter->mReadPasses[0]); CPPUNIT_ASSERT_EQUAL(1, leafIter->mReadPasses[1]); CPPUNIT_ASSERT_EQUAL(2, leafIter->mReadPasses[2]); ++leafIter; CPPUNIT_ASSERT(!leafIter); // second leaf node has now been clipped } #endif // Clear the pass data. writePasses.clear(); { // Verify that passes are written to and read from a non-seekable stream // in the correct order. std::ostringstream ostr(std::ios_base::binary); io::Stream(ostr).write(grids); CPPUNIT_ASSERT_EQUAL(6, int(writePasses.size())); CPPUNIT_ASSERT_EQUAL(0, writePasses[0]); // leaf 0 CPPUNIT_ASSERT_EQUAL(0, writePasses[1]); // leaf 1 CPPUNIT_ASSERT_EQUAL(1, writePasses[2]); // leaf 0 CPPUNIT_ASSERT_EQUAL(1, writePasses[3]); // leaf 1 CPPUNIT_ASSERT_EQUAL(2, writePasses[4]); // leaf 0 CPPUNIT_ASSERT_EQUAL(2, writePasses[5]); // leaf 1 std::istringstream is(ostr.str(), std::ios_base::binary); io::Stream strm(is); const auto streamedGrids = strm.getGrids(); CPPUNIT_ASSERT_EQUAL(1, int(streamedGrids->size())); const auto newGrid = gridPtrCast(*streamedGrids->begin()); CPPUNIT_ASSERT(bool(newGrid)); auto leafIter = newGrid->tree().beginLeaf(); CPPUNIT_ASSERT_EQUAL(3, int(leafIter->mReadPasses.size())); CPPUNIT_ASSERT_EQUAL(0, leafIter->mReadPasses[0]); CPPUNIT_ASSERT_EQUAL(1, leafIter->mReadPasses[1]); CPPUNIT_ASSERT_EQUAL(2, leafIter->mReadPasses[2]); ++leafIter; CPPUNIT_ASSERT_EQUAL(3, int(leafIter->mReadPasses.size())); CPPUNIT_ASSERT_EQUAL(0, leafIter->mReadPasses[0]); CPPUNIT_ASSERT_EQUAL(1, leafIter->mReadPasses[1]); CPPUNIT_ASSERT_EQUAL(2, leafIter->mReadPasses[2]); } } //////////////////////////////////////// void TestFile::testHasGrid() { using namespace openvdb; using namespace openvdb::io; using FloatGrid = openvdb::FloatGrid; using IntGrid = openvdb::Int32Grid; using FloatTree = FloatGrid::TreeType; using IntTree = Int32Grid::TreeType; // 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; using FloatGrid = openvdb::FloatGrid; using FloatTree = FloatGrid::TreeType; using IntTree = Int32Grid::TreeType; // 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 = "testNameIterator.vdb2"; SharedPtr 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] = { "[0]", "[1]", "density", "level_set[0]", "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; using IntGrid = openvdb::Int32Grid; // 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); intGrid->fill(CoordBBox(Coord(21), Coord(22)), /*value=*/1, /*active=*/false); intGrid->fill(CoordBBox(Coord(23), Coord(24)), /*value=*/2, /*active=*/false); CPPUNIT_ASSERT_EQUAL(8, int(IntGrid::TreeType::LeafNodeType::DIM)); 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 = "testCompression.vdb2"; SharedPtr scopedFile(filename, ::remove); size_t uncompressedSize = 0; { // Write the grids out to a file with compression disabled. io::File vdbfile(filename); vdbfile.setCompression(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.setCompression(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 * double(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() != nullptr); 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 leaf nodes with more than two distinct inactive values // are handled correctly (FX-7085). CPPUNIT_ASSERT_EQUAL( intGrid->tree().getValue(Coord(6)), grid->tree().getValue(Coord(6))); CPPUNIT_ASSERT_EQUAL( intGrid->tree().getValue(Coord(21)), grid->tree().getValue(Coord(21))); CPPUNIT_ASSERT_EQUAL( intGrid->tree().getValue(Coord(23)), grid->tree().getValue(Coord(23))); // 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() != nullptr); 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 std::bind(&TestAsyncHelper::validate, this, std::placeholders::_1, std::placeholders::_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; // 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 = "testAsyncref.vdb"; SharedPtr 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 << "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 << "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*/); SharedPtr scopedFile1("testAsyncIOa.vdb", ::remove), scopedFile2("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); while (!queue.empty()) { tbb::this_tbb_thread::sleep(tbb::tick_count::interval_t(1.0/*sec*/)); } } } #ifdef OPENVDB_USE_BLOSC // This tests for a data corruption bug that existed in versions of Blosc prior to 1.5.0 // (see https://github.com/Blosc/c-blosc/pull/63). void TestFile::testBlosc() { openvdb::initialize(); const unsigned char rawdata[] = { 0x93, 0xb0, 0x49, 0xaf, 0x62, 0xad, 0xe3, 0xaa, 0xe4, 0xa5, 0x43, 0x20, 0x24, 0x29, 0xc9, 0xaf, 0xee, 0xad, 0x0b, 0xac, 0x3d, 0xa8, 0x1f, 0x99, 0x53, 0x27, 0xb6, 0x2b, 0x16, 0xb0, 0x5f, 0xae, 0x89, 0xac, 0x51, 0xa9, 0xfc, 0xa1, 0xc9, 0x24, 0x59, 0x2a, 0x2f, 0x2d, 0xb4, 0xae, 0xeb, 0xac, 0x2f, 0xaa, 0xec, 0xa4, 0x53, 0x21, 0x31, 0x29, 0x8f, 0x2c, 0x8e, 0x2e, 0x31, 0xad, 0xd6, 0xaa, 0x6d, 0xa6, 0xad, 0x1b, 0x3e, 0x28, 0x0a, 0x2c, 0xfd, 0x2d, 0xf8, 0x2f, 0x45, 0xab, 0x81, 0xa7, 0x1f, 0x95, 0x02, 0x27, 0x3d, 0x2b, 0x85, 0x2d, 0x75, 0x2f, 0xb6, 0x30, 0x13, 0xa8, 0xb2, 0x9c, 0xf3, 0x25, 0x9c, 0x2a, 0x28, 0x2d, 0x0b, 0x2f, 0x7b, 0x30, 0x68, 0x9e, 0x51, 0x25, 0x31, 0x2a, 0xe6, 0x2c, 0xbc, 0x2e, 0x4e, 0x30, 0x5a, 0xb0, 0xe6, 0xae, 0x0e, 0xad, 0x59, 0xaa, 0x08, 0xa5, 0x89, 0x21, 0x59, 0x29, 0xb0, 0x2c, 0x57, 0xaf, 0x8c, 0xad, 0x6f, 0xab, 0x65, 0xa7, 0xd3, 0x12, 0xf5, 0x27, 0xeb, 0x2b, 0xf6, 0x2d, 0xee, 0xad, 0x27, 0xac, 0xab, 0xa8, 0xb1, 0x9f, 0xa2, 0x25, 0xaa, 0x2a, 0x4a, 0x2d, 0x47, 0x2f, 0x7b, 0xac, 0x6d, 0xa9, 0x45, 0xa3, 0x73, 0x23, 0x9d, 0x29, 0xb7, 0x2c, 0xa8, 0x2e, 0x51, 0x30, 0xf7, 0xa9, 0xec, 0xa4, 0x79, 0x20, 0xc5, 0x28, 0x3f, 0x2c, 0x24, 0x2e, 0x09, 0x30, 0xc8, 0xa5, 0xb1, 0x1c, 0x23, 0x28, 0xc3, 0x2b, 0xba, 0x2d, 0x9c, 0x2f, 0xc3, 0x30, 0x44, 0x18, 0x6e, 0x27, 0x3d, 0x2b, 0x6b, 0x2d, 0x40, 0x2f, 0x8f, 0x30, 0x02, 0x27, 0xed, 0x2a, 0x36, 0x2d, 0xfe, 0x2e, 0x68, 0x30, 0x66, 0xae, 0x9e, 0xac, 0x96, 0xa9, 0x7c, 0xa3, 0xa9, 0x23, 0xc5, 0x29, 0xd8, 0x2c, 0xd7, 0x2e, 0x0e, 0xad, 0x90, 0xaa, 0xe4, 0xa5, 0xf8, 0x1d, 0x82, 0x28, 0x2b, 0x2c, 0x1e, 0x2e, 0x0c, 0x30, 0x53, 0xab, 0x9c, 0xa7, 0xd4, 0x96, 0xe7, 0x26, 0x30, 0x2b, 0x7f, 0x2d, 0x6e, 0x2f, 0xb3, 0x30, 0x74, 0xa8, 0xb1, 0x9f, 0x36, 0x25, 0x3e, 0x2a, 0xfa, 0x2c, 0xdd, 0x2e, 0x65, 0x30, 0xfc, 0xa1, 0xe0, 0x23, 0x82, 0x29, 0x8f, 0x2c, 0x66, 0x2e, 0x23, 0x30, 0x2d, 0x22, 0xfb, 0x28, 0x3f, 0x2c, 0x0a, 0x2e, 0xde, 0x2f, 0xaa, 0x28, 0x0a, 0x2c, 0xc8, 0x2d, 0x8f, 0x2f, 0xb0, 0x30, 0xde, 0x2b, 0xa0, 0x2d, 0x5a, 0x2f, 0x8f, 0x30, 0x12, 0xac, 0x9d, 0xa8, 0x0f, 0xa0, 0x51, 0x25, 0x66, 0x2a, 0x1b, 0x2d, 0x0b, 0x2f, 0x82, 0x30, 0x7b, 0xa9, 0xea, 0xa3, 0x63, 0x22, 0x3f, 0x29, 0x7b, 0x2c, 0x60, 0x2e, 0x26, 0x30, 0x76, 0xa5, 0xf8, 0x1d, 0x4c, 0x28, 0xeb, 0x2b, 0xce, 0x2d, 0xb0, 0x2f, 0xd3, 0x12, 0x1d, 0x27, 0x15, 0x2b, 0x57, 0x2d, 0x2c, 0x2f, 0x85, 0x30, 0x0e, 0x26, 0x74, 0x2a, 0xfa, 0x2c, 0xc3, 0x2e, 0x4a, 0x30, 0x08, 0x2a, 0xb7, 0x2c, 0x74, 0x2e, 0x1d, 0x30, 0x8f, 0x2c, 0x3f, 0x2e, 0xf8, 0x2f, 0x24, 0x2e, 0xd0, 0x2f, 0xc3, 0x30, 0xdb, 0xa6, 0xd3, 0x0e, 0x38, 0x27, 0x3d, 0x2b, 0x78, 0x2d, 0x5a, 0x2f, 0xa3, 0x30, 0x68, 0x9e, 0x51, 0x25, 0x31, 0x2a, 0xe6, 0x2c, 0xbc, 0x2e, 0x4e, 0x30, 0xa9, 0x23, 0x59, 0x29, 0x6e, 0x2c, 0x38, 0x2e, 0x06, 0x30, 0xb8, 0x28, 0x10, 0x2c, 0xce, 0x2d, 0x95, 0x2f, 0xb3, 0x30, 0x9b, 0x2b, 0x7f, 0x2d, 0x39, 0x2f, 0x7f, 0x30, 0x4a, 0x2d, 0xf8, 0x2e, 0x58, 0x30, 0xd0, 0x2e, 0x3d, 0x30, 0x30, 0x30, 0x53, 0x21, 0xc5, 0x28, 0x24, 0x2c, 0xef, 0x2d, 0xc3, 0x2f, 0xda, 0x27, 0x58, 0x2b, 0x6b, 0x2d, 0x33, 0x2f, 0x82, 0x30, 0x9c, 0x2a, 0x00, 0x2d, 0xbc, 0x2e, 0x41, 0x30, 0xb0, 0x2c, 0x60, 0x2e, 0x0c, 0x30, 0x1e, 0x2e, 0xca, 0x2f, 0xc0, 0x30, 0x95, 0x2f, 0x9f, 0x30, 0x8c, 0x30, 0x23, 0x2a, 0xc4, 0x2c, 0x81, 0x2e, 0x23, 0x30, 0x5a, 0x2c, 0x0a, 0x2e, 0xc3, 0x2f, 0xc3, 0x30, 0xad, 0x2d, 0x5a, 0x2f, 0x88, 0x30, 0x0b, 0x2f, 0x5b, 0x30, 0x3a, 0x30, 0x7f, 0x2d, 0x2c, 0x2f, 0x72, 0x30, 0xc3, 0x2e, 0x37, 0x30, 0x09, 0x30, 0xb6, 0x30 }; const char* indata = reinterpret_cast(rawdata); size_t inbytes = sizeof(rawdata); const int compbufbytes = int(inbytes + BLOSC_MAX_OVERHEAD), decompbufbytes = int(inbytes + BLOSC_MAX_OVERHEAD); std::unique_ptr compresseddata(new char[compbufbytes]), outdata(new char[decompbufbytes]); for (int compcode = 0; compcode <= BLOSC_ZLIB; ++compcode) { char* compname = nullptr; if (0 > blosc_compcode_to_compname(compcode, &compname)) continue; /// @todo This changes the compressor setting globally. if (blosc_set_compressor(compname) < 0) continue; for (int typesize = 1; typesize <= 4; ++typesize) { // Compress the data. ::memset(compresseddata.get(), 0, compbufbytes); int compressedbytes = blosc_compress( /*clevel=*/9, /*doshuffle=*/true, typesize, /*srcsize=*/inbytes, /*src=*/indata, /*dest=*/compresseddata.get(), /*destsize=*/compbufbytes); CPPUNIT_ASSERT(compressedbytes > 0); // Decompress the data. ::memset(outdata.get(), 0, decompbufbytes); int outbytes = blosc_decompress( compresseddata.get(), outdata.get(), decompbufbytes); CPPUNIT_ASSERT(outbytes > 0); CPPUNIT_ASSERT_EQUAL(int(inbytes), outbytes); // Compare original and decompressed data. int diff = 0; for (size_t i = 0; i < inbytes; ++i) { if (outdata[i] != indata[i]) ++diff; } if (diff > 0) { const char* mesg = "Your version of the Blosc library is most likely" " out of date; please install the latest version. " "(Earlier versions have a bug that can cause data corruption.)"; CPPUNIT_ASSERT_MESSAGE(mesg, diff == 0); return; } } } } #endif // Copyright (c) 2012-2017 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/TestAttributeArray.cc0000644000000000000000000022247513200122377017212 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #ifdef __clang__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-macros" #endif // Boost.Interprocess uses a header-only portion of Boost.DateTime #define BOOST_DATE_TIME_NO_LIB #ifdef __clang__ #pragma GCC diagnostic pop #endif #include #include #include #include #include #include #include #ifdef _MSC_VER #include // open_existing_file(), close_file() // boost::interprocess::detail was renamed to boost::interprocess::ipcdetail in Boost 1.48. // Ensure that both namespaces exist. namespace boost { namespace interprocess { namespace detail {} namespace ipcdetail {} } } #include #else #include // for struct stat #include // for stat() #endif /// @brief io::MappedFile has a private constructor, so this unit tests uses a matching proxy class ProxyMappedFile { public: explicit ProxyMappedFile(const std::string& filename) : mImpl(new Impl(filename)) { } private: class Impl { public: Impl(const std::string& filename) : mMap(filename.c_str(), boost::interprocess::read_only) , mRegion(mMap, boost::interprocess::read_only) { mLastWriteTime = 0; const char* regionFilename = mMap.get_name(); #ifdef _MSC_VER using namespace boost::interprocess::detail; using namespace boost::interprocess::ipcdetail; using openvdb::Index64; if (void* fh = open_existing_file(regionFilename, boost::interprocess::read_only)) { FILETIME mtime; if (GetFileTime(fh, nullptr, nullptr, &mtime)) { mLastWriteTime = (Index64(mtime.dwHighDateTime) << 32) | mtime.dwLowDateTime; } close_file(fh); } #else struct stat info; if (0 == ::stat(regionFilename, &info)) { mLastWriteTime = openvdb::Index64(info.st_mtime); } #endif } using Notifier = std::function; boost::interprocess::file_mapping mMap; boost::interprocess::mapped_region mRegion; bool mAutoDelete = false; Notifier mNotifier; mutable tbb::atomic mLastWriteTime; }; // class Impl std::unique_ptr mImpl; }; // class ProxyMappedFile /// @brief Functionality similar to openvdb::util::CpuTimer except with prefix padding and no decimals. /// /// @code /// ProfileTimer timer("algorithm 1"); /// // code to be timed goes here /// timer.stop(); /// @endcode class ProfileTimer { public: /// @brief Prints message and starts timer. /// /// @note Should normally be followed by a call to stop() ProfileTimer(const std::string& msg) { (void)msg; #ifdef PROFILE // padd string to 50 characters std::string newMsg(msg); if (newMsg.size() < 50) newMsg.insert(newMsg.end(), 50 - newMsg.size(), ' '); std::cerr << newMsg << " ... "; #endif mT0 = tbb::tick_count::now(); } ~ProfileTimer() { this->stop(); } /// Return Time diference in milliseconds since construction or start was called. inline double delta() const { tbb::tick_count::interval_t dt = tbb::tick_count::now() - mT0; return 1000.0*dt.seconds(); } /// @brief Print time in milliseconds since construction or start was called. inline void stop() const { #ifdef PROFILE std::stringstream ss; ss << std::setw(6) << ::round(this->delta()); std::cerr << "completed in " << ss.str() << " ms\n"; #endif } private: tbb::tick_count mT0; };// ProfileTimer using namespace openvdb; using namespace openvdb::points; class TestAttributeArray: public CppUnit::TestCase { public: void setUp() override { AttributeArray::clearRegistry(); } void tearDown() override { AttributeArray::clearRegistry(); } CPPUNIT_TEST_SUITE(TestAttributeArray); CPPUNIT_TEST(testFixedPointConversion); CPPUNIT_TEST(testRegistry); CPPUNIT_TEST(testAttributeArray); CPPUNIT_TEST(testAccessorEval); CPPUNIT_TEST(testAttributeHandle); CPPUNIT_TEST(testStrided); CPPUNIT_TEST(testDelayedLoad); CPPUNIT_TEST(testQuaternions); CPPUNIT_TEST(testMatrices); CPPUNIT_TEST(testProfile); CPPUNIT_TEST_SUITE_END(); void testFixedPointConversion(); void testRegistry(); void testAttributeArray(); void testAccessorEval(); void testAttributeHandle(); void testStrided(); void testDelayedLoad(); void testQuaternions(); void testMatrices(); void testProfile(); }; // class TestAttributeArray CPPUNIT_TEST_SUITE_REGISTRATION(TestAttributeArray); //////////////////////////////////////// namespace { bool matchingNamePairs(const openvdb::NamePair& lhs, const openvdb::NamePair& rhs) { if (lhs.first != rhs.first) return false; if (lhs.second != rhs.second) return false; return true; } } // namespace //////////////////////////////////////// void TestAttributeArray::testFixedPointConversion() { openvdb::math::Transform::Ptr transform(openvdb::math::Transform::createLinearTransform(/*voxelSize=*/0.1)); const float value = 33.5688040469035f; { // convert to fixed-point value const openvdb::Vec3f worldSpaceValue(value); const openvdb::Vec3f indexSpaceValue = transform->worldToIndex(worldSpaceValue); const float voxelSpaceValue = indexSpaceValue.x() - math::Round(indexSpaceValue.x()) + 0.5f; const uint32_t intValue = floatingPointToFixedPoint(voxelSpaceValue); // convert back to floating-point value const float newVoxelSpaceValue = fixedPointToFloatingPoint(intValue); const openvdb::Vec3f newIndexSpaceValue(newVoxelSpaceValue + math::Round(indexSpaceValue.x()) - 0.5f); const openvdb::Vec3f newWorldSpaceValue = transform->indexToWorld(newIndexSpaceValue); const float newValue = newWorldSpaceValue.x(); CPPUNIT_ASSERT_DOUBLES_EQUAL(value, newValue, /*tolerance=*/1e-6); } { // convert to fixed-point value (vector) const openvdb::Vec3f worldSpaceValue(value, value+1, value+2); const openvdb::Vec3f indexSpaceValue = transform->worldToIndex(worldSpaceValue); const float voxelSpaceValueX = indexSpaceValue.x() - math::Round(indexSpaceValue.x()) + 0.5f; const float voxelSpaceValueY = indexSpaceValue.y() - math::Round(indexSpaceValue.y()) + 0.5f; const float voxelSpaceValueZ = indexSpaceValue.z() - math::Round(indexSpaceValue.z()) + 0.5f; const openvdb::Vec3f voxelSpaceValue(voxelSpaceValueX, voxelSpaceValueY, voxelSpaceValueZ); const openvdb::math::Vec3 intValue = floatingPointToFixedPoint>(voxelSpaceValue); // convert back to floating-point value (vector) const openvdb::Vec3f newVoxelSpaceValue = fixedPointToFloatingPoint(intValue); const float newIndexSpaceValueX = newVoxelSpaceValue.x() + math::Round(indexSpaceValue.x()) - 0.5f; const float newIndexSpaceValueY = newVoxelSpaceValue.y() + math::Round(indexSpaceValue.y()) - 0.5f; const float newIndexSpaceValueZ = newVoxelSpaceValue.z() + math::Round(indexSpaceValue.z()) - 0.5f; const openvdb::Vec3f newIndexSpaceValue(newIndexSpaceValueX, newIndexSpaceValueY, newIndexSpaceValueZ); const openvdb::Vec3f newWorldSpaceValue = transform->indexToWorld(newIndexSpaceValue); CPPUNIT_ASSERT_DOUBLES_EQUAL(worldSpaceValue.x(), newWorldSpaceValue.x(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(worldSpaceValue.y(), newWorldSpaceValue.y(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(worldSpaceValue.z(), newWorldSpaceValue.z(), /*tolerance=*/1e-6); } } namespace { static AttributeArray::Ptr factory1(Index, Index, bool) { return AttributeArray::Ptr(); } static AttributeArray::Ptr factory2(Index, Index, bool) { return AttributeArray::Ptr(); } } // namespace void TestAttributeArray::testRegistry() { using AttributeF = TypedAttributeArray; AttributeArray::clearRegistry(); { // cannot create AttributeArray that is not registered CPPUNIT_ASSERT(!AttributeArray::isRegistered(AttributeF::attributeType())); CPPUNIT_ASSERT_THROW(AttributeArray::create(AttributeF::attributeType(), Index(5)), LookupError); } // manually register the type and factory AttributeArray::registerType(AttributeF::attributeType(), factory1); { // cannot re-register an already registered AttributeArray CPPUNIT_ASSERT(AttributeArray::isRegistered(AttributeF::attributeType())); CPPUNIT_ASSERT_THROW(AttributeArray::registerType(AttributeF::attributeType(), factory2), KeyError); } { // un-registering AttributeArray::unregisterType(AttributeF::attributeType()); CPPUNIT_ASSERT(!AttributeArray::isRegistered(AttributeF::attributeType())); } { // clearing registry AttributeArray::registerType(AttributeF::attributeType(), factory1); AttributeArray::clearRegistry(); CPPUNIT_ASSERT(!AttributeArray::isRegistered(AttributeF::attributeType())); } } void TestAttributeArray::testAttributeArray() { using AttributeArrayF = TypedAttributeArray; using AttributeArrayD = TypedAttributeArray; { AttributeArray::Ptr attr(new AttributeArrayD(50)); CPPUNIT_ASSERT_EQUAL(attr->size(), Index(50)); } { AttributeArray::Ptr attr(new AttributeArrayD(50)); CPPUNIT_ASSERT_EQUAL(Index(50), attr->size()); AttributeArrayD& typedAttr = static_cast(*attr); typedAttr.set(0, 0.5); double value = 0.0; typedAttr.get(0, value); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.5), value, /*tolerance=*/double(0.0)); // test unsafe methods for get() and set() typedAttr.setUnsafe(0, 1.5); typedAttr.getUnsafe(0, value); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.5), value, /*tolerance=*/double(0.0)); // out-of-range get() and set() CPPUNIT_ASSERT_THROW(typedAttr.set(100, 0.5), openvdb::IndexError); CPPUNIT_ASSERT_THROW(typedAttr.set(100, 1), openvdb::IndexError); CPPUNIT_ASSERT_THROW(typedAttr.get(100, value), openvdb::IndexError); CPPUNIT_ASSERT_THROW(typedAttr.get(100), openvdb::IndexError); } #ifdef NDEBUG { // test setUnsafe and getUnsafe on uniform arrays AttributeArrayD::Ptr attr(new AttributeArrayD(50)); CPPUNIT_ASSERT_EQUAL(Index(50), attr->size()); attr->collapse(5.0); CPPUNIT_ASSERT(attr->isUniform()); CPPUNIT_ASSERT_DOUBLES_EQUAL(attr->getUnsafe(10), 5.0, /*tolerance=*/double(0.0)); CPPUNIT_ASSERT(attr->isUniform()); // this is expected behaviour because for performance reasons, array is not implicitly expanded attr->setUnsafe(10, 15.0); CPPUNIT_ASSERT(attr->isUniform()); CPPUNIT_ASSERT_DOUBLES_EQUAL(attr->getUnsafe(5), 15.0, /*tolerance=*/double(0.0)); attr->expand(); CPPUNIT_ASSERT(!attr->isUniform()); attr->setUnsafe(10, 25.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(attr->getUnsafe(5), 15.0, /*tolerance=*/double(0.0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(attr->getUnsafe(10), 25.0, /*tolerance=*/double(0.0)); } #endif using AttributeArrayC = TypedAttributeArray>; { // test hasValueType() AttributeArray::Ptr attrC(new AttributeArrayC(50)); AttributeArray::Ptr attrD(new AttributeArrayD(50)); AttributeArray::Ptr attrF(new AttributeArrayF(50)); CPPUNIT_ASSERT(attrD->hasValueType()); CPPUNIT_ASSERT(attrC->hasValueType()); CPPUNIT_ASSERT(!attrF->hasValueType()); CPPUNIT_ASSERT(!attrD->hasValueType()); CPPUNIT_ASSERT(!attrC->hasValueType()); CPPUNIT_ASSERT(attrF->hasValueType()); } { AttributeArray::Ptr attr(new AttributeArrayC(50)); AttributeArrayC& typedAttr = static_cast(*attr); typedAttr.set(0, 0.5); double value = 0.0; typedAttr.get(0, value); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.5), value, /*tolerance=*/double(0.0001)); // test unsafe methods for get() and set() double value2 = 0.0; typedAttr.setUnsafe(0, double(0.2)); typedAttr.getUnsafe(0, value2); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.2), value2, /*tolerance=*/double(0.0001)); } using AttributeArrayI = TypedAttributeArray; { // Base class API AttributeArray::Ptr attr(new AttributeArrayI(50)); CPPUNIT_ASSERT_EQUAL(Index(50), attr->size()); CPPUNIT_ASSERT_EQUAL((sizeof(AttributeArrayI) + sizeof(int)), attr->memUsage()); CPPUNIT_ASSERT(attr->isType()); CPPUNIT_ASSERT(!attr->isType()); CPPUNIT_ASSERT(*attr == *attr); } { // Typed class API const Index count = 50; const size_t uniformMemUsage = sizeof(AttributeArrayI) + sizeof(int); const size_t expandedMemUsage = sizeof(AttributeArrayI) + count * sizeof(int); AttributeArrayI attr(count); CPPUNIT_ASSERT_EQUAL(attr.size(), Index(count)); CPPUNIT_ASSERT_EQUAL(attr.get(0), 0); CPPUNIT_ASSERT_EQUAL(attr.get(10), 0); CPPUNIT_ASSERT(attr.isUniform()); CPPUNIT_ASSERT_EQUAL(uniformMemUsage, attr.memUsage()); attr.set(0, 10); CPPUNIT_ASSERT(!attr.isUniform()); CPPUNIT_ASSERT_EQUAL(expandedMemUsage, attr.memUsage()); AttributeArrayI attr2(count); attr2.set(0, 10); CPPUNIT_ASSERT(attr == attr2); attr.set(1, 5); CPPUNIT_ASSERT(!attr.compact()); CPPUNIT_ASSERT(!attr.isUniform()); CPPUNIT_ASSERT_EQUAL(attr.get(0), 10); CPPUNIT_ASSERT_EQUAL(attr.get(1), 5); CPPUNIT_ASSERT_EQUAL(attr.get(2), 0); attr.collapse(5); CPPUNIT_ASSERT(attr.isUniform()); CPPUNIT_ASSERT_EQUAL(uniformMemUsage, attr.memUsage()); CPPUNIT_ASSERT_EQUAL(attr.get(0), 5); CPPUNIT_ASSERT_EQUAL(attr.get(20), 5); CPPUNIT_ASSERT_EQUAL(attr.getUnsafe(20), 5); attr.expand(/*fill=*/false); CPPUNIT_ASSERT(!attr.isUniform()); CPPUNIT_ASSERT_EQUAL(expandedMemUsage, attr.memUsage()); attr.collapse(5); CPPUNIT_ASSERT(attr.isUniform()); attr.expand(); CPPUNIT_ASSERT(!attr.isUniform()); CPPUNIT_ASSERT_EQUAL(expandedMemUsage, attr.memUsage()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attr.get(i), 5); } CPPUNIT_ASSERT(attr.compact()); CPPUNIT_ASSERT(attr.isUniform()); CPPUNIT_ASSERT(attr.compact()); attr.expand(); attr.fill(10); CPPUNIT_ASSERT(!attr.isUniform()); CPPUNIT_ASSERT_EQUAL(expandedMemUsage, attr.memUsage()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attr.get(i), 10); } attr.collapse(7); CPPUNIT_ASSERT(attr.isUniform()); CPPUNIT_ASSERT_EQUAL(uniformMemUsage, attr.memUsage()); CPPUNIT_ASSERT_EQUAL(attr.get(0), 7); CPPUNIT_ASSERT_EQUAL(attr.get(20), 7); attr.fill(5); CPPUNIT_ASSERT(attr.isUniform()); CPPUNIT_ASSERT_EQUAL(uniformMemUsage, attr.memUsage()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attr.get(i), 5); } CPPUNIT_ASSERT(!attr.isTransient()); CPPUNIT_ASSERT(!attr.isHidden()); attr.setTransient(true); CPPUNIT_ASSERT(attr.isTransient()); CPPUNIT_ASSERT(!attr.isHidden()); attr.setHidden(true); CPPUNIT_ASSERT(attr.isTransient()); CPPUNIT_ASSERT(attr.isHidden()); attr.setTransient(false); CPPUNIT_ASSERT(!attr.isTransient()); CPPUNIT_ASSERT(attr.isHidden()); attr.setHidden(false); CPPUNIT_ASSERT(!attr.isTransient()); CPPUNIT_ASSERT(!attr.isHidden()); attr.setHidden(true); { // test copy construction AttributeArrayI attrB(attr); CPPUNIT_ASSERT(matchingNamePairs(attr.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attr.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attr.memUsage(), attrB.memUsage()); CPPUNIT_ASSERT_EQUAL(attr.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attr.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attr.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(attr.isCompressed(), attrB.isCompressed()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attr.get(i), attrB.get(i)); CPPUNIT_ASSERT_EQUAL(attr.get(i), attrB.getUnsafe(i)); CPPUNIT_ASSERT_EQUAL(attr.getUnsafe(i), attrB.getUnsafe(i)); } } // attribute array must not be uniform for compression attr.set(1, 7); attr.set(2, 8); attr.set(6, 100); { // test compressed copy construction attr.compress(); #ifdef OPENVDB_USE_BLOSC CPPUNIT_ASSERT(attr.isCompressed()); #endif AttributeArray::Ptr attrCopy = attr.copy(); AttributeArrayI& attrB(AttributeArrayI::cast(*attrCopy)); CPPUNIT_ASSERT(matchingNamePairs(attr.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attr.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attr.memUsage(), attrB.memUsage()); CPPUNIT_ASSERT_EQUAL(attr.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attr.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attr.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(attr.isCompressed(), attrB.isCompressed()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attr.get(i), attrB.get(i)); CPPUNIT_ASSERT_EQUAL(attr.get(i), attrB.getUnsafe(i)); CPPUNIT_ASSERT_EQUAL(attr.getUnsafe(i), attrB.getUnsafe(i)); } } { // test compressed copy construction (uncompress on copy) attr.compress(); #ifdef OPENVDB_USE_BLOSC CPPUNIT_ASSERT(attr.isCompressed()); #endif AttributeArray::Ptr attrCopy = attr.copyUncompressed(); AttributeArrayI& attrB(AttributeArrayI::cast(*attrCopy)); CPPUNIT_ASSERT(!attrB.isCompressed()); attr.decompress(); CPPUNIT_ASSERT(matchingNamePairs(attr.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attr.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attr.memUsage(), attrB.memUsage()); CPPUNIT_ASSERT_EQUAL(attr.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attr.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attr.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(attr.isCompressed(), attrB.isCompressed()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attr.get(i), attrB.get(i)); CPPUNIT_ASSERT_EQUAL(attr.get(i), attrB.getUnsafe(i)); CPPUNIT_ASSERT_EQUAL(attr.getUnsafe(i), attrB.getUnsafe(i)); } } } { // Fixed codec (position range) AttributeArray::Ptr attr1(new AttributeArrayC(50)); AttributeArrayC& fixedPoint = static_cast(*attr1); // position range is -0.5 => 0.5 fixedPoint.set(0, -0.6); fixedPoint.set(1, -0.4); fixedPoint.set(2, 0.4); fixedPoint.set(3, 0.6); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-0.5), fixedPoint.get(0), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-0.4), fixedPoint.get(1), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.4), fixedPoint.get(2), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.5), fixedPoint.get(3), /*tolerance=*/double(0.0001)); } using UnitFixedPointCodec8 = FixedPointCodec; using AttributeArrayUFxpt8 = TypedAttributeArray; { // 8-bit fixed codec (unit range) AttributeArray::Ptr attr1(new AttributeArrayUFxpt8(50)); AttributeArrayUFxpt8& fixedPoint = static_cast(*attr1); // unit range is 0.0 => 1.0 fixedPoint.set(0, -0.2); fixedPoint.set(1, 0.3); fixedPoint.set(2, 0.6); fixedPoint.set(3, 1.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), fixedPoint.get(0), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.3), fixedPoint.get(1), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.6), fixedPoint.get(2), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), fixedPoint.get(3), /*tolerance=*/double(0.0001)); } using UnitFixedPointCodec16 = FixedPointCodec; using AttributeArrayUFxpt16 = TypedAttributeArray; { // 16-bit fixed codec (unit range) AttributeArray::Ptr attr1(new AttributeArrayUFxpt16(50)); AttributeArrayUFxpt16& fixedPoint = static_cast(*attr1); // unit range is 0.0 => 1.0 fixedPoint.set(0, -0.2); fixedPoint.set(1, 0.3); fixedPoint.set(2, 0.6); fixedPoint.set(3, 1.1); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), fixedPoint.get(0), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.3), fixedPoint.get(1), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.6), fixedPoint.get(2), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), fixedPoint.get(3), /*tolerance=*/double(0.0001)); } using AttributeArrayU = TypedAttributeArray; { // UnitVec codec test AttributeArray::Ptr attr1(new AttributeArrayU(50)); AttributeArrayU& unitVec = static_cast(*attr1); // all vectors must be unit length const openvdb::Vec3f vec1(1.0, 0.0, 0.0); const openvdb::Vec3f vec2(openvdb::Vec3f(1.0, 2.0, 3.0).unit()); const openvdb::Vec3f vec3(openvdb::Vec3f(1.0, 2.0, 300000.0).unit()); unitVec.set(0, vec1); unitVec.set(1, vec2); unitVec.set(2, vec3); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec1.x()), unitVec.get(0).x(), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec1.y()), unitVec.get(0).y(), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec1.z()), unitVec.get(0).z(), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec2.x()), unitVec.get(1).x(), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec2.y()), unitVec.get(1).y(), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec2.z()), unitVec.get(1).z(), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec3.x()), unitVec.get(2).x(), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec3.y()), unitVec.get(2).y(), /*tolerance=*/double(0.0001)); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(vec3.z()), unitVec.get(2).z(), /*tolerance=*/double(0.0001)); } { // IO const Index count = 50; AttributeArrayI attrA(count); for (unsigned i = 0; i < unsigned(count); ++i) { attrA.set(i, int(i)); } attrA.setHidden(true); std::ostringstream ostr(std::ios_base::binary); io::setDataCompression(ostr, io::COMPRESS_BLOSC); attrA.write(ostr); AttributeArrayI attrB; std::istringstream istr(ostr.str(), std::ios_base::binary); attrB.read(istr); CPPUNIT_ASSERT(matchingNamePairs(attrA.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attrA.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attrA.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attrA.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attrA.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(attrA.isCompressed(), attrB.isCompressed()); CPPUNIT_ASSERT_EQUAL(attrA.memUsage(), attrB.memUsage()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); } AttributeArrayI attrC(count, 3); attrC.setTransient(true); std::ostringstream ostrC(std::ios_base::binary); attrC.write(ostrC); CPPUNIT_ASSERT_EQUAL(ostrC.str().size(), size_t(0)); std::ostringstream ostrD(std::ios_base::binary); attrC.write(ostrD, /*transient=*/true); CPPUNIT_ASSERT(ostrD.str().size() != size_t(0)); } // Registry AttributeArrayI::registerType(); AttributeArray::Ptr attr = AttributeArray::create( AttributeArrayI::attributeType(), 34); { // Casting AttributeArray::Ptr array = TypedAttributeArray::create(0); CPPUNIT_ASSERT_NO_THROW(TypedAttributeArray::cast(*array)); CPPUNIT_ASSERT_THROW(TypedAttributeArray::cast(*array), TypeError); AttributeArray::ConstPtr constArray = array; CPPUNIT_ASSERT_NO_THROW(TypedAttributeArray::cast(*constArray)); CPPUNIT_ASSERT_THROW(TypedAttributeArray::cast(*constArray), TypeError); } } void TestAttributeArray::testAccessorEval() { using AttributeF = TypedAttributeArray; struct TestAccessor { static float getterError(const AttributeArray* /*array*/, const Index /*n*/) { OPENVDB_THROW(NotImplementedError, ""); } static void setterError [[noreturn]] (AttributeArray* /*array*/, const Index /*n*/, const float& /*value*/) { OPENVDB_THROW(NotImplementedError, ""); } //static float testGetter(const AttributeArray* array, const Index n) { // return AccessorEval::get(&getterError, array, n); //} //static void testSetter(AttributeArray* array, const Index n, const float& value) { // AccessorEval::set(&setterError, array, n, value); //} }; { // test get and set (NullCodec) AttributeF::Ptr attr = AttributeF::create(10); attr->collapse(5.0f); attr->expand(); AttributeArray& array = *attr; // explicit codec is used here so getter and setter are not called AttributeWriteHandle writeHandle(array); writeHandle.mSetter = TestAccessor::setterError; writeHandle.set(4, 15.0f); AttributeHandle handle(array); handle.mGetter = TestAccessor::getterError; const float result1 = handle.get(4); const float result2 = handle.get(6); CPPUNIT_ASSERT_EQUAL(result1, 15.0f); CPPUNIT_ASSERT_EQUAL(result2, 5.0f); } { // test get and set (UnknownCodec) AttributeF::Ptr attr = AttributeF::create(10); attr->collapse(5.0f); attr->expand(); AttributeArray& array = *attr; // unknown codec is used here so getter and setter are called AttributeWriteHandle writeHandle(array); writeHandle.mSetter = TestAccessor::setterError; CPPUNIT_ASSERT_THROW(writeHandle.set(4, 15.0f), NotImplementedError); AttributeHandle handle(array); handle.mGetter = TestAccessor::getterError; CPPUNIT_ASSERT_THROW(handle.get(4), NotImplementedError); } } void TestAttributeArray::testAttributeHandle() { using namespace openvdb::math; using AttributeI = TypedAttributeArray; using AttributeFH = TypedAttributeArray; using AttributeVec3f = TypedAttributeArray; using AttributeHandleRWI = AttributeWriteHandle; AttributeI::registerType(); AttributeFH::registerType(); AttributeVec3f::registerType(); // create a Descriptor and AttributeSet using Descriptor = AttributeSet::Descriptor; Descriptor::Ptr descr = Descriptor::create(AttributeVec3f::attributeType()); unsigned count = 500; AttributeSet attrSet(descr, /*arrayLength=*/count); attrSet.appendAttribute("truncate", AttributeFH::attributeType()); attrSet.appendAttribute("int", AttributeI::attributeType()); // check uniform value implementation { AttributeArray* array = attrSet.get(2); AttributeHandleRWI nonExpandingHandle(*array, /*expand=*/false); CPPUNIT_ASSERT(nonExpandingHandle.isUniform()); AttributeHandleRWI handle(*array); CPPUNIT_ASSERT(!handle.isUniform()); CPPUNIT_ASSERT_EQUAL(handle.size(), array->size()); CPPUNIT_ASSERT_EQUAL(handle.get(0), 0); CPPUNIT_ASSERT_EQUAL(handle.get(10), 0); handle.set(0, 10); CPPUNIT_ASSERT(!handle.isUniform()); handle.collapse(5); CPPUNIT_ASSERT(handle.isUniform()); CPPUNIT_ASSERT_EQUAL(handle.get(0), 5); CPPUNIT_ASSERT_EQUAL(handle.get(20), 5); handle.expand(); CPPUNIT_ASSERT(!handle.isUniform()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(handle.get(i), 5); } CPPUNIT_ASSERT(handle.compact()); CPPUNIT_ASSERT(handle.isUniform()); handle.expand(); handle.fill(10); CPPUNIT_ASSERT(!handle.isUniform()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(handle.get(i), 10); } handle.collapse(7); CPPUNIT_ASSERT(handle.isUniform()); CPPUNIT_ASSERT_EQUAL(handle.get(0), 7); CPPUNIT_ASSERT_EQUAL(handle.get(20), 7); handle.fill(5); CPPUNIT_ASSERT(handle.isUniform()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(handle.get(i), 5); } CPPUNIT_ASSERT(handle.isUniform()); } { AttributeArray* array = attrSet.get(0); AttributeWriteHandle handle(*array); handle.set(5, Vec3f(10)); CPPUNIT_ASSERT_EQUAL(handle.get(5), Vec3f(10)); } { AttributeArray* array = attrSet.get(1); array->compress(); AttributeWriteHandle handle(*array); handle.set(6, float(11)); CPPUNIT_ASSERT_EQUAL(handle.get(6), float(11)); CPPUNIT_ASSERT(!array->isCompressed()); #ifdef OPENVDB_USE_BLOSC array->compress(); CPPUNIT_ASSERT(array->isCompressed()); { AttributeHandle handleRO(*array); CPPUNIT_ASSERT(array->isCompressed()); CPPUNIT_ASSERT_EQUAL(handleRO.get(6), float(11)); CPPUNIT_ASSERT(array->isCompressed()); } CPPUNIT_ASSERT(array->isCompressed()); { AttributeHandle handleRO(*array, /*preserveCompression=*/false); // AttributeHandle uncompresses data on construction CPPUNIT_ASSERT(!array->isCompressed()); CPPUNIT_ASSERT_EQUAL(handleRO.get(6), float(11)); CPPUNIT_ASSERT(!array->isCompressed()); } CPPUNIT_ASSERT(!array->isCompressed()); #endif } // check values have been correctly set without using handles { AttributeVec3f* array = static_cast(attrSet.get(0)); CPPUNIT_ASSERT(array); CPPUNIT_ASSERT_EQUAL(array->get(5), Vec3f(10)); } { AttributeFH* array = static_cast(attrSet.get(1)); CPPUNIT_ASSERT(array); CPPUNIT_ASSERT_EQUAL(array->get(6), float(11)); } } void TestAttributeArray::testStrided() { using AttributeArrayI = TypedAttributeArray; using StridedHandle = AttributeHandle; using StridedWriteHandle = AttributeWriteHandle; { // non-strided array AttributeArrayI::Ptr array = AttributeArrayI::create(/*n=*/2, /*stride=*/1); CPPUNIT_ASSERT(array->hasConstantStride()); CPPUNIT_ASSERT_EQUAL(array->stride(), Index(1)); CPPUNIT_ASSERT_EQUAL(array->size(), Index(2)); CPPUNIT_ASSERT_EQUAL(array->dataSize(), Index(2)); } { // strided array AttributeArrayI::Ptr array = AttributeArrayI::create(/*n=*/2, /*stride=*/3); CPPUNIT_ASSERT(array->hasConstantStride()); CPPUNIT_ASSERT_EQUAL(array->stride(), Index(3)); CPPUNIT_ASSERT_EQUAL(array->size(), Index(2)); CPPUNIT_ASSERT_EQUAL(array->dataSize(), Index(6)); CPPUNIT_ASSERT(array->isUniform()); CPPUNIT_ASSERT_EQUAL(array->get(0), 0); CPPUNIT_ASSERT_EQUAL(array->get(5), 0); CPPUNIT_ASSERT_THROW(array->get(6), IndexError); // out-of-range CPPUNIT_ASSERT_NO_THROW(StridedHandle::create(*array)); CPPUNIT_ASSERT_NO_THROW(StridedWriteHandle::create(*array)); array->collapse(10); CPPUNIT_ASSERT_EQUAL(array->get(0), int(10)); CPPUNIT_ASSERT_EQUAL(array->get(5), int(10)); array->expand(); CPPUNIT_ASSERT_EQUAL(array->get(0), int(10)); CPPUNIT_ASSERT_EQUAL(array->get(5), int(10)); array->collapse(0); CPPUNIT_ASSERT_EQUAL(array->get(0), int(0)); CPPUNIT_ASSERT_EQUAL(array->get(5), int(0)); StridedWriteHandle writeHandle(*array); writeHandle.set(0, 2, 5); writeHandle.set(1, 1, 10); CPPUNIT_ASSERT_EQUAL(writeHandle.stride(), Index(3)); CPPUNIT_ASSERT_EQUAL(writeHandle.size(), Index(2)); // non-interleaved: 0 0 5 0 10 0 CPPUNIT_ASSERT_EQUAL(array->get(2), 5); CPPUNIT_ASSERT_EQUAL(array->get(4), 10); CPPUNIT_ASSERT_EQUAL(writeHandle.get(0, 2), 5); CPPUNIT_ASSERT_EQUAL(writeHandle.get(1, 1), 10); StridedHandle handle(*array); CPPUNIT_ASSERT(handle.hasConstantStride()); CPPUNIT_ASSERT_EQUAL(handle.get(0, 2), 5); CPPUNIT_ASSERT_EQUAL(handle.get(1, 1), 10); CPPUNIT_ASSERT_EQUAL(handle.stride(), Index(3)); CPPUNIT_ASSERT_EQUAL(handle.size(), Index(2)); size_t arrayMem = 64; CPPUNIT_ASSERT_EQUAL(array->memUsage(), sizeof(int) * /*size*/3 * /*stride*/2 + arrayMem); } { // dynamic stride AttributeArrayI::Ptr array = AttributeArrayI::create(/*n=*/2, /*stride=*/7, /*constantStride=*/false); CPPUNIT_ASSERT(!array->hasConstantStride()); // zero indicates dynamic striding CPPUNIT_ASSERT_EQUAL(array->stride(), Index(0)); CPPUNIT_ASSERT_EQUAL(array->size(), Index(2)); // the actual array size CPPUNIT_ASSERT_EQUAL(array->dataSize(), Index(7)); CPPUNIT_ASSERT(array->isUniform()); CPPUNIT_ASSERT_EQUAL(array->get(0), 0); CPPUNIT_ASSERT_EQUAL(array->get(6), 0); CPPUNIT_ASSERT_THROW(array->get(7), IndexError); // out-of-range CPPUNIT_ASSERT_NO_THROW(StridedHandle::create(*array)); CPPUNIT_ASSERT_NO_THROW(StridedWriteHandle::create(*array)); // handle is bound as if a linear array with stride 1 StridedHandle handle(*array); CPPUNIT_ASSERT(!handle.hasConstantStride()); CPPUNIT_ASSERT_EQUAL(handle.stride(), Index(1)); CPPUNIT_ASSERT_EQUAL(handle.size(), array->dataSize()); } } void TestAttributeArray::testDelayedLoad() { using AttributeArrayI = TypedAttributeArray; using AttributeArrayF = TypedAttributeArray; AttributeArrayI::registerType(); AttributeArrayF::registerType(); io::StreamMetadata::Ptr streamMetadata(new io::StreamMetadata); std::string tempDir; if (const char* dir = std::getenv("TMPDIR")) tempDir = dir; #ifdef _MSC_VER if (tempDir.empty()) { char tempDirBuffer[MAX_PATH+1]; int tempDirLen = GetTempPath(MAX_PATH+1, tempDirBuffer); CPPUNIT_ASSERT(tempDirLen > 0 && tempDirLen <= MAX_PATH); tempDir = tempDirBuffer; } #else if (tempDir.empty()) tempDir = P_tmpdir; #endif { // IO const Index count = 50; AttributeArrayI attrA(count); for (unsigned i = 0; i < unsigned(count); ++i) { attrA.set(i, int(i)); } AttributeArrayF attrA2(count); std::string filename; // write out attribute array to a temp file { filename = tempDir + "/openvdb_delayed1"; std::ofstream fileout(filename.c_str(), std::ios_base::binary); io::setStreamMetadataPtr(fileout, streamMetadata); io::setDataCompression(fileout, io::COMPRESS_BLOSC); attrA.writeMetadata(fileout, false, /*paged=*/true); compression::PagedOutputStream outputStreamSize(fileout); outputStreamSize.setSizeOnly(true); attrA.writePagedBuffers(outputStreamSize, false); outputStreamSize.flush(); compression::PagedOutputStream outputStream(fileout); outputStream.setSizeOnly(false); attrA.writePagedBuffers(outputStream, false); outputStream.flush(); attrA2.writeMetadata(fileout, false, /*paged=*/true); compression::PagedOutputStream outputStreamSize2(fileout); outputStreamSize2.setSizeOnly(true); attrA2.writePagedBuffers(outputStreamSize2, false); outputStreamSize2.flush(); compression::PagedOutputStream outputStream2(fileout); outputStream2.setSizeOnly(false); attrA2.writePagedBuffers(outputStream2, false); outputStream2.flush(); fileout.close(); } // abuse File being a friend of MappedFile to get around the private constructor ProxyMappedFile* proxy = new ProxyMappedFile(filename); SharedPtr mappedFile(reinterpret_cast(proxy)); // read in using delayed load and check manual loading of data { AttributeArrayI attrB; AttributeArrayF attrB2; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(matchingNamePairs(attrA.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attrA.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attrA.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attrA.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attrA.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(attrA.isCompressed(), attrB.isCompressed()); AttributeArrayI attrBcopy(attrB); AttributeArrayI attrBequal = attrB; CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(attrBcopy.isOutOfCore()); CPPUNIT_ASSERT(attrBequal.isOutOfCore()); attrB.loadData(); attrBcopy.loadData(); attrBequal.loadData(); CPPUNIT_ASSERT(!attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrBcopy.isOutOfCore()); CPPUNIT_ASSERT(!attrBequal.isOutOfCore()); CPPUNIT_ASSERT_EQUAL(attrA.memUsage(), attrB.memUsage()); CPPUNIT_ASSERT_EQUAL(attrA.memUsage(), attrBcopy.memUsage()); CPPUNIT_ASSERT_EQUAL(attrA.memUsage(), attrBequal.memUsage()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrBcopy.get(i)); CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrBequal.get(i)); } attrB2.readMetadata(filein); compression::PagedInputStream inputStream2(filein); inputStream2.setSizeOnly(true); attrB2.readPagedBuffers(inputStream2); inputStream2.setSizeOnly(false); attrB2.readPagedBuffers(inputStream2); CPPUNIT_ASSERT(matchingNamePairs(attrA2.type(), attrB2.type())); CPPUNIT_ASSERT_EQUAL(attrA2.size(), attrB2.size()); CPPUNIT_ASSERT_EQUAL(attrA2.isUniform(), attrB2.isUniform()); CPPUNIT_ASSERT_EQUAL(attrA2.isTransient(), attrB2.isTransient()); CPPUNIT_ASSERT_EQUAL(attrA2.isHidden(), attrB2.isHidden()); CPPUNIT_ASSERT_EQUAL(attrA2.isCompressed(), attrB2.isCompressed()); AttributeArrayF attrB2copy(attrB2); AttributeArrayF attrB2equal = attrB2; CPPUNIT_ASSERT(attrB2.isOutOfCore()); CPPUNIT_ASSERT(attrB2copy.isOutOfCore()); CPPUNIT_ASSERT(attrB2equal.isOutOfCore()); attrB2.loadData(); attrB2copy.loadData(); attrB2equal.loadData(); CPPUNIT_ASSERT(!attrB2.isOutOfCore()); CPPUNIT_ASSERT(!attrB2copy.isOutOfCore()); CPPUNIT_ASSERT(!attrB2equal.isOutOfCore()); CPPUNIT_ASSERT_EQUAL(attrA2.memUsage(), attrB2.memUsage()); CPPUNIT_ASSERT_EQUAL(attrA2.memUsage(), attrB2copy.memUsage()); CPPUNIT_ASSERT_EQUAL(attrA2.memUsage(), attrB2equal.memUsage()); CPPUNIT_ASSERT_EQUAL(attrA2.get(0), attrB2.get(0)); CPPUNIT_ASSERT_EQUAL(attrA2.get(0), attrB2copy.get(0)); CPPUNIT_ASSERT_EQUAL(attrA2.get(0), attrB2equal.get(0)); } // read in using delayed load and check fill() { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrB.isUniform()); attrB.fill(5); CPPUNIT_ASSERT(!attrB.isOutOfCore()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(5, attrB.get(i)); } } // read in using delayed load and check streaming (write handle) { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrB.isUniform()); attrB.setStreaming(true); { AttributeWriteHandle handle(attrB); CPPUNIT_ASSERT(!attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrB.isUniform()); } CPPUNIT_ASSERT(!attrB.isUniform()); } // read in using delayed load and check streaming (read handle) { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrB.isUniform()); attrB.setStreaming(true); { AttributeHandle handle(attrB); CPPUNIT_ASSERT(!attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrB.isUniform()); } CPPUNIT_ASSERT(attrB.isUniform()); } // read in using delayed load and check implicit load through get() { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); attrB.get(0); CPPUNIT_ASSERT(!attrB.isOutOfCore()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); } } // read in using delayed load and check implicit load through compress() { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrB.isCompressed()); attrB.compress(); #ifdef OPENVDB_USE_BLOSC CPPUNIT_ASSERT(!attrB.isOutOfCore()); CPPUNIT_ASSERT(attrB.isCompressed()); #else CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrB.isCompressed()); #endif } // read in using delayed load and check copy and assignment constructors { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); AttributeArrayI attrC(attrB); AttributeArrayI attrD = attrB; CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(attrC.isOutOfCore()); CPPUNIT_ASSERT(attrD.isOutOfCore()); attrB.loadData(); attrC.loadData(); attrD.loadData(); CPPUNIT_ASSERT(!attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrC.isOutOfCore()); CPPUNIT_ASSERT(!attrD.isOutOfCore()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrC.get(i)); CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrD.get(i)); } } // read in using delayed load and check implicit load through AttributeHandle { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); AttributeHandle handle(attrB); CPPUNIT_ASSERT(!attrB.isOutOfCore()); } // read in using delayed load and check detaching of file (using collapse()) { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrB.isUniform()); attrB.collapse(); CPPUNIT_ASSERT(!attrB.isOutOfCore()); CPPUNIT_ASSERT(attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attrB.get(0), 0); } // read in and write out using delayed load to check writing out-of-core attributes { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); std::string filename2 = tempDir + "/openvdb_delayed5"; std::ofstream fileout2(filename2.c_str(), std::ios_base::binary); io::setStreamMetadataPtr(fileout2, streamMetadata); io::setDataCompression(fileout2, io::COMPRESS_BLOSC); attrB.writeMetadata(fileout2, false, /*paged=*/true); compression::PagedOutputStream outputStreamSize(fileout2); outputStreamSize.setSizeOnly(true); attrB.writePagedBuffers(outputStreamSize, false); outputStreamSize.flush(); compression::PagedOutputStream outputStream(fileout2); outputStream.setSizeOnly(false); attrB.writePagedBuffers(outputStream, false); outputStream.flush(); fileout2.close(); AttributeArrayI attrB2; std::ifstream filein2(filename2.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein2, streamMetadata); io::setMappedFilePtr(filein2, mappedFile); attrB2.readMetadata(filein2); compression::PagedInputStream inputStream2(filein2); inputStream2.setSizeOnly(true); attrB2.readPagedBuffers(inputStream2); inputStream2.setSizeOnly(false); attrB2.readPagedBuffers(inputStream2); CPPUNIT_ASSERT(attrB2.isOutOfCore()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrB.get(i), attrB2.get(i)); } filein2.close(); } // Clean up temp files. std::remove(mappedFile->filename().c_str()); std::remove(filename.c_str()); AttributeArrayI attrUniform(count); // write out uniform attribute array to a temp file { filename = tempDir + "/openvdb_delayed2"; std::ofstream fileout(filename.c_str(), std::ios_base::binary); io::setStreamMetadataPtr(fileout, streamMetadata); io::setDataCompression(fileout, io::COMPRESS_BLOSC); attrUniform.writeMetadata(fileout, false, /*paged=*/true); compression::PagedOutputStream outputStreamSize(fileout); outputStreamSize.setSizeOnly(true); attrUniform.writePagedBuffers(outputStreamSize, false); outputStreamSize.flush(); compression::PagedOutputStream outputStream(fileout); outputStream.setSizeOnly(false); attrUniform.writePagedBuffers(outputStream, false); outputStream.flush(); fileout.close(); } // abuse File being a friend of MappedFile to get around the private constructor proxy = new ProxyMappedFile(filename); mappedFile.reset(reinterpret_cast(proxy)); // read in using delayed load and check fill() { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isUniform()); attrB.fill(5); CPPUNIT_ASSERT(attrB.isUniform()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(5, attrB.get(i)); } } AttributeArrayI attrStrided(count, /*stride=*/3); CPPUNIT_ASSERT_EQUAL(attrStrided.stride(), Index(3)); // Clean up temp files. std::remove(mappedFile->filename().c_str()); std::remove(filename.c_str()); // write out strided attribute array to a temp file { filename = tempDir + "/openvdb_delayed3"; std::ofstream fileout(filename.c_str(), std::ios_base::binary); io::setStreamMetadataPtr(fileout, streamMetadata); io::setDataCompression(fileout, io::COMPRESS_BLOSC); attrStrided.writeMetadata(fileout, false, /*paged=*/true); compression::PagedOutputStream outputStreamSize(fileout); outputStreamSize.setSizeOnly(true); attrStrided.writePagedBuffers(outputStreamSize, false); outputStreamSize.flush(); compression::PagedOutputStream outputStream(fileout); outputStream.setSizeOnly(false); attrStrided.writePagedBuffers(outputStream, false); outputStream.flush(); fileout.close(); } // abuse File being a friend of MappedFile to get around the private constructor proxy = new ProxyMappedFile(filename); mappedFile.reset(reinterpret_cast(proxy)); // read in using delayed load and check fill() { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT_EQUAL(attrB.stride(), Index(3)); } // Clean up temp files. std::remove(mappedFile->filename().c_str()); std::remove(filename.c_str()); // write out compressed attribute array to a temp file { filename = tempDir + "/openvdb_delayed4"; std::ofstream fileout(filename.c_str(), std::ios_base::binary); io::setStreamMetadataPtr(fileout, streamMetadata); io::setDataCompression(fileout, io::COMPRESS_BLOSC); attrA.compress(); attrA.writeMetadata(fileout, false, /*paged=*/true); compression::PagedOutputStream outputStreamSize(fileout); outputStreamSize.setSizeOnly(true); attrA.writePagedBuffers(outputStreamSize, false); outputStreamSize.flush(); compression::PagedOutputStream outputStream(fileout); outputStream.setSizeOnly(false); attrA.writePagedBuffers(outputStream, false); outputStream.flush(); fileout.close(); } // abuse File being a friend of MappedFile to get around the private constructor proxy = new ProxyMappedFile(filename); mappedFile.reset(reinterpret_cast(proxy)); // read in using delayed load and check manual loading of data { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); #ifdef OPENVDB_USE_BLOSC CPPUNIT_ASSERT(attrB.isCompressed()); #endif CPPUNIT_ASSERT(attrB.isOutOfCore()); attrB.loadData(); CPPUNIT_ASSERT(!attrB.isOutOfCore()); #ifdef OPENVDB_USE_BLOSC CPPUNIT_ASSERT(attrB.isCompressed()); #endif CPPUNIT_ASSERT_EQUAL(attrA.memUsage(), attrB.memUsage()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); } } // read in using delayed load and check implicit load through get() { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); attrB.get(0); CPPUNIT_ASSERT(!attrB.isOutOfCore()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); } } #ifdef OPENVDB_USE_BLOSC // read in using delayed load and check no implicit load through compress() { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(attrB.isCompressed()); attrB.compress(); CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(attrB.isCompressed()); } // read in using delayed load and check copy and assignment constructors { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); AttributeArrayI attrC(attrB); AttributeArrayI attrD = attrB; CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(attrC.isOutOfCore()); CPPUNIT_ASSERT(attrD.isOutOfCore()); attrB.loadData(); attrC.loadData(); attrD.loadData(); CPPUNIT_ASSERT(!attrB.isOutOfCore()); CPPUNIT_ASSERT(!attrC.isOutOfCore()); CPPUNIT_ASSERT(!attrD.isOutOfCore()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrC.get(i)); CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrD.get(i)); } } // read in using delayed load and check implicit load through AttributeHandle { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); attrB.readMetadata(filein); compression::PagedInputStream inputStream(filein); inputStream.setSizeOnly(true); attrB.readPagedBuffers(inputStream); inputStream.setSizeOnly(false); attrB.readPagedBuffers(inputStream); CPPUNIT_ASSERT(attrB.isOutOfCore()); CPPUNIT_ASSERT(attrB.isCompressed()); AttributeHandle handle(attrB); CPPUNIT_ASSERT(!attrB.isOutOfCore()); CPPUNIT_ASSERT(attrB.isCompressed()); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), handle.get(i)); } AttributeHandle handle2(attrB, /*preserveCompression=*/false); CPPUNIT_ASSERT(!attrB.isCompressed()); } #endif // Clean up temp files. std::remove(mappedFile->filename().c_str()); std::remove(filename.c_str()); // write out invalid serialization flags as metadata to a temp file { filename = tempDir + "/openvdb_delayed5"; std::ofstream fileout(filename.c_str(), std::ios_base::binary); io::setStreamMetadataPtr(fileout, streamMetadata); io::setDataCompression(fileout, io::COMPRESS_BLOSC); // write out unknown serialization flags to check forwards-compatibility Index64 bytes(0); uint8_t flags(0); uint8_t serializationFlags(Int16(0x10)); Index size(0); fileout.write(reinterpret_cast(&bytes), sizeof(Index64)); fileout.write(reinterpret_cast(&flags), sizeof(uint8_t)); fileout.write(reinterpret_cast(&serializationFlags), sizeof(uint8_t)); fileout.write(reinterpret_cast(&size), sizeof(Index)); fileout.close(); } // abuse File being a friend of MappedFile to get around the private constructor proxy = new ProxyMappedFile(filename); mappedFile.reset(reinterpret_cast(proxy)); // read in using delayed load and check metadata fail due to serialization flags { AttributeArrayI attrB; std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); CPPUNIT_ASSERT_THROW(attrB.readMetadata(filein), openvdb::IoError); } // cleanup temp files std::remove(mappedFile->filename().c_str()); std::remove(filename.c_str()); } } void TestAttributeArray::testQuaternions() { using AttributeQF = TypedAttributeArray>; using AttributeQD = TypedAttributeArray; AttributeQF::registerType(); AttributeQD::registerType(); CPPUNIT_ASSERT(AttributeQF::attributeType().first == "quats"); CPPUNIT_ASSERT(AttributeQD::attributeType().first == "quatd"); AttributeQF test(/*size=*/5); AttributeQD orient(/*size=*/10); { // set some quaternion values AttributeWriteHandle orientHandle(orient); orientHandle.set(4, QuatR(1, 2, 3, 4)); orientHandle.set(7, QuatR::identity()); } { // get some quaternion values AttributeHandle orientHandle(orient); CPPUNIT_ASSERT_EQUAL(orientHandle.get(3), QuatR::zero()); CPPUNIT_ASSERT_EQUAL(orientHandle.get(4), QuatR(1, 2, 3, 4)); CPPUNIT_ASSERT_EQUAL(orientHandle.get(7), QuatR::identity()); } { // create a quaternion array with a zero uniform value AttributeQD zero(/*size=*/10, /*stride=*/1, /*constantStride=*/true, QuatR::zero()); CPPUNIT_ASSERT_EQUAL(zero.get(5), QuatR::zero()); } } void TestAttributeArray::testMatrices() { typedef TypedAttributeArray AttributeM; AttributeM::registerType(); CPPUNIT_ASSERT(AttributeM::attributeType().first == "mat4d"); AttributeM matrix(/*size=*/10); Mat4d testMatrix(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); { // set some matrix values AttributeWriteHandle matrixHandle(matrix); matrixHandle.set(4, testMatrix); matrixHandle.set(7, Mat4d::zero()); } { // get some matrix values AttributeHandle matrixHandle(matrix); CPPUNIT_ASSERT_EQUAL(matrixHandle.get(3), Mat4d::identity()); CPPUNIT_ASSERT_EQUAL(matrixHandle.get(4), testMatrix); CPPUNIT_ASSERT_EQUAL(matrixHandle.get(7), Mat4d::zero()); } { // create a matrix array with a zero uniform value AttributeM zero(/*size=*/10, /*stride=*/1, /*constantStride=*/true, Mat4d::zero()); CPPUNIT_ASSERT_EQUAL(zero.get(5), Mat4d::zero()); } } namespace profile { template void expand(const Name& prefix, AttrT& attr) { ProfileTimer timer(prefix + ": expand"); attr.expand(); } template void set(const Name& prefix, AttrT& attr) { ProfileTimer timer(prefix + ": set"); const Index size = attr.size(); for (Index i = 0; i < size; i++) { attr.setUnsafe(i, typename AttrT::ValueType(i)); } } template void setH(const Name& prefix, AttrT& attr) { using ValueType = typename AttrT::ValueType; ProfileTimer timer(prefix + ": setHandle"); AttributeWriteHandle handle(attr); const Index size = attr.size(); for (Index i = 0; i < size; i++) { handle.set(i, ValueType(i)); } } template void sum(const Name& prefix, const AttrT& attr) { ProfileTimer timer(prefix + ": sum"); typename AttrT::ValueType sum = 0; const Index size = attr.size(); for (Index i = 0; i < size; i++) { sum += attr.getUnsafe(i); } // prevent compiler optimisations removing computation CPPUNIT_ASSERT(sum); } template void sumH(const Name& prefix, const AttrT& attr) { ProfileTimer timer(prefix + ": sumHandle"); using ValueType = typename AttrT::ValueType; ValueType sum = 0; AttributeHandle handle(attr); for (Index i = 0; i < attr.size(); i++) { sum += handle.get(i); } // prevent compiler optimisations removing computation CPPUNIT_ASSERT(sum); } } // namespace profile void TestAttributeArray::testProfile() { using namespace openvdb::util; using namespace openvdb::math; using AttributeArrayF = TypedAttributeArray; using AttributeArrayF16 = TypedAttributeArray>; using AttributeArrayF8 = TypedAttributeArray>; /////////////////////////////////////////////////// #ifdef PROFILE const size_t elements(1000 * 1000 * 1000); std::cerr << std::endl; #else const size_t elements(10 * 1000 * 1000); #endif // std::vector { std::vector values; { ProfileTimer timer("Vector: resize"); values.resize(elements); } { ProfileTimer timer("Vector: set"); for (size_t i = 0; i < elements; i++) { values[i] = float(i); } } { ProfileTimer timer("Vector: sum"); float sum = 0; for (size_t i = 0; i < elements; i++) { sum += float(values[i]); } // to prevent optimisation clean up CPPUNIT_ASSERT(sum); } } // AttributeArray { AttributeArrayF attr(elements); profile::expand("AttributeArray", attr); profile::set("AttributeArray", attr); profile::sum("AttributeArray", attr); } { AttributeArrayF16 attr(elements); profile::expand("AttributeArray", attr); profile::set("AttributeArray", attr); profile::sum("AttributeArray", attr); } { AttributeArrayF8 attr(elements); profile::expand("AttributeArray", attr); profile::set("AttributeArray", attr); profile::sum("AttributeArray", attr); } // AttributeHandle (UnknownCodec) { AttributeArrayF attr(elements); profile::expand("AttributeHandle", attr); profile::setH("AttributeHandle", attr); profile::sumH("AttributeHandle", attr); } { AttributeArrayF16 attr(elements); profile::expand("AttributeHandle", attr); profile::setH("AttributeHandle", attr); profile::sumH("AttributeHandle", attr); } { AttributeArrayF8 attr(elements); profile::expand("AttributeHandle", attr); profile::setH("AttributeHandle", attr); profile::sumH("AttributeHandle", attr); } // AttributeHandle (explicit codec) { AttributeArrayF attr(elements); profile::expand("AttributeHandle", attr); profile::setH("AttributeHandle", attr); profile::sumH("AttributeHandle", attr); } { AttributeArrayF16 attr(elements); profile::expand("AttributeHandle", attr); profile::setH>("AttributeHandle", attr); profile::sumH>("AttributeHandle", attr); } { AttributeArrayF8 attr(elements); profile::expand("AttributeHandle", attr); profile::setH>("AttributeHandle", attr); profile::sumH>("AttributeHandle", attr); } } // Copyright (c) 2012-2017 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/TestMaps.cc0000644000000000000000000006767113200122377015155 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 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.eq(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 = openvdb::StaticPtrCast( 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 = openvdb::StaticPtrCast( 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 = openvdb::StaticPtrCast( 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-2017 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/TestTopologyToLevelSet.cc0000644000000000000000000000636413200122377020030 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TopologyToLevelSet: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TopologyToLevelSet); CPPUNIT_TEST(testConversion); CPPUNIT_TEST_SUITE_END(); void testConversion(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TopologyToLevelSet); void TopologyToLevelSet::testConversion() { typedef openvdb::tree::Tree4::Type Tree543b; typedef openvdb::Grid BoolGrid; typedef openvdb::tree::Tree4::Type Tree543f; typedef openvdb::Grid FloatGrid; ///// const float voxelSize = 0.1f; const openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); BoolGrid maskGrid(false); maskGrid.setTransform(transform); // Define active region maskGrid.fill(openvdb::CoordBBox(openvdb::Coord(0), openvdb::Coord(7)), true); maskGrid.tree().voxelizeActiveTiles(); FloatGrid::Ptr sdfGrid = openvdb::tools::topologyToLevelSet(maskGrid); CPPUNIT_ASSERT(sdfGrid.get() != NULL); CPPUNIT_ASSERT(!sdfGrid->empty()); CPPUNIT_ASSERT_EQUAL(int(openvdb::GRID_LEVEL_SET), int(sdfGrid->getGridClass())); // test inside coord value CPPUNIT_ASSERT(sdfGrid->tree().getValue(openvdb::Coord(3,3,3)) < 0.0f); // test outside coord value CPPUNIT_ASSERT(sdfGrid->tree().getValue(openvdb::Coord(10,10,10)) > 0.0f); } // Copyright (c) 2012-2017 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/TestGrid.cc0000644000000000000000000003732313200122377015131 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #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(testValueConversion); CPPUNIT_TEST(testClipping); CPPUNIT_TEST_SUITE_END(); void testGridRegistry(); void testConstPtr(); void testGetGrid(); void testIsType(); void testTransform(); void testCopyGrid(); void testValueConversion(); void testClipping(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestGrid); //////////////////////////////////////// class ProxyTree: public openvdb::TreeBase { public: using ValueType = int; using BuildType = int; using LeafNodeType = void; using ValueAllCIter = void; using ValueAllIter = void; using ValueOffCIter = void; using ValueOffIter = void; using ValueOnCIter = void; using ValueOnIter = void; using TreeBasePtr = openvdb::TreeBase::Ptr; using Ptr = openvdb::SharedPtr; using ConstPtr = openvdb::SharedPtr; static const openvdb::Index DEPTH; static const ValueType backg; ProxyTree() {} ProxyTree(const ValueType&) {} ProxyTree(const ProxyTree&) = default; ~ProxyTree() override = default; static const openvdb::Name& treeType() { static const openvdb::Name s("proxy"); return s; } const openvdb::Name& type() const override { return treeType(); } openvdb::Name valueType() const override { return "proxy"; } const ValueType& background() const { return backg; } TreeBasePtr copy() const override { return TreeBasePtr(new ProxyTree(*this)); } void readTopology(std::istream& is, bool = false) override { is.seekg(0, std::ios::beg); } void writeTopology(std::ostream& os, bool = false) const override { os.seekp(0); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 void readBuffers(std::istream& is, const openvdb::CoordBBox&, bool /*saveFloatAsHalf*/=false) override { is.seekg(0); } void readNonresidentBuffers() const override {} #endif void readBuffers(std::istream& is, bool /*saveFloatAsHalf*/=false) override { is.seekg(0); } void writeBuffers(std::ostream& os, bool /*saveFloatAsHalf*/=false) const override { os.seekp(0, std::ios::beg); } bool empty() const { return true; } void clear() {} void prune(const ValueType& = 0) {} void clip(const openvdb::CoordBBox&) {} #if OPENVDB_ABI_VERSION_NUMBER >= 3 void clipUnallocatedNodes() override {} #endif #if OPENVDB_ABI_VERSION_NUMBER >= 4 openvdb::Index32 unallocatedLeafCount() const override { return 0; } #endif void getIndexRange(openvdb::CoordBBox&) const override {} bool evalLeafBoundingBox(openvdb::CoordBBox& bbox) const override { bbox.min() = bbox.max() = openvdb::Coord(0, 0, 0); return false; } bool evalActiveVoxelBoundingBox(openvdb::CoordBBox& bbox) const override { bbox.min() = bbox.max() = openvdb::Coord(0, 0, 0); return false; } bool evalActiveVoxelDim(openvdb::Coord& dim) const override { dim = openvdb::Coord(0, 0, 0); return false; } bool evalLeafDim(openvdb::Coord& dim) const override { dim = openvdb::Coord(0, 0, 0); return false; } openvdb::Index treeDepth() const override { return 0; } openvdb::Index leafCount() const override { return 0; } openvdb::Index nonLeafCount() const override { return 0; } openvdb::Index64 activeVoxelCount() const override { return 0UL; } openvdb::Index64 inactiveVoxelCount() const override { return 0UL; } openvdb::Index64 activeLeafVoxelCount() const override { return 0UL; } openvdb::Index64 inactiveLeafVoxelCount() const override { return 0UL; } #if OPENVDB_ABI_VERSION_NUMBER >= 3 openvdb::Index64 activeTileCount() const override { return 0UL; } #endif }; const openvdb::Index ProxyTree::DEPTH = 0; const ProxyTree::ValueType ProxyTree::backg = 0; using ProxyGrid = openvdb::Grid; //////////////////////////////////////// void TestGrid::testGridRegistry() { using namespace openvdb::tree; using TreeType = Tree, 2> > >; using GridType = openvdb::Grid; 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)); } void TestGrid::testValueConversion() { using namespace openvdb; const Coord c0(-10, 40, 845), c1(1, -50, -8), c2(1, 2, 3); const float fval0 = 3.25f, fval1 = 1.0f, fbkgd = 5.0f; // Create a FloatGrid. FloatGrid fgrid(fbkgd); FloatTree& ftree = fgrid.tree(); ftree.setValue(c0, fval0); ftree.setValue(c1, fval1); // Copy the FloatGrid to a DoubleGrid. DoubleGrid dgrid(fgrid); DoubleTree& dtree = dgrid.tree(); // Compare topology. CPPUNIT_ASSERT(dtree.hasSameTopology(ftree)); CPPUNIT_ASSERT(ftree.hasSameTopology(dtree)); // Compare values. ASSERT_DOUBLES_EXACTLY_EQUAL(double(fbkgd), dtree.getValue(c2)); ASSERT_DOUBLES_EXACTLY_EQUAL(double(fval0), dtree.getValue(c0)); ASSERT_DOUBLES_EXACTLY_EQUAL(double(fval1), dtree.getValue(c1)); // Copy the FloatGrid to a BoolGrid. BoolGrid bgrid(fgrid); BoolTree& btree = bgrid.tree(); // Compare topology. CPPUNIT_ASSERT(btree.hasSameTopology(ftree)); CPPUNIT_ASSERT(ftree.hasSameTopology(btree)); // Compare values. CPPUNIT_ASSERT_EQUAL(bool(fbkgd), btree.getValue(c2)); CPPUNIT_ASSERT_EQUAL(bool(fval0), btree.getValue(c0)); CPPUNIT_ASSERT_EQUAL(bool(fval1), btree.getValue(c1)); // Copy the FloatGrid to a Vec3SGrid. Vec3SGrid vgrid(fgrid); Vec3STree& vtree = vgrid.tree(); // Compare topology. CPPUNIT_ASSERT(vtree.hasSameTopology(ftree)); CPPUNIT_ASSERT(ftree.hasSameTopology(vtree)); // Compare values. CPPUNIT_ASSERT_EQUAL(Vec3s(fbkgd), vtree.getValue(c2)); CPPUNIT_ASSERT_EQUAL(Vec3s(fval0), vtree.getValue(c0)); CPPUNIT_ASSERT_EQUAL(Vec3s(fval1), vtree.getValue(c1)); // Verify that a Vec3SGrid can't be copied to an Int32Grid // (because an Int32 can't be constructed from a Vec3S). CPPUNIT_ASSERT_THROW(Int32Grid igrid2(vgrid), openvdb::TypeError); // Verify that a grid can't be converted to another type with a different // tree configuration. using DTree23 = tree::Tree3::Type; using DGrid23 = Grid; CPPUNIT_ASSERT_THROW(DGrid23 d23grid(fgrid), openvdb::TypeError); } //////////////////////////////////////// template void validateClippedGrid(const GridT& clipped, const typename GridT::ValueType& fg) { using namespace openvdb; using ValueT = typename GridT::ValueType; const CoordBBox bbox = clipped.evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(4, bbox.min().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.min().y()); CPPUNIT_ASSERT_EQUAL(-6, bbox.min().z()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().x()); CPPUNIT_ASSERT_EQUAL(4, bbox.max().y()); CPPUNIT_ASSERT_EQUAL(6, bbox.max().z()); CPPUNIT_ASSERT_EQUAL(6 + 6 + 1, int(clipped.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(2, int(clipped.constTree().leafCount())); typename GridT::ConstAccessor acc = clipped.getConstAccessor(); const ValueT bg = clipped.background(); Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = -10; x <= 10; ++x) { for (y = -10; y <= 10; ++y) { for (z = -10; z <= 10; ++z) { if (x == 4 && y == 4 && z >= -6 && z <= 6) { CPPUNIT_ASSERT_EQUAL(fg, acc.getValue(Coord(4, 4, z))); } else { CPPUNIT_ASSERT_EQUAL(bg, acc.getValue(Coord(x, y, z))); } } } } } // See also TestTools::testClipping() void TestGrid::testClipping() { using namespace openvdb; const BBoxd clipBox(Vec3d(4.0, 4.0, -6.0), Vec3d(4.9, 4.9, 6.0)); { const float fg = 5.f; FloatGrid cube(0.f); cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true); #if OPENVDB_ABI_VERSION_NUMBER <= 2 cube.tree().clip(cube.constTransform().worldToIndexNodeCentered(clipBox)); #else cube.clipGrid(clipBox); #endif validateClippedGrid(cube, fg); } { const bool fg = true; BoolGrid cube(false); cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true); #if OPENVDB_ABI_VERSION_NUMBER <= 2 cube.tree().clip(cube.constTransform().worldToIndexNodeCentered(clipBox)); #else cube.clipGrid(clipBox); #endif validateClippedGrid(cube, fg); } { const Vec3s fg(1.f, -2.f, 3.f); Vec3SGrid cube(Vec3s(0.f)); cube.fill(CoordBBox(Coord(-10), Coord(10)), /*value=*/fg, /*active=*/true); #if OPENVDB_ABI_VERSION_NUMBER <= 2 cube.tree().clip(cube.constTransform().worldToIndexNodeCentered(clipBox)); #else cube.clipGrid(clipBox); #endif validateClippedGrid(cube, fg); } /* {// Benchmark multi-threaded copy construction openvdb::util::CpuTimer timer; openvdb::initialize(); openvdb::io::File file("/usr/pic1/Data/OpenVDB/LevelSetModels/crawler.vdb"); file.open(); openvdb::GridBase::Ptr baseGrid = file.readGrid("ls_crawler"); file.close(); openvdb::FloatGrid::Ptr grid = openvdb::gridPtrCast(baseGrid); //grid->tree().print(); timer.start("\nCopy construction"); openvdb::FloatTree fTree(grid->tree()); timer.stop(); timer.start("\nBoolean topology copy construction"); openvdb::BoolTree bTree(grid->tree(), false, openvdb::TopologyCopy()); timer.stop(); timer.start("\nBoolean topology union"); bTree.topologyUnion(fTree); timer.stop(); //bTree.print(); } */ } // Copyright (c) 2012-2017 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.cc0000644000000000000000000005276613200122377015002 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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; { // 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;//fails because MaskT defaults to MaskGrid 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-2017 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/TestPointMask.cc0000644000000000000000000001263513200122377016150 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 using namespace openvdb; using namespace openvdb::points; class TestPointMask: public CppUnit::TestCase { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestPointMask); CPPUNIT_TEST(testMask); CPPUNIT_TEST_SUITE_END(); void testMask(); }; // class TestPointMask void TestPointMask::testMask() { std::vector positions = { {1, 1, 1}, {1, 5, 1}, {2, 1, 1}, {2, 2, 1}, }; const PointAttributeVector pointList(positions); const float voxelSize = 0.1f; openvdb::math::Transform::Ptr transform( openvdb::math::Transform::createLinearTransform(voxelSize)); tools::PointIndexGrid::Ptr pointIndexGrid = tools::createPointIndexGrid(pointList, *transform); PointDataGrid::Ptr points = createPointDataGrid(*pointIndexGrid, pointList, *transform); { // simple topology copy auto mask = convertPointsToMask(*points); CPPUNIT_ASSERT_EQUAL(points->tree().activeVoxelCount(), Index64(4)); CPPUNIT_ASSERT_EQUAL(mask->tree().activeVoxelCount(), Index64(4)); } { // mask grid instead of bool grid auto mask = convertPointsToMask(*points); CPPUNIT_ASSERT_EQUAL(points->tree().activeVoxelCount(), Index64(4)); CPPUNIT_ASSERT_EQUAL(mask->tree().activeVoxelCount(), Index64(4)); } { // identical transform auto mask = convertPointsToMask(*points, *transform); CPPUNIT_ASSERT_EQUAL(points->tree().activeVoxelCount(), Index64(4)); CPPUNIT_ASSERT_EQUAL(mask->tree().activeVoxelCount(), Index64(4)); } // assign point 3 to new group "test" appendGroup(points->tree(), "test"); std::vector groups{0,0,1,0}; setGroup(points->tree(), pointIndexGrid->tree(), groups, "test"); std::vector includeGroups{"test"}; std::vector excludeGroups; { // convert in turn "test" and not "test" auto mask = convertPointsToMask(*points, includeGroups, excludeGroups); CPPUNIT_ASSERT_EQUAL(points->tree().activeVoxelCount(), Index64(4)); CPPUNIT_ASSERT_EQUAL(mask->tree().activeVoxelCount(), Index64(1)); mask = convertPointsToMask(*points, excludeGroups, includeGroups); CPPUNIT_ASSERT_EQUAL(mask->tree().activeVoxelCount(), Index64(3)); } { // use a much larger voxel size that splits the points into two regions const float newVoxelSize(2); openvdb::math::Transform::Ptr newTransform( openvdb::math::Transform::createLinearTransform(newVoxelSize)); auto mask = convertPointsToMask(*points, *newTransform); CPPUNIT_ASSERT_EQUAL(mask->tree().activeVoxelCount(), Index64(2)); mask = convertPointsToMask(*points, *newTransform, includeGroups, excludeGroups); CPPUNIT_ASSERT_EQUAL(mask->tree().activeVoxelCount(), Index64(1)); mask = convertPointsToMask(*points, *newTransform, excludeGroups, includeGroups); CPPUNIT_ASSERT_EQUAL(mask->tree().activeVoxelCount(), Index64(2)); } } CPPUNIT_TEST_SUITE_REGISTRATION(TestPointMask); // Copyright (c) 2012-2017 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/TestIndexIterator.cc0000644000000000000000000002615213200122377017023 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 //for setprecision using namespace openvdb; using namespace openvdb::points; class TestIndexIterator: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestIndexIterator); CPPUNIT_TEST(testValueIndexIterator); CPPUNIT_TEST(testFilterIndexIterator); CPPUNIT_TEST(testProfile); CPPUNIT_TEST_SUITE_END(); void testValueIndexIterator(); void testFilterIndexIterator(); void testProfile(); }; // class TestIndexIterator CPPUNIT_TEST_SUITE_REGISTRATION(TestIndexIterator); //////////////////////////////////////// /// @brief Functionality similar to openvdb::util::CpuTimer except with prefix padding and no decimals. /// /// @code /// ProfileTimer timer("algorithm 1"); /// // code to be timed goes here /// timer.stop(); /// @endcode class ProfileTimer { public: /// @brief Prints message and starts timer. /// /// @note Should normally be followed by a call to stop() ProfileTimer(const std::string& msg) { (void)msg; #ifdef PROFILE // padd string to 50 characters std::string newMsg(msg); if (newMsg.size() < 50) newMsg.insert(newMsg.end(), 50 - newMsg.size(), ' '); std::cerr << newMsg << " ... "; #endif mT0 = tbb::tick_count::now(); } ~ProfileTimer() { this->stop(); } /// Return Time diference in milliseconds since construction or start was called. inline double delta() const { tbb::tick_count::interval_t dt = tbb::tick_count::now() - mT0; return 1000.0*dt.seconds(); } /// @brief Print time in milliseconds since construction or start was called. inline void stop() const { #ifdef PROFILE std::stringstream ss; ss << std::setw(6) << ::round(this->delta()); std::cerr << "completed in " << ss.str() << " ms\n"; #endif } private: tbb::tick_count mT0; };// ProfileTimer //////////////////////////////////////// void TestIndexIterator::testValueIndexIterator() { using namespace openvdb::tree; using LeafNode = LeafNode; using ValueOnIter = LeafNode::ValueOnIter; const int size = LeafNode::SIZE; { // one per voxel offset, all active LeafNode leafNode; for (int i = 0; i < size; i++) { leafNode.setValueOn(i, i+1); } ValueOnIter valueIter = leafNode.beginValueOn(); IndexIter::ValueIndexIter iter(valueIter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(iterCount(iter), Index64(size)); // check assignment operator auto iter2 = iter; CPPUNIT_ASSERT_EQUAL(iterCount(iter2), Index64(size)); ++iter; // check coord value Coord xyz; iter.getCoord(xyz); CPPUNIT_ASSERT_EQUAL(xyz, openvdb::Coord(0, 0, 1)); CPPUNIT_ASSERT_EQUAL(iter.getCoord(), openvdb::Coord(0, 0, 1)); // check iterators retrieval CPPUNIT_ASSERT_EQUAL(iter.valueIter().getCoord(), openvdb::Coord(0, 0, 1)); CPPUNIT_ASSERT_EQUAL(iter.end(), Index32(2)); ++iter; // check coord value iter.getCoord(xyz); CPPUNIT_ASSERT_EQUAL(xyz, openvdb::Coord(0, 1, 0)); CPPUNIT_ASSERT_EQUAL(iter.getCoord(), openvdb::Coord(0, 1, 0)); // check iterators retrieval CPPUNIT_ASSERT_EQUAL(iter.valueIter().getCoord(), openvdb::Coord(0, 1, 0)); CPPUNIT_ASSERT_EQUAL(iter.end(), Index32(3)); } { // one per even voxel offsets, only these active LeafNode leafNode; int offset = 0; for (int i = 0; i < size; i++) { if ((i % 2) == 0) { leafNode.setValueOn(i, ++offset); } else { leafNode.setValueOff(i, offset); } } { ValueOnIter valueIter = leafNode.beginValueOn(); IndexIter::ValueIndexIter iter(valueIter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(iterCount(iter), Index64(size/2)); } } { // one per odd voxel offsets, all active LeafNode leafNode; int offset = 0; for (int i = 0; i < size; i++) { if ((i % 2) == 1) { leafNode.setValueOn(i, offset++); } else { leafNode.setValueOn(i, offset); } } { ValueOnIter valueIter = leafNode.beginValueOn(); IndexIter::ValueIndexIter iter(valueIter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(iterCount(iter), Index64(3)); } } { // one per even voxel offsets, all active LeafNode leafNode; int offset = 0; for (int i = 0; i < size; i++) { if ((i % 2) == 0) { leafNode.setValueOn(i, offset++); } else { leafNode.setValueOn(i, offset); } } { ValueOnIter valueIter = leafNode.beginValueOn(); IndexIter::ValueIndexIter iter(valueIter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(iterCount(iter), Index64(size/2)); } } { // one per voxel offset, none active LeafNode leafNode; for (int i = 0; i < size; i++) { leafNode.setValueOff(i, i); } ValueOnIter valueIter = leafNode.beginValueOn(); IndexIter::ValueIndexIter iter(valueIter); CPPUNIT_ASSERT(!iter); CPPUNIT_ASSERT_EQUAL(iterCount(iter), Index64(0)); } } struct EvenIndexFilter { static bool initialized() { return true; } template bool valid(const IterT& iter) const { return ((*iter) % 2) == 0; } }; struct OddIndexFilter { static bool initialized() { return true; } OddIndexFilter() : mFilter() { } template bool valid(const IterT& iter) const { return !mFilter.valid(iter); } private: EvenIndexFilter mFilter; }; struct ConstantIter { ConstantIter(const int _value) : value(_value) { } int operator*() const { return value; } const int value; }; void TestIndexIterator::testFilterIndexIterator() { { // index iterator with even filter EvenIndexFilter filter; ValueVoxelCIter indexIter(0, 5); IndexIter iter(indexIter, filter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(*iter, Index32(0)); CPPUNIT_ASSERT(iter.next()); CPPUNIT_ASSERT_EQUAL(*iter, Index32(2)); CPPUNIT_ASSERT(iter.next()); CPPUNIT_ASSERT_EQUAL(*iter, Index32(4)); CPPUNIT_ASSERT(!iter.next()); CPPUNIT_ASSERT_EQUAL(iter.end(), Index32(5)); CPPUNIT_ASSERT_EQUAL(filter.valid(ConstantIter(1)), iter.filter().valid(ConstantIter(1))); CPPUNIT_ASSERT_EQUAL(filter.valid(ConstantIter(2)), iter.filter().valid(ConstantIter(2))); } { // index iterator with odd filter OddIndexFilter filter; ValueVoxelCIter indexIter(0, 5); IndexIter iter(indexIter, filter); CPPUNIT_ASSERT_EQUAL(*iter, Index32(1)); CPPUNIT_ASSERT(iter.next()); CPPUNIT_ASSERT_EQUAL(*iter, Index32(3)); CPPUNIT_ASSERT(!iter.next()); } } void TestIndexIterator::testProfile() { using namespace openvdb::util; using namespace openvdb::math; using namespace openvdb::tree; #ifdef PROFILE const int elements(1000 * 1000 * 1000); std::cerr << std::endl; #else const int elements(10 * 1000 * 1000); #endif { // for loop ProfileTimer timer("ForLoop: sum"); volatile int sum = 0; for (int i = 0; i < elements; i++) { sum += i; } CPPUNIT_ASSERT(sum); } { // index iterator ProfileTimer timer("IndexIter: sum"); volatile int sum = 0; ValueVoxelCIter iter(0, elements); for (; iter; ++iter) { sum += *iter; } CPPUNIT_ASSERT(sum); } using LeafNode = LeafNode; LeafNode leafNode; const int size = LeafNode::SIZE; for (int i = 0; i < size - 1; i++) { leafNode.setValueOn(i, (elements / size) * i); } leafNode.setValueOn(size - 1, elements); { // manual value iteration ProfileTimer timer("ValueIteratorManual: sum"); volatile int sum = 0; auto indexIter(leafNode.cbeginValueOn()); int offset = 0; for (; indexIter; ++indexIter) { int start = offset > 0 ? leafNode.getValue(offset - 1) : 0; int end = leafNode.getValue(offset); for (int i = start; i < end; i++) { sum += i; } offset++; } CPPUNIT_ASSERT(sum); } { // value on iterator (all on) ProfileTimer timer("ValueIndexIter: sum"); volatile int sum = 0; auto indexIter(leafNode.cbeginValueAll()); IndexIter::ValueIndexIter iter(indexIter); for (; iter; ++iter) { sum += *iter; } CPPUNIT_ASSERT(sum); } } // Copyright (c) 2012-2017 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/TestPoissonSolver.cc0000644000000000000000000005110313200122377017061 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 unittest/TestPoissonSolver.cc /// @authors D.J. Hill, Peter Cucka #include #include #include //#include // for math::isApproxEqual() #include // for JacobiPreconditioner #include // for csgDifference/Union/Intersection #include // for tools::createLevelSetSphere() #include // for tools::sdfToFogVolume() #include // for createLevelSetBox() #include // for tools::erodeVoxels() #include #include // for boost::math::constants::pi #include class TestPoissonSolver: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestPoissonSolver); CPPUNIT_TEST(testIndexTree); CPPUNIT_TEST(testTreeToVectorToTree); CPPUNIT_TEST(testLaplacian); CPPUNIT_TEST(testSolve); CPPUNIT_TEST(testSolveWithBoundaryConditions); CPPUNIT_TEST(testSolveWithSegmentedDomain); CPPUNIT_TEST_SUITE_END(); void testIndexTree(); void testTreeToVectorToTree(); void testLaplacian(); void testSolve(); void testSolveWithBoundaryConditions(); void testSolveWithSegmentedDomain(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPoissonSolver); //////////////////////////////////////// void TestPoissonSolver::testIndexTree() { using namespace openvdb; using tools::poisson::VIndex; using VIdxTree = FloatTree::ValueConverter::Type; using LeafNodeType = VIdxTree::LeafNodeType; VIdxTree tree; /// @todo populate tree tree::LeafManager leafManager(tree); VIndex testOffset = 0; for (size_t n = 0, N = leafManager.leafCount(); n < N; ++n) { const LeafNodeType& leaf = leafManager.leaf(n); for (LeafNodeType::ValueOnCIter it = leaf.cbeginValueOn(); it; ++it, testOffset++) { CPPUNIT_ASSERT_EQUAL(testOffset, *it); } } //if (testOffset != VIndex(tree.activeVoxelCount())) { // std::cout << "--Testing offsetmap - " // << testOffset<<" != " // << tree.activeVoxelCount() // << " has active tile count " // << tree.activeTileCount()<::Type; FloatGrid::Ptr sphere = tools::createLevelSetSphere( /*radius=*/10.f, /*center=*/Vec3f(0.f), /*voxelSize=*/0.25f); tools::sdfToFogVolume(*sphere); FloatTree& inputTree = sphere->tree(); const Index64 numVoxels = inputTree.activeVoxelCount(); // Generate an index tree. VIdxTree::Ptr indexTree = tools::poisson::createIndexTree(inputTree); CPPUNIT_ASSERT(bool(indexTree)); // Copy the values of the active voxels of the tree into a vector. math::pcg::VectorS::Ptr vec = tools::poisson::createVectorFromTree(inputTree, *indexTree); CPPUNIT_ASSERT_EQUAL(math::pcg::SizeType(numVoxels), vec->size()); { // Convert the vector back to a tree. FloatTree::Ptr inputTreeCopy = tools::poisson::createTreeFromVector( *vec, *indexTree, /*bg=*/0.f); // Check that voxel values were preserved. FloatGrid::ConstAccessor inputAcc = sphere->getConstAccessor(); for (FloatTree::ValueOnCIter it = inputTreeCopy->cbeginValueOn(); it; ++it) { const Coord ijk = it.getCoord(); //if (!math::isApproxEqual(*it, inputTree.getValue(ijk))) { // std::cout << " value error " << *it << " " // << inputTree.getValue(ijk) << std::endl; //} CPPUNIT_ASSERT_DOUBLES_EQUAL(inputAcc.getValue(ijk), *it, /*tolerance=*/1.0e-6); } } } void TestPoissonSolver::testLaplacian() { using namespace openvdb; using tools::poisson::VIndex; using VIdxTree = FloatTree::ValueConverter::Type; // For two different problem sizes, N = 8 and N = 20... for (int N = 8; N <= 20; N += 12) { // Construct an N x N x N volume in which the value of voxel (i, j, k) // is sin(i) * sin(j) * sin(k), using a voxel spacing of pi / N. const double delta = boost::math::constants::pi() / N; FloatTree inputTree(/*background=*/0.f); Coord ijk(0); Int32 &i = ijk[0], &j = ijk[1], &k = ijk[2]; for (i = 1; i < N; ++i) { for (j = 1; j < N; ++j) { for (k = 1; k < N; ++k) { inputTree.setValue(ijk, static_cast( std::sin(delta * i) * std::sin(delta * j) * std::sin(delta * k))); } } } const Index64 numVoxels = inputTree.activeVoxelCount(); // Generate an index tree. VIdxTree::Ptr indexTree = tools::poisson::createIndexTree(inputTree); CPPUNIT_ASSERT(bool(indexTree)); // Copy the values of the active voxels of the tree into a vector. math::pcg::VectorS::Ptr source = tools::poisson::createVectorFromTree(inputTree, *indexTree); CPPUNIT_ASSERT_EQUAL(math::pcg::SizeType(numVoxels), source->size()); // Create a mask of the interior voxels of the source tree. BoolTree interiorMask(/*background=*/false); interiorMask.fill(CoordBBox(Coord(2), Coord(N-2)), /*value=*/true, /*active=*/true); // Compute the Laplacian of the source: // D^2 sin(i) * sin(j) * sin(k) = -3 sin(i) * sin(j) * sin(k) tools::poisson::LaplacianMatrix::Ptr laplacian = tools::poisson::createISLaplacian(*indexTree, interiorMask, /*staggered=*/true); laplacian->scale(1.0 / (delta * delta)); // account for voxel spacing CPPUNIT_ASSERT_EQUAL(math::pcg::SizeType(numVoxels), laplacian->size()); math::pcg::VectorS result(source->size()); laplacian->vectorMultiply(*source, result); // Dividing the result by the source should produce a vector of uniform value -3. // Due to finite differencing, the actual ratio will be somewhat different, though. const math::pcg::VectorS& src = *source; const float expected = // compute the expected ratio using one of the corner voxels float((3.0 * src[1] - 6.0 * src[0]) / (delta * delta * src[0])); for (math::pcg::SizeType n = 0; n < result.size(); ++n) { result[n] /= src[n]; CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, result[n], /*tolerance=*/1.0e-4); } } } void TestPoissonSolver::testSolve() { using namespace openvdb; FloatGrid::Ptr sphere = tools::createLevelSetSphere( /*radius=*/10.f, /*center=*/Vec3f(0.f), /*voxelSize=*/0.25f); tools::sdfToFogVolume(*sphere); math::pcg::State result = math::pcg::terminationDefaults(); result.iterations = 100; result.relativeError = result.absoluteError = 1.0e-4; FloatTree::Ptr outTree = tools::poisson::solve(sphere->tree(), result); CPPUNIT_ASSERT(result.success); CPPUNIT_ASSERT(result.iterations < 60); } //////////////////////////////////////// namespace { struct BoundaryOp { void operator()(const openvdb::Coord& ijk, const openvdb::Coord& neighbor, double& source, double& diagonal) const { if (neighbor.x() == ijk.x() && neighbor.z() == ijk.z()) { // Workaround for spurious GCC 4.8 -Wstrict-overflow warning: const openvdb::Coord::ValueType dy = (ijk.y() - neighbor.y()); if (dy > 0) source -= 1.0; else diagonal -= 1.0; } } }; template void doTestSolveWithBoundaryConditions() { using namespace openvdb; using ValueType = typename TreeType::ValueType; // Solve for the pressure in a cubic tank of liquid that is open at the top. // Boundary conditions are P = 0 at the top, dP/dy = -1 at the bottom // and dP/dx = 0 at the sides. // // P = 0 // +------+ (N,-1,N) // /| /| // (0,-1,0) +------+ | // | | | | dP/dx = 0 // dP/dx = 0 | +----|-+ // |/ |/ // (0,-N-1,0) +------+ (N,-N-1,0) // dP/dy = -1 const int N = 9; const ValueType zero = zeroVal(); const double epsilon = math::Delta::value(); TreeType source(/*background=*/zero); source.fill(CoordBBox(Coord(0, -N-1, 0), Coord(N, -1, N)), /*value=*/zero); math::pcg::State state = math::pcg::terminationDefaults(); state.iterations = 100; state.relativeError = state.absoluteError = epsilon; util::NullInterrupter interrupter; typename TreeType::Ptr solution = tools::poisson::solveWithBoundaryConditions( source, BoundaryOp(), state, interrupter, /*staggered=*/true); CPPUNIT_ASSERT(state.success); CPPUNIT_ASSERT(state.iterations < 60); // Verify that P = -y throughout the solution space. for (typename TreeType::ValueOnCIter it = solution->cbeginValueOn(); it; ++it) { CPPUNIT_ASSERT_DOUBLES_EQUAL( double(-it.getCoord().y()), double(*it), /*tolerance=*/10.0 * epsilon); } } } // unnamed namespace void TestPoissonSolver::testSolveWithBoundaryConditions() { doTestSolveWithBoundaryConditions(); doTestSolveWithBoundaryConditions(); } namespace { openvdb::FloatGrid::Ptr newCubeLS( const int outerLength, // in voxels const int innerLength, // in voxels const openvdb::Vec3I& centerIS, // in index space const float dx, // grid spacing bool openTop) { using namespace openvdb; using BBox = math::BBox; // World space dimensions and center for this box const float outerWS = dx * float(outerLength); const float innerWS = dx * float(innerLength); Vec3f centerWS(centerIS); centerWS *= dx; // Construct world space bounding boxes BBox outerBBox( Vec3f(-outerWS / 2, -outerWS / 2, -outerWS / 2), Vec3f( outerWS / 2, outerWS / 2, outerWS / 2)); BBox innerBBox; if (openTop) { innerBBox = BBox( Vec3f(-innerWS / 2, -innerWS / 2, -innerWS / 2), Vec3f( innerWS / 2, innerWS / 2, outerWS)); } else { innerBBox = BBox( Vec3f(-innerWS / 2, -innerWS / 2, -innerWS / 2), Vec3f( innerWS / 2, innerWS / 2, innerWS / 2)); } outerBBox.translate(centerWS); innerBBox.translate(centerWS); math::Transform::Ptr xform = math::Transform::createLinearTransform(dx); FloatGrid::Ptr cubeLS = tools::createLevelSetBox(outerBBox, *xform); FloatGrid::Ptr inside = tools::createLevelSetBox(innerBBox, *xform); tools::csgDifference(*cubeLS, *inside); return cubeLS; } class LSBoundaryOp { public: LSBoundaryOp(const openvdb::FloatTree& lsTree): mLS(&lsTree) {} LSBoundaryOp(const LSBoundaryOp& other): mLS(other.mLS) {} void operator()(const openvdb::Coord& ijk, const openvdb::Coord& neighbor, double& source, double& diagonal) const { // Doing nothing is equivalent to imposing dP/dn = 0 boundary condition if (neighbor.x() == ijk.x() && neighbor.y() == ijk.y()) { // on top or bottom if (mLS->getValue(neighbor) <= 0.f) { // closed boundary source -= 1.0; } else { // open boundary diagonal -= 1.0; } } } private: const openvdb::FloatTree* mLS; }; } // unnamed namespace void TestPoissonSolver::testSolveWithSegmentedDomain() { // In fluid simulations, incompressibility is enforced by the pressure, which is // computed as a solution of a Poisson equation. Often, procedural animation // of objects (e.g., characters) interacting with liquid will result in boundary // conditions that describe multiple disjoint regions: regions of free surface flow // and regions of trapped fluid. It is this second type of region for which // there may be no consistent pressure (e.g., a shrinking watertight region // filled with incompressible liquid). // // This unit test demonstrates how to use a level set and topological tools // to separate the well-posed problem of a liquid with a free surface // from the possibly ill-posed problem of fully enclosed liquid regions. // // For simplicity's sake, the physical boundaries are idealized as three // non-overlapping cubes, one with an open top and two that are fully closed. // All three contain incompressible liquid (x), and one of the closed cubes // will be partially filled so that two of the liquid regions have a free surface // (Dirichlet boundary condition on one side) while the totally filled cube // would have no free surface (Neumann boundary conditions on all sides). // ________________ ________________ // __ __ | __________ | | __________ | // | |x x x x x | | | | | | | |x x x x x | | // | |x x x x x | | | |x x x x x | | | |x x x x x | | // | |x x x x x | | | |x x x x x | | | |x x x x x | | // | —————————— | | —————————— | | —————————— | // |________________| |________________| |________________| // // The first two regions are clearly well-posed, while the third region // may have no solution (or multiple solutions). // -D.J.Hill using namespace openvdb; using PreconditionerType = math::pcg::IncompleteCholeskyPreconditioner; // Grid spacing const float dx = 0.05f; // Construct the solid boundaries in a single grid. FloatGrid::Ptr solidBoundary; { // Create three non-overlapping cubes. const int outerDim = 41; const int innerDim = 31; FloatGrid::Ptr openDomain = newCubeLS(outerDim, innerDim, /*ctr=*/Vec3I(0, 0, 0), dx, /*open=*/true), closedDomain0 = newCubeLS(outerDim, innerDim, /*ctr=*/Vec3I(60, 0, 0), dx, false), closedDomain1 = newCubeLS(outerDim, innerDim, /*ctr=*/Vec3I(120, 0, 0), dx, false); // Union all three cubes into one grid. tools::csgUnion(*openDomain, *closedDomain0); tools::csgUnion(*openDomain, *closedDomain1); // Strictly speaking the solidBoundary level set should be rebuilt // (with tools::levelSetRebuild()) after the csgUnions to insure a proper // signed distance field, but we will forgo the rebuild in this example. solidBoundary = openDomain; } // Generate the source for the Poisson solver. // For a liquid simulation this will be the divergence of the velocity field // and will coincide with the liquid location. // // We activate by hand cells in distinct solution regions. FloatTree source(/*background=*/0.f); // The source is active in the union of the following "liquid" regions: // Fill the open box. const int N = 15; CoordBBox liquidInOpenDomain(Coord(-N, -N, -N), Coord(N, N, N)); source.fill(liquidInOpenDomain, 0.f); // Totally fill closed box 0. CoordBBox liquidInClosedDomain0(Coord(-N, -N, -N), Coord(N, N, N)); liquidInClosedDomain0.translate(Coord(60, 0, 0)); source.fill(liquidInClosedDomain0, 0.f); // Half fill closed box 1. CoordBBox liquidInClosedDomain1(Coord(-N, -N, -N), Coord(N, N, 0)); liquidInClosedDomain1.translate(Coord(120, 0, 0)); source.fill(liquidInClosedDomain1, 0.f); // Compute the number of voxels in the well-posed region of the source. const Index64 expectedWellPosedVolume = liquidInOpenDomain.volume() + liquidInClosedDomain1.volume(); // Generate a mask that defines the solution domain. // Inactive values of the source map to false and active values map to true. const BoolTree totalSourceDomain(source, /*inactive=*/false, /*active=*/true, TopologyCopy()); // Extract the "interior regions" from the solid boundary. // The result will correspond to the the walls of the boxes unioned with inside of the full box. const BoolTree::ConstPtr interiorMask = tools::extractEnclosedRegion( solidBoundary->tree(), /*isovalue=*/float(0), &totalSourceDomain); // Identify the well-posed part of the problem. BoolTree wellPosedDomain(source, /*inactive=*/false, /*active=*/true, TopologyCopy()); wellPosedDomain.topologyDifference(*interiorMask); CPPUNIT_ASSERT_EQUAL(expectedWellPosedVolume, wellPosedDomain.activeVoxelCount()); // Solve the well-posed Poisson equation. const double epsilon = math::Delta::value(); math::pcg::State state = math::pcg::terminationDefaults(); state.iterations = 200; state.relativeError = state.absoluteError = epsilon; util::NullInterrupter interrupter; // Define boundary conditions that are consistent with solution = 0 // at the liquid/air boundary and with a linear response with depth. LSBoundaryOp boundaryOp(solidBoundary->tree()); // Compute the solution FloatTree::Ptr wellPosedSolutionP = tools::poisson::solveWithBoundaryConditionsAndPreconditioner( source, wellPosedDomain, boundaryOp, state, interrupter, /*staggered=*/true); CPPUNIT_ASSERT_EQUAL(expectedWellPosedVolume, wellPosedSolutionP->activeVoxelCount()); CPPUNIT_ASSERT(state.success); CPPUNIT_ASSERT(state.iterations < 68); // Verify that the solution is linear with depth. for (FloatTree::ValueOnCIter it = wellPosedSolutionP->cbeginValueOn(); it; ++it) { Index32 depth; if (liquidInOpenDomain.isInside(it.getCoord())) { depth = 1 + liquidInOpenDomain.max().z() - it.getCoord().z(); } else { depth = 1 + liquidInClosedDomain1.max().z() - it.getCoord().z(); } CPPUNIT_ASSERT_DOUBLES_EQUAL(double(depth), double(*it), /*tolerance=*/10.0 * epsilon); } #if 0 // Optionally, one could attempt to compute the solution in the enclosed regions. { // Identify the potentially ill-posed part of the problem. BoolTree illPosedDomain(source, /*inactive=*/false, /*active=*/true, TopologyCopy()); illPosedDomain.topologyIntersection(source); // Solve the Poisson equation in the two unconnected regions. FloatTree::Ptr illPosedSoln = tools::poisson::solveWithBoundaryConditionsAndPreconditioner( source, illPosedDomain, LSBoundaryOp(*solidBoundary->tree()), state, interrupter, /*staggered=*/true); } #endif } // Copyright (c) 2012-2017 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.cc0000644000000000000000000002204313200122377015147 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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.23f, 2.34f, 3.45f, 4.56f); 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.23f, 2.34f, 3.45f, 4.56f }; 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-6f; Quat q1(1.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); Vec3s v(1, 2, 3); v.normalize(); float a = float(M_PI / 4.f); 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.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); 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.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); 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.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); 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-6f; Quat q1(1.0f, 2.0f, 3.0f, 4.0f); Quat q2(1.2f, 2.3f, 3.4f, 4.5f); 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-2017 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.cc0000644000000000000000000001016313200122377015735 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000001061713200122377016501 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000005462013200122377017037 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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::createLevelSetSphere() #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.f; 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.f; } } // 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. int 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, numOn); } { Tree323f::ValueOnCIter iter = tree.cbeginValueOn(); CPPUNIT_ASSERT(iter.test()); // Read all active tile and voxel values through a const value iterator. int 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, 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. int 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, 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); } { // FX-7884 regression test using namespace openvdb; const float radius = 4.3f, voxelSize = 0.1f, width = 2.0f; const Vec3f center(15.8f, 13.2f, 16.7f); FloatGrid::Ptr sphereGrid = tools::createLevelSetSphere( radius, center, voxelSize, width); const FloatTree& sphereTree = sphereGrid->tree(); FloatGrid::ValueOffIter iter = sphereGrid->beginValueOff(); iter.setMaxDepth(2); for ( ; iter; ++iter) { const Coord ijk = iter.getCoord(); ASSERT_DOUBLES_EXACTLY_EQUAL(sphereTree.getValue(ijk), *iter); } } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000004373313200122377016221 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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(testBoundingBoxes); CPPUNIT_TEST_SUITE_END(); void testLinearTransform(); void testTransformEquality(); void testBackwardCompatibility(); void testIsIdentity(); void testBoundingBoxes(); }; 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(reinterpret_cast(&tmpMin), sizeof(Coord::ValueType) * 3); ss.write(reinterpret_cast(&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(reinterpret_cast(&tmpMin), sizeof(Coord::ValueType) * 3); ss.write(reinterpret_cast(&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()); } void TestTransform::testBoundingBoxes() { using namespace openvdb; { math::Transform::ConstPtr t = math::Transform::createLinearTransform(0.5); const BBoxd bbox(Vec3d(-8.0), Vec3d(16.0)); BBoxd xBBox = t->indexToWorld(bbox); CPPUNIT_ASSERT_EQUAL(Vec3d(-4.0), xBBox.min()); CPPUNIT_ASSERT_EQUAL(Vec3d(8.0), xBBox.max()); xBBox = t->worldToIndex(xBBox); CPPUNIT_ASSERT_EQUAL(bbox.min(), xBBox.min()); CPPUNIT_ASSERT_EQUAL(bbox.max(), xBBox.max()); } { const double PI = std::atan(1.0) * 4.0, SQRT2 = std::sqrt(2.0); math::Transform::Ptr t = math::Transform::createLinearTransform(1.0); t->preRotate(PI / 4.0, math::Z_AXIS); const BBoxd bbox(Vec3d(-10.0), Vec3d(10.0)); BBoxd xBBox = t->indexToWorld(bbox); // expand in x and y by sqrt(2) CPPUNIT_ASSERT(Vec3d(-10.0 * SQRT2, -10.0 * SQRT2, -10.0).eq(xBBox.min())); CPPUNIT_ASSERT(Vec3d(10.0 * SQRT2, 10.0 * SQRT2, 10.0).eq(xBBox.max())); xBBox = t->worldToIndex(xBBox); // expand again in x and y by sqrt(2) CPPUNIT_ASSERT(Vec3d(-20.0, -20.0, -10.0).eq(xBBox.min())); CPPUNIT_ASSERT(Vec3d(20.0, 20.0, 10.0).eq(xBBox.max())); } /// @todo frustum transform } //////////////////////////////////////// /// @todo Test the new frustum transform. /* void TestTransform::testNonlinearTransform() { using namespace openvdb; double TOL = 1e-7; } */ // Copyright (c) 2012-2017 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/TestNodeManager.cc0000644000000000000000000001620213200122377016415 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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() #include class TestNodeManager: public CppUnit::TestFixture { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestNodeManager); CPPUNIT_TEST(testAll); CPPUNIT_TEST_SUITE_END(); void testAll(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestNodeManager); namespace { template struct NodeCountOp { NodeCountOp() : nodeCount(TreeT::DEPTH, 0), totalCount(0) { } NodeCountOp(const NodeCountOp&, tbb::split) : nodeCount(TreeT::DEPTH, 0), totalCount(0) { } void join(const NodeCountOp& other) { for (size_t i = 0; i < nodeCount.size(); ++i) { nodeCount[i] += other.nodeCount[i]; } totalCount += other.totalCount; } // do nothing for the root node void operator()(const typename TreeT::RootNodeType&) { } // count the internal and leaf nodes template void operator()(const NodeT&) { ++(nodeCount[NodeT::LEVEL]); ++totalCount; } std::vector nodeCount; openvdb::Index64 totalCount; };// NodeCountOp }//unnamed namespace void TestNodeManager::testAll() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3f; using openvdb::Index64; using openvdb::FloatGrid; using openvdb::FloatTree; const Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f; const int dim = 128, half_width = 5; const float voxel_size = 1.0f/dim; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/half_width*voxel_size); FloatTree& tree = grid->tree(); grid->setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/voxel_size)); unittest_util::makeSphere(Coord(dim), center, radius, *grid, unittest_util::SPHERE_SPARSE_NARROW_BAND); CPPUNIT_ASSERT_EQUAL(4, int(FloatTree::DEPTH)); CPPUNIT_ASSERT_EQUAL(3, int(openvdb::tree::NodeManager::LEVELS)); std::vector nodeCount; for (openvdb::Index i=0; i #include #include #include #include #include #include "util.h" // for unittest_util::makeSphere() #include // for std::numeric_limits #include // for boost::math::isnan() and isinf() #define TEST_CSG_VERBOSE 0 #if TEST_CSG_VERBOSE #include #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(testCompDiv); CPPUNIT_TEST(testCompDivByZero); CPPUNIT_TEST(testCompReplace); CPPUNIT_TEST(testBoolTree); #ifdef DWA_OPENVDB CPPUNIT_TEST(testCsg); #endif CPPUNIT_TEST(testCsgCopy); CPPUNIT_TEST(testCompActiveLeafVoxels); CPPUNIT_TEST_SUITE_END(); void testCombine(); void testCombine2(); void testCompMax(); void testCompMin(); void testCompSum(); void testCompProd(); void testCompDiv(); void testCompDivByZero(); void testCompReplace(); void testBoolTree(); void testCsg(); void testCsgCopy(); void testCompActiveLeafVoxels(); 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&); }; 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); }\ template void compDiv(TreeT& a, TreeT& b) { openvdb::tools::compDiv(a, b); }\ inline float orderf(float a, float b) { return a + 100 * b; } inline float maxf(float a, float b) { return std::max(a, b); } inline float minf(float a, float b) { return std::min(a, b); } inline float sumf(float a, float b) { return a + b; } inline float mulf(float a, float b) { return a * b; } inline float divf(float a, float b) { return a / b; } inline openvdb::Vec3f orderv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a+100*b; } inline 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))); } inline 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))); } inline openvdb::Vec3f sumv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a + b; } inline openvdb::Vec3f mulv(const openvdb::Vec3f& a, const openvdb::Vec3f& b) { return a * b; } inline openvdb::Vec3f divv(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::testCompDiv() { testComp(Local::compDiv, Local::divf); testComp(Local::compDiv, Local::divv); } void TestTreeCombine::testCompDivByZero() { const openvdb::Coord c0(0), c1(1), c2(2), c3(3), c4(4); // Verify that integer-valued grids behave well w.r.t. division by zero. { const openvdb::Int32 inf = std::numeric_limits::max(); openvdb::Int32Tree a(/*background=*/1), b(0); a.setValueOn(c0); a.setValueOn(c1); a.setValueOn(c2, -1); a.setValueOn(c3, -1); a.setValueOn(c4, 0); b.setValueOn(c1); b.setValueOn(c3); openvdb::tools::compDiv(a, b); CPPUNIT_ASSERT_EQUAL( inf, a.getValue(c0)); // 1 / 0 CPPUNIT_ASSERT_EQUAL( inf, a.getValue(c1)); // 1 / 0 CPPUNIT_ASSERT_EQUAL(-inf, a.getValue(c2)); // -1 / 0 CPPUNIT_ASSERT_EQUAL(-inf, a.getValue(c3)); // -1 / 0 CPPUNIT_ASSERT_EQUAL( 0, a.getValue(c4)); // 0 / 0 } { const openvdb::Index32 zero(0), inf = std::numeric_limits::max(); openvdb::UInt32Tree a(/*background=*/1), b(0); a.setValueOn(c0); a.setValueOn(c1); a.setValueOn(c2, zero); b.setValueOn(c1); openvdb::tools::compDiv(a, b); CPPUNIT_ASSERT_EQUAL( inf, a.getValue(c0)); // 1 / 0 CPPUNIT_ASSERT_EQUAL( inf, a.getValue(c1)); // 1 / 0 CPPUNIT_ASSERT_EQUAL(zero, a.getValue(c2)); // 0 / 0 } // Verify that non-integer-valued grids don't use integer division semantics. { openvdb::FloatTree a(/*background=*/1.0), b(0.0); a.setValueOn(c0); a.setValueOn(c1); a.setValueOn(c2, -1.0); a.setValueOn(c3, -1.0); a.setValueOn(c4, 0.0); b.setValueOn(c1); b.setValueOn(c3); openvdb::tools::compDiv(a, b); CPPUNIT_ASSERT(boost::math::isinf(a.getValue(c0))); // 1 / 0 CPPUNIT_ASSERT(boost::math::isinf(a.getValue(c1))); // 1 / 0 CPPUNIT_ASSERT(boost::math::isinf(a.getValue(c2))); // -1 / 0 CPPUNIT_ASSERT(boost::math::isinf(a.getValue(c3))); // -1 / 0 CPPUNIT_ASSERT(boost::math::isnan(a.getValue(c4))); // 0 / 0 } } 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::Coord; using openvdb::Vec3d; struct Local { static void floatAverage(const float& a, const float& b, float& result) { result = 0.5f * (a + b); } static void vec3dAverage(const Vec3d& a, const Vec3d& b, Vec3d& result) { result = 0.5 * (a + b); } static void vec3dFloatMultiply(const Vec3d& a, const float& b, Vec3d& result) { result = a * b; } static void vec3dBoolMultiply(const Vec3d& a, const bool& b, Vec3d& result) { result = a * b; } }; const Coord c0(0, 0, 0), c1(0, 0, 1), c2(0, 1, 0), c3(1, 0, 0), c4(1000, 1, 2); openvdb::FloatTree aFloatTree(/*bg=*/1.0), bFloatTree(5.0), outFloatTree(1.0); aFloatTree.setValue(c0, 3.0); aFloatTree.setValue(c1, 3.0); bFloatTree.setValue(c0, -1.0); bFloatTree.setValue(c2, 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(c0), tolerance); // Average of set value 3 and bg value 5 CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, outFloatTree.getValue(c1), tolerance); // Average of bg value 1 and set value 4 CPPUNIT_ASSERT_DOUBLES_EQUAL(2.5, outFloatTree.getValue(c2), tolerance); // Average of bg value 1 and bg value 5 CPPUNIT_ASSERT(outFloatTree.isValueOff(c3)); CPPUNIT_ASSERT(outFloatTree.isValueOff(c4)); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, outFloatTree.getValue(c3), tolerance); CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, outFloatTree.getValue(c4), tolerance); // As above, but combining vector grids: const Vec3d zero(0), one(1), two(2), three(3), four(4), five(5); openvdb::Vec3DTree aVecTree(/*bg=*/one), bVecTree(five), outVecTree(one); aVecTree.setValue(c0, three); aVecTree.setValue(c1, three); bVecTree.setValue(c0, -1.0 * one); bVecTree.setValue(c2, four); outVecTree.combine2(aVecTree, bVecTree, Local::vec3dAverage); // Average of set value 3 and set value -1 CPPUNIT_ASSERT_EQUAL(one, outVecTree.getValue(c0)); // Average of set value 3 and bg value 5 CPPUNIT_ASSERT_EQUAL(four, outVecTree.getValue(c1)); // Average of bg value 1 and set value 4 CPPUNIT_ASSERT_EQUAL(2.5 * one, outVecTree.getValue(c2)); // Average of bg value 1 and bg value 5 CPPUNIT_ASSERT(outVecTree.isValueOff(c3)); CPPUNIT_ASSERT(outVecTree.isValueOff(c4)); CPPUNIT_ASSERT_EQUAL(three, outVecTree.getValue(c3)); CPPUNIT_ASSERT_EQUAL(three, outVecTree.getValue(c4)); // Multiply the vector tree by the scalar tree. { openvdb::Vec3DTree vecTree(one); vecTree.combine2(outVecTree, outFloatTree, Local::vec3dFloatMultiply); // Product of set value (1, 1, 1) and set value 1 CPPUNIT_ASSERT(vecTree.isValueOn(c0)); CPPUNIT_ASSERT_EQUAL(one, vecTree.getValue(c0)); // Product of set value (4, 4, 4) and set value 4 CPPUNIT_ASSERT(vecTree.isValueOn(c1)); CPPUNIT_ASSERT_EQUAL(4 * 4 * one, vecTree.getValue(c1)); // Product of set value (2.5, 2.5, 2.5) and set value 2.5 CPPUNIT_ASSERT(vecTree.isValueOn(c2)); CPPUNIT_ASSERT_EQUAL(2.5 * 2.5 * one, vecTree.getValue(c2)); // Product of bg value (3, 3, 3) and bg value 3 CPPUNIT_ASSERT(vecTree.isValueOff(c3)); CPPUNIT_ASSERT(vecTree.isValueOff(c4)); CPPUNIT_ASSERT_EQUAL(3 * 3 * one, vecTree.getValue(c3)); CPPUNIT_ASSERT_EQUAL(3 * 3 * one, vecTree.getValue(c4)); } // Multiply the vector tree by a boolean tree. { openvdb::BoolTree boolTree(0); boolTree.setValue(c0, true); boolTree.setValue(c1, false); boolTree.setValue(c2, true); openvdb::Vec3DTree vecTree(one); vecTree.combine2(outVecTree, boolTree, Local::vec3dBoolMultiply); // Product of set value (1, 1, 1) and set value 1 CPPUNIT_ASSERT(vecTree.isValueOn(c0)); CPPUNIT_ASSERT_EQUAL(one, vecTree.getValue(c0)); // Product of set value (4, 4, 4) and set value 0 CPPUNIT_ASSERT(vecTree.isValueOn(c1)); CPPUNIT_ASSERT_EQUAL(zero, vecTree.getValue(c1)); // Product of set value (2.5, 2.5, 2.5) and set value 1 CPPUNIT_ASSERT(vecTree.isValueOn(c2)); CPPUNIT_ASSERT_EQUAL(2.5 * one, vecTree.getValue(c2)); // Product of bg value (3, 3, 3) and bg value 0 CPPUNIT_ASSERT(vecTree.isValueOff(c3)); CPPUNIT_ASSERT(vecTree.isValueOff(c4)); CPPUNIT_ASSERT_EQUAL(zero, vecTree.getValue(c3)); CPPUNIT_ASSERT_EQUAL(zero, vecTree.getValue(c4)); } // Verify that a vector tree can't be combined into a scalar tree // (although the reverse is allowed). { struct Local2 { static void f(const float& a, const Vec3d&, float& result) { result = a; } }; openvdb::FloatTree floatTree(5.0), outTree; openvdb::Vec3DTree vecTree(one); CPPUNIT_ASSERT_THROW(outTree.combine2(floatTree, vecTree, Local2::f), openvdb::TypeError); } } //////////////////////////////////////// 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 openvdb::util::CpuTimer 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.delta() << " 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, "small_union_out.vdb2"); refTree = Local::readFile(testDir + "large_union.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorUnion); //Local::writeFile(outTree, "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, "small_intersection_out.vdb2"); refTree = Local::readFile(testDir + "large_intersection.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorIntersect); //Local::writeFile(outTree, "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, "small_difference_out.vdb2"); refTree = Local::readFile(testDir + "large_difference.vdb2"); outTree = visitCsg(*largeTree1, *largeTree2, *refTree, Local::visitorDiff); //Local::writeFile(outTree, "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 openvdb::util::CpuTimer timer; timer.start(); #endif TreePtr aTree(new TreeT(aInputTree)); TreeT bTree(bInputTree); #if TEST_CSG_VERBOSE std::cerr << "deep copy: " << timer.delta() << " ms\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.delta() << " ms\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=*/2); refTree.print(refInfo, /*verbose=*/2); CPPUNIT_ASSERT_EQUAL(refInfo.str(), aInfo.str()); CPPUNIT_ASSERT(aTree->hasSameTopology(refTree)); return aTree; } //////////////////////////////////////// void TestTreeCombine::testCsgCopy() { const float voxelSize = 0.2f; const float radius = 3.0f; openvdb::Vec3f center(0.0f, 0.0f, 0.0f); openvdb::FloatGrid::Ptr gridA = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); openvdb::Coord ijkA = gridA->transform().worldToIndexNodeCentered(center); CPPUNIT_ASSERT(gridA->tree().getValue(ijkA) < 0.0f); // center is inside center.x() += 3.5f; openvdb::FloatGrid::Ptr gridB = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); openvdb::Coord ijkB = gridA->transform().worldToIndexNodeCentered(center); CPPUNIT_ASSERT(gridB->tree().getValue(ijkB) < 0.0f); // center is inside openvdb::FloatGrid::Ptr unionGrid = openvdb::tools::csgUnionCopy(*gridA, *gridB); openvdb::FloatGrid::Ptr intersectionGrid = openvdb::tools::csgIntersectionCopy(*gridA, *gridB); openvdb::FloatGrid::Ptr differenceGrid = openvdb::tools::csgDifferenceCopy(*gridA, *gridB); CPPUNIT_ASSERT(unionGrid.get() != NULL); CPPUNIT_ASSERT(intersectionGrid.get() != NULL); CPPUNIT_ASSERT(differenceGrid.get() != NULL); CPPUNIT_ASSERT(!unionGrid->empty()); CPPUNIT_ASSERT(!intersectionGrid->empty()); CPPUNIT_ASSERT(!differenceGrid->empty()); // test inside / outside sign CPPUNIT_ASSERT(unionGrid->tree().getValue(ijkA) < 0.0f); CPPUNIT_ASSERT(unionGrid->tree().getValue(ijkB) < 0.0f); CPPUNIT_ASSERT(!(intersectionGrid->tree().getValue(ijkA) < 0.0f)); CPPUNIT_ASSERT(!(intersectionGrid->tree().getValue(ijkB) < 0.0f)); CPPUNIT_ASSERT(differenceGrid->tree().getValue(ijkA) < 0.0f); CPPUNIT_ASSERT(!(differenceGrid->tree().getValue(ijkB) < 0.0f)); } //////////////////////////////////////// void TestTreeCombine::testCompActiveLeafVoxels() { {//replace float tree (default argument) openvdb::FloatTree srcTree(0.0f), dstTree(0.0f); dstTree.setValue(openvdb::Coord(1,1,1), 1.0f); srcTree.setValue(openvdb::Coord(1,1,1), 2.0f); srcTree.setValue(openvdb::Coord(8,8,8), 3.0f); CPPUNIT_ASSERT_EQUAL(1, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(2, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(1.0f, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(0.0f, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(!dstTree.isValueOn(openvdb::Coord(8, 8, 8))); openvdb::tools::compActiveLeafVoxels(srcTree, dstTree); CPPUNIT_ASSERT_EQUAL(2, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(0, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(2.0f, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(3.0f, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(8, 8, 8))); } {//replace float tree (lambda expression) openvdb::FloatTree srcTree(0.0f), dstTree(0.0f); dstTree.setValue(openvdb::Coord(1,1,1), 1.0f); srcTree.setValue(openvdb::Coord(1,1,1), 2.0f); srcTree.setValue(openvdb::Coord(8,8,8), 3.0f); CPPUNIT_ASSERT_EQUAL(1, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(2, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(1.0f, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(0.0f, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(!dstTree.isValueOn(openvdb::Coord(8, 8, 8))); openvdb::tools::compActiveLeafVoxels(srcTree, dstTree, [](float &d, float s){d=s;}); CPPUNIT_ASSERT_EQUAL(2, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(0, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(2.0f, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(3.0f, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(8, 8, 8))); } {//add float tree openvdb::FloatTree srcTree(0.0f), dstTree(0.0f); dstTree.setValue(openvdb::Coord(1,1,1), 1.0f); srcTree.setValue(openvdb::Coord(1,1,1), 2.0f); srcTree.setValue(openvdb::Coord(8,8,8), 3.0f); CPPUNIT_ASSERT_EQUAL(1, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(2, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(1.0f, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(0.0f, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(!dstTree.isValueOn(openvdb::Coord(8, 8, 8))); openvdb::tools::compActiveLeafVoxels(srcTree, dstTree, [](float &d, float s){d+=s;}); CPPUNIT_ASSERT_EQUAL(2, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(0, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(3.0f, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(3.0f, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(8, 8, 8))); } { using BufferT = openvdb::FloatTree::LeafNodeType::Buffer; //std::cout << "FloatTree: " << std::is_same::value << '\n'; CPPUNIT_ASSERT((std::is_same::value)); } { using BufferT = openvdb::Vec3fTree::LeafNodeType::Buffer; //std::cout << "Vec3fTree: " << std::is_same::value << '\n'; CPPUNIT_ASSERT((std::is_same::value)); } { using BufferT = openvdb::BoolTree::LeafNodeType::Buffer; //std::cout << "BoolTree: " << std::is_same::value << '\n'; CPPUNIT_ASSERT(!(std::is_same::value)); } { using BufferT = openvdb::MaskTree::LeafNodeType::Buffer; //std::cout << "MaskTree: " << std::is_same::value << '\n'; CPPUNIT_ASSERT(!(std::is_same::value)); } {//replace bool tree openvdb::BoolTree srcTree(false), dstTree(false); dstTree.setValue(openvdb::Coord(1,1,1), true); srcTree.setValue(openvdb::Coord(1,1,1), false); srcTree.setValue(openvdb::Coord(8,8,8), true); //(9,8,8) is inactive but true so it should have no effect srcTree.setValueOnly(openvdb::Coord(9,8,8), true); CPPUNIT_ASSERT_EQUAL(1, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(2, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(true, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(false, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(!dstTree.isValueOn(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT_EQUAL(true, srcTree.getValue(openvdb::Coord(9, 8, 8))); CPPUNIT_ASSERT(!srcTree.isValueOn(openvdb::Coord(9, 8, 8))); using Word = openvdb::BoolTree::LeafNodeType::Buffer::WordType; openvdb::tools::compActiveLeafVoxels(srcTree, dstTree, [](Word &d, Word s){d=s;}); CPPUNIT_ASSERT_EQUAL(2, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(0, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(false, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(true, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(8, 8, 8))); } {// mask tree openvdb::MaskTree srcTree(false), dstTree(false); dstTree.setValueOn(openvdb::Coord(1,1,1)); srcTree.setValueOn(openvdb::Coord(1,1,1)); srcTree.setValueOn(openvdb::Coord(8,8,8)); CPPUNIT_ASSERT_EQUAL(1, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(2, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(true, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(false, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(!dstTree.isValueOn(openvdb::Coord(8, 8, 8))); openvdb::tools::compActiveLeafVoxels(srcTree, dstTree); CPPUNIT_ASSERT_EQUAL(2, int(dstTree.leafCount())); CPPUNIT_ASSERT_EQUAL(0, int(srcTree.leafCount())); CPPUNIT_ASSERT_EQUAL(true, dstTree.getValue(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(1, 1, 1))); CPPUNIT_ASSERT_EQUAL(true, dstTree.getValue(openvdb::Coord(8, 8, 8))); CPPUNIT_ASSERT(dstTree.isValueOn(openvdb::Coord(8, 8, 8))); } } //////////////////////////////////////// // Copyright (c) 2012-2017 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/TestPointDataLeaf.cc0000644000000000000000000013733713200122377016725 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 using namespace openvdb; using namespace openvdb::points; class TestPointDataLeaf: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestPointDataLeaf); CPPUNIT_TEST(testEmptyLeaf); CPPUNIT_TEST(testOffsets); CPPUNIT_TEST(testSetValue); CPPUNIT_TEST(testMonotonicity); CPPUNIT_TEST(testAttributes); CPPUNIT_TEST(testTopologyCopy); CPPUNIT_TEST(testEquivalence); CPPUNIT_TEST(testIterators); CPPUNIT_TEST(testReadWriteCompression); CPPUNIT_TEST(testIO); CPPUNIT_TEST(testSwap); CPPUNIT_TEST(testCopyOnWrite); CPPUNIT_TEST(testCopyDescriptor); CPPUNIT_TEST_SUITE_END(); void testEmptyLeaf(); void testOffsets(); void testSetValue(); void testMonotonicity(); void testAttributes(); void testTopologyCopy(); void testEquivalence(); void testIterators(); void testReadWriteCompression(); void testIO(); void testSwap(); void testCopyOnWrite(); void testCopyDescriptor(); private: }; // class TestPointDataLeaf using LeafType = PointDataTree::LeafNodeType; using ValueType = LeafType::ValueType; using BufferType = LeafType::Buffer; namespace { bool matchingNamePairs(const openvdb::NamePair& lhs, const openvdb::NamePair& rhs) { if (lhs.first != rhs.first) return false; if (lhs.second != rhs.second) return false; return true; } bool zeroLeafValues(const LeafType* leafNode) { for (openvdb::Index i = 0; i < LeafType::SIZE; i++) { if (leafNode->buffer().getValue(i) != LeafType::ValueType(0)) return false; } return true; } bool noAttributeData(const LeafType* leafNode) { const AttributeSet& attributeSet = leafNode->attributeSet(); return attributeSet.size() == 0 && attributeSet.descriptor().size() == 0; } bool monotonicOffsets(const LeafType& leafNode) { int previous = -1; for (auto iter = leafNode.cbeginValueOn(); iter; ++iter) { if (previous > int(*iter)) return false; previous = int(*iter); } return true; } // (borrowed from PointIndexGrid unit test) class PointList { public: using PosType = openvdb::Vec3R; using value_type = openvdb::Vec3R; PointList(const std::vector& points) : mPoints(&points) { } size_t size() const { return mPoints->size(); } void getPos(size_t n, openvdb::Vec3R& xyz) const { xyz = (*mPoints)[n]; } protected: std::vector const * const mPoints; }; // PointList // Generate random points by uniformly distributing points // on a unit-sphere. // (borrowed from PointIndexGrid unit test) std::vector genPoints(const int numPoints) { // init openvdb::math::Random01 randNumber(0); const int n = int(std::sqrt(double(numPoints))); const double xScale = (2.0 * M_PI) / double(n); const double yScale = M_PI / double(n); double x, y, theta, phi; std::vector points; points.reserve(n*n); // 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) points.emplace_back( std::sin(theta)*std::cos(phi), std::sin(theta)*std::sin(phi), std::cos(theta) ); } } return points; } } // namespace void TestPointDataLeaf::testEmptyLeaf() { // empty leaf construction { LeafType* leafNode = new LeafType(); CPPUNIT_ASSERT(leafNode); CPPUNIT_ASSERT(leafNode->isEmpty()); CPPUNIT_ASSERT(!leafNode->buffer().empty()); CPPUNIT_ASSERT(zeroLeafValues(leafNode)); CPPUNIT_ASSERT(noAttributeData(leafNode)); CPPUNIT_ASSERT(leafNode->origin() == openvdb::Coord(0, 0, 0)); delete leafNode; } // empty leaf with non-zero origin construction { openvdb::Coord coord(20, 30, 40); LeafType* leafNode = new LeafType(coord); CPPUNIT_ASSERT(leafNode); CPPUNIT_ASSERT(leafNode->isEmpty()); CPPUNIT_ASSERT(!leafNode->buffer().empty()); CPPUNIT_ASSERT(zeroLeafValues(leafNode)); CPPUNIT_ASSERT(noAttributeData(leafNode)); CPPUNIT_ASSERT(leafNode->origin() == openvdb::Coord(16, 24, 40)); delete leafNode; } } void TestPointDataLeaf::testOffsets() { // offsets for one point per voxel (active = true) { LeafType* leafNode = new LeafType(); for (openvdb::Index i = 0; i < LeafType::SIZE; i++) { leafNode->setOffsetOn(i, i); } CPPUNIT_ASSERT(leafNode->getValue(10) == 10); CPPUNIT_ASSERT(leafNode->isDense()); delete leafNode; } // offsets for one point per voxel (active = false) { LeafType* leafNode = new LeafType(); for (openvdb::Index i = 0; i < LeafType::SIZE; i++) { leafNode->setOffsetOnly(i, i); } CPPUNIT_ASSERT(leafNode->getValue(10) == 10); CPPUNIT_ASSERT(leafNode->isEmpty()); delete leafNode; } // test bulk offset replacement without activity mask update { LeafType* leafNode = new LeafType(); for (openvdb::Index i = 0; i < LeafType::SIZE; ++i) { leafNode->setOffsetOn(i, 10); } std::vector newOffsets(LeafType::SIZE); leafNode->setOffsets(newOffsets, /*updateValueMask*/false); const LeafType::NodeMaskType& valueMask = leafNode->getValueMask(); for (openvdb::Index i = 0; i < LeafType::SIZE; ++i ) { CPPUNIT_ASSERT(valueMask.isOn(i)); } delete leafNode; } // test bulk offset replacement with activity mask update { LeafType* leafNode = new LeafType(); for (openvdb::Index i = 0; i < LeafType::SIZE; ++i) { leafNode->setOffsetOn(i, 10); } std::vector newOffsets(LeafType::SIZE); leafNode->setOffsets(newOffsets, /*updateValueMask*/true); const LeafType::NodeMaskType& valueMask = leafNode->getValueMask(); for (openvdb::Index i = 0; i < LeafType::SIZE; ++i ) { CPPUNIT_ASSERT(valueMask.isOff(i)); } delete leafNode; } // ensure bulk offset replacement fails when vector size doesn't equal number of voxels { LeafType* leafNode = new LeafType(); std::vector newOffsets; CPPUNIT_ASSERT_THROW(leafNode->setOffsets(newOffsets), openvdb::ValueError); delete leafNode; } // test offset validation { using AttributeVec3s = TypedAttributeArray; using AttributeS = TypedAttributeArray; using Descriptor = AttributeSet::Descriptor; // empty Descriptor should throw on leaf node initialize auto emptyDescriptor = std::make_shared(); LeafType* emptyLeafNode = new LeafType(); CPPUNIT_ASSERT_THROW(emptyLeafNode->initializeAttributes(emptyDescriptor, 5), openvdb::IndexError); // create a non-empty Descriptor Descriptor::Ptr descriptor = Descriptor::create(AttributeVec3s::attributeType()); // ensure validateOffsets succeeds for monotonically increasing offsets that fully // utilise the underlying attribute arrays { const size_t numAttributes = 1; LeafType* leafNode = new LeafType(); leafNode->initializeAttributes(descriptor, numAttributes); descriptor = descriptor->duplicateAppend("density", AttributeS::attributeType()); leafNode->appendAttribute(leafNode->attributeSet().descriptor(), descriptor, descriptor->find("density")); std::vector offsets(LeafType::SIZE); offsets.back() = numAttributes; leafNode->setOffsets(offsets); CPPUNIT_ASSERT_NO_THROW(leafNode->validateOffsets()); delete leafNode; } // ensure validateOffsets detects non-monotonic offset values { LeafType* leafNode = new LeafType(); std::vector offsets(LeafType::SIZE); *offsets.begin() = 1; leafNode->setOffsets(offsets); CPPUNIT_ASSERT_THROW(leafNode->validateOffsets(), openvdb::ValueError); delete leafNode; } // ensure validateOffsets detects inconsistent attribute array sizes { descriptor = Descriptor::create(AttributeVec3s::attributeType()); const size_t numAttributes = 1; LeafType* leafNode = new LeafType(); leafNode->initializeAttributes(descriptor, numAttributes); descriptor = descriptor->duplicateAppend("density", AttributeS::attributeType()); leafNode->appendAttribute(leafNode->attributeSet().descriptor(), descriptor, descriptor->find("density")); AttributeSet* newSet = new AttributeSet(leafNode->attributeSet(), numAttributes); newSet->replace("density", AttributeS::create(numAttributes+1)); leafNode->replaceAttributeSet(newSet); std::vector offsets(LeafType::SIZE); offsets.back() = numAttributes; leafNode->setOffsets(offsets); CPPUNIT_ASSERT_THROW(leafNode->validateOffsets(), openvdb::ValueError); delete leafNode; } // ensure validateOffsets detects unused attributes (e.g. final voxel offset not // equal to size of attribute arrays) { descriptor = Descriptor::create(AttributeVec3s::attributeType()); const size_t numAttributes = 1; LeafType* leafNode = new LeafType(); leafNode->initializeAttributes(descriptor, numAttributes); descriptor = descriptor->duplicateAppend("density", AttributeS::attributeType()); leafNode->appendAttribute(leafNode->attributeSet().descriptor(), descriptor, descriptor->find("density")); std::vector offsets(LeafType::SIZE); offsets.back() = numAttributes - 1; leafNode->setOffsets(offsets); CPPUNIT_ASSERT_THROW(leafNode->validateOffsets(), openvdb::ValueError); delete leafNode; } // ensure validateOffsets detects out-of-bounds offset values { descriptor = Descriptor::create(AttributeVec3s::attributeType()); const size_t numAttributes = 1; LeafType* leafNode = new LeafType(); leafNode->initializeAttributes(descriptor, numAttributes); descriptor = descriptor->duplicateAppend("density", AttributeS::attributeType()); leafNode->appendAttribute(leafNode->attributeSet().descriptor(), descriptor, descriptor->find("density")); std::vector offsets(LeafType::SIZE); offsets.back() = numAttributes + 1; leafNode->setOffsets(offsets); CPPUNIT_ASSERT_THROW(leafNode->validateOffsets(), openvdb::ValueError); delete leafNode; } } } void TestPointDataLeaf::testSetValue() { LeafType leaf(openvdb::Coord(0, 0, 0)); openvdb::Coord xyz(0, 0, 0); openvdb::Index index(LeafType::coordToOffset(xyz)); // the following tests are not run when in debug mode // due to assertions firing #ifndef NDEBUG return; #endif // ensure all non-modifiable operations are no-ops leaf.setValueOnly(xyz, 10); leaf.setValueOnly(index, 10); leaf.setValueOff(xyz, 10); leaf.setValueOff(index, 10); leaf.setValueOn(xyz, 10); leaf.setValueOn(index, 10); struct Local { static inline void op(unsigned int& n) { n = 10; } }; leaf.modifyValue(xyz, Local::op); leaf.modifyValue(index, Local::op); leaf.modifyValueAndActiveState(xyz, Local::op); CPPUNIT_ASSERT_EQUAL(0, int(leaf.getValue(xyz))); } void TestPointDataLeaf::testMonotonicity() { LeafType leaf(openvdb::Coord(0, 0, 0)); // assign aggregate values and activate all non-even coordinate sums unsigned sum = 0; for (unsigned int i = 0; i < LeafType::DIM; i++) { for (unsigned int j = 0; j < LeafType::DIM; j++) { for (unsigned int k = 0; k < LeafType::DIM; k++) { if (((i + j + k) % 2) == 0) continue; leaf.setOffsetOn(LeafType::coordToOffset(openvdb::Coord(i, j, k)), sum++); } } } CPPUNIT_ASSERT(monotonicOffsets(leaf)); // manually change a value and ensure offsets become non-monotonic leaf.setOffsetOn(500, 4); CPPUNIT_ASSERT(!monotonicOffsets(leaf)); } void TestPointDataLeaf::testAttributes() { using AttributeVec3s = TypedAttributeArray; using AttributeI = TypedAttributeArray; // create a descriptor using Descriptor = AttributeSet::Descriptor; Descriptor::Ptr descrA = Descriptor::create(AttributeVec3s::attributeType()); // create a leaf and initialize attributes using this descriptor LeafType leaf(openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT_EQUAL(leaf.attributeSet().size(), size_t(0)); leaf.initializeAttributes(descrA, /*arrayLength=*/100); descrA = descrA->duplicateAppend("id", AttributeI::attributeType()); leaf.appendAttribute(leaf.attributeSet().descriptor(), descrA, descrA->find("id")); CPPUNIT_ASSERT_EQUAL(leaf.attributeSet().size(), size_t(2)); { const AttributeArray* array = leaf.attributeSet().get(/*pos=*/0); CPPUNIT_ASSERT_EQUAL(array->size(), Index(100)); } // manually set a voxel leaf.setOffsetOn(LeafType::SIZE - 1, 10); CPPUNIT_ASSERT(!zeroLeafValues(&leaf)); // neither dense nor empty CPPUNIT_ASSERT(!leaf.isDense()); CPPUNIT_ASSERT(!leaf.isEmpty()); // clear the attributes and check voxel values are zero but value mask is not touched leaf.clearAttributes(/*updateValueMask=*/ false); CPPUNIT_ASSERT(!leaf.isDense()); CPPUNIT_ASSERT(!leaf.isEmpty()); CPPUNIT_ASSERT_EQUAL(leaf.attributeSet().size(), size_t(2)); CPPUNIT_ASSERT(zeroLeafValues(&leaf)); // call clearAttributes again, updating the value mask and check it is now inactive leaf.clearAttributes(); CPPUNIT_ASSERT(leaf.isEmpty()); // ensure arrays are uniform const AttributeArray* array0 = leaf.attributeSet().get(/*pos=*/0); const AttributeArray* array1 = leaf.attributeSet().get(/*pos=*/1); CPPUNIT_ASSERT_EQUAL(array0->size(), Index(1)); CPPUNIT_ASSERT_EQUAL(array1->size(), Index(1)); // test leaf returns expected result for hasAttribute() CPPUNIT_ASSERT(leaf.hasAttribute(/*pos*/0)); CPPUNIT_ASSERT(leaf.hasAttribute("P")); CPPUNIT_ASSERT(leaf.hasAttribute(/*pos*/1)); CPPUNIT_ASSERT(leaf.hasAttribute("id")); CPPUNIT_ASSERT(!leaf.hasAttribute(/*pos*/2)); CPPUNIT_ASSERT(!leaf.hasAttribute("test")); // test underlying attributeArray can be accessed by name and index, // and that their types are as expected. const LeafType* constLeaf = &leaf; CPPUNIT_ASSERT(matchingNamePairs(leaf.attributeArray(/*pos*/0).type(), AttributeVec3s::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(leaf.attributeArray("P").type(), AttributeVec3s::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(leaf.attributeArray(/*pos*/1).type(), AttributeI::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(leaf.attributeArray("id").type(), AttributeI::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(constLeaf->attributeArray(/*pos*/0).type(), AttributeVec3s::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(constLeaf->attributeArray("P").type(), AttributeVec3s::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(constLeaf->attributeArray(/*pos*/1).type(), AttributeI::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(constLeaf->attributeArray("id").type(), AttributeI::attributeType())); // check invalid pos or name throws CPPUNIT_ASSERT_THROW(leaf.attributeArray(/*pos=*/3), openvdb::LookupError); CPPUNIT_ASSERT_THROW(leaf.attributeArray("not_there"), openvdb::LookupError); CPPUNIT_ASSERT_THROW(constLeaf->attributeArray(/*pos=*/3), openvdb::LookupError); CPPUNIT_ASSERT_THROW(constLeaf->attributeArray("not_there"), openvdb::LookupError); // test leaf can be successfully cast to TypedAttributeArray and check types CPPUNIT_ASSERT(matchingNamePairs(leaf.attributeArray(/*pos=*/0).type(), AttributeVec3s::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(leaf.attributeArray("P").type(), AttributeVec3s::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(leaf.attributeArray(/*pos=*/1).type(), AttributeI::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(leaf.attributeArray("id").type(), AttributeI::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(constLeaf->attributeArray(/*pos=*/0).type(), AttributeVec3s::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(constLeaf->attributeArray("P").type(), AttributeVec3s::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(constLeaf->attributeArray(/*pos=*/1).type(), AttributeI::attributeType())); CPPUNIT_ASSERT(matchingNamePairs(constLeaf->attributeArray("id").type(), AttributeI::attributeType())); // check invalid pos or name throws CPPUNIT_ASSERT_THROW(leaf.attributeArray(/*pos=*/2), openvdb::LookupError); CPPUNIT_ASSERT_THROW(leaf.attributeArray("test"), openvdb::LookupError); CPPUNIT_ASSERT_THROW(constLeaf->attributeArray(/*pos=*/2), openvdb::LookupError); CPPUNIT_ASSERT_THROW(constLeaf->attributeArray("test"), openvdb::LookupError); // check memory usage = attribute set + base leaf // leaf.initializeAttributes(descrA, /*arrayLength=*/100); const LeafType::BaseLeaf& baseLeaf = static_cast(leaf); const Index64 memUsage = baseLeaf.memUsage() + leaf.attributeSet().memUsage(); CPPUNIT_ASSERT_EQUAL(memUsage, leaf.memUsage()); } void TestPointDataLeaf::testTopologyCopy() { // test topology copy from a float Leaf { using FloatLeaf = openvdb::FloatTree::LeafNodeType; // create a float leaf and activate some values FloatLeaf floatLeaf(openvdb::Coord(0, 0, 0)); floatLeaf.setValueOn(1); floatLeaf.setValueOn(4); floatLeaf.setValueOn(7); floatLeaf.setValueOn(8); CPPUNIT_ASSERT_EQUAL(floatLeaf.onVoxelCount(), Index64(4)); // validate construction of a PointDataLeaf using a TopologyCopy LeafType leaf(floatLeaf, 0, openvdb::TopologyCopy()); CPPUNIT_ASSERT_EQUAL(leaf.onVoxelCount(), Index64(4)); LeafType leaf2(openvdb::Coord(8, 8, 8)); leaf2.setValueOn(1); leaf2.setValueOn(4); leaf2.setValueOn(7); CPPUNIT_ASSERT(!leaf.hasSameTopology(&leaf2)); leaf2.setValueOn(8); CPPUNIT_ASSERT(leaf.hasSameTopology(&leaf2)); // validate construction of a PointDataLeaf using an Off-On TopologyCopy LeafType leaf3(floatLeaf, 1, 2, openvdb::TopologyCopy()); CPPUNIT_ASSERT_EQUAL(leaf3.onVoxelCount(), Index64(4)); } // test topology copy from a PointIndexLeaf { // generate points // (borrowed from PointIndexGrid unit test) const float voxelSize = 0.01f; const openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); std::vector points = genPoints(40000); PointList pointList(points); // construct point index grid using PointIndexGrid = openvdb::tools::PointIndexGrid; PointIndexGrid::Ptr pointGridPtr = openvdb::tools::createPointIndexGrid(pointList, *transform); auto iter = pointGridPtr->tree().cbeginLeaf(); CPPUNIT_ASSERT(iter); // check that the active voxel counts match for all leaves for ( ; iter; ++iter) { LeafType leaf(*iter); CPPUNIT_ASSERT_EQUAL(iter->onVoxelCount(), leaf.onVoxelCount()); } } } void TestPointDataLeaf::testEquivalence() { using AttributeVec3s = TypedAttributeArray; using AttributeF = TypedAttributeArray; using AttributeI = TypedAttributeArray; // create a descriptor using Descriptor = AttributeSet::Descriptor; Descriptor::Ptr descrA = Descriptor::create(AttributeVec3s::attributeType()); // create a leaf and initialize attributes using this descriptor LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.initializeAttributes(descrA, /*arrayLength=*/100); descrA = descrA->duplicateAppend("density", AttributeF::attributeType()); leaf.appendAttribute(leaf.attributeSet().descriptor(), descrA, descrA->find("density")); descrA = descrA->duplicateAppend("id", AttributeI::attributeType()); leaf.appendAttribute(leaf.attributeSet().descriptor(), descrA, descrA->find("id")); // manually activate some voxels leaf.setValueOn(1); leaf.setValueOn(4); leaf.setValueOn(7); // manually change some values in the density array TypedAttributeArray& attr = TypedAttributeArray::cast(leaf.attributeArray("density")); attr.set(0, 5.0f); attr.set(50, 2.0f); attr.set(51, 8.1f); // check deep copy construction (topology and attributes) { LeafType leaf2(leaf); CPPUNIT_ASSERT_EQUAL(leaf.onVoxelCount(), leaf2.onVoxelCount()); CPPUNIT_ASSERT(leaf.hasSameTopology(&leaf2)); CPPUNIT_ASSERT_EQUAL(leaf.attributeSet().size(), leaf2.attributeSet().size()); CPPUNIT_ASSERT_EQUAL(leaf.attributeSet().get(0)->size(), leaf2.attributeSet().get(0)->size()); } // check equivalence { LeafType leaf2(leaf); CPPUNIT_ASSERT(leaf == leaf2); leaf2.setOrigin(openvdb::Coord(0, 8, 0)); CPPUNIT_ASSERT(leaf != leaf2); } { LeafType leaf2(leaf); CPPUNIT_ASSERT(leaf == leaf2); leaf2.setValueOn(10); CPPUNIT_ASSERT(leaf != leaf2); } } void TestPointDataLeaf::testIterators() { using AttributeVec3s = TypedAttributeArray; using AttributeF = TypedAttributeArray; // create a descriptor using Descriptor = AttributeSet::Descriptor; Descriptor::Ptr descrA = Descriptor::create(AttributeVec3s::attributeType()); // create a leaf and initialize attributes using this descriptor const size_t size = LeafType::NUM_VOXELS; LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.initializeAttributes(descrA, /*arrayLength=*/size/2); descrA = descrA->duplicateAppend("density", AttributeF::attributeType()); leaf.appendAttribute(leaf.attributeSet().descriptor(), descrA, descrA->find("density")); { // uniform monotonic offsets, only even active int offset = 0; for (Index i = 0; i < size; i++) { if ((i % 2) == 0) { leaf.setOffsetOn(i, ++offset); } else { leaf.setOffsetOnly(i, ++offset); leaf.setValueOff(i); } } } { // test index on LeafType::IndexOnIter iterOn(leaf.beginIndexOn()); CPPUNIT_ASSERT_EQUAL(iterCount(iterOn), Index64(size/2)); for (int i = 0; iterOn; ++iterOn, i += 2) { CPPUNIT_ASSERT_EQUAL(*iterOn, Index32(i)); } } { // test index off LeafType::IndexOffIter iterOff(leaf.beginIndexOff()); CPPUNIT_ASSERT_EQUAL(iterCount(iterOff), Index64(size/2)); for (int i = 1; iterOff; ++iterOff, i += 2) { CPPUNIT_ASSERT_EQUAL(*iterOff, Index32(i)); } } { // test index all LeafType::IndexAllIter iterAll(leaf.beginIndexAll()); CPPUNIT_ASSERT_EQUAL(iterCount(iterAll), Index64(size)); for (int i = 0; iterAll; ++iterAll, ++i) { CPPUNIT_ASSERT_EQUAL(*iterAll, Index32(i)); } } } void TestPointDataLeaf::testReadWriteCompression() { using namespace openvdb; util::NodeMask<3> valueMask; util::NodeMask<3> childMask; io::StreamMetadata::Ptr nullMetadata; io::StreamMetadata::Ptr streamMetadata(new io::StreamMetadata); { // simple read/write test std::stringstream ss; Index count = 8*8*8; std::unique_ptr srcBuf(new PointDataIndex32[count]); for (Index i = 0; i < count; i++) srcBuf[i] = i; { io::writeCompressedValues(ss, srcBuf.get(), count, valueMask, childMask, false); std::unique_ptr destBuf(new PointDataIndex32[count]); io::readCompressedValues(ss, destBuf.get(), count, valueMask, false); for (Index i = 0; i < count; i++) { CPPUNIT_ASSERT_EQUAL(srcBuf.get()[i], destBuf.get()[i]); } } const char* charBuffer = reinterpret_cast(srcBuf.get()); size_t referenceBytes = compression::bloscCompressedSize(charBuffer, count*sizeof(PointDataIndex32)); { ss.str(""); io::setStreamMetadataPtr(ss, streamMetadata); io::writeCompressedValuesSize(ss, srcBuf.get(), count); io::writeCompressedValues(ss, srcBuf.get(), count, valueMask, childMask, false); int magic = 1924674; ss.write(reinterpret_cast(&magic), sizeof(int)); std::unique_ptr destBuf(new PointDataIndex32[count]); uint16_t size; ss.read(reinterpret_cast(&size), sizeof(uint16_t)); if (size == std::numeric_limits::max()) size = 0; CPPUNIT_ASSERT_EQUAL(size_t(size), referenceBytes); io::readCompressedValues(ss, destBuf.get(), count, valueMask, false); int magic2; ss.read(reinterpret_cast(&magic2), sizeof(int)); CPPUNIT_ASSERT_EQUAL(magic, magic2); for (Index i = 0; i < count; i++) { CPPUNIT_ASSERT_EQUAL(srcBuf.get()[i], destBuf.get()[i]); } io::setStreamMetadataPtr(ss, nullMetadata); } { // repeat but using nullptr for destination to force seek behaviour ss.str(""); io::setStreamMetadataPtr(ss, streamMetadata); io::writeCompressedValuesSize(ss, srcBuf.get(), count); io::writeCompressedValues(ss, srcBuf.get(), count, valueMask, childMask, false); int magic = 3829250; ss.write(reinterpret_cast(&magic), sizeof(int)); uint16_t size; ss.read(reinterpret_cast(&size), sizeof(uint16_t)); uint16_t actualSize(size); if (size == std::numeric_limits::max()) actualSize = 0; CPPUNIT_ASSERT_EQUAL(size_t(actualSize), referenceBytes); streamMetadata->setPass(size); PointDataIndex32* forceSeek = nullptr; io::readCompressedValues(ss, forceSeek, count, valueMask, false); int magic2; ss.read(reinterpret_cast(&magic2), sizeof(int)); CPPUNIT_ASSERT_EQUAL(magic, magic2); io::setStreamMetadataPtr(ss, nullMetadata); } #ifndef OPENVDB_USE_BLOSC { // write to indicate Blosc compression std::stringstream ssInvalid; uint16_t bytes16(100); // clamp to 16-bit unsigned integer ssInvalid.write(reinterpret_cast(&bytes16), sizeof(uint16_t)); std::unique_ptr destBuf(new PointDataIndex32[count]); CPPUNIT_ASSERT_THROW(io::readCompressedValues(ssInvalid, destBuf.get(), count, valueMask, false), RuntimeError); } #endif #ifdef OPENVDB_USE_BLOSC { // mis-matching destination bytes cause decompression failures std::unique_ptr destBuf(new PointDataIndex32[count]); ss.str(""); io::writeCompressedValues(ss, srcBuf.get(), count, valueMask, childMask, false); CPPUNIT_ASSERT_THROW(io::readCompressedValues(ss, destBuf.get(), count+1, valueMask, false), RuntimeError); ss.str(""); io::writeCompressedValues(ss, srcBuf.get(), count, valueMask, childMask, false); CPPUNIT_ASSERT_THROW(io::readCompressedValues(ss, destBuf.get(), 1, valueMask, false), RuntimeError); } #endif { // seek ss.str(""); io::writeCompressedValues(ss, srcBuf.get(), count, valueMask, childMask, false); int test(10772832); ss.write(reinterpret_cast(&test), sizeof(int)); PointDataIndex32* buf = nullptr; io::readCompressedValues(ss, buf, count, valueMask, false); int test2; ss.read(reinterpret_cast(&test2), sizeof(int)); CPPUNIT_ASSERT_EQUAL(test, test2); } } { // two values for non-compressible example std::stringstream ss; Index count = 2; std::unique_ptr srcBuf(new PointDataIndex32[count]); for (Index i = 0; i < count; i++) srcBuf[i] = i; io::writeCompressedValues(ss, srcBuf.get(), count, valueMask, childMask, false); std::unique_ptr destBuf(new PointDataIndex32[count]); io::readCompressedValues(ss, destBuf.get(), count, valueMask, false); for (Index i = 0; i < count; i++) { CPPUNIT_ASSERT_EQUAL(srcBuf.get()[i], destBuf.get()[i]); } } { // throw at limit of 16-bit std::stringstream ss; PointDataIndex32* buf = nullptr; Index count = std::numeric_limits::max(); CPPUNIT_ASSERT_THROW(io::writeCompressedValues(ss, buf, count, valueMask, childMask, false), IoError); CPPUNIT_ASSERT_THROW(io::readCompressedValues(ss, buf, count, valueMask, false), IoError); } } void TestPointDataLeaf::testIO() { using AttributeVec3s = TypedAttributeArray; using AttributeF = TypedAttributeArray; // create a descriptor using Descriptor = AttributeSet::Descriptor; Descriptor::Ptr descrA = Descriptor::create(AttributeVec3s::attributeType()); // create a leaf and initialize attributes using this descriptor const size_t size = LeafType::NUM_VOXELS; LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.initializeAttributes(descrA, /*arrayLength=*/size/2); descrA = descrA->duplicateAppend("density", AttributeF::attributeType()); leaf.appendAttribute(leaf.attributeSet().descriptor(), descrA, descrA->find("density")); // manually activate some voxels leaf.setOffsetOn(1, 10); leaf.setOffsetOn(4, 20); leaf.setOffsetOn(7, 5); // manually change some values in the density array TypedAttributeArray& attr = TypedAttributeArray::cast(leaf.attributeArray("density")); attr.set(0, 5.0f); attr.set(50, 2.0f); attr.set(51, 8.1f); // read and write topology to disk { LeafType leaf2(openvdb::Coord(0, 0, 0)); std::ostringstream ostr(std::ios_base::binary); leaf.writeTopology(ostr); std::istringstream istr(ostr.str(), std::ios_base::binary); leaf2.readTopology(istr); // check topology matches CPPUNIT_ASSERT_EQUAL(leaf.onVoxelCount(), leaf2.onVoxelCount()); CPPUNIT_ASSERT(leaf2.isValueOn(4)); CPPUNIT_ASSERT(!leaf2.isValueOn(5)); // check only topology (values and attributes still empty) CPPUNIT_ASSERT_EQUAL(leaf2.getValue(4), ValueType(0)); CPPUNIT_ASSERT_EQUAL(leaf2.attributeSet().size(), size_t(0)); } // read and write buffers to disk { LeafType leaf2(openvdb::Coord(0, 0, 0)); io::StreamMetadata::Ptr streamMetadata(new io::StreamMetadata); std::ostringstream ostr(std::ios_base::binary); io::setStreamMetadataPtr(ostr, streamMetadata); io::setDataCompression(ostr, io::COMPRESS_BLOSC); leaf.writeTopology(ostr); for (Index b = 0; b < leaf.buffers(); b++) { uint32_t pass = (uint32_t(leaf.buffers()) << 16) | uint32_t(b); streamMetadata->setPass(pass); leaf.writeBuffers(ostr); } { // error checking streamMetadata->setPass(1000); leaf.writeBuffers(ostr); io::StreamMetadata::Ptr meta; io::setStreamMetadataPtr(ostr, meta); CPPUNIT_ASSERT_THROW(leaf.writeBuffers(ostr), openvdb::IoError); } std::istringstream istr(ostr.str(), std::ios_base::binary); io::setStreamMetadataPtr(istr, streamMetadata); io::setDataCompression(istr, io::COMPRESS_BLOSC); // 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. io::setCurrentVersion(istr); leaf2.readTopology(istr); for (Index b = 0; b < leaf.buffers(); b++) { uint32_t pass = (uint32_t(leaf.buffers()) << 16) | uint32_t(b); streamMetadata->setPass(pass); leaf2.readBuffers(istr); } // check topology matches CPPUNIT_ASSERT_EQUAL(leaf.onVoxelCount(), leaf2.onVoxelCount()); CPPUNIT_ASSERT(leaf2.isValueOn(4)); CPPUNIT_ASSERT(!leaf2.isValueOn(5)); // check only topology (values and attributes still empty) CPPUNIT_ASSERT_EQUAL(leaf2.getValue(4), ValueType(20)); CPPUNIT_ASSERT_EQUAL(leaf2.attributeSet().size(), size_t(2)); } { // test multi-buffer IO // create a new grid with a single origin leaf PointDataGrid::Ptr grid = PointDataGrid::create(); grid->setName("points"); grid->tree().addLeaf(new LeafType(leaf)); openvdb::GridCPtrVec grids; grids.push_back(grid); // write to file { io::File file("leaf.vdb"); file.write(grids); file.close(); } { // read grids from file (using delayed loading) PointDataGrid::Ptr gridFromDisk; { io::File file("leaf.vdb"); file.open(); openvdb::GridBase::Ptr baseGrid = file.readGrid("points"); file.close(); gridFromDisk = openvdb::gridPtrCast(baseGrid); } LeafType* leafFromDisk = gridFromDisk->tree().probeLeaf(openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT(leafFromDisk); CPPUNIT_ASSERT(leaf == *leafFromDisk); } #if OPENVDB_ABI_VERSION_NUMBER >= 3 { // read grids from file and pre-fetch PointDataGrid::Ptr gridFromDisk; { io::File file("leaf.vdb"); file.open(); openvdb::GridBase::Ptr baseGrid = file.readGrid("points"); file.close(); gridFromDisk = openvdb::gridPtrCast(baseGrid); } LeafType* leafFromDisk = gridFromDisk->tree().probeLeaf(openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT(leafFromDisk); const AttributeF& attribute( AttributeF::cast(leafFromDisk->constAttributeArray("density"))); CPPUNIT_ASSERT(leafFromDisk->buffer().isOutOfCore()); #if OPENVDB_USE_BLOSC CPPUNIT_ASSERT(attribute.isOutOfCore()); #else // delayed-loading is only available on attribute arrays when using Blosc CPPUNIT_ASSERT(!attribute.isOutOfCore()); #endif prefetch(gridFromDisk->tree()); // ensure out-of-core data is now in-core after pre-fetching CPPUNIT_ASSERT(!leafFromDisk->buffer().isOutOfCore()); CPPUNIT_ASSERT(!attribute.isOutOfCore()); } #endif // OPENVDB_ABI_VERSION_NUMBER >= 3 remove("leaf.vdb"); } { // test multi-buffer IO with varying attribute storage per-leaf // create a new grid with three leaf nodes PointDataGrid::Ptr grid = PointDataGrid::create(); grid->setName("points"); Descriptor::Ptr descrB = Descriptor::create(AttributeVec3s::attributeType()); // create leaf nodes and initialize attributes using this descriptor LeafType leaf0(openvdb::Coord(0, 0, 0)); LeafType leaf1(openvdb::Coord(0, 8, 0)); LeafType leaf2(openvdb::Coord(0, 0, 8)); leaf0.initializeAttributes(descrB, /*arrayLength=*/2); leaf1.initializeAttributes(descrB, /*arrayLength=*/2); leaf2.initializeAttributes(descrB, /*arrayLength=*/2); descrB = descrB->duplicateAppend("density", AttributeF::attributeType()); size_t index = descrB->find("density"); // append density attribute to leaf 0 and leaf 2 (not leaf 1) leaf0.appendAttribute(leaf0.attributeSet().descriptor(), descrB, index); leaf2.appendAttribute(leaf2.attributeSet().descriptor(), descrB, index); // manually change some values in the density array for leaf 0 and leaf 2 TypedAttributeArray& attr0 = TypedAttributeArray::cast(leaf0.attributeArray("density")); attr0.set(0, 2.0f); attr0.set(1, 2.0f); attr0.compact(); // compact only the attribute array in the second leaf TypedAttributeArray& attr2 = TypedAttributeArray::cast(leaf2.attributeArray("density")); attr2.set(0, 5.0f); attr2.set(1, 5.0f); attr2.compact(); CPPUNIT_ASSERT(attr0.isUniform()); CPPUNIT_ASSERT(attr2.isUniform()); grid->tree().addLeaf(new LeafType(leaf0)); grid->tree().addLeaf(new LeafType(leaf1)); grid->tree().addLeaf(new LeafType(leaf2)); openvdb::GridCPtrVec grids; grids.push_back(grid); { // write to file io::File file("leaf.vdb"); file.write(grids); file.close(); } { // read grids from file (using delayed loading) PointDataGrid::Ptr gridFromDisk; { io::File file("leaf.vdb"); file.open(); openvdb::GridBase::Ptr baseGrid = file.readGrid("points"); file.close(); gridFromDisk = openvdb::gridPtrCast(baseGrid); } LeafType* leafFromDisk = gridFromDisk->tree().probeLeaf(openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT(leafFromDisk); CPPUNIT_ASSERT(leaf0 == *leafFromDisk); leafFromDisk = gridFromDisk->tree().probeLeaf(openvdb::Coord(0, 8, 0)); CPPUNIT_ASSERT(leafFromDisk); CPPUNIT_ASSERT(leaf1 == *leafFromDisk); leafFromDisk = gridFromDisk->tree().probeLeaf(openvdb::Coord(0, 0, 8)); CPPUNIT_ASSERT(leafFromDisk); CPPUNIT_ASSERT(leaf2 == *leafFromDisk); } remove("leaf.vdb"); } } void TestPointDataLeaf::testSwap() { using AttributeVec3s = TypedAttributeArray; using AttributeF = TypedAttributeArray; using AttributeI = TypedAttributeArray; // create a descriptor using Descriptor = AttributeSet::Descriptor; Descriptor::Ptr descrA = Descriptor::create(AttributeVec3s::attributeType()); // create a leaf and initialize attributes using this descriptor const Index initialArrayLength = 100; LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.initializeAttributes(descrA, /*arrayLength=*/initialArrayLength); descrA = descrA->duplicateAppend("density", AttributeF::attributeType()); leaf.appendAttribute(leaf.attributeSet().descriptor(), descrA, descrA->find("density")); descrA = descrA->duplicateAppend("id", AttributeI::attributeType()); leaf.appendAttribute(leaf.attributeSet().descriptor(), descrA, descrA->find("id")); // swap out the underlying attribute set with a new attribute set with a matching // descriptor CPPUNIT_ASSERT_EQUAL(initialArrayLength, leaf.attributeSet().get("density")->size()); CPPUNIT_ASSERT_EQUAL(initialArrayLength, leaf.attributeSet().get("id")->size()); descrA = Descriptor::create(AttributeVec3s::attributeType()); const Index newArrayLength = initialArrayLength / 2; AttributeSet* newAttributeSet(new AttributeSet(descrA, /*arrayLength*/newArrayLength)); newAttributeSet->appendAttribute("density", AttributeF::attributeType()); newAttributeSet->appendAttribute("id", AttributeI::attributeType()); leaf.replaceAttributeSet(newAttributeSet); CPPUNIT_ASSERT_EQUAL(newArrayLength, leaf.attributeSet().get("density")->size()); CPPUNIT_ASSERT_EQUAL(newArrayLength, leaf.attributeSet().get("id")->size()); // ensure we refuse to swap when the attribute set is null CPPUNIT_ASSERT_THROW(leaf.replaceAttributeSet(nullptr), openvdb::ValueError); // ensure we refuse to swap when the descriptors do not match, // unless we explicitly allow a mismatch. Descriptor::Ptr descrB = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet* attributeSet = new AttributeSet(descrB, newArrayLength); attributeSet->appendAttribute("extra", AttributeF::attributeType()); CPPUNIT_ASSERT_THROW(leaf.replaceAttributeSet(attributeSet), openvdb::ValueError); leaf.replaceAttributeSet(attributeSet, true); CPPUNIT_ASSERT_EQUAL(const_cast(&leaf.attributeSet()), attributeSet); } void TestPointDataLeaf::testCopyOnWrite() { using AttributeVec3s = TypedAttributeArray; using AttributeF = TypedAttributeArray; // create a descriptor using Descriptor = AttributeSet::Descriptor; Descriptor::Ptr descrA = Descriptor::create(AttributeVec3s::attributeType()); // create a leaf and initialize attributes using this descriptor const Index initialArrayLength = 100; LeafType leaf(openvdb::Coord(0, 0, 0)); leaf.initializeAttributes(descrA, /*arrayLength=*/initialArrayLength); descrA = descrA->duplicateAppend("density", AttributeF::attributeType()); leaf.appendAttribute(leaf.attributeSet().descriptor(), descrA, descrA->find("density")); const AttributeSet& attributeSet = leaf.attributeSet(); CPPUNIT_ASSERT_EQUAL(attributeSet.size(), size_t(2)); // ensure attribute arrays are shared between leaf nodes until write const LeafType leafCopy(leaf); const AttributeSet& attributeSetCopy = leafCopy.attributeSet(); CPPUNIT_ASSERT(attributeSet.isShared(/*pos=*/1)); CPPUNIT_ASSERT(attributeSetCopy.isShared(/*pos=*/1)); // test that from a const leaf, accesses to the attribute arrays do not // make then unique const AttributeArray* constArray = attributeSetCopy.getConst(/*pos=*/1); CPPUNIT_ASSERT(constArray); CPPUNIT_ASSERT(attributeSet.isShared(/*pos=*/1)); CPPUNIT_ASSERT(attributeSetCopy.isShared(/*pos=*/1)); constArray = attributeSetCopy.get(/*pos=*/1); CPPUNIT_ASSERT(attributeSet.isShared(/*pos=*/1)); CPPUNIT_ASSERT(attributeSetCopy.isShared(/*pos=*/1)); constArray = &(leafCopy.attributeArray(/*pos=*/1)); CPPUNIT_ASSERT(attributeSet.isShared(/*pos=*/1)); CPPUNIT_ASSERT(attributeSetCopy.isShared(/*pos=*/1)); constArray = &(leafCopy.attributeArray("density")); CPPUNIT_ASSERT(attributeSet.isShared(/*pos=*/1)); CPPUNIT_ASSERT(attributeSetCopy.isShared(/*pos=*/1)); // test makeUnique is called from non const getters AttributeArray* attributeArray = &(leaf.attributeArray(/*pos=*/1)); CPPUNIT_ASSERT(attributeArray); CPPUNIT_ASSERT(!attributeSet.isShared(/*pos=*/1)); CPPUNIT_ASSERT(!attributeSetCopy.isShared(/*pos=*/1)); } void TestPointDataLeaf::testCopyDescriptor() { using AttributeVec3s = TypedAttributeArray; using AttributeS = TypedAttributeArray; using LeafNode = PointDataTree::LeafNodeType; PointDataTree tree; LeafNode* leaf = tree.touchLeaf(openvdb::Coord(0, 0, 0)); LeafNode* leaf2 = tree.touchLeaf(openvdb::Coord(0, 8, 0)); // create a descriptor using Descriptor = AttributeSet::Descriptor; Descriptor::Inserter names; names.add("density", AttributeS::attributeType()); Descriptor::Ptr descrA = Descriptor::create(AttributeVec3s::attributeType()); // initialize attributes using this descriptor leaf->initializeAttributes(descrA, /*arrayLength=*/100); leaf2->initializeAttributes(descrA, /*arrayLength=*/50); // copy the PointDataTree and ensure that descriptors are shared PointDataTree tree2(tree); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), openvdb::Index32(2)); descrA->setGroup("test", size_t(1)); PointDataTree::LeafCIter iter2 = tree2.cbeginLeaf(); CPPUNIT_ASSERT(iter2->attributeSet().descriptor().hasGroup("test")); ++iter2; CPPUNIT_ASSERT(iter2->attributeSet().descriptor().hasGroup("test")); // call makeDescriptorUnique and ensure that descriptors are no longer shared Descriptor::Ptr newDescriptor = makeDescriptorUnique(tree2); CPPUNIT_ASSERT(newDescriptor); descrA->setGroup("test2", size_t(2)); iter2 = tree2.cbeginLeaf(); CPPUNIT_ASSERT(!iter2->attributeSet().descriptor().hasGroup("test2")); ++iter2; CPPUNIT_ASSERT(!iter2->attributeSet().descriptor().hasGroup("test2")); } CPPUNIT_TEST_SUITE_REGISTRATION(TestPointDataLeaf); // Copyright (c) 2012-2017 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.cc0000644000000000000000000003152013200122377015303 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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; for (int i=0; i<3; ++i) { CPPUNIT_ASSERT_EQUAL(Coord::min()[i], std::numeric_limits::min()); CPPUNIT_ASSERT_EQUAL(Coord::max()[i], std::numeric_limits::max()); } Coord xyz(-1, 2, 4); Coord xyz2 = -xyz; CPPUNIT_ASSERT_EQUAL(Coord(1, -2, -4), xyz2); CPPUNIT_ASSERT_EQUAL(Coord(1, 2, 4), openvdb::math::Abs(xyz)); 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()); } {// Construct bbox from components of min and max const openvdb::Coord min(-1,-2,30), max(20,30,55); openvdb::CoordBBox b(min[0], min[1], min[2], max[0], max[1], max[2]); 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. using Int32 = openvdb::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()); } {// ZYX Iterator 1 const openvdb::Coord min(-1,-2,3), max(2,3,5); const openvdb::CoordBBox b(min, max); const size_t count = b.volume(); size_t n = 0; openvdb::CoordBBox::ZYXIterator ijk(b); for (int i=min[0]; i<=max[0]; ++i) { for (int j=min[1]; j<=max[1]; ++j) { for (int k=min[2]; k<=max[2]; ++k, ++ijk, ++n) { CPPUNIT_ASSERT(ijk); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(i,j,k), *ijk); } } } CPPUNIT_ASSERT_EQUAL(count, n); CPPUNIT_ASSERT(!ijk); ++ijk; CPPUNIT_ASSERT(!ijk); } {// ZYX Iterator 2 const openvdb::Coord min(-1,-2,3), max(2,3,5); const openvdb::CoordBBox b(min, max); const size_t count = b.volume(); size_t n = 0; openvdb::Coord::ValueType unused = 0; for (const auto& ijk: b) { unused += ijk[0]; CPPUNIT_ASSERT(++n <= count); } CPPUNIT_ASSERT_EQUAL(count, n); } {// XYZ Iterator 1 const openvdb::Coord min(-1,-2,3), max(2,3,5); const openvdb::CoordBBox b(min, max); const size_t count = b.volume(); size_t n = 0; openvdb::CoordBBox::XYZIterator ijk(b); for (int k=min[2]; k<=max[2]; ++k) { for (int j=min[1]; j<=max[1]; ++j) { for (int i=min[0]; i<=max[0]; ++i, ++ijk, ++n) { CPPUNIT_ASSERT( ijk ); CPPUNIT_ASSERT_EQUAL( openvdb::Coord(i,j,k), *ijk ); } } } CPPUNIT_ASSERT_EQUAL(count, n); CPPUNIT_ASSERT( !ijk ); ++ijk; CPPUNIT_ASSERT( !ijk ); } {// XYZ Iterator 2 const openvdb::Coord min(-1,-2,3), max(2,3,5); const openvdb::CoordBBox b(min, max); const size_t count = b.volume(); size_t n = 0; for (auto ijk = b.beginXYZ(); ijk; ++ijk) { CPPUNIT_ASSERT( ++n <= count ); } CPPUNIT_ASSERT_EQUAL(count, n); } {// bit-wise operations const openvdb::Coord min(-1,-2,3), max(2,3,5); const openvdb::CoordBBox b(min, max); CPPUNIT_ASSERT_EQUAL(openvdb::CoordBBox(min>>1,max>>1), b>>size_t(1)); CPPUNIT_ASSERT_EQUAL(openvdb::CoordBBox(min>>3,max>>3), b>>size_t(3)); CPPUNIT_ASSERT_EQUAL(openvdb::CoordBBox(min<<1,max<<1), b< #include #include #include class TestPotentialFlow: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestPotentialFlow); CPPUNIT_TEST(testMask); CPPUNIT_TEST(testNeumannVelocities); CPPUNIT_TEST(testUniformStream); CPPUNIT_TEST(testFlowAroundSphere); CPPUNIT_TEST_SUITE_END(); void testMask(); void testNeumannVelocities(); void testUniformStream(); void testFlowAroundSphere(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPotentialFlow); void TestPotentialFlow::testMask() { using namespace openvdb; const float radius = 1.5f; const Vec3f center(0.0f, 0.0f, 0.0f); const float voxelSize = 0.25f; const float halfWidth = 3.0f; FloatGrid::Ptr sphere = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); const int dilation = 5; MaskGrid::Ptr mask = tools::createPotentialFlowMask(*sphere, dilation); MaskGrid::Ptr defaultMask = tools::createPotentialFlowMask(*sphere); CPPUNIT_ASSERT(*mask == *defaultMask); auto acc = mask->getAccessor(); // the isosurface of this sphere is at y = 6 // this mask forms a band dilated outwards from the isosurface by 5 voxels CPPUNIT_ASSERT(!acc.isValueOn(Coord(0, 5, 0))); CPPUNIT_ASSERT(acc.isValueOn(Coord(0, 6, 0))); CPPUNIT_ASSERT(acc.isValueOn(Coord(0, 10, 0))); CPPUNIT_ASSERT(!acc.isValueOn(Coord(0, 11, 0))); { // error on non-uniform voxel size FloatGrid::Ptr nonUniformSphere = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); math::Transform::Ptr nonUniformTransform(new math::Transform( math::MapBase::Ptr(new math::ScaleMap(Vec3d(0.1, 0.2, 0.3))))); nonUniformSphere->setTransform(nonUniformTransform); CPPUNIT_ASSERT_THROW(tools::createPotentialFlowMask(*nonUniformSphere, dilation), openvdb::ValueError); } // this is the minimum mask of one voxel either side of the isosurface mask = tools::createPotentialFlowMask(*sphere, 2); acc = mask->getAccessor(); CPPUNIT_ASSERT(!acc.isValueOn(Coord(0, 5, 0))); CPPUNIT_ASSERT(acc.isValueOn(Coord(0, 6, 0))); CPPUNIT_ASSERT(acc.isValueOn(Coord(0, 7, 0))); CPPUNIT_ASSERT(!acc.isValueOn(Coord(0, 8, 0))); // these should all produce the same masks as the dilation value is clamped MaskGrid::Ptr negativeMask = tools::createPotentialFlowMask(*sphere, -1); MaskGrid::Ptr zeroMask = tools::createPotentialFlowMask(*sphere, 0); MaskGrid::Ptr oneMask = tools::createPotentialFlowMask(*sphere, 1); CPPUNIT_ASSERT(*negativeMask == *mask); CPPUNIT_ASSERT(*zeroMask == *mask); CPPUNIT_ASSERT(*oneMask == *mask); } void TestPotentialFlow::testNeumannVelocities() { using namespace openvdb; const float radius = 1.5f; const Vec3f center(0.0f, 0.0f, 0.0f); const float voxelSize = 0.25f; const float halfWidth = 3.0f; FloatGrid::Ptr sphere = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); MaskGrid::Ptr domain = tools::createPotentialFlowMask(*sphere); { // test identical potential from a wind velocity supplied through grid or background value Vec3d windVelocityValue(0, 0, 10); Vec3dTree::Ptr windTree(new Vec3dTree(sphere->tree(), zeroVal(), TopologyCopy())); dilateVoxels(*windTree, 2, tools::NN_FACE_EDGE_VERTEX); windTree->voxelizeActiveTiles(); for (auto leaf = windTree->beginLeaf(); leaf; ++leaf) { for (auto iter = leaf->beginValueOn(); iter; ++iter) { iter.setValue(windVelocityValue); } } Vec3dGrid::Ptr windGrid(Vec3dGrid::create(windTree)); windGrid->setTransform(sphere->transform().copy()); auto windPotentialFromGrid = tools::createPotentialFlowNeumannVelocities( *sphere, *domain, windGrid, Vec3d(0)); CPPUNIT_ASSERT_EQUAL(windPotentialFromGrid->transform(), sphere->transform()); auto windPotentialFromBackground = tools::createPotentialFlowNeumannVelocities( *sphere, *domain, Vec3dGrid::Ptr(), windVelocityValue); auto accessor = windPotentialFromGrid->getConstAccessor(); auto accessor2 = windPotentialFromBackground->getConstAccessor(); CPPUNIT_ASSERT_EQUAL(windPotentialFromGrid->activeVoxelCount(), windPotentialFromBackground->activeVoxelCount()); for (auto leaf = windPotentialFromGrid->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT_EQUAL(accessor.isValueOn(iter.getCoord()), accessor2.isValueOn(iter.getCoord())); CPPUNIT_ASSERT_EQUAL(accessor.getValue(iter.getCoord()), accessor2.getValue(iter.getCoord())); } } // test potential from a wind velocity supplied through grid background value Vec3dTree::Ptr emptyWindTree( new Vec3dTree(sphere->tree(), windVelocityValue, TopologyCopy())); Vec3dGrid::Ptr emptyWindGrid(Vec3dGrid::create(emptyWindTree)); emptyWindGrid->setTransform(sphere->transform().copy()); auto windPotentialFromGridBackground = tools::createPotentialFlowNeumannVelocities( *sphere, *domain, emptyWindGrid, Vec3d(0)); CPPUNIT_ASSERT_EQUAL(windPotentialFromGridBackground->transform(), sphere->transform()); accessor = windPotentialFromGridBackground->getConstAccessor(); accessor2 = windPotentialFromBackground->getConstAccessor(); CPPUNIT_ASSERT_EQUAL(windPotentialFromGridBackground->activeVoxelCount(), windPotentialFromBackground->activeVoxelCount()); for (auto leaf = windPotentialFromGridBackground->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT_EQUAL(accessor.isValueOn(iter.getCoord()), accessor2.isValueOn(iter.getCoord())); CPPUNIT_ASSERT_EQUAL(accessor.getValue(iter.getCoord()), accessor2.getValue(iter.getCoord())); } } // test potential values are double when applying wind velocity // through grid and background values auto windPotentialFromBoth = tools::createPotentialFlowNeumannVelocities( *sphere, *domain, windGrid, windVelocityValue); tools::prune(windPotentialFromBoth->tree(), Vec3d(1e-3)); tools::prune(windPotentialFromBackground->tree(), Vec3d(1e-3)); accessor = windPotentialFromBoth->getConstAccessor(); accessor2 = windPotentialFromBackground->getConstAccessor(); for (auto leaf = windPotentialFromBoth->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT_EQUAL(accessor.isValueOn(iter.getCoord()), accessor2.isValueOn(iter.getCoord())); CPPUNIT_ASSERT_EQUAL(accessor.getValue(iter.getCoord()), accessor2.getValue(iter.getCoord()) * 2); } } CPPUNIT_ASSERT(*windPotentialFromBoth == *windPotentialFromBackground); } Vec3dGrid::Ptr zeroVelocity = Vec3dGrid::create(Vec3d(0)); { // error if grid is not a levelset FloatGrid::Ptr nonLevelSetSphere = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); nonLevelSetSphere->setGridClass(GRID_FOG_VOLUME); CPPUNIT_ASSERT_THROW(tools::createPotentialFlowNeumannVelocities( *nonLevelSetSphere, *domain, zeroVelocity, Vec3d(5)), openvdb::TypeError); } { // accept double level set grid DoubleGrid::Ptr doubleSphere = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); CPPUNIT_ASSERT_NO_THROW(tools::createPotentialFlowNeumannVelocities( *doubleSphere, *domain, zeroVelocity, Vec3d(5))); } { // zero boundary velocities and background velocity Vec3d zeroVelocityValue(zeroVal()); auto neumannVelocities = tools::createPotentialFlowNeumannVelocities( *sphere, *domain, zeroVelocity, zeroVelocityValue); CPPUNIT_ASSERT_EQUAL(neumannVelocities->activeVoxelCount(), Index64(0)); } } void TestPotentialFlow::testUniformStream() { // this unit test checks the scalar potential and velocity flow field // for a uniform stream which consists of a 100x100x100 cube of // neumann voxels with constant velocity (0, 0, 1) using namespace openvdb; auto transform = math::Transform::createLinearTransform(1.0); auto mask = MaskGrid::create(false); mask->setTransform(transform); auto maskAccessor = mask->getAccessor(); auto neumann = Vec3dGrid::create(Vec3d(0)); auto neumannAccessor = neumann->getAccessor(); for (int i = -50; i < 50; i++) { for (int j = -50; j < 50; j++) { for (int k = -50; k < 50; k++) { Coord ijk(i, j, k); maskAccessor.setValueOn(ijk, true); neumannAccessor.setValueOn(ijk, Vec3d(0, 0, 1)); } } } openvdb::math::pcg::State state = math::pcg::terminationDefaults(); state.iterations = 2000; state.absoluteError = 1e-8; auto potential = tools::computeScalarPotential(*mask, *neumann, state); // check convergence CPPUNIT_ASSERT(state.success); CPPUNIT_ASSERT(state.iterations > 0 && state.iterations < 1000); CPPUNIT_ASSERT(state.absoluteError < 1e-6); CPPUNIT_ASSERT_EQUAL(potential->activeVoxelCount(), mask->activeVoxelCount()); // for uniform flow along the z-axis, the scalar potential should be equal to the z co-ordinate for (auto leaf = potential->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { const double staggeredZ = iter.getCoord().z() + 0.5; CPPUNIT_ASSERT(math::isApproxEqual(iter.getValue(), staggeredZ, /*tolerance*/0.1)); } } auto flow = tools::computePotentialFlow(*potential, *neumann); CPPUNIT_ASSERT_EQUAL(flow->activeVoxelCount(), mask->activeVoxelCount()); // flow velocity should be equal to the input velocity (0, 0, 1) for (auto leaf = flow->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(math::isApproxEqual(iter.getValue().x(), 0.0, /*tolerance*/1e-6)); CPPUNIT_ASSERT(math::isApproxEqual(iter.getValue().y(), 0.0, /*tolerance*/1e-6)); CPPUNIT_ASSERT(math::isApproxEqual(iter.getValue().z(), 1.0, /*tolerance*/1e-6)); } } } void TestPotentialFlow::testFlowAroundSphere() { using namespace openvdb; const float radius = 1.5f; const Vec3f center(0.0f, 0.0f, 0.0f); const float voxelSize = 0.25f; const float halfWidth = 3.0f; const int dilation = 50; FloatGrid::Ptr sphere = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); MaskGrid::Ptr domain = tools::createPotentialFlowMask(*sphere, dilation); { // compute potential flow for a global wind velocity around a sphere Vec3f windVelocity(0, 0, 1); Vec3fGrid::Ptr neumann = tools::createPotentialFlowNeumannVelocities(*sphere, *domain, Vec3fGrid::Ptr(), windVelocity); openvdb::math::pcg::State state = math::pcg::terminationDefaults(); state.iterations = 2000; state.absoluteError = 1e-8; FloatGrid::Ptr potential = tools::computeScalarPotential(*domain, *neumann, state); // compute a laplacian of the potential within the domain (excluding neumann voxels) // and ensure it evaluates to zero auto mask = BoolGrid::create(/*background=*/false); mask->setTransform(potential->transform().copy()); mask->topologyUnion(*potential); auto dilatedSphereMask = tools::interiorMask(*sphere); tools::dilateActiveValues(dilatedSphereMask->tree(), 1); mask->topologyDifference(*dilatedSphereMask); FloatGrid::Ptr laplacian = tools::laplacian(*potential, *mask); for (auto leaf = laplacian->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(math::isApproxEqual(iter.getValue(), 0.0f, /*tolerance*/1e-3f)); } } Vec3fGrid::Ptr flowVel = tools::computePotentialFlow(*potential, *neumann); // compute the divergence of the flow velocity within the domain // (excluding neumann voxels and exterior voxels) // and ensure it evaluates to zero tools::erodeVoxels(mask->tree(), 2, tools::NN_FACE); FloatGrid::Ptr divergence = tools::divergence(*flowVel, *mask); for (auto leaf = divergence->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(math::isApproxEqual(iter.getValue(), 0.0f, /*tolerance*/0.1f)); } } // check the background velocity has been applied correctly Vec3fGrid::Ptr flowVelBackground = tools::computePotentialFlow(*potential, *neumann, windVelocity); CPPUNIT_ASSERT_EQUAL(flowVelBackground->activeVoxelCount(), flowVelBackground->activeVoxelCount()); auto maskAccessor = mask->getConstAccessor(); auto accessor = flowVel->getConstAccessor(); auto accessor2 = flowVelBackground->getConstAccessor(); for (auto leaf = flowVelBackground->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { // ignore values near the neumann boundary if (!maskAccessor.isValueOn(iter.getCoord())) continue; const Vec3f value1 = accessor.getValue(iter.getCoord()); const Vec3f value2 = accessor2.getValue(iter.getCoord()) + windVelocity; CPPUNIT_ASSERT(math::isApproxEqual(value1.x(), value2.x(), /*tolerance=*/1e-3f)); CPPUNIT_ASSERT(math::isApproxEqual(value1.y(), value2.y(), /*tolerance=*/1e-3f)); CPPUNIT_ASSERT(math::isApproxEqual(value1.z(), value2.z(), /*tolerance=*/1e-3f)); } } } { // check double-precision solve DoubleGrid::Ptr sphereDouble = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); Vec3d windVelocity(0, 0, 1); Vec3dGrid::Ptr neumann = tools::createPotentialFlowNeumannVelocities(*sphereDouble, *domain, Vec3dGrid::Ptr(), windVelocity); openvdb::math::pcg::State state = math::pcg::terminationDefaults(); state.iterations = 2000; state.absoluteError = 1e-8; DoubleGrid::Ptr potential = tools::computeScalarPotential(*domain, *neumann, state); CPPUNIT_ASSERT(potential); // compute a laplacian of the potential within the domain (excluding neumann voxels) // and ensure it evaluates to zero auto mask = BoolGrid::create(/*background=*/false); mask->setTransform(potential->transform().copy()); mask->topologyUnion(*potential); auto dilatedSphereMask = tools::interiorMask(*sphereDouble); tools::dilateActiveValues(dilatedSphereMask->tree(), 1); mask->topologyDifference(*dilatedSphereMask); DoubleGrid::Ptr laplacian = tools::laplacian(*potential, *mask); for (auto leaf = laplacian->tree().cbeginLeaf(); leaf; ++leaf) { for (auto iter = leaf->cbeginValueOn(); iter; ++iter) { CPPUNIT_ASSERT(math::isApproxEqual(iter.getValue(), 0.0, /*tolerance*/1e-5)); } } Vec3dGrid::Ptr flowVel = tools::computePotentialFlow(*potential, *neumann); CPPUNIT_ASSERT(flowVel); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000001467113200122377017171 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000003024213200122377020402 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 unittest/TestVolumeRayIntersector.cc /// @author Ken Museth #include #include #include #include #include #include #include #include #include #include #include #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(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t1); CPPUNIT_ASSERT(!inter.march(t0, t1)); } {//same as above but with dilation 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, 1); CPPUNIT_ASSERT(inter.setIndexRay(ray)); double t0=0, t1=0; CPPUNIT_ASSERT(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 0.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, t1); CPPUNIT_ASSERT(!inter.march(t0, t1)); } {//one single leaf node FloatGrid grid(0.0f); grid.tree().setValue(Coord(1,1,1), 1.0f); grid.tree().setValue(Coord(7,3,3), 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(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL( 9.0, t1); CPPUNIT_ASSERT(!inter.march(t0, t1)); } {//same as above but with dilation FloatGrid grid(0.0f); grid.tree().setValue(Coord(1,1,1), 1.0f); grid.tree().setValue(Coord(7,3,3), 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, 1); CPPUNIT_ASSERT(inter.setIndexRay(ray)); double t0=0, t1=0; CPPUNIT_ASSERT(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, t1); CPPUNIT_ASSERT(!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(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, t1); CPPUNIT_ASSERT(!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(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, t1); CPPUNIT_ASSERT(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(25.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(33.0, t1); CPPUNIT_ASSERT(!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(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, t1); CPPUNIT_ASSERT(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(25.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(41.0, t1); CPPUNIT_ASSERT(!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)); std::vector list; inter.hits(list); CPPUNIT_ASSERT(list.size() == 2); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, list[0].t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, list[0].t1); ASSERT_DOUBLES_APPROX_EQUAL(25.0, list[1].t0); ASSERT_DOUBLES_APPROX_EQUAL(41.0, list[1].t1); } {//same as above but now with std::deque instead of std::vector 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)); std::deque list; inter.hits(list); CPPUNIT_ASSERT(list.size() == 2); ASSERT_DOUBLES_APPROX_EQUAL( 1.0, list[0].t0); ASSERT_DOUBLES_APPROX_EQUAL(17.0, list[0].t1); ASSERT_DOUBLES_APPROX_EQUAL(25.0, list[1].t0); ASSERT_DOUBLES_APPROX_EQUAL(41.0, list[1].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(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(18.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(26.0, t1); CPPUNIT_ASSERT(inter.march(t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(34.0, t0); ASSERT_DOUBLES_APPROX_EQUAL(50.0, t1); CPPUNIT_ASSERT(!inter.march(t0, t1)); } {// Test submitted by "Trevor" @ GitHub FloatGrid::Ptr grid = createGrid(0.0f); grid->tree().setValue(Coord(0,0,0), 1.0f); tools::dilateVoxels(grid->tree()); tools::VolumeRayIntersector inter(*grid); //std::cerr << "BBox = " << inter.bbox() << std::endl; const Vec3T eye(-0.25, -0.25, 10.0); const Vec3T dir( 0.00, 0.00, -1.0); const RayT ray(eye, dir); CPPUNIT_ASSERT(inter.setIndexRay(ray));// hits bbox double t0=0, t1=0; CPPUNIT_ASSERT(!inter.march(t0, t1));// misses leafs } {// Test submitted by "Trevor" @ GitHub FloatGrid::Ptr grid = createGrid(0.0f); grid->tree().setValue(Coord(0,0,0), 1.0f); tools::dilateVoxels(grid->tree()); tools::VolumeRayIntersector inter(*grid); //GridPtrVec grids; //grids.push_back(grid); //io::File vdbfile("trevor_v1.vdb"); //vdbfile.write(grids); //std::cerr << "BBox = " << inter.bbox() << std::endl; const Vec3T eye(0.75, 0.75, 10.0); const Vec3T dir( 0.00, 0.00, -1.0); const RayT ray(eye, dir); CPPUNIT_ASSERT(inter.setIndexRay(ray));// hits bbox double t0=0, t1=0; CPPUNIT_ASSERT(inter.march(t0, t1));// misses leafs //std::cerr << "t0=" << t0 << " t1=" << t1 << std::endl; } {// Test derived from the test submitted by "Trevor" @ GitHub FloatGrid grid(0.0f); grid.fill(math::CoordBBox(Coord(-1,-1,-1),Coord(1,1,1)), 1.0f); tools::VolumeRayIntersector inter(grid); //std::cerr << "BBox = " << inter.bbox() << std::endl; const Vec3T eye(-0.25, -0.25, 10.0); const Vec3T dir( 0.00, 0.00, -1.0); const RayT ray(eye, dir); CPPUNIT_ASSERT(inter.setIndexRay(ray));// hits bbox double t0=0, t1=0; CPPUNIT_ASSERT(inter.march(t0, t1));// hits leafs //std::cerr << "t0=" << t0 << " t1=" << t1 << std::endl; } } // Copyright (c) 2012-2017 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/TestLeafOrigin.cc0000644000000000000000000001104313200122377016252 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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/TestLeafMask.cc0000644000000000000000000005077013200122377015730 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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() class TestLeafMask: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestLeafMask); 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(testMedian); CPPUNIT_TEST(testTopologyTree); //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 testMedian(); void testTopologyTree(); //void testFilter(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLeafMask); typedef openvdb::tree::LeafNode LeafType; //////////////////////////////////////// void TestLeafMask::testGetValue() { { LeafType leaf1(openvdb::Coord(0, 0, 0)); openvdb::tree::LeafNode leaf2(openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT( leaf1.memUsage() < leaf2.memUsage() ); //std::cerr << "\nLeafNode uses " << leaf1.memUsage() << " bytes" << std::endl; //std::cerr << "LeafNode uses " << leaf2.memUsage() << " bytes" << std::endl; } { LeafType leaf(openvdb::Coord(0, 0, 0), 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), true); for (openvdb::Index n = 0; n < leaf.numValues(); ++n) { CPPUNIT_ASSERT_EQUAL(true, leaf.getValue(leaf.offsetToLocalCoord(n))); } } {// test Buffer::data() LeafType leaf(openvdb::Coord(0, 0, 0), false); leaf.fill(true); LeafType::Buffer::WordType* w = leaf.buffer().data(); for (openvdb::Index n = 0; n < LeafType::Buffer::WORD_COUNT; ++n) { CPPUNIT_ASSERT_EQUAL(~LeafType::Buffer::WordType(0), w[n]); } } {// test const Buffer::data() LeafType leaf(openvdb::Coord(0, 0, 0), false); leaf.fill(true); const LeafType& cleaf = leaf; const LeafType::Buffer::WordType* w = cleaf.buffer().data(); for (openvdb::Index n = 0; n < LeafType::Buffer::WORD_COUNT; ++n) { CPPUNIT_ASSERT_EQUAL(~LeafType::Buffer::WordType(0), w[n]); } } } void TestLeafMask::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, true); CPPUNIT_ASSERT(leaf.isValueOn(xyz)); leaf.setValueOn(xyz, false); // value and state are the same! 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 TestLeafMask::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 TestLeafMask::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 TestLeafMask::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 TestLeafMask::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, 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);//values and states coinside leaf2.setValueOn(Coord(0, 0, 1)); CPPUNIT_ASSERT(leaf != leaf2);//values and states coinside } {// test LeafNode::operator==() LeafType leaf1(Coord(0 , 0, 0), true); // true and inactive LeafType leaf2(Coord(1 , 0, 0), true); // true and inactive LeafType leaf3(Coord(LeafType::DIM, 0, 0), true); // true and inactive LeafType leaf4(Coord(0 , 0, 0), true, true);//true and active CPPUNIT_ASSERT(leaf1 == leaf2); CPPUNIT_ASSERT(leaf1 != leaf3); CPPUNIT_ASSERT(leaf2 != leaf3); CPPUNIT_ASSERT(leaf1 == leaf4); CPPUNIT_ASSERT(leaf2 == leaf4); CPPUNIT_ASSERT(leaf3 != leaf4); } } void TestLeafMask::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 TestLeafMask::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 TestLeafMask::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 TestLeafMask::testTopologyCopy() { using openvdb::Coord; // LeafNode having the same Log2Dim as LeafType typedef LeafType::ValueConverter::Type FloatLeafType; FloatLeafType fleaf(Coord(10, 20, 30), -1.0); std::set coords; for (openvdb::Index n = 0; n < fleaf.numValues(); n += 10) { Coord xyz = fleaf.offsetToGlobalCoord(n); fleaf.setValueOn(xyz, float(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 TestLeafMask::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 TestLeafMask::testCombine() { struct Local { static void op(openvdb::CombineArgs& args) { args.setResult(args.aIsActive() ^ args.bIsActive());// state = value } }; 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 TestLeafMask::testTopologyTree() { 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(Coord(128),//dim Vec3f(0, 0, 0),//center 5,//radius *inGrid, unittest_util::SPHERE_DENSE); #endif const Index64 floatTreeMem = inTree.memUsage(), floatTreeLeafCount = inTree.leafCount(), floatTreeVoxelCount = inTree.activeVoxelCount(); TreeBase::Ptr outTree(new TopologyTree(inTree, false, true, TopologyCopy())); CPPUNIT_ASSERT(outTree.get() != NULL); TopologyGrid::Ptr outGrid = TopologyGrid::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("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 TestLeafMask::testMedian() { using namespace openvdb; LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/false); bool state = false; CPPUNIT_ASSERT_EQUAL(Index(0), leaf.medianOn(state)); CPPUNIT_ASSERT(state == true); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); leaf.setValue(Coord(0,0,0), true); CPPUNIT_ASSERT_EQUAL(Index(1), leaf.medianOn(state)); CPPUNIT_ASSERT(state == true); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-1, leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); leaf.setValue(Coord(0,0,1), true); CPPUNIT_ASSERT_EQUAL(Index(2), leaf.medianOn(state)); CPPUNIT_ASSERT(state == true); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-2, leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); leaf.setValue(Coord(5,0,1), true); CPPUNIT_ASSERT_EQUAL(Index(3), leaf.medianOn(state)); CPPUNIT_ASSERT(state == true); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-3, leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); leaf.fill(false, false); CPPUNIT_ASSERT_EQUAL(Index(0), leaf.medianOn(state)); CPPUNIT_ASSERT(state == true); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); for (Index i=0; itreePtr(); // CPPUNIT_ASSERT(tree.get() != NULL); // grid->setName("filtered"); // unittest_util::makeSphere(Coord(32),// dim // Vec3f(0, 0, 0),// center // 10,// radius // *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("TestLeafMask::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-2017 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/TestIndexFilter.cc0000644000000000000000000007003013200122377016451 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 using namespace openvdb; using namespace openvdb::points; class TestIndexFilter: public CppUnit::TestCase { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestIndexFilter); CPPUNIT_TEST(testMultiGroupFilter); CPPUNIT_TEST(testRandomLeafFilter); CPPUNIT_TEST(testAttributeHashFilter); CPPUNIT_TEST(testLevelSetFilter); CPPUNIT_TEST(testBBoxFilter); CPPUNIT_TEST(testBinaryFilter); CPPUNIT_TEST_SUITE_END(); void testMultiGroupFilter(); void testRandomLeafFilter(); void testAttributeHashFilter(); void testLevelSetFilter(); void testBBoxFilter(); void testBinaryFilter(); }; // class TestIndexFilter CPPUNIT_TEST_SUITE_REGISTRATION(TestIndexFilter); //////////////////////////////////////// struct OriginLeaf { OriginLeaf(const openvdb::Coord& _leafOrigin, const size_t _size = size_t(0)): leafOrigin(_leafOrigin), size(_size) { } openvdb::Coord origin() const { return leafOrigin; } size_t pointCount() const { return size; } const openvdb::Coord leafOrigin; const size_t size; }; struct SimpleIter { SimpleIter() : i(0) { } int operator*() const { return i; } void operator++() { i++; } openvdb::Coord getCoord() const { return coord; } int i; openvdb::Coord coord; }; template class ThresholdFilter { public: ThresholdFilter(const int threshold) : mThreshold(threshold) { } static bool initialized() { return true; } template void reset(const LeafT&) { } template bool valid(const IterT& iter) const { return LessThan ? *iter < mThreshold : *iter > mThreshold; } private: const int mThreshold; }; // class ThresholdFilter /// @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 define 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) { using ValueT = typename GridType::ValueType; const ValueT zero = openvdb::zeroVal(); typename GridType::Accessor acc = grid.getAccessor(); openvdb::Coord xyz; for (xyz[0]=0; xyz[0] bool multiGroupMatches( const LeafT& leaf, const Index32 size, const std::vector& include, const std::vector& exclude, const std::vector& indices) { using IndexGroupIter = IndexIter; ValueVoxelCIter indexIter(0, size); MultiGroupFilter filter(include, exclude, leaf.attributeSet()); filter.reset(leaf); IndexGroupIter iter(indexIter, filter); for (unsigned i = 0; i < indices.size(); ++i, ++iter) { if (!iter) return false; if (*iter != Index32(indices[i])) return false; } return !iter; } void TestIndexFilter::testMultiGroupFilter() { using LeafNode = PointDataTree::LeafNodeType; using AttributeVec3f = TypedAttributeArray; PointDataTree tree; LeafNode* leaf = tree.touchLeaf(openvdb::Coord(0, 0, 0)); using Descriptor = AttributeSet::Descriptor; Descriptor::Ptr descriptor = Descriptor::create(AttributeVec3f::attributeType()); const Index size = 5; leaf->initializeAttributes(descriptor, size); appendGroup(tree, "even"); appendGroup(tree, "odd"); appendGroup(tree, "all"); appendGroup(tree, "first"); { // construction, copy construction std::vector includeGroups; std::vector excludeGroups; MultiGroupFilter filter(includeGroups, excludeGroups, leaf->attributeSet()); CPPUNIT_ASSERT(!filter.initialized()); MultiGroupFilter filter2 = filter; CPPUNIT_ASSERT(!filter2.initialized()); filter.reset(*leaf); CPPUNIT_ASSERT(filter.initialized()); MultiGroupFilter filter3 = filter; CPPUNIT_ASSERT(filter3.initialized()); } // group population { // even GroupWriteHandle groupHandle = leaf->groupWriteHandle("even"); groupHandle.set(0, true); groupHandle.set(2, true); groupHandle.set(4, true); } { // odd GroupWriteHandle groupHandle = leaf->groupWriteHandle("odd"); groupHandle.set(1, true); groupHandle.set(3, true); } setGroup(tree, "all", true); { // first GroupWriteHandle groupHandle = leaf->groupWriteHandle("first"); groupHandle.set(0, true); } // test multi group iteration { // all (implicit, no include or exclude) std::vector include; std::vector exclude; std::vector indices{0, 1, 2, 3, 4}; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // all include std::vector include{"all"}; std::vector exclude; std::vector indices{0, 1, 2, 3, 4}; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // all exclude std::vector include; std::vector exclude{"all"}; std::vector indices; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // all include and exclude std::vector include{"all"}; std::vector exclude{"all"}; std::vector indices; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // even include std::vector include{"even"}; std::vector exclude; std::vector indices{0, 2, 4}; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // odd include std::vector include{"odd"}; std::vector exclude; std::vector indices{1, 3}; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // odd include and exclude std::vector include{"odd"}; std::vector exclude{"odd"}; std::vector indices; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // odd and first include std::vector include{"odd", "first"}; std::vector exclude; std::vector indices{0, 1, 3}; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // even include, first exclude std::vector include{"even"}; std::vector exclude{"first"}; std::vector indices{2, 4}; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // all include, first and odd exclude std::vector include{"all"}; std::vector exclude{"first", "odd"}; std::vector indices{2, 4}; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } { // odd and first include, even exclude std::vector include{"odd", "first"}; std::vector exclude{"even"}; std::vector indices{1, 3}; CPPUNIT_ASSERT(multiGroupMatches(*leaf, size, include, exclude, indices)); } } void TestIndexFilter::testRandomLeafFilter() { { // generateRandomSubset std::vector values = index_filter_internal::generateRandomSubset( /*seed*/unsigned(0), 1, 20); CPPUNIT_ASSERT_EQUAL(values.size(), size_t(1)); // different seed std::vector values2 = index_filter_internal::generateRandomSubset( /*seed*/unsigned(1), 1, 20); CPPUNIT_ASSERT_EQUAL(values2.size(), size_t(1)); CPPUNIT_ASSERT(values[0] != values2[0]); // different integer type std::vector values3 = index_filter_internal::generateRandomSubset( /*seed*/unsigned(0), 1, 20); CPPUNIT_ASSERT_EQUAL(values3.size(), size_t(1)); CPPUNIT_ASSERT(values[0] == values3[0]); // different random number generator values = index_filter_internal::generateRandomSubset( /*seed*/unsigned(1), 1, 20); CPPUNIT_ASSERT_EQUAL(values.size(), size_t(1)); CPPUNIT_ASSERT(values[0] != values2[0]); // no values values = index_filter_internal::generateRandomSubset( /*seed*/unsigned(0), 0, 20); CPPUNIT_ASSERT_EQUAL(values.size(), size_t(0)); // all values values = index_filter_internal::generateRandomSubset( /*seed*/unsigned(0), 1000, 1000); CPPUNIT_ASSERT_EQUAL(values.size(), size_t(1000)); // ensure all numbers are represented std::sort(values.begin(), values.end()); for (int i = 0; i < 1000; i++) { CPPUNIT_ASSERT_EQUAL(values[i], i); } } { // RandomLeafFilter using RandFilter = RandomLeafFilter; PointDataTree tree; RandFilter filter(tree, 0); filter.mLeafMap[Coord(0, 0, 0)] = std::make_pair(0, 10); filter.mLeafMap[Coord(0, 0, 8)] = std::make_pair(1, 1); filter.mLeafMap[Coord(0, 8, 0)] = std::make_pair(2, 50); { // construction, copy construction CPPUNIT_ASSERT(filter.initialized()); RandFilter filter2 = filter; CPPUNIT_ASSERT(filter2.initialized()); filter.reset(OriginLeaf(Coord(0, 0, 0), 10)); CPPUNIT_ASSERT(filter.initialized()); RandFilter filter3 = filter; CPPUNIT_ASSERT(filter3.initialized()); } { // all 10 values filter.reset(OriginLeaf(Coord(0, 0, 0), 10)); std::vector values; for (SimpleIter iter; *iter < 100; ++iter) { if (filter.valid(iter)) values.push_back(*iter); } CPPUNIT_ASSERT_EQUAL(values.size(), size_t(10)); for (int i = 0; i < 10; i++) { CPPUNIT_ASSERT_EQUAL(values[i], i); } } { // 50 of 100 filter.reset(OriginLeaf(Coord(0, 8, 0), 100)); std::vector values; for (SimpleIter iter; *iter < 100; ++iter) { if (filter.valid(iter)) values.push_back(*iter); } CPPUNIT_ASSERT_EQUAL(values.size(), size_t(50)); // ensure no duplicates std::sort(values.begin(), values.end()); auto it = std::adjacent_find(values.begin(), values.end()); CPPUNIT_ASSERT(it == values.end()); } } } inline void setId(PointDataTree& tree, const size_t index, const std::vector& ids) { int offset = 0; for (auto leafIter = tree.beginLeaf(); leafIter; ++leafIter) { auto id = AttributeWriteHandle::create(leafIter->attributeArray(index)); for (auto iter = leafIter->beginIndexAll(); iter; ++iter) { if (offset >= int(ids.size())) throw std::runtime_error("Out of range"); id->set(*iter, ids[offset++]); } } } void TestIndexFilter::testAttributeHashFilter() { std::vector positions{{1, 1, 1}, {2, 2, 2}, {11, 11, 11}, {12, 12, 12}}; const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); // four points, two leafs CPPUNIT_ASSERT_EQUAL(tree.leafCount(), Index32(2)); appendAttribute(tree, "id"); const size_t index = tree.cbeginLeaf()->attributeSet().descriptor().find("id"); // ascending integers, block one std::vector ids{1, 2, 3, 4}; setId(tree, index, ids); using HashFilter = AttributeHashFilter; { // construction, copy construction HashFilter filter(index, 0.0f); CPPUNIT_ASSERT(!filter.initialized()); HashFilter filter2 = filter; CPPUNIT_ASSERT(!filter2.initialized()); filter.reset(*tree.cbeginLeaf()); CPPUNIT_ASSERT(filter.initialized()); HashFilter filter3 = filter; CPPUNIT_ASSERT(filter3.initialized()); } { // zero percent HashFilter filter(index, 0.0f); auto leafIter = tree.cbeginLeaf(); auto indexIter = leafIter->beginIndexAll(); filter.reset(*leafIter); CPPUNIT_ASSERT(!filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!indexIter); ++leafIter; indexIter = leafIter->beginIndexAll(); filter.reset(*leafIter); CPPUNIT_ASSERT(!filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!indexIter); } { // one hundred percent HashFilter filter(index, 100.0f); auto leafIter = tree.cbeginLeaf(); auto indexIter = leafIter->beginIndexAll(); filter.reset(*leafIter); CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!indexIter); ++leafIter; indexIter = leafIter->beginIndexAll(); filter.reset(*leafIter); CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!indexIter); } { // fifty percent HashFilter filter(index, 50.0f); auto leafIter = tree.cbeginLeaf(); auto indexIter = leafIter->beginIndexAll(); filter.reset(*leafIter); CPPUNIT_ASSERT(!filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!indexIter); ++leafIter; indexIter = leafIter->beginIndexAll(); filter.reset(*leafIter); CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!indexIter); } { // fifty percent, new seed HashFilter filter(index, 50.0f, /*seed=*/100); auto leafIter = tree.cbeginLeaf(); auto indexIter = leafIter->beginIndexAll(); filter.reset(*leafIter); CPPUNIT_ASSERT(!filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!indexIter); ++leafIter; indexIter = leafIter->beginIndexAll(); filter.reset(*leafIter); CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(filter.valid(indexIter)); ++indexIter; CPPUNIT_ASSERT(!indexIter); } } void TestIndexFilter::testLevelSetFilter() { // create a point grid PointDataGrid::Ptr points; { std::vector positions{{1, 1, 1}, {1, 2, 1}, {10.1f, 10, 1}}; const double voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); points = createPointDataGrid(positions, *transform); } // create a sphere levelset FloatGrid::Ptr sphere; { double voxelSize = 0.5; sphere = FloatGrid::create(/*backgroundValue=*/5.0); sphere->setTransform(math::Transform::createLinearTransform(voxelSize)); const openvdb::Coord dim(10, 10, 10); const openvdb::Vec3f center(0.0f, 0.0f, 0.0f); const float radius = 2; makeSphere(dim, center, radius, *sphere); } using LSFilter = LevelSetFilter; { // construction, copy construction LSFilter filter(*sphere, points->transform(), -4.0f, 4.0f); CPPUNIT_ASSERT(!filter.initialized()); LSFilter filter2 = filter; CPPUNIT_ASSERT(!filter2.initialized()); filter.reset(* points->tree().cbeginLeaf()); CPPUNIT_ASSERT(filter.initialized()); LSFilter filter3 = filter; CPPUNIT_ASSERT(filter3.initialized()); } { // capture both points near origin LSFilter filter(*sphere, points->transform(), -4.0f, 4.0f); auto leafIter = points->tree().cbeginLeaf(); auto iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(filter.valid(iter)); ++iter; CPPUNIT_ASSERT(filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); ++leafIter; iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT(!filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); } { // capture just the inner-most point LSFilter filter(*sphere, points->transform(), -0.3f, -0.25f); auto leafIter = points->tree().cbeginLeaf(); auto iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); ++leafIter; iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT(!filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); } { // capture everything but the second point (min > max) LSFilter filter(*sphere, points->transform(), -0.25f, -0.3f); auto leafIter = points->tree().cbeginLeaf(); auto iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(!filter.valid(iter)); ++iter; CPPUNIT_ASSERT(filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); ++leafIter; iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT(filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); } { std::vector positions{{1, 1, 1}, {1, 2, 1}, {10.1f, 10, 1}}; const double voxelSize(0.25); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); points = createPointDataGrid(positions, *transform); } { double voxelSize = 1.0; sphere = FloatGrid::create(/*backgroundValue=*/5.0); sphere->setTransform(math::Transform::createLinearTransform(voxelSize)); const openvdb::Coord dim(40, 40, 40); const openvdb::Vec3f center(10.0f, 10.0f, 0.1f); const float radius = 0.2f; makeSphere(dim, center, radius, *sphere); } { // capture only the last point using a different transform and a new sphere LSFilter filter(*sphere, points->transform(), 0.5f, 1.0f); auto leafIter = points->tree().cbeginLeaf(); auto iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(!filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); ++leafIter; iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(!filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); ++leafIter; iter = leafIter->beginIndexOn(); filter.reset(*leafIter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT(filter.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); } } void TestIndexFilter::testBBoxFilter() { std::vector positions{{1, 1, 1}, {1, 2, 1}, {10.1f, 10, 1}}; const float voxelSize(0.5); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); // check one leaf per point CPPUNIT_ASSERT_EQUAL(tree.leafCount(), Index32(2)); // build some bounding box filters to test BBoxFilter filter1(*transform, BBoxd({0.5, 0.5, 0.5}, {1.5, 1.5, 1.5})); BBoxFilter filter2(*transform, BBoxd({0.5, 0.5, 0.5}, {1.5, 2.01, 1.5})); BBoxFilter filter3(*transform, BBoxd({0.5, 0.5, 0.5}, {11, 11, 1.5})); BBoxFilter filter4(*transform, BBoxd({-10, 0, 0}, {11, 1.2, 1.2})); { // construction, copy construction CPPUNIT_ASSERT(!filter1.initialized()); BBoxFilter filter5 = filter1; CPPUNIT_ASSERT(!filter5.initialized()); filter1.reset(*tree.cbeginLeaf()); CPPUNIT_ASSERT(filter1.initialized()); BBoxFilter filter6 = filter1; CPPUNIT_ASSERT(filter6.initialized()); } // leaf 1 auto leafIter = tree.cbeginLeaf(); { auto iter(leafIter->beginIndexOn()); // point 1 filter1.reset(*leafIter); CPPUNIT_ASSERT(filter1.valid(iter)); filter2.reset(*leafIter); CPPUNIT_ASSERT(filter2.valid(iter)); filter3.reset(*leafIter); CPPUNIT_ASSERT(filter3.valid(iter)); filter4.reset(*leafIter); CPPUNIT_ASSERT(filter4.valid(iter)); ++iter; // point 2 filter1.reset(*leafIter); CPPUNIT_ASSERT(!filter1.valid(iter)); filter2.reset(*leafIter); CPPUNIT_ASSERT(filter2.valid(iter)); filter3.reset(*leafIter); CPPUNIT_ASSERT(filter3.valid(iter)); filter4.reset(*leafIter); CPPUNIT_ASSERT(!filter4.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); } ++leafIter; // leaf 2 { auto iter(leafIter->beginIndexOn()); // point 3 filter1.reset(*leafIter); CPPUNIT_ASSERT(!filter1.valid(iter)); filter2.reset(*leafIter); CPPUNIT_ASSERT(!filter2.valid(iter)); filter3.reset(*leafIter); CPPUNIT_ASSERT(filter3.valid(iter)); filter4.reset(*leafIter); CPPUNIT_ASSERT(!filter4.valid(iter)); ++iter; CPPUNIT_ASSERT(!iter); } } struct NeedsInitializeFilter { inline bool initialized() const { return mInitialized; } template void reset(const LeafT&) { mInitialized = true; } private: bool mInitialized = false; }; void TestIndexFilter::testBinaryFilter() { { // construction, copy construction using InitializeBinaryFilter = BinaryFilter; NeedsInitializeFilter needs1; NeedsInitializeFilter needs2; InitializeBinaryFilter filter(needs1, needs2); CPPUNIT_ASSERT(!filter.initialized()); InitializeBinaryFilter filter2 = filter; CPPUNIT_ASSERT(!filter2.initialized()); filter.reset(OriginLeaf(Coord(0, 0, 0))); CPPUNIT_ASSERT(filter.initialized()); InitializeBinaryFilter filter3 = filter; CPPUNIT_ASSERT(filter3.initialized()); } using LessThanFilter = ThresholdFilter; using GreaterThanFilter = ThresholdFilter; { // less than LessThanFilter filter(5); filter.reset(OriginLeaf(Coord(0, 0, 0))); std::vector values; for (SimpleIter iter; *iter < 100; ++iter) { if (filter.valid(iter)) values.push_back(*iter); } CPPUNIT_ASSERT_EQUAL(values.size(), size_t(5)); for (int i = 0; i < 5; i++) { CPPUNIT_ASSERT_EQUAL(values[i], i); } } { // greater than GreaterThanFilter filter(94); filter.reset(OriginLeaf(Coord(0, 0, 0))); std::vector values; for (SimpleIter iter; *iter < 100; ++iter) { if (filter.valid(iter)) values.push_back(*iter); } CPPUNIT_ASSERT_EQUAL(values.size(), size_t(5)); int offset = 0; for (int i = 95; i < 100; i++) { CPPUNIT_ASSERT_EQUAL(values[offset++], i); } } { // binary and using RangeFilter = BinaryFilter; RangeFilter filter(LessThanFilter(55), GreaterThanFilter(45)); filter.reset(OriginLeaf(Coord(0, 0, 0))); std::vector values; for (SimpleIter iter; *iter < 100; ++iter) { if (filter.valid(iter)) values.push_back(*iter); } CPPUNIT_ASSERT_EQUAL(values.size(), size_t(9)); int offset = 0; for (int i = 46; i < 55; i++) { CPPUNIT_ASSERT_EQUAL(values[offset++], i); } } { // binary or using HeadTailFilter = BinaryFilter; HeadTailFilter filter(LessThanFilter(5), GreaterThanFilter(95)); filter.reset(OriginLeaf(Coord(0, 0, 0))); std::vector values; for (SimpleIter iter; *iter < 100; ++iter) { if (filter.valid(iter)) values.push_back(*iter); } CPPUNIT_ASSERT_EQUAL(values.size(), size_t(9)); int offset = 0; for (int i = 0; i < 5; i++) { CPPUNIT_ASSERT_EQUAL(values[offset++], i); } for (int i = 96; i < 100; i++) { CPPUNIT_ASSERT_EQUAL(values[offset++], i); } } } // Copyright (c) 2012-2017 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/TestDenseSparseTools.cc0000644000000000000000000003102313200122377017470 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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" class TestDenseSparseTools: public CppUnit::TestCase { public: virtual void setUp(); virtual void tearDown() { if (mDense) delete mDense;} CPPUNIT_TEST_SUITE(TestDenseSparseTools); CPPUNIT_TEST(testExtractSparseFloatTree); CPPUNIT_TEST(testExtractSparseBoolTree); CPPUNIT_TEST(testExtractSparseAltDenseLayout); CPPUNIT_TEST(testExtractSparseMaskedTree); CPPUNIT_TEST(testDenseTransform); CPPUNIT_TEST(testOver); CPPUNIT_TEST_SUITE_END(); void testExtractSparseFloatTree(); void testExtractSparseBoolTree(); void testExtractSparseAltDenseLayout(); void testExtractSparseMaskedTree(); void testDenseTransform(); void testOver(); private: openvdb::tools::Dense* mDense; openvdb::math::Coord mijk; }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDenseSparseTools); void TestDenseSparseTools::setUp() { namespace vdbmath = openvdb::math; // Domain for the dense grid vdbmath::CoordBBox domain(vdbmath::Coord(-100, -16, 12), vdbmath::Coord( 90, 103, 100)); // Create dense grid, filled with 0.f mDense = new openvdb::tools::Dense(domain, 0.f); // Insert non-zero values mijk[0] = 1; mijk[1] = -2; mijk[2] = 14; } namespace { // Simple Rule for extracting data greater than a determined mMaskValue // and producing a tree that holds type ValueType namespace vdbmath = openvdb::math; class FloatRule { public: // Standard tree type (e.g. BoolTree or FloatTree in openvdb.h) typedef openvdb::FloatTree ResultTreeType; typedef ResultTreeType::LeafNodeType ResultLeafNodeType; typedef float ResultValueType; typedef float DenseValueType; FloatRule(const DenseValueType& value): mMaskValue(value){} template void operator()(const DenseValueType& a, const IndexOrCoord& offset, ResultLeafNodeType* leaf) const { if (a > mMaskValue) { leaf->setValueOn(offset, a); } } private: const DenseValueType mMaskValue; }; class BoolRule { public: // Standard tree type (e.g. BoolTree or FloatTree in openvdb.h) typedef openvdb::BoolTree ResultTreeType; typedef ResultTreeType::LeafNodeType ResultLeafNodeType; typedef bool ResultValueType; typedef float DenseValueType; BoolRule(const DenseValueType& value): mMaskValue(value){} template void operator()(const DenseValueType& a, const IndexOrCoord& offset, ResultLeafNodeType* leaf) const { if (a > mMaskValue) { leaf->setValueOn(offset, true); } } private: const DenseValueType mMaskValue; }; // Square each value struct SqrOp { float operator()(const float& in) const { return in * in; } }; } void TestDenseSparseTools::testExtractSparseFloatTree() { namespace vdbmath = openvdb::math; FloatRule rule(0.5f); const float testvalue = 1.f; mDense->setValue(mijk, testvalue); const float background(0.f); openvdb::FloatTree::Ptr result = openvdb::tools::extractSparseTree(*mDense, rule, background); // The result should have only one active value. CPPUNIT_ASSERT(result->activeVoxelCount() == 1); // The result should have only one leaf CPPUNIT_ASSERT(result->leafCount() == 1); // The background CPPUNIT_ASSERT_DOUBLES_EQUAL(background, result->background(), 1.e-6); // The stored value CPPUNIT_ASSERT_DOUBLES_EQUAL(testvalue, result->getValue(mijk), 1.e-6); } void TestDenseSparseTools::testExtractSparseBoolTree() { const float testvalue = 1.f; mDense->setValue(mijk, testvalue); const float cutoff(0.5); openvdb::BoolTree::Ptr result = openvdb::tools::extractSparseTree(*mDense, BoolRule(cutoff), false); // The result should have only one active value. CPPUNIT_ASSERT(result->activeVoxelCount() == 1); // The result should have only one leaf CPPUNIT_ASSERT(result->leafCount() == 1); // The background CPPUNIT_ASSERT(result->background() == false); // The stored value CPPUNIT_ASSERT(result->getValue(mijk) == true); } void TestDenseSparseTools::testExtractSparseAltDenseLayout() { namespace vdbmath = openvdb::math; FloatRule rule(0.5f); // Create a dense grid with the alternate data layout // but the same domain as mDense openvdb::tools::Dense dense(mDense->bbox(), 0.f); const float testvalue = 1.f; dense.setValue(mijk, testvalue); const float background(0.f); openvdb::FloatTree::Ptr result = openvdb::tools::extractSparseTree(dense, rule, background); // The result should have only one active value. CPPUNIT_ASSERT(result->activeVoxelCount() == 1); // The result should have only one leaf CPPUNIT_ASSERT(result->leafCount() == 1); // The background CPPUNIT_ASSERT_DOUBLES_EQUAL(background, result->background(), 1.e-6); // The stored value CPPUNIT_ASSERT_DOUBLES_EQUAL(testvalue, result->getValue(mijk), 1.e-6); } void TestDenseSparseTools::testExtractSparseMaskedTree() { namespace vdbmath = openvdb::math; const float testvalue = 1.f; mDense->setValue(mijk, testvalue); // Create a mask with two values. One in the domain of // interest and one outside. The intersection of the active // state topology of the mask and the domain of interest will define // the topology of the extracted result. openvdb::FloatTree mask(0.f); // turn on a point inside the bouding domain of the dense grid mask.setValue(mijk, 5.f); // turn on a point outside the bounding domain of the dense grid vdbmath::Coord outsidePoint = mDense->bbox().min() - vdbmath::Coord(3, 3, 3); mask.setValue(outsidePoint, 1.f); float background = 10.f; openvdb::FloatTree::Ptr result = openvdb::tools::extractSparseTreeWithMask(*mDense, mask, background); // The result should have only one active value. CPPUNIT_ASSERT(result->activeVoxelCount() == 1); // The result should have only one leaf CPPUNIT_ASSERT(result->leafCount() == 1); // The background CPPUNIT_ASSERT_DOUBLES_EQUAL(background, result->background(), 1.e-6); // The stored value CPPUNIT_ASSERT_DOUBLES_EQUAL(testvalue, result->getValue(mijk), 1.e-6); } void TestDenseSparseTools::testDenseTransform() { namespace vdbmath = openvdb::math; vdbmath::CoordBBox domain(vdbmath::Coord(-4, -6, 10), vdbmath::Coord( 1, 2, 15)); // Create dense grid, filled with value const float value(2.f); const float valueSqr(value*value); openvdb::tools::Dense dense(domain, 0.f); dense.fill(value); SqrOp op; vdbmath::CoordBBox smallBBox(vdbmath::Coord(-5, -5, 11), vdbmath::Coord( 0, 1, 13) ); // Apply the transformation openvdb::tools::transformDense(dense, smallBBox, op, true); vdbmath::Coord ijk; // Test results. for (ijk[0] = domain.min().x(); ijk[0] < domain.max().x() + 1; ++ijk[0]) { for (ijk[1] = domain.min().y(); ijk[1] < domain.max().y() + 1; ++ijk[1]) { for (ijk[2] = domain.min().z(); ijk[2] < domain.max().z() + 1; ++ijk[2]) { if (smallBBox.isInside(ijk)) { // the functor was applied here // the value should be base * base CPPUNIT_ASSERT_DOUBLES_EQUAL(dense.getValue(ijk), valueSqr, 1.e-6); } else { // the original value CPPUNIT_ASSERT_DOUBLES_EQUAL(dense.getValue(ijk), value, 1.e-6); } } } } } void TestDenseSparseTools::testOver() { namespace vdbmath = openvdb::math; const vdbmath::CoordBBox domain(vdbmath::Coord(-10, 0, 5), vdbmath::Coord( 10, 5, 10)); const openvdb::Coord ijk = domain.min() + openvdb::Coord(1, 1, 1); // Create dense grid, filled with value const float value(2.f); const float strength(1.f); const float beta(1.f); openvdb::FloatTree src(0.f); src.setValue(ijk, 1.f); openvdb::FloatTree alpha(0.f); alpha.setValue(ijk, 1.f); const float expected = openvdb::tools::ds::OpOver::apply( value, alpha.getValue(ijk), src.getValue(ijk), strength, beta, 1.f); { // testing composite function openvdb::tools::Dense dense(domain, 0.f); dense.fill(value); openvdb::tools::compositeToDense( dense, src, alpha, beta, strength, true /*threaded*/); // Check for over value CPPUNIT_ASSERT_DOUBLES_EQUAL(dense.getValue(ijk), expected, 1.e-6); // Check for original value CPPUNIT_ASSERT_DOUBLES_EQUAL(dense.getValue(openvdb::Coord(1,1,1) + ijk), value, 1.e-6); } { // testing sparse explict sparse composite openvdb::tools::Dense dense(domain, 0.f); dense.fill(value); typedef openvdb::tools::ds::CompositeFunctorTranslator CompositeTool; typedef CompositeTool::OpT Method; openvdb::tools::SparseToDenseCompositor sparseToDense(dense, src, alpha, beta, strength); sparseToDense.sparseComposite(true); // Check for over value CPPUNIT_ASSERT_DOUBLES_EQUAL(dense.getValue(ijk), expected, 1.e-6); // Check for original value CPPUNIT_ASSERT_DOUBLES_EQUAL(dense.getValue(openvdb::Coord(1,1,1) + ijk), value, 1.e-6); } { // testing sparse explict dense composite openvdb::tools::Dense dense(domain, 0.f); dense.fill(value); typedef openvdb::tools::ds::CompositeFunctorTranslator CompositeTool; typedef CompositeTool::OpT Method; openvdb::tools::SparseToDenseCompositor sparseToDense(dense, src, alpha, beta, strength); sparseToDense.denseComposite(true); // Check for over value CPPUNIT_ASSERT_DOUBLES_EQUAL(dense.getValue(ijk), expected, 1.e-6); // Check for original value CPPUNIT_ASSERT_DOUBLES_EQUAL(dense.getValue(openvdb::Coord(1,1,1) + ijk), value, 1.e-6); } } // Copyright (c) 2012-2017 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/TestDiagnostics.cc0000644000000000000000000003663213200122377016515 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TestDiagnostics: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestDiagnostics); CPPUNIT_TEST(testCheck); CPPUNIT_TEST(testDiagnose); CPPUNIT_TEST(testCheckLevelSet); CPPUNIT_TEST(testCheckFogVolume); CPPUNIT_TEST(testUniqueInactiveValues); CPPUNIT_TEST_SUITE_END(); void testCheck(); void testDiagnose(); void testCheckLevelSet(); void testCheckFogVolume(); void testUniqueInactiveValues(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestDiagnostics); //////////////////////////////////////// void TestDiagnostics::testCheck() { const float val = 1.0f; const float nan = std::numeric_limits::quiet_NaN(); const float inf1= std::numeric_limits::infinity(); const openvdb::math::Vec3 inf2(val, inf1, val); {//test CheckNan openvdb::tools::CheckNan c; CPPUNIT_ASSERT(!c(val)); CPPUNIT_ASSERT( c(nan)); CPPUNIT_ASSERT( c(nan)); CPPUNIT_ASSERT(!c(inf1)); CPPUNIT_ASSERT(!c(inf2)); } {//test CheckInf openvdb::tools::CheckInf c; CPPUNIT_ASSERT(!c(val)); CPPUNIT_ASSERT(!c(nan)); CPPUNIT_ASSERT(!c(nan)); CPPUNIT_ASSERT( c(inf1)); CPPUNIT_ASSERT( c(inf2)); } {//test CheckFinite openvdb::tools::CheckFinite c; CPPUNIT_ASSERT(!c(val)); CPPUNIT_ASSERT( c(nan)); CPPUNIT_ASSERT( c(nan)); CPPUNIT_ASSERT( c(inf1)); CPPUNIT_ASSERT( c(inf2)); } {//test CheckMin openvdb::tools::CheckMin c(0.0f); CPPUNIT_ASSERT(!c( 0.5f)); CPPUNIT_ASSERT(!c( 0.0f)); CPPUNIT_ASSERT(!c( 1.0f)); CPPUNIT_ASSERT(!c( 1.1f)); CPPUNIT_ASSERT( c(-0.1f)); } {//test CheckMax openvdb::tools::CheckMax c(0.0f); CPPUNIT_ASSERT( c( 0.5f)); CPPUNIT_ASSERT(!c( 0.0f)); CPPUNIT_ASSERT( c( 1.0f)); CPPUNIT_ASSERT( c( 1.1f)); CPPUNIT_ASSERT(!c(-0.1f)); } {//test CheckRange // first check throw on construction from an invalid range CPPUNIT_ASSERT_THROW(openvdb::tools::CheckRange c(1.0f, 0.0f), openvdb::ValueError); openvdb::tools::CheckRange c(0.0f, 1.0f); CPPUNIT_ASSERT(!c(0.5f)); CPPUNIT_ASSERT(!c(0.0f)); CPPUNIT_ASSERT(!c(1.0f)); CPPUNIT_ASSERT( c(1.1f)); CPPUNIT_ASSERT(c(-0.1f)); } }//testCheck void TestDiagnostics::testDiagnose() { using namespace openvdb; const float val = 1.0f; const float nan = std::numeric_limits::quiet_NaN(); const float inf = std::numeric_limits::infinity(); {//empty grid FloatGrid grid; tools::Diagnose d(grid); tools::CheckNan c; std::string str = d.check(c); //std::cerr << "Empty grid:\n" << str; CPPUNIT_ASSERT_EQUAL(std::string(), str); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {//non-empty grid FloatGrid grid; grid.tree().setValue(Coord(-1,3,6), val); tools::Diagnose d(grid); tools::CheckNan c; std::string str = d.check(c); //std::cerr << "Non-Empty grid:\n" << str; CPPUNIT_ASSERT_EQUAL(std::string(), str); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {//nan grid FloatGrid grid; grid.tree().setValue(Coord(-1,3,6), nan); tools::Diagnose d(grid); tools::CheckNan c; std::string str = d.check(c); //std::cerr << "NaN grid:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(1, int(d.failureCount())); } {//nan and infinite grid FloatGrid grid; grid.tree().setValue(Coord(-1,3,6), nan); grid.tree().setValue(Coord(10,30,60), inf); tools::Diagnose d(grid); tools::CheckFinite c; std::string str = d.check(c); //std::cerr << "Not Finite grid:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(2, int(d.failureCount())); } {//out-of-range grid FloatGrid grid(10.0f); grid.tree().setValue(Coord(-1,3,6), 1.0f); grid.tree().setValue(Coord(10,30,60), 1.5); grid.tree().fill(math::CoordBBox::createCube(math::Coord(0),8), 20.0f, true); tools::Diagnose d(grid); tools::CheckRange c(0.0f, 1.0f); std::string str = d.check(c); //std::cerr << "out-of-range grid:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(3, int(d.failureCount())); } const float radius = 4.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.1f, width = 2.0f, gamma=voxelSize*width; FloatGrid::Ptr gridSphere = tools::createLevelSetSphere(radius, center, voxelSize, width); //gridSphere->print(std::cerr, 2); {// Check min/max of active values math::Extrema ex = tools::extrema(gridSphere->cbeginValueOn()); //std::cerr << "Min = " << ex.min() << " max = " << ex.max() << std::endl; CPPUNIT_ASSERT(ex.min() > -voxelSize*width); CPPUNIT_ASSERT(ex.max() < voxelSize*width); } {// Check min/max of all values math::Extrema ex = tools::extrema(gridSphere->cbeginValueAll()); //std::cerr << "Min = " << ex.min() << " max = " << ex.max() << std::endl; CPPUNIT_ASSERT(ex.min() >= -voxelSize*width); CPPUNIT_ASSERT(ex.max() <= voxelSize*width); } {// check range of all values in a sphere w/o mask tools::CheckRange c(-gamma, gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check range of on values in a sphere w/o mask tools::CheckRange c(-gamma, gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check range of off tiles in a sphere w/o mask tools::CheckRange c(-gamma, gamma); tools::Diagnose d(*gridSphere); {// check off tile iterator FloatGrid::ValueOffCIter i(gridSphere->tree()); i.setMaxDepth(FloatGrid::ValueOffCIter::LEAF_DEPTH - 1); for (; i; ++i) CPPUNIT_ASSERT( math::Abs(*i) <= gamma); } std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check range of sphere w/o mask tools::CheckRange c(0.0f, gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT(d.failureCount() < gridSphere->activeVoxelCount()); } {// check range of sphere w mask tools::CheckRange c(0.0f, gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c, true); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(!str.empty()); CPPUNIT_ASSERT_EQUAL(d.valueCount(), d.valueCount()); CPPUNIT_ASSERT(d.failureCount() < gridSphere->activeVoxelCount()); } {// check min of sphere w/o mask tools::CheckMin c(-gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Min values:\n" << str; CPPUNIT_ASSERT_EQUAL(std::string(), str); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check max of sphere w/o mask tools::CheckMax c(gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "MAX values:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check norm of gradient of sphere w/o mask tools::CheckEikonal c(*gridSphere, 0.97f, 1.03f); tools::Diagnose d(*gridSphere); std::string str = d.check(c, false, true, false, false); //std::cerr << "NormGrad:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check norm of gradient of sphere w/o mask tools::CheckNormGrad c(*gridSphere, 0.75f, 1.25f); tools::Diagnose d(*gridSphere); std::string str = d.check(c, false, true, false, false); //std::cerr << "NormGrad:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check inactive values tools::CheckMagnitude c(gamma); tools::Diagnose d(*gridSphere); std::string str = d.check(c); //std::cerr << "Magnitude:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } }// testDiagnose void TestDiagnostics::testCheckLevelSet() { using namespace openvdb; const float radius = 4.3f; const Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.1f, width = LEVEL_SET_HALF_WIDTH; FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); //tools::CheckLevelSet c(*grid); //std::string str = c.check(); std::string str = tools::checkLevelSet(*grid); CPPUNIT_ASSERT(str.empty()); //std::cerr << "\n" << str << std::endl; grid->tree().setValue(Coord(0,0,0), voxelSize*(width+0.5f)); //str = c.check(); str = tools::checkLevelSet(*grid); CPPUNIT_ASSERT(!str.empty()); //std::cerr << "\n" << str << std::endl; //str = c.check(6); str = tools::checkLevelSet(*grid, 6); CPPUNIT_ASSERT(str.empty()); }// testCheckLevelSet void TestDiagnostics::testCheckFogVolume() { using namespace openvdb; const float radius = 4.3f; const Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.1f, width = LEVEL_SET_HALF_WIDTH; FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); tools::sdfToFogVolume(*grid); //tools::CheckFogVolume c(*grid); //std::string str = c.check(); std::string str = tools::checkFogVolume(*grid); CPPUNIT_ASSERT(str.empty()); //std::cerr << "\n" << str << std::endl; grid->tree().setValue(Coord(0,0,0), 1.5f); //str = c.check(); str = tools::checkFogVolume(*grid); CPPUNIT_ASSERT(!str.empty()); //std::cerr << "\n" << str << std::endl; str = tools::checkFogVolume(*grid, 5); //str = c.check(5); CPPUNIT_ASSERT(str.empty()); }// testCheckFogVolume void TestDiagnostics::testUniqueInactiveValues() { openvdb::FloatGrid grid; grid.tree().setValueOff(openvdb::Coord(0,0,0), -1); grid.tree().setValueOff(openvdb::Coord(0,0,1), -2); grid.tree().setValueOff(openvdb::Coord(0,1,0), -3); grid.tree().setValue(openvdb::Coord(1,0,0), 1); std::vector values; CPPUNIT_ASSERT(openvdb::tools::uniqueInactiveValues(grid, values, 4)); CPPUNIT_ASSERT_EQUAL(4, int(values.size())); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[0], -3.0f)); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[1], -2.0f)); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[2], -1.0f)); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[3], 0.0f)); // test with level set sphere const float radius = 4.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.5f, width = 2.0f; openvdb::FloatGrid::Ptr gridSphere = openvdb::tools::createLevelSetSphere(radius, center, voxelSize, width); CPPUNIT_ASSERT(openvdb::tools::uniqueInactiveValues(*gridSphere.get(), values, 2)); CPPUNIT_ASSERT_EQUAL(2, int(values.size())); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[0], -voxelSize * width)); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[1], voxelSize * width)); // test with fog volume openvdb::tools::sdfToFogVolume(*gridSphere); CPPUNIT_ASSERT(openvdb::tools::uniqueInactiveValues(*gridSphere.get(), values, 1)); CPPUNIT_ASSERT_EQUAL(1, int(values.size())); CPPUNIT_ASSERT(openvdb::math::isApproxEqual(values[0], 0.0f)); } // Copyright (c) 2012-2017 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.cc0000644000000000000000000007576013200122377015311 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #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 Desne::origin() CPPUNIT_ASSERT(openvdb::Coord(-40,-5, 6) == dense.origin()); // Check coordToOffset and offsetToCoord size_t offset = 0; 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]) { //std::cerr << "offset = " << offset << " P = " << P << std::endl; CPPUNIT_ASSERT_EQUAL(offset, dense.coordToOffset(P)); CPPUNIT_ASSERT_EQUAL(P - dense.origin(), dense.offsetToLocalCoord(offset)); CPPUNIT_ASSERT_EQUAL(P, dense.offsetToCoord(offset)); ++offset; } } } // Check Dense::valueCount const int size = static_cast(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 Desne::origin() CPPUNIT_ASSERT(openvdb::Coord(-40,-5, 6) == dense.origin()); // Check coordToOffset and offsetToCoord size_t offset = 0; 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]) { //std::cerr << "offset = " << offset << " P = " << P << std::endl; CPPUNIT_ASSERT_EQUAL(offset, dense.coordToOffset(P)); CPPUNIT_ASSERT_EQUAL(P - dense.origin(), dense.offsetToLocalCoord(offset)); CPPUNIT_ASSERT_EQUAL(P, dense.offsetToCoord(offset)); ++offset; } } } // Check Dense::valueCount const int size = static_cast(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 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 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 Int32 sizeX = 8, sizeY = 8, 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(int(dense.valueCount()) == int(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.0001f; 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 const int sizeX = 8, sizeY = 8, 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_EQUAL(sizeX * sizeY * sizeZ, static_cast(dense.valueCount())); // 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.0001f; 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 = static_cast(denseSmall.valueCount()); float* d = denseSmall.data(); for (int i = 0; i < n; ++i) { d[i] = static_cast(i); } } // Construct large dense grid DenseT denseBig(bboxBig, 0.f); { // insert non-const values const int n = static_cast(denseBig.valueCount()); float* d = denseBig.data(); for (int i = 0; i < n; ++i) { d[i] = static_cast(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-2017 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.cc0000644000000000000000000001230413200122377016623 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TestVolumeToMesh: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestVolumeToMesh); CPPUNIT_TEST(testAuxiliaryDataCollection); CPPUNIT_TEST(testUniformMeshing); CPPUNIT_TEST_SUITE_END(); void testAuxiliaryDataCollection(); void testUniformMeshing(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestVolumeToMesh); //////////////////////////////////////// void TestVolumeToMesh::testAuxiliaryDataCollection() { typedef openvdb::tree::Tree4::Type FloatTreeType; typedef FloatTreeType::ValueConverter::Type BoolTreeType; const float iso = 0.0f; const openvdb::Coord ijk(0,0,0); FloatTreeType inputTree(1.0f); inputTree.setValue(ijk, -1.0f); BoolTreeType intersectionTree(false); openvdb::tools::volume_to_mesh_internal::identifySurfaceIntersectingVoxels( intersectionTree, inputTree, iso); CPPUNIT_ASSERT_EQUAL(size_t(8), size_t(intersectionTree.activeVoxelCount())); typedef FloatTreeType::ValueConverter::Type Int16TreeType; typedef FloatTreeType::ValueConverter::Type Index32TreeType; Int16TreeType signFlagsTree(0); Index32TreeType pointIndexTree(99999); openvdb::tools::volume_to_mesh_internal::computeAuxiliaryData( signFlagsTree, pointIndexTree, intersectionTree, inputTree, iso); const int flags = int(signFlagsTree.getValue(ijk)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::volume_to_mesh_internal::INSIDE)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::volume_to_mesh_internal::EDGES)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::volume_to_mesh_internal::XEDGE)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::volume_to_mesh_internal::YEDGE)); CPPUNIT_ASSERT(bool(flags & openvdb::tools::volume_to_mesh_internal::ZEDGE)); } void TestVolumeToMesh::testUniformMeshing() { typedef openvdb::tree::Tree4::Type FloatTreeType; typedef openvdb::Grid FloatGridType; FloatGridType grid(1.0f); // test voxel region meshing openvdb::CoordBBox bbox(openvdb::Coord(1), openvdb::Coord(6)); grid.tree().fill(bbox, -1.0f); std::vector points; std::vector quads; std::vector triangles; openvdb::tools::volumeToMesh(grid, points, quads); CPPUNIT_ASSERT(!points.empty()); CPPUNIT_ASSERT_EQUAL(size_t(216), quads.size()); points.clear(); quads.clear(); triangles.clear(); grid.clear(); // test tile region meshing grid.tree().addTile(FloatTreeType::LeafNodeType::LEVEL + 1, openvdb::Coord(0), -1.0f, true); openvdb::tools::volumeToMesh(grid, points, quads); CPPUNIT_ASSERT(!points.empty()); CPPUNIT_ASSERT_EQUAL(size_t(384), quads.size()); points.clear(); quads.clear(); triangles.clear(); grid.clear(); // test tile region and bool volume meshing typedef FloatTreeType::ValueConverter::Type BoolTreeType; typedef openvdb::Grid BoolGridType; BoolGridType maskGrid(false); maskGrid.tree().addTile(BoolTreeType::LeafNodeType::LEVEL + 1, openvdb::Coord(0), true, true); openvdb::tools::volumeToMesh(maskGrid, points, quads); CPPUNIT_ASSERT(!points.empty()); CPPUNIT_ASSERT_EQUAL(size_t(384), quads.size()); } // Copyright (c) 2012-2017 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.cc0000644000000000000000000003370013200122377016636 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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/TestPointAttribute.cc0000644000000000000000000004271413200122377017221 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 using namespace openvdb; using namespace openvdb::points; class TestPointAttribute: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestPointAttribute); CPPUNIT_TEST(testAppendDrop); CPPUNIT_TEST(testRename); CPPUNIT_TEST(testBloscCompress); CPPUNIT_TEST_SUITE_END(); void testAppendDrop(); void testRename(); void testBloscCompress(); }; // class TestPointAttribute CPPUNIT_TEST_SUITE_REGISTRATION(TestPointAttribute); //////////////////////////////////////// void TestPointAttribute::testAppendDrop() { using AttributeI = TypedAttributeArray; std::vector positions{{1, 1, 1}, {1, 10, 1}, {10, 1, 1}, {10, 10, 1}}; const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); // check one leaf per point CPPUNIT_ASSERT_EQUAL(tree.leafCount(), Index32(4)); // retrieve first and last leaf attribute sets auto leafIter = tree.cbeginLeaf(); const AttributeSet& attributeSet = leafIter->attributeSet(); ++leafIter; ++leafIter; ++leafIter; const AttributeSet& attributeSet4 = leafIter->attributeSet(); // check just one attribute exists (position) CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(1)); { // append an attribute, different initial values and collapse appendAttribute(tree, "id"); CPPUNIT_ASSERT(tree.beginLeaf()->hasAttribute("id")); AttributeArray& array = tree.beginLeaf()->attributeArray("id"); CPPUNIT_ASSERT(array.isUniform()); CPPUNIT_ASSERT_EQUAL(AttributeI::cast(array).get(0), zeroVal()); dropAttribute(tree, "id"); appendAttribute(tree, "id", 10, /*stride*/1); CPPUNIT_ASSERT(tree.beginLeaf()->hasAttribute("id")); AttributeArray& array2 = tree.beginLeaf()->attributeArray("id"); CPPUNIT_ASSERT(array2.isUniform()); CPPUNIT_ASSERT_EQUAL(AttributeI::cast(array2).get(0), AttributeI::ValueType(10)); array2.expand(); CPPUNIT_ASSERT(!array2.isUniform()); collapseAttribute(tree, "id", 50); AttributeArray& array3 = tree.beginLeaf()->attributeArray("id"); CPPUNIT_ASSERT(array3.isUniform()); CPPUNIT_ASSERT_EQUAL(AttributeI::cast(array3).get(0), AttributeI::ValueType(50)); dropAttribute(tree, "id"); appendAttribute(tree, "name", "test"); AttributeArray& array4 = tree.beginLeaf()->attributeArray("name"); CPPUNIT_ASSERT(array4.isUniform()); StringAttributeHandle handle(array4, attributeSet.descriptor().getMetadata()); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("test")); dropAttribute(tree, "name"); } { // append a strided attribute appendAttribute(tree, "id", 0, /*stride=*/1); AttributeArray& array = tree.beginLeaf()->attributeArray("id"); CPPUNIT_ASSERT_EQUAL(array.stride(), Index(1)); dropAttribute(tree, "id"); appendAttribute(tree, "id", 0, /*stride=*/10); CPPUNIT_ASSERT(tree.beginLeaf()->hasAttribute("id")); AttributeArray& array2 = tree.beginLeaf()->attributeArray("id"); CPPUNIT_ASSERT_EQUAL(array2.stride(), Index(10)); dropAttribute(tree, "id"); } { // append an attribute, check descriptors are as expected, default value test appendAttribute(tree, "id", /*uniformValue*/0, /*stride=*/1, /*constantStride=*/true, /*defaultValue*/TypedMetadata(10).copy(), /*hidden=*/false, /*transient=*/false); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(2)); CPPUNIT_ASSERT(attributeSet.descriptor() == attributeSet4.descriptor()); CPPUNIT_ASSERT(&attributeSet.descriptor() == &attributeSet4.descriptor()); CPPUNIT_ASSERT(attributeSet.descriptor().getMetadata()["default:id"]); } { // append three attributes, check ordering is consistent with insertion appendAttribute(tree, "test3"); appendAttribute(tree, "test1"); appendAttribute(tree, "test2"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(5)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("P"), size_t(0)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("id"), size_t(1)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("test3"), size_t(2)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("test1"), size_t(3)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("test2"), size_t(4)); } { // drop an attribute by index, check ordering remains consistent std::vector indices{2}; dropAttributes(tree, indices); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("P"), size_t(0)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("id"), size_t(1)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("test1"), size_t(2)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("test2"), size_t(3)); } { // drop attributes by index, check ordering remains consistent std::vector indices{1, 3}; dropAttributes(tree, indices); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(2)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("P"), size_t(0)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("test1"), size_t(1)); } { // drop last non-position attribute std::vector indices{1}; dropAttributes(tree, indices); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(1)); } { // attempt (and fail) to drop position std::vector indices{0}; CPPUNIT_ASSERT_THROW(dropAttributes(tree, indices), openvdb::KeyError); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(1)); CPPUNIT_ASSERT(attributeSet.descriptor().find("P") != AttributeSet::INVALID_POS); } { // add back previous attributes appendAttribute(tree, "id"); appendAttribute(tree, "test3"); appendAttribute(tree, "test1"); appendAttribute(tree, "test2"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(5)); } { // attempt (and fail) to drop non-existing attribute std::vector names{"test1000"}; CPPUNIT_ASSERT_THROW(dropAttributes(tree, names), openvdb::KeyError); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(5)); } { // drop by name std::vector names{"test1", "test2"}; dropAttributes(tree, names); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(3)); CPPUNIT_ASSERT(attributeSet.descriptor() == attributeSet4.descriptor()); CPPUNIT_ASSERT(&attributeSet.descriptor() == &attributeSet4.descriptor()); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("P"), size_t(0)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("id"), size_t(1)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("test3"), size_t(2)); } { // attempt (and fail) to drop position std::vector names{"P"}; CPPUNIT_ASSERT_THROW(dropAttributes(tree, names), openvdb::KeyError); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(3)); CPPUNIT_ASSERT(attributeSet.descriptor().find("P") != AttributeSet::INVALID_POS); } { // drop one attribute by name dropAttribute(tree, "test3"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(2)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("P"), size_t(0)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("id"), size_t(1)); } { // drop one attribute by id dropAttribute(tree, 1); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(1)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().find("P"), size_t(0)); } { // attempt to add an attribute with a name that already exists appendAttribute(tree, "test3"); CPPUNIT_ASSERT_THROW(appendAttribute(tree, "test3"), openvdb::KeyError); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(2)); } { // append attributes marked as hidden, transient, group and string appendAttribute(tree, "testHidden", 0, /*stride=*/1, /*constantStride=*/true, Metadata::Ptr(), true, false); appendAttribute(tree, "testTransient", 0, /*stride=*/1, /*constantStride=*/true, Metadata::Ptr(), false, true); appendAttribute(tree, "testString", "", /*stride=*/1, /*constantStride=*/true, Metadata::Ptr(), false, false); const AttributeArray& arrayHidden = leafIter->attributeArray("testHidden"); const AttributeArray& arrayTransient = leafIter->attributeArray("testTransient"); const AttributeArray& arrayString = leafIter->attributeArray("testString"); CPPUNIT_ASSERT(arrayHidden.isHidden()); CPPUNIT_ASSERT(!arrayTransient.isHidden()); CPPUNIT_ASSERT(!arrayHidden.isTransient()); CPPUNIT_ASSERT(arrayTransient.isTransient()); CPPUNIT_ASSERT(!arrayString.isTransient()); CPPUNIT_ASSERT(!isGroup(arrayHidden)); CPPUNIT_ASSERT(!isGroup(arrayTransient)); CPPUNIT_ASSERT(!isGroup(arrayString)); CPPUNIT_ASSERT(!isString(arrayHidden)); CPPUNIT_ASSERT(!isString(arrayTransient)); CPPUNIT_ASSERT(isString(arrayString)); } { // collapsing non-existing attribute throws exception CPPUNIT_ASSERT_THROW(collapseAttribute(tree, "unknown", 0), openvdb::KeyError); CPPUNIT_ASSERT_THROW(collapseAttribute(tree, "unknown", "unknown"), openvdb::KeyError); } } void TestPointAttribute::testRename() { std::vector positions{{1, 1, 1}, {1, 10, 1}, {10, 1, 1}, {10, 10, 1}}; const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); // check one leaf per point CPPUNIT_ASSERT_EQUAL(tree.leafCount(), Index32(4)); const openvdb::TypedMetadata defaultValue(5.0f); appendAttribute(tree, "test1", 0, /*stride=*/1, /*constantStride=*/true, defaultValue.copy()); appendAttribute(tree, "id"); appendAttribute(tree, "test2"); // retrieve first and last leaf attribute sets auto leafIter = tree.cbeginLeaf(); const AttributeSet& attributeSet = leafIter->attributeSet(); ++leafIter; const AttributeSet& attributeSet4 = leafIter->attributeSet(); { // rename one attribute renameAttribute(tree, "test1", "test1renamed"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().size(), size_t(4)); CPPUNIT_ASSERT(attributeSet.descriptor().find("test1") == AttributeSet::INVALID_POS); CPPUNIT_ASSERT(attributeSet.descriptor().find("test1renamed") != AttributeSet::INVALID_POS); CPPUNIT_ASSERT_EQUAL(attributeSet4.descriptor().size(), size_t(4)); CPPUNIT_ASSERT(attributeSet4.descriptor().find("test1") == AttributeSet::INVALID_POS); CPPUNIT_ASSERT(attributeSet4.descriptor().find("test1renamed") != AttributeSet::INVALID_POS); renameAttribute(tree, "test1renamed", "test1"); } { // rename non-existing, matching and existing attributes CPPUNIT_ASSERT_THROW(renameAttribute(tree, "nonexist", "newname"), openvdb::KeyError); CPPUNIT_ASSERT_THROW(renameAttribute(tree, "test1", "test1"), openvdb::KeyError); CPPUNIT_ASSERT_THROW(renameAttribute(tree, "test2", "test1"), openvdb::KeyError); } { // rename multiple attributes std::vector oldNames{"test1", "test2"}; std::vector newNames{"test1renamed"}; CPPUNIT_ASSERT_THROW(renameAttributes(tree, oldNames, newNames), openvdb::ValueError); newNames.push_back("test2renamed"); renameAttributes(tree, oldNames, newNames); renameAttribute(tree, "test1renamed", "test1"); renameAttribute(tree, "test2renamed", "test2"); } { // rename an attribute with a default value CPPUNIT_ASSERT(attributeSet.descriptor().hasDefaultValue("test1")); renameAttribute(tree, "test1", "test1renamed"); CPPUNIT_ASSERT(attributeSet.descriptor().hasDefaultValue("test1renamed")); } } void TestPointAttribute::testBloscCompress() { std::vector positions; for (float i = 1.f; i < 6.f; i += 0.1f) { positions.emplace_back(1, i, 1); positions.emplace_back(1, 1, i); positions.emplace_back(10, i, 1); positions.emplace_back(10, 1, i); } const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); // check two leaves CPPUNIT_ASSERT_EQUAL(tree.leafCount(), Index32(2)); // retrieve first and last leaf attribute sets auto leafIter = tree.beginLeaf(); auto leafIter2 = ++tree.beginLeaf(); { // append an attribute, check descriptors are as expected appendAttribute(tree, "compact"); appendAttribute(tree, "id"); appendAttribute(tree, "id2"); } using AttributeHandleRWI = AttributeWriteHandle; { // set some id values (leaf 1) AttributeHandleRWI handleCompact(leafIter->attributeArray("compact")); AttributeHandleRWI handleId(leafIter->attributeArray("id")); AttributeHandleRWI handleId2(leafIter->attributeArray("id2")); const int size = leafIter->attributeArray("id").size(); CPPUNIT_ASSERT_EQUAL(size, 102); for (int i = 0; i < size; i++) { handleCompact.set(i, 5); handleId.set(i, i); handleId2.set(i, i); } } { // set some id values (leaf 2) AttributeHandleRWI handleCompact(leafIter2->attributeArray("compact")); AttributeHandleRWI handleId(leafIter2->attributeArray("id")); AttributeHandleRWI handleId2(leafIter2->attributeArray("id2")); const int size = leafIter2->attributeArray("id").size(); CPPUNIT_ASSERT_EQUAL(size, 102); for (int i = 0; i < size; i++) { handleCompact.set(i, 10); handleId.set(i, i); handleId2.set(i, i); } } compactAttributes(tree); CPPUNIT_ASSERT(leafIter->attributeArray("compact").isUniform()); CPPUNIT_ASSERT(leafIter2->attributeArray("compact").isUniform()); bloscCompressAttribute(tree, "id"); #ifdef OPENVDB_USE_BLOSC CPPUNIT_ASSERT(leafIter->attributeArray("id").isCompressed()); CPPUNIT_ASSERT(!leafIter->attributeArray("id2").isCompressed()); CPPUNIT_ASSERT(leafIter2->attributeArray("id").isCompressed()); CPPUNIT_ASSERT(!leafIter2->attributeArray("id2").isCompressed()); CPPUNIT_ASSERT(leafIter->attributeArray("id").memUsage() < leafIter->attributeArray("id2").memUsage()); #endif } // Copyright (c) 2012-2017 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/TestRay.cc0000644000000000000000000004647513200122377015007 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #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(testTimeSpan); CPPUNIT_TEST(testDDA); CPPUNIT_TEST_SUITE_END(); void testInfinity(); void testRay(); void testTimeSpan(); 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 "util.h" // for unittest_util::makeSphere() #include class TestMeanCurvature: public CppUnit::TestFixture { public: void setUp() override { openvdb::initialize(); } void tearDown() override { 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(); AccessorType inAccessor = grid->getConstAccessor(); AccessorType::ValueType alpha, beta, meancurv, normGrad; Coord xyz(35,30,30); // First test an empty grid CPPUNIT_ASSERT(tree.empty()); typedef math::ISMeanCurvature SecondOrder; CPPUNIT_ASSERT(!SecondOrder::result(inAccessor, xyz, alpha, beta)); typedef math::ISMeanCurvature FourthOrder; CPPUNIT_ASSERT(!FourthOrder::result(inAccessor, xyz, alpha, beta)); typedef math::ISMeanCurvature SixthOrder; CPPUNIT_ASSERT(!SixthOrder::result(inAccessor, xyz, alpha, beta)); // Next test a level set sphere 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()); SecondOrder::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); FourthOrder::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); SixthOrder::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); SecondOrder::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(); math::SecondOrderDenseStencil dense_2nd(*grid); math::FourthOrderDenseStencil dense_4th(*grid); math::SixthOrderDenseStencil dense_6th(*grid); AccessorType::ValueType alpha, beta; Coord xyz(35,30,30); dense_2nd.moveTo(xyz); dense_4th.moveTo(xyz); dense_6th.moveTo(xyz); // First test on an empty grid CPPUNIT_ASSERT(tree.empty()); typedef math::ISMeanCurvature SecondOrder; CPPUNIT_ASSERT(!SecondOrder::result(dense_2nd, alpha, beta)); typedef math::ISMeanCurvature FourthOrder; CPPUNIT_ASSERT(!FourthOrder::result(dense_4th, alpha, beta)); typedef math::ISMeanCurvature SixthOrder; CPPUNIT_ASSERT(!SixthOrder::result(dense_6th, alpha, beta)); // Next test on a level set sphere 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); dense_2nd.moveTo(xyz); dense_4th.moveTo(xyz); dense_6th.moveTo(xyz); CPPUNIT_ASSERT(!tree.empty()); CPPUNIT_ASSERT(SecondOrder::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); CPPUNIT_ASSERT(FourthOrder::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); CPPUNIT_ASSERT(SixthOrder::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); CPPUNIT_ASSERT(SecondOrder::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; {// Empty grid test FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); AccessorType inAccessor = grid->getConstAccessor(); Coord xyz(35,30,30); CPPUNIT_ASSERT(tree.empty()); 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(0.0, meancurv, 0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, normGrad, 0.0); meancurv = math::MeanCurvature::result( affine, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( affine, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, meancurv, 0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, normGrad, 0.0); UniformScaleMap uniform; meancurv = math::MeanCurvature::result( uniform, inAccessor, xyz); normGrad = math::MeanCurvature::normGrad( uniform, inAccessor, xyz); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, meancurv, 0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, normGrad, 0.0); 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(0.0, meancurv, 0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, normGrad, 0.0); } { // 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; {// empty grid test FloatGrid::Ptr grid = createGrid(/*background=*/5.0); FloatTree& tree = grid->tree(); 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(0.0, meancurv, 0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, normGrad, 0.00); meancurv = math::MeanCurvature::result( affine, dense_4th); normGrad = math::MeanCurvature::normGrad( affine, dense_4th); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, meancurv, 0.00); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, normGrad, 0.00); UniformScaleMap uniform; meancurv = math::MeanCurvature::result( uniform, dense_6th); normGrad = math::MeanCurvature::normGrad( uniform, dense_6th); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, meancurv, 0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, normGrad, 0.0); 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(0.0, meancurv, 0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, normGrad, 0.0); } { // 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()); math::CurvatureStencil cs(*grid); Coord xyz(20,16,20);//i.e. 8 voxel or 4 world units away from the center cs.moveTo(xyz); // First test on an empty grid CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, cs.meanCurvature(), 0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, cs.meanCurvatureNormGrad(), 0.0); // Next test on a level set sphere 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())); 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-2017 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/TestAttributeArrayString.cc0000644000000000000000000003444713200122377020401 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 using namespace openvdb; using namespace openvdb::points; class TestAttributeArrayString: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestAttributeArrayString); CPPUNIT_TEST(testStringMetaInserter); CPPUNIT_TEST(testStringAttribute); CPPUNIT_TEST(testStringAttributeHandle); CPPUNIT_TEST(testStringAttributeWriteHandle); CPPUNIT_TEST_SUITE_END(); void testStringMetaInserter(); void testStringAttribute(); void testStringAttributeHandle(); void testStringAttributeWriteHandle(); }; // class TestAttributeArrayString CPPUNIT_TEST_SUITE_REGISTRATION(TestAttributeArrayString); //////////////////////////////////////// namespace { bool matchingNamePairs(const openvdb::NamePair& lhs, const openvdb::NamePair& rhs) { if (lhs.first != rhs.first) return false; if (lhs.second != rhs.second) return false; return true; } } // namespace //////////////////////////////////////// void TestAttributeArrayString::testStringMetaInserter() { MetaMap metadata; StringMetaInserter inserter(metadata); { // insert one value inserter.insert("test"); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(1)); StringMetadata::Ptr meta = metadata.getMetadata("string:0"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test")); } { // insert another value inserter.insert("test2"); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(2)); StringMetadata::Ptr meta = metadata.getMetadata("string:0"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test")); meta = metadata.getMetadata("string:1"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test2")); } // remove a value and reset the cache metadata.removeMeta("string:1"); inserter.resetCache(); { // re-insert value inserter.insert("test3"); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(2)); StringMetadata::Ptr meta = metadata.getMetadata("string:0"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test")); meta = metadata.getMetadata("string:1"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test3")); } { // insert and remove to create a gap inserter.insert("test4"); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(3)); metadata.removeMeta("string:1"); inserter.resetCache(); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(2)); StringMetadata::Ptr meta = metadata.getMetadata("string:0"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test")); meta = metadata.getMetadata("string:2"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test4")); } { // insert to fill gap inserter.insert("test10"); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(3)); StringMetadata::Ptr meta = metadata.getMetadata("string:0"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test")); meta = metadata.getMetadata("string:1"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test10")); meta = metadata.getMetadata("string:2"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test4")); } { // insert existing value CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(3)); inserter.insert("test10"); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(3)); } metadata.removeMeta("string:0"); metadata.removeMeta("string:2"); inserter.resetCache(); { // insert other value and string metadata metadata.insertMeta("int:1", Int32Metadata(5)); metadata.insertMeta("irrelevant", StringMetadata("irrelevant")); inserter.resetCache(); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(3)); inserter.insert("test15"); CPPUNIT_ASSERT_EQUAL(metadata.metaCount(), size_t(4)); StringMetadata::Ptr meta = metadata.getMetadata("string:0"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test15")); meta = metadata.getMetadata("string:1"); CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT_EQUAL(meta->value(), openvdb::Name("test10")); } } //////////////////////////////////////// void TestAttributeArrayString::testStringAttribute() { { // Typed class API const Index count = 50; StringAttributeArray attr(count); CPPUNIT_ASSERT(!attr.isTransient()); CPPUNIT_ASSERT(!attr.isHidden()); CPPUNIT_ASSERT(isString(attr)); attr.setTransient(true); CPPUNIT_ASSERT(attr.isTransient()); CPPUNIT_ASSERT(!attr.isHidden()); CPPUNIT_ASSERT(isString(attr)); attr.setHidden(true); CPPUNIT_ASSERT(attr.isTransient()); CPPUNIT_ASSERT(attr.isHidden()); CPPUNIT_ASSERT(isString(attr)); attr.setTransient(false); CPPUNIT_ASSERT(!attr.isTransient()); CPPUNIT_ASSERT(attr.isHidden()); CPPUNIT_ASSERT(isString(attr)); StringAttributeArray attrB(attr); CPPUNIT_ASSERT(matchingNamePairs(attr.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attr.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attr.memUsage(), attrB.memUsage()); CPPUNIT_ASSERT_EQUAL(attr.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attr.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attr.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(isString(attr), isString(attrB)); } { // IO const Index count = 50; StringAttributeArray attrA(count); for (unsigned i = 0; i < unsigned(count); ++i) { attrA.set(i, int(i)); } attrA.setHidden(true); std::ostringstream ostr(std::ios_base::binary); attrA.write(ostr); StringAttributeArray attrB; std::istringstream istr(ostr.str(), std::ios_base::binary); attrB.read(istr); CPPUNIT_ASSERT(matchingNamePairs(attrA.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attrA.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attrA.memUsage(), attrB.memUsage()); CPPUNIT_ASSERT_EQUAL(attrA.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attrA.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attrA.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(isString(attrA), isString(attrB)); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); } } } void TestAttributeArrayString::testStringAttributeHandle() { MetaMap metadata; StringAttributeArray attr(4); StringAttributeHandle handle(attr, metadata); CPPUNIT_ASSERT_EQUAL(handle.size(), Index(4)); CPPUNIT_ASSERT_EQUAL(handle.size(), attr.size()); { // index 0 should always be an empty string Name value = handle.get(0); CPPUNIT_ASSERT_EQUAL(value, Name("")); } // set first element to 101 CPPUNIT_ASSERT(handle.isUniform()); attr.set(2, 102); CPPUNIT_ASSERT(!handle.isUniform()); { // index 101 does not exist as metadata is empty CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("")); CPPUNIT_ASSERT_THROW(handle.get(2), LookupError); } { // add an element to the metadata for 101 metadata.insertMeta("string:101", StringMetadata("test101")); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("")); CPPUNIT_ASSERT_NO_THROW(handle.get(2)); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("test101")); Name name; handle.get(name, 2); CPPUNIT_ASSERT_EQUAL(name, Name("test101")); } { // add a second element to the metadata metadata.insertMeta("string:102", StringMetadata("test102")); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("")); CPPUNIT_ASSERT_NO_THROW(handle.get(2)); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("test101")); Name name; handle.get(name, 2); CPPUNIT_ASSERT_EQUAL(name, Name("test101")); } { // set two more values in the array attr.set(0, 103); attr.set(1, 103); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("test102")); CPPUNIT_ASSERT_EQUAL(handle.get(1), Name("test102")); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("test101")); CPPUNIT_ASSERT_EQUAL(handle.get(3), Name("")); } { // change a value attr.set(1, 102); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("test102")); CPPUNIT_ASSERT_EQUAL(handle.get(1), Name("test101")); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("test101")); CPPUNIT_ASSERT_EQUAL(handle.get(3), Name("")); } { // cannot use a StringAttributeHandle with a non-string attribute TypedAttributeArray invalidAttr(50); CPPUNIT_ASSERT_THROW(StringAttributeHandle(invalidAttr, metadata), TypeError); } } void TestAttributeArrayString::testStringAttributeWriteHandle() { MetaMap metadata; StringAttributeArray attr(4); StringAttributeWriteHandle handle(attr, metadata); { // add some values to metadata metadata.insertMeta("string:45", StringMetadata("testA")); metadata.insertMeta("string:90", StringMetadata("testB")); metadata.insertMeta("string:1000", StringMetadata("testC")); } { // no string values set CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("")); CPPUNIT_ASSERT_EQUAL(handle.get(1), Name("")); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("")); CPPUNIT_ASSERT_EQUAL(handle.get(3), Name("")); } { // cache not reset since metadata changed CPPUNIT_ASSERT_THROW(handle.set(1, "testB"), LookupError); } handle.resetCache(); { // cache now reset CPPUNIT_ASSERT_NO_THROW(handle.set(1, "testB")); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("")); CPPUNIT_ASSERT_EQUAL(handle.get(1), Name("testB")); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("")); CPPUNIT_ASSERT_EQUAL(handle.get(3), Name("")); } { // add another value handle.set(2, "testC"); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("")); CPPUNIT_ASSERT_EQUAL(handle.get(1), Name("testB")); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("testC")); CPPUNIT_ASSERT_EQUAL(handle.get(3), Name("")); } handle.resetCache(); { // compact tests CPPUNIT_ASSERT(!handle.compact()); handle.set(0, "testA"); handle.set(1, "testA"); handle.set(2, "testA"); handle.set(3, "testA"); CPPUNIT_ASSERT(handle.compact()); CPPUNIT_ASSERT(handle.isUniform()); } { // expand tests CPPUNIT_ASSERT(handle.isUniform()); handle.expand(); CPPUNIT_ASSERT(!handle.isUniform()); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("testA")); CPPUNIT_ASSERT_EQUAL(handle.get(1), Name("testA")); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("testA")); CPPUNIT_ASSERT_EQUAL(handle.get(3), Name("testA")); } { // fill tests CPPUNIT_ASSERT(!handle.isUniform()); handle.set(3, "testB"); handle.fill("testC"); CPPUNIT_ASSERT(!handle.isUniform()); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("testC")); CPPUNIT_ASSERT_EQUAL(handle.get(1), Name("testC")); CPPUNIT_ASSERT_EQUAL(handle.get(2), Name("testC")); CPPUNIT_ASSERT_EQUAL(handle.get(3), Name("testC")); } { // collapse tests handle.set(2, "testB"); handle.collapse("testA"); CPPUNIT_ASSERT(handle.isUniform()); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("testA")); handle.expand(); handle.set(2, "testB"); CPPUNIT_ASSERT(!handle.isUniform()); handle.collapse(); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("")); } { // empty string tests handle.collapse(""); CPPUNIT_ASSERT_EQUAL(handle.get(0), Name("")); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000000535513200122377016611 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000003160113200122377015561 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 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(testEquality); CPPUNIT_TEST_SUITE_END(); void testInsert(); void testRemove(); void testGetMetadata(); void testIO(); void testEmptyIO(); void testCopyConstructor(); void testCopyConstructorEmpty(); void testAssignment(); void testEquality(); }; 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_EQUAL(0, int(meta.metaCount())); } 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")); CPPUNIT_ASSERT_THROW(meta.metaValue("meta3"), openvdb::TypeError); CPPUNIT_ASSERT_THROW(meta.metaValue("meta5"), openvdb::LookupError); } void TestMetaMap::testIO() { using namespace openvdb; logging::LevelScope suppressLogging{logging::Level::Fatal}; 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)); #if OPENVDB_ABI_VERSION_NUMBER < 5 CPPUNIT_ASSERT_EQUAL(0, int(meta2.metaCount())); #else CPPUNIT_ASSERT_EQUAL(3, int(meta2.metaCount())); // Verify that writing metadata of unknown type (i.e., UnknownMetadata) is possible. std::ostringstream ostrUnknown(std::ios_base::binary); meta2.writeMeta(ostrUnknown); #endif // 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)); #if OPENVDB_ABI_VERSION_NUMBER >= 5 CPPUNIT_ASSERT_EQUAL(3, int(meta2.metaCount())); #else CPPUNIT_ASSERT_EQUAL(1, int(meta2.metaCount())); #endif 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); } { #if OPENVDB_ABI_VERSION_NUMBER >= 5 // Verify that metadata that was written as UnknownMetadata can // be read as typed metadata once the underlying types are registered. std::istringstream istrUnknown(ostrUnknown.str(), std::ios_base::binary); meta2.clearMetadata(); CPPUNIT_ASSERT_NO_THROW(meta2.readMeta(istrUnknown)); CPPUNIT_ASSERT_EQUAL(meta.metaCount(), meta2.metaCount()); 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"), 0.0); #endif } // 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_EQUAL(0, int(meta2.metaCount())); // 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")); } void TestMetaMap::testEquality() { using namespace openvdb; // Populate a map with data. MetaMap meta; meta.insertMeta("meta1", StringMetadata("testing")); meta.insertMeta("meta2", Int32Metadata(20)); meta.insertMeta("meta3", FloatMetadata(3.14159f)); // Create an empty map. MetaMap meta2; // Verify that the two maps differ. CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); // Copy the first map to the second. meta2 = meta; // Verify that the two maps are equivalent. CPPUNIT_ASSERT(meta == meta2); CPPUNIT_ASSERT(meta2 == meta); // Modify the first map. meta.removeMeta("meta1"); meta.insertMeta("abc", DoubleMetadata(2.0)); // Verify that the two maps differ. CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); // Modify the second map and verify that the two maps differ. meta2 = meta; meta2.insertMeta("meta2", Int32Metadata(42)); CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); meta2 = meta; meta2.insertMeta("meta3", FloatMetadata(2.0001f)); CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); meta2 = meta; meta2.insertMeta("abc", DoubleMetadata(2.0001)); CPPUNIT_ASSERT(meta != meta2); CPPUNIT_ASSERT(meta2 != meta); } // Copyright (c) 2012-2017 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/TestTreeVisitor.cc0000644000000000000000000003060513200122377016517 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TestTreeVisitor.h /// /// @author Peter Cucka #include #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 openvdb::Index((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 = openvdb::Index(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 openvdb::Index(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 = openvdb::Index(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; // 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-2017 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.cc0000644000000000000000000001142213200122377016355 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000000662013200122377015120 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000005547313200122377017011 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #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 >(); accessorTest >(); } void testTree3ConstAccessor2() { constAccessorTest >(); constAccessorTest >(); } void testTree4Accessor2() { accessorTest >(); accessorTest >(); } void testTree4ConstAccessor2() { constAccessorTest >(); constAccessorTest >(); } void testTree5Accessor2() { accessorTest >(); accessorTest >(); } void testTree5ConstAccessor2() { constAccessorTest >(); constAccessorTest >(); } // only cache leaf level void testTree4Accessor1() { accessorTest >(); accessorTest >(); } void testTree4ConstAccessor1() { constAccessorTest >(); constAccessorTest >(); } // disable node caching void testTree4Accessor0() { accessorTest >(); accessorTest >(); } void testTree4ConstAccessor0() { constAccessorTest >(); constAccessorTest >(); } //cache node level 2 void testTree4Accessor12() { accessorTest >(); accessorTest >(); } //cache node level 1 and 3 void testTree5Accessor213() { accessorTest >(); 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), float(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), float(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->root().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->root().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. openvdb::tools::prune(*tree); //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->root().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-2017 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/TestPointScatter.cc0000644000000000000000000007230213200122377016657 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 using namespace openvdb; using namespace openvdb::points; class TestPointScatter: public CppUnit::TestCase { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestPointScatter); CPPUNIT_TEST(testUniformPointScatter); CPPUNIT_TEST(testDenseUniformPointScatter); CPPUNIT_TEST(testNonUniformPointScatter); CPPUNIT_TEST_SUITE_END(); void testUniformPointScatter(); void testDenseUniformPointScatter(); void testNonUniformPointScatter(); }; // class TestPointScatter void TestPointScatter::testUniformPointScatter() { const Index64 total = 50; const math::CoordBBox boxBounds(math::Coord(-1), math::Coord(1)); // 27 voxels across 8 leaves // Test the free function for all default grid types - 50 points across 27 voxels // ensures all voxels receive points { BoolGrid grid; grid.sparseFill(boxBounds, false, /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { DoubleGrid grid; grid.sparseFill(boxBounds, 0.0, /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { FloatGrid grid; grid.sparseFill(boxBounds, 0.0f, /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { Int32Grid grid; grid.sparseFill(boxBounds, 0, /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { Int64Grid grid; grid.sparseFill(boxBounds, 0, /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { MaskGrid grid; grid.sparseFill(boxBounds, /*maskBuffer*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { StringGrid grid; grid.sparseFill(boxBounds, "", /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { Vec3DGrid grid; grid.sparseFill(boxBounds, Vec3d(), /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { Vec3IGrid grid; grid.sparseFill(boxBounds, Vec3i(), /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { Vec3SGrid grid; grid.sparseFill(boxBounds, Vec3f(), /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } { PointDataGrid grid; grid.sparseFill(boxBounds, 0, /*active*/true); auto points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); } // Test 0 produces empty grid { BoolGrid grid; grid.sparseFill(boxBounds, false, /*active*/true); auto points = points::uniformPointScatter(grid, 0); CPPUNIT_ASSERT(points->empty()); } // Test single point scatter and topology { BoolGrid grid; grid.sparseFill(boxBounds, false, /*active*/true); auto points = points::uniformPointScatter(grid, 1); CPPUNIT_ASSERT_EQUAL(Index32(1), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(1), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(1), pointCount(points->tree())); } // Test a grid containing tiles scatters correctly BoolGrid grid; grid.tree().addTile(/*level*/1, math::Coord(0), /*value*/true, /*active*/true); const Index32 NUM_VALUES = BoolGrid::TreeType::LeafNodeType::NUM_VALUES; CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES), grid.activeVoxelCount()); auto points = points::uniformPointScatter(grid, total); #ifndef OPENVDB_2_ABI_COMPATIBLE CPPUNIT_ASSERT_EQUAL(Index64(0), points->tree().activeTileCount()); #endif CPPUNIT_ASSERT_EQUAL(Index32(1), points->tree().leafCount()); CPPUNIT_ASSERT(Index64(NUM_VALUES) > points->tree().activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); // Explicitly check P attribute const auto* attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); const auto* array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); using PositionArrayT = TypedAttributeArray; CPPUNIT_ASSERT(array->isType()); size_t size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(total), size); AttributeHandle::Ptr pHandle = AttributeHandle::create(*array); for (size_t i = 0; i < size; ++i) { const Vec3f P = pHandle->get(Index(i)); CPPUNIT_ASSERT(P[0] >=-0.5f); CPPUNIT_ASSERT(P[0] <= 0.5f); CPPUNIT_ASSERT(P[1] >=-0.5f); CPPUNIT_ASSERT(P[1] <= 0.5f); CPPUNIT_ASSERT(P[2] >=-0.5f); CPPUNIT_ASSERT(P[2] <= 0.5f); } // Test the rng seed const Vec3f firstPosition = pHandle->get(0); points = points::uniformPointScatter(grid, total, /*seed*/1); attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); CPPUNIT_ASSERT(array->isType()); size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(total), size); pHandle = AttributeHandle::create(*array); const Vec3f secondPosition = pHandle->get(0); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[0], secondPosition[0])); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[1], secondPosition[1])); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[2], secondPosition[2])); // Test spread points = points::uniformPointScatter(grid, total, /*seed*/1, /*spread*/0.2f); attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); CPPUNIT_ASSERT(array->isType()); size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(total), size); pHandle = AttributeHandle::create(*array); for (size_t i = 0; i < size; ++i) { const Vec3f P = pHandle->get(Index(i)); CPPUNIT_ASSERT(P[0] >=-0.2f); CPPUNIT_ASSERT(P[0] <= 0.2f); CPPUNIT_ASSERT(P[1] >=-0.2f); CPPUNIT_ASSERT(P[1] <= 0.2f); CPPUNIT_ASSERT(P[2] >=-0.2f); CPPUNIT_ASSERT(P[2] <= 0.2f); } // Test mt11213b using mt11213b = std::mersenne_twister_engine; points = points::uniformPointScatter(grid, total); CPPUNIT_ASSERT_EQUAL(Index32(1), points->tree().leafCount()); CPPUNIT_ASSERT(Index64(NUM_VALUES) > points->tree().activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(total, pointCount(points->tree())); // Test no remainder - grid contains one tile, scatter NUM_VALUES points points = points::uniformPointScatter(grid, Index64(NUM_VALUES)); CPPUNIT_ASSERT_EQUAL(Index32(1), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES), pointCount(points->tree())); const auto* const leaf = points->tree().probeConstLeaf(math::Coord(0)); CPPUNIT_ASSERT(leaf); CPPUNIT_ASSERT(leaf->isDense()); const auto* const data = leaf->buffer().data(); CPPUNIT_ASSERT_EQUAL(Index32(1), Index32(data[1] - data[0])); for (size_t i = 1; i < NUM_VALUES; ++i) { const Index32 offset = data[i] - data[i - 1]; CPPUNIT_ASSERT_EQUAL(Index32(1), offset); } } void TestPointScatter::testDenseUniformPointScatter() { const Index32 pointsPerVoxel = 8; const math::CoordBBox boxBounds(math::Coord(-1), math::Coord(1)); // 27 voxels across 8 leaves // Test the free function for all default grid types { BoolGrid grid; grid.sparseFill(boxBounds, false, /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { DoubleGrid grid; grid.sparseFill(boxBounds, 0.0, /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { FloatGrid grid; grid.sparseFill(boxBounds, 0.0f, /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { Int32Grid grid; grid.sparseFill(boxBounds, 0, /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { Int64Grid grid; grid.sparseFill(boxBounds, 0, /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { MaskGrid grid; grid.sparseFill(boxBounds, /*maskBuffer*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { StringGrid grid; grid.sparseFill(boxBounds, "", /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { Vec3DGrid grid; grid.sparseFill(boxBounds, Vec3d(), /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { Vec3IGrid grid; grid.sparseFill(boxBounds, Vec3i(), /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { Vec3SGrid grid; grid.sparseFill(boxBounds, Vec3f(), /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { PointDataGrid grid; grid.sparseFill(boxBounds, 0, /*active*/true); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } // Test 0 produces empty grid { BoolGrid grid; grid.sparseFill(boxBounds, false, /*active*/true); auto points = points::denseUniformPointScatter(grid, 0.0f); CPPUNIT_ASSERT(points->empty()); } // Test topology between 0 - 1 { BoolGrid grid; grid.sparseFill(boxBounds, false, /*active*/true); auto points = points::denseUniformPointScatter(grid, 0.8f); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); // Note that a value of 22 is precomputed as the number of active // voxels/points produced by a value of 0.8 CPPUNIT_ASSERT_EQUAL(Index64(22), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(22), pointCount(points->tree())); // Test below 0 throws CPPUNIT_ASSERT_THROW(points::denseUniformPointScatter(grid, -0.1f), openvdb::ValueError); } // Test a grid containing tiles scatters correctly BoolGrid grid; grid.tree().addTile(/*level*/1, math::Coord(0), /*value*/true, /*active*/true); grid.tree().setValueOn(math::Coord(8,0,0)); // add another leaf const Index32 NUM_VALUES = BoolGrid::TreeType::LeafNodeType::NUM_VALUES; CPPUNIT_ASSERT_EQUAL(Index32(1), grid.tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES + 1), grid.activeVoxelCount()); auto points = points::denseUniformPointScatter(grid, pointsPerVoxel); const Index64 expectedCount = Index64(pointsPerVoxel * (NUM_VALUES + 1)); #ifndef OPENVDB_2_ABI_COMPATIBLE CPPUNIT_ASSERT_EQUAL(Index64(0), points->tree().activeTileCount()); #endif CPPUNIT_ASSERT_EQUAL(Index32(2), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES + 1), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(expectedCount, pointCount(points->tree())); // Explicitly check P attribute const auto* attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); const auto* array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); using PositionArrayT = TypedAttributeArray; CPPUNIT_ASSERT(array->isType()); size_t size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(pointsPerVoxel * NUM_VALUES), size); AttributeHandle::Ptr pHandle = AttributeHandle::create(*array); for (size_t i = 0; i < size; ++i) { const Vec3f P = pHandle->get(Index(i)); CPPUNIT_ASSERT(P[0] >=-0.5f); CPPUNIT_ASSERT(P[0] <= 0.5f); CPPUNIT_ASSERT(P[1] >=-0.5f); CPPUNIT_ASSERT(P[1] <= 0.5f); CPPUNIT_ASSERT(P[2] >=-0.5f); CPPUNIT_ASSERT(P[2] <= 0.5f); } // Test the rng seed const Vec3f firstPosition = pHandle->get(0); points = points::denseUniformPointScatter(grid, pointsPerVoxel, /*seed*/1); attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); CPPUNIT_ASSERT(array->isType()); size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(pointsPerVoxel * NUM_VALUES), size); pHandle = AttributeHandle::create(*array); const Vec3f secondPosition = pHandle->get(0); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[0], secondPosition[0])); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[1], secondPosition[1])); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[2], secondPosition[2])); // Test spread points = points::denseUniformPointScatter(grid, pointsPerVoxel, /*seed*/1, /*spread*/0.2f); attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); CPPUNIT_ASSERT(array->isType()); size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(pointsPerVoxel * NUM_VALUES), size); pHandle = AttributeHandle::create(*array); for (size_t i = 0; i < size; ++i) { const Vec3f P = pHandle->get(Index(i)); CPPUNIT_ASSERT(P[0] >=-0.2f); CPPUNIT_ASSERT(P[0] <= 0.2f); CPPUNIT_ASSERT(P[1] >=-0.2f); CPPUNIT_ASSERT(P[1] <= 0.2f); CPPUNIT_ASSERT(P[2] >=-0.2f); CPPUNIT_ASSERT(P[2] <= 0.2f); } // Test mt11213b using mt11213b = std::mersenne_twister_engine; points = points::denseUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(2), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES + 1), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(expectedCount, pointCount(points->tree())); } void TestPointScatter::testNonUniformPointScatter() { const Index32 pointsPerVoxel = 8; const math::CoordBBox totalBoxBounds(math::Coord(-2), math::Coord(2)); // 125 voxels across 8 leaves const math::CoordBBox activeBoxBounds(math::Coord(-1), math::Coord(1)); // 27 voxels across 8 leaves // Test the free function for all default scalar grid types { BoolGrid grid; grid.sparseFill(totalBoxBounds, false, /*active*/true); grid.sparseFill(activeBoxBounds, true, /*active*/true); auto points = points::nonUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { DoubleGrid grid; grid.sparseFill(totalBoxBounds, 0.0, /*active*/true); grid.sparseFill(activeBoxBounds, 1.0, /*active*/true); auto points = points::nonUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { FloatGrid grid; grid.sparseFill(totalBoxBounds, 0.0f, /*active*/true); grid.sparseFill(activeBoxBounds, 1.0f, /*active*/true); auto points = points::nonUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { Int32Grid grid; grid.sparseFill(totalBoxBounds, 0, /*active*/true); grid.sparseFill(activeBoxBounds, 1, /*active*/true); auto points = points::nonUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { Int64Grid grid; grid.sparseFill(totalBoxBounds, 0, /*active*/true); grid.sparseFill(activeBoxBounds, 1, /*active*/true); auto points = points::nonUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } { MaskGrid grid; grid.sparseFill(totalBoxBounds, /*maskBuffer*/0); grid.sparseFill(activeBoxBounds, /*maskBuffer*/1); auto points = points::nonUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(8), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(27), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 27), pointCount(points->tree())); } BoolGrid grid; // Test below 0 throws CPPUNIT_ASSERT_THROW(points::nonUniformPointScatter(grid, -0.1f), openvdb::ValueError); // Test a grid containing tiles scatters correctly grid.tree().addTile(/*level*/1, math::Coord(0), /*value*/true, /*active*/true); grid.tree().setValueOn(math::Coord(8,0,0), true); // add another leaf const Index32 NUM_VALUES = BoolGrid::TreeType::LeafNodeType::NUM_VALUES; CPPUNIT_ASSERT_EQUAL(Index32(1), grid.tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES + 1), grid.activeVoxelCount()); auto points = points::nonUniformPointScatter(grid, pointsPerVoxel); const Index64 expectedCount = Index64(pointsPerVoxel * (NUM_VALUES + 1)); #ifndef OPENVDB_2_ABI_COMPATIBLE CPPUNIT_ASSERT_EQUAL(Index64(0), points->tree().activeTileCount()); #endif CPPUNIT_ASSERT_EQUAL(Index32(2), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES + 1), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(expectedCount, pointCount(points->tree())); // Explicitly check P attribute const auto* attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); const auto* array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); using PositionArrayT = TypedAttributeArray; CPPUNIT_ASSERT(array->isType()); size_t size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(pointsPerVoxel * NUM_VALUES), size); AttributeHandle::Ptr pHandle = AttributeHandle::create(*array); for (size_t i = 0; i < size; ++i) { const Vec3f P = pHandle->get(Index(i)); CPPUNIT_ASSERT(P[0] >=-0.5f); CPPUNIT_ASSERT(P[0] <= 0.5f); CPPUNIT_ASSERT(P[1] >=-0.5f); CPPUNIT_ASSERT(P[1] <= 0.5f); CPPUNIT_ASSERT(P[2] >=-0.5f); CPPUNIT_ASSERT(P[2] <= 0.5f); } // Test the rng seed const Vec3f firstPosition = pHandle->get(0); points = points::nonUniformPointScatter(grid, pointsPerVoxel, /*seed*/1); attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); CPPUNIT_ASSERT(array->isType()); size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(pointsPerVoxel * NUM_VALUES), size); pHandle = AttributeHandle::create(*array); const Vec3f secondPosition = pHandle->get(0); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[0], secondPosition[0])); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[1], secondPosition[1])); CPPUNIT_ASSERT(!math::isExactlyEqual(firstPosition[2], secondPosition[2])); // Test spread points = points::nonUniformPointScatter(grid, pointsPerVoxel, /*seed*/1, /*spread*/0.2f); attributeSet = &(points->tree().cbeginLeaf()->attributeSet()); CPPUNIT_ASSERT_EQUAL(size_t(1), attributeSet->size()); array = attributeSet->getConst(0); CPPUNIT_ASSERT(array); CPPUNIT_ASSERT(array->isType()); size = array->size(); CPPUNIT_ASSERT_EQUAL(size_t(pointsPerVoxel * NUM_VALUES), size); pHandle = AttributeHandle::create(*array); for (size_t i = 0; i < size; ++i) { const Vec3f P = pHandle->get(Index(i)); CPPUNIT_ASSERT(P[0] >=-0.2f); CPPUNIT_ASSERT(P[0] <= 0.2f); CPPUNIT_ASSERT(P[1] >=-0.2f); CPPUNIT_ASSERT(P[1] <= 0.2f); CPPUNIT_ASSERT(P[2] >=-0.2f); CPPUNIT_ASSERT(P[2] <= 0.2f); } // Test varying counts Int32Grid countGrid; // tets negative values equate to 0 countGrid.tree().setValueOn(Coord(0), -1); for (int i = 1; i < 8; ++i) { countGrid.tree().setValueOn(Coord(i), i); } points = points::nonUniformPointScatter(countGrid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(1), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(7), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(pointsPerVoxel * 28), pointCount(points->tree())); for (int i = 1; i < 8; ++i) { CPPUNIT_ASSERT(points->tree().isValueOn(Coord(i))); auto& value = points->tree().getValue(Coord(i)); Index32 expected(0); for (Index32 j = i; j > 0; --j) expected += j; CPPUNIT_ASSERT_EQUAL(Index32(expected * pointsPerVoxel), Index32(value)); } // Test mt11213b using mt11213b = std::mersenne_twister_engine; points = points::nonUniformPointScatter(grid, pointsPerVoxel); CPPUNIT_ASSERT_EQUAL(Index32(2), points->tree().leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(NUM_VALUES + 1), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(expectedCount, pointCount(points->tree())); } CPPUNIT_TEST_SUITE_REGISTRATION(TestPointScatter); // Copyright (c) 2012-2017 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.cc0000644000000000000000000035636013200122377015150 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #include // for tools::setValueOnMin(), et al. #include #include // for io::RealToHalf #include // for Abs() #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); using ValueType = float; using LeafNodeType = openvdb::tree::LeafNode; using InternalNodeType1 = openvdb::tree::InternalNode; using InternalNodeType2 = openvdb::tree::InternalNode; using RootNodeType = openvdb::tree::RootNode; class TestTree: public CppUnit::TestFixture { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTree); CPPUNIT_TEST(testChangeBackground); 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(testFill); CPPUNIT_TEST(testSignedFloodFill); CPPUNIT_TEST(testPruneInactive); CPPUNIT_TEST(testPruneLevelSet); CPPUNIT_TEST(testTouchLeaf); CPPUNIT_TEST(testProbeLeaf); CPPUNIT_TEST(testAddLeaf); CPPUNIT_TEST(testAddTile); CPPUNIT_TEST(testGetNodes); CPPUNIT_TEST(testStealNodes); CPPUNIT_TEST(testProcessBBox); CPPUNIT_TEST(testStealNode); CPPUNIT_TEST_SUITE_END(); void testChangeBackground(); 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 testFill(); void testSignedFloodFill(); void testPruneLevelSet(); void testPruneInactive(); void testTouchLeaf(); void testProbeLeaf(); void testAddLeaf(); void testAddTile(); void testGetNodes(); void testStealNodes(); void testProcessBBox(); void testStealNode(); private: template void testWriteHalf(); template void doTestMerge(openvdb::MergePolicy); template void doTestTopologyDifference(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTree); void TestTree::testChangeBackground() { const int dim = 128; const openvdb::Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f, voxelSize = 1.0f / (dim-1), halfWidth = 4, gamma = halfWidth * voxelSize; using GridT = openvdb::FloatGrid; const openvdb::Coord inside(int(center[0]*dim), int(center[1]*dim), int(center[2]*dim)); const openvdb::Coord outside(dim); {//changeBackground GridT::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, halfWidth); openvdb::FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(grid->tree().isValueOff(outside)); ASSERT_DOUBLES_EXACTLY_EQUAL( gamma, tree.getValue(outside)); CPPUNIT_ASSERT(tree.isValueOff(inside)); ASSERT_DOUBLES_EXACTLY_EQUAL(-gamma, tree.getValue(inside)); const float background = gamma*3.43f; openvdb::tools::changeBackground(tree, background); CPPUNIT_ASSERT(grid->tree().isValueOff(outside)); ASSERT_DOUBLES_EXACTLY_EQUAL( background, tree.getValue(outside)); CPPUNIT_ASSERT(tree.isValueOff(inside)); ASSERT_DOUBLES_EXACTLY_EQUAL(-background, tree.getValue(inside)); } {//changeLevelSetBackground GridT::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, halfWidth); openvdb::FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(grid->tree().isValueOff(outside)); ASSERT_DOUBLES_EXACTLY_EQUAL( gamma, tree.getValue(outside)); CPPUNIT_ASSERT(tree.isValueOff(inside)); ASSERT_DOUBLES_EXACTLY_EQUAL(-gamma, tree.getValue(inside)); const float v1 = gamma*3.43f, v2 = -gamma*6.457f; openvdb::tools::changeAsymmetricLevelSetBackground(tree, v1, v2); CPPUNIT_ASSERT(grid->tree().isValueOff(outside)); ASSERT_DOUBLES_EXACTLY_EQUAL( v1, tree.getValue(outside)); CPPUNIT_ASSERT(tree.isValueOff(inside)); ASSERT_DOUBLES_EXACTLY_EQUAL( v2, tree.getValue(inside)); } } 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() { using GridType = openvdb::Grid; using ValueT = typename TreeType::ValueType; ValueT 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() { using ValueT = typename TreeT::ValueType; 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); } /// Specialization for Coord trees template<> void evalMinMaxTest() { using CoordTree = openvdb::tree::Tree4::Type; const openvdb::Coord backg(5,4,-6), a(5,4,-7), b(5,5,-6); CoordTree tree(backg); // No set voxels (defaults to min = max = zero) openvdb::Coord minVal=openvdb::Coord::max(), maxVal=openvdb::Coord::min(); tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), minVal); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), maxVal); // Only one set voxel tree.setValue(openvdb::Coord(0, 0, 0), a); minVal=openvdb::Coord::max(); maxVal=openvdb::Coord::min(); tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(a, minVal); CPPUNIT_ASSERT_EQUAL(a, maxVal); // Multiple set voxels tree.setValue(openvdb::Coord(-10, -10, -10), b); minVal=openvdb::Coord::max(); maxVal=openvdb::Coord::min(); tree.evalMinMax(minVal, maxVal); CPPUNIT_ASSERT_EQUAL(a, minVal); CPPUNIT_ASSERT_EQUAL(b, maxVal); } } // unnamed namespace void TestTree::testEvalMinMax() { evalMinMaxTest(); 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 = "testIO.dbg"; openvdb::SharedPtr 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; using RootT = typename TreeType::RootNodeType; using LeafT = typename TreeType::LeafNodeType; 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 :) using MyTree = openvdb::tree::Tree4::Type; 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)}; // serial version 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(false); CPPUNIT_ASSERT_EQUAL(3 ,tree.getValueDepth(xyz[0])); CPPUNIT_ASSERT_EQUAL(3 ,tree.getValueDepth(xyz[1])); } // multi-threaded version 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(true); CPPUNIT_ASSERT_EQUAL(3 ,tree.getValueDepth(xyz[0])); CPPUNIT_ASSERT_EQUAL(3 ,tree.getValueDepth(xyz[1])); } #if 0 const CoordBBox bbox(openvdb::Coord(-30,-50,-30), openvdb::Coord(530,610,623)); {// benchmark serial MyTree tree(background); tree.sparseFill( bbox, 1.0f, /*state*/true); openvdb::util::CpuTimer timer("\nserial voxelizeActiveTiles"); tree.voxelizeActiveTiles(/*threaded*/false); timer.stop(); } {// benchmark parallel MyTree tree(background); tree.sparseFill( bbox, 1.0f, /*state*/true); openvdb::util::CpuTimer timer("\nparallel voxelizeActiveTiles"); tree.voxelizeActiveTiles(/*threaded*/true); timer.stop(); } #endif } 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); } } {// test union of a leaf and a tile if (openvdb::FloatTree::DEPTH > 2) { const int leafLevel = openvdb::FloatTree::DEPTH - 1; const int tileLevel = leafLevel - 1; const openvdb::Coord xyz(0); openvdb::FloatTree tree0; tree0.addTile(tileLevel, xyz, /*value=*/0, /*activeState=*/true); CPPUNIT_ASSERT(tree0.isValueOn(xyz)); openvdb::FloatTree tree1; tree1.touchLeaf(xyz)->setValuesOn(); CPPUNIT_ASSERT(tree1.isValueOn(xyz)); tree0.topologyUnion(tree1); CPPUNIT_ASSERT(tree0.isValueOn(xyz)); CPPUNIT_ASSERT_EQUAL(tree0.getValueDepth(xyz), leafLevel); } } }// 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()); openvdb::tools::pruneInactive(tree1); 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()); openvdb::tools::pruneInactive(tree1); 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()); openvdb::tools::pruneInactive(tree1); 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()); openvdb::tools::pruneInactive(tree1); 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 bigRegion(openvdb::Coord(-9), openvdb::Coord(10)); openvdb::CoordBBox smallRegion(openvdb::Coord( 1), openvdb::Coord(10)); openvdb::BoolGrid::Ptr gridBig = openvdb::BoolGrid::create(false); gridBig->fill(bigRegion, 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(smallRegion, 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()); openvdb::tools::pruneInactive(tree1); 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()); openvdb::tools::pruneInactive(tree1); 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()); openvdb::tools::pruneInactive(tree1); 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()); openvdb::tools::pruneInactive(tree1); 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()); openvdb::tools::pruneInactive(tree1); 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()); openvdb::tools::pruneInactive(tree0); 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::testFill() { // Use a custom tree configuration to ensure we flood-fill at all levels! using LeafT = openvdb::tree::LeafNode;//4^3 using InternalT = openvdb::tree::InternalNode;//4^3 using RootT = openvdb::tree::RootNode;// child nodes are 16^3 using TreeT = openvdb::tree::Tree; const float outside = 2.0f, inside = -outside; const openvdb::CoordBBox bbox{openvdb::Coord{-3, -50, 30}, openvdb::Coord{13, 11, 323}}, otherBBox{openvdb::Coord{400, 401, 402}, openvdb::Coord{600}}; {// sparse fill openvdb::Grid::Ptr grid = openvdb::Grid::create(outside); TreeT& tree = grid->tree(); CPPUNIT_ASSERT(!tree.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree.activeVoxelCount()); for (openvdb::CoordBBox::Iterator ijk(bbox); ijk; ++ijk) { ASSERT_DOUBLES_EXACTLY_EQUAL(outside, tree.getValue(*ijk)); } tree.sparseFill(bbox, inside, /*active=*/true); CPPUNIT_ASSERT(tree.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(bbox.volume()), tree.activeVoxelCount()); for (openvdb::CoordBBox::Iterator ijk(bbox); ijk; ++ijk) { ASSERT_DOUBLES_EXACTLY_EQUAL(inside, tree.getValue(*ijk)); } } {// dense fill openvdb::Grid::Ptr grid = openvdb::Grid::create(outside); TreeT& tree = grid->tree(); CPPUNIT_ASSERT(!tree.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(openvdb::Index64(0), tree.activeVoxelCount()); for (openvdb::CoordBBox::Iterator ijk(bbox); ijk; ++ijk) { ASSERT_DOUBLES_EXACTLY_EQUAL(outside, tree.getValue(*ijk)); } // Add some active tiles. tree.sparseFill(otherBBox, inside, /*active=*/true); CPPUNIT_ASSERT(tree.hasActiveTiles()); CPPUNIT_ASSERT_EQUAL(otherBBox.volume(), tree.activeVoxelCount()); tree.denseFill(bbox, inside, /*active=*/true); // In OpenVDB 4.0.0 and earlier, denseFill() densified active tiles // throughout the tree. Verify that it no longer does that. CPPUNIT_ASSERT(tree.hasActiveTiles()); // i.e., otherBBox CPPUNIT_ASSERT_EQUAL(bbox.volume() + otherBBox.volume(), tree.activeVoxelCount()); for (openvdb::CoordBBox::Iterator ijk(bbox); ijk; ++ijk) { ASSERT_DOUBLES_EXACTLY_EQUAL(inside, tree.getValue(*ijk)); } tree.clear(); CPPUNIT_ASSERT(!tree.hasActiveTiles()); tree.sparseFill(otherBBox, inside, /*active=*/true); CPPUNIT_ASSERT(tree.hasActiveTiles()); tree.denseFill(bbox, inside, /*active=*/false); CPPUNIT_ASSERT(tree.hasActiveTiles()); // i.e., otherBBox CPPUNIT_ASSERT_EQUAL(otherBBox.volume(), tree.activeVoxelCount()); // In OpenVDB 4.0.0 and earlier, denseFill() filled sparsely if given // an inactive fill value. Verify that it now fills densely. const int leafDepth = int(tree.treeDepth()) - 1; for (openvdb::CoordBBox::Iterator ijk(bbox); ijk; ++ijk) { CPPUNIT_ASSERT_EQUAL(leafDepth, tree.getValueDepth(*ijk)); ASSERT_DOUBLES_EXACTLY_EQUAL(inside, tree.getValue(*ijk)); } } }// testFill void TestTree::testSignedFloodFill() { // Use a custom tree configuration to ensure we flood-fill at all levels! using LeafT = openvdb::tree::LeafNode;//4^3 using InternalT = openvdb::tree::InternalNode;//4^3 using RootT = openvdb::tree::RootNode;// child nodes are 16^3 using TreeT = openvdb::tree::Tree; const float outside = 2.0f, inside = -outside, radius = 20.0f; {//first test flood filling of a leaf node const LeafT::ValueType fill0=5, fill1=-fill0; openvdb::tools::SignedFloodFillOp sff(fill0, fill1); int D = LeafT::dim(), C=D/2; openvdb::Coord origin(0,0,0), left(0,0,C-1), right(0,0,C); LeafT leaf(origin,fill0); for (int i=0; i::Ptr grid = openvdb::Grid::create(outside); TreeT& tree = grid->tree(); const RootT& root = tree.root(); 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(static_cast(C[0]), static_cast(C[1]), static_cast(C[2])); openvdb::Coord xyz; for (xyz[0]=0; xyz[0]transform().indexToWorld(xyz); const float dist = float((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 = float((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 openvdb::tools::signedFloodFill(tree); 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 = float((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; const 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()); openvdb::tools::prune(tree); CPPUNIT_ASSERT(tree.empty()); openvdb::tools::pruneInactive(tree); CPPUNIT_ASSERT(tree.empty()); // Set some active values. tree.setValue(Coord(-5, 10, 20), 0.1f); tree.setValue(Coord(-5,-10, 20), 0.4f); tree.setValue(Coord(-5, 10,-20), 0.5f); tree.setValue(Coord(-5,-10,-20), 0.7f); tree.setValue(Coord( 5, 10, 20), 0.0f); tree.setValue(Coord( 5,-10, 20), 0.2f); tree.setValue(Coord( 5,-10,-20), 0.6f); tree.setValue(Coord( 5, 10,-20), 0.3f); // 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. openvdb::tools::prune(tree); 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. openvdb::tools::pruneInactive(tree); 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. openvdb::tools::prune(tree); CPPUNIT_ASSERT_EQUAL(Index64(4), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(8), tree.leafCount()); // Verify that pruneInactive() prunes the nodes containing only inactive voxels. openvdb::tools::pruneInactive(tree); 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. openvdb::tools::prune(tree); CPPUNIT_ASSERT_EQUAL(Index64(0), tree.activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index32(4), tree.leafCount()); // Verify that pruneInactive() prunes all of the remaining leaf nodes. openvdb::tools::pruneInactive(tree); 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); openvdb::tools::pruneLevelSet(tree); 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) != nullptr); 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) != nullptr); 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) == nullptr); 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) != nullptr); 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) == nullptr); 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) != nullptr); 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) == nullptr); 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) != nullptr); 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) == nullptr); 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) != nullptr); 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; using LeafT = FloatTree::LeafNodeType; const Coord ijk(100); FloatGrid grid; FloatTree& tree = grid.tree(); tree.setValue(ijk, 5.0); const LeafT* oldLeaf = tree.probeLeaf(ijk); CPPUNIT_ASSERT(oldLeaf != nullptr); 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) != nullptr); const Index lvl = FloatTree::DEPTH >> 1; OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN if (lvl > 0) tree.addTile(lvl,ijk, 3.0, /*active=*/true); else tree.addTile(1,ijk, 3.0, /*active=*/true); OPENVDB_NO_UNREACHABLE_CODE_WARNING_END CPPUNIT_ASSERT(tree.probeLeaf(ijk) == nullptr); 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="< and Tree::getNodes()"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::vector std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::getNodes()"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() const with std::vector std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::getNodes() const"); const FloatTree& tmp = tree; tmp.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::vector and std::vector::reserve std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector, std::vector::reserve and Tree::getNodes"); array.reserve(tree.leafCount()); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::deque std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::getNodes"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::deque std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::getNodes"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(size_t(1), array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); } {//testing Tree::getNodes() with std::deque std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::getNodes"); tree.getNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(size_t(1), array.size()); CPPUNIT_ASSERT_EQUAL(leafCount, size_t(tree.leafCount())); } /* {//testing Tree::getNodes() with std::deque where T is not part of the tree configuration using NodeT = openvdb::tree::LeafNode; std::deque array; tree.getNodes(array);//should NOT compile since NodeT is not part of the FloatTree configuration } {//testing Tree::getNodes() const with std::deque where T is not part of the tree configuration using NodeT = openvdb::tree::LeafNode; std::deque array; const FloatTree& tmp = tree; tmp.getNodes(array);//should NOT compile since NodeT is not part of the FloatTree configuration } */ }// testGetNodes void TestTree::testStealNodes() { //unittest_util::CpuTimer timer; using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Vec3f; using openvdb::FloatGrid; using openvdb::FloatTree; const Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f; const int dim = 128, half_width = 5; const float voxel_size = 1.0f/dim; FloatGrid::Ptr grid = FloatGrid::create(/*background=*/half_width*voxel_size); const FloatTree& tree = grid->tree(); grid->setTransform(openvdb::math::Transform::createLinearTransform(/*voxel size=*/voxel_size)); unittest_util::makeSphere( Coord(dim), center, radius, *grid, unittest_util::SPHERE_SPARSE_NARROW_BAND); const size_t leafCount = tree.leafCount(); const size_t voxelCount = tree.activeVoxelCount(); {//testing Tree::stealNodes() with std::vector FloatTree tree2 = tree; std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::stealNodes()"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::stealNodes() with std::vector FloatTree tree2 = tree; std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::stealNodes()"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::stealNodes() const with std::vector FloatTree tree2 = tree; std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector and Tree::stealNodes() const"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::stealNodes() with std::vector and std::vector::reserve FloatTree tree2 = tree; std::vector array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::vector, std::vector::reserve and Tree::stealNodes"); array.reserve(tree2.leafCount()); tree2.stealNodes(array, 0.0f, false); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::deque FloatTree tree2 = tree; std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::stealNodes"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); size_t sum = 0; for (size_t i=0; ionVoxelCount(); CPPUNIT_ASSERT_EQUAL(voxelCount, sum); } {//testing Tree::getNodes() with std::deque FloatTree tree2 = tree; std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::stealNodes"); tree2.stealNodes(array, 0.0f, true); //timer.stop(); CPPUNIT_ASSERT_EQUAL(size_t(1), array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); } {//testing Tree::getNodes() with std::deque FloatTree tree2 = tree; std::deque array; CPPUNIT_ASSERT_EQUAL(size_t(0), array.size()); //timer.start("\nstd::deque and Tree::stealNodes"); tree2.stealNodes(array); //timer.stop(); CPPUNIT_ASSERT_EQUAL(size_t(1), array.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), size_t(tree2.leafCount())); } /* {//testing Tree::stealNodes() with std::deque where T is not part of the tree configuration FloatTree tree2 = tree; using NodeT = openvdb::tree::LeafNode; std::deque array; //should NOT compile since NodeT is not part of the FloatTree configuration tree2.stealNodes(array, 0.0f, true); } */ }// testStealNodes void TestTree::testStealNode() { using openvdb::Index; using openvdb::FloatTree; const float background=0.0f, value = 5.6f, epsilon=0.000001f; const openvdb::Coord xyz(-23,42,70); {// stal a LeafNode using NodeT = FloatTree::LeafNodeType; CPPUNIT_ASSERT_EQUAL(Index(0), NodeT::getLevel()); FloatTree tree(background); CPPUNIT_ASSERT_EQUAL(Index(0), tree.leafCount()); CPPUNIT_ASSERT(!tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, tree.getValue(xyz), epsilon); CPPUNIT_ASSERT(tree.root().stealNode(xyz, value, false) == nullptr); tree.setValue(xyz, value); CPPUNIT_ASSERT_EQUAL(Index(1), tree.leafCount()); CPPUNIT_ASSERT(tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(value, tree.getValue(xyz), epsilon); NodeT* node = tree.root().stealNode(xyz, background, false); CPPUNIT_ASSERT(node != nullptr); CPPUNIT_ASSERT_EQUAL(Index(0), tree.leafCount()); CPPUNIT_ASSERT(!tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, tree.getValue(xyz), epsilon); CPPUNIT_ASSERT(tree.root().stealNode(xyz, value, false) == nullptr); CPPUNIT_ASSERT_DOUBLES_EQUAL(value, node->getValue(xyz), epsilon); CPPUNIT_ASSERT(node->isValueOn(xyz)); delete node; } {// steal a bottom InternalNode using NodeT = FloatTree::RootNodeType::ChildNodeType::ChildNodeType; CPPUNIT_ASSERT_EQUAL(Index(1), NodeT::getLevel()); FloatTree tree(background); CPPUNIT_ASSERT_EQUAL(Index(0), tree.leafCount()); CPPUNIT_ASSERT(!tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, tree.getValue(xyz), epsilon); CPPUNIT_ASSERT(tree.root().stealNode(xyz, value, false) == nullptr); tree.setValue(xyz, value); CPPUNIT_ASSERT_EQUAL(Index(1), tree.leafCount()); CPPUNIT_ASSERT(tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(value, tree.getValue(xyz), epsilon); NodeT* node = tree.root().stealNode(xyz, background, false); CPPUNIT_ASSERT(node != nullptr); CPPUNIT_ASSERT_EQUAL(Index(0), tree.leafCount()); CPPUNIT_ASSERT(!tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, tree.getValue(xyz), epsilon); CPPUNIT_ASSERT(tree.root().stealNode(xyz, value, false) == nullptr); CPPUNIT_ASSERT_DOUBLES_EQUAL(value, node->getValue(xyz), epsilon); CPPUNIT_ASSERT(node->isValueOn(xyz)); delete node; } {// steal a top InternalNode using NodeT = FloatTree::RootNodeType::ChildNodeType; CPPUNIT_ASSERT_EQUAL(Index(2), NodeT::getLevel()); FloatTree tree(background); CPPUNIT_ASSERT_EQUAL(Index(0), tree.leafCount()); CPPUNIT_ASSERT(!tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, tree.getValue(xyz), epsilon); CPPUNIT_ASSERT(tree.root().stealNode(xyz, value, false) == nullptr); tree.setValue(xyz, value); CPPUNIT_ASSERT_EQUAL(Index(1), tree.leafCount()); CPPUNIT_ASSERT(tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(value, tree.getValue(xyz), epsilon); NodeT* node = tree.root().stealNode(xyz, background, false); CPPUNIT_ASSERT(node != nullptr); CPPUNIT_ASSERT_EQUAL(Index(0), tree.leafCount()); CPPUNIT_ASSERT(!tree.isValueOn(xyz)); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, tree.getValue(xyz), epsilon); CPPUNIT_ASSERT(tree.root().stealNode(xyz, value, false) == nullptr); CPPUNIT_ASSERT_DOUBLES_EQUAL(value, node->getValue(xyz), epsilon); CPPUNIT_ASSERT(node->isValueOn(xyz)); delete node; } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000002045413200122377015132 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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)); } {// isNegative CPPUNIT_ASSERT(!std::is_signed::value); CPPUNIT_ASSERT(std::is_signed::value); CPPUNIT_ASSERT(!std::is_signed::value); //CPPUNIT_ASSERT(std::is_signed::value);//fails! //CPPUNIT_ASSERT(std::is_signed::value);//fails! CPPUNIT_ASSERT( math::isNegative(-1.0f)); CPPUNIT_ASSERT(!math::isNegative( 1.0f)); CPPUNIT_ASSERT( math::isNegative(-1.0)); CPPUNIT_ASSERT(!math::isNegative( 1.0)); CPPUNIT_ASSERT(!math::isNegative(true)); CPPUNIT_ASSERT(!math::isNegative(false)); CPPUNIT_ASSERT(!math::isNegative(1u)); CPPUNIT_ASSERT( math::isNegative(-1)); CPPUNIT_ASSERT(!math::isNegative( 1)); } } 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-2017 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/TestPointGroup.cc0000644000000000000000000005155213200122377016352 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #ifdef _MSC_VER #include #endif using namespace openvdb; using namespace openvdb::points; class TestPointGroup: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestPointGroup); CPPUNIT_TEST(testDescriptor); CPPUNIT_TEST(testAppendDrop); CPPUNIT_TEST(testCompact); CPPUNIT_TEST(testSet); CPPUNIT_TEST(testFilter); CPPUNIT_TEST_SUITE_END(); void testDescriptor(); void testAppendDrop(); void testCompact(); void testSet(); void testFilter(); }; // class TestPointGroup CPPUNIT_TEST_SUITE_REGISTRATION(TestPointGroup); //////////////////////////////////////// class FirstFilter { public: static bool initialized() { return true; } template void reset(const LeafT&) { } template bool valid(const IterT& iter) const { return *iter == 0; } }; // class FirstFilter //////////////////////////////////////// namespace { bool testStringVector(std::vector& input) { return input.size() == 0; } bool testStringVector(std::vector& input, const Name& name1) { if (input.size() != 1) return false; if (input[0] != name1) return false; return true; } bool testStringVector(std::vector& input, const Name& name1, const Name& name2) { if (input.size() != 2) return false; if (input[0] != name1) return false; if (input[1] != name2) return false; return true; } } // namespace void TestPointGroup::testDescriptor() { // test missing groups deletion { // no groups, empty Descriptor std::vector groups; AttributeSet::Descriptor descriptor; deleteMissingPointGroups(groups, descriptor); CPPUNIT_ASSERT(testStringVector(groups)); } { // one group, empty Descriptor std::vector groups{"group1"}; AttributeSet::Descriptor descriptor; deleteMissingPointGroups(groups, descriptor); CPPUNIT_ASSERT(testStringVector(groups)); } { // one group, Descriptor with same group std::vector groups{"group1"}; AttributeSet::Descriptor descriptor; descriptor.setGroup("group1", 0); deleteMissingPointGroups(groups, descriptor); CPPUNIT_ASSERT(testStringVector(groups, "group1")); } { // one group, Descriptor with different group std::vector groups{"group1"}; AttributeSet::Descriptor descriptor; descriptor.setGroup("group2", 0); deleteMissingPointGroups(groups, descriptor); CPPUNIT_ASSERT(testStringVector(groups)); } { // three groups, Descriptor with three groups, one different std::vector groups{"group1", "group3", "group4"}; AttributeSet::Descriptor descriptor; descriptor.setGroup("group1", 0); descriptor.setGroup("group2", 0); descriptor.setGroup("group4", 0); deleteMissingPointGroups(groups, descriptor); CPPUNIT_ASSERT(testStringVector(groups, "group1", "group4")); } } //////////////////////////////////////// void TestPointGroup::testAppendDrop() { std::vector positions{{1, 1, 1}, {1, 10, 1}, {10, 1, 1}, {10, 10, 1}}; const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); // check one leaf per point CPPUNIT_ASSERT_EQUAL(tree.leafCount(), Index32(4)); // retrieve first and last leaf attribute sets PointDataTree::LeafCIter leafIter = tree.cbeginLeaf(); const AttributeSet& attributeSet = leafIter->attributeSet(); ++leafIter; ++leafIter; ++leafIter; const AttributeSet& attributeSet4 = leafIter->attributeSet(); { // throw on append or drop an empty group CPPUNIT_ASSERT_THROW(appendGroup(tree, ""), openvdb::KeyError); CPPUNIT_ASSERT_THROW(dropGroup(tree, ""), openvdb::KeyError); } { // append a group appendGroup(tree, "test"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(1)); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test")); CPPUNIT_ASSERT(attributeSet4.descriptor().hasGroup("test")); } { // append a group with non-unique name (repeat the append) appendGroup(tree, "test"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(1)); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test")); CPPUNIT_ASSERT(attributeSet4.descriptor().hasGroup("test")); } { // append multiple groups std::vector names{"test2", "test3"}; appendGroups(tree, names); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(3)); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test")); CPPUNIT_ASSERT(attributeSet4.descriptor().hasGroup("test")); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test2")); CPPUNIT_ASSERT(attributeSet4.descriptor().hasGroup("test2")); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test3")); CPPUNIT_ASSERT(attributeSet4.descriptor().hasGroup("test3")); } { // append to a copy PointDataTree tree2(tree); appendGroup(tree2, "copy1"); CPPUNIT_ASSERT(!attributeSet.descriptor().hasGroup("copy1")); CPPUNIT_ASSERT(tree2.beginLeaf()->attributeSet().descriptor().hasGroup("copy1")); } { // drop a group dropGroup(tree, "test2"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(2)); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test")); CPPUNIT_ASSERT(attributeSet4.descriptor().hasGroup("test")); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test3")); CPPUNIT_ASSERT(attributeSet4.descriptor().hasGroup("test3")); } { // drop multiple groups std::vector names{"test", "test3"}; dropGroups(tree, names); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(0)); } { // drop a copy appendGroup(tree, "copy2"); PointDataTree tree2(tree); dropGroup(tree2, "copy2"); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("copy2")); CPPUNIT_ASSERT(!tree2.beginLeaf()->attributeSet().descriptor().hasGroup("copy2")); dropGroup(tree, "copy2"); } { // set group membership appendGroup(tree, "test"); setGroup(tree, "test", true); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "test"), Index64(4)); setGroup(tree, "test", false); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "test"), Index64(0)); dropGroup(tree, "test"); } { // drop all groups appendGroup(tree, "test"); appendGroup(tree, "test2"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(2)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(1)); dropGroups(tree); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(0)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(0)); } } void TestPointGroup::testCompact() { std::vector positions{{1, 1, 1}}; const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); // check one leaf CPPUNIT_ASSERT_EQUAL(tree.leafCount(), Index32(1)); // retrieve first and last leaf attribute sets PointDataTree::LeafCIter leafIter = tree.cbeginLeaf(); const AttributeSet& attributeSet = leafIter->attributeSet(); std::stringstream ss; { // append nine groups for (int i = 0; i < 8; i++) { ss.str(""); ss << "test" << i; appendGroup(tree, ss.str()); } CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(8)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(1)); appendGroup(tree, "test8"); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test0")); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test7")); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test8")); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(9)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(2)); } { // drop first attribute then compact dropGroup(tree, "test5", /*compact=*/false); CPPUNIT_ASSERT(!attributeSet.descriptor().hasGroup("test5")); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(8)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(2)); compactGroups(tree); CPPUNIT_ASSERT(!attributeSet.descriptor().hasGroup("test5")); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test7")); CPPUNIT_ASSERT(attributeSet.descriptor().hasGroup("test8")); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(8)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(1)); } { // append seventeen groups, drop most of them, then compact for (int i = 0; i < 17; i++) { ss.str(""); ss << "test" << i; appendGroup(tree, ss.str()); } CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(17)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(3)); // delete all but 0, 5, 9, 15 for (int i = 0; i < 17; i++) { if (i == 0 || i == 5 || i == 9 || i == 15) continue; ss.str(""); ss << "test" << i; dropGroup(tree, ss.str(), /*compact=*/false); } CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(3)); // make a copy PointDataTree tree2(tree); // compact - should now occupy one attribute compactGroups(tree); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().count(GroupAttributeArray::attributeType()), size_t(1)); // check descriptor has been deep copied CPPUNIT_ASSERT_EQUAL(tree2.cbeginLeaf()->attributeSet().descriptor().groupMap().size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(tree2.cbeginLeaf()->attributeSet().descriptor().count(GroupAttributeArray::attributeType()), size_t(3)); } } void TestPointGroup::testSet() { // four points in the same leaf std::vector positions = { {1, 1, 1}, {1, 2, 1}, {2, 1, 1}, {2, 2, 1}, {100, 100, 100}, {100, 101, 100} }; const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); const PointAttributeVector pointList(positions); openvdb::tools::PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid(pointList, *transform); PointDataGrid::Ptr grid = createPointDataGrid(*pointIndexGrid, pointList, *transform); PointDataTree& tree = grid->tree(); appendGroup(tree, "test"); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(6)); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "test"), Index64(0)); std::vector membership{1, 0, 1, 1, 0, 1}; // copy tree for descriptor sharing test PointDataTree tree2(tree); setGroup(tree, pointIndexGrid->tree(), membership, "test"); // check that descriptor remains shared appendGroup(tree2, "copy1"); CPPUNIT_ASSERT(!tree.cbeginLeaf()->attributeSet().descriptor().hasGroup("copy1")); dropGroup(tree2, "copy1"); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(6)); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "test"), Index64(4)); { // IO // setup temp directory std::string tempDir; if (const char* dir = std::getenv("TMPDIR")) tempDir = dir; #ifdef _MSC_VER if (tempDir.empty()) { char tempDirBuffer[MAX_PATH+1]; int tempDirLen = GetTempPath(MAX_PATH+1, tempDirBuffer); CPPUNIT_ASSERT(tempDirLen > 0 && tempDirLen <= MAX_PATH); tempDir = tempDirBuffer; } #else if (tempDir.empty()) tempDir = P_tmpdir; #endif std::string filename; // write out grid to a temp file { filename = tempDir + "/openvdb_test_point_load"; io::File fileOut(filename); GridCPtrVec grids{grid}; fileOut.write(grids); } // read test groups { io::File fileIn(filename); fileIn.open(); GridPtrVecPtr grids = fileIn.getGrids(); fileIn.close(); CPPUNIT_ASSERT_EQUAL(grids->size(), size_t(1)); PointDataGrid::Ptr inputGrid = GridBase::grid((*grids)[0]); PointDataTree& treex = inputGrid->tree(); CPPUNIT_ASSERT(treex.cbeginLeaf()); const PointDataGrid::TreeType::LeafNodeType& leaf = *treex.cbeginLeaf(); const AttributeSet::Descriptor& descriptor = leaf.attributeSet().descriptor(); CPPUNIT_ASSERT(descriptor.hasGroup("test")); CPPUNIT_ASSERT_EQUAL(descriptor.groupMap().size(), size_t(1)); CPPUNIT_ASSERT_EQUAL(pointCount(treex), Index64(6)); CPPUNIT_ASSERT_EQUAL(groupPointCount(treex, "test"), Index64(4)); } std::remove(filename.c_str()); } } void TestPointGroup::testFilter() { const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid; { // four points in the same leaf std::vector positions = { {1, 1, 1}, {1, 2, 1}, {2, 1, 1}, {2, 2, 1}, {100, 100, 100}, {100, 101, 100} }; const PointAttributeVector pointList(positions); openvdb::tools::PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid(pointList, *transform); grid = createPointDataGrid(*pointIndexGrid, pointList, *transform); } PointDataTree& tree = grid->tree(); { // first point filter appendGroup(tree, "first"); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(6)); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "first"), Index64(0)); FirstFilter filter; setGroupByFilter(tree, "first", filter); auto iter = tree.cbeginLeaf(); for ( ; iter; ++iter) { CPPUNIT_ASSERT_EQUAL(iter->groupPointCount("first"), Index64(1)); } CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "first"), Index64(2)); } const openvdb::BBoxd bbox(openvdb::Vec3d(0, 1.5, 0), openvdb::Vec3d(101, 100.5, 101)); { // bbox filter appendGroup(tree, "bbox"); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(6)); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "bbox"), Index64(0)); BBoxFilter filter(*transform, bbox); setGroupByFilter(tree, "bbox", filter); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "bbox"), Index64(3)); } { // first point filter and bbox filter (intersection of the above two filters) appendGroup(tree, "first_bbox"); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(6)); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "first_bbox"), Index64(0)); using FirstBBoxFilter = BinaryFilter; FirstFilter firstFilter; BBoxFilter bboxFilter(*transform, bbox); FirstBBoxFilter filter(firstFilter, bboxFilter); setGroupByFilter(tree, "first_bbox", filter); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "first_bbox"), Index64(1)); std::vector positions; for (auto iter = tree.cbeginLeaf(); iter; ++iter) { GroupFilter filterx("first_bbox", iter->attributeSet()); auto filterIndexIter = iter->beginIndexOn(filterx); auto handle = AttributeHandle::create(iter->attributeArray("P")); for ( ; filterIndexIter; ++filterIndexIter) { const openvdb::Coord ijk = filterIndexIter.getCoord(); positions.push_back(handle->get(*filterIndexIter) + ijk.asVec3d()); } } CPPUNIT_ASSERT_EQUAL(positions.size(), size_t(1)); CPPUNIT_ASSERT_EQUAL(positions[0], Vec3f(100, 100, 100)); } { // add 1000 points in three leafs (positions aren't important) std::vector positions(1000, {1, 1, 1}); positions.insert(positions.end(), 1000, {1, 1, 9}); positions.insert(positions.end(), 1000, {9, 9, 9}); const PointAttributeVector pointList(positions); openvdb::tools::PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid(pointList, *transform); grid = createPointDataGrid(*pointIndexGrid, pointList, *transform); PointDataTree& newTree = grid->tree(); CPPUNIT_ASSERT_EQUAL(pointCount(newTree), Index64(3000)); // random - maximum appendGroup(newTree, "random_maximum"); const Index64 target = 1001; setGroupByRandomTarget(newTree, "random_maximum", target); CPPUNIT_ASSERT_EQUAL(groupPointCount(newTree, "random_maximum"), target); // random - percentage appendGroup(newTree, "random_percentage"); setGroupByRandomPercentage(newTree, "random_percentage", 33.333333f); CPPUNIT_ASSERT_EQUAL(groupPointCount(newTree, "random_percentage"), Index64(1000)); } } // Copyright (c) 2012-2017 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/TestStreamCompression.cc0000644000000000000000000005527213200122377017724 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // io::COMPRESS_BLOSC #ifdef __clang__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-macros" #endif // Boost.Interprocess uses a header-only portion of Boost.DateTime #define BOOST_DATE_TIME_NO_LIB #ifdef __clang__ #pragma GCC diagnostic pop #endif #include #include #include #include #include #include #include #include // for BOOST_VERSION #include #ifdef _MSC_VER #include // open_existing_file(), close_file() // boost::interprocess::detail was renamed to boost::interprocess::ipcdetail in Boost 1.48. // Ensure that both namespaces exist. namespace boost { namespace interprocess { namespace detail {} namespace ipcdetail {} } } #include #else #include // for struct stat #include // for stat() #include // for unlink() #endif #include #include // for std::iota() #ifdef OPENVDB_USE_BLOSC #include // A Blosc optimization introduced in 1.11.0 uses a slightly smaller block size for // HCR codecs (LZ4, ZLIB, ZSTD), which otherwise fails a few regression test cases #if BLOSC_VERSION_MAJOR > 0 && BLOSC_VERSION_MINOR > 10 #define BLOSC_HCR_BLOCKSIZE_OPTIMIZATION #endif #endif /// @brief io::MappedFile has a private constructor, so this unit tests uses a matching proxy class ProxyMappedFile { public: explicit ProxyMappedFile(const std::string& filename) : mImpl(new Impl(filename)) { } private: class Impl { public: Impl(const std::string& filename) : mMap(filename.c_str(), boost::interprocess::read_only) , mRegion(mMap, boost::interprocess::read_only) { mLastWriteTime = 0; const char* regionFilename = mMap.get_name(); #ifdef _MSC_VER using namespace boost::interprocess::detail; using namespace boost::interprocess::ipcdetail; using openvdb::Index64; if (void* fh = open_existing_file(regionFilename, boost::interprocess::read_only)) { FILETIME mtime; if (GetFileTime(fh, nullptr, nullptr, &mtime)) { mLastWriteTime = (Index64(mtime.dwHighDateTime) << 32) | mtime.dwLowDateTime; } close_file(fh); } #else struct stat info; if (0 == ::stat(regionFilename, &info)) { mLastWriteTime = openvdb::Index64(info.st_mtime); } #endif } using Notifier = std::function; boost::interprocess::file_mapping mMap; boost::interprocess::mapped_region mRegion; bool mAutoDelete = false; Notifier mNotifier; mutable tbb::atomic mLastWriteTime; }; // class Impl std::unique_ptr mImpl; }; // class ProxyMappedFile using namespace openvdb; using namespace openvdb::compression; class TestStreamCompression: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestStreamCompression); CPPUNIT_TEST(testBlosc); CPPUNIT_TEST(testPagedStreams); CPPUNIT_TEST_SUITE_END(); void testBlosc(); void testPagedStreams(); }; // class TestStreamCompression CPPUNIT_TEST_SUITE_REGISTRATION(TestStreamCompression); //////////////////////////////////////// void TestStreamCompression::testBlosc() { // ensure that the library and unit tests are both built with or without Blosc enabled #ifdef OPENVDB_USE_BLOSC CPPUNIT_ASSERT(bloscCanCompress()); #else CPPUNIT_ASSERT(!bloscCanCompress()); #endif const int count = 256; { // valid buffer // compress std::unique_ptr uncompressedBuffer(new int[count]); for (int i = 0; i < count; i++) { uncompressedBuffer.get()[i] = i / 2; } size_t uncompressedBytes = count * sizeof(int); size_t compressedBytes; size_t testCompressedBytes = bloscCompressedSize( reinterpret_cast(uncompressedBuffer.get()), uncompressedBytes); std::unique_ptr compressedBuffer = bloscCompress( reinterpret_cast(uncompressedBuffer.get()), uncompressedBytes, compressedBytes); #ifdef OPENVDB_USE_BLOSC CPPUNIT_ASSERT(compressedBytes < uncompressedBytes); CPPUNIT_ASSERT(compressedBuffer); CPPUNIT_ASSERT_EQUAL(testCompressedBytes, compressedBytes); // uncompressedSize CPPUNIT_ASSERT_EQUAL(uncompressedBytes, bloscUncompressedSize(compressedBuffer.get())); // decompress std::unique_ptr newUncompressedBuffer = bloscDecompress(compressedBuffer.get(), uncompressedBytes); // incorrect number of expected bytes CPPUNIT_ASSERT_THROW(newUncompressedBuffer = bloscDecompress(compressedBuffer.get(), 1), openvdb::RuntimeError); CPPUNIT_ASSERT(newUncompressedBuffer); #else CPPUNIT_ASSERT(!compressedBuffer); CPPUNIT_ASSERT_EQUAL(testCompressedBytes, size_t(0)); // uncompressedSize CPPUNIT_ASSERT_THROW(bloscUncompressedSize(compressedBuffer.get()), openvdb::RuntimeError); // decompress std::unique_ptr newUncompressedBuffer; CPPUNIT_ASSERT_THROW( newUncompressedBuffer = bloscDecompress(compressedBuffer.get(), uncompressedBytes), openvdb::RuntimeError); CPPUNIT_ASSERT(!newUncompressedBuffer); #endif } { // one value (below minimum bytes) std::unique_ptr uncompressedBuffer(new int[1]); uncompressedBuffer.get()[0] = 10; size_t compressedBytes; std::unique_ptr compressedBuffer = bloscCompress( reinterpret_cast(uncompressedBuffer.get()), sizeof(int), compressedBytes); CPPUNIT_ASSERT(!compressedBuffer); CPPUNIT_ASSERT_EQUAL(compressedBytes, size_t(0)); } { // padded buffer std::unique_ptr largeBuffer(new char[2048]); for (int paddedCount = 1; paddedCount < 256; paddedCount++) { std::unique_ptr newTest(new char[paddedCount]); for (int i = 0; i < paddedCount; i++) newTest.get()[i] = char(0); #ifdef OPENVDB_USE_BLOSC size_t compressedBytes; std::unique_ptr compressedBuffer = bloscCompress( newTest.get(), paddedCount, compressedBytes); // compress into a large buffer to check for any padding issues size_t compressedSizeBytes; bloscCompress(largeBuffer.get(), compressedSizeBytes, size_t(2048), newTest.get(), paddedCount); // regardless of compression, these numbers should always match CPPUNIT_ASSERT_EQUAL(compressedSizeBytes, compressedBytes); // no compression performed due to buffer being too small if (paddedCount <= BLOSC_MINIMUM_BYTES) { CPPUNIT_ASSERT(!compressedBuffer); } else { CPPUNIT_ASSERT(compressedBuffer); CPPUNIT_ASSERT(compressedBytes > 0); CPPUNIT_ASSERT(int(compressedBytes) < paddedCount); std::unique_ptr uncompressedBuffer = bloscDecompress( compressedBuffer.get(), paddedCount); CPPUNIT_ASSERT(uncompressedBuffer); for (int i = 0; i < paddedCount; i++) { CPPUNIT_ASSERT_EQUAL((uncompressedBuffer.get())[i], newTest[i]); } } #endif } } { // invalid buffer (out of range) // compress std::vector smallBuffer; smallBuffer.reserve(count); for (int i = 0; i < count; i++) smallBuffer[i] = i; size_t invalidBytes = INT_MAX - 1; size_t testCompressedBytes = bloscCompressedSize( reinterpret_cast(&smallBuffer[0]), invalidBytes); CPPUNIT_ASSERT_EQUAL(testCompressedBytes, size_t(0)); std::unique_ptr buffer = bloscCompress( reinterpret_cast(&smallBuffer[0]), invalidBytes, testCompressedBytes); CPPUNIT_ASSERT(!buffer); CPPUNIT_ASSERT_EQUAL(testCompressedBytes, size_t(0)); // decompress #ifdef OPENVDB_USE_BLOSC std::unique_ptr compressedBuffer = bloscCompress( reinterpret_cast(&smallBuffer[0]), count * sizeof(int), testCompressedBytes); CPPUNIT_ASSERT_THROW(buffer = bloscDecompress( reinterpret_cast(compressedBuffer.get()), invalidBytes - 16), openvdb::RuntimeError); CPPUNIT_ASSERT(!buffer); CPPUNIT_ASSERT_THROW(bloscDecompress( reinterpret_cast(compressedBuffer.get()), count * sizeof(int) + 1), openvdb::RuntimeError); #endif } { // uncompressible buffer const int uncompressedCount = 32; std::vector values; values.reserve(uncompressedCount); // 128 bytes for (int i = 0; i < uncompressedCount; i++) values.push_back(i*10000); std::random_shuffle(values.begin(), values.end()); std::unique_ptr uncompressedBuffer(new int[values.size()]); for (size_t i = 0; i < values.size(); i++) uncompressedBuffer.get()[i] = values[i]; size_t uncompressedBytes = values.size() * sizeof(int); size_t compressedBytes; std::unique_ptr compressedBuffer = bloscCompress( reinterpret_cast(uncompressedBuffer.get()), uncompressedBytes, compressedBytes); CPPUNIT_ASSERT(!compressedBuffer); CPPUNIT_ASSERT_EQUAL(compressedBytes, size_t(0)); } } void TestStreamCompression::testPagedStreams() { { // one small value std::ostringstream ostr(std::ios_base::binary); PagedOutputStream ostream(ostr); int foo = 5; ostream.write(reinterpret_cast(&foo), sizeof(int)); CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(0)); ostream.flush(); CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(sizeof(int))); } { // small values up to page threshold std::ostringstream ostr(std::ios_base::binary); PagedOutputStream ostream(ostr); for (int i = 0; i < PageSize; i++) { uint8_t oneByte = 255; ostream.write(reinterpret_cast(&oneByte), sizeof(uint8_t)); } CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(0)); std::vector values; values.assign(PageSize, uint8_t(255)); size_t compressedSize = compression::bloscCompressedSize( reinterpret_cast(&values[0]), PageSize); uint8_t oneMoreByte(255); ostream.write(reinterpret_cast(&oneMoreByte), sizeof(char)); if (compressedSize == 0) { CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(PageSize)); } else { CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(compressedSize)); } } { // one large block at exactly page threshold std::ostringstream ostr(std::ios_base::binary); PagedOutputStream ostream(ostr); std::vector values; values.assign(PageSize, uint8_t(255)); ostream.write(reinterpret_cast(&values[0]), values.size()); CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(0)); } { // two large blocks at page threshold + 1 byte std::ostringstream ostr(std::ios_base::binary); PagedOutputStream ostream(ostr); std::vector values; values.assign(PageSize + 1, uint8_t(255)); ostream.write(reinterpret_cast(&values[0]), values.size()); size_t compressedSize = compression::bloscCompressedSize( reinterpret_cast(&values[0]), values.size()); #ifndef OPENVDB_USE_BLOSC compressedSize = values.size(); #endif CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(compressedSize)); ostream.write(reinterpret_cast(&values[0]), values.size()); CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(compressedSize * 2)); uint8_t oneMoreByte(255); ostream.write(reinterpret_cast(&oneMoreByte), sizeof(uint8_t)); ostream.flush(); CPPUNIT_ASSERT_EQUAL(ostr.tellp(), std::streampos(compressedSize * 2 + 1)); } { // one full page std::stringstream ss(std::ios_base::out | std::ios_base::in | std::ios_base::binary); // write PagedOutputStream ostreamSizeOnly(ss); ostreamSizeOnly.setSizeOnly(true); CPPUNIT_ASSERT_EQUAL(ss.tellp(), std::streampos(0)); std::vector values; values.resize(PageSize); std::iota(values.begin(), values.end(), 0); // ascending integer values ostreamSizeOnly.write(reinterpret_cast(&values[0]), values.size()); ostreamSizeOnly.flush(); #ifdef OPENVDB_USE_BLOSC // two integers - compressed size and uncompressed size CPPUNIT_ASSERT_EQUAL(ss.tellp(), std::streampos(sizeof(int)*2)); #else // one integer - uncompressed size CPPUNIT_ASSERT_EQUAL(ss.tellp(), std::streampos(sizeof(int))); #endif PagedOutputStream ostream(ss); ostream.write(reinterpret_cast(&values[0]), values.size()); ostream.flush(); #ifdef OPENVDB_USE_BLOSC #ifdef BLOSC_HCR_BLOCKSIZE_OPTIMIZATION CPPUNIT_ASSERT_EQUAL(ss.tellp(), std::streampos(4422)); #else CPPUNIT_ASSERT_EQUAL(ss.tellp(), std::streampos(4452)); #endif #else CPPUNIT_ASSERT_EQUAL(ss.tellp(), std::streampos(PageSize+sizeof(int))); #endif // read CPPUNIT_ASSERT_EQUAL(ss.tellg(), std::streampos(0)); PagedInputStream istream(ss); istream.setSizeOnly(true); PageHandle::Ptr handle = istream.createHandle(values.size()); #ifdef OPENVDB_USE_BLOSC // two integers - compressed size and uncompressed size CPPUNIT_ASSERT_EQUAL(ss.tellg(), std::streampos(sizeof(int)*2)); #else // one integer - uncompressed size CPPUNIT_ASSERT_EQUAL(ss.tellg(), std::streampos(sizeof(int))); #endif istream.read(handle, values.size(), false); #ifdef OPENVDB_USE_BLOSC #ifdef BLOSC_HCR_BLOCKSIZE_OPTIMIZATION CPPUNIT_ASSERT_EQUAL(ss.tellg(), std::streampos(4422)); #else CPPUNIT_ASSERT_EQUAL(ss.tellg(), std::streampos(4452)); #endif #else CPPUNIT_ASSERT_EQUAL(ss.tellg(), std::streampos(PageSize+sizeof(int))); #endif std::unique_ptr newValues(reinterpret_cast(handle->read().release())); CPPUNIT_ASSERT(newValues); for (size_t i = 0; i < values.size(); i++) { CPPUNIT_ASSERT_EQUAL(values[i], newValues.get()[i]); } } std::string tempDir; if (const char* dir = std::getenv("TMPDIR")) tempDir = dir; #ifdef _MSC_VER if (tempDir.empty()) { char tempDirBuffer[MAX_PATH+1]; int tempDirLen = GetTempPath(MAX_PATH+1, tempDirBuffer); CPPUNIT_ASSERT(tempDirLen > 0 && tempDirLen <= MAX_PATH); tempDir = tempDirBuffer; } #else if (tempDir.empty()) tempDir = P_tmpdir; #endif { std::string filename = tempDir + "/openvdb_page1"; io::StreamMetadata::Ptr streamMetadata(new io::StreamMetadata); { // ascending values up to 10 million written in blocks of PageSize/3 std::ofstream fileout(filename.c_str(), std::ios_base::binary); io::setStreamMetadataPtr(fileout, streamMetadata); io::setDataCompression(fileout, openvdb::io::COMPRESS_BLOSC); std::vector values; values.resize(10*1000*1000); std::iota(values.begin(), values.end(), 0); // ascending integer values // write page sizes PagedOutputStream ostreamSizeOnly(fileout); ostreamSizeOnly.setSizeOnly(true); CPPUNIT_ASSERT_EQUAL(fileout.tellp(), std::streampos(0)); int increment = PageSize/3; for (size_t i = 0; i < values.size(); i += increment) { if (size_t(i+increment) > values.size()) { ostreamSizeOnly.write( reinterpret_cast(&values[0]+i), values.size() - i); } else { ostreamSizeOnly.write(reinterpret_cast(&values[0]+i), increment); } } ostreamSizeOnly.flush(); #ifdef OPENVDB_USE_BLOSC int pages = static_cast(fileout.tellp() / (sizeof(int)*2)); #else int pages = static_cast(fileout.tellp() / (sizeof(int))); #endif CPPUNIT_ASSERT_EQUAL(pages, 10); // write PagedOutputStream ostream(fileout); for (size_t i = 0; i < values.size(); i += increment) { if (size_t(i+increment) > values.size()) { ostream.write(reinterpret_cast(&values[0]+i), values.size() - i); } else { ostream.write(reinterpret_cast(&values[0]+i), increment); } } ostream.flush(); #ifdef OPENVDB_USE_BLOSC #ifdef BLOSC_HCR_BLOCKSIZE_OPTIMIZATION CPPUNIT_ASSERT_EQUAL(fileout.tellp(), std::streampos(42424)); #else CPPUNIT_ASSERT_EQUAL(fileout.tellp(), std::streampos(42724)); #endif #else CPPUNIT_ASSERT_EQUAL(fileout.tellp(), std::streampos(values.size()+sizeof(int)*pages)); #endif // abuse File being a friend of MappedFile to get around the private constructor ProxyMappedFile* proxy = new ProxyMappedFile(filename); SharedPtr mappedFile(reinterpret_cast(proxy)); // read std::ifstream filein(filename.c_str(), std::ios_base::in | std::ios_base::binary); io::setStreamMetadataPtr(filein, streamMetadata); io::setMappedFilePtr(filein, mappedFile); CPPUNIT_ASSERT_EQUAL(filein.tellg(), std::streampos(0)); PagedInputStream istreamSizeOnly(filein); istreamSizeOnly.setSizeOnly(true); std::vector handles; for (size_t i = 0; i < values.size(); i += increment) { if (size_t(i+increment) > values.size()) { handles.push_back(istreamSizeOnly.createHandle(values.size() - i)); } else { handles.push_back(istreamSizeOnly.createHandle(increment)); } } #ifdef OPENVDB_USE_BLOSC // two integers - compressed size and uncompressed size CPPUNIT_ASSERT_EQUAL(filein.tellg(), std::streampos(pages*sizeof(int)*2)); #else // one integer - uncompressed size CPPUNIT_ASSERT_EQUAL(filein.tellg(), std::streampos(pages*sizeof(int))); #endif PagedInputStream istream(filein); int pageHandle = 0; for (size_t i = 0; i < values.size(); i += increment) { if (size_t(i+increment) > values.size()) { istream.read(handles[pageHandle++], values.size() - i); } else { istream.read(handles[pageHandle++], increment); } } // first three handles live in the same page Page& page0 = handles[0]->page(); Page& page1 = handles[1]->page(); Page& page2 = handles[2]->page(); Page& page3 = handles[3]->page(); CPPUNIT_ASSERT(page0.isOutOfCore()); CPPUNIT_ASSERT(page1.isOutOfCore()); CPPUNIT_ASSERT(page2.isOutOfCore()); CPPUNIT_ASSERT(page3.isOutOfCore()); handles[0]->read(); // store the Page shared_ptr Page::Ptr page = handles[0]->mPage; // verify use count is four (one plus three handles) CPPUNIT_ASSERT_EQUAL(page.use_count(), long(4)); // on reading from the first handle, all pages referenced // in the first three handles are in-core CPPUNIT_ASSERT(!page0.isOutOfCore()); CPPUNIT_ASSERT(!page1.isOutOfCore()); CPPUNIT_ASSERT(!page2.isOutOfCore()); CPPUNIT_ASSERT(page3.isOutOfCore()); handles[1]->read(); CPPUNIT_ASSERT(handles[0]->mPage); handles[2]->read(); handles.erase(handles.begin()); handles.erase(handles.begin()); handles.erase(handles.begin()); // after all three handles have been read, // page should have just one use count (itself) CPPUNIT_ASSERT_EQUAL(page.use_count(), long(1)); } std::remove(filename.c_str()); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000034561713200122377015354 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #include #include #include #include "util.h" // for unittest_util::makeSphere() #include #include #include // for std::sort #include #include // Uncomment to test on models from our web-site //#define TestTools_DATA_PATH "/home/kmu/src/openvdb/data/" //#define TestTools_DATA_PATH "/usr/pic1/Data/OpenVDB/LevelSetModels/" #define ASSERT_DOUBLES_EXACTLY_EQUAL(expected, actual) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((expected), (actual), /*tolerance=*/0.0); class TestTools: public CppUnit::TestFixture { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestTools); CPPUNIT_TEST(testDilateVoxels); CPPUNIT_TEST(testDilateActiveValues); CPPUNIT_TEST(testErodeVoxels); CPPUNIT_TEST(testActivate); CPPUNIT_TEST(testFilter); CPPUNIT_TEST(testFloatApply); CPPUNIT_TEST(testInteriorMask); CPPUNIT_TEST(testLevelSetSphere); CPPUNIT_TEST(testLevelSetPlatonic); 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(testPrune); CPPUNIT_TEST(testVolumeAdvect); CPPUNIT_TEST(testTransformValues); CPPUNIT_TEST(testVectorApply); CPPUNIT_TEST(testAccumulate); CPPUNIT_TEST(testUtil); CPPUNIT_TEST(testVectorTransformer); CPPUNIT_TEST_SUITE_END(); void testDilateVoxels(); void testDilateActiveValues(); void testErodeVoxels(); void testActivate(); void testFilter(); void testFloatApply(); void testInteriorMask(); void testLevelSetSphere(); void testLevelSetPlatonic(); void testLevelSetAdvect(); void testLevelSetMeasure(); void testLevelSetMorph(); void testMagnitude(); void testMaskedMagnitude(); void testNormalize(); void testMaskedNormalize(); void testPointAdvect(); void testPointScatter(); void testPrune(); void testVolumeAdvect(); void testTransformValues(); void testVectorApply(); void testAccumulate(); void testUtil(); void testVectorTransformer(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestTools); #if 0 namespace { // 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 << 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; }; } // unnamed namespace #endif void TestTools::testDilateVoxels() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Index32; using openvdb::Index64; using Tree543f = openvdb::tree::Tree4::Type; Tree543f::Ptr tree(new Tree543f); openvdb::tools::changeBackground(*tree, /*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()); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeTileCount()); tree->setValue(Coord(leafDim, leafDim - 1, leafDim - 1), 1.0); CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim + 1), tree->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeTileCount()); openvdb::tools::dilateVoxels(*tree); CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim + 1 + 5), tree->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeTileCount()); } { // 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 using GridType = openvdb::Grid; 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 using GridType = openvdb::Grid; 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; // } {// test dilateVoxels6 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::Morphology m(tree1); m.dilateVoxels6(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n= openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=1) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6), tree1.activeVoxelCount()); } } } } {// test dilateVoxels18 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::Morphology m(tree1); m.dilateVoxels18(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n= openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=2) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12), tree1.activeVoxelCount()); } } } } {// test dilateVoxels26 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::Morphology m(tree1); m.dilateVoxels26(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n = openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=3) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12 + 8), tree1.activeVoxelCount()); } } } } /* // Performance test {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateVoxels6"); openvdb::tools::dilateVoxels(grid->tree()); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); // grid->print(std::cerr, 3); } {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateVoxels18"); openvdb::tools::dilateVoxels(grid->tree(), 1, openvdb::tools::NN_FACE_EDGE); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); //grid->print(std::cerr, 3); } {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateVoxels26"); openvdb::tools::dilateVoxels(grid->tree(), 1, openvdb::tools::NN_FACE_EDGE_VERTEX); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); //grid->print(std::cerr, 3); } */ #ifdef TestTools_DATA_PATH { openvdb::initialize();//required whenever I/O of OpenVDB files is performed! const std::string path(TestTools_DATA_PATH); std::vector filenames; filenames.push_back("armadillo.vdb"); filenames.push_back("buddha.vdb"); filenames.push_back("bunny.vdb"); filenames.push_back("crawler.vdb"); filenames.push_back("dragon.vdb"); filenames.push_back("iss.vdb"); filenames.push_back("utahteapot.vdb"); openvdb::util::CpuTimer timer; for ( size_t i=0; i\"" << filenames[i] << "\" =====================" << std::endl; std::cerr << "Reading \"" << filenames[i] << "\" ...\n"; openvdb::io::File file( path + filenames[i] ); file.open(false);//disable delayed loading openvdb::FloatGrid::Ptr model = openvdb::gridPtrCast(file.getGrids()->at(0)); openvdb::MaskTree mask(model->tree(), false, true, openvdb::TopologyCopy() ); timer.start("Calling dilateVoxels on grid"); openvdb::tools::dilateVoxels(model->tree(), 1, openvdb::tools::NN_FACE); timer.stop(); //model->tree().print(std::cout, 3); timer.start("Calling dilateVoxels on mask"); openvdb::tools::dilateVoxels(mask, 1, openvdb::tools::NN_FACE); timer.stop(); //mask.print(std::cout, 3); CPPUNIT_ASSERT_EQUAL(model->activeVoxelCount(), mask.activeVoxelCount()); } } #endif } void TestTools::testDilateActiveValues() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Index32; using openvdb::Index64; using Tree543f = openvdb::tree::Tree4::Type; Tree543f::Ptr tree(new Tree543f); openvdb::tools::changeBackground(*tree, /*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::dilateActiveValues(*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()); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeTileCount()); // This has no effect openvdb::tools::dilateActiveValues(*tree, 1, openvdb::tools::NN_FACE, openvdb::tools::IGNORE_TILES); CPPUNIT_ASSERT_EQUAL(Index32(0), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(Index64(leafDim * leafDim * leafDim), tree->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeTileCount()); } { // 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()); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeTileCount()); // Adds 6 faces of voxels, each of size leafDim^2 openvdb::tools::dilateActiveValues(*tree, 1, openvdb::tools::NN_FACE, openvdb::tools::EXPAND_TILES); CPPUNIT_ASSERT_EQUAL(Index32(1+6), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(Index64((leafDim + 6) * leafDim * leafDim), tree->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(0), tree->activeTileCount()); } { // 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()); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeTileCount()); // Adds 6 faces of voxels, each of size leafDim^2 openvdb::tools::dilateActiveValues(*tree, 1, openvdb::tools::NN_FACE, openvdb::tools::PRESERVE_TILES); CPPUNIT_ASSERT_EQUAL(Index32(6), tree->leafCount()); CPPUNIT_ASSERT_EQUAL(Index64((leafDim + 6) * leafDim * leafDim), tree->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeTileCount()); } { // 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::dilateActiveValues(*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::dilateActiveValues(*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::dilateActiveValues(*tree); } } {// dialte a narrow band of a sphere using GridType = openvdb::Grid; 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::dilateActiveValues(grid.tree()); CPPUNIT_ASSERT(grid.tree().activeVoxelCount() > count); } {// dilate a fog volume of a sphere using GridType = openvdb::Grid; 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::dilateActiveValues(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::dilateActiveValues(grid->tree()); // CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); // //std::cerr << "\nAfter: active voxel count = " // // << grid->tree().activeVoxelCount() << std::endl; // } {// test dilateVoxels6 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::dilateActiveValues(tree1, 1, openvdb::tools::NN_FACE); //openvdb::tools::Morphology m(tree1); //m.dilateVoxels6(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n= openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=1) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6), tree1.activeVoxelCount()); } } } } {// test dilateVoxels18 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::dilateActiveValues(tree1, 1, openvdb::tools::NN_FACE_EDGE); //openvdb::tools::Morphology m(tree1); //m.dilateVoxels18(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n= openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=2) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12), tree1.activeVoxelCount()); } } } } {// test dilateVoxels26 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); Tree543f tree1(0.0f); CPPUNIT_ASSERT_EQUAL(Index64(0), tree1.activeVoxelCount()); tree1.setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree1.activeVoxelCount()); CPPUNIT_ASSERT(tree1.isValueOn(ijk)); openvdb::tools::dilateActiveValues(tree1, 1, openvdb::tools::NN_FACE_EDGE_VERTEX); //openvdb::tools::Morphology m(tree1); //m.dilateVoxels26(); for (int i=-1; i<=1; ++i) { for (int j=-1; j<=1; ++j) { for (int k=-1; k<=1; ++k) { const openvdb::Coord xyz = ijk.offsetBy(i,j,k), d=ijk-xyz; const int n = openvdb::math::Abs(d[0]) + openvdb::math::Abs(d[1]) + openvdb::math::Abs(d[2]); if (n<=3) { CPPUNIT_ASSERT( tree1.isValueOn(xyz)); } else { CPPUNIT_ASSERT(!tree1.isValueOn(xyz)); } } } } CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12 + 8), tree1.activeVoxelCount()); } } } } /* // Performance test {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateActiveValues6"); openvdb::tools::dilateActiveValues(grid->tree(), 1, openvdb::tools::NN_FACE); //openvdb::tools::dilateVoxels(grid->tree()); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); // grid->print(std::cerr, 3); } {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateActiveValues18"); openvdb::tools::dilateActiveValues(grid->tree(), 1, openvdb::tools::NN_FACE_EDGE); //openvdb::tools::dilateVoxels(grid->tree(), 1, openvdb::tools::NN_FACE_EDGE); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); //grid->print(std::cerr, 3); } {// dialte a narrow band of a sphere const float radius = 335.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 0.5f, width = 3.25f; openvdb::FloatGrid::Ptr grid = openvdb::tools::createLevelSetSphere( radius, center, voxelSize, width); //grid->print(std::cerr, 3); const openvdb::Index64 count = grid->tree().activeVoxelCount(); openvdb::util::CpuTimer t; t.start("sphere dilateActiveValues26"); openvdb::tools::dilateActiveValues(grid->tree(), 1, openvdb::tools::NN_FACE_EDGE_VERTEX); //openvdb::tools::dilateVoxels(grid->tree(), 1, openvdb::tools::NN_FACE_EDGE_VERTEX); t.stop(); CPPUNIT_ASSERT(grid->tree().activeVoxelCount() > count); //grid->print(std::cerr, 3); } */ #ifdef TestTools_DATA_PATH { openvdb::initialize();//required whenever I/O of OpenVDB files is performed! const std::string path(TestTools_DATA_PATH); std::vector filenames; filenames.push_back("armadillo.vdb"); filenames.push_back("buddha.vdb"); filenames.push_back("bunny.vdb"); filenames.push_back("crawler.vdb"); filenames.push_back("dragon.vdb"); filenames.push_back("iss.vdb"); filenames.push_back("utahteapot.vdb"); openvdb::util::CpuTimer timer; for ( size_t i=0; i\"" << filenames[i] << "\" =====================" << std::endl; std::cerr << "Reading \"" << filenames[i] << "\" ...\n"; openvdb::io::File file( path + filenames[i] ); file.open(false);//disable delayed loading openvdb::FloatGrid::Ptr model = openvdb::gridPtrCast(file.getGrids()->at(0)); openvdb::MaskTree mask(model->tree(), false, true, openvdb::TopologyCopy() ); timer.start("Calling dilateActiveValues on grid"); openvdb::tools::dilateActiveValues(model->tree(), 1, openvdb::tools::NN_FACE); timer.stop(); //model->tree().print(std::cout, 3); timer.start("Calling dilateActiveValues on mask"); openvdb::tools::dilateActiveValues(mask, 1, openvdb::tools::NN_FACE); timer.stop(); //mask.print(std::cout, 3); CPPUNIT_ASSERT_EQUAL(model->activeVoxelCount(), mask.activeVoxelCount()); } } #endif } void TestTools::testErodeVoxels() { using openvdb::CoordBBox; using openvdb::Coord; using openvdb::Index32; using openvdb::Index64; using TreeType = openvdb::tree::Tree4::Type; TreeType::Ptr tree(new TreeType); openvdb::tools::changeBackground(*tree, /*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 aTree) { CPPUNIT_ASSERT_EQUAL(activeVoxelCount, int(aTree->activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(leafCount, int(aTree->leafCount())); CPPUNIT_ASSERT_EQUAL(nonLeafCount, int(aTree->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 using GridType = openvdb::Grid; 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 using GridType = openvdb::Grid; 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); } {//erode6 for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { tree->clear(); const openvdb::Coord ijk(x,y,z); CPPUNIT_ASSERT_EQUAL(Index64(0), tree->activeVoxelCount()); tree->setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); CPPUNIT_ASSERT(tree->isValueOn(ijk)); openvdb::tools::dilateVoxels(*tree, 1, openvdb::tools::NN_FACE); CPPUNIT_ASSERT_EQUAL(Index64(1 + 6), tree->activeVoxelCount()); openvdb::tools::erodeVoxels( *tree, 1, openvdb::tools::NN_FACE); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); CPPUNIT_ASSERT(tree->isValueOn(ijk)); } } } } #if 0 {//erode18 /// @todo Not implemented yet for (int iter=1; iter<4; ++iter) { for (int x=0; x<8; ++x) { for (int y=0; y<8; ++y) { for (int z=0; z<8; ++z) { const openvdb::Coord ijk(x,y,z); tree->clear(); CPPUNIT_ASSERT_EQUAL(Index64(0), tree->activeVoxelCount()); tree->setValue(ijk, 1.0f); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); CPPUNIT_ASSERT(tree->isValueOn(ijk)); //openvdb::tools::dilateVoxels(*tree, iter, openvdb::tools::NN_FACE_EDGE); openvdb::tools::dilateVoxels(*tree, iter, openvdb::tools::NN_FACE); //std::cerr << "Dilated to: " << tree->activeVoxelCount() << std::endl; //if (iter==1) { // CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12), tree->activeVoxelCount()); //} openvdb::tools::erodeVoxels( *tree, iter, openvdb::tools::NN_FACE_EDGE); CPPUNIT_ASSERT_EQUAL(Index64(1), tree->activeVoxelCount()); CPPUNIT_ASSERT(tree->isValueOn(ijk)); } } } } } #endif #if 0 {//erode26 /// @todo Not implemented yet tree->clear(); tree->setValue(openvdb::Coord(3,4,5), 1.0f); openvdb::tools::dilateVoxels(*tree, 1, openvdb::tools::NN_FACE_EDGE_VERTEX); CPPUNIT_ASSERT_EQUAL(Index64(1 + 6 + 12 + 8), tree->activeVoxelCount()); openvdb::tools::erodeVoxels( *tree, 1, openvdb::tools::NN_FACE_EDGE_VERTEX); //openvdb::tools::dilateVoxels(*tree, 12, openvdb::tools::NN_FACE_EDGE); //openvdb::tools::erodeVoxels( *tree, 12, openvdb::tools::NN_FACE_EDGE); CPPUNIT_ASSERT_EQUAL(1, int(tree->activeVoxelCount())); CPPUNIT_ASSERT(tree->isValueOn(openvdb::Coord(3,4,5))); } #endif } 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-6f)); // 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; xactiveVoxelCount()); // For a level set, tools::interiorMask() should return a mask // of the interior of the isosurface. lsgrid.setGridClass(GRID_LEVEL_SET); mask = tools::interiorMask(lsgrid); CPPUNIT_ASSERT_EQUAL(intBand.volume(), mask->activeVoxelCount()); } void TestTools::testLevelSetSphere() { const float radius = 4.3f; const openvdb::Vec3f center(15.8f, 13.2f, 16.7f); const float voxelSize = 1.5f, width = 3.25f; const int dim = 32; openvdb::FloatGrid::Ptr grid1 = openvdb::tools::createLevelSetSphere(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()); }// testLevelSetSphere void TestTools::testLevelSetPlatonic() { using namespace openvdb; const float scale = 0.5f; const Vec3f center(1.0f, 2.0f, 3.0f); const float voxelSize = 0.025f, width = 2.0f, background = width*voxelSize; const Coord ijk(int(center[0]/voxelSize), int(center[1]/voxelSize), int(center[2]/voxelSize));// inside // The tests below are not particularly good (a visual inspection // in Houdini is much better) but at least it exercises the code // and performs an elementary suite of tests. {// test tetrahedron FloatGrid::Ptr ls = tools::createLevelSetTetrahedron(scale, center, voxelSize, width); CPPUNIT_ASSERT(ls->activeVoxelCount() > 0); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-ls->background(), ls->tree().getValue(ijk), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ls->background(), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); } {// test cube FloatGrid::Ptr ls = tools::createLevelSetCube(scale, center, voxelSize, width); CPPUNIT_ASSERT(ls->activeVoxelCount() > 0); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-ls->background(),ls->tree().getValue(ijk), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ls->background(), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); } {// test octahedron FloatGrid::Ptr ls = tools::createLevelSetOctahedron(scale, center, voxelSize, width); CPPUNIT_ASSERT(ls->activeVoxelCount() > 0); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-ls->background(),ls->tree().getValue(ijk), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ls->background(), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); } {// test icosahedron FloatGrid::Ptr ls = tools::createLevelSetIcosahedron(scale, center, voxelSize, width); CPPUNIT_ASSERT(ls->activeVoxelCount() > 0); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-ls->background(),ls->tree().getValue(ijk), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ls->background(), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); } {// test dodecahedron FloatGrid::Ptr ls = tools::createLevelSetDodecahedron(scale, center, voxelSize, width); CPPUNIT_ASSERT(ls->activeVoxelCount() > 0); CPPUNIT_ASSERT(ls->tree().isValueOff(ijk)); CPPUNIT_ASSERT_DOUBLES_EQUAL(-ls->background(),ls->tree().getValue(ijk), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ls->background(), 1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); } }// testLevelSetPlatonic void TestTools::testLevelSetAdvect() { // Uncomment sections below to run this (time-consuming) test using namespace openvdb; const int dim = 128; const Vec3f center(0.35f, 0.35f, 0.35f); const float radius = 0.15f, voxelSize = 1.0f/(dim-1); const float halfWidth = 3.0f, gamma = halfWidth*voxelSize; using GridT = FloatGrid; //using VectT = Vec3fGrid; {//test tracker::resize GridT::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, halfWidth); using TrackerT = tools::LevelSetTracker; TrackerT tracker(*grid); tracker.setSpatialScheme(math::FIRST_BIAS); tracker.setTemporalScheme(math::TVD_RK1); ASSERT_DOUBLES_EXACTLY_EQUAL( gamma, grid->background()); ASSERT_DOUBLES_EXACTLY_EQUAL( halfWidth, tracker.getHalfWidth()); CPPUNIT_ASSERT(!tracker.resize()); {// check range of on values in a sphere w/o mask tools::CheckRange c(-gamma, gamma); tools::Diagnose d(*grid); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check norm of gradient of sphere w/o mask tools::CheckNormGrad c(*grid, 0.9f, 1.1f); tools::Diagnose d(*grid); std::string str = d.check(c, false, true, false, false); //std::cerr << "NormGrad:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } CPPUNIT_ASSERT(tracker.resize(4)); ASSERT_DOUBLES_EXACTLY_EQUAL( 4*voxelSize, grid->background()); ASSERT_DOUBLES_EXACTLY_EQUAL( 4.0f, tracker.getHalfWidth()); {// check range of on values in a sphere w/o mask const float g = gamma + voxelSize; tools::CheckRange c(-g, g); tools::Diagnose d(*grid); std::string str = d.check(c); //std::cerr << "Values out of range:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } {// check norm of gradient of sphere w/o mask tools::CheckNormGrad c(*grid, 0.4f, 1.1f); tools::Diagnose d(*grid); std::string str = d.check(c, false, true, false, false); //std::cerr << "NormGrad:\n" << str; CPPUNIT_ASSERT(str.empty()); CPPUNIT_ASSERT_EQUAL(0, int(d.valueCount())); CPPUNIT_ASSERT_EQUAL(0, int(d.failureCount())); } } /* {//test tracker GridT::Ptr grid = openvdb::tools::createLevelSetSphere(radius, center, voxelSize); using TrackerT = openvdb::tools::LevelSetTracker; 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); using FieldT = openvdb::tools::EnrightField; FieldT field; using AdvectT = openvdb::tools::LevelSetAdvection; 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)); using FieldT = openvdb::tools::DiscreteField; FieldT field(vect); using AdvectT = openvdb::tools::LevelSetAdvection; 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 field(vect); using AdvectT = openvdb::tools::LevelSetAdvection; 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() { using GridT = openvdb::FloatGrid; {//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); using MorphT = openvdb::tools::LevelSetMorphing; 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); //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 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)); using MorphT = openvdb::tools::LevelSetMorphing; 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 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)); using MorphT = openvdb::tools::LevelSetMorphing; 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% using GridT = openvdb::FloatGrid; 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( float(r), C, float(voxelSize)); using MeasureT = openvdb::tools::LevelSetMeasure; 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(float(r), C, float(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 /* 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() { using namespace openvdb; { FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const 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())); VectorGrid::Ptr gradGrid = tools::gradient(*grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(gradGrid->activeVoxelCount())); FloatGrid::Ptr mag = tools::magnitude(*gradGrid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(mag->activeVoxelCount())); FloatGrid::ConstAccessor accessor = mag->getConstAccessor(); 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); } { // Test on a grid with (only) tile values. Vec3fGrid grid; Vec3fTree& tree = grid.tree(); CPPUNIT_ASSERT(tree.empty()); const Vec3f v(1.f, 2.f, 2.f); const float expectedLength = v.length(); tree.addTile(/*level=*/1, Coord(-100), v, /*active=*/true); tree.addTile(/*level=*/1, Coord(100), v, /*active=*/true); CPPUNIT_ASSERT(!tree.empty()); FloatGrid::Ptr length = tools::magnitude(grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(length->activeVoxelCount())); for (auto it = length->cbeginValueOn(); it; ++it) { CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedLength, *it, 1.0e-6); } } } void TestTools::testMaskedMagnitude() { using namespace openvdb; { FloatGrid::Ptr grid = FloatGrid::create(/*background=*/5.0); FloatTree& tree = grid->tree(); CPPUNIT_ASSERT(tree.empty()); const Coord dim(64,64,64); const 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())); VectorGrid::Ptr gradGrid = tools::gradient(*grid); CPPUNIT_ASSERT_EQUAL(int(tree.activeVoxelCount()), int(gradGrid->activeVoxelCount())); // create a masking grid const CoordBBox maskbbox(Coord(35, 30, 30), Coord(41, 41, 41)); BoolGrid::Ptr maskGrid = BoolGrid::create(false); maskGrid->fill(maskbbox, true/*value*/, true/*activate*/); // compute the magnitude in masked region FloatGrid::Ptr mag = tools::magnitude(*gradGrid, *maskGrid); FloatGrid::ConstAccessor accessor = mag->getConstAccessor(); // test in the masked region 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); } { // Test on a grid with (only) tile values. Vec3fGrid grid; Vec3fTree& tree = grid.tree(); CPPUNIT_ASSERT(tree.empty()); const Vec3f v(1.f, 2.f, 2.f); const float expectedLength = v.length(); tree.addTile(/*level=*/1, Coord(100), v, /*active=*/true); const int expectedActiveVoxelCount = int(tree.activeVoxelCount()); tree.addTile(/*level=*/1, Coord(-100), v, /*active=*/true); CPPUNIT_ASSERT(!tree.empty()); BoolGrid mask; mask.fill(CoordBBox(Coord(90), Coord(200)), true, true); FloatGrid::Ptr length = tools::magnitude(grid, mask); CPPUNIT_ASSERT_EQUAL(expectedActiveVoxelCount, int(length->activeVoxelCount())); for (auto it = length->cbeginValueOn(); it; ++it) { CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedLength, *it, 1.0e-6); } } } 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); using Vec3Type = openvdb::VectorGrid::ValueType; using ValueIter = openvdb::VectorGrid::ValueOnIter; 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); using Vec3Type = openvdb::VectorGrid::ValueType; using ValueIter = openvdb::VectorGrid::ValueOnIter; 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(float(i), float(i), float(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(float(i + 1), float(i + 1), float(i + 1)); CPPUNIT_ASSERT_EQUAL(expected, pointList[i]); } // reset values for (size_t i = 0; i < numPoints; i++) { pointList[i] = openvdb::Vec3f(float(i), float(i), float(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); } } } //////////////////////////////////////// namespace { struct PointList { struct Point { double x,y,z; }; std::vector list; openvdb::Index64 size() const { return openvdb::Index64(list.size()); } void add(const openvdb::Vec3d &p) { Point q={p[0],p[1],p[2]}; list.push_back(q); } }; } void TestTools::testPointScatter() { using GridType = openvdb::FloatGrid; const openvdb::Coord dim(64, 64, 64); const openvdb::Vec3f center(35.0f, 30.0f, 40.0f); const float radius = 20.0; using RandGen = std::mersenne_twister_engine; // mt11213b RandGen mtRand; GridType::Ptr grid = GridType::create(/*background=*/2.0); unittest_util::makeSphere( dim, center, radius, *grid, unittest_util::SPHERE_DENSE_NARROW_BAND); {// test fixed point count scattering const openvdb::Index64 pointCount = 1000; PointList points; openvdb::tools::UniformPointScatter scatter(points, pointCount, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT_EQUAL( pointCount, scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( pointCount, points.size() ); } {// test uniform density scattering 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( scatter.getVoxelCount(), scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( scatter.getVoxelCount(), points.size() ); } {// test non-uniform density scattering 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( scatter.getVoxelCount() < scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( scatter.getPointCount(), points.size() ); } {// test dense uniform scattering const size_t pointsPerVoxel = 8; PointList points; openvdb::tools::DenseUniformPointScatter scatter(points, pointsPerVoxel, mtRand); scatter.operator()(*grid); CPPUNIT_ASSERT_EQUAL( scatter.getVoxelCount()*pointsPerVoxel, scatter.getPointCount() ); CPPUNIT_ASSERT_EQUAL( scatter.getPointCount(), points.size() ); } } //////////////////////////////////////// void TestTools::testVolumeAdvect() { using namespace openvdb; Vec3fGrid velocity(Vec3f(1.0f, 0.0f, 0.0f)); using GridT = FloatGrid; using AdvT = tools::VolumeAdvection; using SamplerT = tools::Sampler<1>; {//test non-uniform grids (throws) GridT::Ptr density0 = GridT::create(0.0f); density0->transform().preScale(Vec3d(1.0, 2.0, 3.0));//i.e. non-uniform voxels AdvT a(velocity); CPPUNIT_ASSERT_THROW((a.advect(*density0, 0.1f)), RuntimeError); } {// test spatialOrder and temporalOrder AdvT a(velocity); // Default should be SEMI CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(1, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::SEMI); CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(1, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::MID); CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(2, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::RK3); CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(3, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::RK4); CPPUNIT_ASSERT_EQUAL(1, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(4, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); a.setIntegrator(tools::Scheme::MAC); CPPUNIT_ASSERT_EQUAL(2, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(2, a.temporalOrder()); CPPUNIT_ASSERT( a.isLimiterOn()); a.setIntegrator(tools::Scheme::BFECC); CPPUNIT_ASSERT_EQUAL(2, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(2, a.temporalOrder()); CPPUNIT_ASSERT( a.isLimiterOn()); a.setLimiter(tools::Scheme::NO_LIMITER); CPPUNIT_ASSERT_EQUAL(2, a.spatialOrder()); CPPUNIT_ASSERT_EQUAL(2, a.temporalOrder()); CPPUNIT_ASSERT(!a.isLimiterOn()); } {//test RK4 advect without a mask GridT::Ptr density0 = GridT::create(0.0f), density1; density0->fill(CoordBBox(Coord(0),Coord(6)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord( 3,3,3)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(24,3,3)), 0.0f); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord(24,3,3))); AdvT a(velocity); a.setIntegrator(tools::Scheme::RK4); for (int i=1; i<=240; ++i) { density1 = a.advect(*density0, 0.1f); //std::ostringstream ostr; //ostr << "densityAdvect" << "_" << i << ".vdb"; //std::cerr << "Writing " << ostr.str() << std::endl; //openvdb::io::File file(ostr.str()); //openvdb::GridPtrVec grids; //grids.push_back(density1); //file.write(grids); density0 = density1; } CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(3,3,3)), 0.0f); CPPUNIT_ASSERT(density0->tree().getValue(Coord(24,3,3)) > 0.0f); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord(24,3,3))); } {//test MAC advect without a mask GridT::Ptr density0 = GridT::create(0.0f), density1; density0->fill(CoordBBox(Coord(0),Coord(6)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord( 3,3,3)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(24,3,3)), 0.0f); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord(24,3,3))); AdvT a(velocity); a.setIntegrator(tools::Scheme::BFECC); for (int i=1; i<=240; ++i) { density1 = a.advect(*density0, 0.1f); //std::ostringstream ostr; //ostr << "densityAdvect" << "_" << i << ".vdb"; //std::cerr << "Writing " << ostr.str() << std::endl; //openvdb::io::File file(ostr.str()); //openvdb::GridPtrVec grids; //grids.push_back(density1); //file.write(grids); density0 = density1; } CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(3,3,3)), 0.0f); CPPUNIT_ASSERT(density0->tree().getValue(Coord(24,3,3)) > 0.0f); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord(24,3,3))); } {//test advect with a mask GridT::Ptr density0 = GridT::create(0.0f), density1; density0->fill(CoordBBox(Coord(0),Coord(6)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord( 3,3,3)), 1.0f); CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(24,3,3)), 0.0f); CPPUNIT_ASSERT( density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT(!density0->tree().isValueOn(Coord(24,3,3))); BoolGrid::Ptr mask = BoolGrid::create(false); mask->fill(CoordBBox(Coord(4,0,0),Coord(30,8,8)), true); AdvT a(velocity); a.setGrainSize(0); a.setIntegrator(tools::Scheme::MAC); //a.setIntegrator(tools::Scheme::BFECC); //a.setIntegrator(tools::Scheme::RK4); for (int i=1; i<=240; ++i) { density1 = a.advect(*density0, *mask, 0.1f); //std::ostringstream ostr; //ostr << "densityAdvectMask" << "_" << i << ".vdb"; //std::cerr << "Writing " << ostr.str() << std::endl; //openvdb::io::File file(ostr.str()); //openvdb::GridPtrVec grids; //grids.push_back(density1); //file.write(grids); density0 = density1; } CPPUNIT_ASSERT_EQUAL(density0->tree().getValue(Coord(3,3,3)), 1.0f); CPPUNIT_ASSERT(density0->tree().getValue(Coord(24,3,3)) > 0.0f); CPPUNIT_ASSERT(density0->tree().isValueOn(Coord( 3,3,3))); CPPUNIT_ASSERT(density0->tree().isValueOn(Coord(24,3,3))); } /* {//benchmark on a sphere util::CpuTimer timer; GridT::Ptr density0 = GridT::create(0.0f), density1; density0->fill(CoordBBox(Coord(0), Coord(600)), 1.0f); timer.start("densify"); density0->tree().voxelizeActiveTiles(); timer.stop(); AdvT a(velocity); a.setGrainSize(1); //a.setLimiter(tools::Scheme::NO_LIMITER); //a.setIntegrator(tools::Scheme::MAC); //a.setIntegrator(tools::Scheme::BFECC); a.setIntegrator(tools::Scheme::RK4); for (int i=1; i<=10; ++i) { timer.start("Volume Advection"); density1 = a.advect(*density0, 0.1f); timer.stop(); std::ostringstream ostr; ostr << "densityAdvectMask" << "_" << i << ".vdb"; std::cerr << "Writing " << ostr.str() << std::endl; io::File file(ostr.str()); GridPtrVec grids; grids.push_back(density1); file.write(grids); density0.swap(density1); } } */ }// testVolumeAdvect //////////////////////////////////////// void TestTools::testFloatApply() { using ValueIter = openvdb::FloatTree::ValueOnIter; struct Local { static inline float op(float x) { return x * 2.f; } 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, float(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(float(xyz[0] + xyz[1] + xyz[2])); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, it.getValue(), /*tolerance=*/0.0); } } //////////////////////////////////////// namespace { template struct MatMul { openvdb::math::Mat3s mat; MatMul(const openvdb::math::Mat3s& _mat): mat(_mat) {} openvdb::Vec3s xform(const openvdb::Vec3s& v) const { return mat.transform(v); } void operator()(const IterT& it) const { it.setValue(xform(*it)); } }; } void TestTools::testVectorApply() { using ValueIter = openvdb::VectorTree::ValueOnIter; 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(float(x), float(y), float(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(float(xyz[0]), float(xyz[1]), float(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 { using LeafRange = openvdb::tree::LeafManager::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 { using ValueT = typename InIterT::ValueT; using Accessor = typename openvdb::tree::ValueAccessor; // 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; using Tree323f = openvdb::tree::Tree4::Type; using Tree323v = openvdb::tree::Tree4::Type; 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, float(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(float(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; using CharTree = openvdb::tree::Tree4::Type; // 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); } //////////////////////////////////////// void TestTools::testVectorTransformer() { using namespace openvdb; Mat4d xform = Mat4d::identity(); xform.preTranslate(Vec3d(0.1, -2.5, 3)); xform.preScale(Vec3d(0.5, 1.1, 2)); xform.preRotate(math::X_AXIS, 30.0 * M_PI / 180.0); xform.preRotate(math::Y_AXIS, 300.0 * M_PI / 180.0); Mat4d invXform = xform.inverse(); invXform = invXform.transpose(); { // Set some vector values in a grid, then verify that tools::transformVectors() // transforms them as expected for each VecType. const Vec3s refVec0(0, 0, 0), refVec1(1, 0, 0), refVec2(0, 1, 0), refVec3(0, 0, 1); Vec3SGrid grid; Vec3SGrid::Accessor acc = grid.getAccessor(); #define resetGrid() \ { \ grid.clear(); \ acc.setValue(Coord(0), refVec0); \ acc.setValue(Coord(1), refVec1); \ acc.setValue(Coord(2), refVec2); \ acc.setValue(Coord(3), refVec3); \ } // Verify that grid values are in world space by default. CPPUNIT_ASSERT(grid.isInWorldSpace()); resetGrid(); grid.setVectorType(VEC_INVARIANT); tools::transformVectors(grid, xform); CPPUNIT_ASSERT(acc.getValue(Coord(0)).eq(refVec0)); CPPUNIT_ASSERT(acc.getValue(Coord(1)).eq(refVec1)); CPPUNIT_ASSERT(acc.getValue(Coord(2)).eq(refVec2)); CPPUNIT_ASSERT(acc.getValue(Coord(3)).eq(refVec3)); resetGrid(); grid.setVectorType(VEC_COVARIANT); tools::transformVectors(grid, xform); CPPUNIT_ASSERT(acc.getValue(Coord(0)).eq(invXform.transform3x3(refVec0))); CPPUNIT_ASSERT(acc.getValue(Coord(1)).eq(invXform.transform3x3(refVec1))); CPPUNIT_ASSERT(acc.getValue(Coord(2)).eq(invXform.transform3x3(refVec2))); CPPUNIT_ASSERT(acc.getValue(Coord(3)).eq(invXform.transform3x3(refVec3))); resetGrid(); grid.setVectorType(VEC_COVARIANT_NORMALIZE); tools::transformVectors(grid, xform); CPPUNIT_ASSERT_EQUAL(refVec0, acc.getValue(Coord(0))); CPPUNIT_ASSERT(acc.getValue(Coord(1)).eq(invXform.transform3x3(refVec1).unit())); CPPUNIT_ASSERT(acc.getValue(Coord(2)).eq(invXform.transform3x3(refVec2).unit())); CPPUNIT_ASSERT(acc.getValue(Coord(3)).eq(invXform.transform3x3(refVec3).unit())); resetGrid(); grid.setVectorType(VEC_CONTRAVARIANT_RELATIVE); tools::transformVectors(grid, xform); CPPUNIT_ASSERT(acc.getValue(Coord(0)).eq(xform.transform3x3(refVec0))); CPPUNIT_ASSERT(acc.getValue(Coord(1)).eq(xform.transform3x3(refVec1))); CPPUNIT_ASSERT(acc.getValue(Coord(2)).eq(xform.transform3x3(refVec2))); CPPUNIT_ASSERT(acc.getValue(Coord(3)).eq(xform.transform3x3(refVec3))); resetGrid(); grid.setVectorType(VEC_CONTRAVARIANT_ABSOLUTE); /// @todo This doesn't really test the behavior w.r.t. homogeneous coords. tools::transformVectors(grid, xform); CPPUNIT_ASSERT(acc.getValue(Coord(0)).eq(xform.transformH(refVec0))); CPPUNIT_ASSERT(acc.getValue(Coord(1)).eq(xform.transformH(refVec1))); CPPUNIT_ASSERT(acc.getValue(Coord(2)).eq(xform.transformH(refVec2))); CPPUNIT_ASSERT(acc.getValue(Coord(3)).eq(xform.transformH(refVec3))); // Verify that transformVectors() has no effect on local-space grids. resetGrid(); grid.setVectorType(VEC_CONTRAVARIANT_RELATIVE); grid.setIsInWorldSpace(false); tools::transformVectors(grid, xform); CPPUNIT_ASSERT(acc.getValue(Coord(0)).eq(refVec0)); CPPUNIT_ASSERT(acc.getValue(Coord(1)).eq(refVec1)); CPPUNIT_ASSERT(acc.getValue(Coord(2)).eq(refVec2)); CPPUNIT_ASSERT(acc.getValue(Coord(3)).eq(refVec3)); #undef resetGrid } { // Verify that transformVectors() operates only on vector-valued grids. FloatGrid scalarGrid; CPPUNIT_ASSERT_THROW(tools::transformVectors(scalarGrid, xform), TypeError); } } //////////////////////////////////////// void TestTools::testPrune() { /// @todo Add more unit-tests! using namespace openvdb; {// try prunning a tree with const values const float value = 5.345f; FloatTree tree(value); CPPUNIT_ASSERT_EQUAL(Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(Index32(1), tree.nonLeafCount()); // root node CPPUNIT_ASSERT(tree.empty()); tree.fill(CoordBBox(Coord(-10), Coord(10)), value, /*active=*/false); CPPUNIT_ASSERT(!tree.empty()); tools::prune(tree); CPPUNIT_ASSERT_EQUAL(Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(Index32(1), tree.nonLeafCount()); // root node CPPUNIT_ASSERT(tree.empty()); } {// Prune a tree with a single leaf node with random values in the range [0,1] using LeafNodeT = tree::LeafNode; const float val = 1.0, tol = 1.1f; // Fill a leaf node with random values in the range [0,1] LeafNodeT *leaf = new LeafNodeT(Coord(0), val, true); math::Random01 r(145); std::vector data(LeafNodeT::NUM_VALUES); for (Index i=0; isetValueOnly(i, v); } // Insert leaf node into an empty tree FloatTree tree(val); tree.addLeaf(leaf); CPPUNIT_ASSERT_EQUAL(Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(Index32(3), tree.nonLeafCount()); // root+2*internal tools::prune(tree);// tolerance is zero CPPUNIT_ASSERT_EQUAL(Index32(1), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(Index32(3), tree.nonLeafCount()); // root+2*internal tools::prune(tree, tol); CPPUNIT_ASSERT_EQUAL(Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(Index32(3), tree.nonLeafCount()); // root+2*internal std::sort(data.begin(), data.end()); const float median = data[(LeafNodeT::NUM_VALUES-1)>>1]; ASSERT_DOUBLES_EXACTLY_EQUAL(median, tree.getValue(Coord(0))); } /* {// Benchmark serial prune util::CpuTimer timer; initialize();//required whenever I/O of OpenVDB files is performed! io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/crawler.vdb"); sourceFile.open(false);//disable delayed loading FloatGrid::Ptr grid = gridPtrCast(sourceFile.getGrids()->at(0)); const Index32 leafCount = grid->tree().leafCount(); timer.start("\nSerial tolerance prune"); grid->tree().prune(); timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, grid->tree().leafCount()); } {// Benchmark parallel prune util::CpuTimer timer; initialize();//required whenever I/O of OpenVDB files is performed! io::File sourceFile("/usr/pic1/Data/OpenVDB/LevelSetModels/crawler.vdb"); sourceFile.open(false);//disable delayed loading FloatGrid::Ptr grid = gridPtrCast(sourceFile.getGrids()->at(0)); const Index32 leafCount = grid->tree().leafCount(); timer.start("\nParallel tolerance prune"); tools::prune(grid->tree()); timer.stop(); CPPUNIT_ASSERT_EQUAL(leafCount, grid->tree().leafCount()); } */ } // Copyright (c) 2012-2017 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/TestAttributeGroup.cc0000644000000000000000000004406013200122377017220 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 using namespace openvdb; using namespace openvdb::points; class TestAttributeGroup: public CppUnit::TestCase { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestAttributeGroup); CPPUNIT_TEST(testAttributeGroup); CPPUNIT_TEST(testAttributeGroupHandle); CPPUNIT_TEST(testAttributeGroupFilter); CPPUNIT_TEST_SUITE_END(); void testAttributeGroup(); void testAttributeGroupHandle(); void testAttributeGroupFilter(); }; // class TestAttributeGroup CPPUNIT_TEST_SUITE_REGISTRATION(TestAttributeGroup); //////////////////////////////////////// namespace { bool matchingNamePairs(const openvdb::NamePair& lhs, const openvdb::NamePair& rhs) { if (lhs.first != rhs.first) return false; if (lhs.second != rhs.second) return false; return true; } } // namespace //////////////////////////////////////// void TestAttributeGroup::testAttributeGroup() { { // Typed class API const size_t count = 50; GroupAttributeArray attr(count); CPPUNIT_ASSERT(!attr.isTransient()); CPPUNIT_ASSERT(!attr.isHidden()); CPPUNIT_ASSERT(isGroup(attr)); attr.setTransient(true); CPPUNIT_ASSERT(attr.isTransient()); CPPUNIT_ASSERT(!attr.isHidden()); CPPUNIT_ASSERT(isGroup(attr)); attr.setHidden(true); CPPUNIT_ASSERT(attr.isTransient()); CPPUNIT_ASSERT(attr.isHidden()); CPPUNIT_ASSERT(isGroup(attr)); attr.setTransient(false); CPPUNIT_ASSERT(!attr.isTransient()); CPPUNIT_ASSERT(attr.isHidden()); CPPUNIT_ASSERT(isGroup(attr)); GroupAttributeArray attrB(attr); CPPUNIT_ASSERT(matchingNamePairs(attr.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attr.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attr.memUsage(), attrB.memUsage()); CPPUNIT_ASSERT_EQUAL(attr.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attr.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attr.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(isGroup(attr), isGroup(attrB)); } { // casting TypedAttributeArray floatAttr(4); AttributeArray& floatArray = floatAttr; const AttributeArray& constFloatArray = floatAttr; CPPUNIT_ASSERT_THROW(GroupAttributeArray::cast(floatArray), TypeError); CPPUNIT_ASSERT_THROW(GroupAttributeArray::cast(constFloatArray), TypeError); GroupAttributeArray groupAttr(4); AttributeArray& groupArray = groupAttr; const AttributeArray& constGroupArray = groupAttr; CPPUNIT_ASSERT_NO_THROW(GroupAttributeArray::cast(groupArray)); CPPUNIT_ASSERT_NO_THROW(GroupAttributeArray::cast(constGroupArray)); } { // IO const size_t count = 50; GroupAttributeArray attrA(count); for (unsigned i = 0; i < unsigned(count); ++i) { attrA.set(i, int(i)); } attrA.setHidden(true); std::ostringstream ostr(std::ios_base::binary); attrA.write(ostr); GroupAttributeArray attrB; std::istringstream istr(ostr.str(), std::ios_base::binary); attrB.read(istr); CPPUNIT_ASSERT(matchingNamePairs(attrA.type(), attrB.type())); CPPUNIT_ASSERT_EQUAL(attrA.size(), attrB.size()); CPPUNIT_ASSERT_EQUAL(attrA.memUsage(), attrB.memUsage()); CPPUNIT_ASSERT_EQUAL(attrA.isUniform(), attrB.isUniform()); CPPUNIT_ASSERT_EQUAL(attrA.isTransient(), attrB.isTransient()); CPPUNIT_ASSERT_EQUAL(attrA.isHidden(), attrB.isHidden()); CPPUNIT_ASSERT_EQUAL(isGroup(attrA), isGroup(attrB)); for (unsigned i = 0; i < unsigned(count); ++i) { CPPUNIT_ASSERT_EQUAL(attrA.get(i), attrB.get(i)); } } } void TestAttributeGroup::testAttributeGroupHandle() { GroupAttributeArray attr(4); GroupHandle handle(attr, 3); CPPUNIT_ASSERT_EQUAL(handle.size(), Index(4)); CPPUNIT_ASSERT_EQUAL(handle.size(), attr.size()); // construct bitmasks const GroupType bitmask3 = GroupType(1) << 3; const GroupType bitmask6 = GroupType(1) << 6; const GroupType bitmask36 = GroupType(1) << 3 | GroupType(1) << 6; // enable attribute 1,2,3 for group permutations of 3 and 6 attr.set(0, 0); attr.set(1, bitmask3); attr.set(2, bitmask6); attr.set(3, bitmask36); CPPUNIT_ASSERT(attr.get(2) != bitmask36); CPPUNIT_ASSERT_EQUAL(attr.get(3), bitmask36); { // group 3 valid for attributes 1 and 3 (using specific offset) GroupHandle handle3(attr, 3); CPPUNIT_ASSERT(!handle3.get(0)); CPPUNIT_ASSERT(handle3.get(1)); CPPUNIT_ASSERT(!handle3.get(2)); CPPUNIT_ASSERT(handle3.get(3)); } { // test group 3 valid for attributes 1 and 3 (unsafe access) GroupHandle handle3(attr, 3); CPPUNIT_ASSERT(!handle3.getUnsafe(0)); CPPUNIT_ASSERT(handle3.getUnsafe(1)); CPPUNIT_ASSERT(!handle3.getUnsafe(2)); CPPUNIT_ASSERT(handle3.getUnsafe(3)); } { // group 6 valid for attributes 2 and 3 (using specific offset) GroupHandle handle6(attr, 6); CPPUNIT_ASSERT(!handle6.get(0)); CPPUNIT_ASSERT(!handle6.get(1)); CPPUNIT_ASSERT(handle6.get(2)); CPPUNIT_ASSERT(handle6.get(3)); } { // groups 3 and 6 only valid for attribute 3 (using bitmask) GroupHandle handle36(attr, bitmask36, GroupHandle::BitMask()); CPPUNIT_ASSERT(!handle36.get(0)); CPPUNIT_ASSERT(!handle36.get(1)); CPPUNIT_ASSERT(!handle36.get(2)); CPPUNIT_ASSERT(handle36.get(3)); } // clear the array attr.fill(0); CPPUNIT_ASSERT_EQUAL(attr.get(1), GroupType(0)); // write handles GroupWriteHandle writeHandle3(attr, 3); GroupWriteHandle writeHandle6(attr, 6); // test collapse CPPUNIT_ASSERT_EQUAL(writeHandle3.get(1), false); CPPUNIT_ASSERT_EQUAL(writeHandle6.get(1), false); CPPUNIT_ASSERT(writeHandle6.compact()); CPPUNIT_ASSERT(writeHandle6.isUniform()); attr.expand(); CPPUNIT_ASSERT(!writeHandle6.isUniform()); CPPUNIT_ASSERT(writeHandle3.collapse(true)); CPPUNIT_ASSERT(attr.isUniform()); CPPUNIT_ASSERT(writeHandle3.isUniform()); CPPUNIT_ASSERT(writeHandle6.isUniform()); CPPUNIT_ASSERT_EQUAL(writeHandle3.get(1), true); CPPUNIT_ASSERT_EQUAL(writeHandle6.get(1), false); CPPUNIT_ASSERT(writeHandle3.collapse(false)); CPPUNIT_ASSERT(writeHandle3.isUniform()); CPPUNIT_ASSERT_EQUAL(writeHandle3.get(1), false); attr.fill(0); writeHandle3.set(1, true); CPPUNIT_ASSERT(!attr.isUniform()); CPPUNIT_ASSERT(!writeHandle3.isUniform()); CPPUNIT_ASSERT(!writeHandle6.isUniform()); CPPUNIT_ASSERT(!writeHandle3.collapse(true)); CPPUNIT_ASSERT(!attr.isUniform()); CPPUNIT_ASSERT(!writeHandle3.isUniform()); CPPUNIT_ASSERT(!writeHandle6.isUniform()); CPPUNIT_ASSERT_EQUAL(writeHandle3.get(1), true); CPPUNIT_ASSERT_EQUAL(writeHandle6.get(1), false); writeHandle6.set(2, true); CPPUNIT_ASSERT(!writeHandle3.collapse(false)); CPPUNIT_ASSERT(!writeHandle3.isUniform()); attr.fill(0); writeHandle3.set(1, true); writeHandle6.set(2, true); writeHandle3.set(3, true); writeHandle6.set(3, true); { // group 3 valid for attributes 1 and 3 (using specific offset) GroupHandle handle3(attr, 3); CPPUNIT_ASSERT(!handle3.get(0)); CPPUNIT_ASSERT(handle3.get(1)); CPPUNIT_ASSERT(!handle3.get(2)); CPPUNIT_ASSERT(handle3.get(3)); CPPUNIT_ASSERT(!writeHandle3.get(0)); CPPUNIT_ASSERT(writeHandle3.get(1)); CPPUNIT_ASSERT(!writeHandle3.get(2)); CPPUNIT_ASSERT(writeHandle3.get(3)); } { // group 6 valid for attributes 2 and 3 (using specific offset) GroupHandle handle6(attr, 6); CPPUNIT_ASSERT(!handle6.get(0)); CPPUNIT_ASSERT(!handle6.get(1)); CPPUNIT_ASSERT(handle6.get(2)); CPPUNIT_ASSERT(handle6.get(3)); CPPUNIT_ASSERT(!writeHandle6.get(0)); CPPUNIT_ASSERT(!writeHandle6.get(1)); CPPUNIT_ASSERT(writeHandle6.get(2)); CPPUNIT_ASSERT(writeHandle6.get(3)); } writeHandle3.set(3, false); { // group 3 valid for attributes 1 and 3 (using specific offset) GroupHandle handle3(attr, 3); CPPUNIT_ASSERT(!handle3.get(0)); CPPUNIT_ASSERT(handle3.get(1)); CPPUNIT_ASSERT(!handle3.get(2)); CPPUNIT_ASSERT(!handle3.get(3)); CPPUNIT_ASSERT(!writeHandle3.get(0)); CPPUNIT_ASSERT(writeHandle3.get(1)); CPPUNIT_ASSERT(!writeHandle3.get(2)); CPPUNIT_ASSERT(!writeHandle3.get(3)); } { // group 6 valid for attributes 2 and 3 (using specific offset) GroupHandle handle6(attr, 6); CPPUNIT_ASSERT(!handle6.get(0)); CPPUNIT_ASSERT(!handle6.get(1)); CPPUNIT_ASSERT(handle6.get(2)); CPPUNIT_ASSERT(handle6.get(3)); CPPUNIT_ASSERT(!writeHandle6.get(0)); CPPUNIT_ASSERT(!writeHandle6.get(1)); CPPUNIT_ASSERT(writeHandle6.get(2)); CPPUNIT_ASSERT(writeHandle6.get(3)); } } class GroupNotFilter { public: explicit GroupNotFilter(const AttributeSet::Descriptor::GroupIndex& index) : mFilter(index) { } inline bool initialized() const { return mFilter.initialized(); } template void reset(const LeafT& leaf) { mFilter.reset(leaf); } template bool valid(const IterT& iter) const { return !mFilter.valid(iter); } private: GroupFilter mFilter; }; // class GroupNotFilter struct HandleWrapper { HandleWrapper(const GroupHandle& handle) : mHandle(handle) { } GroupHandle groupHandle(const AttributeSet::Descriptor::GroupIndex& /*index*/) const { return mHandle; } private: const GroupHandle mHandle; }; // struct HandleWrapper void TestAttributeGroup::testAttributeGroupFilter() { using GroupIndex = AttributeSet::Descriptor::GroupIndex; GroupIndex zeroIndex; typedef IndexIter IndexGroupAllIter; GroupAttributeArray attrGroup(4); const Index32 size = attrGroup.size(); { // group values all zero ValueVoxelCIter indexIter(0, size); GroupFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 0))); IndexGroupAllIter iter(indexIter, filter); CPPUNIT_ASSERT(!iter); } // enable attributes 0 and 2 for groups 3 and 6 const GroupType bitmask = GroupType(1) << 3 | GroupType(1) << 6; attrGroup.set(0, bitmask); attrGroup.set(2, bitmask); // index iterator only valid in groups 3 and 6 { ValueVoxelCIter indexIter(0, size); GroupFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 0))); CPPUNIT_ASSERT(!IndexGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 1))); CPPUNIT_ASSERT(!IndexGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 2))); CPPUNIT_ASSERT(!IndexGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 3))); CPPUNIT_ASSERT(IndexGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 4))); CPPUNIT_ASSERT(!IndexGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 5))); CPPUNIT_ASSERT(!IndexGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 6))); CPPUNIT_ASSERT(IndexGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 7))); CPPUNIT_ASSERT(!IndexGroupAllIter(indexIter, filter)); } attrGroup.set(1, bitmask); attrGroup.set(3, bitmask); using IndexNotGroupAllIter = IndexIter; // index iterator only not valid in groups 3 and 6 { ValueVoxelCIter indexIter(0, size); GroupNotFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 0))); CPPUNIT_ASSERT(IndexNotGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 1))); CPPUNIT_ASSERT(IndexNotGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 2))); CPPUNIT_ASSERT(IndexNotGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 3))); CPPUNIT_ASSERT(!IndexNotGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 4))); CPPUNIT_ASSERT(IndexNotGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 5))); CPPUNIT_ASSERT(IndexNotGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 6))); CPPUNIT_ASSERT(!IndexNotGroupAllIter(indexIter, filter)); filter.reset(HandleWrapper(GroupHandle(attrGroup, 7))); CPPUNIT_ASSERT(IndexNotGroupAllIter(indexIter, filter)); } // clear group membership for attributes 1 and 3 attrGroup.set(1, GroupType(0)); attrGroup.set(3, GroupType(0)); { // index in group next ValueVoxelCIter indexIter(0, size); GroupFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 3))); IndexGroupAllIter iter(indexIter, filter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(*iter, Index32(0)); CPPUNIT_ASSERT(iter.next()); CPPUNIT_ASSERT_EQUAL(*iter, Index32(2)); CPPUNIT_ASSERT(!iter.next()); } { // index in group prefix ++ ValueVoxelCIter indexIter(0, size); GroupFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 3))); IndexGroupAllIter iter(indexIter, filter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(*iter, Index32(0)); IndexGroupAllIter old = ++iter; CPPUNIT_ASSERT_EQUAL(*old, Index32(2)); CPPUNIT_ASSERT_EQUAL(*iter, Index32(2)); CPPUNIT_ASSERT(!iter.next()); } { // index in group postfix ++/-- ValueVoxelCIter indexIter(0, size); GroupFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 3))); IndexGroupAllIter iter(indexIter, filter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(*iter, Index32(0)); IndexGroupAllIter old = iter++; CPPUNIT_ASSERT_EQUAL(*old, Index32(0)); CPPUNIT_ASSERT_EQUAL(*iter, Index32(2)); CPPUNIT_ASSERT(!iter.next()); } { // index not in group next ValueVoxelCIter indexIter(0, size); GroupNotFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 3))); IndexNotGroupAllIter iter(indexIter, filter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(*iter, Index32(1)); CPPUNIT_ASSERT(iter.next()); CPPUNIT_ASSERT_EQUAL(*iter, Index32(3)); CPPUNIT_ASSERT(!iter.next()); } { // index not in group prefix ++ ValueVoxelCIter indexIter(0, size); GroupNotFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 3))); IndexNotGroupAllIter iter(indexIter, filter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(*iter, Index32(1)); IndexNotGroupAllIter old = ++iter; CPPUNIT_ASSERT_EQUAL(*old, Index32(3)); CPPUNIT_ASSERT_EQUAL(*iter, Index32(3)); CPPUNIT_ASSERT(!iter.next()); } { // index not in group postfix ++ ValueVoxelCIter indexIter(0, size); GroupNotFilter filter(zeroIndex); filter.reset(HandleWrapper(GroupHandle(attrGroup, 3))); IndexNotGroupAllIter iter(indexIter, filter); CPPUNIT_ASSERT(iter); CPPUNIT_ASSERT_EQUAL(*iter, Index32(1)); IndexNotGroupAllIter old = iter++; CPPUNIT_ASSERT_EQUAL(*old, Index32(1)); CPPUNIT_ASSERT_EQUAL(*iter, Index32(3)); CPPUNIT_ASSERT(!iter.next()); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000006364013200122377016320 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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; 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; 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(float(location.x()), float(location.y()), 0.f)); } } } 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 // 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] = static_cast(::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(testStencilsMatch); CPPUNIT_TEST_SUITE_END(); void test(); void testTree(); void testAccessor(); void testConstantValues(); void testFillValues(); void testNegativeIndices(); void testStencilsMatch(); }; 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); {//using BoxSampler // 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); } {//using Sampler<1> // 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.375f))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.f))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.f))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.f))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.f))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.f))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.f))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1f))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792f))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71f))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01f))); } 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.375f))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.f))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.f))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.f))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.f))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.f))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.f))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1f))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792f))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71f))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01f))); } 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.375f))); val = interpolator.sampleVoxel(10.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.0f))); val = interpolator.sampleVoxel(11.0, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0f))); val = interpolator.sampleVoxel(11.0, 11.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0f))); val = interpolator.sampleVoxel(11.0, 11.0, 11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.0f))); val = interpolator.sampleVoxel(9.0, 11.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0f))); val = interpolator.sampleVoxel(9.0, 10.0, 9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0f))); val = interpolator.sampleVoxel(10.1, 10.0, 10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1f))); val = interpolator.sampleVoxel(10.8, 10.8, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792f))); val = interpolator.sampleVoxel(10.1, 10.8, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.8, 10.1, 10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(10.5, 10.1, 10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71f))); val = interpolator.sampleVoxel(10.5, 10.8, 10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01f))); } 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.375f))); val = interpolator.sampleVoxel(-10.0, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.0f))); val = interpolator.sampleVoxel(-11.0, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0f))); val = interpolator.sampleVoxel(-11.0, -11.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(2.0f))); val = interpolator.sampleVoxel(-11.0, -11.0, -11.0); CPPUNIT_ASSERT(val.eq(Vec3s(3.0f))); val = interpolator.sampleVoxel(-9.0, -11.0, -9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0f))); val = interpolator.sampleVoxel(-9.0, -10.0, -9.0); CPPUNIT_ASSERT(val.eq(Vec3s(4.0f))); val = interpolator.sampleVoxel(-10.1, -10.0, -10.0); CPPUNIT_ASSERT(val.eq(Vec3s(1.1f))); val = interpolator.sampleVoxel(-10.8, -10.8, -10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.792f))); val = interpolator.sampleVoxel(-10.1, -10.8, -10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(-10.8, -10.1, -10.5); CPPUNIT_ASSERT(val.eq(Vec3s(2.41f))); val = interpolator.sampleVoxel(-10.5, -10.1, -10.8); CPPUNIT_ASSERT(val.eq(Vec3s(2.71f))); val = interpolator.sampleVoxel(-10.5, -10.8, -10.1); CPPUNIT_ASSERT(val.eq(Vec3s(2.01f))); } template void TestLinearInterp::testStencilsMatch() { typedef typename GridType::ValueType ValueType; GridType grid; typename GridType::TreeType& tree = grid.tree(); // using mostly recurring numbers tree.setValue(openvdb::Coord(0, 0, 0), ValueType(1.0/3.0)); tree.setValue(openvdb::Coord(0, 1, 0), ValueType(1.0/11.0)); tree.setValue(openvdb::Coord(0, 0, 1), ValueType(1.0/81.0)); tree.setValue(openvdb::Coord(1, 0, 0), ValueType(1.0/97.0)); tree.setValue(openvdb::Coord(1, 1, 0), ValueType(1.0/61.0)); tree.setValue(openvdb::Coord(0, 1, 1), ValueType(9.0/7.0)); tree.setValue(openvdb::Coord(1, 0, 1), ValueType(9.0/11.0)); tree.setValue(openvdb::Coord(1, 1, 1), ValueType(22.0/7.0)); const openvdb::Vec3f pos(7.0f/12.0f, 1.0f/3.0f, 2.0f/3.0f); {//using BoxSampler and BoxStencil openvdb::tools::GridSampler interpolator(grid); openvdb::math::BoxStencil stencil(grid); typename GridType::ValueType val1 = interpolator.sampleVoxel(pos.x(), pos.y(), pos.z()); stencil.moveTo(pos); typename GridType::ValueType val2 = stencil.interpolation(pos); CPPUNIT_ASSERT_EQUAL(val1, val2); } } template<> void TestLinearInterp::testStencilsMatch() {} // Copyright (c) 2012-2017 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/TestPointPartitioner.cc0000644000000000000000000001022613200122377017547 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TestPointPartitioner: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestPointPartitioner); CPPUNIT_TEST(testPartitioner); CPPUNIT_TEST_SUITE_END(); void testPartitioner(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPointPartitioner); //////////////////////////////////////// namespace { struct PointList { typedef openvdb::Vec3s PosType; PointList(const std::vector& points) : mPoints(&points) {} size_t size() const { return mPoints->size(); } void getPos(size_t n, PosType& xyz) const { xyz = (*mPoints)[n]; } protected: std::vector const * const mPoints; }; // PointList } // namespace //////////////////////////////////////// void TestPointPartitioner::testPartitioner() { const size_t pointCount = 10000; const float voxelSize = 0.1f; std::vector points(pointCount, openvdb::Vec3s(0.f)); for (size_t n = 1; n < pointCount; ++n) { points[n].x() = points[n-1].x() + voxelSize; } PointList pointList(points); const openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); typedef openvdb::tools::UInt32PointPartitioner PointPartitioner; PointPartitioner::Ptr partitioner = PointPartitioner::create(pointList, *transform); CPPUNIT_ASSERT(!partitioner->empty()); // The default interpretation should be cell-centered. CPPUNIT_ASSERT(partitioner->usingCellCenteredTransform()); const size_t expectedPageCount = pointCount / (1u << PointPartitioner::LOG2DIM); CPPUNIT_ASSERT_EQUAL(expectedPageCount, partitioner->size()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0), partitioner->origin(0)); PointPartitioner::IndexIterator it = partitioner->indices(0); CPPUNIT_ASSERT(it.test()); CPPUNIT_ASSERT_EQUAL(it.size(), size_t(1 << PointPartitioner::LOG2DIM)); PointPartitioner::IndexIterator itB = partitioner->indices(0); CPPUNIT_ASSERT_EQUAL(++it, ++itB); CPPUNIT_ASSERT(it != ++itB); std::vector indices; for (it.reset(); it; ++it) { indices.push_back(*it); } CPPUNIT_ASSERT_EQUAL(it.size(), indices.size()); size_t idx = 0; for (itB.reset(); itB; ++itB) { CPPUNIT_ASSERT_EQUAL(indices[idx++], *itB); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000001662113200122377016623 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 createLevelSetBox() #include // for csgDifference() class TestLevelSetUtil: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestLevelSetUtil); CPPUNIT_TEST(testSDFToFogVolume); CPPUNIT_TEST(testSDFInteriorMask); CPPUNIT_TEST(testExtractEnclosedRegion); CPPUNIT_TEST(testSegmentationTools); CPPUNIT_TEST_SUITE_END(); void testSDFToFogVolume(); void testSDFInteriorMask(); void testExtractEnclosedRegion(); void testSegmentationTools(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestLevelSetUtil); //////////////////////////////////////// void TestLevelSetUtil::testSDFToFogVolume() { 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); } } void TestLevelSetUtil::testSDFInteriorMask() { typedef openvdb::FloatGrid FloatGrid; typedef openvdb::BoolGrid BoolGrid; typedef openvdb::Vec3s Vec3s; typedef openvdb::math::BBox BBoxs; typedef openvdb::math::Transform Transform; BBoxs bbox(Vec3s(0.0, 0.0, 0.0), Vec3s(1.0, 1.0, 1.0)); Transform::Ptr transform = Transform::createLinearTransform(0.1); FloatGrid::Ptr sdfGrid = openvdb::tools::createLevelSetBox(bbox, *transform); BoolGrid::Ptr maskGrid = openvdb::tools::sdfInteriorMask(*sdfGrid); // test inside coord value openvdb::Coord ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(0.5, 0.5, 0.5)); CPPUNIT_ASSERT(maskGrid->tree().getValue(ijk) == true); // test outside coord value ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(1.5, 1.5, 1.5)); CPPUNIT_ASSERT(maskGrid->tree().getValue(ijk) == false); } void TestLevelSetUtil::testExtractEnclosedRegion() { typedef openvdb::FloatGrid FloatGrid; typedef openvdb::BoolGrid BoolGrid; typedef openvdb::Vec3s Vec3s; typedef openvdb::math::BBox BBoxs; typedef openvdb::math::Transform Transform; BBoxs regionA(Vec3s(0.0f, 0.0f, 0.0f), Vec3s(3.0f, 3.0f, 3.0f)); BBoxs regionB(Vec3s(1.0f, 1.0f, 1.0f), Vec3s(2.0f, 2.0f, 2.0f)); Transform::Ptr transform = Transform::createLinearTransform(0.1); FloatGrid::Ptr sdfGrid = openvdb::tools::createLevelSetBox(regionA, *transform); FloatGrid::Ptr sdfGridB = openvdb::tools::createLevelSetBox(regionB, *transform); openvdb::tools::csgDifference(*sdfGrid, *sdfGridB); BoolGrid::Ptr maskGrid = openvdb::tools::extractEnclosedRegion(*sdfGrid); // test inside ls region coord value openvdb::Coord ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(1.5, 1.5, 1.5)); CPPUNIT_ASSERT(maskGrid->tree().getValue(ijk) == true); // test outside coord value ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(3.5, 3.5, 3.5)); CPPUNIT_ASSERT(maskGrid->tree().getValue(ijk) == false); } void TestLevelSetUtil::testSegmentationTools() { typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Vec3s Vec3s; typedef openvdb::math::BBox BBoxs; typedef openvdb::math::Transform Transform; { // Test SDF segmentation // Create two sdf boxes with overlapping narrow-bands. BBoxs regionA(Vec3s(0.0f, 0.0f, 0.0f), Vec3s(2.0f, 2.0f, 2.0f)); BBoxs regionB(Vec3s(2.5f, 0.0f, 0.0f), Vec3s(4.3f, 2.0f, 2.0f)); Transform::Ptr transform = Transform::createLinearTransform(0.1); FloatGrid::Ptr sdfGrid = openvdb::tools::createLevelSetBox(regionA, *transform); FloatGrid::Ptr sdfGridB = openvdb::tools::createLevelSetBox(regionB, *transform); openvdb::tools::csgUnion(*sdfGrid, *sdfGridB); std::vector segments; // This tool will not identify two separate segments when the narrow-bands overlap. openvdb::tools::segmentActiveVoxels(*sdfGrid, segments); CPPUNIT_ASSERT(segments.size() == 1); segments.clear(); // This tool should properly identify two separate segments openvdb::tools::segmentSDF(*sdfGrid, segments); CPPUNIT_ASSERT(segments.size() == 2); // test inside ls region coord value openvdb::Coord ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(1.5, 1.5, 1.5)); CPPUNIT_ASSERT(segments[0]->tree().getValue(ijk) < 0.0f); // test outside coord value ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(3.5, 3.5, 3.5)); CPPUNIT_ASSERT(segments[0]->tree().getValue(ijk) > 0.0f); } { // Test fog volume with active tiles openvdb::FloatGrid::Ptr grid = openvdb::FloatGrid::create(0.0); grid->fill(openvdb::CoordBBox(openvdb::Coord(0), openvdb::Coord(50)), 1.0); grid->fill(openvdb::CoordBBox(openvdb::Coord(60), openvdb::Coord(100)), 1.0); CPPUNIT_ASSERT(grid->tree().hasActiveTiles() == true); std::vector segments; openvdb::tools::segmentActiveVoxels(*grid, segments); CPPUNIT_ASSERT(segments.size() == 2); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000000564313200122377017117 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000001056613200122377017170 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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, float(1.0 + double(n) * 0.5)); } 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.root().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-2017 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/TestMeshToVolume.cc0000644000000000000000000001411013200122377016620 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TestMeshToVolume: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestMeshToVolume); CPPUNIT_TEST(testUtils); CPPUNIT_TEST(testConversion); CPPUNIT_TEST(testCreateLevelSetBox); CPPUNIT_TEST_SUITE_END(); void testUtils(); void testConversion(); void testCreateLevelSetBox(); }; 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::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 math::Transform::Ptr xform = math::Transform::createLinearTransform(); tools::QuadAndTriangleDataAdapter mesh(points, quads); FloatGrid::Ptr grid = tools::meshToVolume(mesh, *xform); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(int(GRID_LEVEL_SET), int(grid->getGridClass())); CPPUNIT_ASSERT_EQUAL(1, int(grid->baseTree().leafCount())); grid = tools::meshToLevelSet(*xform, points, quads); CPPUNIT_ASSERT(grid.get() != NULL); CPPUNIT_ASSERT_EQUAL(int(GRID_LEVEL_SET), int(grid->getGridClass())); CPPUNIT_ASSERT_EQUAL(1, int(grid->baseTree().leafCount())); } void TestMeshToVolume::testCreateLevelSetBox() { typedef openvdb::FloatGrid FloatGrid; typedef openvdb::Vec3s Vec3s; typedef openvdb::math::BBox BBoxs; typedef openvdb::math::Transform Transform; BBoxs bbox(Vec3s(0.0, 0.0, 0.0), Vec3s(1.0, 1.0, 1.0)); Transform::Ptr transform = Transform::createLinearTransform(0.1); FloatGrid::Ptr grid = openvdb::tools::createLevelSetBox(bbox, *transform); double gridBackground = grid->background(); double expectedBackground = transform->voxelSize().x() * double(openvdb::LEVEL_SET_HALF_WIDTH); CPPUNIT_ASSERT_DOUBLES_EQUAL(expectedBackground, gridBackground, 1e-6); CPPUNIT_ASSERT(grid->tree().leafCount() > 0); // test inside coord value openvdb::Coord ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(0.5, 0.5, 0.5)); CPPUNIT_ASSERT(grid->tree().getValue(ijk) < 0.0f); // test outside coord value ijk = transform->worldToIndexNodeCentered(openvdb::Vec3d(1.5, 1.5, 1.5)); CPPUNIT_ASSERT(grid->tree().getValue(ijk) > 0.0f); } // Copyright (c) 2012-2017 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.cc0000644000000000000000000002000413200122377014314 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for std::shuffle() #include // for EXIT_SUCCESS #include // for strrchr() #include #include #include #include #include #include namespace { using StringVec = std::vector; void usage(const char* progName, std::ostream& ostrm) { ostrm << "Usage: " << progName << " [options]\n" << "Which: runs OpenVDB library unit tests\n" << "Options:\n" << " -f file read whitespace-separated names of tests to be run\n" << " from the given file (\"#\" comments are supported)\n" << " -l list all available tests\n" << " -shuffle run tests in random order\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"; #ifdef OPENVDB_USE_LOG4CPLUS ostrm << "\n" << " -error log fatal and non-fatal errors (default: log only fatal errors)\n" << " -warn log warnings and errors\n" << " -info log info messages, warnings and errors\n" << " -debug log debugging messages, info messages, warnings and errors\n"; #endif } void getTestNames(StringVec& nameVec, const CppUnit::Test* test) { if (test) { const int numChildren = test->getChildTestCount(); if (numChildren == 0) { nameVec.push_back(test->getName()); } else { for (int i = 0; i < test->getChildTestCount(); ++i) { getTestNames(nameVec, test->getChildTestAt(i)); } } } } int run(int argc, char* argv[]) { const char* progName = argv[0]; if (const char* ptr = ::strrchr(progName, '/')) progName = ptr + 1; bool shuffle = false, verbose = false; StringVec tests; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (arg == "-l") { StringVec allTests; getTestNames(allTests, CppUnit::TestFactoryRegistry::getRegistry().makeTest()); for (const auto& name: allTests) { std::cout << name << "\n"; } return EXIT_SUCCESS; } else if (arg == "-shuffle") { shuffle = true; } else if (arg == "-v") { verbose = true; } else if (arg == "-t") { if (i + 1 < argc) { ++i; tests.push_back(argv[i]); } else { OPENVDB_LOG_FATAL("missing test name after \"-t\""); usage(progName, std::cerr); return EXIT_FAILURE; } } else if (arg == "-f") { if (i + 1 < argc) { ++i; std::ifstream file{argv[i]}; if (file.fail()) { OPENVDB_LOG_FATAL("unable to read file " << argv[i]); return EXIT_FAILURE; } while (file) { // Read a whitespace-separated string from the file. std::string test; file >> test; if (!test.empty()) { if (test[0] != '#') { tests.push_back(test); } else { // If the string starts with a comment symbol ("#"), // skip it and jump to the end of the line. while (file) { if (file.get() == '\n') break; } } } } } else { OPENVDB_LOG_FATAL("missing filename after \"-f\""); usage(progName, std::cerr); return EXIT_FAILURE; } } else if (arg == "-h" || arg == "-help" || arg == "--help") { usage(progName, std::cout); return EXIT_SUCCESS; } else { OPENVDB_LOG_FATAL("unrecognized option \"" << arg << "\""); usage(progName, std::cerr); return EXIT_FAILURE; } } try { CppUnit::TestFactoryRegistry& registry = CppUnit::TestFactoryRegistry::getRegistry(); auto* root = registry.makeTest(); if (!root) { throw std::runtime_error( "CppUnit test registry was not initialized properly"); } if (!shuffle) { if (tests.empty()) tests.push_back(""); } else { // Get the names of all selected tests and their children. StringVec allTests; if (tests.empty()) { getTestNames(allTests, root); } else { for (const auto& name: tests) { getTestNames(allTests, root->findTest(name)); } } // Randomly shuffle the list of names. std::random_device randDev; std::mt19937 generator(randDev()); std::shuffle(allTests.begin(), allTests.end(), generator); tests.swap(allTests); } CppUnit::TestRunner runner; runner.addTest(root); CppUnit::TestResult controller; CppUnit::TestResultCollector result; controller.addListener(&result); CppUnit::TextTestProgressListener progress; CppUnit::BriefTestProgressListener vProgress; if (verbose) { controller.addListener(&vProgress); } else { controller.addListener(&progress); } for (size_t i = 0; i < tests.size(); ++i) { runner.run(controller, tests[i]); } CppUnit::CompilerOutputter outputter(&result, std::cerr); outputter.write(); return result.wasSuccessful() ? EXIT_SUCCESS : EXIT_FAILURE; } catch (std::exception& e) { OPENVDB_LOG_FATAL(e.what()); return EXIT_FAILURE; } } } // anonymous namespace int main(int argc, char *argv[]) { openvdb::logging::initialize(argc, argv); return run(argc, argv); } // Copyright (c) 2012-2017 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.cc0000644000000000000000000003677113200122377020673 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 unittest/TestLevelSetRayIntersector.cc /// @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 #include #endif #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); Real time = 0; CPPUNIT_ASSERT(lsri.intersectsWS(ray, xyz, time)); ASSERT_DOUBLES_APPROX_EQUAL(15.0, xyz[0]); ASSERT_DOUBLES_APPROX_EQUAL( 0.0, xyz[1]); ASSERT_DOUBLES_APPROX_EQUAL( 0.0, xyz[2]); ASSERT_DOUBLES_APPROX_EQUAL(13.0, time); double t0=0, t1=0; CPPUNIT_ASSERT(ray.intersects(c, r, t0, t1)); ASSERT_DOUBLES_APPROX_EQUAL(t0, time); //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("sphere_serial"); stats.print("First hit"); hist.print("First hit"); } } #endif // STATS_TEST #undef STATS_TEST // Copyright (c) 2012-2017 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.cc0000644000000000000000000005367613200122377015162 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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; 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 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-2017 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.cc0000644000000000000000000001376413200122377016515 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000002731413200122377017353 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 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); openvdb::tools::prune(outGrid->tree()); // 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 20 x 20 x 20 cube. inGrid.fill(CoordBBox(Coord(5), Coord(24)), /*value=*/1.0); CPPUNIT_ASSERT_EQUAL(8000, int(inGrid.activeVoxelCount())); CPPUNIT_ASSERT(inGrid.tree().activeTileCount() > 0); {//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(0.5, 0.5, 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 double the resolution of the input grid // in x and y and the same resolution in z. CPPUNIT_ASSERT_EQUAL(32000, int(outGrid.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(Coord(40, 40, 20), outGrid.evalActiveVoxelDim()); CPPUNIT_ASSERT_EQUAL(CoordBBox(Coord(9, 9, 5), Coord(48, 48, 24)), outGrid.evalActiveVoxelBoundingBox()); for (auto it = outGrid.tree().cbeginValueOn(); it; ++it) { CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, *it, 1.0e-6); } } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000001332413200122377017501 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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.05; // 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(1.0, n0.length(), 1e-6); n1 = QuantizedUnitVec::unpack(QuantizedUnitVec::pack(n0)); CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, n1.length(), 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-2017 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/TestLeafBool.cc0000644000000000000000000005027713200122377015732 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "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(testMedian); //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 testMedian(); //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))); } } {// test Buffer::data() LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/false); leaf.fill(true); LeafType::Buffer::WordType* w = leaf.buffer().data(); for (openvdb::Index n = 0; n < LeafType::Buffer::WORD_COUNT; ++n) { CPPUNIT_ASSERT_EQUAL(~LeafType::Buffer::WordType(0), w[n]); } } {// test const Buffer::data() LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/false); leaf.fill(true); const LeafType& cleaf = leaf; const LeafType::Buffer::WordType* w = cleaf.buffer().data(); for (openvdb::Index n = 0; n < LeafType::Buffer::WORD_COUNT; ++n) { CPPUNIT_ASSERT_EQUAL(~LeafType::Buffer::WordType(0), w[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); } {// test LeafNode::operator==() LeafType leaf1(Coord(0 , 0, 0), true); // true and inactive LeafType leaf2(Coord(1 , 0, 0), true); // true and inactive LeafType leaf3(Coord(LeafType::DIM, 0, 0), true); // true and inactive LeafType leaf4(Coord(0 , 0, 0), true, true);//true and active CPPUNIT_ASSERT(leaf1 == leaf2); CPPUNIT_ASSERT(leaf1 != leaf3); CPPUNIT_ASSERT(leaf2 != leaf3); CPPUNIT_ASSERT(leaf1 != leaf4); CPPUNIT_ASSERT(leaf2 != leaf4); CPPUNIT_ASSERT(leaf3 != leaf4); } } 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, float(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("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::testMedian() { using namespace openvdb; LeafType leaf(openvdb::Coord(0, 0, 0), /*background=*/false); bool state = false; CPPUNIT_ASSERT_EQUAL(Index(0), leaf.medianOn(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); leaf.setValue(Coord(0,0,0), true); CPPUNIT_ASSERT_EQUAL(Index(1), leaf.medianOn(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-1, leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); leaf.setValue(Coord(0,0,1), true); CPPUNIT_ASSERT_EQUAL(Index(2), leaf.medianOn(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-2, leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); leaf.setValue(Coord(5,0,1), true); CPPUNIT_ASSERT_EQUAL(Index(3), leaf.medianOn(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-3, leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); leaf.fill(false, false); CPPUNIT_ASSERT_EQUAL(Index(0), leaf.medianOn(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), leaf.medianOff(state)); CPPUNIT_ASSERT(state == false); CPPUNIT_ASSERT(!leaf.medianAll()); for (Index i=0; itreePtr(); // 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("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-2017 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.cc0000644000000000000000000001005113200122377015063 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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/TestAttributeSet.cc0000644000000000000000000011712713200122377016664 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TestAttributeSet: public CppUnit::TestCase { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestAttributeSet); CPPUNIT_TEST(testAttributeSetDescriptor); CPPUNIT_TEST(testAttributeSet); CPPUNIT_TEST(testAttributeSetGroups); CPPUNIT_TEST_SUITE_END(); void testAttributeSetDescriptor(); void testAttributeSet(); void testAttributeSetGroups(); }; // class TestAttributeSet CPPUNIT_TEST_SUITE_REGISTRATION(TestAttributeSet); //////////////////////////////////////// using namespace openvdb; using namespace openvdb::points; namespace { bool matchingAttributeSets(const AttributeSet& lhs, const AttributeSet& rhs) { if (lhs.size() != rhs.size()) return false; if (lhs.memUsage() != rhs.memUsage()) return false; if (lhs.descriptor() != rhs.descriptor()) return false; for (size_t n = 0, N = lhs.size(); n < N; ++n) { const AttributeArray* a = lhs.getConst(n); const AttributeArray* b = rhs.getConst(n); if (a->size() != b->size()) return false; if (a->isUniform() != b->isUniform()) return false; if (a->isCompressed() != b->isCompressed()) return false; if (a->isHidden() != b->isHidden()) return false; if (a->type() != b->type()) return false; } return true; } bool attributeSetMatchesDescriptor( const AttributeSet& attrSet, const AttributeSet::Descriptor& descriptor) { if (descriptor.size() != attrSet.size()) return false; // check default metadata const openvdb::MetaMap& meta1 = descriptor.getMetadata(); const openvdb::MetaMap& meta2 = attrSet.descriptor().getMetadata(); // build vector of all default keys std::vector defaultKeys; for (auto it = meta1.beginMeta(), itEnd = meta1.endMeta(); it != itEnd; ++it) { const openvdb::Name& name = it->first; if (name.compare(0, 8, "default:") == 0) { defaultKeys.push_back(name); } } for (auto it = meta2.beginMeta(), itEnd = meta2.endMeta(); it != itEnd; ++it) { const openvdb::Name& name = it->first; if (name.compare(0, 8, "default:") == 0) { if (std::find(defaultKeys.begin(), defaultKeys.end(), name) != defaultKeys.end()) { defaultKeys.push_back(name); } } } // compare metadata value from each metamap for (const openvdb::Name& name : defaultKeys) { openvdb::Metadata::ConstPtr metaValue1 = meta1[name]; openvdb::Metadata::ConstPtr metaValue2 = meta2[name]; if (!metaValue1) return false; if (!metaValue2) return false; if (*metaValue1 != *metaValue2) return false; } // ensure descriptor and attributes are still in sync for (const auto& namePos : attrSet.descriptor().map()) { const size_t pos = descriptor.find(namePos.first); if (pos != size_t(namePos.second)) return false; if (descriptor.type(pos) != attrSet.get(pos)->type()) return false; } return true; } bool testStringVector(std::vector& input) { return input.empty(); } bool testStringVector(std::vector& input, const std::string& name1) { if (input.size() != 1) return false; if (input[0] != name1) return false; return true; } bool testStringVector(std::vector& input, const std::string& name1, const std::string& name2) { if (input.size() != 2) return false; if (input[0] != name1) return false; if (input[1] != name2) return false; return true; } } //unnamed namespace //////////////////////////////////////// void TestAttributeSet::testAttributeSetDescriptor() { // Define and register some common attribute types using AttributeVec3f = TypedAttributeArray; using AttributeS = TypedAttributeArray; using AttributeI = TypedAttributeArray; using Descriptor = AttributeSet::Descriptor; { // error on invalid construction Descriptor::Ptr invalidDescr = Descriptor::create(AttributeVec3f::attributeType()); CPPUNIT_ASSERT_THROW(invalidDescr->duplicateAppend("P", AttributeS::attributeType()), openvdb::KeyError); } Descriptor::Ptr descrA = Descriptor::create(AttributeVec3f::attributeType()); descrA = descrA->duplicateAppend("density", AttributeS::attributeType()); descrA = descrA->duplicateAppend("id", AttributeI::attributeType()); Descriptor::Ptr descrB = Descriptor::create(AttributeVec3f::attributeType()); descrB = descrB->duplicateAppend("density", AttributeS::attributeType()); descrB = descrB->duplicateAppend("id", AttributeI::attributeType()); CPPUNIT_ASSERT_EQUAL(descrA->size(), descrB->size()); CPPUNIT_ASSERT(*descrA == *descrB); descrB->setGroup("test", size_t(0)); descrB->setGroup("test2", size_t(1)); Descriptor descrC(*descrB); CPPUNIT_ASSERT(descrB->hasSameAttributes(descrC)); CPPUNIT_ASSERT(descrC.hasGroup("test")); CPPUNIT_ASSERT(*descrB == descrC); descrC.dropGroup("test"); descrC.dropGroup("test2"); CPPUNIT_ASSERT(!descrB->hasSameAttributes(descrC)); CPPUNIT_ASSERT(!descrC.hasGroup("test")); CPPUNIT_ASSERT(*descrB != descrC); descrC.setGroup("test2", size_t(1)); descrC.setGroup("test3", size_t(0)); CPPUNIT_ASSERT(!descrB->hasSameAttributes(descrC)); descrC.dropGroup("test3"); descrC.setGroup("test", size_t(0)); CPPUNIT_ASSERT(descrB->hasSameAttributes(descrC)); Descriptor::Inserter names; names.add("P", AttributeVec3f::attributeType()); names.add("density", AttributeS::attributeType()); names.add("id", AttributeI::attributeType()); // rebuild NameAndTypeVec Descriptor::NameAndTypeVec rebuildNames; descrA->appendTo(rebuildNames); CPPUNIT_ASSERT_EQUAL(rebuildNames.size(), names.vec.size()); for (auto itA = rebuildNames.cbegin(), itB = names.vec.cbegin(), itEndA = rebuildNames.cend(), itEndB = names.vec.cend(); itA != itEndA && itB != itEndB; ++itA, ++itB) { CPPUNIT_ASSERT_EQUAL(itA->name, itB->name); CPPUNIT_ASSERT_EQUAL(itA->type.first, itB->type.first); CPPUNIT_ASSERT_EQUAL(itA->type.second, itB->type.second); } Descriptor::NameToPosMap groupMap; openvdb::MetaMap metadata; // hasSameAttributes (note: uses protected create methods) { Descriptor::Ptr descr1 = Descriptor::create(Descriptor::Inserter() .add("P", AttributeVec3f::attributeType()) .add("test", AttributeI::attributeType()) .add("id", AttributeI::attributeType()) .vec, groupMap, metadata); // test same names with different types, should be false Descriptor::Ptr descr2 = Descriptor::create(Descriptor::Inserter() .add("P", AttributeVec3f::attributeType()) .add("test", AttributeS::attributeType()) .add("id", AttributeI::attributeType()) .vec, groupMap, metadata); CPPUNIT_ASSERT(!descr1->hasSameAttributes(*descr2)); // test different names, should be false Descriptor::Ptr descr3 = Descriptor::create(Descriptor::Inserter() .add("P", AttributeVec3f::attributeType()) .add("test2", AttributeI::attributeType()) .add("id", AttributeI::attributeType()) .vec, groupMap, metadata); CPPUNIT_ASSERT(!descr1->hasSameAttributes(*descr3)); // test same names and types but different order, should be true Descriptor::Ptr descr4 = Descriptor::create(Descriptor::Inserter() .add("test", AttributeI::attributeType()) .add("id", AttributeI::attributeType()) .add("P", AttributeVec3f::attributeType()) .vec, groupMap, metadata); CPPUNIT_ASSERT(descr1->hasSameAttributes(*descr4)); } { // Test uniqueName Descriptor::Inserter names2; Descriptor::Ptr emptyDescr = Descriptor::create(AttributeVec3f::attributeType()); const openvdb::Name uniqueNameEmpty = emptyDescr->uniqueName("test"); CPPUNIT_ASSERT_EQUAL(uniqueNameEmpty, openvdb::Name("test")); names2.add("test", AttributeS::attributeType()); names2.add("test1", AttributeI::attributeType()); Descriptor::Ptr descr1 = Descriptor::create(names2.vec, groupMap, metadata); const openvdb::Name uniqueName1 = descr1->uniqueName("test"); CPPUNIT_ASSERT_EQUAL(uniqueName1, openvdb::Name("test0")); Descriptor::Ptr descr2 = descr1->duplicateAppend(uniqueName1, AttributeI::attributeType()); const openvdb::Name uniqueName2 = descr2->uniqueName("test"); CPPUNIT_ASSERT_EQUAL(uniqueName2, openvdb::Name("test2")); } { // Test name validity CPPUNIT_ASSERT(Descriptor::validName("test1")); CPPUNIT_ASSERT(Descriptor::validName("abc_def")); CPPUNIT_ASSERT(Descriptor::validName("abc|def")); CPPUNIT_ASSERT(Descriptor::validName("abc:def")); CPPUNIT_ASSERT(!Descriptor::validName("")); CPPUNIT_ASSERT(!Descriptor::validName("test1!")); CPPUNIT_ASSERT(!Descriptor::validName("abc=def")); CPPUNIT_ASSERT(!Descriptor::validName("abc def")); CPPUNIT_ASSERT(!Descriptor::validName("abc*def")); } { // Test enforcement of valid names Descriptor::Ptr descr = Descriptor::create(Descriptor::Inserter().add( "test1", AttributeS::attributeType()).vec, groupMap, metadata); CPPUNIT_ASSERT_THROW(descr->rename("test1", "test1!"), openvdb::RuntimeError); CPPUNIT_ASSERT_THROW(descr->setGroup("group1!", 1), openvdb::RuntimeError); Descriptor::NameAndType invalidAttr("test1!", AttributeS::attributeType()); CPPUNIT_ASSERT_THROW(descr->duplicateAppend(invalidAttr.name, invalidAttr.type), openvdb::RuntimeError); const openvdb::Index64 offset(0); const openvdb::Index64 zeroLength(0); const openvdb::Index64 oneLength(1); // write a stream with an invalid attribute std::ostringstream attrOstr(std::ios_base::binary); attrOstr.write(reinterpret_cast(&oneLength), sizeof(openvdb::Index64)); openvdb::writeString(attrOstr, invalidAttr.type.first); openvdb::writeString(attrOstr, invalidAttr.type.second); openvdb::writeString(attrOstr, invalidAttr.name); attrOstr.write(reinterpret_cast(&offset), sizeof(openvdb::Index64)); attrOstr.write(reinterpret_cast(&zeroLength), sizeof(openvdb::Index64)); // write a stream with an invalid group std::ostringstream groupOstr(std::ios_base::binary); groupOstr.write(reinterpret_cast(&zeroLength), sizeof(openvdb::Index64)); groupOstr.write(reinterpret_cast(&oneLength), sizeof(openvdb::Index64)); openvdb::writeString(groupOstr, "group1!"); groupOstr.write(reinterpret_cast(&offset), sizeof(openvdb::Index64)); // read the streams back Descriptor inputDescr; std::istringstream attrIstr(attrOstr.str(), std::ios_base::binary); CPPUNIT_ASSERT_THROW(inputDescr.read(attrIstr), openvdb::IoError); std::istringstream groupIstr(groupOstr.str(), std::ios_base::binary); CPPUNIT_ASSERT_THROW(inputDescr.read(groupIstr), openvdb::IoError); } { // Test empty string parse std::vector includeNames; std::vector excludeNames; Descriptor::parseNames(includeNames, excludeNames, ""); CPPUNIT_ASSERT(testStringVector(includeNames)); CPPUNIT_ASSERT(testStringVector(excludeNames)); } { // Test single token parse std::vector includeNames; std::vector excludeNames; bool includeAll = false; Descriptor::parseNames(includeNames, excludeNames, includeAll, "group1"); CPPUNIT_ASSERT(!includeAll); CPPUNIT_ASSERT(testStringVector(includeNames, "group1")); CPPUNIT_ASSERT(testStringVector(excludeNames)); } { // Test parse with two include tokens std::vector includeNames; std::vector excludeNames; Descriptor::parseNames(includeNames, excludeNames, "group1 group2"); CPPUNIT_ASSERT(testStringVector(includeNames, "group1", "group2")); CPPUNIT_ASSERT(testStringVector(excludeNames)); } { // Test parse with one include and one ^ exclude token std::vector includeNames; std::vector excludeNames; Descriptor::parseNames(includeNames, excludeNames, "group1 ^group2"); CPPUNIT_ASSERT(testStringVector(includeNames, "group1")); CPPUNIT_ASSERT(testStringVector(excludeNames, "group2")); } { // Test parse with one include and one ! exclude token std::vector includeNames; std::vector excludeNames; Descriptor::parseNames(includeNames, excludeNames, "group1 !group2"); CPPUNIT_ASSERT(testStringVector(includeNames, "group1")); CPPUNIT_ASSERT(testStringVector(excludeNames, "group2")); } { // Test parse one include and one exclude backwards std::vector includeNames; std::vector excludeNames; Descriptor::parseNames(includeNames, excludeNames, "^group1 group2"); CPPUNIT_ASSERT(testStringVector(includeNames, "group2")); CPPUNIT_ASSERT(testStringVector(excludeNames, "group1")); } { // Test parse with two exclude tokens std::vector includeNames; std::vector excludeNames; Descriptor::parseNames(includeNames, excludeNames, "^group1 ^group2"); CPPUNIT_ASSERT(testStringVector(includeNames)); CPPUNIT_ASSERT(testStringVector(excludeNames, "group1", "group2")); } { // Test parse multiple includes and excludes at the same time std::vector includeNames; std::vector excludeNames; Descriptor::parseNames(includeNames, excludeNames, "group1 ^group2 ^group3 group4"); CPPUNIT_ASSERT(testStringVector(includeNames, "group1", "group4")); CPPUNIT_ASSERT(testStringVector(excludeNames, "group2", "group3")); } { // Test parse misplaced negate character failure std::vector includeNames; std::vector excludeNames; CPPUNIT_ASSERT_THROW(Descriptor::parseNames(includeNames, excludeNames, "group1 ^ group2"), openvdb::RuntimeError); } { // Test parse (*) character std::vector includeNames; std::vector excludeNames; bool includeAll = false; Descriptor::parseNames(includeNames, excludeNames, includeAll, "*"); CPPUNIT_ASSERT(includeAll); CPPUNIT_ASSERT(testStringVector(includeNames)); CPPUNIT_ASSERT(testStringVector(excludeNames)); } { // Test parse invalid character failure std::vector includeNames; std::vector excludeNames; CPPUNIT_ASSERT_THROW(Descriptor::parseNames(includeNames, excludeNames, "group$1"), openvdb::RuntimeError); } { // Test hasGroup(), setGroup(), dropGroup(), clearGroups() Descriptor descr; CPPUNIT_ASSERT(!descr.hasGroup("test1")); descr.setGroup("test1", 1); CPPUNIT_ASSERT(descr.hasGroup("test1")); CPPUNIT_ASSERT_EQUAL(descr.groupMap().at("test1"), size_t(1)); descr.setGroup("test5", 5); CPPUNIT_ASSERT(descr.hasGroup("test1")); CPPUNIT_ASSERT(descr.hasGroup("test5")); CPPUNIT_ASSERT_EQUAL(descr.groupMap().at("test1"), size_t(1)); CPPUNIT_ASSERT_EQUAL(descr.groupMap().at("test5"), size_t(5)); descr.setGroup("test1", 2); CPPUNIT_ASSERT(descr.hasGroup("test1")); CPPUNIT_ASSERT(descr.hasGroup("test5")); CPPUNIT_ASSERT_EQUAL(descr.groupMap().at("test1"), size_t(2)); CPPUNIT_ASSERT_EQUAL(descr.groupMap().at("test5"), size_t(5)); descr.dropGroup("test1"); CPPUNIT_ASSERT(!descr.hasGroup("test1")); CPPUNIT_ASSERT(descr.hasGroup("test5")); descr.setGroup("test3", 3); CPPUNIT_ASSERT(descr.hasGroup("test3")); CPPUNIT_ASSERT(descr.hasGroup("test5")); descr.clearGroups(); CPPUNIT_ASSERT(!descr.hasGroup("test1")); CPPUNIT_ASSERT(!descr.hasGroup("test3")); CPPUNIT_ASSERT(!descr.hasGroup("test5")); } // I/O test std::ostringstream ostr(std::ios_base::binary); descrA->write(ostr); Descriptor inputDescr; std::istringstream istr(ostr.str(), std::ios_base::binary); inputDescr.read(istr); CPPUNIT_ASSERT_EQUAL(descrA->size(), inputDescr.size()); CPPUNIT_ASSERT(*descrA == inputDescr); } void TestAttributeSet::testAttributeSet() { // Define and register some common attribute types using AttributeS = TypedAttributeArray; using AttributeI = TypedAttributeArray; using AttributeL = TypedAttributeArray; using AttributeVec3s = TypedAttributeArray; using Descriptor = AttributeSet::Descriptor; Descriptor::NameToPosMap groupMap; openvdb::MetaMap metadata; { // construction Descriptor::Ptr descr = Descriptor::create(AttributeVec3s::attributeType()); descr = descr->duplicateAppend("test", AttributeI::attributeType()); AttributeSet attrSet(descr); CPPUNIT_ASSERT_EQUAL(attrSet.size(), size_t(2)); Descriptor::Ptr newDescr = Descriptor::create(AttributeVec3s::attributeType()); CPPUNIT_ASSERT_THROW(attrSet.resetDescriptor(newDescr), openvdb::LookupError); CPPUNIT_ASSERT_NO_THROW( attrSet.resetDescriptor(newDescr, /*allowMismatchingDescriptors=*/true)); } { // transfer of flags on construction AttributeSet attrSet(Descriptor::create(AttributeVec3s::attributeType())); AttributeArray::Ptr array1 = attrSet.appendAttribute( "hidden", AttributeS::attributeType()); array1->setHidden(true); AttributeArray::Ptr array2 = attrSet.appendAttribute( "transient", AttributeS::attributeType()); array2->setTransient(true); AttributeSet attrSet2(attrSet, size_t(1)); CPPUNIT_ASSERT(attrSet2.getConst("hidden")->isHidden()); CPPUNIT_ASSERT(attrSet2.getConst("transient")->isTransient()); } // construct { // invalid append Descriptor::Ptr descr = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet invalidAttrSetA(descr, /*arrayLength=*/50); CPPUNIT_ASSERT_THROW(invalidAttrSetA.appendAttribute("id", AttributeI::attributeType(), /*stride=*/0, /*constantStride=*/true), openvdb::ValueError); CPPUNIT_ASSERT(invalidAttrSetA.find("id") == AttributeSet::INVALID_POS); CPPUNIT_ASSERT_THROW(invalidAttrSetA.appendAttribute("id", AttributeI::attributeType(), /*stride=*/49, /*constantStride=*/false), openvdb::ValueError); CPPUNIT_ASSERT_NO_THROW( invalidAttrSetA.appendAttribute("testStride1", AttributeI::attributeType(), /*stride=*/50, /*constantStride=*/false)); CPPUNIT_ASSERT_NO_THROW( invalidAttrSetA.appendAttribute("testStride2", AttributeI::attributeType(), /*stride=*/51, /*constantStride=*/false)); } Descriptor::Ptr descr = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet attrSetA(descr, /*arrayLength=*/50); attrSetA.appendAttribute("id", AttributeI::attributeType()); // check equality against duplicate array Descriptor::Ptr descr2 = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet attrSetA2(descr2, /*arrayLength=*/50); attrSetA2.appendAttribute("id", AttributeI::attributeType()); CPPUNIT_ASSERT(attrSetA == attrSetA2); // expand uniform values and check equality attrSetA.get("P")->expand(); attrSetA2.get("P")->expand(); CPPUNIT_ASSERT(attrSetA == attrSetA2); CPPUNIT_ASSERT_EQUAL(size_t(2), attrSetA.size()); CPPUNIT_ASSERT_EQUAL(openvdb::Index(50), attrSetA.get(0)->size()); CPPUNIT_ASSERT_EQUAL(openvdb::Index(50), attrSetA.get(1)->size()); { // copy CPPUNIT_ASSERT(!attrSetA.isShared(0)); CPPUNIT_ASSERT(!attrSetA.isShared(1)); AttributeSet attrSetB(attrSetA); CPPUNIT_ASSERT(matchingAttributeSets(attrSetA, attrSetB)); CPPUNIT_ASSERT(attrSetA.isShared(0)); CPPUNIT_ASSERT(attrSetA.isShared(1)); CPPUNIT_ASSERT(attrSetB.isShared(0)); CPPUNIT_ASSERT(attrSetB.isShared(1)); attrSetB.makeUnique(0); attrSetB.makeUnique(1); CPPUNIT_ASSERT(matchingAttributeSets(attrSetA, attrSetB)); CPPUNIT_ASSERT(!attrSetA.isShared(0)); CPPUNIT_ASSERT(!attrSetA.isShared(1)); CPPUNIT_ASSERT(!attrSetB.isShared(0)); CPPUNIT_ASSERT(!attrSetB.isShared(1)); } { // attribute insertion AttributeSet attrSetB(attrSetA); attrSetB.makeUnique(0); attrSetB.makeUnique(1); Descriptor::Ptr targetDescr = Descriptor::create(Descriptor::Inserter() .add("P", AttributeVec3s::attributeType()) .add("id", AttributeI::attributeType()) .add("test", AttributeS::attributeType()) .vec, groupMap, metadata); Descriptor::Ptr descrB = attrSetB.descriptor().duplicateAppend("test", AttributeS::attributeType()); // should throw if we attempt to add the same attribute name but a different type CPPUNIT_ASSERT_THROW( descrB->insert("test", AttributeI::attributeType()), openvdb::KeyError); // shouldn't throw if we attempt to add the same attribute name and type CPPUNIT_ASSERT_NO_THROW(descrB->insert("test", AttributeS::attributeType())); openvdb::TypedMetadata defaultValueTest(5); // add a default value of the wrong type openvdb::TypedMetadata defaultValueInt(5); CPPUNIT_ASSERT_THROW(descrB->setDefaultValue("test", defaultValueInt), openvdb::TypeError); // add a default value with a name that does not exist CPPUNIT_ASSERT_THROW(descrB->setDefaultValue("badname", defaultValueTest), openvdb::LookupError); // add a default value for test of 5 descrB->setDefaultValue("test", defaultValueTest); { openvdb::Metadata::Ptr meta = descrB->getMetadata()["default:test"]; CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT(meta->typeName() == "float"); } // ensure attribute order persists CPPUNIT_ASSERT_EQUAL(descrB->find("P"), size_t(0)); CPPUNIT_ASSERT_EQUAL(descrB->find("id"), size_t(1)); CPPUNIT_ASSERT_EQUAL(descrB->find("test"), size_t(2)); { // simple method AttributeSet attrSetC(attrSetB); attrSetC.makeUnique(0); attrSetC.makeUnique(1); attrSetC.appendAttribute("test", AttributeS::attributeType(), /*stride=*/1, /*constantStride=*/true, defaultValueTest.copy()); CPPUNIT_ASSERT(attributeSetMatchesDescriptor(attrSetC, *descrB)); } { // descriptor-sharing method AttributeSet attrSetC(attrSetB); attrSetC.makeUnique(0); attrSetC.makeUnique(1); attrSetC.appendAttribute(attrSetC.descriptor(), descrB, size_t(2)); CPPUNIT_ASSERT(attributeSetMatchesDescriptor(attrSetC, *targetDescr)); } // add a default value for pos of (1, 3, 1) openvdb::TypedMetadata defaultValuePos( AttributeVec3s::ValueType(1, 3, 1)); descrB->setDefaultValue("P", defaultValuePos); { openvdb::Metadata::Ptr meta = descrB->getMetadata()["default:P"]; CPPUNIT_ASSERT(meta); CPPUNIT_ASSERT(meta->typeName() == "vec3s"); CPPUNIT_ASSERT_EQUAL(descrB->getDefaultValue("P"), defaultValuePos.value()); } // remove default value CPPUNIT_ASSERT(descrB->hasDefaultValue("test")); descrB->removeDefaultValue("test"); CPPUNIT_ASSERT(!descrB->hasDefaultValue("test")); } { // attribute removal Descriptor::Ptr descr1 = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet attrSetB(descr1, /*arrayLength=*/50); attrSetB.appendAttribute("test", AttributeI::attributeType()); attrSetB.appendAttribute("id", AttributeL::attributeType()); attrSetB.appendAttribute("test2", AttributeI::attributeType()); attrSetB.appendAttribute("id2", AttributeL::attributeType()); attrSetB.appendAttribute("test3", AttributeI::attributeType()); descr1 = attrSetB.descriptorPtr(); Descriptor::Ptr targetDescr = Descriptor::create(AttributeVec3s::attributeType()); targetDescr = targetDescr->duplicateAppend("id", AttributeL::attributeType()); targetDescr = targetDescr->duplicateAppend("id2", AttributeL::attributeType()); // add some default values openvdb::TypedMetadata defaultOne(AttributeI::ValueType(1)); descr1->setDefaultValue("test", defaultOne); descr1->setDefaultValue("test2", defaultOne); openvdb::TypedMetadata defaultThree(AttributeL::ValueType(3)); descr1->setDefaultValue("id", defaultThree); std::vector toDrop{ descr1->find("test"), descr1->find("test2"), descr1->find("test3")}; CPPUNIT_ASSERT_EQUAL(toDrop[0], size_t(1)); CPPUNIT_ASSERT_EQUAL(toDrop[1], size_t(3)); CPPUNIT_ASSERT_EQUAL(toDrop[2], size_t(5)); { // simple method AttributeSet attrSetC(attrSetB); attrSetC.makeUnique(0); attrSetC.makeUnique(1); attrSetC.makeUnique(2); attrSetC.makeUnique(3); CPPUNIT_ASSERT(attrSetC.descriptor().getMetadata()["default:test"]); attrSetC.dropAttributes(toDrop); CPPUNIT_ASSERT_EQUAL(attrSetC.size(), size_t(3)); CPPUNIT_ASSERT(attributeSetMatchesDescriptor(attrSetC, *targetDescr)); // check default values have been removed for the relevant attributes const Descriptor& descrC = attrSetC.descriptor(); CPPUNIT_ASSERT(!descrC.getMetadata()["default:test"]); CPPUNIT_ASSERT(!descrC.getMetadata()["default:test2"]); CPPUNIT_ASSERT(!descrC.getMetadata()["default:test3"]); CPPUNIT_ASSERT(descrC.getMetadata()["default:id"]); } { // reverse removal order std::vector toDropReverse{ descr1->find("test3"), descr1->find("test2"), descr1->find("test")}; AttributeSet attrSetC(attrSetB); attrSetC.makeUnique(0); attrSetC.makeUnique(1); attrSetC.makeUnique(2); attrSetC.makeUnique(3); attrSetC.dropAttributes(toDropReverse); CPPUNIT_ASSERT_EQUAL(attrSetC.size(), size_t(3)); CPPUNIT_ASSERT(attributeSetMatchesDescriptor(attrSetC, *targetDescr)); } { // descriptor-sharing method AttributeSet attrSetC(attrSetB); attrSetC.makeUnique(0); attrSetC.makeUnique(1); attrSetC.makeUnique(2); attrSetC.makeUnique(3); Descriptor::Ptr descrB = attrSetB.descriptor().duplicateDrop(toDrop); attrSetC.dropAttributes(toDrop, attrSetC.descriptor(), descrB); CPPUNIT_ASSERT_EQUAL(attrSetC.size(), size_t(3)); CPPUNIT_ASSERT(attributeSetMatchesDescriptor(attrSetC, *targetDescr)); } { // test duplicateDrop configures group mapping AttributeSet attrSetC; const size_t GROUP_BITS = sizeof(GroupType) * CHAR_BIT; attrSetC.appendAttribute("test1", AttributeI::attributeType()); attrSetC.appendAttribute("__group1", GroupAttributeArray::attributeType()); attrSetC.appendAttribute("test2", AttributeI::attributeType()); attrSetC.appendAttribute("__group2", GroupAttributeArray::attributeType()); attrSetC.appendAttribute("__group3", GroupAttributeArray::attributeType()); attrSetC.appendAttribute("__group4", GroupAttributeArray::attributeType()); // 5 attributes exist - append a group as the sixth and then drop Descriptor::Ptr descriptor = attrSetC.descriptorPtr(); size_t count = descriptor->count(GroupAttributeArray::attributeType()); CPPUNIT_ASSERT_EQUAL(count, size_t(4)); descriptor->setGroup("test_group1", /*offset*/0); // __group1 descriptor->setGroup("test_group2", /*offset=8*/GROUP_BITS); // __group2 descriptor->setGroup("test_group3", /*offset=16*/GROUP_BITS*2); // __group3 descriptor->setGroup("test_group4", /*offset=28*/GROUP_BITS*3 + GROUP_BITS/2); // __group4 descriptor = descriptor->duplicateDrop({ 1, 2, 3 }); count = descriptor->count(GroupAttributeArray::attributeType()); CPPUNIT_ASSERT_EQUAL(count, size_t(2)); CPPUNIT_ASSERT_EQUAL(size_t(3), descriptor->size()); CPPUNIT_ASSERT(!descriptor->hasGroup("test_group1")); CPPUNIT_ASSERT(!descriptor->hasGroup("test_group2")); CPPUNIT_ASSERT(descriptor->hasGroup("test_group3")); CPPUNIT_ASSERT(descriptor->hasGroup("test_group4")); CPPUNIT_ASSERT_EQUAL(descriptor->find("__group1"), size_t(AttributeSet::INVALID_POS)); CPPUNIT_ASSERT_EQUAL(descriptor->find("__group2"), size_t(AttributeSet::INVALID_POS)); CPPUNIT_ASSERT_EQUAL(descriptor->find("__group3"), size_t(1)); CPPUNIT_ASSERT_EQUAL(descriptor->find("__group4"), size_t(2)); CPPUNIT_ASSERT_EQUAL(descriptor->groupOffset("test_group3"), size_t(0)); CPPUNIT_ASSERT_EQUAL(descriptor->groupOffset("test_group4"), size_t(GROUP_BITS + GROUP_BITS/2)); } } // replace existing arrays // this replace call should not take effect since the new attribute // array type does not match with the descriptor type for the given position. AttributeArray::Ptr floatAttr(new AttributeS(15)); CPPUNIT_ASSERT(attrSetA.replace(1, floatAttr) == AttributeSet::INVALID_POS); AttributeArray::Ptr intAttr(new AttributeI(10)); CPPUNIT_ASSERT(attrSetA.replace(1, intAttr) != AttributeSet::INVALID_POS); CPPUNIT_ASSERT_EQUAL(openvdb::Index(10), attrSetA.get(1)->size()); { // reorder attribute set Descriptor::Ptr descr1 = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet attrSetA1(descr1); attrSetA1.appendAttribute("test", AttributeI::attributeType()); attrSetA1.appendAttribute("id", AttributeI::attributeType()); attrSetA1.appendAttribute("test2", AttributeI::attributeType()); descr1 = attrSetA1.descriptorPtr(); Descriptor::Ptr descr2x = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet attrSetB1(descr2x); attrSetB1.appendAttribute("test2", AttributeI::attributeType()); attrSetB1.appendAttribute("test", AttributeI::attributeType()); attrSetB1.appendAttribute("id", AttributeI::attributeType()); CPPUNIT_ASSERT(attrSetA1 != attrSetB1); attrSetB1.reorderAttributes(descr1); CPPUNIT_ASSERT(attrSetA1 == attrSetB1); } { // metadata test Descriptor::Ptr descr1A = Descriptor::create(AttributeVec3s::attributeType()); Descriptor::Ptr descr2A = Descriptor::create(AttributeVec3s::attributeType()); openvdb::MetaMap& meta = descr1A->getMetadata(); meta.insertMeta("exampleMeta", openvdb::FloatMetadata(2.0)); AttributeSet attrSetA1(descr1A); AttributeSet attrSetB1(descr2A); AttributeSet attrSetC1(attrSetA1); CPPUNIT_ASSERT(attrSetA1 != attrSetB1); CPPUNIT_ASSERT(attrSetA1 == attrSetC1); } // add some metadata and register the type openvdb::MetaMap& meta = attrSetA.descriptor().getMetadata(); meta.insertMeta("exampleMeta", openvdb::FloatMetadata(2.0)); { // I/O test std::ostringstream ostr(std::ios_base::binary); attrSetA.write(ostr); AttributeSet attrSetB; std::istringstream istr(ostr.str(), std::ios_base::binary); attrSetB.read(istr); CPPUNIT_ASSERT(matchingAttributeSets(attrSetA, attrSetB)); } { // I/O transient test AttributeArray* array = attrSetA.get(0); array->setTransient(true); std::ostringstream ostr(std::ios_base::binary); attrSetA.write(ostr); AttributeSet attrSetB; std::istringstream istr(ostr.str(), std::ios_base::binary); attrSetB.read(istr); // ensures transient attribute is not written out CPPUNIT_ASSERT_EQUAL(attrSetB.size(), size_t(1)); std::ostringstream ostr2(std::ios_base::binary); attrSetA.write(ostr2, /*transient=*/true); AttributeSet attrSetC; std::istringstream istr2(ostr2.str(), std::ios_base::binary); attrSetC.read(istr2); CPPUNIT_ASSERT_EQUAL(attrSetC.size(), size_t(2)); } } void TestAttributeSet::testAttributeSetGroups() { // Define and register some common attribute types using AttributeI = TypedAttributeArray; using AttributeVec3s = TypedAttributeArray; using Descriptor = AttributeSet::Descriptor; Descriptor::NameToPosMap groupMap; openvdb::MetaMap metadata; { // construct Descriptor::Ptr descr = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet attrSet(descr, /*arrayLength=*/3); attrSet.appendAttribute("id", AttributeI::attributeType()); CPPUNIT_ASSERT(!descr->hasGroup("test1")); } { // group offset Descriptor::Ptr descr = Descriptor::create(AttributeVec3s::attributeType()); descr->setGroup("test1", 1); CPPUNIT_ASSERT(descr->hasGroup("test1")); CPPUNIT_ASSERT_EQUAL(descr->groupMap().at("test1"), size_t(1)); AttributeSet attrSet(descr); CPPUNIT_ASSERT_EQUAL(attrSet.groupOffset("test1"), size_t(1)); } { // group index Descriptor::Ptr descr = Descriptor::create(AttributeVec3s::attributeType()); AttributeSet attrSet(descr); attrSet.appendAttribute("test", AttributeI::attributeType()); attrSet.appendAttribute("test2", AttributeI::attributeType()); attrSet.appendAttribute("group1", GroupAttributeArray::attributeType()); attrSet.appendAttribute("test3", AttributeI::attributeType()); attrSet.appendAttribute("group2", GroupAttributeArray::attributeType()); attrSet.appendAttribute("test4", AttributeI::attributeType()); attrSet.appendAttribute("group3", GroupAttributeArray::attributeType()); descr = attrSet.descriptorPtr(); std::stringstream ss; for (int i = 0; i < 17; i++) { ss.str(""); ss << "test" << i; descr->setGroup(ss.str(), i); } Descriptor::GroupIndex index15 = attrSet.groupIndex(15); CPPUNIT_ASSERT_EQUAL(index15.first, size_t(5)); CPPUNIT_ASSERT_EQUAL(index15.second, uint8_t(7)); CPPUNIT_ASSERT_EQUAL(attrSet.groupOffset(index15), size_t(15)); CPPUNIT_ASSERT_EQUAL(attrSet.groupOffset("test15"), size_t(15)); Descriptor::GroupIndex index15b = attrSet.groupIndex("test15"); CPPUNIT_ASSERT_EQUAL(index15b.first, size_t(5)); CPPUNIT_ASSERT_EQUAL(index15b.second, uint8_t(7)); Descriptor::GroupIndex index16 = attrSet.groupIndex(16); CPPUNIT_ASSERT_EQUAL(index16.first, size_t(7)); CPPUNIT_ASSERT_EQUAL(index16.second, uint8_t(0)); CPPUNIT_ASSERT_EQUAL(attrSet.groupOffset(index16), size_t(16)); CPPUNIT_ASSERT_EQUAL(attrSet.groupOffset("test16"), size_t(16)); Descriptor::GroupIndex index16b = attrSet.groupIndex("test16"); CPPUNIT_ASSERT_EQUAL(index16b.first, size_t(7)); CPPUNIT_ASSERT_EQUAL(index16b.second, uint8_t(0)); // check out of range exception CPPUNIT_ASSERT_NO_THROW(attrSet.groupIndex(23)); CPPUNIT_ASSERT_THROW(attrSet.groupIndex(24), LookupError); } { // group unique name Descriptor::Ptr descr = Descriptor::create(AttributeVec3s::attributeType()); const openvdb::Name uniqueNameEmpty = descr->uniqueGroupName("test"); CPPUNIT_ASSERT_EQUAL(uniqueNameEmpty, openvdb::Name("test")); descr->setGroup("test", 1); descr->setGroup("test1", 2); const openvdb::Name uniqueName1 = descr->uniqueGroupName("test"); CPPUNIT_ASSERT_EQUAL(uniqueName1, openvdb::Name("test0")); descr->setGroup(uniqueName1, 3); const openvdb::Name uniqueName2 = descr->uniqueGroupName("test"); CPPUNIT_ASSERT_EQUAL(uniqueName2, openvdb::Name("test2")); } { // group rename Descriptor::Ptr descr = Descriptor::create(AttributeVec3s::attributeType()); descr->setGroup("test", 1); descr->setGroup("test1", 2); size_t pos = descr->renameGroup("test", "test1"); CPPUNIT_ASSERT(pos == AttributeSet::INVALID_POS); CPPUNIT_ASSERT(descr->hasGroup("test")); CPPUNIT_ASSERT(descr->hasGroup("test1")); pos = descr->renameGroup("test", "test2"); CPPUNIT_ASSERT_EQUAL(pos, size_t(1)); CPPUNIT_ASSERT(!descr->hasGroup("test")); CPPUNIT_ASSERT(descr->hasGroup("test1")); CPPUNIT_ASSERT(descr->hasGroup("test2")); } } // Copyright (c) 2012-2017 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/TestPointConversion.cc0000644000000000000000000011071613200122377017401 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #ifdef _MSC_VER #include #endif using namespace openvdb; using namespace openvdb::points; class TestPointConversion: public CppUnit::TestCase { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestPointConversion); CPPUNIT_TEST(testPointConversion); CPPUNIT_TEST(testStride); CPPUNIT_TEST(testComputeVoxelSize); CPPUNIT_TEST_SUITE_END(); void testPointConversion(); void testStride(); void testComputeVoxelSize(); }; // class TestPointConversion CPPUNIT_TEST_SUITE_REGISTRATION(TestPointConversion); // Simple Attribute Wrapper template struct AttributeWrapper { using ValueType = T; using PosType = T; using value_type = T; struct Handle { Handle(AttributeWrapper& attribute) : mBuffer(attribute.mAttribute) , mStride(attribute.mStride) { } template void set(size_t n, openvdb::Index m, const ValueType& value) { mBuffer[n * mStride + m] = static_cast(value); } template void set(size_t n, openvdb::Index m, const openvdb::math::Vec3& value) { mBuffer[n * mStride + m] = static_cast(value); } private: std::vector& mBuffer; Index mStride; }; // struct Handle explicit AttributeWrapper(const Index stride) : mStride(stride) { } void expand() { } void compact() { } void resize(const size_t n) { mAttribute.resize(n); } size_t size() const { return mAttribute.size(); } std::vector& buffer() { return mAttribute; } template void get(ValueT& value, size_t n, openvdb::Index m = 0) const { value = mAttribute[n * mStride + m]; } template void getPos(size_t n, ValueT& value) const { this->get(value, n); } private: std::vector mAttribute; Index mStride; }; // struct AttributeWrapper struct GroupWrapper { GroupWrapper() = default; void setOffsetOn(openvdb::Index index) { mGroup[index] = short(1); } void finalize() { } void resize(const size_t n) { mGroup.resize(n, short(0)); } size_t size() const { return mGroup.size(); } std::vector& buffer() { return mGroup; } private: std::vector mGroup; }; // struct GroupWrapper struct PointData { int id; Vec3f position; Vec3i xyz; float uniform; openvdb::Name string; short group; bool operator<(const PointData& other) const { return id < other.id; } }; // PointData // Generate random points by uniformly distributing points // on a unit-sphere. inline void genPoints(const int numPoints, const double scale, const bool stride, AttributeWrapper& position, AttributeWrapper& xyz, AttributeWrapper& id, AttributeWrapper& uniform, AttributeWrapper& string, GroupWrapper& group) { // init openvdb::math::Random01 randNumber(0); const int n = int(std::sqrt(double(numPoints))); const double xScale = (2.0 * M_PI) / double(n); const double yScale = M_PI / double(n); double x, y, theta, phi; openvdb::Vec3f pos; position.resize(n*n); xyz.resize(stride ? n*n*3 : 1); id.resize(n*n); uniform.resize(n*n); string.resize(n*n); group.resize(n*n); AttributeWrapper::Handle positionHandle(position); AttributeWrapper::Handle xyzHandle(xyz); AttributeWrapper::Handle idHandle(id); AttributeWrapper::Handle uniformHandle(uniform); AttributeWrapper::Handle stringHandle(string); int i = 0; // 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) pos[0] = static_cast(std::sin(theta)*std::cos(phi)*scale); pos[1] = static_cast(std::sin(theta)*std::sin(phi)*scale); pos[2] = static_cast(std::cos(theta)*scale); positionHandle.set(i, /*stride*/0, pos); idHandle.set(i, /*stride*/0, i); uniformHandle.set(i, /*stride*/0, 100.0f); if (stride) { xyzHandle.set(i, 0, i); xyzHandle.set(i, 1, i*i); xyzHandle.set(i, 2, i*i*i); } // add points with even id to the group if ((i % 2) == 0) { group.setOffsetOn(i); stringHandle.set(i, /*stride*/0, "testA"); } else { stringHandle.set(i, /*stride*/0, "testB"); } i++; } } } //////////////////////////////////////// void TestPointConversion::testPointConversion() { // generate points const size_t count(1000000); AttributeWrapper position(1); AttributeWrapper xyz(1); AttributeWrapper id(1); AttributeWrapper uniform(1); AttributeWrapper string(1); GroupWrapper group; genPoints(count, /*scale=*/ 100.0, /*stride=*/false, position, xyz, id, uniform, string, group); CPPUNIT_ASSERT_EQUAL(position.size(), count); CPPUNIT_ASSERT_EQUAL(id.size(), count); CPPUNIT_ASSERT_EQUAL(uniform.size(), count); CPPUNIT_ASSERT_EQUAL(string.size(), count); CPPUNIT_ASSERT_EQUAL(group.size(), count); // convert point positions into a Point Data Grid const float voxelSize = 1.0f; openvdb::math::Transform::Ptr transform(openvdb::math::Transform::createLinearTransform(voxelSize)); tools::PointIndexGrid::Ptr pointIndexGrid = tools::createPointIndexGrid(position, *transform); PointDataGrid::Ptr pointDataGrid = createPointDataGrid(*pointIndexGrid, position, *transform); tools::PointIndexTree& indexTree = pointIndexGrid->tree(); PointDataTree& tree = pointDataGrid->tree(); // add id and populate appendAttribute(tree, "id"); populateAttribute>(tree, indexTree, "id", id); // add uniform and populate appendAttribute(tree, "uniform"); populateAttribute>(tree, indexTree, "uniform", uniform); // add string and populate appendAttribute(tree, "string"); // reset the descriptors PointDataTree::LeafIter leafIter = tree.beginLeaf(); const AttributeSet::Descriptor& descriptor = leafIter->attributeSet().descriptor(); auto newDescriptor = std::make_shared(descriptor); for (; leafIter; ++leafIter) { leafIter->resetDescriptor(newDescriptor); } populateAttribute>( tree, indexTree, "string", string); // add group and set membership appendGroup(tree, "test"); setGroup(tree, indexTree, group.buffer(), "test"); CPPUNIT_ASSERT_EQUAL(indexTree.leafCount(), tree.leafCount()); // read/write grid to a temp file std::string tempDir; if (const char* dir = std::getenv("TMPDIR")) tempDir = dir; #ifdef _MSC_VER if (tempDir.empty()) { char tempDirBuffer[MAX_PATH+1]; int tempDirLen = GetTempPath(MAX_PATH+1, tempDirBuffer); CPPUNIT_ASSERT(tempDirLen > 0 && tempDirLen <= MAX_PATH); tempDir = tempDirBuffer; } #else if (tempDir.empty()) tempDir = P_tmpdir; #endif std::string filename = tempDir + "/openvdb_test_point_conversion"; io::File fileOut(filename); GridCPtrVec grids; grids.push_back(pointDataGrid); fileOut.write(grids); fileOut.close(); io::File fileIn(filename); fileIn.open(); GridPtrVecPtr readGrids = fileIn.getGrids(); fileIn.close(); CPPUNIT_ASSERT_EQUAL(readGrids->size(), size_t(1)); pointDataGrid = GridBase::grid((*readGrids)[0]); PointDataTree& inputTree = pointDataGrid->tree(); // create accessor and iterator for Point Data Tree PointDataTree::LeafCIter leafCIter = inputTree.cbeginLeaf(); CPPUNIT_ASSERT_EQUAL(5, int(leafCIter->attributeSet().size())); CPPUNIT_ASSERT(leafCIter->attributeSet().find("id") != AttributeSet::INVALID_POS); CPPUNIT_ASSERT(leafCIter->attributeSet().find("uniform") != AttributeSet::INVALID_POS); CPPUNIT_ASSERT(leafCIter->attributeSet().find("P") != AttributeSet::INVALID_POS); CPPUNIT_ASSERT(leafCIter->attributeSet().find("string") != AttributeSet::INVALID_POS); const auto idIndex = static_cast(leafCIter->attributeSet().find("id")); const auto uniformIndex = static_cast(leafCIter->attributeSet().find("uniform")); const auto stringIndex = static_cast(leafCIter->attributeSet().find("string")); const AttributeSet::Descriptor::GroupIndex groupIndex = leafCIter->attributeSet().groupIndex("test"); // convert back into linear point attribute data AttributeWrapper outputPosition(1); AttributeWrapper outputId(1); AttributeWrapper outputUniform(1); AttributeWrapper outputString(1); GroupWrapper outputGroup; // test offset the whole point block by an arbitrary amount Index64 startOffset = 10; outputPosition.resize(startOffset + position.size()); outputId.resize(startOffset + id.size()); outputUniform.resize(startOffset + uniform.size()); outputString.resize(startOffset + string.size()); outputGroup.resize(startOffset + group.size()); std::vector includeGroups; std::vector excludeGroups; std::vector pointOffsets; getPointOffsets(pointOffsets, inputTree, includeGroups, excludeGroups); convertPointDataGridPosition(outputPosition, *pointDataGrid, pointOffsets, startOffset, includeGroups, excludeGroups); convertPointDataGridAttribute(outputId, inputTree, pointOffsets, startOffset, idIndex, 1, includeGroups, excludeGroups); convertPointDataGridAttribute(outputUniform, inputTree, pointOffsets, startOffset, uniformIndex, 1, includeGroups, excludeGroups); convertPointDataGridAttribute(outputString, inputTree, pointOffsets, startOffset, stringIndex, 1, includeGroups, excludeGroups); convertPointDataGridGroup(outputGroup, inputTree, pointOffsets, startOffset, groupIndex, includeGroups, excludeGroups); // pack and sort the new buffers based on id std::vector pointData(count); for (unsigned int i = 0; i < count; i++) { pointData[i].id = outputId.buffer()[startOffset + i]; pointData[i].position = outputPosition.buffer()[startOffset + i]; pointData[i].uniform = outputUniform.buffer()[startOffset + i]; pointData[i].string = outputString.buffer()[startOffset + i]; pointData[i].group = outputGroup.buffer()[startOffset + i]; } std::sort(pointData.begin(), pointData.end()); // compare old and new buffers for (unsigned int i = 0; i < count; i++) { CPPUNIT_ASSERT_EQUAL(id.buffer()[i], pointData[i].id); CPPUNIT_ASSERT_EQUAL(group.buffer()[i], pointData[i].group); CPPUNIT_ASSERT_EQUAL(uniform.buffer()[i], pointData[i].uniform); CPPUNIT_ASSERT_EQUAL(string.buffer()[i], pointData[i].string); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i].x(), pointData[i].position.x(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i].y(), pointData[i].position.y(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i].z(), pointData[i].position.z(), /*tolerance=*/1e-6); } // convert based on even group const size_t halfCount = count / 2; outputPosition.resize(startOffset + halfCount); outputId.resize(startOffset + halfCount); outputUniform.resize(startOffset + halfCount); outputString.resize(startOffset + halfCount); outputGroup.resize(startOffset + halfCount); includeGroups.push_back("test"); pointOffsets.clear(); getPointOffsets(pointOffsets, inputTree, includeGroups, excludeGroups); convertPointDataGridPosition(outputPosition, *pointDataGrid, pointOffsets, startOffset, includeGroups, excludeGroups); convertPointDataGridAttribute(outputId, inputTree, pointOffsets, startOffset, idIndex, /*stride*/1, includeGroups, excludeGroups); convertPointDataGridAttribute(outputUniform, inputTree, pointOffsets, startOffset, uniformIndex, /*stride*/1, includeGroups, excludeGroups); convertPointDataGridAttribute(outputString, inputTree, pointOffsets, startOffset, stringIndex, /*stride*/1, includeGroups, excludeGroups); convertPointDataGridGroup(outputGroup, inputTree, pointOffsets, startOffset, groupIndex, includeGroups, excludeGroups); CPPUNIT_ASSERT_EQUAL(size_t(outputPosition.size() - startOffset), size_t(halfCount)); CPPUNIT_ASSERT_EQUAL(size_t(outputId.size() - startOffset), size_t(halfCount)); CPPUNIT_ASSERT_EQUAL(size_t(outputUniform.size() - startOffset), size_t(halfCount)); CPPUNIT_ASSERT_EQUAL(size_t(outputString.size() - startOffset), size_t(halfCount)); CPPUNIT_ASSERT_EQUAL(size_t(outputGroup.size() - startOffset), size_t(halfCount)); pointData.clear(); for (unsigned int i = 0; i < halfCount; i++) { PointData data; data.id = outputId.buffer()[startOffset + i]; data.position = outputPosition.buffer()[startOffset + i]; data.uniform = outputUniform.buffer()[startOffset + i]; data.string = outputString.buffer()[startOffset + i]; data.group = outputGroup.buffer()[startOffset + i]; pointData.push_back(data); } std::sort(pointData.begin(), pointData.end()); // compare old and new buffers for (unsigned int i = 0; i < halfCount; i++) { CPPUNIT_ASSERT_EQUAL(id.buffer()[i*2], pointData[i].id); CPPUNIT_ASSERT_EQUAL(group.buffer()[i*2], pointData[i].group); CPPUNIT_ASSERT_EQUAL(uniform.buffer()[i*2], pointData[i].uniform); CPPUNIT_ASSERT_EQUAL(string.buffer()[i*2], pointData[i].string); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i*2].x(), pointData[i].position.x(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i*2].y(), pointData[i].position.y(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i*2].z(), pointData[i].position.z(), /*tolerance=*/1e-6); } std::remove(filename.c_str()); } //////////////////////////////////////// void TestPointConversion::testStride() { // generate points const size_t count(40000); AttributeWrapper position(1); AttributeWrapper xyz(3); AttributeWrapper id(1); AttributeWrapper uniform(1); AttributeWrapper string(1); GroupWrapper group; genPoints(count, /*scale=*/ 100.0, /*stride=*/true, position, xyz, id, uniform, string, group); CPPUNIT_ASSERT_EQUAL(position.size(), count); CPPUNIT_ASSERT_EQUAL(xyz.size(), count*3); CPPUNIT_ASSERT_EQUAL(id.size(), count); // convert point positions into a Point Data Grid const float voxelSize = 1.0f; openvdb::math::Transform::Ptr transform(openvdb::math::Transform::createLinearTransform(voxelSize)); tools::PointIndexGrid::Ptr pointIndexGrid = tools::createPointIndexGrid(position, *transform); PointDataGrid::Ptr pointDataGrid = createPointDataGrid(*pointIndexGrid, position, *transform); tools::PointIndexTree& indexTree = pointIndexGrid->tree(); PointDataTree& tree = pointDataGrid->tree(); // add id and populate appendAttribute(tree, "id"); populateAttribute>(tree, indexTree, "id", id); // add xyz and populate appendAttribute(tree, "xyz", 0, /*stride=*/3); populateAttribute>(tree, indexTree, "xyz", xyz, /*stride=*/3); // create accessor and iterator for Point Data Tree PointDataTree::LeafCIter leafCIter = tree.cbeginLeaf(); CPPUNIT_ASSERT_EQUAL(3, int(leafCIter->attributeSet().size())); CPPUNIT_ASSERT(leafCIter->attributeSet().find("id") != AttributeSet::INVALID_POS); CPPUNIT_ASSERT(leafCIter->attributeSet().find("P") != AttributeSet::INVALID_POS); CPPUNIT_ASSERT(leafCIter->attributeSet().find("xyz") != AttributeSet::INVALID_POS); const auto idIndex = static_cast(leafCIter->attributeSet().find("id")); const auto xyzIndex = static_cast(leafCIter->attributeSet().find("xyz")); // convert back into linear point attribute data AttributeWrapper outputPosition(1); AttributeWrapper outputXyz(3); AttributeWrapper outputId(1); // test offset the whole point block by an arbitrary amount Index64 startOffset = 10; outputPosition.resize(startOffset + position.size()); outputXyz.resize((startOffset + id.size())*3); outputId.resize(startOffset + id.size()); std::vector pointOffsets; getPointOffsets(pointOffsets, tree); convertPointDataGridPosition(outputPosition, *pointDataGrid, pointOffsets, startOffset); convertPointDataGridAttribute(outputId, tree, pointOffsets, startOffset, idIndex); convertPointDataGridAttribute(outputXyz, tree, pointOffsets, startOffset, xyzIndex, /*stride=*/3); // pack and sort the new buffers based on id std::vector pointData; pointData.resize(count); for (unsigned int i = 0; i < count; i++) { pointData[i].id = outputId.buffer()[startOffset + i]; pointData[i].position = outputPosition.buffer()[startOffset + i]; for (unsigned int j = 0; j < 3; j++) { pointData[i].xyz[j] = outputXyz.buffer()[startOffset * 3 + i * 3 + j]; } } std::sort(pointData.begin(), pointData.end()); // compare old and new buffers for (unsigned int i = 0; i < count; i++) { CPPUNIT_ASSERT_EQUAL(id.buffer()[i], pointData[i].id); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i].x(), pointData[i].position.x(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i].y(), pointData[i].position.y(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_DOUBLES_EQUAL(position.buffer()[i].z(), pointData[i].position.z(), /*tolerance=*/1e-6); CPPUNIT_ASSERT_EQUAL(Vec3i(xyz.buffer()[i*3], xyz.buffer()[i*3+1], xyz.buffer()[i*3+2]), pointData[i].xyz); } } //////////////////////////////////////// void TestPointConversion::testComputeVoxelSize() { struct Local { static PointDataGrid::Ptr genPointsGrid(const float voxelSize, const AttributeWrapper& positions) { math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); tools::PointIndexGrid::Ptr pointIndexGrid = tools::createPointIndexGrid(positions, *transform); return createPointDataGrid(*pointIndexGrid, positions, *transform); } }; // minimum and maximum voxel sizes const auto minimumVoxelSize = static_cast(math::Pow(double(3e-15), 1.0/3.0)); const auto maximumVoxelSize = static_cast(math::Pow(double(std::numeric_limits::max()), 1.0/3.0)); AttributeWrapper position(/*stride*/1); AttributeWrapper positionD(/*stride*/1); // test with no positions { const float voxelSize = computeVoxelSize(position, /*points per voxel*/8); CPPUNIT_ASSERT_EQUAL(voxelSize, 0.1f); } // test with one point { position.resize(1); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(0.0f)); const float voxelSize = computeVoxelSize(position, /*points per voxel*/8); CPPUNIT_ASSERT_EQUAL(voxelSize, 0.1f); } // test with n points, where n > 1 && n <= num points per voxel { position.resize(7); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(-8.6f, 0.0f,-23.8f)); positionHandle.set(1, 0, Vec3f( 8.6f, 7.8f, 23.8f)); for (size_t i = 2; i < 7; ++ i) positionHandle.set(i, 0, Vec3f(0.0f)); float voxelSize = computeVoxelSize(position, /*points per voxel*/8); CPPUNIT_ASSERT_DOUBLES_EQUAL(18.5528f, voxelSize, /*tolerance=*/1e-4); voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(5.51306f, voxelSize, /*tolerance=*/1e-4); // test decimal place accuracy voxelSize = computeVoxelSize(position, /*points per voxel*/1, math::Mat4d::identity(), 10); CPPUNIT_ASSERT_DOUBLES_EQUAL(5.5130610466f, voxelSize, /*tolerance=*/1e-9); voxelSize = computeVoxelSize(position, /*points per voxel*/1, math::Mat4d::identity(), 1); CPPUNIT_ASSERT_EQUAL(5.5f, voxelSize); voxelSize = computeVoxelSize(position, /*points per voxel*/1, math::Mat4d::identity(), 0); CPPUNIT_ASSERT_EQUAL(6.0f, voxelSize); } // test coplanar points (Y=0) { position.resize(5); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(0.0f, 0.0f, 10.0f)); positionHandle.set(1, 0, Vec3f(0.0f, 0.0f, -10.0f)); positionHandle.set(2, 0, Vec3f(20.0f, 0.0f, -10.0f)); positionHandle.set(3, 0, Vec3f(20.0f, 0.0f, 10.0f)); positionHandle.set(4, 0, Vec3f(10.0f, 0.0f, 0.0f)); float voxelSize = computeVoxelSize(position, /*points per voxel*/5); CPPUNIT_ASSERT_DOUBLES_EQUAL(20.0f, voxelSize, /*tolerance=*/1e-4); voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(11.696f, voxelSize, /*tolerance=*/1e-4); } // test collinear points (X=0, Y=0) { position.resize(5); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(0.0f, 0.0f, 10.0f)); positionHandle.set(1, 0, Vec3f(0.0f, 0.0f, -10.0f)); positionHandle.set(2, 0, Vec3f(0.0f, 0.0f, -10.0f)); positionHandle.set(3, 0, Vec3f(0.0f, 0.0f, 10.0f)); positionHandle.set(4, 0, Vec3f(0.0f, 0.0f, 0.0f)); float voxelSize = computeVoxelSize(position, /*points per voxel*/5); CPPUNIT_ASSERT_DOUBLES_EQUAL(20.0f, voxelSize, /*tolerance=*/1e-4); voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(8.32034f, voxelSize, /*tolerance=*/1e-4); } // test min limit collinear points (X=0, Y=0, Z=+/-float min) { position.resize(2); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(0.0f, 0.0f, -std::numeric_limits::min())); positionHandle.set(1, 0, Vec3f(0.0f, 0.0f, std::numeric_limits::min())); float voxelSize = computeVoxelSize(position, /*points per voxel*/2); CPPUNIT_ASSERT_DOUBLES_EQUAL(minimumVoxelSize, voxelSize, /*tolerance=*/1e-4); voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(minimumVoxelSize, voxelSize, /*tolerance=*/1e-4); } // test max limit collinear points (X=+/-float max, Y=0, Z=0) { position.resize(2); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(-std::numeric_limits::max(), 0.0f, 0.0f)); positionHandle.set(1, 0, Vec3f(std::numeric_limits::max(), 0.0f, 0.0f)); float voxelSize = computeVoxelSize(position, /*points per voxel*/2); CPPUNIT_ASSERT_DOUBLES_EQUAL(maximumVoxelSize, voxelSize, /*tolerance=*/1e-4); voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(maximumVoxelSize, voxelSize, /*tolerance=*/1e-4); } // max pointsPerVoxel { position.resize(2); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(0)); positionHandle.set(1, 0, Vec3f(1)); float voxelSize = computeVoxelSize(position, /*points per voxel*/std::numeric_limits::max()); CPPUNIT_ASSERT_EQUAL(voxelSize, 1.0f); } // limits test { positionD.resize(2); AttributeWrapper::Handle positionHandleD(positionD); positionHandleD.set(0, 0, Vec3d(0)); positionHandleD.set(1, 0, Vec3d(std::numeric_limits::max())); float voxelSize = computeVoxelSize(positionD, /*points per voxel*/2); CPPUNIT_ASSERT_EQUAL(voxelSize, maximumVoxelSize); } { const float smallest(std::numeric_limits::min()); position.resize(4); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(0.0f)); positionHandle.set(1, 0, Vec3f(smallest)); positionHandle.set(2, 0, Vec3f(smallest, 0.0f, 0.0f)); positionHandle.set(3, 0, Vec3f(smallest, 0.0f, smallest)); float voxelSize = computeVoxelSize(position, /*points per voxel*/4); CPPUNIT_ASSERT_EQUAL(voxelSize, minimumVoxelSize); voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(minimumVoxelSize, voxelSize, /*tolerance=*/1e-4); PointDataGrid::Ptr grid = Local::genPointsGrid(voxelSize, position); CPPUNIT_ASSERT_EQUAL(grid->activeVoxelCount(), Index64(1)); } // the smallest possible vector extent that can exist from an input set // without being clamped to the minimum voxel size // is Tolerance::value() + std::numeric_limits::min() { position.resize(2); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(0.0f)); positionHandle.set(1, 0, Vec3f(math::Tolerance::value() + std::numeric_limits::min())); float voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_EQUAL(voxelSize, minimumVoxelSize); } // in-between smallest extent and ScaleMap determinant test { position.resize(2); AttributeWrapper::Handle positionHandle(position); positionHandle.set(0, 0, Vec3f(0.0f)); positionHandle.set(1, 0, Vec3f(math::Tolerance::value()*1e8 + std::numeric_limits::min())); float voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_EQUAL(voxelSize, float(math::Pow(double(3e-15), 1.0/3.0))); } { const float smallValue(1e-5f); position.resize(300000); AttributeWrapper::Handle positionHandle(position); for (size_t i = 0; i < 100000; ++ i) { positionHandle.set(i, 0, Vec3f(smallValue*float(i), 0, 0)); positionHandle.set(i+100000, 0, Vec3f(0, smallValue*float(i), 0)); positionHandle.set(i+200000, 0, Vec3f(0, 0, smallValue*float(i))); } float voxelSize = computeVoxelSize(position, /*points per voxel*/10); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.00012f, voxelSize, /*tolerance=*/1e-4); voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(2e-5, voxelSize, /*tolerance=*/1e-6); PointDataGrid::Ptr grid = Local::genPointsGrid(voxelSize, position); CPPUNIT_ASSERT_EQUAL(grid->activeVoxelCount(), Index64(150001)); // check zero decimal place still returns valid result voxelSize = computeVoxelSize(position, /*points per voxel*/1, math::Mat4d::identity(), 0); CPPUNIT_ASSERT_DOUBLES_EQUAL(2e-5, voxelSize, /*tolerance=*/1e-6); } // random position generation within two bounds of equal size. // This test distributes 1000 points within a 1x1x1 box centered at (0,0,0) // and another 1000 points within a separate 1x1x1 box centered at (20,20,20). // Points are randomly positioned however can be defined as having a stochastic // distribution. Tests that sparsity between these data sets causes no issues // and that computeVoxelSize produces accurate results { position.resize(2000); AttributeWrapper::Handle positionHandle(position); openvdb::math::Random01 randNumber(0); // positions between -0.5 and 0.5 for (size_t i = 0; i < 1000; ++ i) { const Vec3f pos(randNumber() - 0.5f); positionHandle.set(i, 0, pos); } // positions between 19.5 and 20.5 for (size_t i = 1000; i < 2000; ++ i) { const Vec3f pos(randNumber() - 0.5f + 20.0f); positionHandle.set(i, 0, pos); } float voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.00052f, voxelSize, /*tolerance=*/1e-4); PointDataGrid::Ptr grid = Local::genPointsGrid(voxelSize, position); const auto pointsPerVoxel = static_cast( math::Round(2000.0f / static_cast(grid->activeVoxelCount()))); CPPUNIT_ASSERT_EQUAL(pointsPerVoxel, Index64(1)); } // random position generation within three bounds of varying size. // This test distributes 1000 points within a 1x1x1 box centered at (0.5,0.5,0,5) // another 1000 points within a separate 10x10x10 box centered at (15,15,15) and // a final 1000 points within a separate 50x50x50 box centered at (75,75,75) // Points are randomly positioned however can be defined as having a stochastic // distribution. Tests that sparsity between these data sets causes no issues as // well as computeVoxelSize producing a good average result { position.resize(3000); AttributeWrapper::Handle positionHandle(position); openvdb::math::Random01 randNumber(0); // positions between 0 and 1 for (size_t i = 0; i < 1000; ++ i) { const Vec3f pos(randNumber()); positionHandle.set(i, 0, pos); } // positions between 10 and 20 for (size_t i = 1000; i < 2000; ++ i) { const Vec3f pos((randNumber() * 10.0f) + 10.0f); positionHandle.set(i, 0, pos); } // positions between 50 and 100 for (size_t i = 2000; i < 3000; ++ i) { const Vec3f pos((randNumber() * 50.0f) + 50.0f); positionHandle.set(i, 0, pos); } float voxelSize = computeVoxelSize(position, /*points per voxel*/10); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.24758f, voxelSize, /*tolerance=*/1e-3); PointDataGrid::Ptr grid = Local::genPointsGrid(voxelSize, position); auto pointsPerVoxel = static_cast( math::Round(3000.0f/ static_cast(grid->activeVoxelCount()))); CPPUNIT_ASSERT(math::isApproxEqual(pointsPerVoxel, Index64(10), Index64(2))); voxelSize = computeVoxelSize(position, /*points per voxel*/1); CPPUNIT_ASSERT_DOUBLES_EQUAL(0.00231f, voxelSize, /*tolerance=*/1e-4); grid = Local::genPointsGrid(voxelSize, position); pointsPerVoxel = static_cast( math::Round(3000.0f/ static_cast(grid->activeVoxelCount()))); CPPUNIT_ASSERT_EQUAL(pointsPerVoxel, Index64(1)); } // Generate a sphere // NOTE: The sphere does NOT provide uniform distribution const size_t count(40000); position.resize(0); AttributeWrapper xyz(1); AttributeWrapper id(1); AttributeWrapper uniform(1); AttributeWrapper string(1); GroupWrapper group; genPoints(count, /*scale=*/ 100.0, /*stride=*/false, position, xyz, id, uniform, string, group); CPPUNIT_ASSERT_EQUAL(position.size(), count); CPPUNIT_ASSERT_EQUAL(id.size(), count); CPPUNIT_ASSERT_EQUAL(uniform.size(), count); CPPUNIT_ASSERT_EQUAL(string.size(), count); CPPUNIT_ASSERT_EQUAL(group.size(), count); // test a distributed point set around a sphere { const float voxelSize = computeVoxelSize(position, /*points per voxel*/2); CPPUNIT_ASSERT_DOUBLES_EQUAL(2.6275f, voxelSize, /*tolerance=*/0.01); PointDataGrid::Ptr grid = Local::genPointsGrid(voxelSize, position); const Index64 pointsPerVoxel = count / grid->activeVoxelCount(); CPPUNIT_ASSERT_EQUAL(pointsPerVoxel, Index64(2)); } // test with given target transforms { // test that a different scale doesn't change the result openvdb::math::Transform::Ptr transform1(openvdb::math::Transform::createLinearTransform(0.33)); openvdb::math::Transform::Ptr transform2(openvdb::math::Transform::createLinearTransform(0.87)); math::UniformScaleMap::ConstPtr scaleMap1 = transform1->constMap(); math::UniformScaleMap::ConstPtr scaleMap2 = transform2->constMap(); CPPUNIT_ASSERT(scaleMap1.get()); CPPUNIT_ASSERT(scaleMap2.get()); math::AffineMap::ConstPtr affineMap1 = scaleMap1->getAffineMap(); math::AffineMap::ConstPtr affineMap2 = scaleMap2->getAffineMap(); float voxelSize1 = computeVoxelSize(position, /*points per voxel*/2, affineMap1->getMat4()); float voxelSize2 = computeVoxelSize(position, /*points per voxel*/2, affineMap2->getMat4()); CPPUNIT_ASSERT_EQUAL(voxelSize1, voxelSize2); // test that applying a rotation roughly calculates to the same result for this example // NOTE: distribution is not uniform // Rotate by 45 degrees in X, Y, Z transform1->postRotate(M_PI / 4.0, math::X_AXIS); transform1->postRotate(M_PI / 4.0, math::Y_AXIS); transform1->postRotate(M_PI / 4.0, math::Z_AXIS); affineMap1 = transform1->constMap(); CPPUNIT_ASSERT(affineMap1.get()); float voxelSize3 = computeVoxelSize(position, /*points per voxel*/2, affineMap1->getMat4()); CPPUNIT_ASSERT_DOUBLES_EQUAL(voxelSize1, voxelSize3, 0.1); // test that applying a translation roughly calculates to the same result for this example transform1->postTranslate(Vec3d(-5.0f, 3.3f, 20.1f)); affineMap1 = transform1->constMap(); CPPUNIT_ASSERT(affineMap1.get()); float voxelSize4 = computeVoxelSize(position, /*points per voxel*/2, affineMap1->getMat4()); CPPUNIT_ASSERT_DOUBLES_EQUAL(voxelSize1, voxelSize4, 0.1); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000004476613200122377017450 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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. #include #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.f; 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.f; } } } 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 CPPUNIT_ASSERT(tree.empty()); // Partially fill a region with inactive background values. tree.fill(CoordBBox(Coord(27), Coord(254)), background, /*active=*/false); // Confirm that after pruning, the tree is empty. openvdb::tools::prune(tree); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(0), tree.leafCount()); CPPUNIT_ASSERT_EQUAL(openvdb::Index32(1), tree.nonLeafCount()); // root node CPPUNIT_ASSERT(tree.empty()); } // 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-2017 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/TestClip.cc0000644000000000000000000002224013200122377015123 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for math::NonlinearFrustumMap #include // See also TestGrid::testClipping() class TestClip: public CppUnit::TestFixture { public: static const openvdb::CoordBBox kCubeBBox, kInnerBBox; TestClip(): mCube{ []() { auto cube = openvdb::FloatGrid{0.0f}; cube.fill(kCubeBBox, /*value=*/5.0f, /*active=*/true); return cube; }()} {} void setUp() override; void tearDown() override; CPPUNIT_TEST_SUITE(TestClip); CPPUNIT_TEST(testBBox); CPPUNIT_TEST(testFrustum); CPPUNIT_TEST(testMaskGrid); CPPUNIT_TEST(testBoolMask); CPPUNIT_TEST(testInvertedBoolMask); CPPUNIT_TEST(testNonBoolMask); CPPUNIT_TEST(testInvertedNonBoolMask); CPPUNIT_TEST_SUITE_END(); void testBBox(); void testFrustum(); void testMaskGrid(); void testBoolMask(); void testInvertedBoolMask(); void testNonBoolMask(); void testInvertedNonBoolMask(); private: void validate(const openvdb::FloatGrid&); const openvdb::FloatGrid mCube; }; const openvdb::CoordBBox // The volume to be clipped is a 21 x 21 x 21 solid cube. TestClip::kCubeBBox{openvdb::Coord{-10}, openvdb::Coord{10}}, // The clipping mask is a 1 x 1 x 13 segment extending along the Z axis inside the cube. TestClip::kInnerBBox{openvdb::Coord{4, 4, -6}, openvdb::Coord{4, 4, 6}}; CPPUNIT_TEST_SUITE_REGISTRATION(TestClip); //////////////////////////////////////// void TestClip::setUp() { openvdb::initialize(); } void TestClip::tearDown() { openvdb::uninitialize(); } void TestClip::validate(const openvdb::FloatGrid& clipped) { using namespace openvdb; const CoordBBox bbox = clipped.evalActiveVoxelBoundingBox(); CPPUNIT_ASSERT_EQUAL(kInnerBBox.min().x(), bbox.min().x()); CPPUNIT_ASSERT_EQUAL(kInnerBBox.min().y(), bbox.min().y()); CPPUNIT_ASSERT_EQUAL(kInnerBBox.min().z(), bbox.min().z()); CPPUNIT_ASSERT_EQUAL(kInnerBBox.max().x(), bbox.max().x()); CPPUNIT_ASSERT_EQUAL(kInnerBBox.max().y(), bbox.max().y()); CPPUNIT_ASSERT_EQUAL(kInnerBBox.max().z(), bbox.max().z()); CPPUNIT_ASSERT_EQUAL(6 + 6 + 1, int(clipped.activeVoxelCount())); CPPUNIT_ASSERT_EQUAL(2, int(clipped.constTree().leafCount())); FloatGrid::ConstAccessor acc = clipped.getConstAccessor(); const float bg = clipped.background(); Coord xyz; int &x = xyz[0], &y = xyz[1], &z = xyz[2]; for (x = kCubeBBox.min().x(); x <= kCubeBBox.max().x(); ++x) { for (y = kCubeBBox.min().y(); y <= kCubeBBox.max().y(); ++y) { for (z = kCubeBBox.min().z(); z <= kCubeBBox.max().z(); ++z) { if (x == 4 && y == 4 && z >= -6 && z <= 6) { CPPUNIT_ASSERT_EQUAL(5.f, acc.getValue(Coord(4, 4, z))); } else { CPPUNIT_ASSERT_EQUAL(bg, acc.getValue(Coord(x, y, z))); } } } } } //////////////////////////////////////// // Test clipping against a bounding box. void TestClip::testBBox() { using namespace openvdb; BBoxd clipBox(Vec3d(4.0, 4.0, -6.0), Vec3d(4.9, 4.9, 6.0)); FloatGrid::Ptr clipped = tools::clip(mCube, clipBox); validate(*clipped); } // Test clipping against a camera frustum. void TestClip::testFrustum() { using namespace openvdb; const auto d = double(kCubeBBox.max().z()); const math::NonlinearFrustumMap frustum{ /*position=*/Vec3d{0.0, 0.0, 5.0 * d}, /*direction=*/Vec3d{0.0, 0.0, -1.0}, /*up=*/Vec3d{0.0, d / 2.0, 0.0}, /*aspect=*/1.0, /*near=*/4.0 * d + 1.0, /*depth=*/kCubeBBox.dim().z() - 2.0, /*x_count=*/100, /*z_count=*/100}; const auto frustumIndexBBox = frustum.getBBox(); { auto clipped = tools::clip(mCube, frustum); const auto bbox = clipped->evalActiveVoxelBoundingBox(); const auto cubeDim = kCubeBBox.dim(); CPPUNIT_ASSERT_EQUAL(kCubeBBox.min().z() + 1, bbox.min().z()); CPPUNIT_ASSERT_EQUAL(kCubeBBox.max().z() - 1, bbox.max().z()); CPPUNIT_ASSERT(int(bbox.volume()) < int(cubeDim.x() * cubeDim.y() * (cubeDim.z() - 2))); // Note: mCube index space corresponds to world space. for (auto it = clipped->beginValueOn(); it; ++it) { const auto xyz = frustum.applyInverseMap(it.getCoord().asVec3d()); CPPUNIT_ASSERT(frustumIndexBBox.isInside(xyz)); } } { auto tile = openvdb::FloatGrid{0.0f}; tile.tree().addTile(/*level=*/2, Coord{0}, /*value=*/5.0f, /*active=*/true); auto clipped = tools::clip(tile, frustum); CPPUNIT_ASSERT(!clipped->empty()); for (auto it = clipped->beginValueOn(); it; ++it) { const auto xyz = frustum.applyInverseMap(it.getCoord().asVec3d()); CPPUNIT_ASSERT(frustumIndexBBox.isInside(xyz)); } clipped = tools::clip(tile, frustum, /*keepInterior=*/false); CPPUNIT_ASSERT(!clipped->empty()); for (auto it = clipped->beginValueOn(); it; ++it) { const auto xyz = frustum.applyInverseMap(it.getCoord().asVec3d()); CPPUNIT_ASSERT(!frustumIndexBBox.isInside(xyz)); } } } // Test clipping against a MaskGrid. void TestClip::testMaskGrid() { using namespace openvdb; MaskGrid mask(false); mask.fill(kInnerBBox, true, true); FloatGrid::Ptr clipped = tools::clip(mCube, mask); validate(*clipped); } // Test clipping against a boolean mask grid. void TestClip::testBoolMask() { using namespace openvdb; BoolGrid mask(false); mask.fill(kInnerBBox, true, true); FloatGrid::Ptr clipped = tools::clip(mCube, mask); validate(*clipped); } // Test clipping against a boolean mask grid with mask inversion. void TestClip::testInvertedBoolMask() { using namespace openvdb; // Construct a mask grid that is the "inverse" of the mask used in the other tests. // (This is not a true inverse, since the mask's active voxel bounds are finite.) BoolGrid mask(false); mask.fill(kCubeBBox, true, true); mask.fill(kInnerBBox, false, false); // Clipping against the "inverted" mask with mask inversion enabled // should give the same results as clipping normally against the normal mask. FloatGrid::Ptr clipped = tools::clip(mCube, mask, /*keepInterior=*/false); clipped->pruneGrid(); validate(*clipped); } // Test clipping against a non-boolean mask grid. void TestClip::testNonBoolMask() { using namespace openvdb; Int32Grid mask(0); mask.fill(kInnerBBox, -5, true); FloatGrid::Ptr clipped = tools::clip(mCube, mask); validate(*clipped); } // Test clipping against a non-boolean mask grid with mask inversion. void TestClip::testInvertedNonBoolMask() { using namespace openvdb; // Construct a mask grid that is the "inverse" of the mask used in the other tests. // (This is not a true inverse, since the mask's active voxel bounds are finite.) Grid mask(0); auto paddedCubeBBox = kCubeBBox; paddedCubeBBox.expand(2); mask.fill(paddedCubeBBox, 99, true); mask.fill(kInnerBBox, 0, false); // Clipping against the "inverted" mask with mask inversion enabled // should give the same results as clipping normally against the normal mask. FloatGrid::Ptr clipped = tools::clip(mCube, mask, /*keepInterior=*/false); clipped->pruneGrid(); validate(*clipped); } // Copyright (c) 2012-2017 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/TestParticleAtlas.cc0000644000000000000000000001360213200122377016766 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "util.h" // for genPoints struct TestParticleAtlas: public CppUnit::TestCase { CPPUNIT_TEST_SUITE(TestParticleAtlas); CPPUNIT_TEST(testParticleAtlas); CPPUNIT_TEST_SUITE_END(); void testParticleAtlas(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestParticleAtlas); //////////////////////////////////////// namespace { class ParticleList { public: typedef openvdb::Vec3R PosType; typedef PosType::value_type ScalarType; ParticleList(const std::vector& points, const std::vector& radius) : mPoints(&points) , mRadius(&radius) { } // Return the number of points in the array size_t size() const { return mPoints->size(); } // Return the world-space position for the nth particle. void getPos(size_t n, PosType& xyz) const { xyz = (*mPoints)[n]; } // Return the world-space radius for the nth particle. void getRadius(size_t n, ScalarType& radius) const { radius = (*mRadius)[n]; } protected: std::vector const * const mPoints; std::vector const * const mRadius; }; // ParticleList template bool hasDuplicates(const std::vector& items) { std::vector vec(items); std::sort(vec.begin(), vec.end()); size_t duplicates = 0; for (size_t n = 1, N = vec.size(); n < N; ++n) { if (vec[n] == vec[n-1]) ++duplicates; } return duplicates != 0; } } // namespace //////////////////////////////////////// void TestParticleAtlas::testParticleAtlas() { // generate points const size_t numParticle = 40000; const double minVoxelSize = 0.01; std::vector points; unittest_util::genPoints(numParticle, points); std::vector radius; for (size_t n = 0, N = points.size() / 2; n < N; ++n) { radius.push_back(minVoxelSize); } for (size_t n = points.size() / 2, N = points.size(); n < N; ++n) { radius.push_back(minVoxelSize * 2.0); } ParticleList particles(points, radius); // construct data structure typedef openvdb::tools::ParticleAtlas<> ParticleAtlas; ParticleAtlas atlas; CPPUNIT_ASSERT(atlas.empty()); CPPUNIT_ASSERT(atlas.levels() == 0); atlas.construct(particles, minVoxelSize); CPPUNIT_ASSERT(!atlas.empty()); CPPUNIT_ASSERT(atlas.levels() == 2); CPPUNIT_ASSERT( openvdb::math::isApproxEqual(atlas.minRadius(0), minVoxelSize)); CPPUNIT_ASSERT( openvdb::math::isApproxEqual(atlas.minRadius(1), minVoxelSize * 2.0)); typedef openvdb::tools::ParticleAtlas<>::Iterator ParticleAtlasIterator; ParticleAtlasIterator it(atlas); CPPUNIT_ASSERT(atlas.levels() == 2); std::vector indices; indices.reserve(numParticle); it.updateFromLevel(0); CPPUNIT_ASSERT(it); CPPUNIT_ASSERT_EQUAL(it.size(), numParticle - (points.size() / 2)); for (; it; ++it) { indices.push_back(*it); } it.updateFromLevel(1); CPPUNIT_ASSERT(it); CPPUNIT_ASSERT_EQUAL(it.size(), (points.size() / 2)); for (; it; ++it) { indices.push_back(*it); } CPPUNIT_ASSERT_EQUAL(numParticle, indices.size()); CPPUNIT_ASSERT(!hasDuplicates(indices)); openvdb::Vec3R center = points[0]; double searchRadius = minVoxelSize * 10.0; it.worldSpaceSearchAndUpdate(center, searchRadius, particles); CPPUNIT_ASSERT(it); indices.clear(); for (; it; ++it) { indices.push_back(*it); } CPPUNIT_ASSERT_EQUAL(it.size(), indices.size()); CPPUNIT_ASSERT(!hasDuplicates(indices)); openvdb::BBoxd bbox; for (size_t n = 0, N = points.size() / 2; n < N; ++n) { bbox.expand(points[n]); } it.worldSpaceSearchAndUpdate(bbox, particles); CPPUNIT_ASSERT(it); indices.clear(); for (; it; ++it) { indices.push_back(*it); } CPPUNIT_ASSERT_EQUAL(it.size(), indices.size()); CPPUNIT_ASSERT(!hasDuplicates(indices)); } // Copyright (c) 2012-2017 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.cc0000644000000000000000000003232613200122377017341 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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] = static_cast(::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(float(d), float(d), float(d)); } /// Specialization for Vec3s grids template<> inline bool TestQuadraticInterp::relEq( const openvdb::Vec3s& v1, const openvdb::Vec3s& v2) { return v1.eq(v2, float(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.5f, 10.5f, 10.5f, constValue(1.703125) }, { 10.0f, 10.0f, 10.0f, one }, { 11.0f, 10.0f, 10.0f, two }, { 11.0f, 11.0f, 10.0f, two }, { 11.0f, 11.0f, 11.0f, three }, { 9.0f, 11.0f, 9.0f, four }, { 9.0f, 10.0f, 9.0f, four }, { 10.1f, 10.0f, 10.0f, constValue(1.01) }, { 10.8f, 10.8f, 10.8f, constValue(2.513344) }, { 10.1f, 10.8f, 10.5f, constValue(1.8577) }, { 10.8f, 10.1f, 10.5f, constValue(1.8577) }, { 10.5f, 10.1f, 10.8f, constValue(2.2927) }, { 10.5f, 10.8f, 10.1f, 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.5f, 10.5f, 10.5f, two }, { 10.0f, 10.0f, 10.0f, two }, { 10.1f, 10.0f, 10.0f, two }, { 10.8f, 10.8f, 10.8f, two }, { 10.1f, 10.8f, 10.5f, two }, { 10.8f, 10.1f, 10.5f, two }, { 10.5f, 10.1f, 10.8f, two }, { 10.5f, 10.8f, 10.1f, 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.5f, 10.5f, 10.5f, fillValue }, { 10.0f, 10.0f, 10.0f, fillValue }, { 10.1f, 10.0f, 10.0f, fillValue }, { 10.8f, 10.8f, 10.8f, fillValue }, { 10.1f, 10.8f, 10.5f, fillValue }, { 10.8f, 10.1f, 10.5f, fillValue }, { 10.5f, 10.1f, 10.8f, fillValue }, { 10.5f, 10.8f, 10.1f, 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.5f, -10.5f, -10.5f, constValue(-104.75586) }, { -10.0f, -10.0f, -10.0f, one }, { -11.0f, -10.0f, -10.0f, two }, { -11.0f, -11.0f, -10.0f, two }, { -11.0f, -11.0f, -11.0f, three }, { -9.0f, -11.0f, -9.0f, four }, { -9.0f, -10.0f, -9.0f, four }, { -10.1f, -10.0f, -10.0f, constValue(-10.28504) }, { -10.8f, -10.8f, -10.8f, constValue(-62.84878) }, { -10.1f, -10.8f, -10.5f, constValue(-65.68951) }, { -10.8f, -10.1f, -10.5f, constValue(-65.68951) }, { -10.5f, -10.1f, -10.8f, constValue(-65.40736) }, { -10.5f, -10.8f, -10.1f, constValue(-66.30510) }, }; const size_t numVals = sizeof(testVals) / sizeof(TestVal); executeTest(grid, testVals, numVals); } // Copyright (c) 2012-2017 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.cc0000644000000000000000000001560313200122377016211 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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] = static_cast(::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-2017 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/TestPointCount.cc0000644000000000000000000007711513200122377016351 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for std::remove() #include // for std::getenv() #include #include #ifdef _MSC_VER #include #endif using namespace openvdb; using namespace openvdb::points; class TestPointCount: public CppUnit::TestCase { public: void setUp() override { openvdb::initialize(); } void tearDown() override { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestPointCount); CPPUNIT_TEST(testCount); CPPUNIT_TEST(testGroup); CPPUNIT_TEST(testOffsets); CPPUNIT_TEST(testCountGrid); CPPUNIT_TEST_SUITE_END(); void testCount(); void testGroup(); void testOffsets(); void testCountGrid(); }; // class TestPointCount using LeafType = PointDataTree::LeafNodeType; using ValueType = LeafType::ValueType; struct NotZeroFilter { NotZeroFilter() = default; static bool initialized() { return true; } template void reset(const LeafT&) { } template bool valid(const IterT& iter) const { return *iter != 0; } }; void TestPointCount::testCount() { // create a tree and check there are no points PointDataGrid::Ptr grid = createGrid(); PointDataTree& tree = grid->tree(); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(0)); // add a new leaf to a tree and re-test LeafType* leafPtr = tree.touchLeaf(openvdb::Coord(0, 0, 0)); LeafType& leaf(*leafPtr); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(0)); // now manually set some offsets leaf.setOffsetOn(0, 4); leaf.setOffsetOn(1, 7); ValueVoxelCIter voxelIter = leaf.beginValueVoxel(openvdb::Coord(0, 0, 0)); IndexIter testIter(voxelIter, NullFilter()); leaf.beginIndexVoxel(openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT_EQUAL(int(*leaf.beginIndexVoxel(openvdb::Coord(0, 0, 0))), 0); CPPUNIT_ASSERT_EQUAL(int(leaf.beginIndexVoxel(openvdb::Coord(0, 0, 0)).end()), 4); CPPUNIT_ASSERT_EQUAL(int(*leaf.beginIndexVoxel(openvdb::Coord(0, 0, 1))), 4); CPPUNIT_ASSERT_EQUAL(int(leaf.beginIndexVoxel(openvdb::Coord(0, 0, 1)).end()), 7); // test filtered, index voxel iterator CPPUNIT_ASSERT_EQUAL(int(*leaf.beginIndexVoxel(openvdb::Coord(0, 0, 0), NotZeroFilter())), 1); CPPUNIT_ASSERT_EQUAL(int(leaf.beginIndexVoxel(openvdb::Coord(0, 0, 0), NotZeroFilter()).end()), 4); { LeafType::IndexVoxelIter iter = leaf.beginIndexVoxel(openvdb::Coord(0, 0, 0)); CPPUNIT_ASSERT_EQUAL(int(*iter), 0); CPPUNIT_ASSERT_EQUAL(int(iter.end()), 4); LeafType::IndexVoxelIter iter2 = leaf.beginIndexVoxel(openvdb::Coord(0, 0, 1)); CPPUNIT_ASSERT_EQUAL(int(*iter2), 4); CPPUNIT_ASSERT_EQUAL(int(iter2.end()), 7); CPPUNIT_ASSERT_EQUAL(iterCount(iter2), Index64(7 - 4)); // check pointCount ignores active/inactive state leaf.setValueOff(1); LeafType::IndexVoxelIter iter3 = leaf.beginIndexVoxel(openvdb::Coord(0, 0, 1)); CPPUNIT_ASSERT_EQUAL(iterCount(iter3), Index64(7 - 4)); leaf.setValueOn(1); } // one point per voxel for (unsigned int i = 0; i < LeafType::SIZE; i++) { leaf.setOffsetOn(i, i); } CPPUNIT_ASSERT_EQUAL(leaf.pointCount(), Index64(LeafType::SIZE - 1)); CPPUNIT_ASSERT_EQUAL(leaf.onPointCount(), Index64(LeafType::SIZE - 1)); CPPUNIT_ASSERT_EQUAL(leaf.offPointCount(), Index64(0)); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(LeafType::SIZE - 1)); CPPUNIT_ASSERT_EQUAL(activePointCount(tree), Index64(LeafType::SIZE - 1)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(tree), Index64(0)); // manually de-activate two voxels leaf.setValueOff(100); leaf.setValueOff(101); CPPUNIT_ASSERT_EQUAL(leaf.pointCount(), Index64(LeafType::SIZE - 1)); CPPUNIT_ASSERT_EQUAL(leaf.onPointCount(), Index64(LeafType::SIZE - 3)); CPPUNIT_ASSERT_EQUAL(leaf.offPointCount(), Index64(2)); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(LeafType::SIZE - 1)); CPPUNIT_ASSERT_EQUAL(activePointCount(tree), Index64(LeafType::SIZE - 3)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(tree), Index64(2)); // one point per every other voxel and de-activate empty voxels unsigned sum = 0; for (unsigned int i = 0; i < LeafType::SIZE; i++) { leaf.setOffsetOn(i, sum); if (i % 2 == 0) sum++; } leaf.updateValueMask(); CPPUNIT_ASSERT_EQUAL(leaf.pointCount(), Index64(LeafType::SIZE / 2)); CPPUNIT_ASSERT_EQUAL(leaf.onPointCount(), Index64(LeafType::SIZE / 2)); CPPUNIT_ASSERT_EQUAL(leaf.offPointCount(), Index64(0)); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(LeafType::SIZE / 2)); CPPUNIT_ASSERT_EQUAL(activePointCount(tree), Index64(LeafType::SIZE / 2)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(tree), Index64(0)); // add a new non-empty leaf and check totalPointCount is correct LeafType* leaf2Ptr = tree.touchLeaf(openvdb::Coord(0, 0, 8)); LeafType& leaf2(*leaf2Ptr); // on adding, tree now obtains ownership and is reponsible for deletion for (unsigned int i = 0; i < LeafType::SIZE; i++) { leaf2.setOffsetOn(i, i); } CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(LeafType::SIZE / 2 + LeafType::SIZE - 1)); CPPUNIT_ASSERT_EQUAL(activePointCount(tree), Index64(LeafType::SIZE / 2 + LeafType::SIZE - 1)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(tree), Index64(0)); } void TestPointCount::testGroup() { using namespace openvdb::math; using Descriptor = AttributeSet::Descriptor; // four points in the same leaf std::vector positions{{1, 1, 1}, {1, 2, 1}, {2, 1, 1}, {2, 2, 1}}; const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); // setup temp directory std::string tempDir; if (const char* dir = std::getenv("TMPDIR")) tempDir = dir; #ifdef _MSC_VER if (tempDir.empty()) { char tempDirBuffer[MAX_PATH+1]; int tempDirLen = GetTempPath(MAX_PATH+1, tempDirBuffer); CPPUNIT_ASSERT(tempDirLen > 0 && tempDirLen <= MAX_PATH); tempDir = tempDirBuffer; } #else if (tempDir.empty()) tempDir = P_tmpdir; #endif std::string filename; // check one leaf CPPUNIT_ASSERT_EQUAL(tree.leafCount(), Index32(1)); // retrieve first and last leaf attribute sets PointDataTree::LeafIter leafIter = tree.beginLeaf(); const AttributeSet& attributeSet = leafIter->attributeSet(); // ensure zero groups CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(0)); {// add an empty group appendGroup(tree, "test"); CPPUNIT_ASSERT_EQUAL(attributeSet.descriptor().groupMap().size(), size_t(1)); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(4)); CPPUNIT_ASSERT_EQUAL(activePointCount(tree), Index64(4)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(tree), Index64(0)); CPPUNIT_ASSERT_EQUAL(leafIter->pointCount(), Index64(4)); CPPUNIT_ASSERT_EQUAL(leafIter->onPointCount(), Index64(4)); CPPUNIT_ASSERT_EQUAL(leafIter->offPointCount(), Index64(0)); // no points found when filtered by the empty group CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "test"), Index64(0)); CPPUNIT_ASSERT_EQUAL(leafIter->groupPointCount("test"), Index64(0)); } { // assign two points to the group, test offsets and point counts const Descriptor::GroupIndex index = attributeSet.groupIndex("test"); CPPUNIT_ASSERT(index.first != AttributeSet::INVALID_POS); CPPUNIT_ASSERT(index.first < attributeSet.size()); AttributeArray& array = leafIter->attributeArray(index.first); CPPUNIT_ASSERT(isGroup(array)); GroupAttributeArray& groupArray = GroupAttributeArray::cast(array); groupArray.set(0, GroupType(1) << index.second); groupArray.set(3, GroupType(1) << index.second); // only two out of four points should be found when group filtered CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(leafIter->groupPointCount("test"), Index64(2)); { CPPUNIT_ASSERT_EQUAL(activeGroupPointCount(tree, "test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(inactiveGroupPointCount(tree, "test"), Index64(0)); } CPPUNIT_ASSERT_NO_THROW(leafIter->validateOffsets()); // manually modify offsets so one of the points is marked as inactive std::vector offsets, modifiedOffsets; offsets.resize(PointDataTree::LeafNodeType::SIZE); modifiedOffsets.resize(PointDataTree::LeafNodeType::SIZE); for (Index n = 0; n < PointDataTree::LeafNodeType::NUM_VALUES; n++) { const unsigned offset = leafIter->getValue(n); offsets[n] = offset; modifiedOffsets[n] = offset > 0 ? offset - 1 : offset; } leafIter->setOffsets(modifiedOffsets); // confirm that validation fails CPPUNIT_ASSERT_THROW(leafIter->validateOffsets(), openvdb::ValueError); // replace offsets with original offsets but leave value mask leafIter->setOffsets(offsets, /*updateValueMask=*/ false); // confirm that validation now succeeds CPPUNIT_ASSERT_NO_THROW(leafIter->validateOffsets()); // ensure active / inactive point counts are correct CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(leafIter->groupPointCount("test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(activeGroupPointCount(tree, "test"), Index64(1)); CPPUNIT_ASSERT_EQUAL(inactiveGroupPointCount(tree, "test"), Index64(1)); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(4)); CPPUNIT_ASSERT_EQUAL(activePointCount(tree), Index64(3)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(tree), Index64(1)); // write out grid to a temp file { filename = tempDir + "/openvdb_test_point_load"; io::File fileOut(filename); GridCPtrVec grids{grid}; fileOut.write(grids); } // test point count of a delay-loaded grid { io::File fileIn(filename); fileIn.open(); GridPtrVecPtr grids = fileIn.getGrids(); fileIn.close(); CPPUNIT_ASSERT_EQUAL(grids->size(), size_t(1)); PointDataGrid::Ptr inputGrid = GridBase::grid((*grids)[0]); CPPUNIT_ASSERT(inputGrid); PointDataTree& inputTree = inputGrid->tree(); #if OPENVDB_ABI_VERSION_NUMBER >= 3 CPPUNIT_ASSERT_EQUAL(pointCount(inputTree, /*inCoreOnly=*/true), Index64(0)); CPPUNIT_ASSERT_EQUAL(activePointCount(inputTree, /*inCoreOnly=*/true), Index64(0)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(inputTree, /*inCoreOnly=*/true), Index64(0)); CPPUNIT_ASSERT_EQUAL(groupPointCount(inputTree, "test", /*inCoreOnly=*/true), Index64(0)); CPPUNIT_ASSERT_EQUAL(activeGroupPointCount(inputTree, "test", /*inCoreOnly=*/true), Index64(0)); CPPUNIT_ASSERT_EQUAL(inactiveGroupPointCount(inputTree, "test", /*inCoreOnly=*/true), Index64(0)); #else CPPUNIT_ASSERT_EQUAL(pointCount(inputTree, /*inCoreOnly=*/true), Index64(4)); CPPUNIT_ASSERT_EQUAL(activePointCount(inputTree, /*inCoreOnly=*/true), Index64(3)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(inputTree, /*inCoreOnly=*/true), Index64(1)); CPPUNIT_ASSERT_EQUAL(groupPointCount(inputTree, "test", /*inCoreOnly=*/true), Index64(2)); CPPUNIT_ASSERT_EQUAL(activeGroupPointCount(inputTree, "test", /*inCoreOnly=*/true), Index64(1)); CPPUNIT_ASSERT_EQUAL(inactiveGroupPointCount(inputTree, "test", /*inCoreOnly=*/true), Index64(1)); #endif CPPUNIT_ASSERT_EQUAL(pointCount(inputTree, /*inCoreOnly=*/false), Index64(4)); CPPUNIT_ASSERT_EQUAL(activePointCount(inputTree, /*inCoreOnly=*/false), Index64(3)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(inputTree, /*inCoreOnly=*/false), Index64(1)); CPPUNIT_ASSERT_EQUAL(groupPointCount(inputTree, "test", /*inCoreOnly=*/false), Index64(2)); CPPUNIT_ASSERT_EQUAL(activeGroupPointCount(inputTree, "test", /*inCoreOnly=*/false), Index64(1)); CPPUNIT_ASSERT_EQUAL(inactiveGroupPointCount(inputTree, "test", /*inCoreOnly=*/false), Index64(1)); } // update the value mask and confirm point counts once again leafIter->updateValueMask(); CPPUNIT_ASSERT_NO_THROW(leafIter->validateOffsets()); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree, "test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(leafIter->groupPointCount("test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(activeGroupPointCount(tree, "test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(inactiveGroupPointCount(tree, "test"), Index64(0)); CPPUNIT_ASSERT_EQUAL(pointCount(tree), Index64(4)); CPPUNIT_ASSERT_EQUAL(activePointCount(tree), Index64(4)); CPPUNIT_ASSERT_EQUAL(inactivePointCount(tree), Index64(0)); } // create a tree with multiple leaves positions.emplace_back(20, 1, 1); positions.emplace_back(1, 20, 1); positions.emplace_back(1, 1, 20); grid = createPointDataGrid(positions, *transform); PointDataTree& tree2 = grid->tree(); CPPUNIT_ASSERT_EQUAL(tree2.leafCount(), Index32(4)); leafIter = tree2.beginLeaf(); appendGroup(tree2, "test"); { // assign two points to the group const Descriptor::GroupIndex index = leafIter->attributeSet().groupIndex("test"); CPPUNIT_ASSERT(index.first != AttributeSet::INVALID_POS); CPPUNIT_ASSERT(index.first < leafIter->attributeSet().size()); AttributeArray& array = leafIter->attributeArray(index.first); CPPUNIT_ASSERT(isGroup(array)); GroupAttributeArray& groupArray = GroupAttributeArray::cast(array); groupArray.set(0, GroupType(1) << index.second); groupArray.set(3, GroupType(1) << index.second); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree2, "test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(leafIter->groupPointCount("test"), Index64(2)); CPPUNIT_ASSERT_EQUAL(pointCount(tree2), Index64(7)); } ++leafIter; CPPUNIT_ASSERT(leafIter); { // assign another point to the group in a different leaf const Descriptor::GroupIndex index = leafIter->attributeSet().groupIndex("test"); CPPUNIT_ASSERT(index.first != AttributeSet::INVALID_POS); CPPUNIT_ASSERT(index.first < leafIter->attributeSet().size()); AttributeArray& array = leafIter->attributeArray(index.first); CPPUNIT_ASSERT(isGroup(array)); GroupAttributeArray& groupArray = GroupAttributeArray::cast(array); groupArray.set(0, GroupType(1) << index.second); CPPUNIT_ASSERT_EQUAL(groupPointCount(tree2, "test"), Index64(3)); CPPUNIT_ASSERT_EQUAL(leafIter->groupPointCount("test"), Index64(1)); CPPUNIT_ASSERT_EQUAL(pointCount(tree2), Index64(7)); } } void TestPointCount::testOffsets() { using namespace openvdb::math; const float voxelSize(1.0); math::Transform::Ptr transform(math::Transform::createLinearTransform(voxelSize)); // five points across four leafs std::vector positions{{1, 1, 1}, {1, 101, 1}, {2, 101, 1}, {101, 1, 1}, {101, 101, 1}}; PointDataGrid::Ptr grid = createPointDataGrid(positions, *transform); PointDataTree& tree = grid->tree(); { // all point offsets std::vector pointOffsets; Index64 total = getPointOffsets(pointOffsets, tree); CPPUNIT_ASSERT_EQUAL(pointOffsets.size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[0], Index64(1)); CPPUNIT_ASSERT_EQUAL(pointOffsets[1], Index64(3)); CPPUNIT_ASSERT_EQUAL(pointOffsets[2], Index64(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[3], Index64(5)); CPPUNIT_ASSERT_EQUAL(total, Index64(5)); } { // all point offsets when using a non-existant exclude group std::vector pointOffsets; std::vector includeGroups; std::vector excludeGroups{"empty"}; Index64 total = getPointOffsets(pointOffsets, tree, includeGroups, excludeGroups); CPPUNIT_ASSERT_EQUAL(pointOffsets.size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[0], Index64(1)); CPPUNIT_ASSERT_EQUAL(pointOffsets[1], Index64(3)); CPPUNIT_ASSERT_EQUAL(pointOffsets[2], Index64(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[3], Index64(5)); CPPUNIT_ASSERT_EQUAL(total, Index64(5)); } appendGroup(tree, "test"); // add one point to the group from the leaf that contains two points PointDataTree::LeafIter iter = ++tree.beginLeaf(); GroupWriteHandle groupHandle = iter->groupWriteHandle("test"); groupHandle.set(0, true); { // include this group std::vector pointOffsets; std::vector includeGroups{"test"}; std::vector excludeGroups; Index64 total = getPointOffsets(pointOffsets, tree, includeGroups, excludeGroups); CPPUNIT_ASSERT_EQUAL(pointOffsets.size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[0], Index64(0)); CPPUNIT_ASSERT_EQUAL(pointOffsets[1], Index64(1)); CPPUNIT_ASSERT_EQUAL(pointOffsets[2], Index64(1)); CPPUNIT_ASSERT_EQUAL(pointOffsets[3], Index64(1)); CPPUNIT_ASSERT_EQUAL(total, Index64(1)); } { // exclude this group std::vector pointOffsets; std::vector includeGroups; std::vector excludeGroups{"test"}; Index64 total = getPointOffsets(pointOffsets, tree, includeGroups, excludeGroups); CPPUNIT_ASSERT_EQUAL(pointOffsets.size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[0], Index64(1)); CPPUNIT_ASSERT_EQUAL(pointOffsets[1], Index64(2)); CPPUNIT_ASSERT_EQUAL(pointOffsets[2], Index64(3)); CPPUNIT_ASSERT_EQUAL(pointOffsets[3], Index64(4)); CPPUNIT_ASSERT_EQUAL(total, Index64(4)); } // setup temp directory std::string tempDir; if (const char* dir = std::getenv("TMPDIR")) tempDir = dir; #ifdef _MSC_VER if (tempDir.empty()) { char tempDirBuffer[MAX_PATH+1]; int tempDirLen = GetTempPath(MAX_PATH+1, tempDirBuffer); CPPUNIT_ASSERT(tempDirLen > 0 && tempDirLen <= MAX_PATH); tempDir = tempDirBuffer; } #else if (tempDir.empty()) tempDir = P_tmpdir; #endif std::string filename; // write out grid to a temp file { filename = tempDir + "/openvdb_test_point_load"; io::File fileOut(filename); GridCPtrVec grids{grid}; fileOut.write(grids); } // test point offsets for a delay-loaded grid { io::File fileIn(filename); fileIn.open(); GridPtrVecPtr grids = fileIn.getGrids(); fileIn.close(); CPPUNIT_ASSERT_EQUAL(grids->size(), size_t(1)); PointDataGrid::Ptr inputGrid = GridBase::grid((*grids)[0]); CPPUNIT_ASSERT(inputGrid); PointDataTree& inputTree = inputGrid->tree(); std::vector pointOffsets; std::vector includeGroups; std::vector excludeGroups; Index64 total = getPointOffsets(pointOffsets, inputTree, includeGroups, excludeGroups, /*inCoreOnly=*/true); #if OPENVDB_ABI_VERSION_NUMBER >= 3 CPPUNIT_ASSERT_EQUAL(pointOffsets.size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[0], Index64(0)); CPPUNIT_ASSERT_EQUAL(pointOffsets[1], Index64(0)); CPPUNIT_ASSERT_EQUAL(pointOffsets[2], Index64(0)); CPPUNIT_ASSERT_EQUAL(pointOffsets[3], Index64(0)); CPPUNIT_ASSERT_EQUAL(total, Index64(0)); #else CPPUNIT_ASSERT_EQUAL(pointOffsets.size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[0], Index64(1)); CPPUNIT_ASSERT_EQUAL(pointOffsets[1], Index64(3)); CPPUNIT_ASSERT_EQUAL(pointOffsets[2], Index64(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[3], Index64(5)); CPPUNIT_ASSERT_EQUAL(total, Index64(5)); #endif pointOffsets.clear(); total = getPointOffsets(pointOffsets, inputTree, includeGroups, excludeGroups, /*inCoreOnly=*/false); CPPUNIT_ASSERT_EQUAL(pointOffsets.size(), size_t(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[0], Index64(1)); CPPUNIT_ASSERT_EQUAL(pointOffsets[1], Index64(3)); CPPUNIT_ASSERT_EQUAL(pointOffsets[2], Index64(4)); CPPUNIT_ASSERT_EQUAL(pointOffsets[3], Index64(5)); CPPUNIT_ASSERT_EQUAL(total, Index64(5)); } std::remove(filename.c_str()); } namespace { // sum all voxel values template inline Index64 voxelSum(const GridT& grid) { Index64 total = 0; for (auto iter = grid.cbeginValueOn(); iter; ++iter) { total += static_cast(*iter); } return total; } // Generate random points by uniformly distributing points on a unit-sphere. inline void genPoints(std::vector& positions, const int numPoints, const double scale) { // init math::Random01 randNumber(0); const int n = int(std::sqrt(double(numPoints))); const double xScale = (2.0 * M_PI) / double(n); const double yScale = M_PI / double(n); double x, y, theta, phi; Vec3R pos; positions.reserve(n*n); // 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) pos[0] = static_cast(std::sin(theta)*std::cos(phi)*scale); pos[1] = static_cast(std::sin(theta)*std::sin(phi)*scale); pos[2] = static_cast(std::cos(theta)*scale); positions.push_back(pos); } } } } // namespace void TestPointCount::testCountGrid() { using namespace openvdb::math; { // five points std::vector positions{ {1, 1, 1}, {1, 101, 1}, {2, 101, 1}, {101, 1, 1}, {101, 101, 1}}; { // in five voxels math::Transform::Ptr transform(math::Transform::createLinearTransform(1.0f)); PointDataGrid::Ptr points = createPointDataGrid(positions, *transform); // generate a count grid with the same transform Int32Grid::Ptr count = pointCountGrid(*points); CPPUNIT_ASSERT_EQUAL(count->activeVoxelCount(), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count->evalActiveVoxelBoundingBox(), points->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count), pointCount(points->tree())); } { // in four voxels math::Transform::Ptr transform(math::Transform::createLinearTransform(10.0f)); PointDataGrid::Ptr points = createPointDataGrid(positions, *transform); // generate a count grid with the same transform Int32Grid::Ptr count = pointCountGrid(*points); CPPUNIT_ASSERT_EQUAL(count->activeVoxelCount(), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count->evalActiveVoxelBoundingBox(), points->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count), pointCount(points->tree())); } { // in one voxel math::Transform::Ptr transform(math::Transform::createLinearTransform(1000.0f)); PointDataGrid::Ptr points = createPointDataGrid(positions, *transform); // generate a count grid with the same transform Int32Grid::Ptr count = pointCountGrid(*points); CPPUNIT_ASSERT_EQUAL(count->activeVoxelCount(), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count->evalActiveVoxelBoundingBox(), points->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count), pointCount(points->tree())); } { // in four voxels, Int64 grid math::Transform::Ptr transform(math::Transform::createLinearTransform(10.0f)); PointDataGrid::Ptr points = createPointDataGrid(positions, *transform); // generate a count grid with the same transform Int64Grid::Ptr count = pointCountGrid(*points); CPPUNIT_ASSERT_EQUAL(count->activeVoxelCount(), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count->evalActiveVoxelBoundingBox(), points->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count), pointCount(points->tree())); } { // in four voxels, float grid math::Transform::Ptr transform(math::Transform::createLinearTransform(10.0f)); PointDataGrid::Ptr points = createPointDataGrid(positions, *transform); // generate a count grid with the same transform FloatGrid::Ptr count = pointCountGrid(*points); CPPUNIT_ASSERT_EQUAL(count->activeVoxelCount(), points->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count->evalActiveVoxelBoundingBox(), points->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count), pointCount(points->tree())); } { // in four voxels math::Transform::Ptr transform(math::Transform::createLinearTransform(10.0f)); const PointAttributeVector pointList(positions); tools::PointIndexGrid::Ptr pointIndexGrid = tools::createPointIndexGrid(pointList, *transform); PointDataGrid::Ptr points = createPointDataGrid(*pointIndexGrid, pointList, *transform); // assign point 3 to new group "test" appendGroup(points->tree(), "test"); std::vector groups{0,0,1,0,0}; setGroup(points->tree(), pointIndexGrid->tree(), groups, "test"); std::vector includeGroups{"test"}; std::vector excludeGroups; // generate a count grid with the same transform Int32Grid::Ptr count = pointCountGrid(*points, includeGroups, excludeGroups); CPPUNIT_ASSERT_EQUAL(count->activeVoxelCount(), Index64(1)); CPPUNIT_ASSERT_EQUAL(voxelSum(*count), Index64(1)); count = pointCountGrid(*points, excludeGroups, includeGroups); CPPUNIT_ASSERT_EQUAL(count->activeVoxelCount(), Index64(4)); CPPUNIT_ASSERT_EQUAL(voxelSum(*count), Index64(4)); } // TODO: test include and exclude groups } { // 40,000 points on a unit sphere std::vector positions; const size_t total = 40000; genPoints(positions, total, /*scale=*/100.0); CPPUNIT_ASSERT_EQUAL(positions.size(), total); math::Transform::Ptr transform1(math::Transform::createLinearTransform(1.0f)); math::Transform::Ptr transform5(math::Transform::createLinearTransform(5.0f)); PointDataGrid::Ptr points1 = createPointDataGrid(positions, *transform1); PointDataGrid::Ptr points5 = createPointDataGrid(positions, *transform5); CPPUNIT_ASSERT(points1->activeVoxelCount() != points5->activeVoxelCount()); CPPUNIT_ASSERT(points1->evalActiveVoxelBoundingBox() != points5->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(pointCount(points1->tree()), pointCount(points5->tree())); { // generate count grids with the same transform Int32Grid::Ptr count1 = pointCountGrid(*points1); CPPUNIT_ASSERT_EQUAL(count1->activeVoxelCount(), points1->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count1->evalActiveVoxelBoundingBox(), points1->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count1), pointCount(points1->tree())); Int32Grid::Ptr count5 = pointCountGrid(*points5); CPPUNIT_ASSERT_EQUAL(count5->activeVoxelCount(), points5->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count5->evalActiveVoxelBoundingBox(), points5->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count5), pointCount(points5->tree())); } { // generate count grids with differing transforms Int32Grid::Ptr count1 = pointCountGrid(*points5, *transform1); CPPUNIT_ASSERT_EQUAL(count1->activeVoxelCount(), points1->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count1->evalActiveVoxelBoundingBox(), points1->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count1), pointCount(points5->tree())); Int32Grid::Ptr count5 = pointCountGrid(*points1, *transform5); CPPUNIT_ASSERT_EQUAL(count5->activeVoxelCount(), points5->activeVoxelCount()); CPPUNIT_ASSERT_EQUAL(count5->evalActiveVoxelBoundingBox(), points5->evalActiveVoxelBoundingBox()); CPPUNIT_ASSERT_EQUAL(voxelSum(*count5), pointCount(points1->tree())); } } } CPPUNIT_TEST_SUITE_REGISTRATION(TestPointCount); // Copyright (c) 2012-2017 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.cc0000644000000000000000000007711013200122377015777 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 "util.h" // for unittest_util::makeSphere() #include #include class TestGradient: public CppUnit::TestFixture { public: void setUp() override { openvdb::initialize(); } void tearDown() override { 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(testWSGradientStencilFrustum); 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 testWSGradientStencilFrustum(); 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; using AccessorType = FloatGrid::ConstAccessor; 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; 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; using AccessorType = FloatGrid::ConstAccessor; 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 = StaticPtrCast(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 = StaticPtrCast(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 = StaticPtrCast(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::testWSGradientStencilFrustum() { using namespace openvdb; // Construct a frustum that matches the one in TestMaps::testFrustum() 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 Vec3d trans(2,2,2); math::NonlinearFrustumMap::Ptr map = StaticPtrCast( frustum.preScale(Vec3d(10,10,10))->postTranslate(trans)); // Create a grid with this frustum FloatGrid::Ptr grid = FloatGrid::create(/*background=*/0.f); math::Transform::Ptr transform = math::Transform::Ptr( new math::Transform(map)); grid->setTransform(transform); FloatGrid::Accessor acc = grid->getAccessor(); // Totally fill the interior of the frustum with word space distances // from its center. math::Vec3d isCenter(.5 * 101, .5 * 101, .5 * 101); math::Vec3d wsCenter = map->applyMap(isCenter); math::Coord ijk; // convert to IntType Vec3i min(bbox.min()); Vec3i max = Vec3i(bbox.max()) + Vec3i(1, 1, 1); for (ijk[0] = min.x(); ijk[0] < max.x(); ++ijk[0]) { for (ijk[1] = min.y(); ijk[1] < max.y(); ++ijk[1]) { for (ijk[2] = min.z(); ijk[2] < max.z(); ++ijk[2]) { const math::Vec3d wsLocation = transform->indexToWorld(ijk); const float dis = float((wsLocation - wsCenter).length()); acc.setValue(ijk, dis); } } } { // test at location 10, 10, 10 in index space math::Coord xyz(10, 10, 10); math::Vec3s result = math::Gradient::result(*map, acc, xyz); // The Gradient should be unit lenght for this case CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); math::Vec3d wsVec = transform->indexToWorld(xyz); math::Vec3d direction = (wsVec - wsCenter); direction.normalize(); // test the actual direction of the gradient CPPUNIT_ASSERT(direction.eq(result, 0.01 /*tolerance*/)); } { // test at location 30, 30, 60 in index space math::Coord xyz(30, 30, 60); math::Vec3s result = math::Gradient::result(*map, acc, xyz); // The Gradient should be unit lenght for this case CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, result.length(), /*tolerance=*/0.01); math::Vec3d wsVec = transform->indexToWorld(xyz); math::Vec3d direction = (wsVec - wsCenter); direction.normalize(); // test the actual direction of the gradient CPPUNIT_ASSERT(direction.eq(result, 0.01 /*tolerance*/)); } } void TestGradient::testWSGradientStencil() { using namespace openvdb; 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 = StaticPtrCast(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 = StaticPtrCast(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; using AccessorType = FloatGrid::ConstAccessor; 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; 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; 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; 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-2017 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.cc0000644000000000000000000004352413200122377015113 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 math::Random01(), math::Pow3() 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(testIsConstant); CPPUNIT_TEST(testMedian); CPPUNIT_TEST(testFill); 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 testIsConstant(); void testMedian(); void testFill(); }; 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::testIsConstant() { using namespace openvdb; const Coord origin(-9, -2, -8); {// check old version (v3.0 and older) with float // Acceptable range: first-value +/- tolerance const float val = 1.0f, tol = 0.01f; tree::LeafNode leaf(origin, val, true); float v = 0.0f; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val, v); leaf.setValueOff(0); CPPUNIT_ASSERT(!leaf.isConstant(v, stat, tol)); leaf.setValueOn(0); CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); leaf.setValueOn(0, val + 0.99f*tol); CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val + 0.99f*tol, v); leaf.setValueOn(0, val + 1.01f*tol); CPPUNIT_ASSERT(!leaf.isConstant(v, stat, tol)); } {// check old version (v3.0 and older) with double // Acceptable range: first-value +/- tolerance const double val = 1.0, tol = 0.00001; tree::LeafNode leaf(origin, val, true); double v = 0.0; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val, v); leaf.setValueOff(0); CPPUNIT_ASSERT(!leaf.isConstant(v, stat, tol)); leaf.setValueOn(0); CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); leaf.setValueOn(0, val + 0.99*tol); CPPUNIT_ASSERT(leaf.isConstant(v, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val + 0.99*tol, v); leaf.setValueOn(0, val + 1.01*tol); CPPUNIT_ASSERT(!leaf.isConstant(v, stat, tol)); } {// check newer version (v3.2 and newer) with float // Acceptable range: max - min <= tolerance const float val = 1.0, tol = 0.01f; tree::LeafNode leaf(origin, val, true); float vmin = 0.0f, vmax = 0.0f; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val, vmax); leaf.setValueOff(0); CPPUNIT_ASSERT(!leaf.isConstant(vmin, vmax, stat, tol)); leaf.setValueOn(0); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); leaf.setValueOn(0, val + tol); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val + tol, vmax); leaf.setValueOn(0, val + 1.01f*tol); CPPUNIT_ASSERT(!leaf.isConstant(vmin, vmax, stat, tol)); } {// check newer version (v3.2 and newer) with double // Acceptable range: (max- min) <= tolerance const double val = 1.0, tol = 0.000001; tree::LeafNode leaf(origin, val, true); double vmin = 0.0, vmax = 0.0; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val, vmax); leaf.setValueOff(0); CPPUNIT_ASSERT(!leaf.isConstant(vmin, vmax, stat, tol)); leaf.setValueOn(0); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); leaf.setValueOn(0, val + tol); CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT_EQUAL(val, vmin); CPPUNIT_ASSERT_EQUAL(val + tol, vmax); leaf.setValueOn(0, val + 1.01*tol); CPPUNIT_ASSERT(!leaf.isConstant(vmin, vmax, stat, tol)); } {// check newer version (v3.2 and newer) with float and random values typedef tree::LeafNode LeafNodeT; const float val = 1.0, tol = 1.0f; LeafNodeT leaf(origin, val, true); float min = 2.0f, max = -min; math::Random01 r(145);// random values in the range [0,1] for (Index i=0; i max) max = v; leaf.setValueOnly(i, v); } float vmin = 0.0f, vmax = 0.0f; bool stat = false; CPPUNIT_ASSERT(leaf.isConstant(vmin, vmax, stat, tol)); CPPUNIT_ASSERT(stat); CPPUNIT_ASSERT(math::isApproxEqual(min, vmin)); CPPUNIT_ASSERT(math::isApproxEqual(max, vmax)); } } void TestLeaf::testMedian() { using namespace openvdb; const Coord origin(-9, -2, -8); std::vector v{5, 6, 4, 3, 2, 6, 7, 9, 3}; tree::LeafNode leaf(origin, 1.0f, false); float val = 0.0f; CPPUNIT_ASSERT_EQUAL(Index(0), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(0.0f, val); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(0,0,0), v[0]); CPPUNIT_ASSERT_EQUAL(Index(1), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[0], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-1, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(0,0,1), v[1]); CPPUNIT_ASSERT_EQUAL(Index(2), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[0], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-2, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(0,2,1), v[2]); CPPUNIT_ASSERT_EQUAL(Index(3), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[0], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-3, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(1,2,1), v[3]); CPPUNIT_ASSERT_EQUAL(Index(4), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[2], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-4, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(1,2,3), v[4]); CPPUNIT_ASSERT_EQUAL(Index(5), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[2], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-5, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(2,2,1), v[5]); CPPUNIT_ASSERT_EQUAL(Index(6), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[2], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-6, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(2,4,1), v[6]); CPPUNIT_ASSERT_EQUAL(Index(7), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[0], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-7, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(2,6,1), v[7]); CPPUNIT_ASSERT_EQUAL(Index(8), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[0], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-8, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.setValue(Coord(7,2,1), v[8]); CPPUNIT_ASSERT_EQUAL(Index(9), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(v[0], val); CPPUNIT_ASSERT_EQUAL(leaf.numValues()-9, leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(1.0f, val); CPPUNIT_ASSERT_EQUAL(1.0f, leaf.medianAll()); leaf.fill(2.0f, true); CPPUNIT_ASSERT_EQUAL(leaf.numValues(), leaf.medianOn(val)); CPPUNIT_ASSERT_EQUAL(2.0f, val); CPPUNIT_ASSERT_EQUAL(Index(0), leaf.medianOff(val)); CPPUNIT_ASSERT_EQUAL(2.0f, val); CPPUNIT_ASSERT_EQUAL(2.0f, leaf.medianAll()); } void TestLeaf::testFill() { using namespace openvdb; const Coord origin(-9, -2, -8); const float bg = 0.0f, fg = 1.0f; tree::LeafNode leaf(origin, bg, false); const int bboxDim = 1 + int(leaf.dim() >> 1); auto bbox = CoordBBox::createCube(leaf.origin(), bboxDim); CPPUNIT_ASSERT_EQUAL(math::Pow3(bboxDim), int(bbox.volume())); bbox = leaf.getNodeBoundingBox(); leaf.fill(bbox, bg, false); CPPUNIT_ASSERT(leaf.isEmpty()); leaf.fill(bbox, fg, true); CPPUNIT_ASSERT(leaf.isDense()); leaf.fill(bbox, bg, false); CPPUNIT_ASSERT(leaf.isEmpty()); // Fill a region that is larger than the node but that doesn't completely enclose it. bbox.max() = bbox.min() + (bbox.dim() >> 1); bbox.expand(bbox.min() - Coord{10}); leaf.fill(bbox, fg, true); // Verify that fill() correctly clips the fill region to the node. auto clippedBBox = leaf.getNodeBoundingBox(); clippedBBox.intersect(bbox); CPPUNIT_ASSERT_EQUAL(int(clippedBBox.volume()), int(leaf.onVoxelCount())); } // Copyright (c) 2012-2017 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.cc0000644000000000000000000010204613200122377015335 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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(testMinMax); CPPUNIT_TEST(testExtrema); CPPUNIT_TEST(testStats); CPPUNIT_TEST(testHistogram); CPPUNIT_TEST(testGridExtrema); CPPUNIT_TEST(testGridStats); CPPUNIT_TEST(testGridHistogram); CPPUNIT_TEST(testGridOperatorStats); CPPUNIT_TEST_SUITE_END(); void testMinMax(); void testExtrema(); void testStats(); void testHistogram(); void testGridExtrema(); void testGridStats(); void testGridHistogram(); void testGridOperatorStats(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestStats); void TestStats::testMinMax() { {// test Coord which uses lexicographic less than openvdb::math::MinMax s(openvdb::Coord::max(), openvdb::Coord::min()); //openvdb::math::MinMax s;// will not compile since Coord is not a POD type CPPUNIT_ASSERT_EQUAL(openvdb::Coord::max(), s.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord::min(), s.max()); s.add( openvdb::Coord(1,2,3) ); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(1,2,3), s.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(1,2,3), s.max()); s.add( openvdb::Coord(0,2,3) ); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0,2,3), s.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(1,2,3), s.max()); s.add( openvdb::Coord(1,2,4) ); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(0,2,3), s.min()); CPPUNIT_ASSERT_EQUAL(openvdb::Coord(1,2,4), s.max()); } {// test double openvdb::math::MinMax s; CPPUNIT_ASSERT_EQUAL( std::numeric_limits::max(), s.min()); CPPUNIT_ASSERT_EQUAL(-std::numeric_limits::max(), s.max()); s.add( 1.0 ); CPPUNIT_ASSERT_EQUAL(1.0, s.min()); CPPUNIT_ASSERT_EQUAL(1.0, s.max()); s.add( 2.5 ); CPPUNIT_ASSERT_EQUAL(1.0, s.min()); CPPUNIT_ASSERT_EQUAL(2.5, s.max()); s.add( -0.5 ); CPPUNIT_ASSERT_EQUAL(-0.5, s.min()); CPPUNIT_ASSERT_EQUAL( 2.5, s.max()); } {// test int openvdb::math::MinMax s; CPPUNIT_ASSERT_EQUAL(std::numeric_limits::max(), s.min()); CPPUNIT_ASSERT_EQUAL(std::numeric_limits::min(), s.max()); s.add( 1 ); CPPUNIT_ASSERT_EQUAL(1, s.min()); CPPUNIT_ASSERT_EQUAL(1, s.max()); s.add( 2 ); CPPUNIT_ASSERT_EQUAL(1, s.min()); CPPUNIT_ASSERT_EQUAL(2, s.max()); s.add( -5 ); CPPUNIT_ASSERT_EQUAL(-5, s.min()); CPPUNIT_ASSERT_EQUAL( 2, s.max()); } {// test unsigned openvdb::math::MinMax s; CPPUNIT_ASSERT_EQUAL(std::numeric_limits::max(), s.min()); CPPUNIT_ASSERT_EQUAL(uint32_t(0), s.max()); s.add( 1 ); CPPUNIT_ASSERT_EQUAL(uint32_t(1), s.min()); CPPUNIT_ASSERT_EQUAL(uint32_t(1), s.max()); s.add( 2 ); CPPUNIT_ASSERT_EQUAL(uint32_t(1), s.min()); CPPUNIT_ASSERT_EQUAL(uint32_t(2), s.max()); s.add( 0 ); CPPUNIT_ASSERT_EQUAL( uint32_t(0), s.min()); CPPUNIT_ASSERT_EQUAL( uint32_t(2), s.max()); } } void TestStats::testExtrema() { {// trivial test openvdb::math::Extrema 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(1.0, s.range(), 0.000001); //s.print("test"); } {// non-trivial test openvdb::math::Extrema s; const int data[5]={600, 470, 170, 430, 300}; for (int i=0; i<5; ++i) s.add(data[i]); 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(data[0]-data[2], s.range(), 0.000001); //s.print("test"); } {// non-trivial test of Extrema::add(Extrema) openvdb::math::Extrema 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); 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(data[0]-data[2], s.range(), 0.000001); //s.print("test"); } {// Trivial test of Extrema::add(value, n) openvdb::math::Extrema 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(0.0, s.range(), 0.000001); } {// Test 1 of Extrema::add(value), Extrema::add(value, n) and Extrema::add(Extrema) openvdb::math::Extrema s, t; const double val1 = 1.0, val2 = 3.0; 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); 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 void operator()(const GridT::ValueOnCIter& it, StatsT& stats) const { typedef openvdb::math::ISGradient 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::testGridExtrema() { 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::Extrema ex = tools::extrema(grid.cbeginValueOn()); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(42.0, ex.max(), /*tolerance=*/0.0); // Compute inactive value statistics for a grid with only background voxels. grid.tree().setValueOff(Coord(0), background); ex = tools::extrema(grid.cbeginValueOff()); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(background, ex.max(), /*tolerance=*/0.0); } // 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::Extrema ex = tools::extrema(grid.cbeginValueOn(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(-3.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), ex.max(), /*tolerance=*/0.0); } // 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::Extrema& ex) { const float f = *it; if (f > 0.0) { if (it.isVoxelValue()) ex.add(f); else ex.add(f, it.getVoxelCount()); } } }; math::Extrema ex = tools::extrema(grid.cbeginValueOn(), &Local::addIfPositive, threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(1.0), ex.max(), /*tolerance=*/0.0); } // Compute active value statistics for the first-order gradient. for (int threaded = 0; threaded <= 1; ++threaded) { // First, using a custom ValueOp... math::Extrema ex = tools::extrema(grid.cbeginValueOn(), GradOp(grid), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL( double(9.0 + 9.0 + 9.0), ex.max() * ex.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; ex = tools::opExtrema(grid.cbeginValueOn(), MathOp(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL( double(9.0 + 9.0 + 9.0), ex.max() * ex.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::Extrema ex = tools::extrema(grid.cbeginValueOn(), threaded); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(3.0), ex.min(), /*tolerance=*/0.0); CPPUNIT_ASSERT_DOUBLES_EQUAL(double(5.0), ex.max(), /*tolerance=*/0.0); } } } 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 (int i = 0, N = int(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 (int i = 0, N = int(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 (int i = 0, N = int(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-2017 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/TestPointIndexGrid.cc0000644000000000000000000002411513200122377017126 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 genPoints struct TestPointIndexGrid: public CppUnit::TestCase { CPPUNIT_TEST_SUITE(TestPointIndexGrid); CPPUNIT_TEST(testPointIndexGrid); CPPUNIT_TEST(testPointIndexFilter); CPPUNIT_TEST(testWorldSpaceSearchAndUpdate); CPPUNIT_TEST_SUITE_END(); void testPointIndexGrid(); void testPointIndexFilter(); void testWorldSpaceSearchAndUpdate(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestPointIndexGrid); //////////////////////////////////////// namespace { class PointList { public: typedef openvdb::Vec3R PosType; PointList(const std::vector& points) : mPoints(&points) { } size_t size() const { return mPoints->size(); } void getPos(size_t n, PosType& xyz) const { xyz = (*mPoints)[n]; } protected: std::vector const * const mPoints; }; // PointList template bool hasDuplicates(const std::vector& items) { std::vector vec(items); std::sort(vec.begin(), vec.end()); size_t duplicates = 0; for (size_t n = 1, N = vec.size(); n < N; ++n) { if (vec[n] == vec[n-1]) ++duplicates; } return duplicates != 0; } template struct WeightedAverageAccumulator { typedef T ValueType; WeightedAverageAccumulator(T const * const array, const T radius) : mValues(array), mInvRadius(1.0/radius), mWeightSum(0.0), mValueSum(0.0) {} void reset() { mWeightSum = mValueSum = T(0.0); } void operator()(const T distSqr, const size_t pointIndex) { const T weight = T(1.0) - openvdb::math::Sqrt(distSqr) * mInvRadius; mWeightSum += weight; mValueSum += weight * mValues[pointIndex]; } T result() const { return mWeightSum > T(0.0) ? mValueSum / mWeightSum : T(0.0); } private: T const * const mValues; const T mInvRadius; T mWeightSum, mValueSum; }; // struct WeightedAverageAccumulator } // namespace //////////////////////////////////////// void TestPointIndexGrid::testPointIndexGrid() { const float voxelSize = 0.01f; const openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); // generate points std::vector points; unittest_util::genPoints(40000, points); PointList pointList(points); // construct data structure typedef openvdb::tools::PointIndexGrid PointIndexGrid; PointIndexGrid::Ptr pointGridPtr = openvdb::tools::createPointIndexGrid(pointList, *transform); openvdb::CoordBBox bbox; pointGridPtr->tree().evalActiveVoxelBoundingBox(bbox); // coord bbox search typedef PointIndexGrid::ConstAccessor ConstAccessor; typedef openvdb::tools::PointIndexIterator<> PointIndexIterator; ConstAccessor acc = pointGridPtr->getConstAccessor(); PointIndexIterator it(bbox, acc); CPPUNIT_ASSERT(it.test()); CPPUNIT_ASSERT_EQUAL(points.size(), it.size()); // fractional bbox search openvdb::BBoxd region(bbox.min().asVec3d(), bbox.max().asVec3d()); // points are bucketed in a cell-centered fashion, we need to pad the // coordinate range to get the same search region in the fractional bbox. region.expand(voxelSize * 0.5); it.searchAndUpdate(region, acc, pointList, *transform); CPPUNIT_ASSERT(it.test()); CPPUNIT_ASSERT_EQUAL(points.size(), it.size()); { std::vector vec; vec.reserve(it.size()); for (; it; ++it) { vec.push_back(*it); } CPPUNIT_ASSERT_EQUAL(vec.size(), it.size()); CPPUNIT_ASSERT(!hasDuplicates(vec)); } // radial search openvdb::Vec3d center = region.getCenter(); double radius = region.extents().x() * 0.5; it.searchAndUpdate(center, radius, acc, pointList, *transform); CPPUNIT_ASSERT(it.test()); CPPUNIT_ASSERT_EQUAL(points.size(), it.size()); { std::vector vec; vec.reserve(it.size()); for (; it; ++it) { vec.push_back(*it); } CPPUNIT_ASSERT_EQUAL(vec.size(), it.size()); CPPUNIT_ASSERT(!hasDuplicates(vec)); } center = region.min(); it.searchAndUpdate(center, radius, acc, pointList, *transform); CPPUNIT_ASSERT(it.test()); { std::vector vec; vec.reserve(it.size()); for (; it; ++it) { vec.push_back(*it); } CPPUNIT_ASSERT_EQUAL(vec.size(), it.size()); CPPUNIT_ASSERT(!hasDuplicates(vec)); // check that no points where missed. std::vector indexMask(points.size(), 0); for (size_t n = 0, N = vec.size(); n < N; ++n) { indexMask[vec[n]] = 1; } const double r2 = radius * radius; openvdb::Vec3R v; for (size_t n = 0, N = indexMask.size(); n < N; ++n) { v = center - transform->worldToIndex(points[n]); if (indexMask[n] == 0) { CPPUNIT_ASSERT(!(v.lengthSqr() < r2)); } else { CPPUNIT_ASSERT(v.lengthSqr() < r2); } } } // Check partitioning CPPUNIT_ASSERT(openvdb::tools::isValidPartition(pointList, *pointGridPtr)); points[10000].x() += 1.5; // manually modify a few points. points[20000].x() += 1.5; points[30000].x() += 1.5; CPPUNIT_ASSERT(!openvdb::tools::isValidPartition(pointList, *pointGridPtr)); PointIndexGrid::Ptr pointGrid2Ptr = openvdb::tools::getValidPointIndexGrid(pointList, pointGridPtr); CPPUNIT_ASSERT(openvdb::tools::isValidPartition(pointList, *pointGrid2Ptr)); } void TestPointIndexGrid::testPointIndexFilter() { // generate points const float voxelSize = 0.01f; const size_t pointCount = 10000; const openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); std::vector points; unittest_util::genPoints(pointCount, points); PointList pointList(points); // construct data structure typedef openvdb::tools::PointIndexGrid PointIndexGrid; PointIndexGrid::Ptr pointGridPtr = openvdb::tools::createPointIndexGrid(pointList, *transform); std::vector pointDensity(pointCount, 1.0); openvdb::tools::PointIndexFilter filter(pointList, pointGridPtr->tree(), pointGridPtr->transform()); const double radius = 3.0 * voxelSize; WeightedAverageAccumulator accumulator(&pointDensity.front(), radius); double sum = 0.0; for (size_t n = 0, N = points.size(); n < N; ++n) { accumulator.reset(); filter.searchAndApply(points[n], radius, accumulator); sum += accumulator.result(); } CPPUNIT_ASSERT_DOUBLES_EQUAL(sum, double(points.size()), 1e-6); } void TestPointIndexGrid::testWorldSpaceSearchAndUpdate() { // Create random particles in a cube. openvdb::math::Rand01<> rnd(0); const size_t N = 1000000; std::vector pos; pos.reserve(N); // Create a box to query points. openvdb::BBoxd wsBBox(openvdb::Vec3d(0.25), openvdb::Vec3d(0.75)); std::set indexListA; for (size_t i = 0; i < N; ++i) { openvdb::Vec3d p(rnd(), rnd(), rnd()); pos.push_back(p); if (wsBBox.isInside(p)) { indexListA.insert(i); } } // Create a point index grid const double dx = 0.025; openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(dx); PointList pointArray(pos); openvdb::tools::PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid(pointArray, *transform); // Search for points within the box. openvdb::tools::PointIndexGrid::ConstAccessor acc = pointIndexGrid->getConstAccessor(); openvdb::tools::PointIndexIterator pointIndexIter; pointIndexIter.worldSpaceSearchAndUpdate(wsBBox, acc, pointArray, pointIndexGrid->transform()); std::set indexListB; for (; pointIndexIter; ++pointIndexIter) { indexListB.insert(*pointIndexIter); } CPPUNIT_ASSERT_EQUAL(indexListA.size(), indexListB.size()); } // Copyright (c) 2012-2017 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/TestFastSweeping.cc0000644000000000000000000004530013200122377016635 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 FAST_SWEEPING_DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FAST_SWEEPING_DEBUG #include // Uncomment to test on models from our web-site //#define TestFastSweeping_DATA_PATH "/home/kmu/src/data/vdb/" //#define TestFastSweeping_DATA_PATH "/usr/pic1/Data/OpenVDB/LevelSetModels/" #endif class TestFastSweeping: public CppUnit::TestFixture { public: virtual void setUp() { openvdb::initialize(); } virtual void tearDown() { openvdb::uninitialize(); } CPPUNIT_TEST_SUITE(TestFastSweeping); CPPUNIT_TEST(testDilateSDF); CPPUNIT_TEST(testExtendSDF); CPPUNIT_TEST(testMakeSDF); CPPUNIT_TEST_SUITE_END(); void testDilateSDF(); void testExtendSDF(); void testMakeSDF(); void write(const std::string &name, openvdb::FloatGrid::Ptr grid); };// TestFastSweeping CPPUNIT_TEST_SUITE_REGISTRATION(TestFastSweeping); void TestFastSweeping::write(const std::string &name, openvdb::FloatGrid::Ptr grid) { openvdb::GridPtrVec grids; grids.push_back(grid); openvdb::io::File file(name); file.write(grids); } void TestFastSweeping::testDilateSDF() { using namespace openvdb; // Define parameters for the level set sphere to be re-normalized const float radius = 200.0f; const Vec3f center(0.0f, 0.0f, 0.0f); const float voxelSize = 1.0f;//half width const int width = 3, new_width = 50;//half width FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, float(width)); const size_t oldVoxelCount = grid->activeVoxelCount(); tools::FastSweeping fs(*grid); CPPUNIT_ASSERT_EQUAL(size_t(0), fs.sweepingVoxelCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), fs.boundaryVoxelCount()); fs.initDilateSDF(new_width - width); CPPUNIT_ASSERT(fs.sweepingVoxelCount() > 0); CPPUNIT_ASSERT(fs.boundaryVoxelCount() > 0); fs.sweep(); CPPUNIT_ASSERT(fs.sweepingVoxelCount() > 0); CPPUNIT_ASSERT(fs.boundaryVoxelCount() > 0); fs.clear(); CPPUNIT_ASSERT_EQUAL(size_t(0), fs.sweepingVoxelCount()); CPPUNIT_ASSERT_EQUAL(size_t(0), fs.boundaryVoxelCount()); const size_t sweepingVoxelCount = grid->activeVoxelCount(); CPPUNIT_ASSERT(sweepingVoxelCount > oldVoxelCount); {// Check that the norm of the gradient for all active voxels is close to unity tools::Diagnose diagnose(*grid); tools::CheckNormGrad test(*grid, 0.99f, 1.01f); const std::string message = diagnose.check(test, false,// don't generate a mask grid true,// check active voxels false,// ignore active tiles since a level set has none false);// no need to check the background value CPPUNIT_ASSERT(message.empty()); CPPUNIT_ASSERT_EQUAL(size_t(0), diagnose.failureCount()); //std::cout << "\nOutput 1: " << message << std::endl; } {// Make sure all active voxels fail the following test tools::Diagnose diagnose(*grid); tools::CheckNormGrad test(*grid, std::numeric_limits::min(), 0.99f); const std::string message = diagnose.check(test, false,// don't generate a mask grid true,// check active voxels false,// ignore active tiles since a level set has none false);// no need to check the background value CPPUNIT_ASSERT(!message.empty()); CPPUNIT_ASSERT_EQUAL(sweepingVoxelCount, diagnose.failureCount()); //std::cout << "\nOutput 2: " << message << std::endl; } {// Make sure all active voxels fail the following test tools::Diagnose diagnose(*grid); tools::CheckNormGrad test(*grid, 1.01f, std::numeric_limits::max()); const std::string message = diagnose.check(test, false,// don't generate a mask grid true,// check active voxels false,// ignore active tiles since a level set has none false);// no need to check the background value CPPUNIT_ASSERT(!message.empty()); CPPUNIT_ASSERT_EQUAL(sweepingVoxelCount, diagnose.failureCount()); //std::cout << "\nOutput 3: " << message << std::endl; } }// testDilateSDF void TestFastSweeping::testExtendSDF() { using namespace openvdb; // Define parameterS FOR the level set sphere to be re-normalized const float radius = 200.0f; const Vec3f center(0.0f, 0.0f, 0.0f); const float voxelSize = 1.0f, width = 3.0f;//half width const float new_width = 50; {// Use box as a mask //std::cerr << "\nUse box as a mask" << std::endl; FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); CoordBBox bbox(Coord(150,-50,-50), Coord(250,50,50)); MaskGrid mask; mask.sparseFill(bbox, true); #ifdef FAST_SWEEPING_DEBUG this->write("/tmp/box_mask_input.vdb", grid); util::CpuTimer timer("\nParallel sparse fast sweeping with a box mask"); #endif tools::extendSDF(*grid, mask); //tools::FastSweeping fs(*grid); //fs.initExtendSDF(mask); //fs.sweep(); //std::cerr << "voxel count = " << fs.sweepingVoxelCount() << std::endl; //std::cerr << "boundary count = " << fs.boundaryVoxelCount() << std::endl; //CPPUNIT_ASSERT(fs.sweepingVoxelCount() > 0); #ifdef FAST_SWEEPING_DEBUG timer.stop(); this->write("/tmp/box_mask_output.vdb", grid); #endif {// Check that the norm of the gradient for all active voxels is close to unity tools::Diagnose diagnose(*grid); tools::CheckNormGrad test(*grid, 0.99f, 1.01f); const std::string message = diagnose.check(test, false,// don't generate a mask grid true,// check active voxels false,// ignore active tiles since a level set has none false);// no need to check the background value //std::cerr << message << std::endl; const double percent = 100.0*double(diagnose.failureCount())/double(grid->activeVoxelCount()); //std::cerr << "Failures = " << percent << "%" << std::endl; //std::cerr << "Failed: " << diagnose.failureCount() << std::endl; //std::cerr << "Total : " << grid->activeVoxelCount() << std::endl; CPPUNIT_ASSERT(percent < 0.01); //CPPUNIT_ASSERT(message.empty()); //CPPUNIT_ASSERT_EQUAL(size_t(0), diagnose.failureCount()); } } {// Use sphere as a mask //std::cerr << "\nUse sphere as a mask" << std::endl; FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); FloatGrid::Ptr mask = tools::createLevelSetSphere(radius, center, voxelSize, new_width); #ifdef FAST_SWEEPING_DEBUG this->write("/tmp/sphere_mask_input.vdb", grid); util::CpuTimer timer("\nParallel sparse fast sweeping with a sphere mask"); #endif tools::extendSDF(*grid, *mask); //tools::FastSweeping fs(*grid); //fs.initExtendSDF(*mask); //fs.sweep(); #ifdef FAST_SWEEPING_DEBUG timer.stop(); this->write("/tmp/sphere_mask_output.vdb", grid); #endif //std::cerr << "voxel count = " << fs.sweepingVoxelCount() << std::endl; //std::cerr << "boundary count = " << fs.boundaryVoxelCount() << std::endl; //CPPUNIT_ASSERT(fs.sweepingVoxelCount() > 0); {// Check that the norm of the gradient for all active voxels is close to unity tools::Diagnose diagnose(*grid); tools::CheckNormGrad test(*grid, 0.99f, 1.01f); const std::string message = diagnose.check(test, false,// don't generate a mask grid true,// check active voxels false,// ignore active tiles since a level set has none false);// no need to check the background value //std::cerr << message << std::endl; const double percent = 100.0*double(diagnose.failureCount())/double(grid->activeVoxelCount()); //std::cerr << "Failures = " << percent << "%" << std::endl; //std::cerr << "Failed: " << diagnose.failureCount() << std::endl; //std::cerr << "Total : " << grid->activeVoxelCount() << std::endl; //CPPUNIT_ASSERT(message.empty()); //CPPUNIT_ASSERT_EQUAL(size_t(0), diagnose.failureCount()); CPPUNIT_ASSERT(percent < 0.01); //std::cout << "\nOutput 1: " << message << std::endl; } } {// Use dodecahedron as a mask //std::cerr << "\nUse dodecahedron as a mask" << std::endl; FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, width); FloatGrid::Ptr mask = tools::createLevelSetDodecahedron(50, Vec3f(radius, 0.0f, 0.0f), voxelSize, 10); #ifdef FAST_SWEEPING_DEBUG this->write("/tmp/dodecahedron_mask_input.vdb", grid); util::CpuTimer timer("\nParallel sparse fast sweeping with a dodecahedron mask"); #endif tools::extendSDF(*grid, *mask); //tools::FastSweeping fs(*grid); //fs.initExtendSDF(*mask); //std::cerr << "voxel count = " << fs.sweepingVoxelCount() << std::endl; //std::cerr << "boundary count = " << fs.boundaryVoxelCount() << std::endl; //CPPUNIT_ASSERT(fs.sweepingVoxelCount() > 0); //fs.sweep(); //fs.clear(); #ifdef FAST_SWEEPING_DEBUG timer.stop(); this->write("/tmp/dodecahedron_mask_output.vdb", grid); #endif {// Check that the norm of the gradient for all active voxels is close to unity tools::Diagnose diagnose(*grid); tools::CheckNormGrad test(*grid, 0.99f, 1.01f); const std::string message = diagnose.check(test, false,// don't generate a mask grid true,// check active voxels false,// ignore active tiles since a level set has none false);// no need to check the background value //std::cerr << message << std::endl; const double percent = 100.0*double(diagnose.failureCount())/double(grid->activeVoxelCount()); //std::cerr << "Failures = " << percent << "%" << std::endl; //std::cerr << "Failed: " << diagnose.failureCount() << std::endl; //std::cerr << "Total : " << grid->activeVoxelCount() << std::endl; //CPPUNIT_ASSERT(message.empty()); //CPPUNIT_ASSERT_EQUAL(size_t(0), diagnose.failureCount()); CPPUNIT_ASSERT(percent < 0.01); //std::cout << "\nOutput 1: " << message << std::endl; } } #ifdef TestFastSweeping_DATA_PATH {// Use bunny as a mask //std::cerr << "\nUse bunny as a mask" << std::endl; FloatGrid::Ptr grid = tools::createLevelSetSphere(10.0f, Vec3f(-10,0,0), 0.05f, width); openvdb::initialize();//required whenever I/O of OpenVDB files is performed! const std::string path(TestFastSweeping_DATA_PATH); io::File file( path + "bunny.vdb" ); file.open(false);//disable delayed loading FloatGrid::Ptr mask = openvdb::gridPtrCast(file.getGrids()->at(0)); this->write("/tmp/bunny_mask_input.vdb", grid); tools::FastSweeping fs(*grid); util::CpuTimer timer("\nParallel sparse fast sweeping with a bunny mask"); fs.initExtendSDF(*mask); //std::cerr << "voxel count = " << fs.sweepingVoxelCount() << std::endl; //std::cerr << "boundary count = " << fs.boundaryVoxelCount() << std::endl; fs.sweep(); fs.clear(); timer.stop(); this->write("/tmp/bunny_mask_output.vdb", grid); {// Check that the norm of the gradient for all active voxels is close to unity tools::Diagnose diagnose(*grid); tools::CheckNormGrad test(*grid, 0.99f, 1.01f); const std::string message = diagnose.check(test, false,// don't generate a mask grid true,// check active voxels false,// ignore active tiles since a level set has none false);// no need to check the background value //std::cerr << message << std::endl; const double percent = 100.0*double(diagnose.failureCount())/double(grid->activeVoxelCount()); //std::cerr << "Failures = " << percent << "%" << std::endl; //std::cerr << "Failed: " << diagnose.failureCount() << std::endl; //std::cerr << "Total : " << grid->activeVoxelCount() << std::endl; //CPPUNIT_ASSERT(message.empty()); //CPPUNIT_ASSERT_EQUAL(size_t(0), diagnose.failureCount()); CPPUNIT_ASSERT(percent < 4.5);// crossing characteristics! //std::cout << "\nOutput 1: " << message << std::endl; } } #endif }// testExtendSDF void TestFastSweeping::testMakeSDF() { using namespace openvdb; // Define parameterS FOR the level set sphere to be re-normalized const float radius = 200.0f; const Vec3f center(0.0f, 0.0f, 0.0f); const float voxelSize = 1.0f, width = 3.0f;//half width FloatGrid::Ptr grid = tools::createLevelSetSphere(radius, center, voxelSize, float(width)); tools::sdfToFogVolume(*grid); const size_t sweepingVoxelCount = grid->activeVoxelCount(); #ifdef FAST_SWEEPING_DEBUG this->write("/tmp/ls_input.vdb", grid); #endif tools::FastSweeping fs(*grid); #ifdef FAST_SWEEPING_DEBUG util::CpuTimer timer("\nParallel sparse fast sweeping with a fog volume"); #endif fs.initMakeSDF(0.5); CPPUNIT_ASSERT(fs.sweepingVoxelCount() > 0); //std::cerr << "voxel count = " << fs.sweepingVoxelCount() << std::endl; //std::cerr << "boundary count = " << fs.boundaryVoxelCount() << std::endl; fs.sweep(); #ifdef FAST_SWEEPING_DEBUG timer.restart("getSweepingGrid"); #endif auto tmp = fs.getSweepingGrid(); #ifdef FAST_SWEEPING_DEBUG timer.stop(); #endif fs.clear(); CPPUNIT_ASSERT(sweepingVoxelCount > tmp->activeVoxelCount()); #ifdef FAST_SWEEPING_DEBUG this->write("/tmp/sweepingGrid.vdb", tmp); #endif CPPUNIT_ASSERT_EQUAL(sweepingVoxelCount, grid->activeVoxelCount()); #ifdef FAST_SWEEPING_DEBUG this->write("/tmp/fog_output.vdb", grid); #endif {// Check that the norm of the gradient for all active voxels is close to unity tools::Diagnose diagnose(*grid); tools::CheckNormGrad test(*grid, 0.99f, 1.01f); const std::string message = diagnose.check(test, false,// don't generate a mask grid true,// check active voxels false,// ignore active tiles since a level set has none false);// no need to check the background value //std::cerr << message << std::endl; const double percent = 100.0*double(diagnose.failureCount())/double(grid->activeVoxelCount()); //std::cerr << "Failures = " << percent << "%" << std::endl; //std::cerr << "Failed: " << diagnose.failureCount() << std::endl; //std::cerr << "Total : " << grid->activeVoxelCount() << std::endl; CPPUNIT_ASSERT(percent < 3.0); } }// testMakeSDF // Copyright (c) 2012-2017 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/TestConjGradient.cc0000644000000000000000000001636013200122377016611 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 TestConjGradient: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestConjGradient); CPPUNIT_TEST(testJacobi); CPPUNIT_TEST(testIncompleteCholesky); CPPUNIT_TEST(testVectorDotProduct); CPPUNIT_TEST_SUITE_END(); void testJacobi(); void testIncompleteCholesky(); void testVectorDotProduct(); }; CPPUNIT_TEST_SUITE_REGISTRATION(TestConjGradient); //////////////////////////////////////// void TestConjGradient::testJacobi() { using namespace openvdb; typedef math::pcg::SparseStencilMatrix MatrixType; const math::pcg::SizeType rows = 5; MatrixType A(rows); A.setValue(0, 0, 24.0); A.setValue(0, 2, 6.0); A.setValue(1, 1, 8.0); A.setValue(1, 2, 2.0); A.setValue(2, 0, 6.0); A.setValue(2, 1, 2.0); A.setValue(2, 2, 8.0); A.setValue(2, 3, -6.0); A.setValue(2, 4, 2.0); A.setValue(3, 2, -6.0); A.setValue(3, 3, 24.0); A.setValue(4, 2, 2.0); A.setValue(4, 4, 8.0); CPPUNIT_ASSERT(A.isFinite()); MatrixType::VectorType x(rows, 0.0), b(rows, 1.0), expected(rows); expected[0] = 0.0104167; expected[1] = 0.09375; expected[2] = 0.125; expected[3] = 0.0729167; expected[4] = 0.09375; math::pcg::JacobiPreconditioner precond(A); // Solve A * x = b for x. math::pcg::State result = math::pcg::solve( A, b, x, precond, math::pcg::terminationDefaults()); CPPUNIT_ASSERT(result.success); CPPUNIT_ASSERT(result.iterations <= 20); CPPUNIT_ASSERT(x.eq(expected, 1.0e-5)); } void TestConjGradient::testIncompleteCholesky() { using namespace openvdb; typedef math::pcg::SparseStencilMatrix MatrixType; typedef math::pcg::IncompleteCholeskyPreconditioner CholeskyPrecond; const math::pcg::SizeType rows = 5; MatrixType A(5); A.setValue(0, 0, 24.0); A.setValue(0, 2, 6.0); A.setValue(1, 1, 8.0); A.setValue(1, 2, 2.0); A.setValue(2, 0, 6.0); A.setValue(2, 1, 2.0); A.setValue(2, 2, 8.0); A.setValue(2, 3, -6.0); A.setValue(2, 4, 2.0); A.setValue(3, 2, -6.0); A.setValue(3, 3, 24.0); A.setValue(4, 2, 2.0); A.setValue(4, 4, 8.0); CPPUNIT_ASSERT(A.isFinite()); CholeskyPrecond precond(A); { const CholeskyPrecond::TriangularMatrix lower = precond.lowerMatrix(); CholeskyPrecond::TriangularMatrix expected(5); expected.setValue(0, 0, 4.89898); expected.setValue(1, 1, 2.82843); expected.setValue(2, 0, 1.22474); expected.setValue(2, 1, 0.707107); expected.setValue(2, 2, 2.44949); expected.setValue(3, 2, -2.44949); expected.setValue(3, 3, 4.24264); expected.setValue(4, 2, 0.816497); expected.setValue(4, 4, 2.70801); #if 0 std::cout << "Expected:\n"; for (int i = 0; i < 5; ++i) { std::cout << " " << expected.getConstRow(i).str() << std::endl; } std::cout << "Actual:\n"; for (int i = 0; i < 5; ++i) { std::cout << " " << lower.getConstRow(i).str() << std::endl; } #endif CPPUNIT_ASSERT(lower.eq(expected, 1.0e-5)); } { const CholeskyPrecond::TriangularMatrix upper = precond.upperMatrix(); CholeskyPrecond::TriangularMatrix expected(5); { expected.setValue(0, 0, 4.89898); expected.setValue(0, 2, 1.22474); expected.setValue(1, 1, 2.82843); expected.setValue(1, 2, 0.707107); expected.setValue(2, 2, 2.44949); expected.setValue(2, 3, -2.44949); expected.setValue(2, 4, 0.816497); expected.setValue(3, 3, 4.24264); expected.setValue(4, 4, 2.70801); } #if 0 std::cout << "Expected:\n"; for (int i = 0; i < 5; ++i) { std::cout << " " << expected.getConstRow(i).str() << std::endl; } std::cout << "Actual:\n"; for (int i = 0; i < 5; ++i) { std::cout << " " << upper.getConstRow(i).str() << std::endl; } #endif CPPUNIT_ASSERT(upper.eq(expected, 1.0e-5)); } MatrixType::VectorType x(rows, 0.0), b(rows, 1.0), expected(rows); expected[0] = 0.0104167; expected[1] = 0.09375; expected[2] = 0.125; expected[3] = 0.0729167; expected[4] = 0.09375; // Solve A * x = b for x. math::pcg::State result = math::pcg::solve( A, b, x, precond, math::pcg::terminationDefaults()); CPPUNIT_ASSERT(result.success); CPPUNIT_ASSERT(result.iterations <= 20); CPPUNIT_ASSERT(x.eq(expected, 1.0e-5)); } void TestConjGradient::testVectorDotProduct() { using namespace openvdb; typedef math::pcg::Vector VectorType; // Test small vector - runs in series { const size_t length = 1000; VectorType aVec(length, 2.0); VectorType bVec(length, 3.0); VectorType::ValueType result = aVec.dot(bVec); CPPUNIT_ASSERT_DOUBLES_EQUAL(result, 6.0 * length, 1.0e-7); } // Test long vector - runs in parallel { const size_t length = 10034502; VectorType aVec(length, 2.0); VectorType bVec(length, 3.0); VectorType::ValueType result = aVec.dot(bVec); CPPUNIT_ASSERT_DOUBLES_EQUAL(result, 6.0 * length, 1.0e-7); } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000002110213200122377015463 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for remove() #include #define ASSERT_DOUBLES_EXACTLY_EQUAL(a, b) \ CPPUNIT_ASSERT_DOUBLES_EQUAL((a), (b), /*tolerance=*/0.0); class TestStream: public CppUnit::TestCase { public: void setUp() override; void tearDown() override; 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() != nullptr); CPPUNIT_ASSERT(meta.get() != nullptr); // 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() != nullptr); Int32Tree::Ptr density = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(density.get() != nullptr); grid.reset(); grid = findGridByName(*grids, "density_copy"); CPPUNIT_ASSERT(grid.get() != nullptr); CPPUNIT_ASSERT(gridPtrCast(grid)->treePtr().get() != nullptr); // 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() != nullptr); FloatTree::Ptr temperature = gridPtrCast(grid)->treePtr(); CPPUNIT_ASSERT(temperature.get() != nullptr); 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); SharedPtr 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"; SharedPtr 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() != nullptr); CPPUNIT_ASSERT(grids.get() != nullptr); CPPUNIT_ASSERT(!grids->empty()); verifyTestGrids(grids, meta); } // Copyright (c) 2012-2017 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/TestUtil.cc0000644000000000000000000006001013200122377015146 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 //#define BENCHMARK_PAGED_ARRAY // For benchmark comparisons #ifdef BENCHMARK_PAGED_ARRAY #include // for std::deque #include // for std::vector #include // for tbb::concurrent_vector #endif class TestUtil: public CppUnit::TestCase { public: CPPUNIT_TEST_SUITE(TestUtil); CPPUNIT_TEST(testCpuTimer); CPPUNIT_TEST(testPagedArray); CPPUNIT_TEST_SUITE_END(); void testCpuTimer(); void testPagedArray(); using RangeT = tbb::blocked_range; // Multi-threading ArrayT::push_back template struct ArrayPushBack { ArrayPushBack(ArrayT& array) : mArray(&array) {} void parallel(size_t size) {tbb::parallel_for(RangeT(size_t(0), size, mArray->pageSize()), *this);} void serial(size_t size) { (*this)(RangeT(size_t(0), size)); } void unsafe(size_t size) { for (size_t i=0; i!=size; ++i) mArray->push_back_unsafe(i); } void operator()(const RangeT& r) const { for (size_t i=r.begin(), n=r.end(); i!=n; ++i) mArray->push_back(i); } ArrayT* mArray; }; // Multi-threading ArrayT::ValueBuffer::push_back template struct BufferPushBack { BufferPushBack(ArrayT& array) : mBuffer(array) {} void parallel(size_t size) { tbb::parallel_for(RangeT(size_t(0), size, mBuffer.parent().pageSize()), *this); } void serial(size_t size) { (*this)(RangeT(size_t(0), size)); } void operator()(const RangeT& r) const { for (size_t i=r.begin(), n=r.end(); i!=n; ++i) mBuffer.push_back(i); } mutable typename ArrayT::ValueBuffer mBuffer;//local instance }; // Thread Local Storage version of BufferPushBack template struct TLS_BufferPushBack { using PoolT = tbb::enumerable_thread_specific; TLS_BufferPushBack(ArrayT &array) : mArray(&array), mPool(nullptr) {} void parallel(size_t size) { typename ArrayT::ValueBuffer exemplar(*mArray);//dummy used for initialization mPool = new PoolT(exemplar);//thread local storage pool of ValueBuffers tbb::parallel_for(RangeT(size_t(0), size, mArray->pageSize()), *this); for (auto i=mPool->begin(); i!=mPool->end(); ++i) i->flush(); delete mPool; } void operator()(const RangeT& r) const { typename PoolT::reference buffer = mPool->local(); for (size_t i=r.begin(), n=r.end(); i!=n; ++i) buffer.push_back(i); } ArrayT *mArray; PoolT *mPool; }; }; CPPUNIT_TEST_SUITE_REGISTRATION(TestUtil); void TestUtil::testCpuTimer() { const int expected = 259, tolerance = 20;//milliseconds const tbb::tick_count::interval_t sec(expected/1000.0); openvdb::util::CpuTimer timer; tbb::this_tbb_thread::sleep(sec); const int actual1 = static_cast(timer.delta()); CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual1, tolerance); tbb::this_tbb_thread::sleep(sec); const int actual2 = static_cast(timer.delta()); CPPUNIT_ASSERT_DOUBLES_EQUAL(2*expected, actual2, tolerance); } void TestUtil::testPagedArray() { #ifdef BENCHMARK_PAGED_ARRAY const size_t problemSize = 2560000; openvdb::util::CpuTimer timer; std::cerr << "\nProblem size for benchmark: " << problemSize << std::endl; #else const size_t problemSize = 256000; #endif {//serial PagedArray::push_back (check return value) openvdb::util::PagedArray d; CPPUNIT_ASSERT(d.isEmpty()); CPPUNIT_ASSERT_EQUAL(size_t(0), d.size()); CPPUNIT_ASSERT_EQUAL(size_t(8), d.log2PageSize()); CPPUNIT_ASSERT_EQUAL(size_t(1)<; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("1: Serial PagedArray::push_back with default page size"); #endif {// for some reason this: ArrayPushBack tmp(d); tmp.serial(problemSize); }// is faster than: //for (size_t i=0; i> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); for (size_t i=0, n=d.size(); i; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("2: Serial PagedArray::push_back_unsafe with default page size"); #endif {// for some reason this: ArrayPushBack tmp(d); tmp.unsafe(problemSize); }// is faster than: //openvdb::util::PagedArray d; //for (size_t i=0; i; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("3: Parallel PagedArray::push_back with default page size"); #endif //{// for some reason this: // ArrayPushBack tmp(d); // tmp.parallel(problemSize); //}// is faster than: tbb::parallel_for(tbb::blocked_range(0, problemSize, d.pageSize()), [&d](const tbb::blocked_range &range){ for (size_t i=range.begin(); i!=range.end(); ++i) d.push_back(i);}); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(size_t(10), d.log2PageSize()); CPPUNIT_ASSERT_EQUAL(size_t(1)<> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort with a default page size"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); } {//parallel PagedArray::push_back with page size of only 8 using ArrayT = openvdb::util::PagedArray; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("4: Parallel PagedArray::push_back with page size of only 8"); #endif {// for some reason this: ArrayPushBack tmp(d); tmp.parallel(problemSize); }// is faster than: //tbb::parallel_for(tbb::blocked_range(0, problemSize, d.pageSize()), // [&d](const tbb::blocked_range &range){ // for (size_t i=range.begin(); i!=range.end(); ++i) d.push_back(i);}); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(size_t(3), d.log2PageSize()); CPPUNIT_ASSERT_EQUAL(size_t(1)<> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort with a page size of only 8"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i> log2PageSize CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); } #ifdef BENCHMARK_PAGED_ARRAY {//benchmark against a std::vector timer.start("5: Serial std::vector::push_back"); std::vector v; for (size_t i=0; i d; for (size_t i=0; i d2; CPPUNIT_ASSERT_EQUAL(size_t(0), d2.size()); d2.resize(1234); CPPUNIT_ASSERT_EQUAL(size_t(1234), d2.size()); } {//benchmark against a tbb::concurrent_vector::push_back timer.start("7: Serial tbb::concurrent_vector::push_back"); tbb::concurrent_vector v; for (size_t i=0; i; tbb::parallel_for(tbb::blocked_range(0, problemSize, ArrayT::pageSize()), [&v](const tbb::blocked_range &range){ for (size_t i=range.begin(); i!=range.end(); ++i) v.push_back(i);}); timer.stop(); tbb::parallel_sort(v.begin(), v.end()); for (size_t i=0; i; ArrayT d; CPPUNIT_ASSERT_EQUAL(size_t(0), d.size()); d.resize(problemSize); CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(size_t(1)<> log2PageSize CPPUNIT_ASSERT_EQUAL((problemSize-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); d.clear(); CPPUNIT_ASSERT_EQUAL(size_t(0), d.size()); #ifdef BENCHMARK_PAGED_ARRAY timer.start("9: Serial PagedArray::ValueBuffer::push_back"); #endif {// for some reason this: BufferPushBack tmp(d); tmp.serial(problemSize); // is faster than: //ArrayT::ValueBuffer buffer(d); //for (size_t i=0, n=problemSize; i>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); } {//parallel PagedArray::ValueBuffer::push_back using ArrayT = openvdb::util::PagedArray; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("10: Parallel PagedArray::ValueBuffer::push_back"); #endif {// for some reason this: BufferPushBack tmp(d); tmp.parallel(problemSize); }// is faster than: //tbb::parallel_for(tbb::blocked_range(0, problemSize, d.pageSize()), // [&d](const tbb::blocked_range &r){ // typename ArrayT::ValueBuffer buffer(d); // for (size_t i=r.begin(), n=r.end(); i!=n; ++i) buffer.push_back(i);}); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(size_t(1)<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); // Test sorting #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0; i> log2PageSize CPPUNIT_ASSERT_EQUAL(size_t(1)+(problemSize>>d.log2PageSize()), d.pageCount()); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); // test PagedArray::fill const size_t v = 13; d.fill(v); for (size_t i=0, n=d.capacity(); i; ArrayT d; CPPUNIT_ASSERT_EQUAL(size_t(0), d.size()); { ArrayT::ValueBuffer vc(d); vc.push_back(1); vc.push_back(2); CPPUNIT_ASSERT_EQUAL(size_t(0), d.size()); vc.flush(); CPPUNIT_ASSERT_EQUAL(size_t(2), d.size()); CPPUNIT_ASSERT_EQUAL(size_t(1), d[0]); CPPUNIT_ASSERT_EQUAL(size_t(2), d[1]); } CPPUNIT_ASSERT_EQUAL(size_t(2), d.size()); CPPUNIT_ASSERT_EQUAL(size_t(1), d[0]); CPPUNIT_ASSERT_EQUAL(size_t(2), d[1]); } {//thread-local-storage PagedArray::ValueBuffer::push_back followed by parallel sort using ArrayT = openvdb::util::PagedArray; ArrayT d; #ifdef BENCHMARK_PAGED_ARRAY timer.start("11: Parallel TLS PagedArray::ValueBuffer::push_back"); #endif {// for some reason this: TLS_BufferPushBack tmp(d); tmp.parallel(problemSize); }// is faster than: //ArrayT::ValueBuffer exemplar(d);//dummy used for initialization ///tbb::enumerable_thread_specific pool(exemplar);//thread local storage pool of ValueBuffers //tbb::parallel_for(tbb::blocked_range(0, problemSize, d.pageSize()), // [&pool](const tbb::blocked_range &range){ // ArrayT::ValueBuffer &buffer = pool.local(); // for (size_t i=range.begin(); i!=range.end(); ++i) buffer.push_back(i);}); //for (auto i=pool.begin(); i!=pool.end(); ++i) i->flush(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif //std::cerr << "Number of threads for TLS = " << (buffer.end()-buffer.begin()) << std::endl; //d.print(); CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(size_t(1)<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); // Not guaranteed to pass //size_t unsorted = 0; //for (size_t i=0, n=d.size(); i 0 ); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i; ArrayT d, d2; tbb::parallel_for(tbb::blocked_range(0, problemSize, d.pageSize()), [&d](const tbb::blocked_range &range){ ArrayT::ValueBuffer buffer(d); for (size_t i=range.begin(); i!=range.end(); ++i) buffer.push_back(i);}); CPPUNIT_ASSERT_EQUAL(problemSize, d.size()); CPPUNIT_ASSERT_EQUAL(size_t(1)<>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d.pageCount()*d.pageSize(), d.capacity()); CPPUNIT_ASSERT(!d.isPartiallyFull()); d.push_back(problemSize); CPPUNIT_ASSERT(d.isPartiallyFull()); tbb::parallel_for(tbb::blocked_range(problemSize+1, 2*problemSize+1, d2.pageSize()), [&d2](const tbb::blocked_range &range){ ArrayT::ValueBuffer buffer(d2); for (size_t i=range.begin(); i!=range.end(); ++i) buffer.push_back(i);}); //for (size_t i=d.size(), n=i+problemSize; i>d2.log2PageSize(), d2.pageCount()-1); CPPUNIT_ASSERT_EQUAL(d2.pageCount()*d2.pageSize(), d2.capacity()); //d.print(); //d2.print(); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel PagedArray::merge"); #endif d.merge(d2); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif CPPUNIT_ASSERT(d.isPartiallyFull()); //d.print(); //d2.print(); CPPUNIT_ASSERT_EQUAL(2*problemSize+1, d.size()); CPPUNIT_ASSERT_EQUAL((d.size()-1)>>d.log2PageSize(), d.pageCount()-1); CPPUNIT_ASSERT_EQUAL(size_t(0), d2.size()); CPPUNIT_ASSERT_EQUAL(size_t(0), d2.pageCount()); #ifdef BENCHMARK_PAGED_ARRAY timer.start("parallel sort of merged array"); #endif d.sort(); #ifdef BENCHMARK_PAGED_ARRAY timer.stop(); #endif for (size_t i=0, n=d.size(); i array; for (int i=0; i<100000; ++i) array.push_back(i); for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } {//2A openvdb::util::PagedArray array; openvdb::util::PagedArray::ValueBuffer buffer(array); for (int i=0; i<100000; ++i) buffer.push_back(i); buffer.flush(); for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } {//2B openvdb::util::PagedArray array; {//local scope of a single thread openvdb::util::PagedArray::ValueBuffer buffer(array); for (int i=0; i<100000; ++i) buffer.push_back(i); } for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } {//3A openvdb::util::PagedArray array; array.resize(100000); for (int i=0; i<100000; ++i) array[i] = i; for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } {//3B using ArrayT = openvdb::util::PagedArray; ArrayT array; array.resize(100000); for (ArrayT::Iterator i=array.begin(); i!=array.end(); ++i) *i = int(i.pos()); for (int i=0; i<100000; ++i) CPPUNIT_ASSERT_EQUAL(i, array[i]); } } } // Copyright (c) 2012-2017 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.cc0000644000000000000000000001070213200122377016475 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.h0000644000000000000000000001540613200122377014221 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for math::Random01 #include // for pruneLevelSet #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 define 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]((p-center).length() - radius); if (dist <= 0) acc.setValue(xyz, true); } } } } // This method will soon be replaced by the one above!!!!! template 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); } // Generate random points by uniformly distributing points // on a unit-sphere. inline void genPoints(const int numPoints, std::vector& points) { // init openvdb::math::Random01 randNumber(0); const int n = int(std::sqrt(double(numPoints))); const double xScale = (2.0 * M_PI) / double(n); const double yScale = M_PI / double(n); double x, y, theta, phi; openvdb::Vec3R pos; points.reserve(n*n); // 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) pos[0] = std::sin(theta)*std::cos(phi); pos[1] = std::sin(theta)*std::sin(phi); pos[2] = std::cos(theta); points.push_back(pos); } } } // @todo makePlane } // namespace unittest_util #endif // OPENVDB_UNITTEST_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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/COPYRIGHT0000644000000000000000000000255013200122206012472 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/util/0000755000000000000000000000000013200122400012146 5ustar rootrootopenvdb/util/logging.h0000644000000000000000000002727513200122377013777 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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_LOGGING_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_LOGGING_HAS_BEEN_INCLUDED #ifdef OPENVDB_USE_LOG4CPLUS #include #include #include #include #include #include #include #include // for std::remove() #include // for ::strrchr() #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace logging { /// @brief Message severity level enum class Level { Debug = log4cplus::DEBUG_LOG_LEVEL, Info = log4cplus::INFO_LOG_LEVEL, Warn = log4cplus::WARN_LOG_LEVEL, Error = log4cplus::ERROR_LOG_LEVEL, Fatal = log4cplus::FATAL_LOG_LEVEL }; namespace internal { /// @brief log4cplus layout that outputs text in different colors /// for different log levels, using ANSI escape codes class ColoredPatternLayout: public log4cplus::PatternLayout { public: explicit ColoredPatternLayout(const std::string& progName_, bool useColor = true) : log4cplus::PatternLayout( progName_.empty() ? std::string{"%5p: %m%n"} : (progName_ + " %5p: %m%n")) , mUseColor(useColor) , mProgName(progName_) { } ~ColoredPatternLayout() override {} const std::string& progName() const { return mProgName; } void formatAndAppend(log4cplus::tostream& strm, const log4cplus::spi::InternalLoggingEvent& event) override { if (!mUseColor) { log4cplus::PatternLayout::formatAndAppend(strm, event); return; } log4cplus::tostringstream s; switch (event.getLogLevel()) { case log4cplus::DEBUG_LOG_LEVEL: s << "\033[32m"; break; // green case log4cplus::ERROR_LOG_LEVEL: case log4cplus::FATAL_LOG_LEVEL: s << "\033[31m"; break; // red case log4cplus::INFO_LOG_LEVEL: s << "\033[36m"; break; // cyan case log4cplus::WARN_LOG_LEVEL: s << "\033[35m"; break; // magenta } log4cplus::PatternLayout::formatAndAppend(s, event); strm << s.str() << "\033[0m" << std::flush; } // Disable deprecation warnings for std::auto_ptr. #if defined(__ICC) #pragma warning push #pragma warning disable:1478 #elif defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #if defined(LOG4CPLUS_VERSION) && defined(LOG4CPLUS_MAKE_VERSION) #if LOG4CPLUS_VERSION >= LOG4CPLUS_MAKE_VERSION(2, 0, 0) // In log4cplus 2.0.0, std::auto_ptr was replaced with std::unique_ptr. using Ptr = std::unique_ptr; #else using Ptr = std::auto_ptr; #endif #else using Ptr = std::auto_ptr; #endif static Ptr create(const std::string& progName_, bool useColor = true) { return Ptr{new ColoredPatternLayout{progName_, useColor}}; } #if defined(__ICC) #pragma warning pop #elif defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif private: bool mUseColor = true; std::string mProgName; }; // class ColoredPatternLayout inline log4cplus::Logger getLogger() { return log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("openvdb")); } inline log4cplus::SharedAppenderPtr getAppender() { return getLogger().getAppender(LOG4CPLUS_TEXT("OPENVDB")); } } // namespace internal /// @brief Return the current logging level. inline Level getLevel() { switch (internal::getLogger().getLogLevel()) { case log4cplus::DEBUG_LOG_LEVEL: return Level::Debug; case log4cplus::INFO_LOG_LEVEL: return Level::Info; case log4cplus::WARN_LOG_LEVEL: return Level::Warn; case log4cplus::ERROR_LOG_LEVEL: return Level::Error; case log4cplus::FATAL_LOG_LEVEL: break; } return Level::Fatal; } /// @brief Set the logging level. (Lower-level messages will be suppressed.) inline void setLevel(Level lvl) { internal::getLogger().setLogLevel(static_cast(lvl)); } /// @brief If "-debug", "-info", "-warn", "-error" or "-fatal" is found /// in the given array of command-line arguments, set the logging level /// appropriately and remove the relevant argument(s) from the array. inline void setLevel(int& argc, char* argv[]) { for (int i = 1; i < argc; ++i) { // note: skip argv[0] const std::string arg{argv[i]}; bool remove = true; if (arg == "-debug") { setLevel(Level::Debug); } else if (arg == "-error") { setLevel(Level::Error); } else if (arg == "-fatal") { setLevel(Level::Fatal); } else if (arg == "-info") { setLevel(Level::Info); } else if (arg == "-warn") { setLevel(Level::Warn); } else { remove = false; } if (remove) argv[i] = nullptr; } auto end = std::remove(argv + 1, argv + argc, nullptr); argc = static_cast(end - argv); } /// @brief Specify a program name to be displayed in log messages. inline void setProgramName(const std::string& progName, bool useColor = true) { // Change the layout of the OpenVDB appender to use colored text // and to incorporate the supplied program name. if (auto appender = internal::getAppender()) { appender->setLayout(internal::ColoredPatternLayout::create(progName, useColor)); } } /// @brief Initialize the logging system if it is not already initialized. inline void initialize(bool useColor = true) { log4cplus::initialize(); if (internal::getAppender()) return; // already initialized // Create the OpenVDB logger if it doesn't already exist. auto logger = internal::getLogger(); // Disable "additivity", so that OpenVDB-related messages are directed // to the OpenVDB logger only and are not forwarded up the logger tree. logger.setAdditivity(false); // Attach a console appender to the OpenVDB logger. if (auto appender = log4cplus::SharedAppenderPtr{new log4cplus::ConsoleAppender}) { appender->setName(LOG4CPLUS_TEXT("OPENVDB")); logger.addAppender(appender); } setLevel(Level::Warn); setProgramName("", useColor); } /// @brief Initialize the logging system from command-line arguments. /// @details If "-debug", "-info", "-warn", "-error" or "-fatal" is found /// in the given array of command-line arguments, set the logging level /// appropriately and remove the relevant argument(s) from the array. inline void initialize(int& argc, char* argv[], bool useColor = true) { initialize(); setLevel(argc, argv); auto progName = (argc > 0 ? argv[0] : ""); if (const char* ptr = ::strrchr(progName, '/')) progName = ptr + 1; setProgramName(progName, useColor); } } // namespace logging } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #define OPENVDB_LOG(level, message) \ do { \ auto _log = openvdb::logging::internal::getLogger(); \ if (_log.isEnabledFor(log4cplus::level##_LOG_LEVEL)) { \ std::ostringstream _buf; \ _buf << message; \ _log.forcedLog(log4cplus::level##_LOG_LEVEL, _buf.str(), __FILE__, __LINE__); \ } \ } while (0); /// Log an info message of the form 'someVar << "some text" << ...'. #define OPENVDB_LOG_INFO(message) OPENVDB_LOG(INFO, message) /// Log a warning message of the form 'someVar << "some text" << ...'. #define OPENVDB_LOG_WARN(message) OPENVDB_LOG(WARN, message) /// Log an error message of the form 'someVar << "some text" << ...'. #define OPENVDB_LOG_ERROR(message) OPENVDB_LOG(ERROR, message) /// Log a fatal error message of the form 'someVar << "some text" << ...'. #define OPENVDB_LOG_FATAL(message) OPENVDB_LOG(FATAL, message) #ifdef DEBUG /// In debug builds only, log a debugging message of the form 'someVar << "text" << ...'. #define OPENVDB_LOG_DEBUG(message) OPENVDB_LOG(DEBUG, message) #else /// In debug builds only, log a debugging message of the form 'someVar << "text" << ...'. #define OPENVDB_LOG_DEBUG(message) #endif /// @brief Log a debugging message in both debug and optimized builds. /// @warning Don't use this in performance-critical code. #define OPENVDB_LOG_DEBUG_RUNTIME(message) OPENVDB_LOG(DEBUG, message) #else // ifdef OPENVDB_USE_LOG4CPLUS #include #define OPENVDB_LOG_INFO(mesg) #define OPENVDB_LOG_WARN(mesg) do { std::cerr << "WARNING: " << mesg << std::endl; } while (0); #define OPENVDB_LOG_ERROR(mesg) do { std::cerr << "ERROR: " << mesg << std::endl; } while (0); #define OPENVDB_LOG_FATAL(mesg) do { std::cerr << "FATAL: " << mesg << std::endl; } while (0); #define OPENVDB_LOG_DEBUG(mesg) #define OPENVDB_LOG_DEBUG_RUNTIME(mesg) namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace logging { enum class Level { Debug, Info, Warn, Error, Fatal }; inline Level getLevel() { return Level::Warn; } inline void setLevel(Level) {} inline void setLevel(int&, char*[]) {} inline void setProgramName(const std::string&, bool = true) {} inline void initialize() {} inline void initialize(int&, char*[], bool = true) {} } // namespace logging } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_USE_LOG4CPLUS namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace logging { /// @brief A LevelScope object sets the logging level to a given level /// and restores it to the current level when the object goes out of scope. struct LevelScope { Level level; explicit LevelScope(Level newLevel): level(getLevel()) { setLevel(newLevel); } ~LevelScope() { setLevel(level); } }; } // namespace logging } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_LOGGING_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.cc0000644000000000000000000001046413200122377014112 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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) << (double(bytes) / double(one << 40)) << " TB"; group = 4; } else if (bytes >> 30) { ostr << std::setw(width) << (double(bytes) / double(one << 30)) << " GB"; group = 3; } else if (bytes >> 20) { ostr << std::setw(width) << (double(bytes) / double(one << 20)) << " MB"; group = 2; } else if (bytes >> 10) { ostr << std::setw(width) << (double(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) << (double(number) / 1000000000000.0) << " trillion"; group = 4; } else if (number / UINT64_C(1000000000)) { ostr << std::setw(width) << (double(number) / 1000000000.0) << " billion"; group = 3; } else if (number / UINT64_C(1000000)) { ostr << std::setw(width) << (double(number) / 1000000.0) << " million"; group = 2; } else if (number / UINT64_C(1000)) { ostr << std::setw(width) << (double(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-2017 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.h0000644000000000000000000002767513200122377014113 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #include // for std::min(), std::max() #include #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 /// (x=0, z=−1/g) with the circle /// (xxo)² + (zzo)² = r² /// 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())); extreme.x() = xm; extreme.y() = centerLS.y(); extreme.z() = zm; // location in world space of the tangent point extreme2 = secondMap.applyMap(extreme); // 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())); extreme.x() = centerLS.x(); extreme.y() = xm; extreme.z() = zm; extreme2 = secondMap.applyMap(extreme); // 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-2017 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/PagedArray.h0000644000000000000000000007746613200122377014377 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 PagedArray.h /// /// @author Ken Museth /// /// @brief Concurrent, page-based, dynamically-sized linear data /// structure with O(1) random access and STL-compliant /// iterators. It is primarily intended for applications /// that involve multi-threading push_back of (a possibly /// unkown number of) elements into a dynamically growing /// linear array, and fast random access to said elements. #ifndef OPENVDB_UTIL_PAGED_ARRAY_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_PAGED_ARRAY_HAS_BEEN_INCLUDED #include #include #include #include // std::swap #include #include #include #include namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { //////////////////////////////////////// /// @brief Concurrent, page-based, dynamically-sized linear data structure /// with O(1) random access and STL-compliant iterators. It is /// primarily intended for applications that concurrently insert /// (a possibly unkown number of) elements into a dynamically /// growing linear array, and fast random access to said elements. /// /// @note Multiple threads can grow the page-table and push_back /// new elements concurrently. A ValueBuffer provides accelerated /// and threadsafe push_back at the cost of potentially re-ordering /// elements (when multiple instances are used). /// /// @details This data structure employes contiguous pages of elements /// (like a std::deque) which avoids moving data when the /// capacity is out-grown and new pages are allocated. The /// size of the pages can be controlled with the Log2PageSize /// template parameter (defaults to 1024 elements of type ValueT). /// The TableT template parameter is used to define the data /// structure for the page table. The default, std::vector, /// offers fast random access in exchange for slower /// push_back, whereas std:deque offers faster push_back but /// slower random access. /// /// There are three fundamentally different ways to insert elements to /// this container - each with different advanteges and disadvanteges. /// /// The simplest way to insert elements is to use PagedArray::push_back e.g. /// @code /// PagedArray array; /// for (size_t i=0; i<100000; ++i) array.push_back(i); /// @endcode /// or with TBB task-based multi-threading /// @code /// PagedArray array; /// tbb::parallel_for( /// tbb::blocked_range(0, 10000, array.pageSize()), /// [&array](const tbb::blocked_range& range) { /// for (size_t i=range.begin(); i!=range.end(); ++i) array.push_back(i); /// } /// ); /// @endcode /// PagedArray::push_back has the advantage that it's thread-safe and /// preserves the ordering of the inserted elements. In fact it returns /// the linear offset to the added element which can then be used for /// fast O(1) random access. The disadvantage is it's the slowest of /// the three different ways of inserting elements. /// /// The fastest way (by far) to insert elements by means of a PagedArray::ValueBuffer, e.g. /// @code /// PagedArray array; /// PagedArray::ValueBuffer buffer(array); /// for (size_t i=0; i<100000; ++i) buffer.push_back(i); /// buffer.flush(); /// @endcode /// or /// @code /// PagedArray array; /// { /// //local scope of a single thread /// PagedArray::ValueBuffer buffer(array); /// for (size_t i=0; i<100000; ++i) buffer.push_back(i); /// } /// @endcode /// or with TBB task-based multi-threading /// @code /// PagedArray array; /// tbb::parallel_for( /// tbb::blocked_range(0, 10000, array.pageSize()), /// [&array](const tbb::blocked_range& range) { /// PagedArray::ValueBuffer buffer(array); /// for (size_t i=range.begin(); i!=range.end(); ++i) buffer.push_back(i); /// } /// ); /// @endcode /// or with TBB thread-local storage for even better performance (due /// to fewer concurrent instantiations of partially full ValueBuffers) /// @code /// PagedArray array; /// PagedArray::ValueBuffer exemplar(array);//dummy used for initialization /// tbb::enumerable_thread_specific::ValueBuffer> /// pool(exemplar);//thread local storage pool of ValueBuffers /// tbb::parallel_for( /// tbb::blocked_range(0, 10000, array.pageSize()), /// [&pool](const tbb::blocked_range& range) { /// PagedArray::ValueBuffer &buffer = pool.local(); /// for (size_t i=range.begin(); i!=range.end(); ++i) buffer.push_back(i); /// } /// ); /// for (auto i=pool.begin(); i!=pool.end(); ++i) i->flush(); /// @endcode /// This technique generally outperforms PagedArray::push_back, /// std::vector::push_back, std::deque::push_back and even /// tbb::concurrent_vector::push_back. Additionally it /// is thread-safe as long as each thread has it's own instance of a /// PagedArray::ValueBuffer. The only disadvantage is the ordering of /// the elements is undefined if multiple instance of a /// PagedArray::ValueBuffer are employed. This is typically the case /// in the context of multi-threading, where the /// ordering of inserts are undefined anyway. Note that a local scope /// can be used to guarentee that the ValueBuffer has inserted all its /// elements by the time the scope ends. Alternatively the ValueBuffer /// can be explicitly flushed by calling ValueBuffer::flush. /// /// The third way to insert elements is to resize the container and use /// random access, e.g. /// @code /// PagedArray array; /// array.resize(100000); /// for (int i=0; i<100000; ++i) array[i] = i; /// @endcode /// or in terms of the random access iterator /// @code /// PagedArray array; /// array.resize(100000); /// for (auto i=array.begin(); i!=array.end(); ++i) *i = i.pos(); /// @endcode /// While this approach is both fast and thread-safe it suffers from the /// major disadvantage that the problem size, i.e. number of elements, needs to /// be known in advance. If that's the case you might as well consider /// using std::vector or a raw c-style array! In other words the /// PagedArray is most useful in the context of applications that /// involve multi-threading of dynamically growing linear arrays that /// require fast random access. template class TableT = std::vector> class PagedArray { private: class Page; #if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 1700 // Workaround for ICC 15/16 "too few arguments to template" bug (fixed in ICC 17) using PageTableT = TableT>; #else using PageTableT = TableT; #endif public: using ValueType = ValueT; /// @brief Default constructor PagedArray() = default; /// @brief Destructor removed all allocated pages ~PagedArray() { this->clear(); } // Disallow copy construction and assignment PagedArray(const PagedArray&) = delete; PagedArray& operator=(const PagedArray&) = delete; /// @brief Caches values into a local memory Page to improve /// performance of push_back into a PagedArray. /// /// @note The ordering of inserted elements is undefined when /// multiple ValueBuffers are used! /// /// @warning By design this ValueBuffer is not threadsafe so /// make sure to create an instance per thread! class ValueBuffer; /// Const std-compliant iterator class ConstIterator; /// Non-const std-compliant iterator class Iterator; /// @brief Thread safe insertion, adds a new element at /// the end and increases the container size by one and /// returns the linear offset for the inserted element. /// /// @param value value to be added to this PagedArray /// /// @details Constant time complexity. May allocate a new page. size_t push_back(const ValueType& value) { const size_t index = mSize.fetch_and_increment(); if (index >= mCapacity) this->grow(index); (*mPageTable[index >> Log2PageSize])[index] = value; return index; } /// @brief Slightly faster than the thread-safe push_back above. /// /// @param value value to be added to this PagedArray /// /// @note For best performance consider using the ValueBuffer! /// /// @warning Not thread-safe! size_t push_back_unsafe(const ValueType& value) { const size_t index = mSize.fetch_and_increment(); if (index >= mCapacity) { mPageTable.push_back( new Page() ); mCapacity += Page::Size; } (*mPageTable[index >> Log2PageSize])[index] = value; return index; } /// @brief Reduce the page table to fix the current size. /// /// @warning Not thread-safe! void shrink_to_fit(); /// @brief Return a reference to the value at the specified offset /// /// @param i linear offset of the value to be accessed. /// /// @note This random access has constant time complexity. /// /// @warning It is assumed that the i'th element is already allocated! ValueType& operator[](size_t i) { assert(i>Log2PageSize])[i]; } /// @brief Return a const-reference to the value at the specified offset /// /// @param i linear offset of the value to be accessed. /// /// @note This random access has constant time complexity. /// /// @warning It is assumed that the i'th element is already allocated! const ValueType& operator[](size_t i) const { assert(i>Log2PageSize])[i]; } /// @brief Set all elements in the page table to the specified value /// /// @param v value to be filled in all the existing pages of this PagedArray. /// /// @note Multi-threaded void fill(const ValueType& v) { auto op = [&](const tbb::blocked_range& r){ for(size_t i=r.begin(); i!=r.end(); ++i) mPageTable[i]->fill(v); }; tbb::parallel_for(tbb::blocked_range(0, this->pageCount()), op); } /// @brief Copy the first @a count values in this PageArray into /// a raw c-style array, assuming it to be at least @a count /// elements long. /// /// @param p pointer to an array that will used as the destination of the copy. /// @param count number of elements to be copied. /// bool copy(ValueType *p, size_t count) const { size_t last_page = count >> Log2PageSize; if (last_page >= this->pageCount()) return false; auto op = [&](const tbb::blocked_range& r){ for (size_t i=r.begin(); i!=r.end(); ++i) { mPageTable[i]->copy(p+i*Page::Size, Page::Size); } }; if (size_t m = count & Page::Mask) {//count is not divisible by page size tbb::parallel_for(tbb::blocked_range(0, last_page, 32), op); mPageTable[last_page]->copy(p+last_page*Page::Size, m); } else { tbb::parallel_for(tbb::blocked_range(0, last_page+1, 32), op); } return true; } void copy(ValueType *p) const { this->copy(p, mSize); } /// @brief Resize this array to the specified size. /// /// @param size number of elements that this PageArray will contain. /// /// @details Will grow or shrink the page table to contain /// the specified number of elements. It will affect the size(), /// iteration will go over all those elements, push_back will /// insert after them and operator[] can be used directly access /// them. /// /// @note No reserve method is implemented due to efficiency concerns /// (especially for the ValueBuffer) from having to deal with empty pages. /// /// @warning Not thread-safe! void resize(size_t size) { mSize = size; if (size > mCapacity) { this->grow(size-1); } else { this->shrink_to_fit(); } } /// @brief Resize this array to the specified size and initialize /// all values to @a v. /// /// @param size number of elements that this PageArray will contain. /// @param v value of all the @a size values. /// /// @details Will grow or shrink the page table to contain /// the specified number of elements. It will affect the size(), /// iteration will go over all those elements, push_back will /// insert after them and operator[] can be used directly access them. /// /// @note No reserve method is implemented due to efficiency concerns /// (especially for the ValueBuffer) from having to deal with empty pages. /// /// @warning Not thread-safe! void resize(size_t size, const ValueType& v) { this->resize(size); this->fill(v); } /// @brief Return the number of elements in this array. size_t size() const { return mSize; } /// @brief Return the maximum number of elements that this array /// can contain without allocating more memory pages. size_t capacity() const { return mCapacity; } /// @brief Return the number of additional elements that can be /// added to this array without allocating more memory pages. size_t freeCount() const { return mCapacity - mSize; } /// @brief Return the number of allocated memory pages. size_t pageCount() const { return mPageTable.size(); } /// @brief Return the number of elements per memory page. static size_t pageSize() { return Page::Size; } /// @brief Return log2 of the number of elements per memory page. static size_t log2PageSize() { return Log2PageSize; } /// @brief Return the memory footprint of this array in bytes. size_t memUsage() const { return sizeof(*this) + mPageTable.size() * Page::memUsage(); } /// @brief Return true if the container contains no elements. bool isEmpty() const { return mSize == 0; } /// @brief Return true if the page table is partially full, i.e. the /// last non-empty page contains less than pageSize() elements. /// /// @details When the page table is partially full calling merge() /// or using a ValueBuffer will rearrange the ordering of /// existing elements. bool isPartiallyFull() const { return (mSize & Page::Mask) > 0; } /// @brief Removes all elements from the array and delete all pages. /// /// @warning Not thread-safe! void clear() { for (size_t i=0, n=mPageTable.size(); ibegin(), this->end(), std::less() ); } /// @brief Parallel sort of all the elements in descending order. void invSort() { tbb::parallel_sort(this->begin(), this->end(), std::greater()); } //@{ /// @brief Parallel sort of all the elements based on a custom /// functor with the api: /// @code bool operator()(const ValueT& a, const ValueT& b) @endcode /// which returns true if a comes before b. template void sort(Functor func) { tbb::parallel_sort(this->begin(), this->end(), func ); } //@} /// @brief Transfer all the elements (and pages) from the other array to this array. /// /// @param other non-const reference to the PagedArray that will be merged into this PagedArray. /// /// @note The other PagedArray is empty on return. /// /// @warning The ordering of elements is undefined if this page table is partially full! void merge(PagedArray& other); /// @brief Print information for debugging void print(std::ostream& os = std::cout) const { os << "PagedArray:\n" << "\tSize: " << this->size() << " elements\n" << "\tPage table: " << this->pageCount() << " pages\n" << "\tPage size: " << this->pageSize() << " elements\n" << "\tCapacity: " << this->capacity() << " elements\n" << "\tFootprint: " << this->memUsage() << " bytes\n"; } private: friend class ValueBuffer; void grow(size_t index) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); while(index >= mCapacity) { mPageTable.push_back( new Page() ); mCapacity += Page::Size; } } void add_full(Page*& page, size_t size); void add_partially_full(Page*& page, size_t size); void add(Page*& page, size_t size) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); if (size == Page::Size) {//page is full this->add_full(page, size); } else if (size>0) {//page is only partially full this->add_partially_full(page, size); } } PageTableT mPageTable;//holds points to allocated pages tbb::atomic mSize{0};// current number of elements in array size_t mCapacity = 0;//capacity of array given the current page count tbb::spin_mutex mGrowthMutex;//Mutex-lock required to grow pages }; // Public class PagedArray //////////////////////////////////////////////////////////////////////////////// template class TableT> void PagedArray::shrink_to_fit() { if (mPageTable.size() > (mSize >> Log2PageSize) + 1) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); const size_t pageCount = (mSize >> Log2PageSize) + 1; if (mPageTable.size() > pageCount) { delete mPageTable.back(); mPageTable.pop_back(); mCapacity -= Page::Size; } } } template class TableT> void PagedArray::merge(PagedArray& other) { if (&other != this && !other.isEmpty()) { tbb::spin_mutex::scoped_lock lock(mGrowthMutex); // extract last partially full page if it exists Page* page = nullptr; const size_t size = mSize & Page::Mask; //number of elements in the last page if ( size > 0 ) { page = mPageTable.back(); mPageTable.pop_back(); mSize -= size; } // transfer all pages from the other page table mPageTable.insert(mPageTable.end(), other.mPageTable.begin(), other.mPageTable.end()); mSize += other.mSize; mCapacity = Page::Size*mPageTable.size(); other.mSize = 0; other.mCapacity = 0; PageTableT().swap(other.mPageTable); // add back last partially full page if (page) this->add_partially_full(page, size); } } template class TableT> void PagedArray::add_full(Page*& page, size_t size) { assert(size == Page::Size);//page must be full if (mSize & Page::Mask) {//page-table is partially full Page*& tmp = mPageTable.back(); std::swap(tmp, page);//swap last table entry with page } mPageTable.push_back(page); mCapacity += Page::Size; mSize += size; page = nullptr; } template class TableT> void PagedArray::add_partially_full(Page*& page, size_t size) { assert(size > 0 && size < Page::Size);//page must be partially full if (size_t m = mSize & Page::Mask) {//page table is also partially full ValueT *s = page->data(), *t = mPageTable.back()->data() + m; for (size_t i=std::min(mSize+size, mCapacity)-mSize; i; --i) *t++ = *s++; if (mSize+size > mCapacity) {//grow page table mPageTable.push_back( new Page() ); t = mPageTable.back()->data(); for (size_t i=mSize+size-mCapacity; i; --i) *t++ = *s++; mCapacity += Page::Size; } } else {//page table is full so simply append page mPageTable.push_back( page ); mCapacity += Page::Size; page = nullptr; } mSize += size; } //////////////////////////////////////////////////////////////////////////////// // Public member-class of PagedArray template class TableT> class PagedArray:: ValueBuffer { public: using PagedArrayType = PagedArray; /// @brief Constructor from a PageArray ValueBuffer(PagedArray& parent) : mParent(&parent), mPage(new Page()), mSize(0) {} /// @warning This copy-constructor is shallow in the sense that no /// elements are copied, i.e. size = 0. ValueBuffer(const ValueBuffer& other) : mParent(other.mParent), mPage(new Page()), mSize(0) {} /// @brief Destructor that transfers an buffered values to the parent PagedArray. ~ValueBuffer() { mParent->add(mPage, mSize); delete mPage; } ValueBuffer& operator=(const ValueBuffer&) = delete;// disallow copy assignment /// @brief Add a value to the buffer and increment the size. /// /// @details If the internal memory page is full it will /// automaically flush the page to the parent PagedArray. void push_back(const ValueT& v) { (*mPage)[mSize++] = v; if (mSize == Page::Size) this->flush(); } /// @brief Manually transfers the values in this buffer to the parent PagedArray. /// /// @note This method is also called by the destructor and /// push_back so it should only be called if one manually wants to /// sync up the buffer with the array, e.g. during debugging. void flush() { mParent->add(mPage, mSize); if (mPage == nullptr) mPage = new Page(); mSize = 0; } /// @brief Return a reference to the parent PagedArray PagedArrayType& parent() const { return *mParent; } /// @brief Return the current number of elements cached in this buffer. size_t size() const { return mSize; } private: PagedArray* mParent; Page* mPage; size_t mSize; };// Public class PagedArray::ValueBuffer //////////////////////////////////////////////////////////////////////////////// // Const std-compliant iterator // Public member-class of PagedArray template class TableT> class PagedArray:: ConstIterator : public std::iterator { public: using BaseT = std::iterator; using difference_type = typename BaseT::difference_type; // constructors and assignment ConstIterator() : mPos(0), mParent(nullptr) {} ConstIterator(const PagedArray& parent, size_t pos=0) : mPos(pos), mParent(&parent) {} ConstIterator(const ConstIterator& other) : mPos(other.mPos), mParent(other.mParent) {} ConstIterator& operator=(const ConstIterator& other) { mPos=other.mPos; mParent=other.mParent; return *this; } // prefix ConstIterator& operator++() { ++mPos; return *this; } ConstIterator& operator--() { --mPos; return *this; } // postfix ConstIterator operator++(int) { ConstIterator tmp(*this); ++mPos; return tmp; } ConstIterator operator--(int) { ConstIterator tmp(*this); --mPos; return tmp; } // value access const ValueT& operator*() const { return (*mParent)[mPos]; } const ValueT* operator->() const { return &(this->operator*()); } const ValueT& operator[](const difference_type& pos) const { return (*mParent)[mPos+pos]; } // offset ConstIterator& operator+=(const difference_type& pos) { mPos += pos; return *this; } ConstIterator& operator-=(const difference_type& pos) { mPos -= pos; return *this; } ConstIterator operator+(const difference_type &pos) const { return Iterator(*mParent,mPos+pos); } ConstIterator operator-(const difference_type &pos) const { return Iterator(*mParent,mPos-pos); } difference_type operator-(const ConstIterator& other) const { return mPos - other.pos(); } // comparisons bool operator==(const ConstIterator& other) const { return mPos == other.mPos; } bool operator!=(const ConstIterator& other) const { return mPos != other.mPos; } bool operator>=(const ConstIterator& other) const { return mPos >= other.mPos; } bool operator<=(const ConstIterator& other) const { return mPos <= other.mPos; } bool operator< (const ConstIterator& other) const { return mPos < other.mPos; } bool operator> (const ConstIterator& other) const { return mPos > other.mPos; } // non-std methods bool isValid() const { return mParent != nullptr && mPos < mParent->size(); } size_t pos() const { return mPos; } private: size_t mPos; const PagedArray* mParent; };// Public class PagedArray::ConstIterator //////////////////////////////////////////////////////////////////////////////// // Non-const std-compliant iterator // Public member-class of PagedArray template class TableT> class PagedArray:: Iterator : public std::iterator { public: using BaseT = std::iterator; using difference_type = typename BaseT::difference_type; // constructors and assignment Iterator() : mPos(0), mParent(nullptr) {} Iterator(PagedArray& parent, size_t pos=0) : mPos(pos), mParent(&parent) {} Iterator(const Iterator& other) : mPos(other.mPos), mParent(other.mParent) {} Iterator& operator=(const Iterator& other) { mPos=other.mPos; mParent=other.mParent; return *this; } // prefix Iterator& operator++() { ++mPos; return *this; } Iterator& operator--() { --mPos; return *this; } // postfix Iterator operator++(int) { Iterator tmp(*this); ++mPos; return tmp; } Iterator operator--(int) { Iterator tmp(*this); --mPos; return tmp; } // value access ValueT& operator*() const { return (*mParent)[mPos]; } ValueT* operator->() const { return &(this->operator*()); } ValueT& operator[](const difference_type& pos) const { return (*mParent)[mPos+pos]; } // offset Iterator& operator+=(const difference_type& pos) { mPos += pos; return *this; } Iterator& operator-=(const difference_type& pos) { mPos -= pos; return *this; } Iterator operator+(const difference_type &pos) const { return Iterator(*mParent, mPos+pos); } Iterator operator-(const difference_type &pos) const { return Iterator(*mParent, mPos-pos); } difference_type operator-(const Iterator& other) const { return mPos - other.pos(); } // comparisons bool operator==(const Iterator& other) const { return mPos == other.mPos; } bool operator!=(const Iterator& other) const { return mPos != other.mPos; } bool operator>=(const Iterator& other) const { return mPos >= other.mPos; } bool operator<=(const Iterator& other) const { return mPos <= other.mPos; } bool operator< (const Iterator& other) const { return mPos < other.mPos; } bool operator> (const Iterator& other) const { return mPos > other.mPos; } // non-std methods bool isValid() const { return mParent != nullptr && mPos < mParent->size(); } size_t pos() const { return mPos; } private: size_t mPos; PagedArray* mParent; };// Public class PagedArray::Iterator //////////////////////////////////////////////////////////////////////////////// // Private member-class of PagedArray implementing a memory page template class TableT> class PagedArray:: Page { public: static const size_t Size = 1UL << Log2PageSize; static const size_t Mask = Size - 1UL; static size_t memUsage() { return sizeof(ValueT)*Size; } // Raw memory allocation without any initialization Page() : mData(reinterpret_cast(new char[sizeof(ValueT)*Size])) {} ~Page() { delete [] mData; } Page(const Page&) = delete;//copy construction is not implemented Page& operator=(const Page&) = delete;//copy assignment is not implemented ValueT& operator[](const size_t i) { return mData[i & Mask]; } const ValueT& operator[](const size_t i) const { return mData[i & Mask]; } void fill(const ValueT& v) { ValueT* dst = mData; for (size_t i=Size; i; --i) *dst++ = v; } ValueT* data() { return mData; } // Copy the first n elements of this Page to dst (which is assumed to large // enough to hold the n elements). void copy(ValueType *dst, size_t n) const { const ValueT* src = mData; for (size_t i=n; i; --i) *dst++ = *src++; } protected: ValueT* mData; };// Private class PagedArray::Page //////////////////////////////////////////////////////////////////////////////// } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_PAGED_ARRAY_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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/CpuTimer.h0000644000000000000000000001022513200122377014064 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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_CPUTIMER_HAS_BEEN_INCLUDED #define OPENVDB_UTIL_CPUTIMER_HAS_BEEN_INCLUDED #include #include #include // for ostringstream #include //for setprecision namespace openvdb { OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace util { /// @brief Simple timer for basic profiling. /// /// @code /// CpuTimer timer; /// // code here will not be timed! /// timer.start("algorithm"); /// // code to be timed goes here /// timer.stop(); /// @endcode /// /// or to time multiple blocks of code /// /// @code /// CpuTimer timer("algorithm 1"); /// // code to be timed goes here /// timer.restart("algorithm 2"); /// // code to be timed goes here /// timer.stop(); /// @endcode class CpuTimer { public: /// @brief Initiate timer CpuTimer() : mT0(tbb::tick_count::now()) {} /// @brief Prints message and re-start timer. /// /// @note Should normally be followed by a call to stop() CpuTimer(const std::string& msg) { this->start(msg); } /// @brief Start timer. /// /// @note Should normally be followed by a call to time() inline void start() { mT0 = tbb::tick_count::now(); } /// @brief Print message and re-start timer. /// /// @note Should normally be followed by a call to stop() inline void start(const std::string& msg) { std::cerr << msg << " ... "; this->start(); } /// @brief Stop previous timer, print message and re-start timer. /// /// @note Should normally be followed by a call to stop() inline void restart(const std::string& msg) { this->stop(); this->start(msg); } /// Return Time diference in milliseconds since construction or start was called. inline double delta() const { tbb::tick_count::interval_t dt = tbb::tick_count::now() - mT0; return 1000.0*dt.seconds(); } /// @brief Print time in milliseconds since construction or start was called. inline void stop() const { const double t = this->delta(); std::ostringstream ostr; ostr << "completed in " << std::setprecision(3) << t << " ms\n"; std::cerr << ostr.str(); } private: tbb::tick_count mT0; };// CpuTimer } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_CPUTIMER_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000001004713200122377015514 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.cc0000644000000000000000000000544613200122377013420 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.h0000644000000000000000000001245613200122377013757 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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-2017 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.h0000644000000000000000000001363313200122377013257 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 #include // for tree::pruneInactive 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); tools::pruneInactive(*topologyTree, threaded); 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); tools::pruneInactive(*topologyTree, threaded); return topologyTree; } } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_UTIL_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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.h0000644000000000000000000000512613200122377013220 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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, ' '); if (size>0) 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-2017 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.h0000644000000000000000000014146713200122377014235 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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 // for std::min() #include #include #include // for cout #include #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: #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif /// @todo Move this table and others into, say, Util.cc const Byte numBits[256] = { #define COUNTONB2(n) n, n+1, n+1, n+2 #define COUNTONB4(n) COUNTONB2(n), COUNTONB2(n+1), COUNTONB2(n+1), COUNTONB2(n+2) #define COUNTONB6(n) COUNTONB4(n), COUNTONB4(n+1), COUNTONB4(n+1), COUNTONB4(n+2) COUNTONB6(0), COUNTONB6(1), COUNTONB6(1), COUNTONB6(2) }; return numBits[v]; #undef COUNTONB6 #undef COUNTONB4 #undef COUNTONB2 // 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(static_cast(~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 static_cast( (((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); #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif 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); #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif 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); #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif 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) { #ifndef _MSC_VER // Visual C++ doesn't guarantee thread-safe initialization of local statics static #endif 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(nullptr) {} BaseMaskIterator(const BaseMaskIterator&) = default; BaseMaskIterator(Index32 pos, const NodeMask* parent): mPos(pos), mParent(parent) { assert((parent == nullptr && pos == 0) || (parent != nullptr && 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;} BaseMaskIterator& operator=(const BaseMaskIterator& iter) { mPos = iter.mPos; mParent = iter.mParent; return *this; } 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: using BaseType = BaseMaskIterator; 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 != nullptr); 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: using BaseType = BaseMaskIterator; 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 != nullptr); 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: using BaseType = BaseMaskIterator; 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 != nullptr); 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: static_assert(Log2Dim > 2, "expected NodeMask template specialization, got base template"); static const Index32 LOG2DIM = Log2Dim; static const Index32 DIM = 1<> 6;// 2^6=64 using Word = Index64; 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; //using Word = boost::mpl::if_c::type; 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 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; } using OnIterator = OnMaskIterator; using OffIterator = OffMaskIterator; using DenseIterator = DenseMaskIterator; 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 // /// @brief Apply a functor to the words of the this and the other mask. /// /// @details An example that implements the "operator&=" method: /// @code /// struct Op { inline void operator()(W &w1, const W& w2) const { w1 &= w2; } }; /// @endcode template const NodeMask& foreach(const NodeMask& other, const WordOp& op) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) op( *w1, *w2); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const WordOp& op) { Word *w1 = mWords; const Word *w2 = other1.mWords, *w3 = other2.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2, ++w3) op( *w1, *w2, *w3); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const NodeMask& other3, const WordOp& op) { Word *w1 = mWords; const Word *w2 = other1.mWords, *w3 = other2.mWords, *w4 = other3.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2, ++w3, ++w4) op( *w1, *w2, *w3, *w4); return *this; } /// @brief Bitwise intersection const NodeMask& operator&=(const NodeMask& other) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) *w1 &= *w2; return *this; } /// @brief Bitwise union const NodeMask& operator|=(const NodeMask& other) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) *w1 |= *w2; return *this; } /// @brief Bitwise difference const NodeMask& operator-=(const NodeMask& other) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) *w1 &= ~*w2; return *this; } /// @brief Bitwise XOR const NodeMask& operator^=(const NodeMask& other) { Word *w1 = mWords; const Word *w2 = other.mWords; for (Index32 n = WORD_COUNT; n--; ++w1, ++w2) *w1 ^= *w2; return *this; } NodeMask operator!() const { NodeMask m(*this); m.toggle(); 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; } NodeMask operator^(const NodeMask& other) const { NodeMask m(*this); m ^= other; return m; } /// Return the byte size of this NodeMask static Index32 memUsage() { return static_cast(WORD_COUNT*sizeof(Word)); } /// 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] ^= Word(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; } /// Return @c true if bits are either all off OR all on. /// @param isOn Takes on the values of all bits if the method /// returns true - else it is undefined. bool isConstant(bool &isOn) const { isOn = (mWords[0] == ~Word(0));//first word has all bits on if ( !isOn && mWords[0] != Word(0)) return false;//early out const Word *w = mWords + 1, *n = mWords + WORD_COUNT; while( wnth 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()); } void seek(std::istream& is) const { is.seekg(this->memUsage(), std::ios_base::cur); } /// @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; using Word = Byte; 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; } using OnIterator = OnMaskIterator; using OffIterator = OffMaskIterator; using DenseIterator = DenseMaskIterator; 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 // /// @brief Apply a functor to the words of the this and the other mask. /// /// @details An example that implements the "operator&=" method: /// @code /// struct Op { inline void operator()(Word &w1, const Word& w2) const { w1 &= w2; } }; /// @endcode template const NodeMask& foreach(const NodeMask& other, const WordOp& op) { op(mByte, other.mByte); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const WordOp& op) { op(mByte, other1.mByte, other2.mByte); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const NodeMask& other3, const WordOp& op) { op(mByte, other1.mByte, other2.mByte, other3.mByte); return *this; } /// @brief Bitwise intersection const NodeMask& operator&=(const NodeMask& other) { mByte &= other.mByte; return *this; } /// @brief Bitwise union const NodeMask& operator|=(const NodeMask& other) { mByte |= other.mByte; return *this; } /// @brief Bitwise difference const NodeMask& operator-=(const NodeMask& other) { mByte &= static_cast(~other.mByte); return *this; } /// @brief Bitwise XOR const NodeMask& operator^=(const NodeMask& other) { mByte ^= other.mByte; return *this; } NodeMask operator!() const { NodeMask m(*this); m.toggle(); 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; } 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 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 = mByte | static_cast(0x01U << (n & 7)); } /// Set the nth bit off void setOff(Index32 n) { assert( n < 8 ); mByte = mByte & static_cast(~(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 = mByte ^ static_cast(0x01U << (n & 7)); } /// Toggle the state of all bits in the mask void toggle() { mByte = static_cast(~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; } /// Return @c true if bits are either all off OR all on. /// @param isOn Takes on the values of all bits if the method /// returns true - else it is undefined. bool isConstant(bool &isOn) const { isOn = this->isOn(); return isOn || this->isOff(); } Index32 findFirstOn() const { return mByte ? FindLowestOn(mByte) : 8; } Index32 findFirstOff() const { const Byte b = static_cast(~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 { static_assert(sizeof(WordT) == sizeof(Byte), "expected word size to be one byte"); assert(n == 0); return reinterpret_cast(mByte); } template WordT& getWord(Index n) { static_assert(sizeof(WordT) == sizeof(Byte), "expected word size to be one 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); } void seek(std::istream& is) const { is.seekg(1, std::ios_base::cur); } /// @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 = static_cast(mByte & (0xFFU << start)); return b ? FindLowestOn(b) : 8; } Index32 findNextOff(Index32 start) const { if (start>=8) return 8; const Byte b = static_cast(~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; using Word = Index64; 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; } using OnIterator = OnMaskIterator; using OffIterator = OffMaskIterator; using DenseIterator = DenseMaskIterator; 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 // /// @brief Apply a functor to the words of the this and the other mask. /// /// @details An example that implements the "operator&=" method: /// @code /// struct Op { inline void operator()(Word &w1, const Word& w2) const { w1 &= w2; } }; /// @endcode template const NodeMask& foreach(const NodeMask& other, const WordOp& op) { op(mWord, other.mWord); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const WordOp& op) { op(mWord, other1.mWord, other2.mWord); return *this; } template const NodeMask& foreach(const NodeMask& other1, const NodeMask& other2, const NodeMask& other3, const WordOp& op) { op(mWord, other1.mWord, other2.mWord, other3.mWord); return *this; } /// @brief Bitwise intersection const NodeMask& operator&=(const NodeMask& other) { mWord &= other.mWord; return *this; } /// @brief Bitwise union const NodeMask& operator|=(const NodeMask& other) { mWord |= other.mWord; return *this; } /// @brief Bitwise difference const NodeMask& operator-=(const NodeMask& other) { mWord &= ~other.mWord; return *this; } /// @brief Bitwise XOR const NodeMask& operator^=(const NodeMask& other) { mWord ^= other.mWord; return *this; } NodeMask operator!() const { NodeMask m(*this); m.toggle(); 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; } 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 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; } /// Return @c true if bits are either all off OR all on. /// @param isOn Takes on the values of all bits if the method /// returns true - else it is undefined. bool isConstant(bool &isOn) const { isOn = this->isOn(); return isOn || this->isOff(); } 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); } void seek(std::istream& is) const { is.seekg(8, std::ios_base::cur); } /// @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(nullptr) {} 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;} BaseIterator& operator=(const BaseIterator& iter) { mPos = iter.mPos; mBitSize = iter.mBitSize; mParent = iter.mParent; return *this; } 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 != nullptr); 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 != nullptr); 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 != nullptr); 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; i(mIntSize*sizeof(Index32) + sizeof(*this)); } Index32 countOn() const { assert(mBits); Index32 n=0; for (Index32 i=0; i< mIntSize; ++i) n += CountOn(mBits[i]); return n; } Index32 countOff() const { return mBitSize-this->countOn(); } 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; i(mBits), mIntSize * sizeof(Index32)); } void load(std::istream& is) { assert(mBits); is.read(reinterpret_cast(mBits), mIntSize * sizeof(Index32)); } void seek(std::istream& is) const { assert(mBits); is.seekg(mIntSize * sizeof(Index32), std::ios_base::cur); } /// @brief simple print method for debugging void printInfo(std::ostream& os=std::cout) const { os << "RootNodeMask: Bit-size="<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 { 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<(sizeof(Index32*)+(2+mIntSize)*sizeof(Index32));//in bytes } }; // class RootNodeMask } // namespace util } // namespace OPENVDB_VERSION_NAME } // namespace openvdb #endif // OPENVDB_UTIL_NODEMASKS_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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/LICENSE0000644000000000000000000004052613200122206012211 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/openvdb.h0000644000000000000000000000764513200122377013030 0ustar rootroot/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2012-2017 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_OPENVDB_HAS_BEEN_INCLUDED #define OPENVDB_OPENVDB_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 using BoolTree = tree::Tree4::Type; using DoubleTree = tree::Tree4::Type; using FloatTree = tree::Tree4::Type; using Int32Tree = tree::Tree4::Type; using Int64Tree = tree::Tree4::Type; using MaskTree = tree::Tree4::Type; using StringTree = tree::Tree4::Type; using UInt32Tree = tree::Tree4::Type; using Vec2DTree = tree::Tree4::Type; using Vec2ITree = tree::Tree4::Type; using Vec2STree = tree::Tree4::Type; using Vec3DTree = tree::Tree4::Type; using Vec3ITree = tree::Tree4::Type; using Vec3STree = tree::Tree4::Type; using ScalarTree = FloatTree; using TopologyTree = MaskTree; using Vec3dTree = Vec3DTree; using Vec3fTree = Vec3STree; using VectorTree = Vec3fTree; /// Common grid types using BoolGrid = Grid; using DoubleGrid = Grid; using FloatGrid = Grid; using Int32Grid = Grid; using Int64Grid = Grid; using MaskGrid = Grid; using StringGrid = Grid; using Vec3DGrid = Grid; using Vec3IGrid = Grid; using Vec3SGrid = Grid; using ScalarGrid = FloatGrid; using TopologyGrid = MaskGrid; using Vec3dGrid = Vec3DGrid; using Vec3fGrid = Vec3SGrid; using VectorGrid = Vec3fGrid; /// 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_OPENVDB_HAS_BEEN_INCLUDED // Copyright (c) 2012-2017 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-config0000644000000000000000000031146213200122213014045 0ustar rootroot# Doxyfile 1.8.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single 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 #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My 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 = 5.0.0 ALIASES += vdbnamespace="openvdb::v5_0" PREDEFINED = OPENVDB_VERSION_NAME=v5_0 PREDEFINED += OPENVDB_ABI_VERSION_NUMBER=5 PREDEFINED += __declspec(x):= __attribute__(x):= PREDEFINED += OPENVDB_STATIC_SPECIALIZATION="" PREDEFINED += OPENVDB_USE_LOG4CPLUS= #EXPAND_AS_DEFINED = \ # OPENVDB_API \ # OPENVDB_HOUDINI_API \ # OPENVDB_EXPORT \ # OPENVDB_IMPORT \ # OPENVDB_DEPRECATED \ # OPENVDB_STATIC_SPECIALIZATION # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. 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 causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = 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. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES 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. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES 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. # The default value is: YES. 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 and 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. # The default value is: NO. 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. # The default value is: NO. INLINE_INHERITED_MEMB = YES # If the FULL_PATH_NAMES tag is set to YES 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 # The default value is: YES. FULL_PATH_NAMES = NO # 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. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. 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 list of 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. # The default value is: NO. 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-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_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 behavior. 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 behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. 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. # The default value is: NO. 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. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act 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="(ijk)" ALIASES += xyz="(xyz)" 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" # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # 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. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # 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); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # 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. # The default value is: NO. DISTRIBUTE_GROUP_DOC = YES # Set the SUBGROUPING tag to YES 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. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # 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 respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. 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. Does not have any effect # for Java sources. # The default value is: YES. 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 only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO 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. # The default value is: NO. 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 these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. 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 these declarations will be # included in the documentation. # The default value is: NO. 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 these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. 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 then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. 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. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = YES # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES 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. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = 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 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. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = 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. # The default value is: YES. 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. # The default value is: YES. 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. # The default value is: YES. 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. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have 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 value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. 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. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = 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 command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag 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. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag 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. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. 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) # The default value is: $file:$line: $text. 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 standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is 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. # Note: If this tag is empty the current directory is searched. INPUT = . \ io \ math \ points \ 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/points.txt \ doc/python.txt # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (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, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = *.h # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # 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. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. 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 EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # 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. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be 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. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. 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 information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # 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 that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES 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 documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = 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. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES 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. # See also: Section \class. # The default value is: YES. 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. # The default value is: YES. ALPHABETICAL_INDEX = NO # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. 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 a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. 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. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. 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). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. 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 left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefor more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra stylesheet files is of importance (e.g. the last # stylesheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 4 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 222 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # 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. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # 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. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # 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). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # 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. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value 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 # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # 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. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /